Skip to content

Commit

Permalink
feat: show tooltip for external events (#698)
Browse files Browse the repository at this point in the history
Adds the ability to show tooltip on a synced chart. The `<Settings />` component has a new prop that allows the consumer to describe the behavior of external events (in this case the behavior of the tooltip if an external event was received from the chart).
The main setting is the ability to turn on the tooltip visibility if an external pointer event is received. Then the consumer can also configure the placement of the tooltip and the boundary, as it can be done for a normal tooltip.
The `ExternalPointerEventsSettings` is structured like that to accommodate in the future more settings, not only for tooltip but for anything that can be configured in case of external events (like change/update the legend extra value when we receive an event from another chart).

close #695
  • Loading branch information
markov00 authored Jun 18, 2020
1 parent d920ddf commit cc31739
Show file tree
Hide file tree
Showing 31 changed files with 627 additions and 262 deletions.
24 changes: 3 additions & 21 deletions .playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,15 @@
html,
body {
background: blanchedalmond !important;
/*margin-left: 8px !important;*/
/*padding: 8px !important;*/
/*height: 100%;*/
/*width: 2000px;
}
#root {
position: absolute;
/*
top: 0;
left: 0;
*/
/* width: 100%;
height: 100%;*/
/* overflow-x: hidden; */
}

#root {
height: 500px;
}

.chart {
background: white;
/*display: inline-block;
position: relative;
*/
width: 100%;
height: 500px;
width: 500px;
height: 200px;
overflow: auto;
}

Expand All @@ -47,7 +29,7 @@
}

.page {
padding: 100px;
padding: 10px;
}

label {
Expand Down
260 changes: 196 additions & 64 deletions .playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,78 +17,210 @@
* under the License.
*/

import React, { useState } from 'react';

import { Chart, BarSeries, LegendColorPicker, Settings, ScaleType } from '../src';
import { SeededDataGenerator } from '../src/mocks/utils';

const dg = new SeededDataGenerator();

type SetColorFn = (color: string) => void;
const legendColorPickerFn = (setColors: SetColorFn, customColor: string): LegendColorPicker => ({ onClose }) => (
<div id="colorPicker">
<span>Custom Color Picker</span>
<button
id="change"
type="button"
onClick={() => {
setTimeout(() => {
onClose();
setColors(customColor);
}, 0);
}}
>
{customColor}
</button>
<button id="close" type="button" onClick={onClose}>
close
</button>
import React from 'react';

import {
Chart,
Settings,
Axis,
Position,
BarSeries,
ScaleType,
PointerEvent,
LineSeries,
CustomTooltip,
TooltipType,
LineAnnotation, AnnotationDomainTypes,
} from '../src';
import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana';


const TestCustomTooltip: CustomTooltip = (props) => (
<div style={{ border: '1px solid black', background: 'white', fontSize: 12, padding: 4 }}>
<p>Testing a custom tooltip </p>
<ul>
{
props.values.map((value) => (
<li key={value.label} style={{ color: value.color }}>
{value.label}
{' - '}
{value.value}
</li>
))
}
</ul>
</div>
);
function LegendColorPickerMock(props: { onLegendItemClick: () => void; customColor: string }) {
const data = dg.generateGroupedSeries(10, 4, 'split');
const [color, setColor] = useState('red');
export const Playground = () => {
const ref1 = React.createRef<Chart>();
const ref2 = React.createRef<Chart>();
const ref3 = React.createRef<Chart>();
const ref4 = React.createRef<Chart>();

const pointerUpdate = (event: PointerEvent) => {
if (ref1.current) {
ref1.current.dispatchExternalPointerEvent(event);
}
if (ref2.current) {
ref2.current.dispatchExternalPointerEvent(event);
}
if (ref3.current) {
ref3.current.dispatchExternalPointerEvent(event);
}
if (ref4.current) {
ref4.current.dispatchExternalPointerEvent(event);
}
};

return (
<>
<div className="testing">
<button
type="button"
onClick={() => {
setColor('violet');
// if (ref1.current) {
// ref1.current.dispatchExternalPointerEvent({
// chartId: 'chart1',
// type: 'Over',
// scale: 'time',
// value: 1551438420000,
// });
// }
if (ref1.current) {
ref1.current.dispatchExternalPointerEvent({ chartId: 'chart2', type: 'Over', unit: undefined, scale: 'time', value: 1551439800000 });
}
}}
>
change
out

</button>
<Chart className="chart">
<Settings
showLegend
onLegendItemClick={props.onLegendItemClick}
legendColorPicker={legendColorPickerFn(setColor, props.customColor)}
/>
<BarSeries
id="areas"
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
splitSeriesAccessors={['g']}
color={color}
data={data}
/>
</Chart>
</>
);
}

export class Playground extends React.Component {
render() {
return (
<LegendColorPickerMock
customColor="blue"
onLegendItemClick={() => {
// npo

<button
type="button"
onClick={() => {
if (ref1.current) {
ref1.current.dispatchExternalPointerEvent({ chartId: 'chart2', type: 'Over', unit: undefined, scale: 'time', value: 1551439770000 });
}
}}
/>
);
}
}
>
valid

</button>

<div className="chart">
<Chart className="story-chart" ref={ref1} id="chart1">
<Settings onPointerUpdate={pointerUpdate} externalPointerEvents={{ tooltip: { visible: true } }} />
<Axis
id="bottom"
position={Position.Bottom}
title="External tooltip VISIBLE"

/>
<Axis id="left2" position={Position.Left} tickFormat={(d: any) => Number(d).toFixed(2)} />
<LineAnnotation
marker={<div>Hello</div>}
dataValues={[{ dataValue: KIBANA_METRICS.metrics.kibana_os_load[0].data[10][0], details: 'hello' }]}
id="test"
domainType={AnnotationDomainTypes.XDomain}
/>
<BarSeries
id="bars"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(3, 60)}
/>
</Chart>

</div>

<div className="chart">
<Chart className="story-chart" ref={ref2} id="chart2">
<Settings onPointerUpdate={pointerUpdate} externalPointerEvents={{ tooltip: { visible: true, boundary: 'chart' } }} />
<Axis
id="bottom"
position={Position.Bottom}
title="External tooltip VISIBLE - boundary => chart"
/>
<Axis id="left2" title="Left axis" position={Position.Left} tickFormat={(d: any) => Number(d).toFixed(2)} />

<BarSeries
id="bars"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[1].data}
/>
</Chart>

</div>


<div className="chart">
<Chart className="story-chart" ref={ref3}>
<Settings
onPointerUpdate={pointerUpdate}
externalPointerEvents={{ tooltip: { visible: true, boundary: 'chart' } }}
tooltip={{
type: TooltipType.Follow,
customTooltip: TestCustomTooltip,
}}
/>
<Axis
id="bottom"
position={Position.Bottom}
title="External tooltip VISIBLE - boundary => chart"
/>
<Axis id="left2" title="Left axis" position={Position.Left} tickFormat={(d: any) => Number(d).toFixed(2)} />

<LineSeries
id="bars"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 50)}
/>
<LineSeries
id="bars2"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[2].data.slice(20, 30)}
/>
</Chart>
</div>

<div className="chart">
<Chart className="story-chart" ref={ref4} id="chart4">
<Settings onPointerUpdate={pointerUpdate} tooltip={{ type: TooltipType.None }} externalPointerEvents={{ tooltip: { visible: false } }} />
<Axis
id="bottom"
position={Position.Bottom}
title="External tooltip HIDDEN"
/>
<Axis id="left2" title="Left axis" position={Position.Left} tickFormat={(d: any) => Number(d).toFixed(2)} />

<LineSeries
id="bars"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 50)}
/>
<LineSeries
id="bars2"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[2].data.slice(20, 30)}
/>
</Chart>
</div>
</div>
);
};
22 changes: 17 additions & 5 deletions api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ export const DEFAULT_TOOLTIP_TYPE: "vertical";
// Warning: (ae-missing-release-tag) "DefaultSettingsProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'animateData' | 'showLegend' | 'debug' | 'tooltip' | 'showLegendExtra' | 'theme' | 'legendPosition' | 'hideDuplicateAxes' | 'brushAxis' | 'minBrushDelta';
export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'animateData' | 'showLegend' | 'debug' | 'tooltip' | 'showLegendExtra' | 'theme' | 'legendPosition' | 'hideDuplicateAxes' | 'brushAxis' | 'minBrushDelta' | 'externalPointerEvents';

// Warning: (ae-missing-release-tag) "DisplayValueSpec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
Expand Down Expand Up @@ -570,6 +570,16 @@ export type ElementClickListener = (elements: Array<XYChartElementEvent | Partit
// @public (undocumented)
export type ElementOverListener = (elements: Array<XYChartElementEvent | PartitionElementEvent>) => void;

// @alpha
export interface ExternalPointerEventsSettings {
tooltip: {
visible?: boolean;
placement?: Placement;
fallbackPlacements?: Placement[];
boundary?: HTMLElement | 'chart';
};
}

// Warning: (ae-missing-release-tag) "FillStyle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
Expand Down Expand Up @@ -1329,16 +1339,16 @@ export type SeriesTypes = $Values<typeof SeriesTypes>;
// @public (undocumented)
export const Settings: React.FunctionComponent<SettingsSpecProps>;

// Warning: (ae-missing-release-tag) "SettingsSpec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public
export interface SettingsSpec extends Spec {
// (undocumented)
animateData: boolean;
baseTheme?: Theme;
brushAxis?: BrushAxis;
// (undocumented)
debug: boolean;
// @alpha
externalPointerEvents: ExternalPointerEventsSettings;
flatLegend?: boolean;
hideDuplicateAxes: boolean;
// (undocumented)
Expand Down Expand Up @@ -1391,7 +1401,9 @@ export interface SettingsSpec extends Spec {
// Warning: (ae-missing-release-tag) "SettingsSpecProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type SettingsSpecProps = Partial<Omit<SettingsSpec, 'chartType' | 'specType' | 'id'>>;
export type SettingsSpecProps = Partial<Omit<SettingsSpec, 'chartType' | 'specType' | 'id' | 'externalPointerEvents'>> & {
externalPointerEvents?: RecursivePartial<SettingsSpec['externalPointerEvents']>;
};

// Warning: (ae-missing-release-tag) "SharedGeometryStateStyle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
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.
12 changes: 12 additions & 0 deletions integration/tests/interactions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,16 @@ describe('Interactions', () => {
);
});
});

describe('Tooltip sync', () => {
it('show synced tooltips', async() => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/interactions--cursor-update-action',
{ left: 180, top: 80 },
{
screenshotSelector: '#story-root',
}
);
});
});
});
Loading

0 comments on commit cc31739

Please sign in to comment.