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 ofexclusively 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 xAxisreverse
property totrue
. 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!