From fb05e86db72740933292f105da3fce43b8fafb1f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 14:08:52 +0900
Subject: [PATCH] :art:

---
 packages/frontend/src/components/MkChart.vue  | 13 ++--
 .../frontend/src/components/MkChartLegend.vue | 75 +++++++++++++++++++
 .../frontend/src/pages/user/activity.pv.vue   | 15 ++--
 packages/frontend/src/scripts/chart-legend.ts | 12 +++
 packages/frontend/src/scripts/chart-vline.ts  |  4 +-
 5 files changed, 103 insertions(+), 16 deletions(-)
 create mode 100644 packages/frontend/src/components/MkChartLegend.vue
 create mode 100644 packages/frontend/src/scripts/chart-legend.ts

diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index c0562f02e3..ea28cfa794 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -1,6 +1,7 @@
 <template>
 <div class="cbbedffa">
 	<canvas ref="chartEl"></canvas>
+	<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
 	<div v-if="fetching" class="fetching">
 		<MkLoading/>
 	</div>
@@ -24,6 +25,8 @@ import { chartVLine } from '@/scripts/chart-vline';
 import { alpha } from '@/scripts/color';
 import date from '@/filters/date';
 import { initChart } from '@/scripts/init-chart';
+import { chartLegend } from '@/scripts/chart-legend';
+import MkChartLegend from '@/components/MkChartLegend.vue';
 
 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 negate = arr => arr.map(x => -x);
 
@@ -220,11 +225,7 @@ const render = () => {
 			},
 			plugins: {
 				legend: {
-					display: props.detailed,
-					position: 'bottom',
-					labels: {
-						boxWidth: 16,
-					},
+					display: false,
 				},
 				tooltip: {
 					enabled: false,
@@ -264,7 +265,7 @@ const render = () => {
 				gradient,
 			},
 		},
-		plugins: [chartVLine(vLineColor)],
+		plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl)] : [])],
 	});
 };
 
diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue
new file mode 100644
index 0000000000..f33f753723
--- /dev/null
+++ b/packages/frontend/src/components/MkChartLegend.vue
@@ -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>
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index 2d83d1ddc3..7715b66673 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -3,6 +3,7 @@
 	<MkLoading v-if="fetching"/>
 	<div v-show="!fetching" :class="$style.root" class="_panel">
 		<canvas ref="chartEl"></canvas>
+		<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
 	</div>
 </div>
 </template>
@@ -20,6 +21,8 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip';
 import { chartVLine } from '@/scripts/chart-vline';
 import { alpha } from '@/scripts/color';
 import { initChart } from '@/scripts/init-chart';
+import { chartLegend } from '@/scripts/chart-legend';
+import MkChartLegend from '@/components/MkChartLegend.vue';
 
 initChart();
 
@@ -28,6 +31,7 @@ const props = defineProps<{
 }>();
 
 const chartEl = $shallowRef<HTMLCanvasElement>(null);
+let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 30;
@@ -153,14 +157,7 @@ async function renderChart() {
 					},
 				},
 				legend: {
-					display: true,
-					position: 'bottom',
-					padding: {
-						left: 0,
-						right: 0,
-						top: 8,
-						bottom: 0,
-					},
+					display: false,
 				},
 				tooltip: {
 					enabled: false,
@@ -173,7 +170,7 @@ async function renderChart() {
 				gradient,
 			},
 		},
-		plugins: [chartVLine(vLineColor)],
+		plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
 	});
 
 	fetching = false;
diff --git a/packages/frontend/src/scripts/chart-legend.ts b/packages/frontend/src/scripts/chart-legend.ts
new file mode 100644
index 0000000000..6a5370cc87
--- /dev/null
+++ b/packages/frontend/src/scripts/chart-legend.ts
@@ -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;
diff --git a/packages/frontend/src/scripts/chart-vline.ts b/packages/frontend/src/scripts/chart-vline.ts
index 10021583e0..f321443834 100644
--- a/packages/frontend/src/scripts/chart-vline.ts
+++ b/packages/frontend/src/scripts/chart-vline.ts
@@ -1,3 +1,5 @@
+import { Plugin } from 'chart.js';
+
 export const chartVLine = (vLineColor: string) => ({
 	id: 'vLine',
 	beforeDraw(chart, args, options) {
@@ -18,4 +20,4 @@ export const chartVLine = (vLineColor: string) => ({
 			ctx.restore();
 		}
 	},
-});
+}) as Plugin;
-- 
GitLab