summaryrefslogtreecommitdiffstats
path: root/src/bm
diff options
context:
space:
mode:
authorjasplin <qt-info@nokia.com>2010-03-17 14:54:06 +0100
committerjasplin <qt-info@nokia.com>2010-03-17 14:54:06 +0100
commite669c67a6e86c784aa1ab238b95b0771761397fd (patch)
treefa3587c21002ad268c5f726f1dc18731ac02ffc3 /src/bm
parente2d2a8f4d4694b6a4744a31efd55f21bae1af194 (diff)
Refactored some of the plotting code.
This patch organizes some of the plotting code in a better way by employing a template method pattern. There is still a potential for factoring out duplicate code.
Diffstat (limited to 'src/bm')
-rw-r--r--src/bm/bm.pro4
-rw-r--r--src/bm/bmmisc.cpp678
-rw-r--r--src/bm/bmmisc.h11
-rw-r--r--src/bm/bmrequest.cpp29
-rw-r--r--src/bm/bmrequest.h13
-rw-r--r--src/bm/plotter.cpp943
-rw-r--r--src/bm/plotter.h100
7 files changed, 1067 insertions, 711 deletions
diff --git a/src/bm/bm.pro b/src/bm/bm.pro
index 128b5f9..c9b820c 100644
--- a/src/bm/bm.pro
+++ b/src/bm/bm.pro
@@ -1,8 +1,8 @@
TEMPLATE = lib
CONFIG += shared
TARGET = bm
-SOURCES += bm.cpp bmrequest.cpp bmmisc.cpp indextree.cpp
-HEADERS += bm.h bmrequest.h bmmisc.h indextree.h
+SOURCES += bm.cpp bmrequest.cpp bmmisc.cpp indextree.cpp plotter.cpp
+HEADERS += bm.h bmrequest.h bmmisc.h indextree.h plotter.h
QT += network
QT += xml
QT += sql
diff --git a/src/bm/bmmisc.cpp b/src/bm/bmmisc.cpp
index fc3e9b8..2df1887 100644
--- a/src/bm/bmmisc.cpp
+++ b/src/bm/bmmisc.cpp
@@ -21,17 +21,14 @@
**
****************************************************************************/
+#include "bmmisc.h"
#include <QFile>
#include <QSqlError>
#include <QSqlDriver>
#include <QVariant>
-#include <QGraphicsScene>
-#include <QGraphicsSimpleTextItem>
-#include <QPainter>
#include <QDebug>
#include <qnumeric.h>
#include <cstdio>
-#include "bmmisc.h"
bool BMMisc::getTimestamp(
const QStringList &args, int pos, QString *timestamp, QString *error)
@@ -159,679 +156,6 @@ qreal BMMisc::median(const QList<qreal> &values)
return sortedValues.at(sortedValues.size() / 2);
}
-// ### 2 B DOCUMENTED!
-QImage BMMisc::createHistoryPlot(
- const QList<int> &timestamps, const QList<qreal> &values, QVector<bool> &missing,
- const QString &title, QList<const IndexTree::ResultHistory *> *resultHistories,
- QString *error, int basePos, const QString &yCaption, bool showNormDiff, bool showBaseValue,
- qreal baseValue, int baseTimestampPos)
-{
- if (timestamps.isEmpty()) {
- *error = "no data to plot (no values)";
- return QImage();
- }
-
- Q_ASSERT(timestamps.size() == values.size());
- Q_ASSERT((timestamps.size() == missing.size()) || missing.isEmpty());
- for (int i = 0; i < (timestamps.size() - 1); ++i) {
- Q_ASSERT(timestamps.at(i) <= timestamps.at(i + 1));
- }
- Q_ASSERT(basePos < timestamps.size());
-
- if (missing.isEmpty())
- missing = QVector<bool>(timestamps.size(), false);
-
- // --- BEGIN draw scenes ---
-
- const qreal width = 1100;
- const qreal height_main = 450;
- const qreal height_rh = 100;
- const int rhDispSizeMax = 20; // ### could be set at runtime
- const int rhDispSize = qMin(rhDispSizeMax, resultHistories ? resultHistories->size() : -1);
- const qreal rhHeaderHeight = 100;
- const qreal height_rh_tot = resultHistories ? (rhHeaderHeight + rhDispSize * height_rh) : 0;
- const qreal height = height_main + height_rh_tot;
- QGraphicsScene scene_far(0, 0, width, height); // Background scene (rendered first)
- QGraphicsScene scene_mid_aa(0, 0, width, height); // Middle scene (antialiased)
- QGraphicsScene scene_near(0, 0, width, height); // Foreground scene (rendered last)
-
- qreal vmin = -1;
- qreal vmax = -1;
- const int firstNonMissing = missing.indexOf(false);
- if (firstNonMissing >= 0) {
- vmin = vmax = values.at(firstNonMissing);
- for (int i = firstNonMissing + 1; i < values.size(); ++i) {
- if (!missing.at(i)) {
- vmin = qMin(vmin, values.at(i));
- vmax = qMax(vmax, values.at(i));
- }
- }
- }
-
- const qreal pad_left = 150;
- const qreal pad_right = 150;
- const qreal pad_top = 50;
- const qreal pad_bottom = 50;
- const qreal xmin = pad_left;
- const qreal xmax = width - pad_right;
- const qreal xdefault = 0.5 * (xmin + xmax);
- const qreal ymin = pad_top;
- const qreal ymax = height_main - pad_bottom;
- const qreal ydefault = 0.5 * (ymin + ymax);
-
- qreal loTimestamp = timestamps.first();
- qreal hiTimestamp = timestamps.last();
- if (resultHistories) {
- for (int i = 0; i < resultHistories->size(); ++i) {
- loTimestamp =
- qMin(static_cast<int>(loTimestamp), resultHistories->at(i)->timestamps.first());
- hiTimestamp =
- qMax(static_cast<int>(hiTimestamp), resultHistories->at(i)->timestamps.last());
- }
- }
-
- qreal xfact = xdefault;
- if (loTimestamp < hiTimestamp) {
- const qreal tfact = 1.0 / (hiTimestamp - loTimestamp);
- xfact = tfact * (xmax - xmin);
- }
-
- const qreal vfact = 1 / (vmax - vmin); // zero division handled elsewhere
- const qreal yfact = vfact * (ymax - ymin);
-
- // Compute scene coordinates of history curveo ...
- // (### refactor similar code #1)
- QList<qreal> x;
- QList<qreal> y;
- for (int i = 0; i < values.size(); ++i) {
- const qreal t = timestamps.at(i);
- const qreal v = values.at(i);
-// x.append((timestamps.size() > 1) ? (xmin + (t - loTimestamp) * xfact) : xdefault);
- x.append(((timestamps.size() > 1) || (loTimestamp < hiTimestamp))
- ? (xmin + (t - loTimestamp) * xfact)
- : xdefault);
- y.append(missing.at(i) ? -1 : v2y(v, ymax, vmin, yfact, ydefault));
- }
-
- // Draw background ...
- scene_far.setBackgroundBrush(QBrush(Qt::white));
-
- // Draw axis indicators ...
- const qreal indicatorSize = 5;
- const QColor indicatorColor(119, 119, 255, 255);
- // ... x-axis (all values) ...
- {
- QPainterPath path;
- for (int i = 0; i < x.size(); ++i) {
- path.moveTo(x.at(i), ymax);
- path.lineTo(x.at(i), ymax + indicatorSize);
- }
- scene_far.addPath(path, QPen(indicatorColor));
- }
- // ... y-axises (min and max value only) ...
- {
- QPainterPath path;
- path.moveTo(xmin, ymin);
- path.lineTo(xmin - indicatorSize, ymin);
- path.moveTo(xmin, ymax);
- path.lineTo(xmin - indicatorSize, ymax);
- path.moveTo(xmax, ymin);
- path.lineTo(xmax + indicatorSize, ymin);
- path.moveTo(xmax, ymax);
- path.lineTo(xmax + indicatorSize, ymax);
- scene_far.addPath(path, QPen(indicatorColor));
- }
-
- // Draw border rectangle ...
- scene_far.addRect(xmin, ymin, xmax - xmin, ymax - ymin, QPen(QColor(220, 220, 220, 255)));
-
- if (basePos >= 0) {
- // Draw line indicating the base value point ...
- QColor color(0, 0, 255);
- color.setAlpha(100);
- scene_far.addLine(xmin, y.at(basePos), xmax, y.at(basePos), QPen(color, 2));
- }
-
- if (showBaseValue) {
- // Draw line indicating the base value ...
- QColor color(0, 0, 255);
- color.setAlpha(100);
- const qreal y_base = v2y(baseValue, ymax, vmin, yfact, ydefault);
- scene_far.addLine(xmin, y_base, xmax, y_base, QPen(color, 2));
- }
-
- if (baseTimestampPos >= 0) {
- // Draw line indicating the base timestamp point ...
- QColor color(0, 0, 255);
- color.setAlpha(100);
- scene_far.addLine(
- x.at(baseTimestampPos), ymin, x.at(baseTimestampPos), height, QPen(color, 2));
- }
-
- const qreal dpSize = 4;
-
- // Draw history curve between data points that are not missing ...
- if (missing.count(false) > 1)
- {
- int pos = missing.indexOf(false, 0);
- QPainterPath path(QPointF(x.at(pos), y.at(pos)));
- while (true) {
- pos = missing.indexOf(false, pos + 1);
- if (pos == -1)
- break;
- path.lineTo(x.at(pos), y.at(pos));
- }
- scene_mid_aa.addPath(path, QPen(QColor(128, 128, 128, 255)));
- }
-
- // Draw data points (except the one representing the base value if any) ...
- {
- QPainterPath path;
- path.setFillRule(Qt::WindingFill);
- QPainterPath missingPath;
- const qreal ymid = 0.5 * (ymin + ymax);
- const qreal dpSize_2 = 0.5 * dpSize;
- for (int i = 0; i < x.size(); ++i)
- if (missing.at(i)) {
- missingPath.moveTo(x.at(i) - dpSize_2, ymid - dpSize_2);
- missingPath.lineTo(x.at(i) + dpSize_2, ymid + dpSize_2);
- missingPath.moveTo(x.at(i) - dpSize_2, ymid + dpSize_2);
- missingPath.lineTo(x.at(i) + dpSize_2, ymid - dpSize_2);
- } else if ((basePos < 0) || (i != basePos)) {
- path.addRect(x.at(i) - dpSize_2, y.at(i) - dpSize_2, dpSize, dpSize);
- }
-
- const QColor color(0, 0, 0, 255);
- scene_near.addPath(path, QPen(color), QBrush(color));
- const QColor missingColor(255, 0, 0, 255);
- scene_near.addPath(missingPath, QPen(missingColor, 2), QBrush(missingColor));
- }
-
- if ((basePos >= 0) && (!missing.at(basePos))) {
- // Draw the data point representing the base value ...
- QPainterPath path;
- path.addRect(x.at(basePos) - dpSize / 2, y.at(basePos) - dpSize / 2, dpSize, dpSize);
- const QColor color(0, 0, 0, 255);
- scene_near.addPath(path, QPen(color), QBrush(Qt::yellow)); // ### 4 NOW
- }
-
-
- // Draw labels ...
- const qreal labelPad = 10;
- const qreal captionPad = 4;
- qreal captionHeight;
-
- // ... left y axis ...
- {
- QFont font;
- font.setPointSize(14);
- QGraphicsSimpleTextItem *text = scene_near.addSimpleText(yCaption, font);
- const qreal x_ = xmin - text->boundingRect().height() - captionPad;
- const qreal y_ = (ymin + ymax) / 2 + text->boundingRect().width() / 2;
- text->setTransform(QTransform().translate(x_, y_).rotate(-90));
- captionHeight = text->boundingRect().height();
- }
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(QString().setNum(vmin));
- text->setPos(
- xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
- ymax - text->boundingRect().height() / 2);
- }
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(QString().setNum(vmax));
- text->setPos(
- xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
- ymin - text->boundingRect().height() / 2);
- }
-
- if ((basePos >= 0) && (!missing.at(basePos)))
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(QString().setNum(values.at(basePos)));
- text->setPos(
- xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
- y.at(basePos) - text->boundingRect().height() / 2);
-
- // Avoid overlap
- if (!scene_near.collidingItems(text).isEmpty()) {
- scene_near.removeItem(text);
- delete text;
- }
- }
-
- if (showBaseValue && ((baseValue >= vmin) && (baseValue <= vmax)))
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(QString().setNum(baseValue));
- const qreal y_base = v2y(baseValue, ymax, vmin, yfact, ydefault);
- text->setPos(
- xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
- y_base - text->boundingRect().height() / 2);
-
- // Avoid overlap
- if (!scene_near.collidingItems(text).isEmpty()) {
- scene_near.removeItem(text);
- delete text;
- }
- }
-
- if ((basePos >= 0) && (!missing.at(basePos)) && showNormDiff) {
- // ... right y axis ...
- {
- QFont font;
- font.setPointSize(14);
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText("norm. diff. between baseline value and value", font);
- const qreal x_ = xmax + captionPad;
- const qreal y_ = (ymin + ymax) / 2 + text->boundingRect().width() / 2;
- text->setTransform(QTransform().translate(x_, y_).rotate(-90));
- captionHeight = text->boundingRect().height();
- }
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString().setNum(normalizedDifference(values.at(basePos), vmin), 'f', 4));
- text->setPos(
- xmax + captionPad + captionHeight + labelPad,
- ymax - text->boundingRect().height() / 2);
- }
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString().setNum(normalizedDifference(values.at(basePos), vmax), 'f', 4));
- text->setPos(
- xmax + captionPad + captionHeight + labelPad,
- ymin - text->boundingRect().height() / 2);
- }
- {
- QGraphicsSimpleTextItem *text = scene_near.addSimpleText(QString().setNum(0.0, 'f', 4));
- text->setPos(
- xmax + captionPad + captionHeight + labelPad,
- y.last() - text->boundingRect().height() / 2);
-
- // Avoid overlap
- if (!scene_near.collidingItems(text).isEmpty()) {
- scene_near.removeItem(text);
- delete text;
- }
- }
- }
-
- // ... top x axis ...
- {
- QFont font;
- font.setPointSize(16);
- QGraphicsSimpleTextItem *text = scene_near.addSimpleText(title, font);
- const qreal x_ = (xmin + xmax) / 2 - text->boundingRect().width() / 2;
- const qreal y_ = ymin - labelPad - text->boundingRect().height();
- text->translate(x_, y_);
- }
-
- // ... bottom x axis ...
- QDateTime dateTime;
- dateTime.setTime_t(loTimestamp);
- const QString xLabel_lo = dateTime.toString();
- dateTime.setTime_t(hiTimestamp);
- const QString xLabel_hi = dateTime.toString();
- {
- QFont font;
- font.setPointSize(14);
- QGraphicsSimpleTextItem *text = scene_near.addSimpleText("time", font);
- const qreal x_ = (xmin + xmax) / 2 - text->boundingRect().width() / 2;
- const qreal y_ = ymax + labelPad;
- text->translate(x_, y_);
- }
- {
- QGraphicsSimpleTextItem *text = scene_near.addSimpleText(xLabel_lo);
- text->setPos(
- xmin - text->boundingRect().width() / 2,
- ymax + labelPad);
- }
- {
- QGraphicsSimpleTextItem *text = scene_near.addSimpleText(xLabel_hi);
- text->setPos(
- xmax - text->boundingRect().width() / 2,
- ymax + labelPad);
- }
-
-
- // *** BEGIN Result histories ***
-
- if (resultHistories) {
-
- // Sort the result histories on the weighted difference between the target
- // and base values ...
- qSort(
- resultHistories->begin(), resultHistories->end(),
- IndexTree::ResultHistory::LessThan());
-
- qreal wSum = 0.0;
- qreal wdiffSum = 0.0;
- QList<qreal> diffs;
- for (int i = 0; i < resultHistories->size(); ++i) {
- const IndexTree::ResultHistory *rh = resultHistories->at(i);
- const qreal diff =
- log2diff(
- rh->smoothValues.at(rh->targetPos), rh->smoothValues.at(rh->basePos),
- rh->metric);
- const qreal wdiff = rh->totWeight * diff;
- wSum += rh->totWeight;
- wdiffSum += wdiff;
- diffs.append(diff);
- }
-
- QFont font_rh;
- font_rh.setPointSize(10);
- font_rh.setFamily("mono");
- const qreal labelPad_rh_hor = 5;
- const qreal labelPad_rh_ver = 1;
-
- // Draw header ...
- {
- QGraphicsItem *bgRect = scene_far.addRect(
- xmin, height_main, xmax - xmin, rhHeaderHeight,
- QPen(QColor(230, 230, 230, 255)), QBrush(QColor(230, 230, 230, 255)));
- bgRect->setZValue(-1);
-
- qreal textYPos = height_main + labelPad_rh_ver;
-
- {
- const int verboseTimestamp = timestamps.at(baseTimestampPos);
- dateTime.setTime_t(verboseTimestamp);
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString("*** Statistics for verbose timestamp (%1; %2) ***")
- .arg(verboseTimestamp)
- .arg(dateTime.toString()),
- font_rh);
- text->setPos(xmin + labelPad_rh_hor, textYPos);
- textYPos += text->boundingRect().height() + 4 * labelPad_rh_ver;
- }
-
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString(
- "SUMMARY: Weight sum (always 1): %1"
- " "
- "weighted diff. sum (WDS): %2"
- " "
- "pow. value (100 * 2^WDS): %3")
- .arg(wSum)
- .arg(wdiffSum)
- .arg(100 * qPow(2, wdiffSum)),
- font_rh);
- text->setPos(xmin + labelPad_rh_hor, textYPos);
- textYPos += text->boundingRect().height() + 4 * labelPad_rh_ver;
- }
-
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString("Top %1 result histories ranked on absolute weighted difference:")
- .arg(rhDispSize), font_rh);
- text->setPos(xmin + labelPad_rh_hor, textYPos + 2 * labelPad_rh_ver);
- textYPos += text->boundingRect().height() + labelPad_rh_ver;
- }
-
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString(
- " (Brown points: raw values; "
- "Black points: median-smoothed values)"),
- font_rh);
- text->setPos(xmin + labelPad_rh_hor, textYPos + 2 * labelPad_rh_ver);
- textYPos += text->boundingRect().height() + labelPad_rh_ver;
- }
-
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString(
- " (Blue circle: baseline value; Yellow point: target value)"),
- font_rh);
- text->setPos(xmin + labelPad_rh_hor, textYPos + 2 * labelPad_rh_ver);
- textYPos += text->boundingRect().height() + labelPad_rh_ver;
- }
-
- }
-
- for (int i = 0; i < rhDispSize; ++i) {
-
- // ### Note: All the _rh suffixes below should go away once we factor out
- // common code sections in this function! 2 B DONE!
-
- const IndexTree::ResultHistory *rh = resultHistories->at(i);
-
- qreal vmin_rh = rh->values.first();
- qreal vmax_rh = vmin_rh;
- qreal vmin_rh_smooth = rh->smoothValues.first();
- qreal vmax_rh_smooth = vmin_rh_smooth;
- qreal vmin_rh_global = qMin(vmin_rh, vmin_rh_smooth);
- qreal vmax_rh_global = qMax(vmax_rh, vmax_rh_smooth);
- for (int j = 1; j < rh->values.size(); ++j) {
- vmin_rh = qMin(vmin_rh, rh->values.at(j));
- vmax_rh = qMax(vmax_rh, rh->values.at(j));
- vmin_rh_smooth = qMin(vmin_rh_smooth, rh->smoothValues.at(j));
- vmax_rh_smooth = qMax(vmax_rh_smooth, rh->smoothValues.at(j));
- vmin_rh_global = qMin(vmin_rh, vmin_rh_smooth);
- vmax_rh_global = qMax(vmax_rh, vmax_rh_smooth);
- }
-
- const qreal dpSize_rh = 4;
- const qreal dpSize_rh_2 = 0.5 * dpSize_rh;
- const qreal dpSize_rh_large = 6;
- const qreal dpSize_rh_large_2 = 0.5 * dpSize_rh_large;
- const qreal dpSize_rh_large2 = 10;
- const qreal dpSize_rh_large2_2 = 0.5 * dpSize_rh_large2;
- const qreal pad_rh = dpSize_rh * 2;
- const qreal ymin_rh = height_main + rhHeaderHeight + i * height_rh + pad_rh;
- const qreal ymax_rh = height_main + rhHeaderHeight + (i + 1) * height_rh - pad_rh;
- const qreal ydefault_rh = 0.5 * (ymin_rh + ymax_rh);
-
- const qreal vfact_rh = 1 / (vmax_rh - vmin_rh); // zero division handled elsewhere
- const qreal yfact_rh = vfact_rh * (ymax_rh - ymin_rh);
- const qreal vfact_rh_smooth =
- 1 / (vmax_rh_smooth - vmin_rh_smooth); // ditto
- const qreal yfact_rh_smooth = vfact_rh_smooth * (ymax_rh - ymin_rh);
- const qreal vfact_rh_global = 1 / (vmax_rh_global - vmin_rh_global); // ditto
- const qreal yfact_rh_global = vfact_rh_global * (ymax_rh - ymin_rh);
-
- const bool sharedYScale = true;
-
- // Compute scene coordinates of history curve ...
- // (### refactor similar code #1)
- QList<qreal> x_rh;
- QList<qreal> y_rh;
- QList<qreal> y_rh_smooth;
- for (int j = 0; j < rh->values.size(); ++j) {
- const qreal t = rh->timestamps.at(j);
- const qreal v = rh->values.at(j);
- const qreal v_smooth = rh->smoothValues.at(j);
- x_rh.append(
- (rh->timestamps.size() > 1) ? (xmin + (t - loTimestamp) * xfact) : xdefault);
-
- if (sharedYScale) {
- y_rh.append(v2y(v, ymax_rh, vmin_rh_global, yfact_rh_global, ydefault_rh));
- y_rh_smooth.append(
- v2y(v_smooth, ymax_rh, vmin_rh_global, yfact_rh_global, ydefault_rh));
- } else {
- y_rh.append(v2y(v, ymax_rh, vmin_rh, yfact_rh, ydefault_rh));
- y_rh_smooth.append(
- v2y(v_smooth, ymax_rh, vmin_rh_smooth, yfact_rh_smooth, ydefault_rh));
- }
- }
-
- // Draw border rectangle ...
- scene_far.addRect(
- xmin, ymin_rh, xmax - xmin, ymax_rh - ymin_rh, QPen(QColor(200, 200, 200, 255)));
-
- // Draw history curve of raw values ...
- {
- QPainterPath path(QPointF(x_rh.first(), y_rh.first()));
- for (int j = 1; j < x_rh.size(); ++j)
- path.lineTo(x_rh.at(j), y_rh.at(j));
- scene_mid_aa.addPath(path, QPen(QColor(255, 200, 100, 255)));
- }
-
- // Draw history curve of smooth values ...
- {
- QPainterPath path(QPointF(x_rh.first(), y_rh_smooth.first()));
- for (int j = 1; j < x_rh.size(); ++j)
- path.lineTo(x_rh.at(j), y_rh_smooth.at(j));
- scene_mid_aa.addPath(path, QPen(QColor(128, 128, 128, 255)));
- }
-
- // Draw raw data points ...
- {
- QPainterPath path;
- path.setFillRule(Qt::WindingFill);
- for (int j = 0; j < x_rh.size(); ++j)
- path.addRect(
- x_rh.at(j) - dpSize_rh_2, y_rh.at(j) - dpSize_rh_2,
- dpSize_rh, dpSize_rh);
-
- const QColor color(128, 100, 50, 255);
- scene_near.addPath(path, QPen(color), QBrush(color));
- }
-
- // Draw smooth data points except the ones representing the target value ...
- {
- QPainterPath path;
- path.setFillRule(Qt::WindingFill);
- for (int j = 0; j < x_rh.size(); ++j)
- if (j != rh->targetPos) {
- path.addRect(
- x_rh.at(j) - dpSize_rh_2, y_rh_smooth.at(j) - dpSize_rh_2,
- dpSize_rh, dpSize_rh);
- }
-
- const QColor color(0, 0, 0, 255);
- scene_near.addPath(path, QPen(color), QBrush(color));
- }
-
- // Draw the smooth data point representing the base value ...
- {
- QPainterPath path;
- path.addEllipse(
- x_rh.at(rh->basePos) - dpSize_rh_large2_2,
- y_rh_smooth.at(rh->basePos) - dpSize_rh_large2_2,
- dpSize_rh_large2, dpSize_rh_large2);
- const QColor color(0, 0, 255, 255);
- scene_near.addPath(path, QPen(color));
- }
-
- // Draw the smooth data point representing the target value ...
- {
- QPainterPath path;
- path.addRect(
- x_rh.at(rh->targetPos) - dpSize_rh_large_2,
- y_rh_smooth.at(rh->targetPos) - dpSize_rh_large_2,
- dpSize_rh_large, dpSize_rh_large);
- const QColor color(0, 0, 0, 255);
- scene_near.addPath(path, QPen(color), QBrush(Qt::yellow));
- }
-
- // Draw labels ...
-
- // ... base result ID ...
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(QString("Res ID: %1").arg(rh->baseResultId), font_rh);
- text->setPos(xmin - labelPad_rh_hor - text->boundingRect().width(), ymin_rh);
- }
-
- qreal textYPos = ymin_rh;
-
- // ... base value ...
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString(" Base: %1").arg(rh->smoothValues.at(rh->basePos)), font_rh);
- text->setPos(xmax + labelPad_rh_hor, textYPos);
- textYPos += text->boundingRect().height() + labelPad_rh_ver;
- }
-
- // ... target value ...
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString("Target: %1").arg(rh->smoothValues.at(rh->targetPos)), font_rh);
- text->setPos(xmax + labelPad_rh_hor, textYPos);
- textYPos += text->boundingRect().height() + labelPad_rh_ver;
- }
-
- // ... global normalized weight (i.e. these add up to 1) ...
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(
- QString("Weight: %1").arg(rh->totWeight), font_rh);
- text->setPos(xmax + labelPad_rh_hor, textYPos);
- textYPos += text->boundingRect().height() + labelPad_rh_ver;
- }
-
- // ... difference of log2 values ...
- const qreal diff = diffs.at(i);
- {
- QGraphicsSimpleTextItem *text =
- scene_near.addSimpleText(QString(" Diff: %1").arg(diff), font_rh);
- text->setPos(xmax + labelPad_rh_hor, textYPos);
- textYPos += text->boundingRect().height() + labelPad_rh_ver;
- }
-
- // ... weighted difference of log2 values ...
- {
- const qreal wdiff = rh->totWeight * diff;
- const qreal fact = 1.0 / resultHistories->size();
- const QColor color(redToGreenColor(wdiff, 0.0001 * fact, 1.0 * fact, true));
-
- QGraphicsSimpleTextItem *text1 =
- scene_near.addSimpleText(QString(" WDiff: "), font_rh);
- text1->setPos(xmax + labelPad_rh_hor, textYPos);
-
- QGraphicsSimpleTextItem *text2 =
- scene_near.addSimpleText(QString("%1").arg(wdiff), font_rh);
- text2->setPos(xmax + labelPad_rh_hor + text1->boundingRect().width(), textYPos);
-
- // Add a colored background rect ...
- QGraphicsItem *bgRect = scene_near.addRect(
- text2->boundingRect(), QPen(color), QBrush(color));
- bgRect->setPos(xmax + labelPad_rh_hor + text1->boundingRect().width(), textYPos);
- bgRect->setZValue(-1);
-
- textYPos += text1->boundingRect().height() + labelPad_rh_ver;
- }
- }
- }
-
-
- // *** END Result histories ***
-
- // --- END draw scenes ---
-
-
- // Render the scenes to an image ...
- QImage image(scene_far.width(), scene_far.height(), QImage::Format_ARGB32);
- {
- QPainter painter(&image);
- scene_far.render(&painter);
- }
- {
- QPainter painter(&image);
- painter.setRenderHint(QPainter::Antialiasing);
- scene_mid_aa.render(&painter);
- }
- {
- QPainter painter(&image);
- scene_near.render(&painter);
- }
-
- return image;
-}
-
qreal BMMisc::v2y(
const qreal v, const qreal ymax, const qreal vmin, const qreal yfact, const qreal ydefault)
{
diff --git a/src/bm/bmmisc.h b/src/bm/bmmisc.h
index 0496de8..4143908 100644
--- a/src/bm/bmmisc.h
+++ b/src/bm/bmmisc.h
@@ -53,17 +53,6 @@ public:
static bool hasOption(const QStringList &args, const QString &option);
static qreal median(const QList<qreal> &values);
- // ### Clean up argument passing to this function (e.g. pass arguments in a struct etc.):
- // Or perhaps better: Copy this function into two separate functions and factor out
- // common parts.
- // 2 B DONE!
- static QImage createHistoryPlot(
- const QList<int> &timestamps, const QList<qreal> &values, QVector<bool> &missing,
- const QString &title, QList<const IndexTree::ResultHistory *> *resultHistories,
- QString *error, int basePos = -1, const QString &yCaption = QString("value"),
- bool showNormDiff = false, bool showBaseValue = false, qreal baseValue = 0.0,
- int baseTimestampPos = -1);
-
static qreal v2y(
const qreal v, const qreal ymax, const qreal vmin, const qreal yfact, const qreal ydefault);
static QDateTime createCurrDateTime(int timestamp);
diff --git a/src/bm/bmrequest.cpp b/src/bm/bmrequest.cpp
index 66d2ed7..063dd19 100644
--- a/src/bm/bmrequest.cpp
+++ b/src/bm/bmrequest.cpp
@@ -23,6 +23,7 @@
#include "bmrequest.h"
#include "indextree.h"
+#include "plotter.h"
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
@@ -4556,7 +4557,7 @@ void BMRequest_GetResult::handleReply_Raw(const QStringList &args) const
QStringList optValues;
// Dump values if requested ...
- if (BMMisc::getOption(args, "-verbose", &optValues, 1, 0, &error_)) {
+ if (BMMisc::getOption(args, "-verbose", &optValues, 0, 0, &error_)) {
printf(
"\n%-10s %-13s %s\n", "Timestamp", "Value", "(MNR indicates max neighbor ratio)");
@@ -4576,7 +4577,6 @@ void BMRequest_GetResult::handleReply_Raw(const QStringList &args) const
}
-
// Create plot if requested ...
if (BMMisc::getOption(args, "-plot", &optValues, 1, 0, &error_)) {
const QString plotFile = optValues.first().trimmed();
@@ -4595,13 +4595,8 @@ void BMRequest_GetResult::handleReply_Raw(const QStringList &args) const
return;
}
- const QString title =
- QString("Result history containing baseline result %1").arg(resultId);
-
- QVector<bool> missing;
- QImage image =
- BMMisc::createHistoryPlot(
- timestamps, values, missing, title, 0, &error_, basePos);
+ Plotter *plotter = new ResultHistoryPlotter(timestamps, values, resultId, basePos);
+ const QImage image = plotter->createImage(&error_);
if (!image.isNull()) {
if (!BMMisc::saveImageToFile(image, plotFile, &error_))
qDebug() << "failed to save image to file:" << error_;
@@ -5465,10 +5460,11 @@ void BMRequest_IndexGetValues::handleReply_Image(const QStringList &args) const
// Create the plot ...
QString error_;
- QImage image = BMMisc::createHistoryPlot(
- timestamps, indexValues, missing, QString("Qt Performance Index (%1)").arg(indexName),
- resultHistories, &error_, -1, "value", false, true, 100.0,
- timestamps.indexOf(verboseTimestamp));
+ Plotter *plotter =
+ new IndexPlotter(
+ timestamps, indexValues, missing, indexName, resultHistories,
+ timestamps.indexOf(verboseTimestamp));
+ const QImage image = plotter->createImage(&error_);
if (!image.isNull()) {
BMMisc::printImageOutput(
image, "png", QString(), BMMisc::hasOption(args, "-httpHeader"));
@@ -5480,10 +5476,3 @@ void BMRequest_IndexGetValues::handleReply_Image(const QStringList &args) const
qDebug() << "failed to compute index values:" << error.toLatin1().data();
}
}
-
-QImage BMRequest_IndexGetValues::createIndexPlot(QString *error) const
-{
- // 2 B DONE!
- *error = "creating index plot is not implemented yet";
- return QImage();
-}
diff --git a/src/bm/bmrequest.h b/src/bm/bmrequest.h
index 17780c6..6ebbdb5 100644
--- a/src/bm/bmrequest.h
+++ b/src/bm/bmrequest.h
@@ -541,6 +541,11 @@ private:
QByteArray toReplyBuffer();
void handleReply_Raw(const QStringList &args) const;
void handleReply_JSON(const QStringList &args) const { Q_UNUSED(args); /* 2 B DONE! */ }
+
+ // ### 2 B OBSOLETED/REFACTORED
+ // static QImage createPlot(
+ // const QList<int> &timestamps, const QList<qreal> &values, const QString &title,
+ // int basePos, QString *error);
};
// ### 2 B DOCUMENTED (in bmclient.html and bmproto.html)
@@ -624,7 +629,13 @@ private:
void handleReply_JSON(const QStringList &args) const { Q_UNUSED(args); /* 2 B DONE! */ }
void handleReply_HTML(const QStringList &args) const;
void handleReply_Image(const QStringList &args) const;
- QImage createIndexPlot(QString *error) const;
+
+ // ### 2 B OBSOLETED/REFACTORED
+ // static QImage createPlot(
+ // const QList<int> &timestamps, const QList<qreal> &values, QVector<bool> &missing,
+ // const QString &title, QList<const IndexTree::ResultHistory *> *resultHistories,
+ // QString *error, int baseTimestampPos = -1, bool showBaseValue = true,
+ // qreal baseValue = 100.0);
};
#endif // BMREQUEST_H
diff --git a/src/bm/plotter.cpp b/src/bm/plotter.cpp
new file mode 100644
index 0000000..dfb4afa
--- /dev/null
+++ b/src/bm/plotter.cpp
@@ -0,0 +1,943 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the BM project on Qt Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 or 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#include "plotter.h"
+#include "bmmisc.h"
+#include <QGraphicsScene>
+#include <QGraphicsSimpleTextItem>
+#include <QPainter>
+
+// ### Note: There is some potential for refactoring in order to avoid code duplication
+// in this file.
+
+
+// Template method.
+QImage Plotter::createImage(QString *error) const
+{
+ const QRectF sceneRect_ = sceneRect();
+
+ QGraphicsScene scene_far(sceneRect_); // Background scene (rendered first)
+ QGraphicsScene scene_mid_aa(sceneRect_); // Middle scene (antialiased)
+ QGraphicsScene scene_near(sceneRect_); // Foreground scene (rendered last)
+
+ if (!drawScenes(&scene_far, &scene_mid_aa, &scene_near, error))
+ return QImage();
+
+ Q_ASSERT(scene_far.sceneRect() == scene_mid_aa.sceneRect());
+ Q_ASSERT(scene_far.sceneRect() == scene_near.sceneRect());
+
+ QImage image(scene_far.width(), scene_far.height(), QImage::Format_ARGB32);
+ {
+ QPainter painter(&image);
+ scene_far.render(&painter);
+ }
+ {
+ QPainter painter(&image);
+ painter.setRenderHint(QPainter::Antialiasing);
+ scene_mid_aa.render(&painter);
+ }
+ {
+ QPainter painter(&image);
+ scene_near.render(&painter);
+ }
+
+ return image;
+}
+
+ResultHistoryPlotter::ResultHistoryPlotter(
+ const QList<int> &timestamps, const QList<qreal> &values, const int resultId, const int basePos)
+ : timestamps(timestamps), values(values), resultId(resultId), basePos(basePos)
+ , width(1100), height(450)
+{
+}
+
+QRectF ResultHistoryPlotter::sceneRect() const
+{
+ return QRectF(0, 0, width, height);
+}
+
+// ### 2 B DOCUMENTED!
+// ### 2 B DONE: Maybe factor out parts that this function has in common with
+// IndexPlotter::drawScenes()
+bool ResultHistoryPlotter::drawScenes(
+ QGraphicsScene *scene_far, QGraphicsScene *scene_mid_aa, QGraphicsScene *scene_near,
+ QString *error) const
+{
+ if (timestamps.isEmpty()) {
+ if (error)
+ *error = "no data to plot (no values)";
+ return false;
+ }
+ Q_ASSERT(timestamps.size() == values.size());
+ for (int i = 1; i < timestamps.size(); ++i) {
+ Q_ASSERT(timestamps.at(i - 1) <= timestamps.at(i));
+ }
+ Q_ASSERT(basePos < timestamps.size());
+
+ const qreal pad_left = 150;
+ const qreal pad_right = 150;
+ const qreal pad_top = 50;
+ const qreal pad_bottom = 50;
+ const qreal xmin = pad_left;
+ const qreal xmax = width - pad_right;
+ const qreal xdefault = 0.5 * (xmin + xmax);
+ const qreal ymin = pad_top;
+ const qreal ymax = height - pad_bottom; // ### specific
+ const qreal ydefault = 0.5 * (ymin + ymax);
+
+ qreal loTimestamp = timestamps.first();
+ qreal hiTimestamp = timestamps.last();
+
+ qreal xfact = xdefault;
+ if (loTimestamp < hiTimestamp) {
+ const qreal tfact = 1.0 / (hiTimestamp - loTimestamp);
+ xfact = tfact * (xmax - xmin);
+ }
+
+ qreal vmin = -1;
+ qreal vmax = -1;
+ vmin = vmax = values.first();
+ for (int i = 1; i < values.size(); ++i) {
+ vmin = qMin(vmin, values.at(i));
+ vmax = qMax(vmax, values.at(i));
+ }
+
+ const qreal vfact = 1 / (vmax - vmin); // zero division handled elsewhere
+ const qreal yfact = vfact * (ymax - ymin);
+
+ // Compute scene coordinates of history curve ...
+ QList<qreal> x;
+ QList<qreal> y;
+ for (int i = 0; i < values.size(); ++i) {
+ const qreal t = timestamps.at(i);
+ const qreal v = values.at(i);
+ x.append(((timestamps.size() > 1) || (loTimestamp < hiTimestamp))
+ ? (xmin + (t - loTimestamp) * xfact)
+ : xdefault);
+ y.append(BMMisc::v2y(v, ymax, vmin, yfact, ydefault));
+ }
+
+ // Draw background ...
+ scene_far->setBackgroundBrush(QBrush(Qt::white));
+
+ // Draw axis indicators ...
+ const qreal indicatorSize = 5;
+ const QColor indicatorColor(119, 119, 255, 255);
+ // ... x-axis (all values) ...
+ {
+ QPainterPath path;
+ for (int i = 0; i < x.size(); ++i) {
+ path.moveTo(x.at(i), ymax);
+ path.lineTo(x.at(i), ymax + indicatorSize);
+ }
+ scene_far->addPath(path, QPen(indicatorColor));
+ }
+ // ... y-axises (min and max value only) ...
+ {
+ QPainterPath path;
+ path.moveTo(xmin, ymin);
+ path.lineTo(xmin - indicatorSize, ymin);
+ path.moveTo(xmin, ymax);
+ path.lineTo(xmin - indicatorSize, ymax);
+ path.moveTo(xmax, ymin);
+ path.lineTo(xmax + indicatorSize, ymin);
+ path.moveTo(xmax, ymax);
+ path.lineTo(xmax + indicatorSize, ymax);
+ scene_far->addPath(path, QPen(indicatorColor));
+ }
+
+ // Draw border rectangle ...
+ scene_far->addRect(xmin, ymin, xmax - xmin, ymax - ymin, QPen(QColor(220, 220, 220, 255)));
+
+ // ### BEGIN specific
+
+ if (basePos >= 0) {
+ // Draw line indicating the base position ...
+ QColor color(0, 0, 255);
+ color.setAlpha(100);
+ scene_far->addLine(xmin, y.at(basePos), xmax, y.at(basePos), QPen(color, 2));
+ }
+
+ // ### END specific
+
+ const qreal dpSize = 4;
+
+ // Draw history curve between data points ...
+ {
+ QPainterPath path(QPointF(x.first(), y.first()));
+ for (int i = 0; i < x.size(); ++i)
+ path.lineTo(x.at(i), y.at(i));
+ scene_mid_aa->addPath(path, QPen(QColor(128, 128, 128, 255)));
+ }
+
+ // Draw data points (except the one representing the base position if any) ...
+ {
+ QPainterPath path;
+ path.setFillRule(Qt::WindingFill);
+ const qreal dpSize_2 = 0.5 * dpSize;
+ for (int i = 0; i < x.size(); ++i)
+ if ((basePos < 0) || (i != basePos))
+ path.addRect(x.at(i) - dpSize_2, y.at(i) - dpSize_2, dpSize, dpSize);
+
+ const QColor color(0, 0, 0, 255);
+ scene_near->addPath(path, QPen(color), QBrush(color));
+ }
+
+ if (basePos >= 0) {
+ // Draw the data point representing the base position ...
+ QPainterPath path;
+ path.addRect(x.at(basePos) - dpSize / 2, y.at(basePos) - dpSize / 2, dpSize, dpSize);
+ const QColor color(0, 0, 0, 255);
+ scene_near->addPath(path, QPen(color), QBrush(Qt::yellow)); // ### 4 NOW
+ }
+
+
+ // Draw labels ...
+ const qreal labelPad = 10;
+ const qreal captionPad = 4;
+ qreal captionHeight;
+
+ // ... left y axis ...
+ {
+ const QString yCaption = "value"; // ### 4 NOW (could e.g. be metric name)
+ QFont font;
+ font.setPointSize(14);
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText(yCaption, font);
+ const qreal x_ = xmin - text->boundingRect().height() - captionPad;
+ const qreal y_ = (ymin + ymax) / 2 + text->boundingRect().width() / 2;
+ text->setTransform(QTransform().translate(x_, y_).rotate(-90));
+ captionHeight = text->boundingRect().height();
+ }
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(QString().setNum(vmin));
+ text->setPos(
+ xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
+ ymax - text->boundingRect().height() / 2);
+ }
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(QString().setNum(vmax));
+ text->setPos(
+ xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
+ ymin - text->boundingRect().height() / 2);
+ }
+
+ if (basePos >= 0) {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(QString().setNum(values.at(basePos)));
+ text->setPos(
+ xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
+ y.at(basePos) - text->boundingRect().height() / 2);
+
+ // Avoid overlap
+ if (!scene_near->collidingItems(text).isEmpty()) {
+ scene_near->removeItem(text);
+ delete text;
+ }
+ }
+
+ const bool showNormDiff = true; // ### 4 NOW
+
+ if ((basePos >= 0) && showNormDiff) {
+ // ... right y axis ...
+ {
+ QFont font;
+ font.setPointSize(14);
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText("norm. diff. between baseline value and value", font);
+ const qreal x_ = xmax + captionPad;
+ const qreal y_ = (ymin + ymax) / 2 + text->boundingRect().width() / 2;
+ text->setTransform(QTransform().translate(x_, y_).rotate(-90));
+ captionHeight = text->boundingRect().height();
+ }
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString().setNum(
+ BMMisc::normalizedDifference(values.at(basePos), vmin), 'f', 4));
+ text->setPos(
+ xmax + captionPad + captionHeight + labelPad,
+ ymax - text->boundingRect().height() / 2);
+ }
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString().setNum(
+ BMMisc::normalizedDifference(values.at(basePos), vmax), 'f', 4));
+ text->setPos(
+ xmax + captionPad + captionHeight + labelPad,
+ ymin - text->boundingRect().height() / 2);
+ }
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(QString().setNum(0.0, 'f', 4));
+ text->setPos(
+ xmax + captionPad + captionHeight + labelPad,
+ y.last() - text->boundingRect().height() / 2);
+
+ // Avoid overlap
+ if (!scene_near->collidingItems(text).isEmpty()) {
+ scene_near->removeItem(text);
+ delete text;
+ }
+ }
+ }
+
+ // ... top x axis ...
+ {
+ QFont font;
+ font.setPointSize(16);
+ const QString title = QString("Result history containing baseline result %1").arg(resultId);
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText(title, font);
+ const qreal x_ = (xmin + xmax) / 2 - text->boundingRect().width() / 2;
+ const qreal y_ = ymin - labelPad - text->boundingRect().height();
+ text->translate(x_, y_);
+ }
+
+ // ... bottom x axis ...
+ QDateTime dateTime;
+ dateTime.setTime_t(loTimestamp);
+ const QString xLabel_lo = dateTime.toString();
+ dateTime.setTime_t(hiTimestamp);
+ const QString xLabel_hi = dateTime.toString();
+ {
+ QFont font;
+ font.setPointSize(14);
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText("time", font);
+ const qreal x_ = (xmin + xmax) / 2 - text->boundingRect().width() / 2;
+ const qreal y_ = ymax + labelPad;
+ text->translate(x_, y_);
+ }
+ {
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText(xLabel_lo);
+ text->setPos(
+ xmin - text->boundingRect().width() / 2,
+ ymax + labelPad);
+ }
+ {
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText(xLabel_hi);
+ text->setPos(
+ xmax - text->boundingRect().width() / 2,
+ ymax + labelPad);
+ }
+
+ return true;
+}
+
+IndexPlotter::IndexPlotter(
+ const QList<int> &timestamps, const QList<qreal> &values, const QVector<bool> &missing_,
+ const QString &indexName, const QList<const IndexTree::ResultHistory *> *resultHistories,
+ const int baseTimestampPos, const bool showBaseValue, const qreal baseValue)
+ : timestamps(timestamps), values(values), missing(missing_), indexName(indexName)
+ , resultHistories(resultHistories), baseTimestampPos(baseTimestampPos)
+ , showBaseValue(showBaseValue), baseValue(baseValue)
+{
+ width = 1100;
+ height_main = 450;
+ height_rh = 100;
+ const int rhDispSizeMax = 20;
+ rhHeaderHeight = 100;
+ rhDispSize = qMin(rhDispSizeMax, resultHistories ? resultHistories->size() : -1);
+ const qreal height_rh_tot = resultHistories ? (rhHeaderHeight + rhDispSize * height_rh) : 0;
+ height = height_main + height_rh_tot;
+}
+
+QRectF IndexPlotter::sceneRect() const
+{
+ return QRectF(0, 0, width, height);
+}
+
+// ### 2 B DOCUMENTED!
+// ### 2 B DONE: Maybe factor out parts that this function has in common with
+// ResultHistoryPlotter::drawScenes()
+bool IndexPlotter::drawScenes(
+ QGraphicsScene *scene_far, QGraphicsScene *scene_mid_aa, QGraphicsScene *scene_near,
+ QString *error) const
+{
+ // --- BEGIN main graph ---
+
+ if (timestamps.isEmpty()) {
+ if (error)
+ *error = "no data to plot (no values)";
+ return false;
+ }
+ Q_ASSERT(timestamps.size() == values.size());
+ Q_ASSERT(timestamps.size() == missing.size());
+ for (int i = 1; i < timestamps.size(); ++i) {
+ Q_ASSERT(timestamps.at(i - 1) <= timestamps.at(i));
+ }
+
+ const qreal pad_left = 150;
+ const qreal pad_right = 150;
+ const qreal pad_top = 50;
+ const qreal pad_bottom = 50;
+
+ const qreal xmin = pad_left;
+ const qreal xmax = width - pad_right;
+ const qreal xdefault = 0.5 * (xmin + xmax);
+ const qreal ymin = pad_top;
+ const qreal ymax = height_main - pad_bottom;
+ const qreal ydefault = 0.5 * (ymin + ymax);
+
+ qreal loTimestamp = timestamps.first();
+ qreal hiTimestamp = timestamps.last();
+
+ if (resultHistories) {
+ for (int i = 0; i < resultHistories->size(); ++i) {
+ loTimestamp =
+ qMin(static_cast<int>(loTimestamp), resultHistories->at(i)->timestamps.first());
+ hiTimestamp =
+ qMax(static_cast<int>(hiTimestamp), resultHistories->at(i)->timestamps.last());
+ }
+ }
+
+ qreal xfact = xdefault;
+ if (loTimestamp < hiTimestamp) {
+ const qreal tfact = 1.0 / (hiTimestamp - loTimestamp);
+ xfact = tfact * (xmax - xmin);
+ }
+
+ qreal vmin = -1;
+ qreal vmax = -1;
+ const int firstNonMissing = missing.indexOf(false);
+ if (firstNonMissing >= 0) {
+ vmin = vmax = values.at(firstNonMissing);
+ for (int i = firstNonMissing + 1; i < values.size(); ++i) {
+ if (!missing.at(i)) {
+ vmin = qMin(vmin, values.at(i));
+ vmax = qMax(vmax, values.at(i));
+ }
+ }
+ }
+
+ const qreal vfact = 1 / (vmax - vmin); // zero division handled elsewhere
+ const qreal yfact = vfact * (ymax - ymin);
+
+ // Compute scene coordinates of history curve ...
+ QList<qreal> x;
+ QList<qreal> y;
+ for (int i = 0; i < values.size(); ++i) {
+ const qreal t = timestamps.at(i);
+ const qreal v = values.at(i);
+ x.append(((timestamps.size() > 1) || (loTimestamp < hiTimestamp))
+ ? (xmin + (t - loTimestamp) * xfact)
+ : xdefault);
+ y.append(BMMisc::v2y(v, ymax, vmin, yfact, ydefault));
+ }
+
+ // Draw background ...
+ scene_far->setBackgroundBrush(QBrush(Qt::white));
+
+ // Draw axis indicators ...
+ const qreal indicatorSize = 5;
+ const QColor indicatorColor(119, 119, 255, 255);
+ // ... x-axis (all values) ...
+ {
+ QPainterPath path;
+ for (int i = 0; i < x.size(); ++i) {
+ path.moveTo(x.at(i), ymax);
+ path.lineTo(x.at(i), ymax + indicatorSize);
+ }
+ scene_far->addPath(path, QPen(indicatorColor));
+ }
+ // ... y-axises (min and max value only) ...
+ {
+ QPainterPath path;
+ path.moveTo(xmin, ymin);
+ path.lineTo(xmin - indicatorSize, ymin);
+ path.moveTo(xmin, ymax);
+ path.lineTo(xmin - indicatorSize, ymax);
+ path.moveTo(xmax, ymin);
+ path.lineTo(xmax + indicatorSize, ymin);
+ path.moveTo(xmax, ymax);
+ path.lineTo(xmax + indicatorSize, ymax);
+ scene_far->addPath(path, QPen(indicatorColor));
+ }
+
+ // Draw border rectangle ...
+ scene_far->addRect(xmin, ymin, xmax - xmin, ymax - ymin, QPen(QColor(220, 220, 220, 255)));
+
+ if (showBaseValue) {
+ // Draw line indicating the base value ...
+ QColor color(0, 0, 255);
+ color.setAlpha(100);
+ const qreal y_base = BMMisc::v2y(baseValue, ymax, vmin, yfact, ydefault);
+ scene_far->addLine(xmin, y_base, xmax, y_base, QPen(color, 2));
+ }
+
+ if (baseTimestampPos >= 0) {
+ // Draw line indicating the base timestamp point ...
+ QColor color(0, 0, 255);
+ color.setAlpha(100);
+ scene_far->addLine(
+ x.at(baseTimestampPos), ymin, x.at(baseTimestampPos), height, QPen(color, 2));
+ }
+
+ const qreal dpSize = 4;
+
+ // Draw history curve between data points that are not missing ...
+ if (missing.count(false) > 1)
+ {
+ int pos = missing.indexOf(false, 0);
+ QPainterPath path(QPointF(x.at(pos), y.at(pos)));
+ while (true) {
+ pos = missing.indexOf(false, pos + 1);
+ if (pos == -1)
+ break;
+ path.lineTo(x.at(pos), y.at(pos));
+ }
+ scene_mid_aa->addPath(path, QPen(QColor(128, 128, 128, 255)));
+ }
+
+ // Draw data points (except the one representing the base value if any) ...
+ {
+ QPainterPath path;
+ path.setFillRule(Qt::WindingFill);
+ QPainterPath missingPath;
+ const qreal ymid = 0.5 * (ymin + ymax);
+ const qreal dpSize_2 = 0.5 * dpSize;
+ for (int i = 0; i < x.size(); ++i)
+ if (missing.at(i)) {
+ missingPath.moveTo(x.at(i) - dpSize_2, ymid - dpSize_2);
+ missingPath.lineTo(x.at(i) + dpSize_2, ymid + dpSize_2);
+ missingPath.moveTo(x.at(i) - dpSize_2, ymid + dpSize_2);
+ missingPath.lineTo(x.at(i) + dpSize_2, ymid - dpSize_2);
+ } else {
+ path.addRect(x.at(i) - dpSize_2, y.at(i) - dpSize_2, dpSize, dpSize);
+ }
+
+ const QColor color(0, 0, 0, 255);
+ scene_near->addPath(path, QPen(color), QBrush(color));
+ const QColor missingColor(255, 0, 0, 255);
+ scene_near->addPath(missingPath, QPen(missingColor, 2), QBrush(missingColor));
+ }
+
+ // Draw labels ...
+ const qreal labelPad = 10;
+ const qreal captionPad = 4;
+ qreal captionHeight;
+
+ // ... left y axis ...
+ {
+ const QString yCaption = "value"; // ### 4 NOW
+ QFont font;
+ font.setPointSize(14);
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText(yCaption, font);
+ const qreal x_ = xmin - text->boundingRect().height() - captionPad;
+ const qreal y_ = (ymin + ymax) / 2 + text->boundingRect().width() / 2;
+ text->setTransform(QTransform().translate(x_, y_).rotate(-90));
+ captionHeight = text->boundingRect().height();
+ }
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(QString().setNum(vmin));
+ text->setPos(
+ xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
+ ymax - text->boundingRect().height() / 2);
+ }
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(QString().setNum(vmax));
+ text->setPos(
+ xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
+ ymin - text->boundingRect().height() / 2);
+ }
+
+ if (showBaseValue && ((baseValue >= vmin) && (baseValue <= vmax))) {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(QString().setNum(baseValue));
+ const qreal y_base = BMMisc::v2y(baseValue, ymax, vmin, yfact, ydefault);
+ text->setPos(
+ xmin - captionPad - captionHeight - labelPad - text->boundingRect().width(),
+ y_base - text->boundingRect().height() / 2);
+
+ // Avoid overlap
+ if (!scene_near->collidingItems(text).isEmpty()) {
+ scene_near->removeItem(text);
+ delete text;
+ }
+ }
+
+ // ... top x axis ...
+ {
+ QFont font;
+ font.setPointSize(16);
+ const QString title = QString("Qt performance index (%1)").arg(indexName);
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText(title, font);
+ const qreal x_ = (xmin + xmax) / 2 - text->boundingRect().width() / 2;
+ const qreal y_ = ymin - labelPad - text->boundingRect().height();
+ text->translate(x_, y_);
+ }
+
+ // ... bottom x axis ...
+ QDateTime dateTime;
+ dateTime.setTime_t(loTimestamp);
+ const QString xLabel_lo = dateTime.toString();
+ dateTime.setTime_t(hiTimestamp);
+ const QString xLabel_hi = dateTime.toString();
+ {
+ QFont font;
+ font.setPointSize(14);
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText("time", font);
+ const qreal x_ = (xmin + xmax) / 2 - text->boundingRect().width() / 2;
+ const qreal y_ = ymax + labelPad;
+ text->translate(x_, y_);
+ }
+ {
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText(xLabel_lo);
+ text->setPos(
+ xmin - text->boundingRect().width() / 2,
+ ymax + labelPad);
+ }
+ {
+ QGraphicsSimpleTextItem *text = scene_near->addSimpleText(xLabel_hi);
+ text->setPos(
+ xmax - text->boundingRect().width() / 2,
+ ymax + labelPad);
+ }
+
+ // --- END main graph ---
+
+
+ // --- BEGIN result history graphs ---
+
+ if (resultHistories) {
+
+ // Sort the result histories on the weighted difference between the target
+ // and base values ...
+ QList<const IndexTree::ResultHistory *> sortedResultHistories(*resultHistories);
+ Q_ASSERT(sortedResultHistories.size() == resultHistories->size());
+ qSort(
+ sortedResultHistories.begin(), sortedResultHistories.end(),
+ IndexTree::ResultHistory::LessThan());
+
+ qreal wSum = 0.0;
+ qreal wdiffSum = 0.0;
+ QList<qreal> diffs;
+ for (int i = 0; i < sortedResultHistories.size(); ++i) {
+ const IndexTree::ResultHistory *rh = sortedResultHistories.at(i);
+ const qreal diff =
+ BMMisc::log2diff(
+ rh->smoothValues.at(rh->targetPos), rh->smoothValues.at(rh->basePos),
+ rh->metric);
+ const qreal wdiff = rh->totWeight * diff;
+ wSum += rh->totWeight;
+ wdiffSum += wdiff;
+ diffs.append(diff);
+ }
+
+ QFont font_rh;
+ font_rh.setPointSize(10);
+ font_rh.setFamily("mono");
+ const qreal labelPad_rh_hor = 5;
+ const qreal labelPad_rh_ver = 1;
+
+ // Draw header ...
+ {
+ QGraphicsItem *bgRect = scene_far->addRect(
+ xmin, height_main, xmax - xmin, rhHeaderHeight,
+ QPen(QColor(230, 230, 230, 255)), QBrush(QColor(230, 230, 230, 255)));
+ bgRect->setZValue(-1);
+
+ qreal textYPos = height_main + labelPad_rh_ver;
+
+ {
+ const int verboseTimestamp = timestamps.at(baseTimestampPos);
+ dateTime.setTime_t(verboseTimestamp);
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString("*** Statistics for verbose timestamp (%1; %2) ***")
+ .arg(verboseTimestamp)
+ .arg(dateTime.toString()),
+ font_rh);
+ text->setPos(xmin + labelPad_rh_hor, textYPos);
+ textYPos += text->boundingRect().height() + 4 * labelPad_rh_ver;
+ }
+
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString(
+ "SUMMARY: Weight sum (always 1): %1"
+ " "
+ "weighted diff. sum (WDS): %2"
+ " "
+ "pow. value (100 * 2^WDS): %3")
+ .arg(wSum)
+ .arg(wdiffSum)
+ .arg(100 * qPow(2, wdiffSum)),
+ font_rh);
+ text->setPos(xmin + labelPad_rh_hor, textYPos);
+ textYPos += text->boundingRect().height() + 4 * labelPad_rh_ver;
+ }
+
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString("Top %1 result histories ranked on absolute weighted difference:")
+ .arg(rhDispSize), font_rh);
+ text->setPos(xmin + labelPad_rh_hor, textYPos + 2 * labelPad_rh_ver);
+ textYPos += text->boundingRect().height() + labelPad_rh_ver;
+ }
+
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString(
+ " (Brown points: raw values; "
+ "Black points: median-smoothed values)"),
+ font_rh);
+ text->setPos(xmin + labelPad_rh_hor, textYPos + 2 * labelPad_rh_ver);
+ textYPos += text->boundingRect().height() + labelPad_rh_ver;
+ }
+
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString(
+ " (Blue circle: baseline value; Yellow point: target value)"),
+ font_rh);
+ text->setPos(xmin + labelPad_rh_hor, textYPos + 2 * labelPad_rh_ver);
+ textYPos += text->boundingRect().height() + labelPad_rh_ver;
+ }
+
+ }
+
+ for (int i = 0; i < rhDispSize; ++i) {
+
+ // ### Note: All the _rh suffixes below should go away once we factor out
+ // common code sections in this function! 2 B DONE!
+
+ const IndexTree::ResultHistory *rh = sortedResultHistories.at(i);
+
+ qreal vmin_rh = rh->values.first();
+ qreal vmax_rh = vmin_rh;
+ qreal vmin_rh_smooth = rh->smoothValues.first();
+ qreal vmax_rh_smooth = vmin_rh_smooth;
+ qreal vmin_rh_global = qMin(vmin_rh, vmin_rh_smooth);
+ qreal vmax_rh_global = qMax(vmax_rh, vmax_rh_smooth);
+ for (int j = 1; j < rh->values.size(); ++j) {
+ vmin_rh = qMin(vmin_rh, rh->values.at(j));
+ vmax_rh = qMax(vmax_rh, rh->values.at(j));
+ vmin_rh_smooth = qMin(vmin_rh_smooth, rh->smoothValues.at(j));
+ vmax_rh_smooth = qMax(vmax_rh_smooth, rh->smoothValues.at(j));
+ vmin_rh_global = qMin(vmin_rh, vmin_rh_smooth);
+ vmax_rh_global = qMax(vmax_rh, vmax_rh_smooth);
+ }
+
+ const qreal dpSize_rh = 4;
+ const qreal dpSize_rh_2 = 0.5 * dpSize_rh;
+ const qreal dpSize_rh_large = 6;
+ const qreal dpSize_rh_large_2 = 0.5 * dpSize_rh_large;
+ const qreal dpSize_rh_large2 = 10;
+ const qreal dpSize_rh_large2_2 = 0.5 * dpSize_rh_large2;
+ const qreal pad_rh = dpSize_rh * 2;
+ const qreal ymin_rh = height_main + rhHeaderHeight + i * height_rh + pad_rh;
+ const qreal ymax_rh = height_main + rhHeaderHeight + (i + 1) * height_rh - pad_rh;
+ const qreal ydefault_rh = 0.5 * (ymin_rh + ymax_rh);
+
+ const qreal vfact_rh = 1 / (vmax_rh - vmin_rh); // zero division handled elsewhere
+ const qreal yfact_rh = vfact_rh * (ymax_rh - ymin_rh);
+ const qreal vfact_rh_smooth =
+ 1 / (vmax_rh_smooth - vmin_rh_smooth); // ditto
+ const qreal yfact_rh_smooth = vfact_rh_smooth * (ymax_rh - ymin_rh);
+ const qreal vfact_rh_global = 1 / (vmax_rh_global - vmin_rh_global); // ditto
+ const qreal yfact_rh_global = vfact_rh_global * (ymax_rh - ymin_rh);
+
+ const bool sharedYScale = true;
+
+ // Compute scene coordinates of history curve ...
+ QList<qreal> x_rh;
+ QList<qreal> y_rh;
+ QList<qreal> y_rh_smooth;
+ for (int j = 0; j < rh->values.size(); ++j) {
+ const qreal t = rh->timestamps.at(j);
+ const qreal v = rh->values.at(j);
+ const qreal v_smooth = rh->smoothValues.at(j);
+ x_rh.append(
+ (rh->timestamps.size() > 1) ? (xmin + (t - loTimestamp) * xfact) : xdefault);
+
+ if (sharedYScale) {
+ y_rh.append(
+ BMMisc::v2y(v, ymax_rh, vmin_rh_global, yfact_rh_global, ydefault_rh));
+ y_rh_smooth.append(
+ BMMisc::v2y(
+ v_smooth, ymax_rh, vmin_rh_global, yfact_rh_global, ydefault_rh));
+ } else {
+ y_rh.append(
+ BMMisc::v2y(v, ymax_rh, vmin_rh, yfact_rh, ydefault_rh));
+ y_rh_smooth.append(
+ BMMisc::v2y(
+ v_smooth, ymax_rh, vmin_rh_smooth, yfact_rh_smooth, ydefault_rh));
+ }
+ }
+
+ // Draw border rectangle ...
+ scene_far->addRect(
+ xmin, ymin_rh, xmax - xmin, ymax_rh - ymin_rh, QPen(QColor(200, 200, 200, 255)));
+
+ // Draw history curve of raw values ...
+ {
+ QPainterPath path(QPointF(x_rh.first(), y_rh.first()));
+ for (int j = 1; j < x_rh.size(); ++j)
+ path.lineTo(x_rh.at(j), y_rh.at(j));
+ QGraphicsItem *item = scene_mid_aa->addPath(path, QPen(QColor(255, 200, 100, 255)));
+ item->setZValue(-1);
+ }
+
+ // Draw history curve of smooth values ...
+ {
+ QPainterPath path(QPointF(x_rh.first(), y_rh_smooth.first()));
+ for (int j = 1; j < x_rh.size(); ++j)
+ path.lineTo(x_rh.at(j), y_rh_smooth.at(j));
+ scene_mid_aa->addPath(path, QPen(QColor(128, 128, 128, 255)));
+ }
+
+ // Draw raw data points ...
+ {
+ QPainterPath path;
+ path.setFillRule(Qt::WindingFill);
+ for (int j = 0; j < x_rh.size(); ++j)
+ path.addRect(
+ x_rh.at(j) - dpSize_rh_2, y_rh.at(j) - dpSize_rh_2,
+ dpSize_rh, dpSize_rh);
+
+ const QColor color(128, 100, 50, 255);
+ QGraphicsItem *item = scene_near->addPath(path, QPen(color), QBrush(color));
+ item->setZValue(-1);
+ }
+
+ // Draw smooth data points except the ones representing the target value ...
+ {
+ QPainterPath path;
+ path.setFillRule(Qt::WindingFill);
+ for (int j = 0; j < x_rh.size(); ++j)
+ if (j != rh->targetPos) {
+ path.addRect(
+ x_rh.at(j) - dpSize_rh_2, y_rh_smooth.at(j) - dpSize_rh_2,
+ dpSize_rh, dpSize_rh);
+ }
+
+ const QColor color(0, 0, 0, 255);
+ scene_near->addPath(path, QPen(color), QBrush(color));
+ }
+
+ // Draw the smooth data point representing the base value ...
+ {
+ QPainterPath path;
+ path.addEllipse(
+ x_rh.at(rh->basePos) - dpSize_rh_large2_2,
+ y_rh_smooth.at(rh->basePos) - dpSize_rh_large2_2,
+ dpSize_rh_large2, dpSize_rh_large2);
+ const QColor color(0, 0, 255, 255);
+ scene_near->addPath(path, QPen(color));
+ }
+
+ // Draw the smooth data point representing the target value ...
+ {
+ QPainterPath path;
+ path.addRect(
+ x_rh.at(rh->targetPos) - dpSize_rh_large_2,
+ y_rh_smooth.at(rh->targetPos) - dpSize_rh_large_2,
+ dpSize_rh_large, dpSize_rh_large);
+ const QColor color(0, 0, 0, 255);
+ scene_near->addPath(path, QPen(color), QBrush(Qt::yellow));
+ }
+
+ // Draw labels ...
+
+ // ... base result ID ...
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(QString("Res ID: %1").arg(rh->baseResultId), font_rh);
+ text->setPos(xmin - labelPad_rh_hor - text->boundingRect().width(), ymin_rh);
+ }
+
+ qreal textYPos = ymin_rh;
+
+ // ... base value ...
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString(" Base: %1").arg(rh->smoothValues.at(rh->basePos)), font_rh);
+ text->setPos(xmax + labelPad_rh_hor, textYPos);
+ textYPos += text->boundingRect().height() + labelPad_rh_ver;
+ }
+
+ // ... target value ...
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString("Target: %1").arg(rh->smoothValues.at(rh->targetPos)), font_rh);
+ text->setPos(xmax + labelPad_rh_hor, textYPos);
+ textYPos += text->boundingRect().height() + labelPad_rh_ver;
+ }
+
+ // ... global normalized weight (i.e. these add up to 1) ...
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(
+ QString("Weight: %1").arg(rh->totWeight), font_rh);
+ text->setPos(xmax + labelPad_rh_hor, textYPos);
+ textYPos += text->boundingRect().height() + labelPad_rh_ver;
+ }
+
+ // ... difference of log2 values ...
+ const qreal diff = diffs.at(i);
+ {
+ QGraphicsSimpleTextItem *text =
+ scene_near->addSimpleText(QString(" Diff: %1").arg(diff), font_rh);
+ text->setPos(xmax + labelPad_rh_hor, textYPos);
+ textYPos += text->boundingRect().height() + labelPad_rh_ver;
+ }
+
+ // ... weighted difference of log2 values ...
+ {
+ const qreal wdiff = rh->totWeight * diff;
+ const qreal fact = 1.0 / sortedResultHistories.size();
+ const QColor color(BMMisc::redToGreenColor(wdiff, 0.0001 * fact, 1.0 * fact, true));
+
+ QGraphicsSimpleTextItem *text1 =
+ scene_near->addSimpleText(QString(" WDiff: "), font_rh);
+ text1->setPos(xmax + labelPad_rh_hor, textYPos);
+
+ QGraphicsSimpleTextItem *text2 =
+ scene_near->addSimpleText(QString("%1").arg(wdiff), font_rh);
+ text2->setPos(xmax + labelPad_rh_hor + text1->boundingRect().width(), textYPos);
+
+ // Add a colored background rect ...
+ QGraphicsItem *bgRect = scene_near->addRect(
+ text2->boundingRect(), QPen(color), QBrush(color));
+ bgRect->setPos(xmax + labelPad_rh_hor + text1->boundingRect().width(), textYPos);
+ bgRect->setZValue(-1);
+
+ textYPos += text1->boundingRect().height() + labelPad_rh_ver;
+ }
+ }
+ }
+
+ // --- END result history graphs ---
+
+ return true;
+}
diff --git a/src/bm/plotter.h b/src/bm/plotter.h
new file mode 100644
index 0000000..50bbc53
--- /dev/null
+++ b/src/bm/plotter.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the BM project on Qt Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 or 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef PLOTTER_H
+#define PLOTTER_H
+
+#include "indextree.h"
+#include <QList>
+#include <QVector>
+#include <QString>
+#include <QImage>
+
+class QGraphicsScene;
+
+class Plotter
+{
+public:
+ virtual ~Plotter() {}
+ QImage createImage(QString *error = 0) const;
+private:
+ virtual QRectF sceneRect() const = 0;
+ virtual bool drawScenes(
+ QGraphicsScene *scene_far, QGraphicsScene *scene_mid_aa, QGraphicsScene *scene_near,
+ QString *error = 0) const = 0;
+};
+
+class ResultHistoryPlotter : public Plotter
+{
+public:
+ ResultHistoryPlotter(
+ const QList<int> &timestamps, const QList<qreal> &values, int resultId, int basePos = -1);
+
+private:
+ QList<int> timestamps;
+ QList<qreal> values;
+ int resultId;
+ int basePos;
+
+ qreal width;
+ qreal height;
+
+ QRectF sceneRect() const;
+ bool drawScenes(
+ QGraphicsScene *scene_far, QGraphicsScene *scene_mid_aa, QGraphicsScene *scene_near,
+ QString *error) const;
+};
+
+class IndexPlotter : public Plotter
+{
+public:
+ IndexPlotter(
+ const QList<int> &timestamps, const QList<qreal> &values, const QVector<bool> &missing_,
+ const QString &indexName, const QList<const IndexTree::ResultHistory *> *resultHistories,
+ const int baseTimestampPos = -1, const bool showBaseValue = true,
+ const qreal baseValue = 100.0);
+
+private:
+ QList<int> timestamps;
+ QList<qreal> values;
+ QVector<bool> missing;
+ QString indexName;
+ const QList<const IndexTree::ResultHistory *> *resultHistories;
+ int baseTimestampPos;
+ bool showBaseValue;
+ qreal baseValue;
+
+ qreal width;
+ qreal height;
+ qreal height_main;
+ qreal height_rh;
+ qreal rhHeaderHeight;
+ int rhDispSize;
+
+ QRectF sceneRect() const;
+ bool drawScenes(
+ QGraphicsScene *scene_far, QGraphicsScene *scene_mid_aa, QGraphicsScene *scene_near,
+ QString *error) const;
+};
+
+#endif // PLOTTER_H