From 380363bfb8fc02a3eb22d5832edddba76438314d Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Thu, 3 Jun 2021 20:05:38 +0200 Subject: [PATCH] feat(tooltip): stickTo vertical middle of the cursor (#1163) The `Settings.tooltip.stickTo` prop, used to fix the tooltip movement on the free axis cursor, is enhanced with `Middle`, `Center` and `MousePosition` values. `MousePosition` is the default behavior that moves the tooltip along the free axis following the cursor position. `Middle` and `Center` can be used to fix the tooltip position in the middle of the chart respective for horizontally laid charts and the vertical ones. fix #1108 Co-authored-by: Nick Partridge --- api/charts.api.md | 16 +- .../server/generate/vrt_page_template.js | 20 +- .../goal_chart/state/chart_state.tsx | 6 +- .../state/selectors/get_tooltip_anchor.ts | 19 +- .../partition_chart/state/chart_state.tsx | 6 +- .../wordcloud/state/chart_state.tsx | 6 +- .../annotations/line/tooltip.test.tsx | 6 +- .../xy_chart/annotations/rect/tooltip.test.ts | 6 +- .../xy_chart/annotations/rect/tooltip.ts | 6 +- src/chart_types/xy_chart/annotations/types.ts | 6 +- .../xy_chart/annotations/utils.test.ts | 6 +- src/chart_types/xy_chart/annotations/utils.ts | 2 +- .../crosshair_utils.linear_snap.test.ts | 288 +++++++++--------- .../xy_chart/crosshair/crosshair_utils.ts | 124 +++----- .../xy_chart/renderer/canvas/xy_chart.tsx | 8 +- .../xy_chart/renderer/dom/crosshair.tsx | 25 +- .../xy_chart/state/chart_state.tsx | 6 +- .../selectors/compute_chart_dimensions.ts | 38 +-- .../selectors/get_annotation_tooltip_state.ts | 6 +- .../state/selectors/get_cursor_band.ts | 4 +- .../state/selectors/get_tooltip_position.ts | 5 +- .../xy_chart/utils/dimensions.test.ts | 64 +--- src/chart_types/xy_chart/utils/dimensions.ts | 19 -- src/common/constants.ts | 13 + .../__snapshots__/chart.test.tsx.snap | 4 +- src/components/chart.tsx | 4 +- src/components/portal/tooltip_portal.tsx | 7 +- src/components/portal/types.ts | 25 +- src/components/tooltip/tooltip.tsx | 22 +- src/components/tooltip/types.ts | 28 -- src/specs/constants.ts | 16 + src/specs/settings.tsx | 5 +- src/state/chart_state.ts | 5 +- .../get_internal_tooltip_anchor_position.ts | 4 +- stories/area/2_with_time.tsx | 10 +- stories/bar/2_label_value.tsx | 11 +- stories/bar/48_test_tooltip.tsx | 5 +- stories/utils/knobs.ts | 16 +- 38 files changed, 386 insertions(+), 481 deletions(-) diff --git a/api/charts.api.md b/api/charts.api.md index 9971f96f6a..7b550e9f07 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -2020,12 +2020,26 @@ export type TooltipProps = TooltipPortalSettings<'chart'> & { headerFormatter?: TooltipValueFormatter; unit?: string; customTooltip?: CustomTooltip; - stickTo?: Position; + stickTo?: TooltipStickTo; }; // @public export type TooltipSettings = TooltipType | TooltipProps; +// @public +export const TooltipStickTo: Readonly<{ + Top: "top"; + Bottom: "bottom"; + Middle: "middle"; + Left: "left"; + Right: "right"; + Center: "center"; + MousePosition: "MousePosition"; +}>; + +// @public (undocumented) +export type TooltipStickTo = $Values; + // @public export const TooltipType: Readonly<{ VerticalCursor: "vertical"; diff --git a/integration/server/generate/vrt_page_template.js b/integration/server/generate/vrt_page_template.js index 306751c9bc..196097aca5 100644 --- a/integration/server/generate/vrt_page_template.js +++ b/integration/server/generate/vrt_page_template.js @@ -48,7 +48,7 @@ ReactDOM.render(, document.getElementById('story-root') as HTMLElemen `.trim(); } -function pageTemplate(imports, routes) { +function pageTemplate(imports, routes, urls) { return ` import React, { Suspense } from 'react'; @@ -57,7 +57,16 @@ ${imports.join('\n')} export function VRTPage() { const path = new URL(window.location.toString()).searchParams.get('path'); if(!path) { - return

missing url path

; + return (<> +

missing url path

+
    + ${urls + .map((url) => { + return `
  • ${url.slice(7)}
  • `; + }) + .join('\n')} +
+ ); } return ( Loading...}> @@ -74,16 +83,17 @@ function compileVRTPage(examples) { acc.push(...exampleFiles); return acc; }, []); - const { imports, routes } = flatExamples.reduce( + const { imports, routes, urls } = flatExamples.reduce( (acc, { filePath, url }, index) => { acc.imports.push(compileImportTemplate(index, filePath)); acc.routes.push(compileRouteTemplate(index, url)); + acc.urls.push(url); return acc; }, - { imports: [], routes: [] }, + { imports: [], routes: [], urls: [] }, ); - fs.writeFileSync(path.join('integration', 'tmp', 'vrt_page.tsx'), pageTemplate(imports, routes)); + fs.writeFileSync(path.join('integration', 'tmp', 'vrt_page.tsx'), pageTemplate(imports, routes, urls)); fs.writeFileSync(path.join('integration', 'tmp', 'index.tsx'), indexTemplate()); } diff --git a/src/chart_types/goal_chart/state/chart_state.tsx b/src/chart_types/goal_chart/state/chart_state.tsx index 90077346b6..f4e12e68c6 100644 --- a/src/chart_types/goal_chart/state/chart_state.tsx +++ b/src/chart_types/goal_chart/state/chart_state.tsx @@ -110,8 +110,10 @@ export class GoalState implements InternalChartState { const { position } = state.interactions.pointer.current; return { isRotated: false, - x1: position.x, - y1: position.y, + x: position.x, + width: 0, + y: position.y, + height: 0, }; } diff --git a/src/chart_types/heatmap/state/selectors/get_tooltip_anchor.ts b/src/chart_types/heatmap/state/selectors/get_tooltip_anchor.ts index c10b362a64..07e1f32073 100644 --- a/src/chart_types/heatmap/state/selectors/get_tooltip_anchor.ts +++ b/src/chart_types/heatmap/state/selectors/get_tooltip_anchor.ts @@ -19,7 +19,7 @@ import createCachedSelector from 're-reselect'; -import { TooltipAnchorPosition } from '../../../../components/tooltip/types'; +import { AnchorPosition } from '../../../../components/portal/types'; import { GlobalChartState } from '../../../../state/chart_state'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; @@ -32,20 +32,21 @@ function getCurrentPointerPosition(state: GlobalChartState) { /** @internal */ export const getTooltipAnchorSelector = createCachedSelector( [getPickedShapes, computeChartDimensionsSelector, getCurrentPointerPosition], - (shapes, chartDimensions, position): TooltipAnchorPosition => { + (shapes, chartDimensions, position): AnchorPosition => { if (Array.isArray(shapes) && shapes.length > 0) { const firstShape = shapes[0]; return { - isRotated: false, - x1: firstShape.x + chartDimensions.left + firstShape.width / 2, - y1: firstShape.y - chartDimensions.top + firstShape.height, + x: firstShape.x + chartDimensions.left, + width: firstShape.width, + y: firstShape.y - chartDimensions.top, + height: firstShape.height, }; } return { - isRotated: false, - - x1: position.x, - y1: position.y, + x: position.x, + width: 0, + y: position.y, + height: 0, }; }, )(getChartIdSelector); diff --git a/src/chart_types/partition_chart/state/chart_state.tsx b/src/chart_types/partition_chart/state/chart_state.tsx index d7625e17bd..5efd88edfc 100644 --- a/src/chart_types/partition_chart/state/chart_state.tsx +++ b/src/chart_types/partition_chart/state/chart_state.tsx @@ -106,8 +106,10 @@ export class PartitionState implements InternalChartState { const { position } = state.interactions.pointer.current; return { isRotated: false, - x1: position.x, - y1: position.y, + x: position.x, + width: 0, + y: position.y, + height: 0, }; } diff --git a/src/chart_types/wordcloud/state/chart_state.tsx b/src/chart_types/wordcloud/state/chart_state.tsx index 91290c24d8..edaaeefb82 100644 --- a/src/chart_types/wordcloud/state/chart_state.tsx +++ b/src/chart_types/wordcloud/state/chart_state.tsx @@ -87,8 +87,10 @@ export class WordcloudState implements InternalChartState { const { position } = state.interactions.pointer.current; return { isRotated: false, - x1: position.x, - y1: position.y, + x: position.x, + width: 0, + y: position.y, + height: 0, }; } diff --git a/src/chart_types/xy_chart/annotations/line/tooltip.test.tsx b/src/chart_types/xy_chart/annotations/line/tooltip.test.tsx index e3003d7525..8fdf2a3e14 100644 --- a/src/chart_types/xy_chart/annotations/line/tooltip.test.tsx +++ b/src/chart_types/xy_chart/annotations/line/tooltip.test.tsx @@ -173,8 +173,10 @@ describe('Annotation tooltips', () => { isVisible: true, annotationType: AnnotationType.Rectangle, anchor: { - left: 18, - top: 9, + x: 18, + y: 9, + width: 0, + height: 0, }, }); annotationRectangle.hideTooltips = true; diff --git a/src/chart_types/xy_chart/annotations/rect/tooltip.test.ts b/src/chart_types/xy_chart/annotations/rect/tooltip.test.ts index 503e671e2c..b4a35ccdb3 100644 --- a/src/chart_types/xy_chart/annotations/rect/tooltip.test.ts +++ b/src/chart_types/xy_chart/annotations/rect/tooltip.test.ts @@ -46,8 +46,10 @@ describe('Rect annotation tooltip', () => { isVisible: true, annotationType: AnnotationType.Rectangle, anchor: { - top: cursorPosition.y, - left: cursorPosition.x, + x: cursorPosition.x, + y: cursorPosition.y, + width: 0, + height: 0, }, datum: { coordinates: { x0: 0, x1: 10, y0: 0, y1: 10 } }, }; diff --git a/src/chart_types/xy_chart/annotations/rect/tooltip.ts b/src/chart_types/xy_chart/annotations/rect/tooltip.ts index d7bbce8370..fa241c597e 100644 --- a/src/chart_types/xy_chart/annotations/rect/tooltip.ts +++ b/src/chart_types/xy_chart/annotations/rect/tooltip.ts @@ -53,8 +53,10 @@ export function getRectAnnotationTooltipState( isVisible: true, annotationType: AnnotationType.Rectangle, anchor: { - left: cursorPosition.x, - top: cursorPosition.y, + x: cursorPosition.x, + y: cursorPosition.y, + width: 0, + height: 0, }, datum, }; diff --git a/src/chart_types/xy_chart/annotations/types.ts b/src/chart_types/xy_chart/annotations/types.ts index 2f566b6a2f..1263f0ac2b 100644 --- a/src/chart_types/xy_chart/annotations/types.ts +++ b/src/chart_types/xy_chart/annotations/types.ts @@ -76,8 +76,10 @@ export interface AnnotationTooltipState { annotationType: AnnotationType; datum: LineAnnotationDatum | RectAnnotationDatum; anchor: { - top: number; - left: number; + x: number; + y: number; + width: number; + height: number; }; customTooltipDetails?: AnnotationTooltipFormatter; customTooltip?: CustomAnnotationTooltip; diff --git a/src/chart_types/xy_chart/annotations/utils.test.ts b/src/chart_types/xy_chart/annotations/utils.test.ts index 0ea4d823a9..6d82500e8b 100644 --- a/src/chart_types/xy_chart/annotations/utils.test.ts +++ b/src/chart_types/xy_chart/annotations/utils.test.ts @@ -21,7 +21,7 @@ import { MockGlobalSpec } from '../../../mocks/specs'; import { Position, Rotation } from '../../../utils/common'; import { Dimensions } from '../../../utils/dimensions'; import { AnnotationDomainType } from '../utils/specs'; -import { getAnnotationAxis, getTransformedCursor, invertTranformedCursor } from './utils'; +import { getAnnotationAxis, getTransformedCursor, invertTransformedCursor } from './utils'; describe('Annotation utils', () => { const groupId = 'foo-group'; @@ -74,7 +74,7 @@ describe('Annotation utils', () => { }; it.each([0, 90, -90, 180])('Should invert rotated cursor - rotation %d', (rotation) => { expect( - invertTranformedCursor( + invertTransformedCursor( getTransformedCursor(cursorPosition, chartDimensions, rotation), chartDimensions, rotation, @@ -84,7 +84,7 @@ describe('Annotation utils', () => { it.each([0, 90, -90, 180])('Should invert rotated projected cursor - rotation %d', (rotation) => { expect( - invertTranformedCursor( + invertTransformedCursor( getTransformedCursor(cursorPosition, chartDimensions, rotation, true), chartDimensions, rotation, diff --git a/src/chart_types/xy_chart/annotations/utils.ts b/src/chart_types/xy_chart/annotations/utils.ts index ad0da5c3a2..1751f48b5c 100644 --- a/src/chart_types/xy_chart/annotations/utils.ts +++ b/src/chart_types/xy_chart/annotations/utils.ts @@ -86,7 +86,7 @@ export function getTransformedCursor( } /** @internal */ -export function invertTranformedCursor( +export function invertTransformedCursor( cursorPosition: Point, chartDimensions: Dimensions, chartRotation: Rotation | null, diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts index 311ef21748..93b8965693 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts @@ -312,10 +312,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 0, - y1: 0, - y2: 100, + x: 0, + width: 0, + y: 0, + height: 100, }); }); @@ -330,10 +330,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 0, - y1: 0, - y2: 100, + x: 0, + width: 0, + y: 0, + height: 100, }); }); @@ -348,10 +348,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 40, - x2: 40, - y1: 0, - y2: 100, + x: 40, + width: 0, + y: 0, + height: 100, }); }); @@ -366,10 +366,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 90, - x2: 90, - y1: 0, - y2: 100, + x: 90, + width: 0, + y: 0, + height: 100, }); }); @@ -401,10 +401,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 0, - y1: 0, - y2: 100, + x: 0, + width: 0, + y: 0, + height: 100, }); }); @@ -419,10 +419,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 0, - y1: 0, - y2: 100, + x: 0, + width: 0, + y: 0, + height: 100, }); }); @@ -437,10 +437,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 0, - y1: 0, - y2: 100, + x: 0, + width: 0, + y: 0, + height: 100, }); }); @@ -455,10 +455,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 60, - x2: 60, - y1: 0, - y2: 100, + x: 60, + width: 0, + y: 0, + height: 100, }); }); @@ -473,10 +473,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 120, - x2: 120, - y1: 0, - y2: 100, + x: 120, + width: 0, + y: 0, + height: 100, }); }); @@ -510,10 +510,10 @@ describe('Crosshair utils linear scale', () => { ); expect(bandPosition).toEqual({ - x1: 120, - x2: 120, - y1: 0, - y2: 100, + x: 120, + width: 0, + y: 0, + height: 100, }); }); @@ -528,10 +528,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 120, - x2: 120, - y1: 0, - y2: 100, + x: 120, + width: 0, + y: 0, + height: 100, }); }); @@ -546,10 +546,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 80, - x2: 80, - y1: 0, - y2: 100, + x: 80, + width: 0, + y: 0, + height: 100, }); }); @@ -564,10 +564,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 30, - x2: 30, - y1: 0, - y2: 100, + x: 30, + width: 0, + y: 0, + height: 100, }); }); @@ -600,10 +600,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 120, - x2: 120, - y1: 0, - y2: 100, + x: 120, + width: 0, + y: 0, + height: 100, }); }); @@ -618,10 +618,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 120, - x2: 120, - y1: 0, - y2: 100, + x: 120, + width: 0, + y: 0, + height: 100, }); }); @@ -636,10 +636,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 120, - x2: 120, - y1: 0, - y2: 100, + x: 120, + width: 0, + y: 0, + height: 100, }); }); @@ -654,10 +654,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 60, - x2: 60, - y1: 0, - y2: 100, + x: 60, + width: 0, + y: 0, + height: 100, }); }); @@ -672,10 +672,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 0, - y1: 0, - y2: 100, + x: 0, + width: 0, + y: 0, + height: 100, }); }); @@ -708,10 +708,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 0, - y2: 0, + x: 0, + width: 120, + y: 0, + height: 0, }); }); @@ -726,10 +726,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 45, - y2: 45, + x: 0, + width: 120, + y: 45, + height: 0, }); }); @@ -744,10 +744,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 0, - y2: 0, + x: 0, + width: 120, + y: 0, + height: 0, }); }); @@ -762,10 +762,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 0, - y2: 0, + x: 0, + width: 120, + y: 0, + height: 0, }); }); @@ -798,10 +798,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 0, - y2: 0, + x: 0, + width: 120, + y: 0, + height: 0, }); }); @@ -816,10 +816,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 60, - y2: 60, + x: 0, + width: 120, + y: 60, + height: 0, }); }); @@ -834,10 +834,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 0, - y2: 0, + x: 0, + width: 120, + y: 0, + height: 0, }); }); @@ -852,10 +852,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 0, - y2: 0, + x: 0, + width: 120, + y: 0, + height: 0, }); }); @@ -870,10 +870,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 0, - y2: 0, + x: 0, + width: 120, + y: 0, + height: 0, }); }); @@ -906,10 +906,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 100, - y2: 100, + x: 0, + width: 120, + y: 100, + height: 0, }); }); @@ -924,10 +924,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 55, - y2: 55, + x: 0, + width: 120, + y: 55, + height: 0, }); }); @@ -942,10 +942,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 100, - y2: 100, + x: 0, + width: 120, + y: 100, + height: 0, }); }); @@ -960,10 +960,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 100, - y2: 100, + x: 0, + width: 120, + y: 100, + height: 0, }); }); @@ -996,10 +996,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 100, - y2: 100, + x: 0, + width: 120, + y: 100, + height: 0, }); }); @@ -1014,10 +1014,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 40, - y2: 40, + x: 0, + width: 120, + y: 40, + height: 0, }); }); @@ -1032,10 +1032,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 100, - y2: 100, + x: 0, + width: 120, + y: 100, + height: 0, }); }); @@ -1050,10 +1050,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 100, - y2: 100, + x: 0, + width: 120, + y: 100, + height: 0, }); }); @@ -1068,10 +1068,10 @@ describe('Crosshair utils linear scale', () => { 0, ); expect(bandPosition).toEqual({ - x1: 0, - x2: 120, - y1: 100, - y2: 100, + x: 0, + width: 120, + y: 100, + height: 0, }); }); diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts index dcec27b46f..645428630d 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts @@ -17,16 +17,15 @@ * under the License. */ -import { TooltipAnchorPosition } from '../../../components/tooltip/types'; +import { AnchorPosition } from '../../../components/portal/types'; import { Line, Rect } from '../../../geoms/types'; import { Scale } from '../../../scales'; import { isContinuousScale } from '../../../scales/types'; -import { TooltipProps } from '../../../specs/settings'; -import { Position, Rotation } from '../../../utils/common'; +import { TooltipStickTo } from '../../../specs/constants'; +import { Rotation } from '../../../utils/common'; import { Dimensions } from '../../../utils/dimensions'; import { Point } from '../../../utils/point'; import { isHorizontalRotation, isVerticalRotation } from '../state/utils/common'; -import { ChartDimensions } from '../utils/dimensions'; /** @internal */ export const DEFAULT_SNAP_POSITION_BAND = 1; @@ -100,7 +99,7 @@ export function getCursorBandPosition( snapEnabled: boolean, xScale: Scale, totalBarsInCluster?: number, -): Line | Rect | undefined { +): Rect | undefined { const { top, left, width, height } = panel; const { x, y } = cursorPosition; const isHorizontalRotated = isHorizontalRotation(chartRotation); @@ -133,10 +132,10 @@ export function getCursorBandPosition( } if (isLineOrAreaOnly && isContinuousScale(xScale)) { return { - x1: leftPosition, - x2: leftPosition, - y1: top, - y2: top + height, + x: leftPosition, + width: 0, + y: top, + height, }; } return { @@ -157,10 +156,10 @@ export function getCursorBandPosition( } if (isLineOrAreaOnly && isContinuousScale(xScale)) { return { - x1: left, - x2: left + width, - y1: topPosition, - y2: topPosition, + x: left, + width, + y: topPosition, + height: 0, }; } return { @@ -173,84 +172,43 @@ export function getCursorBandPosition( /** @internal */ export function getTooltipAnchorPosition( - { offset }: ChartDimensions, chartRotation: Rotation, - cursorBandPosition: Line | Rect, + cursorBandPosition: Rect, cursorPosition: { x: number; y: number }, panel: Dimensions, - stickTo?: TooltipProps['stickTo'], -): TooltipAnchorPosition { + stickTo: TooltipStickTo = TooltipStickTo.MousePosition, +): AnchorPosition { + const { x, y, width, height } = cursorBandPosition; const isRotated = isVerticalRotation(chartRotation); - const hPosition = getHorizontalTooltipPosition( - cursorPosition.x, - cursorBandPosition, - panel, - offset.left, - isRotated, - stickTo, - ); - const vPosition = getVerticalTooltipPosition( - cursorPosition.y, - cursorBandPosition, - panel, - offset.top, - isRotated, - stickTo, - ); - return { - isRotated, - ...vPosition, - ...hPosition, - }; -} - -function getHorizontalTooltipPosition( - cursorXPosition: number, - cursorBandPosition: Line | Rect, - panel: Dimensions, - globalOffset: number, - isRotated: boolean, - stickTo?: TooltipProps['stickTo'], -): { x0?: number; x1: number } { - if (!isRotated) { - const left = 'x1' in cursorBandPosition ? cursorBandPosition.x1 : cursorBandPosition.x; - const width = 'width' in cursorBandPosition ? cursorBandPosition.width : 0; - return { - x0: left + globalOffset, - x1: left + width + globalOffset, - }; - } - const x = stickTo === Position.Left ? 0 : stickTo === Position.Right ? panel.width : cursorXPosition; - return { - // NOTE: x0 set to zero blocks tooltip placement on left when rotated 90 deg - // Delete this comment before merging and verifying this doesn't break anything. - x1: panel.left + x + globalOffset, - }; -} - -function getVerticalTooltipPosition( - cursorYPosition: number, - cursorBandPosition: Line | Rect, - panel: Dimensions, - globalOffset: number, - isRotated: boolean, - stickTo?: TooltipProps['stickTo'], -): { - y0: number; - y1: number; -} { - const y = stickTo === Position.Top ? 0 : stickTo === Position.Bottom ? panel.height : cursorYPosition; + // horizontal movement of cursor if (!isRotated) { - const yPos = y + panel.top + globalOffset; + const stickY = + stickTo === TooltipStickTo.MousePosition + ? cursorPosition.y + panel.top + : stickTo === TooltipStickTo.Middle + ? y + height / 2 + : stickTo === TooltipStickTo.Bottom + ? y + height + : y; // TooltipStickTo.Top is also ok with that value return { - y0: yPos, - y1: yPos, + x, + width, + y: stickY, + height: 0, }; } - const top = 'y1' in cursorBandPosition ? cursorBandPosition.y1 : cursorBandPosition.y; - const height = 'height' in cursorBandPosition ? cursorBandPosition.height : 0; + const stickX = + stickTo === TooltipStickTo.MousePosition + ? cursorPosition.x + panel.left + : stickTo === TooltipStickTo.Right + ? x + width + : stickTo === TooltipStickTo.Center + ? x + width / 2 + : x; // TooltipStickTo.Left return { - y0: top + globalOffset, - y1: height + top + globalOffset, + x: stickX, + width: 0, + y, + height, }; } diff --git a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx index a1bfbb2dcc..5338c08a66 100644 --- a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx +++ b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx @@ -89,7 +89,7 @@ interface ReactiveChartDispatchProps { onChartRendered: typeof onChartRendered; } interface ReactiveChartOwnProps { - forwardStageRef: RefObject; + forwardCanvasRef: RefObject; } const CLIPPING_MARGINS = 0.5; @@ -148,14 +148,14 @@ class XYChartComponent extends React.Component { } private tryCanvasContext() { - const canvas = this.props.forwardStageRef.current; + const canvas = this.props.forwardCanvasRef.current; this.ctx = canvas && canvas.getContext('2d'); } // eslint-disable-next-line @typescript-eslint/member-ordering render() { const { - forwardStageRef, + forwardCanvasRef, initialized, isChartEmpty, chartContainerDimensions: { width, height }, @@ -170,7 +170,7 @@ class XYChartComponent extends React.Component { return (
{ if (!cursorPosition || !canRenderBand(tooltipType, band.visible, fromExternalEvent)) { return null; } - if ('x1' in cursorPosition) { - const { x1, x2, y1, y2 } = cursorPosition; - const { strokeWidth, stroke, dash } = line; - const strokeDasharray = (dash ?? []).join(' '); - return ( - - - - ); - } const { x, y, width, height } = cursorPosition; + const isLine = width === 0 || height === 0; + const { strokeWidth, stroke, dash } = line; const { fill } = band; + const strokeDasharray = (dash ?? []).join(' '); return ( - ; + {isLine && } + {!isLine && } ); } diff --git a/src/chart_types/xy_chart/state/chart_state.tsx b/src/chart_types/xy_chart/state/chart_state.tsx index 58ae89ce5d..a8080fc93b 100644 --- a/src/chart_types/xy_chart/state/chart_state.tsx +++ b/src/chart_types/xy_chart/state/chart_state.tsx @@ -120,13 +120,13 @@ export class XYAxisChartState implements InternalChartState { return getHighlightedValuesSelector(globalState); } - chartRenderer(containerRef: BackwardRef, forwardStageRef: RefObject) { + chartRenderer(containerRef: BackwardRef, forwardCanvasRef: RefObject) { return ( <> - + - + diff --git a/src/chart_types/xy_chart/state/selectors/compute_chart_dimensions.ts b/src/chart_types/xy_chart/state/selectors/compute_chart_dimensions.ts index ef80000174..df69be257e 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_chart_dimensions.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_chart_dimensions.ts @@ -22,9 +22,7 @@ import createCachedSelector from 're-reselect'; import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; -import { getLegendSizeSelector, LegendSizing } from '../../../../state/selectors/get_legend_size'; import { getSmallMultiplesSpec } from '../../../../state/selectors/get_small_multiples_spec'; -import { HorizontalAlignment, LayoutDirection, VerticalAlignment } from '../../../../utils/common'; import { computeChartDimensions, ChartDimensions } from '../../utils/dimensions'; import { computeAxisTicksDimensionsSelector } from './compute_axis_ticks_dimensions'; import { getAxesStylesSelector } from './get_axis_styles'; @@ -38,49 +36,15 @@ export const computeChartDimensionsSelector = createCachedSelector( computeAxisTicksDimensionsSelector, getAxisSpecsSelector, getAxesStylesSelector, - getLegendSizeSelector, getSmallMultiplesSpec, ], - ( - chartContainerDimensions, - chartTheme, - axesTicksDimensions, - axesSpecs, - axesStyles, - legendSize, - smSpec, - ): ChartDimensions => + (chartContainerDimensions, chartTheme, axesTicksDimensions, axesSpecs, axesStyles, smSpec): ChartDimensions => computeChartDimensions( chartContainerDimensions, chartTheme, axesTicksDimensions, axesStyles, axesSpecs, - getLegendDimension(legendSize), smSpec && smSpec[0], ), )(getChartIdSelector); - -function getLegendDimension({ - position: { direction, vAlign, hAlign }, - width, - height, - margin, -}: LegendSizing): { - top: number; - left: number; -} { - let left = 0; - let top = 0; - - if (direction === LayoutDirection.Vertical && hAlign === HorizontalAlignment.Left) { - left = width + margin * 2; - } else if (direction === LayoutDirection.Horizontal && vAlign === VerticalAlignment.Top) { - top = height + margin * 2; - } - - return { - left, - top, - }; -} diff --git a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts index db63366c93..0cc5218bb5 100644 --- a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts +++ b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts @@ -144,8 +144,10 @@ function getTooltipStateForDOMElements( annotationType: AnnotationType.Line, datum: dimension.datum, anchor: { - top: (dimension.markers[0]?.position.top ?? 0) + dimension.panel.top + chartDimensions.top, - left: (dimension.markers[0]?.position.left ?? 0) + dimension.panel.left + chartDimensions.left, + y: (dimension.markers[0]?.position.top ?? 0) + dimension.panel.top + chartDimensions.top, + x: (dimension.markers[0]?.position.left ?? 0) + dimension.panel.left + chartDimensions.left, + width: 0, + height: 0, }, customTooltipDetails: spec.customTooltipDetails, customTooltip: spec.customTooltip, 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 f10bc5cb92..94aa15b198 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 @@ -19,7 +19,7 @@ import createCachedSelector from 're-reselect'; -import { Line, Rect } from '../../../../geoms/types'; +import { Rect } from '../../../../geoms/types'; import { Scale } from '../../../../scales'; import { SettingsSpec, PointerEvent } from '../../../../specs/settings'; import { GlobalChartState } from '../../../../state/chart_state'; @@ -93,7 +93,7 @@ function getCursorBand( isTooltipSnapEnabled: boolean, geometriesIndexKeys: (string | number)[], smallMultipleScales: SmallMultipleScales, -): ((Line | Rect) & { fromExternalEvent: boolean }) | undefined { +): (Rect & { fromExternalEvent: boolean }) | undefined { if (!xScale) { return; } diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts index 4995fedf76..57bf5d569f 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts @@ -19,7 +19,7 @@ import createCachedSelector from 're-reselect'; -import { TooltipAnchorPosition } from '../../../../components/tooltip/types'; +import { AnchorPosition } from '../../../../components/portal/types'; import { isTooltipType } from '../../../../specs/settings'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; @@ -44,7 +44,7 @@ export const getTooltipAnchorPositionSelector = createCachedSelector( cursorBandPosition, projectedPointerPosition, { horizontal, vertical }, - ): TooltipAnchorPosition | null => { + ): AnchorPosition | null => { if (!cursorBandPosition) { return null; } @@ -60,7 +60,6 @@ export const getTooltipAnchorPositionSelector = createCachedSelector( }; return getTooltipAnchorPosition( - chartDimensions, settings.rotation, cursorBandPosition, projectedPointerPosition, diff --git a/src/chart_types/xy_chart/utils/dimensions.test.ts b/src/chart_types/xy_chart/utils/dimensions.test.ts index d2013540a6..d1bad05aeb 100644 --- a/src/chart_types/xy_chart/utils/dimensions.test.ts +++ b/src/chart_types/xy_chart/utils/dimensions.test.ts @@ -47,10 +47,6 @@ describe('Computed chart dimensions', () => { top: 10, bottom: 10, }; - const legendSize = { - top: 0, - left: 0, - }; const axis1Dims: AxisTicksDimensions = { tickValues: [0, 1], @@ -94,14 +90,7 @@ describe('Computed chart dimensions', () => { const axisDims = new Map(); const axisStyles = new Map(); const axisSpecs: AxisSpec[] = []; - const { chartDimensions } = computeChartDimensions( - parentDim, - chartTheme, - axisDims, - axisStyles, - axisSpecs, - legendSize, - ); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -113,14 +102,7 @@ describe('Computed chart dimensions', () => { const axisStyles = new Map(); const axisSpecs = [axisLeftSpec]; axisDims.set('axis_1', axis1Dims); - const { chartDimensions } = computeChartDimensions( - parentDim, - chartTheme, - axisDims, - axisStyles, - axisSpecs, - legendSize, - ); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -132,14 +114,7 @@ describe('Computed chart dimensions', () => { const axisStyles = new Map(); const axisSpecs = [{ ...axisLeftSpec, position: Position.Right }]; axisDims.set('axis_1', axis1Dims); - const { chartDimensions } = computeChartDimensions( - parentDim, - chartTheme, - axisDims, - axisStyles, - axisSpecs, - legendSize, - ); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -156,14 +131,7 @@ describe('Computed chart dimensions', () => { }, ]; axisDims.set('axis_1', axis1Dims); - const { chartDimensions } = computeChartDimensions( - parentDim, - chartTheme, - axisDims, - axisStyles, - axisSpecs, - legendSize, - ); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -180,14 +148,7 @@ describe('Computed chart dimensions', () => { }, ]; axisDims.set('axis_1', axis1Dims); - const { chartDimensions } = computeChartDimensions( - parentDim, - chartTheme, - axisDims, - axisStyles, - axisSpecs, - legendSize, - ); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -202,7 +163,7 @@ describe('Computed chart dimensions', () => { }, ]; axisDims.set('foo', axis1Dims); - const chartDimensions = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs, legendSize); + const chartDimensions = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); const expectedDims = { chartDimensions: { @@ -212,10 +173,6 @@ describe('Computed chart dimensions', () => { top: 20, }, leftMargin: 10, - offset: { - top: 0, - left: 0, - }, }; expect(chartDimensions).toEqual(expectedDims); @@ -228,14 +185,7 @@ describe('Computed chart dimensions', () => { hide: true, position: Position.Bottom, }); - const hiddenAxisChartDimensions = computeChartDimensions( - parentDim, - chartTheme, - axisDims, - axisStyles, - axisSpecs, - legendSize, - ); + const hiddenAxisChartDimensions = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); expect(hiddenAxisChartDimensions).toEqual(expectedDims); }); diff --git a/src/chart_types/xy_chart/utils/dimensions.ts b/src/chart_types/xy_chart/utils/dimensions.ts index c3ea567ba7..7dc151de79 100644 --- a/src/chart_types/xy_chart/utils/dimensions.ts +++ b/src/chart_types/xy_chart/utils/dimensions.ts @@ -33,13 +33,6 @@ export interface ChartDimensions { * Dimensions relative to canvas element */ chartDimensions: Dimensions; - /** - * Dimensions relative to echChart element - */ - offset: { - top: number; - left: number; - }; /** * Margin to account for ending text overflow */ @@ -57,10 +50,6 @@ export function computeChartDimensions( axisDimensions: Map, axesStyles: Map, axisSpecs: AxisSpec[], - legendSizing: { - top: number; - left: number; - }, smSpec?: SmallMultiplesSpec, ): ChartDimensions { if (parentDimensions.width <= 0 || parentDimensions.height <= 0) { @@ -72,10 +61,6 @@ export function computeChartDimensions( top: 0, }, leftMargin: 0, - offset: { - left: 0, - top: 0, - }, }; } @@ -94,9 +79,5 @@ export function computeChartDimensions( width: chartWidth - chartPaddings.left - chartPaddings.right, height: chartHeight - chartPaddings.top - chartPaddings.bottom, }, - offset: { - top: legendSizing.top, - left: legendSizing.left, - }, }; } diff --git a/src/common/constants.ts b/src/common/constants.ts index 7186cd6c41..d94c20b64a 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -31,3 +31,16 @@ export const RIGHT_ANGLE = TAU / 4; * @internal */ export const GOLDEN_RATIO = 1.618; + +/** @public */ +export const TOP = 'top' as const; +/** @public */ +export const BOTTOM = 'bottom' as const; +/** @public */ +export const LEFT = 'left' as const; +/** @public */ +export const RIGHT = 'right' as const; +/** @public */ +export const MIDDLE = 'middle' as const; +/** @public */ +export const CENTER = 'center' as const; diff --git a/src/components/__snapshots__/chart.test.tsx.snap b/src/components/__snapshots__/chart.test.tsx.snap index 3e5550b593..de3a040a84 100644 --- a/src/components/__snapshots__/chart.test.tsx.snap +++ b/src/components/__snapshots__/chart.test.tsx.snap @@ -68,8 +68,8 @@ exports[`Chart should render the legend name test 1`] = ` - - + +
diff --git a/src/components/chart.tsx b/src/components/chart.tsx index 8b33095897..89dc4c72a4 100644 --- a/src/components/chart.tsx +++ b/src/components/chart.tsx @@ -172,7 +172,7 @@ export class Chart extends React.Component { return ( -
+
@@ -180,7 +180,7 @@ export class Chart extends React.Component { {/* TODO: Add renderFn to error boundary */} {this.props.children} -
+
diff --git a/src/components/portal/tooltip_portal.tsx b/src/components/portal/tooltip_portal.tsx index 294f5a1c38..e9dc102d21 100644 --- a/src/components/portal/tooltip_portal.tsx +++ b/src/components/portal/tooltip_portal.tsx @@ -199,9 +199,8 @@ const TooltipPortalComponent = ({ return; } - const { left, top, width, height } = position; - anchorNode.current.style.left = `${left}px`; - anchorNode.current.style.top = `${top}px`; + const { x, y, width, height } = position; + anchorNode.current.style.transform = `translate(${x}px, ${y}px)`; if (isDefined(width)) { anchorNode.current.style.width = `${width}px`; @@ -211,7 +210,7 @@ const TooltipPortalComponent = ({ anchorNode.current.style.height = `${height}px`; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [visible, anchorNode, position?.left, position?.top, position?.width, position?.height]); + }, [visible, anchorNode, position?.x, position?.y, position?.width, position?.height]); useEffect(() => { if (!position) { diff --git a/src/components/portal/types.ts b/src/components/portal/types.ts index 845ecd2465..e369cb1d10 100644 --- a/src/components/portal/types.ts +++ b/src/components/portal/types.ts @@ -50,12 +50,24 @@ export const Placement = Object.freeze({ export type Placement = $Values; /** @internal */ -export interface AnchorPosition { - left: number; - top: number; - width?: number; - height?: number; -} +export type AnchorPosition = { + /** + * the right position of anchor + */ + x: number; + /** + * the top position of the anchor + */ + y: number; + /** + * the width of the anchor + */ + width: number; + /** + * the height of the anchor + */ + height: number; +}; /** * Used to position tooltip relative to invisible anchor via ref element @@ -111,6 +123,7 @@ export interface TooltipPortalSettings { boundaryPadding?: Partial | number; /** * Custom tooltip offset + * @defaultValue 10 */ offset?: number; } diff --git a/src/components/tooltip/tooltip.tsx b/src/components/tooltip/tooltip.tsx index 9d2c6f884b..0f0e06e714 100644 --- a/src/components/tooltip/tooltip.tsx +++ b/src/components/tooltip/tooltip.tsx @@ -38,7 +38,7 @@ import { getTooltipHeaderFormatterSelector } from '../../state/selectors/get_too import { Rotation, isDefined } from '../../utils/common'; import { TooltipPortal, TooltipPortalSettings, AnchorPosition, Placement } from '../portal'; import { getTooltipSettings } from './get_tooltip_settings'; -import { TooltipInfo, TooltipAnchorPosition } from './types'; +import { TooltipInfo } from './types'; interface TooltipDispatchProps { onPointerMove: typeof onPointerMoveAction; @@ -47,7 +47,7 @@ interface TooltipDispatchProps { interface TooltipStateProps { zIndex: number; visible: boolean; - position: TooltipAnchorPosition | null; + position: AnchorPosition | null; info?: TooltipInfo; headerFormatter?: TooltipValueFormatter; settings?: TooltipSettings; @@ -169,22 +169,6 @@ const TooltipComponent = ({ ); }; - const anchorPosition = useMemo((): AnchorPosition | null => { - if (!position || !visible) { - return null; - } - - const { x0, x1, y0, y1 } = position; - const width = x0 !== undefined ? x1 - x0 : 0; - const height = y0 !== undefined ? y1 - y0 : 0; - return { - left: x1 - width, - width, - top: y1 - height, - height, - }; - }, [visible, position?.x0, position?.x1, position?.y0, position?.y1]); // eslint-disable-line react-hooks/exhaustive-deps - const popperSettings = useMemo((): TooltipPortalSettings | undefined => { if (!settings || typeof settings === 'string') { return; @@ -213,7 +197,7 @@ const TooltipComponent = ({ // increasing by 100 the tooltip portal zIndex to avoid conflicts with highlighters and other elements in the DOM zIndex={zIndex + 100} anchor={{ - position: anchorPosition, + position, ref: chartRef.current, }} settings={popperSettings} diff --git a/src/components/tooltip/types.ts b/src/components/tooltip/types.ts index 37e274fbf5..bb7bfebba5 100644 --- a/src/components/tooltip/types.ts +++ b/src/components/tooltip/types.ts @@ -43,31 +43,3 @@ export interface TooltipInfo { * @public */ export type CustomTooltip = ComponentType; - -/** @internal */ -export interface TooltipAnchorPosition { - /** - * true if the x axis is vertical - */ - isRotated?: boolean; - /** - * the top position of the anchor - */ - y0?: number; - /** - * the bottom position of the anchor - */ - y1: number; - /** - * the right position of anchor - */ - x0?: number; - /** - * the left position of the anchor - */ - x1: number; - /** - * the padding to add between the tooltip position and the final position - */ - padding?: number; -} diff --git a/src/specs/constants.ts b/src/specs/constants.ts index fa1f49f1c7..c4e16dfbfa 100644 --- a/src/specs/constants.ts +++ b/src/specs/constants.ts @@ -20,6 +20,7 @@ import { $Values } from 'utility-types'; import { ChartType } from '../chart_types'; +import { BOTTOM, CENTER, LEFT, MIDDLE, RIGHT, TOP } from '../common/constants'; import { Position } from '../utils/common'; import { LIGHT_THEME } from '../utils/themes/light_theme'; import { SettingsSpec } from './settings'; @@ -107,6 +108,21 @@ export const BrushAxis = Object.freeze({ /** @public */ export type BrushAxis = $Values; +/** + * The position to stick the tooltip to + * @public + */ +export const TooltipStickTo = Object.freeze({ + Top: TOP, + Bottom: BOTTOM, + Middle: MIDDLE, + Left: LEFT, + Right: RIGHT, + Center: CENTER, + MousePosition: 'MousePosition' as const, +}); +/** @public */ +export type TooltipStickTo = $Values; /** * Default value for the tooltip type * @defaultValue `vertical` {@link (TooltipType:type) | TooltipType.VerticalCursor} diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index 22a0e6be45..c0eb9e1be0 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -19,7 +19,7 @@ import React, { ComponentType, ReactChild } from 'react'; -import { CustomXDomain, GroupByAccessor, Spec } from '.'; +import { CustomXDomain, GroupByAccessor, Spec, TooltipStickTo } from '.'; import { Cell } from '../chart_types/heatmap/layout/types/viewmodel_types'; import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; import { LegendStrategy } from '../chart_types/partition_chart/layout/utils/highlighted_geoms'; @@ -274,8 +274,9 @@ export type TooltipProps = TooltipPortalSettings<'chart'> & { customTooltip?: CustomTooltip; /** * Stick the tooltip to a specific position within the current cursor + * @defaultValue mousePosition */ - stickTo?: Position; + stickTo?: TooltipStickTo; }; /** diff --git a/src/state/chart_state.ts b/src/state/chart_state.ts index 4999fa86a8..5e736bad99 100644 --- a/src/state/chart_state.ts +++ b/src/state/chart_state.ts @@ -28,7 +28,8 @@ import { XYAxisChartState } from '../chart_types/xy_chart/state/chart_state'; import { CategoryKey } from '../common/category'; import { LegendItem, LegendItemExtraValues } from '../common/legend'; import { SeriesIdentifier, SeriesKey } from '../common/series_id'; -import { TooltipAnchorPosition, TooltipInfo } from '../components/tooltip/types'; +import { AnchorPosition } from '../components/portal/types'; +import { TooltipInfo } from '../components/tooltip/types'; import { DEFAULT_SETTINGS_SPEC, PointerEvent, Spec } from '../specs'; import { Color, keepDistinct } from '../utils/common'; import { Dimensions } from '../utils/dimensions'; @@ -122,7 +123,7 @@ export interface InternalChartState { * Get the tooltip anchor position * @param globalState */ - getTooltipAnchor(globalState: GlobalChartState): TooltipAnchorPosition | null; + getTooltipAnchor(globalState: GlobalChartState): AnchorPosition | null; /** * Called on every state change to activate any event callback diff --git a/src/state/selectors/get_internal_tooltip_anchor_position.ts b/src/state/selectors/get_internal_tooltip_anchor_position.ts index 777d7b7d92..ab078a5a03 100644 --- a/src/state/selectors/get_internal_tooltip_anchor_position.ts +++ b/src/state/selectors/get_internal_tooltip_anchor_position.ts @@ -17,11 +17,11 @@ * under the License. */ -import { TooltipAnchorPosition } from '../../components/tooltip/types'; +import { AnchorPosition } from '../../components/portal/types'; import { GlobalChartState } from '../chart_state'; /** @internal */ -export const getInternalTooltipAnchorPositionSelector = (state: GlobalChartState): TooltipAnchorPosition | null => { +export const getInternalTooltipAnchorPositionSelector = (state: GlobalChartState): AnchorPosition | null => { if (state.internalChartState) { return state.internalChartState.getTooltipAnchor(state); } diff --git a/stories/area/2_with_time.tsx b/stories/area/2_with_time.tsx index 7c9541c631..c29bc2cf6f 100644 --- a/stories/area/2_with_time.tsx +++ b/stories/area/2_with_time.tsx @@ -23,7 +23,7 @@ import React from 'react'; import { AreaSeries, Axis, Chart, Placement, Position, ScaleType, Settings, timeFormatter } from '../../src'; import { isDefined } from '../../src/utils/common'; import { KIBANA_METRICS } from '../../src/utils/data_samples/test_dataset_kibana'; -import { getPlacementKnob, getPositionKnob } from '../utils/knobs'; +import { getChartRotationKnob, getPlacementKnob, getStickToKnob } from '../utils/knobs'; import { SB_SOURCE_PANEL } from '../utils/storybook'; const dateFormatter = timeFormatter('HH:mm'); @@ -32,12 +32,12 @@ export const Example = () => ( ( tickFormat={(d) => Number(d).toFixed(2)} /> { const stackAccessors = isStackedSeries ? ['x'] : undefined; return ( - + Number(d).toFixed(2)} /> ( export const Example = () => { const rotation = getChartRotationKnob(); const tooltipOptions = { + stickTo: getStickToKnob('stickTo'), placement: getPlacementKnob('Tooltip placement'), fallbackPlacements: getFallbackPlacementsKnob(), type: getTooltipTypeKnob(), boundary: getBoundaryKnob(), customTooltip: boolean('Custom Tooltip', false) ? CustomTooltip : undefined, + offset: number('Tooltip offset', 10, { min: 0, max: 20, range: true, step: 1 }), }; const showAxes = boolean('Show axes', false); const showLegend = boolean('Show Legend', false); diff --git a/stories/utils/knobs.ts b/stories/utils/knobs.ts index 4565c0ef60..9382812869 100644 --- a/stories/utils/knobs.ts +++ b/stories/utils/knobs.ts @@ -22,7 +22,7 @@ import { select, array, number, optionsKnob } from '@storybook/addon-knobs'; import { SelectTypeKnobValue } from '@storybook/addon-knobs/dist/components/types'; import { startCase, kebabCase } from 'lodash'; -import { Rotation, Position, Placement, TooltipProps } from '../../src'; +import { Rotation, Position, Placement, TooltipProps, TooltipStickTo } from '../../src'; import { TooltipType } from '../../src/specs/constants'; import { VerticalAlignment, HorizontalAlignment } from '../../src/utils/common'; @@ -140,6 +140,20 @@ export const getPlacementKnob = (name = 'placement', defaultValue?: Placement, g return value || undefined; }; +export const getStickToKnob = (name = 'stickTo', defaultValue = TooltipStickTo.MousePosition, groupId?: string) => { + const value = select( + name, + { + Default: undefined, + ...TooltipStickTo, + }, + defaultValue, + groupId, + ); + + return value || undefined; +}; + export const getEuiPopoverPositionKnob = ( name = 'Popover position', defaultValue: PopoverAnchorPosition = 'leftCenter',