// 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-energy-overlay. * * nymea-app-energy-overlay 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-energy-overlay 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-energy-overlay. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ import QtQuick import QtQuick.Layouts import QtQuick.Controls import "qrc:/ui/components" import Nymea Item { id: root property NymeaEnergy nymeaEnergy: null property Thing wallBox: null readonly property ChargingConfiguration chargingConfiguration: !nymeaEnergy.fetchingData && nymeaEnergy.chargingConfigurations.count > 0 && wallBox ? nymeaEnergy.chargingConfigurations.getChargingConfiguration(wallBox.id) : null property int hourHeight: 50 property int timeWidth: 50 property int elementRadius: hourHeight / 4 property int graphWidth: graphItem.width - timeWidth property real lineWidth: 1 property bool showPrice: true Flickable { id: flickable anchors.fill: parent contentHeight: planColumn.height interactive: contentHeight > height clip: true ScrollBar.vertical: ScrollBar {} ColumnLayout { id: planColumn anchors.horizontalCenter: parent.horizontalCenter width: flickable.width spacing: 0 Item { Layout.preferredHeight: hourHeight / 2 Layout.fillWidth: true } Item { id: graphItem Layout.fillWidth: true Layout.preferredHeight: (nymeaEnergy.schedulerPlanHoursCount) * hourHeight ColumnLayout { id: timeLabelColumn anchors.horizontalCenter: parent.horizontalCenter width: parent.width spacing: 0 Repeater { id: timeLableRepeater model: nymeaEnergy.schedulerPlanHoursCount + 1 delegate: Item { Layout.alignment: Qt.AlignLeft width: timeWidth height: hourHeight Label { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.top text: Qt.formatDateTime(nymeaEnergy.schedulerPlanCalculateDateTimeOffset(nymeaEnergy.schedulerPlanStartDateTime, model.index), "hh:mm") } } } } ColumnLayout { id: spotMarketRankingColumn anchors.horizontalCenter: parent.horizontalCenter width: parent.width spacing: 0 Repeater { id: spotMarketRankingRepeater model: nymeaEnergy.spotMarketScoreEntries delegate: Item { id: spotMarketRankingItem Layout.fillWidth: true Layout.preferredHeight: hourHeight property int elementWidth: graphWidth - timeWidth Rectangle { anchors.left: parent.left anchors.leftMargin: timeWidth * 2 anchors.verticalCenter: parent.verticalCenter height: hourHeight * 0.6 width: showPrice ? elementWidth * (1 - model.linearWeighting) : elementWidth * model.linearWeighting color: Style.rankingColor radius: elementRadius } Label { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: Style.margins text: showPrice ? (model.value / 10).toFixed(2) : model.ranking } } } } Repeater { id: chargingSchedulesRepeater model: ChargingSchedulesProxy { id: schedulesProxy chargingSchedules: nymeaEnergy.chargingSchedules evChargerFilter: wallBox ? wallBox.id : "" } delegate: Rectangle { id: chargingScheduleItem property real startPosition: nymeaEnergy.schedulerPlanCalculatePosition(model.startDateTime, graphItem.height) property real endPosition: nymeaEnergy.schedulerPlanCalculatePosition(model.endDateTime, graphItem.height) property ChargingSchedule schedule: schedulesProxy.get(index) anchors.left: parent.left anchors.leftMargin: timeWidth + Style.smallMargins y: startPosition width: timeWidth - 2 * Style.smallMargins height: endPosition - startPosition opacity: 1.0 radius: elementRadius // border.color: Style.foregroundColor // border.width: 1 color: schedule ? (schedule.chargingAction.issuer === ChargingAction.ChargingActionIssuerSpotMarketCharging ? Style.spotMarketChargingColor : Style.userScheduleColor) : "transparent" } } ColumnLayout { id: hourLinesColumn anchors.horizontalCenter: parent.horizontalCenter width: parent.width spacing: root.hourHeight - root.lineWidth Repeater { id: hourLinesRepeater model: nymeaEnergy.schedulerPlanHoursCount + 1 delegate: Rectangle { Layout.alignment: Qt.AlignRight Layout.preferredWidth: graphWidth Layout.preferredHeight: root.lineWidth color: Style.foregroundColor } } } // Keep this at the top Item { id: currentTimeLine property real strokeWidth: 15 * lineWidth property int strokeHeight: 3 * lineWidth property real linePosition: nymeaEnergy.schedulerPlanCalculateCurrentDateTimeOffset(graphItem.height) anchors.left: parent.left anchors.leftMargin: timeWidth Repeater { id: currentTimeLineRepeater model: Math.abs(Math.round(graphWidth / currentTimeLine.strokeWidth) + 1) delegate: Rectangle { x: graphWidth - (index * currentTimeLine.strokeWidth) y: currentTimeLine.linePosition - height / 2 width: currentTimeLine.strokeWidth height: currentTimeLine.strokeHeight color: Style.red radius: height / 2 opacity: index % 2 === 0 ? 0 : 1 } } Timer { id: currentTimeTimer interval: 1000 repeat: true running: true onTriggered: currentTimeLine.linePosition = nymeaEnergy.schedulerPlanCalculateCurrentDateTimeOffset(graphItem.height) } } } } Item { Layout.preferredHeight: hourHeight * 1.5 Layout.fillWidth: true } } }