Skip to content

Commit ff5ff27

Browse files
fix(heatmap): update tooltip visibility logic to handle empty tooltip info (#2661)
* fix(heatmap): update tooltip visibility logic to handle empty tooltip info * Add unit tests for isTooltipVisibleSelector functionality * Add heatmap tooltip visibility e2e vrt for empty and non-empty tooltip info * test(vrt): update screenshots [skip ci] * Update visibility logic to use the isVisible property * Update the default chart width * test(vrt): update screenshots [skip ci] --------- Co-authored-by: elastic-datavis[bot] <98618603+elastic-datavis[bot]@users.noreply.github.com>
1 parent 13d9455 commit ff5ff27

File tree

9 files changed

+244
-2
lines changed

9 files changed

+244
-2
lines changed

e2e/tests/heatmap_stories.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,19 @@ test.describe('Heatmap stories', () => {
196196
);
197197
});
198198
});
199+
200+
test.describe('Tooltip', () => {
201+
test('should not be shown when the tooltip info is empty', async ({ page }) => {
202+
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
203+
`http://localhost:9001/?path=/story/heatmap-alpha--test-tooltip&globals=toggles.showHeader:true;toggles.showChartTitle:false;toggles.showChartDescription:false;toggles.showChartBoundary:false;theme:light&knob-Boundary%20Element=default&knob-Fallback%20Placements=right,left,top,bottom&knob-Show%20x%20axis%20title=true&knob-Show%20y%20axis%20title=true&knob-Tooltip%20offset=10&knob-Tooltip%20placement=right&knob-chart%20width=700&knob-stickTo=middle&knob-tooltip%20type=vertical`,
204+
{ left: 150, top: 150 },
205+
);
206+
});
207+
test('should be shown when the tooltip info is not empty', async ({ page }) => {
208+
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
209+
`http://localhost:9001/?path=/story/heatmap-alpha--test-tooltip&globals=toggles.showHeader:true;toggles.showChartTitle:false;toggles.showChartDescription:false;toggles.showChartBoundary:false;theme:light&knob-Boundary%20Element=default&knob-Fallback%20Placements=right,left,top,bottom&knob-Show%20x%20axis%20title=true&knob-Show%20y%20axis%20title=true&knob-Tooltip%20offset=10&knob-Tooltip%20placement=left&knob-chart%20width=700&knob-stickTo=middle&knob-tooltip%20type=vertical`,
210+
{ right: 100, top: 150 },
211+
);
212+
});
213+
});
199214
});
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 type { Store } from 'redux';
10+
11+
import { isTooltipVisibleSelector } from './is_tooltip_visible';
12+
import * as tooltipInfo from './tooltip';
13+
import { MockGlobalSpec, MockSeriesSpec } from '../../../../mocks/specs/specs';
14+
import { MockStore } from '../../../../mocks/store/store';
15+
import { ScaleType } from '../../../../scales/constants';
16+
import { onPointerMove } from '../../../../state/actions/mouse';
17+
import type { GlobalChartState } from '../../../../state/chart_state';
18+
19+
jest.mock('./tooltip', () => ({
20+
getTooltipInfoSelector: jest.fn(),
21+
}));
22+
23+
describe('isTooltipVisibleSelector', () => {
24+
let store: Store<GlobalChartState>;
25+
26+
beforeEach(() => {
27+
jest.resetAllMocks();
28+
29+
// By default use the real implementation in mocked version
30+
jest
31+
.mocked(tooltipInfo.getTooltipInfoSelector)
32+
.mockImplementation(jest.requireActual<typeof tooltipInfo>('./tooltip').getTooltipInfoSelector);
33+
34+
store = MockStore.default({ width: 700, height: 300, top: 0, left: 0 }, 'chartId');
35+
MockStore.addSpecs(
36+
[
37+
MockGlobalSpec.settingsNoMargins({
38+
legendPosition: 'right',
39+
theme: {
40+
heatmap: {
41+
xAxisLabel: {
42+
visible: false,
43+
},
44+
yAxisLabel: {
45+
visible: false,
46+
},
47+
},
48+
},
49+
}),
50+
MockSeriesSpec.heatmap({
51+
xScale: { type: ScaleType.Ordinal },
52+
data: [
53+
{
54+
name: 'Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1',
55+
unifiedY: '',
56+
value: 674,
57+
},
58+
{
59+
name: 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24',
60+
unifiedY: '',
61+
value: 574,
62+
},
63+
{
64+
name: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)',
65+
unifiedY: '',
66+
value: 488,
67+
},
68+
],
69+
xAccessor: 'name',
70+
yAccessor: 'unifiedY',
71+
valueAccessor: 'value',
72+
xSortPredicate: 'alphaAsc',
73+
ySortPredicate: 'dataIndex',
74+
}),
75+
],
76+
store,
77+
);
78+
});
79+
80+
it('should return visible: true when tooltip info is available', () => {
81+
store.dispatch(onPointerMove({ position: { x: 200, y: 150 }, time: 0 }));
82+
const state = store.getState();
83+
const result = isTooltipVisibleSelector(state);
84+
85+
expect(result.visible).toBe(true);
86+
});
87+
88+
it('should return visible: false when tooltip info values is an empty array', () => {
89+
store.dispatch(onPointerMove({ position: { x: 0, y: 150 }, time: 0 }));
90+
const state = store.getState();
91+
const result = isTooltipVisibleSelector(state);
92+
93+
expect(result.visible).toBe(false);
94+
});
95+
96+
it('should return visible: false when every element of the tooltipInfo values has isVisible: false', () => {
97+
jest.mocked(tooltipInfo.getTooltipInfoSelector).mockReturnValue({
98+
disableActions: true,
99+
header: null,
100+
values: [
101+
{
102+
color: 'transparent',
103+
datum: '',
104+
displayOnly: true,
105+
formattedValue: '',
106+
isHighlighted: false,
107+
isVisible: false,
108+
label: '',
109+
seriesIdentifier: { specId: 'testHeatmap', key: 'testHeatmap' },
110+
value: '',
111+
},
112+
],
113+
});
114+
const state = store.getState();
115+
const result = isTooltipVisibleSelector(state);
116+
117+
expect(result.visible).toBe(false);
118+
});
119+
});

packages/charts/src/chart_types/heatmap/state/selectors/is_tooltip_visible.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ export const isTooltipVisibleSelector = createCustomCachedSelector(
2626
};
2727
}
2828

29+
const tooltipInfoIsVisible = tooltipInfo.values.some(({ isVisible }) => isVisible);
30+
2931
return {
30-
visible: tooltipInfo.values.length > 0 || pinned,
32+
visible: tooltipInfoIsVisible || pinned,
3133
displayOnly: tooltipInfo.values.every(({ displayOnly }) => displayOnly),
3234
isExternal: false,
3335
isPinnable: tooltipInfo.values.length > 0,

packages/charts/src/chart_types/heatmap/state/selectors/tooltip.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export const getTooltipInfoSelector = createCustomCachedSelector(
8888
label: '',
8989
color: Colors.Transparent.keyword,
9090
isHighlighted: false,
91-
isVisible: true,
91+
isVisible: pickedShapes.text.length > 0,
9292
seriesIdentifier: {
9393
specId: spec.id,
9494
key: spec.id,
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 { action } from '@storybook/addon-actions';
10+
import { boolean, number } from '@storybook/addon-knobs';
11+
import React from 'react';
12+
13+
import { Chart, Heatmap, Settings, Tooltip } from '@elastic/charts';
14+
import type { TooltipProps } from '@elastic/charts';
15+
16+
import type { ChartsStory } from '../../types';
17+
import { useBaseTheme } from '../../use_base_theme';
18+
import { customKnobs } from '../utils/knobs';
19+
20+
export const Example: ChartsStory = (_, { title, description }) => {
21+
const data = [
22+
{
23+
agent: 'Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1',
24+
unifiedY: '',
25+
value: 674,
26+
},
27+
{
28+
agent: 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24',
29+
unifiedY: '',
30+
value: 589,
31+
},
32+
{
33+
agent: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)',
34+
unifiedY: '',
35+
value: 488,
36+
},
37+
];
38+
39+
const tooltipOptions: TooltipProps = {
40+
stickTo: customKnobs.enum.stickTo('stickTo'),
41+
placement: customKnobs.enum.placement('Tooltip placement'),
42+
};
43+
44+
const showXAxisTitle = boolean('Show x axis title', true);
45+
const showYAxisTitle = boolean('Show y axis title', true);
46+
47+
const chartWidth = number('chart width', 700, { step: 1, min: 0 });
48+
49+
return (
50+
<Chart title={title} description={description} size={[chartWidth ? chartWidth : '100%', 320]}>
51+
<Settings
52+
onElementClick={action('onElementClick')}
53+
showLegend
54+
legendPosition="right"
55+
brushAxis="both"
56+
baseTheme={useBaseTheme()}
57+
theme={{
58+
heatmap: {
59+
grid: {
60+
stroke: {
61+
width: 0,
62+
},
63+
},
64+
cell: {
65+
maxWidth: 'fill',
66+
border: {
67+
stroke: 'transparent',
68+
strokeWidth: 1,
69+
},
70+
},
71+
yAxisLabel: {
72+
visible: false,
73+
},
74+
},
75+
}}
76+
onBrushEnd={action('onBrushEnd')}
77+
/>
78+
<Tooltip {...tooltipOptions} />
79+
<Heatmap
80+
id="heatmap8"
81+
colorScale={{
82+
type: 'bands',
83+
bands: [
84+
{ start: -Infinity, end: 500, color: '#AADC32' },
85+
{ start: 500, end: 600, color: '#35B779' },
86+
{ start: 600, end: Infinity, color: '#24868E' },
87+
],
88+
}}
89+
data={data}
90+
xAccessor="agent"
91+
yAccessor="unifiedY"
92+
valueAccessor="value"
93+
valueFormatter={(value) => `${Number(value.toFixed(2))}`}
94+
xSortPredicate="alphaAsc"
95+
xAxisTitle={showXAxisTitle ? 'Agents' : undefined}
96+
yAxisTitle={showYAxisTitle ? 'Bytes' : undefined}
97+
ySortPredicate="dataIndex"
98+
/>
99+
</Chart>
100+
);
101+
};
102+
103+
Example.parameters = {
104+
resize: true,
105+
};

storybook/stories/heatmap/heatmap.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export { Example as timeSnap } from './4_test_time_snap.story';
1717
export { Example as theming } from './5_theming.story';
1818
export { Example as labelRotation } from './6_label_rotation.story';
1919
export { Example as sorting } from './7_sorting.story';
20+
export { Example as testTooltip } from './8_test_tooltip.story';

0 commit comments

Comments
 (0)