AdvancedFeaturesDemo Example

import sys, os
sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../shared")

from DevMachines import __pyside2__, __pyside6__
from DevMachines.QtitanBase import Qtitan
from DevMachines.QtitanChart import (Chart, ChartView2D, ChartBarSeries2D, ChartAreaSeries2D,
    ChartLineSeries2D, ChartSplineSeries2D, ChartAxisTitle, ChartLegend, ChartBarSeriesLabel,
    ChartHitInfo, ChartMarker)

if __pyside2__:
    from PySide2 import QtCore
    from PySide2.QtCore import Qt, qAbs
    from PySide2.QtGui import QBrush, QIcon, QPainter, QPen, QColor
    from PySide2.QtWidgets import (QApplication, QComboBox, QSlider, QLabel,
        QCheckBox, QHeaderView, QTableWidget, QTableWidgetItem, QTableWidgetSelectionRange,
        QToolBar, QAbstractItemView)

if __pyside6__:
    from PySide6 import QtCore
    from PySide6.QtCore import Qt, qAbs
    from PySide6.QtGui import QBrush, QIcon, QPainter, QPen, QColor
    from PySide6.QtWidgets import (QApplication, QComboBox, QSlider, QLabel,
        QCheckBox, QHeaderView, QTableWidget, QTableWidgetItem, QTableWidgetSelectionRange,
        QToolBar, QAbstractItemView)

from DemoChartWindow import DemoChartWindow

class AdvancedChart(Chart):
    def __init__(self, parent = None):
        Chart.__init__(self, parent)
        self.XLine = -1
        self.arMarkers = list()
        self.szMarker = QtCore.QSize(5.0, 5.0)

    def paintEvent(self, event):
        Chart.paintEvent(self, event)
        p = QPainter(self)
        # Draw markers:
        i = 0
        for ptMarker in self.arMarkers:
            series2D = self.series()[i]
            color = series2D.color()
            view = series2D.view()
            axisY = view.axisY()

            rectAxisBounds = axisY.boundingRect()
            rectAxisBounds.setWidth(view.boundingRect().width())

            if rectAxisBounds.contains(ptMarker):
                hints = p.renderHints()
                p.setRenderHints(hints, False)
                p.setRenderHint(QPainter.Antialiasing)

                savePen = p.pen()
                p.setPen(QPen(color.darker()))

                saveBrush = p.brush()
                color.setAlpha(140)
                p.setBrush(color)

                p.drawEllipse(ptMarker, self.szMarker.width(), self.szMarker.height())
                p.setBrush(saveBrush)
                p.setPen(savePen)

                p.setRenderHints(p.renderHints(), False)
                p.setRenderHints(hints, True)
            i = i + 1

        # Draw vertical line:
        if self.XLine != -1.0:
            savePen = p.pen()
            p.setPen(QPen(QBrush(QColor(Qt.red)), 1.0, Qt.DashLine))

            rect = self.viewRect()
            p.drawLine(self.XLine, rect.top(), self.XLine, rect.bottom())

            p.setPen(savePen)

    def resizeEvent(self, event):
        Chart.resizeEvent(self, event)
        self.arMarkers.clear()
        self.XLine = -1.

class MainWindow(DemoChartWindow):
    DEFAULT_INFO  = "Move the mouse cursor over the chart and see information on hovered chart component"
    Combined = 1
    SecondaryAxis = 2
    Interactive = 3
    def __init__(self):
        DemoChartWindow.__init__(self, "Advanced Features")
        self.setChart(AdvancedChart())
        self.createSeriesParametrs()
        self.seriesChanged(self.seriesSwitcher.currentIndex())

        self.connect(self.chart, QtCore.SIGNAL("chartMouseTrack(ChartHitInfo*)"), self,
            QtCore.SLOT("mouseTrack(ChartHitInfo*)"), Qt.DirectConnection)
        self.connect(self.chart, QtCore.SIGNAL("chartMouseUp(ChartHitInfo*)"), self,
            QtCore.SLOT("mouseUp(ChartHitInfo*)"), Qt.DirectConnection)

    def createSeriesParametrs(self):
        # Option Series
        seriesTypeGroup = self.createGroupParameters(self.tr("Series"))
        localLayout = seriesTypeGroup.layout()
        self.seriesSwitcher = QComboBox(seriesTypeGroup)
        self.seriesSwitcher.addItem(self.tr("Combined"), MainWindow.Combined)
        self.seriesSwitcher.addItem(self.tr("Secondary Axis"), MainWindow.SecondaryAxis)
        self.seriesSwitcher.addItem(self.tr("Interactive"), MainWindow.Interactive)
        self.connect(self.seriesSwitcher, QtCore.SIGNAL("currentIndexChanged(int)"), self,
            QtCore.SLOT("seriesChanged(int)"))

        # SecondaryAxis
        self.showSecondaryAxisX = QCheckBox(self.tr("Secondary X Axis"), seriesTypeGroup)
        self.showSecondaryAxisX.setCheckState(Qt.Checked)
        self.showSecondaryAxisX.setVisible(False)
        self.connect(self.showSecondaryAxisX, QtCore.SIGNAL("stateChanged(int)"),
            self, QtCore.SLOT("showSecondaryAxisXChanged(int)"))

        self.showSecondaryAxisY = QCheckBox(self.tr("Secondary Y Axis"), seriesTypeGroup)
        self.showSecondaryAxisY.setCheckState(Qt.Checked)
        self.showSecondaryAxisY.setVisible(False)
        self.connect(self.showSecondaryAxisY, QtCore.SIGNAL("stateChanged(int)"), self,
            QtCore.SLOT("showSecondaryAxisYChanged(int)"))

        # Option Labels
        self.createLabelsGroup()
        self.dataLabelsGroup.setVisible(False)
        self.connect(self.angleDataLabelsSwitcher, QtCore.SIGNAL("currentIndexChanged(int)"),
            self, QtCore.SLOT("updateAngleChanged(int)"))

        self.hitTestResult = QLabel(MainWindow.DEFAULT_INFO, seriesTypeGroup)
        self.hitTestResult.setWordWrap(True)
        self.hitTestResult.setVisible(False)
        self.hitTestResult.setMinimumWidth(180)
        self.hitTestResult.setMaximumWidth(180)
        self.hitTestResult.setMinimumHeight(100)
        self.hitTestResult.setMaximumHeight(100)

        localLayout.addRow(self.seriesSwitcher)
        localLayout.addRow(self.showSecondaryAxisX)
        localLayout.addRow(self.showSecondaryAxisY)
        localLayout.addRow(self.hitTestResult)

    def createCombined(self):
        self.createTitle(self.tr("Area, Bar and Line"))

        self.chart.legend().setVisible(True)
        self.chart.legend().setVerticalAlignment(ChartLegend.LegendCenter)

        series1 = ChartAreaSeries2D()
        series1.setName(self.tr("Area"))
        self.chart.appendSeries(series1)

        series2 = ChartBarSeries2D()
        series2.setTransparency(180)
        series2.setName(self.tr("Bar"))
        self.chart.appendSeries(series2)

        series3 = ChartLineSeries2D()
        series3.setName(self.tr("Line"))
        self.chart.appendSeries(series3)

        arTemp = [
            # 0     1        2      3      4      5      6      7       8      9      10    11
            [ 28.5,  29.5,   24.5,  29.0,  32.6,  34.5,  36.5,  28.0,  34.6,  36.0,  40.0,  39.0 ],  # 0 ChartAreaSeries2D
            [ 23.5,  28.0,   34.0,  32.0,  31.0,  25.0,  39.0,  40.0,  42.0,  39.0,  45.0,  50.0 ],  # 1 ChartBarSeries2D
            [ 3.0,   5.0,    9.0,   13.0,  17.0,  21.0,  25.0,  29.0,  36.0,  42.0,  48.0,  54.0 ]] # 2 ChartLineSeries2D

        for month in range(1, 13):
            strMonth = QtCore.QLocale.system().monthName(month, QtCore.QLocale.ShortFormat)

            series1.addAxisPointY(arTemp[0][month - 1], strMonth)
            series2.addAxisPointY(arTemp[1][month - 1], strMonth)
            series3.addAxisPointY(arTemp[2][month - 1], strMonth)

    def createSecondaryAxis(self):
        self.chart.legend().setVisible(True)
        self.chart.legend().setVerticalAlignment(ChartLegend.LegendCenter)

        series1 = ChartSplineSeries2D()
        series1.setName(self.tr("Series1"))
        self.chart.appendSeries(series1)
        series1.setMarkerType(ChartMarker.Triangle)
        series1.setMarkerSize(16)

        series1.addXY(1, 0.0)
        series1.addXY(2, 2.0)
        series1.addXY(3, 5.0)
        series1.addXY(4, 3.0)
        series1.addXY(5, 3.5)
        series1.addXY(6, 5.0)
        series1.addXY(7, 8.0)
        series1.addXY(8, 7.0)

        series2 = ChartSplineSeries2D()
        series2.setName(self.tr("Series2"))
        self.chart.appendSeries(series2)
        series2.setMarkerType(ChartMarker.Star)
        series2.setMarkerSize(16)

        series2.addXY(1, 2.0)
        series2.addXY(2, 6.0)
        series2.addXY(3, 12.0)
        series2.addXY(4, 16.0)
        series2.addXY(5, 14.0)
        series2.addXY(6, 9.0)
        series2.addXY(7, 6.0)
        series2.addXY(8, 2.0)

    def createInteractive(self):
        self.createTitle(self.tr("Weather Report"))
        self.chart.setAreasOrientation(Qt.Vertical)
        self.chart.legend().setVisible(True)
        self.chart.legend().setVerticalAlignment(ChartLegend.LegendCenter)

        view1 = ChartView2D()
        self.chart.appendView(view1)
        self.axisY1 = view1.axisY()
        titleY = self.axisY1.title()
        titleY.setText(self.tr("Pressure"))
        titleY.setVisible(True)
        view1.axisY().setAutoRange(False)
        view1.axisY().setFixedRange(15, 25)
        view1.axisX().setVisible(False)

        view2 = ChartView2D()
        self.chart.appendView(view2)
        titleY = view2.axisY().title()
        titleY.setText(self.tr("Temperature"))
        titleY.setVisible(True)
        view2.axisY().setAutoRange(False)
        view2.axisY().setFixedRange(1012, 1025)
        view2.axisX().setVisible(False)

        view3 = ChartView2D()
        self.chart.appendView(view3)
        self.axisY2 = view3.axisY()
        titleY = self.axisY2.title()
        titleY.setText(self.tr("Humidity,%"))
        titleY.setVisible(True)
        view3.axisX().title().setText(self.tr("Days"))
        view3.axisX().title().setVisible(True)

        # Add series and data points:
        series1 = ChartLineSeries2D()
        series1.setName(self.tr("Temperature"))
        series1.setMarkerVisible(False)
        series1.label().setVisible(True)
        series1.label().setAngle(-270)
        view1.appendSeries(series1)

        series2 = ChartAreaSeries2D()
        series2.setName(self.tr("Pressure"))
        series2.setMarkerVisible(False)
        series2.label().setVisible(False)
        view2.appendSeries(series2)

        series3 = ChartBarSeries2D()
        series3.setName(self.tr("Humidity"))
        series3.label().setVisible(False)
        view3.appendSeries(series3)

        for day in range(1, 32):
            series1.addXY(day, 20.0 + self.getRandomValue(-5.0, 5.0))
            series2.addXY(day, 1020.0 + self.getRandomValue(-4.0, 4.0))
            series3.addXY(day, 50.0 + self.getRandomValue(-40.0, 40.0))

    def updateValueParameters(self):
        self.dataLabelsGroup.setVisible(False)
        DemoChartWindow.updateValueParameters(self)
        index = self.seriesSwitcher.currentIndex()
        if index == MainWindow.SecondaryAxis:
            self.showSecondaryAxisX.setVisible(True)
            self.showSecondaryAxisY.setVisible(True)
            if self.showSecondaryAxisX.isVisible() and self.showSecondaryAxisY.isVisible():
                self.showSecondaryAxisXChanged(self.showSecondaryAxisX.checkState())
                self.showSecondaryAxisYChanged(self.showSecondaryAxisY.checkState())
        else:
            self.showSecondaryAxisX.setVisible(False)
            self.showSecondaryAxisY.setVisible(False)

        if index == MainWindow.Combined:
            self.dataLabelsGroup.setVisible(True)
            self.showDataLabels(self.dataLabelsGroup.isChecked())
            if self.angleDataLabelsSwitcher and self.angleDataLabelsSwitcher.isVisible():
                self.labelsAngleChanged(self.angleDataLabelsSwitcher.currentIndex())
                listSeries = self.chart.series()
                for series in listSeries:
                    if series.inherits("Qtitan::ChartBarSeries2D"):
                        label = series.label()
                        label.setPosition(ChartBarSeriesLabel.ChartBarLabelTop)

    def seriesChanged(self, index):
        self.chart.XLine = -1.
        self.chart.arMarkers.clear()
        self.chart.clearSeries()
        self.chart.clearTitles()
        self.chart.setBackgroundBrush(QBrush())

        v = self.seriesSwitcher.itemData(index)
        if int(v) == MainWindow.Combined:
            self.chart.enableMouseTrackingMode(ChartHitInfo.HitNone)
            self.hitTestResult.setVisible(False)
            self.createCombined()
        elif int(v) == MainWindow.SecondaryAxis:
            self.chart.enableMouseTrackingMode(ChartHitInfo.HitNone)
            self.hitTestResult.setVisible(False)
            self.createSecondaryAxis()
        elif int(v) == MainWindow.Interactive:
            self.chart.enableMouseTrackingMode(ChartHitInfo.HitChartArea | ChartHitInfo.HitTitle | ChartHitInfo.HitDataPoint |
                ChartHitInfo.HitLegend | ChartHitInfo.HitAxis | ChartHitInfo.HitAxisName | ChartHitInfo.HitView)
            self.hitTestResult.setVisible(True)
            self.createInteractive()

        self.updateValueParameters()

    def showSecondaryAxisXChanged(self, state):
        view2D = self.chart.views()[0]
        if Qt.Checked != state:
            view2D.secondaryAxisX().setVisible(False)
        else:
            self.showAxisXChanged(Qt.Checked)

        titleX = view2D.axisX().title()

        if Qt.Checked == state:
            view2D.secondaryAxisX().title().setText(self.tr("Series 2 X"))
            view2D.secondaryAxisX().title().setVisible(True)
            titleX.setText(self.tr("Series 1 X"))
            titleX.setVisible(True)
        else:
            titleX.setText(self.tr("Series 1 and 2 X"))
            titleX.setVisible(True)

        series = self.chart.series()[1]
        series.setSecondaryAxisX(Qt.Checked == state)

    def showAxisXChanged(self, state):
        view2D = self.chart.views()[0]
        view2D.secondaryAxisX().setVisible(Qt.Checked == state)

    def showSecondaryAxisYChanged(self, state):
        view2D = self.chart.views()[0]
        if Qt.Checked != state:
            view2D.secondaryAxisY().setVisible(False)
        else:
            self.showAxisYChanged(Qt.Checked)

        titleY = view2D.axisY().title()
        if Qt.Checked == state:
            view2D.secondaryAxisY().title().setText(self.tr("Series 2 Y"))
            view2D.secondaryAxisY().title().setVisible(True)
            titleY.setText(self.tr("Series 1 Y"))
            titleY.setVisible(True)
        else:
            titleY.setText(self.tr("Series 1 and 2 Y"))
            titleY.setVisible(True)

        series = self.chart.series()[1]
        series.setSecondaryAxisX(Qt.Checked == state)

    def showAxisYChanged(self, state):
        view2D = self.chart.views()[0]
        view2D.secondaryAxisY().setVisible(Qt.Checked == state)

    def updateAngleChanged(self, angle):
        self.updateValueParameters()

    def mouseTrack(self, hit):
        listViews = self.chart.views()
        if hit == None or len(listViews) == 0:
            return

        view2D = listViews[0]

        pt = hit.hitPoint()
        axisXX = view2D.axisX()

        # Get X Position on the chart
        realXMarker = axisXX.valueFromPoint(pt)

        # left marker index
        xValue = int(realXMarker)

        redraw = False
        seriesCount = len(self.chart.series())
        DemoChartWindow.resize_list(self.chart.arMarkers, seriesCount, QtCore.QPoint(0, 0))

        i = 0
        for series2D in  self.chart.series():
            dp1 = series2D.at(xValue)
            dp2 = series2D.at(xValue + 1)

            if dp1 != None and dp2 != None:
                view = series2D.view()
                axisX = view.axisX()
                axisY = view.axisY()

                x1 = axisX.valueToPoint(dp1.valueX())
                x2 = axisX.valueToPoint(dp2.valueX())

                y1 = axisY.valueToPoint(dp1.valueY())
                y2 = axisY.valueToPoint(dp2.valueY())

                pt1 = QtCore.QPoint(x1, y1)
                pt2 = QtCore.QPoint(x2, y2)

                ptMarker = None
                if qAbs(pt.x() - pt1.x()) < qAbs(pt.x() - pt2.x()):
                    ptMarker = pt1
                else:
                    ptMarker = pt2

                if len(self.chart.arMarkers) < seriesCount or self.chart.arMarkers[i] != ptMarker:
                    self.chart.arMarkers[i] = ptMarker
                    if i == 0:
                        self.chart.XLine = ptMarker.x()
                    redraw = True
            i = i + 1

        if redraw:
            self.chart.repaint()

        cursor =  Qt.ArrowCursor
        strInfo = ""
        if hit.hitInfo() == ChartHitInfo.HitDataPoint:
            strInfo = "Hovered Element: Data Point\nClick left mouse button to get additional information"
            cursor = Qt.PointingHandCursor
        elif hit.hitInfo() == ChartHitInfo.HitChartArea:
            strInfo = "Hovered Element: Chart Area"
        elif hit.hitInfo() == ChartHitInfo.HitAxis or hit.hitInfo() == ChartHitInfo.HitAxisName:
            axis = None
            strName = "NoName"
            value = 0.0
            for view in listViews:
                axisX = view.axisX()
                axisY = view.axisY()

                if axisX.axisID() == hit.axisID1():
                    axis = axisX
                elif axisY.axisID() == hit.axisID1():
                    axis = axisY
                elif self.axisY1.axisID() == hit.axisID1():
                    axis = self.axisY1
                elif self.axisY2.axisID() == hit.axisID1():
                    axis = self.axisY2

                if hit.hitInfo() == ChartHitInfo.HitAxisName:
                    strInfo = "Hovered Element: Axis Name\r\nAxis Name: {}".format(strName)
                else:
                    strInfo = "Hovered Element: Axis\r\nAxis Name: {}\r\nValue = {}".format(strName, value)

                if axis != None:
                    strName = axis.title().text()
                    value = axis.valueFromPoint(pt)
                    break

        elif hit.hitInfo() == ChartHitInfo.HitTitle:
            strInfo = "Hovered Element: Chart Title"
        elif hit.hitInfo() == ChartHitInfo.HitView:
            for view in listViews:
                axisX = view.axis(hit.axisID1())
                axisY = view.axis(hit.axisID2())

                if axisX != None and axisY != None:
                    valueX = int(axisX.valueFromPoint(pt) + 1.5)
                    valueY = axisY.valueFromPoint(pt)
                    strInfo = "Hovered Element: Chart View\r\nX Value: {}\r\nY Value: {}".format(valueX, valueY)
                    break;

            if strInfo == "":
                strInfo = "Hovered Element: Chart View"

        elif hit.hitInfo() == ChartHitInfo.HitLegend:
            strInfo = "Hovered Element: Legend"
        else:
            strInfo = MainWindow.DEFAULT_INFO

        self.hitTestResult.setText(strInfo)
        self.setCursor(cursor)

    def mouseUp(self, hit):
        if hit.mouseButton == Qt.NoButton or hit.hitInfo != ChartHitInfo.HitDataPoint:
            return
        series = self.chart.series()[hit.index()]
        strInfo = "Clicked on the Data Point:\r\n\nSeries Index: {}\r\nSeries Name: {}\r\nData Point Index: {}\r\nData Point Value: {}".format(
            hit.index(), series.name(), hit.pointIndex(), series.at(hit.pointIndex()).valueY())

        QMessageBox.information(self, qApp.applicationName(), strInfo)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    if __pyside2__:
        sys.exit(app.exec_())
    if __pyside6__:
        sys.exit(app.exec())