musrfit/src/musredit_qt5/mupp/plotter/PMuppCanvas.cpp

681 lines
22 KiB
C++

/***************************************************************************
PMuppCanvas.cpp
Author: Andreas Suter
e-mail: andreas.suter@psi.ch
***************************************************************************/
/***************************************************************************
* Copyright (C) 2007-2025 by Andreas Suter *
* andreas.suter@psi.ch *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <TColor.h>
#include <TROOT.h>
#include <TGFileDialog.h>
#include <TGMsgBox.h>
#include <TString.h>
#include <TObjArray.h>
#include <TObjString.h>
#include <TRandom.h>
#include <TAxis.h>
#include "PMuppCanvas.h"
static const char *gFiletypes[] = { "Data files", "*.dat",
"All files", "*",
0, 0 };
ClassImpQ(PMuppCanvas)
//--------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------
/**
* @brief PMuppCanvas::PMuppCanvas
*/
PMuppCanvas::PMuppCanvas()
{
fFtokName = TString("");
fImp = nullptr;
fBar = nullptr;
fPopupMain = nullptr;
gStyle->SetHistMinimumZero(kTRUE); // needed to enforce proper bar option handling
}
//--------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------
/**
* @brief PMuppCanvas::PMuppCanvas
* @param title
* @param wtopx
* @param wtopy
* @param ww
* @param wh
* @param markerSytleList
* @param markerSizeList
* @param colorList
*/
PMuppCanvas::PMuppCanvas(const Char_t *title, Int_t wtopx, Int_t wtopy,
Int_t ww, Int_t wh, const PIntVector markerSytleList,
const PDoubleVector markerSizeList, const PIntVector colorList,
const int mupp_instance) :
fMuppInstance(mupp_instance),
fMarkerStyleList(markerSytleList),
fMarkerSizeList(markerSizeList),
fColorList(colorList)
{
fValid = true;
fFtokName = TString("");
// install IPC message queue timer
fCheckMsgQueue = std::make_unique<TTimer>();
if (fCheckMsgQueue == nullptr) {
fValid = false;
std::cerr << "**ERROR** couldn't start IPC message queue timer..." << std::endl;
return;
}
fCheckMsgQueue->Connect("Timeout()", "PMuppCanvas", this, "CheckIPCMsgQueue()");
fCheckMsgQueue->Start(200, kFALSE);
CreateStyle();
InitMuppCanvas(title, wtopx, wtopy, ww, wh);
}
//--------------------------------------------------------------------------
// Done (SIGNAL)
//--------------------------------------------------------------------------
/**
* <p>Signal emitted that the user wants to terminate the application.
*
* \param status Status info
*/
void PMuppCanvas::Done(Int_t status)
{
Emit("Done(Int_t)", status);
}
//--------------------------------------------------------------------------
// HandleCmdKey (SLOT)
//--------------------------------------------------------------------------
/**
* <p>Filters keyboard events, and if they are a command key (see below) carries out the
* necessary actions.
* <p>Currently implemented command keys:
* - 'q' quit mupp_plot
*
* \param event event type
* \param x character key
* \param y not used
* \param selected not used
*/
void PMuppCanvas::HandleCmdKey(Int_t event, Int_t x, Int_t y, TObject *selected)
{
if (event != kKeyPress)
return;
// handle keys and popup menu entries
if (x == 'q') { // quit
Done(0);
} else if (x == 'b') { // about
new TGMsgBox(gClient->GetRoot(), 0, "About mupp", "created by Andreas Suter\nPSI/NUM/LMU/LEM\n2018", kMBIconAsterisk);
} else if (x == 'l') { // draw with lines
fWithLines = !fWithLines;
UpdateGraphs();
}
}
//--------------------------------------------------------------------------
// HandleMenuPopup (SLOT)
//--------------------------------------------------------------------------
/**
* <p>Handles the mupp menu.
*
* \param id identification key of the selected menu
*/
void PMuppCanvas::HandleMenuPopup(Int_t id)
{
if (id == P_MENU_ID_WITH_LINE) {
fWithLines = !fWithLines;
UpdateGraphs();
} else if (id == P_MENU_ID_EXPORT) {
ExportData();
} else if (id == P_MENU_ID_ABOUT) {
new TGMsgBox(gClient->GetRoot(), 0, "About mupp", "created by Andreas Suter\nPSI/NUM/LMU/LEM\n2017", kMBIconAsterisk);
}
}
//--------------------------------------------------------------------------
// LastCanvasClosed (SLOT)
//--------------------------------------------------------------------------
/**
* <p>Slot called when the last canvas has been closed. Will emit Done(0) which will
* terminate the application.
*/
void PMuppCanvas::LastCanvasClosed()
{
if (gROOT->GetListOfCanvases()->IsEmpty()) {
Done(0);
}
}
//--------------------------------------------------------------------------
// WindowClosed (SLOT)
//--------------------------------------------------------------------------
/**
* <p>Slot called when the canvas is closed. Seems to be necessary on some systems.
*/
void PMuppCanvas::WindowClosed()
{
gROOT->GetListOfCanvases()->Remove(fMainCanvas.get());
LastCanvasClosed();
}
//--------------------------------------------------------------------------
// CreateStyle (private)
//--------------------------------------------------------------------------
/**
* <p> Set styles for the canvas. Perhaps one could transfer them to the startup-file in the future.
*/
void PMuppCanvas::CreateStyle()
{
TString muppStyle("muppStyle");
fStyle = std::make_unique<TStyle>(muppStyle, muppStyle);
fStyle->SetOptStat(0); // no statistics options
fStyle->SetOptTitle(0); // no title
// set margins
fStyle->SetPadLeftMargin(0.15);
fStyle->SetPadRightMargin(0.05);
fStyle->SetPadBottomMargin(0.12);
fStyle->SetPadTopMargin(0.05);
fStyle->cd();
}
//--------------------------------------------------------------------------
/**
* @brief PMuppCanvas::InitMuppCanvas
* @param title
* @param wtopx
* @param wtopy
* @param ww
* @param wh
*/
void PMuppCanvas::InitMuppCanvas(const Char_t *title, Int_t wtopx, Int_t wtopy, Int_t ww, Int_t wh)
{
fValid = false;
// invoke canvas
TString canvasName = TString("fMuppCanvas");
fMainCanvas = std::make_unique<TCanvas>(canvasName.Data(), title, wtopx, wtopy, ww, wh);
if (fMainCanvas == nullptr) {
std::cerr << std::endl << ">> PMuppCanvas::InitMuppCanvas(): **PANIC ERROR** Couldn't invoke " << canvasName.Data();
std::cerr << std::endl;
return;
}
fMainCanvas->SetFillColor(0);
fImp = (TRootCanvas*)fMainCanvas->GetCanvasImp();
fImp->Connect("CloseWindow()", "PMuppCanvas", this, "WindowClosed()");
fBar = fImp->GetMenuBar();
fPopupMain = fBar->AddPopup("&Mupp");
fPopupMain->AddEntry("Toggle with&Line", P_MENU_ID_WITH_LINE);
fPopupMain->AddEntry("Export &Data", P_MENU_ID_EXPORT);
fPopupMain->AddSeparator();
fPopupMain->AddEntry("A&bout", P_MENU_ID_ABOUT);
fBar->MapSubwindows();
fBar->Layout();
fPopupMain->Connect("TGPopupMenu", "Activated(Int_t)", "PMuppCanvas", this, "HandleMenuPopup(Int_t)");
fValid = true;
fMainCanvas->cd();
fMainCanvas->Show();
fMainCanvas->Connect("ProcessedEvent(Int_t,Int_t,Int_t,TObject*)", "PMuppCanvas",
this, "HandleCmdKey(Int_t,Int_t,Int_t,TObject*)");
}
//--------------------------------------------------------------------------
/**
* @brief PMuppCanvas::CheckIPCMsgQueue
*/
void PMuppCanvas::CheckIPCMsgQueue()
{
int msqid, flags, type, key, status;
ssize_t msgLen;
struct mbuf msg;
char str[1024];
// get msqid
if (fFtokName.IsWhitespace()) {
strncpy(str, getenv("HOME"), sizeof(str)-1);
if (strlen(str) == 0) {
std::cerr << std::endl;
std::cerr << "**ERROR** couldn't get value of the environment variable HOME." << std::endl << std::endl;
return;
}
memset(str, '\0', sizeof(str));
snprintf(str, sizeof(str), "%s/.musrfit/mupp/_mupp_ftok_%d.dat", getenv("HOME"), fMuppInstance);
std::ifstream fin(str, std::ifstream::in);
if (!fin.is_open()) {
std::cerr << std::endl;
std::cerr << "**ERROR** couldn't open " << str << std::endl;
return;
}
fin.getline(str, 1024);
fin.close();
fFtokName = str;
}
key = ftok(fFtokName.Data(), fMuppInstance);
flags = IPC_CREAT;
msqid = msgget(key, flags | S_IRUSR | S_IWUSR);
if (msqid == -1) {
std::cerr << std::endl;
std::cerr << "**ERROR** couldn't get msqid." << std::endl << std::endl;
return;
}
// get message
flags = IPC_NOWAIT;
type = 1;
msgLen = msgrcv(msqid, &msg, PMUPP_MAX_MTEXT, type, flags);
if (msgLen > 0) {
status = ReadPlotData(msg.mtext);
if (status != 0) {
std::cerr << "**ERROR** reading " << msg.mtext << std::endl;
}
}
}
//--------------------------------------------------------------------------
/**
* @brief PMuppCanvas::ReadPlotData
* @param fln
* @return
*/
int PMuppCanvas::ReadPlotData(const Char_t *fln)
{
std::ifstream fin(fln, std::ifstream::in);
char line[1024];
if (!fin.is_open()) {
std::cerr << "**ERROR** Couldn't open " << fln << std::endl;
return -1;
}
TString str;
TObjArray *tok;
TObjString *ostr;
Ssiz_t idx;
Int_t noOfDataTokens=0;
PDataCollection data;
PDataPoint dataPoint;
fPlotData.clear();
while (fin.good()) {
fin.getline(line, 1024);
if (line[0] == '%') {
if (strstr(line, "% collName")) {
noOfDataTokens = 0;
InitDataCollection(data); // make sure that one starts with a fresh collection
str = line;
idx = str.First("=");
str = str.Remove(0,idx+2);
data.name = str;
} else if (strstr(line, "% end")) {
fPlotData.push_back(data);
}
} else if (strlen(line) == 0) { // ignore empty lines
continue;
} else if (strstr(line, "xLabel:")) { // header information
str = line;
tok = str.Tokenize(":,");
if (tok == 0) {
std::cerr << "**ERROR** Couldn't tokenize the label line." << std::endl;
fin.close();
return -2;
}
if (tok->GetEntries() < 4) {
std::cerr << "**ERROR** label line doesn't contain sufficient information." << std::endl;
fin.close();
return -3;
}
ostr = dynamic_cast<TObjString*>(tok->At(1));
data.xLabel = ostr->GetString();
noOfDataTokens++;
for (Int_t i=3; i<tok->GetEntries(); i+=2) {
ostr = dynamic_cast<TObjString*>(tok->At(i));
data.yLabel.push_back(ostr->GetString());
noOfDataTokens++;
}
// clean up
delete tok;
tok = 0;
} else { // data lines
str = line;
tok = str.Tokenize(", ");
if (tok == 0) {
std::cerr << "**ERROR** Couldn't tokenize data line." << std::endl;
fin.close();
return -4;
}
if (tok->GetEntries() != (noOfDataTokens-1)*3+1) {
std::cerr << "**ERROR** number of token in data line (" << tok->GetEntries() << ") is inconsistent with no of labels (" << noOfDataTokens << ")." << std::endl;
fin.close();
return -4;
}
// resize y-value vector properly
data.yValue.resize(noOfDataTokens-1);
// raw data point
// x-value
ostr = dynamic_cast<TObjString*>(tok->At(0));
str = ostr->GetString();
if (!str.IsFloat()) {
std::cerr << "**ERROR** token found in data line is not a number (" << str << ")." << std::endl;
fin.close();
return -5;
}
data.xValue.push_back(str.Atof());
// y-value(s)
Int_t idx=0;
for (Int_t i=1; i<tok->GetEntries(); i+=3) {
// y-value
ostr = dynamic_cast<TObjString*>(tok->At(i));
str = ostr->GetString();
if (!str.IsFloat()) {
std::cerr << "**ERROR** token found in data line is not a number (" << str << ")." << std::endl;
fin.close();
return -5;
}
dataPoint.y = str.Atof();
// pos y error-value
ostr = dynamic_cast<TObjString*>(tok->At(i+1));
str = ostr->GetString();
if (!str.IsFloat()) {
std::cerr << "**ERROR** token found in data line is not a number (" << str << ")." << std::endl;
fin.close();
return -5;
}
dataPoint.eYpos = str.Atof();
// neg y error-value
ostr = dynamic_cast<TObjString*>(tok->At(i+2));
str = ostr->GetString();
if (!str.IsFloat()) {
std::cerr << "**ERROR** token found in data line is not a number (" << str << ")." << std::endl;
fin.close();
return -5;
}
dataPoint.eYneg = str.Atof();
// push dataPoint back into data structure
data.yValue[idx].push_back(dataPoint);
idx++;
}
// clean up
delete tok;
tok = 0;
}
}
fin.close();
// check if data structure makes sense
for (UInt_t i=0; i<fPlotData.size(); i++) {
std::cerr << "debug> collection " << i << std::endl;
std::cerr << "debug> name : " << fPlotData[i].name << std::endl;
std::cerr << "debug> x-label : " << fPlotData[i].xLabel << std::endl;
std::cerr << "debug> x-vales : " << std::endl;
std::cerr << "debug> ";
for (UInt_t j=0; j<fPlotData[i].xValue.size(); j++) {
std::cerr << fPlotData[i].xValue[j] << ", ";
}
std::cerr << std::endl;
for (UInt_t j=0; j<fPlotData[i].yValue.size(); j++) {
std::cerr << "debug> y-label : " << fPlotData[i].yLabel[j] << std::endl;
std::cerr << "debug> y-values " << j << ": " << std::endl;
for (UInt_t k=0; k<fPlotData[i].yValue[j].size(); k++) {
std::cerr << "debug> " << k << ": " << fPlotData[i].yValue[j][k].y << "(" << fPlotData[i].yValue[j][k].eYneg << "/" << fPlotData[i].yValue[j][k].eYpos << ")" << std::endl;
}
}
std::cerr << std::endl;
}
// feed graph objects
UpdateGraphs();
return 0;
}
//--------------------------------------------------------------------------
/**
* @brief PMuppCanvas::InitDataCollection
* @param coll
*/
void PMuppCanvas::InitDataCollection(PDataCollection &coll)
{
coll.name = TString("");
coll.xLabel = TString("");
coll.yLabel.clear();
coll.xValue.clear();
coll.yValue.clear();
}
//--------------------------------------------------------------------------
/**
* @brief PMuppCanvas::UpdateGraphs
*/
void PMuppCanvas::UpdateGraphs()
{
// first: get rid of all previous plots
for (UInt_t i=0; i<fGraphE.size(); i++) {
delete fGraphE[i];
}
fGraphE.clear();
fMultiGraph.reset();
// second: create all the necessary graphs
TGraphAsymmErrors *gg = 0;
UInt_t idxS = 0, idxC = 0;
Int_t color;
TString str;
if (fMultiGraph == nullptr) { // first time called
for (UInt_t i=0; i<fPlotData.size(); i++) { // loop over all collections
for (UInt_t j=0; j<fPlotData[i].yValue.size(); j++) { // loop over all graph's within the collection
gg = new TGraphAsymmErrors(fPlotData[i].xValue.size());
for (UInt_t k=0; k<fPlotData[i].yValue[j].size(); k++) {
gg->SetPoint(k, fPlotData[i].xValue[k], fPlotData[i].yValue[j][k].y);
gg->SetPointError(k, 0.0, 0.0, fabs(fPlotData[i].yValue[j][k].eYneg), fPlotData[i].yValue[j][k].eYpos);
// set marker style and size
if (idxS < fMarkerStyleList.size()) {
gg->SetMarkerStyle(fMarkerStyleList[idxS]);
gg->SetMarkerSize(fMarkerSizeList[idxS]);
} else { // random choice of the marker
// still missing as35
}
// set color
if (idxC < fColorList.size()) {
gg->SetMarkerColor(fColorList[idxC]);
gg->SetLineColor(fColorList[idxC]);
gg->SetFillColor(kWhite);
} else { // random choise of the color
TRandom rand(i+j+k);
color = TColor::GetColor((Int_t)rand.Integer(255), (Int_t)rand.Integer(255), (Int_t)rand.Integer(255));
gg->SetMarkerColor(color);
gg->SetLineColor(color);
gg->SetFillColor(kWhite);
}
}
idxS++;
idxC++;
// set name
str = fPlotData[i].yLabel[j];
if (fPlotData[i].yValue.size() > 1) {
str += "-";
str += j;
}
gg->SetName(str);
gg->SetTitle(str);
// set x-axis title
gg->GetXaxis()->SetTitle(fPlotData[i].xLabel);
// set y-axis title
gg->GetYaxis()->SetTitle(fPlotData[i].yLabel[j]);
fGraphE.push_back(gg);
}
}
fMultiGraph = std::make_unique<TMultiGraph>();
if (fMultiGraph == nullptr) {
std::cerr << "**ERROR** couldn't create necessary TMultiGraph object." << std::endl;
return;
}
if (fGraphE.size() == 0) {
std::cerr << "**ERROR** NO graphs present! This should never happen." << std::endl;
return;
}
for (UInt_t i=0; i<fGraphE.size(); i++) {
fMultiGraph->Add(fGraphE[i]);
}
if (fWithLines) {
fMultiGraph->Draw("ALP");
} else {
fMultiGraph->Draw("AP");
}
// set x-axis limits. This is needed that the graphs x-axis starts at 0.0
Double_t xmin=fMultiGraph->GetXaxis()->GetXmin();
Double_t xmax=fMultiGraph->GetXaxis()->GetXmax();
if (xmin > 0.0) {
fMultiGraph->GetXaxis()->SetLimits(0.0, xmax);
}
// set fMultiGraph x/y-axis title
if (fGraphE.size() == 1) { // only one data set
fMultiGraph->GetXaxis()->SetTitle(fGraphE[0]->GetXaxis()->GetTitle());
fMultiGraph->GetXaxis()->SetTitleSize(0.05);
fMultiGraph->GetYaxis()->SetTitle(fGraphE[0]->GetYaxis()->GetTitle());
fMultiGraph->GetYaxis()->SetTitleOffset(1.25);
fMultiGraph->GetYaxis()->SetTitleSize(0.05);
fMultiGraph->GetYaxis()->SetLabelOffset(0.01);
fMultiGraph->GetYaxis()->SetDecimals();
} else { // multiple data sets
fMultiGraph->GetXaxis()->SetTitle(fGraphE[0]->GetXaxis()->GetTitle()); // need a better solution, as35
fMultiGraph->GetXaxis()->SetTitleSize(0.05);
fMultiGraph->GetYaxis()->SetTitle(fGraphE[0]->GetYaxis()->GetTitle()); // need a better solution, as35
fMultiGraph->GetYaxis()->SetTitleOffset(1.25);
fMultiGraph->GetYaxis()->SetTitleSize(0.05);
fMultiGraph->GetYaxis()->SetLabelOffset(0.01);
fMultiGraph->GetYaxis()->SetDecimals();
}
gPad->Modified();
fMainCanvas->cd();
fMainCanvas->Update();
} else { // update of the graphs
// still missing, as35
}
}
//--------------------------------------------------------------------------
/**
* @brief PMuppCanvas::ExportData
*/
void PMuppCanvas::ExportData()
{
static TString dir(".");
TGFileInfo fi;
fi.fFileTypes = gFiletypes;
fi.fIniDir = StrDup(dir);
fi.fOverwrite = true;
new TGFileDialog(0, fImp, kFDSave, &fi);
if (fi.fFilename && strlen(fi.fFilename)) {
std::ofstream fout(fi.fFilename, std::ios_base::out);
// write header
for (int i=0; i<fPlotData.size(); i++) {
fout << fPlotData[i].xLabel.Strip(TString::kLeading).Data().Data() << ", ";
for (int j=0; j<fPlotData[i].yLabel.size(); j++) {
if ((i == fPlotData.size()-1) && (j == fPlotData[i].yLabel.size()-1))
fout << fPlotData[i].yLabel[j].Strip(TString::kLeading).Data().Data() << ", " << fPlotData[i].yLabel[j].Strip(TString::kLeading).Data().Data() << "ErrPos, " << fPlotData[i].yLabel[j].Strip(TString::kLeading).Data().Data() << "ErrNeg";
else
fout << fPlotData[i].yLabel[j].Strip(TString::kLeading).Data().Data() << ", " << fPlotData[i].yLabel[j].Strip(TString::kLeading).Data().Data() << "ErrPos, " << fPlotData[i].yLabel[j].Strip(TString::kLeading).Data().Data() << "ErrNeg, ";
}
}
fout << std::endl;
// search the longest data set
Int_t maxLength=0;
for (int i=0; i<fPlotData.size(); i++) {
if (maxLength < fPlotData[i].xValue.size())
maxLength = fPlotData[i].xValue.size();
}
// write data
for (int i=0; i<maxLength; i++) { // maximal data set length
for (int j=0; j<fPlotData.size(); j++) { // number of x-data sets
// write x-value
if (i < fPlotData[j].xValue.size()) // make sure that the entry exists
fout << fPlotData[j].xValue[i] << ", ";
else
fout << " , ";
// write y-value and y-value error
for (int k=0; k<fPlotData[j].yValue.size(); k++) { // number of y-data sets
if ((j == fPlotData.size()-1) && (k == fPlotData[j].yValue.size()-1))
if (i < fPlotData[j].yValue[k].size())
fout << fPlotData[j].yValue[k][i].y << ", " << fPlotData[j].yValue[k][i].eYpos << ", " << fPlotData[j].yValue[k][i].eYneg;
else
fout << ", , , ";
else
if (i < fPlotData[j].yValue[k].size())
fout << fPlotData[j].yValue[k][i].y << ", " << fPlotData[j].yValue[k][i].eYpos << ", " << fPlotData[j].yValue[k][i].eYneg << ", ";
else
fout << ", , , ";
}
}
fout << std::endl;
}
fout.close();
}
}