From bcc3d63bb126dc1714a8bf2a94d072a0c92a0231 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Wed, 26 Feb 2020 12:39:21 -0600 Subject: [PATCH] feat: xAccessor can be a function accessor (#574) --- .../utils/__snapshots__/series.test.ts.snap | 869 ++++++++++++++++++ src/chart_types/xy_chart/utils/series.test.ts | 28 + src/chart_types/xy_chart/utils/series.ts | 6 +- src/chart_types/xy_chart/utils/specs.ts | 4 +- src/utils/accessor.ts | 19 +- 5 files changed, 919 insertions(+), 7 deletions(-) diff --git a/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap b/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap index 6484ea9d0c..47f1020b1b 100644 --- a/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap +++ b/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap @@ -18275,6 +18275,875 @@ Array [ ] `; +exports[`Series functional accessors Can split dataset into 2Y2G series 1`] = ` +Array [ + Object { + "data": Array [ + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 0, + "y1": 1, + "y2": 4, + }, + "x": 0, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 1, + "y1": 2, + "y2": 1, + }, + "x": 1, + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 2, + "y1": 10, + "y2": 5, + }, + "x": 2, + "y0": null, + "y1": 10, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 3, + "y1": 7, + "y2": 3, + }, + "x": 3, + "y0": null, + "y1": 7, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 6, + "y1": 7, + "y2": 3, + }, + "x": 6, + "y0": null, + "y1": 7, + }, + ], + "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-direct-cdn}", + "seriesKeys": Array [ + "cdn.google.com", + "direct-cdn", + "y1", + ], + "specId": "spec1", + "splitAccessors": Map { + "g1" => "cdn.google.com", + "g2" => "direct-cdn", + }, + "yAccessor": "y1", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 0, + "y1": 1, + "y2": 4, + }, + "x": 0, + "y0": null, + "y1": 4, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 1, + "y1": 2, + "y2": 1, + }, + "x": 1, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 2, + "y1": 10, + "y2": 5, + }, + "x": 2, + "y0": null, + "y1": 5, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 3, + "y1": 7, + "y2": 3, + }, + "x": 3, + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 6, + "y1": 7, + "y2": 3, + }, + "x": 6, + "y0": null, + "y1": 3, + }, + ], + "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-direct-cdn}", + "seriesKeys": Array [ + "cdn.google.com", + "direct-cdn", + "y2", + ], + "specId": "spec1", + "splitAccessors": Map { + "g1" => "cdn.google.com", + "g2" => "direct-cdn", + }, + "yAccessor": "y2", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 0, + "y1": 1, + "y2": 4, + }, + "x": 0, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 1, + "y1": 2, + "y2": 1, + }, + "x": 1, + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 2, + "y1": 10, + "y2": 5, + }, + "x": 2, + "y0": null, + "y1": 10, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 3, + "y1": 7, + "y2": 3, + }, + "x": 3, + "y0": null, + "y1": 7, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 6, + "y1": 7, + "y2": 3, + }, + "x": 6, + "y0": null, + "y1": 7, + }, + ], + "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}", + "seriesKeys": Array [ + "cdn.google.com", + "indirect-cdn", + "y1", + ], + "specId": "spec1", + "splitAccessors": Map { + "g1" => "cdn.google.com", + "g2" => "indirect-cdn", + }, + "yAccessor": "y1", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 0, + "y1": 1, + "y2": 4, + }, + "x": 0, + "y0": null, + "y1": 4, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 1, + "y1": 2, + "y2": 1, + }, + "x": 1, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 2, + "y1": 10, + "y2": 5, + }, + "x": 2, + "y0": null, + "y1": 5, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 3, + "y1": 7, + "y2": 3, + }, + "x": 3, + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 6, + "y1": 7, + "y2": 3, + }, + "x": 6, + "y0": null, + "y1": 3, + }, + ], + "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cdn.google.com|g2-indirect-cdn}", + "seriesKeys": Array [ + "cdn.google.com", + "indirect-cdn", + "y2", + ], + "specId": "spec1", + "splitAccessors": Map { + "g1" => "cdn.google.com", + "g2" => "indirect-cdn", + }, + "yAccessor": "y2", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 0, + "y1": 3, + "y2": 6, + }, + "x": 0, + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 1, + "y1": 2, + "y2": 5, + }, + "x": 1, + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 2, + "y1": 3, + "y2": 1, + }, + "x": 2, + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 3, + "y1": 6, + "y2": 4, + }, + "x": 3, + "y0": null, + "y1": 6, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 6, + "y1": 6, + "y2": 4, + }, + "x": 6, + "y0": null, + "y1": 6, + }, + ], + "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-direct-cdn}", + "seriesKeys": Array [ + "cloudflare.com", + "direct-cdn", + "y1", + ], + "specId": "spec1", + "splitAccessors": Map { + "g1" => "cloudflare.com", + "g2" => "direct-cdn", + }, + "yAccessor": "y1", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 0, + "y1": 3, + "y2": 6, + }, + "x": 0, + "y0": null, + "y1": 6, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 1, + "y1": 2, + "y2": 5, + }, + "x": 1, + "y0": null, + "y1": 5, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 2, + "y1": 3, + "y2": 1, + }, + "x": 2, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 3, + "y1": 6, + "y2": 4, + }, + "x": 3, + "y0": null, + "y1": 4, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 6, + "y1": 6, + "y2": 4, + }, + "x": 6, + "y0": null, + "y1": 4, + }, + ], + "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-direct-cdn}", + "seriesKeys": Array [ + "cloudflare.com", + "direct-cdn", + "y2", + ], + "specId": "spec1", + "splitAccessors": Map { + "g1" => "cloudflare.com", + "g2" => "direct-cdn", + }, + "yAccessor": "y2", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 0, + "y1": 3, + "y2": 6, + }, + "x": 0, + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 1, + "y1": 2, + "y2": 5, + }, + "x": 1, + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 2, + "y1": 3, + "y2": 1, + }, + "x": 2, + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 3, + "y1": 6, + "y2": 4, + }, + "x": 3, + "y0": null, + "y1": 6, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 6, + "y1": 6, + "y2": 4, + }, + "x": 6, + "y0": null, + "y1": 6, + }, + ], + "key": "spec{spec1}yAccessor{y1}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}", + "seriesKeys": Array [ + "cloudflare.com", + "indirect-cdn", + "y1", + ], + "specId": "spec1", + "splitAccessors": Map { + "g1" => "cloudflare.com", + "g2" => "indirect-cdn", + }, + "yAccessor": "y1", + }, + Object { + "data": Array [ + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 0, + "y1": 3, + "y2": 6, + }, + "x": 0, + "y0": null, + "y1": 6, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 1, + "y1": 2, + "y2": 5, + }, + "x": 1, + "y0": null, + "y1": 5, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 2, + "y1": 3, + "y2": 1, + }, + "x": 2, + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 3, + "y1": 6, + "y2": 4, + }, + "x": 3, + "y0": null, + "y1": 4, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 6, + "y1": 6, + "y2": 4, + }, + "x": 6, + "y0": null, + "y1": 4, + }, + ], + "key": "spec{spec1}yAccessor{y2}splitAccessors{g1-cloudflare.com|g2-indirect-cdn}", + "seriesKeys": Array [ + "cloudflare.com", + "indirect-cdn", + "y2", + ], + "specId": "spec1", + "splitAccessors": Map { + "g1" => "cloudflare.com", + "g2" => "indirect-cdn", + }, + "yAccessor": "y2", + }, +] +`; + +exports[`Series functional accessors Can split dataset with custom _all xAccessor 1`] = ` +Array [ + Object { + "data": Array [ + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 0, + "y1": 1, + "y2": 4, + }, + "x": "_all", + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 0, + "y1": 1, + "y2": 4, + }, + "x": "_all", + "y0": null, + "y1": 1, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 0, + "y1": 3, + "y2": 6, + }, + "x": "_all", + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 0, + "y1": 3, + "y2": 6, + }, + "x": "_all", + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 1, + "y1": 2, + "y2": 1, + }, + "x": "_all", + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 1, + "y1": 2, + "y2": 1, + }, + "x": "_all", + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 1, + "y1": 2, + "y2": 5, + }, + "x": "_all", + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 1, + "y1": 2, + "y2": 5, + }, + "x": "_all", + "y0": null, + "y1": 2, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 2, + "y1": 10, + "y2": 5, + }, + "x": "_all", + "y0": null, + "y1": 10, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 2, + "y1": 10, + "y2": 5, + }, + "x": "_all", + "y0": null, + "y1": 10, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 2, + "y1": 3, + "y2": 1, + }, + "x": "_all", + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 2, + "y1": 3, + "y2": 1, + }, + "x": "_all", + "y0": null, + "y1": 3, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 3, + "y1": 7, + "y2": 3, + }, + "x": "_all", + "y0": null, + "y1": 7, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 3, + "y1": 7, + "y2": 3, + }, + "x": "_all", + "y0": null, + "y1": 7, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 3, + "y1": 6, + "y2": 4, + }, + "x": "_all", + "y0": null, + "y1": 6, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 3, + "y1": 6, + "y2": 4, + }, + "x": "_all", + "y0": null, + "y1": 6, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "direct-cdn", + "x": 6, + "y1": 7, + "y2": 3, + }, + "x": "_all", + "y0": null, + "y1": 7, + }, + Object { + "datum": Object { + "g1": "cdn.google.com", + "g2": "indirect-cdn", + "x": 6, + "y1": 7, + "y2": 3, + }, + "x": "_all", + "y0": null, + "y1": 7, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "direct-cdn", + "x": 6, + "y1": 6, + "y2": 4, + }, + "x": "_all", + "y0": null, + "y1": 6, + }, + Object { + "datum": Object { + "g1": "cloudflare.com", + "g2": "indirect-cdn", + "x": 6, + "y1": 6, + "y2": 4, + }, + "x": "_all", + "y0": null, + "y1": 6, + }, + ], + "key": "spec{spec1}yAccessor{y1}splitAccessors{}", + "seriesKeys": Array [ + "y1", + ], + "specId": "spec1", + "splitAccessors": Map {}, + "yAccessor": "y1", + }, +] +`; + exports[`Series should compute data series for stacked specs 1`] = ` Array [ Object { diff --git a/src/chart_types/xy_chart/utils/series.test.ts b/src/chart_types/xy_chart/utils/series.test.ts index ad45e2c06d..4898d28150 100644 --- a/src/chart_types/xy_chart/utils/series.test.ts +++ b/src/chart_types/xy_chart/utils/series.test.ts @@ -20,6 +20,7 @@ import { SpecTypes } from '../../../specs/settings'; import { MockSeriesSpec } from '../../../mocks/specs'; import { SeededDataGenerator } from '../../../mocks/utils'; import { MockSeriesIdentifier } from '../../../mocks/series/series_identifiers'; +import { AccessorFn } from '../../../utils/accessor'; const dg = new SeededDataGenerator(); @@ -882,4 +883,31 @@ describe('Series', () => { }); }); }); + + describe('functional accessors', () => { + test('Can split dataset into 2Y2G series', () => { + const xAccessor: AccessorFn = (d) => d.x; + const splittedSeries = splitSeries({ + id: 'spec1', + data: TestDataset.BARCHART_2Y2G, + xAccessor, + yAccessors: ['y1', 'y2'], + splitSeriesAccessors: ['g1', 'g2'], + }); + expect(splittedSeries.rawDataSeries.length).toBe(8); + expect(splittedSeries.rawDataSeries).toMatchSnapshot(); + }); + + test('Can split dataset with custom _all xAccessor', () => { + const xAccessor: AccessorFn = () => '_all'; + const splittedSeries = splitSeries({ + id: 'spec1', + data: TestDataset.BARCHART_2Y2G, + xAccessor, + yAccessors: ['y1'], + }); + expect(splittedSeries.rawDataSeries.length).toBe(1); + expect(splittedSeries.rawDataSeries).toMatchSnapshot(); + }); + }); }); diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index 582c130f11..a7b6304cd2 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -1,5 +1,5 @@ import { ColorConfig } from '../../../utils/themes/theme'; -import { Accessor } from '../../../utils/accessor'; +import { Accessor, getAccessorValue, AccessorFn } from '../../../utils/accessor'; import { GroupId, SpecId } from '../../../utils/ids'; import { splitSpecsByGroupId, YBasicSeriesSpec } from '../domains/y_domain'; import { formatNonStackedDataSeriesValues } from './nonstacked_series_utils'; @@ -213,11 +213,11 @@ function getSplitAccessors(datum: Datum, accessors: Accessor[] = []): Map any; export type AccessorFn = UnaryAccessorFn; export type IndexedAccessorFn = UnaryAccessorFn | BinaryAccessorFn; -export type AccessorString = string | number; -export type Accessor = AccessorString; +type AccessorObjectKey = string; +type AccessorArrayIndex = number; +export type Accessor = AccessorObjectKey | AccessorArrayIndex; /** * Accessor format for _banded_ series as postfix string or accessor function @@ -39,3 +40,17 @@ export function getAccessorFormatLabel(accessor: AccessorFormat, label: string): return accessor(label); } + +/** + * Helper function to get accessor value from string, number or function + * + * @param {Datum} datum + * @param {AccessorString|AccessorFn} accessor + */ +export function getAccessorValue(datum: Datum, accessor: Accessor | AccessorFn) { + if (typeof accessor === 'function') { + return accessor(datum); + } + + return datum[accessor]; +}