🎨
This commit is contained in:
		
							parent
							
								
									c93f091ba8
								
							
						
					
					
						commit
						fb05e86db7
					
				
					 5 changed files with 103 additions and 16 deletions
				
			
		|  | @ -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)] : [])], | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										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"/> | ||||
| 	<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; | ||||
|  |  | |||
							
								
								
									
										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) => ({ | ||||
| 	id: 'vLine', | ||||
| 	beforeDraw(chart, args, options) { | ||||
|  | @ -18,4 +20,4 @@ export const chartVLine = (vLineColor: string) => ({ | |||
| 			ctx.restore(); | ||||
| 		} | ||||
| 	}, | ||||
| }); | ||||
| }) as Plugin; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue