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 0000000000..f2bd48c9f2 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-lines-tooltip-options-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-visibility-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-options-visually-looks-correct-1-snap.png similarity index 100% rename from integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-visibility-visually-looks-correct-1-snap.png rename to integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-options-visually-looks-correct-1-snap.png diff --git a/src/chart_types/xy_chart/annotations/rect/tooltip.ts b/src/chart_types/xy_chart/annotations/rect/tooltip.ts index 8864dc0d39..4a978f0634 100644 --- a/src/chart_types/xy_chart/annotations/rect/tooltip.ts +++ b/src/chart_types/xy_chart/annotations/rect/tooltip.ts @@ -21,7 +21,7 @@ import { Rotation } from '../../../../utils/commons'; import { Dimensions } from '../../../../utils/dimensions'; import { Point } from '../../../../utils/point'; import { AnnotationTypes } from '../../utils/specs'; -import { AnnotationTooltipFormatter, AnnotationTooltipState, Bounds } from '../types'; +import { AnnotationTooltipState, Bounds } from '../types'; import { getTransformedCursor } from '../utils'; import { isWithinRectBounds } from './dimensions'; import { AnnotationRectProps } from './types'; @@ -32,7 +32,6 @@ export function computeRectAnnotationTooltipState( annotationRects: AnnotationRectProps[], chartRotation: Rotation, chartDimensions: Dimensions, - renderTooltip?: AnnotationTooltipFormatter, ): AnnotationTooltipState | null { const rotatedProjectedCursorPosition = getTransformedCursor(cursorPosition, chartDimensions, chartRotation, true); const totalAnnotationRect = annotationRects.length; @@ -54,7 +53,6 @@ export function computeRectAnnotationTooltipState( top: cursorPosition.y, }, ...(details && { details }), - ...(renderTooltip && { renderTooltip }), }; } } diff --git a/src/chart_types/xy_chart/annotations/tooltip.ts b/src/chart_types/xy_chart/annotations/tooltip.ts index 92f03a8fb9..87f04c3b1e 100644 --- a/src/chart_types/xy_chart/annotations/tooltip.ts +++ b/src/chart_types/xy_chart/annotations/tooltip.ts @@ -17,6 +17,7 @@ * under the License. */ +import { TooltipPortalSettings } from '../../../components/portal'; import { Rotation } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; import { AnnotationId } from '../../../utils/ids'; @@ -48,7 +49,9 @@ export function computeAnnotationTooltipState( if (spec.hideTooltips || !annotationDimension) { continue; } - const { groupId } = spec; + const { groupId, customTooltip, customTooltipDetails } = spec; + + const tooltipSettings = getTooltipSettings(spec); if (isLineAnnotation(spec)) { if (spec.hideLines) { @@ -64,7 +67,12 @@ export function computeAnnotationTooltipState( ); if (lineAnnotationTooltipState) { - return lineAnnotationTooltipState; + return { + ...lineAnnotationTooltipState, + tooltipSettings, + customTooltip, + customTooltipDetails, + }; } } else if (isRectAnnotation(spec)) { const rectAnnotationTooltipState = computeRectAnnotationTooltipState( @@ -72,14 +80,32 @@ export function computeAnnotationTooltipState( annotationDimension as AnnotationRectProps[], chartRotation, chartDimensions, - spec.renderTooltip, ); if (rectAnnotationTooltipState) { - return rectAnnotationTooltipState; + return { + ...rectAnnotationTooltipState, + tooltipSettings, + customTooltip, + customTooltipDetails: customTooltipDetails ?? spec.renderTooltip, + }; } } } return null; } + +function getTooltipSettings({ + placement, + fallbackPlacements, + boundary, + offset, +}: AnnotationSpec): TooltipPortalSettings<'chart'> { + 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, +);