Path: blob/main/src/utils/gui/tracker/GUIParameterTracker.cpp
169685 views
/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.3// This program and the accompanying materials are made available under the4// terms of the Eclipse Public License 2.0 which is available at5// https://www.eclipse.org/legal/epl-2.0/6// This Source Code may also be made available under the following Secondary7// Licenses when the conditions for such availability set forth in the Eclipse8// Public License 2.0 are satisfied: GNU General Public License, version 29// or later which is available at10// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html11// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later12/****************************************************************************/13/// @file GUIParameterTracker.cpp14/// @author Daniel Krajzewicz15/// @author Jakob Erdmann16/// @author Michael Behrisch17/// @date Sept 200218///19// A window which displays the time line of one (or more) value(s)20/****************************************************************************/21#include <config.h>2223#include <string>24#include <fstream>25#include <utils/common/MsgHandler.h>26#include <utils/common/ToString.h>27#include <utils/common/StringUtils.h>28#include <utils/common/SUMOTime.h>29#include <utils/foxtools/MFXUtils.h>30#include <utils/iodevices/OutputDevice.h>31#include <utils/gui/div/GLHelper.h>32#include <utils/gui/globjects/GUIGlObject.h>33#include <utils/gui/div/GUIIOGlobals.h>34#include <utils/gui/div/GUIDesigns.h>35#include <utils/gui/windows/GUIAppEnum.h>36#include <utils/gui/windows/GUIMainWindow.h>37#include <utils/gui/images/GUIIconSubSys.h>38#include <foreign/fontstash/fontstash.h>39#include <utils/gui/globjects/GLIncludes.h>40#include "GUIParameterTracker.h"414243// ===========================================================================44// FOX callback mapping45// ===========================================================================46FXDEFMAP(GUIParameterTracker) GUIParameterTrackerMap[] = {47FXMAPFUNC(SEL_CONFIGURE, 0, GUIParameterTracker::onConfigure),48FXMAPFUNC(SEL_PAINT, 0, GUIParameterTracker::onPaint),49FXMAPFUNC(SEL_COMMAND, MID_SIMSTEP, GUIParameterTracker::onSimStep),50FXMAPFUNC(SEL_COMMAND, GUIParameterTracker::MID_MULTIPLOT, GUIParameterTracker::onMultiPlot),51FXMAPFUNC(SEL_COMMAND, GUIParameterTracker::MID_AGGREGATIONINTERVAL, GUIParameterTracker::onCmdChangeAggregation),52FXMAPFUNC(SEL_COMMAND, GUIParameterTracker::MID_SAVE, GUIParameterTracker::onCmdSave),5354};5556// Macro for the GLTestApp class hierarchy implementation57FXIMPLEMENT(GUIParameterTracker, FXMainWindow, GUIParameterTrackerMap, ARRAYNUMBER(GUIParameterTrackerMap))5859// ===========================================================================60// static value definitions61// ===========================================================================62std::set<GUIParameterTracker*> GUIParameterTracker::myMultiPlots;63std::vector<RGBColor> GUIParameterTracker::myColors;646566// ===========================================================================67// method definitions68// ===========================================================================69GUIParameterTracker::GUIParameterTracker(GUIMainWindow& app,70const std::string& name)71: FXMainWindow(app.getApp(), "Tracker", nullptr, nullptr, DECOR_ALL, 20, 20, 300, 200),72myApplication(&app) {73buildToolBar();74app.addChild(this);75FXVerticalFrame* glcanvasFrame = new FXVerticalFrame(this, FRAME_SUNKEN | LAYOUT_SIDE_TOP | LAYOUT_FILL_X | LAYOUT_FILL_Y, 0, 0, 0, 0, 0, 0, 0, 0);76myPanel = new GUIParameterTrackerPanel(glcanvasFrame, *myApplication, *this);77setTitle(name.c_str());78setIcon(GUIIconSubSys::getIcon(GUIIcon::APP_TRACKER));7980if (myColors.size() == 0) {81myColors = {RGBColor::BLACK, RGBColor::GREEN, RGBColor::RED, RGBColor::BLUE, RGBColor::ORANGE, RGBColor::CYAN, RGBColor::MAGENTA};8283}84}858687GUIParameterTracker::~GUIParameterTracker() {88myMultiPlots.erase(this);89myApplication->removeChild(this);90for (std::vector<TrackerValueDesc*>::iterator i1 = myTracked.begin(); i1 != myTracked.end(); i1++) {91delete (*i1);92}93// deleted by GUINet94for (std::vector<GLObjectValuePassConnector<double>*>::iterator i2 = myValuePassers.begin(); i2 != myValuePassers.end(); i2++) {95delete (*i2);96}97delete myToolBarDrag;98delete myToolBar;99}100101102void103GUIParameterTracker::create() {104FXMainWindow::create();105myToolBarDrag->create();106}107108109void110GUIParameterTracker::buildToolBar() {111myToolBarDrag = new FXToolBarShell(this, GUIDesignToolBar);112myToolBar = new FXToolBar(this, myToolBarDrag, LAYOUT_SIDE_TOP | LAYOUT_FILL_X | FRAME_RAISED);113new FXToolBarGrip(myToolBar, myToolBar, FXToolBar::ID_TOOLBARGRIP, GUIDesignToolBarGrip);114// save button115GUIDesigns::buildFXButton(myToolBar, "", "", + TL("Save the data..."),116GUIIconSubSys::getIcon(GUIIcon::SAVE), this, GUIParameterTracker::MID_SAVE, GUIDesignButtonToolbar);117118// aggregation interval combo119myAggregationInterval = new MFXComboBoxIcon(myToolBar, nullptr, false, GUIDesignComboBoxVisibleItems,120this, MID_AGGREGATIONINTERVAL, GUIDesignComboBoxStatic);121myAggregationInterval->appendIconItem("1s");122myAggregationInterval->appendIconItem("1min");123myAggregationInterval->appendIconItem("5min");124myAggregationInterval->appendIconItem("15min");125myAggregationInterval->appendIconItem("30min");126myAggregationInterval->appendIconItem("60min");127128myMultiPlot = new FXCheckButton(myToolBar, TL("Multiplot"), this, MID_MULTIPLOT);129myMultiPlot->setCheck(false);130}131132133bool134GUIParameterTracker::addTrackedMultiplot(GUIGlObject& o, ValueSource<double>* src, TrackerValueDesc* newTracked) {135bool first = true;136for (GUIParameterTracker* tr : myMultiPlots) {137if (first) {138first = false;139} else {140// each Tracker gets its own copy to simplify cleanup141newTracked = new TrackerValueDesc(newTracked->getName(), RGBColor::BLACK, newTracked->getRecordingBegin(),142STEPS2TIME(newTracked->getAggregationSpan()));143src = src->copy();144}145tr->addTracked(o, src, newTracked);146}147return myMultiPlots.size() > 0;148}149150151void152GUIParameterTracker::addTracked(GUIGlObject& o, ValueSource<double>* src,153TrackerValueDesc* newTracked) {154myTracked.push_back(newTracked);155// build connection (is automatically set into an execution map)156myValuePassers.push_back(new GLObjectValuePassConnector<double>(o, src, newTracked));157update();158}159160161long162GUIParameterTracker::onConfigure(FXObject* sender, FXSelector sel, void* ptr) {163myPanel->onConfigure(sender, sel, ptr);164return FXMainWindow::onConfigure(sender, sel, ptr);165}166167168long169GUIParameterTracker::onPaint(FXObject* sender, FXSelector sel, void* ptr) {170myPanel->onPaint(sender, sel, ptr);171return FXMainWindow::onPaint(sender, sel, ptr);172}173174175long176GUIParameterTracker::onSimStep(FXObject*, FXSelector, void*) {177update();178return 1;179}180181long182GUIParameterTracker::onCmdChangeAggregation(FXObject*, FXSelector, void*) {183int index = myAggregationInterval->getCurrentItem();184int aggInt = 0;185switch (index) {186case 0:187aggInt = 1;188break;189case 1:190aggInt = 60;191break;192case 2:193aggInt = 60 * 5;194break;195case 3:196aggInt = 60 * 15;197break;198case 4:199aggInt = 60 * 30;200break;201case 5:202aggInt = 60 * 60;203break;204default:205throw 1;206}207for (TrackerValueDesc* const tvd : myTracked) {208tvd->setAggregationSpan(TIME2STEPS(aggInt));209}210return 1;211}212213214long215GUIParameterTracker::onCmdSave(FXObject*, FXSelector, void*) {216FXString file = MFXUtils::getFilename2Write(this, TL("Save Data"),217SUMOXMLDefinitions::CSVFileExtensions.getMultilineString().c_str(),218GUIIconSubSys::getIcon(GUIIcon::EMPTY), gCurrentFolder);219if (file == "") {220return 1;221}222try {223OutputDevice& dev = OutputDevice::getDevice(file.text());224// write header225std::vector<TrackerValueDesc*>::iterator i;226dev << "# Time";227for (i = myTracked.begin(); i != myTracked.end(); ++i) {228TrackerValueDesc* tvd = *i;229dev << ';' << tvd->getName();230}231dev << '\n';232// count entries233int max = 0;234for (i = myTracked.begin(); i != myTracked.end(); ++i) {235TrackerValueDesc* tvd = *i;236int sizei = (int)tvd->getAggregatedValues().size();237if (max < sizei) {238max = sizei;239}240tvd->unlockValues();241}242// write entries243SUMOTime t = myTracked.empty() ? 0 : myTracked.front()->getRecordingBegin();244SUMOTime dt = myTracked.empty() ? DELTA_T : myTracked.front()->getAggregationSpan();245for (int j = 0; j < max; j++) {246dev << time2string(t);247for (i = myTracked.begin(); i != myTracked.end(); ++i) {248TrackerValueDesc* tvd = *i;249dev << ';' << tvd->getAggregatedValues()[j];250tvd->unlockValues();251}252dev << '\n';253t += dt;254}255dev.close();256} catch (IOError& e) {257FXMessageBox::error(this, MBOX_OK, TL("Storing failed!"), "%s", e.what());258}259return 1;260}261262263long264GUIParameterTracker::onMultiPlot(FXObject*, FXSelector, void*) {265if (myMultiPlot->getCheck()) {266myMultiPlots.insert(this);267} else {268myMultiPlots.erase(this);269}270return 1;271}272273/* -------------------------------------------------------------------------274* GUIParameterTracker::GUIParameterTrackerPanel-methods275* ----------------------------------------------------------------------- */276FXDEFMAP(GUIParameterTracker::GUIParameterTrackerPanel) GUIParameterTrackerPanelMap[] = {277FXMAPFUNC(SEL_CONFIGURE, 0, GUIParameterTracker::GUIParameterTrackerPanel::onConfigure),278FXMAPFUNC(SEL_MOTION, 0, GUIParameterTracker::GUIParameterTrackerPanel::onMouseMove),279FXMAPFUNC(SEL_PAINT, 0, GUIParameterTracker::GUIParameterTrackerPanel::onPaint),280281};282283// Macro for the GLTestApp class hierarchy implementation284FXIMPLEMENT(GUIParameterTracker::GUIParameterTrackerPanel, FXGLCanvas, GUIParameterTrackerPanelMap, ARRAYNUMBER(GUIParameterTrackerPanelMap))285286287288GUIParameterTracker::GUIParameterTrackerPanel::GUIParameterTrackerPanel(289FXComposite* c, GUIMainWindow& app,290GUIParameterTracker& parent)291: FXGLCanvas(c, app.getGLVisual(), app.getBuildGLCanvas(), (FXObject*) nullptr, (FXSelector) 0, LAYOUT_SIDE_TOP | LAYOUT_FILL_X | LAYOUT_FILL_Y, 0, 0, 300, 200),292myParent(&parent) {}293294295GUIParameterTracker::GUIParameterTrackerPanel::~GUIParameterTrackerPanel() {}296297298void299GUIParameterTracker::GUIParameterTrackerPanel::drawValues() {300glMatrixMode(GL_PROJECTION);301glLoadIdentity();302glMatrixMode(GL_MODELVIEW);303glLoadIdentity();304glDisable(GL_TEXTURE_2D);305for (int i = 0; i < (int)myParent->myTracked.size(); i++) {306TrackerValueDesc* desc = myParent->myTracked[i];307glPushMatrix();308drawValue(*desc, myColors[i % myColors.size()], i);309glPopMatrix();310}311}312313314void315GUIParameterTracker::GUIParameterTrackerPanel::drawValue(TrackerValueDesc& desc,316const RGBColor& col,317int index) {318const double fontWidth = 0.1 * 300. / myWidthInPixels;319const double fontHeight = 0.1 * 300. / myHeightInPixels;320const bool isMultiPlot = myParent->myTracked.size() > 1;321const std::vector<double>& values = desc.getAggregatedValues();322if (values.size() < 2) {323// draw name324glTranslated(-.9, 0.9, 0);325GLHelper::drawText(desc.getName(), Position((double)index / (double)myParent->myTracked.size(), 0.), 1, fontHeight, col, 0, FONS_ALIGN_LEFT | FONS_ALIGN_MIDDLE, fontWidth);326desc.unlockValues();327return;328}329//330// apply scaling331GLHelper::pushMatrix();332333// apply the positiopn offset of the display334glScaled(0.8, 0.8, 1);335// apply value range scaling336double ys = (double) 2.0 / (double) desc.getRange();337glScaled(1.0, ys, 1.0);338glTranslated(-1.0, -desc.getYCenter(), 0);339340// draw value bounderies341// draw minimum boundary342glBegin(GL_LINES);343glVertex2d(0, desc.getMin());344glVertex2d(2.0, desc.getMin());345glEnd();346glBegin(GL_LINES);347glVertex2d(0, desc.getMax());348glVertex2d(2.0, desc.getMax());349glEnd();350GLHelper::setColor(col.changedAlpha(-178));351for (int a = 1; a < 6; a++) {352const double yp = desc.getRange() / 6.0 * (double) a + desc.getMin();353glBegin(GL_LINES);354glVertex2d(0, yp);355glVertex2d(2.0, yp);356glEnd();357}358359double latest = 0;360double mx = (2 * myMouseX / myWidthInPixels - 1) / 0.8 + 1;361int mIndex = 0;362double mouseValue = std::numeric_limits<double>::max();363latest = values.back();364// init values365const double xStep = 2.0 / (double) values.size();366std::vector<double>::const_iterator i = values.begin();367double yp = (*i);368double xp = 0;369i++;370GLHelper::setColor(col);371for (; i != values.end(); i++) {372double yn = (*i);373double xn = xp + xStep;374if (xp < mx && mx < xn) {375mouseValue = yp;376mIndex = (int)(i - values.begin()) - 1;377glPushMatrix();378GLHelper::setColor(isMultiPlot ? col.changedBrightness(-40).changedAlpha(-100) : RGBColor::BLUE);379glTranslated(xn, yn, 0);380glScaled(20.0 / myWidthInPixels, 10.0 * desc.getRange() / myHeightInPixels, 0);381GLHelper::drawFilledCircle(1, 8);382GLHelper::setColor(col);383glPopMatrix();384}385glBegin(GL_LINES);386glVertex2d(xp, yp);387glVertex2d(xn, yn);388glEnd();389yp = yn;390xp = xn;391}392desc.unlockValues();393GLHelper::popMatrix();394395// draw value bounderies and descriptions396GLHelper::setColor(col);397398// draw min time399SUMOTime beginStep = desc.getRecordingBegin();400std::string begStr = time2string(beginStep);401double w = 50 / myWidthInPixels;402glTranslated(-0.8 - w / 2., -0.88, 0);403GLHelper::drawText(begStr, Position(0, 0), 1, fontHeight, RGBColor::BLACK, 0, FONS_ALIGN_LEFT | FONS_ALIGN_MIDDLE, fontWidth);404glTranslated(0.8 + w / 2., 0.88, 0);405406// draw max time407glTranslated(0.75, -0.88, 0);408GLHelper::drawText(time2string(beginStep + static_cast<SUMOTime>(values.size() * desc.getAggregationSpan())),409Position(0, 0), 1, fontHeight, RGBColor::BLACK, 0, FONS_ALIGN_LEFT | FONS_ALIGN_MIDDLE, fontWidth);410glTranslated(-0.75, 0.88, 0);411412// draw min value413glTranslated(-0.98, -0.82, 0);414GLHelper::drawText(toString(desc.getMin()), Position(0, index * fontHeight), 1, fontHeight, col, 0, FONS_ALIGN_LEFT | FONS_ALIGN_MIDDLE, fontWidth);415glTranslated(0.98, 0.82, 0);416417// draw max value418glTranslated(-0.98, 0.78, 0);419GLHelper::drawText(toString(desc.getMax()), Position(0, -index * fontHeight), 1, fontHeight, col, 0, FONS_ALIGN_LEFT | FONS_ALIGN_MIDDLE, fontWidth);420glTranslated(0.98, -0.78, 0);421422// draw name423glTranslated(-0.98, .92, 0);424GLHelper::drawText(desc.getName(), Position((double)index / (double)myParent->myTracked.size(), 0.), 1, fontHeight, col, 0, FONS_ALIGN_LEFT | FONS_ALIGN_MIDDLE, fontWidth);425glTranslated(0.98, -.92, 0);426427// draw current value (with contrasting color)428double p = (double) 0.8 -429((double) 1.6 / (desc.getMax() - desc.getMin()) * (latest - desc.getMin()));430glTranslated(-0.98, -(p + .02), 0);431GLHelper::drawText(toString(latest), Position(isMultiPlot ? 0.1 : 0, 0), 1, fontHeight, isMultiPlot ? col.changedBrightness(50) : RGBColor::RED, 0, FONS_ALIGN_LEFT | FONS_ALIGN_MIDDLE, fontWidth);432glTranslated(0.98, p + .02, 0);433434// draw moused value435if (mouseValue != std::numeric_limits<double>::max()) {436p = (double) 0.8 -437((double) 1.6 / (desc.getMax() - desc.getMin()) * (mouseValue - desc.getMin()));438glTranslated(-0.98, -(p + .02), 0);439GLHelper::drawText(toString(mouseValue), Position(isMultiPlot ? 0.1 : 0, 0), 1, fontHeight, isMultiPlot ? col.changedBrightness(-40) : RGBColor::BLUE, 0, FONS_ALIGN_LEFT | FONS_ALIGN_MIDDLE, fontWidth);440glTranslated(0.98, p + .02, 0);441442if (index == 0) {443// time is the same for all plots so we only draw it once444const std::string mouseTime = time2string(beginStep + static_cast<SUMOTime>(mIndex * desc.getAggregationSpan()));445glTranslated(1.6 * (double)mIndex / (double)values.size() - 0.8, -0.9, 0);446GLHelper::drawText(mouseTime, Position(0, 0), 1, fontHeight, isMultiPlot ? col.changedBrightness(-40) : RGBColor::BLUE, 0, FONS_ALIGN_LEFT | FONS_ALIGN_MIDDLE, fontWidth);447}448}449450}451452453long454GUIParameterTracker::GUIParameterTrackerPanel::onConfigure(FXObject*,455FXSelector, void*) {456if (makeCurrent()) {457myWidthInPixels = myParent->getWidth();458myHeightInPixels = myParent->getHeight();459if (myWidthInPixels != 0 && myHeightInPixels != 0) {460glViewport(0, 0, myWidthInPixels - 1, myHeightInPixels - 1);461glClearColor(1.0, 1.0, 1.0, 1);462glDisable(GL_DEPTH_TEST);463glDisable(GL_LIGHTING);464glDisable(GL_LINE_SMOOTH);465glEnable(GL_BLEND);466glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);467glEnable(GL_ALPHA_TEST);468glDisable(GL_COLOR_MATERIAL);469glLineWidth(1);470glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);471}472makeNonCurrent();473}474return 1;475}476477478long479GUIParameterTracker::GUIParameterTrackerPanel::onPaint(FXObject*,480FXSelector, void*) {481if (!isEnabled()) {482return 1;483}484if (makeCurrent()) {485myWidthInPixels = getWidth();486myHeightInPixels = getHeight();487if (myWidthInPixels != 0 && myHeightInPixels != 0) {488glViewport(0, 0, myWidthInPixels - 1, myHeightInPixels - 1);489glClearColor(1.0, 1.0, 1.0, 1);490glDisable(GL_DEPTH_TEST);491glDisable(GL_LIGHTING);492glDisable(GL_LINE_SMOOTH);493glEnable(GL_BLEND);494glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);495glEnable(GL_ALPHA_TEST);496glDisable(GL_COLOR_MATERIAL);497glLineWidth(1);498glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);499// draw500glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);501drawValues();502swapBuffers();503}504makeNonCurrent();505}506return 1;507}508509510long511GUIParameterTracker::GUIParameterTrackerPanel::onMouseMove(FXObject*, FXSelector, void* ptr) {512FXEvent* event = (FXEvent*) ptr;513myMouseX = event->win_x;514update();515return 1;516}517518519520/****************************************************************************/521522523