Skip to content

Commit 99aac32

Browse files
committed
feat: simplify length limit implementation
1 parent eb3db71 commit 99aac32

6 files changed

Lines changed: 53 additions & 172 deletions

File tree

packages/charts/api/charts.api.md

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,8 @@ export interface AxisStyle {
299299
rotation: number;
300300
offset: TextOffset;
301301
alignment: TextAlignment;
302-
truncation?: Truncate;
302+
maxLength?: Pixels | string;
303+
truncate?: 'start' | 'middle' | 'end';
303304
};
304305
// (undocumented)
305306
tickLine: TickStyle;
@@ -3540,16 +3541,6 @@ export interface TreeNode extends AngleFromTo {
35403541
y1: TreeLevel;
35413542
}
35423543

3543-
// @public (undocumented)
3544-
export interface Truncate {
3545-
// (undocumented)
3546-
position?: 'end' | 'start' | 'middle';
3547-
// (undocumented)
3548-
relative?: Ratio;
3549-
// (undocumented)
3550-
width?: Pixels;
3551-
}
3552-
35533544
// @public
35543545
export interface UnaryAccessorFn<D extends BaseDatum = any, Return = any> {
35553546
// (undocumented)

packages/charts/src/chart_types/xy_chart/state/selectors/axis_tick_formatter.test.ts

Lines changed: 0 additions & 97 deletions
This file was deleted.

packages/charts/src/chart_types/xy_chart/state/selectors/axis_tick_formatter.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { fitText } from '../../../../common/text_utils';
1313
import { createCustomCachedSelector } from '../../../../state/create_selector';
1414
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_spec';
1515
import type { TextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator';
16-
import type { Rotation } from '../../../../utils/common';
16+
import { getPercentageValue, type Rotation } from '../../../../utils/common';
1717
import type { SpecId } from '../../../../utils/ids';
1818
import type { AxisStyle } from '../../../../utils/themes/theme';
1919
import { defaultTickFormatter, isXDomain } from '../../utils/axis_utils';
@@ -26,28 +26,26 @@ export type AxisLabelFormatter<V = unknown> = (value: V) => string;
2626
/** @internal */
2727
export type AxisLabelFormatters = { x: Map<SpecId, AxisLabelFormatter>; y: Map<SpecId, AxisLabelFormatter> };
2828

29+
function resolveSize(value: number | string | undefined, containerWidth: number): number | undefined {
30+
if (value === undefined) return undefined;
31+
32+
const resolved = getPercentageValue(value, containerWidth, NaN);
33+
if (!Number.isFinite(resolved) || resolved < 0) return undefined;
34+
35+
return resolved;
36+
}
37+
2938
/** @internal */
3039
export function withTickLabelTruncation(
3140
measure: TextMeasure,
3241
tickLabel: AxisStyle['tickLabel'],
33-
width: number,
42+
containerWidth: number,
3443
): <V>(formatter: AxisLabelFormatter<V>) => AxisLabelFormatter<V> {
35-
const { truncation, fontSize, fontStyle, fontFamily, fill } = tickLabel;
36-
37-
let maxWidth;
44+
const { fontSize, fontStyle, fontFamily, fill, truncate, maxLength } = tickLabel;
3845

39-
if (truncation?.width === undefined && truncation?.relative !== undefined) {
40-
maxWidth = truncation.relative * width;
41-
}
42-
if (truncation?.width !== undefined && truncation?.relative === undefined) {
43-
maxWidth = truncation.width;
44-
}
45-
if (truncation?.width !== undefined && truncation?.relative !== undefined) {
46-
maxWidth = Math.max(truncation.width, truncation.relative * width);
47-
}
48-
if (!maxWidth || maxWidth <= 0 || !Number.isFinite(maxWidth)) return (formatter) => formatter;
46+
const maxWidth = resolveSize(maxLength, containerWidth);
4947

50-
const position = truncation?.position ?? 'end';
48+
if (maxWidth === undefined || maxWidth <= 0) return (formatter) => formatter;
5149

5250
const font: Font = {
5351
fontStyle: fontStyle ?? 'normal',
@@ -57,7 +55,7 @@ export function withTickLabelTruncation(
5755
textColor: fill,
5856
};
5957

60-
return (formatter) => (value) => fitText(measure, formatter(value), maxWidth, fontSize, font, position).text;
58+
return (formatter) => (value) => fitText(measure, formatter(value), maxWidth, fontSize, font, truncate ?? 'end').text;
6159
}
6260

6361
/** @internal */

packages/charts/src/common/text_utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import { ELLIPSIS } from '../renderers/canvas/primitives/text';
1515
import { integerSnap, monotonicHillClimb } from '../solvers/monotonic_hill_climb';
1616
import type { TextMeasure } from '../utils/bbox/canvas_text_bbox_calculator';
1717
import type { Datum } from '../utils/common';
18-
import type { Truncate } from '../utils/themes/theme';
18+
19+
/** @internal */
20+
export type Truncate = 'start' | 'middle' | 'end';
1921

2022
const FONT_WEIGHTS_NUMERIC = [100, 200, 300, 400, 450, 500, 600, 700, 800, 900] as const;
2123
const FONT_WEIGHTS_ALPHA = ['normal', 'bold', 'lighter', 'bolder', 'inherit', 'initial', 'unset'] as const;
@@ -154,7 +156,7 @@ export function fitText(
154156
allottedWidth: number,
155157
fontSize: number,
156158
font: Font,
157-
position: Truncate['position'] = 'end',
159+
position: Truncate = 'end',
158160
) {
159161
const truncateText = (build: (k: number) => string, min: number) => {
160162
return truncate(measure, desiredText, allottedWidth, fontSize, font, build, min);

packages/charts/src/utils/themes/theme.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -159,23 +159,6 @@ export interface Opacity {
159159
opacity: number;
160160
}
161161

162-
/** @public */
163-
export interface Truncate {
164-
/**
165-
* Absolute maximum label width in pixels. When both `width` and `relative` are provided, the greater value is used.
166-
*/
167-
width?: Pixels;
168-
/**
169-
* Relative maximum label width in `[0, 1]` of chart container width.
170-
*/
171-
relative?: Ratio;
172-
/**
173-
* Ellipsis placement when truncation is applied.
174-
* @defaultValue 'end'
175-
*/
176-
position?: 'end' | 'start' | 'middle';
177-
}
178-
179162
/** @public */
180163
export interface AxisStyle {
181164
axisTitle: TextStyle & Visible;
@@ -193,10 +176,14 @@ export interface AxisStyle {
193176
offset: TextOffset;
194177
alignment: TextAlignment;
195178
/**
196-
* When set, tick labels are truncated with an ellipsis so their measured width
197-
* does not exceed `width` (unrotated text-direction pixels).
179+
* The maximum size of the tick label.
180+
* If a number, it is in pixels. If a string, it is relative to the container width, e.g. '20%'.
181+
*/
182+
maxLength?: Pixels | string;
183+
/**
184+
* The position of the ellipsis when the tick label is truncated. Defaults to 'end'.
198185
*/
199-
truncation?: Truncate;
186+
truncate?: 'start' | 'middle' | 'end';
200187
};
201188
tickLine: TickStyle;
202189
gridLine: {

storybook/stories/axes/16_tick_label_truncation.story.tsx

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
* Side Public License, v 1.
77
*/
88

9-
import { boolean, number, select } from '@storybook/addon-knobs';
9+
import { select, text } from '@storybook/addon-knobs';
1010
import React from 'react';
1111

12-
import type { Truncate } from '@elastic/charts';
1312
import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '@elastic/charts';
1413

1514
import type { ChartsStory } from '../../types';
@@ -24,33 +23,34 @@ const data = [
2423
{ x: 'com.example.something.worker.01', y: 4 },
2524
];
2625

26+
type EllipsisPosition = 'start' | 'middle' | 'end';
27+
28+
/** Knob helper: empty → unset; plain number → px; value like `25` or `25.5 %` → percentage string. */
29+
function parseThemeSize(raw: string): number | string | undefined {
30+
const s = raw.trim();
31+
if (!s) return undefined;
32+
const pct = s.match(/^([\d.]+)\s*%$/);
33+
if (pct) return `${pct[1]}%`;
34+
const n = Number(s);
35+
if (Number.isFinite(n)) return n;
36+
return s;
37+
}
38+
2739
export const Example: ChartsStory = (_, { title, description }) => {
28-
const widthPx = number('Truncation width', 120, { min: 0, max: 400, step: 10 });
29-
const relative = number('Truncation relative', 0.3, { min: 0, max: 1, step: 0.05 });
30-
const position = select<NonNullable<Truncate['position']>>(
31-
'Truncation position',
32-
{ end: 'end', start: 'start', middle: 'middle' },
33-
'middle',
34-
);
40+
const maxLength = parseThemeSize(text('maxLength', '120'));
41+
const truncate = select<EllipsisPosition>('truncate', { end: 'end', start: 'start', middle: 'middle' }, 'middle');
42+
const rotation = select('Chart rotation', { '0°': 0, '90°': 90, '-90°': -90 }, 90);
43+
44+
const tickLabel = {
45+
...(maxLength !== undefined ? { maxLength } : {}),
46+
truncate,
47+
};
3548

3649
return (
3750
<Chart title={title} description={description}>
38-
<Settings baseTheme={useBaseTheme()} rotation={90} />
39-
<Axis id="bottom" position={Position.Bottom} title="Count" />
40-
<Axis
41-
id="left"
42-
position={Position.Left}
43-
title="Team"
44-
style={{
45-
tickLabel: {
46-
truncation: {
47-
width: widthPx,
48-
relative,
49-
position,
50-
},
51-
},
52-
}}
53-
/>
51+
<Settings baseTheme={useBaseTheme()} rotation={rotation} />
52+
<Axis id="bottom" position={Position.Bottom} title="Count" style={{ tickLabel }} />
53+
<Axis id="left" position={Position.Left} title="Team" style={{ tickLabel }} />
5454

5555
<BarSeries
5656
id="bars"

0 commit comments

Comments
 (0)