Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
markov00 committed Jan 26, 2024
1 parent 3b3aa03 commit d78a678
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 15 deletions.
17 changes: 9 additions & 8 deletions packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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

/** @internal */
export function getAxesDimensions(
parentDimensions: Dimensions,
theme: Theme,
axisDimensions: AxesTicksDimensions,
axesStyles: Map<AxisId, AxisStyle | null>,
axisSpecs: AxisSpec[],
smSpec: SmallMultiplesSpec | null,
): PerSideDistance & { margin: { left: number } } {
const verticalAxesCount =
axisSpecs.reduce((count, spec) => {
return count + (isVerticalAxis(spec.position) ? 1 : 0);
}, 0) * 2;
const sizes = [...axisDimensions].reduce(
(acc, [id, tickLabelBounds]) => {
const axisSpec = getSpecsById<AxisSpec>(axisSpecs, id);
Expand All @@ -74,8 +75,8 @@ export function getAxesDimensions(
if (isVerticalAxis(axisSpec.position)) {
acc.axisLabelOverflow.top = Math.max(acc.axisLabelOverflow.top, top);
acc.axisLabelOverflow.bottom = Math.max(acc.axisLabelOverflow.bottom, bottom);
acc.axisMainSize.left += left;
acc.axisMainSize.right += right;
acc.axisMainSize.left += Math.min(left, parentDimensions.width / verticalAxesCount);
acc.axisMainSize.right += Math.min(right, parentDimensions.width / verticalAxesCount);
} else {
// find the max half label size to accommodate the left/right labels
acc.axisMainSize.top += top;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface AxisProps {
debug: boolean;
renderingArea: Dimensions;
layerGirth: number;
maxLabelSize: number;
}

/** @internal */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
*/

import { AxisProps } from '.';
import { measureText } from '../../../../../utils/bbox/canvas_text_bbox_calculator';
import { Position } from '../../../../../utils/common';
import { wrapText } from '../../../../../utils/text/wrap';
import { AxisTick, getTickLabelPosition } from '../../../utils/axis_utils';
import { renderText } from '../primitives/text';
import { renderDebugRectCenterRotated } from '../utils/debug';
Expand All @@ -19,7 +21,7 @@ export function renderTickLabel(
ctx: CanvasRenderingContext2D,
tick: AxisTick,
showTicks: boolean,
{ axisSpec: { position, timeAxisLayerCount }, dimension, size, debug, axisStyle }: AxisProps,
{ axisSpec: { position, timeAxisLayerCount }, dimension, size, debug, axisStyle, maxLabelSize }: AxisProps,
layerGirth: number,
) {
const labelStyle = axisStyle.tickLabel;
Expand All @@ -36,7 +38,22 @@ export function renderTickLabel(
);

const center = { x: tickLabelProps.x + tickLabelProps.offsetX, y: tickLabelProps.y + tickLabelProps.offsetY };

const textMeasure = measureText(ctx);
const wrappedText = wrapText(
tick.label,
{
fontFamily: labelStyle.fontFamily,
fontStyle: labelStyle.fontStyle ?? 'normal',
fontVariant: 'normal',
fontWeight: 'normal',
textColor: labelStyle.fill,
},
labelStyle.fontSize,
maxLabelSize,
1,
textMeasure,
'en', // TODO
);
if (debug) {
const { maxLabelBboxWidth, maxLabelBboxHeight, maxLabelTextWidth: width, maxLabelTextHeight: height } = dimension;
// full text container
Expand All @@ -52,7 +69,7 @@ export function renderTickLabel(
renderText(
ctx,
center,
tick.label,
wrappedText[0] ?? '',
{
fontFamily: labelStyle.fontFamily,
fontStyle: labelStyle.fontStyle ?? 'normal',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function renderPanelSubstrates(ctx: CanvasRenderingContext2D, props: Axes
dimension,
visibleTicks: ticks,
parentSize,
maxLabelSize,
} = geometry;
const axisSpec = getSpecsById<AxisSpec>(axesSpecs, id);

Expand Down Expand Up @@ -105,6 +106,7 @@ export function renderPanelSubstrates(ctx: CanvasRenderingContext2D, props: Axes
debug,
renderingArea,
layerGirth,
maxLabelSize,
},
locale,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import { axisSpecsLookupSelector } from './get_specs';
import { getVisibleTickSetsSelector } from './visible_ticks';
import { createCustomCachedSelector } from '../../../../state/create_selector';
import { computeSmallMultipleScalesSelector } from '../../../../state/selectors/compute_small_multiple_scales';
import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions';
import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme';
import { getAxesGeometries } from '../../utils/axis_utils';

/** @internal */
export const computeAxesGeometriesSelector = createCustomCachedSelector(
[
getChartContainerDimensionsSelector,
computeChartDimensionsSelector,
getChartThemeSelector,
axisSpecsLookupSelector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ function getVisibleTicks(

const { showOverlappingTicks, showOverlappingLabels, position } = axisSpec;
const requiredSpace = isVerticalAxis(position) ? labelBox.maxLabelBboxHeight / 2 : labelBox.maxLabelBboxWidth / 2;
const bypassOverlapCheck = showOverlappingLabels || isMultilayerTimeAxis;
const bypassOverlapCheck = scale.type === ScaleType.Ordinal || showOverlappingLabels || isMultilayerTimeAxis;
return bypassOverlapCheck
? allTicks
: [...allTicks]
Expand Down
21 changes: 19 additions & 2 deletions packages/charts/src/chart_types/xy_chart/utils/axis_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isHorizontalAxis, isVerticalAxis } from './axis_type_utils';
import { computeXScale, computeYScales } from './scales';
import { SmallMultipleScales, hasSMDomain, getPanelSize } from '../../../common/panel_utils';
import { ScaleBand, ScaleContinuous } from '../../../scales';
import { isContinuousScale } from '../../../scales/types';
import { AxisSpec, SettingsSpec } from '../../../specs';
import {
degToRad,
Expand Down Expand Up @@ -252,13 +253,15 @@ export const getAllAxisLayersGirth = (

/** @internal */
export function getPosition(
parentDimension: Dimensions,
{ chartDimensions }: { chartDimensions: Dimensions },
chartMargins: PerSideDistance,
{ axisTitle, axisPanelTitle, tickLine, tickLabel }: AxisStyle,
{ title, position, hide, timeAxisLayerCount }: AxisSpec,
{ maxLabelBboxHeight, maxLabelBboxWidth }: TickLabelBounds,
smScales: SmallMultipleScales,
{ top: cumTopSum, bottom: cumBottomSum, left: cumLeftSum, right: cumRightSum }: PerSideDistance,
verticalAxisCount: number,
) {
const tickDimension = shouldShowTicks(tickLine, hide) ? tickLine.size + tickLine.padding : 0;
const labelPaddingSum = tickLabel.visible ? innerPad(tickLabel.padding) + outerPad(tickLabel.padding) : 0;
Expand All @@ -267,7 +270,8 @@ export function getPosition(
const scaleBand = vertical ? smScales.vertical : smScales.horizontal;
const panelTitleDimension = hasSMDomain(scaleBand) ? getTitleDimension(axisPanelTitle) : 0;
const maxLabelBboxGirth = tickLabel.visible ? (vertical ? maxLabelBboxWidth : maxLabelBboxHeight) : 0;
const shownLabelSize = getAllAxisLayersGirth(timeAxisLayerCount, maxLabelBboxGirth, !vertical);
const layerGrith = getAllAxisLayersGirth(timeAxisLayerCount, maxLabelBboxGirth, !vertical);
const shownLabelSize = vertical ? Math.min(parentDimension.width / (verticalAxisCount * 2), layerGrith) : layerGrith;
const parallelSize = labelPaddingSum + shownLabelSize + tickDimension + titleDimension + panelTitleDimension;
return {
leftIncrement: position === Position.Left ? parallelSize + chartMargins.left : 0,
Expand Down Expand Up @@ -307,32 +311,39 @@ export interface AxisGeometry {
};
dimension: TickLabelBounds;
visibleTicks: AxisTick[];
maxLabelSize: number;
}

/** @internal */
export function getAxesGeometries(
parentDimension: Dimensions,
chartDims: { chartDimensions: Dimensions; leftMargin: number },
{ chartPaddings, chartMargins, axes: sharedAxesStyle }: Theme,
axisSpecs: Map<AxisId, AxisSpec>,
axesStyles: Map<AxisId, AxisStyle | null>,
smScales: SmallMultipleScales,
visibleTicksSet: Map<AxisId, Projection>,
): AxisGeometry[] {
const verticalAxesCount = [...axisSpecs.values()].reduce((count, spec) => {
return count + (isVerticalAxis(spec.position) ? 1 : 0);
}, 0);
const panel = getPanelSize(smScales);
return [...visibleTicksSet].reduce(
(acc: PerSideDistance & { geoms: AxisGeometry[] }, [axisId, { ticks, labelBox }]: [AxisId, Projection]) => {
(acc: PerSideDistance & { geoms: AxisGeometry[] }, [axisId, { ticks, labelBox, scale }]: [AxisId, Projection]) => {
const axisSpec = axisSpecs.get(axisId);
if (axisSpec) {
const vertical = isVerticalAxis(axisSpec.position);
const axisStyle = axesStyles.get(axisId) ?? sharedAxesStyle;
const { dimensions, topIncrement, bottomIncrement, leftIncrement, rightIncrement } = getPosition(
parentDimension,
chartDims,
chartMargins,
axisStyle,
axisSpec,
labelBox,
smScales,
acc,
verticalAxesCount,
);
acc.top += topIncrement;
acc.bottom += bottomIncrement;
Expand All @@ -348,6 +359,12 @@ export function getAxesGeometries(
width: labelBox.isHidden ? 0 : vertical ? dimensions.width : panel.width,
height: labelBox.isHidden ? 0 : vertical ? panel.height : dimensions.height,
},
maxLabelSize:
vertical && !isContinuousScale(scale)
? parentDimension.width / (verticalAxesCount * 2)
: isContinuousScale(scale)
? Infinity
: scale.step,
});
} else {
throw new Error(`Cannot compute scale for axis spec ${axisId}`); // todo move this feedback as upstream as possible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface ChartDimensions {
axisSpecs: AxisSpec[],
smSpec: SmallMultiplesSpec | null,
): ChartDimensions {
const axesDimensions = getAxesDimensions(theme, axisTickDimensions, axesStyles, axisSpecs, smSpec);
const axesDimensions = getAxesDimensions(parentDimensions, theme, axisTickDimensions, axesStyles, axisSpecs, smSpec);
const chartWidth = parentDimensions.width - axesDimensions.left - axesDimensions.right;
const chartHeight = parentDimensions.height - axesDimensions.top - axesDimensions.bottom;
const pad = theme.chartPaddings;
Expand Down
118 changes: 118 additions & 0 deletions storybook/stories/bar/a1_horizontal_bars.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';

import { Axis, BarSeries, Chart, GroupBy, Position, ScaleType, Settings, SmallMultiples } from '@elastic/charts';

import { ChartsStory } from '../../types';
import { useBaseTheme } from '../../use_base_theme';

export const Example: ChartsStory = (_, { title, description }) => {
return (
<div style={{ border: '1px solid black', position: 'relative', width: '100%', height: '100%' }}>
<div style={{ resize: 'both', border: '1x solid black', overflow: 'hidden', width: '100%', height: '50%' }}>
<Chart title={title} description={description}>
<Settings
baseTheme={useBaseTheme()}
rotation={90}
theme={{
chartMargins: { top: 0, left: 0, right: 0, bottom: 0 },
chartPaddings: { top: 0, left: 0, right: 0, bottom: 0 },
axes: {
axisTitle: {
padding: 0,
},
axisPanelTitle: {
padding: 0,
},
tickLabel: {
padding: 0,
},
tickLine: {
padding: 0,
},
},
}}
/>

<Axis id="left" position={Position.Left} />
<Axis id="right" position={Position.Right} />
<Axis id="bottom" position={Position.Bottom} />
<BarSeries
id="horizontal bar chart"
xScaleType={ScaleType.Ordinal}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[
{ x: 'artifacts.elastic.co', y: 2, g: 'cluster a' },
{ x: 'www.elastic.co', y: 7, g: 'cluster a' },
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 3, g: 'cluster a' },
{ x: 'docker.elastic.co', y: 6, g: 'cluster a' },

{ x: 'artifacts.elastic.co', y: 10, g: 'cluster B' },
{ x: 'www.elastic.co', y: 17, g: 'cluster B' },
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 23, g: 'cluster B' },
{ x: 'docker.elastic.co', y: 8, g: 'cluster B' },
]}
/>
{/* <GroupBy id="g" by={(spec, datum) => datum.g} sort="alphaAsc" />
<SmallMultiples splitHorizontally="g" /> */}
</Chart>
</div>
<div style={{ resize: 'both', overflow: 'hidden', width: '100%', height: '50%' }}>
<Chart title={title} description={description}>
<Settings
baseTheme={useBaseTheme()}
theme={{
chartMargins: { top: 0, left: 0, right: 0, bottom: 0 },
chartPaddings: { top: 0, left: 0, right: 0, bottom: 0 },
axes: {
axisTitle: {
padding: 0,
},
axisPanelTitle: {
padding: 0,
},
tickLabel: {
padding: 0,
},
tickLine: {
padding: 0,
},
},
}}
/>

<Axis id="left" position={Position.Bottom} />
<BarSeries
id="horizontal bar chart"
xScaleType={ScaleType.Ordinal}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[
{ x: 'artifacts.elastic.co', y: 2, g: 'cluster a' },
{ x: 'www.elastic.co', y: 7, g: 'cluster a' },
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 3, g: 'cluster a' },
{ x: 'docker.elastic.co', y: 6, g: 'cluster a' },

{ x: 'artifacts.elastic.co', y: 10, g: 'cluster B' },
{ x: 'www.elastic.co', y: 17, g: 'cluster B' },
{ x: 'cdn.elastic-elastic-elastic.orgcdn.elastic-elastic-elastic.org', y: 23, g: 'cluster B' },
{ x: 'docker.elastic.co', y: 8, g: 'cluster B' },
]}
/>
{/* <GroupBy id="g" by={(spec, datum) => datum.g} sort="alphaAsc" />
<SmallMultiples splitHorizontally="g" /> */}
</Chart>
</div>
</div>
);
};
1 change: 1 addition & 0 deletions storybook/stories/bar/bars.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ export { Example as testDualYAxis } from './49_test_dual_axis.story';
export { Example as testUseDefaultGroupDomain } from './56_test_use_dfl_gdomain.story';
export { Example as testRectBorder } from './57_test_rect_border_bars.story';
export { Example as dataValue } from './58_data_values.story';
export { Example as horizontalBarChart } from './a1_horizontal_bars.story';

0 comments on commit d78a678

Please sign in to comment.