diff --git a/src/Charts/GenericPlot.cpp b/src/Charts/GenericPlot.cpp index 5887b5f9c..386de7950 100644 --- a/src/Charts/GenericPlot.cpp +++ b/src/Charts/GenericPlot.cpp @@ -440,7 +440,26 @@ GenericPlot::eventHandler(int source, void *obj, QEvent *e) //fprintf(stderr,"POS: %g:%g | %d:%d\n", spos.x(), spos.y(), wpos.x(), wpos.y()); //fprintf(stderr,"%s: mouse MOVE for obj=%u\n", source ? "widget" : "scene", (void*)obj); fflush(stderr); { + // see if selection tool cares about new mouse position updatescene = selector->moved(spos); + + // XXX look for nearest point for each series + // XXX will need to refactor into selection tool and reuse for id selected items there + foreach(QAbstractSeries *series, qchart->series()) { + Quadtree *tree= quadtrees.value(series,NULL); + if (tree != NULL) { + // lets convert cursor pos to value pos to find nearest + double pixels = 10 * dpiXFactor; // within 10 pixels + QRectF srect(spos-QPointF(pixels,pixels), spos+QPointF(pixels,pixels)); + QRectF vrect(qchart->mapToValue(srect.topLeft(),series), qchart->mapToValue(srect.bottomRight(),series)); + QPointF vpos = qchart->mapToValue(spos, series); + + // find a candidate + QList tohere; + tree->candidates(vrect, tohere); + if (tohere.count()) fprintf(stderr, "hover %d candidates\n", tohere.count()); fflush(stderr); + } + } } break; @@ -694,6 +713,9 @@ GenericPlot::initialiseChart(QString title, int type, bool animate) barseries=NULL; } + foreach(Quadtree *tree, quadtrees) delete tree; + quadtrees.clear(); + foreach(AxisInfo *axisinfo, axisinfos) delete axisinfo; axisinfos.clear(); @@ -819,6 +841,7 @@ GenericPlot::addCurve(QString name, QVector xseries, QVector yse add->setOpacity(double(opacity) / 100.0); // 0-100% to 0.0-1.0 values // data + Calculator calc; // watching as we add for (int i=0; iappend(xseries.at(i), yseries.at(i)); @@ -826,8 +849,22 @@ GenericPlot::addCurve(QString name, QVector xseries, QVector yse xaxis->point(xseries.at(i), yseries.at(i)); yaxis->point(xseries.at(i), yseries.at(i)); + // calculate stuff + calc.addPoint(QPointF(xseries.at(i), yseries.at(i))); + } + QTime stopwatch; + stopwatch.start(); + // set the quadtree up - now we know the ranges... + 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))); + fprintf(stderr, "quadtree creation took %u ms for %u records\n", stopwatch.elapsed(), calc.count); fflush(stderr); + fprintf(stderr, "quadtree created %u nodes\n", tree->nodes.count()); fflush(stderr); + if (tree->nodes.count()) quadtrees.insert(add, tree); + // hardware support? chartview->setRenderHint(QPainter::Antialiasing); add->setUseOpenGL(opengl); // for scatter or line only apparently diff --git a/src/Charts/GenericPlot.h b/src/Charts/GenericPlot.h index 586da330b..843aefa44 100644 --- a/src/Charts/GenericPlot.h +++ b/src/Charts/GenericPlot.h @@ -30,6 +30,7 @@ #include #include #include +#include "Quadtree.h" #include "GoldenCheetah.h" #include "Settings.h" @@ -221,6 +222,9 @@ class GenericPlot : public QWidget { // curves QMapcurves; + // quadtrees + QMap quadtrees; + // axes QMapaxisinfos; diff --git a/src/Core/Quadtree.cpp b/src/Core/Quadtree.cpp new file mode 100644 index 000000000..25843c14f --- /dev/null +++ b/src/Core/Quadtree.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020 Mark Liversedge (liversedge@gmail.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "Quadtree.h" + +// add a node +void +QuadtreeNode::insert(Quadtree *root, QPointF value) +{ + if (!contains(value)) return; + + // here will do + if (leaf && contents.count() < maxentries) { + if (!contents.contains(value)) contents.append(value); // de dupe + return; + } + + // full, so split out + if (leaf) split(root); + + // find me a home below + for(int i=0; i<4; i++) aabb[i]->insert(root, value); +} + +// get candidates +int +QuadtreeNode::candidates(QRectF rect, QList &here) +{ + // nope + if (!intersect(rect)) return 0; + + if (leaf) { + + // lemme see if any of mine match + int found=0; + for(int i=0; icandidates(rect, here); + return count; + } +} + +// split leaf into nodes (when to many entries) +void +QuadtreeNode::split(Quadtree *root) +{ + leaf=false; + aabb[0]=root->newnode(topleft, mid); + aabb[1]=root->newnode(QPointF(mid.x(),topleft.y()), QPointF(bottomright.x(), mid.y())); + aabb[2]=root->newnode(QPointF(topleft.x(),mid.y()), QPointF(mid.x(), bottomright.y())); + aabb[3]=root->newnode(mid,bottomright); + + // find them homes and clear here + for(int i=0; iinsert(root, contents.at(i)); + contents.clear(); + +} + +Quadtree::Quadtree(QPointF topleft, QPointF bottomright) +{ + root = new QuadtreeNode(topleft, bottomright); +} + +// manage the entire child tree on a single qvector to delete quickly +void +Quadtree::reset (QPointF topleft, QPointF bottomright) +{ + for(int i=0; iinsert(this, point); +} diff --git a/src/Core/Quadtree.h b/src/Core/Quadtree.h new file mode 100644 index 000000000..b3b866c7b --- /dev/null +++ b/src/Core/Quadtree.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020 Mark Liversedge (liversedge@gmail.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GC_Quadtree_h +#define _GC_Quadtree_h 1 + +#include +#include + +class Quadtree; +class QuadtreeNode +{ + static const int maxdepth=12; + static const int maxentries=25; + + public: + + // constructor makes an empty leaf + QuadtreeNode(QPointF topleft, QPointF bottomright) : + 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()) && + 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 node + void insert(Quadtree *root, QPointF value); + + // get candidates in same quadrant (might be miles away for big quadrant). + int candidates(QRectF,QList&tohere); + + protected: + + // split leaf into nodes (when to many entries) + void split(Quadtree *root); + + private: + // AABB - index into nodes on Quadtree class. + QuadtreeNode *aabb[4]; + + // the points in this quadrant + QList contents; + + // geom of quadrant + QPointF topleft, mid, bottomright; + + // if no children in aabb leaf==true + bool leaf; +}; + +class Quadtree +{ + public: + Quadtree (QPointF topleft, QPointF bottomright); + ~Quadtree(); + + // add a point + void insert(QPointF x); + + // find points in same quadrant as cursor, of course might be long way away... + int candidates(QRectF point, QList&tohere) { return root->candidates(point, tohere); } + + // manage the entire child tree on a single qvector to delete quickly + QuadtreeNode *newnode(QPointF topleft, QPointF bottomright); + void reset (QPointF topleft, QPointF bottomright); + QVector nodes; + + protected: + QuadtreeNode *root; +}; + + +#endif diff --git a/src/src.pro b/src/src.pro index 81c588297..cbb508a92 100644 --- a/src/src.pro +++ b/src/src.pro @@ -725,7 +725,7 @@ HEADERS += Core/Athlete.h Core/Context.h Core/DataFilter.h Core/FreeSearch.h Cor Core/IdleTimer.h Core/IntervalItem.h Core/NamedSearch.h Core/RideCache.h Core/RideCacheModel.h Core/RideDB.h \ Core/RideItem.h Core/Route.h Core/RouteParser.h Core/Season.h Core/SeasonParser.h Core/Secrets.h Core/Settings.h \ Core/Specification.h Core/TimeUtils.h Core/Units.h Core/UserData.h Core/Utils.h \ - Core/Measures.h Core/BodyMeasures.h Core/HrvMeasures.h Core/BlinnSolver.h + Core/Measures.h Core/BodyMeasures.h Core/HrvMeasures.h Core/BlinnSolver.h Core\Quadtree.h # device and file IO or edit HEADERS += FileIO/ArchiveFile.h FileIO/AthleteBackup.h FileIO/Bin2RideFile.h FileIO/BinRideFile.h \ @@ -819,7 +819,7 @@ SOURCES += Core/Athlete.cpp Core/Context.cpp Core/DataFilter.cpp Core/FreeSearch Core/IntervalItem.cpp Core/main.cpp Core/NamedSearch.cpp Core/RideCache.cpp Core/RideCacheModel.cpp Core/RideItem.cpp \ Core/Route.cpp Core/RouteParser.cpp Core/Season.cpp Core/SeasonParser.cpp Core/Settings.cpp Core/Specification.cpp \ Core/TimeUtils.cpp Core/Units.cpp Core/UserData.cpp Core/Utils.cpp \ - Core/Measures.cpp Core/BodyMeasures.cpp Core/HrvMeasures.cpp Core/BlinnSolver.cpp + Core/Measures.cpp Core/BodyMeasures.cpp Core/HrvMeasures.cpp Core/BlinnSolver.cpp Core\Quadtree.cpp ## File and Device IO and Editing SOURCES += FileIO/ArchiveFile.cpp FileIO/AthleteBackup.cpp FileIO/Bin2RideFile.cpp FileIO/BinRideFile.cpp \