10

Using the Zoom Line Example I have made a Python QChartView class that can scroll with the arrow keys and zoom with the plus and minus keys. (see my code below).

When I scroll left I would expect that the grid lines and axis ticks scroll the same amount as the data. However, only the data (the QLineSeries) scrolls to the left. The 5 grid lines remain at the same positions but their tick values are updated. This is undesirable as the new tick values can be anything.

I have looked in the documentation but could not find how to make the grid scroll together with the data. Am I missing something?

I would also like to be able to set the ticks to explicit values (so that I can perhaps implement the scrolling behavior myself). Is it possible to set the axis tick values to specific values?

My example code:

import sys
from math import pi, sin, sqrt

from PyQt5.QtChart import QLineSeries, QChart, QChartView
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication

class ZoomPanChartView(QChartView):
    """ QChartView that can zoom/pan with the keys
    """
    def __init__(self, chart):
        super().__init__(chart)

        self.zoomFactor = sqrt(2) # QCharts default is 2
        self.panPixels = 10

    def keyPressEvent(self, keyEvent):
        """ Panning (scrolling) is done with the arrow keys. 
            Zooming goes with the plus and minus keys.
            The '=' key resets.
        """
        key = keyEvent.key()

        if key == Qt.Key_Equal:
            self.chart().zoomReset()
        if key == Qt.Key_Plus:
            self.chart().zoom(self.zoomFactor)
        elif key == Qt.Key_Minus:
            self.chart().zoom(1/self.zoomFactor)
        elif key == Qt.Key_Left:
            self.chart().scroll(-self.panPixels, 0)
        elif key == Qt.Key_Right:
            self.chart().scroll(+self.panPixels, 0)
        elif key == Qt.Key_Up:
            self.chart().scroll(0, +self.panPixels)
        elif key == Qt.Key_Down:
            self.chart().scroll(0, -self.panPixels)
        elif key == Qt.Key_0:
            self.chart().axisX().applyNiceNumbers() # changes the range
        else:
            super().keyPressEvent(keyEvent)


def main():
    app = QApplication(sys.argv)
    chart = QChart()

    series = QLineSeries()
    for i in range(0, 100):
        x = i * pi / 20
        y = sin(x)
        series.append(x, y)

    chart.addSeries(series)
    chart.createDefaultAxes()
    chart.axisY().setRange(-1, 1)
    chart.legend().hide()

    chartView = ZoomPanChartView(chart)
    chartView.show()
    chartView.resize(400, 300)
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()    
titusjan
  • 5,376
  • 2
  • 24
  • 43
  • I'm having the same issue. For the grid to be human-readable I'd like the grid step to be set and fixed, independently of scroll and panning. So, I'd like to set the grid tick values myself as well. This way it would move as you move around. QChart's grid is not human readable at all. I'll try a few things and will say here if one of them works. – A. Vieira May 26 '17 at 08:54

3 Answers3

5

You can use QCategoryAxis to place ticks where you want:

initialize:

ch = self.chView.chart()
self.chartAxisX = QCategoryAxis(labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue, startValue=0.0)
ch.setAxisX(self.chartAxisX)
self.chartAxisY = QCategoryAxis(labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue, startValue=0.0)
ch.setAxisY(self.chartAxisY)

add series:

        ch.addSeries(s)
        s.attachAxis(self.chartAxisX)
        s.attachAxis(self.chartAxisY)

set ticks at multiples of 5:

        for s in self.chartAxisX.categoriesLabels():
            self.chartAxisX.remove(s)
        for i in range(0, int(max_x_value) + 1, 5):
            self.chartAxisX.append(str(i), i)
        self.chartAxisX.setRange(0.0, max_x_value)

or use this generic function for any interval:

def format_axis(axis, min_value, max_value, step):
    for s in axis.categoriesLabels():
        axis.remove(s)
    axis.setStartValue(min_value)
    for i in range(ceil(min_value / step), floor(max_value / step) + 1):
        v = i * step
        axis.append('%g' % v, v)
    axis.setRange(min_value, max_value)

format_axis(self.chartAxisX, -1.1, 0.98, 0.25)
panda-34
  • 4,089
  • 20
  • 25
  • Thanks for your answer. In the mean time I've decided to use [PyQtGraph](http://www.pyqtgraph.org/) but, who knows, I might use QtCharts in a future project. – titusjan Sep 24 '17 at 17:41
1

The best I could find is setting a QValueAxis as the axis on QChart and calling QValueAxis::applyNiceNumbers() to adjust the range, i.e. max and min of the current scale, so that the numbers shown are a bit more human readable. But this will alter data's position instead of gridlines' positions. You can check the function's behaviour on the horizontalBarChart example.

I thought of using a QLineSeries data-set to make the grid myself, but I would need to change the tick's positions on the axis, which, as far as I was able to determine, is not easily made with current QChart.

Short answer: you can't do it with QCharts..

I've been working with Qwt library for some time and I can attest that the grid there behaves as expected and other behaviors are a bit more mature as well. Panning moves the grip around and zooming makes the grid resize in steps to stay human-readable. Maybe it's worth checking.

A. Vieira
  • 1,213
  • 2
  • 11
  • 27
1

IMO you can do this with QCharts and QValueAxis:

  QValueAxis *axisY = new QValueAxis;
  axisY->setTickType(QValueAxis::TicksDynamic);
  axisY->setTickAnchor(0.0);
  axisY->setTickInterval(0.2);

See e.g. Nice Label Algoritm on how to determine nice tick intervals.