diff --git a/src/charts/PieChart/PieChart.stories.ts b/src/charts/PieChart/PieChart.stories.ts index 0aa01286..d538f8f3 100644 --- a/src/charts/PieChart/PieChart.stories.ts +++ b/src/charts/PieChart/PieChart.stories.ts @@ -39,6 +39,23 @@ export function Labels() { return root; } +export function OverlappingLabels() { + const root = document.createElement('div'); + + new PieChart( + root, + { + labels: ['Big Slice', 11231231, 'Test the string', new Date(), 124124124], + series: [96, 1, 1, 1, 1] + }, + { + preventOverlappingLabelOffset: 12 + } + ); + + return root; +} + export function LabelInterpolation() { const root = document.createElement('div'); const data = { diff --git a/src/charts/PieChart/PieChart.ts b/src/charts/PieChart/PieChart.ts index 084957b8..a4f24267 100644 --- a/src/charts/PieChart/PieChart.ts +++ b/src/charts/PieChart/PieChart.ts @@ -70,7 +70,9 @@ const defaultOptions = { // Label direction can be 'neutral', 'explode' or 'implode'. The labels anchor will be positioned based on those settings as well as the fact if the labels are on the right or left side of the center of the chart. Usually explode is useful when labels are positioned far away from the center. labelDirection: 'neutral', // If true empty values will be ignored to avoid drawing unnecessary slices and labels - ignoreEmptyValues: false + ignoreEmptyValues: false, + // If Nonzero check if a label has overlapping text then move it the number of pixels up and left (Should be half of label font size + 1 but you can tweak it as you prefer) + preventOverlappingLabelOffset: 0 }; /** @@ -199,6 +201,7 @@ export class PieChart extends BaseChart { const normalizedData = normalizeData(data); const seriesGroups: Svg[] = []; let labelsGroup: Svg; + const labelPositions: any[] = []; let labelRadius: number; let startAngle = options.startAngle; @@ -387,7 +390,7 @@ export class PieChart extends BaseChart { // If we need to show labels we need to add the label for this slice now if (options.showLabel) { - let labelPosition; + let labelPosition: any; if (data.series.length === 1) { // If we have only 1 series, we can position the label in the center of the pie @@ -421,6 +424,31 @@ export class PieChart extends BaseChart { ); if (interpolatedValue || interpolatedValue === 0) { + if (options.preventOverlappingLabelOffset) { + const textSize = options.preventOverlappingLabelOffset; + + const labelMover = (lp: any, item: any) => { + const length = // Tested with all three data types string, number, and date. + ((normalizedData.labels[index] + '') as string)?.length ?? 1; // Default to 1 character length + + if ( + lp.y > item.y - textSize && + lp.y < item.y + textSize && + lp.x > item.x - length * textSize && + lp.x < item.x + length * textSize + ) { + lp.y -= textSize; + lp.x -= textSize; + labelMover(lp, item); + } + }; + + labelPositions.forEach(item => { + labelMover(labelPosition, item); + }); + labelPositions.push(labelPosition); + } + const labelElement = labelsGroup .elem( 'text', diff --git a/src/charts/PieChart/PieChart.types.ts b/src/charts/PieChart/PieChart.types.ts index cd7882ae..08165eb5 100644 --- a/src/charts/PieChart/PieChart.types.ts +++ b/src/charts/PieChart/PieChart.types.ts @@ -82,6 +82,10 @@ export interface PieChartOptions extends Omit { * If true empty values will be ignored to avoid drawing unnecessary slices and labels */ ignoreEmptyValues?: boolean; + /** + * If nonzero labels will not overlap. + */ + preventOverlappingLabelOffset?: number; } export type PieChartOptionsWithDefaults = RequiredKeys< @@ -93,7 +97,8 @@ export type PieChartOptionsWithDefaults = RequiredKeys< | 'labelOffset' | 'labelPosition' | 'labelInterpolationFnc' - | 'labelDirection', + | 'labelDirection' + | 'preventOverlappingLabelOffset', 'classNames' >;