#include <KLocalizedString>
#endif
+#include <algorithm>
+
#include <QBrush>
#include <QDebug>
#include <QPen>
byteCost(), time);
}
} else {
- const auto label = m_data.labels.value(column).toHtmlEscaped();
+ const auto label = Util::wrapLabel(m_data.labels.value(column), 96);
switch (m_type) {
case Allocations:
return i18n("<qt>%2 allocations after %3 from:<p "
Q_ASSERT(m_data.labels.size() < ChartRows::MAX_NUM_COST);
beginResetModel();
m_data = data;
+ m_timestamps.clear();
+ const int rows = rowCount();
+ m_timestamps.reserve(rows);
+ for (int row = 0; row < rows; ++row)
+ {
+ m_timestamps.append(m_data.rows[row].timeStamp);
+ }
m_columnDataSetBrushes.clear();
m_columnDataSetPens.clear();
- const auto columns = columnCount();
+ const int columns = columnCount();
for (int i = 0; i < columns; ++i) {
auto color = colorForColumn(i, columns);
#ifdef QWT_FOUND
{
beginResetModel();
m_data = {};
+ m_timestamps = {};
m_columnDataSetBrushes = {};
m_columnDataSetPens = {};
endResetModel();
qint64 ChartModel::getTimestamp(int row) const
{
- return m_data.rows[row].timeStamp;
+ return m_timestamps[row];
+}
+
+qint64 ChartModel::getCost(int row, int column) const
+{
+ return m_data.rows[row].cost[column / 2];
}
QString ChartModel::getColumnLabel(int column) const
{
return m_columnDataSetBrushes[column];
}
+
+int ChartModel::getRowForTimestamp(qint64 timestamp) const
+{
+ // check if 'timestamp' is not greater than the maximum available timestamp
+ int lastIndex = m_timestamps.size() - 1;
+ if (!((lastIndex >= 0) && (timestamp <= m_timestamps[lastIndex])))
+ {
+ return false;
+ }
+ int result;
+ // find the first element that is greater than 'timestamp'
+ auto up_it = std::upper_bound(m_timestamps.begin(), m_timestamps.end(), timestamp);
+ if (up_it != m_timestamps.end())
+ {
+ result = up_it - m_timestamps.begin() - 1;
+ }
+ else // all elements are not greater than 'timestamp'
+ {
+ result = lastIndex; // due to check in the beginning of the function
+ }
+ return result;
+}
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
qint64 getTimestamp(int row) const;
+ qint64 getCost(int row, int column) const;
QString getColumnLabel(int column) const;
const QPen& getColumnDataSetPen(int column) const;
const QBrush& getColumnDataSetBrush(int column) const;
+ // get an index of the chart row which timestamp is less than or equal to 'timestamp'
+ // and also it's the maximum from all such timestamps (i.e. the row's timestamp is the
+ // nearest to 'timestamp' to the left or is equal to 'timestamp') - if 'timestamp' is
+ // not less than the minimum timestamp and not greater than the maximum timestamp
+ // of all rows, otherwise return -1
+ int getRowForTimestamp(qint64 timestamp) const;
+
public slots:
void resetData(const ChartData& data);
void clearData();
private:
ChartData m_data;
Type m_type;
+ QVector<qint64> m_timestamps;
// we cache the pens and brushes as constructing them requires allocations
// otherwise
QVector<QPen> m_columnDataSetPens;
int rows = m_model->rowCount();
if (rows > 0)
{
- m_boundingRect.setLeft(m_model->data(m_model->index(0, 0)).toDouble());
- m_boundingRect.setTop(m_model->data(m_model->index(0, m_column)).toDouble());
- m_boundingRect.setRight(m_model->data(m_model->index(rows - 1, 0)).toDouble());
- qreal maxCost = -1E9;
+ m_boundingRect.setLeft(m_model->getTimestamp(0));
+ m_boundingRect.setTop(m_model->getCost(0, m_column));
+ m_boundingRect.setRight(m_model->getTimestamp(rows - 1));
+ qint64 maxCost = -1;
for (int row = 0; row < rows; ++row)
{
- qreal cost = m_model->data(m_model->index(row, m_column)).toDouble();
+ qint64 cost = m_model->getCost(row, m_column);
if (cost > maxCost)
{
maxCost = cost;
}
m_boundingRect.setBottom(maxCost);
}
+ else
+ {
+ m_boundingRect = {};
+ }
}
QPointF ChartModel2QwtSeriesData::sample(size_t i) const
{
int row = (int)i;
- qreal timeStamp = m_model->data(m_model->index(row, 0)).toDouble();
- qreal cost = m_model->data(m_model->index(row, m_column)).toDouble();
- return QPointF(timeStamp, cost);
+ return QPointF(m_model->getTimestamp(row), m_model->getCost(row, m_column));
}
QRectF ChartModel2QwtSeriesData::boundingRect() const
#include <qwt_scale_draw.h>
#include <qwt_symbol.h>
-#include <QRegularExpression>
+#include <QToolTip>
+
+#include <limits>
class TimeScaleDraw: public QwtScaleDraw
{
protected:
virtual QwtText trackerTextF(const QPointF &pos) const
{
- QString value;
- if (m_plot->isSizeModel())
+//qDebug() << "pos.x=" << pos.x() << "; pos.y=" << pos.y();
+ if ((pos.x() < 0) || (pos.y() < 0))
+ {
+ return {};
+ }
+ QString s;
+ if (m_plot->getCurveTooltip(pos, s))
{
- value = Util::formatByteSize(pos.y());
+ m_plot->clearTooltip();
}
- else
+ else // show default text
{
- value = QString::number((qint64)pos.y());
+ QString value;
+ if (m_plot->isSizeModel())
+ {
+ value = Util::formatByteSize(pos.y());
+ }
+ else
+ {
+ value = QString::number((qint64)pos.y());
+ }
+ s = QString(" %1 : %2 ").arg(Util::formatTime((qint64)pos.x())).arg(value);
+ m_plot->restoreTooltip();
}
- QwtText text(QString(" %1 : %2 ").arg(Util::formatTime((qint64)pos.x())).arg(value));
+ QwtText text(s);
text.setColor(Qt::white);
QColor c = rubberBandPen().color();
text.setBorderPen(QPen(c));
}
}
-static QString getCurveTitle(QString label)
-{
- const int MaxLineLength = 48;
- const int LastLineExtra = 12; // take into account the label continuation " (max=...)"
-
- int labelLength = label.size();
- if (labelLength + LastLineExtra <= MaxLineLength)
- {
- return label.toHtmlEscaped();
- }
- static QRegularExpression delimBefore("[(<]");
- static QRegularExpression delimAfter("[- .,)>\\/]");
- QString result;
- do
- {
- int i = -1;
- int wrapAfter = 0;
- int i1 = label.indexOf(delimBefore, MaxLineLength);
- int i2 = label.indexOf(delimAfter, MaxLineLength - 1);
- if (i1 >= 0)
- {
- if (i2 >= 0)
- {
- if (i2 < i1)
- {
- i = i2;
- wrapAfter = 1;
- }
- else
- {
- i = i1;
- }
- }
- else
- {
- i = i1;
- }
- }
- else
- {
- i = i2;
- wrapAfter = 1;
- }
- if (i < 0)
- {
- break;
- }
- i += wrapAfter;
- result += label.left(i).toHtmlEscaped();
- label.remove(0, i);
- if (label.isEmpty()) // special: avoid <br> at the end
- {
- return result;
- }
- result += "<br>";
- labelLength -= i;
- }
- while (labelLength + LastLineExtra > MaxLineLength);
- result += label.toHtmlEscaped();
- return result;
-}
-
void ChartWidgetQwtPlot::rebuild(bool resetZoomAndPan)
{
+ const int MaxTitleLineLength = 48;
+
detachItems();
if (resetZoomAndPan)
QRectF bounds = adapter->boundingRect();
qint64 maxCost = bounds.bottom();
- QString title = getCurveTitle(m_model->getColumnLabel(column));
- title += QString(" (max=<b>%1</b>)").arg(
- m_isSizeModel ? Util::formatByteSize(maxCost) : QString::number(maxCost));
- auto curve = new QwtPlotCurve(title);
+ QString titleEnd = QString(" (max=<b>%1</b>)").arg(m_isSizeModel
+ ? Util::formatByteSize(maxCost) : QString::number(maxCost));
+ int lastLineExtra = titleEnd.length() - 2 * 3; // minus the two "<b>" tags length
+ auto curve = new QwtPlotCurve(
+ Util::wrapLabel(m_model->getColumnLabel(column), MaxTitleLineLength, lastLineExtra) +
+ titleEnd);
curve->setRenderHint(QwtPlotItem::RenderAntialiased, true);
curve->setYAxis(QwtPlot::yRight);
replot();
}
}
+
+bool ChartWidgetQwtPlot::getCurveTooltip(const QPointF &position, QString &tooltip) const
+{
+ qint64 timestamp = position.x();
+ qreal cost = position.y();
+ if ((timestamp < 0) || (cost < 0))
+ {
+ return false;
+ }
+ int row = m_model->getRowForTimestamp(position.x());
+ if (row < 0)
+ {
+ return false;
+ }
+ // find a column which value (cost) for the row found is greater than or equal to
+ // 'cost' and at the same time this value is the smallest from all such values
+ qint64 minCostFound = std::numeric_limits<qint64>::max();
+ int columnFound = -1;
+ int column = 1;
+ if (!hasOption(ShowTotal))
+ {
+ column += 2;
+ }
+ int columns = m_model->columnCount();
+ for (; column < columns; column += 2)
+ {
+ qint64 columnCost = m_model->getCost(row, column);
+ if ((columnCost >= cost) && (columnCost <= minCostFound))
+ {
+ minCostFound = columnCost;
+ columnFound = column;
+ }
+ }
+ if (columnFound >= 0)
+ {
+//qDebug() << "timestamp: " << timestamp << " ms; row timestamp: " << m_model->getTimestamp(row) << " ms; cost="
+// << cost << "; row=" << row << "; minCostFound=" << minCostFound;
+ tooltip = QString(" %1 ").arg(
+ m_model->data(m_model->index(row, columnFound), Qt::ToolTipRole).toString());
+ return true;
+ }
+ return false;
+}
+
+void ChartWidgetQwtPlot::clearTooltip()
+{
+ if (m_plotTooltip.isEmpty())
+ {
+ m_plotTooltip = parentWidget()->toolTip();
+ }
+ parentWidget()->setToolTip(QString());
+ QToolTip::hideText();
+}
+
+void ChartWidgetQwtPlot::restoreTooltip()
+{
+ if (!m_plotTooltip.isEmpty())
+ {
+ parentWidget()->setToolTip(m_plotTooltip);
+ }
+}
void setModel(ChartModel* model);
+ bool isSizeModel() const { return m_isSizeModel; }
+
Options options() const { return m_options; }
bool hasOption(Options option) const { return (m_options & option) != 0; }
void resetZoom();
- bool isSizeModel() const { return m_isSizeModel; }
+ bool getCurveTooltip(const QPointF &position, QString &tooltip) const;
private:
+ friend class Zoomer;
+
+ void clearTooltip();
+
+ void restoreTooltip();
+
ChartModel *m_model;
bool m_isSizeModel;
Zoomer *m_zoomer;
QwtScaleDiv m_xScaleDiv, m_yScaleDiv;
+
+ QString m_plotTooltip;
};
inline ChartWidgetQwtPlot::Options operator | (ChartWidgetQwtPlot::Options i, ChartWidgetQwtPlot::Options f)
#include "util.h"
+#include <QRegularExpression>
#include <QString>
#ifndef NO_K_LIB
return result;
#endif
}
+
+QString Util::wrapLabel(QString label, int maxLineLength, int lastLineExtra,
+ const QString& delimiter)
+{
+ int labelLength = label.size();
+ if (labelLength + lastLineExtra <= maxLineLength)
+ {
+ return label.toHtmlEscaped();
+ }
+ static QRegularExpression delimBefore("[(<]");
+ static QRegularExpression delimAfter("[- .,)>\\/]");
+ QString result;
+ do
+ {
+ int i = -1;
+ int wrapAfter = 0;
+ int i1 = label.indexOf(delimBefore, maxLineLength);
+ int i2 = label.indexOf(delimAfter, maxLineLength - 1);
+ if (i1 >= 0)
+ {
+ if (i2 >= 0)
+ {
+ if (i2 < i1)
+ {
+ i = i2;
+ wrapAfter = 1;
+ }
+ else
+ {
+ i = i1;
+ }
+ }
+ else
+ {
+ i = i1;
+ }
+ }
+ else
+ {
+ i = i2;
+ wrapAfter = 1;
+ }
+ if (i < 0)
+ {
+ break;
+ }
+ i += wrapAfter;
+ result += label.left(i).toHtmlEscaped();
+ label.remove(0, i);
+ if (label.isEmpty()) // special: avoid <br> at the end
+ {
+ return result;
+ }
+ result += delimiter;
+ labelLength -= i;
+ }
+ while (labelLength + lastLineExtra > maxLineLength);
+ result += label.toHtmlEscaped();
+ return result;
+}
#define UTIL_H
#include <qglobal.h>
-
-class QString;
+#include <QString>
namespace Util {
QString formatByteSize(double size, int precision = 1);
+QString wrapLabel(QString label, int maxLineLength, int lastLineExtra = 0,
+ const QString &delimiter = QString("<br>"));
+
}
#endif // UTIL_H