Search code examples
qtqmlqtquick2qtcharts

How to use ScrolLeft and ScrollRight Methods of QML Charts


I need to scroll a QT Graph using a scrollbar and for that, i have created my own custom scrollbar using Rectangle and MouseArea which can be dragged.

When i tried to scroll a Chart using ScrollRight and ScrollLeft Methods i am not able to link/bind the ScrollBar X with the ChartView content X.Following is the code :

import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtCharts 2.0

Window {
    id: window
    width: 640
    height: 480
    visible: true

    ChartView {
        id: chartview
        width: parent.width
        height: 300

        LineSeries {
            name: "LineSeries"
            axisX: ValueAxis {
                min: 0
                max: 100
                tickCount: 12
                labelFormat: "%.0f"
            }

            axisY: ValueAxis {
                min: 0
                max: 70
                tickCount: 5
                labelFormat: "%.0f"
            }
            XYPoint { x: 0; y: 0.0 }
            XYPoint { x: 1.1; y: 3.2 }
            XYPoint { x: 1.9; y: 2.4 }
            XYPoint { x: 2.1; y: 2.1 }
            XYPoint { x: 2.9; y: 2.6 }
            XYPoint { x: 3.4; y: 2.3 }
            XYPoint { x: 200.1; y: 3.1 }
        }
    }

    /* Rectangle base for horizontal scroll bar */
    Rectangle{
        id:rectHorScrollBase
        width:parent.width
        height:parent.height * 0.10
        anchors.top:chartview.bottom
        anchors.topMargin: (parent.height * 0.01)
        color:"transparent"
        visible: true

        /* Rectangle indicating scroll path*/
        Rectangle {
            id:rectHorScrollPath
            width: parent.width
            height: parent.height
            anchors.left:parent.left

            radius: 2
            color: "lightblue"
        }

        /* Actual scroll rectaangle which will be dragged */
        Rectangle {
            id: rectHorScroll
            property var prevX : 0

            anchors.top : parent.top
            width: 50
            height: parent.height
            color: "green"

            /* Mouser area to drag the rectangle and to move Chart */
            MouseArea {
                id:mouseAreaHorScroll

                anchors.fill: parent
                drag.target: parent
                drag.minimumX: 0
                drag.maximumX: chartview.width - width
                drag.axis: Drag.XAxis

                onReleased: {
                    console.log("x in Released ===",rectHorScroll.x)
                    rectHorScroll.prevX = rectHorScroll.x
                }
            }

            onXChanged: {

                // HOW TO HANDLE THE SCROLL BASED ON x ? 
                if(mouseAreaHorScroll.drag.active)
                {
                    if(x > prevX)
                    {
                        chartview.scrollRight(x) // NOT WORKING
                    }
                    else
                    {
                        chartview.scrollLeft(x) //NOT WORKING
                    }
                }
            }
        }
    }
}

1) How to map scrollBar X with ChartView Content X ?

2) The scrollbar should not go beyond maximum and minimum Value of X Axis. How to do that?

3) There should be a sync between ScrollBar and ChartView.


Solution

  • Basically it works as expected. Let us analyze what you are doing:

    onXChanged: {
    
        // HOW TO HANDLE THE SCROLL BASED ON x ?
        if(mouseAreaHorScroll.drag.active)
        {
            if(x > prevX)
            {
                chartview.scrollRight(x) // NOT WORKING
            }
            else
            {
                chartview.scrollLeft(x) //NOT WORKING
            }
        }
    }
    

    First of all, you scrollRight when x is larger than prevX - which works perfectly. However you scrollRight for x pixels - which grows with the movement, so the movement is not linear to the movement of the scroll bar.

    The scrollLeft is indeed (almost) never invoked, which is obvious: you don't update prevX so only when x === 0 ( = prevX) you scrollLeft(0) - which doesn't move.

    The first change we need to apply, is to update prevX at the end of the handler.
    The second change is, that we only want to move as far as we moved the handle since the last run of the code.

    The solution therefore looks like this:

    onXChanged: {
    
        // HOW TO HANDLE THE SCROLL BASED ON x ?
        if(mouseAreaHorScroll.drag.active)
        {
            if(x > prevX)
            {
                chartview.scrollRight(x - prevX) // Only move the difference
            }
            else
            {
                chartview.scrollLeft(prevX - x) //Only move the difference
            }
            prevX = x // Update prevX
        }
    }
    

    Instead of handling that stuff manually, and calling those functions, you can also just map the visible part of the axis to x - remove your handles for this and change axisX accordingly:

    axisX: ValueAxis {
        min: rectHorScroll.x / (rectHorScrollBase.width - rectHorScroll.width) * 100
        max: rectHorScroll.x / (rectHorScrollBase.width - rectHorScroll.width) * 100 + 100
        tickCount: 12
        labelFormat: "%.0f"
    }
    

    Here you need to find the right way to apply the mapping, that you get the right range.

    Then you can also use regular components, like a Slider to simulate the scrollbar:

    import QtQuick 2.6
    import QtQuick.Window 2.2
    import QtQuick.Controls 2.0
    import QtCharts 2.0
    
    ApplicationWindow {
        id: window
        width: 640
        height: 480
        visible: true
    
        ChartView {
            id: chartview
            width: parent.width
            height: 300
    
            LineSeries {
                name: "LineSeries"
                axisX: ValueAxis {
                    property real minValue: 0
                    property real maxValue: 200
                    property real range: 100
                    min: minValue + sb.position * (maxValue - minValue - range)
                    max: minValue + sb.position * (maxValue - minValue - range) + range
                    tickCount: 12
                    labelFormat: "%.0f"
                }
    
                axisY: ValueAxis {
                    min: 0
                    max: 70
                    tickCount: 5
                    labelFormat: "%.0f"
                }
                XYPoint { x: 0; y: 0.0 }
                XYPoint { x: 1.1; y: 3.2 }
                XYPoint { x: 1.9; y: 2.4 }
                XYPoint { x: 2.1; y: 2.1 }
                XYPoint { x: 2.9; y: 2.6 }
                XYPoint { x: 3.4; y: 2.3 }
                XYPoint { x: 200.1; y: 3.1 }
            }
    
    
        }
    
        Slider {
            id: sb
            anchors {
                bottom: parent.bottom
                left: parent.left
                right: parent.right
            }
            height: 30
        }
    }