Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Rewrote profile graph drawing code.

Graphing is now reasonably generic to allow for future graphs to be fairly
easily added without too much replication of code.
  • Loading branch information...
commit 0b65a2bc99ea926111c0ff19723bd5e2ac84827d 1 parent 023a4f8
James Benton authored
Showing with 3,306 additions and 2,507 deletions.
  1. +8 −2 gui/CMakeLists.txt
  2. +173 −0 gui/calldurationgraph.h
  3. +99 −0 gui/graphing/frameaxiswidget.cpp
  4. +32 −0 gui/graphing/frameaxiswidget.h
  5. +141 −0 gui/graphing/graphaxiswidget.cpp
  6. +73 −0 gui/graphing/graphaxiswidget.h
  7. +48 −0 gui/graphing/graphing.h
  8. +42 −0 gui/graphing/graphlabelwidget.h
  9. +187 −0 gui/graphing/graphview.cpp
  10. +93 −0 gui/graphing/graphview.h
  11. +583 −0 gui/graphing/graphwidget.cpp
  12. +112 −0 gui/graphing/graphwidget.h
  13. +82 −0 gui/graphing/heatmapverticalaxiswidget.cpp
  14. +24 −0 gui/graphing/heatmapverticalaxiswidget.h
  15. +276 −0 gui/graphing/heatmapview.cpp
  16. +124 −0 gui/graphing/heatmapview.h
  17. +258 −0 gui/graphing/histogramview.cpp
  18. +41 −0 gui/graphing/histogramview.h
  19. +80 −0 gui/graphing/timeaxiswidget.cpp
  20. +16 −0 gui/graphing/timeaxiswidget.h
  21. +0 −666 gui/graphwidget.cpp
  22. +0 −105 gui/graphwidget.h
  23. +202 −148 gui/profiledialog.cpp
  24. +2 −9 gui/profiledialog.h
  25. +470 −0 gui/profileheatmap.h
  26. +5 −4 gui/profiletablemodel.cpp
  27. +128 −0 gui/profiling.h
  28. +0 −1,078 gui/timelinewidget.cpp
  29. +0 −148 gui/timelinewidget.h
  30. +7 −347 gui/ui/profiledialog.ui
View
10 gui/CMakeLists.txt
@@ -10,7 +10,6 @@ set(qapitrace_SRCS
apitracemodel.cpp
argumentseditor.cpp
glsledit.cpp
- graphwidget.cpp
imageviewer.cpp
jumpwidget.cpp
mainwindow.cpp
@@ -26,8 +25,15 @@ set(qapitrace_SRCS
traceloader.cpp
traceprocess.cpp
trimprocess.cpp
- timelinewidget.cpp
vertexdatainterpreter.cpp
+ graphing/frameaxiswidget.cpp
+ graphing/graphwidget.cpp
+ graphing/graphaxiswidget.cpp
+ graphing/graphview.cpp
+ graphing/heatmapview.cpp
+ graphing/heatmapverticalaxiswidget.cpp
+ graphing/histogramview.cpp
+ graphing/timeaxiswidget.cpp
)
qt4_automoc(${qapitrace_SRCS})
View
173 gui/calldurationgraph.h
@@ -0,0 +1,173 @@
+#ifndef CALLDURATIONGRAPH_H
+#define CALLDURATIONGRAPH_H
+
+#include "graphing/graphwidget.h"
+#include "trace_profiler.hpp"
+#include "profiling.h"
+
+/**
+ * Wrapper for call duration graphs.
+ *
+ * This implements the transformSelectionIn and transformSelectionOut to
+ * allow sharing the selection between the graphs and the heatmap as they
+ * are using different scales. The duration graphs have call.no on the X-axis
+ * whereas the heatmap has time on the X axis.
+ */
+class CallDurationGraph : public GraphWidget {
+public:
+ CallDurationGraph(QWidget* parent = 0) :
+ GraphWidget(parent),
+ m_profile(NULL)
+ {
+ }
+
+ void setProfile(const trace::Profile* profile)
+ {
+ m_profile = profile;
+ }
+
+protected:
+ /* Transform from time-based horizontal selection to call no based. */
+ virtual SelectionState transformSelectionIn(SelectionState state)
+ {
+ if (!m_profile || state.type != SelectionState::Horizontal) {
+ return state;
+ }
+
+ qint64 timeStart = state.start;
+ qint64 timeEnd = state.end;
+
+ std::vector<trace::Profile::Call>::const_iterator itr;
+
+ itr =
+ Profiling::binarySearchTimespan<
+ trace::Profile::Call,
+ &trace::Profile::Call::cpuStart,
+ &trace::Profile::Call::cpuDuration>
+ (m_profile->calls.begin(), m_profile->calls.end(), timeStart, true);
+
+ state.start = itr - m_profile->calls.begin();
+
+ itr =
+ Profiling::binarySearchTimespan<
+ trace::Profile::Call,
+ &trace::Profile::Call::cpuStart,
+ &trace::Profile::Call::cpuDuration>
+ (m_profile->calls.begin(), m_profile->calls.end(), timeEnd, true);
+
+ state.end = itr - m_profile->calls.begin();
+
+ return state;
+ }
+
+ virtual SelectionState transformSelectionOut(SelectionState state)
+ {
+ if (!m_profile || state.type != SelectionState::Horizontal) {
+ return state;
+ }
+
+ qint64 start = qMax<qint64>(0, state.start);
+ qint64 end = qMin<qint64>(state.end, m_profile->calls.size());
+
+ /* Call based -> time based */
+ state.start = m_profile->calls[start].cpuStart;
+ state.end = m_profile->calls[end].cpuStart + m_profile->calls[end].cpuDuration;
+
+ return state;
+ }
+
+private:
+ const trace::Profile* m_profile;
+};
+
+/* Data provider for call duration graphs */
+class CallDurationDataProvider : public GraphDataProvider {
+public:
+ CallDurationDataProvider(const trace::Profile* profile, bool gpu) :
+ m_gpu(gpu),
+ m_profile(profile),
+ m_selectionState(NULL)
+ {
+ }
+
+ virtual qint64 size() const
+ {
+ return m_profile ? m_profile->calls.size() : 0;
+ }
+
+ virtual bool selected(qint64 index) const
+ {
+ if (m_selectionState) {
+ if (m_selectionState->type == SelectionState::Horizontal) {
+ if (m_selectionState->start <= index && index < m_selectionState->end) {
+ return true;
+ }
+ } else if (m_selectionState->type == SelectionState::Vertical) {
+ return m_profile->calls[index].program == m_selectionState->start;
+ }
+ }
+
+ return false;
+ }
+
+ virtual void setSelectionState(SelectionState* state)
+ {
+ m_selectionState = state;
+ }
+
+ virtual qint64 value(qint64 index) const
+ {
+ if (m_gpu) {
+ return m_profile->calls[index].gpuDuration;
+ } else {
+ return m_profile->calls[index].cpuDuration;
+ }
+ }
+
+ virtual void itemDoubleClicked(qint64 index) const
+ {
+ if (!m_profile) {
+ return;
+ }
+
+ if (index < 0 || index >= m_profile->calls.size()) {
+ return;
+ }
+
+ const trace::Profile::Call& call = m_profile->calls[index];
+ Profiling::jumpToCall(call.no);
+ }
+
+ virtual QString itemTooltip(qint64 index) const
+ {
+ if (!m_profile) {
+ return QString();
+ }
+
+ if (index < 0 || index >= m_profile->calls.size()) {
+ return QString();
+ }
+
+ const trace::Profile::Call& call = m_profile->calls[index];
+
+ QString text;
+ text = QString::fromStdString(call.name);
+ text += QString("\nCall: %1").arg(call.no);
+ text += QString("\nCPU Duration: %1").arg(Profiling::getTimeString(call.cpuDuration));
+
+ if (call.pixels >= 0) {
+ text += QString("\nGPU Duration: %1").arg(Profiling::getTimeString(call.gpuDuration));
+ text += QString("\nPixels Drawn: %1").arg(QLocale::system().toString((qlonglong)call.pixels));
+ text += QString("\nProgram: %1").arg(call.program);
+ }
+
+ return text;
+ }
+
+private:
+ bool m_gpu;
+ const trace::Profile* m_profile;
+ SelectionState* m_selectionState;
+};
+
+#endif
View
99 gui/graphing/frameaxiswidget.cpp
@@ -0,0 +1,99 @@
+#include "frameaxiswidget.h"
+
+#include <QPainter>
+
+FrameAxisWidget::FrameAxisWidget(QWidget* parent) :
+ GraphAxisWidget(parent),
+ m_data(NULL)
+{
+ setSelectable(GraphAxisWidget::Range);
+}
+
+void FrameAxisWidget::setDataProvider(FrameDataProvider* data)
+{
+ delete m_data;
+ m_data = data;
+}
+
+void FrameAxisWidget::paintEvent(QPaintEvent *)
+{
+ if (!m_data || m_orientation != GraphAxisWidget::Horizontal) {
+ /* TODO: Vertical axis support */
+ return;
+ }
+
+ QPainter painter(this);
+ painter.setPen(Qt::black);
+ painter.setBrush(Qt::lightGray);
+ painter.drawRect(0, 0, width() - 1, height() - 1);
+
+ qint64 range = m_valueEnd - m_valueBegin;
+ double dxdv = width() / (double)range;
+ double scroll = dxdv * m_valueBegin;
+ int lastLabel = -9999;
+
+ /* Iterate over frames, drawing a label when there is space to do so */
+ for (unsigned i = 0; i < m_data->size(); ++i) {
+ static const int padding = 4;
+ qint64 start = m_data->frameStart(i);
+ qint64 end = m_data->frameEnd(i);
+ bool visible = false;
+
+ if (start > m_valueEnd) {
+ break;
+ }
+
+ if (end < m_valueBegin) {
+ visible = false;
+ }
+
+ double left = dxdv * start;
+ double right = dxdv * end;
+ QString text = QString("%1").arg(i);
+
+ int width = painter.fontMetrics().width(text) + padding * 2;
+
+ if (right > scroll) {
+ visible = true;
+ }
+
+ if (left - lastLabel > width) {
+ lastLabel = left + width;
+
+ if (visible) {
+ int textX;
+
+ if (left < scroll && right - left > width) {
+ if (right - scroll > width) {
+ textX = 0;
+ } else {
+ textX = right - scroll - width;
+ }
+ } else {
+ textX = left - scroll;
+ }
+
+ painter.drawText(textX + padding, 0, width - padding, height() - 5, Qt::AlignLeft | Qt::AlignVCenter, text);
+ painter.drawLine(left - scroll, height() / 2, left - scroll, height() - 1);
+ }
+ } else if (visible) {
+ painter.drawLine(left - scroll, height() * 3/4.0, left - scroll, height() - 1);
+ }
+ }
+
+ /* Draw selection */
+ if (hasSelection()) {
+ double left = (dxdv * m_selectionState->start) - scroll;
+ double right = (dxdv * m_selectionState->end) - scroll;
+
+ painter.setPen(Qt::green);
+
+ if (left >= 0 && left <= width()) {
+ painter.drawLine(left, 0, left, height());
+ }
+
+ if (right >= 0 && right <= width()) {
+ painter.drawLine(right, 0, right, height());
+ }
+ }
+}
View
32 gui/graphing/frameaxiswidget.h
@@ -0,0 +1,32 @@
+#ifndef FRAMEAXISWIDGET_H
+#define FRAMEAXISWIDGET_H
+
+#include "graphaxiswidget.h"
+
+class FrameDataProvider {
+public:
+ /* Number of frames */
+ virtual unsigned size() const = 0;
+
+ /* Start and end values of frame */
+ virtual qint64 frameStart(unsigned frame) const = 0;
+ virtual qint64 frameEnd(unsigned frame) const = 0;
+};
+
+/**
+ * A generic axis which will draw frame numbers over a period of values.
+ * Frames designated by start / end values.
+ */
+class FrameAxisWidget : public GraphAxisWidget {
+public:
+ FrameAxisWidget(QWidget* parent = 0);
+
+ void setDataProvider(FrameDataProvider* data);
+
+ virtual void paintEvent(QPaintEvent *e);
+
+protected:
+ FrameDataProvider* m_data;
+};
+
+#endif
View
141 gui/graphing/graphaxiswidget.cpp
@@ -0,0 +1,141 @@
+#include "graphaxiswidget.h"
+
+#include <QMouseEvent>
+
+GraphAxisWidget::GraphAxisWidget(QWidget* parent) :
+ QWidget(parent),
+ m_selectable(None),
+ m_selectionState(NULL)
+{
+}
+
+
+bool GraphAxisWidget::hasSelection()
+{
+ if (!m_selectionState) {
+ return false;
+ }
+
+ if (m_selectionState->type == SelectionState::Horizontal && m_orientation == GraphAxisWidget::Horizontal) {
+ return true;
+ }
+
+ if (m_selectionState->type == SelectionState::Vertical && m_orientation == GraphAxisWidget::Vertical) {
+ return true;
+ }
+
+ return false;
+}
+
+
+void GraphAxisWidget::setSelectable(SelectionStyle selectable)
+{
+ m_selectable = selectable;
+}
+
+
+void GraphAxisWidget::setSelectionState(SelectionState* state)
+{
+ m_selectionState = state;
+}
+
+
+void GraphAxisWidget::setOrientation(Orientation v)
+{
+ m_orientation = v;
+
+ if (m_orientation == Horizontal) {
+ setMinimumWidth(60);
+ } else {
+ setMinimumHeight(60);
+ }
+}
+
+
+void GraphAxisWidget::mouseMoveEvent(QMouseEvent *e)
+{
+ if (m_selectable == None) {
+ return;
+ }
+
+ int pos, max;
+
+ if (m_orientation == Horizontal) {
+ pos = e->x();
+ max = width();
+ } else {
+ pos = e->y();
+ max = height();
+ }
+
+ double value = m_valueEnd - m_valueBegin;
+ value *= pos / (double)max;
+ value += m_valueBegin;
+
+ if (e->buttons().testFlag(Qt::LeftButton)) {
+ m_selectionState->start = qMin<qint64>(m_mousePressValue, value);
+ m_selectionState->end = qMax<qint64>(m_mousePressValue, value);
+ m_selectionState->type = m_orientation == Horizontal ? SelectionState::Horizontal : SelectionState::Vertical;
+ emit selectionChanged();
+ update();
+ }
+}
+
+
+void GraphAxisWidget::mousePressEvent(QMouseEvent *e)
+{
+ if (m_selectable == None) {
+ return;
+ }
+
+ int pos, max;
+
+ if (m_orientation == Horizontal) {
+ pos = e->x();
+ max = width();
+ } else {
+ pos = e->y();
+ max = height();
+ }
+
+ double value = m_valueEnd - m_valueBegin;
+ value *= pos / (double)max;
+ value += m_valueBegin;
+
+ m_mousePressPosition = e->pos();
+ m_mousePressValue = value;
+}
+
+
+void GraphAxisWidget::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (m_selectable == None) {
+ return;
+ }
+
+ int dx = qAbs(m_mousePressPosition.x() - e->x());
+ int dy = qAbs(m_mousePressPosition.y() - e->y());
+
+ if (dx + dy < 2) {
+ m_selectionState->type = SelectionState::None;
+ emit selectionChanged();
+ }
+}
+
+
+void GraphAxisWidget::setRange(qint64 min, qint64 max)
+{
+ m_valueMin = min;
+ m_valueMax = max;
+ update();
+}
+
+
+void GraphAxisWidget::setView(qint64 start, qint64 end)
+{
+ m_valueBegin = start;
+ m_valueEnd = end;
+ update();
+}
+
+#include "graphaxiswidget.moc"
View
73 gui/graphing/graphaxiswidget.h
@@ -0,0 +1,73 @@
+#ifndef GRAPHAXISWIDGET_H
+#define GRAPHAXISWIDGET_H
+
+#include "graphing.h"
+
+#include <QWidget>
+
+/**
+ * The generic base class of all graph axes.
+ *
+ * Handles orientation, simple selections, and view area.
+ */
+class GraphAxisWidget : public QWidget {
+ Q_OBJECT
+public:
+ enum Orientation {
+ Horizontal,
+ Vertical
+ };
+
+ enum SelectionStyle {
+ None,
+ Single,
+ Range
+ };
+
+public:
+ GraphAxisWidget(QWidget* parent = 0);
+ virtual ~GraphAxisWidget(){}
+
+ /* Is this axis part of the active selection */
+ bool hasSelection();
+
+ void setSelectable(SelectionStyle selectable);
+ void setSelectionState(SelectionState* state);
+
+ void setOrientation(Orientation v);
+
+ virtual void mouseMoveEvent(QMouseEvent *e);
+ virtual void mousePressEvent(QMouseEvent *e);
+ virtual void mouseReleaseEvent(QMouseEvent *e);
+
+public slots:
+ /* The minimum and maximum values of this axis */
+ void setRange(qint64 min, qint64 max);
+
+ /* The currently visible range of values */
+ void setView(qint64 start, qint64 end);
+
+signals:
+ void selectionChanged();
+
+protected:
+ Orientation m_orientation;
+
+ /* The min/max value of this axis */
+ qint64 m_valueMin;
+ qint64 m_valueMax;
+
+ /* The highest and lowest currently visible value */
+ qint64 m_valueBegin;
+ qint64 m_valueEnd;
+
+ /* Selection */
+ SelectionStyle m_selectable;
+ SelectionState* m_selectionState;
+
+ /* Mouse tracking */
+ QPoint m_mousePressPosition;
+ qint64 m_mousePressValue;
+};
+
+#endif
View
48 gui/graphing/graphing.h
@@ -0,0 +1,48 @@
+#ifndef GRAPHING_H
+#define GRAPHING_H
+
+#include <QString>
+
+/**
+ * A simple struct to hold a horizontal or vertical selection
+ */
+struct SelectionState {
+ enum SelectionType {
+ None,
+ Horizontal,
+ Vertical
+ };
+
+ SelectionType type;
+ qint64 start;
+ qint64 end;
+};
+
+
+/**
+ * Fairly generic data provider for graphs
+ */
+class GraphDataProvider {
+public:
+ virtual ~GraphDataProvider(){}
+
+ /* Number of elements in graph */
+ virtual qint64 size() const = 0;
+
+ /* Returns value for index */
+ virtual qint64 value(qint64 index) const = 0;
+
+ /* Is the item at index selected */
+ virtual bool selected(qint64 index) const = 0;
+
+ /* Get mouse hover tooltip for item */
+ virtual QString itemTooltip(qint64 index) const = 0;
+
+ /* Called on item double click */
+ virtual void itemDoubleClicked(qint64 index) const = 0;
+
+ /* Set pointer to selection state */
+ virtual void setSelectionState(SelectionState* state) = 0;
+};
+
+#endif
View
42 gui/graphing/graphlabelwidget.h
@@ -0,0 +1,42 @@
+#ifndef GRAPHLABELWIDGET_H
+#define GRAPHLABELWIDGET_H
+
+#include <QWidget>
+#include <QPainter>
+
+/**
+ * A very simple label widget, basically a box with text in.
+ */
+class GraphLabelWidget : public QWidget {
+public:
+ GraphLabelWidget(QString text = QString(), QWidget* parent = 0) :
+ QWidget(parent),
+ m_flags(Qt::AlignHCenter | Qt::AlignVCenter),
+ m_text(text)
+ {
+ }
+
+ void setText(const QString& text)
+ {
+ m_text = text;
+ }
+
+ void setFlags(int flags)
+ {
+ m_flags = flags;
+ }
+
+ virtual void paintEvent(QPaintEvent *)
+ {
+ QPainter painter(this);
+ painter.setPen(Qt::black);
+ painter.fillRect(rect(), Qt::lightGray);
+ painter.drawText(rect(), m_flags, m_text);
+ }
+
+protected:
+ int m_flags;
+ QString m_text;
+};
+
+#endif
View
187 gui/graphing/graphview.cpp
@@ -0,0 +1,187 @@
+#include "graphview.h"
+
+#include <QMouseEvent>
+#include <QApplication>
+
+GraphView::GraphView(QWidget* parent) :
+ QWidget(parent),
+ m_selectionState(NULL),
+ m_viewLeft(0),
+ m_viewRight(0),
+ m_viewBottom(0),
+ m_viewTop(0),
+ m_graphLeft(0),
+ m_graphRight(0),
+ m_graphBottom(0),
+ m_graphTop(0),
+ m_viewWidth(0),
+ m_viewWidthMin(0),
+ m_viewWidthMax(0),
+ m_viewHeight(0),
+ m_viewHeightMin(0),
+ m_viewHeightMax(0)
+{
+ memset(&m_previous, -1, sizeof(m_previous));
+}
+
+void GraphView::update()
+{
+ if (m_graphLeft != m_previous.m_graphLeft || m_graphRight != m_previous.m_graphRight) {
+ m_previous.m_graphLeft = m_graphLeft;
+ m_previous.m_graphRight = m_graphRight;
+
+ emit horizontalRangeChanged(m_graphLeft, m_graphRight);
+ }
+
+ if (m_viewLeft != m_previous.m_viewLeft || m_viewRight != m_previous.m_viewRight) {
+ m_previous.m_viewLeft = m_viewLeft;
+ m_previous.m_viewRight = m_viewRight;
+
+ emit horizontalViewChanged(m_viewLeft, m_viewRight);
+ }
+
+ if (m_graphBottom != m_previous.m_graphBottom || m_graphTop != m_previous.m_graphTop) {
+ m_previous.m_graphBottom = m_graphBottom;
+ m_previous.m_graphTop = m_graphTop;
+
+ emit verticalRangeChanged(m_graphBottom, m_graphTop);
+ }
+
+ if (m_viewBottom != m_previous.m_viewBottom || m_viewTop != m_previous.m_viewTop) {
+ m_previous.m_viewBottom = m_viewBottom;
+ m_previous.m_viewTop = m_viewTop;
+
+ emit verticalViewChanged(m_viewBottom, m_viewTop);
+ }
+
+ QWidget::update();
+}
+
+void GraphView::resizeEvent(QResizeEvent *)
+{
+ m_viewHeight = height();
+ m_viewHeightMin = m_viewHeight;
+ m_viewHeightMax = m_viewHeight;
+
+ m_viewTop = m_viewBottom + m_viewHeight;
+
+ update();
+}
+
+void GraphView::wheelEvent(QWheelEvent *e)
+{
+ int zoomPercent = 10;
+
+ /* If holding Ctrl key then zoom 2x faster */
+ if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
+ zoomPercent = 20;
+ }
+
+ /* Zoom view by adjusting width */
+ double dt = m_viewWidth;
+ double size = dt;
+ size *= -e->delta();
+
+ /* Zoom deltas normally come in increments of 120 */
+ size /= 120 * (100 / zoomPercent);
+
+ m_viewWidth += size;
+ m_viewWidth = qBound(m_viewWidthMin, m_viewWidth, m_viewWidthMax);
+
+ /* Scroll view to zoom around mouse */
+ dt -= m_viewWidth;
+ dt *= e->x();
+ dt /= width();
+
+ m_viewLeft = dt + m_viewLeft;
+ m_viewLeft = qBound(m_graphLeft, m_viewLeft, m_graphRight - m_viewWidth);
+ m_viewRight = m_viewLeft + m_viewWidth;
+
+ update();
+}
+
+void GraphView::mouseMoveEvent(QMouseEvent *e)
+{
+ if (e->buttons().testFlag(Qt::LeftButton)) {
+ /* Horizontal scroll */
+ double dvdx = m_viewWidth / (double)width();
+ dvdx *= m_mousePressPosition.x() - e->pos().x();
+
+ m_viewLeft = m_mousePressViewLeft + dvdx;
+ m_viewLeft = qBound(m_graphLeft, m_viewLeft, m_graphRight - m_viewWidth);
+ m_viewRight = m_viewLeft + m_viewWidth;
+
+ /* Vertical scroll */
+ double dvdy = m_viewHeight / (double)height();
+ dvdy *= m_mousePressPosition.y() - e->pos().y();
+
+ m_viewBottom = m_mousePressViewBottom + dvdy;
+ m_viewBottom = qBound(m_graphBottom, m_viewBottom, m_graphTop - m_viewHeight);
+ m_viewTop = m_viewBottom + m_viewHeight;
+
+ update();
+ }
+}
+
+void GraphView::mousePressEvent(QMouseEvent *e)
+{
+ m_mousePressPosition = e->pos();
+ m_mousePressViewLeft = m_viewLeft;
+ m_mousePressViewBottom = m_viewBottom;
+}
+
+void GraphView::mouseDoubleClickEvent(QMouseEvent *e)
+{
+ if (m_selectionState) {
+ m_selectionState->type = SelectionState::None;
+ emit selectionChanged();
+ }
+}
+
+void GraphView::setSelectionState(SelectionState* state)
+{
+ m_selectionState = state;
+}
+
+void GraphView::setHorizontalView(qint64 start, qint64 end)
+{
+ m_viewLeft = qBound(m_graphLeft, start, m_graphRight - (end - start));
+ m_viewRight = qBound(m_graphLeft, end, m_graphRight);
+ m_viewWidth = m_viewRight - m_viewLeft;
+ update();
+}
+
+void GraphView::setVerticalView(qint64 start, qint64 end)
+{
+ m_viewBottom = qBound(m_graphBottom, start, m_graphTop - (end - start));
+ m_viewTop = qBound(m_graphBottom, end, m_graphTop);
+ m_viewHeight = m_viewTop - m_viewBottom;
+ update();
+}
+
+void GraphView::setDefaultView(qint64 min, qint64 max)
+{
+ m_graphLeft = min;
+ m_graphRight = max;
+ m_viewWidth = max - min;
+
+ m_viewWidthMin = 1;
+ m_viewWidthMax = m_viewWidth;
+
+ m_viewLeft = min;
+ m_viewRight = max;
+
+ m_viewHeight = height();
+ m_viewHeightMin = m_viewHeight;
+ m_viewHeightMax = m_viewHeight;
+
+ m_viewBottom = 0;
+ m_viewTop = m_viewHeight;
+
+ m_graphBottom = 0;
+ m_graphTop = m_viewHeight;
+
+ update();
+}
+
+#include "graphview.moc"
View
93 gui/graphing/graphview.h
@@ -0,0 +1,93 @@
+#ifndef GRAPHVIEW_H
+#define GRAPHVIEW_H
+
+#include "graphing.h"
+
+#include <QWidget>
+
+/**
+ * The generic base class for a graph's view, this is the component that
+ * displays the actual data for the graph.
+ *
+ * - Stores the view area within the graph
+ * - Simple user interaction such as translating and zooming with mouse
+ * - Selection tracking synchronised with axis
+ */
+class GraphView : public QWidget {
+ Q_OBJECT
+public:
+ GraphView(QWidget* parent = 0);
+ virtual ~GraphView(){}
+
+ virtual void update();
+
+ virtual void resizeEvent(QResizeEvent *);
+
+ virtual void wheelEvent(QWheelEvent *e);
+ virtual void mouseMoveEvent(QMouseEvent *e);
+ virtual void mousePressEvent(QMouseEvent *e);
+ virtual void mouseDoubleClickEvent(QMouseEvent *e);
+
+ virtual void setSelectionState(SelectionState* state);
+
+ void setHorizontalView(qint64 start, qint64 end);
+ void setVerticalView(qint64 start, qint64 end);
+
+protected:
+ void setDefaultView(qint64 min, qint64 max);
+
+signals:
+ void selectionChanged();
+
+ void verticalViewChanged(qint64 start, qint64 end);
+ void verticalRangeChanged(qint64 min, qint64 max);
+
+ void horizontalRangeChanged(qint64 min, qint64 max);
+ void horizontalViewChanged(qint64 start, qint64 end);
+
+protected:
+ /* Viewport area */
+ qint64 m_viewLeft;
+ qint64 m_viewRight;
+ qint64 m_viewBottom;
+ qint64 m_viewTop;
+
+ /* Graph limits */
+ qint64 m_graphLeft;
+ qint64 m_graphRight;
+ qint64 m_graphBottom;
+ qint64 m_graphTop;
+
+ /* Viewport width (m_viewRight - m_viewLeft), used for zoom */
+ qint64 m_viewWidth;
+ qint64 m_viewWidthMin;
+ qint64 m_viewWidthMax;
+
+ /* Viewport height (m_viewTop - m_viewBottom), used for zoom */
+ qint64 m_viewHeight;
+ qint64 m_viewHeightMin;
+ qint64 m_viewHeightMax;
+
+ /* Mouse tracking */
+ QPoint m_mousePressPosition;
+ qint64 m_mousePressViewLeft;
+ qint64 m_mousePressViewBottom;
+
+ /* Selection */
+ SelectionState* m_selectionState;
+
+ /* State from the last update() call */
+ struct PreviousUpdate {
+ qint64 m_viewLeft;
+ qint64 m_viewRight;
+ qint64 m_viewBottom;
+ qint64 m_viewTop;
+
+ qint64 m_graphLeft;
+ qint64 m_graphRight;
+ qint64 m_graphBottom;
+ qint64 m_graphTop;
+ } m_previous;
+};
+
+#endif
View
583 gui/graphing/graphwidget.cpp
@@ -0,0 +1,583 @@
+#include "graphwidget.h"
+
+#include <QScrollBar>
+
+GraphWidget::GraphWidget(QWidget* parent) :
+ QWidget(parent),
+ m_view(NULL),
+ m_label(NULL),
+ m_axisTop(NULL),
+ m_axisLeft(NULL),
+ m_axisRight(NULL),
+ m_axisBottom(NULL),
+ m_horizontalScrollbar(NULL),
+ m_horizontalMin(0),
+ m_horizontalMax(0),
+ m_horizontalStart(0),
+ m_horizontalEnd(0),
+ m_horizontalScrollbarPolicy(Qt::ScrollBarAlwaysOff),
+ m_verticalScrollbar(NULL),
+ m_verticalMin(0),
+ m_verticalMax(0),
+ m_verticalStart(0),
+ m_verticalEnd(0),
+ m_verticalScrollbarPolicy(Qt::ScrollBarAlwaysOff)
+{
+ m_selection.type = SelectionState::None;
+
+ m_verticalScrollbar = new QScrollBar(this);
+ m_verticalScrollbar->setOrientation(Qt::Vertical);
+ m_verticalScrollbar->hide();
+ m_verticalScrollbar->resize(m_verticalScrollbar->sizeHint());
+
+ m_horizontalScrollbar = new QScrollBar(this);
+ m_horizontalScrollbar->setOrientation(Qt::Horizontal);
+ m_horizontalScrollbar->hide();
+ m_horizontalScrollbar->resize(m_horizontalScrollbar->sizeHint());
+
+ updateLayout();
+ setAutoFillBackground(true);
+}
+
+
+GraphView* GraphWidget::view()
+{
+ return m_view;
+}
+
+
+GraphLabelWidget* GraphWidget::label()
+{
+ return m_label;
+}
+
+
+GraphAxisWidget* GraphWidget::axis(AxisPosition pos)
+{
+ switch(pos) {
+ case AxisTop:
+ return m_axisTop;
+
+ case AxisLeft:
+ return m_axisLeft;
+
+ case AxisRight:
+ return m_axisRight;
+
+ case AxisBottom:
+ return m_axisBottom;
+
+ default:
+ return NULL;
+ }
+}
+
+
+void GraphWidget::setView(GraphView* view)
+{
+ delete m_view;
+ m_view = view;
+
+ updateConnections();
+
+ m_view->setSelectionState(&m_selection);
+ m_view->show();
+ m_view->update();
+}
+
+
+void GraphWidget::setLabel(GraphLabelWidget* label)
+{
+ delete m_label;
+ m_label = label;
+}
+
+
+void GraphWidget::setAxis(AxisPosition pos, GraphAxisWidget* axis)
+{
+ switch(pos) {
+ case AxisTop:
+ delete m_axisTop;
+ m_axisTop = axis;
+ m_axisTop->setOrientation(GraphAxisWidget::Horizontal);
+ m_axisTop->setSelectionState(&m_selection);
+ break;
+
+ case AxisLeft:
+ delete m_axisLeft;
+ m_axisLeft = axis;
+ m_axisLeft->setOrientation(GraphAxisWidget::Vertical);
+ m_axisLeft->setSelectionState(&m_selection);
+ break;
+
+ case AxisRight:
+ delete m_axisRight;
+ m_axisRight = axis;
+ m_axisRight->setOrientation(GraphAxisWidget::Vertical);
+ m_axisRight->setSelectionState(&m_selection);
+ break;
+
+ case AxisBottom:
+ delete m_axisBottom;
+ m_axisBottom = axis;
+ m_axisBottom->setOrientation(GraphAxisWidget::Horizontal);
+ m_axisBottom->setSelectionState(&m_selection);
+ break;
+ }
+
+ updateConnections();
+ updateSelection();
+ axis->show();
+}
+
+
+void GraphWidget::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy)
+{
+ m_horizontalScrollbarPolicy = policy;
+ updateScrollbars();
+}
+
+
+void GraphWidget::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy)
+{
+ m_verticalScrollbarPolicy = policy;
+ updateScrollbars();
+}
+
+
+void GraphWidget::resizeEvent(QResizeEvent *e)
+{
+ updateLayout();
+ update();
+}
+
+
+/* Used if a selection would be shared between graphs with different axis */
+SelectionState GraphWidget::transformSelectionIn(SelectionState state)
+{
+ return state;
+}
+
+
+/* Used if a selection would be shared between graphs with different axis */
+SelectionState GraphWidget::transformSelectionOut(SelectionState state)
+{
+ return state;
+}
+
+
+/* Update the scrollbars based on current view */
+void GraphWidget::updateScrollbars()
+{
+ /* Vertical scroll bar */
+ qint64 size = (m_verticalMax - m_verticalMin) - (m_verticalEnd - m_verticalStart);
+
+ if (size <= INT_MAX) {
+ m_verticalScrollbar->setValue(m_verticalStart - m_verticalMin);
+ m_verticalScrollbar->setPageStep(m_verticalEnd - m_verticalStart);
+ m_verticalScrollbar->setRange(0, size);
+ } else {
+ /* QScrollBar only supports up to INT_MAX values,
+ * here we must scale our values to match this */
+ double curSize = m_verticalEnd - m_verticalStart;
+ double pages = (m_verticalMax - m_verticalMin) / curSize;
+ double value = (m_verticalStart - m_verticalMin) / curSize;
+
+ m_verticalScrollbar->setValue(value);
+ m_verticalScrollbar->setPageStep(1);
+ m_verticalScrollbar->setRange(0, pages);
+ }
+
+ /* Adhere to scrollbar policy */
+ bool visible = false;
+
+ if (m_verticalScrollbarPolicy == Qt::ScrollBarAlwaysOn) {
+ visible = true;
+ } else if (m_verticalScrollbarPolicy == Qt::ScrollBarAlwaysOff) {
+ visible = false;
+ } else if (m_verticalScrollbarPolicy == Qt::ScrollBarAsNeeded) {
+ visible = m_verticalMin != m_verticalStart || m_verticalMax != m_verticalEnd;
+ }
+
+ if (visible != m_verticalScrollbar->isVisible()) {
+ m_verticalScrollbar->setVisible(visible);
+ updateLayout();
+ }
+
+ /* Horizontal scroll bar */
+ size = (m_horizontalMax - m_horizontalMin) - (m_horizontalEnd - m_horizontalStart);
+
+ if (size <= INT_MAX) {
+ m_horizontalScrollbar->setValue(m_horizontalStart - m_horizontalMin);
+ m_horizontalScrollbar->setPageStep(m_horizontalEnd - m_horizontalStart);
+ m_horizontalScrollbar->setRange(0, size);
+ } else {
+ /* QScrollBar only supports up to INT_MAX values,
+ * here we must scale our values to match this */
+ double dxdv = INT_MAX / (double)size;
+ double value = (m_horizontalStart - m_horizontalMin) * dxdv;
+ double pageStep = (m_horizontalEnd - m_horizontalStart) * dxdv;
+
+ m_horizontalScrollbar->setValue((int)value);
+ m_horizontalScrollbar->setPageStep((int)pageStep);
+ m_horizontalScrollbar->setRange(0, INT_MAX);
+ }
+
+ /* Adhere to scrollbar policy */
+ visible = false;
+
+ if (m_horizontalScrollbarPolicy == Qt::ScrollBarAlwaysOn) {
+ visible = true;
+ } else if (m_horizontalScrollbarPolicy == Qt::ScrollBarAlwaysOff) {
+ visible = false;
+ } else if (m_horizontalScrollbarPolicy == Qt::ScrollBarAsNeeded) {
+ visible = m_horizontalMin != m_horizontalStart || m_horizontalMax != m_horizontalEnd;
+ }
+
+ if (visible != m_horizontalScrollbar->isVisible()) {
+ m_horizontalScrollbar->setVisible(visible);
+ updateLayout();
+ }
+}
+
+
+/* Update all signal / slot connections */
+void GraphWidget::updateConnections()
+{
+ if (m_view) {
+ connect(m_view, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+
+ connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), this, SLOT(horizontalViewChange(qint64,qint64)), Qt::UniqueConnection);
+ connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), this, SLOT(horizontalRangeChange(qint64,qint64)), Qt::UniqueConnection);
+
+ connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), this, SLOT(verticalViewChange(qint64,qint64)), Qt::UniqueConnection);
+ connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), this, SLOT(verticalRangeChange(qint64,qint64)), Qt::UniqueConnection);
+ }
+
+ if (m_axisTop) {
+ if (m_view) {
+ connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), m_axisTop, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
+ connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), m_axisTop, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
+ }
+
+ connect(m_axisTop, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+ }
+
+ if (m_axisLeft) {
+ if (m_view) {
+ connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), m_axisLeft, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
+ connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), m_axisLeft, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
+ }
+
+ connect(m_axisLeft, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+ }
+
+ if (m_axisRight) {
+ if (m_view) {
+ connect(m_view, SIGNAL(verticalViewChanged(qint64,qint64)), m_axisRight, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
+ connect(m_view, SIGNAL(verticalRangeChanged(qint64,qint64)), m_axisRight, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
+ }
+
+ connect(m_axisRight, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+ }
+
+ if (m_axisBottom) {
+ if (m_view) {
+ connect(m_view, SIGNAL(horizontalViewChanged(qint64,qint64)), m_axisBottom, SLOT(setView(qint64,qint64)), Qt::UniqueConnection);
+ connect(m_view, SIGNAL(horizontalRangeChanged(qint64,qint64)), m_axisBottom, SLOT(setRange(qint64,qint64)), Qt::UniqueConnection);
+ }
+
+ connect(m_axisBottom, SIGNAL(selectionChanged()), this, SLOT(updateSelection()), Qt::UniqueConnection);
+ }
+
+ if (m_horizontalScrollbar) {
+ connect(m_horizontalScrollbar, SIGNAL(actionTriggered(int)), this, SLOT(horizontalScrollAction(int)));
+ }
+
+ if (m_verticalScrollbar) {
+ connect(m_verticalScrollbar, SIGNAL(actionTriggered(int)), this, SLOT(verticalScrollAction(int)));
+ }
+}
+
+
+/* Recalculate the layout */
+void GraphWidget::updateLayout()
+{
+ int x, y;
+ int padX = 0, padY = 0;
+
+ if (m_axisTop) {
+ padY += m_axisTop->height();
+ }
+
+ if (m_axisBottom) {
+ padY += m_axisBottom->height();
+ }
+
+ if (m_axisLeft) {
+ padX += m_axisLeft->width();
+ }
+
+ if (m_axisRight) {
+ padX += m_axisRight->width();
+ }
+
+ if (m_horizontalScrollbar->isVisible()) {
+ padY += m_horizontalScrollbar->height();
+ }
+
+ if (m_verticalScrollbar->isVisible()) {
+ padX += m_verticalScrollbar->width();
+ }
+
+ if (m_axisTop) {
+ x = m_axisLeft ? m_axisLeft->width() : 0;
+ y = 0;
+
+ m_axisTop->move(x, y);
+ m_axisTop->resize(width() - padX, m_axisTop->height());
+ }
+
+ if (m_axisBottom) {
+ x = m_axisLeft ? m_axisLeft->width() : 0;
+ y = height() - m_axisBottom->height();
+
+ if (m_horizontalScrollbar->isVisible()) {
+ y -= m_horizontalScrollbar->height();
+ }
+
+ m_axisBottom->move(x, y);
+ m_axisBottom->resize(width() - padX, m_axisBottom->height());
+ }
+
+ if (m_axisLeft) {
+ x = 0;
+ y = m_axisTop ? m_axisTop->height() : 0;
+
+ m_axisLeft->move(x, y);
+ m_axisLeft->resize(m_axisLeft->width(), height() - padY);
+ }
+
+ if (m_axisRight) {
+ x = width() - m_axisRight->width();
+ y = m_axisTop ? m_axisTop->height() : 0;
+
+ if (m_verticalScrollbar->isVisible()) {
+ x -= m_verticalScrollbar->width();
+ }
+
+ m_axisRight->move(x, y);
+ m_axisRight->resize(m_axisRight->width(), height() - padY);
+ }
+
+ if (m_view) {
+ x = m_axisLeft ? m_axisLeft->width() : 0;
+ y = m_axisTop ? m_axisTop->height() : 0;
+
+ m_view->move(x, y);
+ m_view->resize(width() - padX, height() - padY);
+ }
+
+ if (m_label) {
+ if (m_axisTop && m_axisLeft) {
+ m_label->move(0, 0);
+ m_label->resize(m_axisLeft->width(), m_axisTop->height());
+ }
+ }
+
+ if (m_verticalScrollbar) {
+ m_verticalScrollbar->move(width() - m_verticalScrollbar->width(), 0);
+
+ if (m_horizontalScrollbar) {
+ m_verticalScrollbar->resize(m_verticalScrollbar->width(), height() - m_horizontalScrollbar->height());
+ } else {
+ m_verticalScrollbar->resize(m_verticalScrollbar->width(), height());
+ }
+ }
+
+ if (m_horizontalScrollbar) {
+ m_horizontalScrollbar->move(0, height() - m_horizontalScrollbar->height());
+
+ if (m_verticalScrollbar) {
+ m_horizontalScrollbar->resize(width() - m_verticalScrollbar->width(), m_horizontalScrollbar->height());
+ } else {
+ m_horizontalScrollbar->resize(width(), m_horizontalScrollbar->height());
+ }
+ }
+}
+
+
+void GraphWidget::setSelection(SelectionState state)
+{
+ m_selection = transformSelectionIn(state);
+ updateSelection(false);
+}
+
+
+void GraphWidget::setHorizontalView(qint64 start, qint64 end)
+{
+ if (m_view) {
+ m_view->setHorizontalView(start, end);
+ }
+}
+
+
+void GraphWidget::setVerticalView(qint64 start, qint64 end)
+{
+ if (m_view) {
+ m_view->setVerticalView(start, end);
+ }
+}
+
+
+/* Called when the view is translated / zoomed */
+void GraphWidget::verticalViewChange(qint64 start, qint64 end)
+{
+ m_verticalStart = start;
+ m_verticalEnd = end;
+ updateScrollbars();
+
+ emit verticalViewChanged(start, end);
+}
+
+
+void GraphWidget::verticalRangeChange(qint64 start, qint64 end)
+{
+ m_verticalMin = start;
+ m_verticalMax = end;
+ updateScrollbars();
+
+ emit verticalRangeChanged(start, end);
+}
+
+
+void GraphWidget::horizontalViewChange(qint64 start, qint64 end)
+{
+ m_horizontalStart = start;
+ m_horizontalEnd = end;
+ updateScrollbars();
+
+ emit horizontalViewChanged(start, end);
+}
+
+
+void GraphWidget::horizontalRangeChange(qint64 start, qint64 end)
+{
+ m_horizontalMin = start;
+ m_horizontalMax = end;
+ updateScrollbars();
+
+ emit horizontalRangeChanged(start, end);
+}
+
+
+/* User interaction with horizontal scroll bar */
+void GraphWidget::horizontalScrollAction(int /*action*/)
+{
+ int value = m_horizontalScrollbar->sliderPosition();
+ qint64 size = (m_horizontalMax - m_horizontalMin) - (m_horizontalEnd - m_horizontalStart);
+
+ /* Calculate the new scroll values */
+ if (size <= INT_MAX) {
+ m_horizontalEnd -= m_horizontalStart;
+ m_horizontalStart = value + m_horizontalMin;
+ m_horizontalEnd += value;
+ } else {
+ /* QScrollBar only supports up to INT_MAX values, here we must scale
+ * our values to match this */
+ double dxdv = INT_MAX / (double)size;
+
+ size = m_horizontalEnd - m_horizontalStart;
+ m_horizontalStart = value / dxdv + m_horizontalMin;
+ m_horizontalEnd = m_horizontalStart + size;
+ }
+
+ /* Update view */
+ if (m_view) {
+ m_view->setHorizontalView(m_horizontalStart, m_horizontalEnd);
+ }
+
+ /* Update horizontal axes */
+ if (m_axisTop) {
+ m_axisTop->setView(m_horizontalStart, m_horizontalEnd);
+ }
+
+ if (m_axisBottom) {
+ m_axisBottom->setView(m_horizontalStart, m_horizontalEnd);
+ }
+
+ /* Inform the world of our changes! */
+ emit horizontalViewChanged(m_horizontalStart, m_horizontalEnd);
+}
+
+
+/* User interaction with vertical scroll bar */
+void GraphWidget::verticalScrollAction(int /*action*/)
+{
+ int value = m_verticalScrollbar->sliderPosition();
+ qint64 size = (m_verticalMax - m_verticalMin) - (m_verticalEnd - m_verticalStart);
+
+ /* Calculate the new scroll values */
+ if (size <= INT_MAX) {
+ m_verticalEnd -= m_verticalStart;
+ m_verticalStart = value + m_verticalMin;
+ m_verticalEnd += value;
+ } else {
+ /* QScrollBar only supports up to INT_MAX values, here we must scale
+ * our values to match this */
+ double dxdv = INT_MAX / (double)size;
+
+ size = m_verticalEnd - m_verticalStart;
+ m_verticalStart = value / dxdv + m_verticalMin;
+ m_verticalEnd = m_verticalStart + size;
+ }
+
+ /* Update view */
+ if (m_view) {
+ m_view->setVerticalView(m_verticalStart, m_verticalEnd);
+ }
+
+ /* Update vertical axes */
+ if (m_axisLeft) {
+ m_axisLeft->setView(m_verticalStart, m_verticalEnd);
+ }
+
+ if (m_axisRight) {
+ m_axisRight->setView(m_verticalStart, m_verticalEnd);
+ }
+
+ /* Inform the world of our changes! */
+ emit verticalViewChanged(m_verticalStart, m_verticalEnd);
+}
+
+
+/* Update child elements when selection changes */
+void GraphWidget::updateSelection(bool emitSignal)
+{
+ if (m_view) {
+ m_view->update();
+ }
+
+ if (m_axisTop) {
+ m_axisTop->update();
+ }
+
+ if (m_axisLeft) {
+ m_axisLeft->update();
+ }
+
+ if (m_axisRight) {
+ m_axisRight->update();
+ }
+
+ if (m_axisBottom) {
+ m_axisBottom->update();
+ }
+
+ if (emitSignal) {
+ emit selectionChanged(transformSelectionOut(m_selection));
+ }
+}
+
+
+#include "graphwidget.moc"
View
112 gui/graphing/graphwidget.h
@@ -0,0 +1,112 @@
+#ifndef GRAPHWIDGET_H
+#define GRAPHWIDGET_H
+
+#include "graphview.h"
+#include "graphaxiswidget.h"
+#include "graphlabelwidget.h"
+
+class QScrollBar;
+
+/**
+ * The generic GraphWidget class which combines the elements of a graph,
+ * the axis, view, scrollbars and label.
+ */
+class GraphWidget : public QWidget {
+ Q_OBJECT
+public:
+ enum AxisPosition {
+ AxisTop,
+ AxisLeft,
+ AxisRight,
+ AxisBottom
+ };
+
+public:
+ GraphWidget(QWidget* parent = 0);
+ virtual ~GraphWidget(){}
+
+ GraphView* view();
+ GraphLabelWidget* label();
+ GraphAxisWidget* axis(AxisPosition pos);
+
+ void setView(GraphView* view);
+ void setLabel(GraphLabelWidget* label);
+ void setAxis(AxisPosition pos, GraphAxisWidget* axis);
+
+ void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy);
+ void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy);
+
+ virtual void resizeEvent(QResizeEvent *e);
+
+protected:
+ /* Used if a selection would be shared between graphs with different axis */
+ virtual SelectionState transformSelectionIn(SelectionState state);
+ virtual SelectionState transformSelectionOut(SelectionState state);
+
+ /* Update the scrollbars based on current view */
+ void updateScrollbars();
+
+ /* Update all signal / slot connections */
+ void updateConnections();
+
+ /* Recalculate child widget layout */
+ void updateLayout();
+
+public slots:
+ void setSelection(SelectionState state);
+
+ /* Set view areas */
+ void setHorizontalView(qint64 start, qint64 end);
+ void setVerticalView(qint64 start, qint64 end);
+
+protected slots:
+ /* View changed by translation / zooming */
+ void verticalViewChange(qint64 start, qint64 end);
+ void verticalRangeChange(qint64 start, qint64 end);
+ void horizontalViewChange(qint64 start, qint64 end);
+ void horizontalRangeChange(qint64 start, qint64 end);
+
+ /* User interaction with scroll bars */
+ void horizontalScrollAction(int action);
+ void verticalScrollAction(int action);
+
+ /* Update child elements when selection changes */
+ void updateSelection(bool emitSignal = true);
+
+signals:
+ void selectionChanged(SelectionState state);
+
+ void verticalViewChanged(qint64 start, qint64 end);
+ void verticalRangeChanged(qint64 start, qint64 end);
+
+ void horizontalViewChanged(qint64 start, qint64 end);
+ void horizontalRangeChanged(qint64 start, qint64 end);
+
+protected:
+ SelectionState m_selection;
+
+ GraphView* m_view;
+
+ GraphLabelWidget* m_label;
+
+ GraphAxisWidget* m_axisTop;
+ GraphAxisWidget* m_axisLeft;
+ GraphAxisWidget* m_axisRight;
+ GraphAxisWidget* m_axisBottom;
+
+ QScrollBar* m_horizontalScrollbar;
+ qint64 m_horizontalMin;
+ qint64 m_horizontalMax;
+ qint64 m_horizontalStart;
+ qint64 m_horizontalEnd;
+ Qt::ScrollBarPolicy m_horizontalScrollbarPolicy;
+
+ QScrollBar* m_verticalScrollbar;
+ qint64 m_verticalMin;
+ qint64 m_verticalMax;
+ qint64 m_verticalStart;
+ qint64 m_verticalEnd;
+ Qt::ScrollBarPolicy m_verticalScrollbarPolicy;
+};
+
+#endif
View
82 gui/graphing/heatmapverticalaxiswidget.cpp
@@ -0,0 +1,82 @@
+#include "heatmapverticalaxiswidget.h"
+
+#include <qmath.h>
+#include <QPainter>
+#include <QMouseEvent>
+
+HeatmapVerticalAxisWidget::HeatmapVerticalAxisWidget(QWidget* parent) :
+ GraphAxisWidget(parent),
+ m_data(NULL)
+{
+ m_rowHeight = 20;
+}
+
+void HeatmapVerticalAxisWidget::setDataProvider(HeatmapDataProvider* data)
+{
+ delete m_data;
+ m_data = data;
+
+ m_valueMin = 0;
+ m_valueMax = (data->dataRows() + data->headerRows()) * m_rowHeight;
+
+ m_valueBegin = m_valueMin;
+ m_valueEnd = m_valueMax;
+
+ update();
+}
+
+void HeatmapVerticalAxisWidget::mouseDoubleClickEvent(QMouseEvent *e)
+{
+ if (e->pos().y() < m_data->headerRows() * m_rowHeight) {
+ m_selectionState->type = SelectionState::None;
+ emit selectionChanged();
+ } else {
+ int row = e->pos().y();
+ row -= m_data->headerRows() * m_rowHeight;
+ row += m_valueBegin;
+ row /= m_rowHeight;
+
+ if (row >= m_data->dataRows()) {
+ m_selectionState->type = SelectionState::None;
+ emit selectionChanged();
+ } else {
+ m_selectionState->type = SelectionState::Vertical;
+ m_selectionState->start = m_data->dataRowAt(row);
+ m_selectionState->end = m_selectionState->start;
+ emit selectionChanged();
+ }
+ }
+}
+
+void HeatmapVerticalAxisWidget::paintEvent(QPaintEvent *)
+{
+ if (!m_data) {
+ return;
+ }
+
+ QPainter painter(this);
+ painter.setPen(Qt::black);
+ painter.setBrush(Qt::lightGray);
+ painter.drawRect(0, 0, width() - 1, height() - 1);
+
+ /* Draw scrollable rows */
+ painter.translate(0, m_data->headerRows() * m_rowHeight - m_valueBegin % m_rowHeight);
+ int rowStart = m_valueBegin / m_rowHeight;
+ int rowEnd = qMin<int>(qCeil(m_valueEnd / (double)m_rowHeight), m_data->dataRows());
+
+ for (unsigned i = rowStart; i < rowEnd; ++i) {
+ painter.drawText(0, 0, width(), m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_data->dataLabel(i));
+ painter.drawLine(0, m_rowHeight, width(), m_rowHeight);
+ painter.translate(0, m_rowHeight);
+ }
+
+ /* Draw fixed position headers */
+ painter.resetTransform();
+ painter.drawRect(0, 0, width() - 1, m_data->headerRows() * m_rowHeight);
+
+ for (unsigned i = 0; i < m_data->headerRows(); ++i) {
+ painter.drawText(0, 0, width(), m_rowHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_data->headerLabel(i));
+ painter.drawLine(0, m_rowHeight, width(), m_rowHeight);
+ painter.translate(0, m_rowHeight);
+ }
+}
View
24 gui/graphing/heatmapverticalaxiswidget.h
@@ -0,0 +1,24 @@
+#ifndef HEATMAPVERTICALAXISWIDGET_H
+#define HEATMAPVERTICALAXISWIDGET_H
+
+#include "heatmapview.h"
+#include "graphaxiswidget.h"
+
+/**
+ * Vertical axis specifically for heatmap displaying header and data rows
+ */
+class HeatmapVerticalAxisWidget : public GraphAxisWidget {
+public:
+ HeatmapVerticalAxisWidget(QWidget* parent);
+
+ void setDataProvider(HeatmapDataProvider* data);
+
+ virtual void mouseDoubleClickEvent(QMouseEvent *e);
+ virtual void paintEvent(QPaintEvent *);
+
+protected:
+ int m_rowHeight;
+ HeatmapDataProvider* m_data;
+};
+
+#endif
View
276 gui/graphing/heatmapview.cpp
@@ -0,0 +1,276 @@
+#include "heatmapview.h"
+
+#include <qmath.h>
+#include <QToolTip>
+#include <QPainter>
+#include <QMouseEvent>
+
+HeatmapView::HeatmapView(QWidget* parent) :
+ GraphView(parent),
+ m_data(NULL)
+{
+ m_rowHeight = 20;
+ setMouseTracking(true);
+}
+
+
+void HeatmapView::setDataProvider(HeatmapDataProvider* data)
+{
+ delete m_data;
+ m_data = data;
+
+ if (m_data) {
+ m_data->setSelectionState(m_selectionState);
+
+ setDefaultView(m_data->start(), m_data->end());
+
+ m_viewWidthMin = 1000;
+
+ m_graphBottom = 0;
+ m_graphTop = (m_data->headerRows() + m_data->dataRows()) * m_rowHeight;
+ } else {
+ setDefaultView(0, 0);
+ m_graphBottom = m_graphTop = 0;
+ }
+
+ update();
+}
+
+
+void HeatmapView::setSelectionState(SelectionState* state)
+{
+ if (m_data) {
+ m_data->setSelectionState(state);
+ }
+
+ GraphView::setSelectionState(state);
+}
+
+
+void HeatmapView::mouseMoveEvent(QMouseEvent *e)
+{
+ GraphView::mouseMoveEvent(e);
+
+ if (e->buttons() || !m_data) {
+ QToolTip::hideText();
+ return;
+ }
+
+ qint64 index = itemAtPosition(e->pos());
+
+ if (index >= 0) {
+ QToolTip::showText(e->globalPos(), m_data->itemTooltip(index));
+ } else {
+ QToolTip::hideText();
+ }
+}
+
+
+void HeatmapView::mouseDoubleClickEvent(QMouseEvent *e)
+{
+ if (m_data && e->button() == Qt::LeftButton) {
+ qint64 index = itemAtPosition(e->pos());
+
+ if (index >= 0) {
+ m_data->itemDoubleClicked(index);
+ return;
+ }
+ }
+
+ GraphView::mouseDoubleClickEvent(e);
+}
+
+
+void HeatmapView::paintEvent(QPaintEvent *)
+{
+ if (!m_data) {
+ return;
+ }
+
+ QPainter painter(this);
+ painter.fillRect(0, m_data->headerRows() * m_rowHeight, width(), height(), Qt::white);
+
+ /* Draw data rows */
+ painter.translate(0, m_data->headerRows() * m_rowHeight - m_viewBottom % m_rowHeight);
+ int rowStart = m_viewBottom / m_rowHeight;
+ int rowEnd = qMin<int>(qCeil(m_viewTop / (double)m_rowHeight), m_data->dataRows());
+
+ for (unsigned i = rowStart; i < rowEnd; ++i) {
+ HeatmapRowIterator* itr = m_data->dataRowIterator(i, m_viewLeft, m_viewRight, width());
+ paintRow(painter, itr);
+ painter.translate(0, m_rowHeight);
+ delete itr;
+ }
+
+ /* Draw Header */
+ painter.resetTransform();
+ painter.fillRect(0, 0, width(), m_data->headerRows() * m_rowHeight, Qt::white);
+
+ for (unsigned i = 0; i < m_data->headerRows(); ++i) {
+ HeatmapRowIterator* itr = m_data->headerRowIterator(i, m_viewLeft, m_viewRight, width());
+ paintRow(painter, itr);
+ painter.translate(0, m_rowHeight);
+ delete itr;
+ }
+
+ /* Draw Axis Lines */
+ painter.resetTransform();
+ painter.setPen(Qt::black);
+ painter.drawLine(0, m_rowHeight, width(), m_rowHeight);
+ painter.drawLine(0, m_rowHeight * 2, width(), m_rowHeight * 2);
+
+ painter.setPen(QColor(240, 240, 240));
+ painter.translate(0, m_data->headerRows() * m_rowHeight - m_viewBottom % m_rowHeight);
+
+ for (unsigned i = rowStart; i < rowEnd; ++i) {
+ painter.drawLine(0, m_rowHeight, width(), m_rowHeight);
+ painter.translate(0, m_rowHeight);
+ }
+
+ /* Draw selection borders */
+ painter.resetTransform();
+ painter.setPen(Qt::green);
+
+ if (m_selectionState->type == SelectionState::Horizontal) {
+ double dxdt = width() / (double)m_viewWidth;
+ double scroll = m_viewLeft * dxdt;
+ double left = (m_selectionState->start * dxdt) - scroll;
+ double right = (m_selectionState->end * dxdt) - scroll;
+
+ /* Highlight time */
+ if (left >= 0 && left <= width()) {
+ painter.drawLine(left, 0, left, height());
+ }
+
+ if (right >= 0 && right <= width()) {
+ painter.drawLine(right, 0, right, height());
+ }
+ } else if (m_selectionState->type == SelectionState::Vertical) {
+ /* Highlight row */
+ int row = m_selectionState->start;
+
+ for (unsigned i = rowStart; i < rowEnd; ++i) {
+ if (m_data->dataRowAt(i) == row) {
+ row = i - rowStart;
+
+ painter.translate(0, m_data->headerRows() * m_rowHeight - m_viewBottom % m_rowHeight);
+ painter.drawLine(0, (row + 1) * m_rowHeight, width(), (row + 1) * m_rowHeight);
+
+ if (row > 0) {
+ painter.drawLine(0, row * m_rowHeight, width(), row * m_rowHeight);
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+
+void HeatmapView::paintRow(QPainter& painter, HeatmapRowIterator* itr)
+{
+ bool selection = m_selectionState ? m_selectionState->type != SelectionState::None : false;
+
+ while (itr->next())
+ {
+ double heat = itr->heat();
+ int width = itr->width();
+ int x = itr->step();
+
+ /* Gamma correction */
+ heat = qPow(heat, 1.0 / 2.0);
+
+ if (width == 1) {
+ /* Draw single line */
+ if (selection) {
+ double selectedHeat = itr->selectedHeat();
+
+ if (selectedHeat >= 0.999) {
+ heat = 255.0 - qBound(0.0, heat * 255.0, 255.0);
+
+ if (itr->isGpu()) {
+ painter.setPen(QColor(255, heat, heat));
+ } else {
+ painter.setPen(QColor(heat, heat, 255));
+ }
+
+ painter.drawLine(x, 0, x, m_rowHeight - 1);
+ } else {
+ heat = 255.0 - qBound(0.0, heat * 100.0, 100.0);
+ painter.setPen(QColor(heat, heat, heat));
+ painter.drawLine(x, 0, x, m_rowHeight - 1);
+
+ if (selectedHeat > 0.001) {
+ selectedHeat = qPow(selectedHeat, 1.0 / 2.0);
+ selectedHeat = qBound(0.0, selectedHeat * 255.0, 255.0);
+
+ if (itr->isGpu()) {
+ painter.setPen(QColor(255, 0, 0, selectedHeat));
+ } else {
+ painter.setPen(QColor(0, 0, 255, selectedHeat));
+ }
+
+ painter.drawLine(x, 0, x, m_rowHeight - 1);
+ }
+ }
+ } else {
+ heat = qBound(0.0, heat * 255.0, 255.0);
+
+ if (itr->isGpu()) {
+ painter.setPen(QColor(255, 255 - heat, 255 - heat));
+ } else {
+ painter.setPen(QColor(255 - heat, 255 - heat, 255));
+ }
+
+ painter.drawLine(x, 0, x, m_rowHeight - 1);
+ }
+ } else {
+ double selectedHeat = itr->selectedHeat();
+
+ if (selection && selectedHeat < 0.9) {
+ painter.fillRect(x, 0, width, m_rowHeight, QColor(255 - 100, 255 - 100, 255 - 100));
+ } else if (itr->isGpu()) {
+ painter.fillRect(x, 0, width, m_rowHeight, QColor(255, 0, 0));
+ } else {
+ painter.fillRect(x, 0, width, m_rowHeight, QColor(0, 0, 255));
+ }
+
+ if (width > 6) {
+ painter.setPen(Qt::white);
+ QString elided = painter.fontMetrics().elidedText(itr->label(), Qt::ElideRight, width - 1);
+
+ painter.drawText(x + 1, 0, width - 1, m_rowHeight - 1,
+ Qt::AlignLeft | Qt::AlignVCenter,
+ elided);
+ }
+ }
+ }
+}
+
+
+qint64 HeatmapView::itemAtPosition(QPoint pos)
+{
+ if (!m_data) {
+ return -1;
+ }
+
+ double t = m_viewWidth / (double)width();
+ t *= pos.x();
+ t += m_viewLeft;
+
+ qint64 time = (qint64)t;
+ qint64 index;
+
+ if (pos.y() < m_data->headerRows() * m_rowHeight) {
+ int row = pos.y() / m_rowHeight;
+ index = m_data->headerItemAt(row, time);
+ } else {
+ int row = pos.y();
+ row -= m_data->headerRows() * m_rowHeight;
+ row += m_viewBottom;
+ row /= m_rowHeight;
+ index = m_data->dataItemAt(row, time);
+ }
+
+ return index;
+}
View
124 gui/graphing/heatmapview.h
@@ -0,0 +1,124 @@
+#ifndef HEATMAPVIEW_H
+#define HEATMAPVIEW_H
+
+#include "graphview.h"
+
+/**
+ * The heatmap iterator will only return data when there is something to draw,
+ * this allows much faster access to the data in the case where the view is
+ * zoomed out to the point of where there is multiple calls in one pixel,
+ * it automagically calculates the heat for that pixel.
+ */
+class HeatmapRowIterator {
+public:
+ virtual ~HeatmapRowIterator(){}
+
+ /* Go to the next visible heat map */
+ virtual bool next() = 0;
+
+ /* Is the current value GPU or CPU heat */
+ virtual bool isGpu() const = 0;
+
+ /* Current step (normally x coordinate) */
+ virtual int step() const = 0;
+
+ /* Current width (in steps) */
+ virtual int width() const = 0;
+
+ /* Current heat */
+ virtual float heat() const = 0;
+
+ /* Heat value for selected calls */
+ virtual float selectedHeat() const = 0;
+
+ /* Label only used when there is a single call in this heat */
+ virtual QString label() const = 0;
+};
+
+
+/**
+ * Data provider for the whole heatmap
+ */
+class HeatmapDataProvider {
+public:
+ virtual ~HeatmapDataProvider(){}
+
+ /* The start and end values (time on x-axis) for the heatmap */
+ virtual qint64 start() const = 0;
+ virtual qint64 end() const = 0;
+
+ /*
+ * Header rows (fixed at top of heatmap view)
+ */
+
+ /* Header row count */
+ virtual unsigned headerRows() const = 0;
+
+ /* Label to be used in the vertical axis */
+ virtual QString headerLabel(unsigned row) const = 0;
+
+ /* Get identifier (program no) for row */
+ virtual qint64 headerRowAt(unsigned row) const = 0;
+
+ /* Get item at row and time */
+ virtual qint64 headerItemAt(unsigned row, qint64 time) const = 0;
+
+ /* Get iterator for a row between start and end time for steps */
+ virtual HeatmapRowIterator* headerRowIterator(int row, qint64 start, qint64 end, int steps) const = 0;
+
+ /*
+ * Data rows (scrollable in heatmap view)
+ */
+
+ /* Data row count */
+ virtual unsigned dataRows() const = 0;
+
+ /* Label to be used in the vertical axis */
+ virtual QString dataLabel(unsigned row) const = 0;
+
+ /* Get identifier (program no) for row */
+ virtual qint64 dataRowAt(unsigned row) const = 0;
+
+ /* Get item at row and time */
+ virtual qint64 dataItemAt(unsigned row, qint64 time) const = 0;
+
+ /* Get iterator for a row between start and end time for steps */
+ virtual HeatmapRowIterator* dataRowIterator(int row, qint64 start, qint64 end, int steps) const = 0;
+
+ /* Handle double click on item */
+ virtual void itemDoubleClicked(qint64 index) const = 0;
+
+ /* Get mouse over tooltip for item */
+ virtual QString itemTooltip(qint64 index) const = 0;
+
+ /* Set the selection */
+ virtual void setSelectionState(SelectionState* state) = 0;
+};
+
+
+/**
+ * A not very generic heatmap for row based data
+ */
+class HeatmapView : public GraphView {
+public:
+ HeatmapView(QWidget* parent);
+
+ void setDataProvider(HeatmapDataProvider* data);
+ void setSelectionState(SelectionState* state);
+
+ virtual void mouseMoveEvent(QMouseEvent *e);
+ virtual void mouseDoubleClickEvent(QMouseEvent *e);
+
+ virtual void paintEvent(QPaintEvent *e);
+ virtual void paintRow(QPainter& painter, HeatmapRowIterator* itr);
+
+
+protected:
+ qint64 itemAtPosition(QPoint pos);
+
+protected:
+ int m_rowHeight;
+ HeatmapDataProvider* m_data;
+};
+
+#endif
View
258 gui/graphing/histogramview.cpp
@@ -0,0 +1,258 @@
+#include "histogramview.h"
+
+#include <QPen>
+#include <QBrush>
+#include <qmath.h>
+#include <QPainter>
+#include <QToolTip>
+#include <QMouseEvent>
+
+HistogramView::HistogramView(QWidget* parent) :
+ GraphView(parent),
+ m_data(NULL)
+{
+ setMouseTracking(true);
+
+ m_gradientUnselected.setColorAt(0.9, QColor(200, 200, 200));
+ m_gradientUnselected.setColorAt(0.0, QColor(220, 220, 220));
+
+ m_gradientSelected.setColorAt(0.9, QColor(0, 0, 210));
+ m_gradientSelected.setColorAt(0.0, QColor(130, 130, 255));
+}
+
+
+void HistogramView::setDataProvider(GraphDataProvider* data)
+{
+ delete m_data;
+ m_data = data;
+
+ if (m_data) {
+ m_data->setSelectionState(m_selectionState);
+ setDefaultView(0, m_data->size());
+ m_viewWidthMin = 10;
+ } else {
+ setDefaultView(0, 0);
+ }
+}
+
+
+void HistogramView::setSelectionState(SelectionState* state)
+{
+ if (m_data) {
+ m_data->setSelectionState(state);
+ }
+
+ GraphView::setSelectionState(state);
+}
+
+
+void HistogramView::setSelectedGradient(const QLinearGradient& gradient)
+{
+ m_gradientSelected = gradient;
+}
+
+
+void HistogramView::setUnelectedGradient(const QLinearGradient& gradient)
+{
+ m_gradientUnselected = gradient;
+}
+
+
+void HistogramView::mouseMoveEvent(QMouseEvent *e)
+{
+ GraphView::mouseMoveEvent(e);
+
+ if (e->buttons() || !m_data) {
+ QToolTip::hideText();
+ return;
+ }
+
+ qint64 index = itemAtPosition(e->pos());
+ qint64 time = valueAtPosition(e->pos());
+
+ if (m_data->value(index) >= time) {
+ QToolTip::showText(e->globalPos(), m_data->itemTooltip(index));
+ } else {
+ QToolTip::hideText();
+ }
+}
+
+
+void HistogramView::mouseDoubleClickEvent(QMouseEvent *e)
+{
+ if (e->button() == Qt::LeftButton) {
+ qint64 index = itemAtPosition(e->pos());
+ qint64 time = valueAtPosition(e->pos());
+
+ if (m_data->value(index) >= time) {
+ m_data->itemDoubleClicked(index);
+ return;
+ }
+ }
+
+ GraphView::mouseDoubleClickEvent(e);
+}
+
+
+void HistogramView::update()
+{
+ m_graphBottom = 0;
+ m_graphTop = 0;
+
+ if (m_data) {
+ for (qint64 i = m_viewLeft; i < m_viewRight; ++i) {
+ qint64 value = m_data->value(i);
+
+ if (value > m_graphTop) {
+ m_graphTop = value;
+ }
+ }
+ }
+
+ GraphView::update();
+}
+
+
+void HistogramView::resizeEvent(QResizeEvent *)
+{
+ m_gradientSelected.setStart(0, height());
+ m_gradientUnselected.setStart(0, height());
+}
+
+
+/* Draw the histogram
+ *
+ * When the view is zoomed such that there is more than one item occupying a single pixel
+ * the one with the highest value will be displayed.
+ */
+void HistogramView::paintEvent(QPaintEvent *)
+{
+ if (!m_data) {
+ return;
+ }
+
+ QBrush selectedBrush = QBrush(m_gradientSelected);
+ QPen selectedPen = QPen(selectedBrush, 1);
+
+ QBrush unselectedBrush = QBrush(m_gradientUnselected);
+ QPen unselectedPen = QPen(unselectedBrush, 1);
+
+ QPainter painter(this);
+ painter.fillRect(0, 0, width(), height(), Qt::white);
+
+ double dydv = height() / (double)m_graphTop;
+ double dxdv = width() / (double)(m_viewRight - m_viewLeft);
+ bool selection = m_selectionState && m_selectionState->type != SelectionState::None;
+
+ if (dxdv < 1.0) {
+ /* Less than one pixel per item */
+ qint64 longestValue = m_graphBottom;
+ qint64 longestSelected = m_graphBottom;
+ int lastX = 0;
+ double x = 0;
+
+ if (selection) {
+ painter.setPen(unselectedPen);
+ } else {
+ painter.setPen(selectedPen);
+ }
+
+ for (qint64 i = m_viewLeft; i < m_viewRight; ++i) {
+ qint64 value = m_data->value(i);
+ int ix;
+
+ if (value > longestValue) {
+ longestValue = value;
+ }
+
+ if (selection && m_data->selected(i) && value > longestSelected) {
+ longestSelected = value;
+ }
+
+ x += dxdv;
+ ix = (int)x;
+
+ if (lastX != ix) {
+ painter.drawLine(lastX, height(), lastX, height() - (longestValue * dydv));
+
+ if (selection && longestSelected > m_graphBottom) {
+ painter.setPen(selectedPen);
+ painter.drawLine(lastX, height(), lastX, height() - (longestSelected * dydv));
+ painter.setPen(unselectedPen);
+ longestSelected = m_graphBottom;
+ }
+
+ longestValue = m_graphBottom;
+ lastX = ix;
+ }
+ }
+ } else {
+ /* Draw rectangles for graph */
+ double x = 0;
+
+ for (qint64 i = m_viewLeft; i < m_viewRight; ++i, x += dxdv) {
+ qint64 value = m_data->value(i);
+ int y = qMax<int>(1, value * dydv);
+
+ if (!selection || m_data->selected(i)) {
+ painter.fillRect(x, height() - y, dxdv, y, selectedBrush);
+ } else {
+ painter.fillRect(x, height() - y, dxdv, y, unselectedBrush);
+ }
+ }
+ }
+
+ /* Draw the borders for the selection */
+ if (m_selectionState && m_selectionState->type == SelectionState::Horizontal) {
+ double dxdt = width() / (double)m_viewWidth;
+ double scroll = m_viewLeft * dxdt;
+ double left = (m_selectionState->start * dxdt) - scroll;
+ double right = (m_selectionState->end * dxdt) - scroll;
+
+ painter.setPen(Qt::green);
+
+ if (left >= 0 && left <= width()) {
+ painter.drawLine(left, 0, left, height());
+ }
+
+ if (right >= 0 && right <= width()) {
+ painter.drawLine(right, 0, right, height());
+ }
+ }
+}
+
+
+/* Find the item with the highest value at pos.x() +/- 1,
+ * the mouse must be within the bar height-wise.
+ */
+qint64 HistogramView::itemAtPosition(QPoint pos) {
+ double dvdx = m_viewWidth / (double)width();
+
+ qint64 left = qFloor(dvdx) * (pos.x() - 1) + m_viewLeft;
+ qint64 right = qCeil(dvdx) * (pos.x() + 1) + m_viewLeft;
+
+ qint64 longestIndex = 0;
+ qint64 longestValue = 0;
+
+ left = qBound<qint64>(0, left, m_data->size() - 1);
+ right = qBound<qint64>(0, right, m_data->size() - 1);
+
+ for (qint64 i = left; i <= right; ++i) {
+ if (m_data->value(i) > longestValue) {
+ longestValue = m_data->value(i);
+ longestIndex = i;
+ }
+ }
+
+ return longestIndex;
+}
+
+
+/* Return the value at position */
+qint64 HistogramView::valueAtPosition(QPoint pos) {
+ double value = m_graphTop / (double)height();
+ value *= height() - pos.y();
+ value += m_graphBottom;
+ return (qint64)value;
+}
+
View
41 gui/graphing/histogramview.h
@@ -0,0 +1,41 @@
+#ifndef HISTOGRAMVIEW_H
+#define HISTOGRAMVIEW_H
+
+#include "graphview.h"
+
+/**
+ * Histogram graph view.
+ *
+ * When the view is zoomed such that there is more than one item occupying
+ * a single pixel the one with the highest value will be displayed.
+ */
+class HistogramView : public GraphView {
+public:
+ HistogramView(QWidget* parent);
+
+ void setDataProvider(GraphDataProvider* data);
+ void setSelectionState(SelectionState* state);
+
+ /* Gradient colours for selected and unselected items */
+ void setSelectedGradient(const QLinearGradient& gradient);
+ void setUnelectedGradient(const QLinearGradient& gradient);
+
+ virtual void mouseMoveEvent(QMouseEvent *e);
+ virtual void mouseDoubleClickEvent(QMouseEvent *e);
+
+ virtual void update();
+ virtual void resizeEvent(QResizeEvent *e);
+ virtual void paintEvent(QPaintEvent *e);
+
+protected:
+ qint64 itemAtPosition(QPoint pos);
+ qint64 valueAtPosition(QPoint pos);
+
+protected:
+ QLinearGradient m_gradientSelected;
+ QLinearGradient m_gradientUnselected;
+
+ GraphDataProvider* m_data;
+};
+
+#endif
View
80 gui/graphing/timeaxiswidget.cpp
@@ -0,0 +1,80 @@
+#include "timeaxiswidget.h"
+#include "profiling.h"
+
+#include <qmath.h>
+#include <QPainter>
+
+TimeAxisWidget::TimeAxisWidget(QWidget* parent) :
+ GraphAxisWidget(parent)
+{
+}
+
+void TimeAxisWidget::paintEvent(QPaintEvent *)
+{
+ /* TODO: Support selections? */
+ /* TODO: Vertical scrolling? */
+
+ QPainter painter(this);
+ painter.setPen(Qt::black);
+ painter.setBrush(Qt::lightGray);
+ painter.drawRect(0, 0, width() - 1, height() - 1);
+
+ if (m_orientation == GraphAxisWidget::Vertical) {
+ int axisHeight = height() - 1;
+ int fontHeight = painter.fontMetrics().height();
+ int ticks = axisHeight / (fontHeight * 2);
+
+ double range = m_valueMax - m_valueMin;
+ double step = range / (double)ticks;
+ double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0)));
+ step = qFloor((step / step10) * 2) * (step10 / 2);
+
+ if (step <= 0) {
+ return;
+ }
+
+ painter.resetTransform();
+
+ for (double tick = 0; tick <= range; tick += step) {
+ int y = axisHeight - ((tick / range) * axisHeight);
+
+ painter.drawLine(width() - 8, y, width(), y);
+
+ painter.drawText(0,
+ qBound(0, y - fontHeight / 2, axisHeight - fontHeight),
+ width() - 10,
+ fontHeight,
+ Qt::AlignRight | Qt::AlignVCenter,
+ Profiling::getTimeString(tick, m_valueMax));
+ }
+ } else {
+ int axisWidth = width() - 1;
+ int fontWidth = painter.fontMetrics().width("0.000 ns");
+ int fontHeight= painter.fontMetrics().height();
+ int ticks = axisWidth / (fontWidth * 2);
+
+ double range = m_valueMax - m_valueMin;
+ double step = range / (double)ticks;
+ double step10 = qPow(10.0, qFloor(qLn(step) / qLn(10.0)));
+ step = qFloor((step / step10) * 2) * (step10 / 2);
+
+ if (step <= 0) {
+ return;
+ }
+
+ painter.resetTransform();
+
+ for (double tick = 0; tick <= range; tick += step) {
+ int x = (tick / range) * axisWidth;
+
+ painter.drawLine(x, 0, x, 8);
+
+ painter.drawText(qBound(0, x - fontWidth / 2, axisWidth - fontWidth),
+ 8,
+ fontWidth,
+ fontHeight,
+ Qt::AlignHCenter | Qt::AlignTop,
+ Profiling::getTimeString(tick, m_valueMax));
+ }
+ }
+}
View
16 gui/graphing/timeaxiswidget.h
@@ -0,0 +1,16 @@
+#ifndef TIMEAXISWIDGET_H
+#define TIMEAXISWIDGET_H
+
+#include "graphaxiswidget.h"
+
+/**
+ * A simple axis that draws text using getTimeString to nicely format time values
+ */
+class TimeAxisWidget : public GraphAxisWidget {
+public:
+ TimeAxisWidget(QWidget* parent = 0);
+
+ virtual void paintEvent(QPaintEvent *e);
+};
+
+#endif
View
666 gui/graphwidget.cpp
@@ -1,666 +0,0 @@
-#include "graphwidget.h"
-#include "timelinewidget.h"
-#include "profiledia