diff --git a/src/Charts/GenericChart.h b/src/Charts/GenericChart.h index 3bc33fedc2..47ddceb8c7 100644 --- a/src/Charts/GenericChart.h +++ b/src/Charts/GenericChart.h @@ -32,6 +32,31 @@ #include +// a chart annotation +class GenericAnnotationInfo { + + enum annotationType { None, Label, VLine, HLine, Voronoi }; + typedef annotationType AnnotationType; + + public: + + GenericAnnotationInfo(AnnotationType); + + AnnotationType type; + + // labels + QList labels; + + // voronoi + QString vname; + QVector vx, vy; //voronoi digram + + // vline and hline + Qt::PenStyle linestyle; + double value; + QString text; +}; + // keeping track of the series info class GenericSeriesInfo { @@ -79,6 +104,8 @@ class GenericSeriesInfo { bool fill; RideMetric::MetricType aggregateby; + QList annotations; + QList annotateLabels; // label annotation QVector voronoix, voronoiy; // voronoi diagram annotation }; diff --git a/src/Charts/GenericPlot.h b/src/Charts/GenericPlot.h index 191d3ac4ba..92c5ef49da 100644 --- a/src/Charts/GenericPlot.h +++ b/src/Charts/GenericPlot.h @@ -59,6 +59,7 @@ class GenericPlot; class GenericLegend; class GenericSelectTool; class GenericAxisInfo; +class GenericAnnotationInfo; // the chart class ChartSpace; diff --git a/src/Charts/UserChart.h b/src/Charts/UserChart.h index a1b9d40734..0060a287c6 100644 --- a/src/Charts/UserChart.h +++ b/src/Charts/UserChart.h @@ -93,7 +93,7 @@ class UserChart : public QWidget { // the actual config GenericChartInfo chartinfo; - QList seriesinfo; + QList seriesinfo; // also contain annotations. QList axisinfo; // annotations generated by the program (not the user) diff --git a/src/Core/DataFilter.cpp b/src/Core/DataFilter.cpp index 7453c93593..22267fd37b 100644 --- a/src/Core/DataFilter.cpp +++ b/src/Core/DataFilter.cpp @@ -213,10 +213,11 @@ static struct { // grammar does not support (a*x>1), instead we can use a*bool(x>1). All non // zero expressions will evaluate to 1. - { "annotate", 0 }, // annotate(type, parms) - add an annotation to the chart - // current supported annotations: + { "annotate", 0 }, // current supported annotations: // annotate(label, string1, string2 .. stringn) - adds label at top of a chart // annotate(voronoi, centers) - associated with a series on a user chart + // annotate(hline, label, style, value) - associated with a series on a user chart + // annotate(vline, label, style, value) - associated with a series on a user chart (see linestyle for vals below) { "arguniq", 1 }, // returns an index of the uniq values in a vector, in the same way // argsort returns an index, can then be used to select from samples @@ -400,6 +401,30 @@ static QStringList pdmodels(Context *context) return returning; } +// whenever we use a line style +static struct { + const char *name; + Qt::PenStyle type; +} linestyles_[] = { + { "solid", Qt::SolidLine }, + { "dash", Qt::DashLine }, + { "dot", Qt::DotLine }, + { "dashdot", Qt::DashDotLine }, + { "dashdotdot", Qt::DashDotDotLine }, + { "", Qt::NoPen }, +}; + +static Qt::PenStyle linestyle(QString name) +{ + int index=0; + while (linestyles_[index].type != Qt::NoPen) { + if (name == linestyles_[index].name) + return linestyles_[index].type; + index++; + } + return Qt::NoPen; // not known +} + QStringList DataFilter::builtins(Context *context) { @@ -525,7 +550,7 @@ DataFilter::builtins(Context *context) } else if (i == 66) { - returning << "annotate(label, ...)"; + returning << "annotate(label|hline|vline|voronoi, ...)"; } else if (i == 67) { @@ -1709,7 +1734,7 @@ void Leaf::validateFilter(Context *context, DataFilterRuntime *df, Leaf *leaf) QRegExp dateRangeValidSymbols("^(start|stop)$", Qt::CaseInsensitive); // date range QRegExp pmcValidSymbols("^(stress|lts|sts|sb|rr|date)$", Qt::CaseInsensitive); QRegExp smoothAlgos("^(sma|ewma)$", Qt::CaseInsensitive); - QRegExp annotateTypes("^(label|voronoi)$", Qt::CaseInsensitive); + QRegExp annotateTypes("^(label|hline|vline|voronoi)$", Qt::CaseInsensitive); QRegExp curveData("^(x|y|z|d|t)$", Qt::CaseInsensitive); QRegExp aggregateFunc("^(mean|sum|max|min|count)$", Qt::CaseInsensitive); QRegExp interpolateAlgorithms("^(linear|cubic|akima|steffen)$", Qt::CaseInsensitive); @@ -2497,20 +2522,41 @@ void Leaf::validateFilter(Context *context, DataFilterRuntime *df, Leaf *leaf) if (leaf->fparms.count() < 2 || leaf->fparms[0]->type != Leaf::Symbol) { leaf->inerror = true; - DataFiltererrors << QString(tr("annotate(label|voronoi, ...) need at least 2 parameters.")); + DataFiltererrors << QString(tr("annotate(label|hline|vline|voronoi, ...) need at least 2 parameters.")); } else { QString type = *(leaf->fparms[0]->lvalue.n); + + // is the type of annotation supported? if (!annotateTypes.exactMatch(type)) { leaf->inerror = true; DataFiltererrors << QString(tr("annotation type '%1' not available").arg(type)); } else { - if (type == "voronoi" && leaf->fparms.count() != 2) { + + // its valid type, but what about the parameters? + if (type == "voronoi" && leaf->fparms.count() != 2) { // VORONOI + + leaf->inerror = true; + DataFiltererrors << QString(tr("annotate(voronoi, centers)")); + + } else if (type == "hline" || type == "vline") { // HLINE and VLINE + + // just make sure the type of line is supported, the other parameters + // can be coerced from whatever the user passed anyway + if (leaf->fparms.count() != 4 || leaf->fparms[2]->type != Leaf::Symbol + || linestyle(*(leaf->fparms[2]->lvalue.n)) == Qt::NoPen) { leaf->inerror = true; - DataFiltererrors << QString(tr("annotate(voronoi, centers)")); - } else { - for(int i=1; ifparms.count(); i++) validateFilter(context, df, leaf->fparms[i]); + DataFiltererrors << QString(tr("annotate(hline|vline, 'label', solid|dash|dot|dashdot|dashdotdot, value)")); + } else { + // make sure the parms are well formed + validateFilter(context, df, leaf->fparms[1]); + validateFilter(context, df, leaf->fparms[3]); + } + + } else { // Any other types, e.g LABEL + + for(int i=1; ifparms.count(); i++) validateFilter(context, df, leaf->fparms[i]); } } } @@ -3312,7 +3358,7 @@ static int monthsTo(QDate from, QDate to) return months; } -Result Leaf::eval(DataFilterRuntime *df, Leaf *leaf, const Result &x, long it, RideItem *m, RideFilePoint *p, const QHash *c, Specification s, DateRange d) +Result Leaf::eval(DataFilterRuntime *df, Leaf *leaf, const Result &x, long it, RideItem *m, RideFilePoint *p, const QHash *c, const Specification &s, const DateRange &d) { // if error state all bets are off //if (inerror) return Result(0); @@ -6072,6 +6118,10 @@ Result Leaf::eval(DataFilterRuntime *df, Leaf *leaf, const Result &x, long it, R df->owner->annotateVoronoi(x,y); } } + + if (type == "hline" || type == "vline") { + } + } // smooth diff --git a/src/Core/DataFilter.h b/src/Core/DataFilter.h index dbaff4db12..40bf5cca71 100644 --- a/src/Core/DataFilter.h +++ b/src/Core/DataFilter.h @@ -126,7 +126,7 @@ class Leaf { // User Metric - using symbols from QHash<..> (RideItem + Interval) and // Spec to delimit samples in R/Python Scripts // - Result eval(DataFilterRuntime *df, Leaf *, const Result &x, long it, RideItem *m, RideFilePoint *p = NULL, const QHash *metrics=NULL, Specification spec=Specification(), DateRange d=DateRange()); + Result eval(DataFilterRuntime *df, Leaf *, const Result &x, long it, RideItem *m, RideFilePoint *p = NULL, const QHash *metrics=NULL, const Specification &spec=Specification(), const DateRange &d=DateRange()); // tree traversal etc void print(int level, DataFilterRuntime*); // print leaf and all children diff --git a/src/Core/Specification.cpp b/src/Core/Specification.cpp index 2ff0ddb3ce..345ad0b325 100644 --- a/src/Core/Specification.cpp +++ b/src/Core/Specification.cpp @@ -27,20 +27,20 @@ Specification::Specification() : it(NULL), recintsecs(0), ri(NULL) {} // does the date pass the specification ? bool -Specification::pass(QDate date) +Specification::pass(QDate date) const { return (dr.pass(date)); } // does the rideitem pass the specification ? bool -Specification::pass(RideItem*item) +Specification::pass(RideItem*item) const { return (dr.pass(item->dateTime.date()) && fs.pass(item->fileName)); } bool -Specification::pass(RideFilePoint *p) +Specification::pass(RideFilePoint *p) const { if (it == NULL) return true; else if ((p->secs+recintsecs) >= it->start && p->secs <= it->stop) return true; @@ -88,7 +88,7 @@ Specification::secsEnd() const } bool -Specification::isEmpty(RideFile *ride) +Specification::isEmpty(RideFile *ride) const { // its null ! if (!ride) return true; diff --git a/src/Core/Specification.h b/src/Core/Specification.h index 0ed0e9a8ce..4242e072da 100644 --- a/src/Core/Specification.h +++ b/src/Core/Specification.h @@ -64,7 +64,7 @@ class FilterSet } // does the name in question pass the filter set ? - bool pass(QString name) { + bool pass(QString name) const { foreach(QSet set, filters_) if (!set.contains(name)) return false; @@ -84,16 +84,16 @@ class Specification Specification(); // does the date pass the specification ? - bool pass(QDate); + bool pass(QDate) const; // does the rideitem pass the specification ? - bool pass(RideItem*); + bool pass(RideItem*) const; // does the ridepoint pass the specification ? - bool pass(RideFilePoint *p); + bool pass(RideFilePoint *p) const; // would it yield no data points for this ride ? - bool isEmpty(RideFile *); + bool isEmpty(RideFile *) const; // non-null if exists IntervalItem *interval() { return it; } diff --git a/src/Core/TimeUtils.h b/src/Core/TimeUtils.h index a87a0b19f7..94b4879b3e 100644 --- a/src/Core/TimeUtils.h +++ b/src/Core/TimeUtils.h @@ -47,11 +47,11 @@ class DateRange : QObject DateRange(const DateRange& other); DateRange(QDate from = QDate(), QDate to = QDate(), QString name ="", QColor=QColor(127,127,127)); DateRange& operator=(const DateRange &); - bool operator!=(const DateRange&other) { + bool operator!=(const DateRange&other) const { if (other.from != from || other.to != to || other.name != name) return true; return false; } - bool operator==(const DateRange&other) { + bool operator==(const DateRange&other) const { if (other.from == from && other.to == to && other.name == name) return true; return false; } @@ -62,14 +62,14 @@ class DateRange : QObject QUuid id; // does this date fall in the range selection ? - bool pass(QDate date) { + bool pass(QDate date) const { if (from == QDate() && to == QDate()) return true; if (from == QDate() && date <= to) return true; if (to == QDate() && date >= from) return true; if (date >= from && date <= to) return true; return false; } - bool isValid() { return valid; } + bool isValid() const { return valid; } signals: