From 435c67c2f873c15cd7509f81faed8adf0915208a Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Mon, 6 Jul 2020 08:19:44 -0500 Subject: [PATCH] feat: add custom annotation tooltip (#727) Added a prop to render custom annotation tooltip or tooltip details. Add tooltip portal options for more control of placement, offset, and boundary. Depreciates `renderTooltip` prop in favor of customTooltipDetails prop. Co-authored-by: Marco Vettorello --- api/charts.api.md | 37 ++++-- ...-options-visually-looks-correct-1-snap.png | Bin 0 -> 15407 bytes ...options-visually-looks-correct-1-snap.png} | Bin .../xy_chart/annotations/rect/tooltip.ts | 4 +- .../xy_chart/annotations/tooltip.ts | 34 +++++- src/chart_types/xy_chart/annotations/types.ts | 13 ++- .../dom/annotations/annotation_tooltip.tsx | 32 ++++-- .../renderer/dom/annotations/annotations.tsx | 2 +- .../dom/annotations/tooltip_content.tsx | 20 +++- src/chart_types/xy_chart/utils/specs.ts | 27 ++++- src/components/index.ts | 2 +- src/components/portal/tooltip_portal.tsx | 4 +- src/components/portal/types.ts | 41 +++++-- src/components/portal/utils.ts | 6 +- src/components/tooltip/tooltip.tsx | 8 +- src/index.ts | 2 +- src/specs/settings.tsx | 45 +------- .../annotations/lines/7_tooltip_options.tsx | 108 ++++++++++++++++++ stories/annotations/lines/line.stories.tsx | 2 + stories/annotations/rects/4_styling.tsx | 5 +- ...p_visibility.tsx => 5_tooltip_options.tsx} | 64 +++++++---- stories/annotations/rects/rects.stories.tsx | 2 +- stories/bar/48_test_tooltip.tsx | 65 ++--------- stories/utils/knobs.ts | 54 ++++++++- 24 files changed, 397 insertions(+), 180 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-lines-tooltip-options-visually-looks-correct-1-snap.png rename integration/tests/__image_snapshots__/{all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-visibility-visually-looks-correct-1-snap.png => all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-options-visually-looks-correct-1-snap.png} (100%) create mode 100644 stories/annotations/lines/7_tooltip_options.tsx rename stories/annotations/rects/{5_tooltip_visibility.tsx => 5_tooltip_options.tsx} (53%) diff --git a/api/charts.api.md b/api/charts.api.md index 8864374d21..62af02c8cb 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -23,6 +23,12 @@ export const AnnotationDomainTypes: Readonly<{ // @public (undocumented) export type AnnotationId = string; +// @public +export type AnnotationPortalSettings = TooltipPortalSettings<'chart'> & { + customTooltip?: CustomAnnotationTooltip; + customTooltipDetails?: AnnotationTooltipFormatter; +}; + // @public (undocumented) export type AnnotationSpec = LineAnnotationSpec | RectAnnotationSpec; @@ -232,7 +238,7 @@ export type BarStyleOverride = RecursivePartial | Color | null; // Warning: (ae-missing-release-tag) "BaseAnnotationSpec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface BaseAnnotationSpec extends Spec { +export interface BaseAnnotationSpec extends Spec, AnnotationPortalSettings { annotationType: T; // (undocumented) chartType: typeof ChartTypes.XYAxis; @@ -405,6 +411,12 @@ export const CurveType: Readonly<{ // @public (undocumented) export type CurveType = $Values; +// @public (undocumented) +export type CustomAnnotationTooltip = ComponentType<{ + header?: string; + details?: string; +}> | null; + // @public export type CustomTooltip = ComponentType; @@ -537,11 +549,8 @@ export type ElementOverListener = (elements: Array & { visible?: boolean; - placement?: Placement; - fallbackPlacements?: Placement[]; - boundary?: HTMLElement | 'chart'; }; } @@ -1432,17 +1441,21 @@ export interface TooltipInfo { } // @public -export interface TooltipProps { - boundary?: HTMLElement | 'chart'; - customTooltip?: CustomTooltip; +export interface TooltipPortalSettings { + boundary?: HTMLElement | B; fallbackPlacements?: Placement[]; - headerFormatter?: TooltipValueFormatter; + offset?: number; placement?: Placement; - snap?: boolean; +} + +// @public +export type TooltipProps = TooltipPortalSettings<'chart'> & { type?: TooltipType; - // @alpha + snap?: boolean; + headerFormatter?: TooltipValueFormatter; unit?: string; -} + customTooltip?: CustomTooltip; +}; // @public export type TooltipSettings = TooltipType | TooltipProps; diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-lines-tooltip-options-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-lines-tooltip-options-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..f2bd48c9f2dd609d7de9ac30608456d17b1ac13a GIT binary patch literal 15407 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 { + return { + placement, + fallbackPlacements, + boundary, + offset, + }; +} diff --git a/src/chart_types/xy_chart/annotations/types.ts b/src/chart_types/xy_chart/annotations/types.ts index a60c8242a2..af5467bca8 100644 --- a/src/chart_types/xy_chart/annotations/types.ts +++ b/src/chart_types/xy_chart/annotations/types.ts @@ -17,6 +17,9 @@ * under the License. */ +import { ComponentType } from 'react'; + +import { TooltipPortalSettings } from '../../../components/portal'; import { Position, Color } from '../../../utils/commons'; import { AnnotationType } from '../utils/specs'; import { AnnotationLineProps } from './line/types'; @@ -25,6 +28,12 @@ import { AnnotationRectProps } from './rect/types'; /** @public */ export type AnnotationTooltipFormatter = (details?: string) => JSX.Element | null; +/** @public */ +export type CustomAnnotationTooltip = ComponentType<{ + header?: string; + details?: string; +}> | null; + /** * The header and description strings for an Annotation * @internal @@ -62,7 +71,9 @@ export interface AnnotationTooltipState { top: number; left: number; }; - renderTooltip?: AnnotationTooltipFormatter; + customTooltipDetails?: AnnotationTooltipFormatter; + customTooltip?: CustomAnnotationTooltip; + tooltipSettings?: TooltipPortalSettings<'chart'>; } /** @internal */ 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 892414cf7a..87b1b5abe9 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 @@ -17,21 +17,21 @@ * under the License. */ -import React, { useCallback, useMemo, useEffect } from 'react'; +import React, { useCallback, useMemo, useEffect, RefObject } from 'react'; -import { TooltipPortal, Placement } from '../../../../../components/portal'; +import { TooltipPortal, Placement, TooltipPortalSettings } from '../../../../../components/portal'; import { AnnotationTooltipState } from '../../../annotations/types'; import { TooltipContent } from './tooltip_content'; -interface RectAnnotationTooltipProps { +interface AnnotationTooltipProps { state: AnnotationTooltipState | null; - chartRef: HTMLDivElement | null; + chartRef: RefObject; chartId: string; onScroll?: () => void; } /** @internal */ -export const AnnotationTooltip = ({ state, chartRef, chartId, onScroll }: RectAnnotationTooltipProps) => { +export const AnnotationTooltip = ({ state, chartRef, chartId, onScroll }: AnnotationTooltipProps) => { const renderTooltip = useCallback(() => { if (!state || !state.isVisible) { return null; @@ -54,8 +54,22 @@ export const AnnotationTooltip = ({ state, chartRef, chartId, onScroll }: RectAn } }, []); // eslint-disable-line react-hooks/exhaustive-deps + const popperSettings = useMemo((): TooltipPortalSettings | undefined => { + const settings = state?.tooltipSettings; + if (!settings) { + return; + } + + const { placement, boundary, ...rest } = settings; + + return { + ...rest, + placement: placement ?? state?.anchor?.position ?? Placement.Right, + boundary: boundary === 'chart' && chartRef.current ? chartRef.current : undefined, + }; + }, [state?.tooltipSettings, state?.anchor?.position, chartRef]); + const position = useMemo(() => state?.anchor ?? null, [state?.anchor]); - const placement = useMemo(() => state?.anchor?.position ?? Placement.Right, [state?.anchor?.position]); if (!state?.isVisible) { return null; } @@ -65,12 +79,10 @@ export const AnnotationTooltip = ({ state, chartRef, chartId, onScroll }: RectAn chartId={chartId} anchor={{ position, - ref: chartRef, + ref: chartRef.current, }} visible={state?.isVisible ?? false} - settings={{ - placement, - }} + settings={popperSettings} > {renderTooltip()} diff --git a/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx b/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx index 3a6906a8d2..92546a95d9 100644 --- a/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx +++ b/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx @@ -126,7 +126,7 @@ const AnnotationsComponent = ({ diff --git a/src/chart_types/xy_chart/renderer/dom/annotations/tooltip_content.tsx b/src/chart_types/xy_chart/renderer/dom/annotations/tooltip_content.tsx index 34488b53d6..65e23fa0f1 100644 --- a/src/chart_types/xy_chart/renderer/dom/annotations/tooltip_content.tsx +++ b/src/chart_types/xy_chart/renderer/dom/annotations/tooltip_content.tsx @@ -23,16 +23,22 @@ import { AnnotationTypes } from '../../../../specs'; import { AnnotationTooltipState } from '../../../annotations/types'; /** @internal */ -export const TooltipContent = ({ annotationType, header, details, renderTooltip }: AnnotationTooltipState) => { +export const TooltipContent = ({ + annotationType, + header, + details, + customTooltip: CustomTooltip, + customTooltipDetails, +}: AnnotationTooltipState) => { const renderLine = useCallback(() => (

{header}

-
{details}
+
{customTooltipDetails ? customTooltipDetails(details) : details}
- ), [header, details]); + ), [header, details, customTooltipDetails]); const renderRect = useCallback(() => { - const tooltipContent = renderTooltip ? renderTooltip(details) : details; + const tooltipContent = customTooltipDetails ? customTooltipDetails(details) : details; if (!tooltipContent) { return null; } @@ -44,7 +50,11 @@ export const TooltipContent = ({ annotationType, header, details, renderTooltip ); - }, [details, renderTooltip]); + }, [details, customTooltipDetails]); + + if (CustomTooltip) { + return ; + } switch (annotationType) { case AnnotationTypes.Line: { diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 3b36216f77..afdbf1b1ca 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -20,6 +20,7 @@ import { $Values } from 'utility-types'; import { ChartTypes } from '../..'; +import { TooltipPortalSettings } from '../../../components/portal/types'; import { ScaleContinuousType } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { Spec } from '../../../specs'; @@ -39,7 +40,7 @@ import { BubbleSeriesStyle, } from '../../../utils/themes/theme'; import { PrimitiveValue } from '../../partition_chart/layout/utils/group_by_rollup'; -import { AnnotationTooltipFormatter } from '../annotations/types'; +import { AnnotationTooltipFormatter, CustomAnnotationTooltip } from '../annotations/types'; import { RawDataSeriesDatum, XYChartSeriesIdentifier } from './series'; /** @public */ @@ -756,7 +757,9 @@ export type RectAnnotationSpec = BaseAnnotationSpec< RectAnnotationDatum, RectAnnotationStyle > & { - /** Custom rendering function for tooltip */ + /** + * @deprecated use customTooltipDetails + */ renderTooltip?: AnnotationTooltipFormatter; /** * z-index of the annotation relative to other elements in the chart @@ -765,11 +768,29 @@ export type RectAnnotationSpec = BaseAnnotationSpec< zIndex?: number; }; +/** + * Portal settings for annotation tooltips + * + * @public + */ +export type AnnotationPortalSettings = TooltipPortalSettings<'chart'> & { + /** + * The react component used to render a custom tooltip + * @public + */ + customTooltip?: CustomAnnotationTooltip; + /** + * The react component used to render a custom tooltip details + * @public + */ + customTooltipDetails?: AnnotationTooltipFormatter; +}; + export interface BaseAnnotationSpec< T extends typeof AnnotationTypes.Rectangle | typeof AnnotationTypes.Line, D extends RectAnnotationDatum | LineAnnotationDatum, S extends RectAnnotationStyle | LineAnnotationStyle -> extends Spec { +> extends Spec, AnnotationPortalSettings { chartType: typeof ChartTypes.XYAxis; specType: typeof SpecTypes.Annotation; /** diff --git a/src/components/index.ts b/src/components/index.ts index 54a53b4d9a..069293954b 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -18,4 +18,4 @@ */ export { Chart } from './chart'; -export { Placement } from './portal'; +export { Placement, TooltipPortalSettings } from './portal'; diff --git a/src/components/portal/tooltip_portal.tsx b/src/components/portal/tooltip_portal.tsx index 573ab14716..6a7a98331f 100644 --- a/src/components/portal/tooltip_portal.tsx +++ b/src/components/portal/tooltip_portal.tsx @@ -22,7 +22,7 @@ import { useRef, useEffect, useCallback, ReactNode, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { mergePartial, isDefined } from '../../utils/commons'; -import { PopperSettings, PortalAnchorRef } from './types'; +import { TooltipPortalSettings, PortalAnchorRef } from './types'; import { DEFAULT_POPPER_SETTINGS, getOrCreateNode, isHTMLElement } from './utils'; /** @@ -44,7 +44,7 @@ type PortalTooltipProps = { /** * Settings to control portal positioning */ - settings?: Partial; + settings?: TooltipPortalSettings; /** * Anchor element to use as position reference */ diff --git a/src/components/portal/types.ts b/src/components/portal/types.ts index 349198a30b..ec00997951 100644 --- a/src/components/portal/types.ts +++ b/src/components/portal/types.ts @@ -47,14 +47,6 @@ export const Placement = Object.freeze({ */ export type Placement = $Values; -/** @internal */ -export interface PopperSettings { - fallbackPlacements: Placement[]; - placement: Placement; - boundary?: HTMLElement; - offset?: number; -} - /** @internal */ export interface AnchorPosition { left: number; @@ -80,3 +72,36 @@ export interface PortalAnchorRef { */ ref: HTMLElement | null; } + +/** + * Tooltip portal settings + * + * @public + */ +export interface TooltipPortalSettings { + /** + * Preferred placement of tooltip relative to anchor. + * + * This may not be the final placement given the positioning fallbacks. + * + * @defaultValue `right` {@link (Placement:type) | Placement.Right} + */ + placement?: Placement; + /** + * If given tooltip placement is not suitable, these `Placement`s will + * be used as fallback placements. + */ + fallbackPlacements?: Placement[]; + /** + * Boundary element to contain tooltip within + * + * `'chart'` will use the chart container as the boundary + * + * @defaultValue parent scroll container + */ + boundary?: HTMLElement | B; + /** + * Custom tooltip offset + */ + offset?: number; +} diff --git a/src/components/portal/utils.ts b/src/components/portal/utils.ts index f46b653dae..1149b8c1be 100644 --- a/src/components/portal/utils.ts +++ b/src/components/portal/utils.ts @@ -17,10 +17,12 @@ * under the License. */ -import { PopperSettings, Placement } from './types'; +import { Required } from 'utility-types'; + +import { TooltipPortalSettings, Placement } from './types'; /** @internal */ -export const DEFAULT_POPPER_SETTINGS: PopperSettings = { +export const DEFAULT_POPPER_SETTINGS: Required = { fallbackPlacements: [Placement.Right, Placement.Left, Placement.Top, Placement.Bottom], placement: Placement.Right, offset: 10, diff --git a/src/components/tooltip/tooltip.tsx b/src/components/tooltip/tooltip.tsx index 27e086b66c..8a3993526e 100644 --- a/src/components/tooltip/tooltip.tsx +++ b/src/components/tooltip/tooltip.tsx @@ -36,7 +36,7 @@ import { getInternalTooltipInfoSelector } from '../../state/selectors/get_intern import { getSettingsSpecSelector } from '../../state/selectors/get_settings_specs'; import { getTooltipHeaderFormatterSelector } from '../../state/selectors/get_tooltip_header_formatter'; import { Rotation } from '../../utils/commons'; -import { TooltipPortal, PopperSettings, AnchorPosition, Placement } from '../portal'; +import { TooltipPortal, TooltipPortalSettings, AnchorPosition, Placement } from '../portal'; import { getTooltipSettings } from './get_tooltip_settings'; import { TooltipInfo, TooltipAnchorPosition } from './types'; @@ -50,7 +50,7 @@ interface TooltipStateProps { position: TooltipAnchorPosition | null; info?: TooltipInfo; headerFormatter?: TooltipValueFormatter; - settings: TooltipSettings; + settings?: TooltipSettings; rotation: Rotation; chartId: string; backgroundColor: string; @@ -177,8 +177,8 @@ const TooltipComponent = ({ }; }, [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') { + const popperSettings = useMemo((): TooltipPortalSettings | undefined => { + if (!settings || typeof settings === 'string') { return; } diff --git a/src/index.ts b/src/index.ts index 6d35efa486..576d32b3b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,7 @@ export { timeFormatter, niceTimeFormatter, niceTimeFormatByDay } from './utils/d export { Datum, Position, Rendering, Rotation } from './utils/commons'; export { SeriesIdentifier } from './commons/series_id'; export { XYChartSeriesIdentifier } from './chart_types/xy_chart/utils/series'; -export { AnnotationTooltipFormatter } from './chart_types/xy_chart/annotations/types'; +export { AnnotationTooltipFormatter, CustomAnnotationTooltip } from './chart_types/xy_chart/annotations/types'; export { GeometryValue } from './utils/geometry'; export { Config as PartitionConfig, diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index b5b568dea1..768531fc82 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -24,7 +24,7 @@ import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/grou import { XYChartSeriesIdentifier } from '../chart_types/xy_chart/utils/series'; import { DomainRange } from '../chart_types/xy_chart/utils/specs'; import { SeriesIdentifier } from '../commons/series_id'; -import { Placement } from '../components/portal'; +import { TooltipPortalSettings } from '../components/portal'; import { CustomTooltip } from '../components/tooltip/types'; import { ScaleContinuousType, ScaleOrdinalType } from '../scales'; import { getConnect, specComponentFactory } from '../state/spec_factory'; @@ -140,7 +140,7 @@ export type TooltipValueFormatter = (data: TooltipValue) => JSX.Element | string * The advanced configuration for the tooltip * @public */ -export interface TooltipProps { +export type TooltipProps = TooltipPortalSettings<'chart'> & { /** * The {@link (TooltipType:type) | TooltipType} of the tooltip */ @@ -153,30 +153,10 @@ export interface TooltipProps { * A {@link TooltipValueFormatter} to format the header value */ headerFormatter?: TooltipValueFormatter; - /** - * Preferred placement of tooltip relative to anchor. - * - * This may not be the final placement given the positioning fallbacks. - * - * @defaultValue `right` {@link (Placement:type) | Placement.Right} - */ - placement?: Placement; - /** - * If given tooltip placement is not sutable, these `Placement`s will - * be used as fallback placements. - */ - fallbackPlacements?: Placement[]; - /** - * Boundary element to contain tooltip within - * - * `'chart'` will use the chart container as the boundary - * - * @defaultValue undefined - parent scroll container - */ - boundary?: HTMLElement | 'chart'; /** * Unit for event (i.e. `time`, `feet`, `count`, etc.). * Not currently used/implemented + * * @alpha */ unit?: string; @@ -184,10 +164,10 @@ export interface TooltipProps { * Render custom tooltip given header and values */ customTooltip?: CustomTooltip; -} +}; /** - * Either a {@link (TooltipType:type)} or an {@link (TooltipProps:interface)} configuration + * Either a {@link (TooltipType:type)} or an {@link (TooltipProps:type)} configuration * @public */ export type TooltipSettings = TooltipType | TooltipProps; @@ -200,27 +180,14 @@ export interface ExternalPointerEventsSettings { /** * Tooltip settings used for external events */ - tooltip: { + tooltip: TooltipPortalSettings<'chart'> & { /** * `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 { diff --git a/stories/annotations/lines/7_tooltip_options.tsx b/stories/annotations/lines/7_tooltip_options.tsx new file mode 100644 index 0000000000..2d94b16085 --- /dev/null +++ b/stories/annotations/lines/7_tooltip_options.tsx @@ -0,0 +1,108 @@ +/* + * 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 { boolean, number } from '@storybook/addon-knobs'; +import React from 'react'; + +import { + AnnotationTooltipFormatter, + Axis, + BarSeries, + Chart, + ScaleType, + Settings, + LineAnnotation, AnnotationDomainTypes, LineAnnotationDatum, +} from '../../../src'; +import { CustomAnnotationTooltip } from '../../../src/chart_types/xy_chart/annotations/types'; +import { Icon } from '../../../src/components/icons/icon'; +import { Position } from '../../../src/utils/commons'; +import { + arrayKnobs, + getBoundaryKnob, + getChartRotationKnob, + getFallbackPlacementsKnob, + getPlacementKnob, +} from '../../utils/knobs'; + +function generateAnnotationData(values: any[]): LineAnnotationDatum[] { + return values.map((value, index) => ({ dataValue: value, details: `detail-${index}` })); +} + +export const Example = () => { + const rotation = getChartRotationKnob(); + const boundary = getBoundaryKnob(); + const placement = getPlacementKnob('Tooltip placement'); + const fallbackPlacements = getFallbackPlacementsKnob(); + const offset = number('tooltip offset', 10); + const showCustomTooltip = boolean('custom tooltip', false); + const showCustomDetails = boolean('custom tooltip details', false); + + const dataValues = generateAnnotationData(arrayKnobs('annotation values', ['a', 'c'])); + + const customTooltip: CustomAnnotationTooltip | undefined = showCustomTooltip ? ({ header, details }) => ( +
+

+ custom tooltip - + {' '} + {header} +

+

{details}

+
+ ) : undefined; + const customTooltipDetails: AnnotationTooltipFormatter | undefined = showCustomDetails ? (details) => ( +
+

custom Details

+

{details}

+
+ ) : undefined; + + return ( + + + } + /> + + + + + + ); +}; diff --git a/stories/annotations/lines/line.stories.tsx b/stories/annotations/lines/line.stories.tsx index dea5285c4c..15162f5c0a 100644 --- a/stories/annotations/lines/line.stories.tsx +++ b/stories/annotations/lines/line.stories.tsx @@ -31,5 +31,7 @@ export { Example as xOrdinalDomain } from './2_x_ordinal'; export { Example as xTimeDomain } from './3_x_time'; export { Example as yDomain } from './4_y_domain'; export { Example as styling } from './5_styling'; +export { Example as tooltipOptions } from './7_tooltip_options'; + // for testing export { Example as singleBarHistogram } from './6_test_single_bar_histogram'; diff --git a/stories/annotations/rects/4_styling.tsx b/stories/annotations/rects/4_styling.tsx index 2474837aed..202e232595 100644 --- a/stories/annotations/rects/4_styling.tsx +++ b/stories/annotations/rects/4_styling.tsx @@ -84,13 +84,12 @@ export const Example = () => { const hasCustomTooltip = boolean('has custom tooltip render', false); - const customTooltip = (details?: string) => ( + const customTooltip = ({ details } : { details?: string }) => (
{details}
); - const renderTooltip = hasCustomTooltip ? customTooltip : undefined; const isLeft = boolean('y-domain axis is Position.Left', true); const yAxisTitle = isLeft ? 'y-domain axis (left)' : 'y-domain axis (right)'; @@ -108,7 +107,7 @@ export const Example = () => { dataValues={dataValues} id="rect" style={style} - renderTooltip={renderTooltip} + customTooltip={hasCustomTooltip ? customTooltip : undefined} zIndex={zIndex} hideTooltips={hideTooltips} /> diff --git a/stories/annotations/rects/5_tooltip_visibility.tsx b/stories/annotations/rects/5_tooltip_options.tsx similarity index 53% rename from stories/annotations/rects/5_tooltip_visibility.tsx rename to stories/annotations/rects/5_tooltip_options.tsx index 916d274b7d..822bfb27c2 100644 --- a/stories/annotations/rects/5_tooltip_visibility.tsx +++ b/stories/annotations/rects/5_tooltip_options.tsx @@ -17,23 +17,30 @@ * under the License. */ -import { select } from '@storybook/addon-knobs'; +import { boolean, select } from '@storybook/addon-knobs'; import React from 'react'; -import { AnnotationTooltipFormatter, Axis, BarSeries, Chart, ScaleType, RectAnnotation } from '../../../src'; +import { AnnotationTooltipFormatter, Axis, BarSeries, Chart, ScaleType, RectAnnotation, Settings } from '../../../src'; +import { CustomAnnotationTooltip } from '../../../src/chart_types/xy_chart/annotations/types'; import { Position } from '../../../src/utils/commons'; +import { + getBoundaryKnob, + getChartRotationKnob, + getFallbackPlacementsKnob, + getPlacementKnob, +} from '../../utils/knobs'; export const Example = () => { - const tooltipOptions = { - 'default formatter, details defined': 'default_defined', - 'default formatter, details undefined': 'default_undefined', - 'custom formatter, return element': 'custom_element', - 'custom formatter, return null': 'custom_null', - }; - - const tooltipFormat = select('tooltip format', tooltipOptions, 'default_defined'); - - const isDefaultDefined = tooltipFormat === 'default_defined'; + const boundary = getBoundaryKnob(); + const placement = getPlacementKnob('Tooltip placement'); + const fallbackPlacements = getFallbackPlacementsKnob(); + const rotation = getChartRotationKnob(); + const showCustomTooltip = boolean('custom tooltip', false); + const showCustomDetails = boolean('custom tooltip details', false); + const details = select('details value', { + foo: 'foo', + undefined, + }, 'foo'); const dataValues = [ { @@ -43,27 +50,36 @@ export const Example = () => { y0: 0, y1: 7, }, - details: isDefaultDefined ? 'foo' : undefined, + details, }, ]; - const isCustomTooltipElement = tooltipFormat === 'custom_element'; - const tooltipFormatter: AnnotationTooltipFormatter = () => { - if (!isCustomTooltipElement) { - return null; - } - - return
custom formatter
; - }; - - const isCustomTooltip = tooltipFormat.includes('custom'); + const customTooltip: CustomAnnotationTooltip | undefined = showCustomTooltip ? ({ details }) => ( +
+

+ custom tooltip +

+

{details}

+
+ ) : undefined; + const customTooltipDetails: AnnotationTooltipFormatter | undefined = showCustomDetails ? (details) => ( +
+

custom Details

+

{details}

+
+ ) : undefined; return ( + diff --git a/stories/annotations/rects/rects.stories.tsx b/stories/annotations/rects/rects.stories.tsx index d3442ef2ac..fe53a70b7e 100644 --- a/stories/annotations/rects/rects.stories.tsx +++ b/stories/annotations/rects/rects.stories.tsx @@ -30,4 +30,4 @@ export { Example as linearBarChart } from './1_linear_bar_chart'; export { Example as ordinalBarChart } from './2_ordinal_bar_chart'; export { Example as linearLineChart } from './3_linear_line_chart'; export { Example as styling } from './4_styling'; -export { Example as tooltipVisibility } from './5_tooltip_visibility'; +export { Example as tooltipOptions } from './5_tooltip_options'; diff --git a/stories/bar/48_test_tooltip.tsx b/stories/bar/48_test_tooltip.tsx index e94a466817..8ab029e828 100644 --- a/stories/bar/48_test_tooltip.tsx +++ b/stories/bar/48_test_tooltip.tsx @@ -17,12 +17,18 @@ * under the License. */ -import { select, boolean, optionsKnob } from '@storybook/addon-knobs'; +import { boolean } from '@storybook/addon-knobs'; import React from 'react'; -import { Axis, BarSeries, Chart, Position, ScaleType, Settings, TooltipProps, Placement } from '../../src'; +import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '../../src'; import * as TestDatasets from '../../src/utils/data_samples/test_dataset'; -import { getChartRotationKnob, getPlacementKnob, getTooltipTypeKnob } from '../utils/knobs'; +import { + getBoundaryKnob, + getChartRotationKnob, + getFallbackPlacementsKnob, + getPlacementKnob, + getTooltipTypeKnob, +} from '../utils/knobs'; import { SB_SOURCE_PANEL } from '../utils/storybook'; const CustomTooltip = () => ( @@ -38,62 +44,13 @@ const CustomTooltip = () => ( ); -const getFallbackPlacements = (): Placement[] | undefined => { - const knob = optionsKnob( - 'Fallback Placements', - { - Top: Placement.Top, - Bottom: Placement.Bottom, - Left: Placement.Left, - Right: Placement.Right, - TopStart: Placement.TopStart, - TopEnd: Placement.TopEnd, - BottomStart: Placement.BottomStart, - BottomEnd: Placement.BottomEnd, - RightStart: Placement.RightStart, - RightEnd: Placement.RightEnd, - LeftStart: Placement.LeftStart, - LeftEnd: Placement.LeftEnd, - Auto: Placement.Auto, - AutoStart: Placement.AutoStart, - AutoEnd: Placement.AutoEnd, - }, - [Placement.Right, Placement.Left, Placement.Top, Placement.Bottom], - { - display: 'multi-select', - }, - ); - - if (typeof knob === 'string') { - // @ts-ignore - return knob.split(', '); - } - - // @ts-ignore - if (knob.length === 0) { - return; - } - - return knob; -}; - export const Example = () => { const rotation = getChartRotationKnob(); - // @ts-ignore - const boundary = select( - 'Boundary Element', - { - Chart: 'chart', - 'Document Body': document.body, - Default: undefined, - }, - undefined, - ); const tooltipOptions = { placement: getPlacementKnob('Tooltip placement'), - fallbackPlacements: getFallbackPlacements(), + fallbackPlacements: getFallbackPlacementsKnob(), type: getTooltipTypeKnob(), - boundary, + boundary: getBoundaryKnob(), customTooltip: boolean('Custom Tooltip', false) ? CustomTooltip : undefined, }; const showAxes = boolean('Show axes', false); diff --git a/stories/utils/knobs.ts b/stories/utils/knobs.ts index 6b4b89e167..ce80a2c3cc 100644 --- a/stories/utils/knobs.ts +++ b/stories/utils/knobs.ts @@ -17,9 +17,9 @@ * under the License. */ -import { select, array } from '@storybook/addon-knobs'; +import { select, array, optionsKnob } from '@storybook/addon-knobs'; -import { Rotation, Position, Placement } from '../../src'; +import { Rotation, Position, Placement, TooltipProps } from '../../src'; import { TooltipType } from '../../src/specs/constants'; export const numberSelect = ( @@ -96,3 +96,53 @@ export function arrayKnobs(name: string, values: (string | number)[]): (string | const stringifiedValues = values.map((d) => `${d}`); return array(name, stringifiedValues).map((value: string) => !isNaN(parseFloat(value)) ? parseFloat(value) : value); } + +export const getFallbackPlacementsKnob = (): Placement[] | undefined => { + const knob = optionsKnob( + 'Fallback Placements', + { + Top: Placement.Top, + Bottom: Placement.Bottom, + Left: Placement.Left, + Right: Placement.Right, + TopStart: Placement.TopStart, + TopEnd: Placement.TopEnd, + BottomStart: Placement.BottomStart, + BottomEnd: Placement.BottomEnd, + RightStart: Placement.RightStart, + RightEnd: Placement.RightEnd, + LeftStart: Placement.LeftStart, + LeftEnd: Placement.LeftEnd, + Auto: Placement.Auto, + AutoStart: Placement.AutoStart, + AutoEnd: Placement.AutoEnd, + }, + [Placement.Right, Placement.Left, Placement.Top, Placement.Bottom], + { + display: 'multi-select', + }, + ); + + if (typeof knob === 'string') { + // @ts-ignore + return knob.split(', '); + } + + // @ts-ignore + if (knob.length === 0) { + return; + } + + return knob; +}; + +// @ts-ignore +export const getBoundaryKnob = () => select( + 'Boundary Element', + { + Chart: 'chart', + 'Document Body': document.body, + Default: undefined, + }, + undefined, +);