Implement click-to-zoom in FlameGraph
authorMilian Wolff <milian.wolff@kdab.com>
Wed, 2 Sep 2015 15:04:13 +0000 (17:04 +0200)
committerMilian Wolff <milian.wolff@kdab.com>
Wed, 2 Sep 2015 15:04:13 +0000 (17:04 +0200)
gui/flamegraph.cpp
gui/flamegraph.h

index d5387a7d0d8d2564537610501fa2b7c5035eaa01..170805a5628878a3f3be7c696607f5be7a8115c3 100644 (file)
 #include <KLocalizedString>
 #include <KColorScheme>
 
-namespace {
-
-QColor color(quint64 cost, quint64 maxCost)
-{
-    const double ratio = double(cost) / maxCost;
-    return QColor::fromHsv(120 - ratio * 120, 255, 255, (-((ratio-1) * (ratio-1))) * 120 + 120);
-}
-
 class FrameGraphicsItem : public QGraphicsRectItem
 {
 public:
@@ -61,10 +53,31 @@ public:
         setToolTip(m_label);
     }
 
+    static const QFont font()
+    {
+        static const QFont font(QStringLiteral("monospace"), 10);
+        return font;
+    }
+
+    static const QFontMetrics fontMetrics()
+    {
+        static const QFontMetrics metrics(font());
+        return metrics;
+    }
+
+    static int margin()
+    {
+        return 5;
+    }
+
+    int preferredWidth() const
+    {
+        return fontMetrics().width(m_label) + 2 * margin();
+    }
+
     virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0)
     {
-        const int margin = 5;
-        const int width = rect().width() - 2 * margin;
+        const int width = rect().width() - 2 * margin();
         if (width < 2) {
             // don't try to paint tiny items
             return;
@@ -75,9 +88,7 @@ public:
         // TODO: text should always be displayed in a constant size and not zoomed
         // TODO: items should only be scaled horizontally, not vertically
         // TODO: items should "fit" into the view width
-        static const QFont font(QStringLiteral("monospace"), 10);
-        static const QFontMetrics metrics(font);
-        if (width < metrics.averageCharWidth() * 6) {
+        if (width < fontMetrics().averageCharWidth() * 6) {
             // text is too wide for the current LOD, don't paint it
             return;
         }
@@ -85,8 +96,9 @@ public:
         const int height = rect().height();
 
         const QFont oldFont = painter->font();
-        painter->setFont(font);
-        painter->drawText(margin + rect().x(), rect().y(), width, height, Qt::AlignVCenter | Qt::AlignLeft | Qt::TextSingleLine, metrics.elidedText(m_label, Qt::ElideRight, width));
+        painter->setFont(font());
+        painter->drawText(margin() + rect().x(), rect().y(), width, height, Qt::AlignVCenter | Qt::AlignLeft | Qt::TextSingleLine,
+                          fontMetrics().elidedText(m_label, Qt::ElideRight, width));
         painter->setFont(oldFont);
     }
 
@@ -94,9 +106,18 @@ private:
     QString m_label;
 };
 
+namespace {
+
+QColor color(quint64 cost, quint64 maxCost)
+{
+    const double ratio = double(cost) / maxCost;
+    return QColor::fromHsv(120 - ratio * 120, 255, 255, (-((ratio-1) * (ratio-1))) * 120 + 120);
+}
+
 const qreal h = 25.;
 const qreal x_margin = 0.;
 const qreal y_margin = 2.;
+const qreal minRootWidth = 800.;
 
 // TODO: what is the right value for maxWidth here?
 void toGraphicsItems(const FlameGraphData::Stack& data, qreal totalCostForColor,
@@ -118,12 +139,26 @@ void toGraphicsItems(const FlameGraphData::Stack& data, qreal totalCostForColor,
     }
 }
 
+void scaleItems(FrameGraphicsItem *item, qreal scaleFactor)
+{
+    auto rect = item->rect();
+    rect.moveLeft(rect.left() * scaleFactor);
+    rect.setWidth(rect.width() * scaleFactor);
+    item->setRect(rect);
+    foreach (auto child, item->childItems()) {
+        if (auto frameChild = dynamic_cast<FrameGraphicsItem*>(child)) {
+            scaleItems(frameChild, scaleFactor);
+        }
+    }
+}
+
 }
 
 FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags)
     : QWidget(parent, flags)
     , m_scene(new QGraphicsScene(this))
     , m_view(new QGraphicsView(this))
+    , m_rootItem(nullptr)
 {
     qRegisterMetaType<FlameGraphData>();
 
@@ -149,8 +184,17 @@ bool FlameGraph::eventFilter(QObject* object, QEvent* event)
     if (event->type() == QEvent::MouseButtonRelease) {
         QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
         if (mouseEvent->button() == Qt::LeftButton) {
-            auto item = m_view->itemAt(mouseEvent->pos());
-            qDebug() << item << (item ? item->toolTip() : QString());
+            auto item = dynamic_cast<FrameGraphicsItem*>(m_view->itemAt(mouseEvent->pos()));
+            if (item) {
+                const auto preferredWidth = item->preferredWidth();
+                auto scaleFactor = qreal(preferredWidth) / item->rect().width();
+                if (m_rootItem->rect().width() * scaleFactor < minRootWidth) {
+                    // don't shrink too much, keep the root item at a certain minimum size
+                    scaleFactor = minRootWidth / m_rootItem->rect().width();
+                }
+                scaleItems(m_rootItem, scaleFactor);
+                m_view->centerOn(item);
+            }
             return true;
         }
     }
@@ -172,12 +216,10 @@ void FlameGraph::setData(const FlameGraphData& data)
 
     const QPen pen(KColorScheme(QPalette::Active).foreground().color());
 
-    // TODO: get this out of the view somehow
-    const qreal totalWidth = 800;
-    FrameGraphicsItem* root = new FrameGraphicsItem(QRectF(0, 0, totalWidth, h), totalCost, i18n("total allocations"));
-    root->setPen(pen);
-    toGraphicsItems(data.stack, totalCost, totalCost, root);
-    m_scene->addItem(root);
+    m_rootItem = new FrameGraphicsItem(QRectF(0, 0, minRootWidth, h), totalCost, i18n("total allocations"));
+    m_rootItem->setPen(pen);
+    toGraphicsItems(data.stack, totalCost, totalCost, m_rootItem);
+    m_scene->addItem(m_rootItem);
 
     qDebug() << "took me: " << t.elapsed();
 }
index 1e6571f8143e417f3d0208d3091504c21ebebb4b..787b227fff4a7c7ad11eedc108fd2982cea2ee0f 100644 (file)
@@ -25,6 +25,7 @@
 
 class QGraphicsScene;
 class QGraphicsView;
+class FrameGraphicsItem;
 
 struct FlameGraphData
 {
@@ -56,6 +57,7 @@ private:
     QGraphicsScene* m_scene;
     QGraphicsView* m_view;
     FlameGraphData m_data;
+    FrameGraphicsItem* m_rootItem;
 };
 
 #endif // FLAMEGRAPH_H