Skip to content

Commit 3a6f17a

Browse files
committed
WIP
1 parent 18c67ec commit 3a6f17a

File tree

10 files changed

+192
-15
lines changed

10 files changed

+192
-15
lines changed

packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { SmallMultiplesSpec } from '../../../specs';
1010
import { Position } from '../../../utils/common';
11-
import { innerPad, outerPad, PerSideDistance } from '../../../utils/dimensions';
11+
import { Dimensions, innerPad, outerPad, PerSideDistance } from '../../../utils/dimensions';
1212
import { AxisId } from '../../../utils/ids';
1313
import { AxisStyle, Theme } from '../../../utils/themes/theme';
1414
import { AxesTicksDimensions } from '../state/selectors/compute_axis_ticks_dimensions';
@@ -37,11 +37,7 @@ const getAxisSizeForLabel = (
3737
const maxAxisGirth = axisDimension + (tickLabel.visible ? allLayersGirth : 0);
3838
// gives space to longer labels: if vertical use half of the label height, if horizontal, use half of the max label (not ideal)
3939
// don't overflow when the multiTimeAxis layer is used.
40-
const maxLabelBoxHalfLength = isVerticalAxis(axisSpec.position)
41-
? maxLabelBboxHeight / 2
42-
: axisSpec.timeAxisLayerCount > 0
43-
? 0
44-
: maxLabelBboxWidth / 2;
40+
const maxLabelBoxHalfLength = isVerticalAxis(axisSpec.position) ? maxLabelBboxHeight / 2 : 0;
4541
return horizontal
4642
? {
4743
top: axisSpec.position === Position.Top ? maxAxisGirth + chartMargins.top : 0,
@@ -59,12 +55,17 @@ const getAxisSizeForLabel = (
5955

6056
/** @internal */
6157
export function getAxesDimensions(
58+
parentDimensions: Dimensions,
6259
theme: Theme,
6360
axisDimensions: AxesTicksDimensions,
6461
axesStyles: Map<AxisId, AxisStyle | null>,
6562
axisSpecs: AxisSpec[],
6663
smSpec: SmallMultiplesSpec | null,
6764
): PerSideDistance & { margin: { left: number } } {
65+
const verticalAxesCount =
66+
axisSpecs.reduce((count, spec) => {
67+
return count + (isVerticalAxis(spec.position) ? 1 : 0);
68+
}, 0) * 2;
6869
const sizes = [...axisDimensions].reduce(
6970
(acc, [id, tickLabelBounds]) => {
7071
const axisSpec = getSpecsById<AxisSpec>(axisSpecs, id);
@@ -74,8 +75,8 @@ export function getAxesDimensions(
7475
if (isVerticalAxis(axisSpec.position)) {
7576
acc.axisLabelOverflow.top = Math.max(acc.axisLabelOverflow.top, top);
7677
acc.axisLabelOverflow.bottom = Math.max(acc.axisLabelOverflow.bottom, bottom);
77-
acc.axisMainSize.left += left;
78-
acc.axisMainSize.right += right;
78+
acc.axisMainSize.left += Math.min(left, parentDimensions.width / verticalAxesCount);
79+
acc.axisMainSize.right += Math.min(right, parentDimensions.width / verticalAxesCount);
7980
} else {
8081
// find the max half label size to accommodate the left/right labels
8182
acc.axisMainSize.top += top;

packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface AxisProps {
3030
debug: boolean;
3131
renderingArea: Dimensions;
3232
layerGirth: number;
33+
maxLabelSize: number;
3334
}
3435

3536
/** @internal */

packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
*/
88

99
import { AxisProps } from '.';
10+
import { measureText } from '../../../../../utils/bbox/canvas_text_bbox_calculator';
1011
import { Position } from '../../../../../utils/common';
12+
import { wrapText } from '../../../../../utils/text/wrap';
1113
import { AxisTick, getTickLabelPosition } from '../../../utils/axis_utils';
1214
import { renderText } from '../primitives/text';
1315
import { renderDebugRectCenterRotated } from '../utils/debug';
@@ -19,7 +21,7 @@ export function renderTickLabel(
1921
ctx: CanvasRenderingContext2D,
2022
tick: AxisTick,
2123
showTicks: boolean,
22-
{ axisSpec: { position, timeAxisLayerCount }, dimension, size, debug, axisStyle }: AxisProps,
24+
{ axisSpec: { position, timeAxisLayerCount }, dimension, size, debug, axisStyle, maxLabelSize }: AxisProps,
2325
layerGirth: number,
2426
) {
2527
const labelStyle = axisStyle.tickLabel;
@@ -36,7 +38,22 @@ export function renderTickLabel(
3638
);
3739

3840
const center = { x: tickLabelProps.x + tickLabelProps.offsetX, y: tickLabelProps.y + tickLabelProps.offsetY };
39-
41+
const textMeasure = measureText(ctx);
42+
const wrappedText = wrapText(
43+
tick.label,
44+
{
45+
fontFamily: labelStyle.fontFamily,
46+
fontStyle: labelStyle.fontStyle ?? 'normal',
47+
fontVariant: 'normal',
48+
fontWeight: 'normal',
49+
textColor: labelStyle.fill,
50+
},
51+
labelStyle.fontSize,
52+
maxLabelSize,
53+
1,
54+
textMeasure,
55+
'en', // TODO
56+
);
4057
if (debug) {
4158
const { maxLabelBboxWidth, maxLabelBboxHeight, maxLabelTextWidth: width, maxLabelTextHeight: height } = dimension;
4259
// full text container
@@ -52,7 +69,7 @@ export function renderTickLabel(
5269
renderText(
5370
ctx,
5471
center,
55-
tick.label,
72+
wrappedText[0] ?? '',
5673
{
5774
fontFamily: labelStyle.fontFamily,
5875
fontStyle: labelStyle.fontStyle ?? 'normal',

packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/panels.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export function renderPanelSubstrates(ctx: CanvasRenderingContext2D, props: Axes
6969
dimension,
7070
visibleTicks: ticks,
7171
parentSize,
72+
maxLabelSize,
7273
} = geometry;
7374
const axisSpec = getSpecsById<AxisSpec>(axesSpecs, id);
7475

@@ -105,6 +106,7 @@ export function renderPanelSubstrates(ctx: CanvasRenderingContext2D, props: Axes
105106
debug,
106107
renderingArea,
107108
layerGirth,
109+
maxLabelSize,
108110
},
109111
locale,
110112
);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import { axisSpecsLookupSelector } from './get_specs';
1212
import { getVisibleTickSetsSelector } from './visible_ticks';
1313
import { createCustomCachedSelector } from '../../../../state/create_selector';
1414
import { computeSmallMultipleScalesSelector } from '../../../../state/selectors/compute_small_multiple_scales';
15+
import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions';
1516
import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme';
1617
import { getAxesGeometries } from '../../utils/axis_utils';
1718

1819
/** @internal */
1920
export const computeAxesGeometriesSelector = createCustomCachedSelector(
2021
[
22+
getChartContainerDimensionsSelector,
2123
computeChartDimensionsSelector,
2224
getChartThemeSelector,
2325
axisSpecsLookupSelector,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ function getVisibleTicks(
151151

152152
const { showOverlappingTicks, showOverlappingLabels, position } = axisSpec;
153153
const requiredSpace = isVerticalAxis(position) ? labelBox.maxLabelBboxHeight / 2 : labelBox.maxLabelBboxWidth / 2;
154-
const bypassOverlapCheck = showOverlappingLabels || isMultilayerTimeAxis;
154+
const bypassOverlapCheck = scale.type === ScaleType.Ordinal || showOverlappingLabels || isMultilayerTimeAxis;
155155
return bypassOverlapCheck
156156
? allTicks
157157
: [...allTicks]

packages/charts/src/chart_types/xy_chart/utils/axis_utils.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { isHorizontalAxis, isVerticalAxis } from './axis_type_utils';
1010
import { computeXScale, computeYScales } from './scales';
1111
import { SmallMultipleScales, hasSMDomain, getPanelSize } from '../../../common/panel_utils';
1212
import { ScaleBand, ScaleContinuous } from '../../../scales';
13+
import { isContinuousScale } from '../../../scales/types';
1314
import { AxisSpec, SettingsSpec } from '../../../specs';
1415
import {
1516
degToRad,
@@ -261,13 +262,15 @@ export const getAllAxisLayersGirth = (
261262

262263
/** @internal */
263264
export function getPosition(
265+
parentDimension: Dimensions,
264266
{ chartDimensions }: { chartDimensions: Dimensions },
265267
chartMargins: PerSideDistance,
266268
{ axisTitle, axisPanelTitle, tickLine, tickLabel }: AxisStyle,
267269
{ title, position, hide, timeAxisLayerCount }: AxisSpec,
268270
{ maxLabelBboxHeight, maxLabelBboxWidth }: TickLabelBounds,
269271
smScales: SmallMultipleScales,
270272
{ top: cumTopSum, bottom: cumBottomSum, left: cumLeftSum, right: cumRightSum }: PerSideDistance,
273+
verticalAxisCount: number,
271274
) {
272275
const tickDimension = shouldShowTicks(tickLine, hide) ? tickLine.size + tickLine.padding : 0;
273276
const labelPaddingSum = tickLabel.visible ? innerPad(tickLabel.padding) + outerPad(tickLabel.padding) : 0;
@@ -276,7 +279,8 @@ export function getPosition(
276279
const scaleBand = vertical ? smScales.vertical : smScales.horizontal;
277280
const panelTitleDimension = hasSMDomain(scaleBand) ? getTitleDimension(axisPanelTitle) : 0;
278281
const maxLabelBboxGirth = tickLabel.visible ? (vertical ? maxLabelBboxWidth : maxLabelBboxHeight) : 0;
279-
const shownLabelSize = getAllAxisLayersGirth(timeAxisLayerCount, maxLabelBboxGirth, !vertical);
282+
const layerGrith = getAllAxisLayersGirth(timeAxisLayerCount, maxLabelBboxGirth, !vertical);
283+
const shownLabelSize = vertical ? Math.min(parentDimension.width / (verticalAxisCount * 2), layerGrith) : layerGrith;
280284
const parallelSize = labelPaddingSum + shownLabelSize + tickDimension + titleDimension + panelTitleDimension;
281285
return {
282286
leftIncrement: position === Position.Left ? parallelSize + chartMargins.left : 0,
@@ -316,32 +320,39 @@ export interface AxisGeometry {
316320
};
317321
dimension: TickLabelBounds;
318322
visibleTicks: AxisTick[];
323+
maxLabelSize: number;
319324
}
320325

321326
/** @internal */
322327
export function getAxesGeometries(
328+
parentDimension: Dimensions,
323329
chartDims: { chartDimensions: Dimensions; leftMargin: number },
324330
{ chartPaddings, chartMargins, axes: sharedAxesStyle }: Theme,
325331
axisSpecs: Map<AxisId, AxisSpec>,
326332
axesStyles: Map<AxisId, AxisStyle | null>,
327333
smScales: SmallMultipleScales,
328334
visibleTicksSet: Map<AxisId, Projection>,
329335
): AxisGeometry[] {
336+
const verticalAxesCount = [...axisSpecs.values()].reduce((count, spec) => {
337+
return count + (isVerticalAxis(spec.position) ? 1 : 0);
338+
}, 0);
330339
const panel = getPanelSize(smScales);
331340
return [...visibleTicksSet].reduce(
332-
(acc: PerSideDistance & { geoms: AxisGeometry[] }, [axisId, { ticks, labelBox }]: [AxisId, Projection]) => {
341+
(acc: PerSideDistance & { geoms: AxisGeometry[] }, [axisId, { ticks, labelBox, scale }]: [AxisId, Projection]) => {
333342
const axisSpec = axisSpecs.get(axisId);
334343
if (axisSpec) {
335344
const vertical = isVerticalAxis(axisSpec.position);
336345
const axisStyle = axesStyles.get(axisId) ?? sharedAxesStyle;
337346
const { dimensions, topIncrement, bottomIncrement, leftIncrement, rightIncrement } = getPosition(
347+
parentDimension,
338348
chartDims,
339349
chartMargins,
340350
axisStyle,
341351
axisSpec,
342352
labelBox,
343353
smScales,
344354
acc,
355+
verticalAxesCount,
345356
);
346357
acc.top += topIncrement;
347358
acc.bottom += bottomIncrement;
@@ -357,6 +368,12 @@ export function getAxesGeometries(
357368
width: labelBox.isHidden ? 0 : vertical ? dimensions.width : panel.width,
358369
height: labelBox.isHidden ? 0 : vertical ? panel.height : dimensions.height,
359370
},
371+
maxLabelSize:
372+
vertical && !isContinuousScale(scale)
373+
? parentDimension.width / (verticalAxesCount * 2)
374+
: isContinuousScale(scale)
375+
? Infinity
376+
: scale.step,
360377
});
361378
} else {
362379
throw new Error(`Cannot compute scale for axis spec ${axisId}`); // todo move this feedback as upstream as possible

packages/charts/src/chart_types/xy_chart/utils/dimensions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface ChartDimensions {
4040
axisSpecs: AxisSpec[],
4141
smSpec: SmallMultiplesSpec | null,
4242
): ChartDimensions {
43-
const axesDimensions = getAxesDimensions(theme, axisTickDimensions, axesStyles, axisSpecs, smSpec);
43+
const axesDimensions = getAxesDimensions(parentDimensions, theme, axisTickDimensions, axesStyles, axisSpecs, smSpec);
4444
const chartWidth = parentDimensions.width - axesDimensions.left - axesDimensions.right;
4545
const chartHeight = parentDimensions.height - axesDimensions.top - axesDimensions.bottom;
4646
const pad = theme.chartPaddings;
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
import { range } from 'lodash';
10+
import React from 'react';
11+
12+
import {
13+
Axis,
14+
BarSeries,
15+
Chart,
16+
GroupBy,
17+
Position,
18+
ScaleType,
19+
Settings,
20+
SmallMultiples,
21+
DataGenerator,
22+
} from '@elastic/charts';
23+
import { getRandomNumberGenerator } from '@elastic/charts/src/mocks/utils';
24+
25+
import { ChartsStory } from '../../types';
26+
import { useBaseTheme } from '../../use_base_theme';
27+
const ng = getRandomNumberGenerator();
28+
const rng = new DataGenerator();
29+
const data = rng.generateSMGroupedSeries(3, 2, () => {
30+
return rng.generateSimpleSeries(10).flatMap((d) =>
31+
range(0, 6, 1).map((y) => {
32+
return {
33+
x: `category-longer-then-${d.x}`,
34+
y: ng(0, 1000),
35+
};
36+
}),
37+
);
38+
});
39+
console.log(data);
40+
export const Example: ChartsStory = (_, { title, description }) => {
41+
return (
42+
<div style={{ border: '1px solid black', position: 'relative', width: '100%', height: '100%' }}>
43+
<div style={{ resize: 'both', border: '1x solid black', overflow: 'hidden', width: '100%', height: '50%' }}>
44+
<Chart title={title} description={description}>
45+
<Settings
46+
baseTheme={useBaseTheme()}
47+
rotation={90}
48+
theme={{
49+
chartMargins: { top: 0, left: 0, right: 0, bottom: 0 },
50+
chartPaddings: { top: 0, left: 0, right: 0, bottom: 0 },
51+
axes: {
52+
axisTitle: {
53+
padding: 0,
54+
},
55+
axisPanelTitle: {
56+
padding: 0,
57+
},
58+
tickLabel: {
59+
padding: 0,
60+
},
61+
tickLine: {
62+
padding: 0,
63+
},
64+
},
65+
}}
66+
/>
67+
68+
<Axis id="left" position={Position.Left} />
69+
<Axis id="right" position={Position.Right} />
70+
<Axis id="bottom" position={Position.Bottom} />
71+
<BarSeries
72+
id="horizontal bar chart"
73+
xScaleType={ScaleType.Ordinal}
74+
yScaleType={ScaleType.Linear}
75+
xAccessor="x"
76+
yAccessors={['y']}
77+
data={[
78+
{ x: 'artifacts.elastic.co', y: 2, g: 'cluster a' },
79+
{ x: 'www.elastic.co', y: 7, g: 'cluster a' },
80+
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 3, g: 'cluster a' },
81+
{ x: 'docker.elastic.co', y: 6, g: 'cluster a' },
82+
83+
{ x: 'artifacts.elastic.co', y: 10, g: 'cluster B' },
84+
{ x: 'www.elastic.co', y: 17, g: 'cluster B' },
85+
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 23, g: 'cluster B' },
86+
{ x: 'docker.elastic.co', y: 8, g: 'cluster B' },
87+
]}
88+
/>
89+
{/* <GroupBy id="g" by={(spec, datum) => datum.g} sort="alphaAsc" />
90+
<SmallMultiples splitHorizontally="g" /> */}
91+
</Chart>
92+
</div>
93+
<div style={{ resize: 'both', overflow: 'hidden', width: '100%', height: '50%' }}>
94+
<Chart title={title} description={description}>
95+
<Settings
96+
baseTheme={useBaseTheme()}
97+
rotation={90}
98+
theme={{
99+
chartMargins: { top: 0, left: 0, right: 0, bottom: 0 },
100+
chartPaddings: { top: 0, left: 0, right: 0, bottom: 0 },
101+
axes: {
102+
axisTitle: {
103+
padding: 0,
104+
},
105+
axisPanelTitle: {
106+
padding: 0,
107+
},
108+
tickLabel: {
109+
padding: 0,
110+
},
111+
tickLine: {
112+
padding: 0,
113+
},
114+
},
115+
}}
116+
/>
117+
118+
<Axis id="bottom" position={Position.Bottom} />
119+
<Axis id="left" position={Position.Left} />
120+
{/* <Axis id="right" position={Position.Right} /> */}
121+
<BarSeries
122+
id="horizontal bar chart"
123+
xScaleType={ScaleType.Ordinal}
124+
yScaleType={ScaleType.Linear}
125+
xAccessor="x"
126+
yAccessors={['y']}
127+
data={data}
128+
/>
129+
<GroupBy id="h" by={(spec, datum) => datum.h} sort="alphaAsc" />
130+
<GroupBy id="v" by={(spec, datum) => datum.v} sort="alphaAsc" />
131+
<SmallMultiples splitHorizontally="h" splitVertically="v" />
132+
</Chart>
133+
</div>
134+
</div>
135+
);
136+
};

storybook/stories/bar/bars.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,4 @@ export { Example as testDualYAxis } from './49_test_dual_axis.story';
7272
export { Example as testUseDefaultGroupDomain } from './56_test_use_dfl_gdomain.story';
7373
export { Example as testRectBorder } from './57_test_rect_border_bars.story';
7474
export { Example as dataValue } from './58_data_values.story';
75+
export { Example as horizontalBarChart } from './a1_horizontal_bars.story';

0 commit comments

Comments
 (0)