🎨
This commit is contained in:
parent
c93f091ba8
commit
fb05e86db7
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="cbbedffa">
|
<div class="cbbedffa">
|
||||||
<canvas ref="chartEl"></canvas>
|
<canvas ref="chartEl"></canvas>
|
||||||
|
<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
|
||||||
<div v-if="fetching" class="fetching">
|
<div v-if="fetching" class="fetching">
|
||||||
<MkLoading/>
|
<MkLoading/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,6 +25,8 @@ import { chartVLine } from '@/scripts/chart-vline';
|
||||||
import { alpha } from '@/scripts/color';
|
import { alpha } from '@/scripts/color';
|
||||||
import date from '@/filters/date';
|
import date from '@/filters/date';
|
||||||
import { initChart } from '@/scripts/init-chart';
|
import { initChart } from '@/scripts/init-chart';
|
||||||
|
import { chartLegend } from '@/scripts/chart-legend';
|
||||||
|
import MkChartLegend from '@/components/MkChartLegend.vue';
|
||||||
|
|
||||||
initChart();
|
initChart();
|
||||||
|
|
||||||
|
@ -67,6 +70,8 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
|
||||||
|
|
||||||
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
|
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
|
||||||
const negate = arr => arr.map(x => -x);
|
const negate = arr => arr.map(x => -x);
|
||||||
|
|
||||||
|
@ -220,11 +225,7 @@ const render = () => {
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
display: props.detailed,
|
display: false,
|
||||||
position: 'bottom',
|
|
||||||
labels: {
|
|
||||||
boxWidth: 16,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
@ -264,7 +265,7 @@ const render = () => {
|
||||||
gradient,
|
gradient,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [chartVLine(vLineColor)],
|
plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl)] : [])],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
75
packages/frontend/src/components/MkChartLegend.vue
Normal file
75
packages/frontend/src/components/MkChartLegend.vue
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<div :class="$style.root">
|
||||||
|
<button v-for="item in items" class="_button item" :class="{ disabled: item.hidden }" @click="onClick(item)">
|
||||||
|
<span class="box" :style="{ background: chart.config.type === 'line' ? item.strokeStyle?.toString() : item.fillStyle?.toString() }"></span>
|
||||||
|
{{ item.text }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, shallowRef, watch, PropType, onUnmounted } from 'vue';
|
||||||
|
import { Chart, LegendItem } from 'chart.js';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
});
|
||||||
|
|
||||||
|
let chart = $shallowRef<Chart>();
|
||||||
|
let items = $shallowRef<LegendItem[]>([]);
|
||||||
|
|
||||||
|
function update(_chart: Chart, _items: LegendItem[]) {
|
||||||
|
chart = _chart,
|
||||||
|
items = _items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClick(item: LegendItem) {
|
||||||
|
if (chart == null) return;
|
||||||
|
const { type } = chart.config;
|
||||||
|
if (type === 'pie' || type === 'doughnut') {
|
||||||
|
// Pie and doughnut charts only have a single dataset and visibility is per item
|
||||||
|
chart.toggleDataVisibility(item.index);
|
||||||
|
} else {
|
||||||
|
chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));
|
||||||
|
}
|
||||||
|
chart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
update,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&:global {
|
||||||
|
> .item {
|
||||||
|
font-size: 85%;
|
||||||
|
padding: 4px 12px 4px 8px;
|
||||||
|
border: solid 1px var(--divider);
|
||||||
|
border-radius: 999px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--inputBorderHover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
text-decoration: line-through;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .box {
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 100%;
|
||||||
|
vertical-align: -10%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,6 +3,7 @@
|
||||||
<MkLoading v-if="fetching"/>
|
<MkLoading v-if="fetching"/>
|
||||||
<div v-show="!fetching" :class="$style.root" class="_panel">
|
<div v-show="!fetching" :class="$style.root" class="_panel">
|
||||||
<canvas ref="chartEl"></canvas>
|
<canvas ref="chartEl"></canvas>
|
||||||
|
<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -20,6 +21,8 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip';
|
||||||
import { chartVLine } from '@/scripts/chart-vline';
|
import { chartVLine } from '@/scripts/chart-vline';
|
||||||
import { alpha } from '@/scripts/color';
|
import { alpha } from '@/scripts/color';
|
||||||
import { initChart } from '@/scripts/init-chart';
|
import { initChart } from '@/scripts/init-chart';
|
||||||
|
import { chartLegend } from '@/scripts/chart-legend';
|
||||||
|
import MkChartLegend from '@/components/MkChartLegend.vue';
|
||||||
|
|
||||||
initChart();
|
initChart();
|
||||||
|
|
||||||
|
@ -28,6 +31,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const chartEl = $shallowRef<HTMLCanvasElement>(null);
|
const chartEl = $shallowRef<HTMLCanvasElement>(null);
|
||||||
|
let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let chartInstance: Chart = null;
|
let chartInstance: Chart = null;
|
||||||
const chartLimit = 30;
|
const chartLimit = 30;
|
||||||
|
@ -153,14 +157,7 @@ async function renderChart() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
display: true,
|
display: false,
|
||||||
position: 'bottom',
|
|
||||||
padding: {
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 8,
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
@ -173,7 +170,7 @@ async function renderChart() {
|
||||||
gradient,
|
gradient,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [chartVLine(vLineColor)],
|
plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
|
||||||
});
|
});
|
||||||
|
|
||||||
fetching = false;
|
fetching = false;
|
||||||
|
|
12
packages/frontend/src/scripts/chart-legend.ts
Normal file
12
packages/frontend/src/scripts/chart-legend.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Plugin } from 'chart.js';
|
||||||
|
import MkChartLegend from '@/components/MkChartLegend.vue';
|
||||||
|
|
||||||
|
export const chartLegend = (legend: InstanceType<typeof MkChartLegend>) => ({
|
||||||
|
id: 'htmlLegend',
|
||||||
|
afterUpdate(chart, args, options) {
|
||||||
|
// Reuse the built-in legendItems generator
|
||||||
|
const items = chart.options.plugins.legend.labels.generateLabels(chart);
|
||||||
|
|
||||||
|
legend.update(chart, items);
|
||||||
|
},
|
||||||
|
}) as Plugin;
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Plugin } from 'chart.js';
|
||||||
|
|
||||||
export const chartVLine = (vLineColor: string) => ({
|
export const chartVLine = (vLineColor: string) => ({
|
||||||
id: 'vLine',
|
id: 'vLine',
|
||||||
beforeDraw(chart, args, options) {
|
beforeDraw(chart, args, options) {
|
||||||
|
@ -18,4 +20,4 @@ export const chartVLine = (vLineColor: string) => ({
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
}) as Plugin;
|
||||||
|
|
Loading…
Reference in a new issue