From 9dbfd6e173ff727f101631662d8e9792fe355b4d Mon Sep 17 00:00:00 2001 From: jkriege2 Date: Thu, 17 Sep 2020 16:59:57 +0200 Subject: [PATCH] reworked class hierarchy of bar charts --- doc/dox/whatsnew.dox | 3 +- examples/barchart/README.md | 9 + examples/barchart/barchart.cpp | 83 ++-- lib/jkqtplotter.pri | 2 + lib/jkqtplotter/CMakeLists.txt | 2 + lib/jkqtplotter/graphs/jkqtpbarchart.cpp | 471 ++++++------------- lib/jkqtplotter/graphs/jkqtpbarchart.h | 362 +++++++------- lib/jkqtplotter/graphs/jkqtpbarchartbase.cpp | 273 +++++++++++ lib/jkqtplotter/graphs/jkqtpbarchartbase.h | 172 +++++++ screenshots/barchart_hor.png | Bin 0 -> 12715 bytes 10 files changed, 855 insertions(+), 522 deletions(-) create mode 100644 lib/jkqtplotter/graphs/jkqtpbarchartbase.cpp create mode 100644 lib/jkqtplotter/graphs/jkqtpbarchartbase.h create mode 100644 screenshots/barchart_hor.png diff --git a/doc/dox/whatsnew.dox b/doc/dox/whatsnew.dox index 87d576eab1..471c42bbfa 100644 --- a/doc/dox/whatsnew.dox +++ b/doc/dox/whatsnew.dox @@ -25,7 +25,8 @@ Changes, compared to \ref page_whatsnew_V2019_11 "v2019.11" include:
  • removed/breaking change: removed the overlay elements (derived from JKQTPOverlayElement), which were not very well set up and are more confusing than useful.
  • improved/breaking change: geometric objects now use an adaptive drawing algorithm to represent curves (before e.g. ellipses were always separated into a fixed number of line-segments)
  • improved: constructors and access functions for several geometric objects (e.g. more constructors, additional functions to retrieve parameters in diferent forms, iterators for polygons, ...)
  • -
  • improved/breaking change: reworked class hirarchy of parsed function plots and declared several setters as slots.
  • +
  • improved/breaking change: reworked class hierarchy of parsed function plots and declared several setters as slots.
  • +
  • improved/breaking change: reworked class hierarchy of bar charts.
  • bugfixed/improved: aspect ratio handling in JKQTPlotter.
  • new: added geometric plot objects JKQTPGeoArrow to draw arrows (aka lines with added line-end decorators, also extended JKQTPGeoLine, JKQTPGeoInfiniteLine, JKQTPGeoPolyLines to draw line-end decorator (aka arrows)
  • new: all geometric objects can either be drawn as graphic element (i.e. lines are straight line, even on non-linear axes), or as mathematical curve (i.e. on non-linear axes, lines become the appropriate curve representing the linear function, connecting the given start/end-points). The only exceptions are ellipses (and the derived arcs,pies,chords), which are always drawn as mathematical curves
  • diff --git a/examples/barchart/README.md b/examples/barchart/README.md index 145c354810..66149ed444 100644 --- a/examples/barchart/README.md +++ b/examples/barchart/README.md @@ -89,8 +89,17 @@ int main(int argc, char* argv[]) return app.exec(); } ``` + +Note: Here we use the functions `JKQTPBarVerticalGraph::setXColumn()` and `JKQTPBarVerticalGraph::setYColumn()` to set the columns to use for the stack positions (X) and heights (Y). Instead you can also use the semantic version `JKQTPBarGraphBase::setBarPositionColumn()` and `JKQTPBarGraphBase::setBarHeightColumn()`. + The result looks like this: ![barchart](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/barchart.png) + +In order to draw horizontal error bars, you have to use `JKQTPBarHorizontalGraph` instead of `JKQTPBarVerticalGraph`: + +![barchart](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/barchart_hor.png) + + diff --git a/examples/barchart/barchart.cpp b/examples/barchart/barchart.cpp index d6620ce78c..4ab917a1ff 100644 --- a/examples/barchart/barchart.cpp +++ b/examples/barchart/barchart.cpp @@ -10,16 +10,17 @@ #include "jkqtplotter/graphs/jkqtpbarchart.h" #define Ndata 5 -int main(int argc, char* argv[]) -{ - QApplication app(argc, argv); + +template +void doExample() +{ // 1. create a plotter window and get a pointer to the internal datastore (for convenience) - JKQTPlotter plot; - plot.getPlotter()->setUseAntiAliasingForGraphs(true); // nicer (but slower) plotting - plot.getPlotter()->setUseAntiAliasingForSystem(true); // nicer (but slower) plotting - plot.getPlotter()->setUseAntiAliasingForText(true); // nicer (but slower) text rendering - JKQTPDatastore* ds=plot.getDatastore(); + JKQTPlotter* plot=new JKQTPlotter(); + plot->getPlotter()->setUseAntiAliasingForGraphs(true); // nicer (but slower) plotting + plot->getPlotter()->setUseAntiAliasingForSystem(true); // nicer (but slower) plotting + plot->getPlotter()->setUseAntiAliasingForText(true); // nicer (but slower) text rendering + JKQTPDatastore* ds=plot->getDatastore(); // 2. now we create data for three simple barchart QString L[Ndata]={ "cat. A", "cat. C", "cat. B", "cat. D", "other"}; // unsorted category axis @@ -41,24 +42,24 @@ int main(int argc, char* argv[]) size_t columnY3=ds->addCopiedColumn(Y3, Ndata, "y3"); // 4. create graphs in the plot, which plots the dataset X/Y1, X/Y2 and X/Y3: - JKQTPBarVerticalGraph* graph1=new JKQTPBarVerticalGraph(&plot); - graph1->setXColumn(columnX); - graph1->setYColumn(columnY1); + TCHART* graph1=new TCHART(plot); + graph1->setBarPositionColumn(columnX); + graph1->setBarHeightColumn(columnY1); graph1->setTitle(QObject::tr("dataset 1")); - JKQTPBarVerticalGraph* graph2=new JKQTPBarVerticalGraph(&plot); - graph2->setXColumn(columnX); - graph2->setYColumn(columnY2); + TCHART* graph2=new TCHART(plot); + graph2->setBarPositionColumn(columnX); + graph2->setBarHeightColumn(columnY2); graph2->setTitle(QObject::tr("dataset 2")); - JKQTPBarVerticalGraph* graph3=new JKQTPBarVerticalGraph(&plot); - graph3->setXColumn(columnX); - graph3->setYColumn(columnY3); + TCHART* graph3=new TCHART(plot); + graph3->setBarPositionColumn(columnX); + graph3->setBarHeightColumn(columnY3); graph3->setTitle(QObject::tr("dataset 3")); // 5. add the graphs to the plot, so it is actually displayed - plot.addGraph(graph1); - plot.addGraph(graph2); - plot.addGraph(graph3); + plot->addGraph(graph1); + plot->addGraph(graph2); + plot->addGraph(graph3); // 6. now we set the graphs, so they are plotted side-by-side // This function searches all JKQTPBarHorizontalGraph in the current @@ -66,29 +67,45 @@ int main(int argc, char* argv[]) // side-by-side groups graph1->autoscaleBarWidthAndShift(0.75, 1); - // 7. data is grouped into 5 numbere groups (1..5), but we also have string - // labels for these groups (stored in L). In order to display these labels, - // we have to tell the x-Axis to use these special labels: - plot.getXAxis()->addAxisTickLabels(X, L, Ndata); - // also we can rotate the labels a bit (by 45 degree), so they fit better - plot.getXAxis()->setTickLabelAngle(45); - plot.getXAxis()->setTickLabelFontSize(12); + if (dynamic_cast(graph1)!=nullptr) { + // 7. data is grouped into 5 numbere groups (1..5), but we also have string + // labels for these groups (stored in L). In order to display these labels, + // we have to tell the x-Axis to use these special labels: + plot->getXAxis()->addAxisTickLabels(X, L, Ndata); + // also we can rotate the labels a bit (by 45 degree), so they fit better + plot->getXAxis()->setTickLabelAngle(45); + plot->getXAxis()->setTickLabelFontSize(12); + } else { + // 7. data is grouped into 5 numbere groups (1..5), but we also have string + // labels for these groups (stored in L). In order to display these labels, + // we have to tell the x-Axis to use these special labels: + plot->getYAxis()->addAxisTickLabels(X, L, Ndata); + plot->getYAxis()->setTickLabelFontSize(12); + } // 8. finally we move the plot key/legend to the outside, top-right // and lay it out as a single row // NOTE: plot is a descendent of QWidget, which uses an internal object of // type JKQTBasePlotter, which does the actual plotting. // So many properties of the plot are only available in this internal - // object, which you can access by plot.getPlotter(). - plot.getPlotter()->setKeyPosition(JKQTPKeyOutsideTopRight); - plot.getPlotter()->setKeyLayout(JKQTPKeyLayoutOneRow); + // object, which you can access by plot->getPlotter(). + plot->getPlotter()->setKeyPosition(JKQTPKeyOutsideTopRight); + plot->getPlotter()->setKeyLayout(JKQTPKeyLayoutOneRow); // 9 autoscale the plot so the graph is contained - plot.zoomToFit(); + plot->zoomToFit(); // show plotter and make it a decent size - plot.show(); - plot.resize(600,400); + plot->show(); + plot->resize(600,400); +} + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + + doExample(); + doExample(); return app.exec(); } diff --git a/lib/jkqtplotter.pri b/lib/jkqtplotter.pri index 4c25baeb98..1b12d1f8ca 100644 --- a/lib/jkqtplotter.pri +++ b/lib/jkqtplotter.pri @@ -48,6 +48,7 @@ isEmpty(JKQTP_PLOTTER_PRI_INCLUDED) { $$PWD/jkqtplotter/graphs/jkqtpscatter.h \ $$PWD/jkqtplotter/graphs/jkqtprange.h \ $$PWD/jkqtplotter/graphs/jkqtpspecialline.h \ + $$PWD/jkqtplotter/graphs/jkqtpbarchartbase.h \ $$PWD/jkqtplotter/graphs/jkqtpbarchart.h \ $$PWD/jkqtplotter/graphs/jkqtpevaluatedparametriccurve.h \ $$PWD/jkqtplotter/gui/jkqtpcomboboxes.h \ @@ -97,6 +98,7 @@ isEmpty(JKQTP_PLOTTER_PRI_INCLUDED) { $$PWD/jkqtplotter/graphs/jkqtpscatter.cpp \ $$PWD/jkqtplotter/graphs/jkqtprange.cpp \ $$PWD/jkqtplotter/graphs/jkqtpspecialline.cpp \ + $$PWD/jkqtplotter/graphs/jkqtpbarchartbase.cpp \ $$PWD/jkqtplotter/graphs/jkqtpbarchart.cpp \ $$PWD/jkqtplotter/graphs/jkqtpevaluatedparametriccurve.cpp \ $$PWD/jkqtplotter/gui/jkqtpcomboboxes.cpp \ diff --git a/lib/jkqtplotter/CMakeLists.txt b/lib/jkqtplotter/CMakeLists.txt index 91b8ce34eb..43b640c618 100644 --- a/lib/jkqtplotter/CMakeLists.txt +++ b/lib/jkqtplotter/CMakeLists.txt @@ -40,6 +40,7 @@ set(SOURCES_GRAPHS graphs/jkqtpscatter.cpp graphs/jkqtprange.cpp graphs/jkqtpspecialline.cpp + graphs/jkqtpbarchartbase.cpp graphs/jkqtpbarchart.cpp graphs/jkqtpboxplot.cpp graphs/jkqtpboxplotstylingmixins.cpp @@ -115,6 +116,7 @@ set(HEADERS_GRAPHS graphs/jkqtpscatter.h graphs/jkqtprange.h graphs/jkqtpspecialline.h + graphs/jkqtpbarchartbase.h graphs/jkqtpbarchart.h graphs/jkqtpevaluatedparametriccurve.h ) diff --git a/lib/jkqtplotter/graphs/jkqtpbarchart.cpp b/lib/jkqtplotter/graphs/jkqtpbarchart.cpp index 5a5c1813dd..8fd724f676 100644 --- a/lib/jkqtplotter/graphs/jkqtpbarchart.cpp +++ b/lib/jkqtplotter/graphs/jkqtpbarchart.cpp @@ -37,14 +37,9 @@ JKQTPBarVerticalGraph::JKQTPBarVerticalGraph(JKQTBasePlotter* parent): - JKQTPXYGraph(parent) + JKQTPBarGraphBase(parent) { - baseline=0.0; - width=0.9; - shift=0; - initFillStyle(parent, parentPlotStyle); - initLineStyle(parent, parentPlotStyle); } @@ -53,23 +48,6 @@ JKQTPBarVerticalGraph::JKQTPBarVerticalGraph(JKQTPlotter* parent): { } -void JKQTPBarVerticalGraph::drawKeyMarker(JKQTPEnhancedPainter& painter, QRectF& rect) { - painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();}); - QPen p=getLinePenForRects(painter, parent); - QPen np(Qt::NoPen); - QBrush b=getFillBrush(painter, parent); - //int y=rect.top()+rect.height()/2.0; - painter.setPen(p); - painter.setBrush(b); - painter.drawRect(rect); - -} - -QColor JKQTPBarVerticalGraph::getKeyLabelColor() const { - return getFillColor(); -} - - void JKQTPBarVerticalGraph::draw(JKQTPEnhancedPainter& painter) { #ifdef JKQTBP_AUTOTIMER JKQTPAutoOutputTimer jkaaot("JKQTPBarHorizontalGraph::draw"); @@ -158,139 +136,46 @@ void JKQTPBarVerticalGraph::draw(JKQTPEnhancedPainter& painter) { } bool JKQTPBarVerticalGraph::getXMinMax(double& minx, double& maxx, double& smallestGreaterZero) { - bool start=true; - minx=0; - maxx=0; - smallestGreaterZero=0; - - if (parent==nullptr) return false; - - JKQTPDatastore* datastore=parent->getDatastore(); - int imin=0; - int imax=static_cast(qMin(datastore->getRows(static_cast(xColumn)), datastore->getRows(static_cast(yColumn)))); - if (imaxget(static_cast(xColumn),static_cast(i)); - int sr=datastore->getNextLowerIndex(xColumn, i); - int lr=datastore->getNextHigherIndex(xColumn, i); - double delta, deltap, deltam; - - if (sr<0 && lr<0) { // only one x-value - deltam=0.5; - deltap=0.5; - } else if (lr<0) { // the right-most x-value - deltap=deltam=fabs(xv-datastore->get(xColumn,sr))/2.0; - } else if (sr<0) { // the left-most x-value - deltam=deltap=fabs(datastore->get(xColumn,lr)-xv)/2.0; - } else { - deltam=fabs(xv-datastore->get(xColumn,sr))/2.0; - deltap=fabs(datastore->get(xColumn,lr)-xv)/2.0; - } - delta=deltap+deltam; - - if (JKQTPIsOKFloat(xv) && JKQTPIsOKFloat(delta) ) { - - if (start || xv+shift*delta+width*delta/2.0>maxx) maxx=xv+shift*delta+width*delta/2.0; - if (start || xv+shift*delta-width*delta/2.00) { - smallestGreaterZero=baseline; - miny=baseline; - maxy=baseline; - } - - if (parent==nullptr) return false; - - JKQTPDatastore* datastore=parent->getDatastore(); - int imin=0; - int imax=static_cast(qMin(datastore->getRows(static_cast(xColumn)), datastore->getRows(static_cast(yColumn)))); - if (imaxmaxy) maxy=yv; - if (yvget(static_cast(yColumn),static_cast(i)); - if (JKQTPIsOKFloat(yv)) { - if (yv>maxy) maxy=yv; - if (yvgetGraphCount(); i++) { - JKQTPPlotElement* g=parent->getGraph(i); - JKQTPBarVerticalGraph* gb=qobject_cast(g); - if (gb && gb->isHorizontal()==isHorizontal()) { - cntH++; - } - - } - - double widthH=1.0/cntH*maxWidth*shrinkFactor; - double dH=maxWidth/(cntH); - double h=0.1+dH/2.0; - for (size_t i=0; igetGraphCount(); i++) { - JKQTPPlotElement* g=parent->getGraph(i); - JKQTPBarVerticalGraph* gb=qobject_cast(g); - if (gb && gb->isHorizontal()==isHorizontal()) { - if (cntH>1) { - gb->width=widthH; - gb->shift=h-0.5; - h=h+dH; - } else { - gb->width=maxWidth; - gb->shift=0.0; - } - } - - } - } + return xColumn; } -void JKQTPBarVerticalGraph::autoscaleBarWidthAndShiftSeparatedGroups(double groupWidth) { - autoscaleBarWidthAndShift(groupWidth, 1); +int JKQTPBarVerticalGraph::getBarHeightColumn() const +{ + return yColumn; +} + +void JKQTPBarVerticalGraph::setBarPositionColumn(int column) +{ + xColumn=column; +} + +void JKQTPBarVerticalGraph::setBarPositionColumn(size_t column) +{ + xColumn=column; +} + +void JKQTPBarVerticalGraph::setBarHeightColumn(int column) +{ + yColumn=column; +} + +void JKQTPBarVerticalGraph::setBarHeightColumn(size_t column) +{ + yColumn=column; +} + +bool JKQTPBarVerticalGraph::considerForAutoscaling(JKQTPBarGraphBase *other) const +{ + return (dynamic_cast(other)!=nullptr); } @@ -303,7 +188,7 @@ void JKQTPBarVerticalGraph::autoscaleBarWidthAndShiftSeparatedGroups(double grou JKQTPBarHorizontalGraph::JKQTPBarHorizontalGraph(JKQTBasePlotter *parent): - JKQTPBarVerticalGraph(parent) + JKQTPBarGraphBase(parent) { } @@ -314,6 +199,41 @@ JKQTPBarHorizontalGraph::JKQTPBarHorizontalGraph(JKQTPlotter *parent): } +int JKQTPBarHorizontalGraph::getBarPositionColumn() const +{ + return yColumn; +} + +int JKQTPBarHorizontalGraph::getBarHeightColumn() const +{ + return xColumn; +} + +void JKQTPBarHorizontalGraph::setBarPositionColumn(int column) +{ + yColumn=column; +} + +void JKQTPBarHorizontalGraph::setBarPositionColumn(size_t column) +{ + yColumn=column; +} + +void JKQTPBarHorizontalGraph::setBarHeightColumn(int column) +{ + xColumn=column; +} + +void JKQTPBarHorizontalGraph::setBarHeightColumn(size_t column) +{ + xColumn=column; +} + +bool JKQTPBarHorizontalGraph::considerForAutoscaling(JKQTPBarGraphBase *other) const +{ + return (dynamic_cast(other)!=nullptr); +} + void JKQTPBarHorizontalGraph::draw(JKQTPEnhancedPainter& painter) { #ifdef JKQTBP_AUTOTIMER JKQTPAutoOutputTimer jkaaot("JKQTPBarVerticalGraph::draw"); @@ -401,180 +321,18 @@ void JKQTPBarHorizontalGraph::draw(JKQTPEnhancedPainter& painter) { } bool JKQTPBarHorizontalGraph::getXMinMax(double& minx, double& maxx, double& smallestGreaterZero) { - minx=0; - maxx=0; - smallestGreaterZero=0; - if (baseline>0) { - smallestGreaterZero=baseline; - minx=baseline; - maxx=baseline; - } - - if (parent==nullptr) return false; - - JKQTPDatastore* datastore=parent->getDatastore(); - int imin=0; - int imax=static_cast(qMin(datastore->getRows(static_cast(xColumn)), datastore->getRows(static_cast(yColumn)))); - if (imaxmaxx) maxx=xv; - if (xvget(static_cast(xColumn),static_cast(i)); - if (JKQTPIsOKFloat(xv)) { - if (xv>maxx) maxx=xv; - if (xvgetDatastore(); - int imin=0; - int imax=static_cast(qMin(datastore->getRows(static_cast(xColumn)), datastore->getRows(static_cast(yColumn)))); - if (imaxget(static_cast(yColumn),static_cast(i)); - double delta, deltap, deltam; - int sr=datastore->getNextLowerIndex(yColumn, i); - int lr=datastore->getNextHigherIndex(yColumn, i); - - if (sr<0 && lr<0) { // only one y-value - deltam=0.5; - deltap=0.5; - } else if (lr<0) { // the right-most y-value - deltap=deltam=fabs(yv-datastore->get(yColumn,sr))/2.0; - } else if (sr<0) { // the left-most y-value - deltam=deltap=fabs(datastore->get(yColumn,lr)-yv)/2.0; - } else { - deltam=fabs(yv-datastore->get(yColumn,sr))/2.0; - deltap=fabs(datastore->get(yColumn,lr)-yv)/2.0; - } - delta=deltap+deltam; - if (JKQTPIsOKFloat(yv) && JKQTPIsOKFloat(delta) ) { - - if (start || yv+shift*delta+width*delta/2.0>maxy) maxy=yv+shift*delta+width*delta/2.0; - if (start || yv+shift*delta-width*delta/2.0getCurrentPlotterStyle().graphFillColorDerivationMode, c)); - c.setAlphaF(0.5); - setHighlightingLineColor(c); -} - -void JKQTPBarVerticalGraph::setShift(double __value) -{ - this->shift = __value; -} - -double JKQTPBarVerticalGraph::getShift() const -{ - return this->shift; -} - -void JKQTPBarVerticalGraph::setWidth(double __value) -{ - this->width = __value; -} - -double JKQTPBarVerticalGraph::getWidth() const -{ - return this->width; -} - -void JKQTPBarVerticalGraph::setBaseline(double __value) -{ - this->baseline = __value; -} - -double JKQTPBarVerticalGraph::getBaseline() const -{ - return this->baseline; -} - -void JKQTPBarVerticalGraph::setFillColor_and_darkenedColor(QColor fill, int colorDarker) -{ - setFillColor(fill); - setLineColor(fill.darker(colorDarker)); -} - JKQTPBarHorizontalErrorGraph::JKQTPBarHorizontalErrorGraph(JKQTBasePlotter *parent): JKQTPBarHorizontalGraph(parent) { @@ -641,6 +399,38 @@ void JKQTPBarHorizontalErrorGraph::drawErrorsAfter(JKQTPEnhancedPainter &painter else plotErrorIndicators(painter, parent, this, xColumn, yColumn, 0.0, shift, &sortedIndices); } + + +int JKQTPBarHorizontalErrorGraph::getBarErrorColumn() const +{ + return getXErrorColumn(); +} + +int JKQTPBarHorizontalErrorGraph::getBarLowerErrorColumn() const +{ + return getXErrorColumnLower(); +} + +void JKQTPBarHorizontalErrorGraph::setBarErrorColumn(int column) +{ + setXErrorColumn(column); +} + +void JKQTPBarHorizontalErrorGraph::setBarErrorColumn(size_t column) +{ + setXErrorColumn(column); +} + +void JKQTPBarHorizontalErrorGraph::setBarLowerErrorColumn(int column) +{ + setXErrorColumnLower(column); +} + +void JKQTPBarHorizontalErrorGraph::setBarLowerErrorColumn(size_t column) +{ + setXErrorColumnLower(column); +} + JKQTPBarVerticalErrorGraph::JKQTPBarVerticalErrorGraph(JKQTBasePlotter *parent): JKQTPBarVerticalGraph(parent) { @@ -745,6 +535,36 @@ bool JKQTPBarVerticalErrorGraph::getYMinMax(double &miny, double &maxy, double & } } +int JKQTPBarVerticalErrorGraph::getBarErrorColumn() const +{ + return getYErrorColumn(); +} + +int JKQTPBarVerticalErrorGraph::getBarLowerErrorColumn() const +{ + return getYErrorColumnLower(); +} + +void JKQTPBarVerticalErrorGraph::setBarErrorColumn(int column) +{ + setYErrorColumn(column); +} + +void JKQTPBarVerticalErrorGraph::setBarErrorColumn(size_t column) +{ + setYErrorColumn(column); +} + +void JKQTPBarVerticalErrorGraph::setBarLowerErrorColumn(int column) +{ + setYErrorColumnLower(column); +} + +void JKQTPBarVerticalErrorGraph::setBarLowerErrorColumn(size_t column) +{ + setYErrorColumnLower(column); +} + void JKQTPBarVerticalErrorGraph::drawErrorsAfter(JKQTPEnhancedPainter &painter) { //plotErrorIndicators(painter, parent, this, xColumn, yColumn, shift, 0.0); @@ -752,6 +572,17 @@ void JKQTPBarVerticalErrorGraph::drawErrorsAfter(JKQTPEnhancedPainter &painter) else plotErrorIndicators(painter, parent, this, xColumn, yColumn, shift, 0, &sortedIndices); } + + + + + + + + + + + JKQTPBarVerticalStackableGraph::JKQTPBarVerticalStackableGraph(JKQTBasePlotter *parent): JKQTPBarVerticalGraph(parent), stackParent(nullptr) { @@ -779,7 +610,7 @@ double JKQTPBarVerticalStackableGraph::getParentStackedMax(int index) const if (stackParent) { return stackParent->getStackedMax(index); } else { - return 0.0; + return baseline; } } @@ -793,7 +624,10 @@ const JKQTPBarVerticalStackableGraph *JKQTPBarVerticalStackableGraph::getStackPa return stackParent; } - +JKQTPBarVerticalStackableGraph *JKQTPBarVerticalStackableGraph::getStackParent() +{ + return stackParent; +} double JKQTPBarVerticalStackableGraph::getStackedMax(int index) const { @@ -836,6 +670,11 @@ const JKQTPBarHorizontalStackableGraph *JKQTPBarHorizontalStackableGraph::getSta return stackParent; } +JKQTPBarHorizontalStackableGraph *JKQTPBarHorizontalStackableGraph::getStackParent() +{ + return stackParent; +} + double JKQTPBarHorizontalStackableGraph::getStackedMax(int index) const { @@ -856,7 +695,7 @@ double JKQTPBarHorizontalStackableGraph::getParentStackedMax(int index) const if (stackParent) { return stackParent->getStackedMax(index); } else { - return 0.0; + return baseline; } } diff --git a/lib/jkqtplotter/graphs/jkqtpbarchart.h b/lib/jkqtplotter/graphs/jkqtpbarchart.h index e1eac8946e..574e02c4f7 100644 --- a/lib/jkqtplotter/graphs/jkqtpbarchart.h +++ b/lib/jkqtplotter/graphs/jkqtpbarchart.h @@ -26,12 +26,13 @@ #include "jkqtplotter/jkqtpgraphsbase.h" #include "jkqtplotter/jkqtpgraphsbaseerrors.h" #include "jkqtplotter/jkqtpgraphsbasestylingmixins.h" +#include "jkqtplotter/graphs/jkqtpbarchartbase.h" #ifndef jkqtpgraphsbarchart_H #define jkqtpgraphsbarchart_H -/*! \brief This implements a bar graph with bars starting at \f$ y=0 \f$ to \f$ y=f(x) \f$ +/*! \brief This implements a bar graph with bars starting at \f$ yoverride \f$ to \f$ y=f(x) \f$ \ingroup jkqtplotter_barssticks This class plots a bargraph. This image explains the parameters: @@ -43,7 +44,7 @@ to plot multiple bars for every x-value, by having on JKQTPSpecialLineHorizontalGraph object per set of bars that belong together. For example for three bars per x-value one would set: \verbatim - width=0.3 + widthoverride.3 shift=-0.3 / 0 / +0.3 \endverbatim This results in a bargraph, as shown here: @@ -56,7 +57,7 @@ \see JKQTPBarHorizontalGraph, \ref JKQTPlotterBarcharts, jkqtpstatAddHHistogram1D(), jkqtpstatAddHHistogram1DAutoranged() */ -class JKQTPLOTTER_LIB_EXPORT JKQTPBarVerticalGraph: public JKQTPXYGraph, public JKQTPGraphLineStyleMixin, public JKQTPGraphFillStyleMixin { +class JKQTPLOTTER_LIB_EXPORT JKQTPBarVerticalGraph: public JKQTPBarGraphBase { Q_OBJECT public: /** \brief class constructor */ @@ -66,10 +67,6 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBarVerticalGraph: public JKQTPXYGraph, public /** \brief plots the graph to the plotter object specified as parent */ virtual void draw(JKQTPEnhancedPainter& painter) override; - /** \brief plots a key marker inside the specified rectangle \a rect */ - virtual void drawKeyMarker(JKQTPEnhancedPainter& painter, QRectF& rect) override; - /** \brief returns the color to be used for the key label */ - virtual QColor getKeyLabelColor() const override; /** \brief get the maximum and minimum x-value of the graph * @@ -81,132 +78,35 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBarVerticalGraph: public JKQTPXYGraph, public * The result is given in the two parameters which are call-by-reference parameters! */ virtual bool getYMinMax(double& miny, double& maxy, double& smallestGreaterZero) override; + + /** \brief returns xColumn or yColumn, whichever is used for the position of the bars (depending on whether the barchart is vertical or horizontal \see getBarHeightColumn(), xColumn, yColumn */ + virtual int getBarPositionColumn() const override; + + /** \brief returns xColumn or yColumn, whichever is used for the height of the bars (depending on whether the barchart is vertical or horizontal \see getBarPositionColumn(), xColumn, yColumn */ + virtual int getBarHeightColumn() const override; - /** \brief finds all bar charts of the same orientation and determines width and shift, so they stand side by side - * - * \param maxWidth the maximum (relative) width, that all bars will span of the (doubled) inter-bar distance - * \param shrinkFactor factor, by which the bar are shrinked compared to the available space - * - * \note This function will scale ALL graphs of the parent plot, which were derived from JKQTPBarHorizontalGraph, that match in orientation (as returned by isHorizontal() ). - */ - virtual void autoscaleBarWidthAndShift(double maxWidth=0.9, double shrinkFactor=0.8); + public slots: + /** \brief returns xColumn or yColumn, whichever is used for the position of the bars (depending on whether the barchart is vertical or horizontal \see getBarHeightColumn(), xColumn, yColumn */ + virtual void setBarPositionColumn(int column) override; + + /** \brief returns xColumn or yColumn, whichever is used for the position of the bars (depending on whether the barchart is vertical or horizontal \see getBarHeightColumn(), xColumn, yColumn */ + virtual void setBarPositionColumn(size_t column) override; + + /** \brief returns xColumn or yColumn, whichever is used for the height of the bars (depending on whether the barchart is vertical or horizontal \see getBarPositionColumn(), xColumn, yColumn */ + virtual void setBarHeightColumn(int column) override; + + /** \brief returns xColumn or yColumn, whichever is used for the height of the bars (depending on whether the barchart is vertical or horizontal \see getBarPositionColumn(), xColumn, yColumn */ + virtual void setBarHeightColumn(size_t column) override; - /** \brief equivalent to \c autoscaleBarWidthAndShift(groupWidth,1); - */ - void autoscaleBarWidthAndShiftSeparatedGroups(double groupWidth=0.75); - - /** \brief retruns \c true, if the bars are horizontal (JKQTPBarHorizontalGraph) and \c false if they are vertical (JKQTPBarVerticalGraph) */ - virtual bool isHorizontal() const; - - /** \brief set outline and fill color at the same time - * \see setFillColor_and_darkenedColor() - */ - virtual void setColor(QColor c); - - /*! \copydoc shift */ - void setShift(double __value); - /*! \copydoc shift */ - double getShift() const; - /*! \copydoc width */ - void setWidth(double __value); - /*! \copydoc width */ - double getWidth() const; - /*! \copydoc baseline */ - void setBaseline(double __value); - /*! \copydoc baseline */ - double getBaseline() const; - /** \brief sets the fill color and the color together, where fillColor is set to \a fill and the line-color is set to \c fill.darker(colorDarker) - * \see setColor() - */ - void setFillColor_and_darkenedColor(QColor fill, int colorDarker=200); protected: - /** \brief the width of the bargraphs, relative to the distance between the current and the next x-value - * - * See the following graphic to understand this concept: - * \image html bargraph_basics.png - */ - double width; - /** \brief the shift of the bargraphs, relative to the distance between the current and the next x-value - * - * See the following graphic to understand this concept: - * \image html bargraph_basics.png - */ - double shift; - - /** \brief baseline of the plot (NOTE: 0 is interpreted as until plot border in log-mode!!!) - */ - double baseline; - - - /** \brief used to generate stacked plots: returns the upper boundary of this plot in a stack, for the index-th datapoint - * - * \note This function returns \a baseline in this implementation. It is implemented in the derived classes JKQTPBarVerticalStackableGraph - * and JKQTPBarHorizontalStackableGraph. The function is placed here, so the plotting does not have to be reimplemented in the - * derived classes that allow for stacking, but can be implemented once centrally. - */ - virtual double getStackedMax(int index) const; - - /** \brief calls getStackedMax() on the stack parent (if available) - * - * \note This function returns \c 0.0 in this implementation. It is implemented in the derived classes JKQTPBarVerticalStackableGraph - * and JKQTPBarHorizontalStackableGraph. The function is placed here, so the plotting does not have to be reimplemented in the - * derived classes that allow for stacking, but can be implemented once centrally. - */ - virtual double getParentStackedMax(int index) const; - - /** \brief returns \c true, if a stack parent is set (if available) - * - * \note This function returns \c false in this implementation. It is implemented in the derived classes JKQTPBarVerticalStackableGraph - * and JKQTPBarHorizontalStackableGraph. The function is placed here, so the plotting does not have to be reimplemented in the - * derived classes that allow for stacking, but can be implemented once centrally. - */ - virtual bool hasStackParent() const; + + /** \brief this function is used by autoscaleBarWidthAndShift() to determine whether a given graph shall be taken into account when autoscaling. + * Typically this returns \c true for all JKQTPBarGraphBase-derved objects with the same orientation (horizontal or vertical) */ + virtual bool considerForAutoscaling( JKQTPBarGraphBase* other) const override; }; - - -/*! \brief This implements a bar graph with bars starting at \f$ y=0 \f$ to \f$ y=f(x) \f$ - * Optionally several graphs of this type may be stacked on top of each other - * \ingroup jkqtplotter_barssticks - * - * Draw stacked barcharts by connecting several plots by calling \c setStackedParent(belowPlot) for each plot - * \image html JKQTPBarVerticalGraphStacked.png - * - * \see JKQTPBarVerticalGraph, \ref JKQTPlotterStackedBarChart - */ -class JKQTPLOTTER_LIB_EXPORT JKQTPBarVerticalStackableGraph: public JKQTPBarVerticalGraph { - Q_OBJECT - public: - /** \brief class constructor */ - JKQTPBarVerticalStackableGraph(JKQTBasePlotter* parent=nullptr); - /** \brief class constructor */ - JKQTPBarVerticalStackableGraph(JKQTPlotter* parent); - /** \brief stacks this barchart upon the given \a parentGraph */ - void stackUpon(JKQTPBarVerticalStackableGraph* parentGraph); - /** \brief unstacks this graph (i.e. deletes the parent graph in the stack) */ - void dontStackUpon(); - /** \brief returns the stack parent graph, or \c nullptr */ - const JKQTPBarVerticalStackableGraph* getStackParent() const; - - protected: - - /** \brief if set (!=nullptr), the current plot is drawn stacked onto this plot - * - * draw stacked barcharts by connecting several plots by calling \c setStackedParent(belowPlot) for each plot - */ - JKQTPBarVerticalStackableGraph* stackParent; - - /** \brief used to generate stacked plots: returns the upper boundary of this plot in a stack, for the index-th datapoint */ - virtual double getStackedMax(int index) const override; - /** \brief calls getStackedMax() on the stack parent (if available), or \c 0.0 */ - virtual double getParentStackedMax(int index) const override; - - /** \brief returns \c true, if a stack parent is set (if available) */ - virtual bool hasStackParent() const override; -}; - -/*! \brief This implements a bar graph with bars starting at \f$ y=0 \f$ to \f$ y=f(x) \f$ +/*! \brief This implements a bar graph with bars starting at \f$ yoverride \f$ to \f$ y=f(x) \f$ * and error indicator * \ingroup jkqtplotter_barssticks * @@ -231,6 +131,22 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBarVerticalErrorGraph: public JKQTPBarVertical */ virtual bool getYMinMax(double& miny, double& maxy, double& smallestGreaterZero) override; + /** \brief returns the column that contains the bar height errors */ + int getBarErrorColumn() const; + /** \brief returns the column that contains the lower bar height errors */ + int getBarLowerErrorColumn() const; + public slots: + /** \brief sets the column that contains the bar height errors */ + void setBarErrorColumn(int column) ; + + /** \brief sets the column that contains the bar height errors */ + void setBarErrorColumn(size_t column) ; + /** \brief sets the column that contains the bar height errors */ + void setBarLowerErrorColumn(int column) ; + + /** \brief sets the column that contains the bar height errors */ + void setBarLowerErrorColumn(size_t column) ; + protected: /** \brief this function is used to plot error inidcators before plotting the graphs. */ virtual void drawErrorsAfter(JKQTPEnhancedPainter& painter) override; @@ -239,7 +155,8 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBarVerticalErrorGraph: public JKQTPBarVertical -/*! \brief This implements a bar graph with bars starting at \f$ x=0 \f$ to \f$ x=f(y) \f$ + +/*! \brief This implements a bar graph with bars starting at \f$ xoverride \f$ to \f$ x=f(y) \f$ \ingroup jkqtplotter_barssticks This works much the same as JKQTPBarHorizontalGraph. Here is an example output: @@ -249,7 +166,7 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBarVerticalErrorGraph: public JKQTPBarVertical \see \ref JKQTPlotterBarcharts, jkqtpstatAddVHistogram1D(), jkqtpstatAddVHistogram1DAutoranged() */ -class JKQTPLOTTER_LIB_EXPORT JKQTPBarHorizontalGraph: public JKQTPBarVerticalGraph { +class JKQTPLOTTER_LIB_EXPORT JKQTPBarHorizontalGraph: public JKQTPBarGraphBase { Q_OBJECT public: /** \brief class constructor */ @@ -271,54 +188,34 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBarHorizontalGraph: public JKQTPBarVerticalGra */ virtual bool getYMinMax(double& miny, double& maxy, double& smallestGreaterZero) override; - virtual bool isHorizontal() const override; + /** \brief returns xColumn or yColumn, whichever is used for the position of the bars (depending on whether the barchart is vertical or horizontal \see getBarHeightColumn(), xColumn, yColumn */ + virtual int getBarPositionColumn() const override; + /** \brief returns xColumn or yColumn, whichever is used for the height of the bars (depending on whether the barchart is vertical or horizontal \see getBarPositionColumn(), xColumn, yColumn */ + virtual int getBarHeightColumn() const override; + public slots: + + /** \brief sets xColumn or yColumn, whichever is used for the position of the bars (depending on whether the barchart is vertical or horizontal \see getBarHeightColumn(), xColumn, yColumn */ + virtual void setBarPositionColumn(int column) override; + + /** \brief sets xColumn or yColumn, whichever is used for the position of the bars (depending on whether the barchart is vertical or horizontal \see getBarHeightColumn(), xColumn, yColumn */ + virtual void setBarPositionColumn(size_t column) override; + + /** \brief sets xColumn or yColumn, whichever is used for the height of the bars (depending on whether the barchart is vertical or horizontal \see getBarPositionColumn(), xColumn, yColumn */ + virtual void setBarHeightColumn(int column) override; + + /** \brief sets xColumn or yColumn, whichever is used for the height of the bars (depending on whether the barchart is vertical or horizontal \see getBarPositionColumn(), xColumn, yColumn */ + virtual void setBarHeightColumn(size_t column) override; + protected: + + /** \brief this function is used by autoscaleBarWidthAndShift() to determine whether a given graph shall be taken into account when autoscaling. + * Typically this returns \c true for all JKQTPBarGraphBase-derved objects with the same orientation (horizontal or vertical) */ + virtual bool considerForAutoscaling( JKQTPBarGraphBase* other) const override; }; -/*! \brief This implements a bar graph with bars starting at \f$ y=0 \f$ to \f$ y=f(x) \f$ - * Optionally several graphs of this type may be stacked on top of each other - * \ingroup jkqtplotter_barssticks - * - * Draw stacked barcharts by connecting several plots by calling \c setStackedParent(belowPlot) for each plot - * \image html JKQTPBarHorizontalGraphStacked.png - * - * - * \see JKQTPBarHorizontalGraph, \ref JKQTPlotterStackedBarChart - */ -class JKQTPLOTTER_LIB_EXPORT JKQTPBarHorizontalStackableGraph: public JKQTPBarHorizontalGraph { - Q_OBJECT - public: - /** \brief class constructor */ - JKQTPBarHorizontalStackableGraph(JKQTBasePlotter* parent=nullptr); - /** \brief class constructor */ - JKQTPBarHorizontalStackableGraph(JKQTPlotter* parent); - /** \brief stacks this barchart upon the given \a parentGraph */ - void stackUpon(JKQTPBarHorizontalStackableGraph* parentGraph); - /** \brief unstacks this graph (i.e. deletes the parent graph in the stack) */ - void dontStackUpon(); - /** \brief returns the stack parent graph, or \c nullptr */ - const JKQTPBarHorizontalStackableGraph* getStackParent() const; - - protected: - - /** \brief if set (!=nullptr), the current plot is drawn stacked onto this plot - * - * draw stacked barcharts by connecting several plots by calling \c setStackedParent(belowPlot) for each plot - */ - JKQTPBarHorizontalStackableGraph* stackParent; - - /** \brief used to generate stacked plots: returns the upper boundary of this plot in a stack, for the index-th datapoint */ - virtual double getStackedMax(int index) const override; - /** \brief calls getStackedMax() on the stack parent (if available), or \c 0.0 */ - virtual double getParentStackedMax(int index) const override; - - /** \brief returns \c true, if a stack parent is set (if available) */ - virtual bool hasStackParent() const override; -}; - -/*! \brief This implements a bar graph with bars starting at \f$ x=0 \f$ to \f$ x=f(y) \f$ +/*! \brief This implements a bar graph with bars starting at \f$ xoverride \f$ to \f$ x=f(y) \f$ * and error indicator * \ingroup jkqtplotter_barssticks * @@ -344,6 +241,22 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBarHorizontalErrorGraph: public JKQTPBarHorizo */ virtual bool getXMinMax(double& minx, double& maxx, double& smallestGreaterZero) override; + + /** \brief returns the column that contains the bar height errors */ + int getBarErrorColumn() const; + /** \brief returns the column that contains the lower bar height errors */ + int getBarLowerErrorColumn() const; + public slots: + /** \brief sets the column that contains the bar height errors */ + void setBarErrorColumn(int column) ; + + /** \brief sets the column that contains the bar height errors */ + void setBarErrorColumn(size_t column) ; + /** \brief sets the column that contains the bar height errors */ + void setBarLowerErrorColumn(int column) ; + + /** \brief sets the column that contains the bar height errors */ + void setBarLowerErrorColumn(size_t column) ; protected: /** \brief this function is used to plot error inidcators before plotting the graphs. */ virtual void drawErrorsAfter(JKQTPEnhancedPainter& painter) override; @@ -355,4 +268,109 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPBarHorizontalErrorGraph: public JKQTPBarHorizo + + + + + + + +/*! \brief This implements a bar graph with bars starting at \f$ yoverride \f$ to \f$ y=f(x) \f$ + * Optionally several graphs of this type may be stacked on top of each other + * \ingroup jkqtplotter_barssticks + * + * Draw stacked barcharts by connecting several plots by calling \c setStackedParent(belowPlot) for each plot + * \image html JKQTPBarVerticalGraphStacked.png + * + * \see JKQTPBarVerticalGraph, \ref JKQTPlotterStackedBarChart + */ +class JKQTPLOTTER_LIB_EXPORT JKQTPBarVerticalStackableGraph: public JKQTPBarVerticalGraph { + Q_OBJECT + public: + /** \brief class constructor */ + JKQTPBarVerticalStackableGraph(JKQTBasePlotter* parent=nullptr); + /** \brief class constructor */ + JKQTPBarVerticalStackableGraph(JKQTPlotter* parent); + /** \brief stacks this barchart upon the given \a parentGraph */ + void stackUpon(JKQTPBarVerticalStackableGraph* parentGraph); + /** \brief unstacks this graph (i.e. deletes the parent graph in the stack) */ + void dontStackUpon(); + /** \brief returns the stack parent graph, or \c nullptr */ + const JKQTPBarVerticalStackableGraph* getStackParent() const; + /** \brief returns the stack parent graph, or \c nullptr */ + JKQTPBarVerticalStackableGraph* getStackParent(); + + protected: + + /** \brief if set (!=nullptr), the current plot is drawn stacked onto this plot + * + * draw stacked barcharts by connecting several plots by calling \c setStackedParent(belowPlot) for each plot + */ + JKQTPBarVerticalStackableGraph* stackParent; + + /** \brief used to generate stacked plots: returns the upper boundary of the parent plot in a stack, for the index-th datapoint */ + double getParentStackedMax(int index) const ; + + + /** \brief returns \c true, if a stack parent is set (if available) */ + bool hasStackParent() const ; + /** \brief used to generate stacked plots: returns the upper boundary of this plot in a stack, for the index-th datapoint */ + double getStackedMax(int index) const; +}; + + + + + + +/*! \brief This implements a bar graph with bars starting at \f$ yoverride \f$ to \f$ y=f(x) \f$ + * Optionally several graphs of this type may be stacked on top of each other + * \ingroup jkqtplotter_barssticks + * + * Draw stacked barcharts by connecting several plots by calling \c setStackedParent(belowPlot) for each plot + * \image html JKQTPBarHorizontalGraphStacked.png + * + * + * \see JKQTPBarHorizontalGraph, \ref JKQTPlotterStackedBarChart + */ +class JKQTPLOTTER_LIB_EXPORT JKQTPBarHorizontalStackableGraph: public JKQTPBarHorizontalGraph { + Q_OBJECT + public: + /** \brief class constructor */ + JKQTPBarHorizontalStackableGraph(JKQTBasePlotter* parent=nullptr); + /** \brief class constructor */ + JKQTPBarHorizontalStackableGraph(JKQTPlotter* parent); + /** \brief stacks this barchart upon the given \a parentGraph */ + void stackUpon(JKQTPBarHorizontalStackableGraph* parentGraph); + /** \brief unstacks this graph (i.e. deletes the parent graph in the stack) */ + void dontStackUpon(); + /** \brief returns the stack parent graph, or \c nullptr */ + const JKQTPBarHorizontalStackableGraph* getStackParent() const; + /** \brief returns the stack parent graph, or \c nullptr */ + JKQTPBarHorizontalStackableGraph* getStackParent() ; + + protected: + + /** \brief if set (!=nullptr), the current plot is drawn stacked onto this plot + * + * draw stacked barcharts by connecting several plots by calling \c setStackedParent(belowPlot) for each plot + */ + JKQTPBarHorizontalStackableGraph* stackParent; + + /** \brief used to generate stacked plots: returns the upper boundary of the parent plot in a stack, for the index-th datapoint */ + virtual double getParentStackedMax(int index) const override; + + /** \brief returns \c true, if a stack parent is set (if available) */ + virtual bool hasStackParent() const override; + /** \brief used to generate stacked plots: returns the upper boundary of this plot in a stack, for the index-th datapoint */ + double getStackedMax(int index) const; +}; + + + + + + + + #endif // jkqtpgraphsbarchart_H diff --git a/lib/jkqtplotter/graphs/jkqtpbarchartbase.cpp b/lib/jkqtplotter/graphs/jkqtpbarchartbase.cpp new file mode 100644 index 0000000000..d7d95900f6 --- /dev/null +++ b/lib/jkqtplotter/graphs/jkqtpbarchartbase.cpp @@ -0,0 +1,273 @@ +/* + Copyright (c) 2008-2020 Jan W. Krieger () + + + + This software is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License (LGPL) as published by + the Free Software Foundation, either version 2.1 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 Lesser General Public License (LGPL) for more details. + + You should have received a copy of the GNU Lesser General Public License (LGPL) + along with this program. If not, see . +*/ + + + +#include "jkqtplotter/graphs/jkqtpbarchartbase.h" +#include "jkqtplotter/jkqtpbaseplotter.h" +#include +#include +#include +#include "jkqtplotter/jkqtptools.h" +#include "jkqtplotter/graphs/jkqtpimage.h" +#include "jkqtplotter/jkqtpbaseelements.h" +#include "jkqtplotter/jkqtplotter.h" + +#define SmallestGreaterZeroCompare_xvsgz() if ((xvsgz>10.0*DBL_MIN)&&((smallestGreaterZero<10.0*DBL_MIN) || (xvsgzgetPlotter()) +{ +} + +void JKQTPBarGraphBase::drawKeyMarker(JKQTPEnhancedPainter& painter, QRectF& rect) { + painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();}); + QPen p=getLinePenForRects(painter, parent); + QPen np(Qt::NoPen); + QBrush b=getFillBrush(painter, parent); + //int y=rect.top()+rect.height()/2.0; + painter.setPen(p); + painter.setBrush(b); + painter.drawRect(rect); + +} + +QColor JKQTPBarGraphBase::getKeyLabelColor() const { + return getFillColor(); +} + + +void JKQTPBarGraphBase::autoscaleBarWidthAndShift(double maxWidth, double shrinkFactor) +{ + if (parent) { + double cntH=0; + for (size_t i=0; igetGraphCount(); i++) { + JKQTPPlotElement* g=parent->getGraph(i); + JKQTPBarGraphBase* gb=qobject_cast(g); + if (gb && considerForAutoscaling(gb)) { + cntH++; + } + + } + + double widthH=1.0/cntH*maxWidth*shrinkFactor; + double dH=maxWidth/(cntH); + double h=0.1+dH/2.0; + for (size_t i=0; igetGraphCount(); i++) { + JKQTPPlotElement* g=parent->getGraph(i); + JKQTPBarGraphBase* gb=qobject_cast(g); + if (gb && considerForAutoscaling(gb)) { + if (cntH>1) { + gb->width=widthH; + gb->shift=h-0.5; + h=h+dH; + } else { + gb->width=maxWidth; + gb->shift=0.0; + } + } + + } + } +} + +void JKQTPBarGraphBase::autoscaleBarWidthAndShiftSeparatedGroups(double groupWidth) { + autoscaleBarWidthAndShift(groupWidth, 1); +} + + + +void JKQTPBarGraphBase::setColor(QColor c) +{ + setLineColor(c); + setFillColor(JKQTPGetDerivedColor(parent->getCurrentPlotterStyle().graphFillColorDerivationMode, c)); + c.setAlphaF(0.5); + setHighlightingLineColor(c); +} + +void JKQTPBarGraphBase::setShift(double __value) +{ + this->shift = __value; +} + +double JKQTPBarGraphBase::getShift() const +{ + return this->shift; +} + +void JKQTPBarGraphBase::setWidth(double __value) +{ + this->width = __value; +} + +double JKQTPBarGraphBase::getWidth() const +{ + return this->width; +} + +void JKQTPBarGraphBase::setBaseline(double __value) +{ + this->baseline = __value; +} + +double JKQTPBarGraphBase::getBaseline() const +{ + return this->baseline; +} + +void JKQTPBarGraphBase::setFillColor_and_darkenedColor(QColor fill, int colorDarker) +{ + setFillColor(fill); + setLineColor(fill.darker(colorDarker)); +} + +double JKQTPBarGraphBase::getParentStackedMax(int /*index*/) const +{ + return baseline; +} + +bool JKQTPBarGraphBase::hasStackParent() const +{ + return false; +} + +bool JKQTPBarGraphBase::getValuesMinMax(double &mmin, double &mmax, double &smallestGreaterZero) +{ + mmin=0; + mmax=0; + smallestGreaterZero=0; + if (baseline>0) { + smallestGreaterZero=baseline; + mmin=baseline; + mmax=baseline; + } + + if (getBarPositionColumn()<0 || getBarHeightColumn()<0) return false; + + const size_t poscol=static_cast(getBarPositionColumn()); + const size_t datacol=static_cast(getBarHeightColumn()); + + if (parent==nullptr) return false; + + JKQTPDatastore* datastore=parent->getDatastore(); + int imin=0; + int imax=static_cast(qMin(datastore->getRows(poscol), datastore->getRows(datacol))); + if (imaxmmax) mmax=yv; + if (yvget(datacol,static_cast(i)); + if (JKQTPIsOKFloat(yv)) { + if (yv>mmax) mmax=yv; + if (yv(getBarPositionColumn()); + const size_t datacol=static_cast(getBarHeightColumn()); + + if (parent==nullptr) return false; + + JKQTPDatastore* datastore=parent->getDatastore(); + int imin=0; + int imax=static_cast(qMin(datastore->getRows(poscol), datastore->getRows(datacol))); + if (imaxget(poscol,static_cast(i)); + int sr=datastore->getNextLowerIndex(poscol, i); + int lr=datastore->getNextHigherIndex(poscol, i); + double delta, deltap, deltam; + + if (sr<0 && lr<0) { // only one x-value + deltam=0.5; + deltap=0.5; + } else if (lr<0) { // the right-most x-value + deltap=deltam=fabs(xv-datastore->get(poscol,sr))/2.0; + } else if (sr<0) { // the left-most x-value + deltam=deltap=fabs(datastore->get(poscol,lr)-xv)/2.0; + } else { + deltam=fabs(xv-datastore->get(poscol,sr))/2.0; + deltap=fabs(datastore->get(poscol,lr)-xv)/2.0; + } + delta=deltap+deltam; + + if (JKQTPIsOKFloat(xv) && JKQTPIsOKFloat(delta) ) { + + if (start || xv+shift*delta+width*delta/2.0>mmax) mmax=xv+shift*delta+width*delta/2.0; + if (start || xv+shift*delta-width*delta/2.0) + + + + This software is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License (LGPL) as published by + the Free Software Foundation, either version 2.1 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 Lesser General Public License (LGPL) for more details. + + You should have received a copy of the GNU Lesser General Public License (LGPL) + along with this program. If not, see . +*/ + +#include +#include +#include +#include "jkqtplotter/jkqtptools.h" +#include "jkqtplotter/jkqtplotter_imexport.h" +#include "jkqtplotter/jkqtpimagetools.h" +#include "jkqtplotter/jkqtpgraphsbase.h" +#include "jkqtplotter/jkqtpgraphsbaseerrors.h" +#include "jkqtplotter/jkqtpgraphsbasestylingmixins.h" +#ifndef jkqtpgraphsbarchartbase_H +#define jkqtpgraphsbarchartbase_H + + + +/** \brief This is a base-class for all bar graphs with vertical or horizontal orientation (the orientation is implemented in dervied classes!) + * \ingroup jkqtplotter_barssticks + * + * This class plots a bargraph. This image explains the parameters: + * + * \image html bargraph_basics.png + * + * By default the sift parameter is, so the bar is centered at the x-value. The width is 0.9, + * so adjacent bars are plotted with a small distance between them. It is possible to use these two parameters + * to plot multiple bars for every x-value, by having on JKQTPSpecialLineHorizontalGraph object per + * set of bars that belong together. For example for three bars per x-value one would set: + * \verbatim + * width=0.3 + * shift=-0.3 / 0 / +0.3 + * \endverbatim + * This results in a bargraph, as shown here: + * + * \image html plot_bargraphverplot.png + * + * + * + * \see JKQTPBarHorizontalGraph, JKQTPBarVerticalGraph + */ +class JKQTPLOTTER_LIB_EXPORT JKQTPBarGraphBase: public JKQTPXYGraph, public JKQTPGraphLineStyleMixin, public JKQTPGraphFillStyleMixin { + Q_OBJECT + public: + /** \brief class constructor */ + JKQTPBarGraphBase(JKQTBasePlotter* parent=nullptr); + /** \brief class constructor */ + JKQTPBarGraphBase(JKQTPlotter* parent); + + /** \brief plots a key marker inside the specified rectangle \a rect */ + virtual void drawKeyMarker(JKQTPEnhancedPainter& painter, QRectF& rect) override; + /** \brief returns the color to be used for the key label */ + virtual QColor getKeyLabelColor() const override; + + + + /*! \copydoc shift */ + double getShift() const; + /*! \copydoc width */ + double getWidth() const; + /*! \copydoc baseline */ + double getBaseline() const; + /** \brief sets the fill color and the color together, where fillColor is set to \a fill and the line-color is set to \c fill.darker(colorDarker) + * \see setColor() + */ + void setFillColor_and_darkenedColor(QColor fill, int colorDarker=200); + + /** \brief returns xColumn or yColumn, whichever is used for the position of the bars (depending on whether the barchart is vertical or horizontal \see getBarHeightColumn(), xColumn, yColumn */ + virtual int getBarPositionColumn() const =0; + + /** \brief returns xColumn or yColumn, whichever is used for the height of the bars (depending on whether the barchart is vertical or horizontal \see getBarPositionColumn(), xColumn, yColumn */ + virtual int getBarHeightColumn() const =0; + public slots: + + /** \brief finds all bar charts of the same orientation and determines width and shift, so they stand side by side + * + * \param maxWidth the maximum (relative) width, that all bars will span of the (doubled) inter-bar distance + * \param shrinkFactor factor, by which the bar are shrinked compared to the available space + * + * \note This function will scale ALL graphs of the parent plot, which were derived from JKQTPBarHorizontalGraph, that match in orientation (as returned by isHorizontal() ). + */ + virtual void autoscaleBarWidthAndShift(double maxWidth=0.9, double shrinkFactor=0.8); + + /** \brief equivalent to \c autoscaleBarWidthAndShift(groupWidth,1); + */ + void autoscaleBarWidthAndShiftSeparatedGroups(double groupWidth=0.75); + /*! \copydoc shift */ + void setShift(double __value); + /*! \copydoc width */ + void setWidth(double __value); + /*! \copydoc baseline */ + void setBaseline(double __value); + + /** \brief set outline and fill color at the same time + * \see setFillColor_and_darkenedColor() + */ + virtual void setColor(QColor c); + + + /** \brief returns xColumn or yColumn, whichever is used for the position of the bars (depending on whether the barchart is vertical or horizontal \see getBarHeightColumn(), xColumn, yColumn */ + virtual void setBarPositionColumn(int column) =0; + + /** \brief returns xColumn or yColumn, whichever is used for the position of the bars (depending on whether the barchart is vertical or horizontal \see getBarHeightColumn(), xColumn, yColumn */ + virtual void setBarPositionColumn(size_t column) =0; + + /** \brief returns xColumn or yColumn, whichever is used for the height of the bars (depending on whether the barchart is vertical or horizontal \see getBarPositionColumn(), xColumn, yColumn */ + virtual void setBarHeightColumn(int column) =0; + + /** \brief returns xColumn or yColumn, whichever is used for the height of the bars (depending on whether the barchart is vertical or horizontal \see getBarPositionColumn(), xColumn, yColumn */ + virtual void setBarHeightColumn(size_t column) =0; + protected: + /** \brief the width of the bargraphs, relative to the distance between the current and the next x-value + * + * See the following graphic to understand this concept: + * \image html bargraph_basics.png + */ + double width; + /** \brief the shift of the bargraphs, relative to the distance between the current and the next x-value + * + * See the following graphic to understand this concept: + * \image html bargraph_basics.png + */ + double shift; + + /** \brief baseline of the plot (NOTE: 0 is interpreted as until plot border in log-mode!!!) + */ + double baseline; + + /** \brief this function is used by autoscaleBarWidthAndShift() to determine whether a given graph shall be taken into account when autoscaling. + * Typically this returns \c true for all JKQTPBarGraphBase-derved objects with the same orientation (horizontal or vertical) */ + virtual bool considerForAutoscaling( JKQTPBarGraphBase* other) const=0; + + /** \brief used to generate stacked plots: returns the upper boundary of the parent plot in a stack, for the index-th datapoint */ + virtual double getParentStackedMax(int index) const ; + + /** \brief returns \c true, if a stack parent is set (if available) */ + virtual bool hasStackParent() const ; + + /** \brief get the maximum and minimum value in the box-elongation (i.e. value) direction of the graph + * + * The result is given in the two parameters which are call-by-reference parameters! + */ + bool getValuesMinMax(double& mmin, double& mmax, double& smallestGreaterZero) ; + /** \brief get the maximum and minimum value of the box positions of the graph + * + * The result is given in the two parameters which are call-by-reference parameters! + */ + bool getPositionsMinMax(double& mmin, double& mmax, double& smallestGreaterZero) ; +}; + + + + + + + +#endif // jkqtpgraphsbarchartbase_H diff --git a/screenshots/barchart_hor.png b/screenshots/barchart_hor.png new file mode 100644 index 0000000000000000000000000000000000000000..542dba75850de908dff0e6597ed6ce3fd824374a GIT binary patch literal 12715 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