Skip to content

Commit 659d27e

Browse files
authored
feat: auto legend resize (#316)
* Remove UI legend toggle. `showLegend` prop on settings unchanged. * allow content to set size * refactor: measure vert legend width with static buffer * fix: legend toggle issue for top/bottom legends * Refactor css flex styles to use grid for more shinny UI!= * Remove legendId, fix safari max-height inheritance issue, remove uneeded legendList styles, revert empty state flex value. * remove unused styles BREAKING CHANGE: `theme.legend.spacingBuffer` added to `Theme` type. Controls the width buffer between the legend label and value. #268
1 parent 2b838d7 commit 659d27e

33 files changed

+495
-438
lines changed

.playground/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5+
<title>Charts Playground</title>
56
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
67
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
78
<title>Document</title>

.playground/playgroud.tsx

Lines changed: 113 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { loremIpsum } from 'lorem-ipsum';
23

34
import {
45
Axis,
@@ -9,88 +10,129 @@ import {
910
Position,
1011
ScaleType,
1112
Settings,
12-
LineSeries,
13+
AreaSeries,
14+
mergeWithDefaultTheme,
1315
} from '../src';
1416
import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana';
15-
import { CursorEvent } from '../src/specs/settings';
16-
import { CursorUpdateListener } from '../src/chart_types/xy_chart/store/chart_state';
1717

1818
export class Playground extends React.Component {
19-
ref1 = React.createRef<Chart>();
20-
ref2 = React.createRef<Chart>();
21-
ref3 = React.createRef<Chart>();
22-
23-
onCursorUpdate: CursorUpdateListener = (event?: CursorEvent) => {
24-
this.ref1.current!.dispatchExternalCursorEvent(event);
25-
this.ref2.current!.dispatchExternalCursorEvent(event);
26-
this.ref3.current!.dispatchExternalCursorEvent(event);
27-
};
28-
2919
render() {
30-
return (
31-
<>
32-
{renderChart(
33-
'1',
34-
this.ref1,
35-
KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 15),
36-
this.onCursorUpdate,
37-
true,
38-
)}
39-
{renderChart(
40-
'2',
41-
this.ref2,
42-
KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 15),
43-
this.onCursorUpdate,
44-
true,
45-
)}
46-
{renderChart('2', this.ref3, KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(15, 30), this.onCursorUpdate)}
47-
</>
48-
);
20+
return <>{this.renderChart(Position.Bottom)}</>;
4921
}
50-
}
51-
52-
function renderChart(
53-
key: string,
54-
ref: React.RefObject<Chart>,
55-
data: any,
56-
onCursorUpdate?: CursorUpdateListener,
57-
timeSeries: boolean = false,
58-
) {
59-
return (
60-
<div key={key} className="chart">
61-
<Chart ref={ref}>
62-
<Settings tooltip={{ type: 'vertical' }} debug={false} showLegend={true} onCursorUpdate={onCursorUpdate} />
63-
<Axis
64-
id={getAxisId('timestamp')}
65-
title="timestamp"
66-
position={Position.Bottom}
67-
tickFormat={niceTimeFormatter([1555819200000, 1555905600000])}
68-
/>
69-
<Axis id={getAxisId('count')} title="count" position={Position.Left} tickFormat={(d) => d.toFixed(2)} />
70-
<LineSeries
71-
id={getSpecId('dataset A with long title')}
72-
xScaleType={timeSeries ? ScaleType.Time : ScaleType.Linear}
22+
renderChart(legendPosition: Position) {
23+
const renderMore = () => {
24+
const random = Math.floor(Math.random() * 3) + 1;
25+
const id = loremIpsum({ count: random, units: 'words' });
26+
return (
27+
<AreaSeries
28+
id={getSpecId(id)}
29+
xScaleType={ScaleType.Time}
7330
yScaleType={ScaleType.Linear}
74-
data={data}
31+
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 15)}
7532
xAccessor={0}
76-
lineSeriesStyle={{
33+
areaSeriesStyle={{
34+
point: {
35+
visible: true,
36+
strokeWidth: 3,
37+
radius: 10,
38+
},
7739
line: {
78-
stroke: 'red',
79-
opacity: 1,
40+
strokeWidth: 10,
8041
},
8142
}}
8243
yAccessors={[1]}
8344
/>
84-
<LineSeries
85-
id={getSpecId('dataset B')}
86-
xScaleType={ScaleType.Time}
87-
yScaleType={ScaleType.Linear}
88-
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 15)}
89-
xAccessor={0}
90-
yAccessors={[1]}
91-
stackAccessors={[0]}
92-
/>
93-
</Chart>
94-
</div>
95-
);
45+
);
46+
};
47+
const theme = mergeWithDefaultTheme({
48+
lineSeriesStyle: {
49+
line: {
50+
stroke: 'violet',
51+
strokeWidth: 4,
52+
},
53+
point: {
54+
fill: 'yellow',
55+
stroke: 'black',
56+
strokeWidth: 2,
57+
radius: 6,
58+
},
59+
},
60+
});
61+
return (
62+
<div className="chart">
63+
<Chart>
64+
<Settings debug={false} showLegend={true} legendPosition={legendPosition} rotation={0} theme={theme} />
65+
<Axis
66+
id={getAxisId('timestamp')}
67+
title="timestamp"
68+
position={Position.Bottom}
69+
tickFormat={niceTimeFormatter([1555819200000, 1555905600000])}
70+
/>
71+
<Axis id={getAxisId('count')} title="count" position={Position.Left} tickFormat={(d) => d.toFixed(2)} />
72+
73+
<AreaSeries
74+
id={getSpecId('dataset B')}
75+
xScaleType={ScaleType.Time}
76+
yScaleType={ScaleType.Linear}
77+
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 15)}
78+
xAccessor={0}
79+
yAccessors={[1]}
80+
stackAccessors={[0]}
81+
areaSeriesStyle={{
82+
line: {
83+
// opacity:1,
84+
strokeWidth: 10,
85+
},
86+
point: {
87+
visible: true,
88+
strokeWidth: 3,
89+
radius: 10,
90+
},
91+
}}
92+
/>
93+
<AreaSeries
94+
id={getSpecId('dataset C')}
95+
xScaleType={ScaleType.Time}
96+
yScaleType={ScaleType.Linear}
97+
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 15)}
98+
xAccessor={0}
99+
yAccessors={[1]}
100+
stackAccessors={[0]}
101+
areaSeriesStyle={{
102+
line: {
103+
// opacity:1,
104+
strokeWidth: 10,
105+
},
106+
point: {
107+
visible: true,
108+
strokeWidth: 3,
109+
radius: 10,
110+
},
111+
}}
112+
/>
113+
<AreaSeries
114+
id={getSpecId('dataset A')}
115+
xScaleType={ScaleType.Time}
116+
yScaleType={ScaleType.Linear}
117+
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 15)}
118+
xAccessor={0}
119+
areaSeriesStyle={{
120+
point: {
121+
visible: true,
122+
strokeWidth: 3,
123+
radius: 10,
124+
},
125+
line: {
126+
strokeWidth: 10,
127+
},
128+
}}
129+
yAccessors={[1]}
130+
/>
131+
{Array(10)
132+
.fill(null)
133+
.map(renderMore)}
134+
</Chart>
135+
</div>
136+
);
137+
}
96138
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
"husky": "^1.3.1",
9595
"jest": "^24.1.0",
9696
"jest-environment-jsdom-fourteen": "^0.1.0",
97+
"lorem-ipsum": "^2.0.3",
9798
"node-sass": "^4.11.0",
9899
"postcss-cli": "^6.1.3",
99100
"prettier": "1.16.4",

src/chart_types/xy_chart/store/chart_state.test.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,6 @@ describe('Chart Store', () => {
108108
expect(axesTicks.get(AXIS_ID)).not.toBeUndefined();
109109
});
110110

111-
test('can toggle legend visibility', () => {
112-
store.toggleLegendCollapsed();
113-
expect(store.legendCollapsed.get()).toBe(true);
114-
115-
store.toggleLegendCollapsed();
116-
expect(store.legendCollapsed.get()).toBe(false);
117-
});
118-
119111
test('can set legend visibility', () => {
120112
store.showLegend.set(false);
121113
store.setShowLegend(true);
@@ -509,7 +501,7 @@ describe('Chart Store', () => {
509501
};
510502

511503
localStore.computeChart();
512-
expect(localStore.initialized.get()).toBe(false);
504+
expect(localStore.chartInitialized.get()).toBe(false);
513505
});
514506

515507
test('only computes chart if series specs exist', () => {
@@ -524,7 +516,7 @@ describe('Chart Store', () => {
524516

525517
localStore.seriesSpecs = new Map();
526518
localStore.computeChart();
527-
expect(localStore.initialized.get()).toBe(false);
519+
expect(localStore.chartInitialized.get()).toBe(false);
528520
});
529521

530522
test('can set the color for a series', () => {

src/chart_types/xy_chart/store/chart_state.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ export class ChartStore {
119119
debug = false;
120120
id = uuid.v4();
121121
specsInitialized = observable.box(false);
122-
initialized = observable.box(false);
122+
chartInitialized = observable.box(false);
123+
legendInitialized = observable.box(false);
123124
enableHistogramMode = observable.box(false);
124125

125126
parentDimensions: Dimensions = {
@@ -232,15 +233,9 @@ export class ChartStore {
232233
canDataBeAnimated = false;
233234

234235
showLegend = observable.box(false);
235-
legendCollapsed = observable.box(false);
236-
legendPosition: Position | undefined;
236+
legendPosition = observable.box<Position>(Position.Right);
237237
showLegendDisplayValue = observable.box(true);
238238

239-
toggleLegendCollapsed = action(() => {
240-
this.legendCollapsed.set(!this.legendCollapsed.get());
241-
this.computeChart();
242-
});
243-
244239
/**
245240
* determine if crosshair cursor should be visible based on cursor position and brush enablement
246241
*/
@@ -833,7 +828,7 @@ export class ChartStore {
833828
}
834829

835830
computeChart() {
836-
this.initialized.set(false);
831+
this.chartInitialized.set(false);
837832
// compute only if parent dimensions are computed
838833
if (this.parentDimensions.width === 0 || this.parentDimensions.height === 0) {
839834
return;
@@ -879,6 +874,14 @@ export class ChartStore {
879874
this.deselectedDataSeries,
880875
);
881876

877+
if (!this.legendInitialized.get()) {
878+
this.legendInitialized.set(true);
879+
880+
if (this.legendItems.size > 0 && this.showLegend.get()) {
881+
return;
882+
}
883+
}
884+
882885
this.isChartEmpty = isAllSeriesDeselected(this.legendItems);
883886

884887
const { xDomain, yDomain, formattedDataSeries } = this.seriesDomainsAndData;
@@ -913,14 +916,12 @@ export class ChartStore {
913916
});
914917
bboxCalculator.destroy();
915918

916-
// // compute chart dimensions
919+
// compute chart dimensions
917920
const computedChartDims = computeChartDimensions(
918921
this.parentDimensions,
919922
this.chartTheme,
920923
this.axesTicksDimensions,
921924
this.axesSpecs,
922-
this.showLegend.get() && !this.legendCollapsed.get(),
923-
this.legendPosition,
924925
);
925926
this.chartDimensions = computedChartDims.chartDimensions;
926927

@@ -940,7 +941,6 @@ export class ChartStore {
940941
this.enableHistogramMode.get(),
941942
);
942943

943-
// tslint:disable-next-line:no-console
944944
this.geometries = seriesGeometries.geometries;
945945
this.xScale = seriesGeometries.scales.xScale;
946946

@@ -958,17 +958,15 @@ export class ChartStore {
958958
computedChartDims,
959959
this.chartTheme,
960960
this.chartRotation,
961-
this.showLegend.get() && !this.legendCollapsed.get(),
962961
this.axesSpecs,
963962
this.axesTicksDimensions,
964963
xDomain,
965964
yDomain,
966965
totalBarsInCluster,
967966
this.enableHistogramMode.get(),
968-
this.legendPosition,
969967
barsPadding,
970968
);
971-
// tslint:disable-next-line:no-console
969+
972970
this.axesPositions = axisTicksPositions.axisPositions;
973971
this.axesTicks = axisTicksPositions.axisTicks;
974972
this.axesVisibleTicks = axisTicksPositions.axisVisibleTicks;
@@ -992,6 +990,6 @@ export class ChartStore {
992990
// temporary disabled until
993991
// https://github.com/elastic/elastic-charts/issues/89 and https://github.com/elastic/elastic-charts/issues/41
994992
this.canDataBeAnimated = false;
995-
this.initialized.set(true);
993+
this.chartInitialized.set(true);
996994
}
997995
}

0 commit comments

Comments
 (0)