[Solved] Drawing Linear graph using QML canvas



  • I need to draw a boundary graph and then update the current position of the machine (x,y,z posistions comes from cpp side) on the boundary graph. looks like this.
    All i did is this. drawn x and y axis and the labels and small ticks. took some points and connected them. What is wrong is what i am showing on the xlabels in different to the values that i have plotted .There is no binding between them.All i need to show is a small rectangle with current position on it(which varies frequently.)as shown here. Any suggesion or hints will be really helpful. Thanks

    Item{
            id:root_chart
            //        anchors.fill: parent
            anchors.left: root_checkbox.right
            height: parent.height
            anchors.right: parent.right
            Canvas {
                id: chart
    
    
                property int maxPoints: 200
                property var xValues:[0,4,9,13,9,4,0]
                property var yValues: [0,3,3,0,-4,-4,0]
    
                anchors.fill: parent
                anchors.margins: 50
                antialiasing: true
                renderStrategy: Canvas.Cooperative
    
                property color linecolor: "#808080"
                property color background: "#f5f5f5"
    
                property int yRef: chart.height/2 - chart.xAxisHeight
                property int xRef: chart.yAxisWidth + 100
    
                property int yAxisWidth: 80   // x pos of y-axis in pixel
                property int xAxisHeight: 40  // y distance of x-axis from bottom in pixel
    
                property bool fillAreas: false
                property int scalingFactor: 30
    
                function drawAxis(ctx) {
                    var yAxis = {
                        labels: ["+6 dB", "0 dB", "-6 dB", "-12 dB", "-18 dB"],
                        lines: [0.0, 0.22, 0.44, 0.66, 0.88]
                    }
    
                    var xAxis = {
                        labels: ["100 mm", "150 mm", "200 mm", "250 mm", "300 mm",
                            "350 mm", "400 mm", "450 mm", "500 mm", "550 mm"],
                        lines: [0.0, 0.15, 0.25, 0.35, 0.50,
                            0.60, 0.70, 0.82, 0.92, 1.00]
                    }
    
                    var y0 = 50.0;
                    var y1 = chart.height - chart.xAxisHeight
                    var x0 = chart.yAxisWidth
                    var x1 = chart.width - 100
    
                    // clear
                    ctx.fillStyle = background
                    ctx.fillRect(0,0,width,height)
    
                    // frame
                    ctx.strokeStyle = linecolor;
                    ctx.fillStyle = "#999999";
                    ctx.lineWidth = 1
    
                    ctx.beginPath();
                    ctx.moveTo(x0,y0);
    
                    ctx.lineTo(x1,y0);
                    ctx.lineTo(x1,y1);
                    ctx.lineTo(x0,y1);
                    ctx.lineTo(x0,y0);
                    ctx.stroke()
    
                    var w
                    var yp
    
                    //drawing the small lines on the X and Y axis
                    for (var y = 0; y < yAxis.lines.length; y++)
                    {
                        ctx.beginPath();
                        ctx.strokeStyle = "black"
                        yp = (y1 - y0) * yAxis.lines[y] + y0
                        ctx.moveTo(x0 - 10, yp);
                        ctx.lineTo(x0, yp);
                        ctx.stroke();
                        ctx.closePath();
    
                        //                w = ctx.measureText(yAxis.labels[y]).width
                        //                ctx.fillText(yAxis.labels[y], x0 - w -5, yp + 5)
                    }
    
                    for (var x = 0; x < xAxis.lines.length; x++)
                    {
                        ctx.beginPath();
                        var xp = (x1 - x0) * xAxis.lines[x] + x0
                        ctx.moveTo(xp, y1);
                        ctx.lineTo(xp, y1 + 5);
                        ctx.stroke();
                        ctx.closePath();
    
                        ctx.fillText(xAxis.labels[x], xp - 20, y1 + 20)
                    }
                    // move to reference point
                    ctx.beginPath();
                    ctx.arc(xRef,yRef,1,0,2*Math.PI);
                    ctx.stroke();
                    ctx.closePath();
                    //move to points
                    ctx.beginPath();
                    ctx.moveTo(xRef,yRef);
                    for( var i=0;i< xValues.length;i++){
                        ctx.lineTo(xRef+(xValues[i]*scalingFactor),yRef+(yValues[i]*scalingFactor))
                    }
                    ctx.lineWidth = 2
                    ctx.stroke();
                    ctx.closePath();
    
                    var dx = (x1 - x0) / (maxPoints - 1)
                    var dy = (y1 - y0) / 50.0
                    ctx.beginPath();
                    //                ctx.arc(x0,y1 - data1[0] * dy,20,0,2*Math.PI);
                    //                ctx.fillStyle = "#FF0000";
    
                    //                ctx.stroke();
                    //                ctx.closePath();
                    ctx.moveTo(x0, y1 - data1[0] * dy)
                    // move to tool tip
                    for( var i=0;i<data1.length;i++){
                        //moving y position
                        yp = y1 - data1[i] * dy;
                        ctx.lineTo(x0 + i * dx, yp)
                        ctx.stroke();
                    }
                }
                onPaint: {
                    var ctx = getContext("2d");
                    ctx.save();
                    drawAxis(ctx)
                    ctx.restore();
                }
            }
        }
    

  • Moderators

    @vishnu Adding a Rectangle item and setting its x and y position should work. Set its z value greater than that of Canvas to bring it on top.



  • @p3c0
    Thanks.It worked but i am not able to map the logical value to the screen pixel values. Since i binded the value to x and y of rectangle, It looks different in different platforms. What i mean to say is the values i get from cpp varies from -1500 to +1500. Now How can i map the logical value to x,y, pixels of various screen sizes? Thanks


  • Moderators



  • @p3c0
    I didn't really get how to use it so I wrote my own function to map them

    function map(value,srcMax,srcMin,dstMax,dstMin){
            console.log(srcMax,srcMin,dstMax,dstMin," value ",value)
            return Number(((((dstMax - dstMin)*(value - srcMin)/(srcMax - srcMin))+dstMin)).toFixed(1)) ;
        }
    

    I hope it will be useful for someone. one more question. I have drawn the graph assuming positive axis of X to the right(like standard one). How can make it to the left.As shown in here


  • Moderators

    @vishnu Using negative x value while drawing ? Passing negative values to lineTo or moveTo from point of origin should work.



  • @p3c0
    I got a new error
    Cannot read property 'length' of undefined
    I have defined the properties like this

     property var physicalValue_x: []
        property var physicalValue_z: []
        property var logicalValue_x: [-80,-40,40,80,220,400,740,680,170,-170,-250,-220,-80,-40,-20]
        property var logicalValue_z: [820,820,820,820,780,670,560,305,295,285,380,780,820,820,820]
    
    function map(value,srcMax,srcMin,dstMax,dstMin){
            //        console.log(srcMax,srcMin,dstMax,dstMin," value ",value)
            return Number(((((dstMax - dstMin)*(value - srcMin)/(srcMax - srcMin))+dstMin)).toFixed(1)) ;
        }
    
        function logicalToPhysical()
        {
    //        if(root.physicalValue_x)
            for(var i=0;i<logicalValue_z.length;i++)//error 'length undefined'
            {
                var pos_x = root.map(logicalValue_x[i],maxRange_x,minRange_x,x1,x0)
                physicalValue_x[i] = pos_x
                var pos_z = root.map(logicalValue_z[i],maxRange_z,minRange_z,y1,y0)
                physicalValue_z[i] = pos_z // here type error
            }
            console.log("physical x values"+physicalValue_x)
            console.log("physical z values"+physicalValue_z)
        }
    

    if i uncomment the checkif(root.physicalValue_x) i get the error at for loop Cannot read property 'length' of undefined.
    If I comment it i get 'TypeError: Type error' at physicalValue_z[i] = pos_z also 'TypeError: Cannot read property '0' of undefined' at var pos_z. I think the properties are not instantiated properly but eventually they will. What is the best practice to avoid these errors. Thanks


  • Moderators

    @vishnu This means it is out of its scope. Access logicalValue_z just like you accessed physicalValue_x using root.



  • @p3c0
    As you said i have changed like this

     function logicalToPhysical()
        {
    //        if(root.physicalValue_x)
            for(var i=0;i<root.logicalValue_x.length;i++)
            {
                var pos_x = root.map(root.logicalValue_x[i],root.maxRange_x,root.minRange_x,root.x1,root.x0)
                root.physicalValue_x[i] = pos_x
                var pos_z = root.map(root.logicalValue_z[i],root.maxRange_z,root.minRange_z,root.y1,root.y0) // here is the error
                root.physicalValue_z[i] = pos_z
            }
            console.log("physical x values"+root.physicalValue_x)
            console.log("physical z values"+root.physicalValue_z)
        }
    

    But still the error TypeError: Cannot read property '0' of undefined at var pos_z;
    FIY:these are the properties in addition to above properties

      property real y0 : 50.0;
        property real y1 : chart.height - chart.xAxisHeight
        property real x0 : chart.yAxisWidth
        property real x1 : chart.width - 100
    

  • Moderators

    @vishnu Did Cannot read property 'length' of undefined for logicalValue_z go ?
    For the other check which of the other properties is not accessible.



  • @p3c0
    Nope. if put the check if(root.physicalValue_x). The error TypeError: Cannot read property 'length'of undefined still appears at for(var i=0;i<root.logicalValue_x.length;i++).If I dont put the check then i get TypeError: Cannot read property '0' of undefined at var pos_z = root.map(root.logicalValue_z[i],root.maxRange_z,root.minRange_z,root.y1,root.y0).


  • Moderators

    @vishnu Can you post a minimal example which replicates above scenario ? Something is not clear here.



  • @p3c0
    okay.

    Rectangle {
        id: root
        width: 1280; height: 800
        property var physicalValue_x: []
        property var physicalValue_z: []
        property var logicalValue_x: [-80,-40,40,80,220,400,740,680,170,-170,-250,-220,-80,-40,-20]
        property var logicalValue_z: [820,820,820,820,780,670,560,305,295,285,380,780,820,820,820]
    
        property int maxRange_x: 1040
        property int minRange_x: -400
        property int maxRange_z: 1020
        property int minRange_z: 100
        property real y0 : 50.0;
        property real y1 : chart.height - chart.xAxisHeight
        property real x0 : chart.yAxisWidth
        property real x1 : chart.width - 100
    
        function map(value,srcMax,srcMin,dstMax,dstMin){
            return Number(((((dstMax - dstMin)*(value - srcMin)/(srcMax - srcMin))+dstMin)).toFixed(1)) ;
        }
    
        function logicalToPhysical()
        {
            for(var i=0;i<root.logicalValue_x.length;i++)
            {
                var pos_x = root.map(root.logicalValue_x[i],root.maxRange_x,root.minRange_x,root.x1,root.x0)
                root.physicalValue_x[i] = pos_x
                var pos_z = root.map(root.logicalValue_z[i],root.maxRange_z,root.minRange_z,root.y1,root.y0)
                root.physicalValue_z[i] = pos_z
            }
            console.log("physical x values"+root.physicalValue_x)
            console.log("physical z values"+root.physicalValue_z)
        }
    Item{
            id:root_chart
            //        anchors.fill: parent
            anchors.left: root_checkbox.right
            height: parent.height
            anchors.right: parent.right
            Canvas {
                id: chart
                property int maxPoints: 200
                Component.onCompleted: {
                    logicalToPhysical();/
                }
            onWidthChanged: {
                    console.log("chart width is changing ")
                    logicalToPhysical() //call 1 
                    requestPaint()
                }
                onHeightChanged: {
                    console.log("chart height is changing ")
                    logicalToPhysical()// call 2
                    requestPaint()
                }
         }
    

    I found that because of these calls 1 and 2 the error is coming.


  • Moderators

    @vishnu This doesnot show any error specific to what you posted earlier.



  • @p3c0
    I think i better post the whole file. sorry for the inconvenience.

    import QtQuick 2.4
    import QtQuick.Controls 1.2
    Rectangle {
        id: root
        width: 1280; height: 800
    
        property variant data1: [20.0]
        property double currentPos_x:591
        property double currentPos_y:60
        property double currentPos_z:193
    
        property var physicalValue_x: []
        property var physicalValue_z: []
        property var logicalValue_x: [-80,-40,40,80,220,400,740,680,170,-170,-250,-220,-80,-40,-20]
        property var logicalValue_z: [820,820,820,820,780,670,560,305,295,285,380,780,820,820,820]
    
        property double showPos_x
        property double showPos_y
        property double showPos_z
    
    
        property int maxRange_x: 1040
        property int minRange_x: -400
        property int maxRange_z: 1020
        property int minRange_z: 100
        property real y0 : 50.0;
        property real y1 : chart.height - chart.xAxisHeight
        property real x0 : chart.yAxisWidth
        property real x1 : chart.width - 100
    
        function map(value,srcMax,srcMin,dstMax,dstMin){
            //        console.log(srcMax,srcMin,dstMax,dstMin," value ",value)
            return Number(((((dstMax - dstMin)*(value - srcMin)/(srcMax - srcMin))+dstMin)).toFixed(1)) ;
        }
    
        function logicalToPhysical()
        {
            for(var i=0;i<root.logicalValue_x.length;i++)
            {
                var pos_x = root.map(root.logicalValue_x[i],root.maxRange_x,root.minRange_x,root.x1,root.x0)
                root.physicalValue_x[i] = pos_x
                var pos_z = root.map(root.logicalValue_z[i],root.maxRange_z,root.minRange_z,root.y1,root.y0)
                root.physicalValue_z[i] = pos_z
            }
            console.log("physical x values"+root.physicalValue_x)
            console.log("physical z values"+root.physicalValue_z)
        }
    
        function inputToGraph(byteIndex,bitIndex,bitValue){
    
            if(bitIndex === -1){
                if(byteIndex === 36){
                    //update x position
                    showPos_x=Number((bitValue/1000).toFixed(1));
                    var pos_x= root.map(bitValue/1000,maxRange_x,minRange_x,x1,x0);
                    currentPos_x=pos_x;
    //                console.log("current pos x map value"+currentPos_x)
                }
                if(byteIndex === 40){
                    //update y position
                    showPos_y=Number((bitValue/1000).toFixed(1));
                    currentPos_y=bitValue/1000;
                    //                console.log("current pos y map value"+currentPos_y)
                }
                if(byteIndex === 44){
                    //update z position
                    showPos_z=Number((bitValue/1000).toFixed(1));
                    var pos_z= root.map(bitValue/1000,maxRange_z,minRange_z,y1,y0);
                    currentPos_z=pos_z;
    //                console.log("current pos z map value"+currentPos_z)
                }
            }
        }
    
        Connections{
            target: tcpCommunicationPort
            onDataFromCpp:{
                inputToGraph(byteIndex,bitIndex,bitValue)
            }
        }
        Rectangle{
            id:root_checkbox
            anchors.left: parent.left
            width: parent.width/4
            height: parent.height
            Row{
                spacing: 50
                anchors.centerIn: parent
                Rectangle{
                    width: triggerButton.width
                    height: triggerButton.height
                    border.color: "red"
                    TextInput{
                        id: timeInput
                        anchors.centerIn: parent
                    }
                }
                Button{
                    id:triggerButton
                    text:"trigger"
                    onClicked: {
                        //                    var y0 = 50.0;
                        //                    var y1 = chart.height - chart.xAxisHeight
                        //                    var x0 = chart.yAxisWidth
                        //                    var x1 = chart.width - 100
                        //                    var mappedzvalue = root.map(521,maxRange_x,minRange_x,x1,x0);
                        //                    //                                        point2.ypos = mappedzvalue
                        //                    var mappedxvalue = root.map(717,maxRange_z,minRange_z,y1,y0)
                        //                    //                                        point2.xpos =Number((mappedxvalue).toFixed(1))
                        //                    console.log("mapped z value",mappedzvalue)
                        //                    console.log("mapped x value",mappedxvalue)
                        inputToGraph(36,-1,0);
                        inputToGraph(44,-1,0);
                    }
                }
                Button{
                    id:triggerButton1
                    text:"trigger1"
                    onClicked: {
                        console.log(" current ")
                    }
                }
            }
        }
    
    
        Item{
            id:root_chart
            //        anchors.fill: parent
            anchors.left: root_checkbox.right
            height: parent.height
            anchors.right: parent.right
            Canvas {
                id: chart
                property int maxPoints: 200
                Component.onCompleted: {
                    logicalToPhysical();
                }
                onWidthChanged: {
                    console.log("chart width is changing ")
                    if(root.physicalValue_x){
                        logicalToPhysical()
                        requestPaint()
                    }
                }
                onHeightChanged: {
                    console.log("chart height is changing ")
                    logicalToPhysical()
                    requestPaint()
                }
    
                width: parent.width
                height: parent.height
                anchors.margins: 50
                antialiasing: true
                renderStrategy: Canvas.Cooperative
    
                property color linecolor: "#808080"
                property color background: "#f5f5f5"
    
                //            property int yRef: chart.height/2 - chart.xAxisHeight
                //            property int xRef: chart.yAxisWidth + 100
    
                property int yAxisWidth: 80   // x pos of y-axis in pixel
                property int xAxisHeight: 40  // y distance of x-axis from bottom in pixel
    
                property bool fillAreas: false
                property int scalingFactor: 30
    
                function drawAxis(ctx) {
                    var yAxis = {
                        labels: ["+6 dB", "0 dB", "-6 dB", "-12 dB", "-18 dB"],
                        lines: [0.0, 0.22, 0.44, 0.66, 0.88]
                    }
    
                    var xAxis = {
                        labels: ["100 mm", "150 mm", "200 mm", "250 mm", "300 mm",
                            "350 mm", "400 mm", "450 mm", "500 mm", "550 mm"],
                        lines: [0.0, 0.15, 0.25, 0.35, 0.50,
                            0.60, 0.70, 0.82, 0.92, 1.00]
                    }
    
                    //                var y0 = 50.0;
                    //                var y1 = chart.height - chart.xAxisHeight
                    //                var x0 = chart.yAxisWidth
                    //                var x1 = chart.width - 100
    
                    // clear
                    ctx.fillStyle = background
                    ctx.fillRect(0,0,width,height)
    
                    // frame
                    ctx.strokeStyle = linecolor;
                    ctx.fillStyle = "#999999";
                    ctx.lineWidth = 1
    
                    ctx.beginPath();
                    ctx.moveTo(x0,y0);
    
                    ctx.lineTo(x1,y0);
                    ctx.lineTo(x1,y1);
                    ctx.lineTo(x0,y1);
                    ctx.lineTo(x0,y0);
                    ctx.stroke()
    
                    var w
                    var yp
    
                    //drawing the small lines on the X and Y axis
                    for (var y = 0; y < yAxis.lines.length; y++)
                    {
                        ctx.beginPath();
                        ctx.strokeStyle = "black"
                        yp = (y1 - y0) * yAxis.lines[y] + y0
                        ctx.moveTo(x0 - 10, yp);
                        ctx.lineTo(x0, yp);
                        ctx.stroke();
                        ctx.closePath();
    
                        //                w = ctx.measureText(yAxis.labels[y]).width
                        //                ctx.fillText(yAxis.labels[y], x0 - w -5, yp + 5)
                    }
    
                    for (var x = 0; x < xAxis.lines.length; x++)
                    {
                        ctx.beginPath();
                        var xp = (x1 - x0) * xAxis.lines[x] + x0
                        ctx.moveTo(xp, y1);
                        ctx.lineTo(xp, y1 + 5);
                        ctx.stroke();
                        ctx.closePath();
    
                        ctx.fillText(xAxis.labels[x], xp - 20, y1 + 20)
                    }
                    ctx.beginPath();
                    console.log("drawing the graph")
                    console.log("chart width"+ chart.width);
    
                    ctx.moveTo(physicalValue_x[0],physicalValue_z[0]);
                    for( var i=1;i< physicalValue_x.length;i++){
                        ctx.lineTo(physicalValue_x[i],physicalValue_z[i])
                    }
                    ctx.lineWidth = 2
                    ctx.stroke();
                    ctx.closePath();
                }
                onPaint: {
                    var ctx = getContext("2d");
                    //                ctx.save();
                    drawAxis(ctx)
                    //                ctx.restore();
                }
            }
            Rectangle {
                id: point1
                x: currentPos_x
                y: currentPos_z
                z:10
                radius: 5
                width: 60; height: 15
                color: "#800096d6"
                Text{
                    id:currentpoisionText
                    anchors.centerIn: parent
                    text: showPos_x+","+showPos_z
                }
            }
        }
    }
    

    try to ignore the functioninputToGraph and connections because it is from cpp side.


  • Moderators

    @vishnu It appears to be the problem with the initialization of properties as well as the intialization of items from where these properties are accessed. It seems that when Canvas is created and its heightChanged signal is invoked the property logicalValue_z is not initialized yet and thus causes that error but very first time only. Later it doesnt seems to give that error. This can be avoided by adding another condition where you can check if that property is initialized just like you checked physicalValue_x

    if(physicalValue_x && logicalValue_z){
        logicalToPhysical()
        requestPaint()
    }
    


  • @p3c0
    perfect. May i know what does this mean exactly? if(physicalValue_x && logicalValue_z) . I am assuming at this point physicalValue_x!=0 and logicalValue_z !=0 .then it enters inside the condition.


  • Moderators

    @vishnu The control will go ahead if and only if both properties are initialized. Its a normal & operation.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.