Sessions UI: visualization, month/year charts (#15902)
This commit is contained in:
parent
1bd521fc24
commit
cfa35f295b
38 changed files with 3271 additions and 715 deletions
237
assets/js/components/Sessions/EnergyHistoryChart.vue
Normal file
237
assets/js/components/Sessions/EnergyHistoryChart.vue
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="position: relative; height: 300px" class="my-3">
|
||||
<Bar :data="chartData" :options="options" />
|
||||
</div>
|
||||
<LegendList :legends="legends" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Bar } from "vue-chartjs";
|
||||
import { BarController, BarElement, CategoryScale, LinearScale, Legend, Tooltip } from "chart.js";
|
||||
import { registerChartComponents, commonOptions, tooltipLabelColor } from "./chartConfig";
|
||||
import LegendList from "./LegendList.vue";
|
||||
import formatter, { POWER_UNIT } from "../../mixins/formatter";
|
||||
import colors from "../../colors";
|
||||
import { GROUPS, PERIODS } from "./types";
|
||||
|
||||
registerChartComponents([BarController, BarElement, CategoryScale, LinearScale, Legend, Tooltip]);
|
||||
|
||||
export default {
|
||||
name: "EnergyHistoryChart",
|
||||
components: { Bar, LegendList },
|
||||
props: {
|
||||
sessions: { type: Array, default: () => [] },
|
||||
groupBy: { type: String, default: GROUPS.NONE },
|
||||
period: { type: String, default: PERIODS.TOTAL },
|
||||
colorMappings: { type: Object, default: () => ({ loadpoint: {}, vehicle: {} }) },
|
||||
},
|
||||
mixins: [formatter],
|
||||
computed: {
|
||||
firstDay() {
|
||||
if (this.sessions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return new Date(this.sessions[0].created);
|
||||
},
|
||||
month() {
|
||||
return this.firstDay?.getMonth() + 1;
|
||||
},
|
||||
year() {
|
||||
return this.firstDay?.getFullYear();
|
||||
},
|
||||
lastDay() {
|
||||
if (this.sessions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return new Date(this.sessions[this.sessions.length - 1].created);
|
||||
},
|
||||
chartData() {
|
||||
console.log("update energy history data");
|
||||
const result = {};
|
||||
const groups = new Set();
|
||||
|
||||
if (this.sessions.length > 0) {
|
||||
//const lastDay = new Date(this.year, this.month, 0);
|
||||
//const daysInMonth = this.lastDay.getDate();
|
||||
let xFrom, xTo;
|
||||
if (this.period === PERIODS.TOTAL) {
|
||||
xFrom = this.firstDay.getFullYear();
|
||||
xTo = this.lastDay.getFullYear();
|
||||
} else if (this.period === PERIODS.YEAR) {
|
||||
xFrom = 1;
|
||||
xTo = 12;
|
||||
} else {
|
||||
xFrom = 1;
|
||||
xTo = new Date(
|
||||
this.lastDay.getFullYear(),
|
||||
this.lastDay.getMonth() + 1,
|
||||
0
|
||||
).getDate();
|
||||
}
|
||||
|
||||
// initialize result with empty arrays
|
||||
for (let i = xFrom; i <= xTo; i++) {
|
||||
result[i] = {};
|
||||
}
|
||||
|
||||
// Populate with actual data
|
||||
this.sessions.forEach((session) => {
|
||||
let index;
|
||||
const date = new Date(session.created);
|
||||
if (this.period === PERIODS.MONTH) {
|
||||
index = date.getDate();
|
||||
} else if (this.period === PERIODS.YEAR) {
|
||||
index = date.getMonth() + 1;
|
||||
} else {
|
||||
index = date.getFullYear();
|
||||
}
|
||||
|
||||
if (this.groupBy === GROUPS.NONE) {
|
||||
groups.add("grid");
|
||||
groups.add("self");
|
||||
const charged = session.chargedEnergy;
|
||||
const self = (charged / 100) * session.solarPercentage;
|
||||
const grid = charged - self;
|
||||
result[index].self = (result[index].self || 0) + self;
|
||||
result[index].grid = (result[index].grid || 0) + grid;
|
||||
} else {
|
||||
const groupKey = session[this.groupBy];
|
||||
groups.add(groupKey);
|
||||
result[index][groupKey] =
|
||||
(result[index][groupKey] || 0) + session.chargedEnergy;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const datasets = Array.from(groups).map((group) => {
|
||||
const colorGroup = this.groupBy === GROUPS.NONE ? "solar" : this.groupBy;
|
||||
const backgroundColor = this.colorMappings[colorGroup][group];
|
||||
const label =
|
||||
this.groupBy === GROUPS.NONE ? this.$t(`sessions.group.${group}`) : group;
|
||||
|
||||
return {
|
||||
backgroundColor,
|
||||
label,
|
||||
data: Object.values(result).map((day) => day[group] || 0),
|
||||
borderRadius: (context) => {
|
||||
const threshold = 0.04; // 400 Wh
|
||||
const { dataIndex, datasetIndex } = context;
|
||||
const currentValue = context.dataset.data[dataIndex];
|
||||
const previousValuesExist = context.chart.data.datasets
|
||||
.slice(datasetIndex + 1)
|
||||
.some((dataset) => (dataset?.data[dataIndex] || 0) > threshold);
|
||||
return currentValue > threshold && !previousValuesExist
|
||||
? { topLeft: 10, topRight: 10 }
|
||||
: { topLeft: 0, topRight: 0 };
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
labels: Object.keys(result),
|
||||
datasets: datasets,
|
||||
};
|
||||
},
|
||||
legends() {
|
||||
return this.chartData.datasets.map((dataset) => ({
|
||||
label: dataset.label,
|
||||
color: dataset.backgroundColor,
|
||||
value: this.fmtWh(
|
||||
dataset.data.reduce((acc, curr) => acc + curr, 0) * 1e3,
|
||||
POWER_UNIT.AUTO
|
||||
),
|
||||
}));
|
||||
},
|
||||
options() {
|
||||
return {
|
||||
...commonOptions,
|
||||
locale: this.$i18n?.locale,
|
||||
color: colors.text,
|
||||
borderSkipped: false,
|
||||
maxBarThickness: 40,
|
||||
animation: false,
|
||||
plugins: {
|
||||
...commonOptions.plugins,
|
||||
tooltip: {
|
||||
...commonOptions.plugins.tooltip,
|
||||
mode: "index",
|
||||
intersect: false,
|
||||
positioner: (context) => {
|
||||
const { chart, tooltipPosition } = context;
|
||||
const { tooltip } = chart;
|
||||
const { width, height } = tooltip;
|
||||
const { x, y } = tooltipPosition();
|
||||
const { innerWidth, innerHeight } = window;
|
||||
|
||||
return {
|
||||
x: Math.min(x, innerWidth - width),
|
||||
y: Math.min(y, innerHeight - height),
|
||||
};
|
||||
},
|
||||
callbacks: {
|
||||
title: (tooltipItem) => {
|
||||
const { label } = tooltipItem[0];
|
||||
if (this.period === PERIODS.TOTAL) {
|
||||
return label;
|
||||
} else if (this.period === PERIODS.YEAR) {
|
||||
const date = new Date(this.year, label - 1, 1);
|
||||
return this.fmtMonth(date);
|
||||
} else {
|
||||
const date = new Date(this.year, this.month, label);
|
||||
return this.fmtDayMonth(date);
|
||||
}
|
||||
},
|
||||
label: (tooltipItem) => {
|
||||
const datasetLabel = tooltipItem.dataset.label || "";
|
||||
const value = tooltipItem.raw || 0;
|
||||
return (
|
||||
datasetLabel + ": " + this.fmtWh(value * 1e3, POWER_UNIT.AUTO)
|
||||
);
|
||||
},
|
||||
labelColor: tooltipLabelColor(false),
|
||||
labelPointStyle: function () {
|
||||
return {
|
||||
pointStyle: "circle",
|
||||
};
|
||||
},
|
||||
labelTextColor: (item) => {
|
||||
return !item.raw ? colors.muted : "#fff";
|
||||
},
|
||||
},
|
||||
itemSort: function (a, b) {
|
||||
return b.datasetIndex - a.datasetIndex;
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
border: { display: false },
|
||||
grid: { display: false },
|
||||
ticks: { color: colors.muted },
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
border: { display: false },
|
||||
grid: { color: colors.border },
|
||||
title: {
|
||||
text: "kWh",
|
||||
display: true,
|
||||
color: colors.muted,
|
||||
},
|
||||
ticks: {
|
||||
callback: (value) => this.fmtWh(value * 1e3, POWER_UNIT.KW, false, 0),
|
||||
color: colors.muted,
|
||||
maxTicksLimit: 6,
|
||||
},
|
||||
position: "right",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue