From ca004a66f3f5a0847d2f39314a1526298ca03b8a Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Tue, 2 Nov 2021 09:47:59 -0500 Subject: [PATCH] fix(interactions): line cursor above the chart, band cursor below (#1453) (#1457) --- .../xy_chart/renderer/dom/cursor_band.tsx | 86 +++++++++++++++++ .../{crosshair.tsx => cursor_crossline.tsx} | 69 ++------------ .../xy_chart/renderer/dom/cursor_line.tsx | 92 +++++++++++++++++++ .../xy_chart/state/chart_state.tsx | 8 +- .../__snapshots__/chart.test.tsx.snap | 12 ++- 5 files changed, 201 insertions(+), 66 deletions(-) create mode 100644 packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_band.tsx rename packages/charts/src/chart_types/xy_chart/renderer/dom/{crosshair.tsx => cursor_crossline.tsx} (60%) create mode 100644 packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_line.tsx diff --git a/packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_band.tsx b/packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_band.tsx new file mode 100644 index 0000000000..a4992d3fd0 --- /dev/null +++ b/packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_band.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { connect } from 'react-redux'; + +import { Rect } from '../../../../geoms/types'; +import { getTooltipType } from '../../../../specs'; +import { TooltipType } from '../../../../specs/constants'; +import { GlobalChartState } from '../../../../state/chart_state'; +import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; +import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; +import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; +import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; +import { Rotation } from '../../../../utils/common'; +import { LIGHT_THEME } from '../../../../utils/themes/light_theme'; +import { Theme } from '../../../../utils/themes/theme'; +import { getCursorBandPositionSelector } from '../../state/selectors/get_cursor_band'; + +interface CursorBandProps { + theme: Theme; + chartRotation: Rotation; + cursorPosition?: Rect; + tooltipType: TooltipType; + fromExternalEvent?: boolean; +} + +function canRenderBand(type: TooltipType, visible: boolean, fromExternalEvent?: boolean) { + return visible && (type === TooltipType.Crosshairs || type === TooltipType.VerticalCursor || fromExternalEvent); +} + +class CursorBandComponent extends React.Component { + static displayName = 'CursorBand'; + + render() { + const { + theme: { + crosshair: { band }, + }, + cursorPosition, + tooltipType, + fromExternalEvent, + } = this.props; + const isBand = (cursorPosition?.width ?? 0) > 0 && (cursorPosition?.height ?? 0) > 0; + if (!isBand || !cursorPosition || !canRenderBand(tooltipType, band.visible, fromExternalEvent)) { + return null; + } + const { x, y, width, height } = cursorPosition; + const { fill } = band; + return ( + + + + ); + } +} + +const mapStateToProps = (state: GlobalChartState): CursorBandProps => { + if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) { + return { + theme: LIGHT_THEME, + chartRotation: 0, + tooltipType: TooltipType.None, + }; + } + const settings = getSettingsSpecSelector(state); + const cursorBandPosition = getCursorBandPositionSelector(state); + const fromExternalEvent = cursorBandPosition?.fromExternalEvent; + const tooltipType = getTooltipType(settings, fromExternalEvent); + + return { + theme: getChartThemeSelector(state), + chartRotation: getChartRotationSelector(state), + cursorPosition: cursorBandPosition, + tooltipType, + fromExternalEvent, + }; +}; + +/** @internal */ +export const CursorBand = connect(mapStateToProps)(CursorBandComponent); diff --git a/packages/charts/src/chart_types/xy_chart/renderer/dom/crosshair.tsx b/packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_crossline.tsx similarity index 60% rename from packages/charts/src/chart_types/xy_chart/renderer/dom/crosshair.tsx rename to packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_crossline.tsx index 8d4019bd9b..1eedfe3daf 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/dom/crosshair.tsx +++ b/packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_crossline.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Line, Rect } from '../../../../geoms/types'; +import { Line } from '../../../../geoms/types'; import { getTooltipType } from '../../../../specs'; import { TooltipType } from '../../../../specs/constants'; import { GlobalChartState } from '../../../../state/chart_state'; @@ -23,62 +23,22 @@ import { Theme } from '../../../../utils/themes/theme'; import { getCursorBandPositionSelector } from '../../state/selectors/get_cursor_band'; import { getCursorLinePositionSelector } from '../../state/selectors/get_cursor_line'; -interface CrosshairProps { +interface CursorCrossLineProps { theme: Theme; chartRotation: Rotation; - cursorPosition?: Rect; cursorCrossLinePosition?: Line; tooltipType: TooltipType; - fromExternalEvent?: boolean; - zIndex: number; -} - -function canRenderBand(type: TooltipType, visible: boolean, fromExternalEvent?: boolean) { - return visible && (type === TooltipType.Crosshairs || type === TooltipType.VerticalCursor || fromExternalEvent); } function canRenderHelpLine(type: TooltipType, visible: boolean) { return visible && type === TooltipType.Crosshairs; } -class CrosshairComponent extends React.Component { - static displayName = 'Crosshair'; +class CursorCrossLineComponent extends React.Component { + static displayName = 'CursorCrossLine'; - renderCursor() { - const { - zIndex, - theme: { - crosshair: { band, line }, - }, - cursorPosition, - tooltipType, - fromExternalEvent, - } = this.props; - - if (!cursorPosition || !canRenderBand(tooltipType, band.visible, fromExternalEvent)) { - return null; - } - 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 && } - - ); - } - - renderCrossLine() { + render() { const { - zIndex, theme: { crosshair: { crossLine }, }, @@ -98,29 +58,19 @@ class CrosshairComponent extends React.Component { }; return ( - + ); } - - render() { - return ( - <> - {this.renderCursor()} - {this.renderCrossLine()} - - ); - } } -const mapStateToProps = (state: GlobalChartState): CrosshairProps => { +const mapStateToProps = (state: GlobalChartState): CursorCrossLineProps => { if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) { return { theme: LIGHT_THEME, chartRotation: 0, tooltipType: TooltipType.None, - zIndex: 0, }; } const settings = getSettingsSpecSelector(state); @@ -131,13 +81,10 @@ const mapStateToProps = (state: GlobalChartState): CrosshairProps => { return { theme: getChartThemeSelector(state), chartRotation: getChartRotationSelector(state), - cursorPosition: cursorBandPosition, cursorCrossLinePosition: getCursorLinePositionSelector(state), tooltipType, - fromExternalEvent, - zIndex: state.zIndex, }; }; /** @internal */ -export const Crosshair = connect(mapStateToProps)(CrosshairComponent); +export const CursorCrossLine = connect(mapStateToProps)(CursorCrossLineComponent); diff --git a/packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_line.tsx b/packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_line.tsx new file mode 100644 index 0000000000..dd7de0d96a --- /dev/null +++ b/packages/charts/src/chart_types/xy_chart/renderer/dom/cursor_line.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { connect } from 'react-redux'; + +import { Rect } from '../../../../geoms/types'; +import { getTooltipType } from '../../../../specs'; +import { TooltipType } from '../../../../specs/constants'; +import { GlobalChartState } from '../../../../state/chart_state'; +import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; +import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; +import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; +import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; +import { Rotation } from '../../../../utils/common'; +import { LIGHT_THEME } from '../../../../utils/themes/light_theme'; +import { Theme } from '../../../../utils/themes/theme'; +import { getCursorBandPositionSelector } from '../../state/selectors/get_cursor_band'; + +interface CursorLineProps { + theme: Theme; + chartRotation: Rotation; + cursorPosition?: Rect; + tooltipType: TooltipType; + fromExternalEvent?: boolean; + isLine: boolean; +} + +function canRenderBand(type: TooltipType, visible: boolean, fromExternalEvent?: boolean) { + return visible && (type === TooltipType.Crosshairs || type === TooltipType.VerticalCursor || fromExternalEvent); +} + +class CursorLineComponent extends React.Component { + static displayName = 'CursorLine'; + + render() { + const { + theme: { + crosshair: { band, line }, + }, + cursorPosition, + tooltipType, + fromExternalEvent, + isLine, + } = this.props; + + if (!cursorPosition || !canRenderBand(tooltipType, band.visible, fromExternalEvent) || !isLine) { + return null; + } + const { x, y, width, height } = cursorPosition; + const { strokeWidth, stroke, dash } = line; + const strokeDasharray = (dash ?? []).join(' '); + return ( + + + + ); + } +} + +const mapStateToProps = (state: GlobalChartState): CursorLineProps => { + if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) { + return { + theme: LIGHT_THEME, + chartRotation: 0, + tooltipType: TooltipType.None, + isLine: false, + }; + } + const settings = getSettingsSpecSelector(state); + const cursorBandPosition = getCursorBandPositionSelector(state); + const fromExternalEvent = cursorBandPosition?.fromExternalEvent; + const tooltipType = getTooltipType(settings, fromExternalEvent); + const isLine = cursorBandPosition?.width === 0 || cursorBandPosition?.height === 0; + + return { + theme: getChartThemeSelector(state), + chartRotation: getChartRotationSelector(state), + cursorPosition: cursorBandPosition, + tooltipType, + fromExternalEvent, + isLine, + }; +}; + +/** @internal */ +export const CursorLine = connect(mapStateToProps)(CursorLineComponent); diff --git a/packages/charts/src/chart_types/xy_chart/state/chart_state.tsx b/packages/charts/src/chart_types/xy_chart/state/chart_state.tsx index 050c833f2e..1af9fb9502 100644 --- a/packages/charts/src/chart_types/xy_chart/state/chart_state.tsx +++ b/packages/charts/src/chart_types/xy_chart/state/chart_state.tsx @@ -19,7 +19,9 @@ import { InitStatus } from '../../../state/selectors/get_internal_is_intialized' import { htmlIdGenerator } from '../../../utils/common'; import { XYChart } from '../renderer/canvas/xy_chart'; import { Annotations } from '../renderer/dom/annotations'; -import { Crosshair } from '../renderer/dom/crosshair'; +import { CursorBand } from '../renderer/dom/cursor_band'; +import { CursorCrossLine } from '../renderer/dom/cursor_crossline'; +import { CursorLine } from '../renderer/dom/cursor_line'; import { Highlighter } from '../renderer/dom/highlighter'; import { computeChartDimensionsSelector } from './selectors/compute_chart_dimensions'; import { computeLegendSelector } from './selectors/compute_legend'; @@ -112,8 +114,10 @@ export class XYAxisChartState implements InternalChartState { chartRenderer(containerRef: BackwardRef, forwardCanvasRef: RefObject) { return ( <> - + + + diff --git a/packages/charts/src/components/__snapshots__/chart.test.tsx.snap b/packages/charts/src/components/__snapshots__/chart.test.tsx.snap index d0682a2ccf..df63303f9f 100644 --- a/packages/charts/src/components/__snapshots__/chart.test.tsx.snap +++ b/packages/charts/src/components/__snapshots__/chart.test.tsx.snap @@ -67,9 +67,9 @@ exports[`Chart should render the legend name test 1`] = `
- - - + + +
@@ -95,6 +95,12 @@ exports[`Chart should render the legend name test 1`] = ` + + + + + +