Skip to content

Commit

Permalink
feat(xy): hide labels that protrude the bar geometry (#1233)
Browse files Browse the repository at this point in the history
The overflowConstraints prop now replaces the existing BarSeries.displayValueSettings.hideClippedValue prop extending the existing feature with the ability to hide labels that protrude the bar geometry.

fix #1234

BREAKING CHANGE: an API change is introduced: `hideClippedValue` is removed in favor of `overflowConstraints?: Array<LabelOverflowConstraint>;`. The array can contain one or multiple overflow constraints enumerated as `LabelOverflowConstraint`
  • Loading branch information
markov00 authored Jul 14, 2021
1 parent 1a36560 commit be1fb3d
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 64 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions integration/tests/bar_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ describe('Bar series stories', () => {
});
});
});
it('clip both geometry and chart area values', async () => {
await common.expectChartAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/bar-chart--with-value-label&knob-show single series=&knob-show value label=true&knob-alternating value label=&knob-contain value label within bar element=&knob-hide label if overflows chart edges=true&knob-hide label if overflows bar geometry=true&knob-debug=&knob-value font size=11&knob-value color=%23000&knob-offsetX=0&knob-offsetY=10&knob-data volume size=s&knob-split series=&knob-stacked series=&knob-chartRotation=0&knob-legend=right',
);
});
});

describe('functional accessors', () => {
Expand Down
12 changes: 11 additions & 1 deletion packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -645,9 +645,10 @@ export type Direction = $Values<typeof Direction>;

// @public (undocumented)
export interface DisplayValueSpec {
hideClippedValue?: boolean;
isAlternatingValueLabel?: boolean;
// @deprecated
isValueContainedInElement?: boolean;
overflowConstraints?: Array<LabelOverflowConstraint>;
showValueLabel?: boolean;
valueFormatter?: TickFormatter;
}
Expand Down Expand Up @@ -1093,6 +1094,15 @@ export type Key = CategoryKey;
// @public (undocumented)
export type LabelAccessor = (value: PrimitiveValue) => string;

// @public (undocumented)
export const LabelOverflowConstraint: Readonly<{
BarGeometry: "barGeometry";
ChartEdges: "chartEdges";
}>;

// @public (undocumented)
export type LabelOverflowConstraint = $Values<typeof LabelOverflowConstraint>;

// @public (undocumented)
export interface LayerValue {
depth: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Dimensions } from '../../../../../utils/dimensions';
import { BarGeometry } from '../../../../../utils/geometry';
import { Point } from '../../../../../utils/point';
import { Theme, TextAlignment } from '../../../../../utils/themes/theme';
import { LabelOverflowConstraint } from '../../../utils/specs';
import { renderText, wrapLines } from '../primitives/text';
import { renderDebugRect } from '../utils/debug';
import { withPanelTransform } from '../utils/panel_transform';
Expand Down Expand Up @@ -45,7 +46,7 @@ export function renderBarValues(ctx: CanvasRenderingContext2D, props: BarValuesP
if (!displayValue) {
continue;
}
const { text, fontSize, fontScale } = displayValue;
const { text, fontSize, fontScale, overflowConstraints, isValueContainedInElement } = displayValue;
let textLines = {
lines: [text],
width: displayValue.width,
Expand All @@ -60,23 +61,28 @@ export function renderBarValues(ctx: CanvasRenderingContext2D, props: BarValuesP
textOpacity: 1,
};

const { x, y, align, baseline, rect } = positionText(
const { x, y, align, baseline, rect, overflow } = positionText(
bars[i],
displayValue,
rotation,
barSeriesStyle.displayValue,
alignment,
);

if (displayValue.isValueContainedInElement) {
if (isValueContainedInElement) {
const width = rotation === 0 || rotation === 180 ? bars[i].width : bars[i].height;
textLines = wrapLines(ctx, textLines.lines[0], font, fontSize, width, 100);
}
if (displayValue.hideClippedValue && isOverflow(rect, renderingArea, rotation)) {
if (overflowConstraints.has(LabelOverflowConstraint.ChartEdges) && isOverflow(rect, renderingArea, rotation)) {
continue;
}
if (overflowConstraints.has(LabelOverflowConstraint.BarGeometry) && overflow) {
continue;
}
if (debug) {
renderDebugRect(ctx, rect);
withPanelTransform(ctx, panel, rotation, renderingArea, (ctx) => {
renderDebugRect(ctx, rect);
});
}
const { width, height } = textLines;
const linesLength = textLines.lines.length;
Expand Down Expand Up @@ -108,6 +114,7 @@ export function renderBarValues(ctx: CanvasRenderingContext2D, props: BarValuesP
}
}
}

function repositionTextLine(
origin: Point,
chartRotation: Rotation,
Expand Down Expand Up @@ -253,7 +260,7 @@ function positionText(
chartRotation: Rotation,
offsets: { offsetX: number; offsetY: number },
alignment?: TextAlignment,
): { x: number; y: number; align: TextAlign; baseline: TextBaseline; rect: Rect } {
): { x: number; y: number; align: TextAlign; baseline: TextBaseline; rect: Rect; overflow: boolean } {
const { offsetX, offsetY } = offsets;

const { alignmentOffsetX, alignmentOffsetY } = computeAlignmentOffset(geom, valueBox, chartRotation, alignment);
Expand All @@ -273,6 +280,7 @@ function positionText(
width: valueBox.width,
height: valueBox.height,
},
overflow: valueBox.width > geom.width || valueBox.height > geom.height,
};
}
case CHART_DIRECTION.RightToLeft: {
Expand All @@ -289,6 +297,7 @@ function positionText(
width: valueBox.height,
height: valueBox.width,
},
overflow: valueBox.height > geom.width || valueBox.width > geom.height,
};
}
case CHART_DIRECTION.LeftToRight: {
Expand All @@ -305,6 +314,7 @@ function positionText(
width: valueBox.height,
height: valueBox.width,
},
overflow: valueBox.height > geom.width || valueBox.width > geom.height,
};
}
case CHART_DIRECTION.BottomUp:
Expand All @@ -322,6 +332,7 @@ function positionText(
width: valueBox.width,
height: valueBox.height,
},
overflow: valueBox.width > geom.width || valueBox.height > geom.height,
};
}
}
Expand Down
28 changes: 14 additions & 14 deletions packages/charts/src/chart_types/xy_chart/rendering/bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { BandedAccessorType, BarGeometry } from '../../../utils/geometry';
import { BarSeriesStyle, DisplayValueStyle } from '../../../utils/themes/theme';
import { IndexedGeometryMap } from '../utils/indexed_geometry_map';
import { DataSeries, DataSeriesDatum, XYChartSeriesIdentifier } from '../utils/series';
import { BarStyleAccessor, DisplayValueSpec, StackMode } from '../utils/specs';
import { BarStyleAccessor, DisplayValueSpec, LabelOverflowConstraint, StackMode } from '../utils/specs';

/** @internal */
export function renderBars(
Expand Down Expand Up @@ -115,19 +115,14 @@ export function renderBars(
const x = xScaled + xScale.bandwidth * orderIndex + xScale.bandwidth / 2 - width / 2;

const originalY1Value = stackMode === StackMode.Percentage ? y1 - (y0 ?? 0) : initialY1;
const formattedDisplayValue =
displayValueSettings && displayValueSettings.valueFormatter
? displayValueSettings.valueFormatter(originalY1Value)
: undefined;
const formattedDisplayValue = displayValueSettings?.valueFormatter?.(originalY1Value);

// only show displayValue for even bars if showOverlappingValue
const displayValueText =
displayValueSettings && displayValueSettings.isAlternatingValueLabel && barGeometries.length % 2
? undefined
: formattedDisplayValue;
displayValueSettings?.isAlternatingValueLabel && barGeometries.length % 2 ? undefined : formattedDisplayValue;

const { displayValueWidth, fixedFontScale } = computeBoxWidth(
displayValueText || '',
displayValueText ?? '',
{ padding, fontSize, fontFamily, bboxCalculator, width },
displayValueSettings,
);
Expand All @@ -143,21 +138,26 @@ export function renderBars(
fixedFontScale,
fontSize,
);
const overflowConstraints: Set<LabelOverflowConstraint> = new Set(
displayValueSettings?.overflowConstraints ?? [
LabelOverflowConstraint.ChartEdges,
LabelOverflowConstraint.BarGeometry,
],
);

const hideClippedValue = displayValueSettings ? displayValueSettings.hideClippedValue : undefined;
// Based on rotation scale the width of the text box
const bboxWidthFactor = isHorizontalRotation ? textScalingFactor : 1;

const displayValue =
displayValueSettings && displayValueSettings.showValueLabel
const displayValue: BarGeometry['displayValue'] | undefined =
displayValueText && displayValueSettings?.showValueLabel
? {
fontScale: textScalingFactor,
fontSize: fixedFontScale,
text: displayValueText,
width: bboxWidthFactor * displayValueWidth,
height: textScalingFactor * fixedFontScale,
hideClippedValue,
isValueContainedInElement: displayValueSettings.isValueContainedInElement,
overflowConstraints,
isValueContainedInElement: displayValueSettings?.isValueContainedInElement ?? false,
}
: undefined;

Expand Down
41 changes: 35 additions & 6 deletions packages/charts/src/chart_types/xy_chart/utils/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,18 +373,47 @@ export type YDomainRange = YDomainBase & DomainRange & LogScaleOptions;
/** @public */
export type CustomXDomain = (DomainRange & Pick<LogScaleOptions, 'logBase'>) | OrdinalDomain;

/** @public */
export const LabelOverflowConstraint = Object.freeze({
BarGeometry: 'barGeometry' as const,
ChartEdges: 'chartEdges' as const,
});

/** @public */
export type LabelOverflowConstraint = $Values<typeof LabelOverflowConstraint>;

/** @public */
export interface DisplayValueSpec {
/** Show value label in chart element */
/**
* Show value label in chart element
* @defaultValue false
*/
showValueLabel?: boolean;
/** If value labels are shown, skips every other label */
/**
* If value labels are shown, skips every other label
* @defaultValue false
*/
isAlternatingValueLabel?: boolean;
/** Function for formatting values; will use axis tickFormatter if none specified */
/**
* Function for formatting values; will use axis tickFormatter if none specified
* @defaultValue false
*/
valueFormatter?: TickFormatter;
/** If true will contain value label within element, else dimensions are computed based on value */
/**
* If true will contain value label within element, else dimensions are computed based on value
* @deprecated This feature is deprecated and will be removed. Wrapping numbers into multiple lines
* is not considered a good practice.
* @defaultValue false
*/
isValueContainedInElement?: boolean;
/** If true will hide values that are clipped at chart edges */
hideClippedValue?: boolean;

/**
* An option to hide the value label on certain conditions:
* - `barGeometry` the label is not rendered if the width/height overflows the associated bar geometry,
* - `chartEdges` the label is not rendered if it overflows the chart projection area.
* @defaultValue ['barGeometry', 'chartEdges']
*/
overflowConstraints?: Array<LabelOverflowConstraint>;
}

/** @public */
Expand Down
7 changes: 4 additions & 3 deletions packages/charts/src/utils/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { $Values } from 'utility-types';

import { XYChartSeriesIdentifier } from '../chart_types/xy_chart/utils/series';
import { LabelOverflowConstraint } from '../chart_types/xy_chart/utils/specs';
import { Fill, Stroke } from '../geoms/types';
import { Color } from './common';
import { Dimensions } from './dimensions';
Expand Down Expand Up @@ -91,13 +92,13 @@ export interface BarGeometry {
};
color: Color;
displayValue?: {
fontScale?: number;
fontScale: number;
fontSize: number;
text: any;
width: number;
height: number;
hideClippedValue?: boolean;
isValueContainedInElement?: boolean;
overflowConstraints: Set<LabelOverflowConstraint>;
isValueContainedInElement: boolean;
};
seriesIdentifier: XYChartSeriesIdentifier;
value: GeometryValue;
Expand Down
Loading

0 comments on commit be1fb3d

Please sign in to comment.