Add QChart to Python Chart (3c of 5)

Added Quadtree for identifying points on the plot when
hovering with mouse, but should also be reused for the
rect selection tool (it currently iterates over all points
for a series).

This commit just adds basic algorithm will need to follow
up with a) refactor it into the selection tool and
b) display the hover value somewhere (legend?)
This commit is contained in:
Mark Liversedge
2020-02-17 22:47:33 +00:00
parent 6d3811f981
commit 6fb3baae95
5 changed files with 255 additions and 2 deletions

View File

@@ -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<QPointF> 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<double> xseries, QVector<double> 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; i<xseries.size() && i<yseries.size(); i++) {
add->append(xseries.at(i), yseries.at(i));
@@ -826,8 +849,22 @@ GenericPlot::addCurve(QString name, QVector<double> xseries, QVector<double> 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; i<xseries.size() && i<yseries.size(); i++)
if (xseries.at(i) != 0 && yseries.at(i) != 0) // 0,0 is common and lets ignore (usually means no data)
tree->insert(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

View File

@@ -30,6 +30,7 @@
#include <string.h>
#include <QtCharts>
#include <QGraphicsItem>
#include "Quadtree.h"
#include "GoldenCheetah.h"
#include "Settings.h"
@@ -221,6 +222,9 @@ class GenericPlot : public QWidget {
// curves
QMap<QString, QAbstractSeries *>curves;
// quadtrees
QMap<QAbstractSeries*, Quadtree*> quadtrees;
// axes
QMap<QString, AxisInfo *>axisinfos;

121
src/Core/Quadtree.cpp Normal file
View File

@@ -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<QPointF> &here)
{
// nope
if (!intersect(rect)) return 0;
if (leaf) {
// lemme see if any of mine match
int found=0;
for(int i=0; i<contents.count(); i++) {
if (rect.contains(contents.at(i))) {
here.append(contents.at(i));
found++;
}
}
return found;
} else {
// recurse through my children
int count=0;
for (int i=0; i<4; i++)
count += aabb[i]->candidates(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; i<contents.count(); i++)
for(int j=0; j<4; j++)
aabb[j]->insert(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; i<nodes.count(); i++) delete nodes.at(i);
nodes.clear();
delete root;
root = new QuadtreeNode(topleft,bottomright);
}
Quadtree::~Quadtree()
{
// zap the nodes
for(int i=0; i<nodes.count(); i++) delete nodes.at(i);
nodes.clear();
delete root;
}
QuadtreeNode *
Quadtree::newnode(QPointF topleft, QPointF bottomright)
{
QuadtreeNode *add = new QuadtreeNode(topleft, bottomright);
nodes.append(add);
return add;
}
void Quadtree::insert(QPointF point)
{
root->insert(this, point);
}

91
src/Core/Quadtree.h Normal file
View File

@@ -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 <QPointF>
#include <QList>
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<QPointF>&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<QPointF> 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<QPointF>&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<QuadtreeNode *> nodes;
protected:
QuadtreeNode *root;
};
#endif

View File

@@ -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 \