diff --git a/.playground/index.html b/.playground/index.html
index 4f0a564770..2403a54bfc 100644
--- a/.playground/index.html
+++ b/.playground/index.html
@@ -9,24 +9,6 @@
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 {
@@ -34,8 +16,8 @@
/*display: inline-block;
position: relative;
*/
- width: 100%;
- height: 500px;
+ width: 500px;
+ height: 200px;
overflow: auto;
}
@@ -47,7 +29,7 @@
}
.page {
- padding: 100px;
+ padding: 10px;
}
label {
diff --git a/.playground/playground.tsx b/.playground/playground.tsx
index e19342b187..40ad254eee 100644
--- a/.playground/playground.tsx
+++ b/.playground/playground.tsx
@@ -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 }) => (
-
-
Custom Color Picker
-
-
+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) => (
+
+
Testing a custom tooltip
+
+ {
+ props.values.map((value) => (
+ -
+ {value.label}
+ {' - '}
+ {value.value}
+
+ ))
+ }
+
);
-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
();
+ const ref2 = React.createRef();
+ const ref3 = React.createRef();
+ const ref4 = React.createRef();
+
+ 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 (
- <>
+
-
-
-
-
- >
- );
-}
-
-export class Playground extends React.Component {
- render() {
- return (
-
{
- // npo
+
+
+
+
+
+
+
+ Number(d).toFixed(2)} />
+ Hello }
+ dataValues={[{ dataValue: KIBANA_METRICS.metrics.kibana_os_load[0].data[10][0], details: 'hello' }]}
+ id="test"
+ domainType={AnnotationDomainTypes.XDomain}
+ />
+
+
+
+
+
+
+
+
+
+ Number(d).toFixed(2)} />
+
+
+
+
+
+
+
+
+
+
+
+ Number(d).toFixed(2)} />
+
+
+
+
+
+
+
+
+
+
+ Number(d).toFixed(2)} />
+
+
+
+
+
+
+ );
+};
diff --git a/api/charts.api.md b/api/charts.api.md
index 53f209317e..b919712ea9 100644
--- a/api/charts.api.md
+++ b/api/charts.api.md
@@ -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)
//
@@ -570,6 +570,16 @@ export type ElementClickListener = (elements: Array) => 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)
@@ -1329,9 +1339,7 @@ export type SeriesTypes = $Values;
// @public (undocumented)
export const Settings: React.FunctionComponent;
-// 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;
@@ -1339,6 +1347,8 @@ export interface SettingsSpec extends Spec {
brushAxis?: BrushAxis;
// (undocumented)
debug: boolean;
+ // @alpha
+ externalPointerEvents: ExternalPointerEventsSettings;
flatLegend?: boolean;
hideDuplicateAxes: boolean;
// (undocumented)
@@ -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>;
+export type SettingsSpecProps = Partial> & {
+ externalPointerEvents?: RecursivePartial;
+};
// Warning: (ae-missing-release-tag) "SharedGeometryStateStyle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-interactions-cursor-update-action-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-interactions-cursor-update-action-visually-looks-correct-1-snap.png
index b0923cea76..5112f10000 100644
Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-interactions-cursor-update-action-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-interactions-cursor-update-action-visually-looks-correct-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/interactions-test-ts-interactions-tooltip-sync-show-synced-tooltips-1-snap.png b/integration/tests/__image_snapshots__/interactions-test-ts-interactions-tooltip-sync-show-synced-tooltips-1-snap.png
new file mode 100644
index 0000000000..14c30a26dd
Binary files /dev/null and b/integration/tests/__image_snapshots__/interactions-test-ts-interactions-tooltip-sync-show-synced-tooltips-1-snap.png differ
diff --git a/integration/tests/interactions.test.ts b/integration/tests/interactions.test.ts
index adc2fe9151..7e7b02d61f 100644
--- a/integration/tests/interactions.test.ts
+++ b/integration/tests/interactions.test.ts
@@ -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',
+ }
+ );
+ });
+ });
});
diff --git a/src/chart_types/goal_chart/state/chart_state.tsx b/src/chart_types/goal_chart/state/chart_state.tsx
index 85306023a9..3c4f1be200 100644
--- a/src/chart_types/goal_chart/state/chart_state.tsx
+++ b/src/chart_types/goal_chart/state/chart_state.tsx
@@ -93,7 +93,7 @@ export class GoalState implements InternalChartState {
}
isTooltipVisible(globalState: GlobalChartState) {
- return isTooltipVisibleSelector(globalState);
+ return { visible: isTooltipVisibleSelector(globalState), isExternal: false };
}
getTooltipInfo(globalState: GlobalChartState) {
diff --git a/src/chart_types/partition_chart/state/chart_state.tsx b/src/chart_types/partition_chart/state/chart_state.tsx
index cd2c2a5006..a5045bc2f3 100644
--- a/src/chart_types/partition_chart/state/chart_state.tsx
+++ b/src/chart_types/partition_chart/state/chart_state.tsx
@@ -95,7 +95,7 @@ export class PartitionState implements InternalChartState {
}
isTooltipVisible(globalState: GlobalChartState) {
- return isTooltipVisibleSelector(globalState);
+ return { visible: isTooltipVisibleSelector(globalState), isExternal: false };
}
getTooltipInfo(globalState: GlobalChartState) {
diff --git a/src/chart_types/xy_chart/renderer/dom/annotations/annotation_tooltip.tsx b/src/chart_types/xy_chart/renderer/dom/annotations/annotation_tooltip.tsx
index 0e7eb78d4b..892414cf7a 100644
--- a/src/chart_types/xy_chart/renderer/dom/annotations/annotation_tooltip.tsx
+++ b/src/chart_types/xy_chart/renderer/dom/annotations/annotation_tooltip.tsx
@@ -56,7 +56,9 @@ export const AnnotationTooltip = ({ state, chartRef, chartId, onScroll }: RectAn
const position = useMemo(() => state?.anchor ?? null, [state?.anchor]);
const placement = useMemo(() => state?.anchor?.position ?? Placement.Right, [state?.anchor?.position]);
-
+ if (!state?.isVisible) {
+ return null;
+ }
return (
{
tooltipType: TooltipType.None,
};
}
+ const settings = getSettingsSpecSelector(state);
+ const cursorBandPosition = getCursorBandPositionSelector(state);
+
+ const tooltipType = getTooltipType(settings, cursorBandPosition?.fromExternalEvent);
return {
theme: getChartThemeSelector(state),
chartRotation: getChartRotationSelector(state),
- cursorBandPosition: getCursorBandPositionSelector(state),
+ cursorBandPosition,
cursorLinePosition: getCursorLinePositionSelector(state),
- tooltipType: getTooltipTypeSelector(state),
+ tooltipType,
};
};
diff --git a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts
index c461cbd466..c7d81e0199 100644
--- a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts
+++ b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts
@@ -245,7 +245,7 @@ describe('Chart state pointer interactions', () => {
// no tooltip values exist if we have a TooltipType === None
expect(tooltipInfo.tooltip.values.length).toBe(0);
let isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(false);
+ expect(isTooltipVisible.visible).toBe(false);
updatedSettings = {
...settingSpec,
@@ -261,7 +261,7 @@ describe('Chart state pointer interactions', () => {
const highlightedGeometries = getHighlightedGeomsSelector(store.getState());
expect(highlightedGeometries.length).toBe(1);
isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(true);
+ expect(isTooltipVisible.visible).toBe(true);
});
describe('mouse over with Ordinal scale', () => {
@@ -415,7 +415,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
expect(cursorBandPosition?.left).toBe(chartLeft + 0);
expect(cursorBandPosition?.width).toBe(45);
let isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(true);
+ expect(isTooltipVisible.visible).toBe(true);
tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.tooltip.values.length).toBe(1);
expect(tooltipInfo.highlightedGeometries.length).toBe(1);
@@ -443,7 +443,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
projectedPointerPosition = getProjectedPointerPositionSelector(store.getState());
expect(projectedPointerPosition).toEqual({ x: -1, y: -1 });
isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(false);
+ expect(isTooltipVisible.visible).toBe(false);
tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.tooltip.values.length).toBe(0);
expect(tooltipInfo.highlightedGeometries.length).toBe(0);
@@ -460,7 +460,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
expect(cursorBandPosition?.left).toBe(chartLeft + 0);
expect(cursorBandPosition?.width).toBe(45);
let isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(true);
+ expect(isTooltipVisible.visible).toBe(true);
let tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.highlightedGeometries.length).toBe(1);
expect(tooltipInfo.tooltip.values.length).toBe(1);
@@ -487,7 +487,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
projectedPointerPosition = getProjectedPointerPositionSelector(store.getState());
expect(projectedPointerPosition).toEqual({ x: -1, y: 89 });
isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(false);
+ expect(isTooltipVisible.visible).toBe(false);
tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.tooltip.values.length).toBe(0);
expect(tooltipInfo.highlightedGeometries.length).toBe(0);
@@ -508,7 +508,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
expect(cursorBandPosition?.left).toBe(chartLeft + 0);
expect(cursorBandPosition?.width).toBe(45);
let isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(true);
+ expect(isTooltipVisible.visible).toBe(true);
let tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.highlightedGeometries.length).toBe(1);
expect(tooltipInfo.tooltip.values.length).toBe(1);
@@ -540,7 +540,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
expect(cursorBandPosition?.left).toBe(chartLeft + 45);
expect(cursorBandPosition?.width).toBe(45);
isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(true);
+ expect(isTooltipVisible.visible).toBe(true);
tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.tooltip.values.length).toBe(1);
expect(tooltipInfo.highlightedGeometries.length).toBe(0);
@@ -561,7 +561,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
expect(cursorBandPosition?.left).toBe(chartLeft + 0);
expect(cursorBandPosition?.width).toBe(45);
let isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(true);
+ expect(isTooltipVisible.visible).toBe(true);
let tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.highlightedGeometries.length).toBe(1);
expect(tooltipInfo.tooltip.values.length).toBe(1);
@@ -593,7 +593,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
expect(cursorBandPosition?.left).toBe(chartLeft + 45);
expect(cursorBandPosition?.width).toBe(45);
isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(true);
+ expect(isTooltipVisible.visible).toBe(true);
tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.tooltip.values.length).toBe(1);
// we are over the second bar here
@@ -638,7 +638,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
expect(cursorBandPosition?.width).toBe(45);
const isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(true);
+ expect(isTooltipVisible.visible).toBe(true);
tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.highlightedGeometries.length).toBe(0);
expect(tooltipInfo.tooltip.values.length).toBe(1);
@@ -690,7 +690,7 @@ function mouseOverTestSuite(scaleType: ScaleType) {
expect(cursorBandPosition?.left).toBe(chartLeft + 45);
expect(cursorBandPosition?.width).toBe(45);
const isTooltipVisible = isTooltipVisibleSelector(store.getState());
- expect(isTooltipVisible).toBe(true);
+ expect(isTooltipVisible.visible).toBe(true);
const tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState());
expect(tooltipInfo.highlightedGeometries.length).toBe(1);
expect(tooltipInfo.tooltip.values.length).toBe(1);
diff --git a/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts b/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts
index 39d29f9b04..6c7cfdc26f 100644
--- a/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts
+++ b/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts
@@ -86,7 +86,7 @@ function getCursorBand(
totalBarsInCluster: number,
isTooltipSnapEnabled: boolean,
geometriesIndexKeys: (string | number)[],
-): (Dimensions & { visible: boolean }) | undefined {
+): (Dimensions & { visible: boolean, fromExternalEvent: boolean }) | undefined {
// update che cursorBandPosition based on chart configuration
const isLineAreaOnly = isLineAreaOnlyChart(seriesSpecs);
if (!xScale) {
@@ -94,10 +94,12 @@ function getCursorBand(
}
let pointerPosition = orientedProjectedPoinerPosition;
let xValue;
+ let fromExternalEvent = false;
+ // external pointer events takes precendence over the current mouse pointer
if (isValidPointerOverEvent(xScale, externalPointerEvent)) {
+ fromExternalEvent = true;
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 };
@@ -111,7 +113,8 @@ function getCursorBand(
return;
}
}
- return getCursorBandPosition(
+
+ const cursorBand = getCursorBandPosition(
settingsSpec.rotation,
chartDimensions,
pointerPosition,
@@ -123,4 +126,8 @@ function getCursorBand(
xScale,
isLineAreaOnly ? 1 : totalBarsInCluster,
);
+ return {
+ ...cursorBand,
+ fromExternalEvent,
+ };
}
diff --git a/src/chart_types/xy_chart/state/selectors/get_elements_at_cursor_pos.ts b/src/chart_types/xy_chart/state/selectors/get_elements_at_cursor_pos.ts
index 721154aa2b..facacd197a 100644
--- a/src/chart_types/xy_chart/state/selectors/get_elements_at_cursor_pos.ts
+++ b/src/chart_types/xy_chart/state/selectors/get_elements_at_cursor_pos.ts
@@ -64,7 +64,7 @@ function getElementAtCursorPosition(
if (isValidPointerOverEvent(scales.xScale, externalPointerEvent)) {
const x = scales.xScale.pureScale(externalPointerEvent.value);
- if (x == null || x > chartDimensions.width + chartDimensions.left) {
+ if (x == null || x > chartDimensions.width + chartDimensions.left || x < 0) {
return [];
}
// TODO: Handle external event with spatial points
diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts
index c7863a33ce..1b0f1b9a7f 100644
--- a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts
+++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts
@@ -27,6 +27,7 @@ import {
TooltipValueFormatter,
isFollowTooltipType,
SettingsSpec,
+ getTooltipType,
} from '../../../../specs';
import { TooltipType } from '../../../../specs/constants';
import { GlobalChartState } from '../../../../state/chart_state';
@@ -48,7 +49,6 @@ import { getElementAtCursorPositionSelector } from './get_elements_at_cursor_pos
import { getOrientedProjectedPointerPositionSelector } from './get_oriented_projected_pointer_position';
import { getProjectedPointerPositionSelector } from './get_projected_pointer_position';
import { getSeriesSpecsSelector, getAxisSpecsSelector } from './get_specs';
-import { getTooltipTypeSelector } from './get_tooltip_type';
import { hasSingleSeriesSelector } from './has_single_series';
const EMPTY_VALUES = Object.freeze({
@@ -79,7 +79,6 @@ export const getTooltipInfoAndGeometriesSelector = createCachedSelector(
hasSingleSeriesSelector,
getComputedScalesSelector,
getElementAtCursorPositionSelector,
- getTooltipTypeSelector,
getExternalPointerEventStateSelector,
getTooltipHeaderFormatterSelector,
],
@@ -96,18 +95,17 @@ function getTooltipAndHighlightFromValue(
hasSingleSeries: boolean,
scales: ComputedScales,
matchingGeoms: IndexedGeometry[],
- tooltipType: TooltipType,
externalPointerEvent: PointerEvent | null,
tooltipHeaderFormatter?: TooltipValueFormatter,
): TooltipAndHighlightedGeoms {
if (!scales.xScale || !scales.yScales) {
return EMPTY_VALUES;
}
- if (tooltipType === TooltipType.None) {
- return EMPTY_VALUES;
- }
+
let { x, y } = orientedProjectedPointerPosition;
+ let tooltipType = getTooltipType(settings);
if (isValidPointerOverEvent(scales.xScale, externalPointerEvent)) {
+ tooltipType = getTooltipType(settings, true);
const scaledX = scales.xScale.pureScale(externalPointerEvent.value);
if (scaledX === null) {
@@ -120,6 +118,10 @@ function getTooltipAndHighlightFromValue(
return EMPTY_VALUES;
}
+ if (tooltipType === TooltipType.None) {
+ return EMPTY_VALUES;
+ }
+
if (matchingGeoms.length === 0) {
return EMPTY_VALUES;
}
diff --git a/src/chart_types/xy_chart/state/selectors/is_tooltip_visible.ts b/src/chart_types/xy_chart/state/selectors/is_tooltip_visible.ts
index 35112a3d93..8852bfe0b3 100644
--- a/src/chart_types/xy_chart/state/selectors/is_tooltip_visible.ts
+++ b/src/chart_types/xy_chart/state/selectors/is_tooltip_visible.ts
@@ -25,35 +25,39 @@ import { TooltipType } from '../../../../specs/constants';
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
@@ -61,4 +65,9 @@ function isTooltipVisible(
&& tooltip.values.length > 0
&& !isAnnotationTooltipVisible
);
+ const isExternalTooltip = externalTooltipVisible && tooltip.values.length > 0;
+ return {
+ visible: isLocalTooltop || isExternalTooltip,
+ isExternal: externalTooltipVisible,
+ };
}
diff --git a/src/chart_types/xy_chart/state/utils/utils.ts b/src/chart_types/xy_chart/state/utils/utils.ts
index d8eb585365..37660b5945 100644
--- a/src/chart_types/xy_chart/state/utils/utils.ts
+++ b/src/chart_types/xy_chart/state/utils/utils.ts
@@ -482,10 +482,7 @@ function renderGeometries(
const valueFormatter = yAxis && yAxis.tickFormat ? yAxis.tickFormat : identity;
const displayValueSettings = spec.displayValueSettings
- ? {
- valueFormatter,
- ...spec.displayValueSettings,
- }
+ ? { valueFormatter, ...spec.displayValueSettings }
: undefined;
const renderedBars = renderBars(
diff --git a/src/components/__snapshots__/chart.test.tsx.snap b/src/components/__snapshots__/chart.test.tsx.snap
index 00df8cd9ad..7b12935f4f 100644
--- a/src/components/__snapshots__/chart.test.tsx.snap
+++ b/src/components/__snapshots__/chart.test.tsx.snap
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Chart should render the legend name test 1`] = `""`;
+exports[`Chart should render the legend name test 1`] = `""`;
diff --git a/src/components/legend/legend.tsx b/src/components/legend/legend.tsx
index c5a0340921..b536fa35c5 100644
--- a/src/components/legend/legend.tsx
+++ b/src/components/legend/legend.tsx
@@ -68,57 +68,49 @@ interface LegendDispatchProps {
setTemporaryColor: typeof setTemporaryColor;
setPersistedColor: typeof setPersistedColor;
}
-type LegendProps = LegendStateProps & LegendDispatchProps;
-/**
- * @internal
- */
-export class LegendComponent extends React.Component {
- static displayName = 'Legend';
-
- render() {
- const {
- items,
- position,
- size,
- debug,
- chartTheme: { chartMargins, legend },
- } = this.props;
- if (items.length === 0) {
- return null;
- }
- const legendContainerStyle = getLegendStyle(position, size);
- const legendListStyle = getLegendListStyle(position, chartMargins, legend);
- const legendClasses = classNames('echLegend', `echLegend--${position}`, {
- 'echLegend--debug': debug,
- });
+function LegendComponent(props: LegendStateProps & LegendDispatchProps) {
+ const {
+ items,
+ position,
+ size,
+ debug,
+ chartTheme: { chartMargins, legend },
+ } = props;
+ if (items.length === 0) {
+ return null;
+ }
+ const legendContainerStyle = getLegendStyle(position, size);
+ const legendListStyle = getLegendListStyle(position, chartMargins, legend);
+ const legendClasses = classNames('echLegend', `echLegend--${position}`, {
+ 'echLegend--debug': debug,
+ });
- const itemProps: Omit = {
- position,
- totalItems: items.length,
- extraValues: this.props.extraValues,
- showExtra: this.props.showExtra,
- onMouseOut: this.props.onItemOut,
- onMouseOver: this.props.onItemOver,
- onClick: this.props.onItemClick,
- clearTemporaryColorsAction: this.props.clearTemporaryColors,
- setPersistedColorAction: this.props.setPersistedColor,
- setTemporaryColorAction: this.props.setTemporaryColor,
- mouseOutAction: this.props.onItemOutAction,
- mouseOverAction: this.props.onItemOverAction,
- toggleDeselectSeriesAction: this.props.onToggleDeselectSeriesAction,
- colorPicker: this.props.colorPicker,
- };
- return (
-
-
-
- {items.map((item, index) => renderLegendItem(item, itemProps, items.length, index))}
-
-
+ const itemProps: Omit
= {
+ position,
+ totalItems: items.length,
+ extraValues: props.extraValues,
+ showExtra: props.showExtra,
+ onMouseOut: props.onItemOut,
+ onMouseOver: props.onItemOver,
+ onClick: props.onItemClick,
+ clearTemporaryColorsAction: props.clearTemporaryColors,
+ setPersistedColorAction: props.setPersistedColor,
+ setTemporaryColorAction: props.setTemporaryColor,
+ mouseOutAction: props.onItemOutAction,
+ mouseOverAction: props.onItemOverAction,
+ toggleDeselectSeriesAction: props.onToggleDeselectSeriesAction,
+ colorPicker: props.colorPicker,
+ };
+ return (
+
+
+
+ {items.map((item, index) => renderLegendItem(item, itemProps, items.length, index))}
+
- );
- }
+
+ );
}
const mapDispatchToProps = (dispatch: Dispatch): LegendDispatchProps =>
@@ -145,6 +137,7 @@ const EMPTY_DEFAULT_STATE = {
size: { width: 0, height: 0 },
showExtra: false,
};
+
const mapStateToProps = (state: GlobalChartState): LegendStateProps => {
if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) {
return EMPTY_DEFAULT_STATE;
diff --git a/src/components/portal/_portal.scss b/src/components/portal/_portal.scss
index 90e6ecdc71..aa799d9276 100644
--- a/src/components/portal/_portal.scss
+++ b/src/components/portal/_portal.scss
@@ -11,4 +11,6 @@
.echTooltipPortal__invisible {
position: fixed;
visibility: hidden;
+ width: 0;
+ height: 0;
}
diff --git a/src/components/portal/tooltip_portal.tsx b/src/components/portal/tooltip_portal.tsx
index 1a7a19ff05..573ab14716 100644
--- a/src/components/portal/tooltip_portal.tsx
+++ b/src/components/portal/tooltip_portal.tsx
@@ -18,8 +18,7 @@
*/
import { createPopper, Instance } from '@popperjs/core';
-import classNames from 'classnames';
-import React, { useRef, useEffect, useCallback, ReactNode, useMemo, useState } from 'react';
+import { useRef, useEffect, useCallback, ReactNode, useMemo } from 'react';
import { createPortal } from 'react-dom';
import { mergePartial, isDefined } from '../../utils/commons';
@@ -57,22 +56,21 @@ type PortalTooltipProps = {
};
const TooltipPortalComponent = ({ anchor, scope, settings, children, visible, chartId }: PortalTooltipProps) => {
- /**
- * Used to skip first render for new position, which is used for capture initial position
- */
- const [invisible, setInvisible] = useState(!(visible ?? false));
/**
* Anchor element used to position tooltip
*/
- const anchorNode = useRef(
- isHTMLElement(anchor) ? anchor : getOrCreateNode(`echAnchor${scope}__${chartId}`, anchor?.ref ?? undefined),
+ const anchorNode = useRef(isHTMLElement(anchor)
+ ? anchor
+ : getOrCreateNode(`echAnchor${scope}__${chartId}`, undefined, anchor?.ref ?? undefined),
);
/**
* This must not be removed from DOM throughout life of this component.
* Otherwise the portal will loose reference to the correct node.
*/
- const portalNode = useRef(getOrCreateNode(`echTooltipPortal${scope}`));
+ const portalNodeElement = getOrCreateNode(`echTooltipPortal${scope}__${chartId}`, 'echTooltipPortal__invisible');
+
+ const portalNode = useRef(portalNodeElement);
/**
* Popper instance used to manage position of tooltip.
@@ -81,6 +79,7 @@ const TooltipPortalComponent = ({ anchor, scope, settings, children, visible, ch
const popperSettings = useMemo(
() => mergePartial(DEFAULT_POPPER_SETTINGS, settings, { mergeOptionalPartialValues: true }),
+
[settings],
);
@@ -185,11 +184,11 @@ const TooltipPortalComponent = ({ anchor, scope, settings, children, visible, ch
}, [visible, anchorNode, position?.left, position?.top, position?.width, position?.height]);
useEffect(() => {
- if (position === null) {
- setInvisible(true);
- } else {
- setInvisible(false);
+ if (!position) {
+ portalNode.current.classList.add('echTooltipPortal__invisible');
+ return;
}
+ portalNode.current.classList.remove('echTooltipPortal__invisible');
}, [position]);
useEffect(() => {
@@ -199,12 +198,7 @@ const TooltipPortalComponent = ({ anchor, scope, settings, children, visible, ch
}
}, [updateAnchorDimensions, popper]);
- return createPortal(
-
- {children}
-
,
- portalNode.current,
- );
+ return createPortal(children, portalNode.current);
};
TooltipPortalComponent.displayName = 'TooltipPortal';
diff --git a/src/components/portal/utils.ts b/src/components/portal/utils.ts
index 7d5dc87dc3..f46b653dae 100644
--- a/src/components/portal/utils.ts
+++ b/src/components/portal/utils.ts
@@ -31,7 +31,7 @@ export const DEFAULT_POPPER_SETTINGS: PopperSettings = {
*
* @internal
*/
-export function getOrCreateNode(id: string, parent: HTMLElement = document.body): HTMLDivElement {
+export function getOrCreateNode(id: string, className?: string, parent: HTMLElement = document.body): HTMLDivElement {
// eslint-disable-next-line unicorn/prefer-query-selector
const node = document.getElementById(id);
if (node) {
@@ -40,6 +40,9 @@ export function getOrCreateNode(id: string, parent: HTMLElement = document.body)
const newNode = document.createElement('div');
newNode.id = id;
+ if (className) {
+ newNode.classList.add(className);
+ }
parent.appendChild(newNode);
return newNode;
}
diff --git a/src/components/tooltip/get_tooltip_settings.ts b/src/components/tooltip/get_tooltip_settings.ts
new file mode 100644
index 0000000000..6b134e80e3
--- /dev/null
+++ b/src/components/tooltip/get_tooltip_settings.ts
@@ -0,0 +1,37 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { TooltipSettings, isTooltipType, SettingsSpec } from '../../specs/settings';
+
+/** @internal */
+export function getTooltipSettings(settings: SettingsSpec, isExternalTooltipVisible: boolean): TooltipSettings {
+ if (!isExternalTooltipVisible) {
+ return settings.tooltip;
+ }
+ if (isTooltipType(settings.tooltip)) {
+ return {
+ type: settings.tooltip,
+ ...settings.externalPointerEvents.tooltip,
+ };
+ }
+ return {
+ ...settings.tooltip,
+ ...settings.externalPointerEvents.tooltip,
+ };
+}
diff --git a/src/components/tooltip/tooltip.tsx b/src/components/tooltip/tooltip.tsx
index 9a8f9703f1..37b2ca7449 100644
--- a/src/components/tooltip/tooltip.tsx
+++ b/src/components/tooltip/tooltip.tsx
@@ -23,7 +23,7 @@ import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { TooltipValueFormatter, TooltipSettings, TooltipValue } from '../../specs';
-import { onPointerMove } from '../../state/actions/mouse';
+import { onPointerMove as onPointerMoveAction } from '../../state/actions/mouse';
import { GlobalChartState, BackwardRef } from '../../state/chart_state';
import { getChartRotationSelector } from '../../state/selectors/get_chart_rotation';
import { getChartThemeSelector } from '../../state/selectors/get_chart_theme';
@@ -35,14 +35,16 @@ import { getSettingsSpecSelector } from '../../state/selectors/get_settings_spec
import { getTooltipHeaderFormatterSelector } from '../../state/selectors/get_tooltip_header_formatter';
import { Rotation } from '../../utils/commons';
import { TooltipPortal, PopperSettings, AnchorPosition, Placement } from '../portal';
+import { getTooltipSettings } from './get_tooltip_settings';
import { TooltipInfo, TooltipAnchorPosition } from './types';
+
interface TooltipDispatchProps {
- onPointerMove: typeof onPointerMove;
+ onPointerMove: typeof onPointerMoveAction;
}
interface TooltipStateProps {
- isVisible: boolean;
+ visible: boolean;
position: TooltipAnchorPosition | null;
info?: TooltipInfo;
headerFormatter?: TooltipValueFormatter;
@@ -64,7 +66,7 @@ const TooltipComponent = ({
position,
getChartContainerRef,
settings,
- isVisible,
+ visible,
rotation,
chartId,
onPointerMove,
@@ -138,7 +140,7 @@ const TooltipComponent = ({
);
const renderTooltip = () => {
- if (!info || !isVisible) {
+ if (!info || !visible) {
return null;
}
@@ -156,7 +158,7 @@ const TooltipComponent = ({
};
const anchorPosition = useMemo((): AnchorPosition | null => {
- if (!position || !isVisible) {
+ if (!position || !visible) {
return null;
}
@@ -169,7 +171,7 @@ const TooltipComponent = ({
top: y1 - height,
height,
};
- }, [isVisible, position?.x0, position?.x1, position?.y0, position?.y1]); // eslint-disable-line react-hooks/exhaustive-deps
+ }, [visible, position?.x0, position?.x1, position?.y0, position?.y1]); // eslint-disable-line react-hooks/exhaustive-deps
const popperSettings = useMemo((): Partial | undefined => {
if (typeof settings === 'string') {
@@ -189,7 +191,9 @@ const TooltipComponent = ({
boundary: boundary === 'chart' && chartRef.current ? chartRef.current : undefined,
};
}, [settings, chartRef, rotation]);
-
+ if (!visible) {
+ return null;
+ }
return (
{renderTooltip()}
@@ -209,7 +213,7 @@ const TooltipComponent = ({
TooltipComponent.displayName = 'Tooltip';
const HIDDEN_TOOLTIP_PROPS = {
- isVisible: false,
+ visible: false,
info: undefined,
position: null,
headerFormatter: undefined,
@@ -220,18 +224,24 @@ const HIDDEN_TOOLTIP_PROPS = {
};
const mapDispatchToProps = (dispatch: Dispatch): TooltipDispatchProps =>
- bindActionCreators({ onPointerMove }, dispatch);
+ bindActionCreators({ onPointerMove: onPointerMoveAction }, dispatch);
const mapStateToProps = (state: GlobalChartState): TooltipStateProps => {
if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) {
return HIDDEN_TOOLTIP_PROPS;
}
+ const { visible, isExternal } = getInternalIsTooltipVisibleSelector(state);
+ if (state.chartId === 'chart4') {
+ // console.log(visible, isExternal);
+ }
+ const settingsSpec = getSettingsSpecSelector(state);
+ const settings = getTooltipSettings(settingsSpec, isExternal);
return {
- isVisible: getInternalIsTooltipVisibleSelector(state),
+ visible,
info: getInternalTooltipInfoSelector(state),
position: getInternalTooltipAnchorPositionSelector(state),
headerFormatter: getTooltipHeaderFormatterSelector(state),
- settings: getSettingsSpecSelector(state).tooltip,
+ settings,
rotation: getChartRotationSelector(state),
chartId: state.chartId,
backgroundColor: getChartThemeSelector(state).background.color,
diff --git a/src/mocks/specs/specs.ts b/src/mocks/specs/specs.ts
index eb72f7ec30..0f330876ba 100644
--- a/src/mocks/specs/specs.ts
+++ b/src/mocks/specs/specs.ts
@@ -41,7 +41,7 @@ import {
AxisSpec,
} from '../../chart_types/xy_chart/utils/specs';
import { ScaleType } from '../../scales/constants';
-import { SettingsSpec, SpecTypes, TooltipType } from '../../specs';
+import { SettingsSpec, SpecTypes, DEFAULT_SETTINGS_SPEC } from '../../specs';
import { Datum, mergePartial, Position, RecursivePartial } from '../../utils/commons';
import { LIGHT_THEME } from '../../utils/themes/light_theme';
@@ -267,25 +267,7 @@ export class MockSeriesSpecs {
/** @internal */
export class MockGlobalSpec {
- private static readonly settingsBase: SettingsSpec = {
- id: '__global__settings___',
- chartType: ChartTypes.Global,
- specType: SpecTypes.Settings,
- rendering: 'canvas' as const,
- rotation: 0 as const,
- animateData: true,
- showLegend: false,
- resizeDebounce: 10,
- debug: false,
- tooltip: {
- type: TooltipType.VerticalCursor,
- snap: true,
- },
- legendPosition: Position.Right,
- showLegendExtra: true,
- hideDuplicateAxes: false,
- theme: LIGHT_THEME,
- };
+ private static readonly settingsBase: SettingsSpec = DEFAULT_SETTINGS_SPEC;
private static readonly axisBase: AxisSpec = {
id: 'yAxis',
diff --git a/src/specs/constants.ts b/src/specs/constants.ts
index f045db411f..4ec732533f 100644
--- a/src/specs/constants.ts
+++ b/src/specs/constants.ts
@@ -93,6 +93,11 @@ export const DEFAULT_SETTINGS_SPEC: SettingsSpec = {
type: DEFAULT_TOOLTIP_TYPE,
snap: DEFAULT_TOOLTIP_SNAP,
},
+ externalPointerEvents: {
+ tooltip: {
+ visible: false,
+ },
+ },
legendPosition: Position.Right,
showLegendExtra: false,
hideDuplicateAxes: false,
diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx
index 8cf37edde0..b5b568dea1 100644
--- a/src/specs/settings.tsx
+++ b/src/specs/settings.tsx
@@ -29,7 +29,7 @@ import { CustomTooltip } from '../components/tooltip/types';
import { ScaleContinuousType, ScaleOrdinalType } from '../scales';
import { getConnect, specComponentFactory } from '../state/spec_factory';
import { Accessor } from '../utils/accessor';
-import { Position, Rendering, Rotation, Color } from '../utils/commons';
+import { Position, Rendering, Rotation, Color, RecursivePartial } from '../utils/commons';
import { Domain } from '../utils/domain';
import { GeometryValue } from '../utils/geometry';
import { GroupId } from '../utils/ids';
@@ -171,7 +171,7 @@ export interface TooltipProps {
*
* `'chart'` will use the chart container as the boundary
*
- * @defaultValue parent scroll container
+ * @defaultValue undefined - parent scroll container
*/
boundary?: HTMLElement | 'chart';
/**
@@ -187,11 +187,42 @@ export interface TooltipProps {
}
/**
- * Either a TooltipType or an object with configuration of type, snap, and/or headerFormatter
+ * Either a {@link (TooltipType:type)} or an {@link (TooltipProps:interface)} configuration
* @public
*/
export type TooltipSettings = TooltipType | TooltipProps;
+/**
+ * The settings for handling external events.
+ * @alpha
+ */
+export interface ExternalPointerEventsSettings {
+ /**
+ * Tooltip settings used for external events
+ */
+ tooltip: {
+ /**
+ * `true` to show the tooltip when the chart receive an
+ * external pointer event, 'false' to hide the tooltip.
+ * @defaultValue `false`
+ */
+ visible?: boolean;
+ /**
+ * {@inheritDoc TooltipProps.placement}
+ */
+ placement?: Placement;
+ /**
+ * {@inheritDoc TooltipProps.fallbackPlacements}
+ */
+ fallbackPlacements?: Placement[];
+ /**
+ * {@inheritDoc TooltipProps.boundary}
+ */
+ boundary?: HTMLElement | 'chart';
+ }
+
+}
+
export interface LegendColorPickerProps {
/**
* Anchor used to position picker
@@ -221,6 +252,10 @@ export type LegendColorPicker = React.ComponentType;
*/
export type MarkBuffer = number | ((radius: number) => number);
+/**
+ * The Spec used for Chart settings
+ * @public
+ */
export interface SettingsSpec extends Spec {
/**
* Partial theme to be merged with base
@@ -244,9 +279,14 @@ export interface SettingsSpec extends Spec {
animateData: boolean;
showLegend: boolean;
/**
- * The tooltip configuration forr the chart {@link TooltipSettings}
+ * The tooltip configuration {@link TooltipSettings}
*/
tooltip: TooltipSettings;
+ /**
+ * {@inheritDoc ExternalPointerEventsSettings}
+ * @alpha
+ */
+ externalPointerEvents: ExternalPointerEventsSettings;
debug: boolean;
legendPosition: Position;
/**
@@ -312,9 +352,12 @@ export type DefaultSettingsProps =
| 'legendPosition'
| 'hideDuplicateAxes'
| 'brushAxis'
- | 'minBrushDelta';
+ | 'minBrushDelta'
+ | 'externalPointerEvents';
-export type SettingsSpecProps = Partial>;
+export type SettingsSpecProps = Partial> & {
+ externalPointerEvents?: RecursivePartial
+};
export const Settings: React.FunctionComponent = getConnect()(
specComponentFactory(DEFAULT_SETTINGS_SPEC),
@@ -351,8 +394,11 @@ export function isFollowTooltipType(type: TooltipType) {
}
/** @internal */
-export function getTooltipType(settings: SettingsSpec): TooltipType {
+export function getTooltipType(settings: SettingsSpec, externalTooltip = false): TooltipType {
const defaultType = TooltipType.VerticalCursor;
+ if (externalTooltip) {
+ return getExternalTooltipType(settings);
+ }
const { tooltip } = settings;
if (tooltip === undefined || tooltip === null) {
return defaultType;
@@ -365,3 +411,13 @@ export function getTooltipType(settings: SettingsSpec): TooltipType {
}
return defaultType;
}
+
+
+/**
+ * Always return a Vertical Cursor for external pointer events or None if hidden
+ * @internal
+ * @param settings - the SettingsSpec
+ */
+export function getExternalTooltipType({ externalPointerEvents: { tooltip: { visible } } }: SettingsSpec): TooltipType {
+ return visible ? TooltipType.VerticalCursor : TooltipType.None;
+}
diff --git a/src/state/chart_state.ts b/src/state/chart_state.ts
index 603eeb33ad..7b4469395d 100644
--- a/src/state/chart_state.ts
+++ b/src/state/chart_state.ts
@@ -100,10 +100,10 @@ export interface InternalChartState {
*/
getPointerCursor(globalState: GlobalChartState): string;
/**
- * `true` if the tooltip is visible, `false` otherwise
+ * Describe if the tooltip is visible and comes from an external source
* @param globalState
*/
- isTooltipVisible(globalState: GlobalChartState): boolean;
+ isTooltipVisible(globalState: GlobalChartState): { visible: boolean, isExternal: boolean };
/**
* Get the tooltip information to display
* @param globalState the GlobalChartState
diff --git a/src/state/selectors/get_internal_is_tooltip_visible.ts b/src/state/selectors/get_internal_is_tooltip_visible.ts
index dfb76c0011..659ac4aef8 100644
--- a/src/state/selectors/get_internal_is_tooltip_visible.ts
+++ b/src/state/selectors/get_internal_is_tooltip_visible.ts
@@ -20,9 +20,9 @@
import { GlobalChartState } from '../chart_state';
/** @internal */
-export const getInternalIsTooltipVisibleSelector = (state: GlobalChartState): boolean => {
+export const getInternalIsTooltipVisibleSelector = (state: GlobalChartState): { visible: boolean, isExternal: boolean } => {
if (state.internalChartState) {
return state.internalChartState.isTooltipVisible(state);
}
- return false;
+ return { visible: false, isExternal: false };
};
diff --git a/src/state/selectors/has_external_pointer_event.ts b/src/state/selectors/has_external_pointer_event.ts
new file mode 100644
index 0000000000..5fb6e4b701
--- /dev/null
+++ b/src/state/selectors/has_external_pointer_event.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { PointerEventType } from '../../specs';
+import { GlobalChartState } from '../chart_state';
+
+/** @internal */
+export const hasExternalEventSelector = ({ externalEvents: { pointer } }: GlobalChartState) =>
+ pointer !== null && pointer.type !== PointerEventType.Out;
diff --git a/src/state/selectors/is_external_tooltip_visible.ts b/src/state/selectors/is_external_tooltip_visible.ts
new file mode 100644
index 0000000000..be96e16054
--- /dev/null
+++ b/src/state/selectors/is_external_tooltip_visible.ts
@@ -0,0 +1,46 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import createCachedSelector from 're-reselect';
+
+import { computeChartDimensionsSelector } from '../../chart_types/xy_chart/state/selectors/compute_chart_dimensions';
+import { getComputedScalesSelector } from '../../chart_types/xy_chart/state/selectors/get_computed_scales';
+import { PointerEventType } from '../../specs';
+import { GlobalChartState } from '../chart_state';
+import { getChartIdSelector } from './get_chart_id';
+import { getSettingsSpecSelector } from './get_settings_specs';
+import { hasExternalEventSelector } from './has_external_pointer_event';
+
+const getExternalEventPointer = ({ externalEvents: { pointer } }: GlobalChartState) => pointer;
+
+/** @internal */
+export const isExternalTooltipVisibleSelector = createCachedSelector(
+ [getSettingsSpecSelector, hasExternalEventSelector, getExternalEventPointer, getComputedScalesSelector, computeChartDimensionsSelector],
+ ({ externalPointerEvents }, hasExternalEvent, pointer, { xScale }, { chartDimensions }): boolean => {
+ if (!pointer || pointer.type !== PointerEventType.Over || externalPointerEvents.tooltip?.visible === false) {
+ return false;
+ }
+ const x = xScale.pureScale(pointer.value);
+
+ if (x == null || x > chartDimensions.width + chartDimensions.left || x < 0) {
+ return false;
+ }
+ return hasExternalEvent && externalPointerEvents.tooltip?.visible === true;
+ }
+)(getChartIdSelector);
diff --git a/stories/interactions/16_cursor_update_action.tsx b/stories/interactions/16_cursor_update_action.tsx
index ea7c934800..74705e7c72 100644
--- a/stories/interactions/16_cursor_update_action.tsx
+++ b/stories/interactions/16_cursor_update_action.tsx
@@ -20,35 +20,87 @@
import { action } from '@storybook/addon-actions';
import React from 'react';
-import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '../../src';
+import { Axis, BarSeries, Chart, Position, ScaleType, Settings, PointerEvent, Placement, niceTimeFormatter } from '../../src';
+import { KIBANA_METRICS } from '../../src/utils/data_samples/test_dataset_kibana';
+import { palettes } from '../../src/utils/themes/colors';
+import { SB_SOURCE_PANEL } from '../utils/storybook';
-const onPointerUpdate = action('onPointerUpdate');
+export const Example = () => {
+ const ref1 = React.createRef();
+ const ref2 = React.createRef();
+ const pointerUpdate = (event: PointerEvent) => {
+ action('onPointerUpdate')(event);
+ if (ref1.current) {
+ ref1.current.dispatchExternalPointerEvent(event);
+ }
+ if (ref2.current) {
+ ref2.current.dispatchExternalPointerEvent(event);
+ }
+ };
+ const { data } = KIBANA_METRICS.metrics.kibana_os_load[0];
+ const data1 = KIBANA_METRICS.metrics.kibana_os_load[0].data;
+ const data2 = KIBANA_METRICS.metrics.kibana_os_load[1].data;
+ return (
+ <>
+
+
+
+ Number(d).toFixed(2)} />
-export const Example = () => (
-
-
-
- Number(d).toFixed(2)} />
+
+
+
+
+
-
-);
+ />
+ Number(d).toFixed(2)}
+ domain={{ min: 5, max: 20 }}
+ />
+
+
+
+ >
+ );
+};
Example.story = {
parameters: {
info: {
text: 'Sends an event every time the cursor changes. This is provided to sync cursors between multiple charts.',
},
+ options: { selectedPanel: SB_SOURCE_PANEL },
},
};