🎨
This commit is contained in:
		
							parent
							
								
									c93f091ba8
								
							
						
					
					
						commit
						fb05e86db7
					
				
					 5 changed files with 103 additions and 16 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue