Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Custom QML Chart (realtime data)
QtWS25 Last Chance

Custom QML Chart (realtime data)

Scheduled Pinned Locked Moved Solved QML and Qt Quick
qmlchartplotchartvi
8 Posts 7 Posters 5.6k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • R Offline
    R Offline
    romain.donze
    wrote on 20 Aug 2021, 10:20 last edited by romain.donze
    #1

    Hello everyone,

    For a project, i need to implement a relitively complex custom chart (see image)

    Capture du 2021-08-20 12-05-43.png

    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 infos

    Data 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)

    1 Reply Last reply
    0
    • R Offline
      R Offline
      romain.donze
      wrote on 24 Aug 2021, 14:12 last edited by romain.donze
      #2

      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:

      Capture du 2021-08-24 16-06-10.png

      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!

      T 1 Reply Last reply 19 Apr 2023, 03:42
      6
      • P Offline
        P Offline
        psryan
        wrote on 21 Jul 2022, 19:32 last edited by
        #3

        @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!

        B 1 Reply Last reply 22 Jul 2022, 20:54
        0
        • P psryan
          21 Jul 2022, 19:32

          @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!

          B Offline
          B Offline
          Bob64
          wrote on 22 Jul 2022, 20:54 last edited by
          #4

          @psryan it's a member of the Context2d QML type. See the docs for that and Canvas.

          1 Reply Last reply
          0
          • R romain.donze
            24 Aug 2021, 14:12

            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:

            Capture du 2021-08-24 16-06-10.png

            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!

            T Offline
            T Offline
            Thanh Nhan
            wrote on 19 Apr 2023, 03:42 last edited by
            #5

            @romain-donze can you share this project for me? I'm new bie

            1 Reply Last reply
            1
            • Y Offline
              Y Offline
              yuanhui
              wrote on 9 Nov 2023, 07:23 last edited by
              #6

              Awesome! Where it the "Style.colorWhite" defined in? Could you please share the color style table(qss file?)?

              1 Reply Last reply
              0
              • Bjorn92B Offline
                Bjorn92B Offline
                Bjorn92
                wrote on 8 Dec 2023, 10:21 last edited by
                #7

                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

                1 Reply Last reply
                1
                • B Bob64 referenced this topic on 21 Feb 2024, 10:48
                • A Offline
                  A Offline
                  AraSaWCH
                  wrote on 11 Jun 2024, 09:09 last edited by
                  #8

                  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.

                  1 Reply Last reply
                  0

                  • Login

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • Users
                  • Groups
                  • Search
                  • Get Qt Extensions
                  • Unsolved