Search code examples
pythonpyqtqmlpyqt5qtcharts

QML PieSeries custom model with custom data


I want to load some custom data (not Just label and size, but something else I need too). Let say - I want to load every slice's color out of the model too. How can I access the "third column" value - "black" or "white" and make QML to use it? This is the code I have:

main.py:

import sys
from PyQt5 import QtCore, QtWidgets, QtQuick
import custom_model

if __name__ == '__main__':
    app = QtWidgets.QApplication( sys.argv )

    view = QtQuick.QQuickView()

    outer_ring_model = custom_model.OuterRingModel()

    view.rootContext().setContextProperty( 'outerRingModel', outer_ring_model )

    view.setSource( QtCore.QUrl( "main.qml" ) )
    view.show()

    sys.exit( app.exec_() )

custom_model.py:

from PyQt5 import QtCore

class OuterRingModel( QtCore.QAbstractTableModel ):

    def __init__( self, parent=None ):
        super().__init__( parent )

        self.column_count = 2
        self.row_count = 100
        self.data = []

        i = 0
        while i < (self.row_count / 2):
            self.data.append(['Black', 50, 'black'])
            self.data.append(['White', 50, 'white'])
            i += 1

    def rowCount( self, parent ):
        return len( self.data )

    def columnCount( self, parent ):
        return self.column_count

    def data( self, index, role ):
        if role == QtCore.Qt.DisplayRole:
            return self.data[ index.row() ][ index.column() ]
        elif role == QtCore.Qt.EditRole:
            return self.data[ index.row() ][ index.column() ]
        elif role == QtCore.Qt.BackgroundRole:
            return QtGui.QColor( QtCore.Qt.white )
        return QtCore.QVariant()

main.qml:

import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 1.4
import QtCharts 2.1

Grid {
    id: grid1
    width: 1024
    height: 600
    spacing: 10
    rows: 1
    columns: 2

    Component.onCompleted: { update() }

    ChartView {
        id: chart
        width: 600
        height: 600
        antialiasing: true
        animationDuration: 1000
        animationOptions: ChartView.AllAnimations
        title: "MyTitle"
        legend.visible: false

        PieSeries {
            id: serie0
            name: "Outer Ring"
            size: 0.75
            holeSize: 0.7
            onSliceAdded: {
                slice.color = Qt.lighter("red", 1.5)
                // slice.color = HOW_TO_IMPORT_MODEL_DATA_HERE
            }

            VPieModelMapper {
                id: model0
                model: outerRingModel
                labelsColumn: 0
                valuesColumn: 1
                firstRow: 0
                rowCount: 100
            }
        }
    }
}

Solution

  • What you require cannot be implemented in QML since you cannot know the index associated with the slice so you will have to implement that logic in Python/C++. In addition, your model has errors that I have already corrected.

    main.py

    import os
    import sys
    
    from PyQt5 import QtCore, QtGui, QtWidgets, QtQuick, QtChart
    
    import custom_model
    
    
    class Manager(QtCore.QObject):
        @QtCore.pyqtSlot(
            QtChart.QPieSeries, QtChart.QPieSlice, QtCore.QAbstractItemModel, int
        )
        def updateSlice(self, serie, sl, model, firstRow):
            row = firstRow + serie.slices().index(sl)
            index = model.index(row, 2)
            color = model.data(index) or ""
            sl.setColor(QtGui.QColor(color))
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
    
        view = QtQuick.QQuickView()
    
        manager = Manager()
        outer_ring_model = custom_model.OuterRingModel()
    
        view.rootContext().setContextProperty("manager", manager)
        view.rootContext().setContextProperty("outerRingModel", outer_ring_model)
    
        filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), "main.qml")
    
        view.setSource(QtCore.QUrl.fromLocalFile(filename))
        view.show()
    
        sys.exit(app.exec_())
    

    custom_model.py

    from PyQt5 import QtCore
    
    
    class OuterRingModel(QtCore.QAbstractTableModel):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.column_count = 3
            self._data = []
    
            for i in range(50):
                self._data.append(["Black", 50, "black"])
                self._data.append(["White", 50, "white"])
    
        def rowCount(self, parent):
            return len(self._data)
    
        def columnCount(self, parent):
            return self.column_count
    
        def data(self, index, role=QtCore.Qt.DisplayRole):
            if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole,):
                return self._data[index.row()][index.column()]
    

    main.qml

    import QtQuick 2.7
    import QtQuick.Window 2.0
    import QtQuick.Controls 1.4
    import QtCharts 2.1
    
    Grid {
        id: grid1
        width: 1024
        height: 600
        spacing: 10
        rows: 1
        columns: 2
    
        VPieModelMapper {
            id: mapper0
            model: outerRingModel
            series: serie0
            labelsColumn: 0
            valuesColumn: 1
            firstRow: 0
            rowCount: 100
        }
    
        ChartView {
            id: chart
            width: 600
            height: 600
            antialiasing: true
            animationDuration: 1000
            animationOptions: ChartView.AllAnimations
            title: "MyTitle"
            legend.visible: false
    
            PieSeries {
                id: serie0
                name: "Outer Ring"
                size: 0.75
                holeSize: 0.7
                onSliceAdded: {
                    manager.updateSlice(mapper0.series, slice, mapper0.model, mapper0.firstRow)
                }   
            }
        }
    }
    

    enter image description here