[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. ThanksItem{ 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(); } } }
-
@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 -
@p3c0
I didn't really get how to use it so I wrote my own function to map themfunction 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
-
@p3c0
I got a new error
Cannot read property 'length' of undefined
I have defined the properties like thisproperty 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 check
if(root.physicalValue_x)
i get the error at for loopCannot read property 'length' of undefined
.
If I comment it i get 'TypeError: Type error' atphysicalValue_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 -
@p3c0
As you said i have changed like thisfunction 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 propertiesproperty real y0 : 50.0; property real y1 : chart.height - chart.xAxisHeight property real x0 : chart.yAxisWidth property real x1 : chart.width - 100
-
@p3c0
Nope. if put the check if(root.physicalValue_x). The errorTypeError: Cannot read property 'length'
of undefined still appears atfor(var i=0;i<root.logicalValue_x.length;i++)
.If I dont put the check then i getTypeError: Cannot read property '0' of undefined
atvar pos_z = root.map(root.logicalValue_z[i],root.maxRange_z,root.minRange_z,root.y1,root.y0)
. -
@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.
-
@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 function
inputToGraph
andconnections
because it is from cpp side. -
@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 itsheightChanged
signal is invoked the propertylogicalValue_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 checkedphysicalValue_x
if(physicalValue_x && logicalValue_z){ logicalToPhysical() requestPaint() }