Any examples of drawing linear graph with pinch to zoom functionality ?
-
Hello,
I would like to draw a linear graph. On touch screen devices I would like to have zoom functionality to this graph. When I pinch to zoom, the graph should enlarge according to the zooming factor. Also, the X, Y values should increase and decrease according to the zoom. Can any one throw some hints how to do this?
As example.Since it is a single qml file you can copy paste the code and view it in Qt quick 2 preview (qmlscene)import QtQuick 2.0 Rectangle { id: root width: 1280; height: 800 property string density: width > 1280 ? "hdpi" : "mdpi" property real dsf: width > 1280 ? 1.5 : 1.0 property int maxPoints: 200 property variant data1: [20.0] Timer { id: dataGen interval: 50; running: true; repeat: true onTriggered: { var data = data1 var val = data[data.length - 1] val = Math.max(0, Math.min(50.0, val - 2.0 + Math.random() * 4.0)) data.push(val) if (data.length > maxPoints) data.shift() data1 = data chart.requestPaint() } } Canvas { id: chart anchors.fill: parent anchors.margins: 50 antialiasing: true // renderTarget: Canvas.FramebufferObject renderStrategy: Canvas.Cooperative property color linecolor: "#808080" property color background: "#f5f5f5" 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 real upperMarker: point1.y - chart.y + point1.radius property real lowerMarker: point2.y - chart.y + point2.radius property bool fillAreas: true 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: ["20 Hz", "50 Hz", "100 Hz", "200 Hz", "500 Hz", "1 kHz", "2 kHz", "5 kHz", "10 kHz", "20 kHz"], 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 for (var y = 0; y < yAxis.lines.length; y++) { ctx.beginPath(); 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) } if (fillAreas) { // draw a yellow rectangle behind the chart, to simulate areas under lower marker ctx.fillStyle = "rgba(255, 255, 0, 0.2)" ctx.fillRect(x0,lowerMarker, (x1 - x0) * data1.length / maxPoints - 1, y1 - lowerMarker) } // vertical gradient to colorize chart line according markers var upp = (upperMarker - y0) / (y1 -y0) var lpp = (lowerMarker - y0) / (y1 -y0) var gradientV = ctx.createLinearGradient(0,y0,0,y1) gradientV.addColorStop(0.00, "red") gradientV.addColorStop(upp, "red") gradientV.addColorStop(upp + 0.001, "black") gradientV.addColorStop(lpp - 0.001, "black") gradientV.addColorStop(lpp, "#ffe000") gradientV.addColorStop(1.00, "#ffe000") if (fillAreas) { // vertical gradient to fill chart area according markers var gradientV2 = ctx.createLinearGradient(0,y0,0,y1) gradientV2.addColorStop(0.00, "rgba(255, 0, 0, 0.3)") gradientV2.addColorStop(upp, "rgba(255, 0, 0, 0.3)") gradientV2.addColorStop(upp + 0.001, background) gradientV2.addColorStop(1.00, background) } // draw chart line var dx = (x1 - x0) / (maxPoints - 1) var dy = (y1 - y0) / 50.0 ctx.strokeStyle = gradientV //"#000000" ctx.lineWidth = 2 ctx.fillStyle = gradientV2 ctx.beginPath(); ctx.moveTo(x0, y1 - data1[0] * dy) var up = 0 var area = [] for (var i = 1; i < data1.length; i++) { yp = y1 - data1[i] * dy ctx.lineTo(x0 + i * dx, yp) // check areas outside markers if ((yp < upperMarker) && !up) { area.push(i) up = 1 } if ((yp > upperMarker) && up) { area.push(i) up = 0 } // lower } ctx.stroke(); // draws the line if (fillAreas) { ctx.lineTo(x0 + data1.length * dx, y1) ctx.lineTo(x0, y1) ctx.closePath(); ctx.fill(); // fills the chart } // draw horizontal marker // tbd pixel to chart dimensions function and invers ctx.strokeStyle = linecolor; ctx.fillStyle = linecolor; // text ctx.beginPath() ctx.moveTo(x0, upperMarker) ctx.lineTo(x1, upperMarker) ctx.stroke(); ctx.closePath(); var val = Number(50.0 + (0.0 - 50.0 ) / (y1 - y0) * (upperMarker - y0)).toFixed(0) + "A" w = ctx.measureText(val).width ctx.fillText(val, x0 - w - 30, upperMarker + 5) ctx.beginPath() ctx.moveTo(x0, lowerMarker) ctx.lineTo(x1, lowerMarker) ctx.stroke(); ctx.closePath(); val = Number(50.0 + (0.0 - 50.0 ) / (y1 - y0) * (lowerMarker - y0)).toFixed(0) + "A" w = ctx.measureText(val).width ctx.fillText(val, x0 - w - 30, lowerMarker + 5) // draw upperMarker areas ctx.fillStyle = "rgba(255, 0, 0, 0.3)" ctx.strokeStyle = "#ff0000" while (area.length > 0) { var start = area.shift() var end = area.length > 0 ? area.shift() : data1.length // replaced by gradient fill // ctx.beginPath(); // ctx.moveTo(x0 + start * dx, upperMarker) // for (var i = start; i < end; i++) { // yp = y1 - data1[i] * dy // ctx.lineTo(x0 + i * dx, yp) // } // ctx.lineTo(x0 + end * dx, upperMarker) // ctx.closePath(); // ctx.fill(); ctx.beginPath(); ctx.moveTo(x0 + start * dx, y1) ctx.lineTo(x0 + end * dx, y1) ctx.closePath(); ctx.stroke(); } } onPaint: { var ctx = getContext("2d"); ctx.save(); drawAxis(ctx) ctx.restore(); } } Rectangle { id: point1 x: chart.x + chart.yAxisWidth - width/2 y: 200 width: 40 * dsf; height: width radius: width/2 color: "#800096d6" Rectangle { anchors.centerIn: parent width: parent.width * 0.4; height: width radius: width/2 color: "#0096d6" } MouseArea { anchors.fill: parent drag.target: point1 drag.axis: Drag.YAxis drag.minimumY: 80 drag.maximumY: point2.y - 50 } } Rectangle { id: point2 x: chart.x + chart.yAxisWidth - width/2 y: chart.y + chart.height - 200 width: 40 * dsf; height: width radius: width/2 color: "#800096d6" Rectangle { anchors.centerIn: parent width: parent.width * 0.4; height: width radius: width/2 color: "#0096d6" } MouseArea { anchors.fill: parent drag.target: point2 drag.axis: Drag.YAxis drag.minimumY: point1.y + 50 drag.maximumY: chart.y + chart.height - 60 } } }
-
@vishnu May be you should try
scale
property ? Or you have do it on your own by calculating the new coordinates of child items based upon its parent. May be you should have look at the related algorithms. Unfortunately I too don't have much knowledge in it. -
@p3c0
I tried this example from internet.import QtQuick 2.3 import QtQuick.Window 2.2 Window { id: container visible: true width: 1280 height: 768 Rectangle { id: rect gradient: Gradient { GradientStop { position: 0.0; color: "#ffffff" } GradientStop { position: 0.33; color: "yellow" } GradientStop { position: 1.0; color: "green" } } border.width: 2 width: 600 height: 600 transform: Scale { id: scaler origin.x: pinchArea.m_x2 origin.y: pinchArea.m_y2 xScale: pinchArea.m_zoom2 yScale: pinchArea.m_zoom2 } PinchArea { id: pinchArea anchors.fill: parent property real m_x1: 0 property real m_y1: 0 property real m_y2: 0 property real m_x2: 0 property real m_zoom1: 0.5 property real m_zoom2: 0.5 property real m_max: 2 property real m_min: 0.5 onPinchStarted: { console.log("Pinch Started") m_x1 = scaler.origin.x m_y1 = scaler.origin.y m_x2 = pinch.startCenter.x m_y2 = pinch.startCenter.y rect.x = rect.x + (pinchArea.m_x1-pinchArea.m_x2)*(1-pinchArea.m_zoom1) rect.y = rect.y + (pinchArea.m_y1-pinchArea.m_y2)*(1-pinchArea.m_zoom1) } onPinchUpdated: { console.log("Pinch Updated") m_zoom1 = scaler.xScale var dz = pinch.scale-pinch.previousScale var newZoom = m_zoom1+dz if (newZoom <= m_max && newZoom >= m_min) { m_zoom2 = newZoom } } MouseArea { id: dragArea hoverEnabled: true anchors.fill: parent drag.target: rect drag.filterChildren: true } } } }
I didn't understand the logic completely. Esp:
onPinchStarted: { console.log("Pinch Started") m_x1 = scaler.origin.x m_y1 = scaler.origin.y m_x2 = pinch.startCenter.x m_y2 = pinch.startCenter.y rect.x = rect.x + (pinchArea.m_x1-pinchArea.m_x2)*(1-pinchArea.m_zoom1) rect.y = rect.y + (pinchArea.m_y1-pinchArea.m_y2)*(1-pinchArea.m_zoom1) }
Can you please explain what is happening here
m_x1 = scaler.origin.x
andm_x2 = pinch.startCenter.x
,rect.x = rect.x + (pinchArea.m_x1-pinchArea.m_x2)*(1-pinchArea.m_zoom1)
.what i understood is getting the coordinates of 2 fingers and updating the x coordinate of rectangle.is it right ?onPinchUpdated: { console.log("Pinch Updated") m_zoom1 = scaler.xScale var dz = pinch.scale-pinch.previousScale var newZoom = m_zoom1+dz if (newZoom <= m_max && newZoom >= m_min) { m_zoom2 = newZoom } }
May be you should have look at the related algorithms
What kind of algorithms should i search for ? .
Can i apply the same logic, used above to the rectangle , to the canvas area ?Thanks -
@p3c0
I used scale property of Canvas element.transform: Scale { id: scaler origin.x: pinchArea.m_x2 origin.y: pinchArea.m_y2 xScale: pinchArea.m_zoom2 yScale: pinchArea.m_zoom2 }
But the problem is whole graph is zooming in and out. What I would like to do is Just to zoom the graph, not the xaxis and yaxis.But able to change the X and Y values accordingly. Can you please suggest some methods or ideas or approach on how to solve this issue ?Thanks