diff --git a/e2e/screenshots/all.test.ts-snapshots/baselines/test-cases/domain-edges-chrome-linux.png b/e2e/screenshots/all.test.ts-snapshots/baselines/test-cases/domain-edges-chrome-linux.png new file mode 100644 index 0000000000..0be294b8ee Binary files /dev/null and b/e2e/screenshots/all.test.ts-snapshots/baselines/test-cases/domain-edges-chrome-linux.png differ diff --git a/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/custom-1ms-chrome-linux.png b/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/custom-1ms-chrome-linux.png new file mode 100644 index 0000000000..82a2b4bbca Binary files /dev/null and b/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/custom-1ms-chrome-linux.png differ diff --git a/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/custom-domain-chrome-linux.png b/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/custom-domain-chrome-linux.png new file mode 100644 index 0000000000..a45ef47deb Binary files /dev/null and b/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/custom-domain-chrome-linux.png differ diff --git a/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/custom-empty-chrome-linux.png b/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/custom-empty-chrome-linux.png new file mode 100644 index 0000000000..4a1194e809 Binary files /dev/null and b/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/custom-empty-chrome-linux.png differ diff --git a/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/data-domain-chrome-linux.png b/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/data-domain-chrome-linux.png new file mode 100644 index 0000000000..e68fa1216b Binary files /dev/null and b/e2e/screenshots/test_cases_stories.test.ts-snapshots/test-cases-stories/legend-last-value-should-be-aligned-across-areas-and-bars/data-domain-chrome-linux.png differ diff --git a/e2e/tests/test_cases_stories.test.ts b/e2e/tests/test_cases_stories.test.ts index f4228ae200..46b5ac49bf 100644 --- a/e2e/tests/test_cases_stories.test.ts +++ b/e2e/tests/test_cases_stories.test.ts @@ -80,4 +80,31 @@ test.describe('Test cases stories', () => { ); }); }); + + test.describe('Legend last value should be aligned across areas and bars', () => { + test('data domain', async ({ page }) => { + await common.expectChartAtUrlToMatchScreenshot(page)( + 'http://localhost:9001/?path=/story/test-cases--domain-edges&globals=toggles.showHeader:true;toggles.showChartTitle:false;toggles.showChartDescription:false;toggles.showChartBoundary:false;theme:light&knob-custom domain=&knob-start time=0&knob-end time=19&knob-subtract 1ms=', + { screenshotSelector: '#story-root' }, + ); + }); + test('custom domain', async ({ page }) => { + await common.expectChartAtUrlToMatchScreenshot(page)( + 'http://localhost:9001/?path=/story/test-cases--domain-edges&globals=toggles.showHeader:true;toggles.showChartTitle:false;toggles.showChartDescription:false;toggles.showChartBoundary:false;theme:light&knob-custom domain=true&knob-start time=0&knob-end time=19&knob-subtract 1ms=', + { screenshotSelector: '#story-root' }, + ); + }); + test('custom -1ms', async ({ page }) => { + await common.expectChartAtUrlToMatchScreenshot(page)( + 'http://localhost:9001/?path=/story/test-cases--domain-edges&globals=toggles.showHeader:true;toggles.showChartTitle:false;toggles.showChartDescription:false;toggles.showChartBoundary:false;theme:light&knob-custom domain=true&knob-start time=0&knob-end time=19&knob-subtract 1ms=true', + { screenshotSelector: '#story-root' }, + ); + }); + test('custom empty', async ({ page }) => { + await common.expectChartAtUrlToMatchScreenshot(page)( + 'http://localhost:9001/?path=/story/test-cases--domain-edges&globals=toggles.showHeader:true;toggles.showChartTitle:false;toggles.showChartDescription:false;toggles.showChartBoundary:false;theme:light&knob-custom domain=true&knob-end time=10.2&knob-start time=0&knob-subtract 1ms=true', + { screenshotSelector: '#story-root' }, + ); + }); + }); }); diff --git a/packages/charts/src/chart_types/xy_chart/domains/types.ts b/packages/charts/src/chart_types/xy_chart/domains/types.ts index 3726b2f0e2..709f7b7fba 100644 --- a/packages/charts/src/chart_types/xy_chart/domains/types.ts +++ b/packages/charts/src/chart_types/xy_chart/domains/types.ts @@ -22,6 +22,7 @@ export type XDomain = Pick & { /** the configured timezone in the specs or the fallback to the browser local timezone */ timeZone: string; domain: OrdinalDomain | ContinuousDomain; + dataDomain: OrdinalDomain | ContinuousDomain; desiredTickCount: number; }; diff --git a/packages/charts/src/chart_types/xy_chart/domains/x_domain.ts b/packages/charts/src/chart_types/xy_chart/domains/x_domain.ts index 28f40dbc83..b7fdd51de6 100644 --- a/packages/charts/src/chart_types/xy_chart/domains/x_domain.ts +++ b/packages/charts/src/chart_types/xy_chart/domains/x_domain.ts @@ -28,7 +28,8 @@ export function mergeXDomain( locale: string, fallbackScale?: XScaleType, ): XDomain { - let seriesXComputedDomains; + let domain; + let dataDomain; let minInterval = 0; if (type === ScaleType.Ordinal || fallbackScale === ScaleType.Ordinal) { @@ -36,10 +37,11 @@ export function mergeXDomain( Logger.warn(`Each X value in a ${type} x scale needs be be a number. Using ordinal x scale as fallback.`); } - seriesXComputedDomains = computeOrdinalDataDomain([...xValues], false, true, locale); + dataDomain = computeOrdinalDataDomain([...xValues], false, true, locale); + domain = dataDomain; if (customDomain) { if (Array.isArray(customDomain)) { - seriesXComputedDomains = [...customDomain]; + domain = [...customDomain]; } else { if (fallbackScale === ScaleType.Ordinal) { Logger.warn(`xDomain ignored for fallback ordinal scale. Options to resolve: @@ -54,7 +56,8 @@ export function mergeXDomain( } } else { const domainOptions = { min: NaN, max: NaN, fit: true }; - seriesXComputedDomains = computeContinuousDataDomain([...xValues] as number[], type, domainOptions); + dataDomain = computeContinuousDataDomain([...xValues] as number[], type, domainOptions); + domain = dataDomain; let customMinInterval: undefined | number; if (customDomain) { @@ -62,13 +65,13 @@ export function mergeXDomain( Logger.warn('xDomain for continuous scale should be a DomainRange object, not an array'); } else { customMinInterval = customDomain.minInterval; - const [computedDomainMin, computedDomainMax] = seriesXComputedDomains; + const [computedDomainMin, computedDomainMax] = domain; if (Number.isFinite(customDomain.min) && Number.isFinite(customDomain.max)) { if (customDomain.min > customDomain.max) { Logger.warn('Custom xDomain is invalid: min is greater than max. Custom domain is ignored.'); } else { - seriesXComputedDomains = [customDomain.min, customDomain.max]; + domain = [customDomain.min, customDomain.max]; } } else if (Number.isFinite(customDomain.min)) { if (customDomain.min > computedDomainMax) { @@ -76,7 +79,7 @@ export function mergeXDomain( 'Custom xDomain is invalid: custom min is greater than computed max. Custom domain is ignored.', ); } else { - seriesXComputedDomains = [customDomain.min, computedDomainMax]; + domain = [customDomain.min, computedDomainMax]; } } else if (Number.isFinite(customDomain.max)) { if (computedDomainMin > customDomain.max) { @@ -84,7 +87,7 @@ export function mergeXDomain( 'Custom xDomain is invalid: computed min is greater than custom max. Custom domain is ignored.', ); } else { - seriesXComputedDomains = [computedDomainMin, customDomain.max]; + domain = [computedDomainMin, customDomain.max]; } } } @@ -97,7 +100,8 @@ export function mergeXDomain( type: fallbackScale ?? type, nice, isBandScale, - domain: seriesXComputedDomains, + domain, + dataDomain, minInterval, timeZone: getValidatedTimeZone(timeZone), logBase: customDomain && 'logBase' in customDomain ? customDomain.logBase : 10, // fixme preexisting TS workaround diff --git a/packages/charts/src/chart_types/xy_chart/legend/legend.test.ts b/packages/charts/src/chart_types/xy_chart/legend/legend.test.ts index fc64d40ffb..6061c1d45b 100644 --- a/packages/charts/src/chart_types/xy_chart/legend/legend.test.ts +++ b/packages/charts/src/chart_types/xy_chart/legend/legend.test.ts @@ -8,7 +8,6 @@ import { Store } from 'redux'; -import { getLegendExtra } from './legend'; import { ChartType } from '../..'; import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs/specs'; import { MockStore } from '../../../mocks/store/store'; @@ -24,9 +23,9 @@ import { getSeriesName } from '../utils/series'; import { AxisSpec, BasicSeriesSpec, SeriesType } from '../utils/specs'; const nullDisplayValue = { - formatted: null, raw: null, - legendSizingLabel: null, + formatted: '', + legendSizingLabel: '', }; const spec1: BasicSeriesSpec = { @@ -402,37 +401,4 @@ describe('Legends', () => { name = getSeriesName(seriesIdentifier1, false, false, specWithSplit); expect(name).toBe('Spec 1 title'); }); - it('should return correct legendSizingLabel with linear scale and showExtraLegend set to true', () => { - const formatter = (d: string | number) => `${Number(d).toFixed(2)} dogs`; - const lastValues = { y0: null, y1: 14 }; - const showExtraLegend = true; - const xScaleIsLinear = ScaleType.Linear; - - expect(getLegendExtra(showExtraLegend, xScaleIsLinear, formatter, 'y1', lastValues)).toMatchObject({ - raw: 14, - formatted: '14.00 dogs', - legendSizingLabel: '14.00 dogs', - }); - }); - it('should return formatted to null with ordinal scale and showExtraLegend set to true', () => { - const formatter = (d: string | number) => `${Number(d).toFixed(2)} dogs`; - const lastValues = { y0: null, y1: 14 }; - - expect(getLegendExtra(true, ScaleType.Ordinal, formatter, 'y1', lastValues)).toMatchObject({ - raw: 14, - formatted: null, - legendSizingLabel: '14.00 dogs', - }); - }); - it('should return legendSizingLabel null with showLegendExtra set to false', () => { - const formatter = (d: string | number) => `${Number(d).toFixed(2)} dogs`; - const lastValues = { y0: null, y1: 14 }; - const showLegendExtra = false; - - expect(getLegendExtra(showLegendExtra, ScaleType.Ordinal, formatter, 'y1', lastValues)).toMatchObject({ - raw: null, - formatted: null, - legendSizingLabel: null, - }); - }); }); diff --git a/packages/charts/src/chart_types/xy_chart/legend/legend.ts b/packages/charts/src/chart_types/xy_chart/legend/legend.ts index e2bcee48ce..f94c39984f 100644 --- a/packages/charts/src/chart_types/xy_chart/legend/legend.ts +++ b/packages/charts/src/chart_types/xy_chart/legend/legend.ts @@ -9,15 +9,14 @@ import { Color } from '../../../common/colors'; import { LegendItem } from '../../../common/legend'; import { SeriesKey, SeriesIdentifier } from '../../../common/series_id'; -import { ScaleType } from '../../../scales/constants'; -import { SettingsSpec, TickFormatterOptions } from '../../../specs'; +import { SettingsSpec } from '../../../specs'; import { isDefined, mergePartial } from '../../../utils/common'; import { BandedAccessorType } from '../../../utils/geometry'; import { getLegendCompareFn, SeriesCompareFn } from '../../../utils/series_sort'; import { PointStyle, Theme } from '../../../utils/themes/theme'; -import { getXScaleTypeFromSpec } from '../scales/get_api_scales'; +import { XDomain } from '../domains/types'; +import { LegendValue, getLegendValue } from '../state/utils/get_last_value'; import { getAxesSpecForSpecId, getSpecsById } from '../state/utils/spec'; -import { LastValues } from '../state/utils/types'; import { Y0_ACCESSOR_POSTFIX, Y1_ACCESSOR_POSTFIX } from '../tooltip/tooltip'; import { defaultTickFormatter } from '../utils/axis_utils'; import { defaultXYLegendSeriesSort } from '../utils/default_series_sort_fn'; @@ -34,6 +33,7 @@ import { AxisSpec, BasicSeriesSpec, Postfixes, + StackMode, isAreaSeriesSpec, isBarSeriesSpec, isBubbleSeriesSpec, @@ -61,27 +61,6 @@ function getBandedLegendItemLabel(name: string, yAccessor: BandedAccessorType, p : `${name}${postfixes.y0AccessorFormat}`; } -/** @internal */ -export function getLegendExtra( - showLegendExtra: boolean, - xScaleType: ScaleType, - formatter: (value: any, options?: TickFormatterOptions | undefined) => string, - key: keyof LastValues, - lastValue?: LastValues, -): LegendItem['defaultExtra'] { - if (showLegendExtra) { - const rawValue = (lastValue && lastValue[key]) ?? null; - const formattedValue = rawValue !== null ? formatter(rawValue) : null; - - return { - raw: rawValue !== null ? rawValue : null, - formatted: xScaleType === ScaleType.Ordinal ? null : formattedValue, - legendSizingLabel: formattedValue, - }; - } - return { raw: null, formatted: null, legendSizingLabel: null }; -} - /** @internal */ function getPointStyle(spec: BasicSeriesSpec, theme: Theme): PointStyle | undefined { if (isBubbleSeriesSpec(spec)) { @@ -95,8 +74,8 @@ function getPointStyle(spec: BasicSeriesSpec, theme: Theme): PointStyle | undefi /** @internal */ export function computeLegend( + xDomain: XDomain, dataSeries: DataSeries[], - lastValues: Map, seriesColors: Map, specs: BasicSeriesSpec[], axesSpecs: AxisSpec[], @@ -108,10 +87,11 @@ export function computeLegend( const legendItems: LegendItem[] = []; const defaultColor = theme.colors.defaultVizColor; + const legendValueMode = LegendValue.LastValue; + dataSeries.forEach((series) => { const { specId, yAccessor } = series; const banded = isBandedSpec(series.spec); - const key = getSeriesKey(series, series.groupId); const spec = getSpecsById(specs, specId); const dataSeriesKey = getSeriesKey( { @@ -129,33 +109,47 @@ export function computeLegend( if (name === '' || !spec) return; const postFixes = getPostfix(spec); - const labelY1 = banded ? getBandedLegendItemLabel(name, BandedAccessorType.Y1, postFixes) : name; - // Use this to get axis spec w/ tick formatter const { yAxis } = getAxesSpecForSpecId(axesSpecs, spec.groupId, settingsSpec.rotation); const formatter = spec.tickFormat ?? yAxis?.tickFormat ?? defaultTickFormatter; const { hideInLegend } = spec; - const lastValue = lastValues.get(key); const seriesIdentifier = getSeriesIdentifierFromDataSeries(series); - const xScaleType = getXScaleTypeFromSpec(spec.xScaleType); const pointStyle = getPointStyle(spec, theme); + const itemValue = getLegendValue(series, xDomain, legendValueMode, (d) => { + return series.stackMode === StackMode.Percentage + ? d.y1 === null || d.y0 === null + ? null + : d.y1 - d.y0 + : d.initialY1; + }); + const formattedItemValue = itemValue !== null ? formatter(itemValue) : ''; + legendItems.push({ color, - label: labelY1, + label: banded ? getBandedLegendItemLabel(name, BandedAccessorType.Y1, postFixes) : name, seriesIdentifiers: [seriesIdentifier], childId: BandedAccessorType.Y1, isSeriesHidden, isItemHidden: hideInLegend, isToggleable: true, - defaultExtra: getLegendExtra(settingsSpec.showLegendExtra, xScaleType, formatter, 'y1', lastValue), + defaultExtra: { + raw: itemValue, + formatted: formattedItemValue, + legendSizingLabel: formattedItemValue, + }, path: [{ index: 0, value: seriesIdentifier.key }], keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()], pointStyle, }); if (banded) { + const bandedItemValue = getLegendValue(series, xDomain, legendValueMode, (d) => { + return series.stackMode === StackMode.Percentage ? d.y0 : d.initialY0; + }); + const bandedFormattedItemValue = bandedItemValue !== null ? formatter(bandedItemValue) : ''; + const labelY0 = getBandedLegendItemLabel(name, BandedAccessorType.Y0, postFixes); legendItems.push({ color, @@ -165,7 +159,11 @@ export function computeLegend( isSeriesHidden, isItemHidden: hideInLegend, isToggleable: true, - defaultExtra: getLegendExtra(settingsSpec.showLegendExtra, xScaleType, formatter, 'y0', lastValue), + defaultExtra: { + raw: bandedItemValue, + formatted: bandedFormattedItemValue, + legendSizingLabel: bandedFormattedItemValue, + }, path: [{ index: 0, value: seriesIdentifier.key }], keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()], pointStyle, diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/compute_legend.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/compute_legend.ts index 5515df1aa9..0f124750be 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/compute_legend.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/compute_legend.ts @@ -17,7 +17,6 @@ import { getDeselectedSeriesSelector } from '../../../../state/selectors/get_des import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_spec'; import { computeLegend } from '../../legend/legend'; import { DataSeries } from '../../utils/series'; -import { getLastValues } from '../utils/get_last_value'; /** @internal */ export const computeLegendSelector = createCustomCachedSelector( @@ -42,8 +41,8 @@ export const computeLegendSelector = createCustomCachedSelector( siDataSeriesMap: Record, ): LegendItem[] => { return computeLegend( + xDomain, formattedDataSeries, - getLastValues(formattedDataSeries, xDomain), seriesColors, seriesSpecs, axesSpecs, diff --git a/packages/charts/src/chart_types/xy_chart/state/utils/get_last_value.ts b/packages/charts/src/chart_types/xy_chart/state/utils/get_last_value.ts index 11f35dbe51..b8e7db1b62 100644 --- a/packages/charts/src/chart_types/xy_chart/state/utils/get_last_value.ts +++ b/packages/charts/src/chart_types/xy_chart/state/utils/get_last_value.ts @@ -6,58 +6,52 @@ * Side Public License, v 1. */ -import { LastValues } from './types'; -import { SeriesKey } from '../../../../common/series_id'; +import { $Values } from 'utility-types'; + import { ScaleType } from '../../../../scales/constants'; import { XDomain } from '../../domains/types'; import { isDatumFilled } from '../../rendering/utils'; -import { DataSeries, getSeriesKey, XYChartSeriesIdentifier } from '../../utils/series'; -import { StackMode } from '../../utils/specs'; +import { DataSeries, DataSeriesDatum } from '../../utils/series'; + +/** @internal */ +export const LegendValue = Object.freeze({ + None: 'none' as const, + LastValue: 'lastValue' as const, + LastNonNullValue: 'lastNonNullValue' as const, +}); +/** @internal */ +export type LegendValue = $Values; /** + * This method return a value from a DataSeries that correspond to the type of value requested. + * It in general compute the last, min, max, avg, sum of the value in a series. + * NOTE: not every type can work correctly with the data provided, for example a sum will not work when using the percentage chart * @internal - * @param dataSeries - * @param xDomain */ -export function getLastValues(dataSeries: DataSeries[], xDomain: XDomain): Map { +export function getLegendValue( + series: DataSeries, + xDomain: XDomain, + type: LegendValue, + valueAccessor: (d: DataSeriesDatum) => number | null, +): number | null { // See https://github.com/elastic/elastic-charts/issues/2050 if (xDomain.type === ScaleType.Ordinal) { - return new Map(); + return null; } - const lastValues = new Map(); - - // we need to get the latest - dataSeries.forEach((series) => { - if (series.data.length === 0) { - return; - } - - const last = series.data.at(-1); - if (!last) { - return; - } - if (isDatumFilled(last)) { - return; - } - - if (last.x !== xDomain.domain.at(-1)) { - // we have a dataset that is not filled with all x values - // and the last value of the series is not the last value for every series - // let's skip it - return; - } - const { y0, y1, initialY0, initialY1 } = last; - const seriesKey = getSeriesKey(series as XYChartSeriesIdentifier, series.groupId); - - if (series.stackMode === StackMode.Percentage) { - const y1InPercentage = y1 === null || y0 === null ? null : y1 - y0; - lastValues.set(seriesKey, { y0, y1: y1InPercentage }); - return; - } - if (initialY0 !== null || initialY1 !== null) { - lastValues.set(seriesKey, { y0: initialY0, y1: initialY1 }); + switch (type) { + case LegendValue.LastNonNullValue: { + const last = series.data.findLast((d) => d.x === xDomain.dataDomain[1] && valueAccessor(d) !== null); + return last ? valueAccessor(last) : null; } - }); - return lastValues; + case LegendValue.LastValue: + const last = series.data.findLast((d) => d.x === xDomain.dataDomain[1]); + if (last && !isDatumFilled(last)) { + return valueAccessor(last); + } + return null; + default: + case LegendValue.None: + return null; + } } diff --git a/packages/charts/src/chart_types/xy_chart/state/utils/utils.test.ts b/packages/charts/src/chart_types/xy_chart/state/utils/utils.test.ts index c1e95e0195..0fa3efacd3 100644 --- a/packages/charts/src/chart_types/xy_chart/state/utils/utils.test.ts +++ b/packages/charts/src/chart_types/xy_chart/state/utils/utils.test.ts @@ -73,6 +73,7 @@ describe('Chart State utils', () => { expect(domains.xDomain).toEqual( MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0, 3], + dataDomain: [0, 3], isBandScale: false, minInterval: 1, logBase: 10, @@ -125,6 +126,7 @@ describe('Chart State utils', () => { expect(domains.xDomain).toEqual( MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0, 3], + dataDomain: [0, 3], isBandScale: false, minInterval: 1, logBase: 10, diff --git a/packages/charts/src/mocks/xy/domains.ts b/packages/charts/src/mocks/xy/domains.ts index 2d7ac6b59d..50191ed98f 100644 --- a/packages/charts/src/mocks/xy/domains.ts +++ b/packages/charts/src/mocks/xy/domains.ts @@ -27,6 +27,7 @@ export class MockXDomain { minInterval: 0, timeZone: 'local', domain: [0, 1], + dataDomain: [0, 1], }; static default(partial?: RecursivePartial) { diff --git a/storybook/stories/test_cases/21_domain_edges.story.tsx b/storybook/stories/test_cases/21_domain_edges.story.tsx new file mode 100644 index 0000000000..0904fa0969 --- /dev/null +++ b/storybook/stories/test_cases/21_domain_edges.story.tsx @@ -0,0 +1,222 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { boolean, number } from '@storybook/addon-knobs'; +import React from 'react'; + +import { + LineSeries, + Axis, + BarSeries, + Chart, + ScaleType, + Settings, + Position, + HistogramBarSeries, + niceTimeFormatter, +} from '@elastic/charts'; +import { getRandomNumberGenerator } from '@elastic/charts/src/mocks/utils'; + +import { ChartsStory } from '../../types'; +import { useBaseTheme } from '../../use_base_theme'; + +const rng = getRandomNumberGenerator('chart'); +const start = Date.parse('2023-07-01T00:00:00.000Z').valueOf(); +const interval = 1000 * 60 * 5; +const data = Array.from({ length: 20 }, (d, i) => { + return [start + interval * i, Math.floor(rng(2, 10))]; +}); + +export const Example: ChartsStory = (_, { title, description }) => { + const customDomain = boolean('custom domain', false); + const startTimeSlider = number('start time', 0, { min: 0, max: 20, range: true, step: 0.2 }); + const rangeSlider = number('end time', 19, { min: 1, max: 25, range: true, step: 0.2 }); + const subtract = boolean('subtract 1ms', false); + + const tickFormat = (d: number) => new Date(d).toISOString(); + const xDomain = customDomain + ? { min: start + interval * startTimeSlider, max: start + interval * rangeSlider - (subtract ? 1 : 0) } + : undefined; + const domain: [number, number] = [xDomain?.min ?? data[0][0] ?? 0, xDomain?.max ?? data.at(-1)?.[0] ?? 0]; + const limitedData = data + .filter((d) => d[0] <= (xDomain?.max ?? Infinity)) + .map((d, i) => [d[0], i === 10 ? null : d[1]]); + + return ( + <> +

+ {xDomain ? 'configured' : 'data'} domain: {new Date(domain[0]).toISOString()} to{' '} + {new Date(domain[1]).toISOString()} +

+ + + + + + + + + + + + + + + niceTimeFormatter(domain)(d, { timeZone: 'UTC' })} + /> + + + + + ); +}; + +Example.parameters = { + resize: { + height: '100%', + maxHeight: '100%', + overflow: 'hidden', + resize: 'none', + }, +}; diff --git a/storybook/stories/test_cases/test_cases.stories.tsx b/storybook/stories/test_cases/test_cases.stories.tsx index 0dff7160e5..ba47ce1729 100644 --- a/storybook/stories/test_cases/test_cases.stories.tsx +++ b/storybook/stories/test_cases/test_cases.stories.tsx @@ -21,3 +21,4 @@ export { Example as testPointsOutsideOfDomain } from './8_test_points_outside_of export { Example as duplicateLabelsInPartitionLegend } from './9_duplicate_labels_in_partition_legend.story'; export { Example as highlighterZIndex } from './10_highlighter_z_index.story'; export { Example as resizeDebounce } from './11_resize_debounce.story'; +export { Example as domainEdges } from './21_domain_edges.story';