From 85cbbc3840a4f025f0ff9bbbe180980d0ee4ddfc Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Mon, 2 Mar 2020 18:59:07 +0000 Subject: [PATCH] Add QChart to Python Chart (4b of 5) Stack charts now when set as an option, in which case all data series are given their own plot. Or alternatively, where data series have different x-axis names, they get a plot for each x-axis. Need to fixup a) minimum height as can get squashed and b) scroll area needed as well as c) layout direction. --- src/Charts/GenericChart.cpp | 161 ++++++++++++++++++++++++++++++--- src/Charts/GenericChart.h | 171 ++++++++++++++++++++++++++++++++++-- src/Charts/GenericPlot.cpp | 3 +- src/Charts/GenericPlot.h | 56 +----------- 4 files changed, 320 insertions(+), 71 deletions(-) diff --git a/src/Charts/GenericChart.cpp b/src/Charts/GenericChart.cpp index a09674e92..043fbede2 100644 --- a/src/Charts/GenericChart.cpp +++ b/src/Charts/GenericChart.cpp @@ -25,26 +25,47 @@ #include +// +// The generic chart manages collections of genericplots +// if the stack option is selected we create a plot for +// each data series. +// +// If series do not have a common x-axis we create a +// separate plot, with series that share a common x-axis +// sharing the same plot +// +// The layout is horizontal or vertical in which case the +// generic chart will use vbox or hbox layouts. XXX TODO +// +// Charts have a minimum width and height that needs to be +// honoured too, since multiple series stacked can get +// cramped, so these are placed into a scroll area XXX TODO +// GenericChart::GenericChart(QWidget *parent, Context *context) : QWidget(parent), context(context) { // intitialise state info mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(0); mainLayout->setContentsMargins(0,0,0,0); - - plot = new GenericPlot(this,context); - mainLayout->addWidget(plot); } // set chart settings bool GenericChart::initialiseChart(QString title, int type, bool animate, int legendpos, bool stack, int orientation) { - // for now ... - Q_UNUSED(stack) - Q_UNUSED(orientation) + // Remember the settings, we use them for every new plot + this->title = title; + this->type = type; + this->animate = animate; + this->legendpos = legendpos; + this->stack = stack; + this->orientation = orientation; - return plot->initialiseChart(title, type, animate, legendpos); + // store info as its passed to us + newSeries.clear(); + newAxes.clear(); + + return true; } // add a curve, associating an axis @@ -54,7 +75,8 @@ GenericChart::addCurve(QString name, QVector xseries, QVector ys int line, int symbol, int size, QString color, int opacity, bool opengl) { - return plot->addCurve(name, xseries, yseries, xname, yname, labels, colors, line, symbol, size, color, opacity, opengl); + newSeries << GenericSeriesInfo(name, xseries, yseries, xname, yname, labels, colors, line, symbol, size, color, opacity, opengl); + return true; } // configure axis, after curves added @@ -62,12 +84,131 @@ bool GenericChart::configureAxis(QString name, bool visible, int align, double min, double max, int type, QString labelcolor, QString color, bool log, QStringList categories) { - return plot->configureAxis(name, visible, align, min, max, type, labelcolor, color, log, categories); + newAxes << GenericAxisInfo(name, visible, align, min, max, type, labelcolor, color, log, categories); + return true; } // post processing clean up / add decorations / helpers etc void GenericChart::finaliseChart() { - plot->finaliseChart(); + setUpdatesEnabled(false); // lets not open our kimono here + + // ok, so lets work out what we need, run through the curves + // and allocate to a plot, where we have separate plots if + // we have stacked set -or- for each xaxis, remembering to + // add the axisinfo each time too. + QList newPlots; + QStringList xaxes; + for(int i=0; i=0) add.axes << newAxes[ax]; + + // yaxis info + ax=GenericAxisInfo::findAxis(newAxes, newSeries[i].yname); + if (ax>=0) add.axes << newAxes[ax]; + + // add to list + newPlots << add; + xaxes << newSeries[i].xname; + + } else { + + // otherwise add all series to the same plot + // with a common x axis + int index = xaxes.indexOf(newSeries[i].xname); + if (index >=0) { + // woop, we already got this xaxis + newPlots[index].series << newSeries[i]; + + // yaxis info + int ax=GenericAxisInfo::findAxis(newAxes, newSeries[i].yname); + if (ax>=0) newPlots[index].axes << newAxes[ax]; + + } else { + + GenericPlotInfo add(newSeries[i].xname); + add.series << newSeries[i]; + + // xaxis info + int ax=GenericAxisInfo::findAxis(newAxes, newSeries[i].xname); + if (ax>=0) add.axes << newAxes[ax]; + + // yaxis info + ax=GenericAxisInfo::findAxis(newAxes, newSeries[i].yname); + if (ax>=0) add.axes << newAxes[ax]; + + // add to list + newPlots << add; + xaxes << newSeries[i].xname; + } + } + } + + // lets find existing plots or create new ones + // first we mark all existing plots as deleteme + for(int i=0; iaddWidget(newPlots[i].plot); + mainLayout->setStretchFactor(newPlots[i].plot, 10);// make them all the same + } else { + newPlots[i].plot = currentPlots[index].plot; // reuse + currentPlots[index].state = GenericPlotInfo::matched; // don't deleteme ! + } + } + + // delete all the deleteme plots + for(int i=0; iinitialiseChart(title, type, animate, legendpos); + + // add curves + QListIterators(newPlots[i].series); + while(s.hasNext()) { + GenericSeriesInfo p=s.next(); + newPlots[i].plot->addCurve(p.name, p.xseries, p.yseries, p.xname, p.yname, p.labels, p.colors, p.line, p.symbol, p.size, p.color, p.opacity, p.opengl); + } + + // set axis + QListIteratora(newPlots[i].axes); + while(a.hasNext()) { + GenericAxisInfo p=a.next(); + newPlots[i].plot->configureAxis(p.name, p.visible, p.align,p.minx, p.maxx, p.type, p.labelcolorstring, p.axiscolorstring, p.log, p.categories); + } + + newPlots[i].plot->finaliseChart(); + newPlots[i].state = GenericPlotInfo::active; + } + + // set current and wipe rest + currentPlots=newPlots; + + // and zap the state + newAxes.clear(); + newSeries.clear(); + + // and display + setUpdatesEnabled(true); } diff --git a/src/Charts/GenericChart.h b/src/Charts/GenericChart.h index 983e25e12..396b27267 100644 --- a/src/Charts/GenericChart.h +++ b/src/Charts/GenericChart.h @@ -30,6 +30,154 @@ #include "GenericLegend.h" #include "GenericPlot.h" +// keeping track of the series info +class GenericSeriesInfo { + + public: + + GenericSeriesInfo(QString name, QVector xseries, QVector yseries, QString xname, QString yname, + QStringList labels, QStringList colors, + int line, int symbol, int size, QString color, int opacity, bool opengl) : + name(name), xseries(xseries), yseries(yseries), xname(xname), yname(yname), + labels(labels), colors(colors), + line(line), symbol(symbol), size(size), color(color), opacity(opacity), opengl(opengl) + {} + + // properties, from setCurve(...) + QString name; + QVector xseries; + QVector yseries; + QString xname; + QString yname; + QStringList labels; + QStringList colors; + int line; + int symbol; + int size; + QString color; + int opacity; + bool opengl; +}; + +// keeping track of all our plots +class GenericPlotInfo { + + public: + + // when working out what to do with existing plots + enum { init, active, matched, deleteme } state; + + // initial + GenericPlotInfo(QString xaxis) : state(init), plot(NULL), xaxis(xaxis) {} + + bool matches(const GenericPlotInfo &other) const { + // we need to have same series and same xaxis + if (other.xaxis != xaxis) return false; + + // same number of series + if (other.series.count() != series.count()) return false; + + // all my series are in their series? + foreach(GenericSeriesInfo info, series) { + bool found=false; + // is in other? + foreach(GenericSeriesInfo oinfo, other.series) { + if (oinfo.name == info.name && oinfo.yname == info.yname && oinfo.xname == info.xname) + found=true; + } + if (found == false) return false; + } + return true; + } + + static int findPlot(const QListlist, const GenericPlotInfo findme) { + for(int i=0; i series; + QList axes; +}; + +// general axis info +class GenericAxisInfo { +public: + GenericAxisInfo(QString name, bool visible, int align, double min, double max, + int type, QString labelcolor, QString color, bool log, QStringList categories) : + type(static_cast(type)), + name(name), align(static_cast(align)), + minx(min), maxx(max), visible(visible), log(log), + labelcolorstring(labelcolor), axiscolorstring(color), categories(categories) + {} + + enum axisinfoType { CONTINUOUS=0, // Continious range + DATERANGE=1, // Date + TIME=2, // Duration, Time + CATEGORY=3 // labelled with categories + }; + typedef enum axisinfoType AxisInfoType; + + static int findAxis(QListinfos, QString name) { + for (int i=0; imaxx) maxx=x; + if (xmaxy) maxy=y; + if (y series; + + // data is all public to avoid tedious get/set + AxisInfoType type; // what type of axis is this? + QString name; + Qt::Orientations orientation; + Qt::AlignmentFlag align; + double miny, maxy, minx, maxx; // updated as we see points, set the range + bool visible,fixed, log, minorgrid, majorgrid; // settings + QColor labelcolor, axiscolor; // aesthetics + QString labelcolorstring, axiscolorstring; + QStringList categories; +}; + // the chart class GenericChart : public QWidget { @@ -63,15 +211,28 @@ class GenericChart : public QWidget { // legend and selector need acces to these QVBoxLayout *mainLayout; - GenericPlot *plot; // for now... - // layout options + // chart settings + QString title; + int type; + bool animate; + int legendpos; bool stack; // stack series instead of on same chart - Qt::Orientation orientation; // layout horizontal or vertical + int orientation; // layout horizontal or vertical + + // when we get new settings/calls we + // collect together to prepare before + // actually creating plots since we want + // to see the whole picture- try to reuse + // plots as much as possible to avoid + // the cost of creating new ones. + QList newSeries; + QList newAxes; + + // active plots + QList currentPlots; private: Context *context; }; - - #endif diff --git a/src/Charts/GenericPlot.cpp b/src/Charts/GenericPlot.cpp index c81ad24d3..786457552 100644 --- a/src/Charts/GenericPlot.cpp +++ b/src/Charts/GenericPlot.cpp @@ -17,6 +17,7 @@ */ #include "GenericPlot.h" +#include "GenericChart.h" #include "Colors.h" #include "TabView.h" @@ -208,7 +209,7 @@ GenericPlot::setSeriesVisible(QString name, bool visible) // annotations void -GenericPlot::addAnnotation(AnnotationType type, QAbstractSeries*series, double value) +GenericPlot::addAnnotation(AnnotationType , QAbstractSeries*series, double value) { fprintf(stderr, "add annotation line: for %s at value %f\n", series->name().toStdString().c_str(), value); fflush(stderr); diff --git a/src/Charts/GenericPlot.h b/src/Charts/GenericPlot.h index b638c9b4e..48cf887dc 100644 --- a/src/Charts/GenericPlot.h +++ b/src/Charts/GenericPlot.h @@ -54,59 +54,7 @@ class GenericPlot; class GenericLegend; class GenericSelectTool; - -// general axis info -class GenericAxisInfo { -public: - enum axisinfoType { CONTINUOUS=0, // Continious range - DATERANGE=1, // Date - TIME=2, // Duration, Time - CATEGORY=3 // labelled with categories - }; - typedef enum axisinfoType AxisInfoType; - - GenericAxisInfo(Qt::Orientations orientation, QString name) : name(name), orientation(orientation) { - miny=maxy=minx=maxx=0; - fixed=log=false; - visible=minorgrid=majorgrid=true; - type=CONTINUOUS; - axiscolor=labelcolor=GColor(CPLOTMARKER); - } - - void point(double x, double y) { - if (fixed) return; - if (x>maxx) maxx=x; - if (xmaxy) maxy=y; - if (y series; - - // data is all public to avoid tedious get/set - QString name; - Qt::Orientations orientation; - Qt::AlignmentFlag align; - double miny, maxy, minx, maxx; // updated as we see points, set the range - bool visible,fixed, log, minorgrid, majorgrid; // settings - QColor labelcolor, axiscolor; // aesthetics - QStringList categories; - AxisInfoType type; // what type of axis is this? -}; +class GenericAxisInfo; // the chart class GenericPlot : public QWidget { @@ -202,6 +150,4 @@ class GenericPlot : public QWidget { // alternates as axis added bool left, bottom; }; - - #endif