From 5d47b170b42868cbd9ca40ae4e5aea7522ea3cfd Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Thu, 5 Dec 2024 12:47:17 +0300 Subject: [PATCH] feat: add legend title alignment option (#17) --- .../__data__/pie/continuous-legend.ts | 18 +++++------ src/components/Legend/index.tsx | 30 +++++++++++++++---- src/hooks/useSeries/prepare-legend.ts | 1 + src/hooks/useSeries/types.ts | 1 + src/types/chart/legend.ts | 2 ++ 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/__stories__/__data__/pie/continuous-legend.ts b/src/__stories__/__data__/pie/continuous-legend.ts index 2e2059f..58145dc 100644 --- a/src/__stories__/__data__/pie/continuous-legend.ts +++ b/src/__stories__/__data__/pie/continuous-legend.ts @@ -1,22 +1,20 @@ -import {groups} from 'd3'; - +import {formatNumber} from '../../../libs'; import type {ChartData, PieSeriesData} from '../../../types'; import {getContinuesColorFn} from '../../../utils'; -import nintendoGames from '../nintendoGames'; function prepareData(): ChartData { const colors = ['rgb(255, 61, 100)', 'rgb(255, 198, 54)', 'rgb(84, 165, 32)']; const stops = [0, 0.5, 1]; - const gamesByPlatform = groups(nintendoGames, (item) => item.platform); - const data: PieSeriesData[] = gamesByPlatform.map(([platform, games]) => ({ - name: platform, - value: games.length, - label: `${platform}(${games.length})`, - })); + const data: PieSeriesData[] = [ + {name: 'A', value: 1200000}, + {name: 'B', value: 900000}, + {name: 'C', value: 1310000}, + ]; const getColor = getContinuesColorFn({colors, stops, values: data.map((d) => d.value)}); data.forEach((d) => { d.color = getColor(d.value); + d.label = formatNumber(d.value, {unit: 'auto'}); }); return { @@ -32,7 +30,7 @@ function prepareData(): ChartData { legend: { enabled: true, type: 'continuous', - title: {text: 'Games by platform'}, + title: {text: 'Legend for continues color'}, colorScale: { colors: colors, stops, diff --git a/src/components/Legend/index.tsx b/src/components/Legend/index.tsx index 0aedc95..0d5a97d 100644 --- a/src/components/Legend/index.tsx +++ b/src/components/Legend/index.tsx @@ -13,6 +13,7 @@ import type { SymbolLegendSymbol, } from '../../hooks'; import {getLineDashArray} from '../../hooks/useShapes/utils'; +import {formatNumber} from '../../libs'; import { block, createGradientRect, @@ -39,9 +40,9 @@ const getLegendPosition = (args: { align: PreparedLegend['align']; contentWidth: number; width: number; - offsetWidth: number; + offsetWidth?: number; }) => { - const {align, offsetWidth, width, contentWidth} = args; + const {align, offsetWidth = 0, width, contentWidth} = args; const top = 0; if (align === 'left') { @@ -311,14 +312,16 @@ export const Legend = (props: Props) => { }); // ticks + const scale = scaleLinear(domain, [0, legend.width]) as AxisScale; const xAxisGenerator = axisBottom({ - scale: scaleLinear(domain, [0, legend.width]) as AxisScale, + scale, ticks: { items: [[0, -rectHeight]], labelsMargin: legend.ticks.labelsMargin, labelsLineHeight: legend.ticks.labelsLineHeight, maxTickCount: 4, tickColor: '#fff', + labelFormat: (value: number) => formatNumber(value, {unit: 'auto'}), }, domain: { size: legend.width, @@ -334,15 +337,32 @@ export const Legend = (props: Props) => { } if (legend.title.enable) { - const {maxWidth: labelWidth} = getLabelsSize({ + const {maxWidth: titleWidth} = getLabelsSize({ labels: [legend.title.text], style: legend.title.style, }); + let dx = 0; + switch (legend.title.align) { + case 'center': { + dx = legend.width / 2 - titleWidth / 2; + break; + } + case 'right': { + dx = legend.width - titleWidth; + break; + } + case 'left': + default: { + dx = 0; + break; + } + } + svgElement .append('g') .attr('class', b('title')) .append('text') - .attr('dx', legend.width / 2 - labelWidth / 2) + .attr('dx', dx) .attr('font-weight', legend.title.style.fontWeight ?? null) .attr('font-size', legend.title.style.fontSize ?? null) .attr('fill', legend.title.style.fontColor ?? null) diff --git a/src/hooks/useSeries/prepare-legend.ts b/src/hooks/useSeries/prepare-legend.ts index b07fa11..ff9e761 100644 --- a/src/hooks/useSeries/prepare-legend.ts +++ b/src/hooks/useSeries/prepare-legend.ts @@ -90,6 +90,7 @@ export const getPreparedLegend = (args: { margin: titleMargin, style: titleStyle, height: titleHeight, + align: get(legend, 'title.align', 'left'), }, width: legendWidth, ticks, diff --git a/src/hooks/useSeries/types.ts b/src/hooks/useSeries/types.ts index 0920e8b..23fbc9b 100644 --- a/src/hooks/useSeries/types.ts +++ b/src/hooks/useSeries/types.ts @@ -56,6 +56,7 @@ export type PreparedLegend = Required> margin: number; style: BaseTextStyle; height: number; + align: Required['title']>['align']; }; ticks: { labelsMargin: number; diff --git a/src/types/chart/legend.ts b/src/types/chart/legend.ts index 957428b..d52c404 100644 --- a/src/types/chart/legend.ts +++ b/src/types/chart/legend.ts @@ -41,6 +41,8 @@ export type ChartLegend = { * Defaults to 4 for horizontal axes, 8 for vertical. * */ margin?: number; + /** The horizontal alignment of the title. */ + align?: 'left' | 'center' | 'right'; }; /* Gradient color settings for continuous legend type */ colorScale?: {