Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Append negative CategoryAxis Labels



  • Hello everyone,

    I am trying to use a CategoryAxis to implement a sliding grid chart (this one). So far I got it to slide to the left, but I cannot make it sliding to the right for the simple reason that, according to the CategoryAxis documentation, I can only append new labels on top of the highest, but neither below the lowest one, nor in between existing ones. I tested if that behavior holds in the actual QML code, and indeed I cannot add any label whose value is smaller than the highest: the method fails silently.

    Did I overlook something? Is there any solution for this? I found nothing on the internet with regard to this issue.

    I attach a very simple QML showing the issue.

    import QtQuick 2.9
    import QtQuick.Layouts 1.0
    import QtQuick.Controls 2.0
    import QtCharts 2.1
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 480
        title: "Append CategoryAxis Labels"
    
    
        ColumnLayout {
            anchors.rightMargin: 20
            anchors.leftMargin: 20
            anchors.bottomMargin: 20
            anchors.topMargin: 20
            anchors.fill: parent
    
            ChartView {
                id: chartView
                width: 631
                height: 400
                Layout.fillHeight: true
                Layout.fillWidth: true
                title: "Sliding Grid"
                antialiasing: true
                legend {
                    alignment: Qt.AlignBottom
                }
    
                CategoryAxis {
                    id: xAxis
                    min: 0
                    max: 10
    
                    labelsPosition: CategoryAxis.AxisLabelsPositionOnValue;
                }
    
                ValueAxis {
                    id: yAxis
                    min: 0
                    max: 10
    
                    tickCount: 5
                    minorTickCount: 5
                }
    
                LineSeries {
                    axisX: xAxis
                    axisY: yAxis
                    XYPoint { x: 0; y: 0 }
                    XYPoint { x: 1.5; y: 2.1 }
                    XYPoint { x: 3; y: 3.3 }
                    XYPoint { x: 4.5; y: 2.1 }
                    XYPoint { x: 6; y: 6.9 }
                    XYPoint { x: 7.5; y: 3.0 }
                    XYPoint { x: 9; y: 3.3 }
                    XYPoint { x: 10; y: 2.3 }
                }
    
            }
    
            RowLayout {
                id: rowLayout
                width: 100
                height: 100
    
                Button {
                    text: "Add this label to xAxis"
                    onClicked: xAxis.append( newLabel.text, newLabel.text);
                }
    
                TextField {
                    id: newLabel
                }
    
            }
    
        }
    }
    
    


  • @zansara
    A brute-force solution to your problem, but which still satisfies your stringent requirement of exclusively QML ( at most some JS )

    The following lets you add labels in arbitrary order:

                Button {
                    text: "Add this label to xAxis"
    
                    property variant vals: []
    
                    onClicked: {
                        vals.push(parseFloat(newLabel.text))//or unshift, or...
                        vals.sort() //better: insert to array such that no sorting is needed...
    
                        //Remove all labels
                        var labels = xAxis.categoriesLabels
                        for (var i=xAxis.count-1; i>=0; --i){
                            xAxis.remove(labels[i])
                        }
    
                        //Add labels
                        for (i=0; i<vals.length; ++i){
                            xAxis.append(vals[i].toString(), vals[i]);
                        }
    
                        xAxis.min = vals[0]
                        xAxis.max = vals[vals.length-1]
                    }
                }
    


  • @Diracsbracket
    Thanks for your reply. I tested your code by replacing the button into my above snippet and it works fine, even if I must admit it looks expensive if the labels are many and the data flows fast. However, given the API of CategoryAxis for QML, this is probably the only way to go.

    You specified that this is the only way to respect my requirements. Out of curiosity, are there other solutions you might suggest?

    Many thanks!



  • @Diracsbracket
    In the meantime I have also found a possible trick to solve my issue: it was enough to set the xAxis reverse property to true. Then I mirror all the x values of the LineSeries to be positive and I prepend a minus sign to the categories labels, so that they "seems" to be growing in the negative direction. However, it really feels like I'm hacking this component a bit. I think CategoryAxis should allow inserting new categories in any position.

    I attach the code of the reverse solution:

    import QtQuick 2.9
    import QtQuick.Layouts 1.0
    import QtQuick.Controls 2.0
    import QtCharts 2.1
    
    ApplicationWindow {
        id: root
        visible: true
        width: 640
        height: 480
    
        property int numpoints: 628;
        property double step: 0.01;
    
        Timer {
            id: timer;
            interval: 1;
            repeat: true;
            onTriggered: {
                draw( xAxis, yAxis, lineSeries, lineSeries.at(0).y );
                if( xAxis.max - (-xAxis.categoriesLabels[xAxis.categoriesLabels.length -1]) > 1 ){  // --> Watch out for negatives
                    xAxis.append(("-"+xAxis.max.toFixed(0)), xAxis.max); 
                    xAxis.remove(xAxis.categoriesLabels[0]);
                }
            }
        }
    
        function draw(xax, yax, lineSeries, dataPoint){
            lineSeries.remove(0);
            xax.min = xax.min + step;
            xax.max = xax.max + step;
    
            var x = lineSeries.at(lineSeries.count-1).x + step ;
            lineSeries.append( x, dataPoint );
            if(dataPoint > yax.max){
                yax.max = dataPoint +1;
            } else if(dataPoint < yax.min){
                yax.min = dataPoint -1;
            }
        }
    
    
        ColumnLayout {
            anchors.rightMargin: 20
            anchors.leftMargin: 20
            anchors.bottomMargin: 20
            anchors.topMargin: 20
            anchors.fill: parent
    
            ChartView {
                id: chartView
                width: 631
                height: 400
                Layout.fillHeight: true
                Layout.fillWidth: true
                title: "Sliding Grid"
                antialiasing: true
                legend {
                    alignment: Qt.AlignBottom
                }
    
                CategoryAxis {
                    id: xAxis
                    reverse: true // ----> This is the important property!
                    min: 0
                    max: numpoints * step + (numpoints*step)/6;
    
                    labelsPosition: CategoryAxis.AxisLabelsPositionOnValue;
                    Component.onCompleted: {
                        for(var i=0; i<max+1; i++){
                            xAxis.append( ("-"+i), i );
                        }
                    }
                }
    
                ValueAxis {
                    id: yAxis
                    min: 0
                    max: 10
    
                    tickCount: 5
                    minorTickCount: 5
                }
    
                LineSeries {
                    id: lineSeries
                    name: "Line Series"
                    axisX: xAxis
                    axisY: yAxis
    
                    Component.onCompleted: {
                        for (var i = 0; i < numpoints; i++) {
                            lineSeries.append(i*step, Math.sin(i*step) * 3 + 5);
                        }
                        timer.start();
                    }
                }
            }
        }
    }
    


  • @zansara said in Append negative CategoryAxis Labels:

    Out of curiosity, are there other solutions you might suggest

    Well, you mentioned it already in that other post of yours, which you reference above, i.e. go the C++ way and using e.g. a library like QCustomPlot which is completely customizable/hackable since it only consists of a single CPP file and a single h file. Then, you could try and import it in your QML app. Most QML types are defined in C++ after all...

    it was enough to set the xAxis reverse property to true

    Nice one!



  • @Diracsbracket said in Append negative CategoryAxis Labels:

    Well, you mentioned it already in that other post of yours, which you reference above, i.e. go the C++ way and using e.g. a library like QCustomPlot which is completely customizable/hackable since it only consists of a single CPP file and a single h file. Then, you could try and import it in your QML app.

    I think I will try this way as well. QtCharts feels very counter-intuitive even for the most basic operations... Thanks again!


Log in to reply