Custom QML Chart (realtime data)
-
Hello everyone,
For a project, i need to implement a relitively complex custom chart (see image)
It must be able to display some tolerances (in red at 20 and orange at 10 for example)
as well as min and max values (in green and red) and other kind of custom infosData are comming every ~2ms but I am only adding 1/10 data to my serie to keep it fluid.
I am already able to plot live data without any additional infos on the charts using ChartView and LineSeries in QML.
So now I need to go further customizing my chart and i don't realy now of a good way to do that.
I have already try CategoryAxis but i can't set CategoryRange color independently. And if I add more than one axis, they are not alligned.
So what would be the best way to do something like you can see in the image? I am open to any kind of third party library as long as it is usable through QML and relatively performant in embedded linux (Boot2Qt)
Thanks in advance for any suggestion!
Edit: I also tried the QCustomPlot example "Real Time Data Demo", but QCustomPlot::replot is waaaayy to slow for a realtime application (~200ms on my device even with openGl enabled)
-
So, after some thought and reading on the web, I decided to use Canvas to draw annotations and other infos on the chart while it gives a lot of flexibility. As for the refresh method, I implemented something similar to the qmloscilloscope example. I should have watched it earlier.
For those interested in, here is a demo of what I can achieve with the use of QML Canvas:
With some code:
import QtQuick 2.15 import QtCharts 2.15 import "qrc:/" ChartView { id: root property alias axisX: axisXId property alias axisY: axisYId property real yAxisLowerBound: 0 property real yAxisUpperBound: 25 property real xAxisLowerBound: 0 property real xAxisUpperBound: 1000 property string labelFontFamily: Style.primaryFont property int labelFontSize: 16 property color labelFontColor: Style.colorWhite QtObject { id: internal property var context: null } margins.top:0 margins.bottom:0 margins.left:0 margins.right:0 //antialiasing: true backgroundColor: "transparent" legend.visible: false property var backgroundPaint: function() { drawCachedZone(220, 11, 450, 19, "#99FFFFFF"); drawXCachedZone(yAxisLowerBound, 100, "#99FFFFFF"); } property var foregroundPaint: function() { drawYTick(20, Style.colorError, 2); drawYTick(10, Style.colorFailure, 2); drawXTick(200, Style.colorSuccess, 2); drawXTick(500, Style.colorVariant, 2); drawXPeak(800, 22, Style.colorAccent); drawXPit(900, 8, Style.colorWarning); drawXPoint(850, 15, Style.colorSuccess); drawYPeak(400, 4, Style.colorAccent); drawYPit(300, 3, Style.colorWarning); drawYPoint(200, 2, Style.colorSuccess); drawBox(600, 2, 700, 8, Style.colorWarning, 3); } function repaintCanvas() { backgroundCanvas.requestPaint(); foregroundCanvas.requestPaint(); } function drawYTick(y, color, lineWidth) { drawLine(xAxisLowerBound, y, xAxisUpperBound, y, color, lineWidth); drawYLabel(y, Code.floatToStringRound(y,1), Style.colorWhite); } function drawXTick(x, color, lineWidth) { drawLine(x, yAxisLowerBound, x, yAxisUpperBound, color, lineWidth) drawXLabel(x, Code.floatToStringRound(x,1), Style.colorWhite); } function drawBox(startX, startY, endX, endY, color, lineWidth) { drawLine(startX, startY, startX, endY, color, lineWidth); drawLine(startX, endY, endX, endY, color, lineWidth); drawLine(endX, endY, endX, startY, color, lineWidth); drawLine(endX, startY, startX, startY, color, lineWidth); } function drawXPit(x, y, color) { drawLine(x, yAxisLowerBound, x, y, color, 1) drawXLabel(x, Code.floatToStringRound(x,1), color); drawTrianglePoint(x, y, color, 180) } function drawXPeak(x, y, color) { drawLine(x, yAxisLowerBound, x, y, color, 1) drawXLabel(x, Code.floatToStringRound(x,1), color); drawTrianglePoint(x, y, color, 0) } function drawXPoint(x, y, color) { drawLine(x, yAxisLowerBound, x, y, color, 1) drawXLabel(x, Code.floatToStringRound(x,1), color); drawSquarePoint(x, y, color); } function drawYPit(x, y, color) { drawLine(x, y, xAxisLowerBound, y, color, 1) drawYLabel(y, Code.floatToStringRound(y,1), color); drawTrianglePoint(x, y, color, 270) } function drawYPeak(x, y, color) { drawLine(x, y, xAxisLowerBound, y, color, 1) drawYLabel(y, Code.floatToStringRound(y,1), color); drawTrianglePoint(x, y, color, 90) } function drawYPoint(x, y, color) { drawLine(x, y, xAxisLowerBound, y, color, 1) drawYLabel(y, Code.floatToStringRound(y,1), color); drawSquarePoint(x, y, color); } function drawYLabel(y, text, color) { var ctx = internal.context var yy = root.mapToPosition(Qt.point(0,y)).y; ctx.save(); ctx.fillStyle = color ctx.translate(root.plotArea.x, yy); ctx.textAlign = "right"; ctx.fillText(text, -5, root.labelFontSize/3); ctx.restore(); } function drawXLabel(x, text, color) { var ctx = internal.context var xx = root.mapToPosition(Qt.point(x,0)).x; ctx.save(); ctx.fillStyle = color ctx.translate(xx, root.plotArea.y+root.plotArea.height); ctx.textAlign = "center"; ctx.fillText(text, 0, root.labelFontSize); ctx.restore(); } function drawText(x, y, text, color) { var ctx = internal.context var point = root.mapToPosition(Qt.point(x,y)); ctx.save(); ctx.fillStyle = color ctx.translate(point.x, point.y); ctx.textAlign = "center"; ctx.fillText(text, 0, root.labelFontSize/3); ctx.restore(); } function drawLine(startX, startY, endX, endY, color, lineWidth) { var ctx = internal.context ctx.save(); ctx.lineWidth = lineWidth; ctx.strokeStyle = color; ctx.beginPath(); var pointStart = root.mapToPosition(Qt.point(startX,startY)); var pointEnd = root.mapToPosition(Qt.point(endX,endY)); ctx.moveTo(pointStart.x,pointStart.y); ctx.lineTo(pointEnd.x,pointEnd.y); ctx.stroke(); ctx.restore(); } function drawYCachedZone(startY, endY, color) { drawCachedZone(xAxisLowerBound, startY, xAxisUpperBound, endY, color); } function drawXCachedZone(startX, endX, color) { drawCachedZone(startX, yAxisLowerBound, endX, yAxisUpperBound, color); } function drawCachedZone(startX, startY, endX, endY, color) { var ctx = internal.context ctx.save(); ctx.beginPath(); ctx.fillStyle = color var pointStart = root.mapToPosition(Qt.point(startX,startY)); var pointEnd = root.mapToPosition(Qt.point(endX,endY)); ctx.fillRect(pointStart.x, pointStart.y, pointEnd.x-pointStart.x, pointEnd.y-pointStart.y); ctx.stroke(); ctx.restore(); } function drawTrianglePoint(x, y, color, rotation) { var ctx = internal.context var point = root.mapToPosition(Qt.point(x,y)); ctx.save(); ctx.translate(point.x, point.y); ctx.rotate(Code.degToRad(rotation)) ctx.moveTo(0, 0); ctx.lineTo(-10, -10); ctx.lineTo(+10, -10); ctx.closePath(); ctx.fillStyle = color; ctx.fill(); ctx.restore(); } function drawSquarePoint(x, y, color) { var ctx = internal.context var point = root.mapToPosition(Qt.point(x,y)); ctx.save(); ctx.moveTo(point.x-5, point.y-5); ctx.lineTo(point.x-5, point.y+5); ctx.lineTo(point.x+5, point.y+5); ctx.lineTo(point.x+5, point.y-5); ctx.closePath(); ctx.fillStyle = color; ctx.fill(); ctx.restore(); } onPlotAreaChanged: repaintCanvas() Canvas{ id: foregroundCanvas anchors.fill: root contextType: "2d" renderStrategy: Canvas.Threaded renderTarget: Canvas.FramebufferObject onPaint: { context.reset(); // Load font only once since it is a heavy operation context.font = '%1px "%2"'.arg(root.labelFontSize).arg(root.labelFontFamily); internal.context=context; root.foregroundPaint(); } } Canvas{ id: backgroundCanvas z: -1 anchors.fill: root contextType: "2d" renderStrategy: Canvas.Threaded renderTarget: Canvas.FramebufferObject onPaint: { context.reset(); // Load font only once since it is a heavy operation context.font = '%1px "%2"'.arg(root.labelFontSize).arg(root.labelFontFamily); internal.context=context; root.backgroundPaint(); } } ValueAxis { id: axisXId min: xAxisLowerBound max: xAxisUpperBound tickCount: 2 labelsVisible: true labelsColor: root.labelFontColor labelsFont.family: root.labelFontFamily labelsFont.pixelSize: root.labelFontSize } ValueAxis { id: axisYId min: yAxisLowerBound max: yAxisUpperBound tickCount: 2 labelsVisible: true labelsColor: root.labelFontColor labelsFont.family: root.labelFontFamily labelsFont.pixelSize: root.labelFontSize } }
Feel free to comment!
-
@romain-donze said in Custom QML Chart (realtime data):
ctx.save();
Hey,
This chart looks awesome - I'm trying to utilize some elements here and draw some axis lines on my own chartview.
Can you explain what is happening with the ctx.save() method?
I cant find it defined anywhere.Is it in the cpp code?
Thanks!
-
@romain-donze can you share this project for me? I'm new bie
-
Hi @romain-donze ,
thanks for sharing your project. I am planning to do a similar project and I have chosen the qmloscilloscope as a starting point myself. However, your post here and your shared code already took some work off my chest, thanks alot.
However, I am new to QML (doing some webinars on the side) and I am curious, how you have implemented the chart QML code. Is there a main.qml which calls it, and when I paste it into my IDE, clang is shouting at me for not finding ValueAxis as a component. I guess, you created a separate ValueAxis.qml?
If you could give me just a high level explanation of how to use your chart, that'd be awesome!
Cheers,
Bjørn -
-
Hi,
Thanks for sharing your experience. I tried to lunch the code but it only shows a "crashed" error.
@romain-donze would you please (if possible) share this project for me? I'm also a newbie and trying to visualize some real time data in histograph chart.