diff --git a/JKQtPlotterBuildAllExamples.pro b/JKQtPlotterBuildAllExamples.pro index afd32ab3b5..6c2e2d01b9 100644 --- a/JKQtPlotterBuildAllExamples.pro +++ b/JKQtPlotterBuildAllExamples.pro @@ -4,6 +4,7 @@ SUBDIRS += jkqtplotterlib \ jkqtmathtext_simpletest \ jkqtplot_test \ jkqtplotter_simpletest \ + test_multiplot jkqtplotterlib.file = lib/jkqtplotterlib.pro @@ -20,6 +21,9 @@ jkqtplot_test.depends = jkqtplotterlib jkqtplotter_simpletest.file = test/simpletest/jkqtplotter_simpletest.pro jkqtplotter_simpletest.depends = jkqtplotterlib +test_multiplot.file = test/test_multiplot/test_multiplot.pro +test_multiplot.depends = jkqtplotterlib + defineTest(addSimpleTest) { test_name = $$1 SUBDIRS += jkqtplotter_simpletest_$${test_name} diff --git a/README.md b/README.md index f3af7bbdd5..70c0819f8c 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,12 @@ All test-projects are Qt-projects that use qmake to build. You can load them int | [![](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/jkqtplotter_simpletest_imageplot_opencv_small.png)](https://github.com/jkriege2/JKQtPlotter/tree/master/test/simpletest_imageplot_opencv) | [1-channel OpenCV cv::Mat Image Plot](https://github.com/jkriege2/JKQtPlotter/tree/master/test/simpletest_imageplot_opencv) | `JKQTPColumnMathImage`
image data copied from OpenCV cv::Mat-structure into a single column of the internal datastore | | [![](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/jkqtplotter_simpletest_rgbimageplot_opencv_small.png)](https://github.com/jkriege2/JKQtPlotter/tree/master/test/simpletest_rgbimageplot_opencv) | [RGB OpenCV cv::Mat Image Plot](https://github.com/jkriege2/JKQtPlotter/tree/master/test/simpletest_rgbimageplot_opencv) | `JKQTPColumnRGBMathImage`
image data copied from OpenCV cv::Mat-structure into three columns of the internal datastore | +### GUI Tools and Plot Layout + +| Screenshot | Description | Notes | +|:-------------:| ------------- | ------------- | +| [![](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/test_multiplot_small.png)](https://github.com/jkriege2/JKQtPlotter/tree/master/test/test_multiplot) | [Layouting Several Plots](https://github.com/jkriege2/JKQtPlotter/tree/master/test/test_multiplot) | Combining plots in Qt Layouts
linking plot axes
copy data from a `std::map` int the datastore | + ### Tools and Special Features | Screenshot | Description | Notes | diff --git a/screenshots/test_multiplot.png b/screenshots/test_multiplot.png new file mode 100644 index 0000000000..79032773ea Binary files /dev/null and b/screenshots/test_multiplot.png differ diff --git a/screenshots/test_multiplot_printpreview.png b/screenshots/test_multiplot_printpreview.png new file mode 100644 index 0000000000..ff6201b4aa Binary files /dev/null and b/screenshots/test_multiplot_printpreview.png differ diff --git a/screenshots/test_multiplot_small.png b/screenshots/test_multiplot_small.png new file mode 100644 index 0000000000..58cb49a8c3 Binary files /dev/null and b/screenshots/test_multiplot_small.png differ diff --git a/test/test_multiplot/README.md b/test/test_multiplot/README.md new file mode 100644 index 0000000000..b8a9e7fffb --- /dev/null +++ b/test/test_multiplot/README.md @@ -0,0 +1,108 @@ +[Back to JKQTPlotter main page](https://github.com/jkriege2/JKQtPlotter/) + +# JKQtPlotter + +## Layouting Several Plots +This project (see `./test/test_multiplot/`) shows how several JKQtPlotter widgets can be combined to in a layout (based on the [Qt layouting system](http://doc.qt.io/qt-5/layout.html)). It also shows how axes in such a layout can be linked to improve user experience. + +The source code of the main application can be found in [`test_multiplot.cpp`](https://github.com/jkriege2/JKQtPlotter/blob/master/test/simpletest_stepplots/test_multiplot.cpp). + +First three plots are generated and put into a [QGridLayout](http://doc.qt.io/qt-5/qgridlayout.html): + +```c++ +// 1. create a widget + QWidget mainWidget; + mainWidget.setWindowTitle("JKQtPlotter(s) in a QGridLayout"); + + // 2. Create a QGridLayout for the plots and add it to the widget. + QGridLayout* layout=new QGridLayout(); + mainWidget.setLayout(layout); + + // 3.1 create a main plotter widget and add it to the layout + JKQtPlotter* plotMain=new JKQtPlotter(&mainWidget); + layout->addWidget(plotMain, 0,0); + JKQTPdatastore* ds=plotMain->getDatastore(); + + // 3.2 create a second and third plotter widget and add them to the + // layout below and at the bottom right of the plotMain. + // Also configure it to use the same datastore as plotMain + JKQtPlotter* plotResid=new JKQtPlotter(false, &mainWidget, ds); + layout->addWidget(plotResid, 1,0); + JKQtPlotter* plotResidHist=new JKQtPlotter(false, &mainWidget, ds); + layout->addWidget(plotResidHist, 1,1); + + // 3.3 set relative sizes of the plots via the layout (small plots have 1/3 the width and height of the large plot + layout->setRowStretch(0,3); + layout->setRowStretch(1,1); + layout->setColumnStretch(0,3); + layout->setColumnStretch(1,1); +``` + +With this simple setup, all three plots would be arranged by the QLayout, but they were all independent. This example could be part of a data fitting application, where the main plot shows data and a fit curve. A plot below that will display the residulas (errors) of the fit. Now if a user zooms one of the plots, he would expect that athe x-axes of the two plots are synchronized. The same for a third plot on the rhs of the residuals, which will show a residual histogram. This linking of the axes can be achieved by the following code: + +```c++ + // 3.4 synchronize width/x-axis of plotResid to width/x-axis of plotMain + plotResid->get_plotter()->synchronizeToMaster(plotMain->get_plotter(), true, false, true, true); + + // 3.5 synchronize y-axis of width/plotResidHist to y-axis of width/plotResid + plotResidHist->get_plotter()->synchronizeToMaster(plotResid->get_plotter(), false, true, true, true); +``` + +Finally: When printing or saving an image of the plots, the plotter will no know anything about the arrangement of the plots and the plots cannot be printed/drawn in the same arrangement as in the window. If you want to arrange the plots in the same layout in a printout, as in the window, you will have to tell the main plot, in which arrangement to print the plots: + +```c++ + // 3.6 ensure that the plot are printed/exported in whole, when printing in plotMain + plotMain->get_plotter()->set_gridPrinting(true); + plotMain->get_plotter()->addGridPrintingPlotter(0,1,plotResid->get_plotter()); + plotMain->get_plotter()->addGridPrintingPlotter(1,1,plotResidHist->get_plotter()); +``` + +In the first line, grid-printing (i.e. the layouted printing of several graphs) is activated. Then the arrangement of the two slave plots `plotResid` and `plotResidHist` is defined as (`x,y`)-shifts with respect to the master plot `plotMain`. + +Now some data is generated and several curves are added to the graphs. See [`test_multiplot.cpp`](https://github.com/jkriege2/JKQtPlotter/blob/master/test/simpletest_stepplots/test_multiplot.cpp) for the full source code. + +Finally the axes and plots need a bit of formatting to make them look nicer: + +```c++ + // 6.1 axis labels, distributed over the several plots + plotMain->get_yAxis()->set_axisLabel("y axis"); + plotResid->get_xAxis()->set_axisLabel("x axis"); + plotResid->get_yAxis()->set_axisLabel("residuals"); + plotResidHist->get_xAxis()->set_axisLabel("frequency"); + // 6.2 switch off the tick labels on the axes that directly face another plot + plotMain->get_xAxis()->set_drawMode1(JKQTPCADMticks); + plotResidHist->get_yAxis()->set_drawMode1(JKQTPCADMticks); + // 6.3 show tick labels on the rhs y-axis of the residual histogram plot + plotResidHist->get_yAxis()->set_drawMode2(JKQTPCADMticksAndLabels); + // 6.4 hide keys in all plots but the main plot + plotResid->get_plotter()->set_showKey(false); + plotResidHist->get_plotter()->set_showKey(false); + // 6.5 hide position label and toolbars in the plots except main plot + plotResid->set_displayToolbar(false); + plotResid->set_displayMousePosition(false); + plotResidHist->set_displayToolbar(false); + plotResidHist->set_displayMousePosition(false); + plotMain->set_toolbarAlwaysOn(true); +``` + +As a last step, the axes are scaled automatically, so the data fills the plots: + +```c++ + // 7. scale plots automatically to data + plotResid->zoomToFit(); + plotResidHist->zoomToFit(); + plotMain->zoomToFit(); +``` + +The result looks like this: + +![test_multiplot](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/test_multiplot.png) + +You push the print button (![test_multiplot](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/lib/jkqtplotterressources/images/jkqtp_24_print.png)) to open a print preview dialog, which will give an impression of how the three plots will be arranged in a printout: + +![test_multiplot](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/test_multiplot_printpreview.png) + + + + +[Back to JKQTPlotter main page](https://github.com/jkriege2/JKQtPlotter/) \ No newline at end of file diff --git a/test/test_multiplot/test_multiplot.cpp b/test/test_multiplot/test_multiplot.cpp new file mode 100644 index 0000000000..3287a5dce1 --- /dev/null +++ b/test/test_multiplot/test_multiplot.cpp @@ -0,0 +1,153 @@ +#include +#include "jkqtplotter/jkqtplotter.h" +#include "jkqtplotter/jkqtpelements.h" +#include "jkqtplotter/jkqtpparsedfunctionelements.h" +#include "jkqtplotter/jkqtpbarchartelements.h" +#include +#include + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + + // 1. create a widget + QWidget mainWidget; + mainWidget.setWindowTitle("JKQtPlotter(s) in a QGridLayout"); + + // 2. Create a QGridLayout for the plots and add it to the widget. + QGridLayout* layout=new QGridLayout(); + mainWidget.setLayout(layout); + + // 3.1 create a main plotter widget and add it to the layout + JKQtPlotter* plotMain=new JKQtPlotter(&mainWidget); + layout->addWidget(plotMain, 0,0); + JKQTPdatastore* ds=plotMain->getDatastore(); + + // 3.2 create a second and third plotter widget and add them to the + // layout below and at the bottom right of the plotMain. + // Also configure it to use the same datastore as plotMain + JKQtPlotter* plotResid=new JKQtPlotter(false, &mainWidget, ds); + layout->addWidget(plotResid, 1,0); + JKQtPlotter* plotResidHist=new JKQtPlotter(false, &mainWidget, ds); + layout->addWidget(plotResidHist, 1,1); + + // 3.3 synchronize width/x-axis of plotResid to width/x-axis of plotMain + plotResid->get_plotter()->synchronizeToMaster(plotMain->get_plotter(), true, false, true, true); + + // 3.4 synchronize y-axis of width/plotResidHist to y-axis of width/plotResid + plotResidHist->get_plotter()->synchronizeToMaster(plotResid->get_plotter(), false, true, true, true); + + // 3.5 ensure that the plot are printed/exported in whole, when printing in plotMain + plotMain->get_plotter()->set_gridPrinting(true); + plotMain->get_plotter()->addGridPrintingPlotter(0,1,plotResid->get_plotter()); + plotMain->get_plotter()->addGridPrintingPlotter(1,1,plotResidHist->get_plotter()); + + // 3.6 set relative sizes of the plots via the layout (small plots have 1/3 the width and height of the large plot + layout->setRowStretch(0,3); + layout->setRowStretch(1,1); + layout->setColumnStretch(0,3); + layout->setColumnStretch(1,1); + + + + // 4. now we create some (artificial) data: + // - in plotMain we show an exponential curve and and some datapoints + // - in plotResid we show the residuals, i.e. the difference between the curve and the datapoints + // - in plotResidHist we will show a histogram of the residuals (calculated in histogram) + std::random_device rd; + std::mt19937 gen(rd()); + std::normal_distribution<> d(0,0.5); + + std::vector dataX, dataY, dataRY; + std::map histogram; + const int Ndata=60; + for(int n=0; n(Ndata)*static_cast(n)-0.5; + const double y=2.0*(1.0+cos(x)); + const double yd=y+d(gen); + dataX.push_back(x); + dataY.push_back(yd); + dataRY.push_back(y-yd); + // calculate a simple type of histogram in a std:map, bins are 1/4=0.25 wide + const double hbin=std::round((y-yd)*4.0)/4.0; + if (histogram.find(hbin)==histogram.end()) { + histogram[hbin]=0; + } else { + histogram[hbin]++; + } + } + + // 5. and add the data to the datastore + size_t cX=ds->addCopiedColumn(dataX, "dataX"); + size_t cY=ds->addCopiedColumn(dataY, "dataY"); + size_t cRY=ds->addCopiedColumn(dataRY, "residY"); + std::pair cH=ds->addCopiedMap(histogram, "histX", "histY"); + + // 5.1 plot of the data + JKQTPxyLineGraph* graphD=new JKQTPxyLineGraph(plotMain); + graphD->set_xColumn(cX); + graphD->set_yColumn(cY); + graphD->set_drawLine(false); + graphD->set_symbol(JKQTPcross); + graphD->set_symbolSize(10); + graphD->set_title("measurement data"); + plotMain->addGraph(graphD); + + // 5.2 plot of the graph as an interpreted function, set as string "2*(1+cos(x))" + JKQTPxParsedFunctionLineGraph* graphFit=new JKQTPxParsedFunctionLineGraph(plotMain); + graphFit->set_function("2*(1+cos(x))"); + graphFit->set_title("fit"); + plotMain->addGraph(graphFit); + + // 5.3 residuals plot + JKQTPxyLineGraph* graphResid=new JKQTPxyLineGraph(plotResid); + graphResid->set_xColumn(cX); + graphResid->set_yColumn(cRY); + graphResid->set_drawLine(false); + graphResid->set_symbol(JKQTPplus); + graphResid->set_symbolSize(10); + graphResid->set_drawLine(true); + graphResid->set_lineWidth(0.5); + graphResid->set_title("residuals"); + plotResid->addGraph(graphResid); + + // 5.3 residual histogram plot + JKQTPbarHorizontalGraph* graphResidHist=new JKQTPbarHorizontalGraph(plotResidHist); + graphResidHist->set_xColumn(cH.second); + graphResidHist->set_yColumn(cH.first); + graphResidHist->set_title("histogram"); + plotResidHist->addGraph(graphResidHist); + + // 6.1 axis labels, distributed over the several plots + plotMain->get_yAxis()->set_axisLabel("y axis"); + plotResid->get_xAxis()->set_axisLabel("x axis"); + plotResid->get_yAxis()->set_axisLabel("residuals"); + plotResidHist->get_xAxis()->set_axisLabel("frequency"); + // 6.2 switch off the tick labels on the axes that directly face another plot + plotMain->get_xAxis()->set_drawMode1(JKQTPCADMticks); + plotResidHist->get_yAxis()->set_drawMode1(JKQTPCADMticks); + // 6.3 show tick labels on the rhs y-axis of the residual histogram plot + plotResidHist->get_yAxis()->set_drawMode2(JKQTPCADMticksAndLabels); + // 6.4 hide keys in all plots but the main plot + plotResid->get_plotter()->set_showKey(false); + plotResidHist->get_plotter()->set_showKey(false); + // 6.5 hide position label and toolbars in the plots except main plot + plotResid->set_displayToolbar(false); + plotResid->set_displayMousePosition(false); + plotResidHist->set_displayToolbar(false); + plotResidHist->set_displayMousePosition(false); + plotMain->set_toolbarAlwaysOn(true); + + + // 7. scale plots automatically to data + plotResid->zoomToFit(); + plotResidHist->zoomToFit(); + plotMain->zoomToFit(); + + // 8. show plotter and make it a decent size + mainWidget.show(); + mainWidget.move(32,32); + mainWidget.resize(800,600); + + return app.exec(); +} diff --git a/test/test_multiplot/test_multiplot.pro b/test/test_multiplot/test_multiplot.pro new file mode 100644 index 0000000000..c48573fe36 --- /dev/null +++ b/test/test_multiplot/test_multiplot.pro @@ -0,0 +1,22 @@ +# source code for this simple demo +SOURCES = test_multiplot.cpp + +# configure Qt +CONFIG += qt +QT += core gui xml svg +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport + +# output executable name +TARGET = test_multiplot + + +# include JKQtPlotter source code +DEPENDPATH += . ../../lib +INCLUDEPATH += ../../lib +CONFIG (debug, debug|release):LIBS += -L../../lib/debug -ljkqtplotterlib +CONFIG (release):LIBS += -L../../lib/release -ljkqtplotterlib + + +# here you can activate some debug options +#DEFINES += SHOW_JKQTPLOTTER_DEBUG +#DEFINES += JKQTBP_AUTOTIMER diff --git a/test/test_multiplot/test_multiplot_and_lib.pro b/test/test_multiplot/test_multiplot_and_lib.pro new file mode 100644 index 0000000000..83fc904dae --- /dev/null +++ b/test/test_multiplot/test_multiplot_and_lib.pro @@ -0,0 +1,8 @@ +TEMPLATE = subdirs + +SUBDIRS += jkqtplotterlib test_multiplot + +jkqtplotterlib.file = ../../lib/jkqtplotterlib.pro + +test_multiplot.file=$$PWD/test_multiplot.pro +test_multiplot.depends = jkqtplotterlib