Skip to content

Commit

Permalink
Horizontal and Vertical Line Annotations on UserChart
Browse files Browse the repository at this point in the history
.. annotate(hline|vline, "text", style, value) to add a horizontal or
   vertical line to the plot for the current series on a UserChart.

   style is one of solid, dash, dot, dashdot or dashdotdot which are
   the standard Qt pen styles for drawing lines.

   I also took the opportunity to refactor how annotations are passed
   from the datafilter down to the generic plot. This should make it
   far easier to add annotations in the future.

.. fixed a SEGV in the voronoi annotation, which was related to memory
   management and the sqrt_nsites variable (honestly, I am amazed it
   ever worked).

.. labels in Python and R charts are now broken, will fixup shortly when
   worked out how it should work (annotations are related to a series).
  • Loading branch information
liversedge committed Oct 12, 2021
1 parent 9976746 commit a3f42c0
Show file tree
Hide file tree
Showing 16 changed files with 272 additions and 169 deletions.
4 changes: 2 additions & 2 deletions contrib/voronoi/Voronoi.cpp
Expand Up @@ -730,8 +730,8 @@ Voronoi::getfree(Freelist * fl)
Freenode * t ;
if (fl->head == (Freenode *)NULL)
{
t = (Freenode *) myalloc(sqrt_nsites * fl->nodesize) ;
for(i = 0 ; i < sqrt_nsites ; i++)
t = (Freenode *) myalloc(100 * fl->nodesize) ;
for(i = 0 ; i < 100 ; i++)
{
makefree((Freenode *)((char *)t+i*fl->nodesize), fl) ;
}
Expand Down
84 changes: 83 additions & 1 deletion src/Charts/GenericAnnotations.cpp
Expand Up @@ -19,6 +19,34 @@
#include "GenericAnnotations.h"
#include "GenericPlot.h"

// utils for mucking about with qbastractaxis
static double qtchartaxismin(QAbstractAxis *axis)
{
double returning;

switch (axis->type()) {
case QAbstractAxis::AxisTypeValue: returning = static_cast<QValueAxis*>(axis)->min(); break;
case QAbstractAxis::AxisTypeDateTime: returning = static_cast<QDateTimeAxis*>(axis)->min().toMSecsSinceEpoch(); break;
case QAbstractAxis::AxisTypeLogValue: returning = static_cast<QLogValueAxis*>(axis)->min(); break;
default: returning=-9999;
}

return returning;
}

static double qtchartaxismax(QAbstractAxis *axis)
{
double returning;

switch (axis->type()) {
case QAbstractAxis::AxisTypeValue: returning = static_cast<QValueAxis*>(axis)->max(); break;
case QAbstractAxis::AxisTypeDateTime: returning = static_cast<QDateTimeAxis*>(axis)->max().toMSecsSinceEpoch(); break;
case QAbstractAxis::AxisTypeLogValue: returning = static_cast<QLogValueAxis*>(axis)->max(); break;
default: returning=9999;
}

return returning;
}
//
// Controller (its the thing attached to the chart's scene
//
Expand All @@ -39,7 +67,7 @@ void GenericAnnotationController::removeAnnotation(GenericAnnotation *item) { an
QRectF GenericAnnotationController::boundingRect() const { return plot->qchart->plotArea(); }

//
// line painter
// lines painter
//
GenericLines::GenericLines(GenericAnnotationController *controller) : curve(NULL), controller(controller) {}
GenericLines::~GenericLines() {}
Expand All @@ -61,3 +89,57 @@ GenericLines::paint(QPainter*painter, const QStyleOptionGraphicsItem *, QWidget*
painter->drawLine(from, to);
}
}


//
// straight line with text painter
//
StraightLine::StraightLine(GenericAnnotationController *controller) : curve(NULL), controller(controller) {}
StraightLine::~StraightLine() {}

void
StraightLine::paint(QPainter*painter, const QStyleOptionGraphicsItem *, QWidget*)
{
if (curve == NULL) return;

QPen pen(Qt::lightGray);
pen.setStyle(style);
painter->setPen(pen);

painter->setClipRect(controller->plot->qchart->plotArea());

double min=0, max=0;
// get min/max from the other axis
foreach(QAbstractAxis *axis, curve->attachedAxes()) {
if (axis->orientation() == orientation) {
// set min and max
min = qtchartaxismin(axis);
max = qtchartaxismax(axis);
}
}

// draw a line from min to max at value on our axis
QPointF p1, p2;
if (orientation == Qt::Vertical) {
p1 = QPointF(value,min);
p2 = QPointF(value,max);
} else {
p1 = QPointF(min, value);
p2 = QPointF(max, value);
}

QPointF from = controller->plot->qchart->mapToPosition(p1, curve);
QPointF to = controller->plot->qchart->mapToPosition(p2, curve);


QFont font; // default font size
painter->setFont(font);
QFontMetrics fm(font);
double height = fm.boundingRect(text).height() + 4;

painter->drawText(orientation == Qt::Horizontal ? from + QPointF(4,-4) : to + QPointF(4, height), text);

pen.setColor(GColor(CPLOTMARKER));
painter->setPen(pen);
painter->drawLine(from, to);
}
28 changes: 28 additions & 0 deletions src/Charts/GenericAnnotations.h
Expand Up @@ -82,4 +82,32 @@ class GenericLines : public GenericAnnotation
GenericAnnotationController *controller;
};

// horizontal or vertical line with a text label
class StraightLine : public GenericAnnotation
{
public:

StraightLine(GenericAnnotationController *);
~StraightLine();

void setCurve(QAbstractSeries *curve) { this->curve=curve; }
void setValue(double value) { this->value = value; controller->update(); }
void setText(QString text) { this->text = text; controller->update(); }
void setStyle(Qt::PenStyle style) { this->style = style; controller->update(); }
void setOrientation(int orientation) { this->orientation = orientation; controller->update(); }

void paint(QPainter*, const QStyleOptionGraphicsItem *, QWidget*);

private:

// lines to draw
QAbstractSeries *curve;
double value;
QString text;
int orientation;
Qt::PenStyle style;

GenericAnnotationController *controller;
};

#endif
49 changes: 7 additions & 42 deletions src/Charts/GenericChart.cpp
Expand Up @@ -144,50 +144,23 @@ GenericChart::initialiseChart(QString title, int type, bool animate, int legendp
bool
GenericChart::addCurve(QString name, QVector<double> xseries, QVector<double> yseries, QVector<QString> fseries, QString xname, QString yname,
QStringList labels, QStringList colors, int line, int symbol, int size, QString color, int opacity, bool opengl,
bool legend, bool datalabels, bool fill)
bool legend, bool datalabels, bool fill, RideMetric::MetricType mtype, QList<GenericAnnotationInfo> annotations)
{

newSeries << GenericSeriesInfo(name, xseries, yseries, fseries, xname, yname, labels, colors, line, symbol, size, color, opacity, opengl, legend, datalabels, fill);
newSeries << GenericSeriesInfo(name, xseries, yseries, fseries, xname, yname, labels, colors, line, symbol, size, color, opacity, opengl, legend, datalabels, fill, mtype, annotations);
return true;
}

// helper for python
// helper for python - no aggregateby, no annotations
bool
GenericChart::addCurve(QString name, QVector<double> xseries, QVector<double> yseries, QStringList fseries, QString xname, QString yname,
QStringList labels, QStringList colors, int line, int symbol, int size, QString color, int opacity, bool opengl,
bool legend, bool datalabels, bool fill)
{
QVector<QString> flist;
for(int i=0; i<fseries.count(); i++) flist << fseries.at(i);
newSeries << GenericSeriesInfo(name, xseries, yseries, flist, xname, yname, labels, colors, line, symbol, size, color, opacity, opengl, legend, datalabels, fill);
return true;
}

// add a label to a series
bool
GenericChart::annotateLabel(QString name, QStringList list)
{
// add to the curve
for(int i=0; i<newSeries.count(); i++)
if (newSeries[i].name == name)
newSeries[i].annotateLabels << list;

return true;
}

// add a voronoi to a series
bool
GenericChart::annotateVoronoi(QString name, QVector<double>x, QVector<double>y)
{
if (x.count() < 2) return false;

// add to the curve
for(int i=0; i<newSeries.count(); i++)
if (newSeries[i].name == name) {
newSeries[i].voronoix =x;
newSeries[i].voronoiy =y;
}

newSeries << GenericSeriesInfo(name, xseries, yseries, flist, xname, yname, labels, colors, line, symbol, size, color, opacity,
opengl, legend, datalabels, fill, RideMetric::Average, QList<GenericAnnotationInfo>());
return true;
}

Expand Down Expand Up @@ -381,6 +354,7 @@ GenericChart::finaliseChart()
target->setStretchFactor(newPlots[i].plot, 10);// make them all the same
} else {
newPlots[i].plot = currentPlots[index].plot; // reuse
newPlots[i].plot->clearAnnotations(); // zap annottions left behind
currentPlots[index].state = GenericPlotInfo::matched; // don't deleteme !

// make sure its in the right layout (might remove and add back to the same layout!)
Expand Down Expand Up @@ -429,17 +403,8 @@ GenericChart::finaliseChart()

// add curve-- with additional axis info in advance since it finally is added to an actual chart
newPlots[i].plot->addCurve(p.name, p.xseries, p.yseries, p.fseries, p.xname, p.yname, p.labels, p.colors, p.line,
p.symbol, p.size, p.color, p.opacity, p.opengl, p.legend, p.datalabels, p.fill);

// did we get some labels associated with the curve?
QListIterator<QStringList>l(p.annotateLabels);
while(l.hasNext()) {
QStringList list=l.next();
newPlots[i].plot->addAnnotation(GenericPlot::LABEL, list.join(" "), RGBColor(QColor(p.color)));
}
p.symbol, p.size, p.color, p.opacity, p.opengl, p.legend, p.datalabels, p.fill, p.annotations);

// did we have a voronoi diagram?
if (p.voronoix.count() >= 2) newPlots[i].plot->addVoronoi(p.name, p.voronoix, p.voronoiy);
}

// set axis
Expand Down
27 changes: 10 additions & 17 deletions src/Charts/GenericChart.h
Expand Up @@ -35,17 +35,16 @@
// a chart annotation
class GenericAnnotationInfo {

enum annotationType { None, Label, VLine, HLine, Voronoi };
typedef annotationType AnnotationType;

public:

GenericAnnotationInfo(AnnotationType);

enum annotationType { None, Label, VLine, HLine, Voronoi };
typedef annotationType AnnotationType;
AnnotationType type;

GenericAnnotationInfo(AnnotationType type) : type(type) {}

// labels
QList<QLabel *> labels;
QStringList labels;

// voronoi
QString vname;
Expand All @@ -64,12 +63,12 @@ class GenericSeriesInfo {

GenericSeriesInfo(QString name, QVector<double> xseries, QVector<double> yseries, QVector<QString> fseries, QString xname, QString yname,
QStringList labels, QStringList colors, int line, int symbol, int size, QString color, int opacity, bool opengl,
bool legend, bool datalabels, bool fill, RideMetric::MetricType aggregateby = RideMetric::Average) :
bool legend, bool datalabels, bool fill, RideMetric::MetricType aggregateby, QList<GenericAnnotationInfo> annotations) :
user1(NULL), user2(NULL), user3(NULL), user4(NULL),
name(name), xseries(xseries), yseries(yseries), fseries(fseries), xname(xname), yname(yname),
labels(labels), colors(colors),
line(line), symbol(symbol), size(size), color(color), opacity(opacity), opengl(opengl), legend(legend), datalabels(datalabels), fill(fill),
aggregateby(aggregateby)
aggregateby(aggregateby), annotations(annotations)
{}

GenericSeriesInfo() :
Expand Down Expand Up @@ -105,9 +104,6 @@ class GenericSeriesInfo {
RideMetric::MetricType aggregateby;

QList<GenericAnnotationInfo> annotations;

QList <QStringList> annotateLabels; // label annotation
QVector<double> voronoix, voronoiy; // voronoi diagram annotation
};


Expand Down Expand Up @@ -314,9 +310,10 @@ class GenericChart : public QWidget {
// add a curve, associating an axis
bool addCurve(QString name, QVector<double> xseries, QVector<double> yseries, QVector<QString> fseries, QString xname, QString yname,
QStringList labels, QStringList colors,
int line, int symbol, int size, QString color, int opacity, bool opengl, bool legend, bool datalabels, bool fill);
int line, int symbol, int size, QString color, int opacity, bool opengl, bool legend, bool datalabels,
bool fill, RideMetric::MetricType mtype, QList<GenericAnnotationInfo>annotations);

// helper for Python charts fseries is a stringlist
// helper for Python and R charts fseries is a stringlist
bool addCurve(QString name, QVector<double> xseries, QVector<double> yseries, QStringList fseries, QString xname, QString yname,
QStringList labels, QStringList colors,
int line, int symbol, int size, QString color, int opacity, bool opengl, bool legend, bool datalabels, bool fill);
Expand All @@ -325,10 +322,6 @@ class GenericChart : public QWidget {
bool configureAxis(QString name, bool visible, int align, double min, double max,
int type, QString labelcolor, QString color, bool log, QStringList categories);

// annotations
bool annotateLabel(QString name, QStringList strings); // add a label alongside a series
bool annotateVoronoi(QString name, QVector<double>x, QVector<double>y); // add a voronoi diagram

// plot background
void setBackgroundColor(QColor);
void setGraphicsItem(QGraphicsItem *item);
Expand Down
7 changes: 7 additions & 0 deletions src/Charts/GenericLegend.cpp
Expand Up @@ -272,6 +272,13 @@ GenericLegend::addLabel(QLabel *label)
layout->addWidget(label);
}

// removes from layout but does not delete it.
void
GenericLegend::removeLabel(QLabel *label)
{
layout->removeWidget(label);
}

void
GenericLegend::removeSeries(QString name)
{
Expand Down
1 change: 1 addition & 0 deletions src/Charts/GenericLegend.h
Expand Up @@ -97,6 +97,7 @@ class GenericLegend : public QWidget {
void addSeries(QString name, QColor color);
void addX(QString name, bool datetime, QString datetimeformat);
void addLabel(QLabel *label);
void removeLabel(QLabel *label);
void removeSeries(QString name);
void removeAllSeries();
void setScale(double);
Expand Down

0 comments on commit a3f42c0

Please sign in to comment.