Improvements in the energy view
This commit is contained in:
parent
da9e5d8d7f
commit
6d049101f4
@ -183,9 +183,9 @@ void EnergyLogs::appendEntry(EnergyLogEntry *entry)
|
|||||||
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
|
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
|
||||||
m_list.append(entry);
|
m_list.append(entry);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
|
emit countChanged();
|
||||||
emit entryAdded(entry);
|
emit entryAdded(entry);
|
||||||
emit entriesAdded({entry});
|
emit entriesAdded({entry});
|
||||||
emit countChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnergyLogs::appendEntries(const QList<EnergyLogEntry *> &entries)
|
void EnergyLogs::appendEntries(const QList<EnergyLogEntry *> &entries)
|
||||||
|
|||||||
@ -81,8 +81,6 @@ QString PowerBalanceLogs::logsName() const
|
|||||||
|
|
||||||
void PowerBalanceLogs::addEntry(PowerBalanceLogEntry *entry)
|
void PowerBalanceLogs::addEntry(PowerBalanceLogEntry *entry)
|
||||||
{
|
{
|
||||||
appendEntry(entry);
|
|
||||||
|
|
||||||
if (entry->consumption() < m_minValue) {
|
if (entry->consumption() < m_minValue) {
|
||||||
m_minValue = entry->consumption();
|
m_minValue = entry->consumption();
|
||||||
emit minValueChanged();
|
emit minValueChanged();
|
||||||
@ -117,11 +115,12 @@ void PowerBalanceLogs::addEntry(PowerBalanceLogEntry *entry)
|
|||||||
emit maxValueChanged();
|
emit maxValueChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appendEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
EnergyLogEntry *PowerBalanceLogs::find(const QDateTime ×tamp) const
|
EnergyLogEntry *PowerBalanceLogs::find(const QDateTime ×tamp) const
|
||||||
{
|
{
|
||||||
// qWarning() << "Finding log entry for timestamp:" << timestamp;
|
qWarning() << "Finding log entry for timestamp:" << timestamp;
|
||||||
int oldest = 0;
|
int oldest = 0;
|
||||||
int newest = rowCount() - 1;
|
int newest = rowCount() - 1;
|
||||||
EnergyLogEntry *entry = nullptr;
|
EnergyLogEntry *entry = nullptr;
|
||||||
@ -131,7 +130,7 @@ EnergyLogEntry *PowerBalanceLogs::find(const QDateTime ×tamp) const
|
|||||||
EnergyLogEntry *newestEntry = get(newest);
|
EnergyLogEntry *newestEntry = get(newest);
|
||||||
int middle = (newest - oldest) / 2 + oldest;
|
int middle = (newest - oldest) / 2 + oldest;
|
||||||
EnergyLogEntry *middleEntry = get(middle);
|
EnergyLogEntry *middleEntry = get(middle);
|
||||||
// qWarning() << "Oldest:" << oldestEntry->timestamp().toString() << "Middle:" << middleEntry->timestamp().toString() << "Newest:" << newestEntry->timestamp().toString() << ":" << (newest - oldest);
|
qWarning() << "Oldest:" << oldestEntry->timestamp().toString() << "Middle:" << middleEntry->timestamp().toString() << "Newest:" << newestEntry->timestamp().toString() << ":" << (newest - oldest);
|
||||||
if (timestamp <= oldestEntry->timestamp()) {
|
if (timestamp <= oldestEntry->timestamp()) {
|
||||||
return oldestEntry;
|
return oldestEntry;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -180,7 +180,7 @@ void BarSeriesAdapter::logEntryAdded(LogEntry *entry)
|
|||||||
|
|
||||||
m_timeslots[slotIdx].entries.append(entry);
|
m_timeslots[slotIdx].entries.append(entry);
|
||||||
m_set->replace(slotIdx, m_timeslots[slotIdx].value());
|
m_set->replace(slotIdx, m_timeslots[slotIdx].value());
|
||||||
qDebug() << "Adding entry" << entry->timestamp() << "timestlot" << timeSlotStart << "at" << slotIdx << "value" << m_timeslots[slotIdx].value();
|
// qDebug() << "Adding entry" << entry->timestamp() << "timestlot" << timeSlotStart << "at" << slotIdx << "value" << m_timeslots[slotIdx].value();
|
||||||
|
|
||||||
// if (!m_timeslots.contains(timeSlotStart)) {
|
// if (!m_timeslots.contains(timeSlotStart)) {
|
||||||
// TimeSlot timeslot;
|
// TimeSlot timeslot;
|
||||||
|
|||||||
@ -85,8 +85,6 @@ void ThingsProxy::setParentProxy(ThingsProxy *parentProxy)
|
|||||||
if (!m_engine) {
|
if (!m_engine) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSortRole(Things::RoleName);
|
|
||||||
sort(0);
|
|
||||||
connect(m_parentProxy, SIGNAL(countChanged()), this, SIGNAL(countChanged()));
|
connect(m_parentProxy, SIGNAL(countChanged()), this, SIGNAL(countChanged()));
|
||||||
connect(m_parentProxy, &QAbstractItemModel::dataChanged, this, [this]() {
|
connect(m_parentProxy, &QAbstractItemModel::dataChanged, this, [this]() {
|
||||||
if (m_engine) {
|
if (m_engine) {
|
||||||
|
|||||||
@ -271,5 +271,6 @@
|
|||||||
<file>ui/system/PackageListPage.qml</file>
|
<file>ui/system/PackageListPage.qml</file>
|
||||||
<file>ui/mainviews/energy/StatsBase.qml</file>
|
<file>ui/mainviews/energy/StatsBase.qml</file>
|
||||||
<file>ui/mainviews/energy/EnergySettingsPage.qml</file>
|
<file>ui/mainviews/energy/EnergySettingsPage.qml</file>
|
||||||
|
<file>ui/mainviews/energy/ConsumersPieChart.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@ -95,7 +95,7 @@ MainViewBase {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: app.margins / 2
|
anchors.margins: app.margins / 2
|
||||||
contentHeight: energyGrid.childrenRect.height
|
contentHeight: energyGrid.childrenRect.height
|
||||||
visible: energyMeters.count > 0
|
visible: !engine.thingManager.fetchingData && engine.jsonRpcClient.experiences.hasOwnProperty("Energy")
|
||||||
topMargin: root.topMargin
|
topMargin: root.topMargin
|
||||||
|
|
||||||
// GridLayout directly in a flickable causes problems at initialisation
|
// GridLayout directly in a flickable causes problems at initialisation
|
||||||
@ -117,6 +117,7 @@ MainViewBase {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: width
|
||||||
energyManager: energyManager
|
energyManager: energyManager
|
||||||
|
visible: producers.count > 0
|
||||||
}
|
}
|
||||||
CurrentProductionBalancePieChart {
|
CurrentProductionBalancePieChart {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -128,6 +129,7 @@ MainViewBase {
|
|||||||
PowerConsumptionBalanceHistory {
|
PowerConsumptionBalanceHistory {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: width
|
||||||
|
visible: producers.count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
PowerProductionBalanceHistory {
|
PowerProductionBalanceHistory {
|
||||||
@ -136,7 +138,7 @@ MainViewBase {
|
|||||||
visible: producers.count > 0
|
visible: producers.count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsumersBarChart {
|
ConsumersPieChart {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: width
|
||||||
energyManager: energyManager
|
energyManager: energyManager
|
||||||
@ -144,6 +146,15 @@ MainViewBase {
|
|||||||
colors: root.thingColors
|
colors: root.thingColors
|
||||||
consumers: consumers
|
consumers: consumers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConsumersBarChart {
|
||||||
|
// Layout.fillWidth: true
|
||||||
|
// Layout.preferredHeight: width
|
||||||
|
// energyManager: energyManager
|
||||||
|
// visible: consumers.count > 0
|
||||||
|
// colors: root.thingColors
|
||||||
|
// consumers: consumers
|
||||||
|
// }
|
||||||
ConsumersHistory {
|
ConsumersHistory {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: width
|
||||||
@ -157,6 +168,7 @@ MainViewBase {
|
|||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: width
|
||||||
energyManager: energyManager
|
energyManager: energyManager
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsumerStats {
|
ConsumerStats {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: width
|
||||||
@ -172,12 +184,18 @@ MainViewBase {
|
|||||||
EmptyViewPlaceholder {
|
EmptyViewPlaceholder {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width - app.margins * 2
|
width: parent.width - app.margins * 2
|
||||||
visible: !engine.jsonRpcClient.experiences.hasOwnProperty("Energy")
|
visible: !engine.thingManager.fetchingData && !engine.jsonRpcClient.experiences.hasOwnProperty("Energy")
|
||||||
title: qsTr("Energy plugin not installed installed.")
|
title: qsTr("Energy plugin not installed installed.")
|
||||||
text: qsTr("This %1 system does not have the energy extensions installed.").arg(Configuration.systemName)
|
text: qsTr("This %1 system does not have the energy extensions installed.").arg(Configuration.systemName)
|
||||||
imageSource: "../images/smartmeter.svg"
|
imageSource: "../images/smartmeter.svg"
|
||||||
buttonText: qsTr("Install energy plugin")
|
buttonText: qsTr("Install energy plugin")
|
||||||
|
buttonVisible: packagesFilterModel.count > 0
|
||||||
onButtonClicked: pageStack.push(Qt.resolvedUrl("../system/PackageListPage.qml"), {filter: "nymea-experience-plugin-energy"})
|
onButtonClicked: pageStack.push(Qt.resolvedUrl("../system/PackageListPage.qml"), {filter: "nymea-experience-plugin-energy"})
|
||||||
|
PackagesFilterModel {
|
||||||
|
id: packagesFilterModel
|
||||||
|
packages: engine.systemController.packages
|
||||||
|
nameFilter: "nymea-experience-plugin-energy"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EmptyViewPlaceholder {
|
EmptyViewPlaceholder {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|||||||
@ -43,14 +43,12 @@ StatsBase {
|
|||||||
// print("config:", config.startTime(), config.sampleList(), config.sampleListNames())
|
// print("config:", config.startTime(), config.sampleList(), config.sampleListNames())
|
||||||
|
|
||||||
powerLogs.sampleRate = config.sampleRate
|
powerLogs.sampleRate = config.sampleRate
|
||||||
powerLogs.startTime = config.startTime()
|
powerLogs.startTime = new Date(config.startTime().getTime() - config.sampleRate * 60000)
|
||||||
powerLogs.sampleList = config.sampleList()
|
|
||||||
|
|
||||||
barSeries.clear();
|
barSeries.clear();
|
||||||
barSeries.thingBarSetMap = ({})
|
barSeries.thingBarSetMap = ({})
|
||||||
|
|
||||||
valueAxis.max = 0
|
valueAxis.max = 0
|
||||||
categoryAxis.categories = config.sampleListNames()
|
|
||||||
|
|
||||||
chartView.animationOptions = ChartView.SeriesAnimations
|
chartView.animationOptions = ChartView.SeriesAnimations
|
||||||
|
|
||||||
@ -66,45 +64,132 @@ StatsBase {
|
|||||||
|
|
||||||
onFetchingDataChanged: {
|
onFetchingDataChanged: {
|
||||||
if (!fetchingData) {
|
if (!fetchingData) {
|
||||||
barSeries.clear();
|
var config = root.configs[selectionTabs.currentValue.config]
|
||||||
for (var j = 0; j < consumers.count; j++) {
|
|
||||||
// Note: Needs to be let, not var so the lambda capture below copies it instead of capturing the reference
|
// First grouping log entries by timestamp
|
||||||
let consumer = consumers.get(j)
|
var groupedEntries = []
|
||||||
// print("ConsumerStats: Adding thing:", consumer.name)
|
var groupedEntry = {}
|
||||||
let totalEnergyConsumedState = consumer.stateByName("totalEnergyConsumed")
|
for (var i = powerLogs.count - 1; i >= 0; i--) {
|
||||||
// print("Adding consumer:", consumer.name, consumer.id)
|
var entry = powerLogs.get(i);
|
||||||
var consumptionValues = []
|
// print("grouping entry:", entry.timestamp, "current group entry", groupedEntry.timestamp, groupedEntry.hasOwnProperty("timestamp"))
|
||||||
for (var i = 0; i < sampleList.length; i++) {
|
if (!groupedEntry.hasOwnProperty("timestamp")) {
|
||||||
var start = powerLogs.find(consumer.id, new Date(sampleList[i]))
|
groupedEntry.timestamp = entry.timestamp;
|
||||||
var startValue = start !== null ? start.totalConsumption : 0
|
// print("Starting new groupentry", groupedEntry.timestamp, entry.timestamp)
|
||||||
var end = i < sampleList.length -1 ? powerLogs.find(consumer.id, new Date(sampleList[i+1])) : null
|
}
|
||||||
var endValue = end !== null ? end.totalConsumption : 0
|
if (groupedEntry.timestamp.getTime() !== entry.timestamp.getTime()) {
|
||||||
if (i == sampleList.length - 1) {
|
if (groupedEntries.length > config.count) {
|
||||||
endValue = totalEnergyConsumedState.value
|
break;
|
||||||
|
}
|
||||||
|
// print("finalizing grouped entry", groupedEntry.timestamp)
|
||||||
|
groupedEntries.unshift(groupedEntry);
|
||||||
|
groupedEntry = {
|
||||||
|
timestamp: entry.timestamp
|
||||||
|
}
|
||||||
|
// print("Starting new groupentry", groupedEntry.timestamp, entry.timestamp)
|
||||||
|
}
|
||||||
|
groupedEntry[entry.thingId] = entry.totalConsumption
|
||||||
|
}
|
||||||
|
if (groupedEntry.hasOwnProperty("timestamp") && groupedEntries.length <= config.count) {
|
||||||
|
// print("finalizing grouped entry", groupedEntry.timestamp)
|
||||||
|
groupedEntries.unshift(groupedEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
chartView.animationOptions = ChartView.NoAnimation
|
||||||
|
|
||||||
|
|
||||||
|
var labels = []
|
||||||
|
var entries = []
|
||||||
|
|
||||||
|
var newestLogTimestamp = powerLogs.count > 0 ? powerLogs.get(powerLogs.count - 1).timestamp : new Date();
|
||||||
|
|
||||||
|
for (var i = 0; i < config.count; i++) {
|
||||||
|
var groupedEntry = groupedEntries[groupedEntries.length - i - 1]
|
||||||
|
// print("have grouped entry:", groupedEntry ? groupedEntry.timestamp : "null")
|
||||||
|
|
||||||
|
// if it's the first, let's add a generated entry which shows the total from the newest log to the current live value
|
||||||
|
if (i == 0) {
|
||||||
|
var liveEntry = {}
|
||||||
|
for (var j = 0; j < consumers.count; j++) {
|
||||||
|
var consumer = consumers.get(j)
|
||||||
|
// print("Got consumer:", consumer.id, consumer.name)
|
||||||
|
var value = consumer.stateByName("totalEnergyConsumed").value;
|
||||||
|
if (groupedEntry) {
|
||||||
|
value -= groupedEntry.hasOwnProperty(consumer.id) ? groupedEntry[consumer.id] : 0
|
||||||
|
}
|
||||||
|
liveEntry[consumer.id] = value
|
||||||
|
valueAxis.adjustMax(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// print("adding sample", new Date(sampleList[i]), start ? start.timestamp : "X", " - ", end ? end.timestamp : "X")
|
// print("Adding live entry", JSON.stringify(liveEntry))
|
||||||
// print("values. start:", startValue, "end", endValue, "diff", endValue - startValue)
|
entries.unshift(liveEntry)
|
||||||
var consumptionValue = endValue - startValue
|
|
||||||
// print("Value", consumptionValue)
|
|
||||||
consumptionValues.push(consumptionValue)
|
|
||||||
valueAxis.adjustMax(consumptionValue)
|
|
||||||
}
|
}
|
||||||
let barSet = barSeries.append(consumer.name, consumptionValues)
|
|
||||||
barSet.color = root.colors[j % root.colors.length]
|
|
||||||
barSet.borderWidth = 0
|
|
||||||
barSet.borderColor = barSet.color
|
|
||||||
barSeries.thingBarSetMap[consumer] = barSet
|
|
||||||
|
|
||||||
totalEnergyConsumedState.onValueChanged.connect(function() {
|
// Add the actual entry
|
||||||
var sampleList = root.configs[selectionTabs.currentValue.config].sampleList()
|
var graphEntry = {}
|
||||||
var lastSample = sampleList[sampleList.length - 1]
|
var labelTime = new Date();
|
||||||
// print("sampleList:", powerLogs.sampleList)
|
|
||||||
var start = powerLogs.find(consumer.id, new Date(lastSample))
|
if (groupedEntry) {
|
||||||
// print("consumer value changed:", consumer.name, totalEnergyConsumedState.value, start.timestamp, start.totalConsumption)
|
var previousGroupedEntry = groupedEntries[groupedEntries.length - i - 2]
|
||||||
var barSet = barSeries.thingBarSetMap[consumer]
|
for (var j = 0; j < consumers.count; j++) {
|
||||||
barSet.replace(barSet.count - 1, totalEnergyConsumedState.value - start.totalConsumption)
|
var consumer = consumers.get(j)
|
||||||
})
|
var value = groupedEntry.hasOwnProperty(consumer.id) ? groupedEntry[consumer.id] : 0
|
||||||
|
if (previousGroupedEntry) {
|
||||||
|
var previousValue = previousGroupedEntry.hasOwnProperty(consumer.id) ? previousGroupedEntry[consumer.id] : 0
|
||||||
|
value -= previousValue
|
||||||
|
}
|
||||||
|
graphEntry[consumer.id] = value
|
||||||
|
valueAxis.adjustMax(value)
|
||||||
|
}
|
||||||
|
labelTime = groupedEntry.timestamp
|
||||||
|
} else {
|
||||||
|
for (var j = 0; j < consumers.count; j++) {
|
||||||
|
var consumer = consumers.get(j)
|
||||||
|
graphEntry[consumer.id] = 0
|
||||||
|
}
|
||||||
|
labelTime = new Date(newestLogTimestamp.getTime() - config.sampleRate * i * 60000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// print("Adding entry:", labelTime, config.toLabel(labelTime), JSON.stringify(graphEntry))
|
||||||
|
entries.unshift(graphEntry)
|
||||||
|
labels.unshift(labelTime)
|
||||||
|
|
||||||
|
// Given we've added 2 entries for the first run but only one label, we'll add the missing label
|
||||||
|
// at the end. This will shift the labels by one entries but that's ok because the logs timestamp
|
||||||
|
// is when the sample was created, but for the user it's better to show the the consumption values
|
||||||
|
// *during* that sample, not *before* the sample
|
||||||
|
if (i == config.count - 1) {
|
||||||
|
labelTime = new Date(labelTime.getTime() - config.sampleRate * 60000)
|
||||||
|
// print("Adding oldest entry label", labelTime, config.sampleRate, config.toLabel(labelTime))
|
||||||
|
labels.unshift(labelTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// print("assigning categories:", labels)
|
||||||
|
categoryAxis.timestamps = labels
|
||||||
|
|
||||||
|
var map = {}
|
||||||
|
for (var j = 0; j < consumers.count; j++) {
|
||||||
|
var consumer = consumers.get(j)
|
||||||
|
var barSet = barSeries.append(consumer.name, [])
|
||||||
|
barSet.color = root.colors[j % root.colors.length]
|
||||||
|
barSet.borderColor = barSet.color
|
||||||
|
barSet.borderWith = 0
|
||||||
|
map[consumer.id] = barSet
|
||||||
|
}
|
||||||
|
barSeries.thingBarSetMap = map
|
||||||
|
|
||||||
|
chartView.animationOptions = ChartView.SeriesAnimations
|
||||||
|
|
||||||
|
for (var i = 0; i < entries.length; i++) {
|
||||||
|
var entry = entries[i]
|
||||||
|
// print("Adding entry", JSON.stringify(entry))
|
||||||
|
for (var j = 0; j < consumers.count; j++) {
|
||||||
|
var consumer = consumers.get(j)
|
||||||
|
barSeries.thingBarSetMap[consumer.id].append(entry[consumer.id])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,7 +228,7 @@ StatsBase {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: Style.smallMargins
|
Layout.margins: Style.smallMargins
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: qsTr("Consumers statistics")
|
text: qsTr("Consumers totals")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,13 +240,14 @@ StatsBase {
|
|||||||
currentIndex: 0
|
currentIndex: 0
|
||||||
model: ListModel {
|
model: ListModel {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
append({modelData: qsTr("Months"), config: "months" })
|
|
||||||
append({modelData: qsTr("Weeks"), config: "weeks" })
|
|
||||||
append({modelData: qsTr("Days"), config: "days" })
|
|
||||||
append({modelData: qsTr("Hours"), config: "hours" })
|
append({modelData: qsTr("Hours"), config: "hours" })
|
||||||
|
append({modelData: qsTr("Days"), config: "days" })
|
||||||
|
append({modelData: qsTr("Weeks"), config: "weeks" })
|
||||||
|
append({modelData: qsTr("Months"), config: "months" })
|
||||||
|
append({modelData: qsTr("Years"), config: "years" })
|
||||||
// append({modelData: qsTr("Minutes"), config: "minutes" })
|
// append({modelData: qsTr("Minutes"), config: "minutes" })
|
||||||
|
|
||||||
selectionTabs.currentIndex = 2
|
selectionTabs.currentIndex = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onCurrentValueChanged: {
|
onCurrentValueChanged: {
|
||||||
@ -217,6 +303,16 @@ StatsBase {
|
|||||||
lineVisible: false
|
lineVisible: false
|
||||||
titleVisible: false
|
titleVisible: false
|
||||||
shadesVisible: false
|
shadesVisible: false
|
||||||
|
|
||||||
|
categories: {
|
||||||
|
var ret = []
|
||||||
|
for (var i = 0; i < timestamps.length; i++) {
|
||||||
|
ret.push(root.configs[selectionTabs.currentValue.config].toLabel(timestamps[i]))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
property var timestamps: []
|
||||||
}
|
}
|
||||||
axisY: ValueAxis {
|
axisY: ValueAxis {
|
||||||
id: valueAxis
|
id: valueAxis
|
||||||
@ -238,6 +334,78 @@ StatsBase {
|
|||||||
|
|
||||||
property var thingBarSetMap: ({})
|
property var thingBarSetMap: ({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: chartView
|
||||||
|
anchors.leftMargin: chartView.plotArea.x
|
||||||
|
anchors.topMargin: chartView.plotArea.y
|
||||||
|
anchors.rightMargin: chartView.width - chartView.plotArea.width - chartView.plotArea.x
|
||||||
|
anchors.bottomMargin: chartView.height - chartView.plotArea.height - chartView.plotArea.y
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: toolTip
|
||||||
|
property int idx: Math.floor(mouseArea.mouseX * categoryAxis.count / mouseArea.width)
|
||||||
|
visible: mouseArea.containsMouse
|
||||||
|
|
||||||
|
x: Math.min(idx * mouseArea.width / categoryAxis.count, mouseArea.width - width)
|
||||||
|
property double setMaxValue: {
|
||||||
|
var max = 0;
|
||||||
|
for (var i = 0; i < consumers.count; i++) {
|
||||||
|
var consumer = consumers.get(i)
|
||||||
|
max = barSeries.thingBarSetMap.hasOwnProperty(consumer.id) ? Math.max(max, barSeries.thingBarSetMap[consumer.id].at(idx)) : 0
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
y: Math.min(Math.max(mouseArea.height - (setMaxValue * mouseArea.height / valueAxis.max) - height - Style.smallMargins, 0), mouseArea.height - height)
|
||||||
|
|
||||||
|
width: tooltipLayout.implicitWidth + Style.smallMargins * 2
|
||||||
|
height: tooltipLayout.implicitHeight + Style.smallMargins * 2
|
||||||
|
|
||||||
|
Behavior on x { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on y { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on width { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on height { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Style.tileOverlayColor
|
||||||
|
opacity: .8
|
||||||
|
radius: Style.smallCornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: tooltipLayout
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
top: parent.top
|
||||||
|
margins: Style.smallMargins
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: categoryAxis.timestamps.length > toolTip.idx ? root.configs[selectionTabs.currentValue.config].toLongLabel(categoryAxis.timestamps[toolTip.idx]) : ""
|
||||||
|
font: Style.smallFont
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: consumers
|
||||||
|
delegate: RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: root.colors[index % root.colors.length]
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: barSeries.thingBarSetMap.hasOwnProperty(model.id) ? "%1: %2 kWh".arg(model.name).arg(barSeries.thingBarSetMap[model.id].at(toolTip.idx).toFixed(2)) : ""
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,7 +141,9 @@ ChartView {
|
|||||||
series.borderColor = series.color
|
series.borderColor = series.color
|
||||||
|
|
||||||
// print("Adding thingId series", thing.id, thing.name)
|
// print("Adding thingId series", thing.id, thing.name)
|
||||||
d.thingsSeriesMap[thing.id] = series
|
var map = d.thingsSeriesMap
|
||||||
|
map[thing.id] = series
|
||||||
|
d.thingsSeriesMap = map
|
||||||
consumerThingIds.push(thing.id)
|
consumerThingIds.push(thing.id)
|
||||||
}
|
}
|
||||||
thingPowerLogs.thingIds = consumerThingIds;
|
thingPowerLogs.thingIds = consumerThingIds;
|
||||||
@ -235,4 +237,102 @@ ChartView {
|
|||||||
consumptionUpperSeries.append(entry.timestamp.getTime(), entry.consumption)
|
consumptionUpperSeries.append(entry.timestamp.getTime(), entry.consumption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: root.plotArea.x
|
||||||
|
anchors.topMargin: root.plotArea.y
|
||||||
|
anchors.rightMargin: root.width - root.plotArea.width - root.plotArea.x
|
||||||
|
anchors.bottomMargin: root.height - root.plotArea.height - root.plotArea.y
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: parent.height
|
||||||
|
width: 1
|
||||||
|
color: Style.foregroundColor
|
||||||
|
x: mouseArea.mouseX
|
||||||
|
visible: mouseArea.containsMouse
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: toolTip
|
||||||
|
visible: mouseArea.containsMouse
|
||||||
|
|
||||||
|
property int idx: consumptionUpperSeries.count - Math.floor(mouseArea.mouseX * consumptionUpperSeries.count / mouseArea.width)
|
||||||
|
property int seriesIndex: consumptionUpperSeries.count - idx
|
||||||
|
|
||||||
|
property int xOnRight: mouseArea.mouseX + Style.smallMargins
|
||||||
|
property int xOnLeft: mouseArea.mouseX - Style.smallMargins - width
|
||||||
|
x: xOnRight + width < mouseArea.width ? xOnRight : xOnLeft
|
||||||
|
property double maxValue: consumptionUpperSeries.at(seriesIndex).y
|
||||||
|
y: Math.min(Math.max(mouseArea.height - (maxValue * mouseArea.height / valueAxis.max) - height - Style.margins, 0), mouseArea.height - height)
|
||||||
|
|
||||||
|
width: tooltipLayout.implicitWidth + Style.smallMargins * 2
|
||||||
|
height: tooltipLayout.implicitHeight + Style.smallMargins * 2
|
||||||
|
|
||||||
|
property date timestamp: new Date(consumptionUpperSeries.at(seriesIndex).x)
|
||||||
|
|
||||||
|
Behavior on x { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on y { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on width { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on height { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Style.tileOverlayColor
|
||||||
|
opacity: .8
|
||||||
|
radius: Style.smallCornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: tooltipLayout
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
top: parent.top
|
||||||
|
margins: Style.smallMargins
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: toolTip.timestamp.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||||
|
font: Style.smallFont
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: consumptionSeries.color
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
property double rawValue: consumptionUpperSeries.at(toolTip.seriesIndex).y
|
||||||
|
property double displayValue: rawValue >= 1000 ? rawValue / 1000 : rawValue
|
||||||
|
property string unit: rawValue >= 1000 ? "kW" : "W"
|
||||||
|
text: "%1: %2 %3".arg(qsTr("Total")).arg(displayValue.toFixed(2)).arg(unit)
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: consumers
|
||||||
|
delegate: RowLayout {
|
||||||
|
id: consumerToolTipDelegate
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: root.colors[index % root.colors.length]
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
property ThingPowerLogEntry entry: thingPowerLogs.find(model.id, toolTip.timestamp)
|
||||||
|
property double rawValue: entry ? entry.currentPower : 0
|
||||||
|
property double displayValue: rawValue >= 1000 ? rawValue / 1000 : rawValue
|
||||||
|
property string unit: rawValue >= 1000 ? "kW" : "W"
|
||||||
|
text: "%1: %2 %3".arg(model.name).arg(displayValue.toFixed(2)).arg(unit)
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
156
nymea-app/ui/mainviews/energy/ConsumersPieChart.qml
Normal file
156
nymea-app/ui/mainviews/energy/ConsumersPieChart.qml
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import QtQuick 2.8
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Controls.Material 2.1
|
||||||
|
import QtQuick.Layouts 1.2
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import QtCharts 2.2
|
||||||
|
import Nymea 1.0
|
||||||
|
|
||||||
|
ChartView {
|
||||||
|
id: root
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
animationOptions: ChartView.SeriesAnimations
|
||||||
|
title: qsTr("Consumers balance")
|
||||||
|
titleColor: Style.foregroundColor
|
||||||
|
legend.visible: false
|
||||||
|
|
||||||
|
property EnergyManager energyManager: null
|
||||||
|
property ThingsProxy consumers: null
|
||||||
|
property var colors: null
|
||||||
|
|
||||||
|
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 consumption = energyManager.currentPowerConsumption
|
||||||
|
for (var i = 0; i < consumers.count; i++) {
|
||||||
|
consumption -= consumers.get(i).stateByName("currentPower").value
|
||||||
|
}
|
||||||
|
d.unknownSlice.value = consumption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
property var thingsColorMap: ({})
|
||||||
|
property PieSlice unknownSlice: null
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateConsumers() {
|
||||||
|
consumersBalanceSeries.clear();
|
||||||
|
|
||||||
|
var unknownConsumption = energyManager.currentPowerConsumption
|
||||||
|
|
||||||
|
var colorMap = {}
|
||||||
|
for (var i = 0; i < consumers.count; i++) {
|
||||||
|
var consumer = consumers.get(i)
|
||||||
|
colorMap[consumer] = root.colors[i % root.colors.length]
|
||||||
|
let currentPowerState = consumer.stateByName("currentPower")
|
||||||
|
let slice = consumersBalanceSeries.append(consumer.name, currentPowerState.value)
|
||||||
|
slice.color = root.colors[i % root.colors.length]
|
||||||
|
currentPowerState.valueChanged.connect(function() {
|
||||||
|
slice.value = currentPowerState.value
|
||||||
|
})
|
||||||
|
unknownConsumption -= currentPowerState.value
|
||||||
|
}
|
||||||
|
|
||||||
|
d.unknownSlice = consumersBalanceSeries.append(qsTr("Unknown"), unknownConsumption)
|
||||||
|
d.unknownSlice.color = Style.gray
|
||||||
|
|
||||||
|
d.thingsColorMap = colorMap
|
||||||
|
}
|
||||||
|
|
||||||
|
PieSeries {
|
||||||
|
id: consumersBalanceSeries
|
||||||
|
size: 0.9
|
||||||
|
holeSize: 0.7
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: centerLayout
|
||||||
|
x: root.plotArea.x + (root.plotArea.width - width) / 2
|
||||||
|
y: root.plotArea.y + (root.plotArea.height - height) / 2
|
||||||
|
width: root.plotArea.width * 0.65
|
||||||
|
// height: root.plotArea.height * 0.65
|
||||||
|
spacing: Style.smallMargins
|
||||||
|
property int maximumHeight: root.plotArea.height * 0.65
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
Label {
|
||||||
|
text: qsTr("Total")
|
||||||
|
font: Style.smallFont
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "%1 %2"
|
||||||
|
.arg((energyManager.currentPowerConsumption / (energyManager.currentPowerConsumption > 1000 ? 1000 : 1)).toFixed(1))
|
||||||
|
.arg(energyManager.currentPowerConsumption > 1000 ? "kW" : "W")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font: Style.bigFont
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: count * (Style.smallMargins + Style.extraSmallFont.pixelSize + Style.smallFont.pixelSize)
|
||||||
|
Layout.maximumHeight: centerLayout.maximumHeight - y
|
||||||
|
clip: true
|
||||||
|
spacing: Style.smallMargins
|
||||||
|
|
||||||
|
model: ThingsProxy {
|
||||||
|
id: sortedConsumers
|
||||||
|
engine: _engine
|
||||||
|
parentProxy: root.consumers
|
||||||
|
sortStateName: "currentPower"
|
||||||
|
sortOrder: Qt.DescendingOrder
|
||||||
|
}
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
Label {
|
||||||
|
text: model.name
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
property Thing consumer: sortedConsumers.get(index)
|
||||||
|
property State currentPowerState: consumer ? consumer.stateByName("currentPower") : null
|
||||||
|
property double value: currentPowerState ? currentPowerState.value : 0
|
||||||
|
|
||||||
|
color: d.thingsColorMap[consumer]
|
||||||
|
text: "%1 %2"
|
||||||
|
.arg((value / (value > 1000 ? 1000 : 1)).toFixed(1))
|
||||||
|
.arg(value > 1000 ? "kWh" : "W")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font: Style.smallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,7 @@ ChartView {
|
|||||||
id: consumptionPieChart
|
id: consumptionPieChart
|
||||||
backgroundColor: "transparent"
|
backgroundColor: "transparent"
|
||||||
animationOptions: ChartView.SeriesAnimations
|
animationOptions: ChartView.SeriesAnimations
|
||||||
title: qsTr("Current power consumption balance")
|
title: qsTr("My energy mix")
|
||||||
titleColor: Style.foregroundColor
|
titleColor: Style.foregroundColor
|
||||||
legend.visible: false
|
legend.visible: false
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ ChartView {
|
|||||||
id: productionPieChart
|
id: productionPieChart
|
||||||
backgroundColor: "transparent"
|
backgroundColor: "transparent"
|
||||||
animationOptions: ChartView.SeriesAnimations
|
animationOptions: ChartView.SeriesAnimations
|
||||||
title: qsTr("Current power production balance")
|
title: qsTr("My energy production")
|
||||||
titleColor: Style.foregroundColor
|
titleColor: Style.foregroundColor
|
||||||
legend.visible: false
|
legend.visible: false
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ StatsBase {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: Style.smallMargins
|
Layout.margins: Style.smallMargins
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: qsTr("Energy consumption statistics")
|
text: qsTr("Totals")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,54 +36,56 @@ StatsBase {
|
|||||||
Layout.rightMargin: Style.smallMargins
|
Layout.rightMargin: Style.smallMargins
|
||||||
model: ListModel {
|
model: ListModel {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
append({modelData: qsTr("Months"), config: "months" })
|
|
||||||
append({modelData: qsTr("Weeks"), config: "weeks" })
|
|
||||||
append({modelData: qsTr("Days"), config: "days" })
|
|
||||||
append({modelData: qsTr("Hours"), config: "hours" })
|
append({modelData: qsTr("Hours"), config: "hours" })
|
||||||
|
append({modelData: qsTr("Days"), config: "days" })
|
||||||
|
append({modelData: qsTr("Weeks"), config: "weeks" })
|
||||||
|
append({modelData: qsTr("Months"), config: "months" })
|
||||||
|
append({modelData: qsTr("Years"), config: "years" })
|
||||||
// append({modelData: qsTr("Minutes"), config: "minutes" })
|
// append({modelData: qsTr("Minutes"), config: "minutes" })
|
||||||
|
|
||||||
selectionTabs.currentIndex = 2
|
selectionTabs.currentIndex = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onCurrentValueChanged: {
|
onCurrentValueChanged: {
|
||||||
var config = root.configs[currentValue.config]
|
var config = root.configs[currentValue.config]
|
||||||
print("config:", config.startTime(), config.sampleList(), config.sampleListNames())
|
print("config:", config.startTime(), config.sampleRate)
|
||||||
|
|
||||||
powerBalanceLogs.loadingInhibited = true
|
powerBalanceLogs.loadingInhibited = true
|
||||||
powerBalanceLogs.sampleRate = config.sampleRate
|
powerBalanceLogs.sampleRate = config.sampleRate
|
||||||
powerBalanceLogs.startTime = config.startTime()
|
powerBalanceLogs.startTime = new Date(config.startTime().getTime() - config.sampleRate * 60000)
|
||||||
powerBalanceLogs.sampleList = config.sampleList()
|
|
||||||
powerBalanceLogs.loadingInhibited = false
|
powerBalanceLogs.loadingInhibited = false
|
||||||
|
|
||||||
barSeries.clear();
|
barSeries.clear();
|
||||||
|
|
||||||
d.consumptionSet = barSeries.append(qsTr("Consumed"), [])
|
d.consumptionSet = barSeries.append(qsTr("Consumed"), [])
|
||||||
d.consumptionSet.color = Style.blue
|
d.consumptionSet.color = Style.blue
|
||||||
|
d.consumptionSet.borderColor = d.consumptionSet.color
|
||||||
d.consumptionSet.borderWidth = 0
|
d.consumptionSet.borderWidth = 0
|
||||||
d.productionSet = barSeries.append(qsTr("Produced"), [])
|
d.productionSet = barSeries.append(qsTr("Produced"), [])
|
||||||
d.productionSet.color = Style.green
|
d.productionSet.color = Style.green
|
||||||
|
d.productionSet.borderColor = d.productionSet.color
|
||||||
d.productionSet.borderWidth = 0
|
d.productionSet.borderWidth = 0
|
||||||
d.acquisitionSet = barSeries.append(qsTr("From grid"), [])
|
d.acquisitionSet = barSeries.append(qsTr("From grid"), [])
|
||||||
d.acquisitionSet.color = Style.red
|
d.acquisitionSet.color = Style.red
|
||||||
|
d.acquisitionSet.borderColor = d.acquisitionSet.color
|
||||||
d.acquisitionSet.borderWidth = 0
|
d.acquisitionSet.borderWidth = 0
|
||||||
d.returnSet = barSeries.append(qsTr("To grid"), [])
|
d.returnSet = barSeries.append(qsTr("To grid"), [])
|
||||||
d.returnSet.color = Style.orange
|
d.returnSet.color = Style.orange
|
||||||
|
d.returnSet.borderColor = d.returnSet.color
|
||||||
d.returnSet.borderWidth = 0
|
d.returnSet.borderWidth = 0
|
||||||
|
|
||||||
|
|
||||||
valueAxis.max = 0
|
valueAxis.max = 0
|
||||||
categoryAxis.categories = config.sampleListNames()
|
|
||||||
|
|
||||||
chartView.animationOptions = ChartView.SeriesAnimations
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: energyManager
|
target: energyManager
|
||||||
onPowerBalanceChanged: {
|
onPowerBalanceChanged: {
|
||||||
var start = powerBalanceLogs.get(powerBalanceLogs.count - 1)
|
var start = powerBalanceLogs.get(powerBalanceLogs.count - 1 )
|
||||||
// print("balance changed:", d.consumptionSet, powerBalanceLogs, powerBalanceLogs.count)
|
// print("balance changed:", d.consumptionSet, powerBalanceLogs, powerBalanceLogs.count)
|
||||||
// print("updating", start.timestamp, root.energyManager.totalConsumption - (start ? start.totalConsumption : 0))
|
// print("updating", start.timestamp, start.totalConsumption, root.energyManager.totalConsumption, root.energyManager.totalConsumption - (start ? start.totalConsumption : 0))
|
||||||
d.consumptionSet.replace(d.consumptionSet.count - 1, root.energyManager.totalConsumption - (start ? start.totalConsumption : 0))
|
d.consumptionSet.replace(d.consumptionSet.count - 1, root.energyManager.totalConsumption - (start ? start.totalConsumption : 0))
|
||||||
d.productionSet.replace(d.productionSet.count - 1, root.energyManager.totalProduction - (start ? start.totalProduction : 0))
|
d.productionSet.replace(d.productionSet.count - 1, root.energyManager.totalProduction - (start ? start.totalProduction : 0))
|
||||||
d.acquisitionSet.replace(d.acquisitionSet.count - 1, root.energyManager.totalAcquisition - (start ? start.totalAcquisition : 0))
|
d.acquisitionSet.replace(d.acquisitionSet.count - 1, root.energyManager.totalAcquisition - (start ? start.totalAcquisition : 0))
|
||||||
@ -96,48 +98,102 @@ StatsBase {
|
|||||||
engine: _engine
|
engine: _engine
|
||||||
loadingInhibited: true
|
loadingInhibited: true
|
||||||
|
|
||||||
property var sampleList: minutesList
|
|
||||||
|
|
||||||
onFetchingDataChanged: {
|
onFetchingDataChanged: {
|
||||||
if (!fetchingData) {
|
if (!fetchingData) {
|
||||||
if (powerBalanceLogs.count == 0) {
|
chartView.animationOptions = ChartView.NoAnimation
|
||||||
valueAxis.adjustMax(root.energyManager.totalConsumption)
|
|
||||||
valueAxis.adjustMax(root.energyManager.totalAcquisition)
|
|
||||||
valueAxis.adjustMax(root.energyManager.totalProduction)
|
|
||||||
valueAxis.adjustMax(root.energyManager.totalReturn)
|
|
||||||
|
|
||||||
for (var i = 0; i < sampleList.length; i++) {
|
print("Logs fetched")
|
||||||
d.consumptionSet.append(i == sampleList.length - 1 ? root.energyManager.totalConsumption : 0)
|
var config = root.configs[selectionTabs.currentValue.config]
|
||||||
d.productionSet.append(i == sampleList.length - 1 ? root.energyManager.totalProduction : 0)
|
|
||||||
d.acquisitionSet.append(i == sampleList.length - 1 ? root.energyManager.totalAcquisition : 0)
|
var labels = []
|
||||||
d.returnSet.append(i == sampleList.length - 1 ? root.energyManager.totalReturn : 0)
|
var entries = []
|
||||||
|
|
||||||
|
var newestLogTimestamp = powerBalanceLogs.count > 0 ? powerBalanceLogs.get(powerBalanceLogs.count - 1).timestamp : new Date();
|
||||||
|
for (var i = 0; i < config.count; i++) {
|
||||||
|
var entry = powerBalanceLogs.get(powerBalanceLogs.count - i - 1)
|
||||||
|
|
||||||
|
// if it's the first, let's add a generated entry which shows the total from the newest log to the current live value
|
||||||
|
if (i == 0) {
|
||||||
|
var liveEntry = {
|
||||||
|
consumption: energyManager.totalConsumption,
|
||||||
|
production: energyManager.totalProduction,
|
||||||
|
acquisition: energyManager.totalAcquisition,
|
||||||
|
returned: energyManager.totalReturn
|
||||||
|
}
|
||||||
|
if (entry) {
|
||||||
|
liveEntry.consumption -= entry.totalConsumption
|
||||||
|
liveEntry.production -= entry.totalProduction
|
||||||
|
liveEntry.acquisition -= entry.totalAcquisition
|
||||||
|
liveEntry.returned -= entry.totalReturn
|
||||||
|
}
|
||||||
|
print("Adding live entry:", liveEntry.consumption, root.energyManager.totalConsumption, entry ? entry.totalConsumption : 0)
|
||||||
|
entries.unshift(liveEntry)
|
||||||
|
valueAxis.adjustMax(liveEntry.consumption)
|
||||||
|
valueAxis.adjustMax(liveEntry.production)
|
||||||
|
valueAxis.adjustMax(liveEntry.acquisition)
|
||||||
|
valueAxis.adjustMax(liveEntry.returned)
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
|
// Add the actual entry
|
||||||
|
var graphEntry = {
|
||||||
|
consumption: 0,
|
||||||
|
production: 0,
|
||||||
|
acquisition: 0,
|
||||||
|
returned: 0
|
||||||
|
}
|
||||||
|
var labelTime = new Date();
|
||||||
|
if (entry) {
|
||||||
|
// print("Have entry:", entry.timestamp, config.toLabel(entry.timestamp))
|
||||||
|
var previous = powerBalanceLogs.get(powerBalanceLogs.count - i - 2)
|
||||||
|
if (previous) {
|
||||||
|
graphEntry.consumption = entry.totalConsumption - previous.totalConsumption
|
||||||
|
graphEntry.production = entry.totalProduction - previous.totalProduction
|
||||||
|
graphEntry.acquisition = entry.totalAcquisition - previous.totalAcquisition
|
||||||
|
graphEntry.returned = entry.totalReturn - previous.totalReturn
|
||||||
|
} else {
|
||||||
|
graphEntry.consumption = entry.totalConsumption
|
||||||
|
graphEntry.production = entry.totalProduction
|
||||||
|
graphEntry.acquisition = entry.totalAcquisition
|
||||||
|
graphEntry.returned = entry.totalReturn
|
||||||
|
}
|
||||||
|
labelTime = entry.timestamp
|
||||||
|
} else {
|
||||||
|
labelTime = new Date(newestLogTimestamp.getTime() - config.sampleRate * i * 60000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// print("Adding entry:", labelTime, graphEntry.consumption, config.toLabel(labelTime))
|
||||||
|
entries.unshift(graphEntry)
|
||||||
|
labels.unshift(labelTime)
|
||||||
|
|
||||||
|
// Given we've added 2 entries for the first run but only one label, we'll add the missing label
|
||||||
|
// at the end. This will shift the labels by one entries but that's ok because the logs timestamp
|
||||||
|
// is when the sample was created, but for the user it's better to show the the consumption values
|
||||||
|
// *during* that sample, not *before* the sample
|
||||||
|
if (i == config.count - 1) {
|
||||||
|
labelTime = new Date(labelTime.getTime() - config.sampleRate * 60000)
|
||||||
|
// print("Adding oldest entry label", labelTime, config.sampleRate, config.toLabel(labelTime))
|
||||||
|
labels.unshift(labelTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
valueAxis.adjustMax(graphEntry.consumption)
|
||||||
|
valueAxis.adjustMax(graphEntry.production)
|
||||||
|
valueAxis.adjustMax(graphEntry.acquisition)
|
||||||
|
valueAxis.adjustMax(graphEntry.returned)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < sampleList.length; i++) {
|
// print("assigning categories:", labels)
|
||||||
var start = powerBalanceLogs.find(new Date(sampleList[i]))
|
categoryAxis.timestamps = labels
|
||||||
var end = null;
|
|
||||||
if (i+1 < sampleList.length) {
|
|
||||||
end = powerBalanceLogs.find(new Date(sampleList[i+1]))
|
|
||||||
}
|
|
||||||
// print("** stats for:", new Date(sampleList[i]), /*start, end, */"start:", start ? start.totalConsumption : 0, "end:", end ? end.totalConsumption : root.energyManager.totalConsumption)
|
|
||||||
var consumptionValue = (end != null ? end.totalConsumption : root.energyManager.totalConsumption) - (start ? start.totalConsumption : 0)
|
|
||||||
var productionValue = (end != null ? end.totalProduction : root.energyManager.totalProduction) - (start ? start.totalProduction : 0)
|
|
||||||
var acquisitionValue = (end != null ? end.totalAcquisition : root.energyManager.totalAcquisition) - (start ? start.totalAcquisition : 0)
|
|
||||||
var returnValue = (end != null ? end.totalReturn : root.energyManager.totalReturn) - (start ? start.totalReturn : 0)
|
|
||||||
|
|
||||||
valueAxis.adjustMax(consumptionValue)
|
chartView.animationOptions = ChartView.SeriesAnimations
|
||||||
valueAxis.adjustMax(productionValue)
|
|
||||||
valueAxis.adjustMax(acquisitionValue)
|
|
||||||
valueAxis.adjustMax(returnValue)
|
|
||||||
|
|
||||||
d.consumptionSet.append(consumptionValue)
|
|
||||||
d.productionSet.append(productionValue)
|
|
||||||
d.acquisitionSet.append(acquisitionValue)
|
|
||||||
d.returnSet.append(returnValue)
|
|
||||||
|
|
||||||
|
for (var i = 0; i < entries.length; i++) {
|
||||||
|
// print("Appending to set", JSON.stringify(entries[i]))
|
||||||
|
d.consumptionSet.append(entries[i].consumption)
|
||||||
|
d.productionSet.append(entries[i].production)
|
||||||
|
d.acquisitionSet.append(entries[i].acquisition)
|
||||||
|
d.returnSet.append(entries[i].returned)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,14 +202,23 @@ StatsBase {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var config = root.configs[selectionTabs.currentValue.config]
|
||||||
|
|
||||||
|
|
||||||
var start = entry
|
var start = entry
|
||||||
var consumptionValue = root.energyManager.totalConsumption - (start ? start.totalConsumption : 0)
|
var consumptionValue = root.energyManager.totalConsumption - (start ? start.totalConsumption : 0)
|
||||||
var productionValue = root.energyManager.totalProduction - (start ? start.totalProduction : 0)
|
var productionValue = root.energyManager.totalProduction - (start ? start.totalProduction : 0)
|
||||||
var acquisitionValue = root.energyManager.totalAcquisition - (start ? start.totalAcquisition : 0)
|
var acquisitionValue = root.energyManager.totalAcquisition - (start ? start.totalAcquisition : 0)
|
||||||
var returnValue = root.energyManager.totalReturn - (start ? start.totalReturn : 0)
|
var returnValue = root.energyManager.totalReturn - (start ? start.totalReturn : 0)
|
||||||
|
// print("Entry added:", entry.timestamp, entry.totalConsumption, consumptionValue)
|
||||||
|
|
||||||
chartView.animationOptions = ChartView.NoAnimation
|
chartView.animationOptions = ChartView.NoAnimation
|
||||||
categoryAxis.categories = configs[selectionTabs.currentValue.config].sampleListNames()
|
|
||||||
|
var timestamps = categoryAxis.timestamps;
|
||||||
|
timestamps.splice(0, 1)
|
||||||
|
timestamps.push(entry.timestamp)
|
||||||
|
categoryAxis.timestamps = timestamps
|
||||||
|
|
||||||
d.consumptionSet.append(consumptionValue)
|
d.consumptionSet.append(consumptionValue)
|
||||||
d.productionSet.append(productionValue)
|
d.productionSet.append(productionValue)
|
||||||
d.acquisitionSet.append(acquisitionValue)
|
d.acquisitionSet.append(acquisitionValue)
|
||||||
@ -171,7 +236,7 @@ StatsBase {
|
|||||||
id: chartView
|
id: chartView
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
animationOptions: ChartView.NoAnimations
|
animationOptions: ChartView.NoAnimation
|
||||||
|
|
||||||
backgroundColor: "transparent"
|
backgroundColor: "transparent"
|
||||||
legend.alignment: Qt.AlignBottom
|
legend.alignment: Qt.AlignBottom
|
||||||
@ -214,6 +279,17 @@ StatsBase {
|
|||||||
lineVisible: false
|
lineVisible: false
|
||||||
titleVisible: false
|
titleVisible: false
|
||||||
shadesVisible: false
|
shadesVisible: false
|
||||||
|
|
||||||
|
categories: {
|
||||||
|
var ret = []
|
||||||
|
for (var i = 0; i < timestamps.length; i++) {
|
||||||
|
ret.push(root.configs[selectionTabs.currentValue.config].toLabel(timestamps[i]))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
property var timestamps: []
|
||||||
|
|
||||||
}
|
}
|
||||||
axisY: ValueAxis {
|
axisY: ValueAxis {
|
||||||
id: valueAxis
|
id: valueAxis
|
||||||
@ -233,6 +309,106 @@ StatsBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: chartView
|
||||||
|
anchors.leftMargin: chartView.plotArea.x
|
||||||
|
anchors.topMargin: chartView.plotArea.y
|
||||||
|
anchors.rightMargin: chartView.width - chartView.plotArea.width - chartView.plotArea.x
|
||||||
|
anchors.bottomMargin: chartView.height - chartView.plotArea.height - chartView.plotArea.y
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: toolTip
|
||||||
|
property int idx: Math.floor(mouseArea.mouseX * categoryAxis.count / mouseArea.width)
|
||||||
|
visible: mouseArea.containsMouse
|
||||||
|
|
||||||
|
x: Math.min(idx * mouseArea.width / categoryAxis.count, mouseArea.width - width)
|
||||||
|
property double setMaxValue: d.consumptionSet && d.productionSet && d.acquisitionSet && d.returnSet ?
|
||||||
|
Math.max(d.consumptionSet.at(idx), Math.max(d.productionSet.at(idx), Math.max(d.acquisitionSet.at(idx), d.returnSet.at(idx))))
|
||||||
|
: 0
|
||||||
|
y: Math.min(Math.max(mouseArea.height - (setMaxValue * mouseArea.height / valueAxis.max) - height - Style.smallMargins, 0), mouseArea.height - height)
|
||||||
|
width: tooltipLayout.implicitWidth + Style.smallMargins * 2
|
||||||
|
height: tooltipLayout.implicitHeight + Style.smallMargins * 2
|
||||||
|
|
||||||
|
Behavior on x { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on y { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on width { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on height { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Style.tileOverlayColor
|
||||||
|
opacity: .8
|
||||||
|
radius: Style.smallCornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: tooltipLayout
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
top: parent.top
|
||||||
|
margins: Style.smallMargins
|
||||||
|
}
|
||||||
|
// Label {
|
||||||
|
// text: powerBalanceLogs.count + ":" + categoryAxis.count + ":" + toolTip.idx
|
||||||
|
// }
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: categoryAxis.timestamps.length > toolTip.idx ? root.configs[selectionTabs.currentValue.config].toLongLabel(categoryAxis.timestamps[toolTip.idx]) : ""
|
||||||
|
font: Style.smallFont
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.blue
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: d.consumptionSet ? qsTr("Consumed: %1 kWh").arg(d.consumptionSet.at(toolTip.idx).toFixed(2)) : ""
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.green
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: d.productionSet ? qsTr("Produced: %1 kWh").arg(d.productionSet.at(toolTip.idx).toFixed(2)) : ""
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.red
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: d.acquisitionSet ? qsTr("From grid: %1 kWh").arg(d.acquisitionSet.at(toolTip.idx).toFixed(2)) : ""
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.orange
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: d.returnSet ? qsTr("To grid: %1 kWh").arg(d.returnSet.at(toolTip.idx).toFixed(2)) : ""
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ ChartView {
|
|||||||
margins.bottom: 0
|
margins.bottom: 0
|
||||||
margins.top: 0
|
margins.top: 0
|
||||||
|
|
||||||
title: qsTr("Power consumption balance history")
|
title: qsTr("My consumption history")
|
||||||
titleColor: Style.foregroundColor
|
titleColor: Style.foregroundColor
|
||||||
|
|
||||||
legend.alignment: Qt.AlignBottom
|
legend.alignment: Qt.AlignBottom
|
||||||
@ -229,4 +229,101 @@ ChartView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: root.plotArea.x
|
||||||
|
anchors.topMargin: root.plotArea.y
|
||||||
|
anchors.rightMargin: root.width - root.plotArea.width - root.plotArea.x
|
||||||
|
anchors.bottomMargin: root.height - root.plotArea.height - root.plotArea.y
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: parent.height
|
||||||
|
width: 1
|
||||||
|
color: Style.foregroundColor
|
||||||
|
x: mouseArea.mouseX
|
||||||
|
visible: mouseArea.containsMouse
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: toolTip
|
||||||
|
visible: mouseArea.containsMouse
|
||||||
|
|
||||||
|
property int idx: consumptionUpperSeries.count - (Math.floor(mouseArea.mouseX * consumptionUpperSeries.count / mouseArea.width))
|
||||||
|
property int seriesIndex: consumptionUpperSeries.count - idx
|
||||||
|
|
||||||
|
property int xOnRight: mouseArea.mouseX + Style.smallMargins
|
||||||
|
property int xOnLeft: mouseArea.mouseX - Style.smallMargins - width
|
||||||
|
x: xOnRight + width < mouseArea.width ? xOnRight : xOnLeft
|
||||||
|
property double maxValue: consumptionUpperSeries.at(seriesIndex).y
|
||||||
|
y: Math.min(Math.max(mouseArea.height - (maxValue * mouseArea.height / valueAxis.max) - height - Style.margins, 0), mouseArea.height - height)
|
||||||
|
|
||||||
|
width: tooltipLayout.implicitWidth + Style.smallMargins * 2
|
||||||
|
height: tooltipLayout.implicitHeight + Style.smallMargins * 2
|
||||||
|
|
||||||
|
Behavior on x { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on y { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on width { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on height { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Style.tileOverlayColor
|
||||||
|
opacity: .8
|
||||||
|
radius: Style.smallCornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: tooltipLayout
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
top: parent.top
|
||||||
|
margins: Style.smallMargins
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: new Date(consumptionUpperSeries.at(toolTip.seriesIndex).x).toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||||
|
font: Style.smallFont
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.green
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Self production: %1 kW").arg(selfProductionUpperSeries.at(toolTip.seriesIndex).y.toFixed(2))
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.orange
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("From battery: %1 kW").arg(storageUpperSeries.at(toolTip.seriesIndex).y.toFixed(2))
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.red
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("From grid: %1 kW").arg(acquisitionUpperSeries.at(toolTip.seriesIndex).y.toFixed(2))
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ ChartView {
|
|||||||
margins.bottom: 0
|
margins.bottom: 0
|
||||||
margins.top: 0
|
margins.top: 0
|
||||||
|
|
||||||
title: qsTr("Power production balance history")
|
title: qsTr("My production history")
|
||||||
titleColor: Style.foregroundColor
|
titleColor: Style.foregroundColor
|
||||||
|
|
||||||
legend.alignment: Qt.AlignBottom
|
legend.alignment: Qt.AlignBottom
|
||||||
@ -222,4 +222,101 @@ ChartView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: root.plotArea.x
|
||||||
|
anchors.topMargin: root.plotArea.y
|
||||||
|
anchors.rightMargin: root.width - root.plotArea.width - root.plotArea.x
|
||||||
|
anchors.bottomMargin: root.height - root.plotArea.height - root.plotArea.y
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: parent.height
|
||||||
|
width: 1
|
||||||
|
color: Style.foregroundColor
|
||||||
|
x: mouseArea.mouseX
|
||||||
|
visible: mouseArea.containsMouse
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: toolTip
|
||||||
|
visible: mouseArea.containsMouse
|
||||||
|
|
||||||
|
property int idx: productionUpperSeries.count - Math.floor(mouseArea.mouseX * productionUpperSeries.count / mouseArea.width)
|
||||||
|
property int seriesIndex: productionUpperSeries.count - idx
|
||||||
|
|
||||||
|
property int xOnRight: mouseArea.mouseX + Style.smallMargins
|
||||||
|
property int xOnLeft: mouseArea.mouseX - Style.smallMargins - width
|
||||||
|
x: xOnRight + width < mouseArea.width ? xOnRight : xOnLeft
|
||||||
|
property double maxValue: productionUpperSeries.at(seriesIndex).y
|
||||||
|
y: Math.min(Math.max(mouseArea.height - (maxValue * mouseArea.height / valueAxis.max) - height - Style.margins, 0), mouseArea.height - height)
|
||||||
|
|
||||||
|
width: tooltipLayout.implicitWidth + Style.smallMargins * 2
|
||||||
|
height: tooltipLayout.implicitHeight + Style.smallMargins * 2
|
||||||
|
|
||||||
|
Behavior on x { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on y { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on width { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
Behavior on height { NumberAnimation { duration: Style.animationDuration } }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Style.tileOverlayColor
|
||||||
|
opacity: .8
|
||||||
|
radius: Style.smallCornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: tooltipLayout
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
top: parent.top
|
||||||
|
margins: Style.smallMargins
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: new Date(selfConsumptionUpperSeries.at(toolTip.seriesIndex).x).toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||||
|
font: Style.smallFont
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.green
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("consumed: %1 kW").arg(selfConsumptionUpperSeries.at(toolTip.seriesIndex).y.toFixed(2))
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.orange
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("To battery: %1 kW").arg(storageUpperSeries.at(toolTip.seriesIndex).y.toFixed(2))
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Rectangle {
|
||||||
|
width: Style.extraSmallFont.pixelSize
|
||||||
|
height: width
|
||||||
|
color: Style.red
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("To grid: %1 kW").arg(acquisitionUpperSeries.at(toolTip.seriesIndex).y.toFixed(2))
|
||||||
|
font: Style.extraSmallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,42 +4,55 @@ import Nymea 1.0
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int minutesCount: 10
|
property int minutesCount: 9
|
||||||
property int hoursCount: 12
|
property int hoursCount: 11
|
||||||
property int daysCount: 7
|
property int daysCount: 6
|
||||||
property int weeksCount: 12
|
property int weeksCount: 12
|
||||||
property int monthsCount: 12
|
property int monthsCount: 11
|
||||||
|
property int yearsCount: 5
|
||||||
|
|
||||||
property var configs: ({
|
property var configs: ({
|
||||||
minutes: {
|
minutes: {
|
||||||
|
count: minutesCount,
|
||||||
startTime: minutesStart,
|
startTime: minutesStart,
|
||||||
sampleRate: EnergyLogs.SampleRate1Min,
|
sampleRate: EnergyLogs.SampleRate1Min,
|
||||||
sampleList: minutesList,
|
toLabel: minuteLabel,
|
||||||
sampleListNames: minutesListNames
|
toLongLabel: minuteLongLabel
|
||||||
},
|
},
|
||||||
hours: {
|
hours: {
|
||||||
|
count: hoursCount,
|
||||||
startTime: hoursStart,
|
startTime: hoursStart,
|
||||||
sampleRate: EnergyLogs.SampleRate1Hour,
|
sampleRate: EnergyLogs.SampleRate1Hour,
|
||||||
sampleList: hoursList,
|
toLabel: hourLabel,
|
||||||
sampleListNames: hoursListNames
|
toLongLabel: hourLongLabel
|
||||||
},
|
},
|
||||||
days: {
|
days: {
|
||||||
|
count: daysCount,
|
||||||
startTime: daysStart,
|
startTime: daysStart,
|
||||||
sampleRate: EnergyLogs.SampleRate1Day,
|
sampleRate: EnergyLogs.SampleRate1Day,
|
||||||
sampleList: daysList,
|
toLabel: dayLabel,
|
||||||
sampleListNames: daysListNames
|
toLongLabel: dayLongLabel
|
||||||
},
|
},
|
||||||
weeks: {
|
weeks: {
|
||||||
|
count: weeksCount,
|
||||||
startTime: weeksStart,
|
startTime: weeksStart,
|
||||||
sampleRate: EnergyLogs.SampleRate1Week,
|
sampleRate: EnergyLogs.SampleRate1Week,
|
||||||
sampleList: weeksList,
|
toLabel: weekLabel,
|
||||||
sampleListNames: weeksListNames
|
toLongLabel: weekLongLabel
|
||||||
},
|
},
|
||||||
months: {
|
months: {
|
||||||
|
count: monthsCount,
|
||||||
startTime: monthsStart,
|
startTime: monthsStart,
|
||||||
sampleRate: EnergyLogs.SampleRate1Month,
|
sampleRate: EnergyLogs.SampleRate1Month,
|
||||||
sampleList: monthsList,
|
toLabel: monthLabel,
|
||||||
sampleListNames: monthsListNames
|
toLongLabel: monthLongLabel
|
||||||
|
},
|
||||||
|
years: {
|
||||||
|
count: yearsCount,
|
||||||
|
startTime: yearStart,
|
||||||
|
sampleRate: EnergyLogs.SampleRate1Year,
|
||||||
|
toLabel: yearLabel,
|
||||||
|
toLongLabel: yearLabel
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -48,45 +61,24 @@ Item {
|
|||||||
d.setMinutes(d.getMinutes() - minutesCount + 1, 0, 0)
|
d.setMinutes(d.getMinutes() - minutesCount + 1, 0, 0)
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
function minutesList() {
|
function minuteLabel(date) {
|
||||||
var ret = []
|
return date.toLocaleString(Qt.locale(), "hh:mm")
|
||||||
var startTime = minutesStart();
|
|
||||||
for (var i = 0; i < minutesCount; i++) {
|
|
||||||
var last = new Date(startTime)
|
|
||||||
ret.push(last.setTime(last.getTime() + i * 60 * 1000))
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
function minutesListNames() {
|
function minuteLongLabel(date) {
|
||||||
var ret = []
|
return date.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||||
var list = minutesList()
|
|
||||||
for (var i = 0; i < list.length; i++) {
|
|
||||||
ret.push(new Date(list[i]).toLocaleString(Qt.locale(), "hh:mm"))
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function hoursStart() {
|
function hoursStart() {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
d.setHours(d.getHours() - hoursCount + 1, 0, 0, 0)
|
d.setHours(d.getHours() - hoursCount + 1, 0, 0, 0)
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
function hoursList() {
|
function hourLabel(date) {
|
||||||
var ret = []
|
return date.toLocaleString(Qt.locale(), "hh")
|
||||||
var startTime = hoursStart();
|
|
||||||
for (var i = 0; i < hoursCount; i++) {
|
|
||||||
var last = new Date(startTime)
|
|
||||||
ret.push(last.setTime(last.getTime() + i * 60 * 60 * 1000))
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
function hoursListNames() {
|
function hourLongLabel(date) {
|
||||||
var ret = [];
|
return date.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||||
var list = hoursList();
|
|
||||||
for (var i = 0; i < list.length; i++) {
|
|
||||||
ret.push(new Date(list[i]).toLocaleString(Qt.locale(), "hh"));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function daysStart() {
|
function daysStart() {
|
||||||
@ -95,24 +87,11 @@ Item {
|
|||||||
d.setDate(d.getDate() - daysCount + 1);
|
d.setDate(d.getDate() - daysCount + 1);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
function dayLabel(date) {
|
||||||
function daysList() {
|
return date.toLocaleString(Qt.locale(), "ddd")
|
||||||
var ret = []
|
|
||||||
var startTime = daysStart();
|
|
||||||
for (var i = 0; i < daysCount; i++) {
|
|
||||||
var last = new Date(startTime)
|
|
||||||
ret.push(last.setDate(last.getDate() + i))
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
function dayLongLabel(date) {
|
||||||
function daysListNames() {
|
return date.toLocaleDateString(Qt.locale(), Locale.ShortFormat)
|
||||||
var ret = []
|
|
||||||
var list = daysList();
|
|
||||||
for (var i = 0; i < list.length; i++) {
|
|
||||||
ret.push(new Date(list[i]).toLocaleString(Qt.locale(), "ddd"))
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function weeksStart() {
|
function weeksStart() {
|
||||||
@ -121,57 +100,41 @@ Item {
|
|||||||
d.setDate(d.getDate() - d.getDay() - weeksCount * 7);
|
d.setDate(d.getDate() - d.getDay() - weeksCount * 7);
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
function weeksList() {
|
function weekLabel(date) {
|
||||||
var ret = []
|
var yearStart = new Date();
|
||||||
var startTime = weeksStart();
|
yearStart.setHours(0,0,0,0);
|
||||||
for (var i = 0; i < weeksCount; i++) {
|
yearStart.setDate(1);
|
||||||
var last = new Date(startTime)
|
yearStart.setMonth(0);
|
||||||
ret.push(last.setDate(last.getDate() + i * 7))
|
return Math.ceil((((date - yearStart) / 86400000) + 1)/7)
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
function weeksListNames() {
|
function weekLongLabel(date) {
|
||||||
var ret = []
|
var endDate = new Date(date)
|
||||||
var list = weeksList();
|
endDate.setDate(endDate.getDate() + 6)
|
||||||
for (var i = 0; i < list.length; i++) {
|
return date.toLocaleDateString(Qt.locale(), Locale.ShortFormat) + " - " + endDate.toLocaleDateString(Qt.locale(), Locale.ShortFormat)
|
||||||
var d = new Date(list[i])
|
|
||||||
var dayNum = d.getDay() || 7;
|
|
||||||
d.setDate(d.getDate() + 4 - dayNum);
|
|
||||||
ret.push(Math.ceil((((d - yearStart()) / 86400000) + 1)/7))
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function monthsStart() {
|
function monthsStart() {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
d.setHours(0,0,0,0);
|
d.setHours(0,0,0,0);
|
||||||
d.setMonth(d.getMonth() - monthsCount + 1, 1);
|
d.setMonth(d.getMonth() - monthsCount + 1, 1);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
function monthsList() {
|
function monthLabel(date) {
|
||||||
var ret = []
|
return date.toLocaleString(Qt.locale(), "MMM")
|
||||||
var startTime = monthsStart();
|
|
||||||
for (var i = 0; i < monthsCount; i++) {
|
|
||||||
var last = new Date(startTime)
|
|
||||||
ret.push(last.setMonth(last.getMonth() + i))
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
function monthsListNames() {
|
function monthLongLabel(date) {
|
||||||
var ret = []
|
return date.toLocaleString(Qt.locale(), "MMMM yyyy")
|
||||||
var list = monthsList();
|
|
||||||
for (var i = 0; i < list.length; i++) {
|
|
||||||
ret.push(new Date(list[i]).toLocaleString(Qt.locale(), "MMM"))
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function yearStart() {
|
function yearStart() {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
d.setHours(0,0,0,0);
|
d.setHours(0,0,0,0);
|
||||||
d.setDate(1);
|
d.setFullYear(d.getFullYear() - yearsCount + 1, 0, 1)
|
||||||
d.setMonth(0);
|
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
function yearLabel(date) {
|
||||||
|
return date.toLocaleString(Qt.locale(), "yyyy")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user