237 lines
6.6 KiB
Vue
237 lines
6.6 KiB
Vue
<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>
|