diff --git a/src/Charts/GenericLegend.cpp b/src/Charts/GenericLegend.cpp index d1d342564..6072ff783 100644 --- a/src/Charts/GenericLegend.cpp +++ b/src/Charts/GenericLegend.cpp @@ -279,7 +279,7 @@ GenericLegend::removeAllSeries() } void -GenericLegend::setValue(QPointF value, QString name) +GenericLegend::setValue(GPointF value, QString name) { GenericLegendItem *call = items.value(name, NULL); if (call) call->setValue(value.y()); diff --git a/src/Charts/GenericLegend.h b/src/Charts/GenericLegend.h index 9f7dea85d..bd6edbbe2 100644 --- a/src/Charts/GenericLegend.h +++ b/src/Charts/GenericLegend.h @@ -100,7 +100,7 @@ class GenericLegend : public QWidget { public slots: - void setValue(QPointF value, QString name); + void setValue(GPointF value, QString name); void unhover(QString name); void unhoverx(); void setClickable(bool x); diff --git a/src/Charts/GenericPlot.cpp b/src/Charts/GenericPlot.cpp index 539a6797a..0a4aeeec4 100644 --- a/src/Charts/GenericPlot.cpp +++ b/src/Charts/GenericPlot.cpp @@ -22,6 +22,7 @@ #include "Colors.h" #include "TabView.h" #include "RideFileCommand.h" +#include "RideCache.h" #include "Utils.h" #include @@ -85,7 +86,8 @@ GenericPlot::GenericPlot(QWidget *parent, Context *context) : QWidget(parent), c connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32))); // get notifications when values change - connect(selector, SIGNAL(hover(QPointF,QString,QAbstractSeries*)), legend, SLOT(setValue(QPointF,QString))); + connect(selector, SIGNAL(seriesClicked(QAbstractSeries*,GPointF)), this, SLOT(seriesClicked(QAbstractSeries*,GPointF))); + connect(selector, SIGNAL(hover(GPointF,QString,QAbstractSeries*)), legend, SLOT(setValue(GPointF,QString))); connect(selector, SIGNAL(unhover(QString)), legend, SLOT(unhover(QString))); connect(selector, SIGNAL(unhoverx()), legend, SLOT(unhoverx())); connect(legend, SIGNAL(clicked(QString,bool)), this, SLOT(setSeriesVisible(QString,bool))); @@ -95,6 +97,19 @@ GenericPlot::GenericPlot(QWidget *parent, Context *context) : QWidget(parent), c configChanged(0); } +void +GenericPlot::seriesClicked(QAbstractSeries*series, GPointF point) +{ + // user clicked on a point, do we need to click thru? + QVector fseries = filenames.value(series, QVector()); + if (point.index >= 0 && point.index < fseries.count()) { + // click thru + RideItem *item = context->athlete->rideCache->getRide(fseries.at(point.index)); + if (item) context->notifyRideSelected(item); + } +} +bool GenericPlot::eventFilter(QObject *obj, QEvent *e) { return eventHandler(1, obj, e); } + // source 0=scene, 1=widget bool GenericPlot::eventHandler(int, void *, QEvent *e) @@ -245,7 +260,7 @@ void GenericPlot::pieHover(QPieSlice *slice, bool state) { if (havelegend.count() == 0) return; - if (state == true) legend->setValue(QPointF(0, round(slice->percentage()*1000)/10), havelegend.first()); + if (state == true) legend->setValue(GPointF(0, round(slice->percentage()*1000)/10, -1), havelegend.first()); else legend->unhover(havelegend.first()); } @@ -253,7 +268,7 @@ GenericPlot::pieHover(QPieSlice *slice, bool state) void GenericPlot::barsetHover(bool status, int index, QBarSet *) { foreach(QBarSet *barset, barsets) { - if (status) legend->setValue(QPointF(0, barset->at(index)), barset->label()); + if (status) legend->setValue(GPointF(0, barset->at(index), -1), barset->label()); else legend->unhover(barset->label()); } } @@ -371,6 +386,7 @@ GenericPlot::initialiseChart(QString title, int type, bool animate, int legpos) if (charttype != type) { qchart->removeAllSeries(); curves.clear(); + filenames.clear(); barseries=NULL; } @@ -443,7 +459,7 @@ GenericPlot::initialiseChart(QString title, int type, bool animate, int legpos) // rendering to qt chart bool -GenericPlot::addCurve(QString name, QVector xseries, QVector yseries, QVector /** UNUSED fseries **/, QString xname, QString yname, +GenericPlot::addCurve(QString name, QVector xseries, QVector yseries, QVector fseries, QString xname, QString yname, QStringList labels, QStringList colors, int linestyle, int symbol, int size, QString color, int opacity, bool opengl, bool legend, bool datalabels, bool fill) { @@ -627,6 +643,10 @@ GenericPlot::addCurve(QString name, QVector xseries, QVector yse QScatterSeries *add = new QScatterSeries(); add->setName(name); + // handle click thru + connect(add, SIGNAL(clicked(QPointF)), selector, SLOT(seriesClicked())); // catch series clicks + filenames.insert(add, fseries); + // aesthetics if (symbol == 0) add->setVisible(false); // no marker ! else if (symbol == 1) add->setMarkerShape(QScatterSeries::MarkerShapeCircle); @@ -661,7 +681,7 @@ GenericPlot::addCurve(QString name, QVector xseries, QVector yse Quadtree *tree = new Quadtree(QPointF(calc.x.min, calc.y.min), QPointF(calc.x.max, calc.y.max)); for (int i=0; iinsert(QPointF(xseries.at(i), yseries.at(i))); + tree->insert(GPointF(xseries.at(i), yseries.at(i), i)); if (tree->nodes.count() || tree->root->contents.count()) quadtrees.insert(add, tree); diff --git a/src/Charts/GenericPlot.h b/src/Charts/GenericPlot.h index c11d6247f..82f382e5a 100644 --- a/src/Charts/GenericPlot.h +++ b/src/Charts/GenericPlot.h @@ -109,6 +109,7 @@ class GenericPlot : public QWidget { void setSeriesVisible(QString name, bool visible); // watching scene events and managing interaction + void seriesClicked(QAbstractSeries*series, GPointF point); bool eventHandler(int eventsource, void *obj, QEvent *event); void barsetHover(bool status, int index, QBarSet *barset); void plotAreaChanged(); @@ -139,7 +140,6 @@ class GenericPlot : public QWidget { // quadtrees QMap quadtrees; - // annotation labels QList labels; @@ -150,6 +150,9 @@ class GenericPlot : public QWidget { // curves QMapcurves; + // filenames + QMap > filenames; + // decorations (symbols for line charts, lines for scatter) QMapdecorations; diff --git a/src/Charts/GenericSelectTool.cpp b/src/Charts/GenericSelectTool.cpp index b89392d88..78b2198b9 100644 --- a/src/Charts/GenericSelectTool.cpp +++ b/src/Charts/GenericSelectTool.cpp @@ -32,7 +32,7 @@ GenericSelectTool::GenericSelectTool(GenericPlot *host) : QObject(host), QGraphi mode = RECTANGLE; setVisible(true); // always visible - paints on axis setZValue(100); // always on top. - hoverpoint = QPointF(); + hoverpoint = GPointF(); hoverseries = NULL; hoveraxis = NULL; rect = QRectF(0,0,0,0); @@ -111,7 +111,7 @@ void GenericSelectTool::paint(QPainter*painter, const QStyleOptionGraphicsItem * // // SCATTER PLOT HOVERED POINT // - if (hoverpoint != QPointF()) { + if (hoverpoint != GPointF()) { // draw a circle using marker color QColor invert = GCColor::invertColor(GColor(CPLOTBACKGROUND)); painter->setBrush(invert); @@ -380,7 +380,6 @@ QRectF GenericSelectTool::boundingRect() const { return rect; } // trap events and redirect to plot event handler bool GenericSelectTool::sceneEventFilter(QGraphicsItem *watched, QEvent *event) { return host->eventHandler(0, watched,event); } -bool GenericPlot::eventFilter(QObject *obj, QEvent *e) { return eventHandler(1, obj, e); } bool GenericSelectTool::reset() @@ -390,7 +389,7 @@ GenericSelectTool::reset() start=QPointF(0,0); finish=QPointF(0,0); rect = QRectF(0,0,0,0); - hoverpoint = QPointF(); + hoverpoint = GPointF(); hoverseries = NULL; hoverpoints.clear(); hoveraxis = NULL; @@ -402,17 +401,36 @@ GenericSelectTool::reset() } // handle mouse events in selector +bool +GenericSelectTool::seriesClicked() +{ + // clicked on a point in the series + return clicked(QPointF()); // ignore mostly +} + bool GenericSelectTool::clicked(QPointF pos) { bool updatescene = false; + // click on a point to click-thru + if (hoverpoint.index != -1) { // hovering and clicked + emit seriesClicked(hoverseries, hoverpoint); + + // not sure need to do this.... + hoverpoints.clear(); + hoverpoint=GPointF(); + return false; + + } else if (pos == QPointF()) { // series clicked and not hovering + return false; + + } + if (mode == XRANGE || mode == RECTANGLE) { if (hoveraxis) return false; - hoverpoints.clear(); - hoverpoint=QPointF(); if (state==ACTIVE && sceneBoundingRect().contains(pos)) { @@ -506,7 +524,7 @@ GenericSelectTool::released(QPointF pos) // finishing move/resize state = ACTIVE; - hoverpoint=QPointF(); + hoverpoint=GPointF(); hoverpoints.clear(); rectchanged = true; update(rect); @@ -600,8 +618,8 @@ GenericSelectTool::moved(QPointF pos) // this needs to be super quick as mouse // movements are very fast, so we use a // quadtree to find the nearest points - QPointF hoverv; // value // series x,y co-ord used in signal (and legend later) - hoverpoint = QPointF(); // screen coordinates + GPointF hoverv; // value // series x,y co-ord used in signal (and legend later) + hoverpoint = GPointF(); // screen coordinates QAbstractSeries *originalhoverseries = hoverseries; hoverseries = NULL; foreach(QAbstractSeries *series, host->qchart->series()) { @@ -618,18 +636,18 @@ GenericSelectTool::moved(QPointF pos) //QPointF vpos = host->qchart->mapToValue(pos, series); // find candidates all close by using paint co-ords - QList tohere; + QList tohere; tree->candidates(vrect, tohere); QPointF cursorpos=mapFromScene(pos); - foreach(QPointF p, tohere) { + foreach(GPointF p, tohere) { QPointF scpos = mapFromScene(host->qchart->mapToPosition(p, series)); - if (hoverpoint == QPointF()) { - hoverpoint = scpos; + if (hoverpoint == GPointF()) { + hoverpoint = GPointF(scpos.x(), scpos.y(), p.index); hoverseries = series; hoverv = p; } else if ((cursorpos-scpos).manhattanLength() < (cursorpos-hoverpoint).manhattanLength()) { - hoverpoint=scpos; // not happy with this XXX needs more work + hoverpoint=GPointF(scpos.x(), scpos.y(), p.index); hoverseries = series; hoverv = p; } @@ -641,13 +659,13 @@ GenericSelectTool::moved(QPointF pos) } // hoverpoint changed - either a new series selected, a new point, or no point at all - if (originalhoverseries != hoverseries || hoverv != QPointF()) { + if (originalhoverseries != hoverseries || hoverv != GPointF()) { if (hoverseries != originalhoverseries && originalhoverseries != NULL) emit (unhover(originalhoverseries->name())); // old hover changed if (hoverseries != NULL) emit hover(hoverv, hoverseries->name(), hoverseries); // new hover changed } // we need to clear x-axis if we aren't hovering on anything at all - if (hoverv == QPointF()) { + if (hoverv == GPointF()) { emit unhoverx(); } @@ -660,7 +678,7 @@ GenericSelectTool::moved(QPointF pos) // user hovers with a vertical line, but can select a range on the x axis // lets get x axis value (any old series will do as they should have a common x axis spos = pos; - QMap vals; // what values were found + QMap vals; // what values were found double nearestx=-9999; foreach(QAbstractSeries *series, host->qchart->series()) { @@ -689,7 +707,7 @@ GenericSelectTool::moved(QPointF pos) QVector::const_iterator i = std::lower_bound(p.begin(), p.end(), x, CompareQPointFX()); // collect them away - vals.insert(series, QPointF(*i)); + vals.insert(series, GPointF(i->x(), i->y(), i-p.begin())); // nearest x? if (i->x() != 0 && (nearestx == -9999 || (std::fabs(i->x()-xvalue)) < std::fabs((nearestx-xvalue)))) nearestx = i->x(); @@ -699,7 +717,7 @@ GenericSelectTool::moved(QPointF pos) // run over what we found, updating paint points and signal (for legend) hoverpoints.clear(); - QMapIterator i(vals); + QMapIterator i(vals); while (i.hasNext()) { i.next(); if (i.value().x() == nearestx) { @@ -918,7 +936,7 @@ GenericSelectTool::setSeriesVisible(QString name, bool visible) // but for now this is the quickes and simplest way // to avoid artefacts hoverpoints.clear(); - hoverpoint=QPointF(); + hoverpoint=GPointF(); // hide/show and updatescenes may overlap/get out of sync // so we update scene to be absolutely sure the scene diff --git a/src/Charts/GenericSelectTool.h b/src/Charts/GenericSelectTool.h index 27b7c02af..b47fc4085 100644 --- a/src/Charts/GenericSelectTool.h +++ b/src/Charts/GenericSelectTool.h @@ -134,9 +134,11 @@ class GenericSelectTool : public QObject, public QGraphicsItem public slots: void dragStart(); + bool seriesClicked(); Q_SIGNALS: - void hover(QPointF value, QString name, QAbstractSeries*series); // mouse cursor is over a point on the chart + void seriesClicked(QAbstractSeries*,GPointF); + void hover(GPointF value, QString name, QAbstractSeries*series); // mouse cursor is over a point on the chart void unhover(QString name); // mouse cursor is no longer over a point on the chart void unhoverx(); // when we aren't hovering on anything at all @@ -150,7 +152,7 @@ class GenericSelectTool : public QObject, public QGraphicsItem QPointF spos; // last point we saw // scatter does this xxx TODO refactor into hoverpoints - QPointF hoverpoint; + GPointF hoverpoint; QAbstractSeries *hoverseries; // line plot uses this diff --git a/src/Core/Quadtree.cpp b/src/Core/Quadtree.cpp index 03acc5ac3..d0d0ff131 100644 --- a/src/Core/Quadtree.cpp +++ b/src/Core/Quadtree.cpp @@ -20,7 +20,7 @@ // add a node bool -QuadtreeNode::insert(Quadtree *root, QPointF value) +QuadtreeNode::insert(Quadtree *root, GPointF value) { if (!contains(value)) return false; @@ -44,7 +44,7 @@ QuadtreeNode::insert(Quadtree *root, QPointF value) // get candidates int -QuadtreeNode::candidates(QRectF rect, QList &here) +QuadtreeNode::candidates(QRectF rect, QList &here) { // nope if (!intersect(rect)) return 0; @@ -125,7 +125,7 @@ Quadtree::newnode(QPointF topleft, QPointF bottomright) return add; } -bool Quadtree::insert(QPointF point) +bool Quadtree::insert(GPointF point) { bool result= root->insert(this, point); if (result == false) { diff --git a/src/Core/Quadtree.h b/src/Core/Quadtree.h index ba9bf0879..981430795 100644 --- a/src/Core/Quadtree.h +++ b/src/Core/Quadtree.h @@ -23,6 +23,15 @@ #include #include +class GPointF : public QPointF +{ +public: + GPointF() : QPointF(), index(-1) {} + GPointF(double x, double y, int index) : QPointF(x,y), index(index) {} + + int index; +}; + class GenericPlot; class Quadtree; class QuadtreeNode @@ -40,17 +49,17 @@ class QuadtreeNode topleft(topleft), bottomright(bottomright), mid((topleft+bottomright)/2.0), leaf(true) {} // is the point in our space - when inserting - bool contains(QPointF p) { return ((p.x() >= topleft.x() && p.x() <= bottomright.x()) && + bool contains(GPointF p) { return ((p.x() >= topleft.x() && p.x() <= bottomright.x()) && p.y() >= topleft.y() && p.y() <= bottomright.y()); } // do we overlap with the search space - when looking bool intersect(QRectF r) { return r.intersects(QRectF(topleft,bottomright)); } // add a point - return false if not added - bool insert(Quadtree *root, QPointF value); + bool insert(Quadtree *root, GPointF value); // get candidates in same quadrant (might be miles away for big quadrant). - int candidates(QRectF,QList&tohere); + int candidates(QRectF,QList&tohere); protected: @@ -64,7 +73,7 @@ class QuadtreeNode QuadtreeNode *aabb[4]; // the points in this quadrant - QList contents; + QList contents; // if no children in aabb leaf==true bool leaf; @@ -79,10 +88,10 @@ class Quadtree ~Quadtree(); // add a point - returns false if not in range - bool insert(QPointF x); + bool insert(GPointF x); // find points in boundg rect, of course might be long way away... - int candidates(QRectF rect, QList&tohere) { return root->candidates(rect, tohere); } + int candidates(QRectF rect, QList&tohere) { return root->candidates(rect, tohere); } // manage the entire child tree on a single qvector to delete quickly QuadtreeNode *newnode(QPointF topleft, QPointF bottomright);