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
            }
        }
    }
    

  • Moderators

    @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 and m_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


  • Moderators

    @vishnu Yes it will scale the whole Canvas. You will need to handle them inside it. Check out what others do for eg. this



  • @p3c0
    where can i see the source code for this . I have seen 3 lines of code here. Thanks


  • Moderators

    @vishnu RightClick > View Page Source ;)


Log in to reply
 

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