FSUDAQ_Qt6/Histogram1D.h

378 lines
11 KiB
C++

#ifndef HISTOGRAM_1D_H
#define HISTOGRAM_1D_H
#include "qcustomplot.h"
#include "macro.h"
#define MaxNHist 10
//^==============================================
//^==============================================
class Histogram1D : public QCustomPlot{
Q_OBJECT
public:
Histogram1D(QString title, QString xLabel, int xbin, double xmin, double xmax, QWidget * parent = nullptr) : QCustomPlot(parent){
// DebugPrint("%s", "Histogram1D");
isLogY = false;
for( int i = 0; i < MaxNHist; i++ ) showHist[i] = true;
for( int i = 0; i < 3; i ++) txt[i] = nullptr;
nData = 1;
Rebin(xbin, xmin, xmax);
xAxis->setLabel(xLabel);
legend->setVisible(true);
QPen borderPen = legend->borderPen();
borderPen.setWidth(0);
borderPen.setColor(Qt::transparent);
legend->setBorderPen(borderPen);
legend->setFont(QFont("Helvetica", 9));
addGraph();
graph(0)->setName(title);
graph(0)->setPen(QPen(Qt::blue));
graph(0)->setBrush(QBrush(QColor(0, 0, 255, 20)));
xAxis2->setVisible(true);
xAxis2->setTickLabels(false);
yAxis2->setVisible(true);
yAxis2->setTickLabels(false);
// make left and bottom axes always transfer their ranges to right and top axes:
connect(xAxis, SIGNAL(rangeChanged(QCPRange)), xAxis2, SLOT(setRange(QCPRange)));
connect(yAxis, SIGNAL(rangeChanged(QCPRange)), yAxis2, SLOT(setRange(QCPRange)));
graph(0)->setData(xList, yList[0]);
//setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
//setInteractions( QCP::iRangeDrag | QCP::iRangeZoom );
//setSelectionRectMode(QCP::SelectionRectMode::srmZoom);
rescaleAxes();
yAxis->setRangeLower(0);
yAxis->setRangeUpper(10);
for( int i = 0; i < 3; i ++){
txt[i] = new QCPItemText(this);
txt[i]->setPositionAlignment(Qt::AlignLeft);
txt[i]->position->setType(QCPItemPosition::ptAxisRectRatio);
txt[i]->position->setCoords(0.1, 0.1 + 0.1*i);;
txt[i]->setFont(QFont("Helvetica", 9));
}
txt[0]->setText("Under Flow : 0");
txt[1]->setText("Total Entry : 0");
txt[2]->setText("Over Flow : 0");
usingMenu = false;
connect(this, &QCustomPlot::mouseMove, this, [=](QMouseEvent *event){
double x = this->xAxis->pixelToCoord(event->pos().x());
double bin = (x - xMin)/dX;
double z = yList[0][2*qFloor(bin) + 1];
QString coordinates = QString("Bin: %1, Value: %2").arg(qFloor(bin)).arg(z);
QToolTip::showText(event->globalPosition().toPoint(), coordinates, this);
});
connect(this, &QCustomPlot::mousePress, this, [=](QMouseEvent * event){
if (event->button() == Qt::LeftButton && !usingMenu){
setSelectionRectMode(QCP::SelectionRectMode::srmZoom);
}
if (event->button() == Qt::RightButton) {
usingMenu = true;
setSelectionRectMode(QCP::SelectionRectMode::srmNone);
QMenu menu(this);
QAction * a1 = menu.addAction("UnZoom");
QAction * a5 = menu.addAction("Set/UnSet Log-y");
QAction * a6 = nullptr;
if( nData > 1 ) a6 = menu.addAction("Toggle lines display");
QAction * a2 = menu.addAction("Clear hist.");
QAction * a3 = menu.addAction("Toggle Stat.");
QAction * a4 = menu.addAction("Rebin (clear histogram)");
//TODO fitGuass
QAction *selectedAction = menu.exec(event->globalPosition().toPoint());
//*========================================== UnZoom
if( selectedAction == a1 ){
xAxis->setRangeLower(xMin);
xAxis->setRangeUpper(xMax);
yAxis->setRangeLower(0);
yAxis->setRangeUpper(yMax * 1.2 > 10 ? yMax * 1.2 : 10);
replot();
usingMenu = false;
}
//*========================================== Clear Hist
if( selectedAction == a2 ){
Clear();
usingMenu = false;
}
//*========================================== Toggle Stat.
if( selectedAction == a3 ){
for( int i = 0; i < 3; i++){
txt[i]->setVisible( !txt[i]->visible());
}
replot();
usingMenu = false;
}
//*========================================== Rebin
if( selectedAction == a4 ){
QDialog dialog(this);
dialog.setWindowTitle("Rebin histogram");
QFormLayout layout(&dialog);
QLabel * info = new QLabel(&dialog);
info->setStyleSheet("color:red;");
info->setText("This will also clear histogram!!");
layout.addRow(info);
QStringList nameList = {"Num. Bin", "x-Min", "x-Max"};
QLineEdit* lineEdit[3];
for (int i = 0; i < 3; ++i) {
lineEdit[i] = new QLineEdit(&dialog);
layout.addRow(nameList[i] + " : ", lineEdit[i]);
}
lineEdit[0]->setText(QString::number(xBin));
lineEdit[1]->setText(QString::number(xMin));
lineEdit[2]->setText(QString::number(xMax));
QLabel * msg = new QLabel(&dialog);
msg->setStyleSheet("color:red;");
layout.addRow(msg);
QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
layout.addRow(&buttonBox);
double number[3];
QObject::connect(&buttonBox, &QDialogButtonBox::accepted, [&]() {
int OKcount = 0;
bool conversionOk = true;
for( int i = 0; i < 3; i++ ){
number[i] = lineEdit[i]->text().toDouble(&conversionOk);
if( conversionOk ){
OKcount++;
}else{
msg->setText(nameList[i] + " is invalid.");
return;
}
}
if( OKcount == 3 ) {
if( number[2] > number[1] ) {
dialog.accept();
}else{
msg->setText(nameList[2] + " is smaller than " + nameList[1]);
}
}
});
QObject::connect(&buttonBox, &QDialogButtonBox::rejected, [&]() { dialog.reject();});
if( dialog.exec() == QDialog::Accepted ){
Rebin((int)number[0], number[1], number[2]);
emit ReBinned();
UpdatePlot();
}
}
//*========================================== Toggle line Display
if( selectedAction == a6 ){
QDialog dialog(this);
dialog.setWindowTitle("Toggle lines Display");
QFormLayout layout(&dialog);
QCheckBox ** cbline = new QCheckBox *[nData];
for( int i = 0; i < nData; i++ ){
cbline[i] = new QCheckBox(graph(i)->name(), &dialog);
layout.addRow(cbline[i]);
if( showHist[i] ) cbline[i]->setChecked(true);
}
QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
layout.addRow(&buttonBox);
QObject::connect(&buttonBox, &QDialogButtonBox::accepted, [&]() {
for( int i = 0; i < nData; i++ ){
showHist[i] = cbline[i]->isChecked();
}
dialog.accept();
});
QObject::connect(&buttonBox, &QDialogButtonBox::rejected, [&]() { dialog.reject();});
if( dialog.exec() == QDialog::Accepted ){
UpdatePlot();
}
}
//*========================================== Set Log y
if( selectedAction == a5 ){
if( !isLogY ){
this->yAxis->setScaleType(QCPAxis::stLogarithmic);
isLogY = true;
}else{
this->yAxis->setScaleType(QCPAxis::stLinear);
isLogY = false;
}
this->replot();
}
}
});
}
int GetNBin() const {return xBin;}
double GetXMin() const {return xMin;}
double GetXMax() const {return xMax;}
void SetColor(QColor color, unsigned short ID = 0) {
DebugPrint("%s", "Histogram1D");
graph(ID)->setPen(QPen(color));
QColor haha = color;
haha.setAlpha(20);
graph(ID)->setBrush(QBrush(haha));
}
void AddDataList(QString title, QColor color){
nData ++;
addGraph();
graph(nData - 1)->setName(title);
SetColor(color, nData-1);
yList[nData-1].clear();
for( int i = 0; i < xList.count(); i++) yList[nData-1].append(0);
}
void UpdatePlot(){
DebugPrint("%s", "Histogram1D");
for( int ID = 0 ; ID < nData; ID ++) {
graph(ID)->setVisible(showHist[ID]);
graph(ID)->setData(xList, yList[ID]);
}
xAxis->setRangeLower(xMin);
xAxis->setRangeUpper(xMax);
yAxis->setRangeLower(0);
yAxis->setRangeUpper(yMax * 1.2 > 10 ? yMax * 1.2 : 10);
replot();
}
void Clear(){
DebugPrint("%s", "Histogram1D");
for( int ID = 0 ; ID < nData; ID ++) {
for( int i = 0; i < xList.count(); i++) yList[ID][i] = 0;
}
yMax = 0;
txt[0]->setText("Under Flow : 0");
txt[1]->setText("Total Entry : 0");
txt[2]->setText("Over Flow : 0");
totalEntry = 0;
underFlow = 0;
overFlow = 0;
UpdatePlot();
}
void SetLineTitle(QString title, int lineID = 0) { graph(lineID)->setName(title); }
void SetXTitle(QString xTitle) { xAxis->setLabel(xTitle);}
void Rebin(int xbin, double xmin, double xmax){
// DebugPrint("%s", "Histogram1D");
xMin = xmin;
xMax = xmax;
xBin = xbin;
if( xBin > 1000) xBin = 1000;
dX = (xMax - xMin)/(xBin);
xList.clear();
for( int i = 0 ; i < nData ; i ++) yList[i].clear();
for( int i = 0; i <= xBin; i ++ ){
xList.append(xMin + i*dX-(dX)*0.000001);
xList.append(xMin + i*dX);
for( int ID = 0 ; ID < nData; ID ++ ){
yList[ID].append(0);
yList[ID].append(0);
}
}
yMax = 0;
totalEntry = 0;
underFlow = 0;
overFlow = 0;
if( txt[0] ) txt[0]->setText("Under Flow : 0");
if( txt[1] ) txt[1]->setText("Total Entry : 0");
if( txt[2] ) txt[2]->setText("Over Flow : 0");
}
void Fill(double value, unsigned int ID = 0){
// DebugPrint("%s", "Histogram1D");
if( ID == 0 ){
totalEntry ++;
txt[1]->setText("Total Entry : "+ QString::number(totalEntry));
if( value < xMin ) {
underFlow ++;
txt[0]->setText("Under Flow : "+ QString::number(underFlow));
return;
}
if( value > xMax ) {
overFlow ++;
txt[2]->setText("Over Flow : "+ QString::number(overFlow));
return;
}
}else{
if( value < xMin || value > xMax ) return;
}
int bin = qFloor((value - xMin)/dX);
int index1 = 2*bin + 1;
int index2 = index1 + 1;
if( 0 <= index1 && index1 <= 2*xBin) yList[ID][index1] += 1;
if( 0 <= index1 && index2 <= 2*xBin) yList[ID][index2] += 1;
if( showHist[ID] && yList[ID][index1] > yMax ) yMax = yList[ID][index1];
}
void Print(unsigned int ID = 0){
for( int i = 0; i < xList.count(); i++){
printf("%f %f\n", xList[i], yList[ID][i]);
}
}
signals:
void ReBinned(); //ONLY for right click rebin
private:
double xMin, xMax, dX;
int xBin;
double yMax;
int totalEntry;
int underFlow;
int overFlow;
bool isLogY;
unsigned short nData;
QVector<double> xList;
QVector<double> yList[MaxNHist];
QCPItemText * txt[3];
bool usingMenu;
bool showHist[MaxNHist];
};
#endif