Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show tooltip for external events #698

Merged
merged 19 commits into from
Jun 18, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 7 additions & 17 deletions .playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +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; */
}

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

Expand All @@ -40,14 +27,17 @@
width: 100%;
overflow: auto;
}

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

label {
display: block;
}
</style>
</head>

<body>
<div class="page">
<div id="root"></div>
Expand Down
154 changes: 144 additions & 10 deletions .playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,150 @@
* under the License.
*/

import React from 'react';
import React, { useState } from 'react';

import { Example } from '../stories/treemap/6_custom_style';
import { Chart, Settings, Axis, Position, BarSeries, ScaleType, PointerEvent, LineSeries } from '../src';
import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana';

export class Playground extends React.Component {
render() {
return (
<div className="testing">
<div className="chart">{Example()}</div>
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);
}
};
const [show, toggleVisibility] = useState(true);
return (
<div className="testing">
<button
type="button"
onClick={() => {
toggleVisibility(!show);
}}
>
hide/show

</button>

<div className="chart">
<Chart className="story-chart" ref={ref2} 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)} />

<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>

{
show && (
<div className="chart">
<Chart className="story-chart" ref={ref1}>
<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' } }} />
<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}>
<Settings onPointerUpdate={pointerUpdate} 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>
);
};
18 changes: 14 additions & 4 deletions api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,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 @@ -547,6 +547,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 @@ -1268,16 +1278,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
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',
}
);
});
});
});
4 changes: 2 additions & 2 deletions src/chart_types/xy_chart/state/selectors/get_cursor_band.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ function getCursorBand(
let xValue;
if (isValidPointerOverEvent(xScale, externalPointerEvent)) {
const x = xScale.pureScale(externalPointerEvent.value);

if (x == null || x > chartDimensions.width + chartDimensions.left) {
if (x == null || x > chartDimensions.width || x < 0) {
return;
}
pointerPosition = { x, y: 0 };
Expand All @@ -111,6 +110,7 @@ function getCursorBand(
return;
}
}

return getCursorBandPosition(
settingsSpec.rotation,
chartDimensions,
Expand Down
14 changes: 10 additions & 4 deletions src/chart_types/xy_chart/state/selectors/is_tooltip_visible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,46 @@ import { TooltipType, getTooltipType } from '../../../../specs';
import { GlobalChartState, PointerStates } from '../../../../state/chart_state';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
import { isExternalTooltipVisibleSelector } from '../../../../state/selectors/is_external_tooltip_visible';
import { Point } from '../../../../utils/point';
import { getProjectedPointerPositionSelector } from './get_projected_pointer_position';
import { getTooltipInfoSelector } from './get_tooltip_values_highlighted_geoms';
import { isAnnotationTooltipVisibleSelector } from './is_annotation_tooltip_visible';

const hasTooltipTypeDefinedSelector = (state: GlobalChartState): TooltipType | undefined => getTooltipType(getSettingsSpecSelector(state));
const getTooltipTypeSelector = (state: GlobalChartState): TooltipType => getTooltipType(getSettingsSpecSelector(state));

const getPointerSelector = (state: GlobalChartState) => state.interactions.pointer;


/** @internal */
export const isTooltipVisibleSelector = createCachedSelector(
[
hasTooltipTypeDefinedSelector,
getTooltipTypeSelector,
getPointerSelector,
getProjectedPointerPositionSelector,
getTooltipInfoSelector,
isAnnotationTooltipVisibleSelector,
isExternalTooltipVisibleSelector,
],
isTooltipVisible,
)(getChartIdSelector);

function isTooltipVisible(
tooltipType: TooltipType | undefined,
tooltipType: TooltipType,
pointer: PointerStates,
projectedPointerPosition: Point,
tooltip: TooltipInfo,
isAnnotationTooltipVisible: boolean,
externalTooltipVisible: boolean,
) {
return (
const isLocalTooltop = (
tooltipType !== TooltipType.None
&& pointer.down === null
&& projectedPointerPosition.x > -1
&& projectedPointerPosition.y > -1
&& tooltip.values.length > 0
&& !isAnnotationTooltipVisible
);

return isLocalTooltop || externalTooltipVisible;
}
2 changes: 1 addition & 1 deletion src/components/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class Chart extends React.Component<ChartProps, ChartState> {
this.chartContainerRef = createRef();
this.chartStageRef = createRef();

const id = uuid.v4();
const id = props.id ?? uuid.v4();
markov00 marked this conversation as resolved.
Show resolved Hide resolved
const storeReducer = chartStoreReducer(id);
const enhancers = typeof window !== 'undefined' && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true, name: `@elastic/charts (id: ${id})` })()
Expand Down
Loading