mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
121
src/Core/Quadtree.cpp
Normal 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
91
src/Core/Quadtree.h
Normal 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
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user