diff options
author | jasplin <qt-info@nokia.com> | 2010-03-17 14:54:06 +0100 |
---|---|---|
committer | jasplin <qt-info@nokia.com> | 2010-03-17 14:54:06 +0100 |
commit | e669c67a6e86c784aa1ab238b95b0771761397fd (patch) | |
tree | fa3587c21002ad268c5f726f1dc18731ac02ffc3 /src/bm | |
parent | e2d2a8f4d4694b6a4744a31efd55f21bae1af194 (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.pro | 4 | ||||
-rw-r--r-- | src/bm/bmmisc.cpp | 678 | ||||
-rw-r--r-- | src/bm/bmmisc.h | 11 | ||||
-rw-r--r-- | src/bm/bmrequest.cpp | 29 | ||||
-rw-r--r-- | src/bm/bmrequest.h | 13 | ||||
-rw-r--r-- | src/bm/plotter.cpp | 943 | ||||
-rw-r--r-- | src/bm/plotter.h | 100 |
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> ×tamps, 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> ×tamps, 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> ×tamps, 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> ×tamps, 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> ×tamps, 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> ×tamps, 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> ×tamps, 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> ×tamps, 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 |