UNPKG

@opendatasoft/visualizations

Version:

Opendatasoft's components to easily build dashboards and visualizations.

138 lines (131 loc) 5.17 kB
import type { LegendOptions, ChartConfiguration, Chart } from 'chart.js'; import type { _DeepPartialObject } from 'chart.js/dist/types/utils'; import { assureMaxLength } from 'components/utils/formatter'; import { CATEGORY_ITEM_VARIANT } from 'components/Legend/types'; import type { ChartOptions } from './types'; import { defaultValue, DEFAULT_GREY_COLOR } from './utils'; const LEGEND_MAX_LENGTH = 50; const handleHoverPieChart: LegendOptions<'pie'>['onHover'] = (_, item, legend) => { const { tooltip, chartArea } = legend.chart; if (tooltip) { // FIXME: `TooltipModel` doesn't have a `setActiveElements` method. // eslint-disable-next-line @typescript-eslint/no-explicit-any (tooltip as any).setActiveElements( [ { datasetIndex: 0, index: (item as any).index, // eslint-disable-line @typescript-eslint/no-explicit-any }, ], { x: (chartArea.left + chartArea.right) / 2, y: (chartArea.top + chartArea.bottom) / 2, } ); } legend.chart.update(); }; const handleLeavePieChart: LegendOptions<'pie'>['onLeave'] = (_evt, _item, legend) => { legend.chart.update(); }; export function buildLegend(options: ChartOptions) { const legend: _DeepPartialObject<LegendOptions<'pie'>> = { display: defaultValue(options?.legend?.display, false), position: defaultValue(options?.legend?.position, 'bottom'), align: defaultValue(options?.legend?.align, 'center'), ...(options.series[0]?.type === 'pie' && { onHover: handleHoverPieChart }), ...(options.series[0]?.type === 'pie' && { onLeave: handleLeavePieChart }), labels: { boxWidth: 20, boxHeight: defaultValue(options?.legend?.boxStyle, 'rect') === 'rect' ? 16 : 0, filter: (item) => { /* eslint-disable no-param-reassign */ const text = options?.legend?.labels?.text; if (text) { const index = typeof (item as any).index === 'number' // eslint-disable-line @typescript-eslint/no-explicit-any ? (item as any).index // eslint-disable-line @typescript-eslint/no-explicit-any : item.datasetIndex; item.text = text(index); } item.text = assureMaxLength(item.text, LEGEND_MAX_LENGTH); if (options?.legend?.boxStyle === 'dash') { item.lineWidth = 1; item.lineDash = [4, 2]; } else if (options?.legend?.boxStyle === 'rect') { item.borderRadius = 3; } return true; /* eslint-enable no-param-reassign */ }, }, }; return legend; } function buildLegendLabels( index: number, options: ChartOptions, chartConfig: ChartConfiguration ): string { const text = options?.legend?.labels?.text; if (text) { return assureMaxLength(text(index), LEGEND_MAX_LENGTH); } return `${chartConfig.data.labels?.[index]}`; } export function buildPieAndDoughnutCustomLegend({ chart, options, chartConfig, }: { chart: Chart; options: ChartOptions; chartConfig: ChartConfiguration; }) { const { series } = options; const backgroundColors = series[0].backgroundColor?.length ? series[0].backgroundColor : [DEFAULT_GREY_COLOR]; return { type: 'category' as const, position: defaultValue(options?.legend?.position, 'bottom'), align: defaultValue(options?.legend?.align, 'center'), items: chartConfig.data.datasets[0].data.map((_data, i) => ({ color: backgroundColors[i % backgroundColors.length], variant: CATEGORY_ITEM_VARIANT.Box, dashed: false, label: buildLegendLabels(i, options, chartConfig), onClick: (index: number) => { if (chart) { chart.toggleDataVisibility(index); chart.update(); } }, onHover: (index: number, isVisible = true) => { const { tooltip, chartArea } = chart; if (tooltip && isVisible) { tooltip.setActiveElements( [ { datasetIndex: 0, index, }, ], { x: (chartArea.left + chartArea.right) / 2, y: (chartArea.top + chartArea.bottom) / 2, } ); } chart.update(); }, onLeave: () => { const { tooltip } = chart; if (tooltip) { tooltip.setActiveElements([], { x: 0, y: 0 }); } chart.update(); }, })), }; }