diff --git a/e2e/tests/area_stories.test.ts b/e2e/tests/area_stories.test.ts index b61f074dbf..b03bf99bae 100644 --- a/e2e/tests/area_stories.test.ts +++ b/e2e/tests/area_stories.test.ts @@ -86,7 +86,7 @@ test.describe('Area series stories', () => { test.describe('Area with isolated data points', () => { test('render correctly fit function', async ({ page }) => { await common.expectChartAtUrlToMatchScreenshot(page)( - 'http://localhost:9001/?path=/story/line-chart--isolated-data-points&knob-enable fit function=&knob-switch to area=true', + 'http://localhost:9001/?path=/story/line-chart--isolated-data-points&knob-enable fit function=&knob-series type=area', ); }); }); diff --git a/packages/charts/src/chart_types/xy_chart/rendering/line_area_style.ts b/packages/charts/src/chart_types/xy_chart/rendering/line_area_style.ts new file mode 100644 index 0000000000..4005d4728a --- /dev/null +++ b/packages/charts/src/chart_types/xy_chart/rendering/line_area_style.ts @@ -0,0 +1,64 @@ +/* + * 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 { RecursivePartial, mergePartial } from '../../../utils/common'; +import { AreaSeriesStyle, LineSeriesStyle } from '../../../utils/themes/theme'; + +/** @internal */ +export function getLineSeriesStyles( + baseStyle: LineSeriesStyle, + seriesStyle?: RecursivePartial, +): LineSeriesStyle { + if (!seriesStyle) return baseStyle; + + const isolatedPointStyleOverrides = mergePartial( + baseStyle.isolatedPoint, + seriesStyle.isolatedPoint, + undefined, + seriesStyle.point ? [seriesStyle.point] : [], + ); + + return mergePartial( + baseStyle, + { + isolatedPoint: { + ...isolatedPointStyleOverrides, + visible: seriesStyle?.isolatedPoint?.visible ?? baseStyle.isolatedPoint.visible, + }, + }, + undefined, + [seriesStyle], + ); +} + +/** @internal */ +export function getAreaSeriesStyles( + baseStyle: AreaSeriesStyle, + seriesStyle?: RecursivePartial, +): AreaSeriesStyle { + if (!seriesStyle) return baseStyle; + + const isolatedPointStyleOverrides = mergePartial( + baseStyle.isolatedPoint, + seriesStyle.isolatedPoint, + undefined, + seriesStyle.point ? [seriesStyle.point] : [], + ); + + return mergePartial( + baseStyle, + { + isolatedPoint: { + ...isolatedPointStyleOverrides, + visible: seriesStyle?.isolatedPoint?.visible ?? baseStyle.isolatedPoint.visible, + }, + }, + undefined, + [seriesStyle], + ); +} diff --git a/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts b/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts index b666c509db..54375e005e 100644 --- a/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts +++ b/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts @@ -37,6 +37,7 @@ import { renderArea } from '../../rendering/area'; import { renderBars } from '../../rendering/bars'; import { renderBubble } from '../../rendering/bubble'; import { renderLine } from '../../rendering/line'; +import { getAreaSeriesStyles, getLineSeriesStyles } from '../../rendering/line_area_style'; import { defaultXYSeriesSort } from '../../utils/default_series_sort_fn'; import { fillSeries } from '../../utils/fill_series'; import { groupBy } from '../../utils/group_data_series'; @@ -432,10 +433,7 @@ function renderGeometries( geometriesCounts.bubbles += 1; } else if (isLineSeriesSpec(spec)) { const lineShift = barIndexOrder && barIndexOrder.length > 0 ? barIndexOrder.length : 1; - const lineSeriesStyle = spec.lineSeriesStyle - ? mergePartial(chartTheme.lineSeriesStyle, spec.lineSeriesStyle) - : chartTheme.lineSeriesStyle; - + const lineSeriesStyle = getLineSeriesStyles(chartTheme.lineSeriesStyle, spec.lineSeriesStyle); const xScaleOffset = computeXScaleOffset(xScale, enableHistogramMode, spec.histogramModeAlignment); const renderedLines = renderLine( @@ -467,9 +465,7 @@ function renderGeometries( geometriesCounts.lines += 1; } else if (isAreaSeriesSpec(spec)) { const areaShift = barIndexOrder && barIndexOrder.length > 0 ? barIndexOrder.length : 1; - const areaSeriesStyle = spec.areaSeriesStyle - ? mergePartial(chartTheme.areaSeriesStyle, spec.areaSeriesStyle) - : chartTheme.areaSeriesStyle; + const areaSeriesStyle = getAreaSeriesStyles(chartTheme.areaSeriesStyle, spec.areaSeriesStyle); const xScaleOffset = computeXScaleOffset(xScale, enableHistogramMode, spec.histogramModeAlignment); const renderedAreas = renderArea( // move the point on half of the bandwidth if we have mixed bars/lines diff --git a/storybook/stories/line/12_isolated_data_points.story.tsx b/storybook/stories/line/12_isolated_data_points.story.tsx index a115655337..8db5ff65e7 100644 --- a/storybook/stories/line/12_isolated_data_points.story.tsx +++ b/storybook/stories/line/12_isolated_data_points.story.tsx @@ -9,7 +9,7 @@ import { boolean, number } from '@storybook/addon-knobs'; import React from 'react'; -import { Axis, Chart, CurveType, LineSeries, Position, ScaleType, Settings, Fit, AreaSeries } from '@elastic/charts'; +import { Axis, Chart, CurveType, Position, ScaleType, Settings, Fit } from '@elastic/charts'; import { KIBANA_METRICS } from '@elastic/charts/src/utils/data_samples/test_dataset_kibana'; import { ChartsStory } from '../../types'; @@ -18,21 +18,33 @@ import { customKnobs } from '../utils/knobs'; export const Example: ChartsStory = (_, { title, description }) => { const fitEnabled = boolean('enable fit function', false); - const isArea = boolean('switch to area', false); + const [Series] = customKnobs.enum.xySeries('series type', 'line', { exclude: ['bar', 'bubble'] }); const maxDataPoints = number('max data points', 60, { range: true, min: 0, max: KIBANA_METRICS.metrics.kibana_os_load.v1.data.length, step: 1, }); + const pointRadius = number('default point radius', 0, { + range: true, + min: 0, + max: 10, + step: 1, + }); const radius = number('isolated point radius', 2, { range: true, min: 0, - max: 5, - step: 0.01, + max: 10, + step: 1, + }); + const overrideRadius = number('override point radius', 0, { + range: true, + min: 0, + max: 10, + step: 1, }); + const overridePointRadius = boolean('override radius for isolated points', false); - const LineOrAreaSeries = isArea ? AreaSeries : LineSeries; return ( { theme={{ areaSeriesStyle: { point: { - visible: false, + visible: true, + radius: pointRadius, }, isolatedPoint: { radius, @@ -49,7 +62,8 @@ export const Example: ChartsStory = (_, { title, description }) => { }, lineSeriesStyle: { point: { - visible: false, + visible: true, + radius: pointRadius, }, isolatedPoint: { radius, @@ -77,13 +91,43 @@ export const Example: ChartsStory = (_, { title, description }) => { /> - 0 + ? { + point: { + visible: true, + radius: overrideRadius, + }, + isolatedPoint: overridePointRadius + ? { + radius, + } + : {}, + } + : {} + } + lineSeriesStyle={ + overrideRadius > 0 + ? { + point: { + visible: true, + radius: overrideRadius, + }, + isolatedPoint: overridePointRadius + ? { + radius, + } + : {}, + } + : {} + } data={[ ...KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, maxDataPoints).map((d, i) => { if ([1, 10, 12, 20, 22, 24, 28].includes(i)) {