Skip to content

Commit

Permalink
feat(D3 plugin): add halo to pie series
Browse files Browse the repository at this point in the history
  • Loading branch information
kuzmadom committed Dec 28, 2023
1 parent 49cad75 commit 4e1cfb2
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 37 deletions.
7 changes: 3 additions & 4 deletions src/plugins/d3/renderer/hooks/useSeries/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type {BaseTextStyle} from '../../../../../types';
import type {PointMarkerHalo} from '../../../../../types/widget-data/marker';
import type {BaseTextStyle, Halo} from '../../../../../types';

export const DEFAULT_LEGEND_SYMBOL_SIZE = 8;

Expand All @@ -13,8 +12,8 @@ export const DEFAULT_DATALABELS_STYLE: BaseTextStyle = {
fontColor: 'var(--d3-data-labels)',
};

export const DEFAULT_HALO_OPTIONS: Required<PointMarkerHalo> = {
export const DEFAULT_HALO_OPTIONS: Required<Halo> = {
enabled: true,
opacity: 0.25,
radius: 10,
size: 10,
};
15 changes: 13 additions & 2 deletions src/plugins/d3/renderer/hooks/useSeries/prepare-pie.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {PieSeries} from '../../../../../types';
import {ChartKitWidgetSeriesOptions, PieSeries} from '../../../../../types';
import {PreparedLegend, PreparedPieSeries, PreparedSeries} from './types';
import {scaleOrdinal} from 'd3';
import {DEFAULT_PALETTE} from '../../constants';
Expand All @@ -9,14 +9,16 @@ import {prepareLegendSymbol} from './utils';

type PreparePieSeriesArgs = {
series: PieSeries;
seriesOptions?: ChartKitWidgetSeriesOptions;
legend: PreparedLegend;
};

export function preparePieSeries(args: PreparePieSeriesArgs) {
const {series, legend} = args;
const {series, seriesOptions, legend} = args;
const dataNames = series.data.map((d) => d.name);
const colorScale = scaleOrdinal(dataNames, DEFAULT_PALETTE);
const stackId = getRandomCKId();
const seriesHoverState = get(seriesOptions, 'pie.states.hover');

const preparedSeries: PreparedSeries[] = series.data.map<PreparedPieSeries>((dataItem) => {
const result: PreparedPieSeries = {
Expand Down Expand Up @@ -49,6 +51,15 @@ export function preparePieSeries(args: PreparePieSeriesArgs) {
radius: series.radius || '100%',
innerRadius: series.innerRadius || 0,
stackId,
states: {
hover: {
halo: {
enabled: get(seriesHoverState, 'halo.enabled', true),
opacity: get(seriesHoverState, 'halo.opacity', 0.25),
size: get(seriesHoverState, 'halo.size', 10),
},
},
},
};

return result;
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/d3/renderer/hooks/useSeries/prepareSeries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export function prepareSeries(args: {
switch (type) {
case 'pie': {
return series.reduce<PreparedSeries[]>((acc, singleSeries) => {
acc.push(...preparePieSeries({series: singleSeries as PieSeries, legend}));
acc.push(
...preparePieSeries({series: singleSeries as PieSeries, seriesOptions, legend}),
);
return acc;
}, []);
}
Expand Down
23 changes: 13 additions & 10 deletions src/plugins/d3/renderer/hooks/useSeries/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export type LegendConfig = {
};
};

export type PreparedHaloOptions = {
enabled: boolean;
opacity: number;
size: number;
};

type BasePreparedSeries = {
color: string;
name: string;
Expand Down Expand Up @@ -123,6 +129,11 @@ export type PreparedPieSeries = {
distance: number;
connectorCurve: ConnectorCurve;
};
states: {
hover: {
halo: PreparedHaloOptions;
};
};
} & BasePreparedSeries;

export type PreparedLineSeries = {
Expand All @@ -149,11 +160,7 @@ export type PreparedLineSeries = {
radius: number;
borderWidth: number;
borderColor: string;
halo: {
enabled: boolean;
opacity: number;
radius: number;
};
halo: PreparedHaloOptions;
};
};
};
Expand Down Expand Up @@ -188,11 +195,7 @@ export type PreparedAreaSeries = {
radius: number;
borderWidth: number;
borderColor: string;
halo: {
enabled: boolean;
opacity: number;
radius: number;
};
halo: PreparedHaloOptions;
};
};
};
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/d3/renderer/hooks/useShapes/marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function renderMarker<T extends MarkerData>(
.attr('class', haloClassName)
.attr('d', (d) => {
const type = d.point.series.marker.states.normal.symbol;
const radius = d.point.series.marker.states.hover.halo.radius;
const radius = d.point.series.marker.states.hover.halo.size;
return getMarkerSymbol(type, radius);
})
.attr('fill', (d) => d.point.series.color)
Expand Down
40 changes: 38 additions & 2 deletions src/plugins/d3/renderer/hooks/useShapes/pie/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ type PreparePieSeriesArgs = {
svgContainer: SVGSVGElement | null;
};

export function getHaloVisibility(d: PieArcDatum<SegmentData>) {
const enabled = d.data.pie.halo.enabled && d.data.hovered;
return enabled ? '' : 'hidden';
}

export function PieSeriesShapes(args: PreparePieSeriesArgs) {
const {dispatcher, preparedData, seriesOptions, svgContainer} = args;
const hoverOptions = get(seriesOptions, 'pie.states.hover');
const inactiveOptions = get(seriesOptions, 'pie.states.inactive');
const ref = React.useRef<SVGGElement>(null);

React.useEffect(() => {
Expand All @@ -50,6 +57,28 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) {
.style('stroke', (pieData) => pieData.borderColor)
.style('stroke-width', (pieData) => pieData.borderWidth);

shapesSelection
.selectAll('halo')
.data((pieData) => {
if (pieData.halo.enabled) {
return pieData.segments;
}
return [];
})
.join('path')
.attr('d', (d) => {
const arcGenerator = arc<PieArcDatum<SegmentData>>()
.innerRadius(d.data.pie.innerRadius)
.outerRadius(d.data.pie.radius + d.data.pie.halo.size)
.cornerRadius(d.data.pie.borderRadius);
return arcGenerator(d);
})
.attr('class', b('halo'))
.attr('fill', (d) => d.data.color)
.attr('opacity', (d) => d.data.pie.halo.opacity)
.attr('z-index', -1)
.attr('visibility', getHaloVisibility);

shapesSelection
.selectAll(segmentSelector)
.data((pieData) => pieData.segments)
Expand Down Expand Up @@ -102,8 +131,6 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) {
.style('fill', 'none');

const eventName = `hover-shape.pie`;
const hoverOptions = get(seriesOptions, 'pie.states.hover');
const inactiveOptions = get(seriesOptions, 'pie.states.inactive');
svgElement
.on('mousemove', (e) => {
const datum = select<BaseType, PieArcDatum<SegmentData> | PieLabelData>(
Expand Down Expand Up @@ -143,6 +170,9 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) {

shapesSelection.datum((_d, index, list) => {
const pieSelection = select<BaseType, PreparedLineData>(list[index]);
const haloSelection = pieSelection.selectAll<BaseType, PieArcDatum<SegmentData>>(
`.${b('halo')}`,
);

pieSelection
.selectAll<BaseType, PieArcDatum<SegmentData>>(segmentSelector)
Expand All @@ -163,6 +193,12 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) {
}
return initialColor;
});

const currentSegmentHalo = haloSelection.nodes()[i];
select<BaseType, PieArcDatum<SegmentData>>(currentSegmentHalo).attr(
'visibility',
getHaloVisibility,
);
}

setActiveState<SegmentData>({
Expand Down
12 changes: 9 additions & 3 deletions src/plugins/d3/renderer/hooks/useShapes/pie/prepare-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ const getCenter = (
};

export function preparePieData(args: Args): PreparedPieData[] {
const {series: prepapredSeries, boundsWidth, boundsHeight} = args;
const {series: preparedSeries, boundsWidth, boundsHeight} = args;
const maxRadius = Math.min(boundsWidth, boundsHeight) / 2;

const groupedPieSeries = group(prepapredSeries, (pieSeries) => pieSeries.stackId);
const groupedPieSeries = group(preparedSeries, (pieSeries) => pieSeries.stackId);
return Array.from(groupedPieSeries).map<PreparedPieData>(([stackId, items]) => {
const series = items[0];
const {
center,
borderWidth,
Expand All @@ -51,7 +52,7 @@ export function preparePieData(args: Args): PreparedPieData[] {
radius: seriesRadius,
innerRadius: seriesInnerRadius,
dataLabels,
} = items[0];
} = series;
const radius =
calculateNumericProperty({value: seriesRadius, base: maxRadius}) ?? maxRadius;

Expand All @@ -67,6 +68,11 @@ export function preparePieData(args: Args): PreparedPieData[] {
borderRadius,
series: items[0],
connectorCurve: dataLabels.connectorCurve,
halo: {
enabled: series.states.hover.halo.enabled,
opacity: series.states.hover.halo.opacity,
size: series.states.hover.halo.size,
},
};

const segments = items.map<SegmentData>((item) => {
Expand Down
5 changes: 5 additions & 0 deletions src/plugins/d3/renderer/hooks/useShapes/pie/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,9 @@ export type PreparedPieData = {
borderColor: string;
series: PreparedPieSeries;
connectorCurve: ConnectorCurve;
halo: {
enabled: boolean;
opacity: number;
size: number;
};
};
9 changes: 9 additions & 0 deletions src/types/widget-data/halo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** The halo appearing around the hovered part of series(point in line-type series or slice in pie charts) */
export type Halo = {
/** Enable or disable the halo */
enabled?: boolean;
/** The opacity of halo */
opacity?: number;
/** The pixel size of the halo. Radius for point markers or width of the outside slice in pie charts. */
size?: number;
};
1 change: 1 addition & 0 deletions src/types/widget-data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from './line';
export * from './series';
export * from './title';
export * from './tooltip';
export * from './halo';

export type ChartKitWidgetData<T = any> = {
chart?: ChartKitWidgetChart;
Expand Down
9 changes: 0 additions & 9 deletions src/types/widget-data/marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,3 @@ export type PointMarkerOptions = {
/** The width of the point marker's border */
borderWidth?: number;
};

export type PointMarkerHalo = {
/** Enable or disable the halo appearing around the point */
enabled?: boolean;
/** The Opacity of the point halo */
opacity?: number;
/** The radius of the point halo */
radius?: number;
};
1 change: 0 additions & 1 deletion src/types/widget-data/pie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export type PieSeries<T = any> = BaseSeries & {
legend?: ChartKitWidgetLegend & {
symbol?: RectLegendSymbolOptions;
};

dataLabels?: BaseSeries['dataLabels'] & {
/**
* The distance of the data label from the pie's edge.
Expand Down
12 changes: 8 additions & 4 deletions src/types/widget-data/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import type {ScatterSeries, ScatterSeriesData} from './scatter';
import type {BarXSeries, BarXSeriesData} from './bar-x';
import type {LineSeries, LineSeriesData, LineMarkerOptions} from './line';
import type {BarYSeries, BarYSeriesData} from './bar-y';
import type {PointMarkerOptions, PointMarkerHalo} from './marker';
import type {PointMarkerOptions} from './marker';
import type {AreaSeries, AreaSeriesData} from './area';
import type {Halo} from './halo';

import {DashStyle, LineCap} from '../../constants';

Expand Down Expand Up @@ -146,7 +147,10 @@ export type ChartKitWidgetSeriesOptions = {
pie?: {
/** Options for the series states that provide additional styling information to the series. */
states?: {
hover?: BasicHoverState;
hover?: BasicHoverState & {
/** Options for the halo appearing outside the hovered slice */
halo?: Halo;
};
inactive?: BasicInactiveState;
};
};
Expand All @@ -168,7 +172,7 @@ export type ChartKitWidgetSeriesOptions = {
hover?: BasicHoverState & {
marker?: PointMarkerOptions & {
/** Options for the halo appearing around the hovered point */
halo?: PointMarkerHalo;
halo?: Halo;
};
};
inactive?: BasicInactiveState;
Expand Down Expand Up @@ -199,7 +203,7 @@ export type ChartKitWidgetSeriesOptions = {
hover?: BasicHoverState & {
marker?: PointMarkerOptions & {
/** Options for the halo appearing around the hovered point */
halo?: PointMarkerHalo;
halo?: Halo;
};
};
inactive?: BasicInactiveState;
Expand Down

0 comments on commit 4e1cfb2

Please sign in to comment.