Skip to content

Commit

Permalink
feat(xy_charts): render legend inside the chart (#1031)
Browse files Browse the repository at this point in the history
This commit adds the ability to display the legend inside the XY cartesian charts.
The <Settings> property legendPosition will take now an extended version of the position that allows increased customizability. The legend outside the chart only works with the following cases: `direction: vertical, vAlign: top, hAlign: left/right`, `direction: horizontal, hAlign:left, vAlign: top/bottom`,

fix #861
  • Loading branch information
markov00 committed Mar 24, 2021
1 parent 7e2a5e1 commit ba88122
Show file tree
Hide file tree
Showing 43 changed files with 3,159 additions and 2,764 deletions.
4,998 changes: 2,510 additions & 2,488 deletions api/charts.api.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion integration/jest_env_setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ export const toMatchImageSnapshot = configureToMatchImageSnapshot({

expect.extend({ toMatchImageSnapshot });

jest.setTimeout(10 * 1000); // set timeout in milliseconds;
jest.setTimeout(process.env.DEBUG ? 10 * 10 * 1000 : 10 * 1000); // set timeout in milliseconds;
1 change: 1 addition & 0 deletions integration/jest_puppeteer.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const customConfig = {
...(isDebug
? {
launch: {
args: ['--no-sandbox'], // required to connect puppeteer to chromium devtools ws
dumpio: false,
headless: false,
slowMo: 500,
Expand Down
2 changes: 1 addition & 1 deletion integration/page_objects/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ type ScreenshotElementAtUrlOptions = ScreenshotDOMElementOptions & {
/**
* timeout for waiting on element to appear in DOM
*
* @default 10000
* @defaultValue 10000
*/
timeout?: number;
/**
Expand Down
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.
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.
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.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 27 additions & 2 deletions integration/tests/legend_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { PartitionLayout } from '../../src';
import { PartitionLayout, Position } from '../../src';
import { common } from '../page_objects';

describe('Legend stories', () => {
Expand Down Expand Up @@ -62,7 +62,10 @@ describe('Legend stories', () => {
});

it('should render legend action on mouse hover', async () => {
const action = async () => await common.moveMouseRelativeToDOMElement({ left: 30, top: 10 }, '.echLegendItem');
const action = async () => {
await common.disableAnimations();
await common.moveMouseRelativeToDOMElement({ left: 30, top: 10 }, '.echLegendItem');
};
await common.expectChartAtUrlToMatchScreenshot('http://localhost:9001/?path=/story/legend--actions', {
action,
delay: 500, // needed for icon to load
Expand Down Expand Up @@ -170,4 +173,26 @@ describe('Legend stories', () => {
},
);
});
describe('Legend inside chart', () => {
it.each([
[Position.Top, Position.Left],
[Position.Top, Position.Right],
[Position.Bottom, Position.Left],
[Position.Bottom, Position.Right],
])('should correctly display %s %s', async (pos1, pos2) => {
await common.expectChartAtUrlToMatchScreenshot(
`http://localhost:9001/?path=/story/legend--inside-chart&knob-Legend Position[0]=${pos1}&knob-Legend Position[1]=${pos2}&knob-Dark Mode=`,
);
});
it.each([
[Position.Top, Position.Left],
[Position.Top, Position.Right],
[Position.Bottom, Position.Left],
[Position.Bottom, Position.Right],
])('should correctly display %s %s in dark mode', async (pos1, pos2) => {
await common.expectChartAtUrlToMatchScreenshot(
`http://localhost:9001/?path=/story/legend--inside-chart&knob-Legend Position[0]=${pos1}&knob-Legend Position[1]=${pos2}&knob-Dark Mode=true`,
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ export const getGridHeightParamsSelector = createCachedSelector(
],
(
legendSize,
{ showLegend, legendPosition },
{ showLegend },
{ height: containerHeight },
{ xAxisLabel: { padding, visible, fontSize }, grid, maxLegendHeight },
{ yValues },
): GridHeightParams => {
const xAxisHeight = visible ? fontSize : 0;
const totalVerticalPadding = padding * 2;
let legendHeight = 0;
if (showLegend && isHorizontalLegend(legendPosition)) {
if (showLegend && isHorizontalLegend(legendSize.position)) {
legendHeight = maxLegendHeight ?? legendSize.height;
}
const verticalRemainingSpace = containerHeight - xAxisHeight - totalVerticalPadding - legendHeight;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import createCachedSelector from 're-reselect';

import { GlobalChartState } from '../../../../state/chart_state';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { getLegendConfigSelector } from '../../../../state/selectors/get_legend_config_selector';
import { getLegendSizeSelector } from '../../../../state/selectors/get_legend_size';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
import { LayoutDirection } from '../../../../utils/common';
import { Dimensions } from '../../../../utils/dimensions';
import { isVerticalAxis } from '../../../xy_chart/utils/axis_type_utils';
import { getHeatmapConfigSelector } from './get_heatmap_config';

const getParentDimension = (state: GlobalChartState) => state.parentDimensions;
Expand All @@ -33,12 +33,12 @@ const getParentDimension = (state: GlobalChartState) => state.parentDimensions;
* @internal
*/
export const getHeatmapContainerSizeSelector = createCachedSelector(
[getParentDimension, getLegendSizeSelector, getHeatmapConfigSelector, getSettingsSpecSelector],
[getParentDimension, getLegendSizeSelector, getHeatmapConfigSelector, getLegendConfigSelector],
(parentDimensions, legendSize, { maxLegendHeight }, { showLegend, legendPosition }): Dimensions => {
if (!showLegend) {
if (!showLegend || legendPosition.floating) {
return parentDimensions;
}
if (isVerticalAxis(legendPosition)) {
if (legendPosition.direction === LayoutDirection.Vertical) {
return {
left: 0,
top: 0,
Expand Down
5 changes: 3 additions & 2 deletions src/chart_types/partition_chart/layout/utils/legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
import { CategoryKey } from '../../../../common/category';
import { map } from '../../../../common/iterables';
import { LegendItem } from '../../../../common/legend';
import { identity, Position } from '../../../../utils/common';
import { LegendPositionConfig } from '../../../../specs/settings';
import { identity } from '../../../../utils/common';
import { isHierarchicalLegend } from '../../../../utils/legend';
import { Layer } from '../../specs';
import { QuadViewModel } from '../types/viewmodel_types';
Expand All @@ -45,7 +46,7 @@ export function getLegendItems(
layers: Layer[],
flatLegend: boolean | undefined,
legendMaxDepth: number,
legendPosition: Position,
legendPosition: LegendPositionConfig,
quadViewModel: QuadViewModel[],
): LegendItem[] {
const uniqueNames = new Set(map(({ dataName, fillColor }) => makeKey(dataName, fillColor), quadViewModel));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ import createCachedSelector from 're-reselect';

import { LegendItem } from '../../../../common/legend';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
import { getLegendConfigSelector } from '../../../../state/selectors/get_legend_config_selector';
import { getLegendItems } from '../../layout/utils/legend';
import { partitionMultiGeometries } from './geometries';
import { getPartitionSpecs } from './get_partition_specs';

/** @internal */
export const computeLegendSelector = createCachedSelector(
[getPartitionSpecs, getSettingsSpecSelector, partitionMultiGeometries],
[getPartitionSpecs, getLegendConfigSelector, partitionMultiGeometries],
(specs, { flatLegend, legendMaxDepth, legendPosition }, geometries): LegendItem[] =>
specs.flatMap((partitionSpec, i) => {
const quadViewModel = geometries.filter((g) => g.index === i).flatMap((g) => g.quadViewModel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,15 @@ describe('Partition - Legend item extra values', () => {
expect(extraValues.values()).toMatchSnapshot();
});

it('filters all extraValues is depth is 0', () => {
it('filters all extraValues if depth is 0', () => {
const settings = MockGlobalSpec.settings({ legendMaxDepth: 0 });
MockStore.addSpecs([settings, spec], store);

const extraValues = getLegendItemsExtra(store.getState());
expect([...extraValues.keys()]).toEqual([]);
});

it('filters all extraValues is depth is NaN', () => {
it('filters all extraValues if depth is NaN', () => {
const settings = MockGlobalSpec.settings({ legendMaxDepth: NaN });
MockStore.addSpecs([settings, spec], store);

Expand Down
4 changes: 2 additions & 2 deletions src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function renderTickLabel(ctx: CanvasRenderingContext2D, tick: AxisTick, s
const { rotation: tickLabelRotation, alignment, offset } = labelStyle;

const { maxLabelBboxWidth, maxLabelBboxHeight, maxLabelTextWidth, maxLabelTextHeight } = axisTicksDimensions;
const { x, y, offsetX, offsetY, textOffsetX, textOffsetY, align, verticalAlign } = getTickLabelProps(
const { x, y, offsetX, offsetY, textOffsetX, textOffsetY, horizontalAlign, verticalAlign } = getTickLabelProps(
axisStyle,
tick.position,
position,
Expand Down Expand Up @@ -107,7 +107,7 @@ export function renderTickLabel(ctx: CanvasRenderingContext2D, tick: AxisTick, s
...font,
fontSize: labelStyle.fontSize,
fill: labelStyle.fill,
align: align as CanvasTextAlign,
align: horizontalAlign as CanvasTextAlign,
baseline: verticalAlign as CanvasTextBaseline,
},
tickLabelRotation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme';
import { getLegendSizeSelector, LegendSizing } from '../../../../state/selectors/get_legend_size';
import { getSmallMultiplesSpec } from '../../../../state/selectors/get_small_multiples_spec';
import { Position } from '../../../../utils/common';
import { HorizontalAlignment, LayoutDirection, VerticalAlignment } from '../../../../utils/common';
import { computeChartDimensions, ChartDimensions } from '../../utils/dimensions';
import { computeAxisTicksDimensionsSelector } from './compute_axis_ticks_dimensions';
import { getAxesStylesSelector } from './get_axis_styles';
Expand Down Expand Up @@ -62,7 +62,7 @@ export const computeChartDimensionsSelector = createCachedSelector(
)(getChartIdSelector);

function getLegendDimension({
position,
position: { direction, vAlign, hAlign },
width,
height,
margin,
Expand All @@ -73,9 +73,9 @@ function getLegendDimension({
let left = 0;
let top = 0;

if (position === Position.Left) {
if (direction === LayoutDirection.Vertical && hAlign === HorizontalAlignment.Left) {
left = width + margin * 2;
} else if (position === Position.Top) {
} else if (direction === LayoutDirection.Horizontal && vAlign === VerticalAlignment.Top) {
top = height + margin * 2;
}

Expand Down
16 changes: 8 additions & 8 deletions src/chart_types/xy_chart/utils/axis_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@ describe('Axis computational utils', () => {
textOffsetY: 0,
x: 85,
y: 0,
align: 'right',
horizontalAlign: 'right',
verticalAlign: 'middle',
});

Expand All @@ -782,7 +782,7 @@ describe('Axis computational utils', () => {
textOffsetY: 0,
x: 80,
y: 0,
align: 'center',
horizontalAlign: 'center',
verticalAlign: 'middle',
});

Expand All @@ -808,7 +808,7 @@ describe('Axis computational utils', () => {
textOffsetY: 0,
x: 20,
y: 0,
align: 'center',
horizontalAlign: 'center',
verticalAlign: 'middle',
});

Expand All @@ -830,7 +830,7 @@ describe('Axis computational utils', () => {
textOffsetY: 0,
x: 20,
y: 0,
align: 'left',
horizontalAlign: 'left',
verticalAlign: 'middle',
});
});
Expand Down Expand Up @@ -865,7 +865,7 @@ describe('Axis computational utils', () => {
textOffsetX: 0,
x: 0,
y: -5,
align: 'center',
horizontalAlign: 'center',
verticalAlign: 'bottom',
});

Expand All @@ -887,7 +887,7 @@ describe('Axis computational utils', () => {
textOffsetY: 0,
x: 0,
y: -10,
align: 'right',
horizontalAlign: 'right',
verticalAlign: 'middle',
});

Expand All @@ -909,7 +909,7 @@ describe('Axis computational utils', () => {
textOffsetY: 0,
x: 0,
y: 20,
align: 'left',
horizontalAlign: 'left',
verticalAlign: 'middle',
});

Expand All @@ -935,7 +935,7 @@ describe('Axis computational utils', () => {
textOffsetY: -50,
x: 0,
y: 20,
align: 'center',
horizontalAlign: 'center',
verticalAlign: 'top',
});
});
Expand Down
Loading

0 comments on commit ba88122

Please sign in to comment.