diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-multiple-datums-for-tooltip-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-multiple-datums-for-tooltip-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..92ff866129 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-multiple-datums-for-tooltip-visually-looks-correct-1-snap.png differ diff --git a/src/chart_types/partition_chart/layout/types/viewmodel_types.ts b/src/chart_types/partition_chart/layout/types/viewmodel_types.ts index cb484618c8..2716b22389 100644 --- a/src/chart_types/partition_chart/layout/types/viewmodel_types.ts +++ b/src/chart_types/partition_chart/layout/types/viewmodel_types.ts @@ -1,5 +1,5 @@ import { Config } from './config_types'; -import { Coordinate, Distance, PointObject, PointTuple, Radian } from './geometry_types'; +import { Coordinate, Distance, Pixels, PointObject, PointTuple, Radian } from './geometry_types'; import { Font } from './types'; import { config } from '../config/config'; import { ArrayNode, HierarchyOfArrays } from '../utils/group_by_rollup'; @@ -54,6 +54,8 @@ export interface OutsideLinksViewModel { points: Array; } +export type PickFunction = (x: Pixels, y: Pixels) => Array; + export type ShapeViewModel = { config: Config; quadViewModel: QuadViewModel[]; @@ -61,16 +63,19 @@ export type ShapeViewModel = { linkLabelViewModels: LinkLabelVM[]; outsideLinksViewModel: OutsideLinksViewModel[]; diskCenter: PointObject; + pickQuads: PickFunction; }; -export const nullSectorViewModel = (): ShapeViewModel => ({ - config, +export const nullShapeViewModel = (specifiedConfig?: Config, diskCenter?: PointObject): ShapeViewModel => ({ + config: specifiedConfig || config, quadViewModel: [], rowSets: [], linkLabelViewModels: [], outsideLinksViewModel: [], - diskCenter: { x: 0, y: 0 }, + diskCenter: diskCenter || { x: 0, y: 0 }, + pickQuads: () => [], }); + type TreeLevel = number; interface AngleFromTo { diff --git a/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts b/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts index 4f943f6cfd..f5e284ba6f 100644 --- a/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts +++ b/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts @@ -4,12 +4,14 @@ import { Datum } from '../../../../utils/commons'; export const AGGREGATE_KEY = 'value'; // todo later switch back to 'aggregate' export const DEPTH_KEY = 'depth'; export const CHILDREN_KEY = 'children'; +export const INPUT_KEY = 'inputIndex'; export const PARENT_KEY = 'parent'; export const SORT_INDEX_KEY = 'sortIndex'; interface NodeDescriptor { [AGGREGATE_KEY]: number; [DEPTH_KEY]: number; + [INPUT_KEY]?: Array; } export type ArrayEntry = [Key, ArrayNode]; @@ -71,11 +73,13 @@ export function groupByRollup( const keyExists = pointer.has(key); const last = i === keyCount - 1; const node = keyExists && pointer.get(key); + const inputIndices = node ? node[INPUT_KEY] : []; const childrenMap = node ? node[CHILDREN_KEY] : new Map(); const aggregate = node ? node[AGGREGATE_KEY] : identity(); const reductionValue = reducer(aggregate, valueAccessor(n)); pointer.set(key, { [AGGREGATE_KEY]: reductionValue, + [INPUT_KEY]: [...inputIndices, index], [DEPTH_KEY]: i, ...(!last && { [CHILDREN_KEY]: childrenMap }), }); @@ -91,7 +95,7 @@ export function groupByRollup( function getRootArrayNode(): ArrayNode { const children: HierarchyOfArrays = []; - const bootstrap = { [AGGREGATE_KEY]: NaN, [DEPTH_KEY]: NaN, [CHILDREN_KEY]: children }; + const bootstrap = { [AGGREGATE_KEY]: NaN, [DEPTH_KEY]: NaN, [CHILDREN_KEY]: children, [INPUT_KEY]: [] as number[] }; Object.assign(bootstrap, { [PARENT_KEY]: bootstrap }); const result: ArrayNode = bootstrap as ArrayNode; return result; @@ -109,6 +113,7 @@ export function mapsToArrays(root: HierarchyOfMaps, sorter: NodeSorter): Hierarc [DEPTH_KEY]: NaN, [SORT_INDEX_KEY]: NaN, [PARENT_KEY]: parent, + [INPUT_KEY]: [], }; const newValue: ArrayNode = Object.assign( resultNode, diff --git a/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts b/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts index 313e1b12db..0fed9fd8cb 100644 --- a/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts +++ b/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts @@ -9,7 +9,9 @@ import { sunburst } from '../utils/sunburst'; import { IndexedAccessorFn } from '../../../../utils/accessor'; import { argsToRGBString, stringToRGB } from '../utils/d3_utils'; import { + nullShapeViewModel, OutsideLinksViewModel, + PickFunction, QuadViewModel, RawTextGetter, RowSet, @@ -117,7 +119,7 @@ export function makeOutsideLinksViewModel( }) .filter(({ points }: OutsideLinksViewModel) => points.length > 1); } -// todo break up this long function + export function shapeViewModel( textMeasure: TextMeasure, config: Config, @@ -158,14 +160,7 @@ export function shapeViewModel( facts.some((n) => valueAccessor(n) < 0) || facts.reduce((p: number, n) => aggregator.reducer(p, valueAccessor(n)), aggregator.identity()) <= 0 ) { - return { - config, - diskCenter, - quadViewModel: [], - rowSets: [], - linkLabelViewModels: [], - outsideLinksViewModel: [], - }; + return nullShapeViewModel(config, diskCenter); } // We can precompute things invariant of how the rectangle is divvied up. @@ -273,6 +268,18 @@ export function shapeViewModel( valueFormatter, ); + const pickQuads: PickFunction = (x, y) => { + return quadViewModel.filter( + treemapLayout + ? ({ x0, y0, x1, y1 }) => x0 <= x && x <= x1 && y0 <= y && y <= y1 + : ({ x0, y0px, x1, y1px }) => { + const angleX = (Math.atan2(y, x) + TAU / 4 + TAU) % TAU; + const yPx = Math.sqrt(x * x + y * y); + return x0 <= angleX && angleX <= x1 && y0px <= yPx && yPx <= y1px; + }, + ); + }; + // combined viewModel return { config, @@ -281,5 +288,6 @@ export function shapeViewModel( rowSets, linkLabelViewModels, outsideLinksViewModel, + pickQuads, }; } diff --git a/src/chart_types/partition_chart/renderer/canvas/partition.tsx b/src/chart_types/partition_chart/renderer/canvas/partition.tsx index 9564403fa7..d5f2e899cd 100644 --- a/src/chart_types/partition_chart/renderer/canvas/partition.tsx +++ b/src/chart_types/partition_chart/renderer/canvas/partition.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { MouseEvent } from 'react'; import { bindActionCreators, Dispatch } from 'redux'; import { connect } from 'react-redux'; import { onChartRendered } from '../../../../state/actions/chart'; @@ -6,8 +6,9 @@ import { isInitialized } from '../../../../state/selectors/is_initialized'; import { GlobalChartState } from '../../../../state/chart_state'; import { Dimensions } from '../../../../utils/dimensions'; import { partitionGeometries } from '../../state/selectors/geometries'; -import { nullSectorViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; +import { nullShapeViewModel, QuadViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; import { renderPartitionCanvas2d } from './canvas_renderers'; +import { INPUT_KEY } from '../../layout/utils/group_by_rollup'; interface ReactiveChartStateProps { initialized: boolean; @@ -69,6 +70,38 @@ class PartitionComponent extends React.Component { } } + handleMouseMove(e: MouseEvent) { + const { + initialized, + chartContainerDimensions: { width, height }, + } = this.props; + if (!this.canvasRef.current || !this.ctx || !initialized || width === 0 || height === 0) { + return; + } + const picker = this.props.geometries.pickQuads; + const box = this.canvasRef.current.getBoundingClientRect(); + const diskCenter = this.props.geometries.diskCenter; + const x = e.clientX - box.left - diskCenter.x; + const y = e.clientY - box.top - diskCenter.y; + const pickedShapes: Array = picker(x, y); + const datumIndices = new Set(); + pickedShapes.forEach((shape) => { + const node = shape.parent; + const shapeNode = node.children.find(([key]) => key === shape.dataName); + if (shapeNode) { + const indices = shapeNode[1][INPUT_KEY] || []; + indices.forEach((i) => datumIndices.add(i)); + } + }); + /* + console.log( + pickedShapes.map((s) => s.value), + [...datumIndices.values()], + ); + */ + return pickedShapes; // placeholder + } + render() { const { initialized, @@ -84,6 +117,7 @@ class PartitionComponent extends React.Component { className="echCanvasRenderer" width={width * this.devicePixelRatio} height={height * this.devicePixelRatio} + onMouseMove={this.handleMouseMove.bind(this)} style={{ width, height, @@ -103,7 +137,7 @@ const mapDispatchToProps = (dispatch: Dispatch): ReactiveChartDispatchProps => const DEFAULT_PROPS: ReactiveChartStateProps = { initialized: false, - geometries: nullSectorViewModel(), + geometries: nullShapeViewModel(), chartContainerDimensions: { width: 0, height: 0, diff --git a/src/chart_types/partition_chart/state/chart_state.tsx b/src/chart_types/partition_chart/state/chart_state.tsx index 53bd8f9ac2..c36f46c4a4 100644 --- a/src/chart_types/partition_chart/state/chart_state.tsx +++ b/src/chart_types/partition_chart/state/chart_state.tsx @@ -1,7 +1,10 @@ import React from 'react'; -import { InternalChartState } from '../../../state/chart_state'; +import { InternalChartState, GlobalChartState, BackwardRef } from '../../../state/chart_state'; import { ChartTypes } from '../..'; import { Partition } from '../renderer/canvas/partition'; +import { isTooltipVisibleSelector } from '../state/selectors/is_tooltip_visible'; +import { getTooltipInfoSelector } from '../state/selectors/tooltip'; +import { Tooltip } from '../../../components/tooltip'; const EMPTY_MAP = new Map(); export class PartitionState implements InternalChartState { @@ -21,19 +24,29 @@ export class PartitionState implements InternalChartState { getLegendItemsValues() { return EMPTY_MAP; } - chartRenderer() { - return ; + chartRenderer(containerRef: BackwardRef) { + return ( + <> + + + + ); } getPointerCursor() { return 'default'; } - isTooltipVisible() { - return false; + isTooltipVisible(globalState: GlobalChartState) { + return isTooltipVisibleSelector(globalState); } - getTooltipInfo() { - return undefined; + getTooltipInfo(globalState: GlobalChartState) { + return getTooltipInfoSelector(globalState); } - getTooltipAnchor() { - return null; + getTooltipAnchor(state: GlobalChartState) { + const position = state.interactions.pointer.current.position; + return { + isRotated: false, + x1: position.x, + y1: position.y, + }; } } diff --git a/src/chart_types/partition_chart/state/selectors/geometries.ts b/src/chart_types/partition_chart/state/selectors/geometries.ts index 164042ffe1..62b86523ee 100644 --- a/src/chart_types/partition_chart/state/selectors/geometries.ts +++ b/src/chart_types/partition_chart/state/selectors/geometries.ts @@ -3,7 +3,7 @@ import { GlobalChartState } from '../../../../state/chart_state'; import { getSpecsFromStore } from '../../../../state/utils'; import { ChartTypes } from '../../..'; import { render } from './scenegraph'; -import { nullSectorViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; +import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; import { PartitionSpec } from '../../specs/index'; import { SpecTypes } from '../../../../specs/settings'; @@ -15,6 +15,6 @@ export const partitionGeometries = createCachedSelector( [getSpecs, getParentDimensions], (specs, parentDimensions): ShapeViewModel => { const pieSpecs = getSpecsFromStore(specs, ChartTypes.Partition, SpecTypes.Series); - return pieSpecs.length === 1 ? render(pieSpecs[0], parentDimensions) : nullSectorViewModel(); + return pieSpecs.length === 1 ? render(pieSpecs[0], parentDimensions) : nullShapeViewModel(); }, )((state) => state.chartId); diff --git a/src/chart_types/partition_chart/state/selectors/is_tooltip_visible.ts b/src/chart_types/partition_chart/state/selectors/is_tooltip_visible.ts new file mode 100644 index 0000000000..ea65d20c06 --- /dev/null +++ b/src/chart_types/partition_chart/state/selectors/is_tooltip_visible.ts @@ -0,0 +1,20 @@ +import createCachedSelector from 're-reselect'; +import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; + +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { TooltipType, getTooltipType } from '../../../../specs'; +import { getTooltipInfoSelector } from './tooltip'; + +/** + * The brush is available only for Ordinal xScales charts and + * if we have configured an onBrushEnd listener + */ +export const isTooltipVisibleSelector = createCachedSelector( + [getSettingsSpecSelector, getTooltipInfoSelector], + (settingsSpec, tooltipInfo): boolean => { + if (getTooltipType(settingsSpec) === TooltipType.None) { + return false; + } + return tooltipInfo.values.length > 0; + }, +)(getChartIdSelector); diff --git a/src/chart_types/partition_chart/state/selectors/scenegraph.ts b/src/chart_types/partition_chart/state/selectors/scenegraph.ts index f6db36c72f..c08b1d549c 100644 --- a/src/chart_types/partition_chart/state/selectors/scenegraph.ts +++ b/src/chart_types/partition_chart/state/selectors/scenegraph.ts @@ -1,7 +1,7 @@ import { Dimensions } from '../../../../utils/dimensions'; import { shapeViewModel } from '../../layout/viewmodel/viewmodel'; import { measureText } from '../../layout/utils/measure'; -import { ShapeTreeNode, ShapeViewModel, RawTextGetter } from '../../layout/types/viewmodel_types'; +import { ShapeTreeNode, ShapeViewModel, RawTextGetter, nullShapeViewModel } from '../../layout/types/viewmodel_types'; import { DEPTH_KEY } from '../../layout/utils/group_by_rollup'; import { PartitionSpec, Layer } from '../../specs/index'; import { identity, mergePartial, RecursivePartial } from '../../../../utils/commons'; @@ -23,14 +23,7 @@ export function render(partitionSpec: PartitionSpec, parentDimensions: Dimension const partialConfig: RecursivePartial = { ...specConfig, width, height }; const config: Config = mergePartial(defaultConfig, partialConfig); if (!textMeasurerCtx) { - return { - config, - quadViewModel: [], - rowSets: [], - linkLabelViewModels: [], - outsideLinksViewModel: [], - diskCenter: { x: width / 2, y: height / 2 }, - }; + return nullShapeViewModel(config, { x: width / 2, y: height / 2 }); } return shapeViewModel( measureText(textMeasurerCtx), diff --git a/src/chart_types/partition_chart/state/selectors/tooltip.ts b/src/chart_types/partition_chart/state/selectors/tooltip.ts new file mode 100644 index 0000000000..43da080d19 --- /dev/null +++ b/src/chart_types/partition_chart/state/selectors/tooltip.ts @@ -0,0 +1,74 @@ +import createCachedSelector from 're-reselect'; +import { GlobalChartState } from '../../../../state/chart_state'; +import { partitionGeometries } from './geometries'; +import { INPUT_KEY } from '../../layout/utils/group_by_rollup'; +import { QuadViewModel } from '../../layout/types/viewmodel_types'; +import { TooltipInfo } from '../../../../components/tooltip/types'; +import { ChartTypes } from '../../..'; +import { SpecTypes } from '../../../../specs'; +import { getSpecsFromStore } from '../../../../state/utils'; +import { PartitionSpec } from '../../specs'; + +function getCurrentPointerPosition(state: GlobalChartState) { + return state.interactions.pointer.current.position; +} + +function getPieSpecOrNull(state: GlobalChartState): PartitionSpec | null { + const pieSpecs = getSpecsFromStore(state.specs, ChartTypes.Partition, SpecTypes.Series); + return pieSpecs.length > 0 ? pieSpecs[0] : null; +} + +function getValueFormatter(state: GlobalChartState) { + return getPieSpecOrNull(state)?.valueFormatter; +} + +function getLabelFormatters(state: GlobalChartState) { + return getPieSpecOrNull(state)?.layers; +} + +const EMPTY_TOOLTIP = Object.freeze({ + header: null, + values: [], +}); + +export const getTooltipInfoSelector = createCachedSelector( + [getPieSpecOrNull, partitionGeometries, getCurrentPointerPosition, getValueFormatter, getLabelFormatters], + (pieSpec, geoms, pointerPosition, valueFormatter, labelFormatters): TooltipInfo => { + if (!pieSpec || !valueFormatter || !labelFormatters) { + return EMPTY_TOOLTIP; + } + const picker = geoms.pickQuads; + const diskCenter = geoms.diskCenter; + const x = pointerPosition.x - diskCenter.x; + const y = pointerPosition.y - diskCenter.y; + const pickedShapes: Array = picker(x, y); + const datumIndices = new Set(); + const tooltipInfo: TooltipInfo = { + header: null, + values: [], + }; + pickedShapes.forEach((shape) => { + const node = shape.parent; + const formatter = labelFormatters[shape.depth - 1] && labelFormatters[shape.depth - 1].nodeLabel; + + tooltipInfo.values.push({ + label: formatter ? formatter(shape.dataName) : shape.dataName, + color: shape.fillColor, + isHighlighted: false, + isVisible: true, + seriesIdentifier: { + specId: pieSpec.id, + key: pieSpec.id, + }, + value: valueFormatter(shape.value), + }); + const shapeNode = node.children.find(([key]) => key === shape.dataName); + if (shapeNode) { + const indices = shapeNode[1][INPUT_KEY] || []; + indices.forEach((i) => datumIndices.add(i)); + } + }); + + return tooltipInfo; + }, +)((state) => state.chartId); diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts index 3508b06bac..dc1118c7f2 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts @@ -15,132 +15,100 @@ describe('Tooltip position', () => { }; describe('horizontal rotated chart', () => { it('can position the tooltip on the top left corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: false, - y1: 0, - y0: 0, - x0: 10, - x1: 10, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: false, + y1: 0, + y0: 0, + x0: 10, + x1: 10, + padding: 5, + }); expect(position.left).toBe('25px'); expect(position.top).toBe('10px'); }); it('can position the tooltip on the bottom left corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: false, - y0: 90, - y1: 90, - x0: 10, - x1: 10, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: false, + y0: 90, + y1: 90, + x0: 10, + x1: 10, + padding: 5, + }); expect(position.left).toBe('25px'); expect(position.top).toBe('80px'); }); it('can position the tooltip on the top right corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: false, - y0: 0, - y1: 0, - x0: 100, - x1: 100, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: false, + y0: 0, + y1: 0, + x0: 100, + x1: 100, + padding: 5, + }); expect(position.left).toBe('65px'); expect(position.top).toBe('10px'); }); it('can position the tooltip on the bottom right corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: false, - y0: 90, - y1: 90, - x0: 100, - x1: 100, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: false, + y0: 90, + y1: 90, + x0: 100, + x1: 100, + padding: 5, + }); expect(position.left).toBe('65px'); expect(position.top).toBe('80px'); }); }); describe('vertical rotated chart', () => { it('can position the tooltip on the top left corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: true, - y0: 0, - y1: 0, - x1: 10, - x0: 10, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: true, + y0: 0, + y1: 0, + x1: 10, + x0: 10, + padding: 5, + }); expect(position.left).toBe('20px'); expect(position.top).toBe('15px'); }); it('can position the tooltip on the bottom left corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: true, - y0: 90, - y1: 90, - x1: 10, - x0: 10, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: true, + y0: 90, + y1: 90, + x1: 10, + x0: 10, + padding: 5, + }); expect(position.left).toBe('20px'); expect(position.top).toBe('65px'); }); it('can position the tooltip on the top right corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: true, - y0: 0, - y1: 0, - x1: 100, - x0: 100, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: true, + y0: 0, + y1: 0, + x1: 100, + x0: 100, + padding: 5, + }); expect(position.left).toBe('70px'); expect(position.top).toBe('15px'); }); it('can position the tooltip on the bottom right corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: true, - y0: 90, - y1: 90, - x1: 100, - x0: 100, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: true, + y0: 90, + y1: 90, + x1: 100, + x0: 100, + padding: 5, + }); expect(position.left).toBe('70px'); expect(position.top).toBe('65px'); }); diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_type.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_type.ts index 7207474d6e..9d88fcd24e 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_type.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_type.ts @@ -1,23 +1,9 @@ import createCachedSelector from 're-reselect'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { SettingsSpec, TooltipType, isTooltipType, isTooltipProps } from '../../../../specs/settings'; +import { getTooltipType } from '../../../../specs/settings'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; export const getTooltipTypeSelector = createCachedSelector( [getSettingsSpecSelector], getTooltipType, )(getChartIdSelector); - -export function getTooltipType(settings: SettingsSpec): TooltipType | undefined { - const { tooltip } = settings; - if (tooltip === undefined || tooltip === null) { - return undefined; - } - if (isTooltipType(tooltip)) { - return tooltip; - } - if (isTooltipProps(tooltip)) { - return tooltip.type || undefined; - } - return undefined; -} 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 225b76a32a..4f6313a9d5 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 @@ -5,8 +5,7 @@ import { getSettingsSpecSelector } from '../../../../state/selectors/get_setting import { getProjectedPointerPositionSelector } from './get_projected_pointer_position'; import { getTooltipInfoSelector } from './get_tooltip_values_highlighted_geoms'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { getTooltipType } from './get_tooltip_type'; -import { TooltipType } from '../../../../specs'; +import { TooltipType, getTooltipType } from '../../../../specs'; import { isAnnotationTooltipVisibleSelector } from './is_annotation_tooltip_visible'; import { TooltipInfo } from '../../../../components/tooltip/types'; diff --git a/src/chart_types/xy_chart/tooltip/tooltip.ts b/src/chart_types/xy_chart/tooltip/tooltip.ts index bd80731086..2558efc0be 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.ts @@ -29,13 +29,15 @@ export function getSeriesTooltipValues( tooltipValues.forEach(({ value, seriesIdentifier, valueAccessor }) => { const seriesValue = defaultValue ? defaultValue : value; const current = seriesTooltipValues.get(seriesIdentifier.key) || {}; - - seriesTooltipValues.set(seriesIdentifier.key, { + const tooltipValue: TooltipLegendValue = { y0: defaultValue, y1: defaultValue, ...current, - [valueAccessor]: seriesValue, - }); + }; + if (valueAccessor != null && (valueAccessor === 'y0' || valueAccessor === 'y1')) { + tooltipValue[valueAccessor] = seriesValue; + } + seriesTooltipValues.set(seriesIdentifier.key, tooltipValue); }); return seriesTooltipValues; } diff --git a/src/components/tooltip/index.tsx b/src/components/tooltip/index.tsx index 47028c9aad..4c936c6dca 100644 --- a/src/components/tooltip/index.tsx +++ b/src/components/tooltip/index.tsx @@ -75,11 +75,10 @@ class TooltipComponent extends React.Component { } renderHeader(headerData: TooltipValue | null, formatter?: TooltipValueFormatter) { - if (!headerData) { + if (!headerData || !headerData.isVisible) { return null; } - - return formatter ? formatter(headerData) : headerData.value; + return
{formatter ? formatter(headerData) : headerData.value}
; } render() { @@ -90,7 +89,7 @@ class TooltipComponent extends React.Component { } const tooltipComponent = (
-
{this.renderHeader(info.header, headerFormatter)}
+ {this.renderHeader(info.header, headerFormatter)}
{info.values.map(({ seriesIdentifier, valueAccessor, label, value, color, isHighlighted, isVisible }) => { if (!isVisible) { diff --git a/src/components/tooltip/utils.ts b/src/components/tooltip/utils.ts index 6dece3cf88..99a7ec45aa 100644 --- a/src/components/tooltip/utils.ts +++ b/src/components/tooltip/utils.ts @@ -21,6 +21,10 @@ export interface TooltipAnchorPosition { * the left position of the anchor */ x1: number; + /** + * the padding to add between the tooltip position and the final position + */ + padding?: number; } export function getFinalTooltipPosition( @@ -30,18 +34,16 @@ export function getFinalTooltipPosition( tooltip: Dimensions, /** the tooltip anchor computed position not adjusted within chart bounds */ anchorPosition: TooltipAnchorPosition, - /** the padding to add between the tooltip position and the final position */ - padding = 10, ): { left: string | null; top: string | null; } { - const { x1, y1, isRotated } = anchorPosition; + const { x1, y1, isRotated, padding = 10 } = anchorPosition; let left = 0; let top = 0; - const x0 = anchorPosition.x0 || 0; - const y0 = anchorPosition.y0 || 0; + const x0 = anchorPosition.x0 || anchorPosition.x1; + const y0 = anchorPosition.y0 || anchorPosition.y1; if (!isRotated) { const leftOfBand = window.pageXOffset + container.left + x0; diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index 68120c9b15..4725845889 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -101,7 +101,7 @@ export interface TooltipValue { /** * The accessor linked to the current tooltip value */ - valueAccessor: Accessor; + valueAccessor?: Accessor; } export type TooltipValueFormatter = (data: TooltipValue) => JSX.Element | string; @@ -267,3 +267,17 @@ export function isCrosshairTooltipType(type: TooltipType) { export function isFollowTooltipType(type: TooltipType) { return type === TooltipType.Follow; } + +export function getTooltipType(settings: SettingsSpec): TooltipType | undefined { + const { tooltip } = settings; + if (tooltip === undefined || tooltip === null) { + return undefined; + } + if (isTooltipType(tooltip)) { + return tooltip; + } + if (isTooltipProps(tooltip)) { + return tooltip.type || undefined; + } + return undefined; +}