diff --git a/packages/pxweb2-ui/src/lib/components/Chart/LineChart/LineChart.spec.tsx b/packages/pxweb2-ui/src/lib/components/Chart/LineChart/LineChart.spec.tsx index 94ad83347..8356601c3 100644 --- a/packages/pxweb2-ui/src/lib/components/Chart/LineChart/LineChart.spec.tsx +++ b/packages/pxweb2-ui/src/lib/components/Chart/LineChart/LineChart.spec.tsx @@ -191,8 +191,8 @@ describe('LineChart', () => { ]); expect(html).toContain('
2024
'); - expect(html).toContain('Men: 10'); - expect(html).toContain('Women: 12'); + expect(html).toContain('Men: 10'); + expect(html).toContain('Women: 12'); expect(html).toContain('fill="#666666"'); expect(html).toContain('fill="#ff0000"'); expect(html).toContain('; + data?: Record; color?: string; }; +const getConfinedTooltipPosition: NonNullable< + echarts.TooltipComponentOption['position'] +> = (point, _params, _dom, _rect, size) => { + const padding = 8; + const offset = 12; + + const [rawX, rawY] = Array.isArray(point) ? point : [padding, padding]; + + const [contentWidth, contentHeight] = size?.contentSize ?? [0, 0]; + const [viewWidth, viewHeight] = size?.viewSize ?? [0, 0]; + + const desiredX = rawX + offset; + const desiredY = rawY + offset; + + const maxX = Math.max(padding, viewWidth - contentWidth - padding); + const maxY = Math.max(padding, viewHeight - contentHeight - padding); + + const x = Math.min(Math.max(desiredX, padding), maxX); + const y = Math.min(Math.max(desiredY, padding), maxY); + + return [x, y]; +}; + function getTooltipSymbolSvg(symbol: string, color: string): string { switch (symbol) { case 'rect': @@ -41,29 +70,13 @@ function getTooltipSymbolSvg(symbol: string, color: string): string { } } -export function LineChart({ pxtable, colors }: LineChartProps) { - const dataset = useMemo(() => mapPxTableToChartDataset(pxtable), [pxtable]); - - const resolvedColors = useMemo(() => { - return colors && colors.length > 0 - ? colors - : getChartColorsFromCssVariables(); - }, [colors]); - - const option = useMemo( - () => ({ - ...buildDatasetOption(dataset), - grid: { top: 0, bottom: 200, left: '0', right: '0', containLabel: false }, - xAxis: { type: 'category' as const, axisLabel: { rotate: 45 } }, - yAxis: { - name: dataset.unit, - min: (value) => value.min, - }, - legend: { - height: 40 * dataset.series.length, // increase legend height based on number of series to prevent overlap with x-axis labels - }, - series: buildSeriesOption(dataset, 'line', resolvedColors), - tooltip: { +function getTooltip( + tooltipType: TooltipType, + dataset: EChartsDataset, +): echarts.EChartsOption['tooltip'] { + switch (tooltipType) { + case 'axis': + return { trigger: 'axis', formatter: (params: unknown) => { const axisParams = (Array.isArray(params) ? params : [params]) as @@ -85,15 +98,114 @@ export function LineChart({ pxtable, colors }: LineChartProps) { ]; const color = param.color ?? '#666666'; - return `
${getTooltipSymbolSvg(symbol, color)}${param.seriesName}: ${value ?? ''}
`; + return `
${getTooltipSymbolSvg(symbol, color)}${param.seriesName}: ${value ?? ''}
`; }) .join(''); return `
${title}
${rows}
`; }, + }; + case 'item': + return { + trigger: 'item', + axisPointer: { + type: 'cross', + }, + formatter: (params: unknown) => { + const param = params as TooltipParam; + const seriesMeta = dataset.series[param.seriesIndex]; + + if (!seriesMeta) { + return ''; + } + + const row = param.data; + const title = + param.axisValueLabel ?? + (param.axisValue != null ? String(param.axisValue) : undefined) ?? + param.name ?? + (row?.name != null ? String(row.name) : ''); + const value = row?.[seriesMeta.key] ?? ''; + const symbol = + LINE_SERIES_SYMBOLS[param.seriesIndex % LINE_SERIES_SYMBOLS.length]; + const color = param.color ?? '#666666'; + + return ` +
${title}
+
+ + ${getTooltipSymbolSvg(symbol, color)} + + ${param.seriesName}: ${value} +
+ `; + }, + }; + default: + return { + trigger: 'axis', + }; + } +} + +export function LineChart({ + pxtable, + colors, + tooltipType = 'axis', +}: LineChartProps) { + const dataset = useMemo(() => mapPxTableToChartDataset(pxtable), [pxtable]); + + const resolvedColors = useMemo(() => { + return colors && colors.length > 0 + ? colors + : getChartColorsFromCssVariables(); + }, [colors]); + + const series = useMemo(() => { + const baseSeries = buildSeriesOption(dataset, 'line', resolvedColors); + + if (tooltipType !== 'item') { + return baseSeries; + } + + // Show all symbols for 'item' tooltip type to ensure that the tooltip is displayed for all data points + return baseSeries.map((seriesOption) => { + if (seriesOption.type !== 'line') { + return seriesOption; + } + + return { + ...seriesOption, + showSymbol: true, + showAllSymbol: true, + symbolSize: 7, + } as echarts.LineSeriesOption; + }); + }, [dataset, resolvedColors, tooltipType]); + + const option = useMemo( + () => ({ + ...buildDatasetOption(dataset), + grid: { top: 0, bottom: 200, left: '0', right: '0', containLabel: false }, + xAxis: { type: 'category' as const, axisLabel: { rotate: 45 } }, + yAxis: { + name: dataset.unit, + min: (value) => value.min, + }, + legend: { + height: 40 * dataset.series.length, // increase legend height based on number of series to prevent overlap with x-axis labels + }, + series, + tooltip: { + ...getTooltip(tooltipType, dataset), + confine: true, + appendToBody: true, + extraCssText: + 'max-width:min(92vw,340px);white-space:normal;word-break:break-word;overflow-wrap:anywhere;', + position: getConfinedTooltipPosition, }, }), - [dataset, resolvedColors], + [dataset, series, tooltipType], ); const { divRef } = useEChartOption(option); diff --git a/packages/pxweb2/public/config/config.js b/packages/pxweb2/public/config/config.js index 87ec89910..26a9c5b36 100644 --- a/packages/pxweb2/public/config/config.js +++ b/packages/pxweb2/public/config/config.js @@ -38,4 +38,7 @@ globalThis.PxWeb2Config = { sv: '', // Set to your Swedish homepage URL en: '', // Set to your English homepage URL }, + features: { + chartEnabled: true, + }, }; diff --git a/packages/pxweb2/src/app/components/Presentation/Presentation.tsx b/packages/pxweb2/src/app/components/Presentation/Presentation.tsx index 1aa341565..f34092342 100644 --- a/packages/pxweb2/src/app/components/Presentation/Presentation.tsx +++ b/packages/pxweb2/src/app/components/Presentation/Presentation.tsx @@ -338,7 +338,8 @@ export function Presentation({ [classes.fadeChart]: isFadingTable, })} > - + +