diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index c0562f02e351324bafffcee427b8c20d03e1e761..ea28cfa794bd7f5d6d6788b96cc937d92856c8ed 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 0000000000000000000000000000000000000000..f33f7537238a848f13eb42e2dcd5dfb75cdc09f1
--- /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 2d83d1ddc35d66617263bc808df4ded006f89275..7715b666730c329ee32167e3f8ef34d3438268b3 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 0000000000000000000000000000000000000000..6a5370cc8732db13a21cbcf5a22f407b3f0efa2b
--- /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 10021583e054db09688a03fc7e80788df9efdeab..f3214438348581c0f1a95712db030fbe9c1c8bbb 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;