This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
powersync-app/nymea-app/ui/mainviews/energy/ConsumersPieChart.qml
2025-12-05 13:40:20 +01:00

295 lines
11 KiB
QML

// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-app.
*
* nymea-app is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-app is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-app. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import QtCharts
import Nymea
import NymeaApp.Utils
import "qrc:/ui/components"
Item {
id: root
property EnergyManager energyManager: null
property ThingsProxy consumers: null
property bool animationsEnabled: true
property bool titleVisible: true
readonly property Thing rootMeter: engine.thingManager.fetchingData ? null : engine.thingManager.things.getThing(energyManager.rootMeterId)
onRootMeterChanged: updateConsumers()
Connections {
target: engine.thingManager
onFetchingDataChanged: {
if (!engine.thingManager.fetchingData) {
updateConsumers()
}
}
}
Connections {
target: root.consumers
onCountChanged: {
if (!engine.thingManager.fetchingData) {
updateConsumers()
}
}
}
Connections {
target: energyManager
onPowerBalanceChanged: {
var consumersSummation = 0
for (var i = 0; i < consumers.count; i++) {
consumersSummation += consumers.get(i).stateByName("currentPower").value
}
d.consumersSummation = consumersSummation;
if (d.unknownSlice) {
d.unknownSlice.value = Math.max(0, energyManager.currentPowerConsumption - consumersSummation)
}
}
}
Component.onCompleted: updateConsumers()
QtObject {
id: d
property var thingsColorMap: ({})
property PieSlice unknownSlice: null
property PieSlice idleSlice: null
property double consumersSummation: 0
}
function updateConsumers() {
chart.animationOptions = ChartView.NoAnimation
consumersBalanceSeries.clear();
d.unknownSlice = null
d.idleSlice = null
print("cleared consumers pie chart")
if (engine.thingManager.fetchingData) {
return;
}
var colorMap = {}
var consumersSummation = 0;
for (var i = 0; i < consumers.count; i++) {
var consumer = consumers.get(i)
let currentPowerState = consumer.stateByName("currentPower")
let slice = consumersBalanceSeries.append(consumer.name, currentPowerState.value)
slice.color = NymeaUtils.generateColor(Style.generationBaseColor, i)
slice.borderWidth = 0
slice.borderColor = slice.color
colorMap[consumer] = slice.color
currentPowerState.valueChanged.connect(function() {
slice.value = currentPowerState.value
})
consumersSummation += currentPowerState.value
}
d.consumersSummation = consumersSummation
if (root.rootMeter) {
var unknownConsumption = Math.max(0, energyManager.currentPowerConsumption - consumersSummation)
d.unknownSlice = consumersBalanceSeries.append(qsTr("Unknown"), unknownConsumption)
d.unknownSlice.color = Style.gray
d.unknownSlice.borderColor = Style.gray
d.unknownSlice.borderWidth = 0
} else {
d.idleSlice = consumersBalanceSeries.append(qsTr(""), 0.00001)
d.idleSlice.color = Style.tooltipBackgroundColor
d.idleSlice.borderColor = d.idleSlice.color
d.idleSlice.borderWidth = 0
}
d.thingsColorMap = colorMap
chart.animationOptions = Qt.binding(function() {
return root.animationsEnabled ? NymeaUtils.chartsAnimationOptions : ChartView.NoAnimation
})
}
Label {
id: titleLabel
anchors { left: parent.left; top: parent.top; right: parent.right; margins: Style.smallMargins }
horizontalAlignment: Text.AlignHCenter
text: qsTr("Consumers balance")
visible: root.titleVisible
MouseArea {
anchors.fill: parent
onClicked: {
pageStack.push(Qt.resolvedUrl("ConsumersPieChartPage.qml"), {energyManager: root.energyManager, consumers: root.consumers})
}
}
}
ChartView {
id: chart
anchors { left: parent.left; right: parent.right; bottom: parent.bottom; top: titleLabel.bottom}
backgroundColor: "transparent"
animationOptions: animationsEnabled ? NymeaUtils.chartsAnimationOptions : ChartView.NoAnimation
titleColor: Style.foregroundColor
legend.visible: false
margins.left: 0
margins.right: 0
margins.bottom: 0
margins.top: 0
PieSeries {
id: consumersBalanceSeries
size: 0.88
holeSize: 0.7
}
Flickable {
id: centerLayout
x: chart.plotArea.x + (chart.plotArea.width - width) / 2
y: chart.plotArea.y + (chart.plotArea.height - height) / 2
width: Math.min(chart.plotArea.width, chart.plotArea.width) * 0.65
height: Math.min(contentColumn.height + topMargin + bottomMargin, width)
topMargin: Style.smallIconSize
bottomMargin: Style.smallIconSize
opacity: 0
// property int maximumHeight: chart.plotArea.height * 0.65
contentHeight: contentColumn.implicitHeight
ColumnLayout {
id: contentColumn
width: parent.width
spacing: Style.smallMargins
ColumnLayout {
Layout.fillWidth: true
spacing: 0
visible: root.rootMeter
Label {
text: qsTr("Total")
font: Style.smallFont
Layout.topMargin: Style.smallMargins
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Label {
// We're using the maximum value of the energy managers consumption, the sum of all consumers because:
// * in a standard setup, the energy manager would know everything and the consumption will always be greater than the sum of all individual consumers
// * if there is a producer which is unknown to nymea though, it will decrease the consumption on the root meter so it may be smaller than the
// summation of all consumers. In this particular chart that would be nonsense so in the end we'll only lose the "unknown" power consumption in such a setup
property double finalTotal: Math.max(energyManager.currentPowerConsumption, d.consumersSummation)
text: "%1 %2"
.arg((finalTotal / (finalTotal > 1000 ? 1000 : 1)).toFixed(1))
.arg(finalTotal > 1000 ? "kW" : "W")
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
font: Style.bigFont
}
}
Repeater {
model: ThingsProxy {
id: sortedConsumers
engine: _engine
parentProxy: root.consumers
sortStateName: "currentPower"
sortOrder: Qt.DescendingOrder
}
delegate: ColumnLayout {
id: consumerDelegate
width: parent ? parent.width : 0
spacing: 0
property Thing consumer: consumers.getThing(model.id)
property State currentPowerState: consumer ? consumer.stateByName("currentPower") : null
property double value: currentPowerState ? currentPowerState.value : 0
Label {
text: model.name
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
font: Style.extraSmallFont
}
Label {
color: d.thingsColorMap.hasOwnProperty(consumer) ? d.thingsColorMap[consumer] : "transparent"
text: "%1 %2"
.arg((consumerDelegate.value / (consumerDelegate.value > 1000 ? 1000 : 1)).toFixed(1))
.arg(consumerDelegate.value > 1000 ? "kW" : "W")
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
font: Style.smallFont
}
}
}
}
}
Rectangle {
id: innerMask
anchors.fill: centerLayout
radius: width / 2
visible: false
gradient: Gradient {
GradientStop { position: 0; color: "transparent" }
GradientStop { position: 1-(centerLayout.height - downArrow.height * 1.5) / centerLayout.height; color: "red" }
GradientStop { position: (centerLayout.height - downArrow.height * 1.5) / centerLayout.height; color: "red" }
GradientStop { position: 1; color: "transparent" }
}
}
OpacityMask {
anchors.fill: centerLayout
source: centerLayout
maskSource: innerMask
}
ColorIcon {
id: upArrow
anchors { top: centerLayout.top; horizontalCenter: centerLayout.horizontalCenter }
size: Style.smallIconSize
name: "up"
visible: !centerLayout.atYBeginning
}
ColorIcon {
id: downArrow
anchors { bottom: centerLayout.bottom; horizontalCenter: centerLayout.horizontalCenter }
size: Style.smallIconSize
name: "down"
visible: !centerLayout.atYEnd
}
}
}