diff --git a/packages/osd-charts/api/charts.api.md b/packages/osd-charts/api/charts.api.md index a897f1aee94d..6f4772c3a215 100644 --- a/packages/osd-charts/api/charts.api.md +++ b/packages/osd-charts/api/charts.api.md @@ -265,6 +265,17 @@ export type BasicListener = () => undefined | void; // @public (undocumented) export type BasicSeriesSpec = SeriesSpec & SeriesAccessors & SeriesScales; +// Warning: (ae-missing-release-tag) "BinAgg" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const BinAgg: Readonly<{ + Sum: "sum"; + None: "none"; +}>; + +// @public (undocumented) +export type BinAgg = $Values; + // Warning: (ae-missing-release-tag) "BrushAxis" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -523,6 +534,17 @@ export const DEFAULT_TOOLTIP_TYPE: "vertical"; // @public (undocumented) export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'animateData' | 'showLegend' | 'debug' | 'tooltip' | 'showLegendExtra' | 'theme' | 'legendPosition' | 'hideDuplicateAxes' | 'brushAxis' | 'minBrushDelta' | 'externalPointerEvents'; +// Warning: (ae-missing-release-tag) "Direction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const Direction: Readonly<{ + Ascending: "ascending"; + Descending: "descending"; +}>; + +// @public (undocumented) +export type Direction = $Values; + // Warning: (ae-missing-release-tag) "DisplayValueSpec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -920,6 +942,14 @@ export interface Opacity { opacity: number; } +// @public +export interface OrderBy { + // (undocumented) + binAgg?: BinAgg; + // (undocumented) + direction?: Direction; +} + // @public (undocumented) export type PartialTheme = RecursivePartial; @@ -1332,6 +1362,7 @@ export interface SettingsSpec extends Spec { onPointerUpdate?: PointerUpdateListener; // (undocumented) onRenderChange?: RenderChangeListener; + orderOrdinalBinsBy?: OrderBy; // (undocumented) pointBuffer?: MarkBuffer; // (undocumented) diff --git a/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-order-bins-by-sum-visually-looks-correct-1-snap.png b/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-order-bins-by-sum-visually-looks-correct-1-snap.png new file mode 100644 index 000000000000..a3a0e6608109 Binary files /dev/null and b/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-order-bins-by-sum-visually-looks-correct-1-snap.png differ diff --git a/packages/osd-charts/integration/tests/legend_stories.test.ts b/packages/osd-charts/integration/tests/legend_stories.test.ts index 2b827cfed7a1..981b152c832c 100644 --- a/packages/osd-charts/integration/tests/legend_stories.test.ts +++ b/packages/osd-charts/integration/tests/legend_stories.test.ts @@ -59,7 +59,7 @@ describe('Legend stories', () => { const action = async () => await common.moveMouseRelativeToDOMElement({ left: 30, top: 10 }, '.echLegendItem'); await common.expectChartAtUrlToMatchScreenshot('http://localhost:9001/?path=/story/legend--actions', { action, - delay: 200, // needed for icon to load + delay: 500, // needed for icon to load }); }); diff --git a/packages/osd-charts/src/chart_types/xy_chart/domains/x_domain.test.ts b/packages/osd-charts/src/chart_types/xy_chart/domains/x_domain.test.ts index c9236c36ea33..e89e4db67d43 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/domains/x_domain.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/domains/x_domain.test.ts @@ -18,8 +18,9 @@ */ import { ChartTypes } from '../..'; +import { MockSeriesSpecs } from '../../../mocks/specs'; import { ScaleType } from '../../../scales/constants'; -import { SpecTypes } from '../../../specs/constants'; +import { SpecTypes, Direction, BinAgg } from '../../../specs/constants'; import { getDataSeriesBySpecId } from '../utils/series'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; import { convertXScaleTypes, findMinInterval, mergeXDomain } from './x_domain'; @@ -837,4 +838,97 @@ describe('X Domain', () => { expect(attemptToMerge).toThrowError(expectedError); }); }); + + describe('orderOrdinalBinsBySum', () => { + const ordinalSpecs = MockSeriesSpecs.fromPartialSpecs([ + { + id: 'ordinal1', + seriesType: SeriesTypes.Bar, + xScaleType: ScaleType.Ordinal, + data: [ + { x: 'a', y: 2 }, + { x: 'b', y: 4 }, + { x: 'c', y: 8 }, + { x: 'd', y: 6 }, + ], + }, + { + id: 'ordinal2', + seriesType: SeriesTypes.Bar, + xScaleType: ScaleType.Ordinal, + data: [ + { x: 'a', y: 4 }, + { x: 'b', y: 8 }, + { x: 'c', y: 16 }, + { x: 'd', y: 12 }, + ], + }, + ]); + + const linearSpecs = MockSeriesSpecs.fromPartialSpecs([ + { + id: 'linear1', + seriesType: SeriesTypes.Bar, + xScaleType: ScaleType.Linear, + data: [ + { x: 1, y: 2 }, + { x: 2, y: 4 }, + { x: 3, y: 8 }, + { x: 4, y: 6 }, + ], + }, + { + id: 'linear2', + seriesType: SeriesTypes.Bar, + xScaleType: ScaleType.Linear, + data: [ + { x: 1, y: 4 }, + { x: 2, y: 8 }, + { x: 3, y: 16 }, + { x: 4, y: 12 }, + ], + }, + ]); + + it('should sort ordinal xValues by descending sum by default', () => { + const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], {}); + expect(xValues).toEqual(new Set(['c', 'd', 'b', 'a'])); + }); + + it('should sort ordinal xValues by descending sum', () => { + const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], { + binAgg: BinAgg.None, + direction: Direction.Descending, + }); + expect(xValues).toEqual(new Set(['c', 'd', 'b', 'a'])); + }); + + it('should sort ordinal xValues by ascending sum', () => { + const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], { + binAgg: BinAgg.None, + direction: Direction.Ascending, + }); + expect(xValues).toEqual(new Set(['a', 'b', 'd', 'c'])); + }); + + it('should NOT sort ordinal xValues sum', () => { + const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], undefined); + expect(xValues).toEqual(new Set(['a', 'b', 'c', 'd'])); + }); + + it('should NOT sort ordinal xValues sum when undefined', () => { + const { xValues } = getDataSeriesBySpecId(ordinalSpecs, [], { + binAgg: BinAgg.None, + direction: Direction.Descending, + }); + expect(xValues).toEqual(new Set(['a', 'b', 'c', 'd'])); + }); + + it('should NOT sort linear xValue by descending sum', () => { + const { xValues } = getDataSeriesBySpecId(linearSpecs, [], { + direction: Direction.Descending, + }); + expect(xValues).toEqual(new Set([1, 2, 3, 4])); + }); + }); }); diff --git a/packages/osd-charts/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts b/packages/osd-charts/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts index fb62329e7d22..f3bd0fe8401d 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts @@ -38,6 +38,7 @@ export const computeSeriesDomainsSelector = createCachedSelector( customYDomainsByGroupId, deselectedDataSeries, settingsSpec.xDomain, + settingsSpec.orderOrdinalBinsBy, // @ts-ignore blind sort option for vislib settingsSpec.enableVislibSeriesSort, ); diff --git a/packages/osd-charts/src/chart_types/xy_chart/state/utils/utils.ts b/packages/osd-charts/src/chart_types/xy_chart/state/utils/utils.ts index ac50c7f8921f..acedf10e66ff 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/state/utils/utils.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/state/utils/utils.ts @@ -20,6 +20,7 @@ import { SeriesKey, SeriesIdentifier } from '../../../../commons/series_id'; import { Scale } from '../../../../scales'; import { ScaleType } from '../../../../scales/constants'; +import { OrderBy } from '../../../../specs/settings'; import { mergePartial, Rotation, Color, isUniqueArray } from '../../../../utils/commons'; import { CurveType } from '../../../../utils/curves'; import { Dimensions } from '../../../../utils/dimensions'; @@ -204,14 +205,15 @@ export function computeSeriesDomains( customYDomainsByGroupId: Map = new Map(), deselectedDataSeries: SeriesIdentifier[] = [], customXDomain?: DomainRange | Domain, + orderOrdinalBinsBy?: OrderBy, enableVislibSeriesSort?: boolean, ): SeriesDomainsAndData { const { dataSeriesBySpecId, xValues, seriesCollection, fallbackScale } = getDataSeriesBySpecId( seriesSpecs, deselectedDataSeries, + orderOrdinalBinsBy, enableVislibSeriesSort, ); - // compute the x domain merging any custom domain const specsArray = [...seriesSpecs.values()]; const xDomain = mergeXDomain(specsArray, xValues, customXDomain, fallbackScale); diff --git a/packages/osd-charts/src/chart_types/xy_chart/utils/series.test.ts b/packages/osd-charts/src/chart_types/xy_chart/utils/series.test.ts index f278d189fffa..a4051f435852 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/utils/series.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/utils/series.test.ts @@ -48,63 +48,103 @@ const dg = new SeededDataGenerator(); describe('Series', () => { test('Can split dataset into 1Y0G series', () => { - const splittedSeries = splitSeriesDataByAccessors({ - id: 'spec1', - data: TestDataset.BARCHART_1Y0G, - xAccessor: 'x', - yAccessors: ['y1'], - splitSeriesAccessors: ['y'], - }); + const splitSeries = splitSeriesDataByAccessors( + { + id: 'spec1', + data: TestDataset.BARCHART_1Y0G, + xAccessor: 'x', + yAccessors: ['y1'], + splitSeriesAccessors: ['y'], + }, + new Map(), + ); - expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot(); + expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); }); test('Can split dataset into 1Y1G series', () => { - const splittedSeries = splitSeriesDataByAccessors({ - id: 'spec1', - data: TestDataset.BARCHART_1Y1G, - xAccessor: 'x', - yAccessors: ['y'], - }); - expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot(); + const splitSeries = splitSeriesDataByAccessors( + { + id: 'spec1', + data: TestDataset.BARCHART_1Y1G, + xAccessor: 'x', + yAccessors: ['y'], + }, + new Map(), + ); + expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); }); test('Can split dataset into 1Y2G series', () => { - const splittedSeries = splitSeriesDataByAccessors({ - id: 'spec1', - data: TestDataset.BARCHART_1Y2G, - xAccessor: 'x', - yAccessors: ['y'], - splitSeriesAccessors: ['g1', 'g2'], - }); - expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot(); + const splitSeries = splitSeriesDataByAccessors( + { + id: 'spec1', + data: TestDataset.BARCHART_1Y2G, + xAccessor: 'x', + yAccessors: ['y'], + splitSeriesAccessors: ['g1', 'g2'], + }, + new Map(), + ); + expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); }); test('Can split dataset into 2Y0G series', () => { - const splittedSeries = splitSeriesDataByAccessors({ - id: 'spec1', - data: TestDataset.BARCHART_2Y0G, - xAccessor: 'x', - yAccessors: ['y1', 'y2'], - }); - expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot(); + const splitSeries = splitSeriesDataByAccessors( + { + id: 'spec1', + data: TestDataset.BARCHART_2Y0G, + xAccessor: 'x', + yAccessors: ['y1', 'y2'], + }, + new Map(), + ); + expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); }); test('Can split dataset into 2Y1G series', () => { - const splittedSeries = splitSeriesDataByAccessors({ - id: 'spec1', - data: TestDataset.BARCHART_2Y1G, - xAccessor: 'x', - yAccessors: ['y1', 'y2'], - splitSeriesAccessors: ['g'], - }); - expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot(); + const splitSeries = splitSeriesDataByAccessors( + { + id: 'spec1', + data: TestDataset.BARCHART_2Y1G, + xAccessor: 'x', + yAccessors: ['y1', 'y2'], + splitSeriesAccessors: ['g'], + }, + new Map(), + ); + expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); }); test('Can split dataset into 2Y2G series', () => { - const splittedSeries = splitSeriesDataByAccessors({ - id: 'spec1', - data: TestDataset.BARCHART_2Y2G, - xAccessor: 'x', - yAccessors: ['y1', 'y2'], - splitSeriesAccessors: ['g1', 'g2'], - }); - expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot(); + const splitSeries = splitSeriesDataByAccessors( + { + id: 'spec1', + data: TestDataset.BARCHART_2Y2G, + xAccessor: 'x', + yAccessors: ['y1', 'y2'], + splitSeriesAccessors: ['g1', 'g2'], + }, + new Map(), + ); + expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); + }); + it('should get sum of all xValues', () => { + const xValueSums = new Map(); + splitSeriesDataByAccessors( + { + id: 'spec1', + data: TestDataset.BARCHART_1Y1G_ORDINAL, + xAccessor: 'x', + yAccessors: ['y'], + splitSeriesAccessors: ['g'], + }, + xValueSums, + ); + expect(xValueSums).toEqual( + new Map([ + ['a', 3], + ['b', 5], + ['c', 3], + ['d', 4], + ['e', 9], + ]), + ); }); test('Can stack simple dataseries', () => { const store = MockStore.default(); @@ -875,27 +915,33 @@ describe('Series', () => { describe('functional accessors', () => { test('Can split dataset into 2Y2G series', () => { const xAccessor: AccessorFn = (d) => d.x; - const splittedSeries = splitSeriesDataByAccessors({ - id: 'spec1', - data: TestDataset.BARCHART_2Y2G, - xAccessor, - yAccessors: ['y1', 'y2'], - splitSeriesAccessors: ['g1', 'g2'], - }); - expect([...splittedSeries.dataSeries.values()].length).toBe(8); - expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot(); + const splitSeries = splitSeriesDataByAccessors( + { + id: 'spec1', + data: TestDataset.BARCHART_2Y2G, + xAccessor, + yAccessors: ['y1', 'y2'], + splitSeriesAccessors: ['g1', 'g2'], + }, + new Map(), + ); + expect([...splitSeries.dataSeries.values()].length).toBe(8); + expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); }); test('Can split dataset with custom _all xAccessor', () => { const xAccessor: AccessorFn = () => '_all'; - const splittedSeries = splitSeriesDataByAccessors({ - id: 'spec1', - data: TestDataset.BARCHART_2Y2G, - xAccessor, - yAccessors: ['y1'], - }); - expect([...splittedSeries.dataSeries.values()].length).toBe(1); - expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot(); + const splitSeries = splitSeriesDataByAccessors( + { + id: 'spec1', + data: TestDataset.BARCHART_2Y2G, + xAccessor, + yAccessors: ['y1'], + }, + new Map(), + ); + expect([...splitSeries.dataSeries.values()].length).toBe(1); + expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); }); test('Shall ignore undefined values on splitSeriesAccessors', () => { @@ -915,9 +961,9 @@ describe('Series', () => { yAccessors: [1], splitSeriesAccessors: [2], }); - const splittedSeries = splitSeriesDataByAccessors(spec); - expect([...splittedSeries.dataSeries.values()].length).toBe(2); - expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot(); + const splitSeries = splitSeriesDataByAccessors(spec, new Map()); + expect([...splitSeries.dataSeries.values()].length).toBe(2); + expect([...splitSeries.dataSeries.values()]).toMatchSnapshot(); }); test('Should ignore series if splitSeriesAccessors are defined but not contained in any datum', () => { const spec = MockSeriesSpec.bar({ @@ -930,8 +976,8 @@ describe('Series', () => { yAccessors: [1], splitSeriesAccessors: [2], }); - const splittedSeries = splitSeriesDataByAccessors(spec); - expect([...splittedSeries.dataSeries.values()].length).toBe(0); + const splitSeries = splitSeriesDataByAccessors(spec, new Map()); + expect([...splitSeries.dataSeries.values()].length).toBe(0); }); }); }); diff --git a/packages/osd-charts/src/chart_types/xy_chart/utils/series.ts b/packages/osd-charts/src/chart_types/xy_chart/utils/series.ts index e19bafbecceb..4e77f1c1cc3d 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/utils/series.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/utils/series.ts @@ -19,6 +19,8 @@ import { SeriesIdentifier, SeriesKey } from '../../../commons/series_id'; import { ScaleType } from '../../../scales/constants'; +import { BinAgg, Direction } from '../../../specs'; +import { OrderBy } from '../../../specs/settings'; import { ColorOverrides } from '../../../state/chart_state'; import { Accessor, AccessorFn, getAccessorValue } from '../../../utils/accessor'; import { Datum, Color } from '../../../utils/commons'; @@ -72,7 +74,6 @@ export interface XYChartSeriesIdentifier extends SeriesIdentifier { /** @internal */ export type DataSeries = XYChartSeriesIdentifier & { - // seriesColorKey: string; data: DataSeriesDatum[]; }; @@ -124,6 +125,7 @@ export function splitSeriesDataByAccessors( BasicSeriesSpec, 'id' | 'data' | 'xAccessor' | 'yAccessors' | 'y0Accessors' | 'splitSeriesAccessors' | 'markSizeAccessor' >, + xValueSums: Map, enableVislibSeriesSort = false, ): { dataSeries: Map; @@ -160,6 +162,7 @@ export function splitSeriesDataByAccessors( } xValues.push(x); + let sum = xValueSums.get(x) ?? 0; const cleanedDatum = extractYandMarkFromDatum( datum, @@ -174,6 +177,7 @@ export function splitSeriesDataByAccessors( yAccessor: accessor, splitAccessors, }); + sum += cleanedDatum.y1 ?? 0; const newDatum = { x, ...cleanedDatum }; const series = dataSeries.get(seriesKey); if (series) { @@ -188,6 +192,7 @@ export function splitSeriesDataByAccessors( seriesKeys, }); } + xValueSums.set(x, sum); }); }); } else { @@ -211,6 +216,7 @@ export function splitSeriesDataByAccessors( } xValues.push(x); + let sum = xValueSums.get(x) ?? 0; yAccessors.forEach((accessor, index) => { const cleanedDatum = extractYandMarkFromDatum( @@ -226,6 +232,7 @@ export function splitSeriesDataByAccessors( yAccessor: accessor, splitAccessors, }); + sum += cleanedDatum.y1 ?? 0; const newDatum = { x, ...cleanedDatum }; const series = dataSeries.get(seriesKey); if (series) { @@ -241,6 +248,7 @@ export function splitSeriesDataByAccessors( }); } }); + xValueSums.set(x, sum); }); } @@ -418,6 +426,7 @@ function getDataSeriesBySpecGroup( export function getDataSeriesBySpecId( seriesSpecs: BasicSeriesSpec[], deselectedDataSeries: SeriesIdentifier[] = [], + orderOrdinalBinsBy?: OrderBy, enableVislibSeriesSort?: boolean, ): { dataSeriesBySpecId: Map; @@ -427,6 +436,7 @@ export function getDataSeriesBySpecId( } { const dataSeriesBySpecId = new Map(); const seriesCollection = new Map(); + const mutatedXValueSums = new Map(); // the unique set of values along the x axis const globalXValues: Set = new Set(); @@ -441,7 +451,7 @@ export function getDataSeriesBySpecId( isOrdinalScale = true; } - const { dataSeries, xValues } = splitSeriesDataByAccessors(spec, enableVislibSeriesSort); + const { dataSeries, xValues } = splitSeriesDataByAccessors(spec, mutatedXValueSums, enableVislibSeriesSort); // filter deleselected dataseries let filteredDataSeries: DataSeries[] = [...dataSeries.values()]; @@ -478,10 +488,9 @@ export function getDataSeriesBySpecId( return { dataSeriesBySpecId, seriesCollection, - // keep the user order for ordinal scales xValues: isOrdinalScale || !isNumberArray - ? globalXValues + ? getSortedOrdinalXValues(globalXValues, mutatedXValueSums, orderOrdinalBinsBy) : new Set( [...globalXValues].sort((a, b) => { if (typeof a === 'string' || typeof b === 'string') { @@ -494,6 +503,31 @@ export function getDataSeriesBySpecId( }; } +function getSortedOrdinalXValues( + xValues: Set, + xValueSums: Map, + orderOrdinalBinsBy?: OrderBy, +) { + if (!orderOrdinalBinsBy) { + return xValues; // keep the user order for ordinal scales + } + + switch (orderOrdinalBinsBy?.binAgg) { + case BinAgg.None: + return xValues; // keep the user order for ordinal scales + case BinAgg.Sum: + default: + return new Set( + [...xValues].sort((v1, v2) => { + return ( + (orderOrdinalBinsBy.direction === Direction.Ascending ? 1 : -1) * + ((xValueSums.get(v1) ?? 0) - (xValueSums.get(v2) ?? 0)) + ); + }), + ); + } +} + function getSeriesNameFromOptions( options: SeriesNameConfigOptions, { yAccessor, splitAccessors }: XYChartSeriesIdentifier, diff --git a/packages/osd-charts/src/specs/constants.ts b/packages/osd-charts/src/specs/constants.ts index 42f987929561..e4b691647ccc 100644 --- a/packages/osd-charts/src/specs/constants.ts +++ b/packages/osd-charts/src/specs/constants.ts @@ -33,6 +33,38 @@ export const SpecTypes = Object.freeze({ /** @public */ export type SpecTypes = $Values; +/** + * Type of bin aggregations + */ +export const BinAgg = Object.freeze({ + /** + * Order by sum of values in bin + */ + Sum: 'sum' as const, + /** + * Order of values are used as is + */ + None: 'none' as const, +}); +/** @public */ +export type BinAgg = $Values; + +/** + * Direction of sorting + */ +export const Direction = Object.freeze({ + /** + * Least to greatest + */ + Ascending: 'ascending' as const, + /** + * Greatest to least + */ + Descending: 'descending' as const, +}); +/** @public */ +export type Direction = $Values; + export const PointerEventType = Object.freeze({ Over: 'Over' as const, Out: 'Out' as const, diff --git a/packages/osd-charts/src/specs/settings.tsx b/packages/osd-charts/src/specs/settings.tsx index 2ec8ab9cdacf..dfe094c379d5 100644 --- a/packages/osd-charts/src/specs/settings.tsx +++ b/packages/osd-charts/src/specs/settings.tsx @@ -34,7 +34,7 @@ import { Domain } from '../utils/domain'; import { GeometryValue } from '../utils/geometry'; import { GroupId } from '../utils/ids'; import { PartialTheme, Theme } from '../utils/themes/theme'; -import { PointerEventType, TooltipType, BrushAxis, DEFAULT_SETTINGS_SPEC } from './constants'; +import { PointerEventType, TooltipType, BrushAxis, DEFAULT_SETTINGS_SPEC, BinAgg, Direction } from './constants'; export interface LayerValue { groupByRollup: PrimitiveValue; @@ -361,6 +361,19 @@ export interface SettingsSpec extends Spec { * @defaultValue false */ allowBrushingLastHistogramBucket?: boolean; + /** + * Orders ordinal x values + */ + orderOrdinalBinsBy?: OrderBy; +} + +/** + * Order by options + * @public + */ +export interface OrderBy { + binAgg?: BinAgg; + direction?: Direction; } export type DefaultSettingsProps = diff --git a/packages/osd-charts/stories/bar/50_order_bins_by_sum.tsx b/packages/osd-charts/stories/bar/50_order_bins_by_sum.tsx new file mode 100644 index 000000000000..834af224a672 --- /dev/null +++ b/packages/osd-charts/stories/bar/50_order_bins_by_sum.tsx @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { select, boolean } from '@storybook/addon-knobs'; +import React from 'react'; + +import { Axis, BarSeries, Chart, Position, ScaleType, Settings, BinAgg, Direction } from '../../src'; +import { SB_SOURCE_PANEL } from '../utils/storybook'; + +const data = [ + { x1: 'b', x2: 2, g1: 'false', g2: 'Canada', y1: 19, y2: 22 }, + { x1: 'd', x2: 4, g1: 'false', g2: 'USA', y1: 34, y2: 21 }, + { x1: 'd', x2: 4, g1: 'true', g2: 'USA', y1: 49, y2: 169 }, + { x1: 'e', x2: 5, g1: 'false', g2: 'Canada', y1: 40, y2: 77 }, + { x1: 'b', x2: 2, g1: 'true', g2: 'USA', y1: 28, y2: 84 }, + { x1: 'a', x2: 1, g1: 'false', g2: 'USA', y1: 53, y2: 39 }, + { x1: 'a', x2: 1, g1: 'true', g2: 'Canada', y1: 93, y2: 42 }, + { x1: 'c', x2: 3, g1: 'true', g2: 'USA', y1: 55, y2: 72 }, + { x1: 'e', x2: 5, g1: 'true', g2: 'Canada', y1: 96, y2: 74 }, + { x1: 'c', x2: 3, g1: 'false', g2: 'Canada', y1: 87, y2: 39 }, +]; + +export const Example = () => { + const orderOrdinalBinsBy = boolean('enable orderOrdinalBinsBy', true); + const dataType = select( + 'Data type', + { + linear: 'linear', + ordinal: 'ordinal', + }, + 'ordinal', + ); + const direction = + select( + 'Direction', + { + Ascending: Direction.Ascending, + Descending: Direction.Descending, + 'Default (Descending)': undefined, + }, + Direction.Descending, + ) || undefined; + const binAgg = + select( + 'BinAgg', + { + Sum: BinAgg.Sum, + None: BinAgg.None, + 'Default (sum)': undefined, + }, + BinAgg.Sum, + ) || undefined; + return ( + + + + `$${Number(d).toFixed(2)}`} /> + + + + ); +}; + +// storybook configuration +Example.story = { + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + }, +}; diff --git a/packages/osd-charts/stories/bar/bars.stories.tsx b/packages/osd-charts/stories/bar/bars.stories.tsx index 00b7a7c04107..1ff97e6d7d15 100644 --- a/packages/osd-charts/stories/bar/bars.stories.tsx +++ b/packages/osd-charts/stories/bar/bars.stories.tsx @@ -63,6 +63,7 @@ export { Example as scaleToExtent } from './32_scale_to_extent'; export { Example as bandBarChart } from './33_band_bar'; export { Example as minHeight } from './45_min_height'; export { Example as stackedOnlyGroupedAreas } from './47_stacked_only_grouped'; +export { Example as orderBinsBySum } from './50_order_bins_by_sum'; // for testing purposes only export { Example as testLinear } from './34_test_linear';