diff --git a/src/plugins/d3/__stories__/pie/Basic.stories.tsx b/src/plugins/d3/__stories__/pie/Basic.stories.tsx index b7367a4e..11a6b002 100644 --- a/src/plugins/d3/__stories__/pie/Basic.stories.tsx +++ b/src/plugins/d3/__stories__/pie/Basic.stories.tsx @@ -6,6 +6,7 @@ import {settings} from '../../../../libs'; import {D3Plugin} from '../..'; import {BasicPie} from '../../examples/pie/Basic'; import {Donut} from '../../examples/pie/Donut'; +import {DonutWithTotals} from '../../examples/pie/DonutWithTotals'; const ChartStory = ({Chart}: {Chart: React.FC}) => { const [shown, setShown] = React.useState(false); @@ -41,6 +42,13 @@ export const BasicDonutStory: StoryObj = { }, }; +export const DonutWithTotalsStory: StoryObj = { + name: 'Donut with totals', + args: { + Chart: DonutWithTotals, + }, +}; + export default { title: 'Plugins/D3/Pie', decorators: [withKnobs], diff --git a/src/plugins/d3/examples/pie/DonutWithTotals.tsx b/src/plugins/d3/examples/pie/DonutWithTotals.tsx new file mode 100644 index 00000000..e40ed053 --- /dev/null +++ b/src/plugins/d3/examples/pie/DonutWithTotals.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import {groups} from 'd3'; +import {ChartKit} from '../../../../components/ChartKit'; +import type {ChartKitWidgetData} from '../../../../types'; +import {ExampleWrapper} from '../ExampleWrapper'; +import nintendoGames from '../nintendoGames'; +import {CustomShapeRenderer} from '../../utils'; + +function prepareData() { + const gamesByPlatform = groups(nintendoGames, (d) => d.esrb_rating || 'unknown'); + return gamesByPlatform.map(([value, games]) => ({ + name: value, + value: games.length, + })); +} + +export const DonutWithTotals = () => { + const data = prepareData(); + const totals = data.reduce((sum, d) => sum + d.value, 0); + + const widgetData: ChartKitWidgetData = { + series: { + data: [ + { + type: 'pie', + innerRadius: '50%', + data: data, + renderCustomShape: CustomShapeRenderer.pieCenterText(`${totals}`), + }, + ], + }, + legend: {enabled: true}, + title: { + text: 'ESRB ratings', + style: {fontSize: '12px', fontWeight: 'normal'}, + }, + }; + + return ( + + + + ); +}; diff --git a/src/plugins/d3/index.ts b/src/plugins/d3/index.ts index b366ce86..2e58bc6a 100644 --- a/src/plugins/d3/index.ts +++ b/src/plugins/d3/index.ts @@ -2,7 +2,7 @@ import React from 'react'; import {ChartKitPlugin} from '../../types'; export * from './types'; - +export * from './utils'; /** * It is an experemental plugin * diff --git a/src/plugins/d3/renderer/hooks/useSeries/prepare-pie.ts b/src/plugins/d3/renderer/hooks/useSeries/prepare-pie.ts index b112c22a..1dc91b79 100644 --- a/src/plugins/d3/renderer/hooks/useSeries/prepare-pie.ts +++ b/src/plugins/d3/renderer/hooks/useSeries/prepare-pie.ts @@ -60,6 +60,7 @@ export function preparePieSeries(args: PreparePieSeriesArgs) { }, }, }, + renderCustomShape: series.renderCustomShape, }; return result; diff --git a/src/plugins/d3/renderer/hooks/useSeries/types.ts b/src/plugins/d3/renderer/hooks/useSeries/types.ts index 5c582122..41cf1c19 100644 --- a/src/plugins/d3/renderer/hooks/useSeries/types.ts +++ b/src/plugins/d3/renderer/hooks/useSeries/types.ts @@ -141,6 +141,7 @@ export type PreparedPieSeries = { halo: PreparedHaloOptions; }; }; + renderCustomShape?: PieSeries['renderCustomShape']; } & BasePreparedSeries; export type PreparedLineSeries = { diff --git a/src/plugins/d3/renderer/hooks/useShapes/pie/index.tsx b/src/plugins/d3/renderer/hooks/useShapes/pie/index.tsx index 7644b3b7..b24648bd 100644 --- a/src/plugins/d3/renderer/hooks/useShapes/pie/index.tsx +++ b/src/plugins/d3/renderer/hooks/useShapes/pie/index.tsx @@ -55,6 +55,7 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) { .style('stroke', (pieData) => pieData.borderColor) .style('stroke-width', (pieData) => pieData.borderWidth); + // Render halo appearing outside the hovered slice shapesSelection .selectAll('halo') .data((pieData) => { @@ -77,6 +78,7 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) { .attr('z-index', -1) .attr('visibility', getHaloVisibility); + // Render segments shapesSelection .selectAll(segmentSelector) .data((pieData) => pieData.segments) @@ -128,6 +130,19 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) { .attr('stroke-linecap', 'round') .style('fill', 'none'); + // Render custom shapes if defined + shapesSelection.each(function (d, index, nodes) { + const customShape = d.series.renderCustomShape?.({ + series: { + innerRadius: d.innerRadius, + }, + }); + + if (customShape) { + (nodes[index] as Element).append(customShape as Node); + } + }); + const eventName = `hover-shape.pie`; const hoverOptions = get(seriesOptions, 'pie.states.hover'); const inactiveOptions = get(seriesOptions, 'pie.states.inactive'); diff --git a/src/plugins/d3/utils/index.ts b/src/plugins/d3/utils/index.ts new file mode 100644 index 00000000..21d6fd59 --- /dev/null +++ b/src/plugins/d3/utils/index.ts @@ -0,0 +1,5 @@ +import {pieCenterText} from './pie-center-text'; + +export const CustomShapeRenderer = { + pieCenterText, +}; diff --git a/src/plugins/d3/utils/pie-center-text.ts b/src/plugins/d3/utils/pie-center-text.ts new file mode 100644 index 00000000..0d5ad6b6 --- /dev/null +++ b/src/plugins/d3/utils/pie-center-text.ts @@ -0,0 +1,31 @@ +import {create} from 'd3'; +import get from 'lodash/get'; + +import {getLabelsSize} from '../renderer/utils'; + +const MAX_FONT_SIZE = 64; + +export function pieCenterText(text: string, options?: {padding?: number}) { + if (!text) { + return undefined; + } + + const padding = get(options, 'padding', 12); + + return function (args: {series: {innerRadius: number}}) { + let fontSize = MAX_FONT_SIZE; + + const textSize = getLabelsSize({labels: [text], style: {fontSize: `${fontSize}px`}}); + fontSize = (fontSize * (args.series.innerRadius - padding) * 2) / textSize.maxWidth; + + const container = create('svg:g'); + container + .append('text') + .text(text) + .attr('text-anchor', 'middle') + .attr('alignment-baseline', 'middle') + .style('font-size', `${fontSize}px`); + + return container.node(); + }; +} diff --git a/src/types/widget-data/pie.ts b/src/types/widget-data/pie.ts index 3b09d5d9..8e1c2a37 100644 --- a/src/types/widget-data/pie.ts +++ b/src/types/widget-data/pie.ts @@ -1,3 +1,4 @@ +import {BaseType} from 'd3'; import {SeriesType} from '../../constants'; import type {BaseSeries, BaseSeriesData} from './base'; import {ChartKitWidgetLegend, RectLegendSymbolOptions} from './legend'; @@ -74,4 +75,10 @@ export type PieSeries = BaseSeries & { * */ connectorCurve?: ConnectorCurve; }; + /** + * Function for adding custom svg nodes for a series + * + * @return BaseType + * */ + renderCustomShape?: (args: {series: {innerRadius: number}}) => BaseType; };