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

Posted

技术标签:

【中文标题】如何将 qml ScatterSeries 添加到现有的 qml 定义的 ChartView?【英文标题】:How to add qml ScatterSeries to existing qml defined ChartView? 【发布时间】:2019-08-17 12:47:01 【问题描述】:

我正在尝试将系列按需添加到使用 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 中执行此操作的方法。以下是我目前尝试的一些 sn-ps:

QML sn-p(最初只有 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)

addChartSeries 的 Python sn-p:

    @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函数在我的类“dataModel”中,我像这样传递给QML(数据模型类可以很好地用于我用它做的许多其他事情,所以没有问题):

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

【问题讨论】:

【参考方案1】:

QML Charts API 是基于 C++ 使用的 API 编写的,但并不相同,例如 ChartView 是一个不暴露 QChart 的 QQuickItem,不像 QChartView 是一个 QGraphicsView(QWidget),如果它暴露 QChart ,该系列的内容是什么。总之,您将无法使用 C++(Python) 类与 QML 进行交互。

您在开头显示的示例不是针对 C++ 的示例,而是针对 QML 的示例,因此无法直接将其转换为 QML。也不可能直接使用 QtCharts 类在 C++/Python 中创建 QML 系列,一种可能的策略是使用可以评估 QML 元素并将其返回给 C++/Python 的 QQmlExpression。此外,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)
        
    

【讨论】:

这看起来很有用,谢谢。我将尝试按照您的示例在 qml 中创建系列。在加载数据模型之前,我不知道需要多少个系列,但看起来我可以选择在 qml 中使用您的示例的变体创建 n 个新系列。理想情况下,如果用户重新加载新数据,我需要删除并重新创建它们。我猜有一种方法可以删除它们,然后将其附加到加载按钮?一旦我得到它的工作,会告诉你我是怎么做的,并将它标记为接受的答案。 @BobbyG 我的答案是针对您当前的问题,而不是针对您的项目,我不知道您的项目还有什么要求/限制,所以我不能向您保证任何事情。请记住,在 SO 中,我们不会在项目中提供帮助(我们对此非常有限),仅针对特定问题,恕我直言,接受我的答案不应该取决于您的项目。 同意。只是想尝试一下并确保我理解。

以上是关于如何将 qml ScatterSeries 添加到现有的 qml 定义的 ChartView?的主要内容,如果未能解决你的问题,请参考以下文章

创建后如何将项目添加到 QML 网格?

如何将 Switch Qml 添加到 qt 小部件?

Qt3d QML:如何将文本作为覆盖添加到标准示例

如何在 QML 中添加和使用资源?

如何将鼠标滚轮滚动添加到垂直滚动条或滚动区域?

QML MouseArea:如何将鼠标事件传播到其他鼠标区域?