From 2e1648d4c610c71acf6f4370205aa06a737d422f Mon Sep 17 00:00:00 2001 From: Yann Bolliger Date: Mon, 19 Dec 2022 12:32:37 +0100 Subject: [PATCH] feat(legend): add custom legend component (#1889) The settings now have a new prop `customLegend` which lets users provide there own legend React component that is rendered with the chart state into the legend container. Analogously to the `customTooltip` in the `Tooltip`, the `customLegend` takes a React component which will receive the state of the standard legend as props. This allows users to provide their own legend implementation. The legend is still rendered inside of the `div.echLegendListContainer`. Co-authored-by: Marco Vettorello --- .../legend/custom-legend-chrome-linux.png | Bin 0 -> 39656 bytes packages/charts/api/charts.api.md | 41 +++++++- packages/charts/src/common/category.ts | 2 +- .../src/components/legend/custom_legend.tsx | 27 ++++++ .../charts/src/components/legend/legend.tsx | 28 +++++- packages/charts/src/index.ts | 3 +- packages/charts/src/specs/settings.tsx | 37 +++++++- packages/charts/src/specs/tooltip.ts | 17 +--- .../selectors/get_legend_config_selector.ts | 2 + .../src/state/selectors/get_pointer_value.ts | 21 +++++ packages/charts/src/state/types.ts | 25 ++++- .../stories/legend/16_custom_legend.story.tsx | 89 ++++++++++++++++++ storybook/stories/legend/legend.stories.tsx | 1 + 13 files changed, 263 insertions(+), 30 deletions(-) create mode 100644 e2e/screenshots/all.test.ts-snapshots/baselines/legend/custom-legend-chrome-linux.png create mode 100644 packages/charts/src/components/legend/custom_legend.tsx create mode 100644 packages/charts/src/state/selectors/get_pointer_value.ts create mode 100644 storybook/stories/legend/16_custom_legend.story.tsx diff --git a/e2e/screenshots/all.test.ts-snapshots/baselines/legend/custom-legend-chrome-linux.png b/e2e/screenshots/all.test.ts-snapshots/baselines/legend/custom-legend-chrome-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..23700bc900135ba8c9521932a24502459cfe9a40 GIT binary patch literal 39656 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

t8^kR&0T`LqT- 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 TQ=dFAtc=x#OXh_0S0DTbp}wzI literal 0 HcmV?d00001 diff --git a/packages/charts/api/charts.api.md b/packages/charts/api/charts.api.md index 3a4301ca0b..b480ad26a9 100644 --- a/packages/charts/api/charts.api.md +++ b/packages/charts/api/charts.api.md @@ -441,6 +441,9 @@ export interface BubbleSeriesStyle { // @public (undocumented) export type CategoryKey = string; +// @public (undocumented) +export type CategoryLabel = string; + // @public (undocumented) export interface Cell { // Warning: (ae-forgotten-export) The symbol "HeatmapCellDatum" needs to be exported by the entry point index.d.ts @@ -644,6 +647,29 @@ export type CustomAnnotationTooltip = ComponentType<{ datum: LineAnnotationDatum | RectAnnotationDatum; }> | null; +// @public +export type CustomLegend = ComponentType; + +// @public +export interface CustomLegendProps { + // (undocumented) + items: { + seriesIdentifiers: SeriesIdentifier[]; + path: LegendPath; + color: Color; + label: CategoryLabel; + seriesType?: SeriesType; + pointStyle?: PointStyle; + extraValue?: PrimitiveValue; + isSeriesHidden?: boolean; + onItemOverActon: () => void; + onItemOutAction: () => void; + onItemClickAction: (negate: boolean) => void; + }[]; + // (undocumented) + pointerValue?: PointerValue; +} + // @public export type CustomTooltip = ComponentType>; @@ -1582,6 +1608,7 @@ export type LegendPositionConfig = { // @public export interface LegendSpec { + customLegend?: CustomLegend; flatLegend?: boolean; legendAction?: LegendAction; // (undocumented) @@ -2097,6 +2124,13 @@ export const PointerUpdateTrigger: Readonly<{ // @public (undocumented) export type PointerUpdateTrigger = $Values; +// @public +export interface PointerValue { + formattedValue: string; + value: any; + valueAccessor?: Accessor; +} + // @public (undocumented) export const PointShape: Readonly<{ Circle: "circle"; @@ -2445,7 +2479,7 @@ export const Settings: (props: SFProps; +export const settingsBuildProps: BuildProps; // @public (undocumented) export type SettingsProps = ComponentProps; @@ -3006,18 +3040,15 @@ export const TooltipType: Readonly<{ export type TooltipType = $Values; // @public -export interface TooltipValue { +export interface TooltipValue extends PointerValue { color: Color; datum?: D; formattedMarkValue?: string | null; - formattedValue: string; isHighlighted: boolean; isVisible: boolean; label: string; markValue?: number | null; seriesIdentifier: SI; - value: any; - valueAccessor?: Accessor; } // @public diff --git a/packages/charts/src/common/category.ts b/packages/charts/src/common/category.ts index 9b909c3b13..aa53c7d8cb 100644 --- a/packages/charts/src/common/category.ts +++ b/packages/charts/src/common/category.ts @@ -18,5 +18,5 @@ /** @public */ export type CategoryKey = string; -/** @internal */ +/** @public */ export type CategoryLabel = string; diff --git a/packages/charts/src/components/legend/custom_legend.tsx b/packages/charts/src/components/legend/custom_legend.tsx new file mode 100644 index 0000000000..e93a1128e0 --- /dev/null +++ b/packages/charts/src/components/legend/custom_legend.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { connect } from 'react-redux'; + +import { CustomLegendProps, CustomLegend as CustomLegendComponent } from '../../specs'; +import { GlobalChartState } from '../../state/chart_state'; +import { getPointerValueSelector } from '../../state/selectors/get_pointer_value'; + +interface Props extends CustomLegendProps { + component: CustomLegendComponent; +} + +const CustomLegendComponent: React.FC = ({ component: Component, ...props }) => ; + +const mapStateToProps = (state: GlobalChartState) => ({ + pointerValue: getPointerValueSelector(state), +}); + +/** @internal */ +export const CustomLegend = connect(mapStateToProps)(CustomLegendComponent); diff --git a/packages/charts/src/components/legend/legend.tsx b/packages/charts/src/components/legend/legend.tsx index 204de752d1..8d0328f692 100644 --- a/packages/charts/src/components/legend/legend.tsx +++ b/packages/charts/src/components/legend/legend.tsx @@ -33,6 +33,7 @@ import { hasMostlyRTLItems, HorizontalAlignment, LayoutDirection, VerticalAlignm import { Dimensions, Size } from '../../utils/dimensions'; import { LIGHT_THEME } from '../../utils/themes/light_theme'; import { Theme } from '../../utils/themes/theme'; +import { CustomLegend } from './custom_legend'; import { LegendItemProps, renderLegendItem } from './legend_item'; import { getLegendPositionConfig, legendPositionStyle } from './position_style'; import { getLegendStyle, getLegendListStyle } from './style_utils'; @@ -112,11 +113,28 @@ function LegendComponent(props: LegendStateProps & LegendDispatchProps) { const positionStyle = legendPositionStyle(config, size, chartDimensions, containerDimensions); return (
-
-
    - {items.map((item, index) => renderLegendItem(item, itemProps, index))} -
-
+ {config.customLegend ? ( +
+ ({ + ...customProps, + seriesIdentifiers, + path, + extraValue: itemProps.extraValues.get(seriesIdentifiers[0].key)?.get(childId || ''), + onItemOutAction: itemProps.mouseOutAction, + onItemOverActon: () => itemProps.mouseOverAction(path), + onItemClickAction: (negate: boolean) => itemProps.toggleDeselectSeriesAction(seriesIdentifiers, negate), + }))} + /> +
+ ) : ( +
+
    + {items.map((item, index) => renderLegendItem(item, itemProps, index))} +
+
+ )}
); } diff --git a/packages/charts/src/index.ts b/packages/charts/src/index.ts index bb94735a4f..b931372104 100644 --- a/packages/charts/src/index.ts +++ b/packages/charts/src/index.ts @@ -27,6 +27,7 @@ export { DebugStateAxis, DebugStateBase, DebugStateLegendItem, + PointerValue, } from './state/types'; export { toEntries } from './utils/common'; export { CurveType } from './utils/curves'; @@ -43,7 +44,7 @@ export { } from './chart_types/xy_chart/annotations/types'; export { GeometryValue, BandedAccessorType } from './utils/geometry'; export { LegendPath, LegendPathElement } from './state/actions/legend'; -export { CategoryKey } from './common/category'; +export { CategoryKey, CategoryLabel } from './common/category'; export { Layer as PartitionLayer, PartitionProps } from './chart_types/partition_chart/specs/index'; export { FillLabelConfig as PartitionFillLabel, PartitionStyle } from './utils/themes/partition'; export { PartitionLayout } from './chart_types/partition_chart/layout/types/config_types'; diff --git a/packages/charts/src/specs/settings.tsx b/packages/charts/src/specs/settings.tsx index 12394cf6f7..b5b51fe5fb 100644 --- a/packages/charts/src/specs/settings.tsx +++ b/packages/charts/src/specs/settings.tsx @@ -12,15 +12,17 @@ import { CustomXDomain, GroupByAccessor, Spec } from '.'; import { Cell } from '../chart_types/heatmap/layout/types/viewmodel_types'; import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; import { LegendStrategy } from '../chart_types/partition_chart/layout/utils/highlighted_geoms'; -import { LineAnnotationDatum, RectAnnotationDatum } from '../chart_types/specs'; +import { LineAnnotationDatum, RectAnnotationDatum, SeriesType } from '../chart_types/specs'; import { WordModel } from '../chart_types/wordcloud/layout/types/viewmodel_types'; import { XYChartSeriesIdentifier } from '../chart_types/xy_chart/utils/series'; +import { CategoryLabel } from '../common/category'; import { Color } from '../common/colors'; import { SeriesIdentifier } from '../common/series_id'; import { TooltipPortalSettings } from '../components'; import { ScaleContinuousType, ScaleOrdinalType } from '../scales'; import { LegendPath } from '../state/actions/legend'; import { SFProps, useSpecFactory } from '../state/spec_factory'; +import { PointerValue } from '../state/types'; import { HorizontalAlignment, LayoutDirection, @@ -34,7 +36,7 @@ import { Dimensions } from '../utils/dimensions'; import { GeometryValue } from '../utils/geometry'; import { GroupId, SpecId } from '../utils/ids'; import { SeriesCompareFn } from '../utils/series_sort'; -import { PartialTheme, Theme } from '../utils/themes/theme'; +import { PartialTheme, PointStyle, Theme } from '../utils/themes/theme'; import { BinAgg, BrushAxis, Direction, PointerEventType, PointerUpdateTrigger, settingsBuildProps } from './constants'; import { TooltipSettings } from './tooltip'; @@ -365,6 +367,33 @@ export type LegendPositionConfig = { // TODO add grow factor: fill, shrink, fixed column size }; +/** + * The props for {@link CustomLegend} + * @public + */ +export interface CustomLegendProps { + pointerValue?: PointerValue; + items: { + seriesIdentifiers: SeriesIdentifier[]; + path: LegendPath; + color: Color; + label: CategoryLabel; + seriesType?: SeriesType; + pointStyle?: PointStyle; + extraValue?: PrimitiveValue; + isSeriesHidden?: boolean; + onItemOverActon: () => void; + onItemOutAction: () => void; + onItemClickAction: (negate: boolean) => void; + }[]; +} + +/** + * The react component used to render a custom legend + * @public + */ +export type CustomLegend = ComponentType; + /** * The legend configuration * @public @@ -418,6 +447,10 @@ export interface LegendSpec { * A SeriesSortFn to sort the legend values (top-bottom) */ legendSort?: SeriesCompareFn; + /** + * Override the legend with a custom component. + */ + customLegend?: CustomLegend; } /** diff --git a/packages/charts/src/specs/tooltip.ts b/packages/charts/src/specs/tooltip.ts index 350e550425..0b5832edb6 100644 --- a/packages/charts/src/specs/tooltip.ts +++ b/packages/charts/src/specs/tooltip.ts @@ -15,7 +15,7 @@ import { SeriesIdentifier } from '../common/series_id'; import { TooltipPortalSettings } from '../components/portal'; import { CustomTooltip } from '../components/tooltip'; import { buildSFProps, SFProps, useSpecFactory } from '../state/spec_factory'; -import { Accessor } from '../utils/accessor'; +import { PointerValue } from '../state/types'; import { Datum, stripUndefined } from '../utils/common'; import { SpecType, TooltipStickTo, TooltipType } from './constants'; import { Spec } from './index'; @@ -25,19 +25,12 @@ import { SettingsSpec } from './settings'; * This interface describe the properties of single value shown in the tooltip * @public */ -export interface TooltipValue { +export interface TooltipValue + extends PointerValue { /** * The label of the tooltip value */ label: string; - /** - * The value - */ - value: any; - /** - * The formatted value to display - */ - formattedValue: string; /** * The mark value */ @@ -62,10 +55,6 @@ export interface TooltipValue; /** * The datum associated with the current tooltip value * Maybe not available diff --git a/packages/charts/src/state/selectors/get_legend_config_selector.ts b/packages/charts/src/state/selectors/get_legend_config_selector.ts index 3cd60542c6..cc75f9155e 100644 --- a/packages/charts/src/state/selectors/get_legend_config_selector.ts +++ b/packages/charts/src/state/selectors/get_legend_config_selector.ts @@ -22,6 +22,7 @@ export const getLegendConfigSelector = createCustomCachedSelector( legendPosition, legendStrategy, onLegendItemClick, + customLegend, showLegend, onLegendItemMinusClick, onLegendItemOut, @@ -38,6 +39,7 @@ export const getLegendConfigSelector = createCustomCachedSelector( legendPosition: getLegendPositionConfig(legendPosition), legendStrategy, onLegendItemClick, + customLegend, showLegend, onLegendItemMinusClick, onLegendItemOut, diff --git a/packages/charts/src/state/selectors/get_pointer_value.ts b/packages/charts/src/state/selectors/get_pointer_value.ts new file mode 100644 index 0000000000..b20ff8a616 --- /dev/null +++ b/packages/charts/src/state/selectors/get_pointer_value.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { GlobalChartState } from '../chart_state'; +import { PointerValue } from '../types'; + +/** @internal */ +export const getPointerValueSelector = (state: GlobalChartState): PointerValue | undefined => { + // TODO: this is taken from the tooltip header currently. Should in the future + // be implemented separately (and probably used *as* the tooltip header). + const header = state.internalChartState?.getTooltipInfo(state)?.header; + if (header) { + const { value, formattedValue, valueAccessor } = header; + return { value, formattedValue, valueAccessor }; + } +}; diff --git a/packages/charts/src/state/types.ts b/packages/charts/src/state/types.ts index f674c649a5..d815f9fdb1 100644 --- a/packages/charts/src/state/types.ts +++ b/packages/charts/src/state/types.ts @@ -8,8 +8,9 @@ import type { Cell } from '../chart_types/heatmap/layout/types/viewmodel_types'; import { Pixels } from '../common/geometry'; -import { AnnotationType, LineAnnotationDatum, RectAnnotationDatum } from '../specs'; -import type { Position } from '../utils/common'; +import { AnnotationType, BaseDatum, LineAnnotationDatum, RectAnnotationDatum } from '../specs'; +import { Accessor } from '../utils/accessor'; +import type { Datum, Position } from '../utils/common'; import type { GeometryValue } from '../utils/geometry'; import { LineAnnotationStyle, RectAnnotationStyle } from '../utils/themes/theme'; @@ -132,3 +133,23 @@ export interface DebugState { heatmap?: HeatmapDebugState; partition?: PartitionDebugState[]; } + +/** + * Contains the value of the non-dependent variable at the point where the mouse + * pointer is. + * @public + */ +export interface PointerValue { + /** + * The value + */ + value: any; + /** + * The formatted value to display + */ + formattedValue: string; + /** + * The accessor linked to the current tooltip value + */ + valueAccessor?: Accessor; +} diff --git a/storybook/stories/legend/16_custom_legend.story.tsx b/storybook/stories/legend/16_custom_legend.story.tsx new file mode 100644 index 0000000000..09bb220a5e --- /dev/null +++ b/storybook/stories/legend/16_custom_legend.story.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import moment from 'moment'; +import React from 'react'; + +import { + Axis, + AreaSeries, + Chart, + Position, + ScaleType, + Settings, + timeFormatter, + CustomLegend, + Tooltip, +} from '@elastic/charts'; +import { KIBANA_METRICS } from '@elastic/charts/src/utils/data_samples/test_dataset_kibana'; + +import { useBaseTheme } from '../../use_base_theme'; + +const dateFormatter = timeFormatter('HH:mm'); +const data1 = KIBANA_METRICS.metrics.kibana_os_load[0].data.map((d) => [ + ...d, + KIBANA_METRICS.metrics.kibana_os_load[0].metric.label, +]); +const data2 = KIBANA_METRICS.metrics.kibana_os_load[1].data.map((d) => [ + ...d, + KIBANA_METRICS.metrics.kibana_os_load[1].metric.label, +]); +const data3 = KIBANA_METRICS.metrics.kibana_os_load[2].data.map((d) => [ + ...d, + KIBANA_METRICS.metrics.kibana_os_load[2].metric.label, +]); +const allMetrics = [...data3, ...data2, ...data1]; + +export const Example = () => { + const customLegend: CustomLegend = ({ items, pointerValue }) => ( +
+

{pointerValue ? moment(pointerValue?.value).format('HH:mm') : 'System Load'}

+ {items.map((i) => ( + + ))} +
+ ); + + return ( + + + null} /> + + Number(d).toFixed(2)} ticks={5} /> + + + ); +}; + +Example.parameters = { + markdown: `When using a custom legend, please always specify a fixed \`legendSize\` in the \`Settings\` prop to avoid a wrongly computed default legend size.`, +}; diff --git a/storybook/stories/legend/legend.stories.tsx b/storybook/stories/legend/legend.stories.tsx index d72f80e706..89e42e5877 100644 --- a/storybook/stories/legend/legend.stories.tsx +++ b/storybook/stories/legend/legend.stories.tsx @@ -26,3 +26,4 @@ export { Example as actions } from './11_legend_actions.story'; export { Example as margins } from './12_legend_margins.story'; export { Example as singleSeries } from './14_single_series.story'; export { Example as sortItems } from './15_legend_sort.story'; +export { Example as customLegend } from './16_custom_legend.story';