简单柱状图

在 QML 应用中使用 Bars3D

简单柱状图 展示了如何使用 Bars3D 和 QML 制作一个简单的 3D 柱状图。

以下各节描述了如何切换序列并在同一时间内显示多个序列。有关基本 QML 应用功能的信息,请参阅 简单散点图

运行示例

要从 Qt Creator 运行示例,请打开 欢迎 模式并从 示例 中选择示例。有关更多信息,请访问 构建和运行示例

数据

示例数据集是某虚构公司在数年内的月收入和支出。数据定义在 Data.qml 中的列表模型中,如下所述

ListModel {
    id: dataModel
    ListElement{ timestamp: "2016-01"; expenses: "-4";  income: "5" }
    ListElement{ timestamp: "2016-02"; expenses: "-5";  income: "6" }
    ListElement{ timestamp: "2016-03"; expenses: "-7";  income: "4" }
    ...

每个数据项有三个角色:时间戳、收入和支出。时间戳值的格式为:<四位年份>-<两位月份>。通常,您会将年份和月份映射到柱状图的行和列,但您只能显示收入或支出的值。

现在,将数据添加到 Bars3D 图中。在内部创建两个 Bar3DSeries,首先是一个收入序列

Bar3DSeries {
    id: barSeries
    itemLabelFormat: "Income, @colLabel, @rowLabel: @valueLabel"
    baseGradient: barGradient

    ItemModelBarDataProxy {
        id: modelProxy
        itemModel: graphData.model
        rowRole: "timestamp"
        columnRole: "timestamp"
        valueRole: "income"
        rowRolePattern: /^(\d\d\d\d).*$/
        columnRolePattern: /^.*-(\d\d)$/
        rowRoleReplace: "\\1"
        columnRoleReplace: "\\1"
        multiMatchBehavior: ItemModelBarDataProxy.MMBCumulative
    }
    ...

数据附在序列内部 ItemModelBarDataProxyitemModel 属性。对于 valueRole,指定 income 字段,因为它包含您想要的值。由于年份和月份都位于同一字段,因此获取它们要复杂一些。要提取这些值,请分别指定 timestamp 字段作为 rowRolecolumnRole,并另外指定这些角色的搜索模式和个人规则,以便提取字段内容的正确部分。搜索模式是一个普通的 JavaScript 正则表达式,而替换规则指定与正则表达式匹配的字段内容的替换项。在这种情况下,将整个字段内容替换为只是年份或月份,这是行和列的第一个捕获子字符串。有关正则表达式替换功能的信息,请参阅 QString::replace(const QRegExp &rx, const QString &after) 函数文档。

multiMatchBehavior 属性指定当多个项目模型项目匹配相同的行/列组合时应该做什么。在这种情况下,将这些值相加。当显示每月的值时,此属性没有任何效果,因为我们的项目模型中没有重复的月份,但是当您想要显示年总计时,它就变得相关了。

然后,为支出添加另一个序列。

Bar3DSeries {
    id: secondarySeries
    visible: false
    itemLabelFormat: "Expenses, @colLabel, @rowLabel: -@valueLabel"
    baseGradient: secondaryGradient

    ItemModelBarDataProxy {
        id: secondaryProxy
        itemModel: graphData.model
        rowRole: "timestamp"
        columnRole: "timestamp"
        valueRole: "expenses"
        rowRolePattern: /^(\d\d\d\d).*$/
        columnRolePattern: /^.*-(\d\d)$/
        valueRolePattern: /-/
        rowRoleReplace: "\\1"
        columnRoleReplace: "\\1"
        multiMatchBehavior: ItemModelBarDataProxy.MMBCumulative
    }
    ...

模型包含作为负值的支出,但您希望以正条形显示它们,以便它们可以轻松与收入条形进行比较。使用 valueRolePattern 来移除负号以实现此目的。无需指定替换字符串,因为默认替换是一个空字符串。

使用序列的 visible 属性暂时隐藏第二个序列。

自定义轴标签

Axes.qml 重新定义了列轴的分类标签,因为数据包含月份的数字,这会使标签杂乱无章。

CategoryAxis3D {
    id: columnAxis
    labels: ["January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"]
    labelAutoRotation: 30
}

为了使轴标签在低相机角度下更易于阅读,设置自动轴标签旋转。

切换系列

main.qml 中,设置图表和各种 UI 元素。这里有三个有趣的代码块需要突出显示。第一个代码块展示了如何通过简单地更改两个序列的可见性来在收入、支出和两者之间切换可视化的数据。

onClicked: {
    if (text === "Show Expenses") {
        barSeries.visible = false;
        secondarySeries.visible = true;
        barGraph.valueAxis.labelFormat = "-%.2f M\u20AC";
        secondarySeries.itemLabelFormat = "Expenses, @colLabel, @rowLabel: @valueLabel";
        text = "Show Both";
    } else if (text === "Show Both") {
        barSeries.visible = true;
        barGraph.valueAxis.labelFormat = "%.2f M\u20AC";
        secondarySeries.itemLabelFormat = "Expenses, @colLabel, @rowLabel: -@valueLabel";
        text = "Show Income";
    } else { // text === "Show Income"
        secondarySeries.visible = false;
        text = "Show Expenses";
    }
}

调整了轴标签格式和项目选择标签格式,以便负号正确显示为支出,这实际上是解析为正值的。

第二个有趣的代码块是可视化数据通过调整代理属性来更改的地方。

onClicked: {
    if (text === "Show yearly totals") {
        modelProxy.autoRowCategories = true;
        secondaryProxy.autoRowCategories = true;
        modelProxy.columnRolePattern = /^.*$/;
        secondaryProxy.columnRolePattern = /^.*$/;
        graphAxes.value.autoAdjustRange = true;
        barGraph.columnAxis = graphAxes.total;
        text = "Show all years";
    } else if (text === "Show all years") {
        modelProxy.autoRowCategories = true;
        secondaryProxy.autoRowCategories = true;
        modelProxy.columnRolePattern = /^.*-(\d\d)$/;
        secondaryProxy.columnRolePattern = /^.*-(\d\d)$/;
        graphAxes.value.min = 0;
        graphAxes.value.max = 35;
        barGraph.columnAxis = graphAxes.column;
        text = "Show 2020 - 2022";
    } else { // text === "Show 2020 - 2022"
        // Explicitly defining row categories, since we do not want to show data for
        // all years in the model, just for the selected ones.
        modelProxy.autoRowCategories = false;
        secondaryProxy.autoRowCategories = false;
        modelProxy.rowCategories = ["2020", "2021", "2022"];
        secondaryProxy.rowCategories = ["2020", "2021", "2022"];
        text = "Show yearly totals";
    }
}

要显示年度总计,将每年的十二个月合并为一个条形图。这是通过指定一个匹配所有模型项的 columnRolePattern 来实现的。这样,数据代理就只有一个列。前面为代理指定的累积 multiMatchBehavior 现在变得相关,导致每年十二个月的所有值都加到一个条形图上。

要显示年份的子集,请在 ItemModelBarDataProxy 元素上将 autoRowCategories 设置为 false,并明确定义行类别。这样,只有指定的行类别中的项才会被可视化。

第三个有趣的代码块展示了如果已知行和列的值,如何通过使用 ItemModelBarDataProxy 方法 rowCategoryIndex()columnCategoryIndex() 来获取项目行的索引和列索引。

onCurrentRowChanged: {
    var timestamp = graphData.model.get(mainview.currentRow).timestamp;
    var pattern = /(\d\d\d\d)-(\d\d)/;
    var matches = pattern.exec(timestamp);
    var rowIndex = modelProxy.rowCategoryIndex(matches[1]);
    var colIndex;
    if (barGraph.columnAxis == graphAxes.total)
        colIndex = 0 ;// Just one column when showing yearly totals
    else
        colIndex = modelProxy.columnCategoryIndex(matches[2]);
    if (selectedSeries.visible)
        mainview.selectedSeries.selectedBar = Qt.point(rowIndex, colIndex);
    else if (barSeries.visible)
        barSeries.selectedBar = Qt.point(rowIndex, colIndex);
    else
        secondarySeries.selectedBar = Qt.point(rowIndex, colIndex);
}

示例项目 @ code.qt.io

© 2024 Qt 公司有限公司。本文档中包含的贡献的文档版权属于其各自的所有者。本文档是根据自由软件基金会发布的 GNU 自由文档许可证第 1.3 版的条款进行许可的。Qt 和相应的标志是芬兰和/或其他国家的 Qt 公司的商标。所有其他商标均为其各自所有者的财产。