{"id":139,"date":"2014-12-26T22:51:20","date_gmt":"2014-12-26T21:51:20","guid":{"rendered":"http:\/\/www.arnorehn.de\/blog\/?p=139"},"modified":"2016-08-07T11:59:57","modified_gmt":"2016-08-07T09:59:57","slug":"quickplot-a-collection-of-native-qtquick-plot-items","status":"publish","type":"post","link":"https:\/\/www.arnorehn.de\/blog\/2014\/12\/26\/quickplot-a-collection-of-native-qtquick-plot-items\/","title":{"rendered":"QuickPlot: A collection of native QtQuick plotting items"},"content":{"rendered":"<p>For a project at university I recently needed a\u00a0plotting widget to display some\u00a0data. Naturally, Qwt came to my mind. I&#8217;ve already been using it in a number of other projects and it works great.<\/p>\n<p>The one drawback, however: The project was intended to be run on the Raspberry Pi. Now the X-Server on the R-Pi doesn&#8217;t have any 3D acceleration yet, so the performance of Qwt was subpar.<\/p>\n<p>Luckily, Qt has the eglfs backend (using Broadcom&#8217;s EGL libraries), which works very well on the R-Pi. The problem: You\u00a0have to use QML\/QtQuick with that, so Qwt was out of the game (actually, I&#8217;ve played around with\u00a0creating a QtQuick backend for Qwt, but with Qwt being QPainter-based, still, it was nowhere near performant enough). A\u00a0more QtQuick-like solution was needed.<\/p>\n<p>Since QtQuick supports the HTML5 Canvas API, I thought that maybe some HTML5 chart library like <a title=\"Chart.js\" href=\"http:\/\/www.chartjs.org\/\" target=\"_blank\">Chart.js<\/a> could do the trick. The good news: It works pretty much out of the box and you get very pretty charts. The bad news: performance on the Raspberry Pi is more or less abysmal (on a laptop or desktop PC it&#8217;s fine, though). Even some hand-crafted plotting code based on the Canvas API ate the better part of the R-Pi&#8217;s CPU when displaying\u00a0a continuous stream of data. This isn&#8217;t actually that much of a wonder, considering that it was all based on JavaScript and has redrawn the whole plot area in an FBO every time there was a data update.<\/p>\n<p>So all\u00a0of the &#8220;simple&#8221; solutions where dead ends. Eventually I ended up writing\u00a0my own plot items, this time based on QtQuick&#8217;s Scene Graph to get the best\u00a0performance out of the little computer. I was actually surprised that no-one had done something like this yet &#8211; or at least I couldn&#8217;t find anything.<br \/>\nAnyway, here&#8217;s a screenshot of the thing displaying a sine wave with axes:<br \/>\n<a href=\"http:\/\/www.arnorehn.de\/blog\/wp-content\/uploads\/2014\/12\/qtquickplot_sample.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-140 size-medium\" src=\"http:\/\/www.arnorehn.de\/blog\/wp-content\/uploads\/2014\/12\/qtquickplot_sample-300x172.png\" alt=\"\" width=\"300\" height=\"172\" srcset=\"https:\/\/www.arnorehn.de\/blog\/wp-content\/uploads\/2014\/12\/qtquickplot_sample-300x172.png 300w, https:\/\/www.arnorehn.de\/blog\/wp-content\/uploads\/2014\/12\/qtquickplot_sample.png 692w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\nThe sine wave is actually scrolling through the view, always showing the most recent\u00a0300 data points of the data source. It&#8217;s even anti-aliased on most hardware, including the R-Pi, just not on my laptop&#8217;s Intel GPU on which I took the screenshot. The performance\u00a0on the Raspberry Pi is now <strong>very<\/strong> good, using less than 5% of the CPU, while the other solutions where north of 70% for a continuously updated data source (values from <code>top<\/code>, not claiming any sort of\u00a0accuracy).<\/p>\n<p>The API is somewhat inspired by Qwt&#8217;s. There&#8217;s the <code>PlotArea<\/code> Item which is responsible for managing all items of the plot. It keeps a collection of <code>PlotItem<\/code>s, displays the axes and makes the used <code>ScaleEngine<\/code>s (used for the axis limits) known to every item and axis in the plot.<br \/>\nI haven&#8217;t implemented more than the most basic PlotItems and ScaleEngines at the moment (because those are currently the only things I need):<\/p>\n<ul>\n<li><code>Curve<\/code>: exactly what the name implies &#8211; a set of points connected by lines.<\/li>\n<li><code>ScrollingCurve<\/code>: A curve which only displays the last N data points. If the data is continuously updated, this looks as if the curve is scrolling from right to left.<\/li>\n<li><code>TightScaleEngine<\/code>: A <code>ScaleEngine<\/code> which sets the axis limits to the overall minima and maxima of all the plot items.<\/li>\n<li><code>FixedScaleEngine<\/code>: A <code>ScaleEngine<\/code> which sets the axis limits to constant values.<\/li>\n<\/ul>\n<p>The axis ticks are placed based on\u00a0the <code>nicenum<\/code> algorithm from the\u00a0<a title=\"Graphics Gems\" href=\"http:\/\/www.graphicsgems.org\" target=\"_blank\">Graphics Gems<\/a> book series from Academic Press. The API is designed with C++\/Qt data sources in mind: Data is passed as <code>QVector&lt;float&gt;<\/code> or <code>QVector&lt;QPointF&gt;<\/code>, preferrably as an argument to a signal-slot connection. Note that the usage of <code>QVector<\/code> makes setting the data statically from QML a little difficult. I&#8217;m open for suggestions to improve this, but I would really like to avoid converting a batch of data to a <code>QVariantList<\/code> and back just to integrate better with QML\/JS.<\/p>\n<p>You can find the code on <del datetime=\"2015-04-15T05:53:22+00:00\">gitorious<\/del> GitLab, licensed under the GPLv3:\u00a0<a href=\"https:\/\/gitlab.com\/qtquickplot\/qtquickplot\" target=\"_blank\">https:\/\/gitlab.com\/qtquickplot\/qtquickplot<\/a><\/p>\n<p><strong>Edit<\/strong>: Some example code would probably be nice, so here is the QML code used in the above screen shot:<\/p>\n<pre class=\"font-size-enable:false lang:js decode:true\" title=\"QuickPlot example code\">import QtQuick 2.1\r\nimport QuickPlot 1.0\r\n\r\nRectangle {\r\n    visible: true\r\n    width: 640\r\n    height: 480\r\n\r\n    MouseArea {\r\n        anchors.fill: parent\r\n        onClicked: {\r\n            Qt.quit();\r\n        }\r\n    }\r\n\r\n    PlotArea {\r\n        id: plotArea\r\n        anchors.fill: parent\r\n\r\n        yScaleEngine: FixedScaleEngine {\r\n            max: 1.5\r\n            min: -1.5\r\n        }\r\n\r\n        items: [\r\n            ScrollingCurve {\r\n                id: meter;\r\n                numPoints: 300\r\n            }\r\n        ]\r\n    }\r\n\r\n    Timer {\r\n        id: timer;\r\n        interval: 20;\r\n        repeat: true;\r\n        running: true;\r\n\r\n        property real pos: 0\r\n\r\n        onTriggered: {\r\n            meter.appendDataPoint( Math.sin(pos) );\r\n            pos += 0.05;\r\n        }\r\n    }\r\n}<\/pre>\n<p><strong>Edit 2<\/strong>: Due to Gitorious being acquired by GitLab, the code has been moved to the latter: <a href=\"https:\/\/gitlab.com\/qtquickplot\/qtquickplot\" target=\"_blank\">https:\/\/gitlab.com\/qtquickplot\/qtquickplot<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>For a project at university I recently needed a\u00a0plotting widget to display some\u00a0data. Naturally, Qwt came to my mind. I&#8217;ve already been using it in a number of other projects and it works great. The one drawback, however: The project was intended to be run on the Raspberry Pi. Now the X-Server on the R-Pi [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[32],"tags":[25,22,24,26],"class_list":["post-139","post","type-post","status-publish","format-standard","hentry","category-qt","tag-plot","tag-qt","tag-qtquick","tag-raspberry-pi"],"_links":{"self":[{"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/posts\/139","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/comments?post=139"}],"version-history":[{"count":6,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/posts\/139\/revisions"}],"predecessor-version":[{"id":148,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/posts\/139\/revisions\/148"}],"wp:attachment":[{"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/media?parent=139"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/categories?post=139"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/tags?post=139"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}