【问题标题】:How to add qml ScatterSeries to existing qml defined ChartView?如何将 qml ScatterSeries 添加到现有的 qml 定义的 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)

【问题讨论】:

    标签: python qml pyside2


    【解决方案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 中,我们不会在项目中提供帮助(我们对此非常有限),仅针对特定问题,恕我直言,接受我的答案不应该取决于您的项目。
    • 同意。只是想尝试一下并确保我理解。
    猜你喜欢
    • 1970-01-01
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    • 2022-10-23
    • 1970-01-01
    • 2018-05-05
    • 2016-03-10
    • 2020-12-29
    相关资源
    最近更新 更多