如何将 qml ScatterSeries 添加到现有的 qml 定义的 ChartView?

How to add qml ScatterSeries to existing qml defined ChartView?

我正在尝试使用 python 将按需系列添加到 qml 中定义的现有 ChartView。我找到了一个示例,展示了如何在 C++ 中执行此操作(取自 https://doc.qt.io/archives/qt-5.11/qml-qtcharts-chartview.html#createSeries-method):

// lineSeries is a LineSeries object that has already been added to the ChartView; re-use its axes
var myAxisX = chartView.axisX(lineSeries);
var myAxisY = chartView.axisY(lineSeries);
var scatter = chartView.createSeries(ChartView.SeriesTypeScatter, "scatter series", myAxisX, myAxisY);

但我在 python 中找不到执行此操作的方法。以下是我迄今为止尝试的一些片段:

QML 片段(最初只有 1 个散点序列):

        ChartView {
            id: bscan0
            ScatterSeries{
                id: hits0
                axisX: ValueAxis {
                    id: bscan0_xAxix
                    min: 0
                    max: 10
                }
                axisY: ValueAxis {
                    id: bscan0_yAxis
                    min: -105
                    max: 1
                }
            }

QML js 函数将 chartView 传递给 python 因此它可以添加另一个系列:

dataModel.addChartSeries(bscan0, hits0)

Python addChartSeries 片段:

    @Slot(QObject, QObject)
    def addChartSeries(self, chartView, chart_series):
        myAxisX = chartView.axisX(chart_series) # reuse the axis from existing series
        myAxisY = chartView.axisY(chart_series) # reuse the axis from existing series

#         This function equivalent to the c++ one doesn't exit
#         myChartSeries = chartView.createSeries(QtCharts.SeriesTypeScatter, "scatter series", myAxisX, myAxisY)

#       So try another way:
        myChartSeries = QtCharts.QScatterSeries()
        myChartSeries.setName("scatter series")
        myChartSeries.attachAxis(myAxisX) 
        myChartSeries.attachAxis(myAxisY)
        myChartSeries.append(5, -10) 
        myChartSeries.append(5, -20) 
        myChartSeries.append(5, -30)

#         Try to get chart from existing series. Doesn't work
#         Error says that chart_series is not in a chart (it is!)
#         myChart = chart_series.chart()
# Series not in the chart. Please addSeries to chart first.

#         Try to get chart from chartview passed across. Doesn't work
#         Error says that object has no .chart attribute (same for .chart and .chart()):
#         myChart = chartView.chart
# Error: 'PySide2.QtQuick.QQuickItem' object has no attribute 'chart'

#         It seems I must add series to chart like this, not to chartView, 
#         but I can't find a way to get the chart for the chartView.
#         myChart.addSeries(myChartSeries) 

上面的 python 函数在我的 class "dataModel" 中,我像这样传递给 QML(数据模型 class 对我做的很多其他事情都很好有了它所以没问题):

    dataModel = DataModel()    
    self.rootContext().setContextProperty("dataModel", dataModel)

QML 图表 API 是基于 C++ 使用的 API 编写的,但它并不相同,例如 ChartView 是一个 QQuickItem,它不公开 QChart,不像 QChartView 是一个QGraphicsView(QWidget),如果它公开了 QChart,那么系列是一样的。总之,您将无法使用 C++(Python) 类 与 QML 进行交互。

你开头的例子不是C++的例子,而是QML的例子,所以不能直接翻译成QML。也不可能在 C++/Python 中直接使用 QtCharts 类 创建 QML 系列,一种可能的策略是使用可以评估 QML 元素的 QQmlExpression 和 return 它到 C++/Python。另外,createSeries()方法不仅可以添加系列,还可以连接信号。

from enum import Enum, auto
from PySide2 import QtCore, QtGui, QtWidgets, QtQml

# https://code.qt.io/cgit/qt/qtcharts.git/tree/src/chartsqml2/declarativechart_p.h#n105
class SeriesType(Enum):
    SeriesTypeLine = 0
    SeriesTypeArea = auto()
    SeriesTypeBar = auto()
    SeriesTypeStackedBar = auto()
    SeriesTypePercentBar = auto()
    SeriesTypePie = auto()
    SeriesTypeScatter = auto()
    SeriesTypeSpline = auto()
    SeriesTypeHorizontalBar = auto()
    SeriesTypeHorizontalStackedBar = auto()
    SeriesTypeHorizontalPercentBar = auto()
    SeriesTypeBoxPlot = auto()
    SeriesTypeCandlestick = auto()


class DataModel(QtCore.QObject):
    def __init__(self, engine, parent=None):
        super().__init__(parent)
        self.m_engine = engine

    @QtCore.Slot(QtCore.QObject, QtCore.QObject, QtCore.QObject, result=QtCore.QObject)
    def addChartSeries(self, chart_view, chart_axis_x, chart_axis_y):
        context = QtQml.QQmlContext(self.m_engine.rootContext())
        context.setContextProperty("chart_view", chart_view)
        context.setContextProperty("axis_x", chart_axis_x)
        context.setContextProperty("axis_y", chart_axis_y)
        context.setContextProperty("type", SeriesType.SeriesTypeScatter.value)
        script = """chart_view.createSeries(type, "scatter series", axis_x, axis_y);"""
        expression = QtQml.QQmlExpression(context, chart_view, script)
        serie, valueIsUndefined = expression.evaluate()
        if expression.hasError():
            print(expression.error())
            return

        import random

        mx, Mx = chart_axis_x.property("min"), chart_axis_x.property("max")
        my, My = chart_axis_y.property("min"), chart_axis_y.property("max")
        if not valueIsUndefined:
            for _ in range(100):
                x = random.uniform(mx, Mx)
                y = random.uniform(my, My)
                serie.append(x, y)
            # https://doc.qt.io/qt-5/qml-qtcharts-scatterseries.html#borderColor-prop
            serie.setProperty("borderColor", QtGui.QColor("salmon"))
            # https://doc.qt.io/qt-5/qml-qtcharts-scatterseries.html#brush-prop
            serie.setProperty("brush", QtGui.QBrush(QtGui.QColor("green")))
            # https://doc.qt.io/qt-5/qml-qtcharts-scatterseries.html#borderColor-prop
            serie.setProperty("borderWidth", 4.0)
            return serie


if __name__ == "__main__":
    import os
    import sys

    current_dir = os.path.dirname(os.path.realpath(__file__))

    app = QtWidgets.QApplication(sys.argv)
    engine = QtQml.QQmlApplicationEngine()

    dataModel = DataModel(engine)

    engine.rootContext().setContextProperty("dataModel", dataModel)
    file = os.path.join(current_dir, "main.qml")
    engine.load(QtCore.QUrl.fromLocalFile(file))
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtCharts 2.2


ApplicationWindow {
    visible: true
    width: 640
    height: 480

    ChartView {
        anchors.fill: parent
        id: bscan0
        ValueAxis {
            id: bscan0_xAxix
            min: 0
            max: 10
        }

        ValueAxis {
            id: bscan0_yAxis
            min: -100
            max: 100
        }
        Component.onCompleted: {
            var serie = dataModel.addChartSeries(bscan0, bscan0_xAxix, bscan0_yAxis)
        }
    }
}

虽然一般的策略是在QML中创建系列并在C++中填充它/Python,例如:

import random
from PySide2 import QtCore, QtGui, QtWidgets, QtQml, QtCharts


class DataModel(QtCore.QObject):
    @QtCore.Slot(QtCharts.QtCharts.QAbstractSeries)
    def fill_serie(self, serie):
        mx, Mx = 0, 10
        my, My = -100, 100
        for _ in range(100):
            x = random.uniform(mx, Mx)
            y = random.uniform(my, My)
            serie.append(x, y)
        # https://doc.qt.io/qt-5/qml-qtcharts-scatterseries.html#borderColor-prop
        serie.setProperty("borderColor", QtGui.QColor("salmon"))
        # https://doc.qt.io/qt-5/qml-qtcharts-scatterseries.html#brush-prop
        serie.setProperty("brush", QtGui.QBrush(QtGui.QColor("green")))
        # https://doc.qt.io/qt-5/qml-qtcharts-scatterseries.html#borderColor-prop
        serie.setProperty("borderWidth", 4.0)


if __name__ == "__main__":
    import os
    import sys

    current_dir = os.path.dirname(os.path.realpath(__file__))

    app = QtWidgets.QApplication(sys.argv)
    engine = QtQml.QQmlApplicationEngine()

    dataModel = DataModel(engine)

    engine.rootContext().setContextProperty("dataModel", dataModel)
    file = os.path.join(current_dir, "main.qml")
    engine.load(QtCore.QUrl.fromLocalFile(file))
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtCharts 2.2


ApplicationWindow {
    visible: true
    width: 640
    height: 480

    ChartView {
        anchors.fill: parent
        id: bscan0
        ValueAxis {
            id: bscan0_xAxix
            min: 0
            max: 10
        }

        ValueAxis {
            id: bscan0_yAxis
            min: -100
            max: 100
        }
        Component.onCompleted: {
            var serie = bscan0.createSeries(ChartView.SeriesTypeScatter, "scatter series", bscan0_xAxix, bscan0_yAxis);
            dataModel.fill_serie(serie)
        }
    }
}