Transforming QAbstractListModel roles to columns via a proxy for charting

47 views Asked by At

I have a QAbstractListModel (SitesModel) that has two roles (x_test, y_test). Each contains a list of values (e.g., [1, 2, 3], [0.1, 0.7, 0.3]). My implementation of the QAbstractListModel works well and populates a ListView QML-side.

I then have a QSortFilterProxyModel (SiteFilter) which takes the SitesModel as source. This is there to allow users to see one sitesModel row at a time (i.e., it filters the last row). This also works nicely.

Now I want to chart the x_test, y_test list values of the SiteFilter row in a ChartView on the QML end. I have come across VXYModelMapper, which seems ideal - however, it only accepts table columns as input.

After some reading I came across a thread suggesting a QIdentityProxyModel could be used to transform the roles into columns for use in VXYModelMapper. The actual solution is never provided, though.

I have attempted to implement QIdentityProxyModel but I'm a bit lost on how to use it. Particularly, I'm not sure how to actually do the role - column transformation. Should I be implementing mapFromSource to convert the x_test, y_test lists to columns?

My full implementation (with some minor code omitted) so far is below (PySide6).

app.py

def main():
    ...
    sites_model = core.SitesModel()

    site_filter = core.SiteFilter()
    site_filter.setSourceModel(sites_model)

    site_chart = core.SiteChart()
    site_chart.setSourceModel(site_filter)

    engine.rootContext().setContextProperty("sitesModel", sites_model)
    engine.rootContext().setContextProperty("siteFilterModel", site_filter)
    engine.rootContext().setContextProperty("siteChartModel", site_chart)
    ...

core.py

class Site:
    # class for a single site
    def __init__(self, **kwargs):
        props_defaults = {
            'x_test': [],
            'y_test': []
        }

        for (prop, default) in props_defaults.items():
            setattr(self, prop, kwargs.get(prop, default))


class SitesModel(QtCore.QAbstractListModel):
    # core list model containing n sites
    XTestRole = QtCore.Qt.UserRole + 1
    YTestRole = QtCore.Qt.UserRole + 2

    def __init__(self, parent=None):
        super(SitesModel, self).__init__(parent)
        self._sites = []
        self.testing()  # populates a few rows of class Site for testing

    # roleNames ...
    # data ...
    # setData ...
    # rowCount ...
    # etc...
    # testing ...


class SiteFilter(QtCore.QSortFilterProxyModel):
    # filters SitesModel to last row
    def __init__(self, parent=None):
        super(SiteFilter, self).__init__(parent)
        self.row_filter_id = None

    def clearFilter(self):
        self.row_filter_id = None
        self.invalidateFilter()

    def filterAcceptsRow(self, source_row, source_parent):
        if self.row_filter_id:
            if source_row == self.row_filter_id:
                return True
        return False

    @QtCore.Slot()
    def setFilterToLastRow(self):
        sites = self.sourceModel()._sites
        if sites:
            last_id = max([site.id for site in sites])
            self.row_filter_id = last_id
            self.invalidateFilter()


class SiteChart(QtCore.QIdentityProxyModel):
    def __init__(self, parent=None):
        super(SiteChart, self).__init__(parent)

    # need to access x_test, y_test values here and place in 2 columns.
    # these columns will then be read in qml shown below

NewSite.qml

ChartView {
    anchors.fill: parent

    LineSeries {
        VXYModelMapper {
            model: siteChartModel
            xColumn: 0 //x_test
            yColumn: 1 //y_test
        }
    }
}
0

There are 0 answers