From a6ead1c18bfab11173160e3a2c526cec28a9337c Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Thu, 6 Feb 2020 13:13:31 -0600 Subject: [PATCH 01/15] feat: add series label settings --- .playground/playground.tsx | 123 ++++-------------- src/chart_types/xy_chart/legend/legend.ts | 4 +- .../state/selectors/compute_legend.ts | 4 + .../state/selectors/get_label_settings.ts | 9 ++ .../get_tooltip_values_highlighted_geoms.ts | 8 +- src/chart_types/xy_chart/tooltip/tooltip.ts | 4 +- src/chart_types/xy_chart/utils/series.ts | 29 +++-- src/chart_types/xy_chart/utils/specs.ts | 9 +- src/specs/settings.tsx | 12 ++ stories/styling.tsx | 38 +++++- 10 files changed, 123 insertions(+), 117 deletions(-) create mode 100644 src/chart_types/xy_chart/state/selectors/get_label_settings.ts diff --git a/.playground/playground.tsx b/.playground/playground.tsx index 402d76f4bc..c721443b64 100644 --- a/.playground/playground.tsx +++ b/.playground/playground.tsx @@ -1,113 +1,40 @@ import React from 'react'; -import { Chart, LineSeries, ScaleType, Settings, Position, Axis, BarSeries, HistogramBarSeries } from '../src'; -export class Playground extends React.Component<{}, { isSunburstShown: boolean }> { - chartRef: React.RefObject = React.createRef(); - state = { - isSunburstShown: true, - }; - onBrushEnd = (min: number, max: number) => { - // eslint-disable-next-line no-console - console.log({ min, max }); - }; +import { Chart, LineSeries, ScaleType, Position, Settings, Axis } from '../src'; +import { SeededDataGenerator } from '../src/mocks/utils'; + +export class Playground extends React.Component<{}, { isSunburstShown: boolean }> { render() { + const dg = new SeededDataGenerator(); + const data = dg.generateGroupedSeries(10, 2).map((item) => ({ + ...item, + y1: item.y + 100, + })); + return ( <>
- - - + + + + - -
-
- - - - - -
-
- - - - - - - -
-
- - - - -
diff --git a/src/chart_types/xy_chart/legend/legend.ts b/src/chart_types/xy_chart/legend/legend.ts index 42f4df4cf8..809d8fcaba 100644 --- a/src/chart_types/xy_chart/legend/legend.ts +++ b/src/chart_types/xy_chart/legend/legend.ts @@ -10,6 +10,7 @@ import { import { AxisSpec, BasicSeriesSpec, Postfixes, isAreaSeriesSpec, isBarSeriesSpec } from '../utils/specs'; import { Y0_ACCESSOR_POSTFIX, Y1_ACCESSOR_POSTFIX } from '../tooltip/tooltip'; import { BandedAccessorType } from '../../../utils/geometry'; +import { SeriesLabelSettings } from '../../../specs'; interface FormattedLastValues { y0: number | string | null; @@ -60,6 +61,7 @@ export function computeLegend( defaultColor: string, axesSpecs: AxisSpec[], deselectedDataSeries: SeriesIdentifier[] = [], + labelSettings?: SeriesLabelSettings, ): Map { const legendItems: Map = new Map(); const sortedCollection = getSortedDataSeriesColorsValuesMap(seriesCollection); @@ -69,7 +71,7 @@ export function computeLegend( const spec = getSpecsById(specs, seriesIdentifier.specId); const color = seriesColors.get(key) || defaultColor; const hasSingleSeries = seriesCollection.size === 1; - const label = getSeriesLabel(seriesIdentifier, hasSingleSeries, false, spec); + const label = getSeriesLabel(seriesIdentifier, hasSingleSeries, false, spec, labelSettings); const isSeriesVisible = deselectedDataSeries ? getSeriesIndex(deselectedDataSeries, seriesIdentifier) < 0 : true; if (label === '' || !spec) { diff --git a/src/chart_types/xy_chart/state/selectors/compute_legend.ts b/src/chart_types/xy_chart/state/selectors/compute_legend.ts index b91f665dff..97ea2508a7 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_legend.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_legend.ts @@ -6,6 +6,7 @@ import { getSeriesColorsSelector } from './get_series_color_map'; import { computeLegend, LegendItem } from '../../legend/legend'; import { GlobalChartState } from '../../../../state/chart_state'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { getLabelSettingsSelector } from './get_label_settings'; const getDeselectedSeriesSelector = (state: GlobalChartState) => state.interactions.deselectedDataSeries; @@ -17,6 +18,7 @@ export const computeLegendSelector = createCachedSelector( getSeriesColorsSelector, getAxisSpecsSelector, getDeselectedSeriesSelector, + getLabelSettingsSelector, ], ( seriesSpecs, @@ -25,6 +27,7 @@ export const computeLegendSelector = createCachedSelector( seriesColors, axesSpecs, deselectedDataSeries, + labelSettings, ): Map => { return computeLegend( seriesDomainsAndData.seriesCollection, @@ -33,6 +36,7 @@ export const computeLegendSelector = createCachedSelector( chartTheme.colors.defaultVizColor, axesSpecs, deselectedDataSeries, + labelSettings, ); }, )(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/get_label_settings.ts b/src/chart_types/xy_chart/state/selectors/get_label_settings.ts new file mode 100644 index 0000000000..431d7b4f86 --- /dev/null +++ b/src/chart_types/xy_chart/state/selectors/get_label_settings.ts @@ -0,0 +1,9 @@ +import createCachedSelector from 're-reselect'; +import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { SeriesLabelSettings } from '../../../../specs'; + +export const getLabelSettingsSelector = createCachedSelector( + [getSettingsSpecSelector], + ({ seriesLabels }): SeriesLabelSettings | undefined => seriesLabels, +)(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts index 1b529e08bc..87d04cc8db 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts @@ -14,11 +14,12 @@ import { formatTooltip } from '../../tooltip/tooltip'; import { getTooltipHeaderFormatterSelector } from './get_tooltip_header_formatter'; import { isPointOnGeometry } from '../../rendering/rendering'; import { GlobalChartState } from '../../../../state/chart_state'; -import { PointerEvent, isPointerOutEvent } from '../../../../specs'; +import { PointerEvent, isPointerOutEvent, SeriesLabelSettings } from '../../../../specs'; import { isValidPointerOverEvent } from '../../../../utils/events'; import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { hasSingleSeriesSelector } from './has_single_series'; +import { getLabelSettingsSelector } from './get_label_settings'; const EMPTY_VALUES = Object.freeze({ tooltipValues: [], @@ -45,6 +46,7 @@ export const getTooltipValuesAndGeometriesSelector = createCachedSelector( getTooltipTypeSelector, getExternalPointerEventStateSelector, getTooltipHeaderFormatterSelector, + getLabelSettingsSelector, ], getTooltipAndHighlightFromXValue, )((state: GlobalChartState) => { @@ -63,6 +65,7 @@ function getTooltipAndHighlightFromXValue( tooltipType: TooltipType, externalPointerEvent: PointerEvent | null, tooltipHeaderFormatter?: TooltipValueFormatter, + labelSettings?: SeriesLabelSettings, ): TooltipAndHighlightedGeoms { if (!scales.xScale || !scales.yScales) { return EMPTY_VALUES; @@ -131,6 +134,7 @@ function getTooltipAndHighlightFromXValue( isHighlighted, hasSingleSeries, yAxisFormatSpec, + labelSettings, ); // format only one time the x value @@ -138,7 +142,7 @@ function getTooltipAndHighlightFromXValue( // if we have a tooltipHeaderFormatter, then don't pass in the xAxis as the user will define a formatter const xAxisFormatSpec = [0, 180].includes(chartRotation) ? xAxis : yAxis; const formatterAxis = tooltipHeaderFormatter ? undefined : xAxisFormatSpec; - xValueInfo = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis); + xValueInfo = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis, labelSettings); return [xValueInfo, ...acc, formattedTooltip]; } diff --git a/src/chart_types/xy_chart/tooltip/tooltip.ts b/src/chart_types/xy_chart/tooltip/tooltip.ts index b8b1d3f285..1550e52093 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.ts @@ -10,6 +10,7 @@ import { import { IndexedGeometry, BandedAccessorType } from '../../../utils/geometry'; import { getAccessorFormatLabel } from '../../../utils/accessor'; import { getSeriesKey, getSeriesLabel } from '../utils/series'; +import { SeriesLabelSettings } from '../../../specs'; export interface TooltipLegendValue { y0: any; @@ -52,9 +53,10 @@ export function formatTooltip( isHighlighted: boolean, hasSingleSeries: boolean, axisSpec?: AxisSpec, + labelSettings?: SeriesLabelSettings, ): TooltipValue { const seriesKey = getSeriesKey(seriesIdentifier); - let displayName = getSeriesLabel(seriesIdentifier, hasSingleSeries, true, spec); + let displayName = getSeriesLabel(seriesIdentifier, hasSingleSeries, true, spec, labelSettings); if (isBandedSpec(spec.y0Accessors) && (isAreaSeriesSpec(spec) || isBarSeriesSpec(spec))) { const { y0AccessorFormat = Y0_ACCESSOR_POSTFIX, y1AccessorFormat = Y1_ACCESSOR_POSTFIX } = spec; diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index fb14f10fe3..0c66c4c256 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -3,11 +3,12 @@ import { Accessor } from '../../../utils/accessor'; import { GroupId, SpecId } from '../../../utils/ids'; import { splitSpecsByGroupId, YBasicSeriesSpec } from '../domains/y_domain'; import { formatNonStackedDataSeriesValues } from './nonstacked_series_utils'; -import { BasicSeriesSpec, SubSeriesStringPredicate, SeriesTypes, SeriesSpecs } from './specs'; +import { BasicSeriesSpec, SubSeriesLabelAccessor, SeriesTypes, SeriesSpecs } from './specs'; import { formatStackedDataSeriesValues } from './stacked_series_utils'; import { ScaleType } from '../../../utils/scales/scales'; import { LastValues } from '../state/utils'; import { Datum } from '../../../utils/domain'; +import { SeriesLabelSettings } from '../../../specs'; export interface FilledValues { /** the x value */ @@ -385,7 +386,7 @@ export function getSplittedSeries( const getCustomSubSeriesName = (() => { const cache = new Map(); - return (customSubSeriesLabel: SubSeriesStringPredicate, isTooltip: boolean) => ( + return (customLabelAccessor: SubSeriesLabelAccessor, isTooltip: boolean) => ( args: [string | number | null, string | number], ): string | number => { const [accessorKey, accessorLabel] = args; @@ -394,7 +395,14 @@ const getCustomSubSeriesName = (() => { if (cache.has(key)) { return cache.get(key); } else { - const label = customSubSeriesLabel(accessorLabel, accessorKey, isTooltip) || accessorLabel; + let label: string | number | null; + + if (typeof customLabelAccessor === 'function') { + label = customLabelAccessor(accessorLabel, accessorKey, isTooltip) ?? accessorLabel; + } else { + label = customLabelAccessor.get(accessorLabel) ?? accessorLabel; + } + cache.set(key, label); return label; @@ -406,6 +414,7 @@ const getSeriesLabelKeys = ( spec: BasicSeriesSpec, seriesIdentifier: SeriesIdentifier, isTooltip: boolean, + showSimpleLabel?: boolean, ): (string | number)[] => { const isMultipleY = spec.yAccessors.length > 1; @@ -414,12 +423,12 @@ const getSeriesLabelKeys = ( const fullKeyPairs: [string | number | null, string | number][] = [...splitAccessors.entries(), [null, yAccessor]]; const labelKeys = fullKeyPairs.map(getCustomSubSeriesName(spec.customSubSeriesLabel, isTooltip)); - return isMultipleY ? labelKeys : labelKeys.slice(0, -1); + return isMultipleY || !showSimpleLabel ? labelKeys : labelKeys.slice(0, -1); } const { seriesKeys } = seriesIdentifier; - return isMultipleY ? seriesKeys : seriesKeys.slice(0, -1); + return isMultipleY || !showSimpleLabel ? seriesKeys : seriesKeys.slice(0, -1); }; /** @@ -430,6 +439,7 @@ export function getSeriesLabel( hasSingleSeries: boolean, isTooltip: boolean, spec?: BasicSeriesSpec, + labelSettings?: SeriesLabelSettings, ): string { if (spec && spec.customSeriesLabel) { const customLabel = spec.customSeriesLabel(seriesIdentifier, isTooltip); @@ -440,7 +450,10 @@ export function getSeriesLabel( } let label = ''; - const labelKeys = spec ? getSeriesLabelKeys(spec, seriesIdentifier, isTooltip) : seriesIdentifier.seriesKeys; + const labelKeys = spec + ? getSeriesLabelKeys(spec, seriesIdentifier, isTooltip, labelSettings?.full !== true) + : seriesIdentifier.seriesKeys; + const delimiter = labelSettings?.delimiter ?? ' - '; // there is one series, the is only one yAccessor, the first part is not null if (hasSingleSeries || labelKeys.length === 0 || labelKeys[0] == null) { @@ -449,12 +462,12 @@ export function getSeriesLabel( } if (spec.splitSeriesAccessors && labelKeys.length > 0 && labelKeys[0] != null) { - label = labelKeys.join(' - '); + label = labelKeys.join(delimiter); } else { label = spec.name || `${spec.id}`; } } else { - label = labelKeys.join(' - '); + label = labelKeys.join(delimiter); } return label; diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index aa8a77b0f4..5d239902cf 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -55,11 +55,14 @@ export const DEFAULT_GLOBAL_ID = '__global__'; export type FilterPredicate = (series: SeriesIdentifier) => boolean; export type SeriesStringPredicate = (series: SeriesIdentifier, isTooltip: boolean) => string | null; -export type SubSeriesStringPredicate = ( +export type SubSeriesLabel = string | number | null; +export type SubSeriesLabelPredicate = ( accessorLabel: string | number, accessorKey: string | number | null, isTooltip: boolean, -) => string | number | null; +) => SubSeriesLabel; +export type SubSeriesLabelMap = Map; +export type SubSeriesLabelAccessor = SubSeriesLabelPredicate | SubSeriesLabelMap; /** * The fit function type @@ -252,7 +255,7 @@ export interface SeriesSpec extends Spec { * * `customSeriesLabel` takes precedence */ - customSubSeriesLabel?: SubSeriesStringPredicate; + customSubSeriesLabel?: SubSeriesLabelAccessor; } export interface Postfixes { diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index 72d2c615a7..dc949ab5ef 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -63,6 +63,17 @@ interface TooltipProps { unit?: string; } +export interface SeriesLabelSettings { + /** + * Displays full series label even with single `yAccessor` + */ + full?: boolean; + /** + * Delimiter used to join subSeries labels + */ + delimiter?: string; +} + export interface SettingsSpec extends Spec { /** * Partial theme to be merged with base @@ -109,6 +120,7 @@ export interface SettingsSpec extends Spec { onRenderChange?: RenderChangeListener; xDomain?: Domain | DomainRange; resizeDebounce?: number; + seriesLabels?: SeriesLabelSettings; } export type DefaultSettingsProps = diff --git a/stories/styling.tsx b/stories/styling.tsx index 66f757243c..024a7b3d89 100644 --- a/stories/styling.tsx +++ b/stories/styling.tsx @@ -1,4 +1,4 @@ -import { boolean, color, number, select } from '@storybook/addon-knobs'; +import { boolean, color, number, select, text } from '@storybook/addon-knobs'; import React from 'react'; import { switchTheme } from '../.storybook/theme_service'; @@ -33,7 +33,7 @@ import { BarStyleAccessor, PointStyleAccessor, SeriesStringPredicate, - SubSeriesStringPredicate, + SubSeriesLabelAccessor, } from '../src/chart_types/xy_chart/utils/specs'; import moment from 'moment'; import { DateTime } from 'luxon'; @@ -933,7 +933,7 @@ export const addCustomFullAndSubSeriesLabel = () => { return null; }; - const customSubSeriesLabel: SubSeriesStringPredicate = (accessor, key) => { + const customSubSeriesLabel: SubSeriesLabelAccessor = (accessor, key) => { if (key) { // split accessor; if (accessor === 'a') { @@ -990,7 +990,7 @@ export const addCustomSubSeriesLabelFormatting = () => { { x: 2, y: 18, percent: 1, time: start.plus({ month: 2 }).toMillis() }, { x: 3, y: 7, percent: 1, time: start.plus({ month: 3 }).toMillis() }, ]; - const customSubSeriesLabel: SubSeriesStringPredicate = (accessor, key, isTooltip) => { + const customSubSeriesLabel: SubSeriesLabelAccessor = (accessor, key, isTooltip) => { if (key === 'time') { // Format time group if (isTooltip) { @@ -1039,6 +1039,36 @@ addCustomFullAndSubSeriesLabel.story = { name: 'Add custom sub-series label formatting [time/date and percent]', }; +export const seriesLabelSettings = () => { + const dg = new SeededDataGenerator(); + const delimiter = text('Delimiter', ' ~~ '); + const full = boolean('Show full label series', false); + const showMultiple = boolean('Show multiple yAccessors', false); + const data = dg.generateGroupedSeries(10, 2).map((item) => ({ + ...item, + y1: item.y + 10, + })); + + return ( + + + Number(d).toFixed(2)} position={Position.Left} title={'y1'} /> + + + + ); +}; +seriesLabelSettings.story = { + name: 'Series label settings', +}; + export const tickLabelPaddingBothPropAndTheme = () => { const theme: PartialTheme = { axes: { From ebd5fcc4e3f9f0d9a6b52052bd3aa7d7bc7f21e3 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Mon, 10 Feb 2020 12:17:43 -0600 Subject: [PATCH 02/15] tests: update tests --- ...settings-visually-looks-correct-1-snap.png | Bin 0 -> 25693 bytes src/chart_types/xy_chart/utils/series.test.ts | 83 ++++++++++++++++++ src/chart_types/xy_chart/utils/series.ts | 35 +++----- src/mocks/series/seriesIdentifiers.ts | 9 +- 4 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-series-label-settings-visually-looks-correct-1-snap.png diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-series-label-settings-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-series-label-settings-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..402537ada8bea25a3000aaefa9fce6679bd7e596 GIT binary patch literal 25693 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 { test('Can split dataset into 1Y0G series', () => { @@ -661,4 +667,81 @@ describe('Series', () => { expect(datum.y1).toBe(null); expect(datum.y0).toBe(null); }); + describe('#getSeriesLabelKeys', () => { + const data = dg.generateGroupedSeries(50, 2).map((d) => ({ ...d, y2: d.y })); + const spec = MockSeriesSpec.area({ + data, + yAccessors: ['y', 'y2'], + splitSeriesAccessors: ['g'], + }); + const indentifiers = MockSeriesIdentifier.fromSpecs([spec]); + + it('should get series label from spec', () => { + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, spec); + expect(actual).toBe('a - y'); + }); + + describe('Custom labeling', () => { + it('should replace full label', () => { + const label = 'My custom new label'; + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, { + ...spec, + customSeriesLabel: ({ yAccessor, splitAccessors }) => + yAccessor === identifier.yAccessor && splitAccessors.get('g') === 'a' ? label : null, + }); + expect(actual).toBe(label); + }); + + it('should replace full label with customSubSeriesLabel defined', () => { + const label = 'My custom new label'; + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, { + ...spec, + customSeriesLabel: ({ yAccessor, splitAccessors }) => + yAccessor === identifier.yAccessor && splitAccessors.get('g') === 'a' ? label : null, + customSubSeriesLabel: new Map([['a', 'Apple']]), + }); + expect(actual).toBe(label); + }); + + it('should replace yAccessor sub label with function', () => { + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, { + ...spec, + customSubSeriesLabel: (label) => (label === 'y' ? 'Yuuuuup' : null), + }); + expect(actual).toBe('a - Yuuuuup'); + }); + + it('should replace splitAccessor sub label with function', () => { + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, { + ...spec, + customSubSeriesLabel: (label, key) => (key === 'g' && label === 'a' ? 'Apple' : null), + }); + + expect(actual).toBe('Apple - y'); + }); + + it('should replace yAccessor sub label with map', () => { + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, { + ...spec, + customSubSeriesLabel: new Map([['y', 'Yuuuup']]), + }); + expect(actual).toBe('a - Yuuuup'); + }); + + it('should replace splitAccessor sub label with map', () => { + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, { + ...spec, + customSubSeriesLabel: new Map([['a', 'Apple']]), + }); + expect(actual).toBe('Apple - y'); + }); + }); + }); }); diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index 0c66c4c256..d613f5021e 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -383,32 +383,21 @@ export function getSplittedSeries( /** * Get custom series sub-name */ -const getCustomSubSeriesName = (() => { - const cache = new Map(); +const getCustomSubSeriesName = (customLabelAccessor: SubSeriesLabelAccessor, isTooltip: boolean) => ( + args: [string | number | null, string | number], +): string | number => { + const [accessorKey, accessorLabel] = args; - return (customLabelAccessor: SubSeriesLabelAccessor, isTooltip: boolean) => ( - args: [string | number | null, string | number], - ): string | number => { - const [accessorKey, accessorLabel] = args; - const key = [args, isTooltip].join('~~~'); + let label: string | number; - if (cache.has(key)) { - return cache.get(key); - } else { - let label: string | number | null; - - if (typeof customLabelAccessor === 'function') { - label = customLabelAccessor(accessorLabel, accessorKey, isTooltip) ?? accessorLabel; - } else { - label = customLabelAccessor.get(accessorLabel) ?? accessorLabel; - } - - cache.set(key, label); + if (typeof customLabelAccessor === 'function') { + label = customLabelAccessor(accessorLabel, accessorKey, isTooltip) ?? accessorLabel; + } else { + label = customLabelAccessor.get(accessorLabel) ?? accessorLabel; + } - return label; - } - }; -})(); + return label; +}; const getSeriesLabelKeys = ( spec: BasicSeriesSpec, diff --git a/src/mocks/series/seriesIdentifiers.ts b/src/mocks/series/seriesIdentifiers.ts index 38f3b07671..6cddf8088b 100644 --- a/src/mocks/series/seriesIdentifiers.ts +++ b/src/mocks/series/seriesIdentifiers.ts @@ -1,5 +1,4 @@ import { BasicSeriesSpec } from '../../chart_types/xy_chart/utils/specs'; -import { getSpecId } from '../..'; import { SeriesCollectionValue, getSplittedSeries, SeriesIdentifier } from '../../chart_types/xy_chart/utils/series'; import { mergePartial } from '../../utils/commons'; @@ -19,7 +18,7 @@ export class MockSeriesCollection { export class MockSeriesIdentifier { private static readonly base: SeriesIdentifier = { - specId: getSpecId('bars'), + specId: 'bars', yAccessor: 'y', seriesKeys: ['a'], splitAccessors: new Map().set('g', 'a'), @@ -29,4 +28,10 @@ export class MockSeriesIdentifier { static default(partial?: Partial) { return mergePartial(MockSeriesIdentifier.base, partial, { mergeOptionalPartialValues: true }); } + + static fromSpecs(specs: BasicSeriesSpec[]): SeriesIdentifier[] { + const { seriesCollection } = getSplittedSeries(specs); + + return [...seriesCollection.values()].map(({ seriesIdentifier }) => seriesIdentifier); + } } From f6e6d90d348ee4f614e6d16c0a181eacdfc0520b Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Mon, 10 Feb 2020 13:53:20 -0600 Subject: [PATCH 03/15] chore: udpate pr comments --- ...settings-visually-looks-correct-1-snap.png | Bin 25693 -> 0 bytes src/chart_types/xy_chart/legend/legend.ts | 4 +- .../state/selectors/compute_legend.ts | 2 - .../state/selectors/get_label_settings.ts | 9 ---- .../get_tooltip_values_highlighted_geoms.ts | 8 +-- src/chart_types/xy_chart/tooltip/tooltip.ts | 4 +- src/chart_types/xy_chart/utils/series.test.ts | 49 ++++++++++++++++-- src/chart_types/xy_chart/utils/series.ts | 18 +++---- src/chart_types/xy_chart/utils/specs.ts | 5 +- src/specs/settings.tsx | 12 ----- stories/styling.tsx | 30 ----------- 11 files changed, 60 insertions(+), 81 deletions(-) delete mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-series-label-settings-visually-looks-correct-1-snap.png delete mode 100644 src/chart_types/xy_chart/state/selectors/get_label_settings.ts diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-series-label-settings-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-series-label-settings-visually-looks-correct-1-snap.png deleted file mode 100644 index 402537ada8bea25a3000aaefa9fce6679bd7e596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25693 zcmcG$WmuGN_%^7b0#ecqD$+1?GoYw+cXxMpDu}eSLk!*B9n#&M(hN1^(6A4`|Gw|; zvB!Sd5BtdxoSFN%?kmspybO_-6~lN#^5)sIXBZOVUlpG{djWg)?72D0Yv3n)?I!!c zAJ3f>#YCQ!j*tPrdG}1>>t|*6l!IkAZIuhk-V?&aK}S~QxAy7v_$dYPUUHmb+p;o@ zlQ(<}`+4`31{%2ue3HZLGJ}vcXgw{+KJ(wpm-wIC2UFXGOBHfy)Icd-ENMF%e<^iA zm%f36TRyEjPC|OLvHiv(DDMN`il7*9C`%H)dwTUg(EWFy^M5~lZyo;s=PNX8Ap_$x z)T{}U|2YHljxjq#T9VKJ>*vtKx(-o$hyXDSLAbq2sw6e1s%t}rK9!0SX{CObNzlg9!dPza?pRz zSS%;f_^JbIxzm_^JjdYuf^SUsP)4aT=I?af-(4#$oq=DliP%m&FB&v`Xy z!U+0?hPsT|73%E`o;CVFx*v6&Gh{trBn&8(;jc+bZ=xX)i)%Z&zv{4Tcl=yP&%*8e zCvs4M!+)S$Zgfd5%N;g`w<6`IC|6rdem{Xn`E2@duya7p=hY4tS}5g4Gn?7#g~5$( z&;PamrMjy=CLDoA{Oz6&>WP4w4CQ0vF>Ahb@qGv;8{TtUCSv_EXk|0SSxWPHdEBOf zK7m)c$XPYaU|%P2|Ze(PG>>VJ3g)aZ_X^Xk_xXG};uy-`KP}QA^RQ0+(+% zw75~A{{Bz5@M!n)a_V4WnGoV(vJE$ty;wb*dA)$S!^r9Uh$ zQED{V;#yc(#L?@7R%D4O*4dV5PuF-|`YI|-UcqmT99+3sCT^}1+&6tLJi*D_c00+i zw2h68h^QzY8_hcX!$z5@0-adGMpw^&4=r9UJHeEM&)Ju|8Cv~@<7Ij=iuuju8}Xup zZI=e$mCIw_z^{G~ax>@21?{EmU6ACU5t@j1X?oge4KFaVNhZ<*p2HhRIYQxgu#+oB zJ~VC=$D)=HBs^l!J5zIhFdl!|2ds^I^vA%3w3{rwoyxN;^iyY*xsn-D8u_EoP;~nd^%CJ^m+=Mirll#i0GVaPK;e zGH3dZ&~qB9(b9)#s^wtg)m&H4E47{Lm>kG*J-Dd}J^R-}JXA~d^fc1GRy%BP)G-J+ zVQ}JP0qy>bo$0^ySs30-SUKU%?-cJ?0XIdbizQoITjf&a09PBWwe9VJFmTiQ+L5gC z6fF2?U!f0)6^rs($2Zy8vrDB@YjeA*joA z)m3Qw!{5JKm5)9LR}w3?a4~7<`TH8npT?`8_h78%d4&i$>?`*MS(xHi5ZCpF*^G-j zsvO~L-pvg`z~{TMV%&ZH=D-#6?283GcbiL3S6ZP52K%Rs*UqS71zcs;7gC{UU;hBw-U&x5@cjdKR+wHs>E+YN>?qi zn60Hg^Sy#pS2unAmS(y?gTn7-iTp|V++xM|5=n^Y5%c}~>HdL<%;}`FtRacIcHy0D z;eIf~C@%L8ZwF94C<6DSF+B!lA4)U!ADI@xJjQyL$9-A1pm0 zo>KT@HB6I9I0ZZci%VXbwy@YG?Asmt$w%?`>LQ%)VBX8W{h1$wT4^5?d`rw>`4WR< zYEVTdIEu_@K#k*Lq*A6-8TRd%ulL(PY2Y@zokF@wY9i)EEBt1#u+ODB?eS=P*^nAoiaJ+EQ(y@2eS9 zj{4nvCDkS-)yZ(X*Z-JnCNju9O1|g3N>uWC%0YCzAG5XoS%~nrN19WGx$bGOihOSb z6M5+~j^4ztcmK1zMx?aq@sFV(Z6j2mI9sq zcZ+Cxm?tWa+Hj_9##1Jvvn{LK%MfRhi7(pA2<57HX-HVdfdt^gFeU##^=K3KsUF#g zBiF}S8;%VzeGqc2Gc@^7=iB0*l*NO4U1=bdBjq7Jb@&mutSTl1`aNS1Yq}y`d`l?3 zPQuf{Pk}m*rcRZO^fB*quLKFE-}}$Y1lFwtgOzg}Lv{-iFvW-Xzs80R(wt$2na7SU zS$;Wh8{>;Ioi(0p)`x^M!K`2M8}m2h6~@Y>y?v!XP|oc)HZfb8<@ z*fVCKspC#k+n0$$^8{5NE|1K)IPZqqTiTr5gr6-mrm5##V;@J-c}pa zB;G`JVYe;TRdF&k3iq&8|6u{_JHgH>4yz>V36Y{9fKPCkqMHnDh?OW`r-}P;p$4g4pA|?r zZ`Rk>kB^Vj<#w^5|M9wIC^&T)EMUOqlH-;l_w#d7^XG}%b^DOw=UU~}V6JRZ%I zXn8;0Up7BJ-16QZmO+8`U?7ejos9Rbu&}V{C{#vWo#^xD&t!a#flfOk<7IlnvS~uY ztvl`TuUKU%OZZix`yn27kx9a&Q@EOm@Sa9%o zf%P|2QOXRDE5q$HaAZ%qTBBgogqbIS5^@6$*Klfu&a^b?=0N$3WoBNJlN!?KqgrR^ z{pqdNTj>X#UMPpCWulXzWFypL(FqB!|125_ zg2B)29UVI+Cpm7N5ZjAt+VfsBePv!-SS>ZpwO-&ag;SXsdg&86bfrr3p-eUxFVK?Q zXoNUYlNh(zJRem**wHZPBn^9=Av1R_)XQ`9HT%o=KmH9098aCLPWdN$j!#7{G;BC1 zfoc=Er^-(j!F76}Pq;AUXsS$WmT(w*Y5nAi+Tu}BQONIDkNXYw;HmtN2MZO|VZFxe zTf0F?2$wfzicwk>|<$ipwb@4m}IrF^SBmHaquGjyH!b8@bYGmT>TOVfpv z1|DwE@J0K)e^dg@Tygk1q{JK=q`xi&8uD3=cZv4&lpBq~S4DQ-{2mY%Lc`9vy_!%p z8(z5+^tPkWy42&;p=eiMmrqVcX86I)($W&Kcb%4ohK>$&+u-s_C@zoP%Fy`z8+?Yi zjozUWEYC((u=Z&9h3CqixsxzM)A3fweH?>!Tr0j6oldCCzCtSlctCAyC^Z>KA-PL2 zkne^?0_ko%@{ z5Z2S5kYD1u);pOx_5`Z70t2o)sdJuOF;{g$h6a~bHpQJTJ59)kq(xWm z)2C+;2;}3(kNT`cysigCT$X=2)h_m?L&C#f{u_dDynOlc-*&2 z4u0u~WHj$q$wc_54SeW+=C$aIyC_WYK$HuPBVD+fGUy|Qqd62+hh?3!pS#5r1wL!P zN37KM4AeXA%0#w_G+U{ni^AeaDAMKX{PAs`fGxF8@KsM~HSwgtu#xP~|Q;&NU>`Lxse`1(2VIqj1oOFak zN;-IRu}{qBxQSFJm%)u5SloY#io1RiG4%NzVUl8#{b3k%g9%v+Q{qx+uMlOsB4xDzNmL-<{XarK(;-HPNqggZ9mpCLwGF zp!)u@>fs4e{S{U4S?!)jK{Uq~ttR>Az72TWc6q>$oe1w4Ip2JdxlrJ_SZ^20^5rv{ zrzy_PAx_PL?pH{45fm4%eY`HcH}4Z4YGZ1wcpTZdBTEg)Jsw-w=Fw)=7DuxCYBBy%o3?dP9o(9k34KCH2$jX?kH5a`5#x-*Q4 zF{Fh(ey?tfIHWJ?KWpW8c*bMKW%rYJWj-`EQ3km1!HIDXr)QP!iG0OaHYL7$DxT3i ziX6jh-Y2r)FIq*!Gn%OzKMF$HU?}uOnLAE=b`h0k{8`ccy_eW&4v9U_O?K7TzQSSx zf6Nn@7@vT^W@V%eURZl-%52T4Nw4vpX3%5Q-rp}@3K?%zrd8P~6;1OB2sh>)3bAO{HwQlP+iVqzLx_PGO|KkPd56EHtS>?)b5v-;)Q)HMC_n=Q%UAm7XPI27Q^ zx#6X?qdl1p`GGTb=AS$OAskT4om=G&a8xK@<@;xndqPjs0~%`b#~f=b;1J8JH)8+G z0k>4I)S~%8cX(rii<=1BWtI4Hl4#L=sdelhYI)3H!37OaXZ{KeX%`i}(u=6I}vL_->WwF#-$b*Dm z=Sb^UYD$W|lhg2nyY?q!^Z87AnPfq^@l33=P)68})P<$WF#?hKHDvt?Ic{^WX5T;J zB0G%MN=vG{ecSTJhwAfvTB&I^oA>4GnOb;`Gd*Tep&F_s-euM!g3H_6nWqSp3ba7- zIfIo9sn7Xk2Mh-vPxFGzzIt&r3A@mO`o)VvNMH2ED97vyIUoW z&}~o7&3Fh}wtD=D5p{CGIj*$Y?}eyRLq<7sGS%3G@+W`cJDBmsq`bRjK`BzNK07JP znkRL7AX%(5K^7iK>n_|1<8^y@A+s z2QABN**gjzN+L7rBusPOe(EfZT0Xnr18W<2V-f0F6IL14BVtrVC{{}i-Ag@FZIld9*sJrVxSJ02m+&?oKR=arBV92H}dvee%Xwo zBBX57^aTm+Fv5_K&wCaZn@2}}f4+zCc~O+16CY2xTwf>XpJrpxtFqEUjQG+*6eCy(@d}rRvOLWg|uESqVRo9PR@0o*k7yG}i%6{Odh5)aw>Kj(E zj(q3WTCP?#cLT4Qevc>h`j-9@AiKKjM`JkTNBrBjNK-1YGmx(onPgrDogvdV%QxmchyBK(Cf&SqC-kd0C(s{;{O!?iMG{m zIsONmsE|e}b@?A$qfTu)9qsFdgZZ9~ZQ`9)UD><5*d))t9$vZYsil@c6OgI<%1mT% z;b?T^8DSQ2%%}fW zKH`IY@^@jKXj=?stFF}Gi2U0OpNIJkz*HS7Thiie!XjbUIG)1IiRa}J-K-hKW(@fh zO-Z^n$|HV$c1z*|n6Qu#o?~W%<$GamZS7~cqFMMeEiyFh?Cjz)GO@pH;V*nr=2%x! z7mT>bZaTZLLMogkVZ7OmR>8nkL@#O1wLycyD@H(;-Pf#Q0#WkRCcwxDz~9h6BLe zcig+HNfrQe{GjGS&W{(8m`Ix=`O0xa6h}|yK4AxH<6l(NU~(kB$!53#nk?Eu^Y@Uh z*R=!24?|XxCu~4fVB_Fm2tnR-d~!m=TTkY8l+A9meHq8BloUgKpS|3TQ#*5X>gI_w zMVV)?_|+}+evnL0Y{Kpqj$Wo4)$?Ey2g+&Z_#>@+=fdsaI)^yjA^}sk{M*{D^$TpW zPV=xfg7;X8x>VkK#@DiT2yQg1iA`~m{1WRw=5-^464N@xF#krC!LPW)m0*0Ose1CYWix{2WD?31MT|6 zZo|)JzSHT0*2^>kkiqB*(omWJl0V<-ef#$`UypxmuQ7Lrlc{t~?tcz5U7ZlFbTtTuKb_Yx%bq`LRL?9)xj@_qW@3J^ zLB^6PLA7~L8KWSY)C zB}@N`;%i4Uumcd8c3lzvPf_$5;qWz)Wx-ZfhBhB^7ZhAp{^|h0Zepy@w-San9ap0z z6Hm(!X*{Q$+~Bj|#Yf)yMHnlWc5lCwXis3n*oUqP-<`D0J!agqx;>y#(pR^t8LnfV z_7UrR7+qpscn5<&G}oVv`kr=xK--^u+wz)|>RecURf+LuojhVUvUl|o z5~}>Y@xev_;QFfr`fMchKN(xo+?GkoPT@QmK|5LXzg?5%pcc8#^y_-XEjXPwjD!FZ z+A@(Ns3`qC#Lahxe~ALAf=5~!Z8kay6}Z_cdN@(rWt0|TzuP+jd`#F(;Y&p-&bRl`QN*QFXOU<$d+CX#KODka?uZu-RoIez) zZ2SIV$*vu(vg&w3L;YKVoc(8aEIl#;I1a5AdnwC%QQOEmf&4XOX)Sy|@@oEgnRJ&- z85V$wS^JI}sY7VCd`D>!Qf*+0N!%A zllmG*F+ALB?JukQ_Q>#oyzfW80O^*ahc^u#@pnKTd!uqtKa#WZ;Rd1i-vLipls7X} ziCJc8DV4j;a&gu3{iSW+UW+_gk}Vr4Tql2{fq|h(!{R2dg{PE_;|4`at0f%(m|uM6 zoB92zQ>bdE zl$M%OX+B|rh6a;0c0x-=?AwO-@0VX7NG6^O-N}dr#mJ&ppZE^@@jLC%WFc$}lzN3E z6F)Fj9}T>k(P_}F*ha}7WrOY|I{b%~Ys59dW|op`l$Ql| z*i~d>T@^t)XzsplY6*anN5KwGa@RRcZuY_p!Mb203++bU3vVPkCL03oeM`{=Bpdc| zdI^9v3M4q&GF%{EDOQLAfW#}B8&<+e#Xl$BS~*e#m;f~Y>VPjB$@{t~GLu3U-Oh~i z^F7ei@!M(ltl9pE2YnOF;Xznu18_oUT}v}3a~PJcGj+D~-#5E{my?*_$&QQg1AVBs z;eG;~ht;kKs`;7WXRYeuW_6*l3{BaQc9dq$hw6G3$|;+UG!@Ym>E=~2Tg)vAOBY0U z>V@P>`Q^NqiLo;1X+uDJW>5zqzaprw%rN7< zZq!IM_Xcpgxv7Hf#kYsrYz1}1Zsy33CHI1WGn8+R8Fna=Uwl__wxTOj@-pUxTwU!_ ztaBXUXf8$ZSP917H!O#!%@g}UQc>0FV{fgW8(Pmm5b^Tvi*l0uWaM2-};D#8DNu+`>4Cl<`hM*j>m zqfx;&@OM)L-}JbGU25L+xH|NoJ-~~2WQGua=7wZr`0UqJaW=5v=}B&= zJ<}f0li&}1V^$W*m7JYLvtE0>EiQzOegy_Qw3UsT@+r-!T_abF2a?SlA;~=EIP%<0 zT%t6$D8GN(VvC`{^eHA0K=24hC=2Cdg`$}0ZzWZuqbmZeBJzx0OCM$8$e9jBU5ho? z;^W;fsNGMx$D~>wuTf$MA~^ER14B<>L5UFR&JY(afL-u94Fj!DFnL7KFgwu|e2x6m zFg3yd(gLKkIVF#haMD))Nt;eHIzy!*jf6pj#C4lq$t3)3FTE`~5Woc?jh*|N+mXnt zit-c41Htw0*L(7R$KR;*9!ia_)wgKS!&lnkdmeIN3ay2PT)&!m3`c1e5>aWG=RyF_XG3IHC%ZWjjCg3$mqz7=^z@oa>`Y~bWx z+Hx#~@JG=W%t=PKBum)=A1P^J_EKDZHvXK`l}3BSHljdd2=2?`rD2I*_wYQkT;}Om z`M|Nxxp3`RluhyGXvHr7>E)wz7S4=Z?hss;0cc$pf;eEHzpXLAI2loV^je{~uMcf60D zAIwriKkAg3XqW7q9`XfMa%;Er!4pSLd}1S2y`&>BNg`(7Ugw;jZ3posp9Ah?CjAHP z-l@E!2{Zv*p)woo&)V8yj9(8j6D{aHlq(Xsy1ix?NjqMC<;~FUHi>Cd6J!Js0LQK_ zUg=z-y#IyfIlv%sVDL*%JW(lyg1TKAj%#?dcGPSQ24T%U9}JvCkCwkKPW*MQP+T0W zf?Lg_QeM|$6F&g$oKiH0Bo>9>wF4Bm94lY zN%A?O4oC1}^RsV@eW)BMXPypBzk6@aYoxD(+;h7XRHC)Mnkv=OUHbAYzZWp4#X6Ax z0zPx1(Mg$<83Li{_!%fOZ$toV=;Nkh5XJHQPtzNznMwQeREEUd2@8tx8QNQY>OI?F4yefOzRnrl*0 zTk9p)Y$Y+*-2xL>>CX!xrCrvwsjU8~#-kw%O|Nf4j@{$w?H#U_?L~*?n79jYVK59> zX{#Ugq*cvtl$8sdy!rgC*_=Qs~b4b!y3 z$n27zt(=fxWf&On=xSdlD5HiX%#osKMjazsX-Pp!maxGY$YDUlS23==0WdiexbFhf zJ8|x})t`a*l7IV!|5IYWhVem$f1gUU+WE>%05w_)lwx?dhVr0Rhx(&vO1UwG zmfxYjkFafDon2BBKk&jF{pJ8o-i=!OFKS*J=o1E0ShD1bUc9N&`}>6&t76TX&?Yyy zV!r%)(Fl^DN#Pq67TxAP06AtOLZ2$tNp?M4cn(12>J^5s92^_~DrXEpj_>Y@1*EFe z|Kqu;h{lwM1Bj?Rk$RAKAM*aXz9&I7CI8HMHk zXLbS}QsK>zzQ9iGDUfcA+&<6f#l_oeSTDJ!?Q&3EQ{TSsW`vZRQq}D|aO_-H)W-u~ zMDtv;lptg{(78o-R87z4TbXVP3MM8EHT5eyJ2-*vBR^s)T4c`k*#%)~enbqwZaFC6=72H%afuR>|>!Ib)WY_>H*f1Qpv>3T~ zRI%R~vutbjFB6Y)Pc81Zr;h0}oF(K;Hocj_P?gCKU9z$oC{3=c^g>Ns+mfCO_wBZ} zQ72wQ)`&0*xBP#w)X{i;P%_^~VV@h$fPjFP$jM^5*H>49&S`;xF9ih!k1sEg)BlGq z!^OkvDp1NZpDTa=Y`RdzXlo$e*u-RQn`s9IO6eOh;ke8 zWSUi$GhYL{Q$FKp)NHvyvw&_;T&XB;BT(}>&(SuQHAfx9iuzlhU4(7i9y^?|>iGi= z0nLMn__XP`%cP^_RouU+RIOr*MJ1Yk^?+fr$kk>KTs1Yd*2f30T)8yauV1^qTuR8u zbPw*A$xt^pH;YS2A-!W}#*mXc`-UYeE9>s*Y3e7tsavDSz@5r!{OC1^iW~1Zb~Ir$ zDA{U07ZVD_qI5<*adS$lHoJmiMBOblxZ^54j!AqXp4>d=DMoWH`=UrMM9-`O= zrVmUc?R!!?yQ-ysMzo(N@xYF$bXcx|DaKW|3eZH$ls-kjcTp zu+yzUK$n5Nw7guPT!3?Oa)Px?%f-cYyIN3CFkiIe_n6Xe?5S@8rUhU&=v8#=J9ys> zw*$?ol-rD)ecl&xvu*1MVMOZ&rfr@{!Mks62e^oxh@OMRB%gGHonD-= z?pPl{=EP#HDGMk%fcb3O{`tKei_lKEDB7ABx?F43TMy~O&97Ddl}|km8qhpD>7!lv zAbm{N3K$MNx126?I9%Z3B*E}(f!5d8AFP~B=E-Hv?xUihyyM`A1)>7|HBxURc@~hS zo++2GSBtA~)OZXFPi&5mLqJJYGF9^4w6MF`co_ca{KrB)91q8Nc(dU%yaNQd06Y zs!S2?-66QN?5+pup#;nWL1={PEuLKbsge9Hdt(`2klU9+E4{BB4ULTGmGj^F+?;0^ z_s6J~l)aO4^N8ybpt$!+}y-&xm%5g(0H#YX?iO_$R^!#NRUhC%RfyUEFms?5&?B9_}|Fl9Vq%BMGuT zXwILIchzT!%S5(c;l2+vivA69v{AprGc7yb%-c2q#`1$DfE|`3bj$SmVg|^4q7sWp zi`GXNbP~#+2gl-?SJ|SP!I#zn@CZF0uh3b7+X#4y1o)&ANLMx-ZA~1&G+#^{ot6V| z)%oIsz$4n;=uMVqE>;5H%sPx|-sT;szwe3y3tC%tw@;R->y6K2X5mHuOR+{nnwRXe zw<&)bm$@teV*^8Ua&lE0W0^Ve^SWFFym8{$`NdGKAc4~wLy;8n}7aIRB8wISR_Z^N?QU%JA1vgfABE$x1~+dR-VykZ+$KN%*@W?X=dhaA+F62{}OnHg<5tr7>r7b$L?cl3s)F z&F=9(8nKQOZ(nQ5mby|?`S>Iz^BIj7o**XA!T5kx$wevOc?uKb^C(Se8uvplW?GiP zg}kTP_=6a5K83O_y{r+Et5-N_(GPvMk%yV4)6bC{Bu+)CDav5TB(AWBO`Y!9{Y^LO zG7A?hkho5h$@(Cd=Le&RH!2^=g3f`S`Q2r9S(|4xeuWvz$;me}&Qr(uq${tx+e@G& z{-6Fx9IN7Lu-&faKk7zqo}~%=z%HC7Oz(7k7zgOV?lMqhmsKb8VyxcGmw9hVG5G) z{*>cw!jt^%JIMq~UfzV?Es3ievH8ht>s?6#k3gYE?)PRsRH8EgpRY-^P!n+D811E) z8>sTlac8Sfif6#RUh8$Dy}sJDrJ1&qT@B@m1Ld`wA47kVQ#L5gb#SPyk3Xdug3>Hi z04Z#$|EF~I5R^q2&RKNwFDT0bQVw2ducbp0$c(5)Q5a|Mf+S>Yry2%bJ)z_B@?ShJ zOje!pZ{LRIQ$h{yKe};^ZBt<&3ngN3ziAv*g`u3PjF*MDa8noFEV_{$p1cWyuDj5m z&OVUNLhO0J1DwazE+khnQRyGx)F%&y%tb5laZu9#1+1a~NXlF7iBo#eVpfE86gzO3 zPv89TnZGDkqr2oZ|DA_G`=#R zgKNkOdUUST-JguS8oYBp`iQ0_z8F>_*+0PwB;7jB%d6_oP7EG5bUKOwHO^51bEH`C zT}CJqsK0AU*m14*0Ttcq+o75xuM>4ti7bcK$#d2}#%ix5XrA6gsHXYEqIQy%= z5Gxv?E0w{}y0cm=?*B{uaeD62^<(0KKbLHER7QqIXFw&l(9Pj#uobZ+q~S7;Ol#7{ zHw!CW&khxe*m5qSYJ$85w^mwLWiGkzv~xpGF#hGdnLgXP ze%V~@!UZUtpK^a1#iG>GvVgQ-DTd75Ae~N<`-=wIT?0m}KfUw_Z&d)S-s-&GI1u)G z?3koO>JH^MxvWq_jcY&p^<<8Bf#;Z{zFS3RT1uUHU!h>J{isUyJ4!W;SN8hvv$(#> zjjo0WH9$l@G64i-2z->x$TZ9K8JT3L*|#N)R_i&%wSlj)GTOOWqQv+tLTcrN(zRlw zkelF0&%fIap$JZblXEU5SADtB4^7m$pTEJ3-0+?Nquux5HdDDYAd*(~s*AnFWb61d zO)1UQFG>Zv!?xjPN7D5jcU8R_A8lAXM$Fk2uIu=|I#YatefZ`9|L6Jwjc}{v2~D73 z8Z&Vu(8F@|DOC85I5IDeE?Dyn#SZ|SkZ#9nb@j)7oy`0Oxtw0hYOilw;<5&N(=s3| zeJ+Lx6NU=>1)w9EnZY#To>iNfES~m0%vvu^1@+S2I3}Rn7S0vSx?$Po*`oS+N-au3 zukR;3L=P8->K8^&?jak%02ff!gscR$i3pRN@CQo_2V%x@9waN2+1q^3vc+ebSzpKIAI_H$Yt0Y+}b$juMaPPdi;1O}o2x{F=I2uy3kt@oNe zI+BDVJCigWmVo^=eB?*#%6Jj*!9!=@2>FBm@n+3GH-gT3=RIzKrI5;22F;&IEJDn1 zT0bwF)F;HuP+V?y-@M%Nd7f8G0(>-em5?yBnZvgr@&;eIdF<2m!wdlJ{_)x-3e$2` zX~YYz0Hhw2WGnr^3}miT*gUa?!g#xf1?~>(_(u@wW;$v9-D1sihKxN*sXFMAj+(s9 zcnFGzGzL@(f((XW!K{BMPmCb;7tdH>-|fT%7Wvv=c^^jGyxRhl)FPx0s0nZ9i(pn4 zcrL*iB{n-)clAFO3v@C+{wJ8mYf2~_Htd2%ToE!DPJ?j+4`CeOu*;?9b+aGv04vg3tO(>a&G;& zpb3d21zQn+Wk0U4oovB9Ir-^ehk?lpU0%!9@OO;P*V+hYA7U1@hELZPOrar$10hI5 z)>DzW&rRY%Vw-F&*n2HmYhB8>!c`51Z2=Z*{JP${!@DCmBTnnCH+rOWhkQoaD*2%V zm$!3abB|ITw*S!&LFd3gJx+qrtaGN3BlXw(yQUG>SD9G17hYqAh6Hd|m)z#G(8v4S zjYANKoA1<0Ze3+NC2|w67{I9LNHiw?{ne73f1naBagofZ6WJ#QU=F}8$LM!##C%%o zMK%x;{3NQ%Y&1i!f|FZm=QD@)_ke9_$PZ=6{nC1u( zbCq72=A62nKTmAq&Ry3p8Z`N5opPPPi}#ReZMLXoo!63!gKhOm(LH_OEC{Zs%DH_ z-ST1sT5+UgzOyq~q&0Zc{QZH47O>s(BJ%CT{{LO59W~|t{pa!J@=3+2UB#E%``-Z= zVi+lMoNH?@boK?#G_@9W9TsEVBj)oW%d_LFTLcFVl%nq(-zNb}W=eSg2{MSyow?FP zf{=CWT}FU-)@F460Eu+BNEBc`qW2+qG48!x2s*08O!Sl{TjLp4HOqJICg z)sv9;eYZF4Nw=tI-P2u-2jr!%XYKD+itbZ@oIvWdR~QnNV4=Z1<2v$*I2_>dM;oa; zpTsm)K8xTVYQKrX#~w+US6kObP~~WCb0JrwFUW=4wiudHw`0z_fXSFa=jA6)Pkspr ziEt9GP#T#e`K+E!a5hbB$o1I{hkpCBg<6}zT$yA#jmnR}SWgD_jN9BaUUA>R07TjI zjV_EX`!h9`5&Wr`&(zdz(SD~0CZH%4)UwfFk83$&UG;q&b{B@ z(AyR4M;dP-XV+RH6`u#_1IBwV7paBrxRKJ*;iNA{9M+W z*D~FKYpe5kiP;kn4dno%pdOY(jwQW-Cz7K*1FgNA%X*PLcqK^MyLI7H8jWRPLX2zT zQe}~vhlt1v;3BSWZ?hB&^0n$faorN zXK!wF!O}b($d|R85hbbrZH`Op@unFdB*$TE_eb3BD~m6jlAQIgjv^!zGx8_tc`O)y~uV*H9n5PB3#y%J#_M@YcIU0+-S}LwP4I zF(U(jp-J7JS81-+;J&bI^Soft^FDb6g+iWw5t@q! z+J=161KE?z>&s>>S)l0=($eToz*?qf!1c7z-A`iBoQ<=$mEk^HW>PIC+USWeTB>&d zv{OIze6L@XsNR<8on5-#_|?v6Em)>OAlF?{!izcw62TWz^J5it6DI1#(hjsY+zMyi zaKt6p4G&O>DNxIAy#?SZ023IA!$lVFpPCr%!E(mArTYw42`{n8Bi-0iEmgeSqkF#y0%aha75C5-d zn>3A5q|iY>T5`gf*8Wj_K^&acn*>NoPfC-&L|cIMl&Q8YEL3&6r6LZ_YR>GoIW0zy z!K85@Dm_5!iuEW^84JLJ8xbTIaUij|f_Hkf0P)9RV6noS)b;U?yH9BXz`OnVImM>Okdj>3tMCg{$zI*3CG$al9B`Xm+nSd+P z?Tbl(1i&RGMs0_1n4zGcv=1}yl;8kxmhXBnScmmy^$Yenda39#&VyuP3}e;LMs~-C zwloFonTyjpO`uZB(2_!ZproUTOfrC^l%V@7y^bW5?+>2by$Tu%lX9T&!bPU^S_+Fg z$_d@_5nQk0CjD<=RC3B|_+#cqYFf>?Rpxdtc4=TjyglgU+!#uaQEz00#UxF*C>0P| z&bwm)g(}0b;xw`;eRCB?^qSRlgdlBwK*ETAMsx@<)VmQ_D;h5D9J=BvZ<1$v3n^D0b!4uAB#YC zyS9z>n7M8@J%xh0Hl9jtNw9P3xcKyH>xN+(4^Z~yo>YIcv`6l9aYRc$5!A;R=_lLt3|79T)|9dtqr?&1$S!g-St4oX&u?d?eg^dlK-+zmHe=Uw zp8sQMpfS&xUd{x7vkLDJ!JSG# z$T86$^cAyTvhkI*2lc)1E%&4#>o40aGOx?_{vww+hEl^%N_^jvwCIV)03bJ*rfEad z|Iw5J*c_{lMb?;UYyyn83A_yu8k(qXAl&;vg(A;ea!pSRSkx@zJkFm#WDuPdpDA{y z5CLjw*o*qN@4njpXT6{24Q7$ifC2*5g_IG<1Ll_!1j5a040cxy_7@2uLX%Z`5uv39 zhpa1=4~bb>GSNlW!S;=ui`ql`wyY2R+7Yl(7M0CIOx$3GE89NQbLik2^N%X-@`4{s z8cDJ{`*j!)Z4ThMt@n`Q0vO9!exdW!n`lyCX4AKNe;=v(Wm1^Hp>l^cN2=cqkT~@B zeOE}`h#CSzuE4s{2u;WfY7Uw;fTzRa&7?lCYXBecc3GPE?MEl z@Dg(9?3@nK$-@|0F#lJ_=IGM)cII7hgKP2qjo$oRMbv}|ouRrFSFj;^O8v*C=xt8dgR zbk#_xSRDy$6`QU{>kL^gK~-}G=rzOMMpfOU2c&&CcedLd8bErTi|(8wnO<#(YJl-% z9me-Q3;jRxQ&NcV7e0;iTmLQm@fuLmUuV}Hh~DrHB8Yh%6q}Pdf8~J5H=U@ncKT;Z;q+ZMalr>}JhgyNQCQ{iA2V#db3ghD6?_0>C>`7&DmISP z&gIvW?JTTvP%&T&({km3wS}?x!D{+?J z)I@b7$1({&Bd{lt-)8yzX;C{Z_`(+s$U`<~!}9*j=YwlPZ#51bfW>xROu&5{7&k^@ zVsfJWpKcQUz-duu3}+fGnObQ8T+SL}Ng zOYnt}w1CHN7c~%Tl>>m9CYBqz0G~$|efO=-;Bc13#7L|`eoB!_Ab;MmqrKtkyTjc) z0D{2X+~WPA!89{g9VwGy4Gt`w>t$d7Ro-3;Q{dyPC~*@0?^upspN^Hr2utO8=-I*0 ze5)xf8!Q|Vh=+@0YCeCHVWeli=$QHQ`bqLYUqySi5S@(b?dr@@w15~onUxl5jRIsY zf2Qlff4{YymGl1})qQtVQ{A?2M1{`+*cB9PAVm-qkPZqcL=cqTQIRGc0!X)l4bV_U zI-xhEgc{Hf0i_clNJ5cLh=4#s2?XBEcg8*M-Z$QPLr$I5Y~|f`y4gFZ=70Qlzo(%o6=^sFp`=4HK!;hSgY!k-CHPsg4Qlux zQ{4Qz&Wn5hZOQS-=)_E82nUWog?TnN?iuQM$?uIVQuWI3>}J*BvAXx>0&!g82Q>D* zIP+zPedOn5-A^vg;-5zZS+42H4w=o~PD;a zyfR!+;ogUK+O_TMd$20`Vto@ynMs1;Hv|s^G)kx#D6*FD0CRf(>o8d|v z8nHutIig!Jh28i>?Z~+B+V8is4wE<6NXftOm|?}V+gZM}_2QzL)~oNeukAqWMdTyr zpVyj44&A+`uTT4^R6)naDf^_Yk6qUc9FD(cxiqUzWnM&r;ZrTomwqlMyIbTUlEKcLgI9E+gK2Dz>)PU60 zYs$4+a5}^oH@02TxBsx;UAh#pbgD>h-y9z1C^U_D6qkxQ{xf)MZKo)Gbf?_w$QF7l zdQFHvGV_rGC6|aJS3)6ESv!{uqug{d~$f^XDEheG`*@_w^4S zJ{VY7h_)2#>+AO@HWrH*7#KLf3iRSb1z|BUBX4gfSJp!7H=! z1b9!_aT3%Hg}->hfw=NSA{QeqB_(wBtZHRtWpefK{7TE%g8l065s&IYVIiTe7*R7m zB#lOkmn8olWbEF(dwga6@b2fMF}SRt8V6i^2XAU{JJ&2K>X~=9&3EGJUH#yGm-a!8 zd8@;Ox80bX60KHZoCpNnQ0oT0o>MvhxJ$)IRMOc4FQ+qNE%6hvR+~OeJ6N`@1q4@f zR&`A2`TJM%<=}7@mPJPBagWMznqT$G`+IHbHy_CE>zo~{G^p4WGmOra=e<1kK+H#Z zKcT|B+@P(X^?MIv9ufA~QAyq!}J3~FLx0!Nqj)5VLn z%+M~y&$}3BonKs(^0n74;tgG;Gl6iu-hv)sHs(2&;-LJ8Lw{I~oX@k$z6T)vcJhK3 zzHXCNMEUvUsozXaRye8r;ljNFlalMlFFU>Py%I@XmLuy(OHYd>ZyG4eZWSwOhZwJ3 zH88dgsWwo`aLwu#+tkhyp1;xkO=Pzm0 zGyC}k1SW|yN$nVQXEQtHGV_x`v*9xQs`-*wB7LsgBA4#%G=IHn{w<0enCRz&tkONW z6nqka=-h)-vUVnxI?xv)g7a^FxJHe@B%tX1B^fym%`@eW=UtFWEhvkxycc&?#TDlYC>JUP<=;F zLP>j=^`)=G8aCA`SIgYS2!%2mv8}QeLSo%4e$VjB*;WMv;@A&A3#QM|h>k^MTAx)P zRL}04#PE?5JlngV8tBz<*vS6d+tYyu2o3_mWX`R=>$4oZJu5ddzshpmn~i*gqT#i1 zbOtptD4f0RHX~auzxSf0+;#+lwbnA0n!d&eV*F%N?v$Ue>1LGl^ymj}tq=DBLRlZ^ zjd2b#>WH$OxN?d5+-|6p`eiP=cBMDUQrX)=RX?KkK-buwQBR~g_~XZf zo|oyabqs7$@TPBpS$X05Tngm&y|Hy0HoZMP->}ZO-+%52Jn#buJ2>RDE3N;E+bKCHZE2M^A7h^X?rDv- z`qh&f9-5l28ogV-rJ7swnZf2YCmDKb^2%kj$gSS>iHp!gPMt#Yh@B;s3+V4sCfdbT zi;y-7)361ufg=H-k*P(4>-xr9l8pr>Q-Pa<8o{3~rxm^^{k^;J(;oQxL+^k~dE0U-`Wns7$vo7W#e&g+T0+$6X-5^=9Lok)JQ?*uS6MuUq3$?3JZLx_kOP}+n+}C_X#J6mv zo2%lrH@S(^$+ep;_56WO;hF{hP8L2$^rWT6RdDc6*Q%!zhtJoeDLr!<&|~knukjpyyK8WbS>IIf)%r`U4#thA z24&{<_)SF)YB#AQ1fKX9oxEPI8#MPv`7-a%r-Cpg|0pW&YV!*P#adcmFLup%naxWYew zL3+!5-XBzF6&)SDJG35pd+CA&y?XsR;&|V{K>p0k1CyAHw6rtuGZHP*qHACf?fvV= z>4>KE#YGPrJG-WJad`V9JzFhgM{_g&$<94d@$tD=PNmqi#N>SZcnhkrgVs(Y0+j2# z2EF=(Cz9jF%3a+!xPSH$P<7i}vwA3Vr~7|4(8 zve&;5tBG|!Dbe_k$%6x$FDE4h&fr<4-V&^3+Xkb2eRNTRj4pNTyP&u>9tj~dUeOyolsfTT%*l(W>036owz*|x22%;1+AAIK z0+z>DR=iK`n@&NO;OwBdD`+lUH$?E@0lIk< zQ09c8yZa|VsSD)a{Kt$6Ofvut&i^gUa$j~b){O{R>KaK@2~mxFjNzz#CxYh?3+#*OhNl z0_5Fy095|}MX%>onbN%XXB=ATL60E3%q#fh?&pVIs)cint*nF9NCvFBRSvpDM@8AU z#E7CXbh_Wocu6E$|JJQjb;Cus1P={A{cDKNap<7Zu;bE@8t8og-c;DQYX{5XTEHMl zDOm>$AB_Qf_r+Nv(Jd3`KFaJ6)9k4NjnUM>Y9C6pk!4qctf#gN0Xy5l*j&Qu@NtJN zPw#P#O{Q%5odwpaCkd{$_6EZ0P zYh_sMgT?XhRg2B%m3_=TJxg-)@&IjKy?Ui@Xm}0@00LG3q|JeOW!H5zNH?@)Aa$-B zWX#NS!wpnbMlNPOKxrq-Yn{Li=%k;w65$J5tB4GnS2etGZ0%pxYnA0;dTE5gmz!)y(7|s|GydP9dSVtRtoGr zPDu=2;3jE(!!P&>(=e|nP+$Lr&sWa;iwODO`#2MyyO;~^I2yssqtZP7qIJkQtvI)V zxXDRd10$oBVv)m#59j3PYoQ^{$j!|ywQl0@Rp5<|jRhc)9|{GrAQCQF+u~P#^{3M) zeUx_j`JRIEq1^a*?&tL*){Q@~1<*>*adTtMwAB7xe0+Ql9)AW2uAs+v;P$m^&oi|X znfD5CFo~CpO=A=_B!u;5ocL9Uu7IlCvK|<$Gs(O4SlzG~D08Gfv}M*i4}(!N!#cg= z>+S1v#AA?X%#$Z9kh?rrNv$}^gb%k|Fyp^j?I7tX8>gYa|N2F1mdB3*l&n^;9a9uu zPwJS(h1eR)dG`qB1N80b1kA}c?5t(j+OJh8SKEKN^ZS|gpCccgNON7r0L~X;^ZM#2 zh?F8&P8Hd~1D43a^zIFY2NcZg%hxf7`+?4uJ7hD&@!83z;vkd}(CDatu zd41Kuui(bc_C$HFHmN=hxf)AFWo4_sJ63ahyP<_eqHTLZKV$X(s6p&|%E~Us#DB1U ze@X8Y80W2x$u0&r%G+D`1cw0C(QP~+LM@0LeEz`Z3VgKo>M$Eb@w~kAsa8+zC39~H zMs*f_d8e#@U`^_+A69sqqTSyp?arg-DH)ov&9mN)ldbOuPrg@J|5ajI;g)(yLPE-} z{X~IzrS6EEaYI7`z*i(61mo<=N)t|n-;dzXioT`g-1rD63n9>EBd|yev%GhX;VMDS zO7xBgX?Wg0rzj!OO%@E_^;~85a}}Ja?&VIDmsd`D`dD0K|G}FwCkUykp^^`O-^og6 zLe)-|ZHYm2GDuZRlDik$Cq_j_Pk=VSWEK6Fry&Zu$-PP^5{brh2UHu)2Y14oKi`dF zg6wDU|JTL*$$q#;e6Ixb4@W$@EBH@d(1{$KRy7Na$NeY;s^h zujdVy1PpjW`$#a>nUWgHizdwyr0dphW{rIvWULGqjgQy6o`yg}UoNkkbxT_X6Ljn= zgd%AoGlpntdJXwb$?}{pgkobR*a;FSG=91(-D@mNS}sAjR%zq6Dl@?^7RkV1?B@n5 znbD{F(`YUz(~*#W8#M7^|5-`bAq9SZep#Np;dIQ(=Ojh{(jbQ4pzl=T%&%WPaGXiX zWk3c7kl_(^(bd(3sF?{ADzBiRjVu!`1kBsSE-?d~duB>`tFrM8SNh%h5cnt;^9!+s zb(?-b#f712*;lt&hIJXtbsuY#RPoP3bE#fe6TdxrhT1T{6M)oO@)b!bpdRgy!pUe*$#ejNDiXsfYD2OjB9|O+#};Nh}Awt zki|wPC(ol2D*WdvrH-kl7EXXH@})Vp@&tpE@k;2sg`DOjiew5;SH zQz#8XZARD7Fs8ffL>Ca!u5@)MTFdb8&p|iSg5k|&CNI&2iMZ4;yy1BRz&Dr^(C`omvfeMIK*R-45z8-h2NreqR4zLx;Z>g>LnCl<{v3-g8e*{R= zLX|hJTCEfN62cEl!(00-{R^0tFCb~mdS9x5_`R1#pV%PgTns+U%JTTxy3C$X__4tF zlZsxZ7V?|d5A~&o3e6oIMnK2AZ;$3?WL&#>^QLL>{g<%kFO@(rRNP!0gKw=qts0_% z)>dYq4j(xJAkaIawlLvc9sT}&Kd#0aluyL@m)GYcz-F}6n3yAQlN`tzf*ChAchxW_ zv(5#74%KTIW;!j&xtts>KiB!~-C3_jE;|_>2N3ki708BXC=^&aC=K2UnjNUrgA_cx z`_n!YsdsHGx-`!&22km7wkD9wZj=5kV zTuv&0-Av~O#L^)i@oUBip5Qot;AM7gt)=vEfTxN@|9x2CdEn#(C!edCu!F<{h~_Me zHOVK41j)(Emw8Ow;E_XVz~)6jgd0C1Dh`^!Qb*FmQHU<50bVH^OC37jUcScTbpeTI zI)XP#AVx|0%$aAMmAwb0_^NTVWw2*`cA%QE$E { const legendItems: Map = new Map(); const sortedCollection = getSortedDataSeriesColorsValuesMap(seriesCollection); @@ -71,7 +69,7 @@ export function computeLegend( const spec = getSpecsById(specs, seriesIdentifier.specId); const color = seriesColors.get(key) || defaultColor; const hasSingleSeries = seriesCollection.size === 1; - const label = getSeriesLabel(seriesIdentifier, hasSingleSeries, false, spec, labelSettings); + const label = getSeriesLabel(seriesIdentifier, hasSingleSeries, false, spec); const isSeriesVisible = deselectedDataSeries ? getSeriesIndex(deselectedDataSeries, seriesIdentifier) < 0 : true; if (label === '' || !spec) { diff --git a/src/chart_types/xy_chart/state/selectors/compute_legend.ts b/src/chart_types/xy_chart/state/selectors/compute_legend.ts index 97ea2508a7..759c13cb42 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_legend.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_legend.ts @@ -27,7 +27,6 @@ export const computeLegendSelector = createCachedSelector( seriesColors, axesSpecs, deselectedDataSeries, - labelSettings, ): Map => { return computeLegend( seriesDomainsAndData.seriesCollection, @@ -36,7 +35,6 @@ export const computeLegendSelector = createCachedSelector( chartTheme.colors.defaultVizColor, axesSpecs, deselectedDataSeries, - labelSettings, ); }, )(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/get_label_settings.ts b/src/chart_types/xy_chart/state/selectors/get_label_settings.ts deleted file mode 100644 index 431d7b4f86..0000000000 --- a/src/chart_types/xy_chart/state/selectors/get_label_settings.ts +++ /dev/null @@ -1,9 +0,0 @@ -import createCachedSelector from 're-reselect'; -import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { SeriesLabelSettings } from '../../../../specs'; - -export const getLabelSettingsSelector = createCachedSelector( - [getSettingsSpecSelector], - ({ seriesLabels }): SeriesLabelSettings | undefined => seriesLabels, -)(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts index 87d04cc8db..1b529e08bc 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts @@ -14,12 +14,11 @@ import { formatTooltip } from '../../tooltip/tooltip'; import { getTooltipHeaderFormatterSelector } from './get_tooltip_header_formatter'; import { isPointOnGeometry } from '../../rendering/rendering'; import { GlobalChartState } from '../../../../state/chart_state'; -import { PointerEvent, isPointerOutEvent, SeriesLabelSettings } from '../../../../specs'; +import { PointerEvent, isPointerOutEvent } from '../../../../specs'; import { isValidPointerOverEvent } from '../../../../utils/events'; import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { hasSingleSeriesSelector } from './has_single_series'; -import { getLabelSettingsSelector } from './get_label_settings'; const EMPTY_VALUES = Object.freeze({ tooltipValues: [], @@ -46,7 +45,6 @@ export const getTooltipValuesAndGeometriesSelector = createCachedSelector( getTooltipTypeSelector, getExternalPointerEventStateSelector, getTooltipHeaderFormatterSelector, - getLabelSettingsSelector, ], getTooltipAndHighlightFromXValue, )((state: GlobalChartState) => { @@ -65,7 +63,6 @@ function getTooltipAndHighlightFromXValue( tooltipType: TooltipType, externalPointerEvent: PointerEvent | null, tooltipHeaderFormatter?: TooltipValueFormatter, - labelSettings?: SeriesLabelSettings, ): TooltipAndHighlightedGeoms { if (!scales.xScale || !scales.yScales) { return EMPTY_VALUES; @@ -134,7 +131,6 @@ function getTooltipAndHighlightFromXValue( isHighlighted, hasSingleSeries, yAxisFormatSpec, - labelSettings, ); // format only one time the x value @@ -142,7 +138,7 @@ function getTooltipAndHighlightFromXValue( // if we have a tooltipHeaderFormatter, then don't pass in the xAxis as the user will define a formatter const xAxisFormatSpec = [0, 180].includes(chartRotation) ? xAxis : yAxis; const formatterAxis = tooltipHeaderFormatter ? undefined : xAxisFormatSpec; - xValueInfo = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis, labelSettings); + xValueInfo = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis); return [xValueInfo, ...acc, formattedTooltip]; } diff --git a/src/chart_types/xy_chart/tooltip/tooltip.ts b/src/chart_types/xy_chart/tooltip/tooltip.ts index 1550e52093..b8b1d3f285 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.ts @@ -10,7 +10,6 @@ import { import { IndexedGeometry, BandedAccessorType } from '../../../utils/geometry'; import { getAccessorFormatLabel } from '../../../utils/accessor'; import { getSeriesKey, getSeriesLabel } from '../utils/series'; -import { SeriesLabelSettings } from '../../../specs'; export interface TooltipLegendValue { y0: any; @@ -53,10 +52,9 @@ export function formatTooltip( isHighlighted: boolean, hasSingleSeries: boolean, axisSpec?: AxisSpec, - labelSettings?: SeriesLabelSettings, ): TooltipValue { const seriesKey = getSeriesKey(seriesIdentifier); - let displayName = getSeriesLabel(seriesIdentifier, hasSingleSeries, true, spec, labelSettings); + let displayName = getSeriesLabel(seriesIdentifier, hasSingleSeries, true, spec); if (isBandedSpec(spec.y0Accessors) && (isAreaSeriesSpec(spec) || isBarSeriesSpec(spec))) { const { y0AccessorFormat = Y0_ACCESSOR_POSTFIX, y1AccessorFormat = Y1_ACCESSOR_POSTFIX } = spec; diff --git a/src/chart_types/xy_chart/utils/series.test.ts b/src/chart_types/xy_chart/utils/series.test.ts index 9b1f653e18..6746c6dde5 100644 --- a/src/chart_types/xy_chart/utils/series.test.ts +++ b/src/chart_types/xy_chart/utils/series.test.ts @@ -12,7 +12,7 @@ import { cleanDatum, getSeriesLabel, } from './series'; -import { BasicSeriesSpec, LineSeriesSpec, SeriesTypes } from './specs'; +import { BasicSeriesSpec, LineSeriesSpec, SeriesTypes, AreaSeriesSpec } from './specs'; import { formatStackedDataSeriesValues } from './stacked_series_utils'; import * as TestDataset from '../../../utils/data_samples/test_dataset'; import { ChartTypes } from '../..'; @@ -682,6 +682,17 @@ describe('Series', () => { expect(actual).toBe('a - y'); }); + it('should not show y value with single yAccessor', () => { + const specSingleY: AreaSeriesSpec = { + ...spec, + yAccessors: ['y'], + }; + const [identifier] = MockSeriesIdentifier.fromSpecs([spec]); + const actual = getSeriesLabel(identifier, false, false, specSingleY); + + expect(actual).toBe('a'); + }); + describe('Custom labeling', () => { it('should replace full label', () => { const label = 'My custom new label'; @@ -691,9 +702,22 @@ describe('Series', () => { customSeriesLabel: ({ yAccessor, splitAccessors }) => yAccessor === identifier.yAccessor && splitAccessors.get('g') === 'a' ? label : null, }); + expect(actual).toBe(label); }); + it('should have access to all accessors with single y', () => { + const specSingleY: AreaSeriesSpec = { + ...spec, + yAccessors: ['y'], + customSeriesLabel: ({ seriesKeys }) => seriesKeys.join(' - '), + }; + const [identifier] = MockSeriesIdentifier.fromSpecs([spec]); + const actual = getSeriesLabel(identifier, false, false, specSingleY); + + expect(actual).toBe('a - y'); + }); + it('should replace full label with customSubSeriesLabel defined', () => { const label = 'My custom new label'; const [identifier] = indentifiers; @@ -701,7 +725,7 @@ describe('Series', () => { ...spec, customSeriesLabel: ({ yAccessor, splitAccessors }) => yAccessor === identifier.yAccessor && splitAccessors.get('g') === 'a' ? label : null, - customSubSeriesLabel: new Map([['a', 'Apple']]), + customSubSeriesLabel: { a: 'Apple' }, }); expect(actual).toBe(label); }); @@ -729,7 +753,7 @@ describe('Series', () => { const [identifier] = indentifiers; const actual = getSeriesLabel(identifier, false, false, { ...spec, - customSubSeriesLabel: new Map([['y', 'Yuuuup']]), + customSubSeriesLabel: { y: 'Yuuuup' }, }); expect(actual).toBe('a - Yuuuup'); }); @@ -738,10 +762,27 @@ describe('Series', () => { const [identifier] = indentifiers; const actual = getSeriesLabel(identifier, false, false, { ...spec, - customSubSeriesLabel: new Map([['a', 'Apple']]), + customSubSeriesLabel: { a: 'Apple' }, }); expect(actual).toBe('Apple - y'); }); + + it('should replace yAccessor sub label with map for single yAccessor', () => { + const specSingleY: AreaSeriesSpec = { + ...spec, + yAccessors: ['y'], + customSubSeriesLabel: [ + { + y: 'Yuuuup', + }, + true, + ], + }; + const [identifier] = MockSeriesIdentifier.fromSpecs([spec]); + const actual = getSeriesLabel(identifier, false, false, specSingleY); + + expect(actual).toBe('a - Yuuuup'); + }); }); }); }); diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index d613f5021e..75b25a023d 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -8,7 +8,6 @@ import { formatStackedDataSeriesValues } from './stacked_series_utils'; import { ScaleType } from '../../../utils/scales/scales'; import { LastValues } from '../state/utils'; import { Datum } from '../../../utils/domain'; -import { SeriesLabelSettings } from '../../../specs'; export interface FilledValues { /** the x value */ @@ -393,7 +392,8 @@ const getCustomSubSeriesName = (customLabelAccessor: SubSeriesLabelAccessor, isT if (typeof customLabelAccessor === 'function') { label = customLabelAccessor(accessorLabel, accessorKey, isTooltip) ?? accessorLabel; } else { - label = customLabelAccessor.get(accessorLabel) ?? accessorLabel; + const map = Array.isArray(customLabelAccessor) ? customLabelAccessor[0] : customLabelAccessor; + label = map[accessorLabel] ?? accessorLabel; } return label; @@ -403,21 +403,22 @@ const getSeriesLabelKeys = ( spec: BasicSeriesSpec, seriesIdentifier: SeriesIdentifier, isTooltip: boolean, - showSimpleLabel?: boolean, ): (string | number)[] => { const isMultipleY = spec.yAccessors.length > 1; + const alwaysShowYLabel = Array.isArray(spec.customSubSeriesLabel) ? spec.customSubSeriesLabel[1] : false; + const showFullLabel = isMultipleY || alwaysShowYLabel; if (spec.customSubSeriesLabel) { const { yAccessor, splitAccessors } = seriesIdentifier; const fullKeyPairs: [string | number | null, string | number][] = [...splitAccessors.entries(), [null, yAccessor]]; const labelKeys = fullKeyPairs.map(getCustomSubSeriesName(spec.customSubSeriesLabel, isTooltip)); - return isMultipleY || !showSimpleLabel ? labelKeys : labelKeys.slice(0, -1); + return showFullLabel ? labelKeys : labelKeys.slice(0, -1); } const { seriesKeys } = seriesIdentifier; - return isMultipleY || !showSimpleLabel ? seriesKeys : seriesKeys.slice(0, -1); + return showFullLabel ? seriesKeys : seriesKeys.slice(0, -1); }; /** @@ -428,7 +429,6 @@ export function getSeriesLabel( hasSingleSeries: boolean, isTooltip: boolean, spec?: BasicSeriesSpec, - labelSettings?: SeriesLabelSettings, ): string { if (spec && spec.customSeriesLabel) { const customLabel = spec.customSeriesLabel(seriesIdentifier, isTooltip); @@ -439,10 +439,8 @@ export function getSeriesLabel( } let label = ''; - const labelKeys = spec - ? getSeriesLabelKeys(spec, seriesIdentifier, isTooltip, labelSettings?.full !== true) - : seriesIdentifier.seriesKeys; - const delimiter = labelSettings?.delimiter ?? ' - '; + const delimiter = ' - '; + const labelKeys = spec ? getSeriesLabelKeys(spec, seriesIdentifier, isTooltip) : seriesIdentifier.seriesKeys; // there is one series, the is only one yAccessor, the first part is not null if (hasSingleSeries || labelKeys.length === 0 || labelKeys[0] == null) { diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 5d239902cf..2b86250729 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -61,8 +61,9 @@ export type SubSeriesLabelPredicate = ( accessorKey: string | number | null, isTooltip: boolean, ) => SubSeriesLabel; -export type SubSeriesLabelMap = Map; -export type SubSeriesLabelAccessor = SubSeriesLabelPredicate | SubSeriesLabelMap; +export type SubSeriesLabelMap = Record; +// When using map the y label will be dropped when there is a single y, this is to bypass that. +export type SubSeriesLabelAccessor = SubSeriesLabelPredicate | SubSeriesLabelMap | [SubSeriesLabelMap, boolean]; /** * The fit function type diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index dc949ab5ef..72d2c615a7 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -63,17 +63,6 @@ interface TooltipProps { unit?: string; } -export interface SeriesLabelSettings { - /** - * Displays full series label even with single `yAccessor` - */ - full?: boolean; - /** - * Delimiter used to join subSeries labels - */ - delimiter?: string; -} - export interface SettingsSpec extends Spec { /** * Partial theme to be merged with base @@ -120,7 +109,6 @@ export interface SettingsSpec extends Spec { onRenderChange?: RenderChangeListener; xDomain?: Domain | DomainRange; resizeDebounce?: number; - seriesLabels?: SeriesLabelSettings; } export type DefaultSettingsProps = diff --git a/stories/styling.tsx b/stories/styling.tsx index 024a7b3d89..37e5d8ec43 100644 --- a/stories/styling.tsx +++ b/stories/styling.tsx @@ -1039,36 +1039,6 @@ addCustomFullAndSubSeriesLabel.story = { name: 'Add custom sub-series label formatting [time/date and percent]', }; -export const seriesLabelSettings = () => { - const dg = new SeededDataGenerator(); - const delimiter = text('Delimiter', ' ~~ '); - const full = boolean('Show full label series', false); - const showMultiple = boolean('Show multiple yAccessors', false); - const data = dg.generateGroupedSeries(10, 2).map((item) => ({ - ...item, - y1: item.y + 10, - })); - - return ( - - - Number(d).toFixed(2)} position={Position.Left} title={'y1'} /> - - - - ); -}; -seriesLabelSettings.story = { - name: 'Series label settings', -}; - export const tickLabelPaddingBothPropAndTheme = () => { const theme: PartialTheme = { axes: { From 88cecd1ee90c6f88689a1142877eb0f2d0758252 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Mon, 10 Feb 2020 14:03:54 -0600 Subject: [PATCH 04/15] chore: fix type/lint errors --- src/chart_types/xy_chart/state/selectors/compute_legend.ts | 2 -- stories/styling.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/chart_types/xy_chart/state/selectors/compute_legend.ts b/src/chart_types/xy_chart/state/selectors/compute_legend.ts index 759c13cb42..b91f665dff 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_legend.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_legend.ts @@ -6,7 +6,6 @@ import { getSeriesColorsSelector } from './get_series_color_map'; import { computeLegend, LegendItem } from '../../legend/legend'; import { GlobalChartState } from '../../../../state/chart_state'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { getLabelSettingsSelector } from './get_label_settings'; const getDeselectedSeriesSelector = (state: GlobalChartState) => state.interactions.deselectedDataSeries; @@ -18,7 +17,6 @@ export const computeLegendSelector = createCachedSelector( getSeriesColorsSelector, getAxisSpecsSelector, getDeselectedSeriesSelector, - getLabelSettingsSelector, ], ( seriesSpecs, diff --git a/stories/styling.tsx b/stories/styling.tsx index 37e5d8ec43..8bc68026fb 100644 --- a/stories/styling.tsx +++ b/stories/styling.tsx @@ -1,4 +1,4 @@ -import { boolean, color, number, select, text } from '@storybook/addon-knobs'; +import { boolean, color, number, select } from '@storybook/addon-knobs'; import React from 'react'; import { switchTheme } from '../.storybook/theme_service'; From 6e9ee730015843610b102fff2f20128b82f6bf3e Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Tue, 11 Feb 2020 15:27:50 -0600 Subject: [PATCH 05/15] refactor: update custom series label api --- ...rmatting-visually-looks-correct-1-snap.png | Bin 0 -> 33467 bytes ...mappings-visually-looks-correct-1-snap.png | Bin 0 -> 21055 bytes src/chart_types/xy_chart/utils/series.test.ts | 159 ++++++++++++++---- src/chart_types/xy_chart/utils/series.ts | 74 ++++---- src/chart_types/xy_chart/utils/specs.ts | 76 ++++++--- stories/styling.tsx | 118 ++++++++----- 6 files changed, 290 insertions(+), 137 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-formatting-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-custom-series-label-mappings-visually-looks-correct-1-snap.png diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-formatting-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-formatting-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..637f5eeb0721f6ddc52a5b95ae54fb0d53babe91 GIT binary patch literal 33467 zcmb@ubwE^Izb`(DiUMNLt9{*;6-qR zAh}e{?*kvYiAO;Hyy-BQVgvc!ha&3)QiJ}#U+!m*NJszua;hnX6yBeg`&0_nA*2=N zLsU#UbstkxpW4{iKzC-7q@(C=JjB6i9Y~X4Wnua1xYo;IGhL;A9fZeR;(6gbY{3bK z!%xr7ZhTdwxr>heGFxSFeO>bU3c2;Xj!h zFK_;UO;rX42I>!gf90|J6ZP_RDeSvS&NEL=!uy()HpcTm-UX8hC!^yr*`4hhLyos6 zd~0j@v8d%+Mn|L9*Vj$GtpB<;>B5LZlb-lj*-DgjuX9?um>YvdQX%ux)sEn7@eK_P z^YZe(Ym`#myLV6QJToIh-@xE~S{nI>4Ir2rC5PGD6Hoa-u4<{tT#R zx|-Cv=h@j=qL63&`Oy~2R~5SAva;b(O-;=-6}q6qS2Wn_8XD#)ni?9ZDs;cPx=?*!mq5jz z8|R(nB6SX*sb%HhXc-$*(=;(R*DyNLOjT6Ol23YuxM~n7P^2jWr%>>9ZFSYa%1X0t^4Oisu-eHn0n}6cz7q%n&DSk}!6mbRGXHWg!3e-=+9}^J# zI@%iB7|F%EbLY-KlkgZeCr=g*do{^EN znj07tl=dpE>AH%`tluY+TH)qHBBETzu_*AJ*$;Rb#eWZ(0IgeTgpQ)(59Bqlvm3&v zt07gjwL{g8rdHP0d%L@O1K-5(2?+-?WbkrSvTy!a?MWcucV@uF#Xa1fhy>@Arj%_q zTgS`9!~_Aes#w4I&a-FF>>L~hNqcDi%q1bV{x7DH6*N@z^fDE-;A@j3U`&frud-GA zm^9i3(x|JPF1R=?NA+5YewNwJRY=xOq%fzy@O9&P?PAn3dH?e2;&u-H>(rzyR73IG zhWh%L&rMWuOV=3Y?Nl(Lqj51@y`KoWLq|f{4M#n^LipvRrRhpd<$sQ~Yih>C#r=9O zCKLGS(~YkZCzbfD9(Lg~cWd}ut{&W}7yc$WvHt1>#`;+9Fb@_pC9C18$Ns*`ty`x< zS!xi7MS1+wcjR$`j%dBT^2VW3x=DiW$!F*d^ZP4Cka#ZZmeXRgSEZSbfiM~OF!E<% zMQ?vO*v!_5Ii8rvCh*vp042l`hwA+rhxW`Le87;>c z))bAfH7l;vdo^yLqPmBQ!Uu%TkKw7xiCt=WRCeo68ZMk^=L#?01qZuBE{n{Gl9NgM zUcbjw>?t6`q7F7_ueg6Xm&sXGx6d{tBIIp!(cYsiZ9%9kl+fDoAfR|{Eg!5S^Y(~8 zrHwgwH#ZQ2zb?p-TS+koEzDfVncUwC6)H2_BP&NB3@0b#`Yth&!*#fRg&P`re0kM= z+d2XL9v*8YOZ(5+P#>Ess9PyHCg$<5gR8%nr<)zs6|v&mW$5XwF9s3cT_PgO_*(AP zEJIZ1E8+r@T!kHYnbn5;N@uGJjfA+^0h}LETv5?9kZNa9Wxmx9!a0YLDjjY-!kwokM090asVLI?tMj&!4|D$|yAY znz{>vWuXbTzotcX=Le@AP+pERk~6WWKJ#ulm+~6^h0A5Z{lQ?B{ma?ipFcv2oQq&vqjsFHHw!m6r_CN7C*qH{7SWreWgDuYeA z_65~O&x;2pdw5;HIy*;c zdnd~+?a#Y4zI0*~WN5b!{Cw9>*>x8at)=6;bhW_^7Pg%Cg<;&aC);Du{yY4V38Rd~ z#l=h~RSNG#vReur73%5`8g)jzI}ZpI4QdBhnOIo^-JD2=d3nYSjm@PD3?lw)NN??l z8^g~#Y7Sg%qRjK^1aKm=debFNn7}RConev)C2cN+<+e_e3A!cj)}!7d6uRZJ-54<+ z&}iB^JuRC+9Md*5k@DbR)VF%5BFHzwdAtn-OTa;ZfXVG zYmU@6oSKuBwq0GEmi;9e{3)`8&F_4}m)F)}2(x_aNm9|*iY!*}%B~eO8?=t^8;YZr z?k8}*t?-QM7jslUACZRX9F??;RJ}rxm6bi^gmXtk6nsuh6uG!AV;qHRu(PoI7_Fdl znzmp!>xZ-{Tm~m5CK|w`6Zo;Pu-Mpxyv$AX>$9(jfBr7Ydu8<&JBJ-3hab2`poA~; zRS!14#F=prn)Jlx&D7q1=(vqHS!VMpodG^QbR+*3md|a58d6e z*l3i@^@mE~IfJXcBP0If8&j0gnk zv%6DO{;4l#8OQ1pdF&&Eud063yT@tOy$!sL-nh&e#)VJtdVp4*X4;#?mFJDSRhqJs z#AB>T#cY-I^mKh&TeX65K9~I!P@`*)x0@RsTWy_q->$RRXUV52(p7s<#_=BD?m6@j z^6-qK_@~5M&6c3hAOw#OHhyfdukjE&eaXwSQuU~hAI* zpvzG$zoB;?Tzy$*X&YW)Vqzjne0q;Y;HCTM_H9v;z@O98!9Skv;PSq7UoQ$fg+V>S zuVh5l29L|RL`g$UX1!xRCwrmd#l(O@kUp*XqGD(KSA4ntbd@VQKmh`}?or`nn9Y-u z(pfw=+Co#Brcm65zPYkPm~^E~+&Z*W=x~x|4;NrJ>br1=p7#eBN;SlHhD|khI$>T$ zmRX+YsCrkv8!oeqjf+E0L~>!`OhGEgo=KKaQ8L4kPG+CMHMpWEc2Eq%+9+&FtXC?A z0&(v*?mmp6*jUu4x~Al|Y}C{lNEL7~>5dQ6=F}9wbQ*#MJ^u|*9*PJ&CA-z2HtrEPokrv#X9>^g!{mIkx(y9B5d_=V<+N)9CVa zPpiuAT~`bGaG@9FP68ii!$R2?^m+%g4Vz_gmf}os^5Sb6Z}(?w6E@dqs|J4m^{3;>lRA|j&9y}K`oUt*@l z=^yAFR0IVrEn@8&=#8&0eD5R*xJFc1Pdz3gl1URczDP+-O481@E-5K7iqg58TB@mr zb-E<_HtxKNWA6(9V1g{(KObX~2}z$P)sDX|%>eMIPVlQsI9I8#uB7jsBfA`UM|?Ta zDdGfOg*8WnU*SPpFj`wv6R}Ts9)n4r#*c;X_zaAU`1@DS-Hz9DNKD)E&MpaE;=g=R zG3iSLV4|{uiJd+2c2M+V92}JAVQ^bkgHt^KusS{D)_Sjhem#-ErX>=j`*;K47DZ^f zm_cFEov5Ib{K*I6eSU}rd7iJzPGLuZgy<6!+1;!grBjtd8bxNb0}^5SW_lmP6i}(W z*(zEpGV0qOg-fUdZ~`FOw`zCxOt~bVByCQF^dLDod0=QL6*;+YYY?7tp^l*2(Wcne zXnr-A1L(L6kRLNMBzSmu5H8!a9l_a&_upp!Q-HUD*jWW?_F0zn?#YL9n0}$7NHKl(jHajDj);52}tMh$*CC zw`o$b7%T8^&&@BhHZYK9fp*lkv?PAjRPz?eP(Lg_u`K9&zk0uZnqa{h5}|kBF=$>Y~Ao5Dz)K1t`QJ zd?a7%U~yirZ?tF5GDWT6@`;>>*59c8O*u5nH01?!f9s>aKd-RW7fkz^BB_Kq+-ftEbcyUS2WXbA;nBr z7dkU@p}`7r&g(XMQ69$$dDa}8a&vRHg^}szg_M_b{`~nOiaA4wc$#>Gy|KoG0t$?~waHxvYjLn`toP(kvYA2cue{d7 z^)5~X+j-Jro^~y6R$ro6cK7_7Nhe|w;=5oz&3}C!9Gvro9v4EMMt6Gtw$#1P%Ua{K z!MDtOrljdH)Q8=)?^R_1kzt!cTh~;I%N&vXboLNs1*0__0FLi!YcG;~eFascqrqnJ z2mgM=lh|&3FENS;KVPqDbhqt?j20r|HQ1?!3T7;~>y=bbc1>N$syqx9)2 z`^cA%UO~M|$=zLS*YcT?@cF&a&@xKYrvQK9=O{e~L$p8&pM^ldYCWo2UG?Y*Nt zDlkLt>nGgk=%|uV8pMg`GS^D?-2TOr*%RBljwS(vdgK0fXtFs0Xe>Cng{nQ#$FQk=0M-B} z?WK-vvzynW0FqQj+lHR4#Cj8N-kY=Au)hig&#^uQNdLTdMXPq$Oj1e;<;m(eGng(SwQ2-U z{yl)tY-w)(qN7Y|axGWYTOsYkk&G2lgQaC0Q#Q-Mn4;oWTIIy8QQM}mEELRRXD!(} z(K(?RR(8$|@%z+DN=n(fjW%&h`zI&HCve30r0UB3onuOPf_L=vv=*-m+$Cuethj;# zrk1v=9BXexn;f<@CHS@EZ|=;m6Q+}b;Z}NB-USMy*LYdTmthWY4C(`t)wP)mvzeM* zv-S0~_FbzBJ>m1XQ^(*0mZ z`@;7$BV*&z#jTMtn-O9lKVWb;RGnbiUnfG@*-JlRirw8~-n}TES>dCu*9e%+;KpuE z{4Yu(8E?#$D|BA=#?~|RFul4M<*FLe!%Q0q78BDomc2;xM8Ok&US3`v-q%@bVFib< zA(RmDm(9K2;~M+Mj}8~qBMLM3myU}Z zl2ESG5s!)5z41I=SiB)DDA&U0UwsM7qCkE$#W{Yis@IJ#LM{Q%z9Df#rOg}@AVL%y z3Lk-r@v-DH1!4kdR08M#447WMXI8(M+5d#OQjJs&w{s@g77G(>bZ?dXrP|#)d$L>q z33H)3GTu4FH{jd)WVxeQz`pFSkhI_ER^fS?_uLQ33Dy( zFC|Q5J>wA>hDi&mFFju{+S4iRKa%J_SM>jZ^8cSK0y;G)S5ssH@{kXFQgWkT^ft2@4M& zV6W%;?LRz)W~fQkg6J_9o|Nud0H3=2b@zdSnthQLi9fY8mm{&Gts+@jKj zJM%RwS=iVXw+b7nI5@t1{P@wp8XN7ls;cVb8u*JQN*d4r2q>bX(e&7#OtGqv=a8%k z*s$5!5f!;`vSOai@ftdEakhuHvk91S6uP_oTI=u#OCX)leB)R zN>*WWQQX19Zqh^jb?rIW5K3L=VuQl#1ZQ#irR93*f5-PX*qjN_rW{HS_Fk(?{kV6CN(;0g7!~P}fN!NO*>vu1C zJxhOK3v`^b1_msa8Sh6wryC_O#B#Ym2S zbu};0lO6;9_mq>fTs`mPzPy6P2l60l6skyMN zk~CX?^K?$3wv&(Y4fr0wY0b%^=m<8kO)V@4%eA$^%xxPONKUAff}Pu_FD;WjdTGBws%prc9*mk2t42m6$=XzDjIaO zxdDEKe)4@hii-94aaX)oS0697CXOLjd`&4+3zK>=7Ht<1BTlc(l9BDAq7he*oG ze$z=pbzGR&3yaDy4&2c+cRZ;o20}(yaIjHtBCnB=QPJByg9?^3 zzI*?EhI|qqSZt_1Jqg^ANl76<@DOxAHbR^qbtiC#0iAwnaq*^4N(!01y?u8ad%#qs z?NEVsEn2NOa%@x*-NU^FN|-Jxjgs2bk6ZihU;Xn4%w zA-S-Hh`N;!A8#=gn~-20xit9J&8^6$nF?tFfF(AXFZ@k4_g7!vSoU@EHY|tL<(Q-4 z(m)8S%~RCMiWmq1-<1==xB$;1g>{LF-A(1;-rkyGF8X)Pv5njBvJe zC#MB%80z`n5BwpNkGCev%wHyOS$~QG!ix4qX+i?gQhONlXd6&N=I0}>Cy_AMS zl&Sk7D!TlrX|#~8ScrUyR7y(CY%<+Ge1AOBUN(U>#vk1TSZ?ePqzJ!IZ+p^6v^rZa zD|=1H+%rD#lK}(qNeD5!M2UsPcjmO*J5&1~7=3UEJ*-H_vUE-B?<~OH$g?*UP-Z3F z{CGwnb={s{iRU~u8pQu_E0n@$GF@p23f41pf%xlgX4lVmD+zNS-Z8{>OU zcj^SO!_+kO_4Rc!s|dL*e1MA8*oF`XEFmIN+3Y%{g0H7fI0`Hl>d2Qx6%{7i>*13Q zf3$e2W&Z>H^OL92Nww`E$PE>__p}Td!C&u$=Wb-enPVn}ncy5_w$tgBCr$OE6B85P zRnF>g`cpuCrT81Xrx5*{;C;;CpMm#WdBa(?*mX-9n*J}ey-r))3tK}s0KjCT()nha z0*{T0S4#VUMN6CAYEw2s$>i;=>%21?mT&!fcGo7XcyDOVAWt>-4B9CRpfe!pO#5Cn zH8l^3E^?30j)=Hl(L_=9O8i+k7JrmaaDl>`1DcdM3J++?z45$-uPUuk^tT$GTFr1D z?o36R^bnSsI4uM+0vTxGtiLDSbg_>W$Sj%iqzX%I_kZ>DkTVzVidJ<-(J!rS^>EL5 zg@I)b(Ej!u*$eP-*53a=YV`3yySm|j&%(gqG1!2=eBs!J!8&m?*W??BKLs0%C3~K_ zc%5r%4{zSglurm@Bljg35*Ev6dcw{9puEOz7U98Xcizp5DM3O>PoIFJTbSvTyUKIv ze^oAD9qJ?S^M>m+h<(qt?RTR7_aFFj)hv@f)D)b+7B0^yoZat321U zv1tSL?7(z=b_ML?eDS4o&8`f7mVdYLn+~mLzupX(r8N?6{{yR=oSR0HR!8W>1BPYd z-SC-++g9J$IB?CfkZr$NfAV~A@s@g(87-MNM_3rPEiaGd8OIjxOF zN_c<+4i~BgEazso>KvF?c`bc#e&87pYHQvl5r$^)yitdTpP!X|Ss9n-izwW!jECvgiV~<2;WQ9u?&;o;CpIek}5_{R}Z~bH(#W$E`U4 z=$+|+^eE7NTbnwgQtk*;pj9TTtY0eHrJXX@TWobc-gaGOK7C(4$5;rodwzh<+6&?t z$**cQj>Vq*g~gLUKe#!UyZ$hlA7DS8w=|y$wE5MbG<{#JTpl5-(%YMaoEC~mCm`Cp zKE5bK^R0J3Brz#R4a(E^m_q9juPNJo_z}(-27wwmM2gc_r5oWpxfqBt>3*`S$xXX!!q!kNtN8M2DM+ zhu^=`7#J8xDkzw44rc=o1t+_&qeE|@$-nM+(h{UNaJIbn7R4%T=R)7)x6RCGPl=~0 zKE%cSpp@M*Fc5x)xG*#_I+QJt`XjHG4CY2b)zs8(qM(4V4MVZlT5r-YfZr{AA>z%W z`I-bgc27XLUu*J5*J|(}@Bv%mJRIKJ5{Ok?UQWr!cb={7`x|}h{!^@5>s{(9naPui+6XU&&F9c-=o1$RK7dcIZiM7SCS_<6|L>&aG zO#KBWZgy99Hw8EzfbjkSZXe^WMzOSkFIzRyg{RS@q_eH0Ks)-K0NI8Z)Vnj%=|Q|E z(;BblEy0Ehe19l3q&u0{pHjB~xouc`BHWx}&o_8<3N+Hx8G=M&?2)Ftl}^_rXBRIv zL2GorFs@}*kp8vK^-hA~F zk3bg2qPk9`QAm&n%NvuX+qUV1)M&i_1@Q2Prt4{iNcf8m*6d*Fo^CE##xgE+YD^6; zdD-aVUFJ5zKnkQ+eq>{Y-sc)omaBCD6lBWakdQJEMnHX=8)ENp5cJyWN-CqKG^f$y9LCLkzn<-OXi zF>O(M^D_`T5)+9xN1TR6$8xvCK?1~vSw}=V^7H4@FvlbiG?G$K$ZFn#IejX1hP_D? za>UJG^R+Y|vxnL}0c7yK?@d;=93?mp(5dIQmU=*dquhAm0&_6aN#?ie4i|nsc!`UR zZMQMFVz83YqYXCMqfLSDj&?=uor4iAEfk0v`@>fek&(Qv`xF-^JJi%!yM;Qy^5KK7 zf3-gNH#|5%+pZ4oZnliG9c3R%JJNCP0uN4bZt+;M%gT_sm^%pqA>s9RiC-}HUm0N^ck z+6wzVVgVGqY^99LUwget3s8?GCs|pYLE2s*)35fT8y{T5^bWs2SzPK%RmD>TvOE@* z+!9>-HpnH-5A7YXwGV>qN?>H>BVrEhmoHyB!)DuSQ7)n`CX@Y~VFIV;TRZE66A#?o z`GMb!>618}XkaaqP`kZt)!m&KbV!@mckV*5;fwlU^osb=qZh#9;CdnK0w;Fb8qENP z7*GzwfxlW`F9`SsbYt+g&z<{(cKgdxlNHwBRxpGRa?%ZLO-)QhO(L^-svAH+`4V>? zX10%6K%<;Or{ha0<&D<4f4v8+N})L7_rU)#ln5J+HZ__ir`P=Wk-i9GqVOpjL**(>z}GSkTvICXJjBk-55Gu?Oxe( zYX*qtgF`!zqQzV>pqA?_#sVpFTNwWZkjW=Y1{VPWAF5ExR>8m|{`LNhUE{2W5NHt6 z?V*M2^RU#nb(3i5=m%`A|0xucGcg(QCloVc?v)%}c{#V-7Lwq>Vet$|tcP1uzAqn1 z6jd@OGXn?))Fj{t<7_Ca+vey>xfvNz07dFAatFAsM4?VS>69rj3xi4canaGy-Hx|^ z0VN9vlN`3Q#7KAXpL&SN-aYj?uFDo~+Ab1FZHl=g9(=}B0J8x{MLB!Wccz)O-78gY z3sTn{^T>3m7Dq zOfOLz(XksEGl9GoV^<|*?zpzODPpxh-j+H;^yWzU8$Ow7(5-FFkOPTh@)?0 zQL_Jq%C-Nda`V(!>A$N@`FW@tEL`fme}hDt!8`Vi_K#un(pM%%MS8Qc05lJ83ec$) z$Yw}?5G51*U^<0*G0+FnHq(CD9$dyZfw)KkfHVpU3Me6p)egp&9`$FdQ#)rKqo!I7 z5#3jaM}AChMW6DOvH^SfI$(yh61lm<=w8zq0&NhOIvDf>2FAv}KxA2Zyp7SL+UQC+ z9+0OGOLG%=;A5@hnCwLsuC_Xw2l8~vv1w!#v4A(G(H!^XOW9+!c&7oN*dyjh-)Cpj zvS)fLT?M&|fDn?JvXX1H$$g-CnSkA_?s|4=5^(|my<{CUPN@E-bP~UC3TEDwjbK1p z=wObq<39p9Szz$~aJ*%lexK^8i(+$o4DaL`@yus2t2%`&P&d>!bvn7jS75mH=C1BS z_@Ev#nnHk;BQB1YPxsV?NO`q18vwKcxC59QuQh?DBHr>Y1%l7c7YpS0{bns;aL0u#Za6fl?c9tZ;6A ztZFWn%~h+o=6}NXlw_sagn%V(j^)1G9~m|%)Of^JS`sT{2!hD8(_c~ z5+0YXAdHiyPzagdb6nfrZt4E4paG}@kmNIgl`oQsbM@5VYk0`8LJLB0!}~f~Wp8`t z(iP&$EgzMwFoGGxYVUgZC`ClType3Ghm5*O#`g4K3{^76R{sU9*r5mioDmxD9&HA< z$H%Ezd-c;VFw)HZjtg_0>f>}GR9VLQ)VG7}e)?uP){8uX@^6mdBOXGi0=?Upw4z%`WwnGm zOU3~)&Dz8v>1C(yeQ@L%tX`qLAjIZV< zeFFqX9@Zf3fW2v@6v=moiEKx#hiwdqN1dXnqbcYFd#o4U~r?K-!eqSS|LdFF&JM6 z9|gsiO?PM?XRgGp=Yzq@#R>dJ=iq*^J(Z5ML0k735Si}Y-OU112m;y`Ufz^IhMj>y z&`GQ;Hd$p(58Q?YgGI`k`jI#Vs*r)v|1O_x2!wG7QBwWAh?VI&r=V4MPb`^L1K=t16NO`%-}}8IS>?gc8vIO!+E_-W$%nXyetglhC z_T8LU5CBJjrNk7f%gy({D6`m*?-TM}Umkmej_wFN(fbMtVcy#BG3WJc+fkcmP3Nv& zwr=K3yf$3)dj*2@q8UoXHV7&JwtF{U%g7LK{p%=*ARXPO*jW6*p`kcFCpr@o|LYP8 z3X0u>KHGQi$-;BB028gTt7KqBB(!)E{{SeN? zBo6r0YsneXRE91uDWV=mGO!B~Z~lE@t>30BPL=QZ;323INn0AT#neI=4LB zm?81$j0(!K3V$`w9nX3jxQcoC`Rji}b1AnB>8S528$T-ut7h)Dxj{WO=`O<&H%LOETnomg5<+h>~IA4tU|C`xzFUpPH-rn)?acp|; zvy~Vi2#j1MCM3v!0Ao0pxw$z=LCmkOBiox8-;_NO5+d~h>A4}06NJ1ex5!{bcI~vF zuZpjxZNZJU1iIaQ?-Y5##=sPabB5_O4-czox-26jzYTBl`UcY4k@M&4!b?a;FP%qy zz(ssQLU>e$CNXGs$eSv<2WZUJm~GK-T)%0+tE2&&CXjFE)@g59ru?@eE7b-Y|B+aM z6j`1Kflx1kI_5-F{f+v=3OG>%VpZn+XyZjvz++fI6qgGSc7ghW3lL~Z27m}mz&`K~ z3X&-f^ZXZ9C0-E{N+cum(;yDKw6!D$`UQxZxKKWa+lQs6({or4R!Ls`LoYYCW#M4u zyzvAe{3-Z<63bOe%QF-jPfnkhVa$?Df+ZyvMc1lUh~PU(2a)xB!8*XchRNHzfBn+G z{s~gh7v^2kK|>(F-S6+HUcQe$d7Z_m%XIO9HO=>LV7~s1mjPixNkDMYNvyUcexHSv zbpb#NN$DD-K?g!Fs##~Q@gON!RGu$QQaOo1$l+a5b=83i? zcVwyHX0D{H%xgCfdH3#JMn(PgtG+qmecC>W=MNON(?HwVcs?@_f08}t@N;#{9Ln49U7pFVAqOcMElJ6idJQbdRh``< zH8s2>ozcO;_n*xp%lg#vuad>@Z^evrd5ozhwF!V6HrME=*zrsZ<^KS)E4%H_E-p&k zPrR(GbN4^ExCe(EZ8wPg&ms1el>GEu6*~Duo}Ve+S7!BJ_|tLo`_na}iGTXA=+&!77}p3-jw? z^}&A=e^)z>It0OJhojBc4Hs~Jq4RZeud@|A2$g)IUQ&A-cecUpd_(KQ_VNe0wVmlwS$MP>?yY1WkHBe`WKL`=YoG z?c4{3NqboP{$9tz4s|`9KwLGtoixAq#cAQ*# zMtL9qpy>TQiRSXG77T(*%%PX7B_`JRK!i{%x7fSDbXov{7(gYG4e@WbIa8ve=JIGA zY%Z_8rdw-w4h@ZVSwM0zR?ze*P{>)>*;~Q75y9&js0axY?h9WxoIF@0<8}c(a@`nb zU%)pxY#m#J{w#8%NookbS!b+<^X71%Gi;L8sN-P`bVCHl4vKV9z~(LmL+Z`(G(0>U zWX3^@={w-eA8n4LsTJyM&(y|rbaXuXwcim=4$`}N=Z71A(eTHQACEV9@&m0H^db&r zD^o*2RqFy>k%~0fAj-?C_rr(U>(&y2LOaG!M?jm~oAmi?h{R4Mz0_|~k$p<^swFaG zHWISYdU97NGK#u_k3c_MONa1w>;~p*0 zz6kT(+uv{M>dIBGE-YkjXrQE49LiD{yV_n|Z9Cmt3MvO(Y*aivr)2HaNJC+d=YQ)- zAR5ij1HMK!8}$ZI8e7`iE&HChI69JZaj7;COCsw6bJ>4STwbqI)B~Z2n8yy{17r>5 z{5PCnXufbg8Wj!It!g>T;$=eNNVL=RmPIi(v7J~W}s;x-&gAl z%X*Z8+>^lfC4zGSNeT=yp*qQlxs*R$%TGHMvM2A2x`?LR=_k+AQ$@U zqIrxP?4hgOanQ|Ssf+1}i8zqc-bb9w2@m}E^IdOWMY&QUyxs$uTvA&487*xfy;{NU z`BtIu-u&+)4qpR;3vxuiI>M;2a}z)mw*of)t9Ypx;{jpM)~xofYbpOz_O^Z;Ww%u4y6 zIV?U7dFj-Yl&oMSfeHI*q_wRr+KC4_MjuV-uDf+q0vcZ?IbfDVsW=?`=-4r&t9Jb| zd-n8Nd-pc7)2x^Ya5fO>e9M=K^>-FPMz%Zf+H|L3DJSnjIJ6P}laJ4b_j+`TErFk= zL}gnTE!-^QoYmlUwmo~~Ly2+mrKybcU9V;hj8`PtPbBZlh{`-q{9ffF(A|87ctJwR zf=P4p3_Ke^fOPG=V9U|-#bqM%+!o*A_CVBzGLfF7w843|E5nz!vxLi5+thgL;0X^SYg*UaTQ#G;$Z7A!0 z(-@-aPH6PY9?~`3&_v&Fh-feyc=n8(9EA05{Fvl({HT;6z2Zaz+9|?O=@8>C6y7`- zhRZi3JGCge#7UoQz;47D<4zVkJ^_GVqqKtG4rg!s@f>(DpNH+mrbAATB6tW zIsJ{gwg)+uZf<;<4O6}SYpBWb$l;r> z7Z(dk%nwxW2n#;Z-GhMcrU9WMR1kGsEdd;$zsC9@Soqy1RncZ-{m~U+TT0x_6=uQd=|;02TY_yNJ!ik1-2rcu z+e%bh2_xZ`KeCv8zUmyeyliAKl4ny;__~yDV1O03^#vxs<}>oNgZh;f1$_zc1yGA` zm04Q8N(6;+CC0EFWWbh{wvFkkYD>+2DT#*=i~Ne9+$WI$f3M?@C(9*N#&uO#&B&K# z%F$?6*pVR+jZV5sxvKX!PlwN^oU41Zp-_x+i|GPK7nkPNiMaLk*gEPB;4J}z_*D&b zl|LvwodPJ}ry%>Z&!@sw>#}1qTj%QM=Vu2RajcvGrZPtU9uTlLE)3*^vx7Bu7r53z z!rhK(;YO391L2bIN=iTN`d+Qg(s=0( zBwv%WPqo2z?nQ-1Wm0%^3d)mTpKtm2`ubMOZ48FuU(xi*fk}M|9a#gLti#zpj>t=H zJq|+PR8@Yzp7A*7!Q~T}#dN<=!foZXSboT5J=+$vq-(Mr8}1h%p~qpO2%;xcA7qW! zcUi4wD#&$%rQ`*hx*SIl$4iW1t1^UV)li`{$eKXm(eqjR|Q>P3jhUx$1Y@R*K)niQPBrJ#Oe9@Aec)H zXRAcV@EMus&wmKE(}SliTtC6hdoGa@s^8u#n5T30BJ?OQun`DU(RYrvjA?~ED1x)u zSBvzn7{H@8?)udG3PSGq-T>iV2$1t|F}=U6nM{wrVo*QiLYxb~q;+@aLv@;O>q*eq zPZPfFw9$OD6<0mUZ_Au6g*8XqE-No@GIdPC@P=hdD_S!VZnM$@8yXyZ>)c_s+L!Rl zeaBmNOSNaCKb1s5=tzJu{=#@lC_+=~X!Bgp*0xVUUs$g+`eX zf$yFBKU!OP$119Ol7L%PX+uzQ_$$w)!#x7NwbcclWFjReN6EtS`5Lj~4FS6!ZQ)B} zCO$3yry}dowO0bc)&dw&I;m|?)tLzP%nwx=3hej=* z_=JZCA0jL)e09=zO)G`>=#lH)2^CAPu@Pv(CnpCO8SXK9OkvAFn%b()7MS3JgM%BS z0zQR}TFqA|0cN9Rz@e$+ruQR3_%iOWq|i$`%8(p zC-q@I-P}}$Iv#=gFOySs=X5kCSGinfXLX2$E+eZEEA;j))D-hDSkwsx;%H2lyOEUJ z25AzunsI;`7E`C2;SY9FJ9~RpvKU(B4}QX=_YSj8Pfr=FwY0Q&99M%~ErUR&p;|yl zsNshJ4yf_pa{aDLUE~I3oCzZ>uWht_&DQ4HJnD|Ub$1Vnm+JT&(ID<8Bwvq6xFbnH z-@#=IZ5^BIGf*d=Qc~i89IkXU!@K-^#up@02Uq(FZ>>k^6M5Z3t*4xe%obp;x68^n z*w|!aWvPGwDJ3P9A$m^}=y`#Gyg}t5oJ>r;#C(TXxnp@tJ(LIg{G1REL=SF_^G587 ziKNV;UpHcm-vzeP{9yJb8`kjpIzFA?n{aeI1A9mMl)~5=D7?cbE4lgS&vyhvI-S?8 zD=$_no|20j-f0WQ0L>~ug8JFk_MuOek=wTM#yYt71VdBVG)A`VWCu8&Rw6sTeYIds! z8!S~NkBQM8$!ya(O6#?NxG+6g`IgK#j?&JAh6CjI*O$3hDIjP@t8W>!MQ+R*Crd2w zHugG{)TZEpcH=vc6KGRNG+=UsLXFx_bWwH}$6s~3ISFakoTNd`CBz$9IJ)SLKfYQz zJ&Wmf-^-LwX&&BiR4*~Z%zwkR)IH(Y?df?Bh|NE`a@mG-?p1F|*H9uM1z3~pXDfRX zn+`!(dOw>gPj|dOB&0x2r%M&stFWvT!|;kQy(4_@xoC?N=tM4|NCOwBKS}@8 zWh*qz7p1WNyi1dOq)qs^$yxmfbb{z4rSJ)f1KOGSN%?-c(Ocs?p6(@Nd9KyHu0cvTCtE z@HuVTU7p&583ek4sXzoO&F!eWeFSXupOceA0|HR*_`UC_D)0Uk)Ya zy=mk%KbW!-O{d%7QK0KRi}3_ZlLv)hA=7R@ys~F%(=oVsP`fdZHeaENCnjb==?Oj? zNsn7vL~)sQWI>zq+r>a?&?u1snx+}FsvcTeTGlvinKU@M!Y0duiMTPTsi^^dZU(_g zDH$2?Od#>MuEsW<575vI094o>FGf*NPp6k!d!dT=@@a=YFo1zz4^kV%V{Smas+I^Nr&*=x^>V+I$#px+;IrIDCks8v09 zdA|HB67)&4$#?={1=m&i^KRCl-_+}7+%GpU7JCN=C_g4A30+)HZX#I70akq!9uO!d zbq-E;eJJZe6y58^{gp1@?kCRA&%Y0q12<1}In`(&B<<H@%2R@ z^6Le<8Qw@SYm`t3U+$uSLV9N&G|GE`5<4;Jv@?si5~oI<#{R<+8mU6vMj^l9kHFgM`J1ahh)p*gnQq#Ucpp4ke)=2^91AG|s zNBoSt=jQDj(?kfdCFj@ZPaY$$-aA=ZzXq}rpj96Y0?PWE3M=!5Y!&1I zqHww7$U{J>7V@#g9WXha)`4!B4Hclic zU41ld`b18%Y^^qbs&p7M(+7bEV+^JmvXJoIDTPir$Wkdc{IA-+0<5ZZ-G3vB4JwKt zWe_4DC7sftAfki_NC`+uvt<*Ch=Mc{Qc4I)i*zGhTR^&NBi$YQzKfYNXU;uy?m71# z^E~s6!!SG6`o3?yzj%Ld+O&M%{pzBPGt`3XpG>^h9v0dQPv`G3H2-Sj5L%v*ji%yz znLS-{Bb3rdIvQW%V2nCi7K^^RqKcOSa8Xh5iZsd%L2o1@~>P zYZctW(r9gBnmmivoAnfHA`M#t6L9u_LXg@~{NePLB@$gNI*qH@rtOdF;}`Fxtr+L1 zBW~OA1~v6u`$Nnk+SMe24q4Z2C35UTGIG9-Wb&|vbMKB*P2;aO8_r{D{U;7Dw=Csu zjJ07KEyYE)F~YF&Kj=weF;(}%`ugj+%zl*~ojK)iQ(Q>tWN*Rax|?h{U+BB)VB;{X z#}cFyUiItztFN2Acj`mzbf;E2-7s{MNY5CQzb7bpPPldIxJCi{*Ag!mR76aVY!Wg0 znC|NAS^TB()=RfOzd^-66sT;!ja3g$5)|NL*WGbV8ddR@panyJ6=!5+#fcx{9ZA!s zzdm;@g2&Qup^b3q9EljT=FAd0ouZ&Hzh3CFO9acoYP7lu<7fJ2jlkSwTb(&FQhq|+ z($LT&N=t4z)aQ0HlbwZ4?s(zs&tx^vn%?bE0O;qXiHCm-vtQSF)8G`ihXh5y!A*MZ z+##`Bw@v`#^BBVS{y>m#H^$QJj_O*Dql-*C(`*(;=)b6y?jFo4hCHfIcHUIIw1W1L zQ|(0rdE`!S5D3{HK;HR9BMPdb2F2*_%^|h5zc7~Q{D{7h6xGygrBs57U_T1gV^b;b zkCIXd;RLV*wOYXZatMpY3*YCn-QUm=`DP6rvo}n8UVb$^?r^vUX$k9|mfPbV(jY6% zJmG$csphZIqiBDExW_Ssc!wk03LM7J)Me8Bl^%|$`sw}ip5)$C!gq&Mg~C14fol2N zqvK3mll{pEEfChQJ|kF`bQ)`9TKYQ-^b09HDb`B+1{&IbgMkjWH}<~RO_N~?rfdMj zBK+5nA1BFuq@jvrQof9@ThtjRn@9?f2;*`%ZHix~*ROd?CnR(rj>G*ucQO};!8KmX z>bCVaj}8caYgo=Nv^zP17yks8U>PB+t=d$HPO|i2r>vNDeEHKAtCF7>j-)+Ac3@?d z)@XZpP*!%6`5Z)gJhe>;OHg`Td}&@zz%43@nt&j8{a4XaJ#(ahofib%jT!MFhi^rw z=eb|dpsD=~Enrw5tlQVsf?KMr9E37g!F_A>>$iVEZuPh7XQ*cg*vU^o3*>=??fKlN z8*xIsqt(88<*~Vwop=iQ?%p>3Btl+`fkuv-BvY-P)16%NOqhvTj~gZmbYqo~$KLYm9@iENI!+{L zXWuxjd8nR!Sn^YzM+D1`IdbBq~1XUX4Rngnq5X%QfkT4PMO3Y5VXUv^k#pccoL;WNv zeH3q8g=(K6UwwSzXiwb zoUZP7jqBNGUpDeeF>rEzEv;)Xp98?ohuL7X*#<7>wUTUEZ!hz_Q|oc}Is-kVp({o- z206r7HFL(guKz)A^s$&jg$GYPej8k)E>JBv5li-7o(sEpYVFa-_UI{@kUAZQSZ8-dhugw ztf<5KbOVx(_}L^pGvU+Y=+0KYOE&n`W-TZ5S3IfM;};jGsU^Ute*Svp2u@W*$I5Q9 zFM!s5?(rxt)poX(W3V&yJTzkLICDSV*%anEurTT;v$5K#0tt<*v9XF=uH_Nm%vu_Y z@OujZ?8|XiZ6XRkl%ZVs$;tsIv6BZnPV|$QOm!Xs(sA90zW75FpVbREwm2;M_~5ER zPj4*L3XabPT;l!y1q}lA(_7dw;^N{^LOlBZ>ZA}JvsSE!JV3CjU*r!z>@B^iSIHQ! zCHRJE)&y8a9rLIa*n`ldL5|t0NH&{6?VKUZR)@NUtWVN%Sz=u*v#KNf$fS5op4=)qLZqN8M=$^JdLULB zJ!#W9X z`Z24n7~odqSkyPj)Y7%$0xTsyDLEf4TcQ&N{UOaRluf6(_uT|E*_?yV3fbcdi8bI-uhkq?-tVbCNH11y4H#3dvEo|iW_r-_Y{!7`6cy!Vbc z_)F_09cN%fp8hIZqnh@m6@6YV1ql@N#N+I3RKN7bJD%yS!u3_(pS> z4tCw;Dz(h6n3iuQD&Q^p4&C?!3#+SD4b01%fv~X+A*lr<*_K2lTGh0Na&WSM_RE=T z(wE1ftgPG^Elhd%@L{48HUtt&T{9Sw*#Tf*E{8%g8b&ORg=x$zEIWhk%h@(M((w;$$L=Ic+;kg;h<_q8YA zSTe8u$wbNo@({|hX!E)#jet0C!q5D#M?VtWyP`Y&t&4FDc`^FvPxzxlP0kq?7 z?3|Y4BpZjzMvJ_7r{zVFO92MrX3tx9v{h1$YE9%uZ6H``A?|R9{jspc1g1z;3@); z)N^bvwHGCS9SLfGi!W8{T4};tQ!$s?B+eqMZRy8rAAxGW!Rr%K9gHUP)|%}5ObArfz11(` zc$Z>+G8zK0f2)q2*X*5{{_^i&4VNdcMt)WPzrr7*BUK7OV?zU|6fn1WPdUik90u`W zd-y3I?<|{=kwbhmR)GM0^>18gGVFEjstO|X9CKH)4I~@=W{d+#^_MCT0z3Pj!7he^YDgk-6b* z&zjvPyWiw6GX6C|`x(CWWHjMZjTGnO& zI8WO1v?k`}UU_G6fnato^??T@G(m;bj1{H*Uijo3h-Z-LqgLQBZL4I}4yj7#&l?fb z2$@~)1#hvIz~O}8tH+Kv88CKRg9eU=F!-T}g^nC2x zgV0kBEhARZ6gzZED|6-8(w85{Yt)Z^zMjZh?arOQh-jKJ4TD4=U*!k7 z8V$B4P;=}kM@K-L}SBZAUgaZUL{JtIYXwSU}5de#l&<}rp9YuX-axHsrBCs%2{MJ85roYiCIypI6^m7_a z25H2EhpkQbdXLYS)+KW>X2H?+ZjGSK>0bzY@aR(HHFuzNDmW#3SJ zhnTvn`6FSk38>ggp`n_QpwvWq744q)`VDH-+Easomxar^^NM7b7q0ea?IudW!oVA5 zcl?r)+Pa&k$M1JjEyF9=ylqOw@Iy03gKtzJZLR8gx7sA*9+3_&XoXRZl{^Y!hwLj3 zcO9kZ`akctFPk}qrdqSOD4SW=hDSqL*iIW@Z5EFnHN)$kAGGL^Z~Np!&{c@YuKfdk zE-XSX7D*G?$H$K%GzG*!-c@NsWkBz4lD^Y`&K_j+zo7z=r#FWhR8xbwn2-=@02vHH zZ=g~KKD-VzR8F<`*l0nx<_CI92Fsz+aoo$lx%vM5MXsE0ja_$DxUi;jkvE?c>{zqB z&?@9o`!1!-v_<(9<7m6ckDKCMFA zu^=VUE6|qdX74xd6e&pIBMtA%ae<5dvBA!wz%o($Gdt(5VkglFtWKqIT=*CD9$QgzwO13HT@L&1NW=J7AQkfnp>iRr%&yocN#C9dr*F>U%XSVMqgmK$g zmy8e{G46Z?vBE9Qc#fG_1>~|7|Am?p%(!*XX49HVLgp=kI%SxjElf2s;dtqXhZ(!F z)=|6BM~L?JoOT>+j0_6~b5ZC0ffCMg*?o3~l+^0!(?^YA9Njb>v@nq3ybk7hi!n?| z2>tM((SZrD1}KnLTPr4@cu}xFLc#osb#4*&a<@8z3dm(G@zp#_KXB`les||mpQTLe z*%|qBJKGg`mN9gz@&jwV!e=tbozi^|oeelkHj0zYi;heb>DarU@#WV&vD5wbOJxi$ zyH*NwC+COmmvWyZ+`K+0Bgs?$cUe@`)bD|o?` ztZ;}apSMUR(P8c}y0A|zbbjnXmVmx%Q}6tctJi3a61(LdWy*%JhOXz*kE1~O28nCO z!<-5a>Uw7t7F8&t+cfg6>46~l-rE}iMVf)BK4&;Y!x;Yfa5FDIKht*dE_C&R)%rdR z-?#!Edw>5^@DxF!NU@?ZyJ@CK>FUH`R?Fo4y@VY zS~DiixcjSNR`y>D-8Fu+iRfh<3Pjvf7cR)l%O3-YoMi4Ytne|~Sqs|ZY=?~C@gWJO zf$DE^>YDg0i)8FO6pn{6H|x<*J&(2C{SZM-F{bFB%7y2bsl@82!xa^cCr+E5DRuW9RmC4~m5- z^=?h)Z0mhJxnI)L+^y21@2{%z0EgUNFqbeYw&N%HpDKXPOorBcr~l zX~Se&Vv~{u$PXU?2s%1CK4K?-4Mu^cCZCK9b^ts?Vo?HieAZ(}L_|a&$ol{ZiJ#D{ zOs;`uV~mo+X>=I*-ACOa;DWj0ZnoL*Jsi23#8sE&9IUNlfz2*jtfqvn^_CAe3E_bS zwy11V3J}^A6%|1i@&xKO5Kcx&M_O2_-?@ zo;&>dWNR_)$7x;z3h1#zA6%!9Z6llAw3uFFvCFmS2e(_k?Jx3Mp1$QQS~*mJQ`1fJ z&p5o}U@MWVX)ZSDi4}%hgngFwaHJ0$V&%m#v39)|wKuSmVA6U&J>a;kMMQj|9>k!m zw}n9AKt^#`y5sS-uMQz?-=wz9bl}OwpM#z6+xK$>c*<9X>SPIh;L563HTZhpB;>Fy zZpBv%O*65&X?ds6zKLoYfgQkl+Dx_@Ld0?XgsI3_u>+Zn0FK2wgoQg-^P3|Rv|^4; zvvtC0SI2^0rmpba(@RU&N+wca@JX~zkjaYDAC66cJPQvyQ?-ndQNx`K8{TPgUISC3 zqo&NvG^TBV8f`kwnSX*Dz#nz*m|rz)C}6kSGDSsty&Xx+h}pEQD*Wc0at`bagk_Ip zN8cbmARWu z@7uH5fnW#01rnL5X)Ndcf>cbj$@yPOcz%ceIbYb$!?UvqE?M&3#lJTv-$JDHT4 zT3?xJEF+7alT%<&Q+tjfmoF|(Kk5FceH6G5uQxYK%G7{2{+wd8kQ87oQ9S0v{FnQ$ z-@e6FzR{T+NNp3Yx}U2E?wCk^$Ayf1vwepcm7~%qP6~v#JeOSog4rINx+|Ma;y(94 zbAdx}y)S#N6Plm#3$}z1qCJH)H%pukcQ5tZOB1^iyU|IUK@k%Z<9HGj z1oaS_A1HyLw)-)V!B7ht-jS32Za~rq3-`zFh19b)17x70rbawdym#_j;|{LRRAiji zf5UI%ORyXBXs?2oLe%00WywILzb<|pG?lEGHmtXW){Cw@G$jf)4;7!|YsH&>6Q(IU z8@LEoTgM{mwY3E6l+w_Am3_+O09oqa3}#@s6#GvMX8#TaNW3{i|26yMRBH{!eKg>T z4C@8FQht>Qc*IgQU$HoIPICoeU7VdVcXoNvb~pq3nLY`h3+;$Pr=;fGtZ7-Kn2O|e zjT?10Q7H28$`PxkTqL-w;SJ6)!EFENIa2UcdMkd}@MJtJe5`8IKZT@yO_cwcXy z;ouNLUM-P~j}PL~k|$|OPX5{X^*As(I9T$*1A%_`!wajSrd_T>r<&gLR9EBH1dQSS z9tpn@1);_*8RfHM{cUm3Noo(9xjI+Z8Ra5)&LcNBRH}0XI-XZPU&4)&i0r)lqwx2x zZj`gIdgkP0wXvXtTLYun=@@_iSpfjVMomT^i|})LzA`)VVEF`=I398)QZ{UF=PW-% z-^cv`?X7-(44nNi<;Sq5x!Y(gR~nl!Vb|JFAwH8m%0Oivbba8zQoNY_So!ifv4wjIWDlXYTn@fQwc9l ziC6r#IFCH0npQy%@W=`x1;3Sk1-`ff6z<0w;nW;YsLXc+tpso1dt<#W7ZVud`F+6A`+@ilV= zygM$1n9XCN9=o?fuTy8Wdw9kS70z4W4_z$%y*d|-KS4yu5Oh>Vrlx{8q3oaPYvSbp zR$tHHe~is*-FCBA30m%(@2a@FV>tX#jGR}dVzsEsXjbP?vX|navn}R~BxWO>w7Fvg z3&h&1nr-YKED5Amu=cTg27%w%#y%^p&R36~RnK+1T)?_IogZ)E%BbWlzai?awGe#3 z>sL-2m=TJ!CWKb4QZ=G7OL4Of3!+tvql{s^_>-!bZz&lP=wAodUA*Kjv9+dX1OfG4 z(jt$ryS2`oe_1LgP|6^*@SOW^1iNQ5avq9Me}8Kdl##P%lq!5anNFC4BjC_+vL9fn zeevR}=o|y6cx)vb2jW^@r>Cdq!@V*9BMFqR7l4`omf9^MqSu{xzg(S*a|mUrl#qb%y;rAq`x6OA=25cfI^sWEA! z`>gj9ud3)3T9=LIPz1xMiSC*)@eaSW-Dwqu<*lD}f}-`S7GQhL$Yv#LV?sWu)Wgi$ zYcQmu29D{j9COOLx^S$}b$|)bu8iY>$#2kl6yXQny{6;|*ADcvXRw#Prle?A1jcLo zMIcOjz;*)Ev{;etl+9INYC#YyIT(Mz&ml2}l!Sx?7A5o%06aQPMkWmfBPvNHkr>=k z`fFO*>#X!`XGk zb3}M}c%XNvv`l~hV&&6%?e4F%*R`TP|98s5KUUfxP*mq+5c)^X=G*%6ekYmutkf@4 zzbrq>1CSOe=!LGzw|Xb#84w;$dswSNYs#E_z)Kpb`Dy6P9gy$vm-59wy~#hTAWURf zB+7KG*RpgRct<`yz8`TiB*0l^WkYH-xcN)UjBkugPb=KHa|-oWFr8$J6ZssHEO!C% zDul4qfpahXhRGRdZ%6^NpC)BcouskxRit~p;^k4q^Z=2rC~#9K7r6%LHOiMp*b7o>a&j~rreIM*+^U|4JxFD+58Ph9JP67Q zLB`jw6<{lUxNpt7`}1rv-R?-0HwM4E4j_%OGlwCFsiu>kdVhnRrLP*PR(1m=EA!dv zljvttO!dJ`L8$|PQ(*+=Y`isx*E!!-W{(*q5c|KEb+& zo(as(V$I52Kc$iqmc2Rb85BhN`)+_)kS$5dSBi>?P{#ixyQzonU%S9$klh@hs|~g$PM!CkWVc^~ zrLNDb_Ai*V=(*(Ij#fz;qCYgV|6tMmC(_&hua`mgS;kM8aM_(}NekryL}7V=ufot2 z!QX$Tr7ohth5*egqp%d91DNYk8LF?Zk7k7)*^jT)fzkf{ZdqAbn|Yw_Li>w>2`MeDpSQR7W>-~JRUfW-556-fOh(ge$nCy+ z?`(wdqyuO8E%cM+&b5%0(xBgE=?e@&b+I`iwODyqY3XWbLHr;GdEjz47CMrLT*o*$ zIhXpN@9t0=VOXK-`*=F$vTN8~IJ#BQ^C3onq?U@#1Ox_(zbgMEHj>|_lRKkvSy*`Y z`3@zXv3`2q*$wPxW;A7(h^5V}M;KHlJ>$gg`tI&d>8TH(as*#h9x3r*y!-I1HyzSO z^ZWi10ujBowYsW`UNb^e4U11%URkL$;55_>$NJEh_L-ZTvs#$Gne$VP+m35r>YI0o z!p{fWXX^LPlPthEA}5YTONvlX|K$JP!`l68|2LWd>57SsjSUla;sK<2f}H&WJDUu$IL9nC z!dG~im<*sF7b0HnGfys+LB2dGxMEV{8wi8K!jj(Eo~{ehTeRI5(ZNFfzj-bKi;g_I zK%Qs*e(qFD}dsk<`dCvaS($fMsXJLTY z&cb&RkU*5=3eM(%mIy76=inLDUE*B0c`{b^CUhws`e0BO2v43Y2nYw065LGYv05-J zw8N6wqY4Tj3FzVf?YH4>p_AZ7KOHI~fIvXVv$?J7d4(hZ?Bk_F#|oMG{GvO9rbF*9q?EG6(?jk2@1EBBtaJAP(8q z-@&XQCof;o(Ln}12ilm$stRI-z48I{1T|EyO-db(V*xVcA{fU@GW2)@XaLuT0u6MH znVFeiKRyzkq~u|l$&idEzwFZ=#Q*a}zu~_U*`={HK(Josi(3oerNDiG1@KZmq;y-4BL3$hzNW-FCwBh2Z^#Lj+ zq?6AOyv#Fqz>V@ci**JiITq8@(BKUZ-({$)paZ-aY*O7&Ebnj99ztH-ikB6{H^EqP z6a-e;=TEvaE4DS4ttzO@jY9)D)H*XN9a&Ic<9jSGiNHGA)#?%%jUi- zpcpu4eB9hn&}CSqB)TgJs|UI@j5<;@An$O@krj4E9E=3q*xqjYavvsb&6)esb929Q z-JRA!hC16#tD+t^N4|-PiP`MI7xWv#Gje6NHxyoOczzzMq9k1PQNnYsx6k{v>|z3( z4or7M@jG15g@DT)bRqnJgb=`JO*d%2=5ksy16lmYi4$XqF)kypMTf!Oj6f!6!uV?1 zc^x=Nr0@j86YMm2MjinUP7Z0_SY1tkvL4(^9GH!#0DGXtkPt)(WTXk)!liDb7-cZr z+uTALZGpa@87z%~hM3{8F-foqB6Hz%TsG+uyXImIE!G|xf*TCIZt$+a%2z}4tOnd= zKas7g@Lu}WZz)dD2!(-yLJ)2#hD<0&*1ws4LVP^CVf|S|u@Z7*hXw`^30}Ym#PgN8 z$gQP-m3Q|8L@UF1Mh;qDv#wL7(~Y7dsW8aP4eE@5p`l06-=P3kzq<&{1x+y@kf@8E z?Bfm&4lrTOlIDt#P+w7j2<%vtw(G7P@-6%FxuANkgibvu(!ue)}) z4?w5qAvcgl2#3bT4!S|1tCYCY`e8E>9{r z+qMEvF=;D+Dz}BXm*J4kJvVn9CUPJ!hn<(VrndITq9c`?MXo}ox`CeFi+Atd4LvK? zo>*OdH#unnBisS{A?aEZ6GkAuLe{7r1``P}@=EAXKa$sgqqcTM;2bBMZh9Q_BwPM+ zzz3LRpP6~{^S#Said+H$0#E1L+Ektz-yrjbjYuGNhnFXgarCmo}QoEpWM9t*u^Cp z^5rammlzyO?6(M#Pq-S|5Z`9|;x5W>%I2e541d){c&{z-e{N z&Cl0OEThJ(`C-JHhK>$+e|*J1F7#?cvMyba+yx(^ck&*p$x;kK6u$-(0!z|q=S%lw zLg?fISCsf+Labb%c(wm^xj;IPx0kd6>Ew=4NEE;i&vL%9g*oS1|k-mN8XrI!ng n_veULfPcNDFZ)0IkD>%)(J@Tfi4{C3_)AVoQ8G>Z(To2AER?%z literal 0 HcmV?d00001 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-custom-series-label-mappings-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-custom-series-label-mappings-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..50cceb5deebb659c714dba619dafe523062b91ac GIT binary patch literal 21055 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 literal 0 HcmV?d00001 diff --git a/src/chart_types/xy_chart/utils/series.test.ts b/src/chart_types/xy_chart/utils/series.test.ts index 6746c6dde5..2d79844984 100644 --- a/src/chart_types/xy_chart/utils/series.test.ts +++ b/src/chart_types/xy_chart/utils/series.test.ts @@ -718,70 +718,167 @@ describe('Series', () => { expect(actual).toBe('a - y'); }); - it('should replace full label with customSubSeriesLabel defined', () => { - const label = 'My custom new label'; + it('should replace yAccessor sub label with map', () => { const [identifier] = indentifiers; const actual = getSeriesLabel(identifier, false, false, { ...spec, - customSeriesLabel: ({ yAccessor, splitAccessors }) => - yAccessor === identifier.yAccessor && splitAccessors.get('g') === 'a' ? label : null, - customSubSeriesLabel: { a: 'Apple' }, + customSeriesLabel: { + mappings: [ + { + accessor: 'g', + value: 'a', + }, + { + value: 'y', + newValue: 'Yuuuup', + }, + ], + }, }); - expect(actual).toBe(label); + expect(actual).toBe('a - Yuuuup'); }); - it('should replace yAccessor sub label with function', () => { + it('should join with custom delimiter', () => { const [identifier] = indentifiers; const actual = getSeriesLabel(identifier, false, false, { ...spec, - customSubSeriesLabel: (label) => (label === 'y' ? 'Yuuuuup' : null), + customSeriesLabel: { + mappings: [ + { + accessor: 'g', + value: 'a', + }, + { + value: 'y', + }, + ], + delimiter: ' ¯\\_(ツ)_/¯ ', + }, }); - expect(actual).toBe('a - Yuuuuup'); + expect(actual).toBe('a ¯\\_(ツ)_/¯ y'); }); - it('should replace splitAccessor sub label with function', () => { + it('should replace splitAccessor sub label with map', () => { const [identifier] = indentifiers; const actual = getSeriesLabel(identifier, false, false, { ...spec, - customSubSeriesLabel: (label, key) => (key === 'g' && label === 'a' ? 'Apple' : null), + customSeriesLabel: { + mappings: [ + { + accessor: 'g', + value: 'a', + newValue: 'Apple', + }, + { + value: 'y', + }, + ], + }, }); - expect(actual).toBe('Apple - y'); }); - it('should replace yAccessor sub label with map', () => { + it('should mind order of mappings', () => { const [identifier] = indentifiers; const actual = getSeriesLabel(identifier, false, false, { ...spec, - customSubSeriesLabel: { y: 'Yuuuup' }, + customSeriesLabel: { + mappings: [ + { + value: 'y', + newValue: 'Yuuum', + }, + { + accessor: 'g', + value: 'a', + newValue: 'Apple', + }, + ], + }, }); - expect(actual).toBe('a - Yuuuup'); + expect(actual).toBe('Yuuum - Apple'); }); - it('should replace splitAccessor sub label with map', () => { + it('should mind sortIndex of mappings', () => { const [identifier] = indentifiers; const actual = getSeriesLabel(identifier, false, false, { ...spec, - customSubSeriesLabel: { a: 'Apple' }, + customSeriesLabel: { + mappings: [ + { + value: 'y', + newValue: 'Yuuum', + sortIndex: 2, + }, + { + accessor: 'g', + value: 'a', + newValue: 'Apple', + sortIndex: 0, + }, + ], + }, }); - expect(actual).toBe('Apple - y'); + expect(actual).toBe('Apple - Yuuum'); }); - it('should replace yAccessor sub label with map for single yAccessor', () => { - const specSingleY: AreaSeriesSpec = { + it('should allow undefined sortIndex', () => { + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, { ...spec, - yAccessors: ['y'], - customSubSeriesLabel: [ - { - y: 'Yuuuup', - }, - true, - ], - }; - const [identifier] = MockSeriesIdentifier.fromSpecs([spec]); - const actual = getSeriesLabel(identifier, false, false, specSingleY); + customSeriesLabel: { + mappings: [ + { + value: 'y', + newValue: 'Yuuum', + }, + { + accessor: 'g', + value: 'a', + newValue: 'Apple', + sortIndex: 0, + }, + ], + }, + }); + expect(actual).toBe('Apple - Yuuum'); + }); - expect(actual).toBe('a - Yuuuup'); + it('should ignore missing mappings', () => { + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, { + ...spec, + customSeriesLabel: { + mappings: [ + { + accessor: 'g', + value: 'a', + newValue: 'Apple', + }, + { + accessor: 'g', + value: 'Not a mapping', + newValue: 'No Value', + }, + { + value: 'y', + newValue: 'Yuuum', + }, + ], + }, + }); + expect(actual).toBe('Apple - Yuuum'); + }); + + it('should return fallback label if empty string', () => { + const [identifier] = indentifiers; + const actual = getSeriesLabel(identifier, false, false, { + ...spec, + customSeriesLabel: { + mappings: [], + }, + }); + expect(actual).toBe('a - y'); }); }); }); diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index 75b25a023d..6c0e4ed874 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -3,7 +3,7 @@ import { Accessor } from '../../../utils/accessor'; import { GroupId, SpecId } from '../../../utils/ids'; import { splitSpecsByGroupId, YBasicSeriesSpec } from '../domains/y_domain'; import { formatNonStackedDataSeriesValues } from './nonstacked_series_utils'; -import { BasicSeriesSpec, SubSeriesLabelAccessor, SeriesTypes, SeriesSpecs } from './specs'; +import { BasicSeriesSpec, SeriesTypes, SeriesSpecs, SeriesLabelMappingOptions } from './specs'; import { formatStackedDataSeriesValues } from './stacked_series_utils'; import { ScaleType } from '../../../utils/scales/scales'; import { LastValues } from '../state/utils'; @@ -379,47 +379,26 @@ export function getSplittedSeries( }; } -/** - * Get custom series sub-name - */ -const getCustomSubSeriesName = (customLabelAccessor: SubSeriesLabelAccessor, isTooltip: boolean) => ( - args: [string | number | null, string | number], -): string | number => { - const [accessorKey, accessorLabel] = args; - - let label: string | number; - - if (typeof customLabelAccessor === 'function') { - label = customLabelAccessor(accessorLabel, accessorKey, isTooltip) ?? accessorLabel; - } else { - const map = Array.isArray(customLabelAccessor) ? customLabelAccessor[0] : customLabelAccessor; - label = map[accessorLabel] ?? accessorLabel; - } - - return label; -}; - -const getSeriesLabelKeys = ( - spec: BasicSeriesSpec, - seriesIdentifier: SeriesIdentifier, - isTooltip: boolean, -): (string | number)[] => { - const isMultipleY = spec.yAccessors.length > 1; - const alwaysShowYLabel = Array.isArray(spec.customSubSeriesLabel) ? spec.customSubSeriesLabel[1] : false; - const showFullLabel = isMultipleY || alwaysShowYLabel; - - if (spec.customSubSeriesLabel) { - const { yAccessor, splitAccessors } = seriesIdentifier; - const fullKeyPairs: [string | number | null, string | number][] = [...splitAccessors.entries(), [null, yAccessor]]; - const labelKeys = fullKeyPairs.map(getCustomSubSeriesName(spec.customSubSeriesLabel, isTooltip)); - - return showFullLabel ? labelKeys : labelKeys.slice(0, -1); - } - - const { seriesKeys } = seriesIdentifier; +const getSeriesLabelFromOptions = ( + options: SeriesLabelMappingOptions, + { yAccessor, splitAccessors }: SeriesIdentifier, +) => + options.mappings + .sort(({ sortIndex: a = Infinity }, { sortIndex: b = Infinity }) => a - b) + .map(({ accessor, value, newValue }) => { + const accessorValue = accessor ? splitAccessors.get(accessor) : null; + if (accessorValue === value) { + return newValue ?? value; + } - return showFullLabel ? seriesKeys : seriesKeys.slice(0, -1); -}; + if (yAccessor === value) { + return newValue ?? value; + } + return null; + }) + .filter((d) => Boolean(d) || d === 0) + .slice() + .join(options.delimiter ?? ' - '); /** * Get series label based on `SeriesIdentifier` @@ -431,16 +410,23 @@ export function getSeriesLabel( spec?: BasicSeriesSpec, ): string { if (spec && spec.customSeriesLabel) { - const customLabel = spec.customSeriesLabel(seriesIdentifier, isTooltip); + let customLabel: string | number | null = null; + if (typeof spec.customSeriesLabel === 'function') { + customLabel = spec.customSeriesLabel(seriesIdentifier, isTooltip); + } else { + const customMappingLabel = getSeriesLabelFromOptions(spec.customSeriesLabel, seriesIdentifier); + customLabel = customMappingLabel === '' ? null : customMappingLabel; + } if (customLabel !== null) { - return customLabel; + return customLabel.toString(); } } let label = ''; const delimiter = ' - '; - const labelKeys = spec ? getSeriesLabelKeys(spec, seriesIdentifier, isTooltip) : seriesIdentifier.seriesKeys; + const labelKeys = + spec && spec.yAccessors.length > 1 ? seriesIdentifier.seriesKeys : seriesIdentifier.seriesKeys.slice(0, -1); // there is one series, the is only one yAccessor, the first part is not null if (hasSingleSeries || labelKeys.length === 0 || labelKeys[0] == null) { diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 2b86250729..85071127af 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -54,16 +54,60 @@ export type PointStyleAccessor = (datum: RawDataSeriesDatum, seriesIdentifier: S export const DEFAULT_GLOBAL_ID = '__global__'; export type FilterPredicate = (series: SeriesIdentifier) => boolean; -export type SeriesStringPredicate = (series: SeriesIdentifier, isTooltip: boolean) => string | null; -export type SubSeriesLabel = string | number | null; -export type SubSeriesLabelPredicate = ( - accessorLabel: string | number, - accessorKey: string | number | null, - isTooltip: boolean, -) => SubSeriesLabel; -export type SubSeriesLabelMap = Record; -// When using map the y label will be dropped when there is a single y, this is to bypass that. -export type SubSeriesLabelAccessor = SubSeriesLabelPredicate | SubSeriesLabelMap | [SubSeriesLabelMap, boolean]; +export type SeriesLabel = string | number | null; +/** + * Function to create custom series label for a given series + */ +export type SeriesLabelFn = (series: SeriesIdentifier, isTooltip: boolean) => SeriesLabel; +/** + * Accessor mapping to replace labels + */ +export interface SeriesLabelMapping { + /** + * accessor key (i.e. `seriesSlittAccessors`) + * + * ignored for `yAccessor` + */ + accessor?: string; + /** + * Accessor value/label (i.e. `yAccessors` or values from `seriesSlittAccessors`) + */ + value: string | number; + /** + * New Accessor value/label to use in series label + * + * If not provided, the original value will be used + */ + newValue?: string | number; + /** + * Sort order of label, overrides order listed in array. + * + * lower values - front most + * higher values - end most + */ + sortIndex?: number; +} +export interface SeriesLabelMappingOptions { + /** + * Array of accessor mappings to replace labels. + * + * Only provided mappings will be included + * (i.e. if you only provide a single mapping for `yAccessor`, all other series accessor labels will be ignored) + * + * The order of mappings is the order in which the resulting labels will + * be joined, if no `sortIndex` is specified. + * + * If no values are found for a giving mapping in a series, the mapping will be ignored. + */ + mappings: SeriesLabelMapping[]; + /** + * Delimiter to join values/labels + * + * @default ' - ' + */ + delimiter?: string; +} +export type SeriesLabelAccessor = SeriesLabelFn | SeriesLabelMappingOptions; /** * The fit function type @@ -243,20 +287,12 @@ export interface SeriesSpec extends Spec { */ filterSeriesInTooltip?: FilterPredicate; /** - * Custom series naming predicate function. Values are unaffected by `customSubSeriesLabel` changes. - * - * This takes precedence over `customSubSeriesLabel` + * Mechanism to provide custom series labels. * * @param series - `SeriesIdentifier` * @param isTooltip - true if tooltip label, otherwise legend label */ - customSeriesLabel?: SeriesStringPredicate; - /** - * Custom sub series naming predicate function. - * - * `customSeriesLabel` takes precedence - */ - customSubSeriesLabel?: SubSeriesLabelAccessor; + customSeriesLabel?: SeriesLabelAccessor; } export interface Postfixes { diff --git a/stories/styling.tsx b/stories/styling.tsx index 8bc68026fb..c89589f6bf 100644 --- a/stories/styling.tsx +++ b/stories/styling.tsx @@ -32,8 +32,8 @@ import { palettes } from '../src/utils/themes/colors'; import { BarStyleAccessor, PointStyleAccessor, - SeriesStringPredicate, - SubSeriesLabelAccessor, + SeriesLabelMappingOptions, + SeriesLabelFn, } from '../src/chart_types/xy_chart/utils/specs'; import moment from 'moment'; import { DateTime } from 'luxon'; @@ -925,29 +925,15 @@ customSeriesStylesArea.story = { }; export const addCustomFullAndSubSeriesLabel = () => { - const customSeriesLabel: SeriesStringPredicate = ({ yAccessor, splitAccessors }) => { + const customSeriesLabelFn: SeriesLabelFn = ({ yAccessor, splitAccessors }) => { // eslint-disable-next-line react/prop-types if (yAccessor === 'y1' && splitAccessors.get('g') === 'a') { - return 'replace full series name'; + return 'Custom full series name'; } return null; }; - const customSubSeriesLabel: SubSeriesLabelAccessor = (accessor, key) => { - if (key) { - // split accessor; - if (accessor === 'a') { - return 'replace a(from g)'; - } - } else { - // y accessor; - if (accessor === 'y2') { - return 'replace y2'; - } - } - return null; - }; return ( @@ -967,17 +953,61 @@ export const addCustomFullAndSubSeriesLabel = () => { yAccessors={['y1', 'y2']} splitSeriesAccessors={['g']} data={TestDatasets.BARCHART_2Y1G} - customSeriesLabel={customSeriesLabel} - customSubSeriesLabel={customSubSeriesLabel} + customSeriesLabel={customSeriesLabelFn} /> ); }; addCustomFullAndSubSeriesLabel.story = { - name: 'Add custom full and sub series label', + name: 'Add custom series label', +}; + +export const customSeriesLabelMappings = () => { + const customSeriesLabelOptions: SeriesLabelMappingOptions = { + mappings: [ + { + // replace split accessor; + accessor: 'g', + value: 'a', + newValue: 'replace a(from g)', + }, + { + // replace y accessor; + value: 'y2', + newValue: 'replace y2', + }, + ], + delimiter: ' | ', + }; + return ( + + + + Number(d).toFixed(2)} + /> + + + + ); +}; +customSeriesLabelMappings.story = { + name: 'Add custom series label mappings and delimeter', }; -export const addCustomSubSeriesLabelFormatting = () => { +export const addCustomSeriesLabelFormatting = () => { const start = DateTime.fromISO('2019-01-01T00:00:00.000', { zone: 'utc' }); const data = [ { x: 1, y: 3, percent: 0.5, time: start.plus({ month: 1 }).toMillis() }, @@ -990,26 +1020,30 @@ export const addCustomSubSeriesLabelFormatting = () => { { x: 2, y: 18, percent: 1, time: start.plus({ month: 2 }).toMillis() }, { x: 3, y: 7, percent: 1, time: start.plus({ month: 3 }).toMillis() }, ]; - const customSubSeriesLabel: SubSeriesLabelAccessor = (accessor, key, isTooltip) => { - if (key === 'time') { - // Format time group - if (isTooltip) { - // Format tooltip time to be longer - return moment(accessor).format('ll'); - } + const customSeriesLabelFn: SeriesLabelFn = ({ yAccessor, splitAccessors }, isTooltip) => + [ + ...[...splitAccessors.entries()].map(([key, value]) => { + if (key === 'time') { + // Format time group + if (isTooltip) { + // Format tooltip time to be longer + return moment(value).format('ll'); + } - // Format legend to be shorter - return moment(accessor).format('M/YYYY'); - } + // Format legend to be shorter + return moment(value).format('M/YYYY'); + } - if (key === 'percent') { - // Format percent group - return `${(accessor as number) * 100}%`; - } + if (key === 'percent') { + // Format percent group + return `${(value as number) * 100}%`; + } - // don't format yAccessor - return null; - }; + return value; + }), + // don't format yAccessor + yAccessor, + ].join(' - '); return ( @@ -1030,13 +1064,13 @@ export const addCustomSubSeriesLabelFormatting = () => { yAccessors={['y']} splitSeriesAccessors={['time', 'percent']} data={data} - customSubSeriesLabel={customSubSeriesLabel} + customSeriesLabel={customSeriesLabelFn} /> ); }; -addCustomFullAndSubSeriesLabel.story = { - name: 'Add custom sub-series label formatting [time/date and percent]', +addCustomSeriesLabelFormatting.story = { + name: 'Add custom series label formatting (legend/tooltip) [time/date and percent]', }; export const tickLabelPaddingBothPropAndTheme = () => { From f056b77a4a6efa63aa2dcd1c8a76753f40acaebc Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Thu, 13 Feb 2020 13:01:36 +0100 Subject: [PATCH 06/15] chore: add parsing action and split tooltipValues (#516) --- .../xy_chart/renderer/dom/tooltips.tsx | 25 +++--- .../state/chart_state.interactions.test.ts | 40 ++++----- .../xy_chart/state/chart_state.specs.test.ts | 83 +++++++++++++++++++ .../state/chart_state.timescales.test.ts | 72 ++++++++-------- .../state/chart_state.tooltip.test.ts | 15 ++-- .../selectors/get_annotation_tooltip_state.ts | 7 +- .../selectors/get_legend_tooltip_values.ts | 4 +- .../get_tooltip_values_highlighted_geoms.ts | 27 ++++-- .../state/selectors/is_tooltip_visible.ts | 8 +- src/chart_types/xy_chart/tooltip/tooltip.ts | 7 +- src/specs/specs_parser.tsx | 5 +- src/state/actions/specs.ts | 16 +++- src/state/chart_state.ts | 17 ++-- 13 files changed, 218 insertions(+), 108 deletions(-) create mode 100644 src/chart_types/xy_chart/state/chart_state.specs.test.ts diff --git a/src/chart_types/xy_chart/renderer/dom/tooltips.tsx b/src/chart_types/xy_chart/renderer/dom/tooltips.tsx index ba1538e6c3..8d3d14b200 100644 --- a/src/chart_types/xy_chart/renderer/dom/tooltips.tsx +++ b/src/chart_types/xy_chart/renderer/dom/tooltips.tsx @@ -6,7 +6,7 @@ import { GlobalChartState, BackwardRef } from '../../../../state/chart_state'; import { isTooltipVisibleSelector } from '../../state/selectors/is_tooltip_visible'; import { getTooltipHeaderFormatterSelector } from '../../state/selectors/get_tooltip_header_formatter'; import { getTooltipPositionSelector } from '../../state/selectors/get_tooltip_position'; -import { getTooltipValuesSelector } from '../../state/selectors/get_tooltip_values_highlighted_geoms'; +import { getTooltipValuesSelector, TooltipData } from '../../state/selectors/get_tooltip_values_highlighted_geoms'; import { isInitialized } from '../../../../state/selectors/is_initialized'; import { createPortal } from 'react-dom'; import { getFinalTooltipPosition, TooltipPosition } from '../../crosshair/crosshair_utils'; @@ -15,7 +15,7 @@ import { isAnnotationTooltipVisibleSelector } from '../../state/selectors/is_ann interface TooltipStateProps { isTooltipVisible: boolean; isAnnotationTooltipVisible: boolean; - tooltipValues: TooltipValue[]; + tooltip: TooltipData; tooltipPosition: TooltipPosition | null; tooltipHeaderFormatter?: TooltipValueFormatter; } @@ -74,7 +74,7 @@ class TooltipsComponent extends React.Component { } } - renderHeader(headerData?: TooltipValue, formatter?: TooltipValueFormatter) { + renderHeader(headerData: TooltipValue | null, formatter?: TooltipValueFormatter) { if (!headerData) { return null; } @@ -83,21 +83,21 @@ class TooltipsComponent extends React.Component { } render() { - const { isTooltipVisible, tooltipValues, tooltipHeaderFormatter, isAnnotationTooltipVisible } = this.props; + const { isTooltipVisible, tooltip, tooltipHeaderFormatter, isAnnotationTooltipVisible } = this.props; if (!this.portalNode) { return null; } const { getChartContainerRef } = this.props; const chartContainerRef = getChartContainerRef(); - let tooltip; + let tooltipComponent; if (chartContainerRef.current === null || !isTooltipVisible || isAnnotationTooltipVisible) { return null; } else { - tooltip = ( + tooltipComponent = (
-
{this.renderHeader(tooltipValues[0], tooltipHeaderFormatter)}
+
{this.renderHeader(tooltip.header, tooltipHeaderFormatter)}
- {tooltipValues.slice(1).map(({ name, value, color, isHighlighted, seriesKey, yAccessor, isVisible }) => { + {tooltip.values.map(({ name, value, color, isHighlighted, seriesKey, yAccessor, isVisible }) => { if (!isVisible) { return null; } @@ -122,7 +122,7 @@ class TooltipsComponent extends React.Component {
); } - return createPortal(tooltip, this.portalNode); + return createPortal(tooltipComponent, this.portalNode); } } @@ -131,7 +131,10 @@ const mapStateToProps = (state: GlobalChartState): TooltipStateProps => { return { isTooltipVisible: false, isAnnotationTooltipVisible: false, - tooltipValues: [], + tooltip: { + header: null, + values: [], + }, tooltipPosition: null, tooltipHeaderFormatter: undefined, }; @@ -139,7 +142,7 @@ const mapStateToProps = (state: GlobalChartState): TooltipStateProps => { return { isTooltipVisible: isTooltipVisibleSelector(state), isAnnotationTooltipVisible: isAnnotationTooltipVisibleSelector(state), - tooltipValues: getTooltipValuesSelector(state), + tooltip: getTooltipValuesSelector(state), tooltipPosition: getTooltipPositionSelector(state), tooltipHeaderFormatter: getTooltipHeaderFormatterSelector(state), }; diff --git a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts index 0a1c80b5de..15adffbed6 100644 --- a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts @@ -222,7 +222,7 @@ describe('Chart state pointer interactions', () => { store.dispatch(onPointerMove({ x: 10, y: 10 + 70 }, 0)); const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); // no tooltip values exist if we have a TooltipType === None - expect(tooltipData.tooltipValues.length).toBe(0); + expect(tooltipData.tooltip.values.length).toBe(0); let isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(false); @@ -284,7 +284,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { onPointerMoveCaller(state); }); const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltipValues).toEqual([]); + expect(tooltipData.tooltip.values).toEqual([]); }); test('store is correctly configured', () => { @@ -299,13 +299,13 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(onPointerUpdateListener).toBeCalledTimes(1); const tooltipData1 = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData1.tooltipValues.length).toBe(2); + expect(tooltipData1.tooltip.values.length).toBe(1); // avoid calls store.dispatch(onPointerMove({ x: chartLeft + 12, y: chartTop + 12 }, 1)); expect(onPointerUpdateListener).toBeCalledTimes(1); const tooltipData2 = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData2.tooltipValues.length).toBe(2); + expect(tooltipData2.tooltip.values.length).toBe(1); expect(tooltipData1).toEqual(tooltipData2); }); @@ -385,7 +385,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { test('can hover top-left corner of the first bar', () => { let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltipValues).toEqual([]); + expect(tooltipData.tooltip.values).toEqual([]); store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 0 }, 0)); let projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); expect(projectedPointerPosition).toEqual({ x: 0, y: 0 }); @@ -396,7 +396,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { let isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltipValues.length).toBe(2); // x value + 1 y value + expect(tooltipData.tooltip.values.length).toBe(1); expect(tooltipData.highlightedGeometries.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(0); @@ -423,7 +423,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(false); tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltipValues.length).toBe(0); + expect(tooltipData.tooltip.values.length).toBe(0); expect(tooltipData.highlightedGeometries.length).toBe(0); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(1); @@ -441,7 +441,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(isTooltipVisible).toBe(true); let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); expect(tooltipData.highlightedGeometries.length).toBe(1); - expect(tooltipData.tooltipValues.length).toBe(2); // x value + 1 y value + expect(tooltipData.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(0); expect(onOverListener.mock.calls[0][0]).toEqual([ @@ -466,7 +466,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(false); tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltipValues.length).toBe(0); + expect(tooltipData.tooltip.values.length).toBe(0); expect(tooltipData.highlightedGeometries.length).toBe(0); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(1); @@ -488,7 +488,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(isTooltipVisible).toBe(true); let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); expect(tooltipData.highlightedGeometries.length).toBe(1); - expect(tooltipData.tooltipValues.length).toBe(2); + expect(tooltipData.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(0); expect(onOverListener.mock.calls[0][0]).toEqual([ @@ -518,7 +518,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltipValues.length).toBe(2); + expect(tooltipData.tooltip.values.length).toBe(1); expect(tooltipData.highlightedGeometries.length).toBe(0); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(1); @@ -540,7 +540,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(isTooltipVisible).toBe(true); let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); expect(tooltipData.highlightedGeometries.length).toBe(1); - expect(tooltipData.tooltipValues.length).toBe(2); + expect(tooltipData.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(0); expect(onOverListener.mock.calls[0][0]).toEqual([ @@ -570,7 +570,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltipValues.length).toBe(2); + expect(tooltipData.tooltip.values.length).toBe(1); // we are over the second bar here expect(tooltipData.highlightedGeometries.length).toBe(1); expect(onOverListener).toBeCalledTimes(2); @@ -601,7 +601,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(onOutListener).toBeCalledTimes(0); let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); expect(tooltipData.highlightedGeometries.length).toBe(0); - expect(tooltipData.tooltipValues.length).toBe(0); + expect(tooltipData.tooltip.values.length).toBe(0); store.dispatch(onPointerMove({ x: chartLeft + 89, y: chartTop + 0 }, 0)); const projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); @@ -615,7 +615,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(isTooltipVisible).toBe(true); tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); expect(tooltipData.highlightedGeometries.length).toBe(0); - expect(tooltipData.tooltipValues.length).toBe(2); + expect(tooltipData.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(0); expect(onOutListener).toBeCalledTimes(0); }); @@ -667,7 +667,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(isTooltipVisible).toBe(true); const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); expect(tooltipData.highlightedGeometries.length).toBe(1); - expect(tooltipData.tooltipValues.length).toBe(2); + expect(tooltipData.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOverListener.mock.calls[0][0]).toEqual([ [ @@ -744,8 +744,8 @@ function mouseOverTestSuite(scaleType: ScaleType) { test('chart 0 rotation', () => { store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 89 }, 0)); const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltipValues[0].value).toBe('bottom 0'); - expect(tooltipData.tooltipValues[1].value).toBe('left 10'); + expect(tooltipData.tooltip.header?.value).toBe('bottom 0'); + expect(tooltipData.tooltip.values[0].value).toBe('left 10'); }); test('chart 90 deg rotated', () => { @@ -758,8 +758,8 @@ function mouseOverTestSuite(scaleType: ScaleType) { store.dispatch(specParsed()); store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 89 }, 0)); const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltipValues[0].value).toBe('left 1'); - expect(tooltipData.tooltipValues[1].value).toBe('bottom 5'); + expect(tooltipData.tooltip.header?.value).toBe('left 1'); + expect(tooltipData.tooltip.values[0].value).toBe('bottom 5'); }); }); describe('brush', () => { diff --git a/src/chart_types/xy_chart/state/chart_state.specs.test.ts b/src/chart_types/xy_chart/state/chart_state.specs.test.ts new file mode 100644 index 0000000000..be8dd55cf6 --- /dev/null +++ b/src/chart_types/xy_chart/state/chart_state.specs.test.ts @@ -0,0 +1,83 @@ +import { GlobalChartState, chartStoreReducer } from '../../../state/chart_state'; +import { createStore, Store } from 'redux'; +import { upsertSpec, specParsed, specParsing } from '../../../state/actions/specs'; +import { MockSeriesSpec } from '../../../mocks/specs'; +import { getLegendItemsSelector } from '../../../state/selectors/get_legend_items'; + +const data = [ + { x: 0, y: 10 }, + { x: 1, y: 10 }, +]; + +describe('XYChart - specs ordering', () => { + let store: Store; + beforeEach(() => { + const storeReducer = chartStoreReducer('chartId'); + store = createStore(storeReducer); + store.dispatch(specParsing()); + }); + + it('the legend respect the insert [A, B, C] order', () => { + store.dispatch(specParsing()); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); + store.dispatch(specParsed()); + + const legendItems = getLegendItemsSelector(store.getState()); + const labels = [...legendItems.values()].map((item) => item.label); + expect(labels).toEqual(['A', 'B', 'C']); + }); + it('the legend respect the insert order [B, A, C]', () => { + store.dispatch(specParsing()); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); + store.dispatch(specParsed()); + const legendItems = getLegendItemsSelector(store.getState()); + const labels = [...legendItems.values()].map((item) => item.label); + expect(labels).toEqual(['B', 'A', 'C']); + }); + it('the legend respect the order when changing properties of existing specs', () => { + store.dispatch(specParsing()); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); + store.dispatch(specParsed()); + + let legendItems = getLegendItemsSelector(store.getState()); + let labels = [...legendItems.values()].map((item) => item.label); + expect(labels).toEqual(['A', 'B', 'C']); + + store.dispatch(specParsing()); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', name: 'B updated', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); + store.dispatch(specParsed()); + + legendItems = getLegendItemsSelector(store.getState()); + labels = [...legendItems.values()].map((item) => item.label); + expect(labels).toEqual(['A', 'B updated', 'C']); + }); + it('the legend respect the order when changing the order of the specs', () => { + store.dispatch(specParsing()); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); + store.dispatch(specParsed()); + + let legendItems = getLegendItemsSelector(store.getState()); + let labels = [...legendItems.values()].map((item) => item.label); + expect(labels).toEqual(['A', 'B', 'C']); + + store.dispatch(specParsing()); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); + store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); + store.dispatch(specParsed()); + + legendItems = getLegendItemsSelector(store.getState()); + labels = [...legendItems.values()].map((item) => item.label); + expect(labels).toEqual(['B', 'A', 'C']); + }); +}); diff --git a/src/chart_types/xy_chart/state/chart_state.timescales.test.ts b/src/chart_types/xy_chart/state/chart_state.timescales.test.ts index 83978d1b1f..677ec838f0 100644 --- a/src/chart_types/xy_chart/state/chart_state.timescales.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.timescales.test.ts @@ -69,20 +69,20 @@ describe('Render chart', () => { }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip - let tooltipData = getTooltipValuesSelector(store.getState()); - expect(tooltipData.length).toBe(2); // x value + y value - expect(tooltipData[0].value).toBe(day1); // x value - expect(tooltipData[1].value).toBe(10); // y value + let tooltip = getTooltipValuesSelector(store.getState()); + expect(tooltip.values.length).toBe(1); + expect(tooltip.header?.value).toBe(day1); + expect(tooltip.values[0].value).toBe(10); store.dispatch(onPointerMove({ x: 35, y: 10 }, 1)); // check second valid tooltip - tooltipData = getTooltipValuesSelector(store.getState()); - expect(tooltipData.length).toBe(2); // x value + y value - expect(tooltipData[0].value).toBe(day2); // x value - expect(tooltipData[1].value).toBe(22); // y value + tooltip = getTooltipValuesSelector(store.getState()); + expect(tooltip.values.length).toBe(1); + expect(tooltip.header?.value).toBe(day2); + expect(tooltip.values[0].value).toBe(22); store.dispatch(onPointerMove({ x: 76, y: 10 }, 2)); // check third valid tooltip - tooltipData = getTooltipValuesSelector(store.getState()); - expect(tooltipData.length).toBe(2); // x value + y value - expect(tooltipData[0].value).toBe(day3); // x value - expect(tooltipData[1].value).toBe(6); // y value + tooltip = getTooltipValuesSelector(store.getState()); + expect(tooltip.values.length).toBe(1); + expect(tooltip.header?.value).toBe(day3); + expect(tooltip.values[0].value).toBe(6); }); }); describe('line, utc-time, 5m interval', () => { @@ -138,20 +138,20 @@ describe('Render chart', () => { }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip - let tooltipData = getTooltipValuesSelector(store.getState()); - expect(tooltipData.length).toBe(2); // x value + y value - expect(tooltipData[0].value).toBe(date1); // x value - expect(tooltipData[1].value).toBe(10); // y value + let tooltip = getTooltipValuesSelector(store.getState()); + expect(tooltip.values.length).toBe(1); + expect(tooltip.header?.value).toBe(date1); + expect(tooltip.values[0].value).toBe(10); store.dispatch(onPointerMove({ x: 35, y: 10 }, 1)); // check second valid tooltip - tooltipData = getTooltipValuesSelector(store.getState()); - expect(tooltipData.length).toBe(2); // x value + y value - expect(tooltipData[0].value).toBe(date2); // x value - expect(tooltipData[1].value).toBe(22); // y value + tooltip = getTooltipValuesSelector(store.getState()); + expect(tooltip.values.length).toBe(1); + expect(tooltip.header?.value).toBe(date2); + expect(tooltip.values[0].value).toBe(22); store.dispatch(onPointerMove({ x: 76, y: 10 }, 2)); // check third valid tooltip - tooltipData = getTooltipValuesSelector(store.getState()); - expect(tooltipData.length).toBe(2); // x value + y value - expect(tooltipData[0].value).toBe(date3); // x value - expect(tooltipData[1].value).toBe(6); // y value + tooltip = getTooltipValuesSelector(store.getState()); + expect(tooltip.values.length).toBe(1); + expect(tooltip.header?.value).toBe(date3); + expect(tooltip.values[0].value).toBe(6); }); }); describe('line, non utc-time, 5m + 1s interval', () => { @@ -225,20 +225,20 @@ describe('Render chart', () => { }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip - let tooltipData = getTooltipValuesSelector(store.getState()); - expect(tooltipData.length).toBe(2); // x value + y value - expect(tooltipData[0].value).toBe(date1); // x value - expect(tooltipData[1].value).toBe(10); // y value + let tooltip = getTooltipValuesSelector(store.getState()); + expect(tooltip.values.length).toBe(1); + expect(tooltip.header?.value).toBe(date1); + expect(tooltip.values[0].value).toBe(10); store.dispatch(onPointerMove({ x: 35, y: 10 }, 1)); // check second valid tooltip - tooltipData = getTooltipValuesSelector(store.getState()); - expect(tooltipData.length).toBe(2); // x value + y value - expect(tooltipData[0].value).toBe(date2); // x value - expect(tooltipData[1].value).toBe(22); // y value + tooltip = getTooltipValuesSelector(store.getState()); + expect(tooltip.values.length).toBe(1); + expect(tooltip.header?.value).toBe(date2); + expect(tooltip.values[0].value).toBe(22); store.dispatch(onPointerMove({ x: 76, y: 10 }, 2)); // check third valid tooltip - tooltipData = getTooltipValuesSelector(store.getState()); - expect(tooltipData.length).toBe(2); // x value + y value - expect(tooltipData[0].value).toBe(date3); // x value - expect(tooltipData[1].value).toBe(6); // y value + tooltip = getTooltipValuesSelector(store.getState()); + expect(tooltip.values.length).toBe(1); + expect(tooltip.header?.value).toBe(date3); + expect(tooltip.values[0].value).toBe(6); }); }); }); diff --git a/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts b/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts index b03d06915e..c4c703f103 100644 --- a/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts @@ -28,12 +28,12 @@ describe('XYChart - State tooltips', () => { }); describe('should compute tooltip values depending on tooltip type', () => { - it.each<[TooltipType, number, number]>([ - [TooltipType.None, 0, 0], - [TooltipType.Follow, 1, 2], - [TooltipType.VerticalCursor, 1, 2], - [TooltipType.Crosshairs, 1, 2], - ])('tooltip type %s', (tooltipType, expectedHgeomsLength, expectedTooltipValuesLength) => { + it.each<[TooltipType, number, boolean, number]>([ + [TooltipType.None, 0, true, 0], + [TooltipType.Follow, 1, false, 1], + [TooltipType.VerticalCursor, 1, false, 1], + [TooltipType.Crosshairs, 1, false, 1], + ])('tooltip type %s', (tooltipType, expectedHgeomsLength, expectHeader, expectedTooltipValuesLength) => { store.dispatch(onPointerMove({ x: 25, y: 50 }, 0)); store.dispatch( upsertSpec( @@ -47,7 +47,8 @@ describe('XYChart - State tooltips', () => { store.dispatch(specParsed()); const state = store.getState(); const tooltipValues = getTooltipValuesAndGeometriesSelector(state); - expect(tooltipValues.tooltipValues).toHaveLength(expectedTooltipValuesLength); + expect(tooltipValues.tooltip.values).toHaveLength(expectedTooltipValuesLength); + expect(tooltipValues.tooltip.header === null).toBe(expectHeader); expect(tooltipValues.highlightedGeometries).toHaveLength(expectedHgeomsLength); }); }); diff --git a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts index 5dbb58c6f5..d776fe18c8 100644 --- a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts +++ b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts @@ -1,7 +1,6 @@ import createCachedSelector from 're-reselect'; import { Dimensions } from '../../../../utils/dimensions'; import { Point } from '../../../../utils/point'; -import { TooltipValue } from '../../utils/interactions'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; import { getAxisSpecsSelector, getAnnotationSpecsSelector } from './get_specs'; import { AxisSpec, AnnotationSpec, Rotation, AnnotationTypes } from '../../utils/specs'; @@ -15,7 +14,7 @@ import { getChartRotationSelector } from '../../../../state/selectors/get_chart_ import { AnnotationId } from '../../../../utils/ids'; import { computeSeriesGeometriesSelector } from './compute_series_geometries'; import { ComputedGeometries } from '../utils'; -import { getTooltipValuesSelector } from './get_tooltip_values_highlighted_geoms'; +import { getTooltipValuesSelector, TooltipData } from './get_tooltip_values_highlighted_geoms'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { GlobalChartState } from '../../../../state/chart_state'; @@ -47,7 +46,7 @@ function getAnnotationTooltipState( annotationSpecs: AnnotationSpec[], axesSpecs: AxisSpec[], annotationDimensions: Map, - tooltipValues: TooltipValue[], + tooltip: TooltipData, ): AnnotationTooltipState | null { // get positions relative to chart if (x < 0 || y < 0) { @@ -70,7 +69,7 @@ function getAnnotationTooltipState( ); // If there's a highlighted chart element tooltip value, don't show annotation tooltip - const isChartTooltipDisplayed = tooltipValues.some(({ isHighlighted }) => isHighlighted); + const isChartTooltipDisplayed = tooltip.values.some(({ isHighlighted }) => isHighlighted); if ( tooltipState && tooltipState.isVisible && diff --git a/src/chart_types/xy_chart/state/selectors/get_legend_tooltip_values.ts b/src/chart_types/xy_chart/state/selectors/get_legend_tooltip_values.ts index 6c4e692ad4..4f580c3d05 100644 --- a/src/chart_types/xy_chart/state/selectors/get_legend_tooltip_values.ts +++ b/src/chart_types/xy_chart/state/selectors/get_legend_tooltip_values.ts @@ -5,7 +5,7 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; export const getLegendTooltipValuesSelector = createCachedSelector( [getTooltipValuesSelector], - (tooltipData): Map => { - return getSeriesTooltipValues(tooltipData); + ({ values }): Map => { + return getSeriesTooltipValues(values); }, )(getChartIdSelector); diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts index 1b529e08bc..fc90708cc2 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts @@ -21,12 +21,19 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { hasSingleSeriesSelector } from './has_single_series'; const EMPTY_VALUES = Object.freeze({ - tooltipValues: [], + tooltip: { + header: null, + values: [], + }, highlightedGeometries: [], }); +export interface TooltipData { + header: TooltipValue | null; + values: TooltipValue[]; +} export interface TooltipAndHighlightedGeoms { - tooltipValues: TooltipValue[]; + tooltip: TooltipData; highlightedGeometries: IndexedGeometry[]; } @@ -84,7 +91,7 @@ function getTooltipAndHighlightFromXValue( } // build the tooltip value list - let xValueInfo: TooltipValue | null = null; + let tooltipHeader: TooltipValue | null = null; const highlightedGeometries: IndexedGeometry[] = []; const tooltipValues = xMatchingGeoms .filter(({ value: { y } }) => y !== null) @@ -134,27 +141,29 @@ function getTooltipAndHighlightFromXValue( ); // format only one time the x value - if (!xValueInfo) { + if (!tooltipHeader) { // if we have a tooltipHeaderFormatter, then don't pass in the xAxis as the user will define a formatter const xAxisFormatSpec = [0, 180].includes(chartRotation) ? xAxis : yAxis; const formatterAxis = tooltipHeaderFormatter ? undefined : xAxisFormatSpec; - xValueInfo = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis); - return [xValueInfo, ...acc, formattedTooltip]; + tooltipHeader = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis); } return [...acc, formattedTooltip]; }, []); return { - tooltipValues, + tooltip: { + header: tooltipHeader, + values: tooltipValues, + }, highlightedGeometries, }; } export const getTooltipValuesSelector = createCachedSelector( [getTooltipValuesAndGeometriesSelector], - (values): TooltipValue[] => { - return values.tooltipValues; + ({ tooltip }): TooltipData => { + return tooltip; }, )(getChartIdSelector); 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 931919ac5a..d9a659b796 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 @@ -1,10 +1,10 @@ import createCachedSelector from 're-reselect'; -import { TooltipType, TooltipValue, isTooltipType, isTooltipProps } from '../../utils/interactions'; +import { TooltipType, isTooltipType, isTooltipProps } from '../../utils/interactions'; import { Point } from '../../../../utils/point'; import { GlobalChartState, PointerStates } from '../../../../state/chart_state'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { getProjectedPointerPositionSelector } from './get_projected_pointer_position'; -import { getTooltipValuesSelector } from './get_tooltip_values_highlighted_geoms'; +import { getTooltipValuesSelector, TooltipData } from './get_tooltip_values_highlighted_geoms'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; const getTooltipType = (state: GlobalChartState): TooltipType | undefined => { @@ -30,13 +30,13 @@ function isTooltipVisible( tooltipType: TooltipType | undefined, pointer: PointerStates, projectedPointerPosition: Point, - tooltipValues: TooltipValue[], + tooltip: TooltipData, ) { return ( tooltipType !== TooltipType.None && pointer.down === null && projectedPointerPosition.x > -1 && projectedPointerPosition.y > -1 && - tooltipValues.length > 0 + tooltip.values.length > 0 ); } diff --git a/src/chart_types/xy_chart/tooltip/tooltip.ts b/src/chart_types/xy_chart/tooltip/tooltip.ts index b8b1d3f285..77ecaf985b 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.ts @@ -26,12 +26,7 @@ export function getSeriesTooltipValues( // map from seriesKey to TooltipLegendValue const seriesTooltipValues = new Map(); - // First TooltipLegendValue is the header - if (tooltipValues.length <= 1) { - return seriesTooltipValues; - } - - tooltipValues.slice(1).forEach(({ seriesKey, value, yAccessor }) => { + tooltipValues.forEach(({ seriesKey, value, yAccessor }) => { const seriesValue = defaultValue ? defaultValue : value; const current = seriesTooltipValues.get(seriesKey) || {}; diff --git a/src/specs/specs_parser.tsx b/src/specs/specs_parser.tsx index 8c4ae957e0..50ff4a21db 100644 --- a/src/specs/specs_parser.tsx +++ b/src/specs/specs_parser.tsx @@ -1,10 +1,11 @@ import React, { useEffect } from 'react'; import { bindActionCreators, Dispatch } from 'redux'; import { connect } from 'react-redux'; -import { specParsed, specUnmounted } from '../state/actions/specs'; +import { specParsing, specParsed, specUnmounted } from '../state/actions/specs'; export const SpecsParserComponent: React.FunctionComponent<{}> = (props) => { const injected = props as DispatchProps; + injected.specParsing(); useEffect(() => { injected.specParsed(); }); @@ -18,6 +19,7 @@ export const SpecsParserComponent: React.FunctionComponent<{}> = (props) => { }; interface DispatchProps { + specParsing: () => void; specParsed: () => void; specUnmounted: () => void; } @@ -25,6 +27,7 @@ interface DispatchProps { const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => bindActionCreators( { + specParsing, specParsed, specUnmounted, }, diff --git a/src/state/actions/specs.ts b/src/state/actions/specs.ts index 0bf622a9ea..23d8bc0a45 100644 --- a/src/state/actions/specs.ts +++ b/src/state/actions/specs.ts @@ -3,8 +3,13 @@ import { Spec } from '../../specs'; export const UPSERT_SPEC = 'UPSERT_SPEC'; export const REMOVE_SPEC = 'REMOVE_SPEC'; export const SPEC_PARSED = 'SPEC_PARSED'; +export const SPEC_PARSING = 'SPEC_PARSING'; export const SPEC_UNMOUNTED = 'SPEC_UNMOUNTED'; +interface SpecParsingAction { + type: typeof SPEC_PARSING; +} + interface SpecParsedAction { type: typeof SPEC_PARSED; } @@ -35,8 +40,17 @@ export function specParsed(): SpecParsedAction { return { type: SPEC_PARSED }; } +export function specParsing(): SpecParsingAction { + return { type: SPEC_PARSING }; +} + export function specUnmounted(): SpecUnmountedAction { return { type: SPEC_UNMOUNTED }; } -export type SpecActions = SpecParsedAction | SpecUnmountedAction | UpsertSpecAction | RemoveSpecAction; +export type SpecActions = + | SpecParsingAction + | SpecParsedAction + | SpecUnmountedAction + | UpsertSpecAction + | RemoveSpecAction; diff --git a/src/state/chart_state.ts b/src/state/chart_state.ts index ce5f3769aa..09cb75e537 100644 --- a/src/state/chart_state.ts +++ b/src/state/chart_state.ts @@ -1,4 +1,4 @@ -import { SPEC_PARSED, SPEC_UNMOUNTED, UPSERT_SPEC, REMOVE_SPEC } from './actions/specs'; +import { SPEC_PARSED, SPEC_UNMOUNTED, UPSERT_SPEC, REMOVE_SPEC, SPEC_PARSING } from './actions/specs'; import { interactionsReducer } from './reducers/interactions'; import { ChartTypes } from '../chart_types'; import { XYAxisChartState } from '../chart_types/xy_chart/state/chart_state'; @@ -142,6 +142,15 @@ export const chartStoreReducer = (chartId: string) => { const initialState = getInitialState(chartId); return (state = initialState, action: StateActions): GlobalChartState => { switch (action.type) { + case SPEC_PARSING: + return { + ...state, + specsInitialized: false, + chartRendered: false, + specs: { + [DEFAULT_SETTINGS_SPEC.id]: DEFAULT_SETTINGS_SPEC, + }, + }; case SPEC_PARSED: const chartType = findMainChartType(state.specs); @@ -150,7 +159,6 @@ export const chartStoreReducer = (chartId: string) => { return { ...state, specsInitialized: true, - chartRendered: false, chartType, internalChartState, }; @@ -158,7 +166,6 @@ export const chartStoreReducer = (chartId: string) => { return { ...state, specsInitialized: true, - chartRendered: false, chartType, }; } @@ -171,8 +178,6 @@ export const chartStoreReducer = (chartId: string) => { case UPSERT_SPEC: return { ...state, - specsInitialized: false, - chartRendered: false, specs: { ...state.specs, [action.spec.id]: action.spec, @@ -182,8 +187,6 @@ export const chartStoreReducer = (chartId: string) => { const { [action.id]: specToRemove, ...rest } = state.specs; return { ...state, - specsInitialized: false, - chartRendered: false, specs: { ...rest, }, From 2242fca6800995eaa8b7c4402cc8f82cad383c1c Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Fri, 14 Feb 2020 12:44:33 +0100 Subject: [PATCH 07/15] refactor: decouple legend and tooltip phase 1 (#491) Move `Datum`, `Rotation`, `Position` and `Color` to `utils/commons`. Decouple legend from axis position method and move the `scales` to `utils/scales`. --- .../layout/types/config_types.ts | 3 +- .../partition_chart/layout/types/types.ts | 2 - .../layout/types/viewmodel_types.ts | 3 +- .../partition_chart/specs/index.ts | 3 +- .../annotations/annotation_marker.test.tsx | 7 ++-- .../annotations/annotation_utils.test.ts | 7 +--- .../xy_chart/annotations/annotation_utils.ts | 5 +-- .../annotations/line_annotation_tooltip.ts | 5 +-- .../annotations/rect_annotation_tooltip.ts | 5 ++- .../crosshair_utils.linear_snap.test.ts | 2 +- .../crosshair_utils.ordinal_snap.test.ts | 2 +- .../xy_chart/crosshair/crosshair_utils.ts | 4 +- src/chart_types/xy_chart/domains/domain.ts | 2 +- .../xy_chart/domains/x_domain.test.ts | 2 +- src/chart_types/xy_chart/domains/x_domain.ts | 2 +- .../xy_chart/domains/y_domain.test.ts | 2 +- src/chart_types/xy_chart/domains/y_domain.ts | 2 +- .../xy_chart/legend/legend.test.ts | 5 ++- .../xy_chart/renderer/canvas/axes/line.ts | 2 +- .../xy_chart/renderer/canvas/axes/tick.ts | 2 +- .../xy_chart/renderer/canvas/axes/title.ts | 2 +- .../xy_chart/renderer/canvas/values/bar.ts | 2 +- .../xy_chart/renderer/canvas/xy_chart.tsx | 3 +- .../xy_chart/renderer/dom/crosshair.tsx | 2 +- .../xy_chart/renderer/dom/highlighter.tsx | 2 +- .../rendering/rendering.areas.test.ts | 2 +- .../rendering/rendering.bands.test.ts | 2 +- .../xy_chart/rendering/rendering.bars.test.ts | 2 +- .../rendering/rendering.lines.test.ts | 2 +- .../xy_chart/rendering/rendering.ts | 3 +- .../xy_chart/specs/area_series.tsx | 2 +- src/chart_types/xy_chart/specs/axis.tsx | 3 +- src/chart_types/xy_chart/specs/bar_series.tsx | 2 +- .../xy_chart/specs/histogram_bar_series.tsx | 2 +- .../xy_chart/specs/line_series.tsx | 2 +- .../state/chart_state.interactions.test.ts | 5 ++- .../xy_chart/state/chart_state.test.ts | 7 +--- .../state/chart_state.timescales.test.ts | 2 +- .../selectors/get_annotation_tooltip_state.ts | 3 +- .../state/selectors/get_cursor_band.ts | 2 +- .../get_tooltip_values_highlighted_geoms.ts | 3 +- .../state/selectors/is_brush_available.ts | 2 +- .../selectors/is_tooltip_snap_enabled.ts | 2 +- .../state/selectors/merge_y_custom_domains.ts | 3 +- .../state/selectors/on_pointer_move_caller.ts | 2 +- src/chart_types/xy_chart/state/utils.test.ts | 3 +- src/chart_types/xy_chart/state/utils.ts | 5 +-- .../xy_chart/tooltip/tooltip.test.ts | 5 ++- .../xy_chart/utils/axis_utils.test.ts | 5 ++- src/chart_types/xy_chart/utils/axis_utils.ts | 5 +-- .../xy_chart/utils/dimensions.test.ts | 3 +- src/chart_types/xy_chart/utils/dimensions.ts | 3 +- .../xy_chart/utils/fit_function.test.ts | 2 +- .../xy_chart/utils/fit_function.ts | 2 +- .../xy_chart/utils/interactions.ts | 3 +- .../utils/nonstacked_series_utils.test.ts | 2 +- .../xy_chart/utils/nonstacked_series_utils.ts | 2 +- src/chart_types/xy_chart/utils/scales.test.ts | 2 +- src/chart_types/xy_chart/utils/scales.ts | 4 +- src/chart_types/xy_chart/utils/series.test.ts | 2 +- src/chart_types/xy_chart/utils/series.ts | 4 +- src/chart_types/xy_chart/utils/specs.ts | 22 +--------- .../stacked_percent_series_utils.test.ts | 2 +- .../utils/stacked_series_utils.test.ts | 2 +- .../xy_chart/utils/stacked_series_utils.ts | 2 +- src/components/chart.tsx | 3 +- src/components/legend/legend.test.tsx | 2 +- src/components/legend/legend.tsx | 41 ++++++++----------- src/components/legend/legend_item.tsx | 2 +- src/index.ts | 8 ++-- src/mocks/scale/scale.ts | 2 +- src/mocks/specs/specs.ts | 5 +-- .../scales/scales.ts => scales/index.ts} | 27 ++++++------ src/{utils => }/scales/scale_band.test.ts | 2 +- src/{utils => }/scales/scale_band.ts | 4 +- .../scales/scale_continuous.test.ts | 10 ++--- src/{utils => }/scales/scale_continuous.ts | 10 ++--- src/{utils => }/scales/scale_time.test.ts | 3 +- src/{utils => }/scales/scales.test.ts | 3 +- src/specs/settings.test.tsx | 2 +- src/specs/settings.tsx | 7 ++-- src/state/selectors/get_chart_rotation.ts | 2 +- src/utils/accessor.ts | 2 +- src/utils/commons.ts | 21 ++++++++-- src/utils/domain.ts | 4 +- src/utils/events.ts | 2 +- stories/annotations.tsx | 2 +- stories/utils/utils.ts | 2 +- 88 files changed, 175 insertions(+), 202 deletions(-) rename src/{utils/scales/scales.ts => scales/index.ts} (68%) rename src/{utils => }/scales/scale_band.test.ts (99%) rename src/{utils => }/scales/scale_band.ts (96%) rename src/{utils => }/scales/scale_continuous.test.ts (98%) rename src/{utils => }/scales/scale_continuous.ts (97%) rename src/{utils => }/scales/scale_time.test.ts (99%) rename src/{utils => }/scales/scales.test.ts (99%) diff --git a/src/chart_types/partition_chart/layout/types/config_types.ts b/src/chart_types/partition_chart/layout/types/config_types.ts index 52d0f6bad7..b855f8dd8c 100644 --- a/src/chart_types/partition_chart/layout/types/config_types.ts +++ b/src/chart_types/partition_chart/layout/types/config_types.ts @@ -1,6 +1,7 @@ import { Distance, Pixels, Radian, Radius, Ratio, SizeRatio, TimeMs } from './geometry_types'; -import { Color, Font, FontFamily, PartialFont } from './types'; +import { Font, FontFamily, PartialFont } from './types'; import { $Values as Values } from 'utility-types'; +import { Color } from '../../../../utils/commons'; export const PartitionLayout = Object.freeze({ sunburst: 'sunburst', diff --git a/src/chart_types/partition_chart/layout/types/types.ts b/src/chart_types/partition_chart/layout/types/types.ts index daa713563e..c1003a412d 100644 --- a/src/chart_types/partition_chart/layout/types/types.ts +++ b/src/chart_types/partition_chart/layout/types/types.ts @@ -1,7 +1,5 @@ import { ArrayEntry } from '../utils/group_by_rollup'; -export type Color = string; // todo refine later (union type) - export const FONT_VARIANTS = Object.freeze(['normal', 'small-caps'] as const); export type FontVariant = typeof FONT_VARIANTS[number]; 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 31f64dc984..03e2061630 100644 --- a/src/chart_types/partition_chart/layout/types/viewmodel_types.ts +++ b/src/chart_types/partition_chart/layout/types/viewmodel_types.ts @@ -1,8 +1,9 @@ import { Config } from './config_types'; import { Coordinate, Distance, PointObject, PointTuple, Radian } from './geometry_types'; -import { Color, Font } from './types'; +import { Font } from './types'; import { config } from '../config/config'; import { ArrayNode } from '../utils/group_by_rollup'; +import { Color } from '../../../../utils/commons'; export type LinkLabelVM = { link: [PointTuple, ...PointTuple[]]; // at least one point diff --git a/src/chart_types/partition_chart/specs/index.ts b/src/chart_types/partition_chart/specs/index.ts index 5a2bf655ad..36c35a48a1 100644 --- a/src/chart_types/partition_chart/specs/index.ts +++ b/src/chart_types/partition_chart/specs/index.ts @@ -5,8 +5,7 @@ import { getConnect, specComponentFactory } from '../../../state/spec_factory'; import { AccessorFn, IndexedAccessorFn } from '../../../utils/accessor'; import { Spec, SpecTypes } from '../../../specs/index'; import { Config, FillLabelConfig } from '../layout/types/config_types'; -import { RecursivePartial } from '../../../utils/commons'; -import { Datum } from '../../../utils/domain'; +import { RecursivePartial, Datum } from '../../../utils/commons'; type ColorAccessor = (d: Datum, index: number, array: Datum[]) => string; diff --git a/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx b/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx index c0da56e403..eb9f2dd9ce 100644 --- a/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx +++ b/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx @@ -1,11 +1,10 @@ import * as React from 'react'; - -import { AnnotationDomainTypes, AnnotationSpec, Position, Rotation, AnnotationTypes } from '../utils/specs'; +import { AnnotationDomainTypes, AnnotationSpec, AnnotationTypes } from '../utils/specs'; +import { Position, Rotation } from '../../../utils/commons'; import { DEFAULT_ANNOTATION_LINE_STYLE } from '../../../utils/themes/theme'; import { Dimensions } from '../../../utils/dimensions'; import { GroupId } from '../../../utils/ids'; -import { ScaleContinuous } from '../../../utils/scales/scale_continuous'; -import { Scale, ScaleType } from '../../../utils/scales/scales'; +import { Scale, ScaleType, ScaleContinuous } from '../../../scales'; import { computeLineAnnotationDimensions, AnnotationLineProps } from './line_annotation_tooltip'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; diff --git a/src/chart_types/xy_chart/annotations/annotation_utils.test.ts b/src/chart_types/xy_chart/annotations/annotation_utils.test.ts index 98541fcf8f..c34257e562 100644 --- a/src/chart_types/xy_chart/annotations/annotation_utils.test.ts +++ b/src/chart_types/xy_chart/annotations/annotation_utils.test.ts @@ -4,17 +4,14 @@ import { AnnotationSpec, AxisSpec, LineAnnotationSpec, - Position, RectAnnotationSpec, - Rotation, AnnotationTypes, } from '../utils/specs'; +import { Position, Rotation } from '../../../utils/commons'; import { DEFAULT_ANNOTATION_LINE_STYLE } from '../../../utils/themes/theme'; import { Dimensions } from '../../../utils/dimensions'; import { getAxisId, getGroupId, GroupId, AnnotationId } from '../../../utils/ids'; -import { ScaleBand } from '../../../utils/scales/scale_band'; -import { ScaleContinuous } from '../../../utils/scales/scale_continuous'; -import { Scale, ScaleType } from '../../../utils/scales/scales'; +import { Scale, ScaleType, ScaleBand, ScaleContinuous } from '../../../scales'; import { computeAnnotationDimensions, computeAnnotationTooltipState, diff --git a/src/chart_types/xy_chart/annotations/annotation_utils.ts b/src/chart_types/xy_chart/annotations/annotation_utils.ts index 71b6f7bdd2..fd7463e263 100644 --- a/src/chart_types/xy_chart/annotations/annotation_utils.ts +++ b/src/chart_types/xy_chart/annotations/annotation_utils.ts @@ -7,12 +7,10 @@ import { HistogramModeAlignments, isLineAnnotation, isRectAnnotation, - Position, - Rotation, } from '../utils/specs'; import { Dimensions } from '../../../utils/dimensions'; import { AnnotationId, GroupId } from '../../../utils/ids'; -import { Scale, ScaleType } from '../../../utils/scales/scales'; +import { Scale, ScaleType } from '../../../scales'; import { computeXScaleOffset, getAxesSpecForSpecId, isHorizontalRotation, getSpecsById } from '../state/utils'; import { Point } from '../../../utils/point'; import { @@ -25,6 +23,7 @@ import { AnnotationRectProps, computeRectAnnotationDimensions, } from './rect_annotation_tooltip'; +import { Rotation, Position } from '../../../utils/commons'; export type AnnotationTooltipFormatter = (details?: string) => JSX.Element | null; diff --git a/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts index 6b681b98cc..82ace9d8f8 100644 --- a/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts +++ b/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts @@ -2,12 +2,11 @@ import { AnnotationDomainType, AnnotationDomainTypes, AnnotationTypes, - Position, - Rotation, LineAnnotationSpec, LineAnnotationDatum, AxisSpec, } from '../utils/specs'; +import { Position, Rotation } from '../../../utils/commons'; import { AnnotationTooltipState, AnnotationDetails, @@ -19,7 +18,7 @@ import { import { isHorizontalRotation, getAxesSpecForSpecId } from '../state/utils'; import { isHorizontalAxis } from '../utils/axis_utils'; import { Dimensions } from '../../../utils/dimensions'; -import { Scale } from '../../../utils/scales/scales'; +import { Scale } from '../../../scales'; import { GroupId } from '../../../utils/ids'; import { LineAnnotationStyle } from '../../../utils/themes/theme'; import { Point } from '../../../utils/point'; diff --git a/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts index 1794af0fa9..958aa10a68 100644 --- a/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts +++ b/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts @@ -1,7 +1,8 @@ -import { AnnotationTypes, RectAnnotationDatum, RectAnnotationSpec, Rotation } from '../utils/specs'; +import { AnnotationTypes, RectAnnotationDatum, RectAnnotationSpec } from '../utils/specs'; +import { Rotation } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; import { GroupId } from '../../../utils/ids'; -import { Scale } from '../../../utils/scales/scales'; +import { Scale } from '../../../scales'; import { Point } from '../../../utils/point'; import { AnnotationTooltipFormatter, diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts index 938926965a..d0c4dc4020 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts @@ -2,7 +2,7 @@ import { computeXScale } from '../utils/scales'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; import { Dimensions } from '../../../utils/dimensions'; import { getGroupId, getSpecId } from '../../../utils/ids'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { getCursorBandPosition, getSnapPosition } from './crosshair_utils'; import { computeSeriesDomains } from '../state/utils'; import { ChartTypes } from '../..'; diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.ordinal_snap.test.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.ordinal_snap.test.ts index e9527a8ffa..e53d6f5e29 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.ordinal_snap.test.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.ordinal_snap.test.ts @@ -1,7 +1,7 @@ import { computeXScale } from '../utils/scales'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; import { getGroupId, getSpecId } from '../../../utils/ids'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { getSnapPosition } from './crosshair_utils'; import { computeSeriesDomains } from '../state/utils'; import { ChartTypes } from '../..'; diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts index f1ccff6fd9..3852d8416b 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts @@ -1,6 +1,6 @@ -import { Rotation } from '../utils/specs'; +import { Rotation } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; -import { Scale } from '../../../utils/scales/scales'; +import { Scale } from '../../../scales'; import { isHorizontalRotation } from '../state/utils'; import { Point } from '../../../utils/point'; diff --git a/src/chart_types/xy_chart/domains/domain.ts b/src/chart_types/xy_chart/domains/domain.ts index 2278b352bf..c60c93017e 100644 --- a/src/chart_types/xy_chart/domains/domain.ts +++ b/src/chart_types/xy_chart/domains/domain.ts @@ -1,5 +1,5 @@ import { Domain } from '../../../utils/domain'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; export interface BaseDomain { scaleType: ScaleType; diff --git a/src/chart_types/xy_chart/domains/x_domain.test.ts b/src/chart_types/xy_chart/domains/x_domain.test.ts index ede1a8bcea..e32192bb84 100644 --- a/src/chart_types/xy_chart/domains/x_domain.test.ts +++ b/src/chart_types/xy_chart/domains/x_domain.test.ts @@ -1,4 +1,4 @@ -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { getSplittedSeries } from '../utils/series'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; import { convertXScaleTypes, findMinInterval, mergeXDomain } from './x_domain'; diff --git a/src/chart_types/xy_chart/domains/x_domain.ts b/src/chart_types/xy_chart/domains/x_domain.ts index f2aef2bd2f..5f8785994b 100644 --- a/src/chart_types/xy_chart/domains/x_domain.ts +++ b/src/chart_types/xy_chart/domains/x_domain.ts @@ -1,7 +1,7 @@ import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_utils'; import { compareByValueAsc, identity, isNumberArray } from '../../../utils/commons'; import { computeContinuousDataDomain, computeOrdinalDataDomain, Domain } from '../../../utils/domain'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { BasicSeriesSpec, DomainRange, SeriesTypes } from '../utils/specs'; import { BaseDomain } from './domain'; diff --git a/src/chart_types/xy_chart/domains/y_domain.test.ts b/src/chart_types/xy_chart/domains/y_domain.test.ts index ed0831b80a..3527b31ee2 100644 --- a/src/chart_types/xy_chart/domains/y_domain.test.ts +++ b/src/chart_types/xy_chart/domains/y_domain.test.ts @@ -1,4 +1,4 @@ -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { RawDataSeries } from '../utils/series'; import { BasicSeriesSpec, DomainRange, SeriesTypes } from '../utils/specs'; import { BARCHART_1Y0G } from '../../../utils/data_samples/test_dataset'; diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts index cd630c1ed8..9f01c0f135 100644 --- a/src/chart_types/xy_chart/domains/y_domain.ts +++ b/src/chart_types/xy_chart/domains/y_domain.ts @@ -1,6 +1,6 @@ import { BasicSeriesSpec, DomainRange, DEFAULT_GLOBAL_ID, SeriesTypes } from '../utils/specs'; import { GroupId, SpecId, getGroupId } from '../../../utils/ids'; -import { ScaleContinuousType, ScaleType } from '../../../utils/scales/scales'; +import { ScaleContinuousType, ScaleType } from '../../../scales'; import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_utils'; import { BaseDomain } from './domain'; import { RawDataSeries } from '../utils/series'; diff --git a/src/chart_types/xy_chart/legend/legend.test.ts b/src/chart_types/xy_chart/legend/legend.test.ts index f05b317bd0..ea36a3497e 100644 --- a/src/chart_types/xy_chart/legend/legend.test.ts +++ b/src/chart_types/xy_chart/legend/legend.test.ts @@ -1,8 +1,9 @@ import { getAxisId, getGroupId, getSpecId } from '../../../utils/ids'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { computeLegend } from './legend'; import { SeriesCollectionValue, getSeriesLabel } from '../utils/series'; -import { AxisSpec, BasicSeriesSpec, Position, SeriesTypes } from '../utils/specs'; +import { AxisSpec, BasicSeriesSpec, SeriesTypes } from '../utils/specs'; +import { Position } from '../../../utils/commons'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; diff --git a/src/chart_types/xy_chart/renderer/canvas/axes/line.ts b/src/chart_types/xy_chart/renderer/canvas/axes/line.ts index 0e285a572e..20112ed41c 100644 --- a/src/chart_types/xy_chart/renderer/canvas/axes/line.ts +++ b/src/chart_types/xy_chart/renderer/canvas/axes/line.ts @@ -1,6 +1,6 @@ import { isVerticalAxis } from '../../../utils/axis_utils'; import { AxisProps } from '.'; -import { Position } from '../../../utils/specs'; +import { Position } from '../../../../../utils/commons'; export function renderLine(ctx: CanvasRenderingContext2D, props: AxisProps) { const { diff --git a/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts b/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts index 6feeb4a133..c70f2f03f2 100644 --- a/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts +++ b/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts @@ -1,6 +1,6 @@ import { AxisTick, isVerticalAxis } from '../../../utils/axis_utils'; import { AxisProps } from '.'; -import { Position } from '../../../utils/specs'; +import { Position } from '../../../../../utils/commons'; import { TickStyle } from '../../../../../utils/themes/theme'; import { renderLine, MIN_STROKE_WIDTH } from '../primitives/line'; import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils'; diff --git a/src/chart_types/xy_chart/renderer/canvas/axes/title.ts b/src/chart_types/xy_chart/renderer/canvas/axes/title.ts index 5787cdac48..a9ad66f5ac 100644 --- a/src/chart_types/xy_chart/renderer/canvas/axes/title.ts +++ b/src/chart_types/xy_chart/renderer/canvas/axes/title.ts @@ -2,7 +2,7 @@ import { AxisProps } from '.'; import { isHorizontalAxis } from '../../../utils/axis_utils'; import { renderDebugRect } from '../utils/debug'; import { renderText } from '../primitives/text'; -import { Position } from '../../../utils/specs'; +import { Position } from '../../../../../utils/commons'; import { Font, FontStyle } from '../../../../partition_chart/layout/types/types'; export function renderTitle(ctx: CanvasRenderingContext2D, props: AxisProps) { diff --git a/src/chart_types/xy_chart/renderer/canvas/values/bar.ts b/src/chart_types/xy_chart/renderer/canvas/values/bar.ts index 9f48cd3bb1..d43ae48403 100644 --- a/src/chart_types/xy_chart/renderer/canvas/values/bar.ts +++ b/src/chart_types/xy_chart/renderer/canvas/values/bar.ts @@ -1,4 +1,4 @@ -import { Rotation } from '../../../utils/specs'; +import { Rotation } from '../../../../../utils/commons'; import { Dimensions } from '../../../../../utils/dimensions'; import { Theme } from '../../../../../utils/themes/theme'; import { BarGeometry } from '../../../../../utils/geometry'; diff --git a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx index 65d4a71f9c..49646a7b9a 100644 --- a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx +++ b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx @@ -24,10 +24,11 @@ import { getHighlightedSeriesSelector } from '../../state/selectors/get_highligh import { getAnnotationSpecsSelector, getAxisSpecsSelector } from '../../state/selectors/get_specs'; import { Geometries, Transform } from '../../state/utils'; import { AxisLinePosition, AxisTicksDimensions } from '../../utils/axis_utils'; -import { AxisSpec, Rotation, AnnotationSpec } from '../../utils/specs'; +import { AxisSpec, AnnotationSpec } from '../../utils/specs'; import { renderXYChartCanvas2d } from './renderers'; import { isChartEmptySelector } from '../../state/selectors/is_chart_empty'; import { deepEqual } from '../../../../utils/fast_deep_equal'; +import { Rotation } from '../../../../utils/commons'; export interface ReactiveChartStateProps { initialized: boolean; diff --git a/src/chart_types/xy_chart/renderer/dom/crosshair.tsx b/src/chart_types/xy_chart/renderer/dom/crosshair.tsx index fac0424e9e..eef4518d06 100644 --- a/src/chart_types/xy_chart/renderer/dom/crosshair.tsx +++ b/src/chart_types/xy_chart/renderer/dom/crosshair.tsx @@ -4,7 +4,7 @@ import { TooltipType } from '../../utils/interactions'; import { isHorizontalRotation } from '../../state/utils'; import { Dimensions } from '../../../../utils/dimensions'; import { Theme } from '../../../../utils/themes/theme'; -import { Rotation } from '../../../../chart_types/xy_chart/utils/specs'; +import { Rotation } from '../../../../utils/commons'; import { GlobalChartState } from '../../../../state/chart_state'; import { isInitialized } from '../../../../state/selectors/is_initialized'; import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; diff --git a/src/chart_types/xy_chart/renderer/dom/highlighter.tsx b/src/chart_types/xy_chart/renderer/dom/highlighter.tsx index 21e4c3aeb2..4ffb245549 100644 --- a/src/chart_types/xy_chart/renderer/dom/highlighter.tsx +++ b/src/chart_types/xy_chart/renderer/dom/highlighter.tsx @@ -6,7 +6,7 @@ import { isInitialized } from '../../../../state/selectors/is_initialized'; import { computeChartTransformSelector } from '../../state/selectors/compute_chart_transform'; import { getHighlightedGeomsSelector } from '../../state/selectors/get_tooltip_values_highlighted_geoms'; import { Dimensions } from '../../../../utils/dimensions'; -import { Rotation } from '../../../../chart_types/xy_chart/utils/specs'; +import { Rotation } from '../../../../utils/commons'; import { Transform } from '../../state/utils'; import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; import { computeChartDimensionsSelector } from '../../state/selectors/compute_chart_dimensions'; diff --git a/src/chart_types/xy_chart/rendering/rendering.areas.test.ts b/src/chart_types/xy_chart/rendering/rendering.areas.test.ts index d720d447ef..c660ad60e6 100644 --- a/src/chart_types/xy_chart/rendering/rendering.areas.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.areas.test.ts @@ -1,4 +1,4 @@ -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { CurveType } from '../../../utils/curves'; import { IndexedGeometry, PointGeometry, AreaGeometry } from '../../../utils/geometry'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; diff --git a/src/chart_types/xy_chart/rendering/rendering.bands.test.ts b/src/chart_types/xy_chart/rendering/rendering.bands.test.ts index 1f9fa55501..3cff91dff8 100644 --- a/src/chart_types/xy_chart/rendering/rendering.bands.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.bands.test.ts @@ -1,5 +1,5 @@ import { computeSeriesDomains } from '../state/utils'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { CurveType } from '../../../utils/curves'; import { renderArea, renderBars } from './rendering'; import { computeXScale, computeYScales } from '../utils/scales'; diff --git a/src/chart_types/xy_chart/rendering/rendering.bars.test.ts b/src/chart_types/xy_chart/rendering/rendering.bars.test.ts index d9aa07feea..ad23ce4625 100644 --- a/src/chart_types/xy_chart/rendering/rendering.bars.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.bars.test.ts @@ -1,6 +1,6 @@ import { computeSeriesDomains } from '../state/utils'; import { identity } from '../../../utils/commons'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { renderBars } from './rendering'; import { computeXScale, computeYScales } from '../utils/scales'; import { BarSeriesSpec, DomainRange, SeriesTypes } from '../utils/specs'; diff --git a/src/chart_types/xy_chart/rendering/rendering.lines.test.ts b/src/chart_types/xy_chart/rendering/rendering.lines.test.ts index f89a96084c..697b2a5ee1 100644 --- a/src/chart_types/xy_chart/rendering/rendering.lines.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.lines.test.ts @@ -1,5 +1,5 @@ import { computeSeriesDomains } from '../state/utils'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { CurveType } from '../../../utils/curves'; import { renderLine } from './rendering'; import { computeXScale, computeYScales } from '../utils/scales'; diff --git a/src/chart_types/xy_chart/rendering/rendering.ts b/src/chart_types/xy_chart/rendering/rendering.ts index a30aae9bef..3d006d0937 100644 --- a/src/chart_types/xy_chart/rendering/rendering.ts +++ b/src/chart_types/xy_chart/rendering/rendering.ts @@ -8,8 +8,7 @@ import { BarSeriesStyle, GeometryStateStyle, } from '../../../utils/themes/theme'; -import { isLogarithmicScale } from '../../../utils/scales/scale_continuous'; -import { Scale, ScaleType } from '../../../utils/scales/scales'; +import { Scale, ScaleType, isLogarithmicScale } from '../../../scales'; import { CurveType, getCurveFactory } from '../../../utils/curves'; import { DataSeriesDatum, SeriesIdentifier, DataSeries } from '../utils/series'; import { DisplayValueSpec, PointStyleAccessor, BarStyleAccessor } from '../utils/specs'; diff --git a/src/chart_types/xy_chart/specs/area_series.tsx b/src/chart_types/xy_chart/specs/area_series.tsx index c4540ecf4a..1edadc49a5 100644 --- a/src/chart_types/xy_chart/specs/area_series.tsx +++ b/src/chart_types/xy_chart/specs/area_series.tsx @@ -1,5 +1,5 @@ import { AreaSeriesSpec, HistogramModeAlignments, DEFAULT_GLOBAL_ID, SeriesTypes } from '../utils/specs'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { specComponentFactory, getConnect } from '../../../state/spec_factory'; import { ChartTypes } from '../../../chart_types'; import { SpecTypes } from '../../../specs/settings'; diff --git a/src/chart_types/xy_chart/specs/axis.tsx b/src/chart_types/xy_chart/specs/axis.tsx index cda9cf5bdc..66d892751c 100644 --- a/src/chart_types/xy_chart/specs/axis.tsx +++ b/src/chart_types/xy_chart/specs/axis.tsx @@ -1,4 +1,5 @@ -import { AxisSpec, Position, DEFAULT_GLOBAL_ID } from '../utils/specs'; +import { AxisSpec, DEFAULT_GLOBAL_ID } from '../utils/specs'; +import { Position } from '../../../utils/commons'; import { ChartTypes } from '../../../chart_types'; import { specComponentFactory, getConnect } from '../../../state/spec_factory'; import { SpecTypes } from '../../../specs/settings'; diff --git a/src/chart_types/xy_chart/specs/bar_series.tsx b/src/chart_types/xy_chart/specs/bar_series.tsx index 51863fde66..a1a887e38d 100644 --- a/src/chart_types/xy_chart/specs/bar_series.tsx +++ b/src/chart_types/xy_chart/specs/bar_series.tsx @@ -1,5 +1,5 @@ import { BarSeriesSpec, DEFAULT_GLOBAL_ID, SeriesTypes } from '../utils/specs'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { ChartTypes } from '../../../chart_types'; import { specComponentFactory, getConnect } from '../../../state/spec_factory'; import { SpecTypes } from '../../../specs/settings'; diff --git a/src/chart_types/xy_chart/specs/histogram_bar_series.tsx b/src/chart_types/xy_chart/specs/histogram_bar_series.tsx index 00c6a8cd0f..c217566cf2 100644 --- a/src/chart_types/xy_chart/specs/histogram_bar_series.tsx +++ b/src/chart_types/xy_chart/specs/histogram_bar_series.tsx @@ -1,5 +1,5 @@ import { HistogramBarSeriesSpec, DEFAULT_GLOBAL_ID, SeriesTypes } from '../utils/specs'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { specComponentFactory, getConnect } from '../../../state/spec_factory'; import { ChartTypes } from '../../../chart_types'; import { SpecTypes } from '../../../specs/settings'; diff --git a/src/chart_types/xy_chart/specs/line_series.tsx b/src/chart_types/xy_chart/specs/line_series.tsx index 2d654b44d5..8bd029a074 100644 --- a/src/chart_types/xy_chart/specs/line_series.tsx +++ b/src/chart_types/xy_chart/specs/line_series.tsx @@ -1,5 +1,5 @@ import { LineSeriesSpec, DEFAULT_GLOBAL_ID, HistogramModeAlignments, SeriesTypes } from '../utils/specs'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { ChartTypes } from '../../../chart_types'; import { specComponentFactory, getConnect } from '../../../state/spec_factory'; import { SpecTypes } from '../../../specs/settings'; diff --git a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts index 15adffbed6..888c334603 100644 --- a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts @@ -1,7 +1,8 @@ import { createStore, Store } from 'redux'; -import { BarSeriesSpec, BasicSeriesSpec, AxisSpec, Position, SeriesTypes } from '../utils/specs'; +import { BarSeriesSpec, BasicSeriesSpec, AxisSpec, SeriesTypes } from '../utils/specs'; +import { Position } from '../../../utils/commons'; import { TooltipType } from '../utils/interactions'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { chartStoreReducer, GlobalChartState } from '../../../state/chart_state'; import { SettingsSpec, DEFAULT_SETTINGS_SPEC, SpecTypes } from '../../../specs'; import { computeSeriesGeometriesSelector } from './selectors/compute_series_geometries'; diff --git a/src/chart_types/xy_chart/state/chart_state.test.ts b/src/chart_types/xy_chart/state/chart_state.test.ts index 6a603c306c..cff92ad127 100644 --- a/src/chart_types/xy_chart/state/chart_state.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.test.ts @@ -4,15 +4,12 @@ import { AnnotationTypes, AxisSpec, BarSeriesSpec, - Position, RectAnnotationSpec, SeriesTypes, } from '../utils/specs'; +import { Position } from '../../../utils/commons'; import { TooltipType, TooltipValue } from '../utils/interactions'; -import { ScaleBand } from '../../../utils/scales/scale_band'; -import { ScaleContinuous } from '../../../utils/scales/scale_continuous'; -import { ScaleType } from '../../../utils/scales/scales'; -// import { ChartStore } from './chart_state'; +import { ScaleType, ScaleContinuous, ScaleBand } from '../../../scales'; import { IndexedGeometry, GeometryValue, BandedAccessorType } from '../../../utils/geometry'; import { AxisTicksDimensions, isDuplicateAxis } from '../utils/axis_utils'; import { AxisId } from '../../../utils/ids'; diff --git a/src/chart_types/xy_chart/state/chart_state.timescales.test.ts b/src/chart_types/xy_chart/state/chart_state.timescales.test.ts index 677ec838f0..d6c0540ed5 100644 --- a/src/chart_types/xy_chart/state/chart_state.timescales.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.timescales.test.ts @@ -1,5 +1,5 @@ import { LineSeriesSpec, SeriesTypes } from '../utils/specs'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { createStore, Store } from 'redux'; import { chartStoreReducer, GlobalChartState } from '../../../state/chart_state'; import { upsertSpec, specParsed } from '../../../state/actions/specs'; diff --git a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts index d776fe18c8..d3dde59ab8 100644 --- a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts +++ b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts @@ -3,7 +3,8 @@ import { Dimensions } from '../../../../utils/dimensions'; import { Point } from '../../../../utils/point'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; import { getAxisSpecsSelector, getAnnotationSpecsSelector } from './get_specs'; -import { AxisSpec, AnnotationSpec, Rotation, AnnotationTypes } from '../../utils/specs'; +import { AxisSpec, AnnotationSpec, AnnotationTypes } from '../../utils/specs'; +import { Rotation } from '../../../../utils/commons'; import { computeAnnotationTooltipState, AnnotationTooltipState, diff --git a/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts b/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts index d708da0ab6..72eda830de 100644 --- a/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts +++ b/src/chart_types/xy_chart/state/selectors/get_cursor_band.ts @@ -1,7 +1,7 @@ import { Dimensions } from '../../../../utils/dimensions'; import createCachedSelector from 're-reselect'; import { Point } from '../../../../utils/point'; -import { Scale } from '../../../../utils/scales/scales'; +import { Scale } from '../../../../scales'; import { isLineAreaOnlyChart } from '../utils'; import { getCursorBandPosition } from '../../crosshair/crosshair_utils'; import { SettingsSpec, PointerEvent } from '../../../../specs/settings'; diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts index fc90708cc2..99d50da730 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts @@ -8,7 +8,8 @@ import { getComputedScalesSelector } from './get_computed_scales'; import { getElementAtCursorPositionSelector } from './get_elements_at_cursor_pos'; import { IndexedGeometry } from '../../../../utils/geometry'; import { getSeriesSpecsSelector, getAxisSpecsSelector } from './get_specs'; -import { BasicSeriesSpec, AxisSpec, Rotation } from '../../utils/specs'; +import { BasicSeriesSpec, AxisSpec } from '../../utils/specs'; +import { Rotation } from '../../../../utils/commons'; import { getTooltipTypeSelector } from './get_tooltip_type'; import { formatTooltip } from '../../tooltip/tooltip'; import { getTooltipHeaderFormatterSelector } from './get_tooltip_header_formatter'; diff --git a/src/chart_types/xy_chart/state/selectors/is_brush_available.ts b/src/chart_types/xy_chart/state/selectors/is_brush_available.ts index 4c36e69418..1d6669e562 100644 --- a/src/chart_types/xy_chart/state/selectors/is_brush_available.ts +++ b/src/chart_types/xy_chart/state/selectors/is_brush_available.ts @@ -1,7 +1,7 @@ import createCachedSelector from 're-reselect'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { getComputedScalesSelector } from './get_computed_scales'; -import { ScaleType } from '../../../../utils/scales/scales'; +import { ScaleType } from '../../../../scales'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; /** diff --git a/src/chart_types/xy_chart/state/selectors/is_tooltip_snap_enabled.ts b/src/chart_types/xy_chart/state/selectors/is_tooltip_snap_enabled.ts index b1ef7e2ea2..89d55e65c2 100644 --- a/src/chart_types/xy_chart/state/selectors/is_tooltip_snap_enabled.ts +++ b/src/chart_types/xy_chart/state/selectors/is_tooltip_snap_enabled.ts @@ -1,6 +1,6 @@ import createCachedSelector from 're-reselect'; import { computeSeriesGeometriesSelector } from './compute_series_geometries'; -import { Scale } from '../../../../utils/scales/scales'; +import { Scale } from '../../../../scales'; import { getTooltipSnapSelector } from './get_tooltip_snap'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; diff --git a/src/chart_types/xy_chart/state/selectors/merge_y_custom_domains.ts b/src/chart_types/xy_chart/state/selectors/merge_y_custom_domains.ts index 40ff022f8e..c37c9b47fa 100644 --- a/src/chart_types/xy_chart/state/selectors/merge_y_custom_domains.ts +++ b/src/chart_types/xy_chart/state/selectors/merge_y_custom_domains.ts @@ -1,7 +1,8 @@ import createCachedSelector from 're-reselect'; import { getAxisSpecsSelector } from './get_specs'; import { isYDomain, isCompleteBound, isLowerBound, isUpperBound, isBounded } from '../../utils/axis_utils'; -import { AxisSpec, DomainRange, Rotation } from '../../utils/specs'; +import { AxisSpec, DomainRange } from '../../utils/specs'; +import { Rotation } from '../../../../utils/commons'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; diff --git a/src/chart_types/xy_chart/state/selectors/on_pointer_move_caller.ts b/src/chart_types/xy_chart/state/selectors/on_pointer_move_caller.ts index 3a0afaa8a2..42c08ee9aa 100644 --- a/src/chart_types/xy_chart/state/selectors/on_pointer_move_caller.ts +++ b/src/chart_types/xy_chart/state/selectors/on_pointer_move_caller.ts @@ -4,7 +4,7 @@ import { GlobalChartState } from '../../../../state/chart_state'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { SettingsSpec, PointerEvent, PointerEventType } from '../../../../specs'; import { ChartTypes } from '../../../index'; -import { Scale } from '../../../../utils/scales/scales'; +import { Scale } from '../../../../scales'; import { Point } from '../../../../utils/point'; import { getOrientedProjectedPointerPositionSelector } from './get_oriented_projected_pointer_position'; import { computeSeriesGeometriesSelector } from './compute_series_geometries'; diff --git a/src/chart_types/xy_chart/state/utils.test.ts b/src/chart_types/xy_chart/state/utils.test.ts index aa7641cb1a..723049fff1 100644 --- a/src/chart_types/xy_chart/state/utils.test.ts +++ b/src/chart_types/xy_chart/state/utils.test.ts @@ -11,8 +11,7 @@ import { import { BARCHART_1Y0G, BARCHART_1Y1G } from '../../../utils/data_samples/test_dataset'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { SpecId } from '../../../utils/ids'; -import { ScaleContinuous } from '../../../utils/scales/scale_continuous'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType, ScaleContinuous } from '../../../scales'; import { computeSeriesDomains, computeSeriesGeometries, diff --git a/src/chart_types/xy_chart/state/utils.ts b/src/chart_types/xy_chart/state/utils.ts index 3073bcc9a0..2bc7ab826e 100644 --- a/src/chart_types/xy_chart/state/utils.ts +++ b/src/chart_types/xy_chart/state/utils.ts @@ -26,18 +26,17 @@ import { isBarSeriesSpec, isLineSeriesSpec, LineSeriesSpec, - Rotation, isBandedSpec, Fit, FitConfig, SeriesTypes, } from '../utils/specs'; import { ColorConfig, Theme } from '../../../utils/themes/theme'; -import { identity, mergePartial } from '../../../utils/commons'; +import { identity, mergePartial, Rotation } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; import { Domain } from '../../../utils/domain'; import { GroupId, SpecId } from '../../../utils/ids'; -import { Scale } from '../../../utils/scales/scales'; +import { Scale } from '../../../scales'; import { PointGeometry, BarGeometry, AreaGeometry, LineGeometry, IndexedGeometry } from '../../../utils/geometry'; import { LegendItem } from '../legend/legend'; import { Spec } from '../../../specs'; diff --git a/src/chart_types/xy_chart/tooltip/tooltip.test.ts b/src/chart_types/xy_chart/tooltip/tooltip.test.ts index d825b091b0..a43bc62da8 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.test.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.test.ts @@ -1,5 +1,6 @@ -import { ScaleType } from '../../../utils/scales/scales'; -import { AxisSpec, BarSeriesSpec, Position, SeriesTypes } from '../utils/specs'; +import { ScaleType } from '../../../scales'; +import { AxisSpec, BarSeriesSpec, SeriesTypes } from '../utils/specs'; +import { Position } from '../../../utils/commons'; import { formatTooltip } from './tooltip'; import { BarGeometry } from '../../../utils/geometry'; import { ChartTypes } from '../..'; diff --git a/src/chart_types/xy_chart/utils/axis_utils.test.ts b/src/chart_types/xy_chart/utils/axis_utils.test.ts index f90041dd1b..ea09f8e0ee 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -1,9 +1,10 @@ import { XDomain } from '../domains/x_domain'; import { YDomain } from '../domains/y_domain'; -import { AxisSpec, DomainRange, Position, AxisStyle } from './specs'; +import { AxisSpec, DomainRange, AxisStyle } from './specs'; +import { Position } from '../../../utils/commons'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { AxisId, GroupId } from '../../../utils/ids'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { AxisTick, AxisTicksDimensions, diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 6a378a64d4..f607dd7e93 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -6,17 +6,16 @@ import { CompleteBoundedDomain, DomainRange, LowerBoundedDomain, - Position, - Rotation, TickFormatter, UpperBoundedDomain, AxisStyle, TickFormatterOptions, } from './specs'; +import { Position, Rotation } from '../../../utils/commons'; import { AxisConfig, Theme } from '../../../utils/themes/theme'; import { Dimensions, Margins } from '../../../utils/dimensions'; import { AxisId } from '../../../utils/ids'; -import { Scale } from '../../../utils/scales/scales'; +import { Scale } from '../../../scales'; import { BBox, BBoxCalculator } from '../../../utils/bbox/bbox_calculator'; import { getSpecsById } from '../state/utils'; diff --git a/src/chart_types/xy_chart/utils/dimensions.test.ts b/src/chart_types/xy_chart/utils/dimensions.test.ts index f180effc2a..4ad366cb70 100644 --- a/src/chart_types/xy_chart/utils/dimensions.test.ts +++ b/src/chart_types/xy_chart/utils/dimensions.test.ts @@ -1,5 +1,6 @@ import { AxisTicksDimensions } from './axis_utils'; -import { AxisSpec, Position } from './specs'; +import { AxisSpec } from './specs'; +import { Position } from '../../../utils/commons'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { LegendStyle } from '../../../utils/themes/theme'; import { computeChartDimensions } from './dimensions'; diff --git a/src/chart_types/xy_chart/utils/dimensions.ts b/src/chart_types/xy_chart/utils/dimensions.ts index 5a868a5171..04b2d0b583 100644 --- a/src/chart_types/xy_chart/utils/dimensions.ts +++ b/src/chart_types/xy_chart/utils/dimensions.ts @@ -1,5 +1,6 @@ import { AxisTicksDimensions } from './axis_utils'; -import { AxisSpec, Position } from './specs'; +import { AxisSpec } from './specs'; +import { Position } from '../../../utils/commons'; import { Theme } from '../../../utils/themes/theme'; import { AxisId } from '../../../utils/ids'; import { Dimensions } from '../../../utils/dimensions'; diff --git a/src/chart_types/xy_chart/utils/fit_function.test.ts b/src/chart_types/xy_chart/utils/fit_function.test.ts index 7701d5950b..9e500b345b 100644 --- a/src/chart_types/xy_chart/utils/fit_function.test.ts +++ b/src/chart_types/xy_chart/utils/fit_function.test.ts @@ -6,7 +6,7 @@ import { MockDataSeriesDatum, } from '../../../mocks'; import { Fit } from './specs'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { DataSeries } from './series'; import * as seriesUtils from './stacked_series_utils'; diff --git a/src/chart_types/xy_chart/utils/fit_function.ts b/src/chart_types/xy_chart/utils/fit_function.ts index 6e3f1bd818..b53e8f10e1 100644 --- a/src/chart_types/xy_chart/utils/fit_function.ts +++ b/src/chart_types/xy_chart/utils/fit_function.ts @@ -3,7 +3,7 @@ import { DeepNonNullable } from 'utility-types'; import { Fit, FitConfig } from './specs'; import { DataSeries, DataSeriesDatum } from './series'; import { datumXSortPredicate } from './stacked_series_utils'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; /** * Fit type that requires previous and/or next `non-nullable` values diff --git a/src/chart_types/xy_chart/utils/interactions.ts b/src/chart_types/xy_chart/utils/interactions.ts index 158d07da4e..73dff95d9a 100644 --- a/src/chart_types/xy_chart/utils/interactions.ts +++ b/src/chart_types/xy_chart/utils/interactions.ts @@ -1,9 +1,8 @@ import { $Values } from 'utility-types'; -import { Rotation } from './specs'; +import { Datum, Rotation } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; import { Accessor } from '../../../utils/accessor'; import { BarGeometry, PointGeometry, IndexedGeometry, isPointGeometry, isBarGeometry } from '../../../utils/geometry'; -import { Datum } from '../../../utils/domain'; /** The type of tooltip to use */ export const TooltipType = Object.freeze({ diff --git a/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts b/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts index 8ede0992d1..efb48b558e 100644 --- a/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts +++ b/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts @@ -1,5 +1,5 @@ import { RawDataSeries } from './series'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { MockRawDataSeries, MockDataSeries } from '../../../mocks'; import { MockSeriesSpecs, MockSeriesSpec } from '../../../mocks/specs'; diff --git a/src/chart_types/xy_chart/utils/nonstacked_series_utils.ts b/src/chart_types/xy_chart/utils/nonstacked_series_utils.ts index a9e57dd946..7b28b25f69 100644 --- a/src/chart_types/xy_chart/utils/nonstacked_series_utils.ts +++ b/src/chart_types/xy_chart/utils/nonstacked_series_utils.ts @@ -1,7 +1,7 @@ import { DataSeries, DataSeriesDatum, RawDataSeries } from './series'; import { fitFunction } from './fit_function'; import { isAreaSeriesSpec, isLineSeriesSpec, SeriesSpecs, BasicSeriesSpec } from './specs'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { getSpecsById } from '../state/utils'; export const formatNonStackedDataSeriesValues = ( diff --git a/src/chart_types/xy_chart/utils/scales.test.ts b/src/chart_types/xy_chart/utils/scales.test.ts index 576b7ac596..05a42252ab 100644 --- a/src/chart_types/xy_chart/utils/scales.test.ts +++ b/src/chart_types/xy_chart/utils/scales.test.ts @@ -1,4 +1,4 @@ -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { XDomain } from '../domains/x_domain'; import { computeXScale, countBarsInCluster } from './scales'; import { FormattedDataSeries } from './series'; diff --git a/src/chart_types/xy_chart/utils/scales.ts b/src/chart_types/xy_chart/utils/scales.ts index bb7d22e167..78ff50fd3f 100644 --- a/src/chart_types/xy_chart/utils/scales.ts +++ b/src/chart_types/xy_chart/utils/scales.ts @@ -1,7 +1,5 @@ import { GroupId } from '../../../utils/ids'; -import { ScaleBand } from '../../../utils/scales/scale_band'; -import { ScaleContinuous } from '../../../utils/scales/scale_continuous'; -import { Scale, ScaleType } from '../../../utils/scales/scales'; +import { Scale, ScaleType, ScaleBand, ScaleContinuous } from '../../../scales'; import { XDomain } from '../domains/x_domain'; import { YDomain } from '../domains/y_domain'; import { FormattedDataSeries } from './series'; diff --git a/src/chart_types/xy_chart/utils/series.test.ts b/src/chart_types/xy_chart/utils/series.test.ts index 93d3f26098..b00f3aa8fc 100644 --- a/src/chart_types/xy_chart/utils/series.test.ts +++ b/src/chart_types/xy_chart/utils/series.test.ts @@ -1,5 +1,5 @@ import { ColorConfig } from '../../../utils/themes/theme'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { SeriesCollectionValue, getFormattedDataseries, diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index fb14f10fe3..40d9503d94 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -5,9 +5,9 @@ import { splitSpecsByGroupId, YBasicSeriesSpec } from '../domains/y_domain'; import { formatNonStackedDataSeriesValues } from './nonstacked_series_utils'; import { BasicSeriesSpec, SubSeriesStringPredicate, SeriesTypes, SeriesSpecs } from './specs'; import { formatStackedDataSeriesValues } from './stacked_series_utils'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; import { LastValues } from '../state/utils'; -import { Datum } from '../../../utils/domain'; +import { Datum } from '../../../utils/commons'; export interface FilledValues { /** the x value */ diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index aa8a77b0f4..026ee0fbc9 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -9,19 +9,15 @@ import { RectAnnotationStyle, } from '../../../utils/themes/theme'; import { Accessor, AccessorFormat } from '../../../utils/accessor'; -import { RecursivePartial } from '../../../utils/commons'; +import { RecursivePartial, Color, Position, Datum } from '../../../utils/commons'; import { AxisId, GroupId } from '../../../utils/ids'; -import { ScaleContinuousType, ScaleType } from '../../../utils/scales/scales'; +import { ScaleContinuousType, ScaleType } from '../../../scales'; import { CurveType } from '../../../utils/curves'; import { RawDataSeriesDatum, SeriesIdentifier } from './series'; import { AnnotationTooltipFormatter } from '../annotations/annotation_utils'; import { Spec, SpecTypes } from '../../..'; import { ChartTypes } from '../..'; -import { Datum } from '../../../utils/domain'; -export type Rotation = 0 | 90 | -90 | 180; -export type Rendering = 'canvas' | 'svg'; -export type Color = string; export type BarStyleOverride = RecursivePartial | Color | null; export type PointStyleOverride = RecursivePartial | Color | null; @@ -484,20 +480,6 @@ export interface AxisStyle { tickLabelPadding?: number; } -/** - * The position of the axis relative to the chart. - * A left or right positioned axis is a vertical axis. - * A top or bottom positioned axis is an horizontal axis. - */ -export const Position = Object.freeze({ - Top: 'top' as 'top', - Bottom: 'bottom' as 'bottom', - Left: 'left' as 'left', - Right: 'right' as 'right', -}); - -export type Position = $Values; - export const AnnotationTypes = Object.freeze({ Line: 'line' as 'line', Rectangle: 'rectangle' as 'rectangle', diff --git a/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts b/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts index b288a4730b..927acc1a77 100644 --- a/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts +++ b/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts @@ -1,6 +1,6 @@ import { RawDataSeries } from './series'; import { formatStackedDataSeriesValues } from './stacked_series_utils'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; describe('Stacked Series Utils', () => { const STANDARD_DATA_SET: RawDataSeries[] = [ diff --git a/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts b/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts index 85fd02ed96..39a7bd309b 100644 --- a/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts +++ b/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts @@ -1,6 +1,6 @@ import { RawDataSeries } from './series'; import { computeYStackedMapValues, formatStackedDataSeriesValues, getYValueStackMap } from './stacked_series_utils'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; describe('Stacked Series Utils', () => { const EMPTY_DATA_SET: RawDataSeries[] = [ diff --git a/src/chart_types/xy_chart/utils/stacked_series_utils.ts b/src/chart_types/xy_chart/utils/stacked_series_utils.ts index 0a3f02f125..f608de8b94 100644 --- a/src/chart_types/xy_chart/utils/stacked_series_utils.ts +++ b/src/chart_types/xy_chart/utils/stacked_series_utils.ts @@ -1,5 +1,5 @@ import { DataSeries, DataSeriesDatum, RawDataSeries, RawDataSeriesDatum, FilledValues } from './series'; -import { ScaleType } from '../../../utils/scales/scales'; +import { ScaleType } from '../../../scales'; interface StackedValues { values: number[]; diff --git a/src/components/chart.tsx b/src/components/chart.tsx index af2edba66d..d5a86145ef 100644 --- a/src/components/chart.tsx +++ b/src/components/chart.tsx @@ -3,13 +3,12 @@ import classNames from 'classnames'; import { Provider } from 'react-redux'; import { createStore, Store } from 'redux'; import uuid from 'uuid'; - import { SpecsParser } from '../specs/specs_parser'; import { ChartResizer } from './chart_resizer'; import { Legend } from './legend/legend'; import { ChartContainer } from './chart_container'; import { isHorizontalAxis } from '../chart_types/xy_chart/utils/axis_utils'; -import { Position } from '../chart_types/xy_chart/utils/specs'; +import { Position } from '../utils/commons'; import { ChartSize, getChartSize } from '../utils/chart_size'; import { ChartStatus } from './chart_status'; import { chartStoreReducer, GlobalChartState } from '../state/chart_state'; diff --git a/src/components/legend/legend.test.tsx b/src/components/legend/legend.test.tsx index fee9a175cf..68dd197d4f 100644 --- a/src/components/legend/legend.test.tsx +++ b/src/components/legend/legend.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { Chart } from '../chart'; import { Settings, BarSeries } from '../../specs'; -import { ScaleType } from '../../utils/scales/scales'; +import { ScaleType } from '../../scales'; import { DataGenerator } from '../../utils/data_generators/data_generator'; import { Legend } from './legend'; import { LegendListItem } from './legend_item'; diff --git a/src/components/legend/legend.tsx b/src/components/legend/legend.tsx index 27b7c910d1..7ea0c0bc06 100644 --- a/src/components/legend/legend.tsx +++ b/src/components/legend/legend.tsx @@ -1,8 +1,8 @@ import React, { createRef } from 'react'; import classNames from 'classnames'; -import { isVerticalAxis, isHorizontalAxis } from '../../chart_types/xy_chart/utils/axis_utils'; +import { Dispatch, bindActionCreators } from 'redux'; import { connect } from 'react-redux'; -import { Position } from '../../chart_types/xy_chart/utils/specs'; +import { Position } from '../../utils/commons'; import { GlobalChartState } from '../../state/chart_state'; import { getLegendItemsSelector } from '../../state/selectors/get_legend_items'; import { getSettingsSpecSelector } from '../../state/selectors/get_settings_specs'; @@ -10,7 +10,6 @@ import { getChartThemeSelector } from '../../state/selectors/get_chart_theme'; import { getLegendItemsValuesSelector } from '../../state/selectors/get_legend_items_values'; import { getLegendSizeSelector } from '../../state/selectors/get_legend_size'; import { onToggleLegend } from '../../state/actions/legend'; -import { Dispatch, bindActionCreators } from 'redux'; import { LIGHT_THEME } from '../../utils/themes/light_theme'; import { LegendListItem } from './legend_item'; import { Theme } from '../../utils/themes/theme'; @@ -90,7 +89,7 @@ class LegendComponent extends React.Component { getLegendListStyle = (position: Position, { chartMargins, legend }: Theme): LegendListStyle => { const { top: paddingTop, bottom: paddingBottom, left: paddingLeft, right: paddingRight } = chartMargins; - if (isHorizontalAxis(position)) { + if (position === Position.Bottom || position === Position.Top) { return { paddingLeft, paddingRight, @@ -105,7 +104,7 @@ class LegendComponent extends React.Component { }; getLegendStyle = (position: Position, size: BBox): LegendStyle => { - if (isVerticalAxis(position)) { + if (position === Position.Left || position === Position.Right) { const width = `${size.width}px`; return { width, @@ -175,31 +174,23 @@ const mapDispatchToProps = (dispatch: Dispatch): LegendDispatchProps => dispatch, ); +const EMPTY_DEFAULT_STATE = { + legendItems: new Map(), + legendPosition: Position.Right, + showLegend: false, + legendCollapsed: false, + legendItemTooltipValues: new Map(), + debug: false, + chartTheme: LIGHT_THEME, + legendSize: { width: 0, height: 0 }, +}; const mapStateToProps = (state: GlobalChartState): LegendStateProps => { if (!state.specsInitialized) { - return { - legendItems: new Map(), - legendPosition: Position.Right, - showLegend: false, - legendCollapsed: false, - legendItemTooltipValues: new Map(), - debug: false, - chartTheme: LIGHT_THEME, - legendSize: { width: 0, height: 0 }, - }; + return EMPTY_DEFAULT_STATE; } const { legendPosition, showLegend, debug } = getSettingsSpecSelector(state); if (!showLegend) { - return { - legendItems: new Map(), - legendPosition: Position.Right, - showLegend: false, - legendCollapsed: false, - legendItemTooltipValues: new Map(), - debug: false, - chartTheme: LIGHT_THEME, - legendSize: { width: 0, height: 0 }, - }; + return EMPTY_DEFAULT_STATE; } const legendItems = getLegendItemsSelector(state); return { diff --git a/src/components/legend/legend_item.tsx b/src/components/legend/legend_item.tsx index d982dcfeba..c9decc5e99 100644 --- a/src/components/legend/legend_item.tsx +++ b/src/components/legend/legend_item.tsx @@ -5,7 +5,7 @@ import { Icon } from '../icons/icon'; import { LegendItemListener, BasicListener } from '../../specs/settings'; import { LegendItem } from '../../chart_types/xy_chart/legend/legend'; import { onLegendItemOutAction, onLegendItemOverAction } from '../../state/actions/legend'; -import { Position } from '../../chart_types/xy_chart/utils/specs'; +import { Position } from '../../utils/commons'; import { SeriesIdentifier } from '../../chart_types/xy_chart/utils/series'; interface LegendItemProps { diff --git a/src/index.ts b/src/index.ts index e7d04cf272..2e6d548c6b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ +import 'path2d-polyfill'; export * from './specs'; export { Chart } from './components/chart'; export { ChartSize, ChartSizeArray, ChartSizeObject } from './utils/chart_size'; export { SpecId, GroupId, AxisId, AnnotationId, getAxisId, getGroupId, getSpecId, getAnnotationId } from './utils/ids'; -export { ScaleType } from './utils/scales/scales'; +export { ScaleType } from './scales'; export * from './utils/themes/theme'; export { LIGHT_THEME } from './utils/themes/light_theme'; export { DARK_THEME } from './utils/themes/dark_theme'; @@ -10,11 +11,11 @@ export * from './utils/themes/theme_commons'; export { RecursivePartial } from './utils/commons'; export { CurveType } from './utils/curves'; export { timeFormatter, niceTimeFormatter, niceTimeFormatByDay } from './utils/data/formatters'; -import 'path2d-polyfill'; export { DataGenerator } from './utils/data_generators/data_generator'; export { SeriesCollectionValue } from './chart_types/xy_chart/utils/series'; export { ChartTypes } from './chart_types'; -export { Position, Rendering, Rotation, TickFormatter } from './chart_types/xy_chart/utils/specs'; +export { Datum, Position, Rendering, Rotation } from './utils/commons'; +export { TickFormatter } from './chart_types/xy_chart/utils/specs'; export { TooltipType, TooltipValue, TooltipValueFormatter } from './chart_types/xy_chart/utils/interactions'; export { SeriesIdentifier } from './chart_types/xy_chart/utils/series'; export { @@ -40,5 +41,4 @@ export { } from './chart_types/partition_chart/layout/types/config_types'; export { Layer as PartitionLayer } from './chart_types/partition_chart/specs/index'; export { AccessorFn, IndexedAccessorFn } from './utils/accessor'; -export { Datum } from './utils/domain'; export { SpecTypes } from './specs/settings'; diff --git a/src/mocks/scale/scale.ts b/src/mocks/scale/scale.ts index 386988db6a..afd36ef5b4 100644 --- a/src/mocks/scale/scale.ts +++ b/src/mocks/scale/scale.ts @@ -1,5 +1,5 @@ import { mergePartial } from '../../utils/commons'; -import { Scale, ScaleType } from '../../utils/scales/scales'; +import { Scale, ScaleType } from '../../scales'; export class MockScale { private static readonly base: Scale = { diff --git a/src/mocks/specs/specs.ts b/src/mocks/specs/specs.ts index 6f324422a9..572a8d7915 100644 --- a/src/mocks/specs/specs.ts +++ b/src/mocks/specs/specs.ts @@ -1,4 +1,4 @@ -import { mergePartial } from '../../utils/commons'; +import { mergePartial, Position } from '../../utils/commons'; import { SeriesSpecs, DEFAULT_GLOBAL_ID, @@ -9,10 +9,9 @@ import { LineSeriesSpec, BasicSeriesSpec, SeriesTypes, - Position, } from '../../chart_types/xy_chart/utils/specs'; import { getSpecId, getGroupId } from '../../utils/ids'; -import { ScaleType } from '../../utils/scales/scales'; +import { ScaleType } from '../../scales'; import { ChartTypes } from '../../chart_types'; import { SettingsSpec, SpecTypes } from '../../specs'; import { TooltipType } from '../../chart_types/xy_chart/utils/interactions'; diff --git a/src/utils/scales/scales.ts b/src/scales/index.ts similarity index 68% rename from src/utils/scales/scales.ts rename to src/scales/index.ts index f78c4cd8f7..814854fdf2 100644 --- a/src/utils/scales/scales.ts +++ b/src/scales/index.ts @@ -1,5 +1,10 @@ import { $Values } from 'utility-types'; +/** + * A `Scale` interface. A scale can map an input value within a specified domain + * to an output value from a specified range. + * The the value is mapped depending on the `type` (linear, log, sqrt, time, ordinal) + */ export interface Scale { domain: any[]; range: number[]; @@ -29,7 +34,6 @@ export interface Scale { isInverted: boolean; barsPadding: number; } -export type ScaleFunction = (value: any) => number; /** * The scale type @@ -44,17 +48,14 @@ export const ScaleType = Object.freeze({ export type ScaleType = $Values; -export interface ScaleConfig { - accessor: (value: any) => any; - domain: any[]; - type: ScaleType; - clamp?: boolean; -} +export type ScaleContinuousType = Exclude; -export type ScaleContinuousType = - | typeof ScaleType.Linear - | typeof ScaleType.Sqrt - | typeof ScaleType.Log - | typeof ScaleType.Time; export type ScaleOrdinalType = typeof ScaleType.Ordinal; -export type ScaleTypes = ScaleContinuousType | ScaleOrdinalType; + +export { ScaleBand } from './scale_band'; + +export { ScaleContinuous } from './scale_continuous'; + +export function isLogarithmicScale(scale: Scale) { + return scale.type === ScaleType.Log; +} diff --git a/src/utils/scales/scale_band.test.ts b/src/scales/scale_band.test.ts similarity index 99% rename from src/utils/scales/scale_band.test.ts rename to src/scales/scale_band.test.ts index 70b06f23e3..13cd045a89 100644 --- a/src/utils/scales/scale_band.test.ts +++ b/src/scales/scale_band.test.ts @@ -1,4 +1,4 @@ -import { ScaleBand } from './scale_band'; +import { ScaleBand } from '.'; describe('Scale Band', () => { it('shall clone domain and range arrays', () => { diff --git a/src/utils/scales/scale_band.ts b/src/scales/scale_band.ts similarity index 96% rename from src/utils/scales/scale_band.ts rename to src/scales/scale_band.ts index e8cf04f696..af620d8d9a 100644 --- a/src/utils/scales/scale_band.ts +++ b/src/scales/scale_band.ts @@ -1,6 +1,6 @@ import { scaleBand, scaleQuantize, ScaleQuantize } from 'd3-scale'; -import { clamp } from '../commons'; -import { ScaleType, Scale } from './scales'; +import { clamp } from '../utils/commons'; +import { ScaleType, Scale } from '.'; export class ScaleBand implements Scale { readonly bandwidth: number; diff --git a/src/utils/scales/scale_continuous.test.ts b/src/scales/scale_continuous.test.ts similarity index 98% rename from src/utils/scales/scale_continuous.test.ts rename to src/scales/scale_continuous.test.ts index 3443386891..e0aa0f953b 100644 --- a/src/utils/scales/scale_continuous.test.ts +++ b/src/scales/scale_continuous.test.ts @@ -1,10 +1,8 @@ -import { XDomain } from '../../chart_types/xy_chart/domains/x_domain'; -import { computeXScale } from '../../chart_types/xy_chart/utils/scales'; -import { Domain } from '../domain'; +import { XDomain } from '../chart_types/xy_chart/domains/x_domain'; +import { computeXScale } from '../chart_types/xy_chart/utils/scales'; +import { Domain } from '../utils/domain'; import { DateTime, Settings } from 'luxon'; -import { ScaleBand } from './scale_band'; -import { isLogarithmicScale, ScaleContinuous } from './scale_continuous'; -import { ScaleType } from './scales'; +import { ScaleContinuous, ScaleType, ScaleBand, isLogarithmicScale } from '.'; describe('Scale Continuous', () => { test('shall invert on continuous scale linear', () => { diff --git a/src/utils/scales/scale_continuous.ts b/src/scales/scale_continuous.ts similarity index 97% rename from src/utils/scales/scale_continuous.ts rename to src/scales/scale_continuous.ts index 0508a6d15e..9e7108c466 100644 --- a/src/utils/scales/scale_continuous.ts +++ b/src/scales/scale_continuous.ts @@ -10,9 +10,9 @@ import { ScaleTime, } from 'd3-scale'; -import { clamp, mergePartial } from '../commons'; -import { ScaleContinuousType, ScaleType, Scale } from './scales'; -import { getMomentWithTz } from '../data/date_time'; +import { clamp, mergePartial } from '../utils/commons'; +import { ScaleContinuousType, ScaleType, Scale } from '.'; +import { getMomentWithTz } from '../utils/data/date_time'; /** * d3 scales excluding time scale @@ -304,7 +304,3 @@ export class ScaleContinuous implements Scale { return value >= this.domain[0] && value <= this.domain[1]; } } - -export function isLogarithmicScale(scale: Scale) { - return scale.type === ScaleType.Log; -} diff --git a/src/utils/scales/scale_time.test.ts b/src/scales/scale_time.test.ts similarity index 99% rename from src/utils/scales/scale_time.test.ts rename to src/scales/scale_time.test.ts index 870f711a7c..2d45a6b7f5 100644 --- a/src/utils/scales/scale_time.test.ts +++ b/src/scales/scale_time.test.ts @@ -1,6 +1,5 @@ import { DateTime } from 'luxon'; -import { ScaleContinuous } from './scale_continuous'; -import { ScaleType } from './scales'; +import { ScaleContinuous, ScaleType } from '.'; describe('[Scale Time] - timezones', () => { describe('timezone checks', () => { diff --git a/src/utils/scales/scales.test.ts b/src/scales/scales.test.ts similarity index 99% rename from src/utils/scales/scales.test.ts rename to src/scales/scales.test.ts index 36fc568fbe..fc04d7f8ed 100644 --- a/src/utils/scales/scales.test.ts +++ b/src/scales/scales.test.ts @@ -1,7 +1,6 @@ import { DateTime } from 'luxon'; -import { ScaleBand } from './scale_band'; +import { ScaleType, ScaleBand } from '.'; import { limitLogScaleDomain, ScaleContinuous } from './scale_continuous'; -import { ScaleType } from './scales'; describe('Scale Test', () => { test('Create an ordinal scale', () => { diff --git a/src/specs/settings.test.tsx b/src/specs/settings.test.tsx index 6896479197..7c1180a0b7 100644 --- a/src/specs/settings.test.tsx +++ b/src/specs/settings.test.tsx @@ -1,6 +1,6 @@ import { mount } from 'enzyme'; import * as React from 'react'; -import { Position, Rendering, Rotation } from '../chart_types/xy_chart/utils/specs'; +import { Position, Rendering, Rotation } from '../utils/commons'; import { DARK_THEME } from '../utils/themes/dark_theme'; import { TooltipType } from '../chart_types/xy_chart/utils/interactions'; import { Settings, SettingsSpec } from './settings'; diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index 72d2c615a7..388f4ed166 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -1,15 +1,16 @@ import { $Values } from 'utility-types'; -import { DomainRange, Position, Rendering, Rotation } from '../chart_types/xy_chart/utils/specs'; +import { DomainRange } from '../chart_types/xy_chart/utils/specs'; import { PartialTheme, Theme } from '../utils/themes/theme'; import { Domain } from '../utils/domain'; import { TooltipType, TooltipValueFormatter } from '../chart_types/xy_chart/utils/interactions'; -import { ScaleTypes } from '../utils/scales/scales'; import { getConnect, specComponentFactory } from '../state/spec_factory'; import { Spec } from '.'; import { LIGHT_THEME } from '../utils/themes/light_theme'; import { ChartTypes } from '../chart_types'; import { GeometryValue } from '../utils/geometry'; import { SeriesIdentifier } from '../chart_types/xy_chart/utils/series'; +import { Position, Rendering, Rotation } from '../utils/commons'; +import { ScaleContinuousType, ScaleOrdinalType } from '../scales'; export type ElementClickListener = (elements: Array<[GeometryValue, SeriesIdentifier]>) => void; export type ElementOverListener = (elements: Array<[GeometryValue, SeriesIdentifier]>) => void; @@ -42,7 +43,7 @@ export interface BasePointerEvent { */ export interface PointerOverEvent extends BasePointerEvent { type: typeof PointerEventType.Over; - scale: ScaleTypes; + scale: ScaleContinuousType | ScaleOrdinalType; /** * @todo * unit for event (i.e. `time`, `feet`, `count`, etc.) diff --git a/src/state/selectors/get_chart_rotation.ts b/src/state/selectors/get_chart_rotation.ts index 219d72e48c..44c16ea635 100644 --- a/src/state/selectors/get_chart_rotation.ts +++ b/src/state/selectors/get_chart_rotation.ts @@ -1,7 +1,7 @@ import createCachedSelector from 're-reselect'; import { getSettingsSpecSelector } from './get_settings_specs'; -import { Rotation } from '../../chart_types/xy_chart/utils/specs'; +import { Rotation } from '../../utils/commons'; import { getChartIdSelector } from './get_chart_id'; export const getChartRotationSelector = createCachedSelector( diff --git a/src/utils/accessor.ts b/src/utils/accessor.ts index b1853fcdff..c889d15bca 100644 --- a/src/utils/accessor.ts +++ b/src/utils/accessor.ts @@ -1,4 +1,4 @@ -import { Datum } from './domain'; +import { Datum } from './commons'; type UnaryAccessorFn = (datum: Datum) => any; type BinaryAccessorFn = (datum: Datum, index: number) => any; diff --git a/src/utils/commons.ts b/src/utils/commons.ts index ed35146e58..db7e2983e0 100644 --- a/src/utils/commons.ts +++ b/src/utils/commons.ts @@ -1,4 +1,19 @@ -import * as uuid from 'uuid'; +import { v1 as uuidV1 } from 'uuid'; +import { $Values } from 'utility-types'; + +export type Datum = any; +export type Rotation = 0 | 90 | -90 | 180; +export type Rendering = 'canvas' | 'svg'; +export type Color = string; + +export const Position = Object.freeze({ + Top: 'top' as 'top', + Bottom: 'bottom' as 'bottom', + Left: 'left' as 'left', + Right: 'right' as 'right', +}); + +export type Position = $Values; export function identity(value: T): T { return value; @@ -20,8 +35,8 @@ export function clamp(value: number, min: number, max: number): number { * it should begin with an letter to be HTML4 compliant. */ export function htmlIdGenerator(idPrefix?: string) { - const prefix = idPrefix || `i${uuid.v1()}`; - return (suffix?: string) => `${prefix}_${suffix || uuid.v1()}`; + const prefix = idPrefix || `i${uuidV1()}`; + return (suffix?: string) => `${prefix}_${suffix || uuidV1()}`; } /** diff --git a/src/utils/domain.ts b/src/utils/domain.ts index c71224b15f..46cbfe43bd 100644 --- a/src/utils/domain.ts +++ b/src/utils/domain.ts @@ -1,7 +1,7 @@ import { extent, sum } from 'd3-array'; import { nest } from 'd3-collection'; import { Accessor, AccessorFn } from './accessor'; -import { ScaleType } from './scales/scales'; +import { ScaleType } from '../scales'; export type Domain = any[]; @@ -110,5 +110,3 @@ export function computeStackedContinuousDomain( const cumulativeSumAccessor = (d: any) => d.value; return computeContinuousDataDomain(groups, cumulativeSumAccessor, scaleToExtent); } - -export type Datum = any; diff --git a/src/utils/events.ts b/src/utils/events.ts index 2dbd31d7f8..c268b48ee4 100644 --- a/src/utils/events.ts +++ b/src/utils/events.ts @@ -1,5 +1,5 @@ import { PointerEvent, isPointerOverEvent, PointerOverEvent } from '../specs'; -import { Scale } from './scales/scales'; +import { Scale } from '../scales'; export function isValidPointerOverEvent( mainScale: Scale, diff --git a/stories/annotations.tsx b/stories/annotations.tsx index ca34520c40..2d09cc27de 100644 --- a/stories/annotations.tsx +++ b/stories/annotations.tsx @@ -21,7 +21,7 @@ import { Icon } from '../src/components/icons/icon'; import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana'; import { getChartRotationKnob, arrayKnobs } from './common'; import { BandedAccessorType } from '../src/utils/geometry'; -import { Position } from '../src/chart_types/xy_chart/utils/specs'; +import { Position } from '../src/utils/commons'; const dateFormatter = timeFormatter('HH:mm:ss'); diff --git a/stories/utils/utils.ts b/stories/utils/utils.ts index 1d45847672..3003841db8 100644 --- a/stories/utils/utils.ts +++ b/stories/utils/utils.ts @@ -1,6 +1,6 @@ import { arrayToLookup, hueInterpolator } from '../../src/chart_types/partition_chart/layout/utils/calcs'; import { palettes } from '../../src/mocks/hierarchical/palettes'; -import { Datum } from '../../src/utils/domain'; +import { Datum } from '../../src/utils/commons'; import { countryDimension, productDimension, regionDimension } from '../../src/mocks/hierarchical/dimension_codes'; export const productLookup = arrayToLookup((d: Datum) => d.sitc1, productDimension); From a1b19507cf5971f1d6e8c29111216dfe56fe0bd7 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Tue, 18 Feb 2020 16:06:30 -0600 Subject: [PATCH 08/15] refactor: move custom labeling to name and update logic --- ...es-label-visually-looks-correct-1-snap.png | Bin 21159 -> 0 bytes ...g-config-visually-looks-correct-1-snap.png | Bin 0 -> 21092 bytes .../xy_chart/legend/legend.test.ts | 94 ++++++++-------- src/chart_types/xy_chart/legend/legend.ts | 16 +-- .../xy_chart/rendering/rendering.test.ts | 2 +- .../xy_chart/state/chart_state.specs.test.ts | 24 ++-- .../xy_chart/state/chart_state.test.ts | 4 +- src/chart_types/xy_chart/state/utils.test.ts | 8 +- src/chart_types/xy_chart/tooltip/tooltip.ts | 4 +- src/chart_types/xy_chart/utils/series.test.ts | 106 +++++++++--------- src/chart_types/xy_chart/utils/series.ts | 81 +++++++------ src/chart_types/xy_chart/utils/specs.ts | 43 +++---- stories/styling.tsx | 30 ++--- 13 files changed, 207 insertions(+), 205 deletions(-) delete mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-full-and-sub-series-label-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-custom-series-naming-config-visually-looks-correct-1-snap.png diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-full-and-sub-series-label-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-full-and-sub-series-label-visually-looks-correct-1-snap.png deleted file mode 100644 index aa0cd39a38cadd1953198df42fa66d05e3eb3d7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21159 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 Dgbjr9 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-custom-series-naming-config-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-custom-series-naming-config-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..53139dec022aa731de46ba3e82b475915b7930e0 GIT binary patch literal 21092 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%0A_?8>QV!LI! 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 literal 0 HcmV?d00001 diff --git a/src/chart_types/xy_chart/legend/legend.test.ts b/src/chart_types/xy_chart/legend/legend.test.ts index ea36a3497e..80b0fd13bd 100644 --- a/src/chart_types/xy_chart/legend/legend.test.ts +++ b/src/chart_types/xy_chart/legend/legend.test.ts @@ -1,7 +1,7 @@ import { getAxisId, getGroupId, getSpecId } from '../../../utils/ids'; import { ScaleType } from '../../../scales'; import { computeLegend } from './legend'; -import { SeriesCollectionValue, getSeriesLabel } from '../utils/series'; +import { SeriesCollectionValue, getSeriesName } from '../utils/series'; import { AxisSpec, BasicSeriesSpec, SeriesTypes } from '../utils/specs'; import { Position } from '../../../utils/commons'; import { ChartTypes } from '../..'; @@ -118,7 +118,7 @@ describe('Legends', () => { const expected = [ { color: 'red', - label: 'Spec 1 title', + name: 'Spec 1 title', seriesIdentifier: { seriesKeys: ['y1'], specId: 'spec1', @@ -142,7 +142,7 @@ describe('Legends', () => { const expected = [ { color: 'red', - label: 'Spec 1 title', + name: 'Spec 1 title', seriesIdentifier: { seriesKeys: ['y1'], specId: 'spec1', @@ -158,7 +158,7 @@ describe('Legends', () => { }, { color: 'blue', - label: 'a - b', + name: 'a - b', seriesIdentifier: { seriesKeys: ['a', 'b', 'y1'], specId: 'spec1', @@ -182,7 +182,7 @@ describe('Legends', () => { const expected = [ { color: 'red', - label: 'Spec 1 title', + name: 'Spec 1 title', seriesIdentifier: { seriesKeys: ['y1'], specId: 'spec1', @@ -198,7 +198,7 @@ describe('Legends', () => { }, { color: 'green', - label: 'spec2', + name: 'spec2', seriesIdentifier: { seriesKeys: ['y1'], specId: 'spec2', @@ -227,7 +227,7 @@ describe('Legends', () => { const expected = [ { color: 'violet', - label: 'Spec 1 title', + name: 'Spec 1 title', banded: undefined, seriesIdentifier: { seriesKeys: ['y1'], @@ -272,7 +272,7 @@ describe('Legends', () => { const visibility = [...legend.values()].map((item) => item.isSeriesVisible); expect(visibility).toEqual([false, false, true]); }); - it('returns the right series label for a color series', () => { + it('returns the right series name for a color series', () => { const seriesIdentifier1 = { specId: getSpecId(''), yAccessor: 'y1', @@ -289,38 +289,38 @@ describe('Legends', () => { }; // null removed, seriesIdentifier has to be at least an empty array - let label = getSeriesLabel(seriesIdentifier1, true, false); - expect(label).toBe(''); - label = getSeriesLabel(seriesIdentifier1, true, false, spec1); - expect(label).toBe('Spec 1 title'); - label = getSeriesLabel(seriesIdentifier1, true, false, spec2); - expect(label).toBe('spec2'); - label = getSeriesLabel(seriesIdentifier2, true, false, spec1); - expect(label).toBe('Spec 1 title'); - label = getSeriesLabel(seriesIdentifier2, true, false, spec2); - expect(label).toBe('spec2'); + let name = getSeriesName(seriesIdentifier1, true, false); + expect(name).toBe(''); + name = getSeriesName(seriesIdentifier1, true, false, spec1); + expect(name).toBe('Spec 1 title'); + name = getSeriesName(seriesIdentifier1, true, false, spec2); + expect(name).toBe('spec2'); + name = getSeriesName(seriesIdentifier2, true, false, spec1); + expect(name).toBe('Spec 1 title'); + name = getSeriesName(seriesIdentifier2, true, false, spec2); + expect(name).toBe('spec2'); - label = getSeriesLabel(seriesIdentifier1, false, false, spec1); - expect(label).toBe('Spec 1 title'); - label = getSeriesLabel(seriesIdentifier1, false, false, spec2); - expect(label).toBe('spec2'); - label = getSeriesLabel(seriesIdentifier2, false, false, spec1); - expect(label).toBe('a - b'); - label = getSeriesLabel(seriesIdentifier2, false, false, spec2); - expect(label).toBe('a - b'); + name = getSeriesName(seriesIdentifier1, false, false, spec1); + expect(name).toBe('Spec 1 title'); + name = getSeriesName(seriesIdentifier1, false, false, spec2); + expect(name).toBe('spec2'); + name = getSeriesName(seriesIdentifier2, false, false, spec1); + expect(name).toBe('a - b'); + name = getSeriesName(seriesIdentifier2, false, false, spec2); + expect(name).toBe('a - b'); - label = getSeriesLabel(seriesIdentifier1, true, false, spec1); - expect(label).toBe('Spec 1 title'); - label = getSeriesLabel(seriesIdentifier1, true, false, spec2); - expect(label).toBe('spec2'); - label = getSeriesLabel(seriesIdentifier1, true, false); - expect(label).toBe(''); - label = getSeriesLabel(seriesIdentifier1, true, false, spec1); - expect(label).toBe('Spec 1 title'); - label = getSeriesLabel(seriesIdentifier1, true, false, spec2); - expect(label).toBe('spec2'); + name = getSeriesName(seriesIdentifier1, true, false, spec1); + expect(name).toBe('Spec 1 title'); + name = getSeriesName(seriesIdentifier1, true, false, spec2); + expect(name).toBe('spec2'); + name = getSeriesName(seriesIdentifier1, true, false); + expect(name).toBe(''); + name = getSeriesName(seriesIdentifier1, true, false, spec1); + expect(name).toBe('Spec 1 title'); + name = getSeriesName(seriesIdentifier1, true, false, spec2); + expect(name).toBe('spec2'); }); - it('use the splitted value as label if has a single series and splitSeries is used', () => { + it('use the splitted value as name if has a single series and splitSeries is used', () => { const seriesIdentifier1 = { specId: getSpecId(''), yAccessor: 'y1', @@ -347,22 +347,22 @@ describe('Legends', () => { ...spec1, splitSeriesAccessors: ['g'], }; - let label = getSeriesLabel(seriesIdentifier1, true, false, specWithSplit); - expect(label).toBe('Spec 1 title'); + let name = getSeriesName(seriesIdentifier1, true, false, specWithSplit); + expect(name).toBe('Spec 1 title'); - label = getSeriesLabel(seriesIdentifier3, true, false, specWithSplit); - expect(label).toBe('a'); + name = getSeriesName(seriesIdentifier3, true, false, specWithSplit); + expect(name).toBe('a'); // happens when we have multiple values in splitSeriesAccessor // or we have also multiple yAccessors - label = getSeriesLabel(seriesIdentifier2, true, false, specWithSplit); - expect(label).toBe('a - b'); + name = getSeriesName(seriesIdentifier2, true, false, specWithSplit); + expect(name).toBe('a - b'); // happens when the value of a splitSeriesAccessor is null - label = getSeriesLabel(seriesIdentifier1, true, false, specWithSplit); - expect(label).toBe('Spec 1 title'); + name = getSeriesName(seriesIdentifier1, true, false, specWithSplit); + expect(name).toBe('Spec 1 title'); - label = getSeriesLabel(seriesIdentifier1, false, false, specWithSplit); - expect(label).toBe('Spec 1 title'); + name = getSeriesName(seriesIdentifier1, false, false, specWithSplit); + expect(name).toBe('Spec 1 title'); }); }); diff --git a/src/chart_types/xy_chart/legend/legend.ts b/src/chart_types/xy_chart/legend/legend.ts index 42f4df4cf8..b7a9ba1991 100644 --- a/src/chart_types/xy_chart/legend/legend.ts +++ b/src/chart_types/xy_chart/legend/legend.ts @@ -5,7 +5,7 @@ import { getSeriesIndex, getSortedDataSeriesColorsValuesMap, SeriesIdentifier, - getSeriesLabel, + getSeriesName, } from '../utils/series'; import { AxisSpec, BasicSeriesSpec, Postfixes, isAreaSeriesSpec, isBarSeriesSpec } from '../utils/specs'; import { Y0_ACCESSOR_POSTFIX, Y1_ACCESSOR_POSTFIX } from '../tooltip/tooltip'; @@ -19,7 +19,7 @@ interface FormattedLastValues { export type LegendItem = Postfixes & { key: string; color: string; - label: string; + name: string; seriesIdentifier: SeriesIdentifier; isSeriesVisible?: boolean; banded?: boolean; @@ -43,14 +43,14 @@ function getPostfix(spec: BasicSeriesSpec): Postfixes { } export function getItemLabel( - { banded, label, y1AccessorFormat, y0AccessorFormat }: LegendItem, + { banded, name, y1AccessorFormat, y0AccessorFormat }: LegendItem, yAccessor: BandedAccessorType, ) { if (!banded) { - return label; + return name; } - return yAccessor === BandedAccessorType.Y1 ? `${label}${y1AccessorFormat}` : `${label}${y0AccessorFormat}`; + return yAccessor === BandedAccessorType.Y1 ? `${name}${y1AccessorFormat}` : `${name}${y0AccessorFormat}`; } export function computeLegend( @@ -69,10 +69,10 @@ export function computeLegend( const spec = getSpecsById(specs, seriesIdentifier.specId); const color = seriesColors.get(key) || defaultColor; const hasSingleSeries = seriesCollection.size === 1; - const label = getSeriesLabel(seriesIdentifier, hasSingleSeries, false, spec); + const name = getSeriesName(seriesIdentifier, hasSingleSeries, false, spec); const isSeriesVisible = deselectedDataSeries ? getSeriesIndex(deselectedDataSeries, seriesIdentifier) < 0 : true; - if (label === '' || !spec) { + if (name === '' || !spec) { return; } @@ -84,7 +84,7 @@ export function computeLegend( const legendItem: LegendItem = { key, color, - label, + name, banded, seriesIdentifier, isSeriesVisible, diff --git a/src/chart_types/xy_chart/rendering/rendering.test.ts b/src/chart_types/xy_chart/rendering/rendering.test.ts index 3cd4abc0ef..76f441e688 100644 --- a/src/chart_types/xy_chart/rendering/rendering.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.test.ts @@ -105,7 +105,7 @@ describe('Rendering utils', () => { const highlightedLegendItem: LegendItem = { key: 'somekey', color: '', - label: '', + name: '', seriesIdentifier, isSeriesVisible: true, isLegendItemVisible: true, diff --git a/src/chart_types/xy_chart/state/chart_state.specs.test.ts b/src/chart_types/xy_chart/state/chart_state.specs.test.ts index be8dd55cf6..0a00ea6b09 100644 --- a/src/chart_types/xy_chart/state/chart_state.specs.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.specs.test.ts @@ -25,8 +25,8 @@ describe('XYChart - specs ordering', () => { store.dispatch(specParsed()); const legendItems = getLegendItemsSelector(store.getState()); - const labels = [...legendItems.values()].map((item) => item.label); - expect(labels).toEqual(['A', 'B', 'C']); + const names = [...legendItems.values()].map((item) => item.name); + expect(names).toEqual(['A', 'B', 'C']); }); it('the legend respect the insert order [B, A, C]', () => { store.dispatch(specParsing()); @@ -35,8 +35,8 @@ describe('XYChart - specs ordering', () => { store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); store.dispatch(specParsed()); const legendItems = getLegendItemsSelector(store.getState()); - const labels = [...legendItems.values()].map((item) => item.label); - expect(labels).toEqual(['B', 'A', 'C']); + const names = [...legendItems.values()].map((item) => item.name); + expect(names).toEqual(['B', 'A', 'C']); }); it('the legend respect the order when changing properties of existing specs', () => { store.dispatch(specParsing()); @@ -46,8 +46,8 @@ describe('XYChart - specs ordering', () => { store.dispatch(specParsed()); let legendItems = getLegendItemsSelector(store.getState()); - let labels = [...legendItems.values()].map((item) => item.label); - expect(labels).toEqual(['A', 'B', 'C']); + let names = [...legendItems.values()].map((item) => item.name); + expect(names).toEqual(['A', 'B', 'C']); store.dispatch(specParsing()); store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); @@ -56,8 +56,8 @@ describe('XYChart - specs ordering', () => { store.dispatch(specParsed()); legendItems = getLegendItemsSelector(store.getState()); - labels = [...legendItems.values()].map((item) => item.label); - expect(labels).toEqual(['A', 'B updated', 'C']); + names = [...legendItems.values()].map((item) => item.name); + expect(names).toEqual(['A', 'B updated', 'C']); }); it('the legend respect the order when changing the order of the specs', () => { store.dispatch(specParsing()); @@ -67,8 +67,8 @@ describe('XYChart - specs ordering', () => { store.dispatch(specParsed()); let legendItems = getLegendItemsSelector(store.getState()); - let labels = [...legendItems.values()].map((item) => item.label); - expect(labels).toEqual(['A', 'B', 'C']); + let names = [...legendItems.values()].map((item) => item.name); + expect(names).toEqual(['A', 'B', 'C']); store.dispatch(specParsing()); store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); @@ -77,7 +77,7 @@ describe('XYChart - specs ordering', () => { store.dispatch(specParsed()); legendItems = getLegendItemsSelector(store.getState()); - labels = [...legendItems.values()].map((item) => item.label); - expect(labels).toEqual(['B', 'A', 'C']); + names = [...legendItems.values()].map((item) => item.name); + expect(names).toEqual(['B', 'A', 'C']); }); }); diff --git a/src/chart_types/xy_chart/state/chart_state.test.ts b/src/chart_types/xy_chart/state/chart_state.test.ts index cff92ad127..c822144442 100644 --- a/src/chart_types/xy_chart/state/chart_state.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.test.ts @@ -46,7 +46,7 @@ describe.skip('Chart Store', () => { const firstLegendItem: LegendItem = { key: 'color1', color: 'foo', - label: 'bar', + name: 'bar', seriesIdentifier: { specId: SPEC_ID, yAccessor: 'y1', @@ -69,7 +69,7 @@ describe.skip('Chart Store', () => { const secondLegendItem: LegendItem = { key: 'color2', color: 'baz', - label: 'qux', + name: 'qux', seriesIdentifier: { specId: SPEC_ID, yAccessor: '', diff --git a/src/chart_types/xy_chart/state/utils.test.ts b/src/chart_types/xy_chart/state/utils.test.ts index 723049fff1..843b9100cb 100644 --- a/src/chart_types/xy_chart/state/utils.test.ts +++ b/src/chart_types/xy_chart/state/utils.test.ts @@ -1379,7 +1379,7 @@ describe('Chart State utils', () => { legendItems1.set('specId:{bars},colors:{a}', { key: 'specId:{bars},colors:{a}', color: '#1EA593', - label: 'a', + name: 'a', seriesIdentifier: { specId: 'bars', seriesKeys: ['a'], @@ -1393,7 +1393,7 @@ describe('Chart State utils', () => { legendItems1.set('specId:{bars},colors:{b}', { key: 'specId:{bars},colors:{b}', color: '#2B70F7', - label: 'b', + name: 'b', seriesIdentifier: { specId: 'bars', seriesKeys: ['b'], @@ -1411,7 +1411,7 @@ describe('Chart State utils', () => { legendItems2.set('specId:{bars},colors:{a}', { key: 'specId:{bars},colors:{a}', color: '#1EA593', - label: 'a', + name: 'a', seriesIdentifier: { specId: 'bars', seriesKeys: ['a'], @@ -1425,7 +1425,7 @@ describe('Chart State utils', () => { legendItems2.set('specId:{bars},colors:{b}', { key: 'specId:{bars},colors:{b}', color: '#2B70F7', - label: 'b', + name: 'b', seriesIdentifier: { specId: 'bars', seriesKeys: ['b'], diff --git a/src/chart_types/xy_chart/tooltip/tooltip.ts b/src/chart_types/xy_chart/tooltip/tooltip.ts index 77ecaf985b..c06e451cef 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.ts @@ -9,7 +9,7 @@ import { } from '../utils/specs'; import { IndexedGeometry, BandedAccessorType } from '../../../utils/geometry'; import { getAccessorFormatLabel } from '../../../utils/accessor'; -import { getSeriesKey, getSeriesLabel } from '../utils/series'; +import { getSeriesKey, getSeriesName } from '../utils/series'; export interface TooltipLegendValue { y0: any; @@ -49,7 +49,7 @@ export function formatTooltip( axisSpec?: AxisSpec, ): TooltipValue { const seriesKey = getSeriesKey(seriesIdentifier); - let displayName = getSeriesLabel(seriesIdentifier, hasSingleSeries, true, spec); + let displayName = getSeriesName(seriesIdentifier, hasSingleSeries, true, spec); if (isBandedSpec(spec.y0Accessors) && (isAreaSeriesSpec(spec) || isBarSeriesSpec(spec))) { const { y0AccessorFormat = Y0_ACCESSOR_POSTFIX, y1AccessorFormat = Y1_ACCESSOR_POSTFIX } = spec; diff --git a/src/chart_types/xy_chart/utils/series.test.ts b/src/chart_types/xy_chart/utils/series.test.ts index 615d851f4f..6825e0f6d5 100644 --- a/src/chart_types/xy_chart/utils/series.test.ts +++ b/src/chart_types/xy_chart/utils/series.test.ts @@ -10,7 +10,7 @@ import { splitSeries, SeriesIdentifier, cleanDatum, - getSeriesLabel, + getSeriesName, } from './series'; import { BasicSeriesSpec, LineSeriesSpec, SeriesTypes, AreaSeriesSpec } from './specs'; import { formatStackedDataSeriesValues } from './stacked_series_utils'; @@ -667,7 +667,7 @@ describe('Series', () => { expect(datum.y1).toBe(null); expect(datum.y0).toBe(null); }); - describe('#getSeriesLabelKeys', () => { + describe('#getSeriesNameKeys', () => { const data = dg.generateGroupedSeries(50, 2).map((d) => ({ ...d, y2: d.y })); const spec = MockSeriesSpec.area({ data, @@ -678,7 +678,7 @@ describe('Series', () => { it('should get series label from spec', () => { const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, spec); + const actual = getSeriesName(identifier, false, false, spec); expect(actual).toBe('a - y'); }); @@ -688,7 +688,7 @@ describe('Series', () => { yAccessors: ['y'], }; const [identifier] = MockSeriesIdentifier.fromSpecs([spec]); - const actual = getSeriesLabel(identifier, false, false, specSingleY); + const actual = getSeriesName(identifier, false, false, specSingleY); expect(actual).toBe('a'); }); @@ -697,9 +697,9 @@ describe('Series', () => { it('should replace full label', () => { const label = 'My custom new label'; const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, { + const actual = getSeriesName(identifier, false, false, { ...spec, - customSeriesLabel: ({ yAccessor, splitAccessors }) => + name: ({ yAccessor, splitAccessors }) => yAccessor === identifier.yAccessor && splitAccessors.get('g') === 'a' ? label : null, }); @@ -710,27 +710,27 @@ describe('Series', () => { const specSingleY: AreaSeriesSpec = { ...spec, yAccessors: ['y'], - customSeriesLabel: ({ seriesKeys }) => seriesKeys.join(' - '), + name: ({ seriesKeys }) => seriesKeys.join(' - '), }; const [identifier] = MockSeriesIdentifier.fromSpecs([spec]); - const actual = getSeriesLabel(identifier, false, false, specSingleY); + const actual = getSeriesName(identifier, false, false, specSingleY); expect(actual).toBe('a - y'); }); it('should replace yAccessor sub label with map', () => { const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, { + const actual = getSeriesName(identifier, false, false, { ...spec, - customSeriesLabel: { - mappings: [ + name: { + names: [ { accessor: 'g', value: 'a', }, { - value: 'y', - newValue: 'Yuuuup', + accessor: 'y', + name: 'Yuuuup', }, ], }, @@ -740,16 +740,16 @@ describe('Series', () => { it('should join with custom delimiter', () => { const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, { + const actual = getSeriesName(identifier, false, false, { ...spec, - customSeriesLabel: { - mappings: [ + name: { + names: [ { accessor: 'g', value: 'a', }, { - value: 'y', + accessor: 'y', }, ], delimiter: ' ¯\\_(ツ)_/¯ ', @@ -760,17 +760,17 @@ describe('Series', () => { it('should replace splitAccessor sub label with map', () => { const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, { + const actual = getSeriesName(identifier, false, false, { ...spec, - customSeriesLabel: { - mappings: [ + name: { + names: [ { accessor: 'g', value: 'a', - newValue: 'Apple', + name: 'Apple', }, { - value: 'y', + accessor: 'y', }, ], }, @@ -778,20 +778,20 @@ describe('Series', () => { expect(actual).toBe('Apple - y'); }); - it('should mind order of mappings', () => { + it('should mind order of names', () => { const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, { + const actual = getSeriesName(identifier, false, false, { ...spec, - customSeriesLabel: { - mappings: [ + name: { + names: [ { - value: 'y', - newValue: 'Yuuum', + accessor: 'y', + name: 'Yuuum', }, { accessor: 'g', value: 'a', - newValue: 'Apple', + name: 'Apple', }, ], }, @@ -799,21 +799,21 @@ describe('Series', () => { expect(actual).toBe('Yuuum - Apple'); }); - it('should mind sortIndex of mappings', () => { + it('should mind sortIndex of names', () => { const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, { + const actual = getSeriesName(identifier, false, false, { ...spec, - customSeriesLabel: { - mappings: [ + name: { + names: [ { - value: 'y', - newValue: 'Yuuum', + accessor: 'y', + name: 'Yuuum', sortIndex: 2, }, { accessor: 'g', value: 'a', - newValue: 'Apple', + name: 'Apple', sortIndex: 0, }, ], @@ -824,18 +824,18 @@ describe('Series', () => { it('should allow undefined sortIndex', () => { const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, { + const actual = getSeriesName(identifier, false, false, { ...spec, - customSeriesLabel: { - mappings: [ + name: { + names: [ { - value: 'y', - newValue: 'Yuuum', + accessor: 'y', + name: 'Yuuum', }, { accessor: 'g', value: 'a', - newValue: 'Apple', + name: 'Apple', sortIndex: 0, }, ], @@ -844,25 +844,25 @@ describe('Series', () => { expect(actual).toBe('Apple - Yuuum'); }); - it('should ignore missing mappings', () => { + it('should ignore missing names', () => { const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, { + const actual = getSeriesName(identifier, false, false, { ...spec, - customSeriesLabel: { - mappings: [ + name: { + names: [ { accessor: 'g', value: 'a', - newValue: 'Apple', + name: 'Apple', }, { accessor: 'g', value: 'Not a mapping', - newValue: 'No Value', + name: 'No Value', }, { - value: 'y', - newValue: 'Yuuum', + accessor: 'y', + name: 'Yuuum', }, ], }, @@ -872,10 +872,10 @@ describe('Series', () => { it('should return fallback label if empty string', () => { const [identifier] = indentifiers; - const actual = getSeriesLabel(identifier, false, false, { + const actual = getSeriesName(identifier, false, false, { ...spec, - customSeriesLabel: { - mappings: [], + name: { + names: [], }, }); expect(actual).toBe('a - y'); diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index 141437d6b7..8889ef6444 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -3,7 +3,7 @@ import { Accessor } from '../../../utils/accessor'; import { GroupId, SpecId } from '../../../utils/ids'; import { splitSpecsByGroupId, YBasicSeriesSpec } from '../domains/y_domain'; import { formatNonStackedDataSeriesValues } from './nonstacked_series_utils'; -import { BasicSeriesSpec, SeriesTypes, SeriesSpecs, SeriesLabelMappingOptions } from './specs'; +import { BasicSeriesSpec, SeriesTypes, SeriesSpecs, SeriesNameConfigOptions } from './specs'; import { formatStackedDataSeriesValues } from './stacked_series_utils'; import { ScaleType } from '../../../scales'; import { LastValues } from '../state/utils'; @@ -359,10 +359,11 @@ export function getSplittedSeries( const banded = spec.y0Accessors && spec.y0Accessors.length > 0; dataSeries.rawDataSeries.forEach((series) => { + const { data, ...seriesIdentifier } = series; seriesCollection.set(series.key, { banded, specSortIndex: spec.sortIndex, - seriesIdentifier: series as SeriesIdentifier, + seriesIdentifier, }); }); @@ -379,43 +380,52 @@ export function getSplittedSeries( }; } -const getSeriesLabelFromOptions = ( - options: SeriesLabelMappingOptions, +const getSeriesNameFromOptions = ( + options: SeriesNameConfigOptions, { yAccessor, splitAccessors }: SeriesIdentifier, -) => - options.mappings - .sort(({ sortIndex: a = Infinity }, { sortIndex: b = Infinity }) => a - b) - .map(({ accessor, value, newValue }) => { - const accessorValue = accessor ? splitAccessors.get(accessor) : null; - if (accessorValue === value) { - return newValue ?? value; - } + delimiter: string, +) => { + if (!options.names) { + return null; + } - if (yAccessor === value) { - return newValue ?? value; - } - return null; - }) - .filter((d) => Boolean(d) || d === 0) - .slice() - .join(options.delimiter ?? ' - '); + return ( + options.names + .sort(({ sortIndex: a = Infinity }, { sortIndex: b = Infinity }) => a - b) + .map(({ accessor, value, name }) => { + const accessorValue = (accessor ? splitAccessors.get(accessor) : null) ?? null; + if (accessorValue === value) { + return name ?? value; + } + + if (yAccessor === accessor) { + return name ?? accessor; + } + return null; + }) + .filter((d) => Boolean(d) || d === 0) + .slice() + .join(delimiter) || null + ); +}; /** - * Get series label based on `SeriesIdentifier` + * Get series name based on `SeriesIdentifier` */ -export function getSeriesLabel( +export function getSeriesName( seriesIdentifier: SeriesIdentifier, hasSingleSeries: boolean, isTooltip: boolean, spec?: BasicSeriesSpec, ): string { - if (spec && spec.customSeriesLabel) { + let delimiter = ' - '; + if (spec && spec.name && typeof spec.name !== 'string') { let customLabel: string | number | null = null; - if (typeof spec.customSeriesLabel === 'function') { - customLabel = spec.customSeriesLabel(seriesIdentifier, isTooltip); + if (typeof spec.name === 'function') { + customLabel = spec.name(seriesIdentifier, isTooltip); } else { - const customMappingLabel = getSeriesLabelFromOptions(spec.customSeriesLabel, seriesIdentifier); - customLabel = customMappingLabel === '' ? null : customMappingLabel; + delimiter = spec.name.delimiter ?? delimiter; + customLabel = getSeriesNameFromOptions(spec.name, seriesIdentifier, delimiter); } if (customLabel !== null) { @@ -423,27 +433,26 @@ export function getSeriesLabel( } } - let label = ''; - const delimiter = ' - '; - const labelKeys = + let name = ''; + const nameKeys = spec && spec.yAccessors.length > 1 ? seriesIdentifier.seriesKeys : seriesIdentifier.seriesKeys.slice(0, -1); // there is one series, the is only one yAccessor, the first part is not null - if (hasSingleSeries || labelKeys.length === 0 || labelKeys[0] == null) { + if (hasSingleSeries || nameKeys.length === 0 || nameKeys[0] == null) { if (!spec) { return ''; } - if (spec.splitSeriesAccessors && labelKeys.length > 0 && labelKeys[0] != null) { - label = labelKeys.join(delimiter); + if (spec.splitSeriesAccessors && nameKeys.length > 0 && nameKeys[0] != null) { + name = nameKeys.join(delimiter); } else { - label = spec.name || `${spec.id}`; + name = typeof spec.name === 'string' ? spec.name : `${spec.id}`; } } else { - label = labelKeys.join(delimiter); + name = nameKeys.join(delimiter); } - return label; + return name; } function getSortIndex({ specSortIndex }: SeriesCollectionValue, total: number): number { diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index ea29db0ee4..53c968c6aa 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -54,27 +54,25 @@ export type SeriesLabel = string | number | null; /** * Function to create custom series label for a given series */ -export type SeriesLabelFn = (series: SeriesIdentifier, isTooltip: boolean) => SeriesLabel; +export type SeriesNameFn = (series: SeriesIdentifier, isTooltip: boolean) => SeriesLabel; /** * Accessor mapping to replace labels */ -export interface SeriesLabelMapping { +export interface SeriesNameConfig { /** - * accessor key (i.e. `seriesSlittAccessors`) - * - * ignored for `yAccessor` + * accessor key (i.e. `yAccessors` and `seriesSplitAccessors`) */ - accessor?: string; + accessor: string; /** - * Accessor value/label (i.e. `yAccessors` or values from `seriesSlittAccessors`) + * Accessor value (i.e. values from `seriesSplitAccessors`) */ - value: string | number; + value?: string | number; /** - * New Accessor value/label to use in series label + * New name for Accessor value * * If not provided, the original value will be used */ - newValue?: string | number; + name?: string | number; /** * Sort order of label, overrides order listed in array. * @@ -83,19 +81,19 @@ export interface SeriesLabelMapping { */ sortIndex?: number; } -export interface SeriesLabelMappingOptions { +export interface SeriesNameConfigOptions { /** - * Array of accessor mappings to replace labels. + * Array of accessor naming configs to replace series names * - * Only provided mappings will be included + * Only provided configs will be included * (i.e. if you only provide a single mapping for `yAccessor`, all other series accessor labels will be ignored) * - * The order of mappings is the order in which the resulting labels will + * The order of configs is the order in which the resulting labels will * be joined, if no `sortIndex` is specified. * * If no values are found for a giving mapping in a series, the mapping will be ignored. */ - mappings: SeriesLabelMapping[]; + names?: SeriesNameConfig[]; /** * Delimiter to join values/labels * @@ -103,7 +101,7 @@ export interface SeriesLabelMappingOptions { */ delimiter?: string; } -export type SeriesLabelAccessor = SeriesLabelFn | SeriesLabelMappingOptions; +export type SeriesNameAccessor = string | SeriesNameFn | SeriesNameConfigOptions; /** * The fit function type @@ -245,8 +243,10 @@ export interface DisplayValueSpec { export interface SeriesSpec extends Spec { specType: typeof SpecTypes.Series; chartType: typeof ChartTypes.XYAxis; - /** The name or label of the spec */ - name?: string; + /** + * The name of the spec. Also a mechanism to provide custom series names. + */ + name?: SeriesNameAccessor; /** The ID of the spec group, generated via getGroupId method * @default __global__ */ @@ -282,13 +282,6 @@ export interface SeriesSpec extends Spec { * Hide series in tooltip */ filterSeriesInTooltip?: FilterPredicate; - /** - * Mechanism to provide custom series labels. - * - * @param series - `SeriesIdentifier` - * @param isTooltip - true if tooltip label, otherwise legend label - */ - customSeriesLabel?: SeriesLabelAccessor; } export interface Postfixes { diff --git a/stories/styling.tsx b/stories/styling.tsx index c89589f6bf..09715f2291 100644 --- a/stories/styling.tsx +++ b/stories/styling.tsx @@ -32,8 +32,8 @@ import { palettes } from '../src/utils/themes/colors'; import { BarStyleAccessor, PointStyleAccessor, - SeriesLabelMappingOptions, - SeriesLabelFn, + SeriesNameConfigOptions, + SeriesNameFn, } from '../src/chart_types/xy_chart/utils/specs'; import moment from 'moment'; import { DateTime } from 'luxon'; @@ -925,7 +925,7 @@ customSeriesStylesArea.story = { }; export const addCustomFullAndSubSeriesLabel = () => { - const customSeriesLabelFn: SeriesLabelFn = ({ yAccessor, splitAccessors }) => { + const customSeriesNamingFn: SeriesNameFn = ({ yAccessor, splitAccessors }) => { // eslint-disable-next-line react/prop-types if (yAccessor === 'y1' && splitAccessors.get('g') === 'a') { return 'Custom full series name'; @@ -953,7 +953,7 @@ export const addCustomFullAndSubSeriesLabel = () => { yAccessors={['y1', 'y2']} splitSeriesAccessors={['g']} data={TestDatasets.BARCHART_2Y1G} - customSeriesLabel={customSeriesLabelFn} + name={customSeriesNamingFn} /> ); @@ -962,19 +962,19 @@ addCustomFullAndSubSeriesLabel.story = { name: 'Add custom series label', }; -export const customSeriesLabelMappings = () => { - const customSeriesLabelOptions: SeriesLabelMappingOptions = { - mappings: [ +export const customSeriesNamingConfig = () => { + const customSeriesNameOptions: SeriesNameConfigOptions = { + names: [ { // replace split accessor; accessor: 'g', value: 'a', - newValue: 'replace a(from g)', + name: 'replace a(from g)', }, { // replace y accessor; - value: 'y2', - newValue: 'replace y2', + accessor: 'y2', + name: 'replace y2', }, ], delimiter: ' | ', @@ -998,13 +998,13 @@ export const customSeriesLabelMappings = () => { yAccessors={['y1', 'y2']} splitSeriesAccessors={['g']} data={TestDatasets.BARCHART_2Y1G} - customSeriesLabel={customSeriesLabelOptions} + name={customSeriesNameOptions} /> ); }; -customSeriesLabelMappings.story = { - name: 'Add custom series label mappings and delimeter', +customSeriesNamingConfig.story = { + name: 'Add custom series naming and delimeter', }; export const addCustomSeriesLabelFormatting = () => { @@ -1020,7 +1020,7 @@ export const addCustomSeriesLabelFormatting = () => { { x: 2, y: 18, percent: 1, time: start.plus({ month: 2 }).toMillis() }, { x: 3, y: 7, percent: 1, time: start.plus({ month: 3 }).toMillis() }, ]; - const customSeriesLabelFn: SeriesLabelFn = ({ yAccessor, splitAccessors }, isTooltip) => + const customSeriesNamingFn: SeriesNameFn = ({ yAccessor, splitAccessors }, isTooltip) => [ ...[...splitAccessors.entries()].map(([key, value]) => { if (key === 'time') { @@ -1064,7 +1064,7 @@ export const addCustomSeriesLabelFormatting = () => { yAccessors={['y']} splitSeriesAccessors={['time', 'percent']} data={data} - customSeriesLabel={customSeriesLabelFn} + name={customSeriesNamingFn} /> ); From 8262d19b343872a7c2f9a28f02305baca42fbe58 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 19 Feb 2020 16:55:53 +0100 Subject: [PATCH 09/15] docs: fix filteredLabel ids (#552) --- stories/bar_chart.tsx | 505 ++++++++++++++---------------------------- 1 file changed, 166 insertions(+), 339 deletions(-) diff --git a/stories/bar_chart.tsx b/stories/bar_chart.tsx index 5361c00749..0b8a2447ad 100644 --- a/stories/bar_chart.tsx +++ b/stories/bar_chart.tsx @@ -9,9 +9,6 @@ import { BarSeries, Chart, DARK_THEME, - getAxisId, - getGroupId, - getSpecId, HistogramBarSeries, HistogramModeAlignments, LIGHT_THEME, @@ -73,7 +70,7 @@ export const basic = () => { return ( { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { yScaleToDataExtent={false} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { - Number(d).toFixed(2)} - /> + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - + (stackedAsPercentage && !clusterBars ? `${Number(d * 100).toFixed(0)} %` : d)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { ]} /> { ]} /> { - Number(d).toFixed(2)} - /> + Number(d).toFixed(2)} /> { data={KIBANA_METRICS.metrics.kibana_os_load[0].data} /> { data={KIBANA_METRICS.metrics.kibana_os_load[1].data} /> { - Number(d).toFixed(2)} - /> + Number(d).toFixed(2)} /> { data={KIBANA_METRICS.metrics.kibana_os_load[2].data.slice(0, 20)} /> { data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 20)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { const isVisibleFunction: FilterPredicate = (series) => { return series.splitAccessors.size > 0 - ? series.specId === getSpecId('bars') && - series.yAccessor === 'y1' && - series.splitAccessors.get('g1') === 'cloudflare.com' - : series.specId === getSpecId('bars') && series.yAccessor === 'y1'; + ? series.specId === 'bars1' && series.yAccessor === 'y1' && series.splitAccessors.get('g1') === 'cloudflare.com' + : series.specId === 'bars1' && series.yAccessor === 'y1'; }; return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { } return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { return ( Number(d).toFixed(2)} /> { /> { ]; return ( - + Number(d).toFixed(2)} /> { ]; return ( - + Number(d).toFixed(2)} /> { ]; return ( - + Number(d).toFixed(2)} /> { ]; return ( - + Number(d).toFixed(2)} /> { ]; return ( - + Number(d).toFixed(2)} /> { ]; return ( - + Number(d).toFixed(2)} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> { const otherSeries = otherSeriesSelection === 'line' ? ( { /> ) : ( { ]} id={'rect'} /> - - + + {hasHistogramBarSeries && ( { /> )} { enableHistogramMode={boolean('bars-1 enableHistogramMode', false)} /> { return ( - - + + {hasHistogramBarSeries && ( { /> )} { enableHistogramMode={boolean('bars-1 enableHistogramMode', false)} /> { return ( + - { - + { ]; return ( - - + + { ]; return ( - - + + { ]; return ( - + Number(d).toFixed(2)} domain={{ min: 0, max: 15 }} /> Number(d).toFixed(2)} @@ -2071,7 +1903,7 @@ export const stackedOnlyGroupedAreas = () => { domain={{ min: 0, max: 15 }} /> { yScaleToDataExtent={false} /> { /> { yScaleToDataExtent={false} /> { yScaleToDataExtent={false} /> { return ( - - Number(d).toFixed(2)} - /> + + Number(d).toFixed(2)} /> Date: Wed, 19 Feb 2020 13:06:22 -0600 Subject: [PATCH 10/15] Fix story naming --- ...ries-label-visually-looks-correct-1-snap.png | Bin 0 -> 19265 bytes stories/styling.tsx | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-visually-looks-correct-1-snap.png diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..92752b6c79509a509c6f3d8b3a00a70e6b93d368 GIT binary patch literal 19265 zcmb`v1z40_*ET$eg3XOVhae#$AxM`+mx$6ONFzvhC@M$_h%~5_bcu9HiIj8baYk0=?tKK} z$SeXuV0N4co|&sP#=rxC{e9`%h};eu_{$}P?A==rT;k_PT{YfL9W<|O<_lVN_#8j} zlG6GaQ-FM8#IFFdiEy{m!l@kZYQ75(T6~G-_+pe;*ckG}BrB~u({)5JBT|a?nB1+q zat|8fRfyggdwJherMXFThjs#6&Y5dDT~=c+G4A(Cd}IV0RA*g+Bt86z`sI{H{RjTn zu;B~cl6T=pC+K#$KmI3BMTQ*z7u)M6XBqK7oKg(B_*XPEUP%eWzoL}lr33yIq~ytO zIPtHTFcj@@Agpm-n^75a!*F=*tw`@|EVK?57||M)m6cu7ua3{myy~_-*Z3{$GN!UR zGczDnJ}Nva%CDqEprxhdC^0eNo5aM9G{rb>iqlL?On=bQdJgBx<3BZpO~-{3{*!yUz16Z&_gr|)sy0SxF&NwjJM0-I)(*;iQ3oRX|bbl+T>(puSTt% zv){)lqzUIO4Hswipq*KI)gL@CC_%2QuRFTwkqQ-^lW-4R@FU^J#LpVB^mOGddMCkL z#(tEvaz1h9OruGWiiU>F!-p3sDJj=xx(I#t_B^B!p#cGg$S+S2k4;U_Q&9zUnK50x z8qn6J@V&KF$ZqoZv17*^X1g0_vAa7v{5yTHSkpC6@pEY&2}@K}ULM;_=N=RgaPlfU zdrMo}adDA}nYnLwr?gaXVqzjTT?IyEt7$p#WHMM_oSBuC zoQsRgZOwmjI4kcxdRt7%t>Z;kgv;D(KmUOFv7m(hr9iIg$)9zMYTo5E&dvkKwWnSV z7YGOrOv{6Lr(cqjuY9+ZpQF()ayvHd_Qfb_&`OkNlZ`*krs=UoYq7e1-HR0?8Wcug z&(Cza=4RK2J8O#<>*seDnXY`QvOL^t=A&x+rE%)jxRC6mzb;H80|) zdNC3jE2LQ5_7Pm^6cLf0Ai{==t9r+cn%=u|j^yQ9k2&rc;S0Z{sc?cS*m+6`!rd9F z&Ag-EDed*O`$B#~vvN}R9jEr| zoRqf*`$lD$kPB_ovG4hL9vxB5i)lkHai)Iy6ulA^GQFB|Vno25b7*`i#U@Fw&2!7E zvZ~5Lx4zq$-PQJ2)`JHRc-(Y&aL)UE9(ygBMDErB#h7l_#qF0g;vPqBY`AhOoOn=N zw2G3F$GEEYsFFOtyjn#yQ;M2hDrql00Ri&P^G#FJnqqW2OJSkcrP;z2ir^+Kq;6rM z_a(=%BX)M!nI8<%+N#zcy=ycOWYv3A^w)523Rh7_znuD6N4%IO9r{hUDp(=9c1rfV zW`*_T{$%Obvy|94X>u6;yz;m+u}Y!pfGoGh z`XmG?p%2m~K?!oA%ie2K!3Q4Eug^De?>Au(N-jAacFxYtqoboMdqod%(>oavn4BC) zn0<4_ZAqI~AY6|g-8wJfah%?>G60sl&|+QdV{$SDzr!Dh9-GRLh5}g{L5*IXF+u!p z^!5s@#(k+Pa7ETwt*yhXP7W5FlWHQ#;5Ffd$jAo>5#Fc+QcE>#Xo6pOeam4;j-h4>wJ6eNLiYO;~#;R8-Tl+7TnL`n|Ptr6b%s zY~vT}h-atoi0y8B_ReEUHH?Z{ilD=eYoW`ox=x|PCFDg?9V%)*dOuk8n`7A z*UcPt|5?g2wSlMfnR&79dfp3?-S29G`z)qXqx9`7ov`onC=x+O)12 z%};SJ;hZ0E2T;Z|dPuNh!<}Lhm)`c;7Senlp*B(_-5%SZ{^2FAj?yoQPI+ zkA}6l+{^j8wOs7;sv6j(Ga&<#oBmAv{6wF2TRSp4E!Cd;kx&O!R8SWbTyJP<$~W(c zDy^u1;Wg#zmcizGs-|&;q3+h1jVQP?IyyQR7ndJ3`t;SfN34j1%femh%3PwNqQ_5K zbQ*hCyoG7f#uUS5<)a9GLcz1eIKN@RqDUis)29aIUW1K#Gd5Vv-zQCsH6U3Le^An6l zT3a>HuI95g&ki2&Qk)jupRB*;W_%qxEusA2vGaZFvS-W7MS^Z?6r1f{M+NS*I}R^6 zL4l>sdonJbU+20>PG_*Yp}?XPJ0Ym1r6t#ib^noLX=+_P>z3$#V7#%o9~Q{DbE+h5 zr1xi>lK1GZ+h71qC5ZAg5u{Qfugb4*BL%{A)WJ_mXJ2KE zIMhGD?+XszMMj=N>N`aGFOazGOC=;0%8z~#G-C;g13zVEvdkPs3Muu!cC!x9yN<@r z@3CItlIWnesjiRrOZvGWfP0yj$E7T`@&_r}DRa}2b(rnKxpQ}MUY;&ii}P;H%|X>K zt|xQ-`bK&RUoj-}s#_LHlx*&gckW@2DX>V4d0LW6C5MGdXtu9jq~|j~yR~3%qvVjI zSl~1&b!2aUb~MY>m+#RBT5hV~`R_xDdC^#c!7}zJfB#sl^j!x-Our4yI`LVuM+I81 z{by1Z@OXEEDpxE|-Pee>qvn0r0Clhwes!jCSV^_)z34lB0Yb34c_odw|nw zY?Tj@nreJae;Zl7oL7DDs_K}8A)vKQ0o3fT*M8FNZYDP?5CEH5r5Cxy=XasCe0@qwC}-JZ*_@y8y`2m8d|Myvdm4k~;BNqKmBF0ZUK)z??8 z$l{s{jF=rxlQf&%xDU}05K(%dzaM6~uTSswIY8ya zb?u_o$dMkZz8uJB*&iWj!GySs@>?EX(6mw*f6=b?J&>#Fc1?BFi4$kL8mhP z>3nX^XOiRI^8KBDf?Brmr=HhG+a4TK8h>uPmzsP%PVt%-RyR@3lMJfqNB6sS08J2Y z-rh*uUG#pDQll?#P=VN4Ur>~iYCo9v+&vkyJJYl0F?n2ETs(-*@j4+F4WhPB^`N6C zqjPlzg)KGSxzQ9%+gRr-IpTn&9kk-#Sr48XtFVz^9*=^_yit*``HmC)K{~P4fcvR+ zx_zm$nqm!3V8KGq)$zoD@-DITHL~Z=hk3np7`^x9aZ>59Mc<&OCF*!PtG^j3x798YS(!r0A9to=)T`S@jWn!>Yo%y& z&}++}s3S()gok#2u0}l-A&UU z?{7I;WM*b+lsmAwy1Kf!yGO*wD?EM5jhLOCwHp3>!nf|`?b}DXy1LSL&*59WyYbzO zYTcVlBE9ljr zpuqLt#KdT(bMo>=h?e7<8<)r6$Vfs5bCwRLy&Jb zB{y7bsh-|s#cx!gas1S&dg~GqwI7Ns=g9&aRZI&sW)6Ek&Z}4LGCP`^n`ea%SCp!o z@k<2)uquU*=k7*dU+!=mL)~~LvZue-!r@4TuSFgHEWNZgfCr%`@7R>v8);jEKY*Dz5 zm$sK9kCfYsnl1%=bsujAoCcwAiTNSXc0 z$_j&!5T#FT?MgfDq#PMfi{QVXOqDONk;bQ^m|4>+Ae_I&V(aJ|iTSrHN~hHedP@e^}R6(O+Ed82cJ^diO66 zE+rr{F6Y(k9KrN^G&GzrigE3v0h|&yBLB5<#cFu(qI4)7p@lfN?Ru;ulMc^Er6;y4 zk2V&N%2AwX{%z529?fhuDHa8TA3sz?d2z>b6YX599RC4mpA;>Q<(#T;ULr8qToiLy z+Cgf_$Z(rPyasstn`W{cjMX0fIY_cgMYmfOcKH?QASc4vS%8x^${&~s@1;=1#Z=GT zn}+V2kK1bo2=k%vZSBUr7R}SnDMvN4m!hlvD7?)X(N4nYE$Fa!ExlhjY&g7PB)KHy zyHi#i7PN5gwDM0Nl8CJyN3Je)&SbqsJY*I5x(MCctyG15eYRg$?FS|kFvyWJT#`3!RkJ?W|ucFI1Qq#xq$?yh>@ zFl#+F);8%Kwn+uD$+kTPIh0e^w}$Po@X)ojsS6d2+_}b8Bbdag%ZQ+GpPgD~**k%1 z=6fDQUvza6Agi z{q_CUnLOGt7h4o2fSH-Uva<3xi3GJz@NC?mdMPet_c|4l+iK?0Vj1p{+nQkLS=!qO zzUA^CKJ-mpd^{0A{v#Co#uQE!-@z z)}7gF4VY??qu~pWG*5auUgazpLM!OYCd!UNZ^*$-78 zK5Xnxm2YWntw$o`CMO?1_w^-^QC2qXNmHaaO?2+uIscFl@r8j)5LAp4L1YEtdRkiF z=lZhBR+^~G7QP?>ntSZ85BwDf09-KvkivK-0*FZk7S)7uAT)Q@W(jXTe$4dp<;%BG zQ4x`mk`4|xq>@RCh7?>}1ciiz($}e=woLX?$8bLz8L8wQef5pLAL=WRluuV0$*~RR zm4BouQg;ZUY1}sEHPbsRiiN*c)EU%1=6O`C1sEW@-9$Q}rba?7YyI;&7mUlBQ{S^V z*t@DQdVb0I+&P+t?oB<39*!3C2Ra_n>GzXRfr{Esc}q#@C0@8MR_BZj1JakC7H@1? zYmZiVQnoS29?k6)WaP2`3b)+0M}9@v!GEmEFxqoG;pnkq8%l=#tre6N@=Gw%rX~Su z{ptqNrpUOJ465e6ti^$go39cDoGKak<8r$26Co<9)li}eU9YjGnn!)zc9QDOf?j6G z;4-xb-G`#)34tvbvlhpg_*hVl!wn$aG^;0)c}GLyrJO1fv~6nqI1?_rV1r)*$q<&L z|HELv(limh+$p{Fv=IqZL#u>I8x!Nl^HuKalj)u>N8EAsZ84q#`@8GEUZ+pB=87g>Ho(P-nUdaxeF>(rbHF30Mnf~LBf z?<>!Fn#E|2^G<}XJb3s}C?rR>B&;>B^sEmyIl0tjl3Uo}xb^BtcVTL}4N&bom__6a zWS+WP3$p_y&Q-;ck&)S2RqrtAN(`53lYkgI_nRX->GLpa;2bS&WBvzUGh~10tbktg zfHtq-)BI1yFC4tRy`dUP189UPOAv8Bcj3a(n~(u6`$$Vl7Cb@nBMOU(@LasvjdK?- zN}8Ck01^dY`QrKWBjpaW6KmU^o}SeE7O+XM`lbHSjPpLH*D2wmot5BS1)LxlYXWK{Z63!8~X0g*$ z!T~Mn`}CCK2PI>}o-cvhU{7{08MiatwALN0awkDRP5$6MFy3_g_Vep;3P3rf8^2h4 z^X3oE%H`W+*EDKXmBkuYP|an_Ur!MX=075I-{{pbv$DDd5W6iwB&shML*_6O$}wyPy?U3Ze3=drE+Fc z!;3JF)gdYb6ddj9z1TqMvmzN~lbtBw*!y#oS$|FZ%06%qNx;-@@(y5}%+FCkXUePgvGC5xuBX6(MeH+?%<(L5dgV{tieq3i8oPh(v`67v_>Zx>ys z3U@-pZP;fdO(Kqh`6~?Wt0bnwXi3 z$9FY&7khZr@Dki)XQ$1*O`>KGH%AAzL_*@T!gNdH-ahX)7BIaIbG>1CCE1Q1eXN3? z#L&{42K%oZo;Z^FLGcI0OsVhHOQ%}18-?tqC8)s;-LkuP9gbXr4On4kM{D#C_TsP@ z6KoVr{mM9Qa>DOr3Xjr^DXDRO=N^jRK7A!|mM;$oM!0Idjl+C1|p02K< z>$WY3It9puw^8&1#LQpL)xSFX@n>@q5Pj=FFs#-EgkE9iLhoUd5rcT1;9Y;E-`Dzj zUx00^s~-jOwL%L$rO7P&vsrC>We~s;|EfV24Mta=fQcm6cY6suDUE?mv4EGG391(yA;<+C>cQ_goF>wQVuzP2l3 zV(r}64#n4AR4Vy&!Yo% zJn@zgo_F>FGu0JuIMio;Y#huC#Pgza>s)I{{1DB_SnX3&7#uX#flXyHW|8#|E$h$N4RWOk(BNQ zU?>D<&Pj#JPe%^Ci=TZrdD>-d#TAdx7eorFvf2J*V=5zqtAaiM6N8c8g`F2e)=bD2N;+jJU3-Z}c^E8f@J@V~CM z??53{qxB0Q!G>z571(WSi#{FKtz7Tl1(9hE-$_f^Jl*s9n->78RFS>uSvoq_v1p#9 z7_`tH_E0DZ6?NdkCV7Sd?R~0NdG}=5=7C?C>(08z#tP4oBW-Po*egGd+}SA^_ma=k ztD2+zZ$;-rFtWxDA=5FG&lndIRb4ND*?c-LLx~vA9l9?k+Ipd-9;@>MWJ zYjS96Vy}KDZ&9@=z{-rwOgkEFh}I~5TUvy`D0k$kolWL^U4-Y(n&G!Mfl8&ZSG_s= z`uapyCOsBQ_sLEZ-SKt&E5-BW+T5H4$nRYSG9}0bR*$hkTc{dB_G6wV_P$0qH-iKy z@dw+&diw)M#9h3vOU$?Z3T|AwQ5FSk+#~+Dt<~0?5ThiI9ibCZ4Feg)$c6cDZE1lU zbtP0wT)MY57j<6;%zJ$;|9kc03^5w5aC`)fC<5x8-=eNJ^{Eplnyh#V+&2^7tu%G% zFN~B$oTU~10no~=xgC#BUgqa>1My~bY!onGfd-8uJe8|kA?!K!@grmUoQH=;d?Fw$ zrLhl%_sN!q$Fl7cr;zVtNyFNO({Cs?z1kyccJg$t1s>yf`SY>NEq{(q^p3I2D=Tc- zXJQGOjOgrN9;ngtX|ynIjne1EcDxmK?5rt0?(FQ`6r^OfDwHzC#wiXO=QY&2y1v=x zFTH9fdprCAWDI%<0fr99Thc7nQ{}a`)MTit&fx@bwY9rz#2@$ix6prAN-8`dLD9_Y z3bcK|5s`6dH7x(CXXD^VegEDkC50)yllsDiUbEnKlb(}R{@={+>sX^=7WDZr@%z5@ z7rJ#dR1fH>&z+lh8DY5b;^Q5^x!(DqqNo~X-6@xY^>@XN7Mx{FiRzqj&w&q)<-^Qg z?1|V|AhhhOJZM5@YbSMlB~SdINUfai{`Q^a*dBSbs1TrlUbCE%P=R|9=K=o^2>v$> zanWchur>5vTOT@d&I4;yn<&QT&Ep>&+z1t?%pP6nwrs;Z`DzVRs&BPNmm#@$LToVNj)_t4w0m%)>GMTb<>oIq4*M#BxA7_DP zbDElWqCp<22}%g4$%g7mxkmGU@1wOkB6@)H%h0B=Lqk_C!*V#m%1v{mAPxP@;Dg8$F>I!@_?Ur*$*zc0yU7qm&=aEdue~_ zn(MBop2qSGmWbD@t0>T@_}tP-%(urh3;khlHDkL_;jYWiG#fTT2n;Y^PCKEec+DpS zL13d0GQ0vZvad9Kb<$qdG04n`t{L_O6qFJ#)b%_gpt+wxrVDUYubtfA*9`o&}8?ZWRp*BFCT@f%?*HOb}KbU_@$8jMzD9MFcxBM>m)c47V zMe}bzpIum#sQr119ID5ZV6{t&J5Ym$6De%K9$qIT)@{=*q7$6O%CwUH@l)BrK%|-6 zr=_=l1zP~^b(T7AV<1M`bmT?G6Kx>LCqgO~(bKa71AFIA#5<}{khI4%f($_Fy&@P8 z5D>$=YmESMF2$OxW>FUBSQF-7THVk&X0-duH<+CN)=qSb!(#O5)5*h)ND=Wx$ zVN={s?lF4tYEAT82|ql2nH^KoUE`g^I~wqrQA-xj+?v-8J5tvfytf%2GcCVpZv5F+ z4!Xt^x*qH3y2VN{(XHBRBO{-4+0vs9tyMB&dTx)#VytgzUEkNbf-l_@hW{^x`S0ed zzjBd7!XAW9JUI`>9>C^yQ<3WD$jH>P(*Be^i-q$J4EAn{3I|Jy_i`Pv!~9@aO9ZR^ z-j*$J{&T&LA3wgQsoByN!`t%xJ0k~2aE5C7eI1>)>Gn8TS=kTS*+Iunk^QK0cNP^B z>#U8$^BVr~8i9k0cHkld*|7foEnk4Pu!|s-&h`GeIvmAtIIVB-9D($Z)Um+`U#-UcVWw|wWw zkt5OEkG`@YQBU~e@EAum9kPM6G`3G&zfT(o{l9Vi{r}4GgRe_~Z|jtmZbe+2Ulo*) zvoREnBb!{7{lNJvy)$kUn6F+NGD+ZFjt!hCoryI`P`#_89C2qy*Ln11yNOXtIP=KU z`ZE{E$d2SYFX=5-VmnN1{<-BKxzb81F8x_mqr`IU%YH`#`os=)LmsL7b%# zyxAN=`z#iD{Rr%z`0}%d@{JZ{`t6-hD#<{FO&1U7;V1D-|C7=3;vp)i0+4HR;!nF)iTvPp-Z)a?;ldF@~(ITSQpHO%0LJAS3x({XV*Y=?K- zpWVXXIaFxO#L8Oax$A`J&(^F@k&PIvbhR%M_&sVWzHPD~qvjCfsqc|G?hC~Y4GnL? z!k+p05d#Y$a&iCA<@i}x@$Bs8s53pe&QpRSlXeIcU;Zb(tX$6Qcna?9tgLOv+PF27Hse4=5`}7b)TdZ4Ha~|2BG|6dYOt_X|G!Be2;W z(Z3zB-{=P>#b)s&1~4>h^F!x-7Dp>ki;GI*)t+(xG!!ujiKOdBP5B0OM-YppsIzIx zNlJcaY5!@oNL>I4_}y-H!;qU|u*O@$%gf6H2t|k}KVo{1KS)Ajosgf+>laGK_0s=+ z9-gU>!VkT{m`^B$n24y*w37~awBxX=JK}{9pla~B+t;9{r<8=g&9NloI6rVUYzc8|a;zn~S4(%VnzAVUWcKvDi4ra6dW%7D*ZjuXxDts$k>< z4;<_-@r(O>vOrux13++zhbJ6%JSar?bT2GSmE7e&WW^#X8V^6bBRPjVZ$Gv&+-tp0 zU4%|KUN8*wr~6PLhD?)2vxE2Z0GA|stjdB*3{NeU{d|6Eu2&;RO@ZY+g2KYS_*XUiaGiMT`fhy(8zk$H(b`afaLqrrXOffAjNJ zx9{A6&Jv%3&AJ$0k zB(vt)BZJ8tN z;Nl8}q>1d>0zG*H}^}_O6J3?Enu#n#fqd(PERMUlJElC3(1co{_O0yH?9h` z(kX#!gMT`f9*`m12M%$A4EwZ$Op<^N9fH@S?Ghw=d9JDp2(S;|zI~g0o~s<8gTP zZ}jM1Ik)@w&s~-Z`k9{OH8Gc!X95NFjF;J)!?@hLt~5TH3L*K~av%hcjlgz*7Z|>O z|GvDuTw9;qLa&22UNSPCFH<-8o)(QBC(f06!*KN1VbeS;a{%AdejAy5N$Jr$JazL_t@g=hf~&{ z*}_ou>61U6?Advt0&OSU%Rl{bX-jl>zDTpoj@dNHlOHiOG~}`M%P-!(L#TZ$9zP}- z%ihVzwN|d0c%WW++&8QciSCO+XX!*`z%~DmqT|Z!*%p2Mfx0@jLuBb57}KF61&jzf ze`3JF@0m@AD6iWtwM9_~SjXczC=`^8&rgtF{$3qpD@Rks1;$)1-+6V~JS5}yyQ)l1 z{650a?V)5*(HaB#fh&|GTYG!dD{AXcyn261nHpUqJ1*eC=}Ijqj6C-rMb#Kg25BiBLT zb*k2P9LfL8JOL>F%{(#i@T@xMJwJ>eRjszfwNQzJXt>10bpO*yFr`;1t|DqW6k{$Y5 zY*m!f@e!#y)O-TS_9DvxPCEDb>%b2KaWJ($pl|y9EnD$#5$As-NxV#Nd#d{4G9GLd zC2SG5LLyx>rPOe$D9}V-0OyQ$35J4;%0vJYA2oiUZtZ3FxTu4T(OV%X%6v8OiBs`qz}h2e*0XcV8$R8U%Yx{c!LU0b##4{JWVei53u~?$&-K% z?RNISQd6)Uc#j&3! zU3GM5Y;A4RD}N(JiOj~4-^Qzy;(LGLc+Fqw@cxf8I{*G6hZ^qbiqVee$4&`4%~K%I zN#5c><5HY{rR4}U?3!-*+saB&5R+wDSXz}902>hs57|4!0>ElW~RrB7C)bi z9cH8qqXyjE#@{ate|aJgu;Uat=VNq7f&(m@ny!=-5fFoI@j?;6rgnFCM+-Y%ZES3` z8mo+X`}P848I61cg3$`+=AItxY2C2HTvD&q<`f0|8=MoEbE>Esi~UnVZqA|DjrU@) z&(_E(Doz2xwYE4KXFXcpM_F=sHs`&`I-bM(KXqFU{7AYla9m9pcf;gB=TqpiVhABL z0|%ROR&ctHY|I*~KA`ZgV$6Ln`O*PlV`Ee9h>>z~a!Pn(%^z2waSa?_cx8Yc1E+ri z8z(*$X#mFhtQ53VM~@!82ZmuaH8St*R(%9G zzncFdz(6Lsy&T*86uIA-ZPhQtQI&v`luh9iSzTj&pl#JI@FDKDo99(ntNc`e8xJq` zown?!luRjxC9i_E9za+`c=$Ph0keHsCb6 zkBA^Re){y|S|5Uxw6uxoX(>a)kBjq%^U0Yd`C<(ORJhdrrNPOT2-;9Mx$xfri_dHt z_+Lq@*O6+_|6rym_!QP}86B=j4>d*28mq zwsL{TXA$*&Bw5Wh`NCjr1Jx%~7 z^tPrZyqb_HS|;alg^^7t#9h!Z}`_hB5W(3js;DE4Ecl`{cY zSEitgfBf~u2{<()jB={jf6+-TGWJJYd%qd}$PFDG#~hLk{}G&-jVX{JYh>k43v+8_2`APS&m63xmxnuWJnon#fa~;K z`=!O`W9`D|P~Ne$nV3HKZRJ@zG8vN1UK*T}%gD_&0RqJV(;o~JP(M}ce`2Mq$;-1z z)`y1Ey!sRwOm3K3j%0QUdTJ^xA)#GaV!r`sFain@r)y$vYkzrrbR>vi2aQ7eMEG4- zA2%BTJ_`>IpUVmS7wE0k;B;83!us$2`-7bSzDWFuF#jtTIV2>oyzO@P?$NQaG1hE2y_lk$B#r=qOPifBa%+<|f!U9Y2cEcCwmW>X}0a`z2r}2Ts z?f@Ys)=`{`UOBha9*o~?jNP%tTW|gXJfeZEkB3KJBD6kmWn}-1F*!R+JL0P%V^g+4 zzO!mBg?+V_t7f*BuAvoZVbB%74w_b3RVdIUGQ1Yo89Y}e8Yj_cqoSc`Xf46Ri}yC4 zq2|XI0C4yg6cj*n$K$-H15A#Lk`fURlFK+`87}%0hByS3C*t+`5^?Lj3p*z@{Xbk9 zrQ&%N8`~qZDxEf!_wZdL$FPIBIXkySaSX3+dp|vywXkM}Jo}Ttxtl!mJs=&I8?vADN@bVU04e@B^s7*~zUu9xy z_(@3s$FobwxS2hnKQ9p#x1UxRANPI!`~ZE@RY@rXI+k)KYIwCuLvy`B_3X3J4C4WCG}wQCX=&7AT2o z3RbnSm|IB159eo;kB01~TNS)40=TPO=s{<|RVEDdjv^X1V#}1BIkdl2P!j-#vB;L& zO$ERXdYhD#2h1*h&qnmjR*v?-r4W1P{$Q@k%~gU5FgIFJlC72nyx#*|UC$m@KN9-> zdJ>6ivU0MrD!RG}T3T8UR8^CEdmn%uuLam-28LU#O7W%tbfFI)Zt=&pI}e!@WfT;w zq+zM3sR8gX%NA-}19j)Vk&#gxD*OVR8E;L(Pj=fSgN+m%Yu*56z}CBSmsL?w5$6)p zGy8S`vyK_zH>xN%Kq~I0p~v4WRCexB^`K_guIqBDSJ3<7q%AEi(?e%%oe%aWR!T)Y z6`aW@t<25Kazc~RPbK>5g{p_A_B1a%&|Ral6`>dLtM8gMa%#cWA-9dnbrWz)L$eM} zGbc<+$kSgMIdSa$ zyLV*Ah>6*+T=_P0WyU8hjm2?kY;=ZV1`*ZD9NL`xh*h^LJ3$)J zP{OzMzuq7hrdLkg%gM<(NkXFj^y$sL@?!>!1S^@Kp5Fjs<{~+H>#T#tw>J!P19?eI z=ADURa`N)3T3WIA(9zb;0$m-(rVIRT1k5+wEUiXdsRpN9rz@ zSXfxVt!IKEV#tagj{MIwygrY=Gx!dOED(2+jouSNG(|>7Uy%(63hJ%e9LusC$fb-w zc@W_5|E6zeAWtK-+2|=1;+cP>!(L8PlN4NMdXwCNNd^!IRjf z^5g98=F$KVS(l<(Q_1yQ>ShEHM_?TmCf418Ll7emAts^J^Fd5OMxmr7UVw8y8PF{P zbs!_+yx0qDcu>gEOFsFd$Gq9EUX?p@=1g@f{0F&&hnw{2=F>8r9bo6)CM4v5TR9o^ z-j|4kE*sSx+PdIGO?&Jn;zJ6`<_wMCJxFD0^74Um7>pyhfY+DC$7eKZX**FXW(&i` zZXk*pmmq^fe4TdoH^)8QyGUq-nc~{h!1Uy+N^qQsjBA&CnFLN2WkEt{u_(3x7J*9C zCBG}|XF6DrK-=&H^coBLN@RR|HZbqhw5Pm2<>d6i>8P&hh%D&D#nyh@Axe8*Q;j=t zhi&@Zu;2;{ixeETn(Ijq{jV5+C^g^#hz*Z_tqS>^b%V_gLn#K?C^;E{SmcI+=cQTc zQUC^+C1oe4FD7l#AAsGhX}-eF9x-Y?0B5(5H2T#ZW$=P`T>4`}1r-lKGQ6*j14jz3j_dptY^ z3}xS~ODt!8e1KAQ-`2K(DWkL$h$da^bbAC7t*FaA`{}kWAO-kL+9)`5O7D?#>MR?V z6=c@fqND&CSTy*PYE?MpcpYq;D#i<{Kr7n=X7;HS7i*!Y)m1yi1mTD99Gqi*$D$NJ z)7m{eoC)l%7JLGP+FPcJWMuI9yVD2bNL@g-8R>~`ay0!z@39f(j$IfX-S@Sy>(0Y70wCK0sV$I-J2&d~*1` z1oIq1Czk1m>CezEv94BteP0IG!)FU+@$I|Nm;jv+%c)oS2~7QFFo9I?B_N9S8Bi)5 z96kdansg8@cEy@-=vV6i#-kGVCqW1b+pFD)kW-wrh1#R6tU zEQ;YwkUCpof40Z+2|xYm1F}pmN8I)2&!4a5dGBN1q2PN+Q^5!K?!6x>GPQ(n ze;Ec7hKMOtGiKVG#OsTr+5phQQ$W{d2XGKRX;4ZpN&Cp~N(; z;6_Bo(v_3=ja)?O5`^s2+Fb;W9y=x*evK?JrDp8P777x550KF}07>WQRcS#2tzjF7 zp=VA{o4}+1+K>aAHqO2ho#?IvwsSSuo+h1%IhcXG-HlR0#1A@1TR%7jHd6E}T~xqX z7@h)B1VF^uf@V5ZS=ranM5hM@o!0-50OMrJfN~X{GCZsW#WO>{#>;Iw#?+%(US1vo zAQCD7d=ZTqfQR8~&k8MVZO83ZW8KoHw;-~BO_+OIRyt}e>s?jw3E~?PXdDL4q2@u; zas^@-6u4Mk(-85kpM385zSnGP8`3@ zST?X{vfV)GK+1inub;P$8Nh&SJvUxcqn4wU3Y6=@c#XbkM|>B2n^LhwUnajk)xP&N z&BD7t)eeJ2x_W;@dunoWE7#J>sux6Xc3C%cg7XctAD6^%dsquIvwOF0)hQamr~q`z z!o>H3k_P}k6{K2z_ay?va#Z8)c#;Udf8oaG-v9lsW_moKPI*k { +export const addCustomSeriesLabel = () => { const customSeriesNamingFn: SeriesNameFn = ({ yAccessor, splitAccessors }) => { // eslint-disable-next-line react/prop-types if (yAccessor === 'y1' && splitAccessors.get('g') === 'a') { @@ -958,7 +958,7 @@ export const addCustomFullAndSubSeriesLabel = () => { ); }; -addCustomFullAndSubSeriesLabel.story = { +addCustomSeriesLabel.story = { name: 'Add custom series label', }; From 1ed69572407daf2afe956ae4d667fe60d5316029 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Thu, 20 Feb 2020 14:53:20 +0100 Subject: [PATCH 11/15] refactor: decouple tooltip from XY chart (#553) This commit will decouple the tooltip component from the XY chart to allow Partition and other chart type an ease use. BREAKING CHANGE: the `SeriesIdentifier` type is generalized into a simplified object with two values in common: `specId` and `key`. A specialized `XYChartSeriesIdentifier` extends now the base `SeriesIdentifier`. The `SettingsSpec` prop `showLegendDisplayValue` is renamed to `showLegendExtra` and its default value is now `false` hiding the current/last value on the legend by default. close #246 --- .playground/playground.tsx | 8 +- docs/0-Intro/1-Overview.mdx | 4 +- .../partition_chart/state/chart_state.tsx | 9 ++ .../crosshair/crosshair_utils.test.ts | 114 +++++-------- .../xy_chart/crosshair/crosshair_utils.ts | 121 ++++---------- src/chart_types/xy_chart/legend/legend.ts | 6 +- .../xy_chart/renderer/dom/crosshair.tsx | 4 +- .../xy_chart/renderer/dom/tooltips.tsx | 151 ------------------ .../xy_chart/rendering/rendering.test.ts | 8 +- .../xy_chart/rendering/rendering.ts | 16 +- .../state/chart_state.interactions.test.ts | 105 ++++++------ .../xy_chart/state/chart_state.test.ts | 63 +++++--- .../state/chart_state.timescales.test.ts | 20 +-- .../state/chart_state.tooltip.test.ts | 6 +- .../xy_chart/state/chart_state.tsx | 28 +++- .../selectors/get_annotation_tooltip_state.ts | 7 +- .../selectors/get_legend_tooltip_values.ts | 4 +- .../state/selectors/get_tooltip_position.ts | 15 +- .../state/selectors/get_tooltip_snap.ts | 3 +- .../state/selectors/get_tooltip_type.ts | 11 +- .../get_tooltip_values_highlighted_geoms.ts | 43 ++--- .../state/selectors/is_tooltip_visible.ts | 35 ++-- .../selectors/on_element_click_caller.ts | 4 +- .../state/selectors/on_element_out_caller.ts | 4 +- .../state/selectors/on_element_over_caller.ts | 8 +- src/chart_types/xy_chart/state/utils.ts | 13 +- .../xy_chart/tooltip/tooltip.test.ts | 36 ++--- src/chart_types/xy_chart/tooltip/tooltip.ts | 32 ++-- .../xy_chart/utils/interactions.test.ts | 4 +- .../xy_chart/utils/interactions.ts | 62 +------ src/chart_types/xy_chart/utils/series.test.ts | 4 +- src/chart_types/xy_chart/utils/series.ts | 26 +-- src/chart_types/xy_chart/utils/specs.ts | 20 ++- src/components/_index.scss | 2 +- src/components/legend/_legend_item.scss | 12 +- src/components/legend/legend.test.tsx | 10 +- src/components/legend/legend.tsx | 11 +- src/components/legend/legend_item.tsx | 90 +++++------ src/components/tooltip/_index.scss | 1 + src/components/{ => tooltip}/_tooltip.scss | 0 src/components/tooltip/index.tsx | 142 ++++++++++++++++ src/components/tooltip/types.ts | 6 + src/components/tooltip/utils.ts | 78 +++++++++ src/index.ts | 3 +- src/mocks/series/seriesIdentifiers.ts | 14 +- src/mocks/specs/specs.ts | 5 +- src/specs/settings.test.tsx | 9 +- src/specs/settings.tsx | 85 ++++++++-- src/state/actions/legend.ts | 6 +- src/state/chart_state.ts | 62 +++++-- src/state/reducers/interactions.ts | 7 +- .../get_internal_is_tooltip_visible.ts | 9 ++ .../get_internal_tooltip_anchor_position.ts | 10 ++ .../selectors/get_internal_tooltip_info.ts | 10 ++ src/state/selectors/get_legend_size.ts | 8 +- .../selectors/get_tooltip_header_formatter.ts | 7 +- src/utils/domain.ts | 28 +--- src/utils/geometry.ts | 12 +- stories/annotations.tsx | 2 +- stories/area_chart.tsx | 14 +- stories/bar_chart.tsx | 32 ++-- stories/interactions.tsx | 45 ++++-- stories/legend.tsx | 16 +- stories/line_chart.tsx | 10 +- stories/mixed.tsx | 10 +- stories/rotations.tsx | 17 +- stories/styling.tsx | 37 +++-- wiki/overview.md | 4 +- 68 files changed, 959 insertions(+), 849 deletions(-) delete mode 100644 src/chart_types/xy_chart/renderer/dom/tooltips.tsx create mode 100644 src/components/tooltip/_index.scss rename src/components/{ => tooltip}/_tooltip.scss (100%) create mode 100644 src/components/tooltip/index.tsx create mode 100644 src/components/tooltip/types.ts create mode 100644 src/components/tooltip/utils.ts create mode 100644 src/state/selectors/get_internal_is_tooltip_visible.ts create mode 100644 src/state/selectors/get_internal_tooltip_anchor_position.ts create mode 100644 src/state/selectors/get_internal_tooltip_info.ts rename src/{chart_types/xy_chart => }/state/selectors/get_tooltip_header_formatter.ts (58%) diff --git a/.playground/playground.tsx b/.playground/playground.tsx index c3bd5e6f2b..aba458f9ad 100644 --- a/.playground/playground.tsx +++ b/.playground/playground.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Chart, ScaleType, Position, Axis, getAxisId, timeFormatter, getSpecId, AreaSeries } from '../src'; +import { Chart, ScaleType, Position, Axis, getAxisId, timeFormatter, getSpecId, AreaSeries, Settings } from '../src'; import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana'; export class Playground extends React.Component { chartRef: React.RefObject = React.createRef(); @@ -13,6 +13,7 @@ export class Playground extends React.Component { <>
+ { + return [...d, d[1] - 10]; + })} />
diff --git a/docs/0-Intro/1-Overview.mdx b/docs/0-Intro/1-Overview.mdx index 82c5b0e6fd..62e4be3f14 100644 --- a/docs/0-Intro/1-Overview.mdx +++ b/docs/0-Intro/1-Overview.mdx @@ -233,7 +233,7 @@ Return types: ```ts type BarStyleAccessor = ( datum: RawDataSeriesDatum, - seriesIdentifier: SeriesIdentifier, + seriesIdentifier: XYChartSeriesIdentifier, ) => RecursivePartial | Color | null; ``` @@ -250,7 +250,7 @@ Return types: ```ts type PointStyleAccessor = ( datum: RawDataSeriesDatum, - seriesIdentifier: SeriesIdentifier, + seriesIdentifier: XYChartSeriesIdentifier, ) => RecursivePartial | Color | null; ``` diff --git a/src/chart_types/partition_chart/state/chart_state.tsx b/src/chart_types/partition_chart/state/chart_state.tsx index efcd81e594..53bd8f9ac2 100644 --- a/src/chart_types/partition_chart/state/chart_state.tsx +++ b/src/chart_types/partition_chart/state/chart_state.tsx @@ -27,4 +27,13 @@ export class PartitionState implements InternalChartState { getPointerCursor() { return 'default'; } + isTooltipVisible() { + return false; + } + getTooltipInfo() { + return undefined; + } + getTooltipAnchor() { + return null; + } } 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 dbe6e52b22..3508b06bac 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts @@ -1,4 +1,4 @@ -import { getFinalTooltipPosition } from './crosshair_utils'; +import { getFinalTooltipPosition } from '../../../components/tooltip/utils'; describe('Tooltip position', () => { const container = { @@ -19,15 +19,11 @@ describe('Tooltip position', () => { container, tooltip, { - isRotatedHorizontal: true, - vPosition: { - bandHeight: 0, - bandTop: 0, - }, - hPosition: { - bandWidth: 0, - bandLeft: 10, - }, + isRotated: false, + y1: 0, + y0: 0, + x0: 10, + x1: 10, }, 5, ); @@ -39,15 +35,11 @@ describe('Tooltip position', () => { container, tooltip, { - isRotatedHorizontal: true, - vPosition: { - bandHeight: 0, - bandTop: 90, - }, - hPosition: { - bandWidth: 0, - bandLeft: 10, - }, + isRotated: false, + y0: 90, + y1: 90, + x0: 10, + x1: 10, }, 5, ); @@ -59,15 +51,11 @@ describe('Tooltip position', () => { container, tooltip, { - isRotatedHorizontal: true, - vPosition: { - bandHeight: 0, - bandTop: 0, - }, - hPosition: { - bandWidth: 0, - bandLeft: 100, - }, + isRotated: false, + y0: 0, + y1: 0, + x0: 100, + x1: 100, }, 5, ); @@ -79,15 +67,11 @@ describe('Tooltip position', () => { container, tooltip, { - isRotatedHorizontal: true, - vPosition: { - bandHeight: 0, - bandTop: 90, - }, - hPosition: { - bandWidth: 0, - bandLeft: 100, - }, + isRotated: false, + y0: 90, + y1: 90, + x0: 100, + x1: 100, }, 5, ); @@ -101,15 +85,11 @@ describe('Tooltip position', () => { container, tooltip, { - isRotatedHorizontal: false, - vPosition: { - bandHeight: 0, - bandTop: 0, - }, - hPosition: { - bandWidth: 0, - bandLeft: 10, - }, + isRotated: true, + y0: 0, + y1: 0, + x1: 10, + x0: 10, }, 5, ); @@ -121,15 +101,11 @@ describe('Tooltip position', () => { container, tooltip, { - isRotatedHorizontal: false, - vPosition: { - bandHeight: 0, - bandTop: 90, - }, - hPosition: { - bandWidth: 0, - bandLeft: 10, - }, + isRotated: true, + y0: 90, + y1: 90, + x1: 10, + x0: 10, }, 5, ); @@ -141,15 +117,11 @@ describe('Tooltip position', () => { container, tooltip, { - isRotatedHorizontal: false, - vPosition: { - bandHeight: 0, - bandTop: 0, - }, - hPosition: { - bandWidth: 0, - bandLeft: 100, - }, + isRotated: true, + y0: 0, + y1: 0, + x1: 100, + x0: 100, }, 5, ); @@ -161,15 +133,11 @@ describe('Tooltip position', () => { container, tooltip, { - isRotatedHorizontal: false, - vPosition: { - bandHeight: 0, - bandTop: 90, - }, - hPosition: { - bandWidth: 0, - bandLeft: 100, - }, + isRotated: true, + y0: 90, + y1: 90, + x1: 100, + x0: 100, }, 5, ); diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts index 3852d8416b..84200e19ba 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.ts @@ -1,29 +1,14 @@ import { Rotation } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; import { Scale } from '../../../scales'; -import { isHorizontalRotation } from '../state/utils'; +import { isHorizontalRotation, isVerticalRotation } from '../state/utils'; import { Point } from '../../../utils/point'; +import { TooltipAnchorPosition } from '../../../components/tooltip/utils'; export interface SnappedPosition { position: number; band: number; } -export interface TooltipPosition { - /** true if the x axis is horizontal */ - isRotatedHorizontal: boolean; - vPosition: { - /** the top position of the tooltip relative to the parent */ - bandTop: number; - /** the height of the crosshair band if any */ - bandHeight: number; - }; - hPosition: { - /** the left position of the tooltip relative to the parent */ - bandLeft: number; - /** the width of the crosshair band if any */ - bandWidth: number; - }; -} export const DEFAULT_SNAP_POSITION_BAND = 1; @@ -162,32 +147,32 @@ export function getCursorBandPosition( } } -export function getTooltipPosition( +export function getTooltipAnchorPosition( chartDimensions: Dimensions, chartRotation: Rotation, cursorBandPosition: Dimensions, cursorPosition: { x: number; y: number }, isSingleValueXScale: boolean, -): TooltipPosition { - const isHorizontalRotated = isHorizontalRotation(chartRotation); +): TooltipAnchorPosition { + const isRotated = isVerticalRotation(chartRotation); const hPosition = getHorizontalTooltipPosition( cursorPosition.x, cursorBandPosition, chartDimensions, - isHorizontalRotated, + isRotated, isSingleValueXScale, ); const vPosition = getVerticalTooltipPosition( cursorPosition.y, cursorBandPosition, chartDimensions, - isHorizontalRotated, + isRotated, isSingleValueXScale, ); return { - isRotatedHorizontal: isHorizontalRotated, - vPosition, - hPosition, + isRotated, + ...vPosition, + ...hPosition, }; } @@ -195,91 +180,39 @@ function getHorizontalTooltipPosition( cursorXPosition: number, cursorBandPosition: Dimensions, chartDimensions: Dimensions, - isHorizontalRotated: boolean, + isRotated: boolean, isSingleValueXScale: boolean, -): { bandLeft: number; bandWidth: number } { - if (isHorizontalRotated) { +): { x0: number; x1: number } { + if (!isRotated) { return { - bandLeft: cursorBandPosition.left, - bandWidth: isSingleValueXScale ? 0 : cursorBandPosition.width, - }; - } else { - return { - bandWidth: 0, - bandLeft: chartDimensions.left + cursorXPosition, + x0: cursorBandPosition.left, + x1: cursorBandPosition.left + (isSingleValueXScale ? 0 : cursorBandPosition.width), }; } + return { + x0: 0, + x1: chartDimensions.left + cursorXPosition, + }; } function getVerticalTooltipPosition( cursorYPosition: number, cursorBandPosition: Dimensions, chartDimensions: Dimensions, - isHorizontalRotated: boolean, + isRotated: boolean, isSingleValueXScale: boolean, ): { - bandHeight: number; - bandTop: number; + y0: number; + y1: number; } { - if (isHorizontalRotated) { - return { - bandHeight: 0, - bandTop: cursorYPosition + chartDimensions.top, - }; - } else { + if (!isRotated) { return { - bandHeight: isSingleValueXScale ? 0 : cursorBandPosition.height, - bandTop: cursorBandPosition.top, + y0: cursorYPosition + chartDimensions.top, + y1: cursorYPosition + chartDimensions.top, }; } -} - -export function getFinalTooltipPosition( - /** the dimensions of the chart parent container */ - container: Dimensions, - /** the dimensions of the tooltip container */ - tooltip: Dimensions, - /** the tooltip computed position not adjusted within chart bounds */ - tooltipPosition: TooltipPosition, - /** the padding to add between the tooltip position and the final position */ - padding = 10, -): { - left: string | null; - top: string | null; -} { - const { hPosition, vPosition, isRotatedHorizontal: isHorizontalRotated } = tooltipPosition; - let left = 0; - let top = 0; - if (isHorizontalRotated) { - const leftOfBand = window.pageXOffset + container.left + hPosition.bandLeft; - if (hPosition.bandLeft + hPosition.bandWidth + tooltip.width + padding > container.width) { - left = leftOfBand - tooltip.width - padding; - } else { - left = leftOfBand + hPosition.bandWidth + padding; - } - const topOfBand = window.pageYOffset + container.top; - if (vPosition.bandTop + tooltip.height > container.height) { - top = topOfBand + container.height - tooltip.height; - } else { - top = topOfBand + vPosition.bandTop; - } - } else { - const leftOfBand = window.pageXOffset + container.left; - if (hPosition.bandLeft + hPosition.bandWidth + tooltip.width > container.width) { - left = leftOfBand + container.width - tooltip.width; - } else { - left = leftOfBand + hPosition.bandLeft + hPosition.bandWidth; - } - const topOfBand = window.pageYOffset + container.top + vPosition.bandTop; - if (vPosition.bandTop + vPosition.bandHeight + tooltip.height + padding > container.height) { - top = topOfBand - tooltip.height - padding; - } else { - top = topOfBand + vPosition.bandHeight + padding; - } - } - return { - left: `${Math.round(left)}px`, - top: `${Math.round(top)}px`, + y0: cursorBandPosition.top, + y1: (isSingleValueXScale ? 0 : cursorBandPosition.height) + cursorBandPosition.top, }; } diff --git a/src/chart_types/xy_chart/legend/legend.ts b/src/chart_types/xy_chart/legend/legend.ts index 42f4df4cf8..324a00edc6 100644 --- a/src/chart_types/xy_chart/legend/legend.ts +++ b/src/chart_types/xy_chart/legend/legend.ts @@ -4,8 +4,8 @@ import { SeriesCollectionValue, getSeriesIndex, getSortedDataSeriesColorsValuesMap, - SeriesIdentifier, getSeriesLabel, + XYChartSeriesIdentifier, } from '../utils/series'; import { AxisSpec, BasicSeriesSpec, Postfixes, isAreaSeriesSpec, isBarSeriesSpec } from '../utils/specs'; import { Y0_ACCESSOR_POSTFIX, Y1_ACCESSOR_POSTFIX } from '../tooltip/tooltip'; @@ -20,7 +20,7 @@ export type LegendItem = Postfixes & { key: string; color: string; label: string; - seriesIdentifier: SeriesIdentifier; + seriesIdentifier: XYChartSeriesIdentifier; isSeriesVisible?: boolean; banded?: boolean; isLegendItemVisible?: boolean; @@ -59,7 +59,7 @@ export function computeLegend( specs: BasicSeriesSpec[], defaultColor: string, axesSpecs: AxisSpec[], - deselectedDataSeries: SeriesIdentifier[] = [], + deselectedDataSeries: XYChartSeriesIdentifier[] = [], ): Map { const legendItems: Map = new Map(); const sortedCollection = getSortedDataSeriesColorsValuesMap(seriesCollection); diff --git a/src/chart_types/xy_chart/renderer/dom/crosshair.tsx b/src/chart_types/xy_chart/renderer/dom/crosshair.tsx index eef4518d06..edb043e647 100644 --- a/src/chart_types/xy_chart/renderer/dom/crosshair.tsx +++ b/src/chart_types/xy_chart/renderer/dom/crosshair.tsx @@ -1,6 +1,5 @@ import React, { CSSProperties } from 'react'; import { connect } from 'react-redux'; -import { TooltipType } from '../../utils/interactions'; import { isHorizontalRotation } from '../../state/utils'; import { Dimensions } from '../../../../utils/dimensions'; import { Theme } from '../../../../utils/themes/theme'; @@ -13,6 +12,7 @@ import { getCursorLinePositionSelector } from '../../state/selectors/get_cursor_ import { getTooltipTypeSelector } from '../../state/selectors/get_tooltip_type'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { LIGHT_THEME } from '../../../../utils/themes/light_theme'; +import { TooltipType } from '../../../../specs'; interface CrosshairProps { theme: Theme; @@ -108,7 +108,7 @@ const mapStateToProps = (state: GlobalChartState): CrosshairProps => { chartRotation: getChartRotationSelector(state), cursorBandPosition: getCursorBandPositionSelector(state), cursorLinePosition: getCursorLinePositionSelector(state), - tooltipType: getTooltipTypeSelector(state), + tooltipType: getTooltipTypeSelector(state) || TooltipType.VerticalCursor, }; }; diff --git a/src/chart_types/xy_chart/renderer/dom/tooltips.tsx b/src/chart_types/xy_chart/renderer/dom/tooltips.tsx deleted file mode 100644 index 8d3d14b200..0000000000 --- a/src/chart_types/xy_chart/renderer/dom/tooltips.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import { connect } from 'react-redux'; -import { TooltipValue, TooltipValueFormatter } from '../../utils/interactions'; -import { GlobalChartState, BackwardRef } from '../../../../state/chart_state'; -import { isTooltipVisibleSelector } from '../../state/selectors/is_tooltip_visible'; -import { getTooltipHeaderFormatterSelector } from '../../state/selectors/get_tooltip_header_formatter'; -import { getTooltipPositionSelector } from '../../state/selectors/get_tooltip_position'; -import { getTooltipValuesSelector, TooltipData } from '../../state/selectors/get_tooltip_values_highlighted_geoms'; -import { isInitialized } from '../../../../state/selectors/is_initialized'; -import { createPortal } from 'react-dom'; -import { getFinalTooltipPosition, TooltipPosition } from '../../crosshair/crosshair_utils'; -import { isAnnotationTooltipVisibleSelector } from '../../state/selectors/is_annotation_tooltip_visible'; - -interface TooltipStateProps { - isTooltipVisible: boolean; - isAnnotationTooltipVisible: boolean; - tooltip: TooltipData; - tooltipPosition: TooltipPosition | null; - tooltipHeaderFormatter?: TooltipValueFormatter; -} -interface TooltipOwnProps { - getChartContainerRef: BackwardRef; -} -type TooltipProps = TooltipStateProps & TooltipOwnProps; - -class TooltipsComponent extends React.Component { - static displayName = 'Tooltips'; - portalNode: HTMLDivElement | null = null; - tooltipRef: React.RefObject; - - constructor(props: TooltipProps) { - super(props); - this.tooltipRef = React.createRef(); - } - createPortalNode() { - const container = document.getElementById('echTooltipContainerPortal'); - if (container) { - this.portalNode = container as HTMLDivElement; - } else { - this.portalNode = document.createElement('div'); - this.portalNode.id = 'echTooltipContainerPortal'; - document.body.appendChild(this.portalNode); - } - } - componentDidMount() { - this.createPortalNode(); - } - - componentDidUpdate() { - this.createPortalNode(); - const { getChartContainerRef, tooltipPosition } = this.props; - const chartContainerRef = getChartContainerRef(); - - if (!this.tooltipRef.current || !chartContainerRef.current || !this.portalNode || !tooltipPosition) { - return; - } - - const chartContainerBBox = chartContainerRef.current.getBoundingClientRect(); - const tooltipBBox = this.tooltipRef.current.getBoundingClientRect(); - const tooltipStyle = getFinalTooltipPosition(chartContainerBBox, tooltipBBox, tooltipPosition); - - if (tooltipStyle.left) { - this.portalNode.style.left = tooltipStyle.left; - } - if (tooltipStyle.top) { - this.portalNode.style.top = tooltipStyle.top; - } - } - - componentWillUnmount() { - if (this.portalNode && this.portalNode.parentNode) { - this.portalNode.parentNode.removeChild(this.portalNode); - } - } - - renderHeader(headerData: TooltipValue | null, formatter?: TooltipValueFormatter) { - if (!headerData) { - return null; - } - - return formatter ? formatter(headerData) : headerData.value; - } - - render() { - const { isTooltipVisible, tooltip, tooltipHeaderFormatter, isAnnotationTooltipVisible } = this.props; - if (!this.portalNode) { - return null; - } - const { getChartContainerRef } = this.props; - const chartContainerRef = getChartContainerRef(); - let tooltipComponent; - if (chartContainerRef.current === null || !isTooltipVisible || isAnnotationTooltipVisible) { - return null; - } else { - tooltipComponent = ( -
-
{this.renderHeader(tooltip.header, tooltipHeaderFormatter)}
-
- {tooltip.values.map(({ name, value, color, isHighlighted, seriesKey, yAccessor, isVisible }) => { - if (!isVisible) { - return null; - } - const classes = classNames('echTooltip__item', { - /* eslint @typescript-eslint/camelcase:0 */ - echTooltip__rowHighlighted: isHighlighted, - }); - return ( -
- {name} - {value} -
- ); - })} -
-
- ); - } - return createPortal(tooltipComponent, this.portalNode); - } -} - -const mapStateToProps = (state: GlobalChartState): TooltipStateProps => { - if (!isInitialized(state)) { - return { - isTooltipVisible: false, - isAnnotationTooltipVisible: false, - tooltip: { - header: null, - values: [], - }, - tooltipPosition: null, - tooltipHeaderFormatter: undefined, - }; - } - return { - isTooltipVisible: isTooltipVisibleSelector(state), - isAnnotationTooltipVisible: isAnnotationTooltipVisibleSelector(state), - tooltip: getTooltipValuesSelector(state), - tooltipPosition: getTooltipPositionSelector(state), - tooltipHeaderFormatter: getTooltipHeaderFormatterSelector(state), - }; -}; - -export const Tooltips = connect(mapStateToProps)(TooltipsComponent); diff --git a/src/chart_types/xy_chart/rendering/rendering.test.ts b/src/chart_types/xy_chart/rendering/rendering.test.ts index 3cd4abc0ef..4586163712 100644 --- a/src/chart_types/xy_chart/rendering/rendering.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.test.ts @@ -6,7 +6,7 @@ import { getClippedRanges, } from './rendering'; import { BarSeriesStyle, SharedGeometryStateStyle, PointStyle } from '../../../utils/themes/theme'; -import { DataSeriesDatum, SeriesIdentifier } from '../utils/series'; +import { DataSeriesDatum, XYChartSeriesIdentifier } from '../utils/series'; import { mergePartial, RecursivePartial } from '../../../utils/commons'; import { BarGeometry, PointGeometry } from '../../../utils/geometry'; import { MockDataSeries } from '../../../mocks'; @@ -95,7 +95,7 @@ describe('Rendering utils', () => { }); describe('should get common geometry style dependent on legend item highlight state', () => { - const seriesIdentifier: SeriesIdentifier = { + const seriesIdentifier: XYChartSeriesIdentifier = { specId: 'id', yAccessor: 'y1', splitAccessors: new Map(), @@ -222,7 +222,7 @@ describe('Rendering utils', () => { initialY1: 4, initialY0: 5, }; - const seriesIdentifier: SeriesIdentifier = { + const seriesIdentifier: XYChartSeriesIdentifier = { specId: 'test', yAccessor: 'test', splitAccessors: new Map(), @@ -314,7 +314,7 @@ describe('Rendering utils', () => { initialY1: 4, initialY0: 5, }; - const seriesIdentifier: SeriesIdentifier = { + const seriesIdentifier: XYChartSeriesIdentifier = { specId: 'test', yAccessor: 'test', splitAccessors: new Map(), diff --git a/src/chart_types/xy_chart/rendering/rendering.ts b/src/chart_types/xy_chart/rendering/rendering.ts index 3d006d0937..f74410db0b 100644 --- a/src/chart_types/xy_chart/rendering/rendering.ts +++ b/src/chart_types/xy_chart/rendering/rendering.ts @@ -10,7 +10,7 @@ import { } from '../../../utils/themes/theme'; import { Scale, ScaleType, isLogarithmicScale } from '../../../scales'; import { CurveType, getCurveFactory } from '../../../utils/curves'; -import { DataSeriesDatum, SeriesIdentifier, DataSeries } from '../utils/series'; +import { DataSeriesDatum, DataSeries, XYChartSeriesIdentifier } from '../utils/series'; import { DisplayValueSpec, PointStyleAccessor, BarStyleAccessor } from '../utils/specs'; import { IndexedGeometry, @@ -41,7 +41,7 @@ export function mutableIndexedGeometryMapUpsert( export function getPointStyleOverrides( datum: DataSeriesDatum, - seriesIdentifier: SeriesIdentifier, + seriesIdentifier: XYChartSeriesIdentifier, pointStyleAccessor?: PointStyleAccessor, ): Partial | undefined { const styleOverride = pointStyleAccessor && pointStyleAccessor(datum, seriesIdentifier); @@ -61,7 +61,7 @@ export function getPointStyleOverrides( export function getBarStyleOverrides( datum: DataSeriesDatum, - seriesIdentifier: SeriesIdentifier, + seriesIdentifier: XYChartSeriesIdentifier, seriesStyle: BarSeriesStyle, styleAccessor?: BarStyleAccessor, ): BarSeriesStyle { @@ -125,7 +125,7 @@ function renderPoints( y = yScale.scale(yDatum); } const originalY = hasY0Accessors && index === 0 ? initialY0 : initialY1; - const seriesIdentifier: SeriesIdentifier = { + const seriesIdentifier: XYChartSeriesIdentifier = { key: dataSeries.key, specId: dataSeries.specId, yAccessor: dataSeries.yAccessor, @@ -268,7 +268,7 @@ export function renderBars( } : undefined; - const seriesIdentifier: SeriesIdentifier = { + const seriesIdentifier: XYChartSeriesIdentifier = { key: dataSeries.key, specId: dataSeries.specId, yAccessor: dataSeries.yAccessor, @@ -522,7 +522,7 @@ export function getClippedRanges(dataset: DataSeriesDatum[], xScale: Scale, xSca } export function getGeometryStateStyle( - seriesIdentifier: SeriesIdentifier, + seriesIdentifier: XYChartSeriesIdentifier, highlightedLegendItem: LegendItem | null, sharedGeometryStyle: SharedGeometryStateStyle, individualHighlight?: { [key: string]: boolean }, @@ -564,7 +564,3 @@ export function isPointOnGeometry( const { width, height } = indexedGeometry; return yCoordinate >= y && yCoordinate <= y + height && xCoordinate >= x && xCoordinate <= x + width; } - -export function getSeriesIdentifierPrefixedKey(seriesIdentifier: SeriesIdentifier, prefix?: string, postfix?: string) { - return `${prefix || ''}${seriesIdentifier.key}${postfix || ''}`; -} diff --git a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts index 888c334603..b9d464ded8 100644 --- a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts @@ -1,15 +1,14 @@ import { createStore, Store } from 'redux'; import { BarSeriesSpec, BasicSeriesSpec, AxisSpec, SeriesTypes } from '../utils/specs'; import { Position } from '../../../utils/commons'; -import { TooltipType } from '../utils/interactions'; import { ScaleType } from '../../../scales'; import { chartStoreReducer, GlobalChartState } from '../../../state/chart_state'; -import { SettingsSpec, DEFAULT_SETTINGS_SPEC, SpecTypes } from '../../../specs'; +import { SettingsSpec, DEFAULT_SETTINGS_SPEC, SpecTypes, TooltipType } from '../../../specs'; import { computeSeriesGeometriesSelector } from './selectors/compute_series_geometries'; import { getProjectedPointerPositionSelector } from './selectors/get_projected_pointer_position'; import { getHighlightedGeomsSelector, - getTooltipValuesAndGeometriesSelector, + getTooltipInfoAndGeometriesSelector, } from './selectors/get_tooltip_values_highlighted_geoms'; import { isTooltipVisibleSelector } from './selectors/is_tooltip_visible'; import { createOnBrushEndCaller } from './selectors/on_brush_end_caller'; @@ -221,9 +220,9 @@ describe('Chart state pointer interactions', () => { store.dispatch(upsertSpec(updatedSettings)); store.dispatch(specParsed()); store.dispatch(onPointerMove({ x: 10, y: 10 + 70 }, 0)); - const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); + const tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); // no tooltip values exist if we have a TooltipType === None - expect(tooltipData.tooltip.values.length).toBe(0); + expect(tooltipInfo.tooltip.values.length).toBe(0); let isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(false); @@ -284,8 +283,8 @@ function mouseOverTestSuite(scaleType: ScaleType) { onElementOverCaller(state); onPointerMoveCaller(state); }); - const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltip.values).toEqual([]); + const tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.tooltip.values).toEqual([]); }); test('store is correctly configured', () => { @@ -299,15 +298,15 @@ function mouseOverTestSuite(scaleType: ScaleType) { store.dispatch(onPointerMove({ x: chartLeft + 10, y: chartTop + 10 }, 0)); expect(onPointerUpdateListener).toBeCalledTimes(1); - const tooltipData1 = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData1.tooltip.values.length).toBe(1); + const tooltipInfo1 = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo1.tooltip.values.length).toBe(1); // avoid calls store.dispatch(onPointerMove({ x: chartLeft + 12, y: chartTop + 12 }, 1)); expect(onPointerUpdateListener).toBeCalledTimes(1); - const tooltipData2 = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData2.tooltip.values.length).toBe(1); - expect(tooltipData1).toEqual(tooltipData2); + const tooltipInfo2 = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo2.tooltip.values.length).toBe(1); + expect(tooltipInfo1).toEqual(tooltipInfo2); }); test('call pointer update listener on move', () => { @@ -385,8 +384,8 @@ function mouseOverTestSuite(scaleType: ScaleType) { }); test('can hover top-left corner of the first bar', () => { - let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltip.values).toEqual([]); + let tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.tooltip.values).toEqual([]); store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 0 }, 0)); let projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); expect(projectedPointerPosition).toEqual({ x: 0, y: 0 }); @@ -396,9 +395,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(cursorBandPosition!.width).toBe(45); let isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); - tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltip.values.length).toBe(1); - expect(tooltipData.highlightedGeometries.length).toBe(1); + tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.tooltip.values.length).toBe(1); + expect(tooltipInfo.highlightedGeometries.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(0); expect(onOverListener.mock.calls[0][0]).toEqual([ @@ -423,9 +422,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(projectedPointerPosition).toEqual({ x: -1, y: -1 }); isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(false); - tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltip.values.length).toBe(0); - expect(tooltipData.highlightedGeometries.length).toBe(0); + tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.tooltip.values.length).toBe(0); + expect(tooltipInfo.highlightedGeometries.length).toBe(0); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(1); }); @@ -440,9 +439,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(cursorBandPosition!.width).toBe(45); let isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); - let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.highlightedGeometries.length).toBe(1); - expect(tooltipData.tooltip.values.length).toBe(1); + let tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.highlightedGeometries.length).toBe(1); + expect(tooltipInfo.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(0); expect(onOverListener.mock.calls[0][0]).toEqual([ @@ -466,9 +465,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(projectedPointerPosition).toEqual({ x: -1, y: 89 }); isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(false); - tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltip.values.length).toBe(0); - expect(tooltipData.highlightedGeometries.length).toBe(0); + tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.tooltip.values.length).toBe(0); + expect(tooltipInfo.highlightedGeometries.length).toBe(0); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(1); }); @@ -487,9 +486,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(cursorBandPosition!.width).toBe(45); let isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); - let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.highlightedGeometries.length).toBe(1); - expect(tooltipData.tooltip.values.length).toBe(1); + let tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.highlightedGeometries.length).toBe(1); + expect(tooltipInfo.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(0); expect(onOverListener.mock.calls[0][0]).toEqual([ @@ -518,9 +517,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(cursorBandPosition!.width).toBe(45); isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); - tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltip.values.length).toBe(1); - expect(tooltipData.highlightedGeometries.length).toBe(0); + tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.tooltip.values.length).toBe(1); + expect(tooltipInfo.highlightedGeometries.length).toBe(0); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(1); }); @@ -539,9 +538,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(cursorBandPosition!.width).toBe(45); let isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); - let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.highlightedGeometries.length).toBe(1); - expect(tooltipData.tooltip.values.length).toBe(1); + let tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.highlightedGeometries.length).toBe(1); + expect(tooltipInfo.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOutListener).toBeCalledTimes(0); expect(onOverListener.mock.calls[0][0]).toEqual([ @@ -570,10 +569,10 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(cursorBandPosition!.width).toBe(45); isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); - tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltip.values.length).toBe(1); + tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.tooltip.values.length).toBe(1); // we are over the second bar here - expect(tooltipData.highlightedGeometries.length).toBe(1); + expect(tooltipInfo.highlightedGeometries.length).toBe(1); expect(onOverListener).toBeCalledTimes(2); expect(onOverListener.mock.calls[1][0]).toEqual([ [ @@ -600,9 +599,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { test('can hover top-right corner of the chart', () => { expect(onOverListener).toBeCalledTimes(0); expect(onOutListener).toBeCalledTimes(0); - let tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.highlightedGeometries.length).toBe(0); - expect(tooltipData.tooltip.values.length).toBe(0); + let tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.highlightedGeometries.length).toBe(0); + expect(tooltipInfo.tooltip.values.length).toBe(0); store.dispatch(onPointerMove({ x: chartLeft + 89, y: chartTop + 0 }, 0)); const projectedPointerPosition = getProjectedPointerPositionSelector(store.getState()); @@ -614,9 +613,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { const isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); - tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.highlightedGeometries.length).toBe(0); - expect(tooltipData.tooltip.values.length).toBe(1); + tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.highlightedGeometries.length).toBe(0); + expect(tooltipInfo.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(0); expect(onOutListener).toBeCalledTimes(0); }); @@ -666,9 +665,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { expect(cursorBandPosition!.width).toBe(45); const isTooltipVisible = isTooltipVisibleSelector(store.getState()); expect(isTooltipVisible).toBe(true); - const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.highlightedGeometries.length).toBe(1); - expect(tooltipData.tooltip.values.length).toBe(1); + const tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.highlightedGeometries.length).toBe(1); + expect(tooltipInfo.tooltip.values.length).toBe(1); expect(onOverListener).toBeCalledTimes(1); expect(onOverListener.mock.calls[0][0]).toEqual([ [ @@ -744,9 +743,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { }); test('chart 0 rotation', () => { store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 89 }, 0)); - const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltip.header?.value).toBe('bottom 0'); - expect(tooltipData.tooltip.values[0].value).toBe('left 10'); + const tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.tooltip.header?.value).toBe('bottom 0'); + expect(tooltipInfo.tooltip.values[0].value).toBe('left 10'); }); test('chart 90 deg rotated', () => { @@ -758,9 +757,9 @@ function mouseOverTestSuite(scaleType: ScaleType) { store.dispatch(upsertSpec(updatedSettings)); store.dispatch(specParsed()); store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 89 }, 0)); - const tooltipData = getTooltipValuesAndGeometriesSelector(store.getState()); - expect(tooltipData.tooltip.header?.value).toBe('left 1'); - expect(tooltipData.tooltip.values[0].value).toBe('bottom 5'); + const tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); + expect(tooltipInfo.tooltip.header?.value).toBe('left 1'); + expect(tooltipInfo.tooltip.values[0].value).toBe('bottom 5'); }); }); describe('brush', () => { diff --git a/src/chart_types/xy_chart/state/chart_state.test.ts b/src/chart_types/xy_chart/state/chart_state.test.ts index cff92ad127..eadbb35557 100644 --- a/src/chart_types/xy_chart/state/chart_state.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.test.ts @@ -8,14 +8,13 @@ import { SeriesTypes, } from '../utils/specs'; import { Position } from '../../../utils/commons'; -import { TooltipType, TooltipValue } from '../utils/interactions'; import { ScaleType, ScaleContinuous, ScaleBand } from '../../../scales'; import { IndexedGeometry, GeometryValue, BandedAccessorType } from '../../../utils/geometry'; import { AxisTicksDimensions, isDuplicateAxis } from '../utils/axis_utils'; import { AxisId } from '../../../utils/ids'; import { LegendItem } from '../legend/legend'; import { ChartTypes } from '../..'; -import { SpecTypes } from '../../../specs/settings'; +import { SpecTypes, TooltipValue, TooltipType } from '../../../specs/settings'; describe.skip('Chart Store', () => { let store: any = null; // @@ -772,13 +771,15 @@ describe.skip('Chart Store', () => { test.skip('can update the tooltip visibility', () => { const tooltipValue: TooltipValue = { - name: 'a', + label: 'a', value: 'a', color: 'a', isHighlighted: false, - isXValue: false, - seriesKey: 'a', - yAccessor: 'y', + seriesIdentifier: { + specId: 'a', + key: 'a', + }, + valueAccessor: 'y', isVisible: true, }; store.cursorPosition.x = -1; @@ -886,13 +887,15 @@ describe.skip('Chart Store', () => { store.addSeriesSpec(spec); store.setOnBrushEndListener(() => ({})); const tooltipValue: TooltipValue = { - name: 'a', + label: 'a', value: 'a', color: 'a', isHighlighted: false, - isXValue: false, - seriesKey: 'a', - yAccessor: 'y', + seriesIdentifier: { + specId: 'a', + key: 'a', + }, + valueAccessor: 'y', isVisible: true, }; store.xScale = new ScaleContinuous({ type: ScaleType.Linear, domain: [0, 100], range: [0, 100] }); @@ -1030,23 +1033,27 @@ describe.skip('Chart Store', () => { store.annotationDimensions.set(rectAnnotationSpec.id, annotationDimensions); const highlightedTooltipValue: TooltipValue = { - name: 'foo', + label: 'foo', value: 1, color: 'color', isHighlighted: true, - isXValue: false, - seriesKey: 'foo', - yAccessor: 'y', + seriesIdentifier: { + specId: 'a', + key: 'a', + }, + valueAccessor: 'y', isVisible: true, }; const unhighlightedTooltipValue: TooltipValue = { - name: 'foo', + label: 'foo', value: 1, color: 'color', isHighlighted: false, - isXValue: false, - seriesKey: 'foo', - yAccessor: 'y', + seriesIdentifier: { + specId: 'foo', + key: 'foo', + }, + valueAccessor: 'y', isVisible: true, }; @@ -1070,28 +1077,32 @@ describe.skip('Chart Store', () => { expect(store.legendItemTooltipValues.get()).toEqual(new Map()); const headerValue: TooltipValue = { - name: 'header', + label: 'header', value: 'foo', color: 'a', isHighlighted: false, - isXValue: true, - seriesKey: 'headerSeries', + seriesIdentifier: { + specId: 'headerSeries', + key: 'headerSeries', + }, + valueAccessor: BandedAccessorType.Y0, isVisible: true, - yAccessor: BandedAccessorType.Y0, }; store.tooltipData.replace([headerValue]); expect(store.legendItemTooltipValues.get()).toEqual(new Map()); const tooltipValue: TooltipValue = { - name: 'a', + label: 'a', value: 123, color: 'a', isHighlighted: false, - isXValue: false, - seriesKey: 'seriesKeys', + seriesIdentifier: { + specId: 'seriesKeys', + key: 'seriesKeys', + }, + valueAccessor: BandedAccessorType.Y1, isVisible: true, - yAccessor: BandedAccessorType.Y1, }; store.tooltipData.replace([headerValue, tooltipValue]); diff --git a/src/chart_types/xy_chart/state/chart_state.timescales.test.ts b/src/chart_types/xy_chart/state/chart_state.timescales.test.ts index d6c0540ed5..7565f59546 100644 --- a/src/chart_types/xy_chart/state/chart_state.timescales.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.timescales.test.ts @@ -9,7 +9,7 @@ import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { updateParentDimensions } from '../../../state/actions/chart_settings'; import { computeSeriesGeometriesSelector } from './selectors/compute_series_geometries'; import { onPointerMove } from '../../../state/actions/mouse'; -import { getTooltipValuesSelector } from './selectors/get_tooltip_values_highlighted_geoms'; +import { getTooltipInfoSelector } from './selectors/get_tooltip_values_highlighted_geoms'; import { DateTime } from 'luxon'; import { getComputedScalesSelector } from './selectors/get_computed_scales'; import { ChartTypes } from '../..'; @@ -69,17 +69,17 @@ describe('Render chart', () => { }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip - let tooltip = getTooltipValuesSelector(store.getState()); + let tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(day1); expect(tooltip.values[0].value).toBe(10); store.dispatch(onPointerMove({ x: 35, y: 10 }, 1)); // check second valid tooltip - tooltip = getTooltipValuesSelector(store.getState()); + tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(day2); expect(tooltip.values[0].value).toBe(22); store.dispatch(onPointerMove({ x: 76, y: 10 }, 2)); // check third valid tooltip - tooltip = getTooltipValuesSelector(store.getState()); + tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(day3); expect(tooltip.values[0].value).toBe(6); @@ -138,17 +138,17 @@ describe('Render chart', () => { }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip - let tooltip = getTooltipValuesSelector(store.getState()); + let tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date1); expect(tooltip.values[0].value).toBe(10); store.dispatch(onPointerMove({ x: 35, y: 10 }, 1)); // check second valid tooltip - tooltip = getTooltipValuesSelector(store.getState()); + tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date2); expect(tooltip.values[0].value).toBe(22); store.dispatch(onPointerMove({ x: 76, y: 10 }, 2)); // check third valid tooltip - tooltip = getTooltipValuesSelector(store.getState()); + tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date3); expect(tooltip.values[0].value).toBe(6); @@ -225,17 +225,17 @@ describe('Render chart', () => { }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip - let tooltip = getTooltipValuesSelector(store.getState()); + let tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date1); expect(tooltip.values[0].value).toBe(10); store.dispatch(onPointerMove({ x: 35, y: 10 }, 1)); // check second valid tooltip - tooltip = getTooltipValuesSelector(store.getState()); + tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date2); expect(tooltip.values[0].value).toBe(22); store.dispatch(onPointerMove({ x: 76, y: 10 }, 2)); // check third valid tooltip - tooltip = getTooltipValuesSelector(store.getState()); + tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date3); expect(tooltip.values[0].value).toBe(6); diff --git a/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts b/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts index c4c703f103..29938e954e 100644 --- a/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts @@ -3,9 +3,9 @@ import { createStore, Store } from 'redux'; import { upsertSpec, specParsed } from '../../../state/actions/specs'; import { MockSeriesSpec, MockGlobalSpec } from '../../../mocks/specs'; import { updateParentDimensions } from '../../../state/actions/chart_settings'; -import { getTooltipValuesAndGeometriesSelector } from './selectors/get_tooltip_values_highlighted_geoms'; +import { getTooltipInfoAndGeometriesSelector } from './selectors/get_tooltip_values_highlighted_geoms'; import { onPointerMove } from '../../../state/actions/mouse'; -import { TooltipType } from '../utils/interactions'; +import { TooltipType } from '../../../specs'; describe('XYChart - State tooltips', () => { let store: Store; @@ -46,7 +46,7 @@ describe('XYChart - State tooltips', () => { ); store.dispatch(specParsed()); const state = store.getState(); - const tooltipValues = getTooltipValuesAndGeometriesSelector(state); + const tooltipValues = getTooltipInfoAndGeometriesSelector(state); expect(tooltipValues.tooltip.values).toHaveLength(expectedTooltipValuesLength); expect(tooltipValues.tooltip.header === null).toBe(expectHeader); expect(tooltipValues.highlightedGeometries).toHaveLength(expectedHgeomsLength); diff --git a/src/chart_types/xy_chart/state/chart_state.tsx b/src/chart_types/xy_chart/state/chart_state.tsx index 83932a45ae..1c0f4497f4 100644 --- a/src/chart_types/xy_chart/state/chart_state.tsx +++ b/src/chart_types/xy_chart/state/chart_state.tsx @@ -1,20 +1,23 @@ import React, { RefObject } from 'react'; -import { InternalChartState, GlobalChartState, BackwardRef } from '../../../state/chart_state'; -import { ChartTypes } from '../..'; -import { Tooltips } from '../renderer/dom/tooltips'; -import { htmlIdGenerator } from '../../../utils/commons'; +import { XYChart } from '../renderer/canvas/xy_chart'; import { Highlighter } from '../renderer/dom/highlighter'; import { Crosshair } from '../renderer/dom/crosshair'; +import { BrushTool } from '../renderer/dom/brush'; +import { InternalChartState, GlobalChartState, BackwardRef } from '../../../state/chart_state'; +import { TooltipLegendValue } from '../tooltip/tooltip'; +import { ChartTypes } from '../..'; import { AnnotationTooltip } from '../renderer/dom/annotation_tooltips'; import { isBrushAvailableSelector } from './selectors/is_brush_available'; -import { BrushTool } from '../renderer/dom/brush'; import { isChartEmptySelector } from './selectors/is_chart_empty'; import { computeLegendSelector } from './selectors/compute_legend'; import { getLegendTooltipValuesSelector } from './selectors/get_legend_tooltip_values'; -import { TooltipLegendValue } from '../tooltip/tooltip'; import { getPointerCursorSelector } from './selectors/get_cursor_pointer'; import { isBrushingSelector } from './selectors/is_brushing'; -import { XYChart } from '../renderer/canvas/xy_chart'; +import { isTooltipVisibleSelector } from './selectors/is_tooltip_visible'; +import { getTooltipInfoSelector } from './selectors/get_tooltip_values_highlighted_geoms'; +import { htmlIdGenerator } from '../../../utils/commons'; +import { Tooltip } from '../../../components/tooltip'; +import { getTooltipAnchorPositionSelector } from './selectors/get_tooltip_position'; export class XYAxisChartState implements InternalChartState { chartType = ChartTypes.XYAxis; @@ -39,7 +42,7 @@ export class XYAxisChartState implements InternalChartState { - + @@ -49,4 +52,13 @@ export class XYAxisChartState implements InternalChartState { getPointerCursor(globalState: GlobalChartState) { return getPointerCursorSelector(globalState); } + isTooltipVisible(globalState: GlobalChartState) { + return isTooltipVisibleSelector(globalState); + } + getTooltipInfo(globalState: GlobalChartState) { + return getTooltipInfoSelector(globalState); + } + getTooltipAnchor(globalState: GlobalChartState) { + return getTooltipAnchorPositionSelector(globalState); + } } diff --git a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts index d3dde59ab8..880fc2c11b 100644 --- a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts +++ b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts @@ -15,9 +15,10 @@ import { getChartRotationSelector } from '../../../../state/selectors/get_chart_ import { AnnotationId } from '../../../../utils/ids'; import { computeSeriesGeometriesSelector } from './compute_series_geometries'; import { ComputedGeometries } from '../utils'; -import { getTooltipValuesSelector, TooltipData } from './get_tooltip_values_highlighted_geoms'; +import { getTooltipInfoSelector } from './get_tooltip_values_highlighted_geoms'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { GlobalChartState } from '../../../../state/chart_state'; +import { TooltipInfo } from '../../../../components/tooltip/types'; const getCurrentPointerPosition = (state: GlobalChartState) => state.interactions.pointer.current.position; @@ -30,7 +31,7 @@ export const getAnnotationTooltipStateSelector = createCachedSelector( getAnnotationSpecsSelector, getAxisSpecsSelector, computeAnnotationDimensionsSelector, - getTooltipValuesSelector, + getTooltipInfoSelector, ], getAnnotationTooltipState, )(getChartIdSelector); @@ -47,7 +48,7 @@ function getAnnotationTooltipState( annotationSpecs: AnnotationSpec[], axesSpecs: AxisSpec[], annotationDimensions: Map, - tooltip: TooltipData, + tooltip: TooltipInfo, ): AnnotationTooltipState | null { // get positions relative to chart if (x < 0 || y < 0) { diff --git a/src/chart_types/xy_chart/state/selectors/get_legend_tooltip_values.ts b/src/chart_types/xy_chart/state/selectors/get_legend_tooltip_values.ts index 4f580c3d05..35a72e898f 100644 --- a/src/chart_types/xy_chart/state/selectors/get_legend_tooltip_values.ts +++ b/src/chart_types/xy_chart/state/selectors/get_legend_tooltip_values.ts @@ -1,10 +1,10 @@ import createCachedSelector from 're-reselect'; import { getSeriesTooltipValues, TooltipLegendValue } from '../../tooltip/tooltip'; -import { getTooltipValuesSelector } from './get_tooltip_values_highlighted_geoms'; +import { getTooltipInfoSelector } from './get_tooltip_values_highlighted_geoms'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; export const getLegendTooltipValuesSelector = createCachedSelector( - [getTooltipValuesSelector], + [getTooltipInfoSelector], ({ values }): Map => { return getSeriesTooltipValues(values); }, diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts index 3e6364890d..f3c76e5368 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts @@ -1,13 +1,14 @@ import createCachedSelector from 're-reselect'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { getTooltipPosition, TooltipPosition } from '../../crosshair/crosshair_utils'; +import { getTooltipAnchorPosition } from '../../crosshair/crosshair_utils'; import { getProjectedPointerPositionSelector } from './get_projected_pointer_position'; import { getComputedScalesSelector } from './get_computed_scales'; import { getCursorBandPositionSelector } from './get_cursor_band'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { TooltipAnchorPosition } from '../../../../components/tooltip/utils'; -export const getTooltipPositionSelector = createCachedSelector( +export const getTooltipAnchorPositionSelector = createCachedSelector( [ computeChartDimensionsSelector, getSettingsSpecSelector, @@ -15,11 +16,17 @@ export const getTooltipPositionSelector = createCachedSelector( getProjectedPointerPositionSelector, getComputedScalesSelector, ], - ({ chartDimensions }, settings, cursorBandPosition, projectedPointerPosition, scales): TooltipPosition | null => { + ( + { chartDimensions }, + settings, + cursorBandPosition, + projectedPointerPosition, + scales, + ): TooltipAnchorPosition | null => { if (!cursorBandPosition) { return null; } - return getTooltipPosition( + return getTooltipAnchorPosition( chartDimensions, settings.rotation, cursorBandPosition, diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_snap.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_snap.ts index 534ec2b033..366b7a3d7c 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_snap.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_snap.ts @@ -1,7 +1,6 @@ import createCachedSelector from 're-reselect'; -import { isTooltipProps } from '../../utils/interactions'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { SettingsSpec } from '../../../../specs/settings'; +import { SettingsSpec, isTooltipProps } from '../../../../specs/settings'; import { DEFAULT_TOOLTIP_SNAP } from '../../../../specs/settings'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; 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 95a6e0570e..7207474d6e 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,7 +1,6 @@ import createCachedSelector from 're-reselect'; -import { TooltipType, isTooltipProps, isTooltipType } from '../../utils/interactions'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { SettingsSpec } from '../../../../specs/settings'; +import { SettingsSpec, TooltipType, isTooltipType, isTooltipProps } from '../../../../specs/settings'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; export const getTooltipTypeSelector = createCachedSelector( @@ -9,16 +8,16 @@ export const getTooltipTypeSelector = createCachedSelector( getTooltipType, )(getChartIdSelector); -function getTooltipType(settings: SettingsSpec): TooltipType { +export function getTooltipType(settings: SettingsSpec): TooltipType | undefined { const { tooltip } = settings; if (tooltip === undefined || tooltip === null) { - return TooltipType.VerticalCursor; + return undefined; } if (isTooltipType(tooltip)) { return tooltip; } if (isTooltipProps(tooltip)) { - return tooltip.type || TooltipType.VerticalCursor; + return tooltip.type || undefined; } - return TooltipType.VerticalCursor; + return undefined; } diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts index 99d50da730..d237ec2ef1 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts @@ -1,5 +1,4 @@ import createCachedSelector from 're-reselect'; -import { TooltipValue, isFollowTooltipType, TooltipType, TooltipValueFormatter } from '../../utils/interactions'; import { getProjectedPointerPositionSelector } from './get_projected_pointer_position'; import { Point } from '../../../../utils/point'; import { getOrientedProjectedPointerPositionSelector } from './get_oriented_projected_pointer_position'; @@ -12,14 +11,22 @@ import { BasicSeriesSpec, AxisSpec } from '../../utils/specs'; import { Rotation } from '../../../../utils/commons'; import { getTooltipTypeSelector } from './get_tooltip_type'; import { formatTooltip } from '../../tooltip/tooltip'; -import { getTooltipHeaderFormatterSelector } from './get_tooltip_header_formatter'; +import { getTooltipHeaderFormatterSelector } from '../../../../state/selectors/get_tooltip_header_formatter'; import { isPointOnGeometry } from '../../rendering/rendering'; import { GlobalChartState } from '../../../../state/chart_state'; -import { PointerEvent, isPointerOutEvent } from '../../../../specs'; +import { + PointerEvent, + isPointerOutEvent, + TooltipValue, + TooltipType, + TooltipValueFormatter, + isFollowTooltipType, +} from '../../../../specs'; import { isValidPointerOverEvent } from '../../../../utils/events'; import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { hasSingleSeriesSelector } from './has_single_series'; +import { TooltipInfo } from '../../../../components/tooltip/types'; const EMPTY_VALUES = Object.freeze({ tooltip: { @@ -29,18 +36,14 @@ const EMPTY_VALUES = Object.freeze({ highlightedGeometries: [], }); -export interface TooltipData { - header: TooltipValue | null; - values: TooltipValue[]; -} export interface TooltipAndHighlightedGeoms { - tooltip: TooltipData; + tooltip: TooltipInfo; highlightedGeometries: IndexedGeometry[]; } const getExternalPointerEventStateSelector = (state: GlobalChartState) => state.externalEvents.pointer; -export const getTooltipValuesAndGeometriesSelector = createCachedSelector( +export const getTooltipInfoAndGeometriesSelector = createCachedSelector( [ getSeriesSpecsSelector, getAxisSpecsSelector, @@ -68,7 +71,7 @@ function getTooltipAndHighlightFromXValue( hasSingleSeries: boolean, scales: ComputedScales, xMatchingGeoms: IndexedGeometry[], - tooltipType: TooltipType, + tooltipType: TooltipType = TooltipType.VerticalCursor, externalPointerEvent: PointerEvent | null, tooltipHeaderFormatter?: TooltipValueFormatter, ): TooltipAndHighlightedGeoms { @@ -92,9 +95,9 @@ function getTooltipAndHighlightFromXValue( } // build the tooltip value list - let tooltipHeader: TooltipValue | null = null; + let header: TooltipValue | null = null; const highlightedGeometries: IndexedGeometry[] = []; - const tooltipValues = xMatchingGeoms + const values = xMatchingGeoms .filter(({ value: { y } }) => y !== null) .reduce((acc, indexedGeometry) => { const { @@ -142,11 +145,11 @@ function getTooltipAndHighlightFromXValue( ); // format only one time the x value - if (!tooltipHeader) { + if (!header) { // if we have a tooltipHeaderFormatter, then don't pass in the xAxis as the user will define a formatter const xAxisFormatSpec = [0, 180].includes(chartRotation) ? xAxis : yAxis; const formatterAxis = tooltipHeaderFormatter ? undefined : xAxisFormatSpec; - tooltipHeader = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis); + header = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis); } return [...acc, formattedTooltip]; @@ -154,22 +157,22 @@ function getTooltipAndHighlightFromXValue( return { tooltip: { - header: tooltipHeader, - values: tooltipValues, + header, + values, }, highlightedGeometries, }; } -export const getTooltipValuesSelector = createCachedSelector( - [getTooltipValuesAndGeometriesSelector], - ({ tooltip }): TooltipData => { +export const getTooltipInfoSelector = createCachedSelector( + [getTooltipInfoAndGeometriesSelector], + ({ tooltip }): TooltipInfo => { return tooltip; }, )(getChartIdSelector); export const getHighlightedGeomsSelector = createCachedSelector( - [getTooltipValuesAndGeometriesSelector], + [getTooltipInfoAndGeometriesSelector], (values): IndexedGeometry[] => { return values.highlightedGeometries; }, 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 d9a659b796..225b76a32a 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 @@ -1,28 +1,29 @@ import createCachedSelector from 're-reselect'; -import { TooltipType, isTooltipType, isTooltipProps } from '../../utils/interactions'; import { Point } from '../../../../utils/point'; import { GlobalChartState, PointerStates } from '../../../../state/chart_state'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { getProjectedPointerPositionSelector } from './get_projected_pointer_position'; -import { getTooltipValuesSelector, TooltipData } from './get_tooltip_values_highlighted_geoms'; +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 { isAnnotationTooltipVisibleSelector } from './is_annotation_tooltip_visible'; +import { TooltipInfo } from '../../../../components/tooltip/types'; -const getTooltipType = (state: GlobalChartState): TooltipType | undefined => { - const tooltip = getSettingsSpecSelector(state).tooltip; - if (!tooltip) { - return undefined; - } - if (isTooltipType(tooltip)) { - return tooltip; - } - if (isTooltipProps(tooltip)) { - return tooltip.type; - } +const hasTooltipTypeDefinedSelector = (state: GlobalChartState): TooltipType | undefined => { + return getTooltipType(getSettingsSpecSelector(state)); }; + const getPointerSelector = (state: GlobalChartState) => state.interactions.pointer; export const isTooltipVisibleSelector = createCachedSelector( - [getTooltipType, getPointerSelector, getProjectedPointerPositionSelector, getTooltipValuesSelector], + [ + hasTooltipTypeDefinedSelector, + getPointerSelector, + getProjectedPointerPositionSelector, + getTooltipInfoSelector, + isAnnotationTooltipVisibleSelector, + ], isTooltipVisible, )(getChartIdSelector); @@ -30,13 +31,15 @@ function isTooltipVisible( tooltipType: TooltipType | undefined, pointer: PointerStates, projectedPointerPosition: Point, - tooltip: TooltipData, + tooltip: TooltipInfo, + isAnnotationTooltipVisible: boolean, ) { return ( tooltipType !== TooltipType.None && pointer.down === null && projectedPointerPosition.x > -1 && projectedPointerPosition.y > -1 && - tooltip.values.length > 0 + tooltip.values.length > 0 && + !isAnnotationTooltipVisible ); } diff --git a/src/chart_types/xy_chart/state/selectors/on_element_click_caller.ts b/src/chart_types/xy_chart/state/selectors/on_element_click_caller.ts index 89a84e8456..a190cbedae 100644 --- a/src/chart_types/xy_chart/state/selectors/on_element_click_caller.ts +++ b/src/chart_types/xy_chart/state/selectors/on_element_click_caller.ts @@ -6,7 +6,7 @@ import { getHighlightedGeomsSelector } from './get_tooltip_values_highlighted_ge import { SettingsSpec } from '../../../../specs'; import { IndexedGeometry, GeometryValue } from '../../../../utils/geometry'; import { ChartTypes } from '../../../index'; -import { SeriesIdentifier } from '../../utils/series'; +import { XYChartSeriesIdentifier } from '../../utils/series'; const getLastClickSelector = (state: GlobalChartState) => state.interactions.pointer.lastClick; @@ -57,7 +57,7 @@ export function createOnElementClickCaller(): (state: GlobalChartState) => void if (isClicking(prevProps, nextProps)) { if (settings && settings.onElementClick) { - const elements = indexedGeometries.map<[GeometryValue, SeriesIdentifier]>( + const elements = indexedGeometries.map<[GeometryValue, XYChartSeriesIdentifier]>( ({ value, seriesIdentifier }) => [value, seriesIdentifier], ); settings.onElementClick(elements); diff --git a/src/chart_types/xy_chart/state/selectors/on_element_out_caller.ts b/src/chart_types/xy_chart/state/selectors/on_element_out_caller.ts index cf4042073b..4cee610e4f 100644 --- a/src/chart_types/xy_chart/state/selectors/on_element_out_caller.ts +++ b/src/chart_types/xy_chart/state/selectors/on_element_out_caller.ts @@ -1,7 +1,7 @@ import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import createCachedSelector from 're-reselect'; import { - getTooltipValuesAndGeometriesSelector, + getTooltipInfoAndGeometriesSelector, TooltipAndHighlightedGeoms, } from './get_tooltip_values_highlighted_geoms'; import { SettingsSpec } from '../../../../specs'; @@ -40,7 +40,7 @@ export function createOnElementOutCaller(): (state: GlobalChartState) => void { return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartTypes.XYAxis) { selector = createCachedSelector( - [getTooltipValuesAndGeometriesSelector, getSettingsSpecSelector], + [getTooltipInfoAndGeometriesSelector, getSettingsSpecSelector], ({ highlightedGeometries }: TooltipAndHighlightedGeoms, settings: SettingsSpec): void => { const nextProps = { settings, diff --git a/src/chart_types/xy_chart/state/selectors/on_element_over_caller.ts b/src/chart_types/xy_chart/state/selectors/on_element_over_caller.ts index 707fb2c972..07b4cc7ea2 100644 --- a/src/chart_types/xy_chart/state/selectors/on_element_over_caller.ts +++ b/src/chart_types/xy_chart/state/selectors/on_element_over_caller.ts @@ -1,7 +1,7 @@ import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import createCachedSelector from 're-reselect'; import { - getTooltipValuesAndGeometriesSelector, + getTooltipInfoAndGeometriesSelector, TooltipAndHighlightedGeoms, } from './get_tooltip_values_highlighted_geoms'; import { SettingsSpec } from '../../../../specs'; @@ -10,7 +10,7 @@ import { IndexedGeometry, GeometryValue } from '../../../../utils/geometry'; import { Selector } from 'react-redux'; import { ChartTypes } from '../../../index'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { SeriesIdentifier } from '../../utils/series'; +import { XYChartSeriesIdentifier } from '../../utils/series'; interface Props { settings: SettingsSpec | undefined; @@ -50,7 +50,7 @@ export function createOnElementOverCaller(): (state: GlobalChartState) => void { return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartTypes.XYAxis) { selector = createCachedSelector( - [getTooltipValuesAndGeometriesSelector, getSettingsSpecSelector], + [getTooltipInfoAndGeometriesSelector, getSettingsSpecSelector], ({ highlightedGeometries }: TooltipAndHighlightedGeoms, settings: SettingsSpec): void => { const nextProps = { settings, @@ -58,7 +58,7 @@ export function createOnElementOverCaller(): (state: GlobalChartState) => void { }; if (isOverElement(prevProps, nextProps) && settings.onElementOver) { - const elements = highlightedGeometries.map<[GeometryValue, SeriesIdentifier]>( + const elements = highlightedGeometries.map<[GeometryValue, XYChartSeriesIdentifier]>( ({ value, seriesIdentifier }) => [value, seriesIdentifier], ); settings.onElementOver(elements); diff --git a/src/chart_types/xy_chart/state/utils.ts b/src/chart_types/xy_chart/state/utils.ts index 2bc7ab826e..1814bcbc1a 100644 --- a/src/chart_types/xy_chart/state/utils.ts +++ b/src/chart_types/xy_chart/state/utils.ts @@ -12,8 +12,8 @@ import { getFormattedDataseries, getSplittedSeries, getSeriesKey, - SeriesIdentifier, RawDataSeries, + XYChartSeriesIdentifier, } from '../utils/series'; import { AreaSeriesSpec, @@ -99,7 +99,10 @@ export interface SeriesDomainsAndData { * @param series * @param target */ -export function updateDeselectedDataSeries(series: SeriesIdentifier[], target: SeriesIdentifier): SeriesIdentifier[] { +export function updateDeselectedDataSeries( + series: XYChartSeriesIdentifier[], + target: XYChartSeriesIdentifier, +): XYChartSeriesIdentifier[] { const seriesIndex = getSeriesIndex(series, target); const updatedSeries = series ? [...series] : []; @@ -167,7 +170,7 @@ function getLastValues(formattedDataSeries: { // we need to get the latest formattedDataSeries.stacked.forEach((ds) => { ds.dataSeries.forEach((series) => { - const seriesKey = getSeriesKey(series as SeriesIdentifier); + const seriesKey = getSeriesKey(series as XYChartSeriesIdentifier); if (series.data.length > 0) { const last = series.data[series.data.length - 1]; if (last !== null) { @@ -182,7 +185,7 @@ function getLastValues(formattedDataSeries: { }); formattedDataSeries.nonStacked.forEach((ds) => { ds.dataSeries.forEach((series) => { - const seriesKey = getSeriesKey(series as SeriesIdentifier); + const seriesKey = getSeriesKey(series as XYChartSeriesIdentifier); if (series.data.length > 0) { const last = series.data[series.data.length - 1]; if (last !== null) { @@ -210,7 +213,7 @@ function getLastValues(formattedDataSeries: { export function computeSeriesDomains( seriesSpecs: BasicSeriesSpec[], customYDomainsByGroupId: Map = new Map(), - deselectedDataSeries: SeriesIdentifier[] = [], + deselectedDataSeries: XYChartSeriesIdentifier[] = [], customXDomain?: DomainRange | Domain, ): SeriesDomainsAndData { const { splittedSeries, xValues, seriesCollection } = deselectedDataSeries diff --git a/src/chart_types/xy_chart/tooltip/tooltip.test.ts b/src/chart_types/xy_chart/tooltip/tooltip.test.ts index a43bc62da8..08fe890c75 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.test.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.test.ts @@ -100,9 +100,8 @@ describe('Tooltip formatting', () => { test('format simple tooltip', () => { const tooltipValue = formatTooltip(indexedGeometry, SPEC_1, false, false, false, YAXIS_SPEC); expect(tooltipValue).toBeDefined(); - expect(tooltipValue.yAccessor).toBe('y1'); - expect(tooltipValue.name).toBe('bar_1'); - expect(tooltipValue.isXValue).toBe(false); + expect(tooltipValue.valueAccessor).toBe('y1'); + expect(tooltipValue.label).toBe('bar_1'); expect(tooltipValue.isHighlighted).toBe(false); expect(tooltipValue.color).toBe('blue'); expect(tooltipValue.value).toBe('10'); @@ -110,15 +109,15 @@ describe('Tooltip formatting', () => { it('should set name as spec name when provided', () => { const name = 'test - spec'; const tooltipValue = formatTooltip(indexedBandedGeometry, { ...SPEC_1, name }, false, false, false, YAXIS_SPEC); - expect(tooltipValue.name).toBe(name); + expect(tooltipValue.label).toBe(name); }); it('should set name as spec id when name is not provided', () => { const tooltipValue = formatTooltip(indexedBandedGeometry, SPEC_1, false, false, false, YAXIS_SPEC); - expect(tooltipValue.name).toBe(SPEC_1.id); + expect(tooltipValue.label).toBe(SPEC_1.id); }); test('format banded tooltip - upper', () => { const tooltipValue = formatTooltip(indexedBandedGeometry, bandedSpec, false, false, false, YAXIS_SPEC); - expect(tooltipValue.name).toBe('bar_1 - upper'); + expect(tooltipValue.label).toBe('bar_1 - upper'); }); test('format banded tooltip - y1AccessorFormat', () => { const tooltipValue = formatTooltip( @@ -129,7 +128,7 @@ describe('Tooltip formatting', () => { false, YAXIS_SPEC, ); - expect(tooltipValue.name).toBe('bar_1 [max]'); + expect(tooltipValue.label).toBe('bar_1 [max]'); }); test('format banded tooltip - y1AccessorFormat as function', () => { const tooltipValue = formatTooltip( @@ -140,7 +139,7 @@ describe('Tooltip formatting', () => { false, YAXIS_SPEC, ); - expect(tooltipValue.name).toBe('[max] bar_1'); + expect(tooltipValue.label).toBe('[max] bar_1'); }); test('format banded tooltip - lower', () => { const tooltipValue = formatTooltip( @@ -157,7 +156,7 @@ describe('Tooltip formatting', () => { false, YAXIS_SPEC, ); - expect(tooltipValue.name).toBe('bar_1 - lower'); + expect(tooltipValue.label).toBe('bar_1 - lower'); }); test('format banded tooltip - y0AccessorFormat', () => { const tooltipValue = formatTooltip( @@ -174,7 +173,7 @@ describe('Tooltip formatting', () => { false, YAXIS_SPEC, ); - expect(tooltipValue.name).toBe('bar_1 [min]'); + expect(tooltipValue.label).toBe('bar_1 [min]'); }); test('format banded tooltip - y0AccessorFormat as function', () => { const tooltipValue = formatTooltip( @@ -191,7 +190,7 @@ describe('Tooltip formatting', () => { false, YAXIS_SPEC, ); - expect(tooltipValue.name).toBe('[min] bar_1'); + expect(tooltipValue.label).toBe('[min] bar_1'); }); test('format tooltip with seriesKeys name', () => { const geometry: BarGeometry = { @@ -206,9 +205,8 @@ describe('Tooltip formatting', () => { }; const tooltipValue = formatTooltip(geometry, SPEC_1, false, false, false, YAXIS_SPEC); expect(tooltipValue).toBeDefined(); - expect(tooltipValue.yAccessor).toBe('y1'); - expect(tooltipValue.name).toBe('bar_1'); - expect(tooltipValue.isXValue).toBe(false); + expect(tooltipValue.valueAccessor).toBe('y1'); + expect(tooltipValue.label).toBe('bar_1'); expect(tooltipValue.isHighlighted).toBe(false); expect(tooltipValue.color).toBe('blue'); expect(tooltipValue.value).toBe('10'); @@ -223,9 +221,8 @@ describe('Tooltip formatting', () => { }; const tooltipValue = formatTooltip(geometry, SPEC_1, false, false, false, YAXIS_SPEC); expect(tooltipValue).toBeDefined(); - expect(tooltipValue.yAccessor).toBe('y0'); - expect(tooltipValue.name).toBe('bar_1'); - expect(tooltipValue.isXValue).toBe(false); + expect(tooltipValue.valueAccessor).toBe('y0'); + expect(tooltipValue.label).toBe('bar_1'); expect(tooltipValue.isHighlighted).toBe(false); expect(tooltipValue.color).toBe('blue'); expect(tooltipValue.value).toBe('10'); @@ -240,9 +237,8 @@ describe('Tooltip formatting', () => { }; let tooltipValue = formatTooltip(geometry, SPEC_1, true, false, false, YAXIS_SPEC); expect(tooltipValue).toBeDefined(); - expect(tooltipValue.yAccessor).toBe('y0'); - expect(tooltipValue.name).toBe('bar_1'); - expect(tooltipValue.isXValue).toBe(true); + expect(tooltipValue.valueAccessor).toBe('y0'); + expect(tooltipValue.label).toBe('bar_1'); expect(tooltipValue.isHighlighted).toBe(false); expect(tooltipValue.color).toBe('blue'); expect(tooltipValue.value).toBe('1'); diff --git a/src/chart_types/xy_chart/tooltip/tooltip.ts b/src/chart_types/xy_chart/tooltip/tooltip.ts index 77ecaf985b..87cb5bf711 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.ts @@ -1,4 +1,3 @@ -import { TooltipValue } from '../utils/interactions'; import { AxisSpec, BasicSeriesSpec, @@ -9,7 +8,8 @@ import { } from '../utils/specs'; import { IndexedGeometry, BandedAccessorType } from '../../../utils/geometry'; import { getAccessorFormatLabel } from '../../../utils/accessor'; -import { getSeriesKey, getSeriesLabel } from '../utils/series'; +import { getSeriesLabel } from '../utils/series'; +import { TooltipValue } from '../../../specs'; export interface TooltipLegendValue { y0: any; @@ -26,15 +26,15 @@ export function getSeriesTooltipValues( // map from seriesKey to TooltipLegendValue const seriesTooltipValues = new Map(); - tooltipValues.forEach(({ seriesKey, value, yAccessor }) => { + tooltipValues.forEach(({ value, seriesIdentifier, valueAccessor }) => { const seriesValue = defaultValue ? defaultValue : value; - const current = seriesTooltipValues.get(seriesKey) || {}; + const current = seriesTooltipValues.get(seriesIdentifier.key) || {}; - seriesTooltipValues.set(seriesKey, { + seriesTooltipValues.set(seriesIdentifier.key, { y0: defaultValue, y1: defaultValue, ...current, - [yAccessor]: seriesValue, + [valueAccessor]: seriesValue, }); }); return seriesTooltipValues; @@ -43,32 +43,30 @@ export function getSeriesTooltipValues( export function formatTooltip( { color, value: { x, y, accessor }, seriesIdentifier }: IndexedGeometry, spec: BasicSeriesSpec, - isXValue: boolean, + isHeader: boolean, isHighlighted: boolean, hasSingleSeries: boolean, axisSpec?: AxisSpec, ): TooltipValue { - const seriesKey = getSeriesKey(seriesIdentifier); - let displayName = getSeriesLabel(seriesIdentifier, hasSingleSeries, true, spec); + let label = getSeriesLabel(seriesIdentifier, hasSingleSeries, true, spec); if (isBandedSpec(spec.y0Accessors) && (isAreaSeriesSpec(spec) || isBarSeriesSpec(spec))) { const { y0AccessorFormat = Y0_ACCESSOR_POSTFIX, y1AccessorFormat = Y1_ACCESSOR_POSTFIX } = spec; const formatter = accessor === BandedAccessorType.Y0 ? y0AccessorFormat : y1AccessorFormat; - displayName = getAccessorFormatLabel(formatter, displayName); + label = getAccessorFormatLabel(formatter, label); } const isFiltered = spec.filterSeriesInTooltip !== undefined ? spec.filterSeriesInTooltip(seriesIdentifier) : true; - const isVisible = displayName === '' ? false : isFiltered; + const isVisible = label === '' ? false : isFiltered; - const value = isXValue ? x : y; + const value = isHeader ? x : y; const tickFormatOptions: TickFormatterOptions | undefined = spec.timeZone ? { timeZone: spec.timeZone } : undefined; return { - seriesKey, - name: displayName, + seriesIdentifier, + valueAccessor: accessor, + label, value: axisSpec ? axisSpec.tickFormat(value, tickFormatOptions) : emptyFormatter(value), color, - isHighlighted: isXValue ? false : isHighlighted, - isXValue, - yAccessor: accessor, + isHighlighted: isHeader ? false : isHighlighted, isVisible, }; } diff --git a/src/chart_types/xy_chart/utils/interactions.test.ts b/src/chart_types/xy_chart/utils/interactions.test.ts index 355407a2b4..4ae692b32f 100644 --- a/src/chart_types/xy_chart/utils/interactions.test.ts +++ b/src/chart_types/xy_chart/utils/interactions.test.ts @@ -4,11 +4,9 @@ import { areIndexedGeomsEquals, getOrientedXPosition, getOrientedYPosition, - isCrosshairTooltipType, - isFollowTooltipType, - TooltipType, } from './interactions'; import { IndexedGeometry, PointGeometry } from '../../../utils/geometry'; +import { TooltipType, isCrosshairTooltipType, isFollowTooltipType } from '../../../specs'; const seriesStyle = { rect: { diff --git a/src/chart_types/xy_chart/utils/interactions.ts b/src/chart_types/xy_chart/utils/interactions.ts index 73dff95d9a..ac57b67b03 100644 --- a/src/chart_types/xy_chart/utils/interactions.ts +++ b/src/chart_types/xy_chart/utils/interactions.ts @@ -1,52 +1,7 @@ -import { $Values } from 'utility-types'; -import { Datum, Rotation } from '../../../utils/commons'; +import { Rotation } from '../../../utils/commons'; import { Dimensions } from '../../../utils/dimensions'; -import { Accessor } from '../../../utils/accessor'; import { BarGeometry, PointGeometry, IndexedGeometry, isPointGeometry, isBarGeometry } from '../../../utils/geometry'; -/** The type of tooltip to use */ -export const TooltipType = Object.freeze({ - /** Vertical cursor parallel to x axis */ - VerticalCursor: 'vertical' as 'vertical', - /** Vertical and horizontal cursors */ - Crosshairs: 'cross' as 'cross', - /** Follor the mouse coordinates */ - Follow: 'follow' as 'follow', - /** Hide every tooltip */ - None: 'none' as 'none', -}); - -export type TooltipType = $Values; - -export interface TooltipValue { - name: string; - value: any; - color: string; - isHighlighted: boolean; - isXValue: boolean; - seriesKey: string; - yAccessor: Accessor; - isVisible: boolean; -} - -export interface TooltipProps { - type?: TooltipType; - snap?: boolean; - headerFormatter?: TooltipValueFormatter; -} - -export type TooltipValueFormatter = (data: TooltipValue) => JSX.Element | string; - -export interface HighlightedElement { - position: { - x: number; - y: number; - width: number; - height: number; - type: 'rect' | 'circle'; - }; - value: Datum; -} /** * Get the cursor position depending on the chart rotation * @param xPos x position relative to chart @@ -79,13 +34,6 @@ export function getOrientedYPosition(xPos: number, yPos: number, chartRotation: } } -export function isCrosshairTooltipType(type: TooltipType) { - return type === TooltipType.VerticalCursor || type === TooltipType.Crosshairs; -} -export function isFollowTooltipType(type: TooltipType) { - return type === TooltipType.Follow; -} - export function areIndexedGeometryArraysEquals(arr1: IndexedGeometry[], arr2: IndexedGeometry[]) { if (arr1.length !== arr2.length) { return false; @@ -127,11 +75,3 @@ function areBarEqual(ig1: BarGeometry, ig2: BarGeometry) { ig1.height === ig2.height ); } - -export function isTooltipProps(config: TooltipType | TooltipProps): config is TooltipProps { - return typeof config === 'object'; -} - -export function isTooltipType(config: TooltipType | TooltipProps): config is TooltipType { - return typeof config === 'string'; -} diff --git a/src/chart_types/xy_chart/utils/series.test.ts b/src/chart_types/xy_chart/utils/series.test.ts index b00f3aa8fc..a671414d68 100644 --- a/src/chart_types/xy_chart/utils/series.test.ts +++ b/src/chart_types/xy_chart/utils/series.test.ts @@ -8,7 +8,7 @@ import { getSplittedSeries, RawDataSeries, splitSeries, - SeriesIdentifier, + XYChartSeriesIdentifier, cleanDatum, } from './series'; import { BasicSeriesSpec, LineSeriesSpec, SeriesTypes } from './specs'; @@ -555,7 +555,7 @@ describe('Series', () => { const emptyDeselected = getSplittedSeries([splitSpec]); expect(emptyDeselected.splittedSeries.get(specId)!.length).toBe(2); - const deselectedDataSeries: SeriesIdentifier[] = [ + const deselectedDataSeries: XYChartSeriesIdentifier[] = [ { specId, yAccessor: splitSpec.yAccessors[0], diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index 40d9503d94..4c4dd068d0 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -45,21 +45,23 @@ export interface DataSeriesDatum { /** the list of filled values because missing or nulls */ filled?: FilledValues; } - -export interface SeriesIdentifier { +export type SeriesIdentifier = { specId: SpecId; + key: string; +}; + +export interface XYChartSeriesIdentifier extends SeriesIdentifier { yAccessor: string | number; splitAccessors: Map; // does the map have a size vs making it optional seriesKeys: (string | number)[]; - key: string; } -export type DataSeries = SeriesIdentifier & { +export type DataSeries = XYChartSeriesIdentifier & { // seriesColorKey: string; data: DataSeriesDatum[]; }; -export type RawDataSeries = SeriesIdentifier & { +export type RawDataSeries = XYChartSeriesIdentifier & { // seriesColorKey: string; data: RawDataSeriesDatum[]; }; @@ -80,10 +82,10 @@ export type SeriesCollectionValue = { banded?: boolean; lastValue?: LastValues; specSortIndex?: number; - seriesIdentifier: SeriesIdentifier; + seriesIdentifier: XYChartSeriesIdentifier; }; -export function getSeriesIndex(series: SeriesIdentifier[], target: SeriesIdentifier): number { +export function getSeriesIndex(series: XYChartSeriesIdentifier[], target: XYChartSeriesIdentifier): number { if (!series) { return -1; } @@ -149,7 +151,7 @@ export function getSeriesKey({ specId, yAccessor, splitAccessors, -}: Pick): string { +}: Pick): string { const joinedAccessors = [...splitAccessors.entries()] .sort(([a], [b]) => (a > b ? 1 : -1)) .map(([key, value]) => `${key}-${value}`) @@ -332,7 +334,7 @@ function getRawDataSeries( */ export function getSplittedSeries( seriesSpecs: BasicSeriesSpec[], - deselectedDataSeries: SeriesIdentifier[] = [], + deselectedDataSeries: XYChartSeriesIdentifier[] = [], ): { splittedSeries: Map; seriesCollection: Map; @@ -362,7 +364,7 @@ export function getSplittedSeries( seriesCollection.set(series.key, { banded, specSortIndex: spec.sortIndex, - seriesIdentifier: series as SeriesIdentifier, + seriesIdentifier: series as XYChartSeriesIdentifier, }); }); @@ -404,7 +406,7 @@ const getCustomSubSeriesName = (() => { const getSeriesLabelKeys = ( spec: BasicSeriesSpec, - seriesIdentifier: SeriesIdentifier, + seriesIdentifier: XYChartSeriesIdentifier, isTooltip: boolean, ): (string | number)[] => { const isMultipleY = spec.yAccessors.length > 1; @@ -426,7 +428,7 @@ const getSeriesLabelKeys = ( * Get series label based on `SeriesIdentifier` */ export function getSeriesLabel( - seriesIdentifier: SeriesIdentifier, + seriesIdentifier: XYChartSeriesIdentifier, hasSingleSeries: boolean, isTooltip: boolean, spec?: BasicSeriesSpec, diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 026ee0fbc9..c85724ecc8 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -13,7 +13,7 @@ import { RecursivePartial, Color, Position, Datum } from '../../../utils/commons import { AxisId, GroupId } from '../../../utils/ids'; import { ScaleContinuousType, ScaleType } from '../../../scales'; import { CurveType } from '../../../utils/curves'; -import { RawDataSeriesDatum, SeriesIdentifier } from './series'; +import { RawDataSeriesDatum, XYChartSeriesIdentifier } from './series'; import { AnnotationTooltipFormatter } from '../annotations/annotation_utils'; import { Spec, SpecTypes } from '../../..'; import { ChartTypes } from '../..'; @@ -37,7 +37,10 @@ export type SeriesTypes = $Values; * - `RecursivePartial`: Style values to be merged with base bar styles * - `null`: Keep existing bar style */ -export type BarStyleAccessor = (datum: RawDataSeriesDatum, seriesIdentifier: SeriesIdentifier) => BarStyleOverride; +export type BarStyleAccessor = ( + datum: RawDataSeriesDatum, + seriesIdentifier: XYChartSeriesIdentifier, +) => BarStyleOverride; /** * Override for bar styles per datum * @@ -46,11 +49,14 @@ export type BarStyleAccessor = (datum: RawDataSeriesDatum, seriesIdentifier: Ser * - `RecursivePartial`: Style values to be merged with base point styles * - `null`: Keep existing point style */ -export type PointStyleAccessor = (datum: RawDataSeriesDatum, seriesIdentifier: SeriesIdentifier) => PointStyleOverride; +export type PointStyleAccessor = ( + datum: RawDataSeriesDatum, + seriesIdentifier: XYChartSeriesIdentifier, +) => PointStyleOverride; export const DEFAULT_GLOBAL_ID = '__global__'; -export type FilterPredicate = (series: SeriesIdentifier) => boolean; -export type SeriesStringPredicate = (series: SeriesIdentifier, isTooltip: boolean) => string | null; +export type FilterPredicate = (series: XYChartSeriesIdentifier) => boolean; +export type SeriesStringPredicate = (series: XYChartSeriesIdentifier, isTooltip: boolean) => string | null; export type SubSeriesStringPredicate = ( accessorLabel: string | number, accessorKey: string | number | null, @@ -239,7 +245,7 @@ export interface SeriesSpec extends Spec { * * This takes precedence over `customSubSeriesLabel` * - * @param series - `SeriesIdentifier` + * @param series - `XYChartSeriesIdentifier` * @param isTooltip - true if tooltip label, otherwise legend label */ customSeriesLabel?: SeriesStringPredicate; @@ -267,7 +273,7 @@ export interface Postfixes { } export type SeriesColorsArray = string[]; -export type SeriesColorAccessorFn = (seriesIdentifier: SeriesIdentifier) => string | null; +export type SeriesColorAccessorFn = (seriesIdentifier: XYChartSeriesIdentifier) => string | null; export type CustomSeriesColors = SeriesColorsArray | SeriesColorAccessorFn; export interface SeriesAccessors { diff --git a/src/components/_index.scss b/src/components/_index.scss index bae81d02d7..7c487177a2 100644 --- a/src/components/_index.scss +++ b/src/components/_index.scss @@ -1,7 +1,7 @@ @import 'global'; @import 'container'; @import 'annotation'; -@import 'tooltip'; +@import 'tooltip/index'; @import 'icons/index'; @import 'legend/index'; @import 'unavailable_chart'; diff --git a/src/components/legend/_legend_item.scss b/src/components/legend/_legend_item.scss index e312e19eb3..70a62d079c 100644 --- a/src/components/legend/_legend_item.scss +++ b/src/components/legend/_legend_item.scss @@ -10,7 +10,7 @@ $legendItemVerticalPadding: $echLegendRowGap / 2; width: 100%; &:hover { - .echLegendItem__title { + .echLegendItem__label { text-decoration: underline; } } @@ -27,7 +27,7 @@ $legendItemVerticalPadding: $echLegendRowGap / 2; } } - &__title { + &__label { @include euiFontSizeXS; @include euiTextTruncate; flex: 1 1 auto; @@ -37,17 +37,13 @@ $legendItemVerticalPadding: $echLegendRowGap / 2; } } - &__title--selected { - text-decoration: underline; - } - - &__title--hasClickListener { + &__label--hasClickListener { &:hover { cursor: pointer; } } - &__displayValue { + &__extra { @include euiFontSizeXS; text-align: right; margin-left: $euiSizeXS; diff --git a/src/components/legend/legend.test.tsx b/src/components/legend/legend.test.tsx index 68dd197d4f..593d18479d 100644 --- a/src/components/legend/legend.test.tsx +++ b/src/components/legend/legend.test.tsx @@ -11,7 +11,7 @@ describe('Legend', () => { it('shall render the all the series names', () => { const wrapper = mount( - + { it('shall render the all the series names without the data value', () => { const wrapper = mount( - + { const data = dg.generateGroupedSeries(10, numberOfSeries, 'split'); const wrapper = mount( - + { const data = dg.generateGroupedSeries(10, numberOfSeries, 'split'); const wrapper = mount( - + { expect(legendItems).toHaveLength(4); legendItems.forEach((legendItem, i) => { // the click is only enabled on the title - legendItem.find('.echLegendItem__title').simulate('click'); + legendItem.find('.echLegendItem__label').simulate('click'); expect(onLegendItemClick).toBeCalledTimes(i + 1); }); }); diff --git a/src/components/legend/legend.tsx b/src/components/legend/legend.tsx index 7ea0c0bc06..91f6d14e2f 100644 --- a/src/components/legend/legend.tsx +++ b/src/components/legend/legend.tsx @@ -138,19 +138,18 @@ class LegendComponent extends React.Component { } const { key, displayValue, banded } = item; const { legendItemTooltipValues, settings } = this.props; - const { showLegendDisplayValue, legendPosition } = settings; + const { showLegendExtra, legendPosition } = settings; const legendValues = this.getLegendValues(legendItemTooltipValues, key, banded); return legendValues.map((value, index) => { const yAccessor: BandedAccessorType = index === 0 ? BandedAccessorType.Y1 : BandedAccessorType.Y0; return ( void; + toggleDeselectSeriesAction: (legendItemId: XYChartSeriesIdentifier) => void; } /** - * Create a div for the the displayed value - * @param displayValue + * Create a div for the extra text + * @param extra * @param isSeriesVisible */ -function renderDisplayValue(displayValue: string, isSeriesVisible: boolean | undefined) { - const displayValueClassNames = classNames('echLegendItem__displayValue', { - ['echLegendItem__displayValue--hidden']: !isSeriesVisible, +function renderExtra(extra: string, isSeriesVisible: boolean | undefined) { + const extraClassNames = classNames('echLegendItem__extra', { + ['echLegendItem__extra--hidden']: !isSeriesVisible, }); return ( -
- {displayValue} +
+ {extra}
); } /** - * Create a div for the title - * @param title - * @param onTitleClick - * @param hasTitleClickListener - * @param isSelected - * @param showLegendDisplayValue + * Create a div for the label + * @param label + * @param onLabelClick + * @param hasLabelClickListener */ -function renderTitle( - title: string, - onTitleClick: (event: React.MouseEvent) => void, - hasTitleClickListener: boolean, - isSelected: boolean, - showLegendDisplayValue: boolean, +function renderLabel( + onLabelClick: (event: React.MouseEvent) => void, + hasLabelClickListener: boolean, + label?: string, ) { - // TODO add contextual menu panel on click - const titleClassNames = classNames('echLegendItem__title', { - ['echLegendItem__title--hasClickListener']: hasTitleClickListener, - ['echLegendItem__title--selected']: isSelected, - ['echLegendItem__title--hasDisplayValue']: showLegendDisplayValue, + if (!label) { + return null; + } + const labelClassNames = classNames('echLegendItem__label', { + ['echLegendItem__label--hasClickListener']: hasLabelClickListener, }); return ( -
- {title} +
+ {label}
); } @@ -72,7 +67,10 @@ function renderTitle( * @param color * @param isSeriesVisible */ -function renderColor(color: string, isSeriesVisible = true) { +function renderColor(color?: string, isSeriesVisible = true) { + if (!color) { + return null; + } // TODO add color picker if (isSeriesVisible) { return ( @@ -97,24 +95,26 @@ export class LegendListItem extends React.Component { } render() { - const { displayValue, legendItem, legendPosition, label } = this.props; + const { extra, legendItem, legendPosition, label, showExtra, onLegendItemClickListener } = this.props; const { color, isSeriesVisible, seriesIdentifier, isLegendItemVisible } = legendItem; - const onTitleClick = this.onVisibilityClick(seriesIdentifier); - const { showLegendDisplayValue, selectedLegendItem, onLegendItemClickListener } = this.props; - const isSelected = - selectedLegendItem == null ? false : selectedLegendItem.seriesIdentifier.key === seriesIdentifier.key; - const hasTitleClickListener = Boolean(onLegendItemClickListener); - const itemClasses = classNames('echLegendItem', `echLegendItem--${legendPosition}`, { + const onLabelClick = this.onVisibilityClick(seriesIdentifier); + const hasLabelClickListener = Boolean(onLegendItemClickListener); + + const itemClassNames = classNames('echLegendItem', `echLegendItem--${legendPosition}`, { 'echLegendItem-isHidden': !isSeriesVisible, - 'echLegendItem__displayValue--hidden': !isLegendItemVisible, + 'echLegendItem__extra--hidden': !isLegendItemVisible, }); return ( -
- {color && renderColor(color, isSeriesVisible)} - {label && renderTitle(label, onTitleClick, hasTitleClickListener, isSelected, showLegendDisplayValue)} - {showLegendDisplayValue && renderDisplayValue(displayValue, isSeriesVisible)} +
+ {renderColor(color, isSeriesVisible)} + {renderLabel(onLabelClick, hasLabelClickListener, label)} + {showExtra && renderExtra(extra, isSeriesVisible)}
); } @@ -138,7 +138,7 @@ export class LegendListItem extends React.Component { }; // TODO handle shift key - onVisibilityClick = (legendItemId: SeriesIdentifier) => () => { + onVisibilityClick = (legendItemId: XYChartSeriesIdentifier) => () => { const { onLegendItemClickListener, toggleDeselectSeriesAction } = this.props; if (onLegendItemClickListener) { onLegendItemClickListener(legendItemId); diff --git a/src/components/tooltip/_index.scss b/src/components/tooltip/_index.scss new file mode 100644 index 0000000000..0a06e6f1bb --- /dev/null +++ b/src/components/tooltip/_index.scss @@ -0,0 +1 @@ +@import 'tooltip'; diff --git a/src/components/_tooltip.scss b/src/components/tooltip/_tooltip.scss similarity index 100% rename from src/components/_tooltip.scss rename to src/components/tooltip/_tooltip.scss diff --git a/src/components/tooltip/index.tsx b/src/components/tooltip/index.tsx new file mode 100644 index 0000000000..47028c9aad --- /dev/null +++ b/src/components/tooltip/index.tsx @@ -0,0 +1,142 @@ +import classNames from 'classnames'; +import React from 'react'; +import { createPortal } from 'react-dom'; +import { connect } from 'react-redux'; +import { getFinalTooltipPosition, TooltipAnchorPosition } from './utils'; +import { TooltipInfo } from './types'; +import { TooltipValueFormatter, TooltipValue } from '../../specs'; +import { GlobalChartState, BackwardRef } from '../../state/chart_state'; +import { isInitialized } from '../../state/selectors/is_initialized'; +import { getInternalIsTooltipVisibleSelector } from '../../state/selectors/get_internal_is_tooltip_visible'; +import { getTooltipHeaderFormatterSelector } from '../../state/selectors/get_tooltip_header_formatter'; +import { getInternalTooltipInfoSelector } from '../../state/selectors/get_internal_tooltip_info'; +import { getInternalTooltipAnchorPositionSelector } from '../../state/selectors/get_internal_tooltip_anchor_position'; + +interface TooltipStateProps { + isVisible: boolean; + position: TooltipAnchorPosition | null; + info?: TooltipInfo; + headerFormatter?: TooltipValueFormatter; +} +interface TooltipOwnProps { + getChartContainerRef: BackwardRef; +} + +type TooltipProps = TooltipStateProps & TooltipOwnProps; + +class TooltipComponent extends React.Component { + static displayName = 'Tooltip'; + portalNode: HTMLDivElement | null = null; + tooltipRef: React.RefObject; + + constructor(props: TooltipProps) { + super(props); + this.tooltipRef = React.createRef(); + } + createPortalNode() { + const container = document.getElementById('echTooltipContainerPortal'); + if (container) { + this.portalNode = container as HTMLDivElement; + } else { + this.portalNode = document.createElement('div'); + this.portalNode.id = 'echTooltipContainerPortal'; + document.body.appendChild(this.portalNode); + } + } + componentDidMount() { + this.createPortalNode(); + } + + componentDidUpdate() { + this.createPortalNode(); + const { getChartContainerRef, position } = this.props; + const chartContainerRef = getChartContainerRef(); + + if (!this.tooltipRef.current || !chartContainerRef.current || !this.portalNode || !position) { + return; + } + + const chartContainerBBox = chartContainerRef.current.getBoundingClientRect(); + const tooltipBBox = this.tooltipRef.current.getBoundingClientRect(); + const tooltipStyle = getFinalTooltipPosition(chartContainerBBox, tooltipBBox, position); + + if (tooltipStyle.left) { + this.portalNode.style.left = tooltipStyle.left; + } + if (tooltipStyle.top) { + this.portalNode.style.top = tooltipStyle.top; + } + } + + componentWillUnmount() { + if (this.portalNode && this.portalNode.parentNode) { + this.portalNode.parentNode.removeChild(this.portalNode); + } + } + + renderHeader(headerData: TooltipValue | null, formatter?: TooltipValueFormatter) { + if (!headerData) { + return null; + } + + return formatter ? formatter(headerData) : headerData.value; + } + + render() { + const { isVisible, info, headerFormatter, getChartContainerRef } = this.props; + const chartContainerRef = getChartContainerRef(); + if (!this.portalNode || chartContainerRef.current === null || !isVisible || !info) { + return null; + } + const tooltipComponent = ( +
+
{this.renderHeader(info.header, headerFormatter)}
+
+ {info.values.map(({ seriesIdentifier, valueAccessor, label, value, color, isHighlighted, isVisible }) => { + if (!isVisible) { + return null; + } + const classes = classNames('echTooltip__item', { + /* eslint @typescript-eslint/camelcase:0 */ + echTooltip__rowHighlighted: isHighlighted, + }); + return ( +
+ {label} + {value} +
+ ); + })} +
+
+ ); + return createPortal(tooltipComponent, this.portalNode); + } +} + +const HIDDEN_TOOLTIP_PROPS = { + isVisible: false, + info: undefined, + position: null, + headerFormatter: undefined, +}; + +const mapStateToProps = (state: GlobalChartState): TooltipStateProps => { + if (!isInitialized(state)) { + return HIDDEN_TOOLTIP_PROPS; + } + return { + isVisible: getInternalIsTooltipVisibleSelector(state), + info: getInternalTooltipInfoSelector(state), + position: getInternalTooltipAnchorPositionSelector(state), + headerFormatter: getTooltipHeaderFormatterSelector(state), + }; +}; + +export const Tooltip = connect(mapStateToProps)(TooltipComponent); diff --git a/src/components/tooltip/types.ts b/src/components/tooltip/types.ts new file mode 100644 index 0000000000..17b5eb8f68 --- /dev/null +++ b/src/components/tooltip/types.ts @@ -0,0 +1,6 @@ +import { TooltipValue } from '../../specs'; + +export interface TooltipInfo { + header: TooltipValue | null; + values: TooltipValue[]; +} diff --git a/src/components/tooltip/utils.ts b/src/components/tooltip/utils.ts new file mode 100644 index 0000000000..6dece3cf88 --- /dev/null +++ b/src/components/tooltip/utils.ts @@ -0,0 +1,78 @@ +import { Dimensions } from '../../utils/dimensions'; + +export interface TooltipAnchorPosition { + /** + * true if the x axis is vertical + */ + isRotated?: boolean; + /** + * the top position of the anchor + */ + y0?: number; + /** + * the bottom position of the anchor + */ + y1: number; + /** + * the right position of anchor + */ + x0?: number; + /** + * the left position of the anchor + */ + x1: number; +} + +export function getFinalTooltipPosition( + /** the dimensions of the chart parent container */ + container: Dimensions, + /** the dimensions of the tooltip container */ + 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; + let left = 0; + let top = 0; + + const x0 = anchorPosition.x0 || 0; + const y0 = anchorPosition.y0 || 0; + + if (!isRotated) { + const leftOfBand = window.pageXOffset + container.left + x0; + if (x1 + tooltip.width + padding > container.width) { + left = leftOfBand - tooltip.width - padding; + } else { + left = leftOfBand + (x1 - x0) + padding; + } + const topOfBand = window.pageYOffset + container.top; + if (y0 + tooltip.height > container.height) { + top = topOfBand + container.height - tooltip.height; + } else { + top = topOfBand + y0; + } + } else { + const leftOfBand = window.pageXOffset + container.left; + if (x1 + tooltip.width > container.width) { + left = leftOfBand + container.width - tooltip.width; + } else { + left = leftOfBand + x1; + } + const topOfBand = window.pageYOffset + container.top + y0; + if (y1 + tooltip.height + padding > container.height) { + top = topOfBand - tooltip.height - padding; + } else { + top = topOfBand + (y1 - y0) + padding; + } + } + + return { + left: `${Math.round(left)}px`, + top: `${Math.round(top)}px`, + }; +} diff --git a/src/index.ts b/src/index.ts index 2e6d548c6b..fd39490345 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,8 +16,7 @@ export { SeriesCollectionValue } from './chart_types/xy_chart/utils/series'; export { ChartTypes } from './chart_types'; export { Datum, Position, Rendering, Rotation } from './utils/commons'; export { TickFormatter } from './chart_types/xy_chart/utils/specs'; -export { TooltipType, TooltipValue, TooltipValueFormatter } from './chart_types/xy_chart/utils/interactions'; -export { SeriesIdentifier } from './chart_types/xy_chart/utils/series'; +export { SeriesIdentifier, XYChartSeriesIdentifier } from './chart_types/xy_chart/utils/series'; export { AnnotationDomainType, AnnotationDomainTypes, diff --git a/src/mocks/series/seriesIdentifiers.ts b/src/mocks/series/seriesIdentifiers.ts index 38f3b07671..f471994f51 100644 --- a/src/mocks/series/seriesIdentifiers.ts +++ b/src/mocks/series/seriesIdentifiers.ts @@ -1,6 +1,10 @@ import { BasicSeriesSpec } from '../../chart_types/xy_chart/utils/specs'; import { getSpecId } from '../..'; -import { SeriesCollectionValue, getSplittedSeries, SeriesIdentifier } from '../../chart_types/xy_chart/utils/series'; +import { + SeriesCollectionValue, + getSplittedSeries, + XYChartSeriesIdentifier, +} from '../../chart_types/xy_chart/utils/series'; import { mergePartial } from '../../utils/commons'; type SeriesCollection = Map; @@ -18,7 +22,7 @@ export class MockSeriesCollection { } export class MockSeriesIdentifier { - private static readonly base: SeriesIdentifier = { + private static readonly base: XYChartSeriesIdentifier = { specId: getSpecId('bars'), yAccessor: 'y', seriesKeys: ['a'], @@ -26,7 +30,9 @@ export class MockSeriesIdentifier { key: 'spec{bars}yAccessor{y}splitAccessors{g-a}', }; - static default(partial?: Partial) { - return mergePartial(MockSeriesIdentifier.base, partial, { mergeOptionalPartialValues: true }); + static default(partial?: Partial) { + return mergePartial(MockSeriesIdentifier.base, partial, { + mergeOptionalPartialValues: true, + }); } } diff --git a/src/mocks/specs/specs.ts b/src/mocks/specs/specs.ts index 572a8d7915..e8437b427a 100644 --- a/src/mocks/specs/specs.ts +++ b/src/mocks/specs/specs.ts @@ -13,8 +13,7 @@ import { import { getSpecId, getGroupId } from '../../utils/ids'; import { ScaleType } from '../../scales'; import { ChartTypes } from '../../chart_types'; -import { SettingsSpec, SpecTypes } from '../../specs'; -import { TooltipType } from '../../chart_types/xy_chart/utils/interactions'; +import { SettingsSpec, SpecTypes, TooltipType } from '../../specs'; import { LIGHT_THEME } from '../../utils/themes/light_theme'; export class MockSeriesSpec { @@ -149,7 +148,7 @@ export class MockGlobalSpec { snap: true, }, legendPosition: Position.Right, - showLegendDisplayValue: true, + showLegendExtra: true, hideDuplicateAxes: false, theme: LIGHT_THEME, }; diff --git a/src/specs/settings.test.tsx b/src/specs/settings.test.tsx index 7c1180a0b7..5cab312fd4 100644 --- a/src/specs/settings.test.tsx +++ b/src/specs/settings.test.tsx @@ -2,8 +2,7 @@ import { mount } from 'enzyme'; import * as React from 'react'; import { Position, Rendering, Rotation } from '../utils/commons'; import { DARK_THEME } from '../utils/themes/dark_theme'; -import { TooltipType } from '../chart_types/xy_chart/utils/interactions'; -import { Settings, SettingsSpec } from './settings'; +import { Settings, SettingsSpec, TooltipType } from './settings'; import { PartialTheme } from '../utils/themes/theme'; import { LIGHT_THEME } from '../utils/themes/light_theme'; import { chartStoreReducer, GlobalChartState } from '../state/chart_state'; @@ -68,7 +67,7 @@ describe('Settings spec component', () => { snap: false, }, legendPosition: Position.Bottom, - showLegendDisplayValue: false, + showLegendExtra: false, debug: true, xDomain: { min: 0, max: 10 }, }, @@ -84,7 +83,7 @@ describe('Settings spec component', () => { snap: false, }); expect(settingSpec.legendPosition).toBe(Position.Bottom); - expect(settingSpec.showLegendDisplayValue).toEqual(false); + expect(settingSpec.showLegendExtra).toEqual(false); expect(settingSpec.debug).toBe(true); expect(settingSpec.xDomain).toEqual({ min: 0, max: 10 }); }); @@ -176,7 +175,7 @@ describe('Settings spec component', () => { snap: false, }, legendPosition: Position.Bottom, - showLegendDisplayValue: false, + showLegendExtra: false, hideDuplicateAxes: false, debug: true, xDomain: { min: 0, max: 10 }, diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index 388f4ed166..d316532ef5 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -2,20 +2,20 @@ import { $Values } from 'utility-types'; import { DomainRange } from '../chart_types/xy_chart/utils/specs'; import { PartialTheme, Theme } from '../utils/themes/theme'; import { Domain } from '../utils/domain'; -import { TooltipType, TooltipValueFormatter } from '../chart_types/xy_chart/utils/interactions'; import { getConnect, specComponentFactory } from '../state/spec_factory'; import { Spec } from '.'; import { LIGHT_THEME } from '../utils/themes/light_theme'; import { ChartTypes } from '../chart_types'; import { GeometryValue } from '../utils/geometry'; -import { SeriesIdentifier } from '../chart_types/xy_chart/utils/series'; +import { XYChartSeriesIdentifier, SeriesIdentifier } from '../chart_types/xy_chart/utils/series'; import { Position, Rendering, Rotation } from '../utils/commons'; import { ScaleContinuousType, ScaleOrdinalType } from '../scales'; +import { Accessor } from '../utils/accessor'; -export type ElementClickListener = (elements: Array<[GeometryValue, SeriesIdentifier]>) => void; -export type ElementOverListener = (elements: Array<[GeometryValue, SeriesIdentifier]>) => void; +export type ElementClickListener = (elements: Array<[GeometryValue, XYChartSeriesIdentifier]>) => void; +export type ElementOverListener = (elements: Array<[GeometryValue, XYChartSeriesIdentifier]>) => void; export type BrushEndListener = (min: number, max: number) => void; -export type LegendItemListener = (series: SeriesIdentifier | null) => void; +export type LegendItemListener = (series: XYChartSeriesIdentifier | null) => void; export type PointerUpdateListener = (event: PointerEvent) => void; /** * Listener to be called when chart render state changes @@ -57,7 +57,54 @@ export interface PointerOutEvent extends BasePointerEvent { export type PointerEvent = PointerOverEvent | PointerOutEvent; -interface TooltipProps { +/** The type of tooltip to use */ +export const TooltipType = Object.freeze({ + /** Vertical cursor parallel to x axis */ + VerticalCursor: 'vertical' as 'vertical', + /** Vertical and horizontal cursors */ + Crosshairs: 'cross' as 'cross', + /** Follor the mouse coordinates */ + Follow: 'follow' as 'follow', + /** Hide every tooltip */ + None: 'none' as 'none', +}); + +export type TooltipType = $Values; + +export interface TooltipValue { + /** + * The label of the tooltip value + */ + label: string; + /** + * The value to display + */ + value: any; + /** + * The color of the graphic mark (by default the color of the series) + */ + color: string; + /** + * True if the mouse is over the graphic mark connected to the tooltip + */ + isHighlighted: boolean; + /** + * True if the tooltip is visible, false otherwise + */ + isVisible: boolean; + /** + * The idenfitier of the related series + */ + seriesIdentifier: SeriesIdentifier; + /** + * The accessor linked to the current tooltip value + */ + valueAccessor: Accessor; +} + +export type TooltipValueFormatter = (data: TooltipValue) => JSX.Element | string; + +export interface TooltipProps { type?: TooltipType; snap?: boolean; headerFormatter?: TooltipValueFormatter; @@ -90,7 +137,11 @@ export interface SettingsSpec extends Spec { tooltip: TooltipType | TooltipProps; debug: boolean; legendPosition: Position; - showLegendDisplayValue: boolean; + /** + * Show an extra parameter on each legend item defined by the chart type + * @default false + */ + showLegendExtra: boolean; /** * Removes duplicate axes * @@ -123,7 +174,7 @@ export type DefaultSettingsProps = | 'showLegend' | 'debug' | 'tooltip' - | 'showLegendDisplayValue' + | 'showLegendExtra' | 'theme' | 'legendPosition' | 'hideDuplicateAxes'; @@ -155,7 +206,7 @@ export const DEFAULT_SETTINGS_SPEC: SettingsSpec = { snap: DEFAULT_TOOLTIP_SNAP, }, legendPosition: Position.Right, - showLegendDisplayValue: true, + showLegendExtra: false, hideDuplicateAxes: false, theme: LIGHT_THEME, }; @@ -173,3 +224,19 @@ export function isPointerOutEvent(event: PointerEvent | null | undefined): event export function isPointerOverEvent(event: PointerEvent | null | undefined): event is PointerOverEvent { return event !== null && event !== undefined && event.type === PointerEventType.Over; } + +export function isTooltipProps(config: TooltipType | TooltipProps): config is TooltipProps { + return typeof config === 'object'; +} + +export function isTooltipType(config: TooltipType | TooltipProps): config is TooltipType { + return typeof config === 'string'; +} + +export function isCrosshairTooltipType(type: TooltipType) { + return type === TooltipType.VerticalCursor || type === TooltipType.Crosshairs; +} + +export function isFollowTooltipType(type: TooltipType) { + return type === TooltipType.Follow; +} diff --git a/src/state/actions/legend.ts b/src/state/actions/legend.ts index 3defd22d79..4166e62e11 100644 --- a/src/state/actions/legend.ts +++ b/src/state/actions/legend.ts @@ -1,4 +1,4 @@ -import { SeriesIdentifier } from '../../chart_types/xy_chart/utils/series'; +import { XYChartSeriesIdentifier } from '../../chart_types/xy_chart/utils/series'; export const ON_TOGGLE_LEGEND = 'ON_TOGGLE_LEGEND'; export const ON_LEGEND_ITEM_OVER = 'ON_LEGEND_ITEM_OVER'; @@ -18,7 +18,7 @@ interface LegendItemOutAction { interface ToggleDeselectSeriesAction { type: typeof ON_TOGGLE_DESELECT_SERIES; - legendItemId: SeriesIdentifier; + legendItemId: XYChartSeriesIdentifier; } export function onToggleLegend(): ToggleLegendAction { @@ -33,7 +33,7 @@ export function onLegendItemOutAction(): LegendItemOutAction { return { type: ON_LEGEND_ITEM_OUT }; } -export function onToggleDeselectSeriesAction(legendItemId: SeriesIdentifier): ToggleDeselectSeriesAction { +export function onToggleDeselectSeriesAction(legendItemId: XYChartSeriesIdentifier): ToggleDeselectSeriesAction { return { type: ON_TOGGLE_DESELECT_SERIES, legendItemId }; } diff --git a/src/state/chart_state.ts b/src/state/chart_state.ts index 09cb75e537..1cb0c4d29e 100644 --- a/src/state/chart_state.ts +++ b/src/state/chart_state.ts @@ -2,7 +2,7 @@ import { SPEC_PARSED, SPEC_UNMOUNTED, UPSERT_SPEC, REMOVE_SPEC, SPEC_PARSING } f import { interactionsReducer } from './reducers/interactions'; import { ChartTypes } from '../chart_types'; import { XYAxisChartState } from '../chart_types/xy_chart/state/chart_state'; -import { SeriesIdentifier } from '../chart_types/xy_chart/utils/series'; +import { XYChartSeriesIdentifier } from '../chart_types/xy_chart/utils/series'; import { Spec, PointerEvent } from '../specs'; import { DEFAULT_SETTINGS_SPEC } from '../specs/settings'; import { Dimensions } from '../utils/dimensions'; @@ -15,6 +15,8 @@ import { UPDATE_PARENT_DIMENSION } from './actions/chart_settings'; import { EXTERNAL_POINTER_EVENT } from './actions/events'; import { RefObject } from 'react'; import { PartitionState } from '../chart_types/partition_chart/state/chart_state'; +import { TooltipInfo } from '../components/tooltip/types'; +import { TooltipAnchorPosition } from '../components/tooltip/utils'; export type BackwardRef = () => React.RefObject; @@ -23,22 +25,62 @@ export type BackwardRef = () => React.RefObject; * globally by the and */ export interface InternalChartState { - // the chart type + /** + * the chart type + */ chartType: ChartTypes; - // returns a JSX element with the chart rendered (lenged excluded) + /** + * returns a JSX element with the chart rendered (lenged excluded) + * @param containerRef + * @param forwardStageRef + */ chartRenderer(containerRef: BackwardRef, forwardStageRef: RefObject): JSX.Element | null; - // true if the brush is available for this chart type + /** + * true if the brush is available for this chart type + * @param globalState + */ isBrushAvailable(globalState: GlobalChartState): boolean; - // true if the brush is available for this chart type + /** + * true if the brush is available for this chart type + * @param globalState + */ isBrushing(globalState: GlobalChartState): boolean; - // true if the chart is empty (no data displayed) + /** + * true if the chart is empty (no data displayed) + * @param globalState + */ isChartEmpty(globalState: GlobalChartState): boolean; - // return the list of legend items + /** + * return the list of legend items + * @param globalState + */ getLegendItems(globalState: GlobalChartState): Map; - // return the list of values for each legend item + /** + * return the list of values for each legend item + * @param globalState + */ getLegendItemsValues(globalState: GlobalChartState): Map; - // return the CSS pointer cursor depending on the internal chart state + /** + * return the CSS pointer cursor depending on the internal chart state + * @param globalState + */ getPointerCursor(globalState: GlobalChartState): string; + /** + * true if the tooltip is visible, false otherwise + * @param globalState + */ + isTooltipVisible(globalState: GlobalChartState): boolean; + /** + * Get the tooltip information to display + * @param globalState the GlobalChartState + */ + getTooltipInfo(globalState: GlobalChartState): TooltipInfo | undefined; + + /** + * Get the tooltip anchor position + * @param globalState + */ + getTooltipAnchor(globalState: GlobalChartState): TooltipAnchorPosition | null; } export interface SpecList { @@ -67,7 +109,7 @@ export interface InteractionsState { highlightedLegendItemKey: string | null; legendCollapsed: boolean; invertDeselect: boolean; - deselectedDataSeries: SeriesIdentifier[]; + deselectedDataSeries: XYChartSeriesIdentifier[]; } export interface ExternalEventsState { diff --git a/src/state/reducers/interactions.ts b/src/state/reducers/interactions.ts index 203ade3392..3d672a3044 100644 --- a/src/state/reducers/interactions.ts +++ b/src/state/reducers/interactions.ts @@ -7,7 +7,7 @@ import { LegendActions, } from '../actions/legend'; import { ON_MOUSE_DOWN, ON_MOUSE_UP, ON_POINTER_MOVE, MouseActions } from '../actions/mouse'; -import { getSeriesIndex, SeriesIdentifier } from '../../chart_types/xy_chart/utils/series'; +import { getSeriesIndex, XYChartSeriesIdentifier } from '../../chart_types/xy_chart/utils/series'; export function interactionsReducer(state: InteractionsState, action: LegendActions | MouseActions): InteractionsState { switch (action.type) { @@ -107,7 +107,10 @@ export function interactionsReducer(state: InteractionsState, action: LegendActi } } -function toggleDeselectedDataSeries(legendItem: SeriesIdentifier, deselectedDataSeries: SeriesIdentifier[]) { +function toggleDeselectedDataSeries( + legendItem: XYChartSeriesIdentifier, + deselectedDataSeries: XYChartSeriesIdentifier[], +) { const index = getSeriesIndex(deselectedDataSeries, legendItem); if (index > -1) { return [...deselectedDataSeries.slice(0, index), ...deselectedDataSeries.slice(index + 1)]; diff --git a/src/state/selectors/get_internal_is_tooltip_visible.ts b/src/state/selectors/get_internal_is_tooltip_visible.ts new file mode 100644 index 0000000000..a6dbc06f77 --- /dev/null +++ b/src/state/selectors/get_internal_is_tooltip_visible.ts @@ -0,0 +1,9 @@ +import { GlobalChartState } from '../chart_state'; + +export const getInternalIsTooltipVisibleSelector = (state: GlobalChartState): boolean => { + if (state.internalChartState) { + return state.internalChartState.isTooltipVisible(state); + } else { + return false; + } +}; diff --git a/src/state/selectors/get_internal_tooltip_anchor_position.ts b/src/state/selectors/get_internal_tooltip_anchor_position.ts new file mode 100644 index 0000000000..b026df2be1 --- /dev/null +++ b/src/state/selectors/get_internal_tooltip_anchor_position.ts @@ -0,0 +1,10 @@ +import { GlobalChartState } from '../chart_state'; +import { TooltipAnchorPosition } from '../../components/tooltip/utils'; + +export const getInternalTooltipAnchorPositionSelector = (state: GlobalChartState): TooltipAnchorPosition | null => { + if (state.internalChartState) { + return state.internalChartState.getTooltipAnchor(state); + } else { + return null; + } +}; diff --git a/src/state/selectors/get_internal_tooltip_info.ts b/src/state/selectors/get_internal_tooltip_info.ts new file mode 100644 index 0000000000..e15fcf1abf --- /dev/null +++ b/src/state/selectors/get_internal_tooltip_info.ts @@ -0,0 +1,10 @@ +import { GlobalChartState } from '../chart_state'; +import { TooltipInfo } from '../../components/tooltip/types'; + +export const getInternalTooltipInfoSelector = (state: GlobalChartState): TooltipInfo | undefined => { + if (state.internalChartState) { + return state.internalChartState.getTooltipInfo(state); + } else { + return undefined; + } +}; diff --git a/src/state/selectors/get_legend_size.ts b/src/state/selectors/get_legend_size.ts index 18dd4b534e..d641d7051f 100644 --- a/src/state/selectors/get_legend_size.ts +++ b/src/state/selectors/get_legend_size.ts @@ -15,18 +15,18 @@ const legendItemLabelsSelector = createCachedSelector( [getSettingsSpecSelector, getLegendItemsSelector], (settings, legendItems): string[] => { const labels: string[] = []; - const { showLegendDisplayValue } = settings; + const { showLegendExtra } = settings; legendItems.forEach((item) => { const labelY1 = getItemLabel(item, 'y1'); if (item.displayValue.formatted.y1 !== null) { - labels.push(`${labelY1}${showLegendDisplayValue ? item.displayValue.formatted.y1 : ''}`); + labels.push(`${labelY1}${showLegendExtra ? item.displayValue.formatted.y1 : ''}`); } else { labels.push(labelY1); } if (item.banded) { const labelY0 = getItemLabel(item, 'y0'); if (item.displayValue.formatted.y0 !== null) { - labels.push(`${labelY0}${showLegendDisplayValue ? item.displayValue.formatted.y0 : ''}`); + labels.push(`${labelY0}${showLegendExtra ? item.displayValue.formatted.y0 : ''}`); } else { labels.push(labelY0); } @@ -68,7 +68,7 @@ export const getLegendSizeSelector = createCachedSelector( ); bboxCalculator.destroy(); - const { showLegend, showLegendDisplayValue, legendPosition } = settings; + const { showLegend, showLegendExtra: showLegendDisplayValue, legendPosition } = settings; const { legend: { verticalWidth, spacingBuffer }, } = theme; diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_header_formatter.ts b/src/state/selectors/get_tooltip_header_formatter.ts similarity index 58% rename from src/chart_types/xy_chart/state/selectors/get_tooltip_header_formatter.ts rename to src/state/selectors/get_tooltip_header_formatter.ts index 5f9f0cfff8..2dafa8bddd 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_header_formatter.ts +++ b/src/state/selectors/get_tooltip_header_formatter.ts @@ -1,8 +1,7 @@ import createCachedSelector from 're-reselect'; -import { isTooltipProps, TooltipValueFormatter } from '../../utils/interactions'; -import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { SettingsSpec } from '../../../../specs/settings'; -import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { getSettingsSpecSelector } from './get_settings_specs'; +import { SettingsSpec, TooltipValueFormatter, isTooltipProps } from '../../specs/settings'; +import { getChartIdSelector } from './get_chart_id'; export const getTooltipHeaderFormatterSelector = createCachedSelector( [getSettingsSpecSelector], diff --git a/src/utils/domain.ts b/src/utils/domain.ts index 46cbfe43bd..22d9dbdd29 100644 --- a/src/utils/domain.ts +++ b/src/utils/domain.ts @@ -1,35 +1,9 @@ import { extent, sum } from 'd3-array'; import { nest } from 'd3-collection'; -import { Accessor, AccessorFn } from './accessor'; -import { ScaleType } from '../scales'; +import { AccessorFn } from './accessor'; export type Domain = any[]; -export interface SpecDomain { - accessor: Accessor; - level: number; - domain: Domain; - scaleType: ScaleType; - isStacked?: boolean; -} - -export interface ColorDomain { - accessors: Accessor[]; - yAccessors?: Accessor[]; - domain: string[]; - scaleType: ScaleType; -} - -export interface SeriesScales { - groupLevel: number; - xDomain: Domain; - yDomain?: Domain; - xScaleType: ScaleType; - yScaleType?: ScaleType; - xAccessor: Accessor; - yAccessor?: Accessor; -} - export function computeOrdinalDataDomain( data: any[], accessor: AccessorFn, diff --git a/src/utils/geometry.ts b/src/utils/geometry.ts index 00c21ed340..8c0cded6c7 100644 --- a/src/utils/geometry.ts +++ b/src/utils/geometry.ts @@ -1,6 +1,6 @@ import { $Values } from 'utility-types'; import { BarSeriesStyle, PointStyle, AreaStyle, LineStyle, ArcStyle } from './themes/theme'; -import { SeriesIdentifier } from '../chart_types/xy_chart/utils/series'; +import { XYChartSeriesIdentifier } from '../chart_types/xy_chart/utils/series'; /** * The accessor type @@ -36,7 +36,7 @@ export interface PointGeometry { x: number; y: number; }; - seriesIdentifier: SeriesIdentifier; + seriesIdentifier: XYChartSeriesIdentifier; value: GeometryValue; styleOverrides?: Partial; } @@ -53,7 +53,7 @@ export interface BarGeometry { hideClippedValue?: boolean; isValueContainedInElement?: boolean; }; - seriesIdentifier: SeriesIdentifier; + seriesIdentifier: XYChartSeriesIdentifier; value: GeometryValue; seriesStyle: BarSeriesStyle; } @@ -66,7 +66,7 @@ export interface LineGeometry { x: number; y: number; }; - seriesIdentifier: SeriesIdentifier; + seriesIdentifier: XYChartSeriesIdentifier; seriesLineStyle: LineStyle; seriesPointStyle: PointStyle; /** @@ -84,7 +84,7 @@ export interface AreaGeometry { x: number; y: number; }; - seriesIdentifier: SeriesIdentifier; + seriesIdentifier: XYChartSeriesIdentifier; seriesAreaStyle: AreaStyle; seriesAreaLineStyle: LineStyle; seriesPointStyle: PointStyle; @@ -98,7 +98,7 @@ export interface AreaGeometry { export interface ArcGeometry { arc: string; color: string; - seriesIdentifier: SeriesIdentifier; + seriesIdentifier: XYChartSeriesIdentifier; seriesArcStyle: ArcStyle; transform: { x: number; diff --git a/stories/annotations.tsx b/stories/annotations.tsx index 2d09cc27de..2fae0ef2d4 100644 --- a/stories/annotations.tsx +++ b/stories/annotations.tsx @@ -70,7 +70,7 @@ export const lineBasicXDomainContinous = () => { return ( - + { return ( - + { const allMetrics = [...data3, ...data2, ...data1]; return ( - + { const stackedAsPercentage = boolean('stacked as percentage', true); return ( - + { return ( - + { return ( - + { const y1AccessorFormat = text('y1AccessorFormat', ''); return ( - + { return ( - + { const stackAccessors = isStackedSeries ? ['x'] : undefined; return ( - + Number(d).toFixed(2)} /> { return ( - + Number(d).toFixed(2)} /> @@ -435,7 +435,7 @@ withAxisAndLegend.story = { export const stackedWithAxisAndLegend = () => { return ( - + Number(d).toFixed(2)} /> @@ -470,7 +470,7 @@ export const stackedAsPercentage = () => { const clusterBars = boolean('cluster', true); return ( - + { }; return ( - + Number(d).toFixed(2)} /> @@ -558,7 +564,7 @@ clusteredWithAxisAndLegend.story = { export const clusteredMultipleSeriesSpecs = () => { return ( - + Number(d).toFixed(2)} /> @@ -711,7 +717,7 @@ timeStackedUsingVariousSpecs.story = { export const barChart1y0g = () => { return ( - + Number(d).toFixed(2)} /> @@ -733,7 +739,7 @@ barChart1y0g.story = { export const barChart1y1g = () => { return ( - + Number(d).toFixed(2)} /> @@ -756,7 +762,7 @@ barChart1y1g.story = { export const barChart1y2g = () => { return ( - + Number(d).toFixed(2)} /> @@ -779,7 +785,7 @@ barChart1y2g.story = { export const barChart2y0g = () => { return ( - + Number(d).toFixed(2)} /> @@ -801,7 +807,7 @@ barChart2y0g.story = { export const barChart2y1g = () => { return ( - + Number(d).toFixed(2)} /> @@ -830,7 +836,7 @@ export const barChart2y2g = () => { return ( - + Number(d).toFixed(2)} /> @@ -857,7 +863,7 @@ export const tooltipSeriesVisibility = () => { }; return ( - + Number(d).toFixed(2)} /> diff --git a/stories/interactions.tsx b/stories/interactions.tsx index a87b026171..ace87a346e 100644 --- a/stories/interactions.tsx +++ b/stories/interactions.tsx @@ -57,17 +57,17 @@ export default { }; export const barClicksAndHovers = () => { - const headerFormatter: TooltipValueFormatter = (tooltipData: TooltipValue) => { - if (tooltipData.value % 2 === 0) { + const headerFormatter: TooltipValueFormatter = (tooltip: TooltipValue) => { + if (tooltip.value % 2 === 0) { return (

special header for even x values

-

{tooltipData.value}

+

{tooltip.value}

); } - return tooltipData.value; + return tooltip.value; }; const tooltipProps = { @@ -76,7 +76,13 @@ export const barClicksAndHovers = () => { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( { return ( { return ( { return ( - + { return ( - + { button(label, handler, groupId); return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { const splitSeries = boolean('split series', true) ? ['g1', 'g2'] : undefined; return ( - + { return ( - + { }); return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { { return ( - + @@ -104,7 +105,7 @@ negative90DegreeOrdinal.story = { export const rotations0DegOrdinal = () => { return ( - + @@ -132,7 +133,7 @@ rotations0DegOrdinal.story = { export const rotations90DegOrdinal = () => { return ( - + @@ -160,7 +161,7 @@ rotations90DegOrdinal.story = { export const rotations180DegOrdinal = () => { return ( - + @@ -188,7 +189,7 @@ rotations180DegOrdinal.story = { export const negative90DegLinear = () => { return ( - + @@ -216,7 +217,7 @@ negative90DegLinear.story = { export const rotations0DegLinear = () => { return ( - + @@ -244,7 +245,7 @@ rotations0DegLinear.story = { export const rotations90DegLinear = () => { return ( - + @@ -272,7 +273,7 @@ rotations90DegLinear.story = { export const rotations180DegLinear = () => { return ( - + diff --git a/stories/styling.tsx b/stories/styling.tsx index 66f757243c..a5089830f1 100644 --- a/stories/styling.tsx +++ b/stories/styling.tsx @@ -221,7 +221,13 @@ export const marginsAndPaddings = () => { const withTopTitle = boolean('top axis with title', true); return ( - + { theme={theme} baseTheme={darkmode ? DARK_THEME : LIGHT_THEME} debug={boolean('debug', false)} - showLegend={true} + showLegend + showLegendExtra legendPosition={Position.Right} /> @@ -499,7 +506,7 @@ export const partialCustomTheme = () => { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { }; return ( - + { return ( - + RecursivePartial | Color | null; ``` @@ -246,7 +246,7 @@ Return types: ```ts type PointStyleAccessor = ( datum: RawDataSeriesDatum, - seriesIdentifier: SeriesIdentifier, + seriesIdentifier: XYChartSeriesIdentifier, ) => RecursivePartial | Color | null; ``` From 55f9712f27c414b9fe608f646dcd8c310099e0a1 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Thu, 20 Feb 2020 11:23:27 -0600 Subject: [PATCH 12/15] chore: address pr comments --- src/chart_types/xy_chart/utils/series.ts | 13 +++++++------ src/chart_types/xy_chart/utils/specs.ts | 22 +++++++++++----------- stories/styling.tsx | 12 ++++++------ 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index 8889ef6444..cb235b08dd 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -9,6 +9,8 @@ import { ScaleType } from '../../../scales'; import { LastValues } from '../state/utils'; import { Datum } from '../../../utils/commons'; +export const SERIES_DELIMITER = ' - '; + export interface FilledValues { /** the x value */ x?: number | string; @@ -380,11 +382,11 @@ export function getSplittedSeries( }; } -const getSeriesNameFromOptions = ( +export function getSeriesNameFromOptions( options: SeriesNameConfigOptions, { yAccessor, splitAccessors }: SeriesIdentifier, delimiter: string, -) => { +) { if (!options.names) { return null; } @@ -393,7 +395,7 @@ const getSeriesNameFromOptions = ( options.names .sort(({ sortIndex: a = Infinity }, { sortIndex: b = Infinity }) => a - b) .map(({ accessor, value, name }) => { - const accessorValue = (accessor ? splitAccessors.get(accessor) : null) ?? null; + const accessorValue = splitAccessors.get(accessor) ?? null; if (accessorValue === value) { return name ?? value; } @@ -404,10 +406,9 @@ const getSeriesNameFromOptions = ( return null; }) .filter((d) => Boolean(d) || d === 0) - .slice() .join(delimiter) || null ); -}; +} /** * Get series name based on `SeriesIdentifier` @@ -418,7 +419,7 @@ export function getSeriesName( isTooltip: boolean, spec?: BasicSeriesSpec, ): string { - let delimiter = ' - '; + let delimiter = SERIES_DELIMITER; if (spec && spec.name && typeof spec.name !== 'string') { let customLabel: string | number | null = null; if (typeof spec.name === 'function') { diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 53c968c6aa..ef5774aaee 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -50,19 +50,19 @@ export type PointStyleAccessor = (datum: RawDataSeriesDatum, seriesIdentifier: S export const DEFAULT_GLOBAL_ID = '__global__'; export type FilterPredicate = (series: SeriesIdentifier) => boolean; -export type SeriesLabel = string | number | null; +export type SeriesName = string | number | null; /** - * Function to create custom series label for a given series + * Function to create custom series name for a given series */ -export type SeriesNameFn = (series: SeriesIdentifier, isTooltip: boolean) => SeriesLabel; +export type SeriesNameFn = (series: SeriesIdentifier, isTooltip: boolean) => SeriesName; /** - * Accessor mapping to replace labels + * Accessor mapping to replace names */ export interface SeriesNameConfig { /** * accessor key (i.e. `yAccessors` and `seriesSplitAccessors`) */ - accessor: string; + accessor: string | number; /** * Accessor value (i.e. values from `seriesSplitAccessors`) */ @@ -74,10 +74,10 @@ export interface SeriesNameConfig { */ name?: string | number; /** - * Sort order of label, overrides order listed in array. + * Sort order of name, overrides order listed in array. * - * lower values - front most - * higher values - end most + * lower values - left-most + * higher values - right-most */ sortIndex?: number; } @@ -86,16 +86,16 @@ export interface SeriesNameConfigOptions { * Array of accessor naming configs to replace series names * * Only provided configs will be included - * (i.e. if you only provide a single mapping for `yAccessor`, all other series accessor labels will be ignored) + * (i.e. if you only provide a single mapping for `yAccessor`, all other series accessor names will be ignored) * - * The order of configs is the order in which the resulting labels will + * The order of configs is the order in which the resulting names will * be joined, if no `sortIndex` is specified. * * If no values are found for a giving mapping in a series, the mapping will be ignored. */ names?: SeriesNameConfig[]; /** - * Delimiter to join values/labels + * Delimiter to join values/names * * @default ' - ' */ diff --git a/stories/styling.tsx b/stories/styling.tsx index c363f0b824..a7931cfaa6 100644 --- a/stories/styling.tsx +++ b/stories/styling.tsx @@ -924,7 +924,7 @@ customSeriesStylesArea.story = { name: 'custom series styles: area', }; -export const addCustomSeriesLabel = () => { +export const addCustomSeriesName = () => { const customSeriesNamingFn: SeriesNameFn = ({ yAccessor, splitAccessors }) => { // eslint-disable-next-line react/prop-types if (yAccessor === 'y1' && splitAccessors.get('g') === 'a') { @@ -958,8 +958,8 @@ export const addCustomSeriesLabel = () => { ); }; -addCustomSeriesLabel.story = { - name: 'Add custom series label', +addCustomSeriesName.story = { + name: 'Add custom series name', }; export const customSeriesNamingConfig = () => { @@ -1007,7 +1007,7 @@ customSeriesNamingConfig.story = { name: 'Add custom series naming and delimeter', }; -export const addCustomSeriesLabelFormatting = () => { +export const addCustomSeriesNameFormatting = () => { const start = DateTime.fromISO('2019-01-01T00:00:00.000', { zone: 'utc' }); const data = [ { x: 1, y: 3, percent: 0.5, time: start.plus({ month: 1 }).toMillis() }, @@ -1069,8 +1069,8 @@ export const addCustomSeriesLabelFormatting = () => { ); }; -addCustomSeriesLabelFormatting.story = { - name: 'Add custom series label formatting (legend/tooltip) [time/date and percent]', +addCustomSeriesNameFormatting.story = { + name: 'Add custom series name formatting (legend/tooltip) [time/date and percent]', }; export const tickLabelPaddingBothPropAndTheme = () => { From 76f000f697ceea1a0f3bffa29d49dcab63d78b39 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Thu, 20 Feb 2020 12:26:37 -0600 Subject: [PATCH 13/15] chore: update screenshots --- ...formatting-visually-looks-correct-1-snap.png | Bin 33467 -> 0 bytes ...ries-label-visually-looks-correct-1-snap.png | Bin 19265 -> 0 bytes ...formatting-visually-looks-correct-1-snap.png | Bin 0 -> 33718 bytes ...eries-name-visually-looks-correct-1-snap.png | Bin 0 -> 16960 bytes ...formatting-visually-looks-correct-1-snap.png | Bin 31642 -> 0 bytes ...l-mappings-visually-looks-correct-1-snap.png | Bin 21055 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-formatting-visually-looks-correct-1-snap.png delete mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-name-formatting-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-name-visually-looks-correct-1-snap.png delete mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-sub-series-label-formatting-visually-looks-correct-1-snap.png delete mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-custom-series-label-mappings-visually-looks-correct-1-snap.png diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-formatting-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-formatting-visually-looks-correct-1-snap.png deleted file mode 100644 index 637f5eeb0721f6ddc52a5b95ae54fb0d53babe91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33467 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 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-label-visually-looks-correct-1-snap.png deleted file mode 100644 index 92752b6c79509a509c6f3d8b3a00a70e6b93d368..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19265 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 S)GAQ$Pe$sVWa{{KF*(z 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 literal 0 HcmV?d00001 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-sub-series-label-formatting-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-sub-series-label-formatting-visually-looks-correct-1-snap.png deleted file mode 100644 index e779acf24f33a57363c4c21e1af3e463e5c6083d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31642 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 F{tqn5%s2o5 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-custom-series-label-mappings-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-custom-series-label-mappings-visually-looks-correct-1-snap.png deleted file mode 100644 index 50cceb5deebb659c714dba619dafe523062b91ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21055 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 From 9dcb74010d0a0f49a698120df97baf059ea78925 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Fri, 21 Feb 2020 14:58:33 -0600 Subject: [PATCH 14/15] udpate screenshots --- ...ies-name-visually-looks-correct-1-snap.png | Bin 16960 -> 19265 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-name-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-add-custom-series-name-visually-looks-correct-1-snap.png index d71a941247a5105578d4abf1c5265c83b63a797a..92752b6c79509a509c6f3d8b3a00a70e6b93d368 100644 GIT binary patch literal 19265 zcmb`v1z40_*ET$eg3XOVhae#$AxM`+mx$6ONFzvhC@M$_h%~5_bcu9HiIj8baYk0=?tKK} z$SeXuV0N4co|&sP#=rxC{e9`%h};eu_{$}P?A==rT;k_PT{YfL9W<|O<_lVN_#8j} zlG6GaQ-FM8#IFFdiEy{m!l@kZYQ75(T6~G-_+pe;*ckG}BrB~u({)5JBT|a?nB1+q zat|8fRfyggdwJherMXFThjs#6&Y5dDT~=c+G4A(Cd}IV0RA*g+Bt86z`sI{H{RjTn zu;B~cl6T=pC+K#$KmI3BMTQ*z7u)M6XBqK7oKg(B_*XPEUP%eWzoL}lr33yIq~ytO zIPtHTFcj@@Agpm-n^75a!*F=*tw`@|EVK?57||M)m6cu7ua3{myy~_-*Z3{$GN!UR zGczDnJ}Nva%CDqEprxhdC^0eNo5aM9G{rb>iqlL?On=bQdJgBx<3BZpO~-{3{*!yUz16Z&_gr|)sy0SxF&NwjJM0-I)(*;iQ3oRX|bbl+T>(puSTt% zv){)lqzUIO4Hswipq*KI)gL@CC_%2QuRFTwkqQ-^lW-4R@FU^J#LpVB^mOGddMCkL z#(tEvaz1h9OruGWiiU>F!-p3sDJj=xx(I#t_B^B!p#cGg$S+S2k4;U_Q&9zUnK50x z8qn6J@V&KF$ZqoZv17*^X1g0_vAa7v{5yTHSkpC6@pEY&2}@K}ULM;_=N=RgaPlfU zdrMo}adDA}nYnLwr?gaXVqzjTT?IyEt7$p#WHMM_oSBuC zoQsRgZOwmjI4kcxdRt7%t>Z;kgv;D(KmUOFv7m(hr9iIg$)9zMYTo5E&dvkKwWnSV z7YGOrOv{6Lr(cqjuY9+ZpQF()ayvHd_Qfb_&`OkNlZ`*krs=UoYq7e1-HR0?8Wcug z&(Cza=4RK2J8O#<>*seDnXY`QvOL^t=A&x+rE%)jxRC6mzb;H80|) zdNC3jE2LQ5_7Pm^6cLf0Ai{==t9r+cn%=u|j^yQ9k2&rc;S0Z{sc?cS*m+6`!rd9F z&Ag-EDed*O`$B#~vvN}R9jEr| zoRqf*`$lD$kPB_ovG4hL9vxB5i)lkHai)Iy6ulA^GQFB|Vno25b7*`i#U@Fw&2!7E zvZ~5Lx4zq$-PQJ2)`JHRc-(Y&aL)UE9(ygBMDErB#h7l_#qF0g;vPqBY`AhOoOn=N zw2G3F$GEEYsFFOtyjn#yQ;M2hDrql00Ri&P^G#FJnqqW2OJSkcrP;z2ir^+Kq;6rM z_a(=%BX)M!nI8<%+N#zcy=ycOWYv3A^w)523Rh7_znuD6N4%IO9r{hUDp(=9c1rfV zW`*_T{$%Obvy|94X>u6;yz;m+u}Y!pfGoGh z`XmG?p%2m~K?!oA%ie2K!3Q4Eug^De?>Au(N-jAacFxYtqoboMdqod%(>oavn4BC) zn0<4_ZAqI~AY6|g-8wJfah%?>G60sl&|+QdV{$SDzr!Dh9-GRLh5}g{L5*IXF+u!p z^!5s@#(k+Pa7ETwt*yhXP7W5FlWHQ#;5Ffd$jAo>5#Fc+QcE>#Xo6pOeam4;j-h4>wJ6eNLiYO;~#;R8-Tl+7TnL`n|Ptr6b%s zY~vT}h-atoi0y8B_ReEUHH?Z{ilD=eYoW`ox=x|PCFDg?9V%)*dOuk8n`7A z*UcPt|5?g2wSlMfnR&79dfp3?-S29G`z)qXqx9`7ov`onC=x+O)12 z%};SJ;hZ0E2T;Z|dPuNh!<}Lhm)`c;7Senlp*B(_-5%SZ{^2FAj?yoQPI+ zkA}6l+{^j8wOs7;sv6j(Ga&<#oBmAv{6wF2TRSp4E!Cd;kx&O!R8SWbTyJP<$~W(c zDy^u1;Wg#zmcizGs-|&;q3+h1jVQP?IyyQR7ndJ3`t;SfN34j1%femh%3PwNqQ_5K zbQ*hCyoG7f#uUS5<)a9GLcz1eIKN@RqDUis)29aIUW1K#Gd5Vv-zQCsH6U3Le^An6l zT3a>HuI95g&ki2&Qk)jupRB*;W_%qxEusA2vGaZFvS-W7MS^Z?6r1f{M+NS*I}R^6 zL4l>sdonJbU+20>PG_*Yp}?XPJ0Ym1r6t#ib^noLX=+_P>z3$#V7#%o9~Q{DbE+h5 zr1xi>lK1GZ+h71qC5ZAg5u{Qfugb4*BL%{A)WJ_mXJ2KE zIMhGD?+XszMMj=N>N`aGFOazGOC=;0%8z~#G-C;g13zVEvdkPs3Muu!cC!x9yN<@r z@3CItlIWnesjiRrOZvGWfP0yj$E7T`@&_r}DRa}2b(rnKxpQ}MUY;&ii}P;H%|X>K zt|xQ-`bK&RUoj-}s#_LHlx*&gckW@2DX>V4d0LW6C5MGdXtu9jq~|j~yR~3%qvVjI zSl~1&b!2aUb~MY>m+#RBT5hV~`R_xDdC^#c!7}zJfB#sl^j!x-Our4yI`LVuM+I81 z{by1Z@OXEEDpxE|-Pee>qvn0r0Clhwes!jCSV^_)z34lB0Yb34c_odw|nw zY?Tj@nreJae;Zl7oL7DDs_K}8A)vKQ0o3fT*M8FNZYDP?5CEH5r5Cxy=XasCe0@qwC}-JZ*_@y8y`2m8d|Myvdm4k~;BNqKmBF0ZUK)z??8 z$l{s{jF=rxlQf&%xDU}05K(%dzaM6~uTSswIY8ya zb?u_o$dMkZz8uJB*&iWj!GySs@>?EX(6mw*f6=b?J&>#Fc1?BFi4$kL8mhP z>3nX^XOiRI^8KBDf?Brmr=HhG+a4TK8h>uPmzsP%PVt%-RyR@3lMJfqNB6sS08J2Y z-rh*uUG#pDQll?#P=VN4Ur>~iYCo9v+&vkyJJYl0F?n2ETs(-*@j4+F4WhPB^`N6C zqjPlzg)KGSxzQ9%+gRr-IpTn&9kk-#Sr48XtFVz^9*=^_yit*``HmC)K{~P4fcvR+ zx_zm$nqm!3V8KGq)$zoD@-DITHL~Z=hk3np7`^x9aZ>59Mc<&OCF*!PtG^j3x798YS(!r0A9to=)T`S@jWn!>Yo%y& z&}++}s3S()gok#2u0}l-A&UU z?{7I;WM*b+lsmAwy1Kf!yGO*wD?EM5jhLOCwHp3>!nf|`?b}DXy1LSL&*59WyYbzO zYTcVlBE9ljr zpuqLt#KdT(bMo>=h?e7<8<)r6$Vfs5bCwRLy&Jb zB{y7bsh-|s#cx!gas1S&dg~GqwI7Ns=g9&aRZI&sW)6Ek&Z}4LGCP`^n`ea%SCp!o z@k<2)uquU*=k7*dU+!=mL)~~LvZue-!r@4TuSFgHEWNZgfCr%`@7R>v8);jEKY*Dz5 zm$sK9kCfYsnl1%=bsujAoCcwAiTNSXc0 z$_j&!5T#FT?MgfDq#PMfi{QVXOqDONk;bQ^m|4>+Ae_I&V(aJ|iTSrHN~hHedP@e^}R6(O+Ed82cJ^diO66 zE+rr{F6Y(k9KrN^G&GzrigE3v0h|&yBLB5<#cFu(qI4)7p@lfN?Ru;ulMc^Er6;y4 zk2V&N%2AwX{%z529?fhuDHa8TA3sz?d2z>b6YX599RC4mpA;>Q<(#T;ULr8qToiLy z+Cgf_$Z(rPyasstn`W{cjMX0fIY_cgMYmfOcKH?QASc4vS%8x^${&~s@1;=1#Z=GT zn}+V2kK1bo2=k%vZSBUr7R}SnDMvN4m!hlvD7?)X(N4nYE$Fa!ExlhjY&g7PB)KHy zyHi#i7PN5gwDM0Nl8CJyN3Je)&SbqsJY*I5x(MCctyG15eYRg$?FS|kFvyWJT#`3!RkJ?W|ucFI1Qq#xq$?yh>@ zFl#+F);8%Kwn+uD$+kTPIh0e^w}$Po@X)ojsS6d2+_}b8Bbdag%ZQ+GpPgD~**k%1 z=6fDQUvza6Agi z{q_CUnLOGt7h4o2fSH-Uva<3xi3GJz@NC?mdMPet_c|4l+iK?0Vj1p{+nQkLS=!qO zzUA^CKJ-mpd^{0A{v#Co#uQE!-@z z)}7gF4VY??qu~pWG*5auUgazpLM!OYCd!UNZ^*$-78 zK5Xnxm2YWntw$o`CMO?1_w^-^QC2qXNmHaaO?2+uIscFl@r8j)5LAp4L1YEtdRkiF z=lZhBR+^~G7QP?>ntSZ85BwDf09-KvkivK-0*FZk7S)7uAT)Q@W(jXTe$4dp<;%BG zQ4x`mk`4|xq>@RCh7?>}1ciiz($}e=woLX?$8bLz8L8wQef5pLAL=WRluuV0$*~RR zm4BouQg;ZUY1}sEHPbsRiiN*c)EU%1=6O`C1sEW@-9$Q}rba?7YyI;&7mUlBQ{S^V z*t@DQdVb0I+&P+t?oB<39*!3C2Ra_n>GzXRfr{Esc}q#@C0@8MR_BZj1JakC7H@1? zYmZiVQnoS29?k6)WaP2`3b)+0M}9@v!GEmEFxqoG;pnkq8%l=#tre6N@=Gw%rX~Su z{ptqNrpUOJ465e6ti^$go39cDoGKak<8r$26Co<9)li}eU9YjGnn!)zc9QDOf?j6G z;4-xb-G`#)34tvbvlhpg_*hVl!wn$aG^;0)c}GLyrJO1fv~6nqI1?_rV1r)*$q<&L z|HELv(limh+$p{Fv=IqZL#u>I8x!Nl^HuKalj)u>N8EAsZ84q#`@8GEUZ+pB=87g>Ho(P-nUdaxeF>(rbHF30Mnf~LBf z?<>!Fn#E|2^G<}XJb3s}C?rR>B&;>B^sEmyIl0tjl3Uo}xb^BtcVTL}4N&bom__6a zWS+WP3$p_y&Q-;ck&)S2RqrtAN(`53lYkgI_nRX->GLpa;2bS&WBvzUGh~10tbktg zfHtq-)BI1yFC4tRy`dUP189UPOAv8Bcj3a(n~(u6`$$Vl7Cb@nBMOU(@LasvjdK?- zN}8Ck01^dY`QrKWBjpaW6KmU^o}SeE7O+XM`lbHSjPpLH*D2wmot5BS1)LxlYXWK{Z63!8~X0g*$ z!T~Mn`}CCK2PI>}o-cvhU{7{08MiatwALN0awkDRP5$6MFy3_g_Vep;3P3rf8^2h4 z^X3oE%H`W+*EDKXmBkuYP|an_Ur!MX=075I-{{pbv$DDd5W6iwB&shML*_6O$}wyPy?U3Ze3=drE+Fc z!;3JF)gdYb6ddj9z1TqMvmzN~lbtBw*!y#oS$|FZ%06%qNx;-@@(y5}%+FCkXUePgvGC5xuBX6(MeH+?%<(L5dgV{tieq3i8oPh(v`67v_>Zx>ys z3U@-pZP;fdO(Kqh`6~?Wt0bnwXi3 z$9FY&7khZr@Dki)XQ$1*O`>KGH%AAzL_*@T!gNdH-ahX)7BIaIbG>1CCE1Q1eXN3? z#L&{42K%oZo;Z^FLGcI0OsVhHOQ%}18-?tqC8)s;-LkuP9gbXr4On4kM{D#C_TsP@ z6KoVr{mM9Qa>DOr3Xjr^DXDRO=N^jRK7A!|mM;$oM!0Idjl+C1|p02K< z>$WY3It9puw^8&1#LQpL)xSFX@n>@q5Pj=FFs#-EgkE9iLhoUd5rcT1;9Y;E-`Dzj zUx00^s~-jOwL%L$rO7P&vsrC>We~s;|EfV24Mta=fQcm6cY6suDUE?mv4EGG391(yA;<+C>cQ_goF>wQVuzP2l3 zV(r}64#n4AR4Vy&!Yo% zJn@zgo_F>FGu0JuIMio;Y#huC#Pgza>s)I{{1DB_SnX3&7#uX#flXyHW|8#|E$h$N4RWOk(BNQ zU?>D<&Pj#JPe%^Ci=TZrdD>-d#TAdx7eorFvf2J*V=5zqtAaiM6N8c8g`F2e)=bD2N;+jJU3-Z}c^E8f@J@V~CM z??53{qxB0Q!G>z571(WSi#{FKtz7Tl1(9hE-$_f^Jl*s9n->78RFS>uSvoq_v1p#9 z7_`tH_E0DZ6?NdkCV7Sd?R~0NdG}=5=7C?C>(08z#tP4oBW-Po*egGd+}SA^_ma=k ztD2+zZ$;-rFtWxDA=5FG&lndIRb4ND*?c-LLx~vA9l9?k+Ipd-9;@>MWJ zYjS96Vy}KDZ&9@=z{-rwOgkEFh}I~5TUvy`D0k$kolWL^U4-Y(n&G!Mfl8&ZSG_s= z`uapyCOsBQ_sLEZ-SKt&E5-BW+T5H4$nRYSG9}0bR*$hkTc{dB_G6wV_P$0qH-iKy z@dw+&diw)M#9h3vOU$?Z3T|AwQ5FSk+#~+Dt<~0?5ThiI9ibCZ4Feg)$c6cDZE1lU zbtP0wT)MY57j<6;%zJ$;|9kc03^5w5aC`)fC<5x8-=eNJ^{Eplnyh#V+&2^7tu%G% zFN~B$oTU~10no~=xgC#BUgqa>1My~bY!onGfd-8uJe8|kA?!K!@grmUoQH=;d?Fw$ zrLhl%_sN!q$Fl7cr;zVtNyFNO({Cs?z1kyccJg$t1s>yf`SY>NEq{(q^p3I2D=Tc- zXJQGOjOgrN9;ngtX|ynIjne1EcDxmK?5rt0?(FQ`6r^OfDwHzC#wiXO=QY&2y1v=x zFTH9fdprCAWDI%<0fr99Thc7nQ{}a`)MTit&fx@bwY9rz#2@$ix6prAN-8`dLD9_Y z3bcK|5s`6dH7x(CXXD^VegEDkC50)yllsDiUbEnKlb(}R{@={+>sX^=7WDZr@%z5@ z7rJ#dR1fH>&z+lh8DY5b;^Q5^x!(DqqNo~X-6@xY^>@XN7Mx{FiRzqj&w&q)<-^Qg z?1|V|AhhhOJZM5@YbSMlB~SdINUfai{`Q^a*dBSbs1TrlUbCE%P=R|9=K=o^2>v$> zanWchur>5vTOT@d&I4;yn<&QT&Ep>&+z1t?%pP6nwrs;Z`DzVRs&BPNmm#@$LToVNj)_t4w0m%)>GMTb<>oIq4*M#BxA7_DP zbDElWqCp<22}%g4$%g7mxkmGU@1wOkB6@)H%h0B=Lqk_C!*V#m%1v{mAPxP@;Dg8$F>I!@_?Ur*$*zc0yU7qm&=aEdue~_ zn(MBop2qSGmWbD@t0>T@_}tP-%(urh3;khlHDkL_;jYWiG#fTT2n;Y^PCKEec+DpS zL13d0GQ0vZvad9Kb<$qdG04n`t{L_O6qFJ#)b%_gpt+wxrVDUYubtfA*9`o&}8?ZWRp*BFCT@f%?*HOb}KbU_@$8jMzD9MFcxBM>m)c47V zMe}bzpIum#sQr119ID5ZV6{t&J5Ym$6De%K9$qIT)@{=*q7$6O%CwUH@l)BrK%|-6 zr=_=l1zP~^b(T7AV<1M`bmT?G6Kx>LCqgO~(bKa71AFIA#5<}{khI4%f($_Fy&@P8 z5D>$=YmESMF2$OxW>FUBSQF-7THVk&X0-duH<+CN)=qSb!(#O5)5*h)ND=Wx$ zVN={s?lF4tYEAT82|ql2nH^KoUE`g^I~wqrQA-xj+?v-8J5tvfytf%2GcCVpZv5F+ z4!Xt^x*qH3y2VN{(XHBRBO{-4+0vs9tyMB&dTx)#VytgzUEkNbf-l_@hW{^x`S0ed zzjBd7!XAW9JUI`>9>C^yQ<3WD$jH>P(*Be^i-q$J4EAn{3I|Jy_i`Pv!~9@aO9ZR^ z-j*$J{&T&LA3wgQsoByN!`t%xJ0k~2aE5C7eI1>)>Gn8TS=kTS*+Iunk^QK0cNP^B z>#U8$^BVr~8i9k0cHkld*|7foEnk4Pu!|s-&h`GeIvmAtIIVB-9D($Z)Um+`U#-UcVWw|wWw zkt5OEkG`@YQBU~e@EAum9kPM6G`3G&zfT(o{l9Vi{r}4GgRe_~Z|jtmZbe+2Ulo*) zvoREnBb!{7{lNJvy)$kUn6F+NGD+ZFjt!hCoryI`P`#_89C2qy*Ln11yNOXtIP=KU z`ZE{E$d2SYFX=5-VmnN1{<-BKxzb81F8x_mqr`IU%YH`#`os=)LmsL7b%# zyxAN=`z#iD{Rr%z`0}%d@{JZ{`t6-hD#<{FO&1U7;V1D-|C7=3;vp)i0+4HR;!nF)iTvPp-Z)a?;ldF@~(ITSQpHO%0LJAS3x({XV*Y=?K- zpWVXXIaFxO#L8Oax$A`J&(^F@k&PIvbhR%M_&sVWzHPD~qvjCfsqc|G?hC~Y4GnL? z!k+p05d#Y$a&iCA<@i}x@$Bs8s53pe&QpRSlXeIcU;Zb(tX$6Qcna?9tgLOv+PF27Hse4=5`}7b)TdZ4Ha~|2BG|6dYOt_X|G!Be2;W z(Z3zB-{=P>#b)s&1~4>h^F!x-7Dp>ki;GI*)t+(xG!!ujiKOdBP5B0OM-YppsIzIx zNlJcaY5!@oNL>I4_}y-H!;qU|u*O@$%gf6H2t|k}KVo{1KS)Ajosgf+>laGK_0s=+ z9-gU>!VkT{m`^B$n24y*w37~awBxX=JK}{9pla~B+t;9{r<8=g&9NloI6rVUYzc8|a;zn~S4(%VnzAVUWcKvDi4ra6dW%7D*ZjuXxDts$k>< z4;<_-@r(O>vOrux13++zhbJ6%JSar?bT2GSmE7e&WW^#X8V^6bBRPjVZ$Gv&+-tp0 zU4%|KUN8*wr~6PLhD?)2vxE2Z0GA|stjdB*3{NeU{d|6Eu2&;RO@ZY+g2KYS_*XUiaGiMT`fhy(8zk$H(b`afaLqrrXOffAjNJ zx9{A6&Jv%3&AJ$0k zB(vt)BZJ8tN z;Nl8}q>1d>0zG*H}^}_O6J3?Enu#n#fqd(PERMUlJElC3(1co{_O0yH?9h` z(kX#!gMT`f9*`m12M%$A4EwZ$Op<^N9fH@S?Ghw=d9JDp2(S;|zI~g0o~s<8gTP zZ}jM1Ik)@w&s~-Z`k9{OH8Gc!X95NFjF;J)!?@hLt~5TH3L*K~av%hcjlgz*7Z|>O z|GvDuTw9;qLa&22UNSPCFH<-8o)(QBC(f06!*KN1VbeS;a{%AdejAy5N$Jr$JazL_t@g=hf~&{ z*}_ou>61U6?Advt0&OSU%Rl{bX-jl>zDTpoj@dNHlOHiOG~}`M%P-!(L#TZ$9zP}- z%ihVzwN|d0c%WW++&8QciSCO+XX!*`z%~DmqT|Z!*%p2Mfx0@jLuBb57}KF61&jzf ze`3JF@0m@AD6iWtwM9_~SjXczC=`^8&rgtF{$3qpD@Rks1;$)1-+6V~JS5}yyQ)l1 z{650a?V)5*(HaB#fh&|GTYG!dD{AXcyn261nHpUqJ1*eC=}Ijqj6C-rMb#Kg25BiBLT zb*k2P9LfL8JOL>F%{(#i@T@xMJwJ>eRjszfwNQzJXt>10bpO*yFr`;1t|DqW6k{$Y5 zY*m!f@e!#y)O-TS_9DvxPCEDb>%b2KaWJ($pl|y9EnD$#5$As-NxV#Nd#d{4G9GLd zC2SG5LLyx>rPOe$D9}V-0OyQ$35J4;%0vJYA2oiUZtZ3FxTu4T(OV%X%6v8OiBs`qz}h2e*0XcV8$R8U%Yx{c!LU0b##4{JWVei53u~?$&-K% z?RNISQd6)Uc#j&3! zU3GM5Y;A4RD}N(JiOj~4-^Qzy;(LGLc+Fqw@cxf8I{*G6hZ^qbiqVee$4&`4%~K%I zN#5c><5HY{rR4}U?3!-*+saB&5R+wDSXz}902>hs57|4!0>ElW~RrB7C)bi z9cH8qqXyjE#@{ate|aJgu;Uat=VNq7f&(m@ny!=-5fFoI@j?;6rgnFCM+-Y%ZES3` z8mo+X`}P848I61cg3$`+=AItxY2C2HTvD&q<`f0|8=MoEbE>Esi~UnVZqA|DjrU@) z&(_E(Doz2xwYE4KXFXcpM_F=sHs`&`I-bM(KXqFU{7AYla9m9pcf;gB=TqpiVhABL z0|%ROR&ctHY|I*~KA`ZgV$6Ln`O*PlV`Ee9h>>z~a!Pn(%^z2waSa?_cx8Yc1E+ri z8z(*$X#mFhtQ53VM~@!82ZmuaH8St*R(%9G zzncFdz(6Lsy&T*86uIA-ZPhQtQI&v`luh9iSzTj&pl#JI@FDKDo99(ntNc`e8xJq` zown?!luRjxC9i_E9za+`c=$Ph0keHsCb6 zkBA^Re){y|S|5Uxw6uxoX(>a)kBjq%^U0Yd`C<(ORJhdrrNPOT2-;9Mx$xfri_dHt z_+Lq@*O6+_|6rym_!QP}86B=j4>d*28mq zwsL{TXA$*&Bw5Wh`NCjr1Jx%~7 z^tPrZyqb_HS|;alg^^7t#9h!Z}`_hB5W(3js;DE4Ecl`{cY zSEitgfBf~u2{<()jB={jf6+-TGWJJYd%qd}$PFDG#~hLk{}G&-jVX{JYh>k43v+8_2`APS&m63xmxnuWJnon#fa~;K z`=!O`W9`D|P~Ne$nV3HKZRJ@zG8vN1UK*T}%gD_&0RqJV(;o~JP(M}ce`2Mq$;-1z z)`y1Ey!sRwOm3K3j%0QUdTJ^xA)#GaV!r`sFain@r)y$vYkzrrbR>vi2aQ7eMEG4- zA2%BTJ_`>IpUVmS7wE0k;B;83!us$2`-7bSzDWFuF#jtTIV2>oyzO@P?$NQaG1hE2y_lk$B#r=qOPifBa%+<|f!U9Y2cEcCwmW>X}0a`z2r}2Ts z?f@Ys)=`{`UOBha9*o~?jNP%tTW|gXJfeZEkB3KJBD6kmWn}-1F*!R+JL0P%V^g+4 zzO!mBg?+V_t7f*BuAvoZVbB%74w_b3RVdIUGQ1Yo89Y}e8Yj_cqoSc`Xf46Ri}yC4 zq2|XI0C4yg6cj*n$K$-H15A#Lk`fURlFK+`87}%0hByS3C*t+`5^?Lj3p*z@{Xbk9 zrQ&%N8`~qZDxEf!_wZdL$FPIBIXkySaSX3+dp|vywXkM}Jo}Ttxtl!mJs=&I8?vADN@bVU04e@B^s7*~zUu9xy z_(@3s$FobwxS2hnKQ9p#x1UxRANPI!`~ZE@RY@rXI+k)KYIwCuLvy`B_3X3J4C4WCG}wQCX=&7AT2o z3RbnSm|IB159eo;kB01~TNS)40=TPO=s{<|RVEDdjv^X1V#}1BIkdl2P!j-#vB;L& zO$ERXdYhD#2h1*h&qnmjR*v?-r4W1P{$Q@k%~gU5FgIFJlC72nyx#*|UC$m@KN9-> zdJ>6ivU0MrD!RG}T3T8UR8^CEdmn%uuLam-28LU#O7W%tbfFI)Zt=&pI}e!@WfT;w zq+zM3sR8gX%NA-}19j)Vk&#gxD*OVR8E;L(Pj=fSgN+m%Yu*56z}CBSmsL?w5$6)p zGy8S`vyK_zH>xN%Kq~I0p~v4WRCexB^`K_guIqBDSJ3<7q%AEi(?e%%oe%aWR!T)Y z6`aW@t<25Kazc~RPbK>5g{p_A_B1a%&|Ral6`>dLtM8gMa%#cWA-9dnbrWz)L$eM} zGbc<+$kSgMIdSa$ zyLV*Ah>6*+T=_P0WyU8hjm2?kY;=ZV1`*ZD9NL`xh*h^LJ3$)J zP{OzMzuq7hrdLkg%gM<(NkXFj^y$sL@?!>!1S^@Kp5Fjs<{~+H>#T#tw>J!P19?eI z=ADURa`N)3T3WIA(9zb;0$m-(rVIRT1k5+wEUiXdsRpN9rz@ zSXfxVt!IKEV#tagj{MIwygrY=Gx!dOED(2+jouSNG(|>7Uy%(63hJ%e9LusC$fb-w zc@W_5|E6zeAWtK-+2|=1;+cP>!(L8PlN4NMdXwCNNd^!IRjf z^5g98=F$KVS(l<(Q_1yQ>ShEHM_?TmCf418Ll7emAts^J^Fd5OMxmr7UVw8y8PF{P zbs!_+yx0qDcu>gEOFsFd$Gq9EUX?p@=1g@f{0F&&hnw{2=F>8r9bo6)CM4v5TR9o^ z-j|4kE*sSx+PdIGO?&Jn;zJ6`<_wMCJxFD0^74Um7>pyhfY+DC$7eKZX**FXW(&i` zZXk*pmmq^fe4TdoH^)8QyGUq-nc~{h!1Uy+N^qQsjBA&CnFLN2WkEt{u_(3x7J*9C zCBG}|XF6DrK-=&H^coBLN@RR|HZbqhw5Pm2<>d6i>8P&hh%D&D#nyh@Axe8*Q;j=t zhi&@Zu;2;{ixeETn(Ijq{jV5+C^g^#hz*Z_tqS>^b%V_gLn#K?C^;E{SmcI+=cQTc zQUC^+C1oe4FD7l#AAsGhX}-eF9x-Y?0B5(5H2T#ZW$=P`T>4`}1r-lKGQ6*j14jz3j_dptY^ z3}xS~ODt!8e1KAQ-`2K(DWkL$h$da^bbAC7t*FaA`{}kWAO-kL+9)`5O7D?#>MR?V z6=c@fqND&CSTy*PYE?MpcpYq;D#i<{Kr7n=X7;HS7i*!Y)m1yi1mTD99Gqi*$D$NJ z)7m{eoC)l%7JLGP+FPcJWMuI9yVD2bNL@g-8R>~`ay0!z@39f(j$IfX-S@Sy>(0Y70wCK0sV$I-J2&d~*1` z1oIq1Czk1m>CezEv94BteP0IG!)FU+@$I|Nm;jv+%c)oS2~7QFFo9I?B_N9S8Bi)5 z96kdansg8@cEy@-=vV6i#-kGVCqW1b+pFD)kW-wrh1#R6tU zEQ;YwkUCpof40Z+2|xYm1F}pmN8I)2&!4a5dGBN1q2PN+Q^5!K?!6x>GPQ(n ze;Ec7hKMOtGiKVG#OsTr+5phQQ$W{d2XGKRX;4ZpN&Cp~N(; z;6_Bo(v_3=ja)?O5`^s2+Fb;W9y=x*evK?JrDp8P777x550KF}07>WQRcS#2tzjF7 zp=VA{o4}+1+K>aAHqO2ho#?IvwsSSuo+h1%IhcXG-HlR0#1A@1TR%7jHd6E}T~xqX z7@h)B1VF^uf@V5ZS=ranM5hM@o!0-50OMrJfN~X{GCZsW#WO>{#>;Iw#?+%(US1vo zAQCD7d=ZTqfQR8~&k8MVZO83ZW8KoHw;-~BO_+OIRyt}e>s?jw3E~?PXdDL4q2@u; zas^@-6u4Mk(-85kpM385zSnGP8`3@ zST?X{vfV)GK+1inub;P$8Nh&SJvUxcqn4wU3Y6=@c#XbkM|>B2n^LhwUnajk)xP&N z&BD7t)eeJ2x_W;@dunoWE7#J>sux6Xc3C%cg7XctAD6^%dsquIvwOF0)hQamr~q`z z!o>H3k_P}k6{K2z_ay?va#Z8)c#;Udf8oaG-v9lsW_moKPI*k{{KF*(z z6^D;o=M(Qc3|24C4&WI~Jg*M;E{0sku;gTDrg=9;L_}D(XKFhW`xr0WdBm+8(>6?T zC^jrDEj=qJsC)Nr^2JM+=uVyDLw6Tghlx1$p9`x=E+`P{A}=3~Wq@lp(LXUj(;ht7 znv4!}ZI)+XcJCdTw;KH(5hLYO*)wOSNPBe2^?P$~_!?47sk(lLM1SjvQe;e=AT{j@EP#pP8uvkF&ls z$&ufc{qyHt`#wV9*Qwy=&xLH-&Y(49Wglc_X0|moy%`?9(?xdIKS$qXC-SwrI;)_d zKyGX|T(ck|QP$g*>2Q#pNbE{!y+v5-q@DV>tGIcg0T-G(46T!;t&+C*ZUlRrnc444 zC(^9fZ>393mM?fpN;9t%&gj|L+WKX}Ep1v$;LDG%cFQXkciA~#X6he595nJSVxF2a z)l-a@WWjjs>9Z*;^4xmKCg)pTE|}Oq>RdB2pRN=oAdNYA`ZA5L)3aXtP4fYUDIU{Y zwjvgPRtD7Xz7OsYm5hwy;5ORY+FZpwInZV_tkOnUEp_!Fn!#t!o^`)B59Z8BPQK>k zM(z;j34SjEn`21Jc?$IyQ5IpQKGI;6Bxx2WV+ms9#`^H1VFa;VLUF)nDm# z!59fd#l*yDX=x?f;>&c(d{#Q@qAwNR`Ffz#NFansOk>yHLoYc;$H)12dC~CehKh=g zzJB!k{Cqk(x|F)Qn`n)jH(zLIXoQD_Aw99Q%ZT-H8-p&rC;Tqjcx% zOA5RjssWd6w!XGrbelDu(Yo{be4p2f6r<3@apU>L+U}f+&Vkjmem_A|+R@FFzTDe) zq0cSy^@AA%rH4yDJ=`}HY3N|>o^%@bqQDwyeqw9+jJQtbvS z%V!@yI^pQ@)q44w+bj$7gvib+HDtoP;mF<^vz00%AEcwI@uy{}^XBpJP8D2o)0qwP z_1?108fdD-Y${i5xBG@jNyxuCa%6oqjNf~)zEFlY*(2GQ=)otw%_+Cn6?$&Ru665= zf&TX$xPHyoH=6nz^_}ZqHyoMWrz@of&W`kHdrxjoRv#kK(9n1<2O^C}s2LJ#}fH{PpWhB0;?owo6}H zEPpx+PYzp0ua64mcEmd7*cXl$c{)~dOD(Tz_))i{;n)d+?YLFdQt+sA>C#WlMiP!{ zgnv?XJ%m+Mr$XQJAv-&J!9t;AS%r1Jok-U6;0moO^GkR0^bYsTXc#S|II|eNZF%oZ znLL&i``)1`+LBWV8!1i6HZi0SNT(R-T9b$~<%=GS(vpetj$thpq=gsy-j0PE@C++F#+Y&ck?24zNV@&l-DNS+q2(&w)nSZiimEt z^`bUpL%vbhnNHFNQNyM4wqsiLW$vL>7>OPq{(w18Nx}wZg@p0wOAVPV&y6zmG(e~I zaG_Pt`CXTn?^(iF;?(*x8Ae^jDbNw#riODvJTW5X@10H8f-D+dJO3yd2%tW6*rYf{ zICUK=BRYimZ^W8f&)a)sjcu@M!E2fXxa64l`bUeDZxC3#R!u0=?fLC2!Ox4WyEHMR z_NavRP-Y2A!+Hp{!ozx7Ft=HPYLqgLGt7KDZ0{}XQMlr^LCd7pp4;bbR$puIVS;;W zv_EKRfe$~K!Nc0I*eI0|fghc$C8tQ&w=*ECTT_N_!8lQ;F=F%G%4@vG<+$fhpI)Bn z5eRzngcQWOY_0HBCA!{%i~Eo*WPfI&WafP=8BA(y?uP~Sf*GM zpLlm_mhNV#=!wQ8@fRUGyq3kEoaoQu2{FR{(!C0S2!?GOThB5(COQmJfP zdM=bbJ;s2u+cKuoJ3d+DXO@?n?4xCo%zY#6XF#wir65Lb5@M$N)BshWC0hx< z#FCYj)jF9*)7ixK&~=2SqR9*+lM*jc(JVF|mjd(Iih@z0=4&fr;T>mlKG*_OJ*1^7 z8wTHK`{|sKnAf}^6`AZ{O#FUlZI-{7gPlEewM^P`Zt%i5=^#R7CbX))EZe&YOXc}Y z^>A);47DYax(kCl%JQ$a1{ClZiyyDL$sNYd!O{L>Vu1yQPk%MNQi;QB8C}sjTo$v0 zJ4yGTqqgj4HKc)~sg`?6E%6DNncZZ+av#;g-Wdyu_;>fv5x0Vbo;%lCti6|pHh@v2 zkL!F$CT_!wHJbNun{1J%?Ojozku?8!#MGl8UKW~^`HZGs4r9*r^eOX+ZjFipsv3v-eqV2~-i{)cetIpiMvm4$)<*S`KrF{-=lSfn1 z(-j{-c~VfivTNVrOjF+v@cwZ1!u88zR8|@DTszx94pFnU&>+#(NxIIu2vVy8OH4a7fgcc*ZkrVxH_l1J zd6`taUa=*+TyD$KRjq#=nTv~e8@muh?z(gxBjt-@@m|{B=Q8l{Vr)HQf@h|~mVG3N zL~STP-Hk zG2cj$xygd5jw0(`e$w4-;3b(bvdwW6^~r27Sm~!j5NrIOOtM;FH!5!K#|)w!{<{wN zw*|!OrYoX@S+vrtS6Ka56Jugy-NvFVZF)D|rmI?l?9Z)s@> zO+bX+VAz}ACjHiznj7LJ+~x+a3X6!4vhd|FZ#N9u#Nt)-b9B(yn!DI`n~2s)$>|bK zc?E^qNmqA0Dx7@9aQD}Qr5``^;sdtV42tZcKUY?^7dx6p*YgKdz`Cs9=bt@-JnRur z2vNzG8ZOIO#yLIVw62d4CN(E11T)6ga|cx>!p**q%zNrK<>+LAE-7vpx@Yz6C7ZM< zkwEB9Bc!LF!Br#5#o*ZS9%IhQjoFGQPQR5tk8Xln7B(!53k|Q`k4kv`I-TFJND+JE z#*O0)3_QVK-+uXW4Zg}qOuV|WOx83vPuWY$GQC*nLm|1<;RTEdt6o(=cQ3h=8WCpi z2MOC(H43};KK>pjr8`?uA=YG{_;7dqjrH#le1$$M4)WKoS#@OV_p8O>aQNh8PA)Dk zSo8GF&CS0zH0Wq)g@Z=qLSMLW;RYJ5uA&lJXOZ91+Db=HPyY>+Y9PvcWuV?*-ao9! zuP1xUt>xsy`WP8rEz83q4eRDfwOnC0zi%(HwxxMwi;Pim^nLD8CjLX#U#oG)CzhKU zZnB72ZOGf(=SZ^9@{z9aoS*y35eX`qA&cK&Wyo8ybxoaze@o$6E-Wts176@9dE zybY(f{-WR0B_~cLZCF(rjc~u38npe)Xephb^;E~vK95G@ezO&sxuNAGkjSxR z6?;^L*;~)pEJ>-jZ(E`MbwaM@-X+IHH_(k1ssNx@d=}k!dVrO;*5W^;#>C8;AKtI- zMlm7xj9aM}1L+1JlEWtde1-O2aHK6tN8=>XrF@G*z#aOx$0cqMt%h%p$Hv4gfrQg= zbuIAEpQ|cTJ#h8e9}nI9ur4Pz73bZOCwg)^d-FPi!f6SfdJp%}hI1;%`>!22wq18p zQ^=(%JSkLEz%6^*dG_I1KEBRW>V{f{!TCm?2C}>D{4DhhraDQc&o5S1_Q&pK{W+Cg zs6ds`=T13zb(4UhyiP${mTc{7*B$}wh~z|cLU~rVTkn?Mxgl}t2PXoVHlSs0C@oVJ z*N^2aCh8Xt%v*}B%y#y92t@YuWj}dxy|k~~zUJyZGcJ&Hu4l(_79EwHk8HiR6AjO^ zq{|c#{I?#L_dU1(uieT(BT>R~_l=CuzwrOef8u__L&Nhs=ZcMq?zSD~x(H>jIr{R8 zJJ=(Efi?~f9gBkHnFqNO6s`m@{Gx9=lXG)T7>tDEocz3}nuBI(TqVx`j?kMcf!D5I zw`#2_-r2YJ&{MJif*}8n&GRqeeLs!suJZM{N?OXNeXNRDZwe`;R)BbuK6vyUBk0uL zUZaSpD87pqHKL=VpNE7*aN2hl0H1Y?thuXUU=TCy?i19A7)^8XYT3!1?d{fm1UJI= z`V=L0VDmcg6P)h?I+*Y`&7J>(y}%-|Z2;bMmsWT?GTg z#>z^~$tiEzod`G&p3U&c2G7(o=~*)CGa~~CPeP-Joua}psub?nEQOtZNP7Z zS5newYh$HzG7Z=NwqerMyOQ{SfIPf)GQ8emvE#pjdG*K$rg&mjRZtg1FNwKFsT9A- z(ZmlBD6i;fXV(_VZ|J&MFPddo?BG5Bm4Px8#Cn#CYxEs~nq5-7ys1}hsA*{EQ^y!p zn*#3Bs{2k<)1;VAK&ljNMG~RW<9iui$G94oSt5~|Ikl>}tFQYudlI{_+4|Saj5U)i zor)t9?oIa*=f}&d6x1;+RCh&Ug?(4=X=`Z}r04(gm#~NT%u}V^g+-{ACl&&xvJe7Fo6k9I7V#55Rq<-8z zK-5H`ZqyVAL2rjOCHon3&*6;sUD@jsNvdVl{oWhQDaJy1^=3m~jvi+#^O)@y-Fv7 zt^EwUp{lB@r>FOE3@pB#<5#w$auTvO-nNW8jTya41e^t$nOka8)RF5mTe6N;jZH4z zE;YgayeCc>Qov1oMvt?0>Roo)UC&LLAE~z8T%G3+ZH&HDy>Xe7^V-RiCl!4CY-}<$ zG&Os2WKgKzH@n~a6?)849j;Cu>ojclSt&~I0vAG{rsVs^W*DvE-nC$XnDjJ3qDEn% zn?Z$t(sj=G+yp7J?jK?*LRXI=F7IaIRLN}sUxko~@x{wTk6HE48FE}`qB~g!pCDz} zcIZUY?9)%1)pvX-ZPv>}-LEUqj%8b+Ao7P@XfHeM`HFxMD&Sc?;Z3`XlZVQpR~%hq z1$$5>Z%}K7WrCl{F`fjUUMXc2;+01J0CbAE=~VChxF<$bR5U9wQC(SC8RP^^$Dg}S zO6>nbh}pGbh3~b8ki;Ec*A*$EgLFEB>`Drii*HF%H=PKi?kQoj4mw6j&o{Xr*cVHD z#633kw8`B$^CJ!Fcke!0Sxv*y+f0`nq-+3hm*A^t{^7(xBA8k*uekj#d6Q*^d4i#^ zun_J_YDrOgW4NQKVQ+cKwHf!-=2HKKQN}w{Ln}@@b_ZEs^3VMm>;U>J^nfehd6W*) zT05sfA1QH%fysSxLg&`+n?q*+TT%6~6>6qYvrWiC(N(w3d-#`gzPl+bz~_7u|Hr^p zHyUW9e|YBq5={S&+5XoPAMGXVFxAr4oytf{Sdhl&_tJ=SHRTNMrh}OPlhNRVXC5aw%M<-Rv+Z+3m4UWLA9`5x!DB%XUt=pyte~UcBPu;^ah}{=dL9AoU+Go%!g|qfiG{&>&Z~Nk_{T z=7PGn*94e<=;D9Q?{XzGk3>lJ>+y{sjvGEeSQSV_OX$LZzICERyF^hGcbf6<6CF(r z4;)n2$8iCwuC&BpLh7aI@WXrsh@csEv!D{MR1WPx6<3x)T9u zdgjUnaDMTelTX%MgvaWdpHDU$CtVL|DjiQq3UK6i_{Fu(Cd0I_o$AQRdE$zxc4-^0 z76($N;wvbl)kG?-7%2TT?$qyP`7mK^<7nA3$=R>8ObcdJMt#{KRY`4;SZ!9nrHqvy zhK?0U1;N}!=@d`NZUe2ipQazq9)Dm%tX(Gew5O@Ex=nSEpN`v$#DXwbUncVb+8bXj zHpoG$4dS33*yNt9`u34XHaODTw{Lw}j-hUyYxG@vyeurT>ep+%>j^2`p&7~w)%f!j zZ`)l={qlPL0dSm?F+G5?lU?#Y~h+|EXSk?V)kG+F(Zp3!fKWXbo9 zsGieQRD7Ch6xT6kdwO-ZwxUdnPSo?~2apBSBA@rd1$DzBJ9Q|&{6%|!8o7Cl`=*+) zaXb*fh*z(M7Ttbnj~Gy{IukoiKb2Y>tda%;h3@%<%=AsJ!0b)Tge`H zMW~W7d-OIBuf~bFe89-b${HB6sVXaL+t}C;DKN<&RGRBs7BaSs_j~0wj%0go(}gfO z5$<9Wyq3L`<0dw2shvLy4lZHFO%O{J5LK*y1pk3>qp@N&bGzs#{=GpC%tbB5r~07# zjt$+5dqAR906g^1bZR~7+L(DLgsIci*p40n>nOXD%)bxT3*)AUkfZ%9?28470iGk|(0ds{!gKUMoBwU^Ll2FITLE~Qc#YEeZIFveop@ZTN_^(r+*~CI zbaS>&7H7@fi#0Zd-zQy>-xm2^Q*V=!pqe1ziqx0`@el6&Dk=iwj)!`nYpb#-8oRK# zXxIkcRFbqz$9zXyM+eJd?Wl91HS+jxgzZg!-4SD4u-l(gs9l>cm-Vnb|b_|$W*~`Vt&3zpd9mtS~;o;$>_F?uz;QaiN1<*Ck zP{m1Olm8PO{DTZ5IQRe*A8PY$JzLp_4@O};svVuC0XdnOXX9NO#K663@tG~_bYL^a zrmEtgw&Sla5T%6pRa{X!+ct&^^Q_%e|>I|50mn=h9oD>xmv-o~yWMte>R<p}mk~R1jkCafg`r2oBB^&u#7+%Nyq+Xrx&Q;#i+#r2stMUGEgDdt*M`O| zC%BU{kuo-;P`cDQQd1X?7r`eB>b<)HBtjjAg8yPPdG>H%Y~QGfmU=VoN0wh znxPqdeZ_KAQ&BXw{=okI^U{dv*W~YeU{Yq=wUNWGg$MqPqFOJ$Up3ucU;rze)!h!> zk1$_#YOCWoa&R=X8Q!QA5ytB)6;ysSkn=KuvLy|=fv%ez(8xt?! z>R{9gnSVddumAoU*0q+u*y_`RJ*3t&%s}j~0g@!*=;2gqgiLzilTgudCwHgh8WTxS z!MDO}f0!xXaCrQL#YPl_zUY9jOYq+?G8kFQ5hu0M#zhhH>{&AaD5z{Y28J}^3_%Io zR%okRA-ZDrXZpZK`S$HZsAv>eHM7Gpe1d|iF5?Z_%E}=J4jc%Nh)7vq_eLJR@(B%C z_!#XK3HJ}s+ryTuY}wr&#pM_8YQcK zF38QOnH@0^x_E7uPb`AhZ??d5!DnTK^^kP{Ron(Y?(kb1jf%Nw{ybT(_|c2d(8ziV z%{zDA7TI;Zg;tCR4?iOypaL!wL}L&ip8MwOHw1z>7(-_{I4Z}dR!HqruZ2NLW;gB1 z`SH>)*~64~VWWgCaqQUHINJwaiM{2aU^k5!Z#=Aud5T`GKRqll;1AEG#*dac_ zZ&|-WWQ7&#hSI}+&0Ngxy3sFRzSPnE_D8g(TVM;B?%Qo+hUW=J0>WF32{j7~&M+0* z9hH9pL3AVEWWHtNva|`&Jx7|l>4FZL3D?lk>6l@)KnA%hy>!r$eNT}WD;0ddbug?B zOUQUF+@-o9L(BCJe7ld;R}EWmZ%8&JHPr?}Raj2Q^7NPa_)uRW@vD!jBbtg8OD#%n z?b^D${QS!9MB}ss@5}}$V1$CJDNK+d%SY=6F` zD!(y7D@zw2$%)XLk*W}WQorX24b}Y@P{~T?B%8wZu@IbbM~g2_#(z6DrpSzEtLNYXbOIqdjOsI z!KbP6^XKEh_h3El`zQ2z0}%v|IFl#A^Rtf~#H=7n5@dSn7hq+H1TBFQ3pph^^G@{y z&HnxB@F4&Aj{0@Hh6k68ED?fD08e!xltXbOvpPNhVdX#7D;!e6~gTv}RMo}fkg#5Htk?|5sfax2u~ zJ~Z0e+7<>-{Xt09JBIukINGtrM5Y?JHy&1pPFA^JKTI%1935B-#d664_lM^_msUqG zwRR%5upsj{5c?cP$Hp>&qP%+b>U&+CmYrQTr#0N8`-4|LKrW$K&lOONzS|1J;6tZsNEZbV{QOJgxZp-Tn zX20rv2Bpp^P>azrG6FIn5d4)Ls$i?Do)M9eXW7^iInt0fT;;S{hFL!r8zW?mtsCnw zbQg~Y(S+p;tKrOQKbzL-^}`AM96$zwfh!5nIDsQWi#OIYwX?eOF-f2T!fNie1n$^X z0E>anlq5@|I*xV9(-z`-82u6F(&KP@!%aBtmYQ_h_tn+a<>`_1vuDpPwd?-C|pQ%)Cn_iZH?-+;X<#&p;*zH+GP|KtYCb3RpYv@yM` z>N6P-Fmr4OeJT9j_{m<>?xyaXje(+qHlDyw9bf&8^Hbk%n%+udQ?k8<84XFWe^Zt+}t)@@6BO?J%H!a($~*; z@!}8;KL8BH4B_yM7t>*wgZ!6Ql!@8g^hFAQkPZZ&71~4-d{!_Z!lR>k(a)bfQ~y^` zc$J}LgEHmuL#k!GE_%d|>FCiG(gCkOP0})iIu52Ch#?Y@PKw|2*X-(F?X-U*^!gv4 zh)6#Mu)CEa&ox6dh!dR_?;eAU^NZ1kvs{RM-|OqO5wD(uBO*FF1FFF2mQ>}~03> ztcr-PQyt1JiP~;pKYfXL`<6X~QTPTn{N>9xARmB^rNi_Bz=mZJI(&Y9{wzN~`CUIU zo{N5a9{-}2c=?p+28i)k^hI&h}01O$GwEW6-ggbY$ zr^~J@i}oeM`pR!wZUosn(2N7O$EhBV%L?gMIzkaBy#BiuNET=sVb=*Y0J?(TCjUCf zks8Kd2DycsTcN5OuvBTx$!UMjL1kuF;-++N_2}}(>noNF+sg`>2Bkf-BL+N&b))kA zsQTUdei}@P*Z|=*zh0b}{=JAdN4%f#Z1~-LlYh{3e& zf&EwB9eDoI1mrs%pU%YKr-z_Enx7uw5W4f_H?)Soe>oP54G#}bh6pn>A+LmlA-p>P zYdGiEFJI1ZamkB`i9uQfY82xQNCohGHn0*fUbSF-KhjwLXPqIroo=D6O$%1$hqe0eJKlS&MBs{7rjUXg*dHol zqsP%2z%|>mbOk|JRmdAdp89$SBNzIHqM{lsvVU1rV(U}?SXA66kcAs%x<_7-ng>iS zlRz+lna1DnFQOx^XO<-58xBvMgcGcS-*nBequt!x4D#+gwrGgs4gT6P{Gob8jI?OR z?5J#SPrhIa5p7wyhoqkUaKgg_FDIXaa1v0t?2*7^T<0RL}K zl-<=Ah&S<`*vP@F?u8J#E96~EMW8yT0IM|oR^n}O;(N-v0fajzZiY< zhLTcPpTq38WoShdZV$w85c+DHTRN653>6#TOD%8ZgN2-iuK}q>WBSIM!fmAt{ z8ZkE`W4y@%iia>+(!YKCmele0#HT_5!tD$6n;yBEUj83LPZ2|0WHqb8>T+GX&0_&8(MFYAN2gG8KVW3Y{uc7_ojH{&Pd} zh-89)vA&H1sI-w@f7YWs?;0zd(-#_O{8>HpoZ}jzoihij_XOTLy2MuY_N{Dlb2CV1 z*am@=>5&x>i#-b9Uvnq#4AdrSBl%k&?LP+Px3uryZeemVi7{K!GhOwYe8+rO@ESoT8yg}O7zuNSi)_qS z(inu;{MHsmVN(l4jdNgQgfR3)yi?UlC_w8XV;b;1E-w51`-|q5mf&>U01l5$e*OAH zN^=K0XWl{}H~pT=UxySPY-wqJ zoBQsa8Wf%l3anmntHh+2#FRSd8N~iF#vG6Xa#Fk2-93Y+*5FGv69{YTWvE9EK_>ITW3 zIJ&-tr#o_FXeDoUVdE*ay?Hi^bIa$uHu*9kV4w#=JiZ+4evKT;*N|(SfLAH_gnFAUHTXwLcYV;xL>(;CW6!{0um3zwY2} zr0MQnv>vOAMpk23cCUvrW$f6sR}raW0DDbFOm-fgtnBQYU0q!;kTjs!!-VOk)a;Y$A3dqQ+3AlIRfU_JEt@qLGV{tMtxAWZ=P2IcyWrO8%0kdYHbbH{Q_$_#pn?87(_ zA12Dg8+z~U&R#lHz$KU%s&8Vi+(S02P`5XPp#b)^s){sU&D!~u!8Ch&Yhxclc=MWR zz*Zq-XeluHkG80uHuar<46!fHNhrK)LU9?W0$k2N6D%GihJ{TA*H;rBgTdHUJ&?F_ z(9zE0ej|*a<0nsMNc*iL%mD(ba3qbZ;oL*eaAz-Gyx3&_u@SHt*&YO2H7F1mJsbo% zuTQkw$C{*AFue{Qfy5n6CXk%Q0g-~ugR?F`_n^~J?z=xSz}Q*utk_ODdC6iofo`=1 zA+=Dqqf9i*fg1o3K2-M(Hs^Xe@ z*l{*y>EPgCO;~i`RUxpTt*4hhR2c**4OGrI2tR@L0gvfi^v%>O#k*uOva`cE!7PFT zf*MS$9W%dO@fn;EumZooldWkk2d@wAzV_!X)c;L( zvFZL;BMuKK>8YIJen|D8P`fyicUg&6P`74{^<=75RBAR#zLYN<(X+kt3I!*p2>HKbcpxz7xx~Wphuyu21dA|$O7PWz>dTYsg+(1G?I0JRfaPIG$uxJ;VDuv z%`>Y8vsvz!&zWOP)H%o?5Y7qit1=X~JM=B%Q-Iy(=;g^`3t*FF2#b^f)NWpK`faa` z6J`igLjIec$}vJnUZ(|093RRyE0)QE$u;GekyWDdm($IsErRvM4$&Ee{kR|UI!6Du z6l?QlnUph}4?5nfuU%%H&)o=_gy~C-P*51k2W~X|H|%o_|;KdM&s4X zEVI6YrKOAh9WDVTmPG%~@{O(BPpd{1e)v-3t)e0k$XQ(V_m?hq92kRs1UPsV8ynm? z1(|+}PldZ5UWJXrstyhUDrsR*A)GMc=Hcloc4R#q^e{J900rm@(}=Jv1FnRdTb{?P zNt_A~Y#(Nl^nBUh-*4(MFMsW4adN!t5q;N2oJ?A7ZfGaobk{qFK5-qq2~A+4$DY>*X8sbM2aSr_-&~Pa*iHExjZ8^EQ?-cj! z531}f^N9k&t*)jv*J^NGK>@9zQZ?#)L0DMc$43I5uKG^i%c!WJ9&+m2w-5Mqb5Tvr z%~x$~&Zep2wlg}ueAx{%<(x4g9O}QRsi`MUoH)&NSg@+|G&_66k;ll3CaOJEJ9FI6 zzPRVYi9;_!LYlj}_FwVylcEoPOmj-)-ee1xfq?I(C=`g^-a3Ec z9Cbf2R?h=QsWsyR<)1g6=d&54lfuvsAgRvCDRD8O6Px4OFBr!QPM z5(uP(fAyrG$%jD*s{Q==^Bp*5uqvWfmnJor;vlIukz3a1P;HUVuV)K(WSpcIc6Oiw z^b$Z>^1F90ubiIa(G_54r#c>Qk^kwK?>u_rCigKET=uJ-$gba?T|05|Ey)dnps#p85$b$qKv^Kq9V`f@7yd=Uv)J4;Q8wQ{T-o9 zVzOpt9H1R!>SwD$PQAcjj}l^BmXZz z4UCs0sTp?mg~k+j2IzYismY;xueZ1TQFbDyU<0*XPZ15Su^M*U|0r=X6KaZ#irV!O zi{1ILsp)s%fZ4{xNZ1v14YRT3pPH5io+?0WxP-?HR2X)_R9jn}=YUp#Avx$O9#CTd z$J}QXrB6yq+Wqj8*NBvqR6$V@HYVnI4|!~Le#EHUHyZFpCtL5rY%h4d6LLx1Vb4Sz z`%y3B;x?7W&cYZKBqs-ddwcM)Y(a7H;?J66(B#ZCetH&32sA*|EsvH`G*aWNwbJJayV7rI4mDOnpiP(e$mM1|$ zyl2k5Nld)mFP9VmTQ~S2M*t7$49v$!K-YB|>T5H-ob| z8FC#S2HLr*>N-&8d!z zJNr>H7#P>!*PR!#1mpXnB=uxw5w&{69iJnkWDO From 9a6828e4ac70cee6c263b2be1035a920e6ad7d01 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Mon, 24 Feb 2020 10:42:40 -0600 Subject: [PATCH 15/15] chore: address pr comments --- src/chart_types/xy_chart/utils/series.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index 7746586798..582c130f11 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -388,13 +388,14 @@ export function getSeriesNameFromOptions( options: SeriesNameConfigOptions, { yAccessor, splitAccessors }: XYChartSeriesIdentifier, delimiter: string, -) { +): string | null { if (!options.names) { return null; } return ( options.names + .slice() .sort(({ sortIndex: a = Infinity }, { sortIndex: b = Infinity }) => a - b) .map(({ accessor, value, name }) => { const accessorValue = splitAccessors.get(accessor) ?? null;