GenericPlot legend placement and line styles

.. updated to support different linestyles and also legend can
   now be placed above, below, left or right of the chart and
   orientated to list series vertically or horizontally.

.. the vertical legend is a bit ugly as nothing lines up, will
   fix up separately.
This commit is contained in:
Mark Liversedge
2020-03-01 17:37:07 +00:00
parent 6ce905509f
commit e83c9e8bb3
14 changed files with 146 additions and 68 deletions

View File

@@ -161,6 +161,7 @@ GenericLegend::GenericLegend(Context *context, GenericPlot *plot) : context(cont
layout = new QHBoxLayout(this);
layout->addStretch();
orientation=Qt::Horizontal;
xname="";
clickable=true;
@@ -184,6 +185,33 @@ GenericLegend::addSeries(QString name, QColor color)
connect(add, SIGNAL(clicked(QString,bool)), this, SIGNAL(clicked(QString,bool)));
}
void
GenericLegend::setOrientation(Qt::Orientation o)
{
if (orientation == o) return; // already is.
// set new orientation
orientation = o;
QBoxLayout *newlayout;
if (orientation == Qt::Horizontal) newlayout = new QHBoxLayout();
else newlayout = new QVBoxLayout();
newlayout->addStretch();
// remove from layout and redo
QMapIterator<QString, GenericLegendItem*> i(items);
while (i.hasNext()) {
i.next();
layout->removeWidget(i.value());
newlayout->insertWidget(0, i.value());
}
// replace the layout
delete layout;
setLayout(newlayout);
layout = newlayout;
}
void
GenericLegend::addX(QString name)
{

View File

@@ -95,20 +95,23 @@ class GenericLegend : public QWidget {
void clicked(QString name, bool enabled); // someone clicked on a legend and enabled/disabled it
public slots:
void setValue(QPointF value, QString name);
void unhover(QString name);
void unhoverx();
void setClickable(bool x);
void setOrientation(Qt::Orientation);
private:
// a label has a unique name, not directly tide to
// a series or axis value, it depends...
Context *context;
GenericPlot *plot;
QHBoxLayout *layout;
QBoxLayout *layout;
QMap<QString,GenericLegendItem*> items;
QString xname;
bool clickable;
Qt::Orientation orientation;
};
#endif

View File

@@ -33,9 +33,10 @@ GenericPlot::GenericPlot(QWidget *parent, Context *context) : QWidget(parent), c
barseries=NULL;
bottom=left=true;
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout = new QVBoxLayout(this);
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0,0,0,0);
leftLayout = new QHBoxLayout();
// setup the chart
qchart = new QChart();
@@ -67,7 +68,9 @@ GenericPlot::GenericPlot(QWidget *parent, Context *context) : QWidget(parent), c
// add all widgets to the view
mainLayout->addWidget(legend);
mainLayout->addWidget(chartview);
holder=mainLayout;
mainLayout->addLayout(leftLayout);
leftLayout->addWidget(chartview);
// watch for colors changing
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
@@ -327,7 +330,7 @@ GenericPlot::plotAreaChanged()
}
bool
GenericPlot::initialiseChart(QString title, int type, bool animate)
GenericPlot::initialiseChart(QString title, int type, bool animate, int legpos)
{
// if we changed the type, all series must go
if (charttype != type) {
@@ -360,6 +363,31 @@ GenericPlot::initialiseChart(QString title, int type, bool animate)
// by default they are disabled anyway
qchart->setAnimationOptions(animate ? QChart::SeriesAnimations : QChart::NoAnimation);
// lets move the legend, only support top or bottom for now
holder->removeWidget(legend);
switch (legpos) {
case 0: // bottom
legend->setOrientation(Qt::Horizontal);
mainLayout->insertWidget(-1, legend); // at end
holder=mainLayout;
break;
case 1: // left
legend->setOrientation(Qt::Vertical);
leftLayout->insertWidget(0, legend); // at beginning
holder=leftLayout;
break;
case 2: // top
legend->setOrientation(Qt::Horizontal);
mainLayout->insertWidget(0, legend); // at front
holder=mainLayout;
break;
case 3: // right
legend->setOrientation(Qt::Vertical);
leftLayout->insertWidget(-1, legend); // at end
holder=leftLayout;
break;
}
// what kind of selector do we use?
if (charttype==GC_CHART_LINE) selector->setMode(GenericSelectTool::XRANGE);
else selector->setMode(GenericSelectTool::RECTANGLE);
@@ -371,7 +399,7 @@ GenericPlot::initialiseChart(QString title, int type, bool animate)
bool
GenericPlot::addCurve(QString name, QVector<double> xseries, QVector<double> yseries, QString xname, QString yname,
QStringList labels, QStringList colors,
int /*line TODO*/, int /*symbol TODO */, int size, QString color, int opacity, bool opengl)
int linestyle, int /*symbol TODO */, int size, QString color, int opacity, bool opengl)
{
// if curve already exists, remove it
if (charttype==GC_CHART_LINE || charttype==GC_CHART_SCATTER || charttype==GC_CHART_PIE) {
@@ -425,7 +453,7 @@ GenericPlot::addCurve(QString name, QVector<double> xseries, QVector<double> yse
// aesthetics
add->setBrush(Qt::NoBrush);
QPen pen(color);
pen.setStyle(Qt::SolidLine);
pen.setStyle(static_cast<Qt::PenStyle>(linestyle));
pen.setWidth(size);
add->setPen(pen);
add->setOpacity(double(opacity) / 100.0); // 0-100% to 0.0-1.0 values

View File

@@ -134,7 +134,7 @@ class GenericPlot : public QWidget {
void configChanged(qint32);
// set chart settings
bool initialiseChart(QString title, int type, bool animate);
bool initialiseChart(QString title, int type, bool animate, int legendpos);
// add a curve, associating an axis
bool addCurve(QString name, QVector<double> xseries, QVector<double> yseries, QString xname, QString yname,
@@ -166,6 +166,9 @@ class GenericPlot : public QWidget {
QChartView *chartview;
GenericSelectTool *selector;
QChart *qchart;
QVBoxLayout *mainLayout;
QBoxLayout *leftLayout;
QBoxLayout *holder; // who has me at the mo?
// trap widget events and pass to event handler
bool eventFilter(QObject *, QEvent *e);

View File

@@ -274,7 +274,10 @@ void GenericSelectTool::paint(QPainter*painter, const QStyleOptionGraphicsItem *
painter->drawText(maxxp-QPointF(0,4), label);
label=QString("%1").arg(calc.x.min);
painter->drawText(minxp-QPointF(0,4), label);
label=QString("%1").arg(calc.x.mean);
// scatter sees mean, line/timeseries more likely to want dx
if (host->charttype == GC_CHART_SCATTER) label=QString("%1").arg(calc.x.mean);
else label=QString("%1").arg(calc.x.max - calc.x.min);
painter->drawText(avgxp-QPointF(0,4), label);
//
@@ -329,6 +332,7 @@ GenericSelectTool::reset()
hoverseries = NULL;
hoverpoints.clear();
hoveraxis = NULL;
hidden.clear();
resetSelections();
rectchanged=true;
update();

View File

@@ -454,7 +454,7 @@ PythonChart::setWeb(bool x)
renderlayout->insertWidget(0,plot);
// signals to update it
connect(this, SIGNAL(emitChart(QString,int,bool)), plot, SLOT(initialiseChart(QString,int,bool)));
connect(this, SIGNAL(emitChart(QString,int,bool,int)), plot, SLOT(initialiseChart(QString,int,bool,int)));
connect(this, SIGNAL(emitCurve(QString,QVector<double>,QVector<double>,QString,QString,QStringList,QStringList,int,int,int,QString,int,bool)),
plot, SLOT( addCurve(QString,QVector<double>,QVector<double>,QString,QString,QStringList,QStringList,int,int,int,QString,int,bool)));
connect(this, SIGNAL(emitAxis(QString,bool,int,double,double,int,QString,QString,bool,QStringList)),

View File

@@ -141,7 +141,7 @@ class PythonChart : public GcChartWindow, public PythonHost {
signals:
void setUrl(QUrl);
void emitChart(QString title, int type, bool animate);
void emitChart(QString title, int type, bool animate, int legpos);
void emitCurve(QString name, QVector<double> xseries, QVector<double> yseries, QString xname, QString yname,
QStringList labels, QStringList colors,
int line, int symbol, int size, QString color, int opacity, bool opengl);

View File

@@ -58,9 +58,9 @@ Bindings::webpage(QString url) const
}
bool
Bindings::configChart(QString title, int type, bool animate) const
Bindings::configChart(QString title, int type, bool animate, int pos) const
{
python->chart->emitChart(title, type, animate);
python->chart->emitChart(title, type, animate, pos);
return true;
}

View File

@@ -114,7 +114,7 @@ class Bindings {
bool postProcess(QString processor, PyObject *activity = NULL) const;
// working with charts
bool configChart(QString title, int type, bool animate) const;
bool configChart(QString title, int type, bool animate, int pos) const;
bool setCurve(QString name, PyObject *xseries, PyObject *yseries, QString xname, QString yname,
QStringList labels, QStringList colors,
int line, int symbol, int symbolsize, QString color, int opacity, bool opengl) const;

View File

@@ -388,7 +388,7 @@ public:
bool postProcess(QString processor, PyObject *activity = NULL) const;
// working with qt charts
bool configChart(QString title, int type, bool animate) const;
bool configChart(QString title, int type, bool animate, int legpos) const;
bool setCurve(QString name, PyObject *xseries, PyObject *yseries, QString xname, QString yname,
QStringList labels, QStringList colors,
int line, int symbol, int symbolsize, QString color, int opacity, bool opengl) const;

View File

@@ -125,62 +125,64 @@
#define sipName_colors &sipStrings_goldencheetah[616]
#define sipNameNr_labels 623
#define sipName_labels &sipStrings_goldencheetah[623]
#define sipNameNr_metric 630
#define sipName_metric &sipStrings_goldencheetah[630]
#define sipNameNr_legpos 630
#define sipName_legpos &sipStrings_goldencheetah[630]
#define sipNameNr_metric 637
#define sipName_metric &sipStrings_goldencheetah[637]
#define sipNameNr_series 515
#define sipName_series &sipStrings_goldencheetah[515]
#define sipNameNr_season 637
#define sipName_season &sipStrings_goldencheetah[637]
#define sipNameNr_filter 644
#define sipName_filter &sipStrings_goldencheetah[644]
#define sipNameNr_result 651
#define sipName_result &sipStrings_goldencheetah[651]
#define sipNameNr_remove 658
#define sipName_remove &sipStrings_goldencheetah[658]
#define sipNameNr_append 665
#define sipName_append &sipStrings_goldencheetah[665]
#define sipNameNr_align 672
#define sipName_align &sipStrings_goldencheetah[672]
#define sipNameNr_season 644
#define sipName_season &sipStrings_goldencheetah[644]
#define sipNameNr_filter 651
#define sipName_filter &sipStrings_goldencheetah[651]
#define sipNameNr_result 658
#define sipName_result &sipStrings_goldencheetah[658]
#define sipNameNr_remove 665
#define sipName_remove &sipStrings_goldencheetah[665]
#define sipNameNr_append 672
#define sipName_append &sipStrings_goldencheetah[672]
#define sipNameNr_align 679
#define sipName_align &sipStrings_goldencheetah[679]
#define sipNameNr_color 361
#define sipName_color &sipStrings_goldencheetah[361]
#define sipNameNr_yname 678
#define sipName_yname &sipStrings_goldencheetah[678]
#define sipNameNr_xname 684
#define sipName_xname &sipStrings_goldencheetah[684]
#define sipNameNr_title 690
#define sipName_title &sipStrings_goldencheetah[690]
#define sipNameNr_index 696
#define sipName_index &sipStrings_goldencheetah[696]
#define sipNameNr_group 702
#define sipName_group &sipStrings_goldencheetah[702]
#define sipNameNr_xdata 708
#define sipName_xdata &sipStrings_goldencheetah[708]
#define sipNameNr_sport 714
#define sipName_sport &sipStrings_goldencheetah[714]
#define sipNameNr_value 720
#define sipName_value &sipStrings_goldencheetah[720]
#define sipNameNr_build 726
#define sipName_build &sipStrings_goldencheetah[726]
#define sipNameNr_line 732
#define sipName_line &sipStrings_goldencheetah[732]
#define sipNameNr_join 737
#define sipName_join &sipStrings_goldencheetah[737]
#define sipNameNr_name 679
#define sipName_name &sipStrings_goldencheetah[679]
#define sipNameNr_type 742
#define sipName_type &sipStrings_goldencheetah[742]
#define sipNameNr_date 747
#define sipName_date &sipStrings_goldencheetah[747]
#define sipNameNr_log 752
#define sipName_log &sipStrings_goldencheetah[752]
#define sipNameNr_yname 685
#define sipName_yname &sipStrings_goldencheetah[685]
#define sipNameNr_xname 691
#define sipName_xname &sipStrings_goldencheetah[691]
#define sipNameNr_title 697
#define sipName_title &sipStrings_goldencheetah[697]
#define sipNameNr_index 703
#define sipName_index &sipStrings_goldencheetah[703]
#define sipNameNr_group 709
#define sipName_group &sipStrings_goldencheetah[709]
#define sipNameNr_xdata 715
#define sipName_xdata &sipStrings_goldencheetah[715]
#define sipNameNr_sport 721
#define sipName_sport &sipStrings_goldencheetah[721]
#define sipNameNr_value 727
#define sipName_value &sipStrings_goldencheetah[727]
#define sipNameNr_build 733
#define sipName_build &sipStrings_goldencheetah[733]
#define sipNameNr_line 739
#define sipName_line &sipStrings_goldencheetah[739]
#define sipNameNr_join 744
#define sipName_join &sipStrings_goldencheetah[744]
#define sipNameNr_name 686
#define sipName_name &sipStrings_goldencheetah[686]
#define sipNameNr_type 749
#define sipName_type &sipStrings_goldencheetah[749]
#define sipNameNr_date 754
#define sipName_date &sipStrings_goldencheetah[754]
#define sipNameNr_log 759
#define sipName_log &sipStrings_goldencheetah[759]
#define sipNameNr_max 120
#define sipName_max &sipStrings_goldencheetah[120]
#define sipNameNr_min 756
#define sipName_min &sipStrings_goldencheetah[756]
#define sipNameNr_all 760
#define sipName_all &sipStrings_goldencheetah[760]
#define sipNameNr_url 764
#define sipName_url &sipStrings_goldencheetah[764]
#define sipNameNr_min 763
#define sipName_min &sipStrings_goldencheetah[763]
#define sipNameNr_all 767
#define sipName_all &sipStrings_goldencheetah[767]
#define sipNameNr_url 771
#define sipName_url &sipStrings_goldencheetah[771]
#define sipMalloc sipAPI_goldencheetah->api_malloc
#define sipFree sipAPI_goldencheetah->api_free

View File

@@ -1056,19 +1056,21 @@ static PyObject *meth_Bindings_configChart(PyObject *sipSelf, PyObject *sipArgs,
int a0State = 0;
int a1;
bool a2;
int a3;
const ::Bindings *sipCpp;
static const char *sipKwdList[] = {
sipName_title,
sipName_type,
sipName_animate,
sipName_legpos,
};
if (sipParseKwdArgs(&sipParseErr, sipArgs, sipKwds, sipKwdList, NULL, "BJ1ib", &sipSelf, sipType_Bindings, &sipCpp, sipType_QString,&a0, &a0State, &a1, &a2))
if (sipParseKwdArgs(&sipParseErr, sipArgs, sipKwds, sipKwdList, NULL, "BJ1ibi", &sipSelf, sipType_Bindings, &sipCpp, sipType_QString,&a0, &a0State, &a1, &a2, &a3))
{
bool sipRes;
sipRes = sipCpp->configChart(*a0,a1,a2);
sipRes = sipCpp->configChart(*a0,a1,a2,a3);
sipReleaseType(a0,sipType_QString,a0State);
return PyBool_FromLong(sipRes);

View File

@@ -74,6 +74,7 @@ const char sipStrings_goldencheetah[] = {
's', 'y', 'm', 'b', 'o', 'l', 0,
'c', 'o', 'l', 'o', 'r', 's', 0,
'l', 'a', 'b', 'e', 'l', 's', 0,
'l', 'e', 'g', 'p', 'o', 's', 0,
'm', 'e', 't', 'r', 'i', 'c', 0,
's', 'e', 'a', 's', 'o', 'n', 0,
'f', 'i', 'l', 't', 'e', 'r', 0,

View File

@@ -29,8 +29,8 @@ def __GCactivityXdata(name="", activity=None):
return rd
# setting up the chart
def __GCsetChart(title="",type=1,animate=False):
GC.configChart(title,type,animate)
def __GCsetChart(title="",type=1,animate=False,legpos=2):
GC.configChart(title,type,animate,legpos)
# add a curve
def __GCsetCurve(name="",x=list(),y=list(),xaxis="x",yaxis="y", labels=list(), colors=list(),line=1,symbol=0,size=15,color="cyan",opacity=0,opengl=True):
@@ -52,6 +52,13 @@ GC.setChart=__GCsetChart
GC.addCurve=__GCsetCurve
GC.setAxis=__GCconfigAxis
# line style
GC_LINE_NONE=0
GC_LINE_SOLID=1
GC_LINE_DASH=2
GC_LINE_DOT=3
GC_LINE_DASHDOT=4
# constants
GC_ALIGN_BOTTOM=0
GC_ALIGN_LEFT=1