Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
HistogramPlotter.cpp 15.20 KiB
#include "HistogramPlotter.h"
#include "HistogramSettings.h"
#include "GLAppGui/GLToolkit.h"
#include "GLAppGui/GLMessageBox.h"
#include "GLAppGui/GLToggle.h"
#include "GLAppGui/GLList.h"
#include "GLAppGui/GLChart/GLChart.h"
#include "GLAppGui/GLLabel.h"
#include "GLAppGui/GLCombo.h"
#include "GLAppGui/GLButton.h"
#include "GLAppGui/GLTextField.h"
#include "GLAppCore/GLFormula.h"
#include "GLAppCore/GLCoreTypes.h"
#include "Geometry_shared.h"
#include "Facet_shared.h"

#if defined(MOLFLOW)
#include "../../src/MolFlow.h"
extern MolFlow *mApp;
#endif

#if defined(SYNRAD)
#include "../src/SynRad.h"
extern SynRad*mApp;
#endif

extern const char*profType[];

HistogramPlotter::HistogramPlotter(Worker *w) :GLTabWindow() {

	worker = w;

	int wD = 770;
	int hD = 400;

	SetTitle("Histogram plotter");
	SetIconfiable(true);
	lastUpdate = 0.0f;

	HistogramMode bounceMode;
	bounceMode.name = "Bounces before absorption";
	bounceMode.XaxisLabel = "Number of bounces";
	modes.push_back(bounceMode);

	HistogramMode distanceMode;
	distanceMode.name = "Flight distance before absorption";
	distanceMode.XaxisLabel = "Distance [cm]";
	modes.push_back(distanceMode);

#if defined(MOLFLOW)
	HistogramMode timeMode;
	timeMode.name = "Flight time before absorption";
	timeMode.XaxisLabel = "Time [s]";
	modes.push_back(timeMode);
#endif

	SetPanelNumber((int)modes.size());

	for (int i = 0; i < modes.size(); i++) {
		SetPanelName(i, modes[i].name.c_str());
		modes[i].chart = new GLChart(0);
		modes[i].chart->SetBorder(BORDER_BEVEL_IN);
		modes[i].chart->GetY1Axis()->SetGridVisible(true);
		modes[i].chart->GetXAxis()->SetGridVisible(true);
		modes[i].chart->GetY1Axis()->SetAutoScale(true);
		modes[i].chart->GetY2Axis()->SetAutoScale(true);
		modes[i].chart->GetY1Axis()->SetAnnotation(VALUE_ANNO);
		modes[i].chart->GetXAxis()->SetAnnotation(VALUE_ANNO);
		modes[i].chart->GetXAxis()->SetName(modes[i].XaxisLabel.c_str());
		GLTabWindow::Add(i,modes[i].chart); //To distinguish from GLWindow::Add
	}

	histogramSettingsButton = new GLButton(0, "<< Hist.settings");
	GLWindow::Add(histogramSettingsButton); //To distinguish from GLTabWindow::Add

	histCombo = new GLCombo(0);
	GLWindow::Add(histCombo);
	
	selButton = new GLButton(0, "<-Show Facet");
	GLWindow::Add(selButton);
	
	addButton = new GLButton(0, "Add");
	GLWindow::Add(addButton);

	removeButton = new GLButton(0, "Remove");
	GLWindow::Add(removeButton);

	removeAllButton = new GLButton(0, "Rem.all");
	GLWindow::Add(removeAllButton);

	normLabel = new GLLabel("Y scale:");
	GLWindow::Add(normLabel);

	yScaleCombo = new GLCombo(0);
	yScaleCombo->SetEditable(true);
	yScaleCombo->SetSize(2);
	yScaleCombo->SetValueAt(0, "Absolute");
	yScaleCombo->SetValueAt(1, "Normalized");
	yScaleCombo->SetSelectedIndex(0); //Absolute by default
	GLWindow::Add(yScaleCombo);

	logXToggle = new GLToggle(0, "LogX");
	GLWindow::Add(logXToggle);

	logYToggle = new GLToggle(0, "LogY");
	GLWindow::Add(logYToggle);

	copyAllButton = new GLButton(0,"Copy data");
	GLWindow::Add(copyAllButton);

	UpdateBar(); //Build menubar

	
	SetBounds(280, 35, wD, hD); //Position so that settings fit on the left
	SetResizable(true);
	SetMinimumSize(wD, 220);

	RestoreDeviceObjects();
	Refresh();
}

void HistogramPlotter::SetBounds(int x, int y, int w, int h) {

	for (const auto& mode : modes) {
		
		mode.chart->SetBounds( 5, 0, w - 15, h - 85);
	}

	histogramSettingsButton->SetBounds(7, h-70, 90, 18);
	histCombo->SetBounds(110, h - 70, 120, 19);
	selButton->SetBounds(235, h - 70, 75, 19);
	addButton->SetBounds(315, h - 70, 40, 19);
	removeButton->SetBounds(360, h - 70, 45, 19);
	removeAllButton->SetBounds(410, h - 70, 50, 19);
	
	normLabel->SetBounds(w-285, h - 69, 50, 19);
	yScaleCombo->SetBounds(w - 240, h - 70, 80, 19);
	logXToggle->SetBounds(w - 155, h - 70, 40, 19);
	logYToggle->SetBounds(w - 110, h - 70, 40, 19);
	copyAllButton->SetBounds(w-60,h-70,55,19);

	GLTabWindow::SetBounds(x, y, w, h);

}

void HistogramPlotter::Refresh() {
	//Rebuilds combo and calls refreshviews

	InterfaceGeometry *interfGeom = worker->GetGeometry();

	//Collect histogram facets for currently displayed mode
	size_t modeId = GetSelectedTabIndex();
	std::vector<int> histogramFacetIds;

	bool recordGlobal = ((modeId == HISTOGRAM_MODE_BOUNCES && worker->model->sp.globalHistogramParams.recordBounce)
		|| (modeId == HISTOGRAM_MODE_DISTANCE && worker->model->sp.globalHistogramParams.recordDistance)
#if defined(MOLFLOW)
		|| (modeId == HISTOGRAM_MODE_TIME && worker->model->sp.globalHistogramParams.recordTime)
#endif
		);
	if (recordGlobal) histogramFacetIds.push_back(-1); // -1 == Global histogram

	for (size_t i = 0; i < interfGeom->GetNbFacet(); i++) {
		if (
			(modeId == HISTOGRAM_MODE_BOUNCES && interfGeom->GetFacet(i)->sh.facetHistogramParams.recordBounce)
			|| (modeId == HISTOGRAM_MODE_DISTANCE && interfGeom->GetFacet(i)->sh.facetHistogramParams.recordDistance)
#if defined(MOLFLOW)
			|| (modeId == HISTOGRAM_MODE_TIME && interfGeom->GetFacet(i)->sh.facetHistogramParams.recordTime)
#endif
			) {
			histogramFacetIds.push_back((int)i);
		}
	}
	
	//Construct combo
	histCombo->SetSize(histogramFacetIds.size());
	size_t nbProf = 0;
	for (const int id : histogramFacetIds) {
		std::ostringstream name;
		if (id == -1)
			name << "Global";
		else
			name << "Facet #" << (id + 1);
		histCombo->SetValueAt(nbProf++, name.str(),id);
	}
	histCombo->SetSelectedIndex(histogramFacetIds.size() ? 0 : -1);
	//Refresh chart
	refreshChart();
}



void HistogramPlotter::Update(float appTime, bool force) {
	//Calls refreshChart if needed
	if (!IsVisible() || IsIconic()) return;

	if (force) {
		refreshChart();
		lastUpdate = appTime;
		return;
	}
}

void HistogramPlotter::refreshChart() {
	//refreshes chart values
	size_t modeId = GetSelectedTabIndex();
    if (modes[modeId].views.empty()) return;

	int yScaleMode = yScaleCombo->GetSelectedIndex();
	InterfaceGeometry *interfGeom = worker->GetGeometry();
	for (auto& v : modes[modeId].views) {

		if (v->userData1 >= -1 && v->userData1 < (int)interfGeom->GetNbFacet()) {
			v->Reset();

			auto [histogramValues, xMax, xSpacing,nbBins] = GetHistogramValues(v->userData1, modeId);

			modes[modeId].chart->GetXAxis()->SetMaximum(xMax);
			
			switch (yScaleMode) {
				case 0: { //Absolute
					size_t plotLimit = std::min(histogramValues->size(), (size_t)1000);
					for (size_t i = 0; i < plotLimit;i++) {
						v->Add((double)i*xSpacing, (*histogramValues)[i]);
					}
					break;
				}
				case 1: { //Normalized
					double yMax = 0.0;
					size_t plotLimit = std::min(histogramValues->size(), (size_t)1000);
					for (size_t i = 0; i < plotLimit; i++) {
						yMax = std::max(yMax, (*histogramValues)[i]);
					}
					double scaleY = 1.0 / yMax; //Multiplication is faster than division (unless compiler optimizes this away)
					for (size_t i = 0; i < plotLimit; i++) {
						v->Add((double)i*xSpacing, (*histogramValues)[i]*scaleY);
					}
					break;
				}
			}
		}
		v->CommitChange();
	}
}

std::string HistogramPlotter::getData() {
	std::ostringstream result;
	size_t modeId = GetSelectedTabIndex();
    if (modes[modeId].views.empty()) return "";
	result << modes[modeId].name<<std::endl;
	
	InterfaceGeometry *interfGeom = worker->GetGeometry();
	for (auto& v : modes[modeId].views) {
		if (v->userData1 >= -1 && v->userData1 < (int)interfGeom->GetNbFacet()) {
			auto [histogramValues, xMax, xSpacing, nbBins] = GetHistogramValues(v->userData1, modeId);
			if (v->userData1==-1) {
				result << "Global";
			} else {
				result << "Facet " << v->userData1+1;
			}
			result << " histogram"<<std::endl;
			result << "Bins: "<< nbBins << " Max recorded: " << xMax << " Bin spacing: " << xSpacing <<std::endl;
			result << modes[modeId].XaxisLabel << '\t' << "Hits" << std::endl;
			for (size_t i = 0; i < histogramValues->size();i++) {
				if (i==histogramValues->size()-1) { //Only executed if size is at least 1, so no underrun
					result << "Overrange";
				} else {
					result << static_cast<double>(i)*xSpacing;
				}
				result << '\t' << (*histogramValues)[i] << std::endl;
			}
			result << std::endl;
		}
	}
	return result.str();
}

std::tuple<std::vector<double>*,double,double,size_t> HistogramPlotter::GetHistogramValues(int facetId,size_t modeId)
{
	//facetId: -1 if global, otherwise facet id
	//modeId: bounce/distance/time (0/1/2)
	//returns pointer to values, max X value, X bin size, number of values (which can't be derived from the vector size when nothing's recorded yet)

	InterfaceGeometry *interfGeom = worker->GetGeometry();
	double xMax;
	double xSpacing;
	size_t nbBins;

	std::vector<double>* histogramValues;

	if (facetId == -1) { //Global histogram
		if (modeId == HISTOGRAM_MODE_BOUNCES) {
			histogramValues = &(worker->globalHistogramCache.nbHitsHistogram);
			xMax = (double)worker->model->sp.globalHistogramParams.nbBounceMax;
			xSpacing = (double)worker->model->sp.globalHistogramParams.nbBounceBinsize;
			nbBins = worker->model->sp.globalHistogramParams.GetBounceHistogramSize();
		}
		else if (modeId == HISTOGRAM_MODE_DISTANCE) {
			histogramValues = &(worker->globalHistogramCache.distanceHistogram);
			xMax = worker->model->sp.globalHistogramParams.distanceMax;
			xSpacing = worker->model->sp.globalHistogramParams.distanceBinsize;
			nbBins = worker->model->sp.globalHistogramParams.GetDistanceHistogramSize();
		}
#if defined(MOLFLOW)
		else if (modeId == HISTOGRAM_MODE_TIME) {
			histogramValues = &(worker->globalHistogramCache.timeHistogram);
			xMax = worker->model->sp.globalHistogramParams.timeMax;
			xSpacing = worker->model->sp.globalHistogramParams.timeBinsize;
			nbBins = worker->model->sp.globalHistogramParams.GetTimeHistogramSize();
		}
#endif
	}
	else { //Facet histogram
		if (modeId == HISTOGRAM_MODE_BOUNCES) {
			histogramValues = &(interfGeom->GetFacet(facetId)->facetHistogramCache.nbHitsHistogram);
			xMax = (double)interfGeom->GetFacet(facetId)->sh.facetHistogramParams.nbBounceMax;
			xSpacing = (double)(interfGeom->GetFacet(facetId)->sh.facetHistogramParams.nbBounceBinsize);
			nbBins = interfGeom->GetFacet(facetId)->sh.facetHistogramParams.GetBounceHistogramSize();
		}
		else if (modeId == HISTOGRAM_MODE_DISTANCE) {
			histogramValues = &(interfGeom->GetFacet(facetId)->facetHistogramCache.distanceHistogram);
			xMax = interfGeom->GetFacet(facetId)->sh.facetHistogramParams.distanceMax;
			xSpacing = interfGeom->GetFacet(facetId)->sh.facetHistogramParams.distanceBinsize;
			nbBins = interfGeom->GetFacet(facetId)->sh.facetHistogramParams.GetDistanceHistogramSize();
		}
#if defined(MOLFLOW)
		else if (modeId == HISTOGRAM_MODE_TIME) {
			histogramValues = &(interfGeom->GetFacet(facetId)->facetHistogramCache.timeHistogram);
			xMax = interfGeom->GetFacet(facetId)->sh.facetHistogramParams.timeMax;
			xSpacing = interfGeom->GetFacet(facetId)->sh.facetHistogramParams.timeBinsize;
			nbBins = interfGeom->GetFacet(facetId)->sh.facetHistogramParams.GetTimeHistogramSize();
		}
#endif

	}
	return { histogramValues,xMax,xSpacing,nbBins };
}

void HistogramPlotter::addView(int facetId) {
	int modeId = GetSelectedTabIndex();
	InterfaceGeometry *interfGeom = worker->GetGeometry();

	// Check that view is not already added
	{
		bool found = false;

		for (auto v = modes[modeId].views.begin(); v != modes[modeId].views.end() && !found;v++) {
			found = ((*v)->userData1 == facetId);
		}
		if (found) {
			GLMessageBox::Display("Histogram already on chart", "Error", GLDLG_OK, GLDLG_ICONERROR);
			return;
		}
	}

	auto v = new GLDataView();
	std::ostringstream tmp;
	if (facetId == -1)
		tmp << "Global";
	else
		tmp << "Facet " << facetId + 1;
	v->SetName(tmp.str().c_str());
	v->SetViewType(TYPE_BAR);
	v->SetMarker(MARKER_DOT);
	GLColor col = modes[modeId].chart->GetFirstAvailableColor();
	v->SetColor(col);
	v->SetMarkerColor(col);
	v->userData1 = facetId;
	modes[modeId].views.push_back(v);
	modes[modeId].chart->GetY1Axis()->AddDataView(v);
	auto[histogramValues, xMax, xSpacing, nbBins] = GetHistogramValues(facetId, modeId);
	if (nbBins > 1000) {
		GLMessageBox::Display("For performance reasons only the first 1000 histogram points will be plotted.\n"
			"This, among others, will cut the last histogram point representing out-of-limit values.\n"
			"You can still get the data of the whole histogram by using the Copy Data button", "More than 1000 histogram points", { "OK" }, GLDLG_ICONWARNING);
	}
	//Refresh();
}

void HistogramPlotter::remView(int facetId) {
	size_t modeId = GetSelectedTabIndex();
	InterfaceGeometry *interfGeom = worker->GetGeometry();

	bool found = false;
	size_t i = 0;
	for (;!found && i<modes[modeId].views.size();i++) {
		found = (modes[modeId].views[i]->userData1 == facetId);
	}
	if (!found) {
		GLMessageBox::Display("Histogram not on chart", "Error", GLDLG_OK, GLDLG_ICONERROR);
		return;
	}
	modes[modeId].chart->GetY1Axis()->RemoveDataView(modes[modeId].views[--i]);
	SAFE_DELETE(modes[modeId].views[i]);
	modes[modeId].views.erase(modes[modeId].views.begin() + i);
}

void HistogramPlotter::Reset() {
	for (auto& mode : modes) {
		mode.chart->GetY1Axis()->ClearDataView();
		for (auto v : mode.views)
			delete v;
		mode.views.clear();
	}
	Refresh();
}

void HistogramPlotter::ProcessMessage(GLComponent *src, int message) {
	size_t modeId = GetSelectedTabIndex();
	InterfaceGeometry *interfGeom = worker->GetGeometry();
	switch (message) {
	case MSG_BUTTON:
		if (src == selButton) {

			int idx = histCombo->GetSelectedIndex();
			if (idx >= 0) {
				int facetId = histCombo->GetUserValueAt(idx);
				if (facetId >= 0 && facetId <interfGeom->GetNbFacet()) { //Not global histogram
					interfGeom->UnselectAll();
					interfGeom->GetFacet(histCombo->GetUserValueAt(idx))->selected = true;
					interfGeom->UpdateSelection();

					mApp->UpdateFacetParams(true);

					mApp->facetList->SetSelectedRow(histCombo->GetUserValueAt(idx));
					mApp->facetList->ScrollToVisible(histCombo->GetUserValueAt(idx), 1, true);
				}
			}
		}
		else if (src == addButton) {

			int idx = histCombo->GetSelectedIndex();

			if (idx >= 0) {
				addView(histCombo->GetUserValueAt(idx));
				refreshChart();
			}
		}
		else if (src == removeButton) {

			int idx = histCombo->GetSelectedIndex();

			if (idx >= 0) remView(histCombo->GetUserValueAt(idx));
			refreshChart();
		}
		else if (src == removeAllButton) {

			Reset();
		}
		else if (src == histogramSettingsButton) {
			if (!mApp->histogramSettings || !mApp->histogramSettings->IsVisible())
			{
				SAFE_DELETE(mApp->histogramSettings);
				mApp->histogramSettings = new HistogramSettings(interfGeom, worker);
				mApp->histogramSettings->Refresh(interfGeom->GetSelectedFacets());
				mApp->histogramSettings->SetVisible(true);
			} else {
				mApp->histogramSettings->SetVisible(false);				
			}
		} else if (src == copyAllButton) {
			const std::string histogramData = getData();
			GLToolkit::CopyTextToClipboard(histogramData);
		}
		break;
	case MSG_COMBO:
		if (src == yScaleCombo) {
			refreshChart();
		}
		break;
	case MSG_TAB:
	/*
	//Hide/show charts already managed by GLTabWindow

			modeId = GetSelectedTabIndex();
			for (size_t i = 0; i < modes.size(); i++) {
				modes[i].chart->SetVisible(i == modeId);
			}
	*/
			Refresh(); //Rebuild prof combo
		
	
	break;
	case MSG_TOGGLE:
		for (auto& mode : modes) {
			if (src == logXToggle) {
				mode.chart->GetXAxis()->SetScale(logXToggle->GetState());
			}
			else if (src == logYToggle) {
				mode.chart->GetY1Axis()->SetScale(logYToggle->GetState());
			}
		}
		break;
	}

	GLTabWindow::ProcessMessage(src, message);

}