Skip to content

Commit d78a678

Browse files
committed
WIP
1 parent 3b3aa03 commit d78a678

File tree

10 files changed

+174
-15
lines changed

10 files changed

+174
-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,
@@ -252,13 +253,15 @@ export const getAllAxisLayersGirth = (
252253

253254
/** @internal */
254255
export function getPosition(
256+
parentDimension: Dimensions,
255257
{ chartDimensions }: { chartDimensions: Dimensions },
256258
chartMargins: PerSideDistance,
257259
{ axisTitle, axisPanelTitle, tickLine, tickLabel }: AxisStyle,
258260
{ title, position, hide, timeAxisLayerCount }: AxisSpec,
259261
{ maxLabelBboxHeight, maxLabelBboxWidth }: TickLabelBounds,
260262
smScales: SmallMultipleScales,
261263
{ top: cumTopSum, bottom: cumBottomSum, left: cumLeftSum, right: cumRightSum }: PerSideDistance,
264+
verticalAxisCount: number,
262265
) {
263266
const tickDimension = shouldShowTicks(tickLine, hide) ? tickLine.size + tickLine.padding : 0;
264267
const labelPaddingSum = tickLabel.visible ? innerPad(tickLabel.padding) + outerPad(tickLabel.padding) : 0;
@@ -267,7 +270,8 @@ export function getPosition(
267270
const scaleBand = vertical ? smScales.vertical : smScales.horizontal;
268271
const panelTitleDimension = hasSMDomain(scaleBand) ? getTitleDimension(axisPanelTitle) : 0;
269272
const maxLabelBboxGirth = tickLabel.visible ? (vertical ? maxLabelBboxWidth : maxLabelBboxHeight) : 0;
270-
const shownLabelSize = getAllAxisLayersGirth(timeAxisLayerCount, maxLabelBboxGirth, !vertical);
273+
const layerGrith = getAllAxisLayersGirth(timeAxisLayerCount, maxLabelBboxGirth, !vertical);
274+
const shownLabelSize = vertical ? Math.min(parentDimension.width / (verticalAxisCount * 2), layerGrith) : layerGrith;
271275
const parallelSize = labelPaddingSum + shownLabelSize + tickDimension + titleDimension + panelTitleDimension;
272276
return {
273277
leftIncrement: position === Position.Left ? parallelSize + chartMargins.left : 0,
@@ -307,32 +311,39 @@ export interface AxisGeometry {
307311
};
308312
dimension: TickLabelBounds;
309313
visibleTicks: AxisTick[];
314+
maxLabelSize: number;
310315
}
311316

312317
/** @internal */
313318
export function getAxesGeometries(
319+
parentDimension: Dimensions,
314320
chartDims: { chartDimensions: Dimensions; leftMargin: number },
315321
{ chartPaddings, chartMargins, axes: sharedAxesStyle }: Theme,
316322
axisSpecs: Map<AxisId, AxisSpec>,
317323
axesStyles: Map<AxisId, AxisStyle | null>,
318324
smScales: SmallMultipleScales,
319325
visibleTicksSet: Map<AxisId, Projection>,
320326
): AxisGeometry[] {
327+
const verticalAxesCount = [...axisSpecs.values()].reduce((count, spec) => {
328+
return count + (isVerticalAxis(spec.position) ? 1 : 0);
329+
}, 0);
321330
const panel = getPanelSize(smScales);
322331
return [...visibleTicksSet].reduce(
323-
(acc: PerSideDistance & { geoms: AxisGeometry[] }, [axisId, { ticks, labelBox }]: [AxisId, Projection]) => {
332+
(acc: PerSideDistance & { geoms: AxisGeometry[] }, [axisId, { ticks, labelBox, scale }]: [AxisId, Projection]) => {
324333
const axisSpec = axisSpecs.get(axisId);
325334
if (axisSpec) {
326335
const vertical = isVerticalAxis(axisSpec.position);
327336
const axisStyle = axesStyles.get(axisId) ?? sharedAxesStyle;
328337
const { dimensions, topIncrement, bottomIncrement, leftIncrement, rightIncrement } = getPosition(
338+
parentDimension,
329339
chartDims,
330340
chartMargins,
331341
axisStyle,
332342
axisSpec,
333343
labelBox,
334344
smScales,
335345
acc,
346+
verticalAxesCount,
336347
);
337348
acc.top += topIncrement;
338349
acc.bottom += bottomIncrement;
@@ -348,6 +359,12 @@ export function getAxesGeometries(
348359
width: labelBox.isHidden ? 0 : vertical ? dimensions.width : panel.width,
349360
height: labelBox.isHidden ? 0 : vertical ? panel.height : dimensions.height,
350361
},
362+
maxLabelSize:
363+
vertical && !isContinuousScale(scale)
364+
? parentDimension.width / (verticalAxesCount * 2)
365+
: isContinuousScale(scale)
366+
? Infinity
367+
: scale.step,
351368
});
352369
} else {
353370
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: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 React from 'react';
10+
11+
import { Axis, BarSeries, Chart, GroupBy, Position, ScaleType, Settings, SmallMultiples } from '@elastic/charts';
12+
13+
import { ChartsStory } from '../../types';
14+
import { useBaseTheme } from '../../use_base_theme';
15+
16+
export const Example: ChartsStory = (_, { title, description }) => {
17+
return (
18+
<div style={{ border: '1px solid black', position: 'relative', width: '100%', height: '100%' }}>
19+
<div style={{ resize: 'both', border: '1x solid black', overflow: 'hidden', width: '100%', height: '50%' }}>
20+
<Chart title={title} description={description}>
21+
<Settings
22+
baseTheme={useBaseTheme()}
23+
rotation={90}
24+
theme={{
25+
chartMargins: { top: 0, left: 0, right: 0, bottom: 0 },
26+
chartPaddings: { top: 0, left: 0, right: 0, bottom: 0 },
27+
axes: {
28+
axisTitle: {
29+
padding: 0,
30+
},
31+
axisPanelTitle: {
32+
padding: 0,
33+
},
34+
tickLabel: {
35+
padding: 0,
36+
},
37+
tickLine: {
38+
padding: 0,
39+
},
40+
},
41+
}}
42+
/>
43+
44+
<Axis id="left" position={Position.Left} />
45+
<Axis id="right" position={Position.Right} />
46+
<Axis id="bottom" position={Position.Bottom} />
47+
<BarSeries
48+
id="horizontal bar chart"
49+
xScaleType={ScaleType.Ordinal}
50+
yScaleType={ScaleType.Linear}
51+
xAccessor="x"
52+
yAccessors={['y']}
53+
data={[
54+
{ x: 'artifacts.elastic.co', y: 2, g: 'cluster a' },
55+
{ x: 'www.elastic.co', y: 7, g: 'cluster a' },
56+
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 3, g: 'cluster a' },
57+
{ x: 'docker.elastic.co', y: 6, g: 'cluster a' },
58+
59+
{ x: 'artifacts.elastic.co', y: 10, g: 'cluster B' },
60+
{ x: 'www.elastic.co', y: 17, g: 'cluster B' },
61+
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 23, g: 'cluster B' },
62+
{ x: 'docker.elastic.co', y: 8, g: 'cluster B' },
63+
]}
64+
/>
65+
{/* <GroupBy id="g" by={(spec, datum) => datum.g} sort="alphaAsc" />
66+
<SmallMultiples splitHorizontally="g" /> */}
67+
</Chart>
68+
</div>
69+
<div style={{ resize: 'both', overflow: 'hidden', width: '100%', height: '50%' }}>
70+
<Chart title={title} description={description}>
71+
<Settings
72+
baseTheme={useBaseTheme()}
73+
theme={{
74+
chartMargins: { top: 0, left: 0, right: 0, bottom: 0 },
75+
chartPaddings: { top: 0, left: 0, right: 0, bottom: 0 },
76+
axes: {
77+
axisTitle: {
78+
padding: 0,
79+
},
80+
axisPanelTitle: {
81+
padding: 0,
82+
},
83+
tickLabel: {
84+
padding: 0,
85+
},
86+
tickLine: {
87+
padding: 0,
88+
},
89+
},
90+
}}
91+
/>
92+
93+
<Axis id="left" position={Position.Bottom} />
94+
<BarSeries
95+
id="horizontal bar chart"
96+
xScaleType={ScaleType.Ordinal}
97+
yScaleType={ScaleType.Linear}
98+
xAccessor="x"
99+
yAccessors={['y']}
100+
data={[
101+
{ x: 'artifacts.elastic.co', y: 2, g: 'cluster a' },
102+
{ x: 'www.elastic.co', y: 7, g: 'cluster a' },
103+
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 3, g: 'cluster a' },
104+
{ x: 'docker.elastic.co', y: 6, g: 'cluster a' },
105+
106+
{ x: 'artifacts.elastic.co', y: 10, g: 'cluster B' },
107+
{ x: 'www.elastic.co', y: 17, g: 'cluster B' },
108+
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 23, g: 'cluster B' },
109+
{ x: 'docker.elastic.co', y: 8, g: 'cluster B' },
110+
]}
111+
/>
112+
{/* <GroupBy id="g" by={(spec, datum) => datum.g} sort="alphaAsc" />
113+
<SmallMultiples splitHorizontally="g" /> */}
114+
</Chart>
115+
</div>
116+
</div>
117+
);
118+
};

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)