mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-04-15 05:32:21 +00:00
Add QChart to Python Chart (3f of 5)
Added hover code to line chart, so get legend values on hover and marker points as you mouse over the series and an x-axis label at the bottom to show current x value. Also added Utils::removeDP() to remove decimal places from a number string e.g. 24.000 becomes 24, 987.3440500 becomes 987.34405. This is a hack to avoid handling decimal places in the user settings, but will keep as reduces the amount of "digital ink" regardless.
This commit is contained in:
@@ -139,7 +139,7 @@ public:
|
|||||||
//! Layout attributes
|
//! Layout attributes
|
||||||
typedef QFlags<LayoutAttribute> LayoutAttributes;
|
typedef QFlags<LayoutAttribute> LayoutAttributes;
|
||||||
|
|
||||||
QwtText( const QString & = QString::null,
|
QwtText( const QString & = QString(),
|
||||||
TextFormat textFormat = AutoText );
|
TextFormat textFormat = AutoText );
|
||||||
QwtText( const QwtText & );
|
QwtText( const QwtText & );
|
||||||
~QwtText();
|
~QwtText();
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
#include "Colors.h"
|
#include "Colors.h"
|
||||||
#include "TabView.h"
|
#include "TabView.h"
|
||||||
#include "RideFileCommand.h"
|
#include "RideFileCommand.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
GenericPlot::GenericPlot(QWidget *parent, Context *context) : QWidget(parent), context(context)
|
GenericPlot::GenericPlot(QWidget *parent, Context *context) : QWidget(parent), context(context)
|
||||||
{
|
{
|
||||||
@@ -96,9 +99,9 @@ GenericLegendItem::GenericLegendItem(Context *context, QWidget *parent, QString
|
|||||||
void
|
void
|
||||||
GenericLegendItem::configChanged(qint32)
|
GenericLegendItem::configChanged(qint32)
|
||||||
{
|
{
|
||||||
static const double gl_margin = 6 * dpiXFactor;
|
static const double gl_margin = 3 * dpiXFactor;
|
||||||
static const double gl_spacer = 6 * dpiXFactor;
|
static const double gl_spacer = 3 * dpiXFactor;
|
||||||
static const double gl_block = 10 * dpiXFactor;
|
static const double gl_block = 7 * dpiXFactor;
|
||||||
static const double gl_linewidth = 1 * dpiXFactor;
|
static const double gl_linewidth = 1 * dpiXFactor;
|
||||||
|
|
||||||
// we just set geometry for now.
|
// we just set geometry for now.
|
||||||
@@ -121,7 +124,7 @@ GenericLegendItem::configChanged(qint32)
|
|||||||
|
|
||||||
// calculate all the rects used by the painter now since static
|
// calculate all the rects used by the painter now since static
|
||||||
blockrect = QRectF(gl_margin, gl_margin, gl_block, height-gl_margin);
|
blockrect = QRectF(gl_margin, gl_margin, gl_block, height-gl_margin);
|
||||||
linerect = QRectF(gl_margin, height-gl_linewidth, width-gl_margin, gl_linewidth);
|
linerect = QRectF(gl_margin+gl_block, height-gl_linewidth, width-gl_margin, gl_linewidth);
|
||||||
namerect = QRectF(gl_margin + gl_block + gl_spacer, gl_margin, fm.boundingRect(name).width(), fm.boundingRect(name).height());
|
namerect = QRectF(gl_margin + gl_block + gl_spacer, gl_margin, fm.boundingRect(name).width(), fm.boundingRect(name).height());
|
||||||
valuerect =QRectF(namerect.x() + namerect.width() + gl_spacer, gl_margin, fm.boundingRect(valuelabel).width(), fm.boundingRect(valuelabel).height());
|
valuerect =QRectF(namerect.x() + namerect.width() + gl_spacer, gl_margin, fm.boundingRect(valuelabel).width(), fm.boundingRect(valuelabel).height());
|
||||||
|
|
||||||
@@ -144,7 +147,7 @@ GenericLegendItem::paintEvent(QPaintEvent *)
|
|||||||
// block and line
|
// block and line
|
||||||
painter.setBrush(QBrush(color));
|
painter.setBrush(QBrush(color));
|
||||||
painter.setPen(Qt::NoPen);
|
painter.setPen(Qt::NoPen);
|
||||||
painter.drawRect(blockrect);
|
//painter.drawRect(blockrect);
|
||||||
painter.drawRect(linerect);
|
painter.drawRect(linerect);
|
||||||
|
|
||||||
// just paint the value for now
|
// just paint the value for now
|
||||||
@@ -152,6 +155,9 @@ GenericLegendItem::paintEvent(QPaintEvent *)
|
|||||||
if (hasvalue) string=QString("%1").arg(value, 0, 'f', 2);
|
if (hasvalue) string=QString("%1").arg(value, 0, 'f', 2);
|
||||||
else string=" ";
|
else string=" ";
|
||||||
|
|
||||||
|
// remove redundat dps (e.g. trailing zeroes)
|
||||||
|
string = Utils::removeDP(string);
|
||||||
|
|
||||||
// set pen to series color for now
|
// set pen to series color for now
|
||||||
painter.setPen(GCColor::invertColor(GColor(CPLOTBACKGROUND))); // use invert - usually black or white
|
painter.setPen(GCColor::invertColor(GColor(CPLOTBACKGROUND))); // use invert - usually black or white
|
||||||
painter.setFont(QFont());
|
painter.setFont(QFont());
|
||||||
@@ -160,6 +166,7 @@ GenericLegendItem::paintEvent(QPaintEvent *)
|
|||||||
painter.drawText(namerect, name, Qt::AlignHCenter|Qt::AlignVCenter);
|
painter.drawText(namerect, name, Qt::AlignHCenter|Qt::AlignVCenter);
|
||||||
painter.drawText(valuerect, string, Qt::AlignHCenter|Qt::AlignVCenter);
|
painter.drawText(valuerect, string, Qt::AlignHCenter|Qt::AlignVCenter);
|
||||||
painter.restore();
|
painter.restore();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GenericLegend::GenericLegend(Context *context, GenericPlot *plot) : context(context), plot(plot)
|
GenericLegend::GenericLegend(Context *context, GenericPlot *plot) : context(context), plot(plot)
|
||||||
@@ -244,12 +251,64 @@ void SelectionTool::paint(QPainter*painter, const QStyleOptionGraphicsItem *, QW
|
|||||||
painter->save();
|
painter->save();
|
||||||
painter->setClipRect(mapRectFromScene(host->qchart->plotArea()));
|
painter->setClipRect(mapRectFromScene(host->qchart->plotArea()));
|
||||||
|
|
||||||
|
// min max texts
|
||||||
|
QFont stGiles; // hoho - Chart Font St. Giles ... ok you have to be British to get this joke
|
||||||
|
stGiles.fromString(appsettings->value(NULL, GC_FONT_CHARTLABELS, QFont().toString()).toString());
|
||||||
|
stGiles.setPointSize(appsettings->value(NULL, GC_FONT_CHARTLABELS_SIZE, 8).toInt());
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case CIRCLE:
|
case CIRCLE:
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case XRANGE:
|
||||||
|
{
|
||||||
|
|
||||||
|
// current position for each series - we only do first, coz only interested in x axis anyway
|
||||||
|
foreach(QAbstractSeries *series, host->qchart->series()) {
|
||||||
|
|
||||||
|
// convert screen position to value for series
|
||||||
|
QPointF v = host->qchart->mapToValue(spos,series);
|
||||||
|
double miny=0;
|
||||||
|
foreach (QAbstractAxis *axis, series->attachedAxes()) {
|
||||||
|
if (axis->orientation() == Qt::Vertical && axis->type()==QAbstractAxis::AxisTypeValue) {
|
||||||
|
miny=static_cast<QValueAxis*>(axis)->min();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QPointF posxp = mapFromScene(host->qchart->mapToPosition(QPointF(v.x(),miny),series));
|
||||||
|
|
||||||
|
QPen markerpen(GColor(CPLOTMARKER));
|
||||||
|
painter->setPen(markerpen);
|
||||||
|
painter->setBrush(QBrush(GColor(CPLOTBACKGROUND)));
|
||||||
|
|
||||||
|
QFontMetrics fm(stGiles); // adjust position to align centre
|
||||||
|
painter->setFont(stGiles);
|
||||||
|
|
||||||
|
// x value
|
||||||
|
QString label=QString("%1").arg(v.x(),0,'f',0); // no decimal places XXX fixup on series info
|
||||||
|
label = Utils::removeDP(label); // remove unneccessary decimal places
|
||||||
|
painter->drawText(posxp-(QPointF(fm.tightBoundingRect(label).width()/2.0,4)), label);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the points we are hovering over
|
||||||
|
foreach(SeriesPoint p, hoverpoints) {
|
||||||
|
QPointF pos = mapFromScene(host->qchart->mapToPosition(p.xy,p.series));
|
||||||
|
QColor invert = GCColor::invertColor(GColor(CPLOTBACKGROUND));
|
||||||
|
painter->setBrush(invert);
|
||||||
|
painter->setPen(invert);
|
||||||
|
QRectF circle(0,0,5*dpiXFactor,5*dpiYFactor);
|
||||||
|
circle.moveCenter(pos);
|
||||||
|
painter->drawEllipse(circle);
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case RECTANGLE:
|
case RECTANGLE:
|
||||||
{
|
{
|
||||||
if (state == ACTIVE || state == INACTIVE) {
|
if (state == ACTIVE || state == INACTIVE) {
|
||||||
@@ -257,27 +316,24 @@ void SelectionTool::paint(QPainter*painter, const QStyleOptionGraphicsItem *, QW
|
|||||||
if (host->charttype == GC_CHART_LINE || host->charttype == GC_CHART_SCATTER) {
|
if (host->charttype == GC_CHART_LINE || host->charttype == GC_CHART_SCATTER) {
|
||||||
|
|
||||||
|
|
||||||
// min max texts
|
|
||||||
QFont stGiles; // hoho - Chart Font St. Giles ... ok you have to be British to get this joke
|
|
||||||
stGiles.fromString(appsettings->value(NULL, GC_FONT_CHARTLABELS, QFont().toString()).toString());
|
|
||||||
stGiles.setPointSize(appsettings->value(NULL, GC_FONT_CHARTLABELS_SIZE, 8).toInt());
|
|
||||||
painter->setFont(stGiles);
|
painter->setFont(stGiles);
|
||||||
|
|
||||||
|
// hovering around - draw label for current position in axis
|
||||||
|
if (hoverpoint != QPointF()) {
|
||||||
|
// draw a circle using marker color
|
||||||
|
QColor invert = GCColor::invertColor(GColor(CPLOTBACKGROUND));
|
||||||
|
painter->setBrush(invert);
|
||||||
|
painter->setPen(invert);
|
||||||
|
QRectF circle(0,0,10*dpiXFactor,10*dpiYFactor);
|
||||||
|
circle.moveCenter(hoverpoint);
|
||||||
|
painter->drawEllipse(circle);
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// current position for each series
|
// current position for each series
|
||||||
foreach(QAbstractSeries *series, host->qchart->series()) {
|
foreach(QAbstractSeries *series, host->qchart->series()) {
|
||||||
|
|
||||||
// hovering around - draw label for current position in axis
|
|
||||||
if (hoverpoint != QPointF()) {
|
|
||||||
// draw a circle using marker color
|
|
||||||
painter->setBrush(GColor(CPLOTMARKER));
|
|
||||||
painter->setPen(GColor(CPLOTMARKER));
|
|
||||||
QRectF circle(0,0,25,25);
|
|
||||||
circle.moveCenter(hoverpoint);
|
|
||||||
painter->drawEllipse(circle);
|
|
||||||
painter->setBrush(Qt::NoBrush);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert screen position to value for series
|
// convert screen position to value for series
|
||||||
QPointF v = host->qchart->mapToValue(spos,series);
|
QPointF v = host->qchart->mapToValue(spos,series);
|
||||||
@@ -293,6 +349,7 @@ void SelectionTool::paint(QPainter*painter, const QStyleOptionGraphicsItem *, QW
|
|||||||
|
|
||||||
// x value
|
// x value
|
||||||
QString label=QString("%1").arg(v.x(),0,'f',0); // no decimal places XXX fixup on series info
|
QString label=QString("%1").arg(v.x(),0,'f',0); // no decimal places XXX fixup on series info
|
||||||
|
label = Utils::removeDP(label); // remove unneccessary decimal places
|
||||||
painter->drawText(posxp-(QPointF(fm.tightBoundingRect(label).width()/2.0,4)), label);
|
painter->drawText(posxp-(QPointF(fm.tightBoundingRect(label).width()/2.0,4)), label);
|
||||||
|
|
||||||
if (series->type() == QAbstractSeries::SeriesTypeScatter) {
|
if (series->type() == QAbstractSeries::SeriesTypeScatter) {
|
||||||
@@ -305,14 +362,15 @@ void SelectionTool::paint(QPainter*painter, const QStyleOptionGraphicsItem *, QW
|
|||||||
|
|
||||||
// y value
|
// y value
|
||||||
label=QString("%1").arg(v.y(),0,'f',0); // no decimal places XXX fixup on series info
|
label=QString("%1").arg(v.y(),0,'f',0); // no decimal places XXX fixup on series info
|
||||||
|
label = Utils::removeDP(label); // remove unneccessary decimal places
|
||||||
painter->drawText(posyp+QPointF(0,fm.tightBoundingRect(label).height()/2.0), label);
|
painter->drawText(posyp+QPointF(0,fm.tightBoundingRect(label).height()/2.0), label);
|
||||||
|
|
||||||
// tell the legend or whoever else is listening
|
// tell the legend or whoever else is listening
|
||||||
//fprintf(stderr,"cursor (%f,%f) @(%f,%f) for series %s\n", spos.x(), spos.y(),v.x(),v.y(),series->name().toStdString().c_str()); fflush(stderr);
|
//fprintf(stderr,"cursor (%f,%f) @(%f,%f) for series %s\n", spos.x(), spos.y(),v.x(),v.y(),series->name().toStdString().c_str()); fflush(stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state != INACTIVE) {
|
if (state != INACTIVE) {
|
||||||
|
|
||||||
// there is a rectangle to draw on the screen
|
// there is a rectangle to draw on the screen
|
||||||
@@ -444,6 +502,7 @@ SelectionTool::reset()
|
|||||||
rect = QRectF(0,0,0,0);
|
rect = QRectF(0,0,0,0);
|
||||||
hoverpoint = QPointF();
|
hoverpoint = QPointF();
|
||||||
hoverseries = NULL;
|
hoverseries = NULL;
|
||||||
|
hoverpoints.clear();
|
||||||
resetSelections();
|
resetSelections();
|
||||||
update();
|
update();
|
||||||
return true;
|
return true;
|
||||||
@@ -453,37 +512,39 @@ SelectionTool::reset()
|
|||||||
bool
|
bool
|
||||||
SelectionTool::clicked(QPointF pos)
|
SelectionTool::clicked(QPointF pos)
|
||||||
{
|
{
|
||||||
if (state==ACTIVE && sceneBoundingRect().contains(pos)) {
|
if (mode == RECTANGLE) {
|
||||||
|
if (state==ACTIVE && sceneBoundingRect().contains(pos)) {
|
||||||
|
|
||||||
// are we moving?
|
// are we moving?
|
||||||
state = MOVING;
|
state = MOVING;
|
||||||
start = pos;
|
start = pos;
|
||||||
startingpos = this->pos();
|
startingpos = this->pos();
|
||||||
update(rect);
|
update(rect);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// initial sizing - or click hold to drag?
|
// initial sizing - or click hold to drag?
|
||||||
state = SIZING;
|
state = SIZING;
|
||||||
start = pos;
|
start = pos;
|
||||||
finish = QPointF(0,0);
|
finish = QPointF(0,0);
|
||||||
rect = QRectF(-5,-5,5,5);
|
rect = QRectF(-5,-5,5,5);
|
||||||
setPos(start);
|
setPos(start);
|
||||||
|
|
||||||
// time 400ms to drag - after lots of playing
|
// time 400ms to drag - after lots of playing
|
||||||
// this seems a reasonable time period that isnt
|
// this seems a reasonable time period that isnt
|
||||||
// too long but doesn't conflict with straight
|
// too long but doesn't conflict with straight
|
||||||
// forward click to select.
|
// forward click to select.
|
||||||
// its probably no coincidence that this is also
|
// its probably no coincidence that this is also
|
||||||
// the Doherty Threshold for UX
|
// the Doherty Threshold for UX
|
||||||
drag.setInterval(400);
|
drag.setInterval(400);
|
||||||
drag.setSingleShot(true);
|
drag.setSingleShot(true);
|
||||||
drag.start();
|
drag.start();
|
||||||
|
|
||||||
update(rect);
|
update(rect);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -491,27 +552,30 @@ SelectionTool::clicked(QPointF pos)
|
|||||||
bool
|
bool
|
||||||
SelectionTool::released(QPointF)
|
SelectionTool::released(QPointF)
|
||||||
{
|
{
|
||||||
// width and heights can be negative if dragged in reverse
|
if (mode == RECTANGLE) {
|
||||||
if (state == DRAGGING) {
|
|
||||||
|
|
||||||
host->setCursor(Qt::ArrowCursor);
|
// width and heights can be negative if dragged in reverse
|
||||||
state = INACTIVE;
|
if (state == DRAGGING) {
|
||||||
rect = QRectF(0,0,0,0);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else if (rect.width() < 10 && rect.width() > -10 && rect.height() < 10 && rect.height() > -10) {
|
host->setCursor(Qt::ArrowCursor);
|
||||||
|
state = INACTIVE;
|
||||||
|
rect = QRectF(0,0,0,0);
|
||||||
|
return true;
|
||||||
|
|
||||||
// tiny, as in click release - deactivate
|
} else if (rect.width() < 10 && rect.width() > -10 && rect.height() < 10 && rect.height() > -10) {
|
||||||
state = INACTIVE; // reset for any state
|
|
||||||
rect = QRectF(0,0,0,0);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else if (state == SIZING || state == MOVING) {
|
// tiny, as in click release - deactivate
|
||||||
|
state = INACTIVE; // reset for any state
|
||||||
|
rect = QRectF(0,0,0,0);
|
||||||
|
return true;
|
||||||
|
|
||||||
// finishing move/resize
|
} else if (state == SIZING || state == MOVING) {
|
||||||
state = ACTIVE;
|
|
||||||
update(rect);
|
// finishing move/resize
|
||||||
return true;
|
state = ACTIVE;
|
||||||
|
update(rect);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -519,102 +583,166 @@ SelectionTool::released(QPointF)
|
|||||||
void
|
void
|
||||||
SelectionTool::dragStart()
|
SelectionTool::dragStart()
|
||||||
{
|
{
|
||||||
// check still right state for it?
|
if (mode == RECTANGLE) {
|
||||||
if (state == SIZING) {
|
// check still right state for it?
|
||||||
fprintf(stderr, "drag mode!\n"); fflush(stderr);
|
if (state == SIZING) {
|
||||||
host->setCursor(Qt::ClosedHandCursor);
|
fprintf(stderr, "drag mode!\n"); fflush(stderr);
|
||||||
state = DRAGGING;
|
host->setCursor(Qt::ClosedHandCursor);
|
||||||
|
state = DRAGGING;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for std::lower_bound search of x QPointF value
|
||||||
|
struct CompareQPointFX {
|
||||||
|
bool operator()(const QPointF p1, const QPointF p2) {
|
||||||
|
return p1.x() < p2.x();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
SelectionTool::moved(QPointF pos)
|
SelectionTool::moved(QPointF pos)
|
||||||
{
|
{
|
||||||
// only care if we are sizing
|
if (mode == RECTANGLE) {
|
||||||
if (state == SIZING) {
|
|
||||||
|
|
||||||
// cancel the timer to trigger drag
|
// user hovers over points, but can select using a rectangle
|
||||||
drag.stop();
|
|
||||||
|
|
||||||
// work out where we got to
|
if (state == SIZING) {
|
||||||
finish = pos;
|
|
||||||
|
|
||||||
// reshape - rect might have negative sizes if sized backwards
|
// cancel the timer to trigger drag
|
||||||
rect = QRectF(QPointF(0,0), finish-start);
|
drag.stop();
|
||||||
update(rect);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else if (state == MOVING) {
|
// work out where we got to
|
||||||
|
finish = pos;
|
||||||
|
|
||||||
QPointF delta = pos - start;
|
// reshape - rect might have negative sizes if sized backwards
|
||||||
setPos(this->startingpos + delta);
|
rect = QRectF(QPointF(0,0), finish-start);
|
||||||
update(rect);
|
update(rect);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else if (state == DRAGGING) {
|
} else if (state == MOVING) {
|
||||||
|
|
||||||
//QPointF delta = pos - start;
|
QPointF delta = pos - start;
|
||||||
// move axis to reflect new pos...
|
setPos(this->startingpos + delta);
|
||||||
return true;
|
update(rect);
|
||||||
|
return true;
|
||||||
|
|
||||||
} else {
|
} else if (state == DRAGGING) {
|
||||||
|
|
||||||
// remember screen pos of cursor for tracking values
|
//QPointF delta = pos - start;
|
||||||
// when painting on the axis/plot area
|
// move axis to reflect new pos...
|
||||||
spos = pos;
|
return true;
|
||||||
|
|
||||||
// not moving or sizing so just hovering
|
} else {
|
||||||
// look for nearest point for each series
|
|
||||||
// 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
|
|
||||||
QAbstractSeries *originalhoverseries = hoverseries;
|
|
||||||
hoverseries = NULL;
|
|
||||||
foreach(QAbstractSeries *series, host->qchart->series()) {
|
|
||||||
|
|
||||||
Quadtree *tree= host->quadtrees.value(series,NULL);
|
// remember screen pos of cursor for tracking values
|
||||||
if (tree != NULL) {
|
// when painting on the axis/plot area
|
||||||
|
spos = pos;
|
||||||
|
|
||||||
// lets convert cursor pos to value pos to find nearest
|
// not moving or sizing so just hovering
|
||||||
double pixels = 10 * dpiXFactor; // within 10 pixels
|
// look for nearest point for each series
|
||||||
QRectF srect(pos-QPointF(pixels,pixels), pos+QPointF(pixels,pixels));
|
// this needs to be super quick as mouse
|
||||||
QRectF vrect(host->qchart->mapToValue(srect.topLeft(),series), host->qchart->mapToValue(srect.bottomRight(),series));
|
// movements are very fast, so we use a
|
||||||
//QPointF vpos = host->qchart->mapToValue(pos, series);
|
// quadtree to find the nearest points
|
||||||
|
QPointF hoverv; // value // series x,y co-ord used in signal (and legend later)
|
||||||
|
hoverpoint = QPointF(); // screen coordinates
|
||||||
|
QAbstractSeries *originalhoverseries = hoverseries;
|
||||||
|
hoverseries = NULL;
|
||||||
|
foreach(QAbstractSeries *series, host->qchart->series()) {
|
||||||
|
|
||||||
// find candidates all close by using paint co-ords
|
Quadtree *tree= host->quadtrees.value(series,NULL);
|
||||||
QList<QPointF> tohere;
|
if (tree != NULL) {
|
||||||
tree->candidates(vrect, tohere);
|
|
||||||
|
|
||||||
QPointF cursorpos=mapFromScene(pos);
|
// lets convert cursor pos to value pos to find nearest
|
||||||
foreach(QPointF p, tohere) {
|
double pixels = 10 * dpiXFactor; // within 10 pixels
|
||||||
QPointF scpos = mapFromScene(host->qchart->mapToPosition(p, series));
|
QRectF srect(pos-QPointF(pixels,pixels), pos+QPointF(pixels,pixels));
|
||||||
if (hoverpoint == QPointF()) {
|
QRectF vrect(host->qchart->mapToValue(srect.topLeft(),series), host->qchart->mapToValue(srect.bottomRight(),series));
|
||||||
hoverpoint = scpos;
|
//QPointF vpos = host->qchart->mapToValue(pos, series);
|
||||||
hoverseries = series;
|
|
||||||
hoverv = p;
|
// find candidates all close by using paint co-ords
|
||||||
} else if ((cursorpos-scpos).manhattanLength() < (cursorpos-hoverpoint).manhattanLength()) {
|
QList<QPointF> tohere;
|
||||||
hoverpoint=scpos; // not happy with this XXX needs more work
|
tree->candidates(vrect, tohere);
|
||||||
hoverseries = series;
|
|
||||||
hoverv = p;
|
QPointF cursorpos=mapFromScene(pos);
|
||||||
|
foreach(QPointF p, tohere) {
|
||||||
|
QPointF scpos = mapFromScene(host->qchart->mapToPosition(p, series));
|
||||||
|
if (hoverpoint == QPointF()) {
|
||||||
|
hoverpoint = scpos;
|
||||||
|
hoverseries = series;
|
||||||
|
hoverv = p;
|
||||||
|
} else if ((cursorpos-scpos).manhattanLength() < (cursorpos-hoverpoint).manhattanLength()) {
|
||||||
|
hoverpoint=scpos; // not happy with this XXX needs more work
|
||||||
|
hoverseries = series;
|
||||||
|
hoverv = p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if (tohere.count()) fprintf(stderr, "HOVER %d candidates nearby\n", tohere.count()); fflush(stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (tohere.count()) fprintf(stderr, "HOVER %d candidates nearby\n", tohere.count()); fflush(stderr);
|
}
|
||||||
|
|
||||||
|
// hoverpoint changed - either a new series selected, a new point, or no point at all
|
||||||
|
if (originalhoverseries != hoverseries || hoverv != QPointF()) {
|
||||||
|
if (hoverseries != originalhoverseries && originalhoverseries != NULL) emit (unhover(originalhoverseries->name())); // old hover changed
|
||||||
|
if (hoverseries != NULL) emit hover(hoverv, hoverseries->name(), hoverseries); // new hover changed
|
||||||
|
}
|
||||||
|
|
||||||
|
// for mouse moves..
|
||||||
|
update(rect);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// END OF RECTANLGE MODE
|
||||||
|
} else if (mode == XRANGE) {
|
||||||
|
|
||||||
|
// xxx just hover for now, will do sizing shortly
|
||||||
|
// 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<QAbstractSeries*,QPointF> vals; // what values were found
|
||||||
|
double nearestx=-9999;
|
||||||
|
foreach(QAbstractSeries *series, host->qchart->series()) {
|
||||||
|
|
||||||
|
// get x value to search
|
||||||
|
double xvalue=host->qchart->mapToValue(spos,series).x();
|
||||||
|
|
||||||
|
// pointsVector
|
||||||
|
if (series->type() == QAbstractSeries::SeriesTypeLine) {
|
||||||
|
|
||||||
|
// we take a copy, would love to avoid this.
|
||||||
|
QVector<QPointF> p = static_cast<QLineSeries*>(series)->pointsVector();
|
||||||
|
|
||||||
|
// value we want
|
||||||
|
QPointF x= QPointF(xvalue,0);
|
||||||
|
|
||||||
|
// lower_bound to value near x
|
||||||
|
QVector<QPointF>::const_iterator i = std::lower_bound(p.begin(), p.end(), x, CompareQPointFX());
|
||||||
|
|
||||||
|
// collect them away
|
||||||
|
vals.insert(series, QPointF(*i));
|
||||||
|
|
||||||
|
// nearest x?
|
||||||
|
if (nearestx == -9999 || (i->x()-xvalue) < (nearestx-xvalue)) nearestx = i->x();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hoverpoint changed - either a new series selected, a new point, or no point at all
|
// run over what we found, updating paint points and signal (for legend)
|
||||||
if (originalhoverseries != hoverseries || hoverv != QPointF()) {
|
hoverpoints.clear();
|
||||||
if (hoverseries != originalhoverseries && originalhoverseries != NULL) emit (unhover(originalhoverseries->name())); // old hover changed
|
QMapIterator<QAbstractSeries*, QPointF> i(vals);
|
||||||
if (hoverseries != NULL) emit hover(hoverv, hoverseries->name(), hoverseries); // new hover changed
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
if (i.value().x() == nearestx) {
|
||||||
|
SeriesPoint add;
|
||||||
|
add.series = i.key();
|
||||||
|
add.xy = i.value();
|
||||||
|
emit hover(i.value(), i.key()->name(), i.key());
|
||||||
|
if (add.xy.y()) hoverpoints << add; // ignore zeroes
|
||||||
|
} else emit unhover(i.key()->name());
|
||||||
}
|
}
|
||||||
|
if (vals.count()) return true;
|
||||||
// for mouse moves..
|
|
||||||
update(rect);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -622,14 +750,17 @@ SelectionTool::moved(QPointF pos)
|
|||||||
bool
|
bool
|
||||||
SelectionTool::wheel(int delta)
|
SelectionTool::wheel(int delta)
|
||||||
{
|
{
|
||||||
// mouse wheel resizes selection rect if it is active
|
|
||||||
if (state == ACTIVE) {
|
if (mode == RECTANGLE) {
|
||||||
if (delta < 0) {
|
// mouse wheel resizes selection rect if it is active
|
||||||
rect.setSize(rect.size() * 0.9);
|
if (state == ACTIVE) {
|
||||||
} else {
|
if (delta < 0) {
|
||||||
rect.setSize(rect.size() * 1.1);
|
rect.setSize(rect.size() * 0.9);
|
||||||
|
} else {
|
||||||
|
rect.setSize(rect.size() * 1.1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -904,7 +1035,7 @@ SelectionTool::updateScene()
|
|||||||
QPointF point = scatter->at(i); // avoid deep copy
|
QPointF point = scatter->at(i); // avoid deep copy
|
||||||
if (point.y() >= miny && point.y() <= maxy &&
|
if (point.y() >= miny && point.y() <= maxy &&
|
||||||
point.x() >= minx && point.x() <= maxx) {
|
point.x() >= minx && point.x() <= maxx) {
|
||||||
points << point;
|
if (!points.contains(point)) points << point; // avoid dupes
|
||||||
calc.addPoint(point);
|
calc.addPoint(point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1048,6 +1179,10 @@ GenericPlot::initialiseChart(QString title, int type, bool animate)
|
|||||||
// by default they are disabled anyway
|
// by default they are disabled anyway
|
||||||
qchart->setAnimationOptions(animate ? QChart::SeriesAnimations : QChart::NoAnimation);
|
qchart->setAnimationOptions(animate ? QChart::SeriesAnimations : QChart::NoAnimation);
|
||||||
|
|
||||||
|
// what kind of selector do we use?
|
||||||
|
if (charttype==GC_CHART_LINE) selector->setMode(SelectionTool::XRANGE);
|
||||||
|
else selector->setMode(SelectionTool::RECTANGLE);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,12 @@ class Calculator
|
|||||||
QAbstractSeries *series;
|
QAbstractSeries *series;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// hover points etc
|
||||||
|
struct SeriesPoint {
|
||||||
|
QAbstractSeries *series; // series this is a point for
|
||||||
|
QPointF xy; // the actual xy value (not screen position)
|
||||||
|
};
|
||||||
|
|
||||||
// for watcing scene events
|
// for watcing scene events
|
||||||
class SelectionTool : public QObject, public QGraphicsItem
|
class SelectionTool : public QObject, public QGraphicsItem
|
||||||
{
|
{
|
||||||
@@ -193,8 +199,13 @@ class SelectionTool : public QObject, public QGraphicsItem
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
SelectionTool(GenericPlot *);
|
SelectionTool(GenericPlot *);
|
||||||
enum { INACTIVE, SIZING, MOVING, DRAGGING, ACTIVE } state; // what state are we in?
|
enum stateType { INACTIVE, SIZING, MOVING, DRAGGING, ACTIVE } state; // what state are we in?
|
||||||
enum { RECTANGLE, LASSOO, CIRCLE } mode; // what mode are we in?
|
enum modeType { RECTANGLE, XRANGE, LASSOO, CIRCLE } mode; // what mode are we in?
|
||||||
|
typedef modeType SelectionMode;
|
||||||
|
typedef stateType SelectionState;
|
||||||
|
|
||||||
|
// set mode
|
||||||
|
void setMode(SelectionMode mode) { this->mode=mode; }
|
||||||
|
|
||||||
// is invisible and tiny. we are just an observer
|
// is invisible and tiny. we are just an observer
|
||||||
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event);
|
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event);
|
||||||
@@ -235,8 +246,12 @@ class SelectionTool : public QObject, public QGraphicsItem
|
|||||||
GenericPlot *host;
|
GenericPlot *host;
|
||||||
QPointF start, startingpos, finish; // when calculating distances during transitions
|
QPointF start, startingpos, finish; // when calculating distances during transitions
|
||||||
QPointF spos; // last point we saw
|
QPointF spos; // last point we saw
|
||||||
|
|
||||||
|
// scatter does this xxx TODO refactor into hoverpoints
|
||||||
QPointF hoverpoint;
|
QPointF hoverpoint;
|
||||||
QAbstractSeries *hoverseries;
|
QAbstractSeries *hoverseries;
|
||||||
|
// line plot uses this
|
||||||
|
QList<SeriesPoint> hoverpoints;
|
||||||
|
|
||||||
// selections from original during selection
|
// selections from original during selection
|
||||||
QMap<QAbstractSeries*, QAbstractSeries*> selections;
|
QMap<QAbstractSeries*, QAbstractSeries*> selections;
|
||||||
|
|||||||
@@ -253,5 +253,25 @@ searchPath(QString path, QString binary, bool isexec)
|
|||||||
return returning;
|
return returning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString
|
||||||
|
removeDP(QString in)
|
||||||
|
{
|
||||||
|
QString out;
|
||||||
|
if (in.contains('.')) {
|
||||||
|
|
||||||
|
int n=in.indexOf('.');
|
||||||
|
out += in.mid(0,n);
|
||||||
|
int i=in.length()-1;
|
||||||
|
for(; in[i] != '.'; i--)
|
||||||
|
if (in[i] != '0')
|
||||||
|
break;
|
||||||
|
if (in[i]=='.') return out;
|
||||||
|
else out += in.mid(n, i-n+1);
|
||||||
|
return out;
|
||||||
|
} else {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ namespace Utils
|
|||||||
QString jsonprotect(const QString &buffer);
|
QString jsonprotect(const QString &buffer);
|
||||||
QString jsonunprotect(const QString &buffer);
|
QString jsonunprotect(const QString &buffer);
|
||||||
QStringList searchPath(QString path, QString binary, bool isexec=true);
|
QStringList searchPath(QString path, QString binary, bool isexec=true);
|
||||||
|
QString removeDP(QString);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user