Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/>
</div>
<HistoryChartLegend
v-if="legendDisplay && legendLarge"
v-if="legendDisplay"
:chart="chartRef?.chart || null"
class="legend-wrapper q-mt-sm"
/>
Expand All @@ -31,10 +31,6 @@ import {
TimeScale,
Tooltip,
Filler,
ChartEvent,
LegendItem,
LegendElement,
ChartTypeRegistry,
ChartDataset,
ChartType,
} from 'chart.js';
Expand Down Expand Up @@ -69,10 +65,6 @@ const props = defineProps<{

const chartRef = ref<ChartComponentRef | null>(null);

const legendLarge = computed(() =>
lineChartData?.value?.datasets.length > 15 ? true : false,
);

const applyHiddenDatasetsToChart = <TType extends ChartType, TData>(
chart: Chart<TType, TData>,
): void => {
Expand Down Expand Up @@ -125,6 +117,7 @@ const chartRange = computed(
const chargePointDatasets = computed(() =>
chargePointIds.value.map((cpId) => ({
label: `${chargePointNames.value(cpId)}`,
category: 'chargepoint',
unit: 'kW',
borderColor: '#4766b5',
backgroundColor: 'rgba(71, 102, 181, 0.2)',
Expand All @@ -148,6 +141,7 @@ const vehicleDatasets = computed(() =>
if (selectedData.value.some((item) => socKey in item)) {
return {
label: `${vehicle.name} SoC`,
category: 'vehicle',
unit: '%',
borderColor: '#9F8AFF',
borderWidth: 2,
Expand Down Expand Up @@ -175,6 +169,7 @@ const lineChartData = computed(() => {
datasets: [
{
label: gridMeterName.value,
category: 'component',
unit: 'kW',
borderColor: '#a33c42',
backgroundColor: 'rgba(239,182,188, 0.2)',
Expand All @@ -191,6 +186,7 @@ const lineChartData = computed(() => {
},
{
label: 'Hausverbrauch',
category: 'component',
unit: 'kW',
borderColor: '#949aa1',
backgroundColor: 'rgba(148, 154, 161, 0.2)',
Expand All @@ -207,6 +203,7 @@ const lineChartData = computed(() => {
},
{
label: 'PV ges.',
category: 'component',
unit: 'kW',
borderColor: 'green',
backgroundColor: 'rgba(144, 238, 144, 0.2)',
Expand All @@ -223,6 +220,7 @@ const lineChartData = computed(() => {
},
{
label: 'Speicher ges.',
category: 'component',
unit: 'kW',
borderColor: '#b5a647',
backgroundColor: 'rgba(181, 166, 71, 0.2)',
Expand All @@ -239,6 +237,7 @@ const lineChartData = computed(() => {
},
{
label: 'Speicher SoC',
category: 'component',
unit: '%',
borderColor: '#FFB96E',
borderWidth: 2,
Expand All @@ -265,33 +264,7 @@ const chartOptions = computed<ChartOptions<'line'>>(() => ({
animation: false,
plugins: {
legend: {
display: !legendLarge.value && legendDisplay.value,
fullSize: true,
align: 'center' as const,
position: 'bottom' as const,
labels: {
boxWidth: 19,
boxHeight: 0.1,
},
onClick: (
e: ChartEvent,
legendItem: LegendItem,
legend: LegendElement<keyof ChartTypeRegistry>,
) => {
const index = legendItem.datasetIndex!;
const chartInstance = legend.chart;
const datasetName = legendItem.text;

// Toggle visibility using the store
localDataStore.toggleDataset(datasetName);

// Update chart visibility
if (localDataStore.isDatasetHidden(datasetName)) {
chartInstance.hide(index);
} else {
chartInstance.show(index);
}
},
display: false,
},
tooltip: {
mode: 'index' as const,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,70 +1,119 @@
<template>
<q-scroll-area
v-if="chart"
:thumb-style="thumbStyle"
:bar-style="barStyle"
class="custom-legend-container"
>
<q-list dense class="q-pa-none">
<div class="row wrap q-pa-none items-center justify-center">
<q-item
v-for="(dataset, index) in legendItems"
:key="dataset.text || index"
clickable
dense
class="q-py-none"
:class="{ 'legend-item-hidden': dataset.hidden }"
@click="toggleDataset(dataset.text, dataset.datasetIndex)"
>
<q-item-section avatar class="q-pr-none">
<div
class="legend-color-box q-mr-sm"
:style="{ backgroundColor: getItemColor(dataset) }"
></div>
</q-item-section>
<q-item-section>
<q-item-label class="text-caption">{{ dataset.text }}</q-item-label>
</q-item-section>
</q-item>
</div>
</q-list>
</q-scroll-area>
<!-- On smaller screens (<md) always show categories -->
<HistoryChartLegendCategoriesGroup
v-if="$q.screen.lt.md"
:categorizedLegendItems="categorizedLegendItems"
:toggleDataset="toggleDataset"
:getItemColor="getItemColor"
:getItemLineType="getItemLineType"
/>

<!-- On larger screens: show standard legend if legend not large; otherwise show categories -->
<HistoryChartLegendStandard
v-else-if="chart && !$q.screen.lt.sm && !legendLarge"
:items="legendItems"
:toggleDataset="toggleDataset"
:getItemColor="getItemColor"
:getItemLineType="getItemLineType"
/>

<HistoryChartLegendCategoriesGroup
v-else
:categorizedLegendItems="categorizedLegendItems"
:toggleDataset="toggleDataset"
:getItemColor="getItemColor"
:getItemLineType="getItemLineType"
/>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';
import { ref, watch, computed, nextTick } from 'vue';
import { useLocalDataStore } from 'src/stores/localData-store';
import { Chart, LegendItem } from 'chart.js';
import { Chart, ChartDataset, LegendItem } from 'chart.js';
import type {
Category,
CategorizedDataset,
LegendItemWithCategory,
} from './history-chart-model';
import { useMqttStore } from 'src/stores/mqtt-store';
import { useQuasar } from 'quasar';
import HistoryChartLegendCategoriesGroup from './HistoryChartLegendCategoriesGroup.vue';
import HistoryChartLegendStandard from './HistoryChartLegendStandard.vue';

const mqttStore = useMqttStore();
const $q = useQuasar();

const props = defineProps<{
chart: Chart | null;
}>();

const localDataStore = useLocalDataStore();
const legendItems = ref<LegendItem[]>([]);
const legendItems = ref<LegendItemWithCategory[]>([]);

const legendLarge = computed(() => {
return legendItems.value.length > 20;
});

const updateLegendItems = () => {
if (!props.chart) return;
const items =
props.chart.options.plugins?.legend?.labels?.generateLabels?.(
props.chart,
) || [];

items.forEach((item) => {
(items as LegendItemWithCategory[]).forEach((item) => {
if (item.text && localDataStore.isDatasetHidden(item.text)) {
item.hidden = true;
}
// Inject the category from the dataset
const dataset = props.chart?.data.datasets[
item.datasetIndex!
] as unknown as CategorizedDataset;
item.category = dataset.category;
});
legendItems.value = items;
legendItems.value = items as LegendItemWithCategory[];
};

const getItemColor = (item: LegendItem) => {
if (!props.chart || item.datasetIndex === undefined) return '#ccc';
const categorizedLegendItems = computed(() => {
const categories: Record<Category, LegendItemWithCategory[]> = {
chargepoint: [],
vehicle: [],
battery: [],
component: [],
};
for (const item of legendItems.value) {
const category = item.category;
if (category && categories[category]) {
categories[category].push(item);
} else {
categories.component.push(item);
}
}
// Sort each category's items alphabetically
Object.keys(categories).forEach((key) => {
categories[key as Category].sort((a, b) =>
(a.text || '').localeCompare(b.text || '', undefined, { numeric: true }),
);
});
return categories;
});

const getItemColor = (item: LegendItem): string => {
if (!props.chart || item.datasetIndex === undefined) return '#ccc';
const dataset = props.chart.data.datasets[item.datasetIndex];
return (dataset.borderColor as string) || '#ccc';
};

const getItemLineType = (item: LegendItem) => {
if (!props.chart || item.datasetIndex === undefined) return;
const dataset = props.chart.data.datasets[
item.datasetIndex
] as ChartDataset<'line'>;
const borderDash = dataset.borderDash;
return Array.isArray(borderDash) && borderDash.length > 0
? 'dashed'
: 'solid';
};

const toggleDataset = (datasetName?: string, datasetIndex?: number) => {
if (!props.chart || !datasetName || datasetIndex === undefined) return;
localDataStore.toggleDataset(datasetName);
Expand Down Expand Up @@ -96,52 +145,11 @@ watch(
{ immediate: true },
);

const thumbStyle = {
borderRadius: '5px',
backgroundColor: 'var(--q-primary)',
width: '6px',
opacity: '1',
};

const barStyle = {
borderRadius: '5px',
backgroundColor: 'var(--q-secondary)',
width: '6px',
opacity: '1',
};
watch(
() => mqttStore.vehicleList,
async () => {
await nextTick();
updateLegendItems();
},
);
</script>

<style scoped>
.custom-legend-container {
margin-bottom: 5px;
height: 70px;
border-radius: 5px;
text-align: left;
width: 100%;
}

.legend-color-box {
display: inline-block;
width: 20px;
height: 3px;
}

.legend-item-hidden {
opacity: 0.6 !important;
text-decoration: line-through !important;
}

/* Override the avatar section min-width */
:deep(.q-item__section--avatar) {
min-width: 5px !important;
padding-right: 0px !important;
}

/* For very small screens */
@media (max-width: 576px) {
.legend-color-box {
width: 10px;
height: 2px;
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<div class="row justify-center items-center">
<HistoryChartLegendCategory
:label="'Komponenten'"
:items="categorizedLegendItems.component"
:toggleDataset="toggleDataset"
:getItemColor="getItemColor"
:getItemLineType="getItemLineType"
menuAnchor="bottom right"
menuSelf="top right"
/>

<HistoryChartLegendCategory
:label="'Ladepunkte'"
:items="categorizedLegendItems.chargepoint"
:toggleDataset="toggleDataset"
:getItemColor="getItemColor"
:getItemLineType="getItemLineType"
menuAnchor="bottom middle"
menuSelf="top middle"
menuFormat="q-mx-lg"
/>

<HistoryChartLegendCategory
:label="'Fahrzeuge'"
:items="categorizedLegendItems.vehicle"
:toggleDataset="toggleDataset"
:getItemColor="getItemColor"
:getItemLineType="getItemLineType"
menuAnchor="bottom left"
menuSelf="top left"
/>
</div>
</template>

<script setup lang="ts">
import HistoryChartLegendCategory from './HistoryChartLegendCategory.vue';
import type { LegendItem } from 'chart.js';
import type { Category } from './history-chart-model';

defineProps<{
categorizedLegendItems: Record<Category, LegendItem[]>;
toggleDataset: (datasetName: string, datasetIndex: number) => void;
getItemColor: (dataset: LegendItem) => string;
getItemLineType: (dataset: LegendItem) => string | undefined;
}>();
</script>
Loading