From fe47338e6f2a6c64daeba4cd0df68b27925f3de2 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Tue, 23 Mar 2021 16:53:41 +0100 Subject: [PATCH 01/11] feat: nice ticks everywhere --- src/scales/scale_continuous.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/scales/scale_continuous.ts b/src/scales/scale_continuous.ts index 1d89b253e1..3c7d258bc6 100644 --- a/src/scales/scale_continuous.ts +++ b/src/scales/scale_continuous.ts @@ -27,6 +27,7 @@ import { ScaleLogarithmic, ScalePower, ScaleTime, + ScaleContinuousNumeric, } from 'd3-scale'; import { $Values, Required } from 'utility-types'; @@ -285,6 +286,10 @@ export class ScaleContinuous implements Scale { } this.d3Scale.domain(this.domain); + if (type !== ScaleType.Time) { + (this.d3Scale as ScaleContinuousNumeric).domain(this.domain).nice(); + this.domain = this.d3Scale.domain(); + } const safeBarPadding = maxValueWithUpperLimit(barsPadding, 0, 1); this.barsPadding = safeBarPadding; From acee82b721e0112dd52e2cc5835591bb3a93a4aa Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Mon, 29 Mar 2021 11:06:42 +0200 Subject: [PATCH 02/11] feat: add nice prop on scale type --- .../heatmap/layout/viewmodel/viewmodel.ts | 5 +- .../selectors/get_x_axis_right_overflow.ts | 2 +- .../crosshair_utils.linear_snap.test.ts | 9 +-- src/chart_types/xy_chart/domains/nice.ts | 23 +++++++ src/chart_types/xy_chart/domains/types.ts | 41 +++++------- .../xy_chart/domains/x_domain.test.ts | 33 ++++------ src/chart_types/xy_chart/domains/x_domain.ts | 66 ++++++++++++------- .../xy_chart/domains/y_domain.test.ts | 28 ++++---- src/chart_types/xy_chart/domains/y_domain.ts | 42 ++++++------ src/chart_types/xy_chart/legend/legend.ts | 6 +- .../xy_chart/scales/get_scale_config.ts | 49 ++++++++++++++ .../xy_chart/scales/scale_defaults.ts | 31 +++++++++ .../xy_chart/state/utils/utils.test.ts | 13 ++-- src/chart_types/xy_chart/state/utils/utils.ts | 11 ++-- .../xy_chart/utils/axis_utils.test.ts | 23 +++---- src/chart_types/xy_chart/utils/scales.test.ts | 25 +++++-- src/chart_types/xy_chart/utils/scales.ts | 31 ++++----- src/chart_types/xy_chart/utils/series.ts | 5 +- src/chart_types/xy_chart/utils/specs.ts | 5 +- src/scales/scale_continuous.test.ts | 5 +- src/scales/scale_continuous.ts | 11 ++-- src/scales/scales.test.ts | 38 +++++++++-- stories/legend/11_legend_actions.tsx | 1 - 23 files changed, 334 insertions(+), 169 deletions(-) create mode 100644 src/chart_types/xy_chart/domains/nice.ts create mode 100644 src/chart_types/xy_chart/scales/get_scale_config.ts create mode 100644 src/chart_types/xy_chart/scales/scale_defaults.ts diff --git a/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts b/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts index 20302ab943..a37fd9911b 100644 --- a/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts +++ b/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts @@ -108,12 +108,13 @@ export function shapeViewModel( let xValues = xDomain.domain as any[]; const timeScale = - xDomain.scaleType === ScaleType.Time + xDomain.scaleConfig.type === ScaleType.Time ? new ScaleContinuous( { type: ScaleType.Time, domain: xDomain.domain, range: [0, chartDimensions.width], + nice: false, }, { ticks: getTicks(chartDimensions.width, config.xAxisLabel), @@ -313,7 +314,7 @@ export function shapeViewModel( * @param y */ const pickHighlightedArea: PickHighlightedArea = (x: Array, y: Array) => { - if (xDomain.scaleType !== ScaleType.Time) { + if (xDomain.scaleConfig.type !== ScaleType.Time) { return null; } const [startValue, endValue] = x; diff --git a/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts b/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts index 8b6e900943..8879fdd3c1 100644 --- a/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts +++ b/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts @@ -33,7 +33,7 @@ import { getHeatmapTableSelector } from './get_heatmap_table'; export const getXAxisRightOverflow = createCachedSelector( [getHeatmapConfigSelector, getHeatmapTableSelector], ({ xAxisLabel: { fontSize, fontFamily, padding, formatter, width }, timeZone }, { xDomain }): number => { - if (xDomain.scaleType !== ScaleType.Time) { + if (xDomain.scaleConfig.type !== ScaleType.Time) { return 0; } if (typeof width === 'number') { 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 4d9eacbb6c..e5aa73ee4f 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 @@ -21,6 +21,7 @@ import { ChartTypes } from '../..'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes } from '../../../specs/constants'; import { Dimensions } from '../../../utils/dimensions'; +import { getXScaleConfig } from '../scales/get_scale_config'; import { computeSeriesDomains } from '../state/utils/utils'; import { computeXScale } from '../utils/scales'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; @@ -1460,7 +1461,7 @@ describe('Crosshair utils linear scale', () => { domain: [0.5, 3.5], isBandScale: true, minInterval: 1, - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), type: 'xDomain', }, totalBarsInCluster: 1, @@ -1491,7 +1492,7 @@ describe('Crosshair utils linear scale', () => { domain: [-0.5, 2.5], isBandScale: true, minInterval: 1, - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), type: 'xDomain', }, totalBarsInCluster: barSeries.length, @@ -1522,7 +1523,7 @@ describe('Crosshair utils linear scale', () => { domain: [0.5, 3.5], isBandScale: true, minInterval: 1, - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), type: 'xDomain', }, totalBarsInCluster: 1, @@ -1553,7 +1554,7 @@ describe('Crosshair utils linear scale', () => { domain: [-0.5, 2.5], isBandScale: true, minInterval: 1, - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), type: 'xDomain', }, totalBarsInCluster: barSeries.length, diff --git a/src/chart_types/xy_chart/domains/nice.ts b/src/chart_types/xy_chart/domains/nice.ts new file mode 100644 index 0000000000..665fb4d417 --- /dev/null +++ b/src/chart_types/xy_chart/domains/nice.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** @internal */ +export function areAllNiceDomain(nice: Array) { + return nice.length > 0 && nice.every((d) => d); +} diff --git a/src/chart_types/xy_chart/domains/types.ts b/src/chart_types/xy_chart/domains/types.ts index 1b00986de6..fedbd9b56d 100644 --- a/src/chart_types/xy_chart/domains/types.ts +++ b/src/chart_types/xy_chart/domains/types.ts @@ -18,35 +18,30 @@ */ import { ScaleContinuousType } from '../../../scales'; -import { ScaleType } from '../../../scales/constants'; import { LogScaleOptions } from '../../../scales/scale_continuous'; import { OrdinalDomain, ContinuousDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; +import { ScaleConfig } from '../scales/get_scale_config'; +import { XScaleType } from '../utils/specs'; /** @internal */ -export interface BaseDomain { - scaleType: typeof ScaleType.Ordinal | ScaleContinuousType; +export type XDomain = Pick & { + type: 'xDomain'; + scaleConfig: ScaleConfig; /* if the scale needs to be a band scale: used when displaying bars */ isBandScale: boolean; -} + /* the minimum interval of the scale if not-ordinal band-scale */ + minInterval: number; + /** if x domain is time, we should also specify the timezone */ + timeZone?: string; + domain: OrdinalDomain | ContinuousDomain; +}; /** @internal */ -export type XDomain = BaseDomain & - Pick & { - type: 'xDomain'; - /* the minimum interval of the scale if not-ordinal band-scale */ - minInterval: number; - /** if x domain is time, we should also specify the timezone */ - timeZone?: string; - domain: OrdinalDomain | ContinuousDomain; - }; - -/** @internal */ -export type YDomain = BaseDomain & - LogScaleOptions & { - type: 'yDomain'; - isBandScale: false; - scaleType: ScaleContinuousType; - groupId: GroupId; - domain: ContinuousDomain; - }; +export type YDomain = LogScaleOptions & { + type: 'yDomain'; + scaleConfig: ScaleConfig; + isBandScale: false; + groupId: GroupId; + domain: ContinuousDomain; +}; 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 01f7dc5603..f799e70020 100644 --- a/src/chart_types/xy_chart/domains/x_domain.test.ts +++ b/src/chart_types/xy_chart/domains/x_domain.test.ts @@ -22,6 +22,7 @@ import { MockSeriesSpecs } from '../../../mocks/specs'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes, Direction, BinAgg } from '../../../specs/constants'; import { Logger } from '../../../utils/logger'; +import { getXScaleConfig } from '../scales/get_scale_config'; import { getDataSeriesFromSpecs } from '../utils/series'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; import { convertXScaleTypes, findMinInterval, mergeXDomain } from './x_domain'; @@ -33,16 +34,10 @@ jest.mock('../../../utils/logger', () => ({ })); describe('X Domain', () => { - test('Should return null when missing specs or specs types', () => { + test('Should return a default scale when missing specs or specs types', () => { const seriesSpecs: BasicSeriesSpec[] = []; const mainXScale = convertXScaleTypes(seriesSpecs); - expect(mainXScale).toBe(null); - }); - - test('should throw if we miss calling merge X domain without specs configured', () => { - expect(() => { - mergeXDomain([], new Set()); - }).toThrow(); + expect(mainXScale).not.toBeNull(); }); test('Should return correct scale type with single bar', () => { @@ -54,7 +49,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), isBandScale: true, }); }); @@ -68,7 +63,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleType: ScaleType.Ordinal, + scaleConfig: getXScaleConfig(ScaleType.Ordinal), isBandScale: true, }); }); @@ -82,7 +77,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), isBandScale: false, }); }); @@ -96,7 +91,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), isBandScale: false, timeZone: 'utc-3', }); @@ -116,7 +111,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), isBandScale: false, timeZone: 'utc-3', }); @@ -136,7 +131,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), isBandScale: false, timeZone: 'utc', }); @@ -155,7 +150,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleType: ScaleType.Ordinal, + scaleConfig: getXScaleConfig(ScaleType.Ordinal), isBandScale: false, }); }); @@ -172,7 +167,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleType: ScaleType.Ordinal, + scaleConfig: getXScaleConfig(ScaleType.Ordinal), isBandScale: true, }); }); @@ -190,7 +185,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), isBandScale: true, }); }); @@ -497,14 +492,14 @@ describe('X Domain', () => { ], xValues, customDomain, - ScaleType.Ordinal, + { type: ScaleType.Ordinal, nice: false }, ); expect(getResult).not.toThrow(); const mergedDomain = getResult(); expect(mergedDomain.domain).toEqual([0, 'a', 2, 5, 7]); - expect(mergedDomain.scaleType).toEqual(ScaleType.Ordinal); + expect(mergedDomain.scaleConfig.type).toEqual(ScaleType.Ordinal); }); test('Should merge multi bar/line ordinal series correctly', () => { diff --git a/src/chart_types/xy_chart/domains/x_domain.ts b/src/chart_types/xy_chart/domains/x_domain.ts index dd260b7355..a9e377dd8c 100644 --- a/src/chart_types/xy_chart/domains/x_domain.ts +++ b/src/chart_types/xy_chart/domains/x_domain.ts @@ -23,8 +23,10 @@ import { ScaleType } from '../../../scales/constants'; import { compareByValueAsc, identity } from '../../../utils/common'; import { computeContinuousDataDomain, computeOrdinalDataDomain } from '../../../utils/domain'; import { Logger } from '../../../utils/logger'; +import { getXScaleConfig, ScaleConfig } from '../scales/get_scale_config'; import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_type_utils'; import { BasicSeriesSpec, CustomXDomain, SeriesTypes, XScaleType } from '../utils/specs'; +import { areAllNiceDomain } from './nice'; import { XDomain } from './types'; /** @@ -40,21 +42,18 @@ export function mergeXDomain( specs: Optional, 'seriesType'>[], xValues: Set, customXDomain?: CustomXDomain, - fallbackScale?: XScaleType, + fallbackScale?: ScaleConfig, ): XDomain { - const mainXScaleType = convertXScaleTypes(specs); - if (!mainXScaleType) { - throw new Error(`Cannot merge the domain. Missing X scale types ${JSON.stringify(specs)}`); - } + const { scaleConfig, isBandScale, timeZone } = convertXScaleTypes(specs); const values = [...xValues.values()]; let seriesXComputedDomains; let minInterval = 0; - if (mainXScaleType.scaleType === ScaleType.Ordinal || fallbackScale === ScaleType.Ordinal) { - if (mainXScaleType.scaleType !== ScaleType.Ordinal) { + if (scaleConfig.type === ScaleType.Ordinal || fallbackScale?.type === ScaleType.Ordinal) { + if (scaleConfig.type !== ScaleType.Ordinal) { Logger.warn( - `Each X value in a ${mainXScaleType.scaleType} x scale needs be be a number. Using ordinal x scale as fallback.`, + `Each X value in a ${scaleConfig.type} x scale needs be be a number. Using ordinal x scale as fallback.`, ); } @@ -63,10 +62,10 @@ export function mergeXDomain( if (Array.isArray(customXDomain)) { seriesXComputedDomains = customXDomain; } else { - if (fallbackScale === ScaleType.Ordinal) { + if (fallbackScale?.type === ScaleType.Ordinal) { Logger.warn(`xDomain ignored for fallback ordinal scale. Options to resolve: -1) Correct data to match ${mainXScaleType.scaleType} scale type (see previous warning) +1) Correct data to match ${scaleConfig.type} scale type (see previous warning) 2) Change xScaleType to ordinal and set xDomain to Domain array`); } else { Logger.warn( @@ -119,11 +118,11 @@ export function mergeXDomain( return { type: 'xDomain', - scaleType: fallbackScale ?? mainXScaleType.scaleType, - isBandScale: mainXScaleType.isBandScale, + scaleConfig: fallbackScale ?? scaleConfig, + isBandScale, domain: seriesXComputedDomains, minInterval, - timeZone: mainXScaleType.timeZone, + timeZone, logBase: customXDomain && 'logBase' in customXDomain ? customXDomain.logBase : undefined, }; } @@ -184,35 +183,54 @@ export function findMinInterval(xValues: number[]): number { export function convertXScaleTypes( specs: Optional, 'seriesType'>[], ): { - scaleType: XScaleType; + scaleConfig: ScaleConfig; isBandScale: boolean; timeZone?: string; -} | null { +} { const seriesTypes = new Set(); const scaleTypes = new Set(); const timeZones = new Set(); + const niceDomainConfigs: Array = []; specs.forEach((spec) => { + const scaleConfig = getXScaleConfig(spec.xScaleType); + niceDomainConfigs.push(scaleConfig.nice); seriesTypes.add(spec.seriesType); - scaleTypes.add(spec.xScaleType); + scaleTypes.add(scaleConfig.type); if (spec.timeZone) { timeZones.add(spec.timeZone.toLowerCase()); } }); if (specs.length === 0 || seriesTypes.size === 0 || scaleTypes.size === 0) { - return null; + return { + scaleConfig: { + type: ScaleType.Linear, + nice: true, + }, + isBandScale: false, + }; } + const nice = areAllNiceDomain(niceDomainConfigs); const isBandScale = seriesTypes.has(SeriesTypes.Bar); if (scaleTypes.size === 1) { const scaleType = scaleTypes.values().next().value; - let timeZone: string | undefined; - if (scaleType === ScaleType.Time) { - timeZone = timeZones.size > 1 ? 'utc' : timeZones.values().next().value; - } - return { scaleType, isBandScale, timeZone }; + const timeZone = timeZones.size > 1 ? 'utc' : timeZones.values().next().value; + return { scaleConfig: { type: scaleType, nice }, isBandScale, timeZone }; } if (scaleTypes.size > 1 && scaleTypes.has(ScaleType.Ordinal)) { - return { scaleType: ScaleType.Ordinal, isBandScale }; + return { + scaleConfig: { + type: ScaleType.Ordinal, + nice, + }, + isBandScale, + }; } - return { scaleType: ScaleType.Linear, isBandScale }; + return { + scaleConfig: { + type: ScaleType.Linear, + nice, + }, + isBandScale, + }; } 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 f37f168a8b..e52a764268 100644 --- a/src/chart_types/xy_chart/domains/y_domain.test.ts +++ b/src/chart_types/xy_chart/domains/y_domain.test.ts @@ -25,6 +25,7 @@ import { SpecTypes } from '../../../specs/constants'; import { Position } from '../../../utils/common'; import { BARCHART_1Y0G } from '../../../utils/data_samples/test_dataset'; import { Logger } from '../../../utils/logger'; +import { getYScaleConfig } from '../scales/get_scale_config'; import { computeSeriesDomainsSelector } from '../state/selectors/compute_series_domains'; import { BasicSeriesSpec, SeriesTypes, DEFAULT_GLOBAL_ID, StackMode } from '../utils/specs'; import { coerceYScaleTypes, groupSeriesByYGroup } from './y_domain'; @@ -88,7 +89,7 @@ describe('Y Domain', () => { type: 'yDomain', groupId: DEFAULT_GLOBAL_ID, domain: [2, 12], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, }, ]); @@ -116,7 +117,7 @@ describe('Y Domain', () => { type: 'yDomain', groupId: DEFAULT_GLOBAL_ID, domain: [0, 12], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, }, ]); @@ -151,14 +152,14 @@ describe('Y Domain', () => { { groupId: 'a', domain: [2, 12], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, type: 'yDomain', }, { groupId: 'b', domain: [2, 10], - scaleType: ScaleType.Log, + scaleConfig: getYScaleConfig(ScaleType.Log), isBandScale: false, type: 'yDomain', }, @@ -189,7 +190,7 @@ describe('Y Domain', () => { { groupId: 'a', domain: [0, 17], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, type: 'yDomain', }, @@ -218,7 +219,7 @@ describe('Y Domain', () => { { groupId: 'a', domain: [0, 12], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, type: 'yDomain', }, @@ -384,8 +385,9 @@ describe('Y Domain', () => { }); test('Should return a default Scale Linear for YScaleType when there are no specs', () => { - const specs: Pick[] = []; - expect(coerceYScaleTypes(specs)).toBe(ScaleType.Linear); + const specs: BasicSeriesSpec['yScaleType'][] = []; + const specScaleConfigs = specs.map(getYScaleConfig); + expect(coerceYScaleTypes(specScaleConfigs)).toEqual(getYScaleConfig(ScaleType.Linear)); }); test('Should merge Y domain accounting for custom domain limits: complete bounded domain', () => { @@ -409,7 +411,7 @@ describe('Y Domain', () => { type: 'yDomain', groupId: 'a', domain: [0, 20], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, }, ]); @@ -435,7 +437,7 @@ describe('Y Domain', () => { type: 'yDomain', groupId: 'a', domain: [0, 12], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, }, ]); @@ -484,7 +486,7 @@ describe('Y Domain', () => { type: 'yDomain', groupId: 'a', domain: [2, 20], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, }, ]); @@ -533,7 +535,7 @@ describe('Y Domain', () => { { groupId: 'a', domain: [0, 1], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, type: 'yDomain', }, @@ -562,7 +564,7 @@ describe('Y Domain', () => { type: 'yDomain', groupId: 'a', domain: [0, 1], - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, }, ]); diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts index 4bb6199577..275308e742 100644 --- a/src/chart_types/xy_chart/domains/y_domain.ts +++ b/src/chart_types/xy_chart/domains/y_domain.ts @@ -23,11 +23,13 @@ import { identity } from '../../../utils/common'; import { computeContinuousDataDomain, ContinuousDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; +import { getYScaleConfig, ScaleConfig } from '../scales/get_scale_config'; import { getSpecDomainGroupId } from '../state/utils/spec'; import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_type_utils'; import { groupBy } from '../utils/group_data_series'; import { DataSeries } from '../utils/series'; import { BasicSeriesSpec, YDomainRange, SeriesTypes, StackMode } from '../utils/specs'; +import { areAllNiceDomain } from './nice'; import { YDomain } from './types'; export type YBasicSeriesSpec = Pick< @@ -68,9 +70,7 @@ function mergeYDomainForGroup( if (dataSeries.length === 0) { return null; } - const yScaleTypes = dataSeries.map(({ spec: { yScaleType } }) => ({ - yScaleType, - })); + const yScaleTypes = dataSeries.map(({ spec: { yScaleType } }) => getYScaleConfig(yScaleType)); const groupYScaleType = coerceYScaleTypes(yScaleTypes); const [{ stackMode, spec }] = dataSeries; const groupId = getSpecDomainGroupId(spec); @@ -119,7 +119,7 @@ function mergeYDomainForGroup( return { type: 'yDomain', isBandScale: false, - scaleType: groupYScaleType, + scaleConfig: groupYScaleType, groupId, domain, logBase: customDomain?.logBase, @@ -213,19 +213,23 @@ export function isStackedSpec(spec: YBasicSeriesSpec, histogramEnabled: boolean) * @returns {ScaleContinuousType} * @internal */ -export function coerceYScaleTypes(scales: { yScaleType: ScaleContinuousType }[]): ScaleContinuousType { - const scaleTypes = new Set(); - scales.forEach(({ yScaleType }) => { - scaleTypes.add(yScaleType); - }); - return coerceYScale(scaleTypes); -} - -function coerceYScale(scaleTypes: Set): ScaleContinuousType { - if (scaleTypes.size === 1) { - const scales = scaleTypes.values(); - const { value } = scales.next(); - return value; - } - return ScaleType.Linear; +export function coerceYScaleTypes(scales: ScaleConfig[]): ScaleConfig { + const scaleCollection = scales.reduce( + (acc, scale) => { + acc.types.add(scale.type); + acc.nice.push(scale.nice); + return acc; + }, + { + types: new Set(), + nice: [] as Array, + }, + ); + const nice = areAllNiceDomain(scaleCollection.nice); + return scaleCollection.types.size === 1 + ? { type: scaleCollection.types.values().next().value, nice } + : { + type: ScaleType.Linear, + nice, + }; } diff --git a/src/chart_types/xy_chart/legend/legend.ts b/src/chart_types/xy_chart/legend/legend.ts index 78180cd85e..71ed469860 100644 --- a/src/chart_types/xy_chart/legend/legend.ts +++ b/src/chart_types/xy_chart/legend/legend.ts @@ -24,6 +24,7 @@ import { SortSeriesByConfig, TickFormatterOptions } from '../../../specs'; import { Color } from '../../../utils/common'; import { BandedAccessorType } from '../../../utils/geometry'; import { getLegendCompareFn, SeriesCompareFn } from '../../../utils/series_sort'; +import { getXScaleConfig } from '../scales/get_scale_config'; import { getAxesSpecForSpecId, getSpecsById } from '../state/utils/spec'; import { LastValues } from '../state/utils/types'; import { Y0_ACCESSOR_POSTFIX, Y1_ACCESSOR_POSTFIX } from '../tooltip/tooltip'; @@ -137,6 +138,7 @@ export function computeLegend( const lastValue = lastValues.get(key); const seriesIdentifier = getSeriesIdentifierFromDataSeries(series); + const scaleConf = getXScaleConfig(spec.xScaleType); legendItems.push({ color, label: labelY1, @@ -145,7 +147,7 @@ export function computeLegend( isSeriesHidden, isItemHidden: hideInLegend, isToggleable: true, - defaultExtra: getLegendExtra(showLegendExtra, spec.xScaleType, formatter, 'y1', lastValue), + defaultExtra: getLegendExtra(showLegendExtra, scaleConf.type, formatter, 'y1', lastValue), path: [{ index: 0, value: seriesIdentifier.key }], keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()], }); @@ -159,7 +161,7 @@ export function computeLegend( isSeriesHidden, isItemHidden: hideInLegend, isToggleable: true, - defaultExtra: getLegendExtra(showLegendExtra, spec.xScaleType, formatter, 'y0', lastValue), + defaultExtra: getLegendExtra(showLegendExtra, scaleConf.type, formatter, 'y0', lastValue), path: [{ index: 0, value: seriesIdentifier.key }], keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()], }); diff --git a/src/chart_types/xy_chart/scales/get_scale_config.ts b/src/chart_types/xy_chart/scales/get_scale_config.ts new file mode 100644 index 0000000000..2fedab9d4b --- /dev/null +++ b/src/chart_types/xy_chart/scales/get_scale_config.ts @@ -0,0 +1,49 @@ +/* + * 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 { ScaleContinuousType } from '../../../scales'; +import { ScaleType } from '../../../scales/constants'; +import { BasicSeriesSpec, XScaleType } from '../utils/specs'; +import { X_SCALE_DEFAULT, Y_SCALE_DEFAULT } from './scale_defaults'; + +/** @internal */ +export interface ScaleConfig { + type: T; + nice: boolean; +} + +/** @internal */ +export function getXScaleConfig(scaleType: BasicSeriesSpec['xScaleType']): ScaleConfig { + return getScaleConfig(scaleType, X_SCALE_DEFAULT); +} + +/** @internal */ +export function getYScaleConfig(scaleType: BasicSeriesSpec['yScaleType']): ScaleConfig { + return getScaleConfig(scaleType, Y_SCALE_DEFAULT); +} + +/** @internal */ +function getScaleConfig(scaleType: T | ScaleConfig, defaults: ScaleConfig): ScaleConfig { + if (typeof scaleType === 'object') { + return scaleType; + } + return { + ...defaults, + type: scaleType, + }; +} diff --git a/src/chart_types/xy_chart/scales/scale_defaults.ts b/src/chart_types/xy_chart/scales/scale_defaults.ts new file mode 100644 index 0000000000..9c065998d9 --- /dev/null +++ b/src/chart_types/xy_chart/scales/scale_defaults.ts @@ -0,0 +1,31 @@ +/* + * 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 { ScaleType } from '../../../scales/constants'; + +/** @internal */ +export const X_SCALE_DEFAULT = { + type: ScaleType.Ordinal, + nice: false, +}; + +/** @internal */ +export const Y_SCALE_DEFAULT = { + type: ScaleType.Linear, + nice: false, +}; diff --git a/src/chart_types/xy_chart/state/utils/utils.test.ts b/src/chart_types/xy_chart/state/utils/utils.test.ts index 30e2685caf..cd319220c6 100644 --- a/src/chart_types/xy_chart/state/utils/utils.test.ts +++ b/src/chart_types/xy_chart/state/utils/utils.test.ts @@ -30,6 +30,7 @@ import { BARCHART_1Y0G, BARCHART_1Y1G } from '../../../../utils/data_samples/tes import { ContinuousDomain, Range } from '../../../../utils/domain'; import { SpecId } from '../../../../utils/ids'; import { PointShape } from '../../../../utils/themes/theme'; +import { getXScaleConfig, getYScaleConfig } from '../../scales/get_scale_config'; import { getSeriesIndex, XYChartSeriesIdentifier } from '../../utils/series'; import { BasicSeriesSpec, HistogramModeAlignments, SeriesColorAccessorFn } from '../../utils/specs'; import { computeSeriesDomainsSelector } from '../selectors/compute_series_domains'; @@ -80,14 +81,14 @@ describe('Chart State utils', () => { expect(domains.xDomain).toEqual({ domain: [0, 3], isBandScale: false, - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), minInterval: 1, type: 'xDomain', }); expect(domains.yDomains).toEqual([ { domain: [0, 10], - scaleType: ScaleType.Log, + scaleConfig: getYScaleConfig(ScaleType.Log), groupId: 'group1', isBandScale: false, type: 'yDomain', @@ -96,7 +97,7 @@ describe('Chart State utils', () => { }, { domain: [0, 10], - scaleType: ScaleType.Log, + scaleConfig: getYScaleConfig(ScaleType.Log), groupId: 'group2', isBandScale: false, type: 'yDomain', @@ -132,14 +133,14 @@ describe('Chart State utils', () => { expect(domains.xDomain).toEqual({ domain: [0, 3], isBandScale: false, - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), minInterval: 1, type: 'xDomain', }); expect(domains.yDomains).toEqual([ { domain: [0, 5], - scaleType: ScaleType.Log, + scaleConfig: getYScaleConfig(ScaleType.Log), groupId: 'group1', isBandScale: false, type: 'yDomain', @@ -148,7 +149,7 @@ describe('Chart State utils', () => { }, { domain: [0, 9], - scaleType: ScaleType.Log, + scaleConfig: getYScaleConfig(ScaleType.Log), groupId: 'group2', isBandScale: false, type: 'yDomain', diff --git a/src/chart_types/xy_chart/state/utils/utils.ts b/src/chart_types/xy_chart/state/utils/utils.ts index 39de5946be..0636c1cb66 100644 --- a/src/chart_types/xy_chart/state/utils/utils.ts +++ b/src/chart_types/xy_chart/state/utils/utils.ts @@ -146,15 +146,18 @@ export function computeSeriesDomains( const xDomain = mergeXDomain(seriesSpecs, xValues, customXDomain, fallbackScale); // fill series with missing x values - const filledDataSeries = fillSeries(dataSeries, xValues, xDomain.scaleType); + const filledDataSeries = fillSeries(dataSeries, xValues, xDomain.scaleConfig.type); const seriesSortFn = getRenderingCompareFn(sortSeriesBy, (a: SeriesIdentifier, b: SeriesIdentifier) => { return defaultXYSeriesSort(a as DataSeries, b as DataSeries); }); - const formattedDataSeries = getFormattedDataSeries(seriesSpecs, filledDataSeries, xValues, xDomain.scaleType).sort( - seriesSortFn, - ); + const formattedDataSeries = getFormattedDataSeries( + seriesSpecs, + filledDataSeries, + xValues, + xDomain.scaleConfig.type, + ).sort(seriesSortFn); // let's compute the yDomains after computing all stacked values const yDomains = mergeYDomain(formattedDataSeries, customYDomainsByGroupId); 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 abb06a23af..f085123784 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -35,6 +35,7 @@ import { AxisId, GroupId } from '../../../utils/ids'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { AxisStyle, TextOffset } from '../../../utils/themes/theme'; import { XDomain, YDomain } from '../domains/types'; +import { getXScaleConfig, getYScaleConfig } from '../scales/get_scale_config'; import { computeAxesGeometriesSelector } from '../state/selectors/compute_axes_geometries'; import { computeAxisTicksDimensionsSelector } from '../state/selectors/compute_axis_ticks_dimensions'; import { getScale, SmallMultipleScales } from '../state/selectors/compute_small_multiple_scales'; @@ -191,14 +192,14 @@ describe('Axis computational utils', () => { }); const xDomain: XDomain = { type: 'xDomain', - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), domain: [0, 1], isBandScale: false, minInterval: 0, }; const yDomain: YDomain = { - scaleType: ScaleType.Linear, + scaleConfig: getYScaleConfig(ScaleType.Linear), groupId: 'group_1', type: 'yDomain', domain: [0, 1], @@ -281,7 +282,7 @@ describe('Axis computational utils', () => { const bboxCalculator = new SvgTextBBoxCalculator(); const xDomain: XDomain = { type: 'xDomain', - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), domain: [1551438000000, 1551441300000], isBandScale: false, minInterval: 0, @@ -419,7 +420,7 @@ describe('Axis computational utils', () => { { label: '0.7', position: 30 + rotationalOffset, value: 0.7 }, { label: '0.8', position: 20 + rotationalOffset, value: 0.8 }, { label: '0.9', position: 10 + rotationalOffset, value: 0.9 }, - { label: '1', position: 0 + rotationalOffset, value: 1 }, + { label: '1', position: rotationalOffset, value: 1 }, ]; expect(axisPositions).toEqual(expectedAxisPositions); }); @@ -428,7 +429,7 @@ describe('Axis computational utils', () => { const enableHistogramMode = true; const xBandDomain: XDomain = { type: 'xDomain', - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), domain: [0, 100], isBandScale: true, minInterval: 10, @@ -450,7 +451,7 @@ describe('Axis computational utils', () => { const enableHistogramMode = true; const xBandDomain: XDomain = { type: 'xDomain', - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), domain: [1560438420000, 1560438510000], isBandScale: true, minInterval: 90000, @@ -489,7 +490,7 @@ describe('Axis computational utils', () => { const enableHistogramMode = true; const xBandDomain: XDomain = { type: 'xDomain', - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), domain: [1560438420000, 1560438420000], // a single datum scale will have the same value for domain start & end isBandScale: true, minInterval: 90000, @@ -1528,7 +1529,7 @@ describe('Axis computational utils', () => { isBandScale: false, domain: [1547190000000, 1547622000000], minInterval: 86400000, - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), }; const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; @@ -1563,7 +1564,7 @@ describe('Axis computational utils', () => { timeZone: 'utc+1', domain: [1547190000000, 1547622000000], minInterval: 86400000, - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), }; const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; @@ -1605,7 +1606,7 @@ describe('Axis computational utils', () => { isBandScale: false, domain: [1547190000000, 1547622000000], minInterval: 86400000, - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), }; const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; @@ -1645,7 +1646,7 @@ describe('Axis computational utils', () => { isBandScale: false, domain: [1547190000000, 1547622000000], minInterval: 86400000, - scaleType: ScaleType.Time, + scaleConfig: getXScaleConfig(ScaleType.Time), }; const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; diff --git a/src/chart_types/xy_chart/utils/scales.test.ts b/src/chart_types/xy_chart/utils/scales.test.ts index b2b9c3a4d7..6241d02008 100644 --- a/src/chart_types/xy_chart/utils/scales.test.ts +++ b/src/chart_types/xy_chart/utils/scales.test.ts @@ -27,7 +27,10 @@ describe('Series scales', () => { isBandScale: true, domain: [0, 3], minInterval: 1, - scaleType: ScaleType.Linear, + scaleConfig: { + type: ScaleType.Linear, + nice: false, + }, }; const xDomainOrdinal: XDomain = { @@ -35,7 +38,10 @@ describe('Series scales', () => { isBandScale: true, domain: ['a', 'b'], minInterval: 1, - scaleType: ScaleType.Ordinal, + scaleConfig: { + type: ScaleType.Ordinal, + nice: false, + }, }; test('should compute X Scale linear min, max with bands', () => { @@ -69,7 +75,10 @@ describe('Series scales', () => { isBandScale: true, domain: [singleDomainValue, singleDomainValue], minInterval, - scaleType: ScaleType.Linear, + scaleConfig: { + type: ScaleType.Linear, + nice: false, + }, }; const enableHistogramMode = true; @@ -92,7 +101,10 @@ describe('Series scales', () => { isBandScale: true, domain: [singleDomainValue, singleDomainValue], minInterval, - scaleType: ScaleType.Linear, + scaleConfig: { + type: ScaleType.Linear, + nice: false, + }, }; const enableHistogramMode = false; @@ -126,7 +138,10 @@ describe('Series scales', () => { isBandScale: true, domain: [0, 3], minInterval: 1, - scaleType: ScaleType.Linear, + scaleConfig: { + type: ScaleType.Linear, + nice: false, + }, }; const maxRange = 120; const scaleOver0 = computeXScale({ diff --git a/src/chart_types/xy_chart/utils/scales.ts b/src/chart_types/xy_chart/utils/scales.ts index 985298067b..5f7d44e181 100644 --- a/src/chart_types/xy_chart/utils/scales.ts +++ b/src/chart_types/xy_chart/utils/scales.ts @@ -54,17 +54,14 @@ interface XScaleOptions { /** * Compute the x scale used to align geometries to the x axis. - * @param xDomain the x domain - * @param totalBarsInCluster the total number of grouped series - * @param axisLength the length of the x axis * @internal */ export function computeXScale(options: XScaleOptions): Scale { const { xDomain, totalBarsInCluster, range, barsPadding, enableHistogramMode, ticks, integersOnly } = options; - const { scaleType, minInterval, domain, isBandScale, timeZone, logBase } = xDomain; + const { scaleConfig, minInterval, domain, isBandScale, timeZone, logBase } = xDomain; const rangeDiff = Math.abs(range[1] - range[0]); const isInverse = range[1] < range[0]; - if (scaleType === ScaleType.Ordinal) { + if (scaleConfig.type === ScaleType.Ordinal) { const dividend = totalBarsInCluster > 0 ? totalBarsInCluster : 1; const bandwidth = rangeDiff / (domain.length * dividend); return new ScaleBand(domain, range, bandwidth, barsPadding); @@ -77,15 +74,16 @@ export function computeXScale(options: XScaleOptions): Scale { const adjustedDomain = [domainMin, adjustedDomainMax]; const intervalCount = (adjustedDomain[1] - adjustedDomain[0]) / minInterval; - const intervalCountOffest = isSingleValueHistogram ? 0 : 1; - const bandwidth = rangeDiff / (intervalCount + intervalCountOffest); + const intervalCountOffset = isSingleValueHistogram ? 0 : 1; + const bandwidth = rangeDiff / (intervalCount + intervalCountOffset); const { start, end } = getBandScaleRange(isInverse, isSingleValueHistogram, range[0], range[1], bandwidth); - const scale = new ScaleContinuous( + return new ScaleContinuous( { - type: scaleType, + type: scaleConfig.type, domain: adjustedDomain, range: [start, end], + nice: scaleConfig.nice, }, { bandwidth: totalBarsInCluster > 0 ? bandwidth / totalBarsInCluster : bandwidth, @@ -98,11 +96,9 @@ export function computeXScale(options: XScaleOptions): Scale { logBase, }, ); - - return scale; } return new ScaleContinuous( - { type: scaleType, domain, range }, + { type: scaleConfig.type, domain, range, nice: scaleConfig.nice }, { bandwidth: 0, minInterval, @@ -125,19 +121,17 @@ interface YScaleOptions { /** * Compute the y scales, one per groupId for the y axis. - * @param yDomains the y domains - * @param axisLength the axisLength of the y axis * @internal */ export function computeYScales(options: YScaleOptions): Map { - const yScales: Map = new Map(); const { yDomains, range, ticks, integersOnly } = options; - yDomains.forEach(({ scaleType: type, domain, groupId, logBase, logMinLimit }) => { + return yDomains.reduce((yScales, { scaleConfig: { type, nice }, domain, groupId, logBase, logMinLimit }) => { const yScale = new ScaleContinuous( { type, domain, range, + nice, }, { ticks, @@ -147,7 +141,6 @@ export function computeYScales(options: YScaleOptions): Map { }, ); yScales.set(groupId, yScale); - }); - - return yScales; + return yScales; + }, new Map()); } diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index 5bb674e656..5afe82ab1e 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -28,6 +28,7 @@ import { GroupId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; import { ColorConfig } from '../../../utils/themes/theme'; import { groupSeriesByYGroup, isHistogramEnabled, isStackedSpec } from '../domains/y_domain'; +import { ScaleConfig } from '../scales/get_scale_config'; import { SmallMultiplesGroupBy } from '../state/selectors/get_specs'; import { applyFitFunctionToDataSeries } from './fit_function_utils'; import { groupBy } from './group_data_series'; @@ -359,7 +360,7 @@ export function getDataSeriesFromSpecs( xValues: Set; smVValues: Set; smHValues: Set; - fallbackScale?: XScaleType; + fallbackScale?: ScaleConfig; } { let globalDataSeries: DataSeries[] = []; const mutatedXValueSums = new Map(); @@ -454,7 +455,7 @@ export function getDataSeriesFromSpecs( // keep the user order for ordinal scales xValues, ...smallMultipleUniqueValues, - fallbackScale: !isOrdinalScale && !isNumberArray ? ScaleType.Ordinal : undefined, + fallbackScale: !isOrdinalScale && !isNumberArray ? { type: ScaleType.Ordinal, nice: false } : undefined, }; } diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 018cdc304f..051c139c73 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -44,6 +44,7 @@ import { } from '../../../utils/themes/theme'; import { PrimitiveValue } from '../../partition_chart/layout/utils/group_by_rollup'; import { AnnotationTooltipFormatter, CustomAnnotationTooltip } from '../annotations/types'; +import { ScaleConfig } from '../scales/get_scale_config'; import { XYChartSeriesIdentifier, DataSeriesDatum } from './series'; /** @public */ @@ -452,7 +453,7 @@ export interface SeriesScales { * The x axis scale type * @defaultValue `ordinal` {@link (ScaleType:type) | ScaleType.Ordinal} */ - xScaleType: XScaleType; + xScaleType: XScaleType | ScaleConfig; /** * If using a ScaleType.Time this timezone identifier is required to * compute a nice set of xScale ticks. Can be any IANA zone supported by @@ -464,7 +465,7 @@ export interface SeriesScales { * The y axis scale type * @defaultValue `linear` {@link (ScaleType:type) | ScaleType.Linear} */ - yScaleType: ScaleContinuousType; + yScaleType: ScaleContinuousType | ScaleConfig; /** * if true, the min y value is set to the minimum domain value, 0 otherwise * @deprecated use `domain.fit` instead diff --git a/src/scales/scale_continuous.test.ts b/src/scales/scale_continuous.test.ts index 84345fe663..6305260c56 100644 --- a/src/scales/scale_continuous.test.ts +++ b/src/scales/scale_continuous.test.ts @@ -21,6 +21,7 @@ import { DateTime, Settings } from 'luxon'; import { ScaleContinuous, ScaleBand } from '.'; import { XDomain } from '../chart_types/xy_chart/domains/types'; +import { getXScaleConfig } from '../chart_types/xy_chart/scales/get_scale_config'; import { computeXScale } from '../chart_types/xy_chart/utils/scales'; import { ContinuousDomain, Range } from '../utils/domain'; import { LOG_MIN_ABS_DOMAIN, ScaleType } from './constants'; @@ -105,7 +106,7 @@ describe('Scale Continuous', () => { domain: [0, 2], isBandScale: true, minInterval: 1, - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), type: 'xDomain', }; @@ -146,7 +147,7 @@ describe('Scale Continuous', () => { domain: [0, 100], isBandScale: true, minInterval: 10, - scaleType: ScaleType.Linear, + scaleConfig: getXScaleConfig(ScaleType.Linear), type: 'xDomain', }; // we tweak the maxRange removing the bandwidth to correctly compute diff --git a/src/scales/scale_continuous.ts b/src/scales/scale_continuous.ts index 3c7d258bc6..67d457a26f 100644 --- a/src/scales/scale_continuous.ts +++ b/src/scales/scale_continuous.ts @@ -151,6 +151,7 @@ interface ScaleData { domain: any[]; /** The data output range */ range: Range; + nice?: boolean; } /** @@ -261,8 +262,10 @@ export class ScaleContinuous implements Scale { private readonly d3Scale: D3Scale; - constructor(scaleData: ScaleData, options?: Partial) { - const { type, domain, range } = scaleData; + constructor( + { type = ScaleType.Linear, domain = [0, 1], range = [0, 1], nice = false }: ScaleData, + options?: Partial, + ) { const { bandwidth, minInterval, @@ -286,8 +289,8 @@ export class ScaleContinuous implements Scale { } this.d3Scale.domain(this.domain); - if (type !== ScaleType.Time) { - (this.d3Scale as ScaleContinuousNumeric).domain(this.domain).nice(); + if (nice && type !== ScaleType.Time) { + (this.d3Scale as ScaleContinuousNumeric).domain(this.domain).nice(ticks); this.domain = this.d3Scale.domain(); } diff --git a/src/scales/scales.test.ts b/src/scales/scales.test.ts index bcb50ab5a5..9a8664ab0b 100644 --- a/src/scales/scales.test.ts +++ b/src/scales/scales.test.ts @@ -46,7 +46,12 @@ describe('Scale Test', () => { const data = [0, 10]; const minRange = 0; const maxRange = 100; - const linearScale = new ScaleContinuous({ type: ScaleType.Linear, domain: data, range: [minRange, maxRange] }); + const linearScale = new ScaleContinuous({ + type: ScaleType.Linear, + domain: data, + range: [minRange, maxRange], + nice: false, + }); const { domain, range } = linearScale; expect(domain).toEqual([0, 10]); expect(range).toEqual([minRange, maxRange]); @@ -66,7 +71,12 @@ describe('Scale Test', () => { const data = [date1, date3]; const minRange = 0; const maxRange = 100; - const timeScale = new ScaleContinuous({ type: ScaleType.Time, domain: data, range: [minRange, maxRange] }); + const timeScale = new ScaleContinuous({ + type: ScaleType.Time, + domain: data, + range: [minRange, maxRange], + nice: false, + }); const { domain, range } = timeScale; expect(domain).toEqual([date1, date3]); expect(range).toEqual([minRange, maxRange]); @@ -81,7 +91,12 @@ describe('Scale Test', () => { const data = [1, 10]; const minRange = 0; const maxRange = 100; - const logScale = new ScaleContinuous({ type: ScaleType.Log, domain: data, range: [minRange, maxRange] }); + const logScale = new ScaleContinuous({ + type: ScaleType.Log, + domain: data, + range: [minRange, maxRange], + nice: false, + }); const { domain, range } = logScale; expect(domain).toEqual([1, 10]); expect(range).toEqual([minRange, maxRange]); @@ -94,7 +109,12 @@ describe('Scale Test', () => { const data = [0, 10]; const minRange = 0; const maxRange = 100; - const logScale = new ScaleContinuous({ type: ScaleType.Log, domain: data, range: [minRange, maxRange] }); + const logScale = new ScaleContinuous({ + type: ScaleType.Log, + domain: data, + range: [minRange, maxRange], + nice: false, + }); const { domain, range } = logScale; expect(domain).toEqual([1, 10]); expect(range).toEqual([minRange, maxRange]); @@ -107,7 +127,12 @@ describe('Scale Test', () => { const data = [0, 10]; const minRange = 0; const maxRange = 100; - const sqrtScale = new ScaleContinuous({ type: ScaleType.Sqrt, domain: data, range: [minRange, maxRange] }); + const sqrtScale = new ScaleContinuous({ + type: ScaleType.Sqrt, + domain: data, + range: [minRange, maxRange], + nice: false, + }); const { domain, range } = sqrtScale; expect(domain).toEqual([0, 10]); expect(range).toEqual([minRange, maxRange]); @@ -165,7 +190,7 @@ describe('Scale Test', () => { const maxRange = 120; const bandwidth = maxRange / 3; const linearScale = new ScaleContinuous( - { type: ScaleType.Linear, domain: domainLinear, range: [minRange, maxRange - bandwidth] }, // we currently limit the range like that a band linear scale + { type: ScaleType.Linear, domain: domainLinear, range: [minRange, maxRange - bandwidth], nice: false }, // we currently limit the range like that a band linear scale { bandwidth, minInterval: 1 }, ); const ordinalScale = new ScaleBand(domainOrdinal, [minRange, maxRange]); @@ -188,6 +213,7 @@ describe('Scale Test', () => { type: ScaleType.Linear, domain: dataLinear, range: [minRange, maxRange - bandwidth], + nice: false, }, // we currently limit the range like that a band linear scale { bandwidth, minInterval: 1 }, ); diff --git a/stories/legend/11_legend_actions.tsx b/stories/legend/11_legend_actions.tsx index 143b913060..9cbc5b71d0 100644 --- a/stories/legend/11_legend_actions.tsx +++ b/stories/legend/11_legend_actions.tsx @@ -124,7 +124,6 @@ const getAction = (hideActions: boolean, anchorPosition: PopoverAnchorPosition): isOpen={popoverOpen} closePopover={() => setPopoverOpen(false)} panelPaddingSize="none" - withTitle anchorPosition={anchorPosition} ownFocus > From 521a15f96e33c95df07f15d9e739ff82860ff0a9 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 31 Mar 2021 23:27:53 +0200 Subject: [PATCH 03/11] refactor: compute scale API config separately --- .playground/playground.tsx | 2 +- .../heatmap/layout/viewmodel/viewmodel.ts | 4 +- .../state/selectors/get_heatmap_table.ts | 6 +- .../selectors/get_x_axis_right_overflow.ts | 2 +- .../crosshair_utils.linear_snap.test.ts | 55 ++-- .../crosshair_utils.ordinal_snap.test.ts | 30 +- src/chart_types/xy_chart/domains/types.ts | 38 +-- .../xy_chart/domains/x_domain.test.ts | 256 ++++++------------ src/chart_types/xy_chart/domains/x_domain.ts | 101 +++---- .../xy_chart/domains/y_domain.test.ts | 75 ++--- src/chart_types/xy_chart/domains/y_domain.ts | 35 +-- src/chart_types/xy_chart/legend/legend.ts | 4 +- .../rendering/rendering.lines.test.ts | 1 + ...{get_scale_config.ts => get_api_scales.ts} | 23 +- .../xy_chart/scales/scale_defaults.ts | 2 + src/chart_types/xy_chart/scales/types.ts | 25 ++ .../selectors/compute_axes_geometries.ts | 2 +- .../state/selectors/compute_series_domains.ts | 9 +- .../state/selectors/get_api_scale_configs.ts | 125 +++++++++ .../state/selectors/get_data_domain.ts | 18 ++ .../state/selectors/merge_y_custom_domains.ts | 12 - .../xy_chart/state/utils/utils.test.ts | 63 ++--- src/chart_types/xy_chart/state/utils/utils.ts | 31 +-- .../xy_chart/utils/axis_utils.test.ts | 63 ++--- src/chart_types/xy_chart/utils/axis_utils.ts | 10 - src/chart_types/xy_chart/utils/scales.test.ts | 51 +--- src/chart_types/xy_chart/utils/scales.ts | 18 +- src/chart_types/xy_chart/utils/series.ts | 7 +- src/chart_types/xy_chart/utils/specs.ts | 12 +- src/mocks/xy/domains.ts | 68 +++++ src/scales/scale_continuous.test.ts | 15 +- src/scales/scale_continuous.ts | 1 - stories/axes/8_custom_domain.tsx | 158 ++++++++--- stories/interactions/1_bar_clicks.tsx | 2 +- stories/line/2_w_axis.tsx | 10 +- 35 files changed, 733 insertions(+), 601 deletions(-) rename src/chart_types/xy_chart/scales/{get_scale_config.ts => get_api_scales.ts} (64%) create mode 100644 src/chart_types/xy_chart/scales/types.ts create mode 100644 src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts create mode 100644 src/chart_types/xy_chart/state/selectors/get_data_domain.ts create mode 100644 src/mocks/xy/domains.ts diff --git a/.playground/playground.tsx b/.playground/playground.tsx index ad08028b58..2c109e1183 100644 --- a/.playground/playground.tsx +++ b/.playground/playground.tsx @@ -19,7 +19,7 @@ import React from 'react'; -import { Example } from '../stories/small_multiples/7_sunbursts'; +import { Example } from '../stories/line/2_w_axis'; export class Playground extends React.Component { render() { diff --git a/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts b/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts index a37fd9911b..8fb492d5a7 100644 --- a/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts +++ b/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts @@ -108,7 +108,7 @@ export function shapeViewModel( let xValues = xDomain.domain as any[]; const timeScale = - xDomain.scaleConfig.type === ScaleType.Time + xDomain.type === ScaleType.Time ? new ScaleContinuous( { type: ScaleType.Time, @@ -314,7 +314,7 @@ export function shapeViewModel( * @param y */ const pickHighlightedArea: PickHighlightedArea = (x: Array, y: Array) => { - if (xDomain.scaleConfig.type !== ScaleType.Time) { + if (xDomain.type !== ScaleType.Time) { return null; } const [startValue, endValue] = x; diff --git a/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts b/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts index d6860eada2..647516a59d 100644 --- a/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts +++ b/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts @@ -24,6 +24,7 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { getAccessorValue } from '../../../../utils/accessor'; import { mergeXDomain } from '../../../xy_chart/domains/x_domain'; +import { getXAPIScale } from '../../../xy_chart/scales/get_api_scales'; import { HeatmapTable } from './compute_chart_dimensions'; import { getHeatmapSpecSelector } from './get_heatmap_spec'; @@ -72,7 +73,10 @@ export const getHeatmapTableSelector = createCachedSelector( }, ); - resultData.xDomain = mergeXDomain([{ xScaleType: spec.xScaleType }], resultData.xValues, xDomain); + resultData.xDomain = mergeXDomain( + { ...getXAPIScale(spec.xScaleType), isBandScale: false, ticks: 10, customDomain: xDomain }, + resultData.xValues, + ); // sort values by their predicates if (spec.xScaleType === ScaleType.Ordinal) { diff --git a/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts b/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts index 8879fdd3c1..7a2bccc20c 100644 --- a/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts +++ b/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts @@ -33,7 +33,7 @@ import { getHeatmapTableSelector } from './get_heatmap_table'; export const getXAxisRightOverflow = createCachedSelector( [getHeatmapConfigSelector, getHeatmapTableSelector], ({ xAxisLabel: { fontSize, fontFamily, padding, formatter, width }, timeZone }, { xDomain }): number => { - if (xDomain.scaleConfig.type !== ScaleType.Time) { + if (xDomain.type !== ScaleType.Time) { return 0; } if (typeof width === 'number') { 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 e5aa73ee4f..968fd1bace 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 @@ -18,10 +18,12 @@ */ import { ChartTypes } from '../..'; +import { MockGlobalSpec } from '../../../mocks/specs/specs'; +import { MockXDomain } from '../../../mocks/xy/domains'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes } from '../../../specs/constants'; import { Dimensions } from '../../../utils/dimensions'; -import { getXScaleConfig } from '../scales/get_scale_config'; +import { getAPIScaleConfigs } from '../state/selectors/get_api_scale_configs'; import { computeSeriesDomains } from '../state/utils/utils'; import { computeXScale } from '../utils/scales'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; @@ -98,22 +100,35 @@ describe('Crosshair utils linear scale', () => { yScaleType: ScaleType.Linear, }; - const domainGroup = new Map([['group1', { fit: true }]]); - const barSeries = [barSeries1]; - const barSeriesDomains = computeSeriesDomains(barSeries, domainGroup); + const barSeriesDomains = computeSeriesDomains( + barSeries, + getAPIScaleConfigs([], barSeries, MockGlobalSpec.settings()), + ); const multiBarSeries = [barSeries1, barSeries2]; - const multiBarSeriesDomains = computeSeriesDomains(multiBarSeries, domainGroup); + const multiBarSeriesDomains = computeSeriesDomains( + multiBarSeries, + getAPIScaleConfigs([], multiBarSeries, MockGlobalSpec.settings()), + ); const lineSeries = [lineSeries1]; - const lineSeriesDomains = computeSeriesDomains(lineSeries, domainGroup); + const lineSeriesDomains = computeSeriesDomains( + lineSeries, + getAPIScaleConfigs([], lineSeries, MockGlobalSpec.settings()), + ); const multiLineSeries = [lineSeries1, lineSeries2]; - const multiLineSeriesDomains = computeSeriesDomains(multiLineSeries, domainGroup); + const multiLineSeriesDomains = computeSeriesDomains( + multiLineSeries, + getAPIScaleConfigs([], multiLineSeries, MockGlobalSpec.settings()), + ); const mixedLinesBars = [lineSeries1, lineSeries2, barSeries1, barSeries2]; - const mixedLinesBarsSeriesDomains = computeSeriesDomains(mixedLinesBars, domainGroup); + const mixedLinesBarsSeriesDomains = computeSeriesDomains( + mixedLinesBars, + getAPIScaleConfigs([], mixedLinesBars, MockGlobalSpec.settings()), + ); const barSeriesScale = computeXScale({ xDomain: barSeriesDomains.xDomain, @@ -1457,13 +1472,11 @@ describe('Crosshair utils linear scale', () => { const chartDimensions: Dimensions = { top: 0, left: 0, width: 120, height: 120 }; test('cursor at begin of domain', () => { const barSeriesScaleLimited = computeXScale({ - xDomain: { + xDomain: MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0.5, 3.5], isBandScale: true, minInterval: 1, - scaleConfig: getXScaleConfig(ScaleType.Linear), - type: 'xDomain', - }, + }), totalBarsInCluster: 1, range: [0, 120], }); @@ -1488,13 +1501,11 @@ describe('Crosshair utils linear scale', () => { }); test('cursor at end of domain', () => { const barSeriesScaleLimited = computeXScale({ - xDomain: { + xDomain: MockXDomain.fromScaleType(ScaleType.Linear, { domain: [-0.5, 2.5], isBandScale: true, minInterval: 1, - scaleConfig: getXScaleConfig(ScaleType.Linear), - type: 'xDomain', - }, + }), totalBarsInCluster: barSeries.length, range: [0, 120], }); @@ -1519,13 +1530,11 @@ describe('Crosshair utils linear scale', () => { }); test('cursor at top begin of domain', () => { const barSeriesScaleLimited = computeXScale({ - xDomain: { + xDomain: MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0.5, 3.5], isBandScale: true, minInterval: 1, - scaleConfig: getXScaleConfig(ScaleType.Linear), - type: 'xDomain', - }, + }), totalBarsInCluster: 1, range: [0, 120], }); @@ -1550,13 +1559,11 @@ describe('Crosshair utils linear scale', () => { }); test('cursor at top end of domain', () => { const barSeriesScaleLimited = computeXScale({ - xDomain: { + xDomain: MockXDomain.fromScaleType(ScaleType.Linear, { domain: [-0.5, 2.5], isBandScale: true, minInterval: 1, - scaleConfig: getXScaleConfig(ScaleType.Linear), - type: 'xDomain', - }, + }), totalBarsInCluster: barSeries.length, range: [0, 120], }); 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 9ef1fc1ec3..450dd3bd11 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 @@ -18,8 +18,10 @@ */ import { ChartTypes } from '../..'; +import { MockGlobalSpec } from '../../../mocks/specs/specs'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes } from '../../../specs/constants'; +import { getAPIScaleConfigs } from '../state/selectors/get_api_scale_configs'; import { computeSeriesDomains } from '../state/utils/utils'; import { computeXScale } from '../utils/scales'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; @@ -96,22 +98,36 @@ describe('Crosshair utils ordinal scales', () => { yScaleType: ScaleType.Linear, }; - const domainGroup = new Map([['group1', { fit: true }]]); - const barSeries = [barSeries1]; - const barSeriesDomains = computeSeriesDomains(barSeries, domainGroup); + + const barSeriesDomains = computeSeriesDomains( + barSeries, + getAPIScaleConfigs([], barSeries, MockGlobalSpec.settings()), + ); const multiBarSeries = [barSeries1, barSeries2]; - const multiBarSeriesDomains = computeSeriesDomains(multiBarSeries, domainGroup); + const multiBarSeriesDomains = computeSeriesDomains( + multiBarSeries, + getAPIScaleConfigs([], multiBarSeries, MockGlobalSpec.settings()), + ); const lineSeries = [lineSeries1]; - const lineSeriesDomains = computeSeriesDomains(lineSeries, domainGroup); + const lineSeriesDomains = computeSeriesDomains( + lineSeries, + getAPIScaleConfigs([], lineSeries, MockGlobalSpec.settings()), + ); const multiLineSeries = [lineSeries1, lineSeries2]; - const multiLineSeriesDomains = computeSeriesDomains(multiLineSeries, domainGroup); + const multiLineSeriesDomains = computeSeriesDomains( + multiLineSeries, + getAPIScaleConfigs([], multiLineSeries, MockGlobalSpec.settings()), + ); const mixedLinesBars = [lineSeries1, lineSeries2, barSeries1, barSeries2]; - const mixedLinesBarsSeriesDomains = computeSeriesDomains(mixedLinesBars, domainGroup); + const mixedLinesBarsSeriesDomains = computeSeriesDomains( + mixedLinesBars, + getAPIScaleConfigs([], mixedLinesBars, MockGlobalSpec.settings()), + ); const barSeriesScale = computeXScale({ xDomain: barSeriesDomains.xDomain, diff --git a/src/chart_types/xy_chart/domains/types.ts b/src/chart_types/xy_chart/domains/types.ts index fedbd9b56d..cefcb13605 100644 --- a/src/chart_types/xy_chart/domains/types.ts +++ b/src/chart_types/xy_chart/domains/types.ts @@ -21,27 +21,27 @@ import { ScaleContinuousType } from '../../../scales'; import { LogScaleOptions } from '../../../scales/scale_continuous'; import { OrdinalDomain, ContinuousDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; -import { ScaleConfig } from '../scales/get_scale_config'; +import { APIScale } from '../scales/types'; import { XScaleType } from '../utils/specs'; /** @internal */ -export type XDomain = Pick & { - type: 'xDomain'; - scaleConfig: ScaleConfig; - /* if the scale needs to be a band scale: used when displaying bars */ - isBandScale: boolean; - /* the minimum interval of the scale if not-ordinal band-scale */ - minInterval: number; - /** if x domain is time, we should also specify the timezone */ - timeZone?: string; - domain: OrdinalDomain | ContinuousDomain; -}; +export type XDomain = Pick & + APIScale & { + /* if the scale needs to be a band scale: used when displaying bars */ + isBandScale: boolean; + /* the minimum interval of the scale if not-ordinal band-scale */ + minInterval: number; + /** if x domain is time, we should also specify the timezone */ + timeZone?: string; + domain: OrdinalDomain | ContinuousDomain; + ticks: number; + }; /** @internal */ -export type YDomain = LogScaleOptions & { - type: 'yDomain'; - scaleConfig: ScaleConfig; - isBandScale: false; - groupId: GroupId; - domain: ContinuousDomain; -}; +export type YDomain = LogScaleOptions & + APIScale & { + isBandScale: false; + groupId: GroupId; + domain: ContinuousDomain; + ticks: number; + }; 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 f799e70020..2a862e5621 100644 --- a/src/chart_types/xy_chart/domains/x_domain.test.ts +++ b/src/chart_types/xy_chart/domains/x_domain.test.ts @@ -18,11 +18,12 @@ */ import { ChartTypes } from '../..'; -import { MockSeriesSpecs } from '../../../mocks/specs'; +import { MockGlobalSpec, MockSeriesSpec, MockSeriesSpecs } from '../../../mocks/specs'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes, Direction, BinAgg } from '../../../specs/constants'; import { Logger } from '../../../utils/logger'; -import { getXScaleConfig } from '../scales/get_scale_config'; +import { getXAPIScale } from '../scales/get_api_scales'; +import { getAPIScaleConfigs } from '../state/selectors/get_api_scale_configs'; import { getDataSeriesFromSpecs } from '../utils/series'; import { BasicSeriesSpec, SeriesTypes } from '../utils/specs'; import { convertXScaleTypes, findMinInterval, mergeXDomain } from './x_domain'; @@ -49,7 +50,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleConfig: getXScaleConfig(ScaleType.Linear), + ...getXAPIScale(ScaleType.Linear), isBandScale: true, }); }); @@ -63,7 +64,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleConfig: getXScaleConfig(ScaleType.Ordinal), + ...getXAPIScale(ScaleType.Ordinal), isBandScale: true, }); }); @@ -77,7 +78,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleConfig: getXScaleConfig(ScaleType.Linear), + ...getXAPIScale(ScaleType.Linear), isBandScale: false, }); }); @@ -91,7 +92,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleConfig: getXScaleConfig(ScaleType.Time), + ...getXAPIScale(ScaleType.Time), isBandScale: false, timeZone: 'utc-3', }); @@ -111,7 +112,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleConfig: getXScaleConfig(ScaleType.Time), + ...getXAPIScale(ScaleType.Time), isBandScale: false, timeZone: 'utc-3', }); @@ -131,7 +132,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleConfig: getXScaleConfig(ScaleType.Time), + ...getXAPIScale(ScaleType.Time), isBandScale: false, timeZone: 'utc', }); @@ -150,7 +151,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleConfig: getXScaleConfig(ScaleType.Ordinal), + ...getXAPIScale(ScaleType.Ordinal), isBandScale: false, }); }); @@ -167,7 +168,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleConfig: getXScaleConfig(ScaleType.Ordinal), + ...getXAPIScale(ScaleType.Ordinal), isBandScale: true, }); }); @@ -185,7 +186,7 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - scaleConfig: getXScaleConfig(ScaleType.Linear), + ...getXAPIScale(ScaleType.Linear), isBandScale: true, }); }); @@ -224,16 +225,9 @@ describe('X Domain', () => { ], }; const specDataSeries: BasicSeriesSpec[] = [ds1, ds2]; + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain( - [ - { - seriesType: SeriesTypes.Line, - xScaleType: ScaleType.Linear, - }, - ], - xValues, - ); + const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 7]); }); test('Should merge bar series correctly', () => { @@ -270,17 +264,9 @@ describe('X Domain', () => { ], }; const specDataSeries = [ds1, ds2]; - + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain( - [ - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Linear, - }, - ], - xValues, - ); + const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 7]); }); test('Should merge multi bar series correctly', () => { @@ -317,21 +303,9 @@ describe('X Domain', () => { ], }; const specDataSeries = [ds1, ds2]; - + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain( - [ - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Linear, - }, - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Linear, - }, - ], - xValues, - ); + const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 7]); }); test('Should merge multi bar series correctly - 2', () => { @@ -370,19 +344,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain( - [ - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Linear, - }, - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Linear, - }, - ], - xValues, - ); + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + + const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 7]); }); test('Should merge multi bar linear/bar ordinal series correctly', () => { @@ -419,21 +383,9 @@ describe('X Domain', () => { ], }; const specDataSeries = [ds1, ds2]; - + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain( - [ - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Linear, - }, - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Ordinal, - }, - ], - xValues, - ); + const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 1, 2, 5, 7]); }); @@ -478,28 +430,15 @@ describe('X Domain', () => { }; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const getResult = () => - mergeXDomain( - [ - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Linear, - }, - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Linear, - }, - ], - xValues, - customDomain, - { type: ScaleType.Ordinal, nice: false }, - ); + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings({ xDomain: customDomain })); + + const getResult = () => mergeXDomain(apiScalesConfig.x, xValues, { type: ScaleType.Ordinal, nice: false }); expect(getResult).not.toThrow(); const mergedDomain = getResult(); expect(mergedDomain.domain).toEqual([0, 'a', 2, 5, 7]); - expect(mergedDomain.scaleConfig.type).toEqual(ScaleType.Ordinal); + expect(mergedDomain.type).toEqual(ScaleType.Ordinal); }); test('Should merge multi bar/line ordinal series correctly', () => { @@ -538,19 +477,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain( - [ - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Linear, - }, - { - seriesType: SeriesTypes.Line, - xScaleType: ScaleType.Ordinal, - }, - ], - xValues, - ); + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + + const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 1, 2, 5, 7]); }); test('Should merge multi bar/line time series correctly', () => { @@ -589,19 +518,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain( - [ - { - seriesType: SeriesTypes.Bar, - xScaleType: ScaleType.Ordinal, - }, - { - seriesType: SeriesTypes.Line, - xScaleType: ScaleType.Time, - }, - ], - xValues, - ); + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + + const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 1, 2, 5, 7]); }); test('Should merge multi lines series correctly', () => { @@ -640,19 +559,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain( - [ - { - seriesType: SeriesTypes.Line, - xScaleType: ScaleType.Ordinal, - }, - { - seriesType: SeriesTypes.Line, - xScaleType: ScaleType.Linear, - }, - ], - xValues, - ); + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + + const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 1, 2, 5, 7]); }); @@ -685,20 +594,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); + const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); - const mergedDomain = mergeXDomain( - [ - { - seriesType: SeriesTypes.Area, - xScaleType: ScaleType.Linear, - }, - { - seriesType: SeriesTypes.Line, - xScaleType: ScaleType.Ordinal, - }, - ], - xValues, - ); + const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); expect(mergedDomain.domain.length).toEqual(maxValues); }); test('should compute minInterval an ordered list of numbers', () => { @@ -709,7 +607,7 @@ describe('X Domain', () => { const minInterval = findMinInterval([2, 10, 3, 1, 5]); expect(minInterval).toBe(1); }); - test('should compute minInterval an list grether than 9', () => { + test('should compute minInterval an list greater than 9', () => { const minInterval = findMinInterval([0, 2, 4, 6, 8, 10, 20, 30, 40, 50, 80]); expect(minInterval).toBe(2); }); @@ -732,15 +630,19 @@ describe('X Domain', () => { test('should account for custom domain when merging a linear domain: complete bounded domain', () => { const xValues = new Set([1, 2, 3, 4, 5]); const xDomain = { min: 0, max: 3 }; - const specs: Pick[] = [ - { seriesType: SeriesTypes.Line, xScaleType: ScaleType.Linear }, - ]; + const specs = [MockSeriesSpec.line({ xScaleType: ScaleType.Linear })]; - const basicMergedDomain = mergeXDomain(specs, xValues, xDomain); + const basicMergedDomain = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, + xValues, + ); expect(basicMergedDomain.domain).toEqual([0, 3]); const arrayXDomain = [1, 2]; - let { domain } = mergeXDomain(specs, xValues, arrayXDomain); + let { domain } = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: arrayXDomain })).x, + xValues, + ); expect(domain).toEqual([1, 5]); const warnMessage = 'xDomain for continuous scale should be a DomainRange object, not an array'; expect(Logger.warn).toBeCalledWith(warnMessage); @@ -748,7 +650,10 @@ describe('X Domain', () => { (Logger.warn as jest.Mock).mockClear(); const invalidXDomain = { min: 10, max: 0 }; - domain = mergeXDomain(specs, xValues, invalidXDomain).domain; + domain = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + xValues, + ).domain; expect(domain).toEqual([1, 5]); expect(Logger.warn).toBeCalledWith('custom xDomain is invalid, min is greater than max. Custom domain is ignored.'); }); @@ -756,15 +661,16 @@ describe('X Domain', () => { test('should account for custom domain when merging a linear domain: lower bounded domain', () => { const xValues = new Set([1, 2, 3, 4, 5]); const xDomain = { min: 0 }; - const specs: Pick[] = [ - { seriesType: SeriesTypes.Line, xScaleType: ScaleType.Linear }, - ]; + const specs = [MockSeriesSpec.line({ xScaleType: ScaleType.Linear })]; - const mergedDomain = mergeXDomain(specs, xValues, xDomain); + const mergedDomain = mergeXDomain(getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, xValues); expect(mergedDomain.domain).toEqual([0, 5]); const invalidXDomain = { min: 10 }; - const { domain } = mergeXDomain(specs, xValues, invalidXDomain); + const { domain } = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + xValues, + ); expect(domain).toEqual([1, 5]); expect(Logger.warn).toBeCalledWith( 'custom xDomain is invalid, custom min is greater than computed max. Custom domain is ignored.', @@ -774,15 +680,16 @@ describe('X Domain', () => { test('should account for custom domain when merging a linear domain: upper bounded domain', () => { const xValues = new Set([1, 2, 3, 4, 5]); const xDomain = { max: 3 }; - const specs: Pick[] = [ - { seriesType: SeriesTypes.Line, xScaleType: ScaleType.Linear }, - ]; + const specs = [MockSeriesSpec.line({ xScaleType: ScaleType.Linear })]; - const mergedDomain = mergeXDomain(specs, xValues, xDomain); + const mergedDomain = mergeXDomain(getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, xValues); expect(mergedDomain.domain).toEqual([1, 3]); const invalidXDomain = { max: -1 }; - const { domain } = mergeXDomain(specs, xValues, invalidXDomain); + const { domain } = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + xValues, + ); expect(domain).toEqual([1, 5]); expect(Logger.warn).toBeCalledWith( 'custom xDomain is invalid, computed min is greater than custom max. Custom domain is ignored.', @@ -792,14 +699,18 @@ describe('X Domain', () => { test('should account for custom domain when merging an ordinal domain', () => { const xValues = new Set(['a', 'b', 'c', 'd']); const xDomain = ['a', 'b', 'c']; - const specs: Pick[] = [ - { seriesType: SeriesTypes.Bar, xScaleType: ScaleType.Ordinal }, - ]; - const basicMergedDomain = mergeXDomain(specs, xValues, xDomain); + const specs = [MockSeriesSpec.bar({ xScaleType: ScaleType.Ordinal })]; + const basicMergedDomain = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, + xValues, + ); expect(basicMergedDomain.domain).toEqual(['a', 'b', 'c']); const objectXDomain = { max: 10, min: 0 }; - const { domain } = mergeXDomain(specs, xValues, objectXDomain); + const { domain } = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: objectXDomain })).x, + xValues, + ); expect(domain).toEqual(['a', 'b', 'c', 'd']); const warnMessage = 'xDomain for ordinal scale should be an array of values, not a DomainRange object. xDomain is ignored.'; @@ -808,25 +719,29 @@ describe('X Domain', () => { describe('should account for custom minInterval', () => { const xValues = new Set([1, 2, 3, 4, 5]); - const specs: Pick[] = [ - { seriesType: SeriesTypes.Bar, xScaleType: ScaleType.Linear }, - ]; + const specs = [MockSeriesSpec.bar({ xScaleType: ScaleType.Linear })]; test('with valid minInterval', () => { const xDomain = { minInterval: 0.5 }; - const mergedDomain = mergeXDomain(specs, xValues, xDomain); + const mergedDomain = mergeXDomain(getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, xValues); expect(mergedDomain.minInterval).toEqual(0.5); }); test('with valid minInterval greater than computed minInterval for single datum set', () => { const xDomain = { minInterval: 10 }; - const mergedDomain = mergeXDomain(specs, new Set([5]), xDomain); + const mergedDomain = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, + new Set([5]), + ); expect(mergedDomain.minInterval).toEqual(10); }); test('with invalid minInterval greater than computed minInterval for multi data set', () => { const invalidXDomain = { minInterval: 10 }; - const { minInterval } = mergeXDomain(specs, xValues, invalidXDomain); + const { minInterval } = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + xValues, + ); expect(minInterval).toEqual(1); const expectedWarning = 'custom xDomain is invalid, custom minInterval is greater than computed minInterval. Using computed minInterval.'; @@ -835,7 +750,10 @@ describe('X Domain', () => { test('with invalid minInterval less than 0', () => { const invalidXDomain = { minInterval: -1 }; - const { minInterval } = mergeXDomain(specs, xValues, invalidXDomain); + const { minInterval } = mergeXDomain( + getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + xValues, + ); expect(minInterval).toEqual(1); const expectedWarning = 'custom xDomain is invalid, custom minInterval is less than 0. Using computed minInterval.'; diff --git a/src/chart_types/xy_chart/domains/x_domain.ts b/src/chart_types/xy_chart/domains/x_domain.ts index a9e377dd8c..c0c2c0a858 100644 --- a/src/chart_types/xy_chart/domains/x_domain.ts +++ b/src/chart_types/xy_chart/domains/x_domain.ts @@ -23,49 +23,42 @@ import { ScaleType } from '../../../scales/constants'; import { compareByValueAsc, identity } from '../../../utils/common'; import { computeContinuousDataDomain, computeOrdinalDataDomain } from '../../../utils/domain'; import { Logger } from '../../../utils/logger'; -import { getXScaleConfig, ScaleConfig } from '../scales/get_scale_config'; +import { getXAPIScale } from '../scales/get_api_scales'; +import { APIScale } from '../scales/types'; +import { APIScaleConfigs } from '../state/selectors/get_api_scale_configs'; import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_type_utils'; -import { BasicSeriesSpec, CustomXDomain, SeriesTypes, XScaleType } from '../utils/specs'; +import { BasicSeriesSpec, SeriesTypes, XScaleType } from '../utils/specs'; import { areAllNiceDomain } from './nice'; import { XDomain } from './types'; /** * Merge X domain value between a set of chart specification. - * @param specs an array of [{ seriesType, xScaleType }] - * @param xValues a set of unique x values from all specs - * @param customXDomain if specified, a custom xDomain - * @param fallbackScale - * @returns a merged XDomain between all series. * @internal */ export function mergeXDomain( - specs: Optional, 'seriesType'>[], + apiScaleConfig: APIScaleConfigs['x'], xValues: Set, - customXDomain?: CustomXDomain, - fallbackScale?: ScaleConfig, + fallbackScale?: APIScale, ): XDomain { - const { scaleConfig, isBandScale, timeZone } = convertXScaleTypes(specs); - + const { type, nice, isBandScale, timeZone, ticks, customDomain } = apiScaleConfig; const values = [...xValues.values()]; let seriesXComputedDomains; let minInterval = 0; - if (scaleConfig.type === ScaleType.Ordinal || fallbackScale?.type === ScaleType.Ordinal) { - if (scaleConfig.type !== ScaleType.Ordinal) { - Logger.warn( - `Each X value in a ${scaleConfig.type} x scale needs be be a number. Using ordinal x scale as fallback.`, - ); + if (type === ScaleType.Ordinal || fallbackScale?.type === ScaleType.Ordinal) { + if (type !== ScaleType.Ordinal) { + Logger.warn(`Each X value in a ${type} x scale needs be be a number. Using ordinal x scale as fallback.`); } seriesXComputedDomains = computeOrdinalDataDomain(values, identity, false, true); - if (customXDomain) { - if (Array.isArray(customXDomain)) { - seriesXComputedDomains = customXDomain; + if (customDomain) { + if (Array.isArray(customDomain)) { + seriesXComputedDomains = customDomain; } else { if (fallbackScale?.type === ScaleType.Ordinal) { Logger.warn(`xDomain ignored for fallback ordinal scale. Options to resolve: -1) Correct data to match ${scaleConfig.type} scale type (see previous warning) +1) Correct data to match ${type} scale type (see previous warning) 2) Change xScaleType to ordinal and set xDomain to Domain array`); } else { Logger.warn( @@ -80,34 +73,34 @@ export function mergeXDomain( }); let customMinInterval: undefined | number; - if (customXDomain) { - if (Array.isArray(customXDomain)) { + if (customDomain) { + if (Array.isArray(customDomain)) { Logger.warn('xDomain for continuous scale should be a DomainRange object, not an array'); } else { - customMinInterval = customXDomain.minInterval; + customMinInterval = customDomain.minInterval; const [computedDomainMin, computedDomainMax] = seriesXComputedDomains; - if (isCompleteBound(customXDomain)) { - if (customXDomain.min > customXDomain.max) { + if (isCompleteBound(customDomain)) { + if (customDomain.min > customDomain.max) { Logger.warn('custom xDomain is invalid, min is greater than max. Custom domain is ignored.'); } else { - seriesXComputedDomains = [customXDomain.min, customXDomain.max]; + seriesXComputedDomains = [customDomain.min, customDomain.max]; } - } else if (isLowerBound(customXDomain)) { - if (customXDomain.min > computedDomainMax) { + } else if (isLowerBound(customDomain)) { + if (customDomain.min > computedDomainMax) { Logger.warn( 'custom xDomain is invalid, custom min is greater than computed max. Custom domain is ignored.', ); } else { - seriesXComputedDomains = [customXDomain.min, computedDomainMax]; + seriesXComputedDomains = [customDomain.min, computedDomainMax]; } - } else if (isUpperBound(customXDomain)) { - if (computedDomainMin > customXDomain.max) { + } else if (isUpperBound(customDomain)) { + if (computedDomainMin > customDomain.max) { Logger.warn( 'custom xDomain is invalid, computed min is greater than custom max. Custom domain is ignored.', ); } else { - seriesXComputedDomains = [computedDomainMin, customXDomain.max]; + seriesXComputedDomains = [computedDomainMin, customDomain.max]; } } } @@ -117,13 +110,16 @@ export function mergeXDomain( } return { - type: 'xDomain', - scaleConfig: fallbackScale ?? scaleConfig, + ...(fallbackScale ?? { + type, + nice, + }), isBandScale, domain: seriesXComputedDomains, minInterval, timeZone, - logBase: customXDomain && 'logBase' in customXDomain ? customXDomain.logBase : undefined, + logBase: customDomain && 'logBase' in customDomain ? customDomain.logBase : undefined, + ticks, }; } @@ -173,17 +169,16 @@ export function findMinInterval(xValues: number[]): number { /** * Convert the scale types of a set of specification to a generic one. - * If there are at least one `ordinal` scale type, the resulting scale is coerched to ordinal. - * If there are only `continuous` scale types, the resulting scale is coerched to linear. - * If there are only `time` scales, we coerch the timeZone to `utc` only if we have multiple + * If there are at least one `ordinal` scale type, the resulting scale is coerced to ordinal. + * If there are only `continuous` scale types, the resulting scale is coerced to linear. + * If there are only `time` scales, we coerce the timeZone to `utc` only if we have multiple * different timezones. - * @returns the coerched scale type, the timezone and a parameter that describe if its a bandScale or not + * @returns the coerced scale type, the timezone and a parameter that describe if its a bandScale or not * @internal */ export function convertXScaleTypes( specs: Optional, 'seriesType'>[], -): { - scaleConfig: ScaleConfig; +): APIScale & { isBandScale: boolean; timeZone?: string; } { @@ -192,7 +187,7 @@ export function convertXScaleTypes( const timeZones = new Set(); const niceDomainConfigs: Array = []; specs.forEach((spec) => { - const scaleConfig = getXScaleConfig(spec.xScaleType); + const scaleConfig = getXAPIScale(spec.xScaleType); niceDomainConfigs.push(scaleConfig.nice); seriesTypes.add(spec.seriesType); scaleTypes.add(scaleConfig.type); @@ -202,10 +197,8 @@ export function convertXScaleTypes( }); if (specs.length === 0 || seriesTypes.size === 0 || scaleTypes.size === 0) { return { - scaleConfig: { - type: ScaleType.Linear, - nice: true, - }, + type: ScaleType.Linear, + nice: true, isBandScale: false, }; } @@ -214,23 +207,19 @@ export function convertXScaleTypes( if (scaleTypes.size === 1) { const scaleType = scaleTypes.values().next().value; const timeZone = timeZones.size > 1 ? 'utc' : timeZones.values().next().value; - return { scaleConfig: { type: scaleType, nice }, isBandScale, timeZone }; + return { type: scaleType, nice, isBandScale, timeZone }; } if (scaleTypes.size > 1 && scaleTypes.has(ScaleType.Ordinal)) { return { - scaleConfig: { - type: ScaleType.Ordinal, - nice, - }, + type: ScaleType.Ordinal, + nice, isBandScale, }; } return { - scaleConfig: { - type: ScaleType.Linear, - nice, - }, + type: ScaleType.Linear, + nice, isBandScale, }; } 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 e52a764268..19876d2f05 100644 --- a/src/chart_types/xy_chart/domains/y_domain.test.ts +++ b/src/chart_types/xy_chart/domains/y_domain.test.ts @@ -20,12 +20,12 @@ import { ChartTypes } from '../..'; import { MockSeriesSpec, MockGlobalSpec } from '../../../mocks/specs'; import { MockStore } from '../../../mocks/store'; +import { MockYDomain } from '../../../mocks/xy/domains'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes } from '../../../specs/constants'; import { Position } from '../../../utils/common'; import { BARCHART_1Y0G } from '../../../utils/data_samples/test_dataset'; import { Logger } from '../../../utils/logger'; -import { getYScaleConfig } from '../scales/get_scale_config'; import { computeSeriesDomainsSelector } from '../state/selectors/compute_series_domains'; import { BasicSeriesSpec, SeriesTypes, DEFAULT_GLOBAL_ID, StackMode } from '../utils/specs'; import { coerceYScaleTypes, groupSeriesByYGroup } from './y_domain'; @@ -85,13 +85,11 @@ describe('Y Domain', () => { const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { - type: 'yDomain', + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: DEFAULT_GLOBAL_ID, domain: [2, 12], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - }, + }), ]); }); test('Should merge Y domain for zero baseline charts', () => { @@ -113,13 +111,11 @@ describe('Y Domain', () => { const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { - type: 'yDomain', + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: DEFAULT_GLOBAL_ID, domain: [0, 12], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - }, + }), ]); }); test('Should merge Y domain different group', () => { @@ -149,20 +145,16 @@ describe('Y Domain', () => { const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: 'a', domain: [2, 12], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - type: 'yDomain', - }, - { + }), + MockYDomain.fromScaleType(ScaleType.Log, { groupId: 'b', domain: [2, 10], - scaleConfig: getYScaleConfig(ScaleType.Log), isBandScale: false, - type: 'yDomain', - }, + }), ]); }); test('Should merge Y domain same group all stacked', () => { @@ -187,13 +179,11 @@ describe('Y Domain', () => { const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: 'a', domain: [0, 17], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - type: 'yDomain', - }, + }), ]); }); test('Should merge Y domain same group partially stacked', () => { @@ -216,13 +206,11 @@ describe('Y Domain', () => { ); const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: 'a', domain: [0, 12], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - type: 'yDomain', - }, + }), ]); }); @@ -385,9 +373,10 @@ describe('Y Domain', () => { }); test('Should return a default Scale Linear for YScaleType when there are no specs', () => { - const specs: BasicSeriesSpec['yScaleType'][] = []; - const specScaleConfigs = specs.map(getYScaleConfig); - expect(coerceYScaleTypes(specScaleConfigs)).toEqual(getYScaleConfig(ScaleType.Linear)); + expect(coerceYScaleTypes([])).toEqual({ + nice: false, + type: ScaleType.Linear, + }); }); test('Should merge Y domain accounting for custom domain limits: complete bounded domain', () => { @@ -407,13 +396,11 @@ describe('Y Domain', () => { const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { - type: 'yDomain', + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: 'a', domain: [0, 20], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - }, + }), ]); }); test('Should merge Y domain accounting for custom domain limits: partial lower bounded domain', () => { @@ -433,13 +420,11 @@ describe('Y Domain', () => { const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { - type: 'yDomain', + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: 'a', domain: [0, 12], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - }, + }), ]); }); test('Should not merge Y domain with invalid custom domain limits: partial lower bounded domain', () => { @@ -482,13 +467,11 @@ describe('Y Domain', () => { const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { - type: 'yDomain', + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: 'a', domain: [2, 20], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - }, + }), ]); }); test('Should not merge Y domain with invalid custom domain limits: partial upper bounded domain', () => { @@ -532,13 +515,11 @@ describe('Y Domain', () => { const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: 'a', domain: [0, 1], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - type: 'yDomain', - }, + }), ]); }); test('Should merge Y domain with as percentage regadless of custom domains', () => { @@ -560,13 +541,11 @@ describe('Y Domain', () => { ); const { yDomains } = computeSeriesDomainsSelector(store.getState()); expect(yDomains).toEqual([ - { - type: 'yDomain', + MockYDomain.fromScaleType(ScaleType.Linear, { groupId: 'a', domain: [0, 1], - scaleConfig: getYScaleConfig(ScaleType.Linear), isBandScale: false, - }, + }), ]); }); }); diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts index 275308e742..2c472c291b 100644 --- a/src/chart_types/xy_chart/domains/y_domain.ts +++ b/src/chart_types/xy_chart/domains/y_domain.ts @@ -23,12 +23,13 @@ import { identity } from '../../../utils/common'; import { computeContinuousDataDomain, ContinuousDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; -import { getYScaleConfig, ScaleConfig } from '../scales/get_scale_config'; +import { APIScale } from '../scales/types'; +import { APIScaleConfigs } from '../state/selectors/get_api_scale_configs'; import { getSpecDomainGroupId } from '../state/utils/spec'; import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_type_utils'; import { groupBy } from '../utils/group_data_series'; import { DataSeries } from '../utils/series'; -import { BasicSeriesSpec, YDomainRange, SeriesTypes, StackMode } from '../utils/specs'; +import { BasicSeriesSpec, SeriesTypes, StackMode } from '../utils/specs'; import { areAllNiceDomain } from './nice'; import { YDomain } from './types'; @@ -38,21 +39,17 @@ export type YBasicSeriesSpec = Pick< > & { stackMode?: StackMode; enableHistogramMode?: boolean }; /** @internal */ -export function mergeYDomain(dataSeries: DataSeries[], domainsByGroupId: Map): YDomain[] { +export function mergeYDomain(dataSeries: DataSeries[], yScaleAPIConfig: APIScaleConfigs['y']): YDomain[] { const dataSeriesByGroupId = groupBy(dataSeries, ({ spec }) => getSpecDomainGroupId(spec), true); return dataSeriesByGroupId.reduce((acc, groupedDataSeries) => { - const [{ spec }] = groupedDataSeries; - const groupId = getSpecDomainGroupId(spec); - const stacked = groupedDataSeries.filter(({ isStacked, isFiltered }) => isStacked && !isFiltered); const nonStacked = groupedDataSeries.filter(({ isStacked, isFiltered }) => !isStacked && !isFiltered); - const customDomain = domainsByGroupId.get(groupId); const hasNonZeroBaselineTypes = groupedDataSeries.some( ({ seriesType, isFiltered }) => seriesType === SeriesTypes.Bar || (seriesType === SeriesTypes.Area && !isFiltered), ); - const domain = mergeYDomainForGroup(stacked, nonStacked, hasNonZeroBaselineTypes, customDomain); + const domain = mergeYDomainForGroup(stacked, nonStacked, hasNonZeroBaselineTypes, yScaleAPIConfig); if (!domain) { return acc; } @@ -64,16 +61,16 @@ function mergeYDomainForGroup( stacked: DataSeries[], nonStacked: DataSeries[], hasZeroBaselineSpecs: boolean, - customDomain?: YDomainRange, + yAPIScaleConfig: APIScaleConfigs['y'], ): YDomain | null { const dataSeries = [...stacked, ...nonStacked]; if (dataSeries.length === 0) { return null; } - const yScaleTypes = dataSeries.map(({ spec: { yScaleType } }) => getYScaleConfig(yScaleType)); - const groupYScaleType = coerceYScaleTypes(yScaleTypes); + const [{ stackMode, spec }] = dataSeries; const groupId = getSpecDomainGroupId(spec); + const { customDomain, type, nice, ticks } = yAPIScaleConfig[groupId]; let domain: ContinuousDomain; if (stackMode === StackMode.Percentage) { @@ -117,13 +114,14 @@ function mergeYDomainForGroup( } } return { - type: 'yDomain', + type, + nice, isBandScale: false, - scaleConfig: groupYScaleType, groupId, domain, logBase: customDomain?.logBase, logMinLimit: customDomain?.logMinLimit, + ticks, }; } @@ -213,16 +211,19 @@ export function isStackedSpec(spec: YBasicSeriesSpec, histogramEnabled: boolean) * @returns {ScaleContinuousType} * @internal */ -export function coerceYScaleTypes(scales: ScaleConfig[]): ScaleConfig { - const scaleCollection = scales.reduce( +export function coerceYScaleTypes(scales: APIScale[]): APIScale { + const scaleCollection = scales.reduce<{ + types: Set; + nice: Array; + }>( (acc, scale) => { acc.types.add(scale.type); acc.nice.push(scale.nice); return acc; }, { - types: new Set(), - nice: [] as Array, + types: new Set(), + nice: [], }, ); const nice = areAllNiceDomain(scaleCollection.nice); diff --git a/src/chart_types/xy_chart/legend/legend.ts b/src/chart_types/xy_chart/legend/legend.ts index 71ed469860..fea8da7083 100644 --- a/src/chart_types/xy_chart/legend/legend.ts +++ b/src/chart_types/xy_chart/legend/legend.ts @@ -24,7 +24,7 @@ import { SortSeriesByConfig, TickFormatterOptions } from '../../../specs'; import { Color } from '../../../utils/common'; import { BandedAccessorType } from '../../../utils/geometry'; import { getLegendCompareFn, SeriesCompareFn } from '../../../utils/series_sort'; -import { getXScaleConfig } from '../scales/get_scale_config'; +import { getXAPIScale } from '../scales/get_api_scales'; import { getAxesSpecForSpecId, getSpecsById } from '../state/utils/spec'; import { LastValues } from '../state/utils/types'; import { Y0_ACCESSOR_POSTFIX, Y1_ACCESSOR_POSTFIX } from '../tooltip/tooltip'; @@ -138,7 +138,7 @@ export function computeLegend( const lastValue = lastValues.get(key); const seriesIdentifier = getSeriesIdentifierFromDataSeries(series); - const scaleConf = getXScaleConfig(spec.xScaleType); + const scaleConf = getXAPIScale(spec.xScaleType); legendItems.push({ color, label: labelY1, 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 674b74e37c..8a3f7e4660 100644 --- a/src/chart_types/xy_chart/rendering/rendering.lines.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.lines.test.ts @@ -731,6 +731,7 @@ describe('Rendering points - line', () => { const axis = MockGlobalSpec.axis({ position: Position.Left, hide: true, domain: { max: 1 } }); const store = MockStore.default({ width: 100, height: 100, top: 0, left: 0 }); MockStore.addSpecs([pointSeriesSpec, axis, settings], store); + const { geometries: { lines }, geometriesIndex, diff --git a/src/chart_types/xy_chart/scales/get_scale_config.ts b/src/chart_types/xy_chart/scales/get_api_scales.ts similarity index 64% rename from src/chart_types/xy_chart/scales/get_scale_config.ts rename to src/chart_types/xy_chart/scales/get_api_scales.ts index 2fedab9d4b..765b814162 100644 --- a/src/chart_types/xy_chart/scales/get_scale_config.ts +++ b/src/chart_types/xy_chart/scales/get_api_scales.ts @@ -20,30 +20,25 @@ import { ScaleContinuousType } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { BasicSeriesSpec, XScaleType } from '../utils/specs'; import { X_SCALE_DEFAULT, Y_SCALE_DEFAULT } from './scale_defaults'; +import { APIScale } from './types'; /** @internal */ -export interface ScaleConfig { - type: T; - nice: boolean; +export function getXAPIScale(scaleType: BasicSeriesSpec['xScaleType']): APIScale { + return getDefaultAPIScale(scaleType, { type: X_SCALE_DEFAULT.type, nice: X_SCALE_DEFAULT.nice }); } /** @internal */ -export function getXScaleConfig(scaleType: BasicSeriesSpec['xScaleType']): ScaleConfig { - return getScaleConfig(scaleType, X_SCALE_DEFAULT); +export function getYAPIScale(scaleType: BasicSeriesSpec['yScaleType']): APIScale { + return getDefaultAPIScale(scaleType, { type: Y_SCALE_DEFAULT.type, nice: Y_SCALE_DEFAULT.nice }); } /** @internal */ -export function getYScaleConfig(scaleType: BasicSeriesSpec['yScaleType']): ScaleConfig { - return getScaleConfig(scaleType, Y_SCALE_DEFAULT); -} - -/** @internal */ -function getScaleConfig(scaleType: T | ScaleConfig, defaults: ScaleConfig): ScaleConfig { - if (typeof scaleType === 'object') { - return scaleType; +function getDefaultAPIScale(type: T | APIScale, defaults: APIScale): APIScale { + if (typeof type === 'object') { + return type; } return { ...defaults, - type: scaleType, + type, }; } diff --git a/src/chart_types/xy_chart/scales/scale_defaults.ts b/src/chart_types/xy_chart/scales/scale_defaults.ts index 9c065998d9..ef0e6d4134 100644 --- a/src/chart_types/xy_chart/scales/scale_defaults.ts +++ b/src/chart_types/xy_chart/scales/scale_defaults.ts @@ -22,10 +22,12 @@ import { ScaleType } from '../../../scales/constants'; export const X_SCALE_DEFAULT = { type: ScaleType.Ordinal, nice: false, + ticks: 10, }; /** @internal */ export const Y_SCALE_DEFAULT = { type: ScaleType.Linear, nice: false, + ticks: 10, }; diff --git a/src/chart_types/xy_chart/scales/types.ts b/src/chart_types/xy_chart/scales/types.ts new file mode 100644 index 0000000000..18054995c4 --- /dev/null +++ b/src/chart_types/xy_chart/scales/types.ts @@ -0,0 +1,25 @@ +/* + * 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 { ScaleType } from '../../../scales/constants'; + +/** @public */ +export interface APIScale { + type: T; + nice: boolean; +} diff --git a/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts index 7c68a34da9..ecfbcff7f1 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import createCachedSelector from 're-reselect'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; @@ -62,6 +61,7 @@ export const computeAxesGeometriesSelector = createCachedSelector( barsPadding, seriesSpecs, smScales, + // apiScaleConfigs, ): AxisGeometry[] => { const fallBackTickFormatter = seriesSpecs.find(({ tickFormat }) => tickFormat)?.tickFormat ?? defaultTickFormatter; const { xDomain, yDomains } = seriesDomainsAndData; diff --git a/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts b/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts index 824d564fb5..2f83ac4dab 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts @@ -24,8 +24,8 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { SeriesDomainsAndData } from '../utils/types'; import { computeSeriesDomains } from '../utils/utils'; +import { getAPIScaleConfigsSelector } from './get_api_scale_configs'; import { getSeriesSpecsSelector, getSmallMultiplesIndexOrderSelector } from './get_specs'; -import { mergeYCustomDomainsByGroupIdSelector } from './merge_y_custom_domains'; const getDeselectedSeriesSelector = (state: GlobalChartState) => state.interactions.deselectedDataSeries; @@ -33,17 +33,16 @@ const getDeselectedSeriesSelector = (state: GlobalChartState) => state.interacti export const computeSeriesDomainsSelector = createCachedSelector( [ getSeriesSpecsSelector, - mergeYCustomDomainsByGroupIdSelector, getDeselectedSeriesSelector, getSettingsSpecSelector, getSmallMultiplesIndexOrderSelector, + getAPIScaleConfigsSelector, ], - (seriesSpecs, customYDomainsByGroupId, deselectedDataSeries, settingsSpec, smallMultiples): SeriesDomainsAndData => { + (seriesSpecs, deselectedDataSeries, settingsSpec, smallMultiples, apiScaleConfigs): SeriesDomainsAndData => { return computeSeriesDomains( seriesSpecs, - customYDomainsByGroupId, + apiScaleConfigs, deselectedDataSeries, - settingsSpec.xDomain, settingsSpec.orderOrdinalBinsBy, smallMultiples, // @ts-ignore diff --git a/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts new file mode 100644 index 0000000000..276c845b00 --- /dev/null +++ b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts @@ -0,0 +1,125 @@ +/* + * 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 createCachedSelector from 're-reselect'; + +import { ScaleContinuousType } from '../../../../scales'; +import { ScaleType } from '../../../../scales/constants'; +import { SettingsSpec } from '../../../../specs/settings'; +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; +import { GroupId } from '../../../../utils/ids'; +import { convertXScaleTypes } from '../../domains/x_domain'; +import { coerceYScaleTypes } from '../../domains/y_domain'; +import { getYAPIScale } from '../../scales/get_api_scales'; +import { X_SCALE_DEFAULT, Y_SCALE_DEFAULT } from '../../scales/scale_defaults'; +import { APIScale } from '../../scales/types'; +import { isHorizontalAxis, isVerticalAxis } from '../../utils/axis_type_utils'; +import { groupBy } from '../../utils/group_data_series'; +import { + AxisSpec, + BasicSeriesSpec, + CustomXDomain, + DEFAULT_GLOBAL_ID, + XScaleType, + YDomainRange, +} from '../../utils/specs'; +import { isHorizontalRotation } from '../utils/common'; +import { getAxisSpecsSelector, getSeriesSpecsSelector } from './get_specs'; +import { mergeYCustomDomainsByGroupId } from './merge_y_custom_domains'; + +/** @internal */ +export type APIScaleConfigBase = APIScale & { + ticks: number; + customDomain?: D; +}; +type APIXScaleConfigBase = APIScaleConfigBase; +type APIYScaleConfigBase = APIScaleConfigBase; + +/** @internal */ +export interface APIScaleConfigs { + x: APIXScaleConfigBase & { + isBandScale: boolean; + timeZone?: string; + }; + y: Record; +} + +/** @internal */ +export const getAPIScaleConfigsSelector = createCachedSelector( + [getAxisSpecsSelector, getSeriesSpecsSelector, getSettingsSpecSelector], + getAPIScaleConfigs, +)(getChartIdSelector); + +/** @internal */ +export function getAPIScaleConfigs( + axisSpecs: AxisSpec[], + seriesSpecs: BasicSeriesSpec[], + settingsSpec: SettingsSpec, +): APIScaleConfigs { + const isHorizontalChart = isHorizontalRotation(settingsSpec.rotation); + + // x axis + const xAxes = axisSpecs.filter((d) => isHorizontalChart === isHorizontalAxis(d.position)); + const xTicks = xAxes.reduce((acc, curr) => { + return Math.min(acc, curr.ticks ?? acc); + }, X_SCALE_DEFAULT.ticks); + + const xScaleConfig = convertXScaleTypes(seriesSpecs); + const x: APIScaleConfigs['x'] = { + customDomain: settingsSpec.xDomain, + ...xScaleConfig, + ticks: xTicks, + }; + + // y axes + const scaleTypeByGroupId = groupBy(seriesSpecs, ['groupId'], true).reduce< + Record> + >((acc, series) => { + const yScaleTypes = series.map(({ yScaleType }) => getYAPIScale(yScaleType)); + acc[series[0].groupId] = coerceYScaleTypes(yScaleTypes); + return acc; + }, {}); + + const customDomainByGroupId = mergeYCustomDomainsByGroupId(axisSpecs, settingsSpec.rotation); + + const yAxes = axisSpecs.filter((d) => isHorizontalChart === isVerticalAxis(d.position)); + const y = Object.keys(scaleTypeByGroupId).reduce( + (acc, groupId) => { + const axis = yAxes.find((yAxis) => yAxis.groupId === groupId); + const ticks = axis?.ticks ?? Y_SCALE_DEFAULT.ticks; + const apiScale = scaleTypeByGroupId[groupId]; + const customDomain = customDomainByGroupId.get(groupId); + if (!acc[groupId]) { + acc[groupId] = { + customDomain, + ...apiScale, + ticks, + }; + } + acc[groupId].ticks = Math.min(acc[groupId].ticks, ticks); + acc[groupId].customDomain = customDomain; + return acc; + }, + { + [DEFAULT_GLOBAL_ID]: Y_SCALE_DEFAULT, + }, + ); + + return { x, y }; +} diff --git a/src/chart_types/xy_chart/state/selectors/get_data_domain.ts b/src/chart_types/xy_chart/state/selectors/get_data_domain.ts new file mode 100644 index 0000000000..460a858581 --- /dev/null +++ b/src/chart_types/xy_chart/state/selectors/get_data_domain.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ 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 5acc516aa4..865bc15b02 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 @@ -17,23 +17,11 @@ * under the License. */ -import createCachedSelector from 're-reselect'; - -import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { Rotation } from '../../../../utils/common'; import { GroupId } from '../../../../utils/ids'; import { isCompleteBound, isLowerBound, isUpperBound, isBounded } from '../../utils/axis_type_utils'; import { isYDomain } from '../../utils/axis_utils'; import { AxisSpec, YDomainRange } from '../../utils/specs'; -import { getAxisSpecsSelector } from './get_specs'; - -/** @internal */ -export const mergeYCustomDomainsByGroupIdSelector = createCachedSelector( - [getAxisSpecsSelector, getSettingsSpecSelector], - (axisSpecs, settingsSpec): Map => - mergeYCustomDomainsByGroupId(axisSpecs, settingsSpec ? settingsSpec.rotation : 0), -)(getChartIdSelector); /** @internal */ export function mergeYCustomDomainsByGroupId( diff --git a/src/chart_types/xy_chart/state/utils/utils.test.ts b/src/chart_types/xy_chart/state/utils/utils.test.ts index cd319220c6..35294519ff 100644 --- a/src/chart_types/xy_chart/state/utils/utils.test.ts +++ b/src/chart_types/xy_chart/state/utils/utils.test.ts @@ -23,6 +23,7 @@ import { MockDataSeries } from '../../../../mocks/series/series'; import { MockSeriesSpec, MockGlobalSpec } from '../../../../mocks/specs'; import { MockStore } from '../../../../mocks/store'; import { SeededDataGenerator } from '../../../../mocks/utils'; +import { MockXDomain, MockYDomain } from '../../../../mocks/xy/domains'; import { ScaleContinuous } from '../../../../scales'; import { ScaleType } from '../../../../scales/constants'; import { Spec } from '../../../../specs'; @@ -30,11 +31,11 @@ import { BARCHART_1Y0G, BARCHART_1Y1G } from '../../../../utils/data_samples/tes import { ContinuousDomain, Range } from '../../../../utils/domain'; import { SpecId } from '../../../../utils/ids'; import { PointShape } from '../../../../utils/themes/theme'; -import { getXScaleConfig, getYScaleConfig } from '../../scales/get_scale_config'; import { getSeriesIndex, XYChartSeriesIdentifier } from '../../utils/series'; import { BasicSeriesSpec, HistogramModeAlignments, SeriesColorAccessorFn } from '../../utils/specs'; import { computeSeriesDomainsSelector } from '../selectors/compute_series_domains'; import { computeSeriesGeometriesSelector } from '../selectors/compute_series_geometries'; +import { getAPIScaleConfigs } from '../selectors/get_api_scale_configs'; import { computeSeriesDomains, computeXScaleOffset, @@ -77,33 +78,30 @@ describe('Chart State utils', () => { yAccessors: ['y'], data: BARCHART_1Y0G, }); - const domains = computeSeriesDomains([spec1, spec2], new Map()); - expect(domains.xDomain).toEqual({ - domain: [0, 3], - isBandScale: false, - scaleConfig: getXScaleConfig(ScaleType.Linear), - minInterval: 1, - type: 'xDomain', - }); + const apiScaleConfig = getAPIScaleConfigs([], [spec1, spec2], MockGlobalSpec.settings()); + const domains = computeSeriesDomains([spec1, spec2], apiScaleConfig); + expect(domains.xDomain).toEqual( + MockXDomain.fromScaleType(ScaleType.Linear, { + domain: [0, 3], + isBandScale: false, + minInterval: 1, + }), + ); expect(domains.yDomains).toEqual([ - { + MockYDomain.fromScaleType(ScaleType.Log, { domain: [0, 10], - scaleConfig: getYScaleConfig(ScaleType.Log), groupId: 'group1', isBandScale: false, - type: 'yDomain', logBase: undefined, logMinLimit: undefined, - }, - { + }), + MockYDomain.fromScaleType(ScaleType.Log, { domain: [0, 10], - scaleConfig: getYScaleConfig(ScaleType.Log), groupId: 'group2', isBandScale: false, - type: 'yDomain', logBase: undefined, logMinLimit: undefined, - }, + }), ]); expect(domains.formattedDataSeries).toMatchSnapshot(); }); @@ -129,33 +127,30 @@ describe('Chart State utils', () => { stackAccessors: ['x'], data: BARCHART_1Y1G, }); - const domains = computeSeriesDomains([spec1, spec2], new Map()); - expect(domains.xDomain).toEqual({ - domain: [0, 3], - isBandScale: false, - scaleConfig: getXScaleConfig(ScaleType.Linear), - minInterval: 1, - type: 'xDomain', - }); + const apiScaleConfig = getAPIScaleConfigs([], [spec1, spec2], MockGlobalSpec.settings()); + const domains = computeSeriesDomains([spec1, spec2], apiScaleConfig); + expect(domains.xDomain).toEqual( + MockXDomain.fromScaleType(ScaleType.Log, { + domain: [0, 3], + isBandScale: false, + minInterval: 1, + }), + ); expect(domains.yDomains).toEqual([ - { + MockYDomain.fromScaleType(ScaleType.Log, { domain: [0, 5], - scaleConfig: getYScaleConfig(ScaleType.Log), groupId: 'group1', isBandScale: false, - type: 'yDomain', logBase: undefined, logMinLimit: undefined, - }, - { + }), + MockYDomain.fromScaleType(ScaleType.Log, { domain: [0, 9], - scaleConfig: getYScaleConfig(ScaleType.Log), groupId: 'group2', isBandScale: false, - type: 'yDomain', logBase: undefined, logMinLimit: undefined, - }, + }), ]); expect(domains.formattedDataSeries.filter(({ isStacked }) => isStacked)).toMatchSnapshot(); expect(domains.formattedDataSeries.filter(({ isStacked }) => !isStacked)).toMatchSnapshot(); @@ -764,7 +759,7 @@ describe('Chart State utils', () => { data: BARCHART_1Y1G, }); const histogramBar = MockSeriesSpec.histogramBar({ - id: 'histo', + id: 'histogram', groupId: 'group2', yScaleType: ScaleType.Log, xScaleType: ScaleType.Linear, diff --git a/src/chart_types/xy_chart/state/utils/utils.ts b/src/chart_types/xy_chart/state/utils/utils.ts index 0636c1cb66..6f6c45cb7a 100644 --- a/src/chart_types/xy_chart/state/utils/utils.ts +++ b/src/chart_types/xy_chart/state/utils/utils.ts @@ -20,7 +20,7 @@ import { getPredicateFn, Predicate } from '../../../../common/predicate'; import { SeriesKey, SeriesIdentifier } from '../../../../common/series_id'; import { Scale } from '../../../../scales'; -import { CustomXDomain, SortSeriesByConfig } from '../../../../specs'; +import { SortSeriesByConfig } from '../../../../specs'; import { OrderBy } from '../../../../specs/settings'; import { mergePartial, Rotation, Color, isUniqueArray } from '../../../../utils/common'; import { CurveType } from '../../../../utils/curves'; @@ -62,9 +62,9 @@ import { Fit, FitConfig, isBubbleSeriesSpec, - YDomainRange, } from '../../utils/specs'; import { SmallMultipleScales } from '../selectors/compute_small_multiple_scales'; +import { APIScaleConfigs } from '../selectors/get_api_scale_configs'; import { SmallMultiplesGroupBy } from '../selectors/get_specs'; import { isHorizontalRotation } from './common'; import { getSpecsById, getAxesSpecForSpecId, getSpecDomainGroupId } from './spec'; @@ -116,22 +116,12 @@ export function getCustomSeriesColors(dataSeries: DataSeries[]): Map, the custom X domain - * @param deselectedDataSeries is optional; if not supplied, - * @param customXDomain is optional; if not supplied, - * @param orderOrdinalBinsBy - * @param smallMultiples - * @param sortSeriesBy - * @returns `SeriesDomainsAndData` * @internal */ export function computeSeriesDomains( seriesSpecs: BasicSeriesSpec[], - customYDomainsByGroupId: Map = new Map(), + apiScaleConfigs: APIScaleConfigs, deselectedDataSeries: SeriesIdentifier[] = [], - customXDomain?: CustomXDomain, orderOrdinalBinsBy?: OrderBy, smallMultiples?: SmallMultiplesGroupBy, sortSeriesBy?: SeriesCompareFn | SortSeriesByConfig, @@ -143,24 +133,21 @@ export function computeSeriesDomains( smallMultiples, ); // compute the x domain merging any custom domain - const xDomain = mergeXDomain(seriesSpecs, xValues, customXDomain, fallbackScale); + const xDomain = mergeXDomain(apiScaleConfigs.x, xValues, fallbackScale); // fill series with missing x values - const filledDataSeries = fillSeries(dataSeries, xValues, xDomain.scaleConfig.type); + const filledDataSeries = fillSeries(dataSeries, xValues, xDomain.type); const seriesSortFn = getRenderingCompareFn(sortSeriesBy, (a: SeriesIdentifier, b: SeriesIdentifier) => { return defaultXYSeriesSort(a as DataSeries, b as DataSeries); }); - const formattedDataSeries = getFormattedDataSeries( - seriesSpecs, - filledDataSeries, - xValues, - xDomain.scaleConfig.type, - ).sort(seriesSortFn); + const formattedDataSeries = getFormattedDataSeries(seriesSpecs, filledDataSeries, xValues, xDomain.type).sort( + seriesSortFn, + ); // let's compute the yDomains after computing all stacked values - const yDomains = mergeYDomain(formattedDataSeries, customYDomainsByGroupId); + const yDomains = mergeYDomain(formattedDataSeries, apiScaleConfigs.y); // sort small multiples values const horizontalPredicate = smallMultiples?.horizontal?.sort ?? Predicate.DataIndex; 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 f085123784..a8bdc18b23 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -23,6 +23,7 @@ import moment from 'moment-timezone'; import { ChartTypes } from '../..'; import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs/specs'; import { MockStore } from '../../../mocks/store/store'; +import { MockXDomain, MockYDomain } from '../../../mocks/xy/domains'; import { Scale } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { SpecTypes } from '../../../specs/constants'; @@ -34,8 +35,6 @@ import { OrdinalDomain } from '../../../utils/domain'; import { AxisId, GroupId } from '../../../utils/ids'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { AxisStyle, TextOffset } from '../../../utils/themes/theme'; -import { XDomain, YDomain } from '../domains/types'; -import { getXScaleConfig, getYScaleConfig } from '../scales/get_scale_config'; import { computeAxesGeometriesSelector } from '../state/selectors/compute_axes_geometries'; import { computeAxisTicksDimensionsSelector } from '../state/selectors/compute_axis_ticks_dimensions'; import { getScale, SmallMultipleScales } from '../state/selectors/compute_small_multiple_scales'; @@ -190,21 +189,17 @@ describe('Axis computational utils', () => { [1, 1], ], }); - const xDomain: XDomain = { - type: 'xDomain', - scaleConfig: getXScaleConfig(ScaleType.Linear), + const xDomain = MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0, 1], isBandScale: false, minInterval: 0, - }; + }); - const yDomain: YDomain = { - scaleConfig: getYScaleConfig(ScaleType.Linear), + const yDomain = MockYDomain.fromScaleType(ScaleType.Linear, { groupId: 'group_1', - type: 'yDomain', domain: [0, 1], isBandScale: false, - }; + }); const getSmScales = (smHDomain: OrdinalDomain = [], smVDomain: OrdinalDomain = []): SmallMultipleScales => ({ horizontal: getScale(smHDomain, chartDim.width), @@ -280,14 +275,12 @@ describe('Axis computational utils', () => { test('should compute axis dimensions with timeZone', () => { const bboxCalculator = new SvgTextBBoxCalculator(); - const xDomain: XDomain = { - type: 'xDomain', - scaleConfig: getXScaleConfig(ScaleType.Time), + const xDomain = MockXDomain.fromScaleType(ScaleType.Time, { domain: [1551438000000, 1551441300000], isBandScale: false, minInterval: 0, timeZone: 'utc', - }; + }); let axisDimensions = computeAxisTicksDimensions( xAxisWithTime, xDomain, @@ -427,13 +420,11 @@ describe('Axis computational utils', () => { test('should extend ticks to domain + minInterval in histogram mode for linear scale', () => { const enableHistogramMode = true; - const xBandDomain: XDomain = { - type: 'xDomain', - scaleConfig: getXScaleConfig(ScaleType.Linear), + const xBandDomain = MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0, 100], isBandScale: true, minInterval: 10, - }; + }); const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0); const histogramAxisPositions = getAvailableTicks( horizontalAxisSpec, @@ -449,13 +440,11 @@ describe('Axis computational utils', () => { test('should extend ticks to domain + minInterval in histogram mode for time scale', () => { const enableHistogramMode = true; - const xBandDomain: XDomain = { - type: 'xDomain', - scaleConfig: getXScaleConfig(ScaleType.Time), + const xBandDomain = MockXDomain.fromScaleType(ScaleType.Time, { domain: [1560438420000, 1560438510000], isBandScale: true, minInterval: 90000, - }; + }); const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0); const histogramAxisPositions = getAvailableTicks( horizontalAxisSpec, @@ -488,13 +477,11 @@ describe('Axis computational utils', () => { test('should extend ticks to domain + minInterval in histogram mode for a scale with single datum', () => { const enableHistogramMode = true; - const xBandDomain: XDomain = { - type: 'xDomain', - scaleConfig: getXScaleConfig(ScaleType.Time), + const xBandDomain = MockXDomain.fromScaleType(ScaleType.Time, { domain: [1560438420000, 1560438420000], // a single datum scale will have the same value for domain start & end isBandScale: true, minInterval: 90000, - }; + }); const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0); const histogramAxisPositions = getAvailableTicks( horizontalAxisSpec, @@ -1524,13 +1511,11 @@ describe('Axis computational utils', () => { style, tickFormat: formatter, }; - const xDomainTime: XDomain = { - type: 'xDomain', + const xDomainTime = MockXDomain.fromScaleType(ScaleType.Time, { isBandScale: false, domain: [1547190000000, 1547622000000], minInterval: 86400000, - scaleConfig: getXScaleConfig(ScaleType.Time), - }; + }); const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; const tickFormatOption = { timeZone: 'utc+1' }; @@ -1558,14 +1543,12 @@ describe('Axis computational utils', () => { tickFormat: (d, options) => DateTime.fromMillis(d, { setZone: true, zone: options?.timeZone ?? 'utc+1' }).toFormat('HH:mm'), }; - const xDomainTime: XDomain = { - type: 'xDomain', + const xDomainTime = MockXDomain.fromScaleType(ScaleType.Time, { isBandScale: false, timeZone: 'utc+1', domain: [1547190000000, 1547622000000], minInterval: 86400000, - scaleConfig: getXScaleConfig(ScaleType.Time), - }; + }); const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; const tickFormatOption = { timeZone: xDomainTime.timeZone }; @@ -1601,13 +1584,11 @@ describe('Axis computational utils', () => { style, tickFormat: formatter, }; - const xDomainTime: XDomain = { - type: 'xDomain', + const xDomainTime = MockXDomain.fromScaleType(ScaleType.Time, { isBandScale: false, domain: [1547190000000, 1547622000000], minInterval: 86400000, - scaleConfig: getXScaleConfig(ScaleType.Time), - }; + }); const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; const tickFormatOption = { timeZone: 'utc+1' }; @@ -1641,13 +1622,11 @@ describe('Axis computational utils', () => { style, tickFormat: formatter, }; - const xDomainTime: XDomain = { - type: 'xDomain', + const xDomainTime = MockXDomain.fromScaleType(ScaleType.Time, { isBandScale: false, domain: [1547190000000, 1547622000000], minInterval: 86400000, - scaleConfig: getXScaleConfig(ScaleType.Time), - }; + }); const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; const tickFormatOption = { timeZone: 'utc+1' }; diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 139397ce89..4bcb18fd4c 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -167,7 +167,6 @@ export function getScaleForAxisSpec( const yScales = computeYScales({ yDomains, range, - ticks: axisSpec.ticks, integersOnly: axisSpec.integersOnly, }); if (yScales.has(axisSpec.groupId)) { @@ -181,7 +180,6 @@ export function getScaleForAxisSpec( range, barsPadding, enableHistogramMode, - ticks: axisSpec.ticks, integersOnly: axisSpec.integersOnly, }); } @@ -390,14 +388,6 @@ function getVerticalAlign( /** * Gets the computed x/y coordinates & alignment properties for an axis tick label. - * @param isVerticalAxis if the axis is vertical (in contrast to horizontal) - * @param tickPosition position of tick relative to axis line origin and other ticks along it - * @param position position of where the axis sits relative to the visualization - * @param axisSize - * @param tickDimensions - * @param showTicks - * @param textOffset - * @param textAlignment * @internal */ export function getTickLabelProps( diff --git a/src/chart_types/xy_chart/utils/scales.test.ts b/src/chart_types/xy_chart/utils/scales.test.ts index 6241d02008..8750459551 100644 --- a/src/chart_types/xy_chart/utils/scales.test.ts +++ b/src/chart_types/xy_chart/utils/scales.test.ts @@ -17,32 +17,22 @@ * under the License. */ +import { MockXDomain } from '../../../mocks/xy/domains'; import { ScaleType } from '../../../scales/constants'; -import { XDomain } from '../domains/types'; import { computeXScale } from './scales'; describe('Series scales', () => { - const xDomainLinear: XDomain = { - type: 'xDomain', + const xDomainLinear = MockXDomain.fromScaleType(ScaleType.Linear, { isBandScale: true, domain: [0, 3], minInterval: 1, - scaleConfig: { - type: ScaleType.Linear, - nice: false, - }, - }; + }); - const xDomainOrdinal: XDomain = { - type: 'xDomain', + const xDomainOrdinal = MockXDomain.fromScaleType(ScaleType.Ordinal, { isBandScale: true, domain: ['a', 'b'], minInterval: 1, - scaleConfig: { - type: ScaleType.Ordinal, - nice: false, - }, - }; + }); test('should compute X Scale linear min, max with bands', () => { const scale = computeXScale({ xDomain: xDomainLinear, totalBarsInCluster: 1, range: [0, 120] }); @@ -70,20 +60,15 @@ describe('Series scales', () => { const minInterval = 1; test('should return extended domain & range when in histogram mode', () => { - const xDomainSingleValue: XDomain = { - type: 'xDomain', + const xDomain = MockXDomain.fromScaleType(ScaleType.Linear, { isBandScale: true, domain: [singleDomainValue, singleDomainValue], minInterval, - scaleConfig: { - type: ScaleType.Linear, - nice: false, - }, - }; + }); const enableHistogramMode = true; const scale = computeXScale({ - xDomain: xDomainSingleValue, + xDomain, totalBarsInCluster: 1, range: [0, maxRange], barsPadding: 0, @@ -96,20 +81,15 @@ describe('Series scales', () => { }); test('should return unextended domain & range when not in histogram mode', () => { - const xDomainSingleValue: XDomain = { - type: 'xDomain', + const xDomain = MockXDomain.fromScaleType(ScaleType.Linear, { isBandScale: true, domain: [singleDomainValue, singleDomainValue], minInterval, - scaleConfig: { - type: ScaleType.Linear, - nice: false, - }, - }; + }); const enableHistogramMode = false; const scale = computeXScale({ - xDomain: xDomainSingleValue, + xDomain, totalBarsInCluster: 1, range: [0, maxRange], barsPadding: 0, @@ -133,16 +113,11 @@ describe('Series scales', () => { }); describe('bandwidth when totalBarsInCluster is greater than 0 or less than 0', () => { - const xDomainLinear: XDomain = { - type: 'xDomain', + const xDomainLinear = MockXDomain.fromScaleType(ScaleType.Linear, { isBandScale: true, domain: [0, 3], minInterval: 1, - scaleConfig: { - type: ScaleType.Linear, - nice: false, - }, - }; + }); const maxRange = 120; const scaleOver0 = computeXScale({ xDomain: xDomainLinear, diff --git a/src/chart_types/xy_chart/utils/scales.ts b/src/chart_types/xy_chart/utils/scales.ts index 5f7d44e181..b834238af2 100644 --- a/src/chart_types/xy_chart/utils/scales.ts +++ b/src/chart_types/xy_chart/utils/scales.ts @@ -46,7 +46,6 @@ interface XScaleOptions { range: Range; barsPadding?: number; enableHistogramMode?: boolean; - ticks?: number; integersOnly?: boolean; logBase?: LogBase; logMinLimit?: number; @@ -57,11 +56,11 @@ interface XScaleOptions { * @internal */ export function computeXScale(options: XScaleOptions): Scale { - const { xDomain, totalBarsInCluster, range, barsPadding, enableHistogramMode, ticks, integersOnly } = options; - const { scaleConfig, minInterval, domain, isBandScale, timeZone, logBase } = xDomain; + const { xDomain, totalBarsInCluster, range, barsPadding, enableHistogramMode, integersOnly } = options; + const { type, nice, minInterval, domain, isBandScale, timeZone, logBase, ticks } = xDomain; const rangeDiff = Math.abs(range[1] - range[0]); const isInverse = range[1] < range[0]; - if (scaleConfig.type === ScaleType.Ordinal) { + if (type === ScaleType.Ordinal) { const dividend = totalBarsInCluster > 0 ? totalBarsInCluster : 1; const bandwidth = rangeDiff / (domain.length * dividend); return new ScaleBand(domain, range, bandwidth, barsPadding); @@ -80,10 +79,10 @@ export function computeXScale(options: XScaleOptions): Scale { return new ScaleContinuous( { - type: scaleConfig.type, + type, domain: adjustedDomain, range: [start, end], - nice: scaleConfig.nice, + nice, }, { bandwidth: totalBarsInCluster > 0 ? bandwidth / totalBarsInCluster : bandwidth, @@ -98,7 +97,7 @@ export function computeXScale(options: XScaleOptions): Scale { ); } return new ScaleContinuous( - { type: scaleConfig.type, domain, range, nice: scaleConfig.nice }, + { type, domain, range, nice }, { bandwidth: 0, minInterval, @@ -115,7 +114,6 @@ export function computeXScale(options: XScaleOptions): Scale { interface YScaleOptions { yDomains: YDomain[]; range: Range; - ticks?: number; integersOnly?: boolean; } @@ -124,8 +122,8 @@ interface YScaleOptions { * @internal */ export function computeYScales(options: YScaleOptions): Map { - const { yDomains, range, ticks, integersOnly } = options; - return yDomains.reduce((yScales, { scaleConfig: { type, nice }, domain, groupId, logBase, logMinLimit }) => { + const { yDomains, range, integersOnly } = options; + return yDomains.reduce((yScales, { type, nice, ticks, domain, groupId, logBase, logMinLimit }) => { const yScale = new ScaleContinuous( { type, diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts index 5afe82ab1e..662d878309 100644 --- a/src/chart_types/xy_chart/utils/series.ts +++ b/src/chart_types/xy_chart/utils/series.ts @@ -28,7 +28,8 @@ import { GroupId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; import { ColorConfig } from '../../../utils/themes/theme'; import { groupSeriesByYGroup, isHistogramEnabled, isStackedSpec } from '../domains/y_domain'; -import { ScaleConfig } from '../scales/get_scale_config'; +import { X_SCALE_DEFAULT } from '../scales/scale_defaults'; +import { APIScale } from '../scales/types'; import { SmallMultiplesGroupBy } from '../state/selectors/get_specs'; import { applyFitFunctionToDataSeries } from './fit_function_utils'; import { groupBy } from './group_data_series'; @@ -360,7 +361,7 @@ export function getDataSeriesFromSpecs( xValues: Set; smVValues: Set; smHValues: Set; - fallbackScale?: ScaleConfig; + fallbackScale?: APIScale; } { let globalDataSeries: DataSeries[] = []; const mutatedXValueSums = new Map(); @@ -455,7 +456,7 @@ export function getDataSeriesFromSpecs( // keep the user order for ordinal scales xValues, ...smallMultipleUniqueValues, - fallbackScale: !isOrdinalScale && !isNumberArray ? { type: ScaleType.Ordinal, nice: false } : undefined, + fallbackScale: !isOrdinalScale && !isNumberArray ? X_SCALE_DEFAULT : undefined, }; } diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 051c139c73..d9779a4394 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -44,7 +44,7 @@ import { } from '../../../utils/themes/theme'; import { PrimitiveValue } from '../../partition_chart/layout/utils/group_by_rollup'; import { AnnotationTooltipFormatter, CustomAnnotationTooltip } from '../annotations/types'; -import { ScaleConfig } from '../scales/get_scale_config'; +import { APIScale } from '../scales/types'; import { XYChartSeriesIdentifier, DataSeriesDatum } from './series'; /** @public */ @@ -264,11 +264,11 @@ interface DomainBase { /** * Custom minInterval for the domain which will affect data bucket size. * The minInterval cannot be greater than the computed minimum interval between any two adjacent data points. - * Further, if you specify a custom numeric minInterval for a timeseries, please note that due to the restriction + * Further, if you specify a custom numeric minInterval for a time-series, please note that due to the restriction * above, the specified numeric minInterval will be interpreted as a fixed interval. - * This means that, for example, if you have yearly timeseries data that ranges from 2016 to 2019 and you manually + * This means that, for example, if you have yearly time-series data that ranges from 2016 to 2019 and you manually * compute the interval between 2016 and 2017, you'll have 366 days due to 2016 being a leap year. This will not - * be a valid interval because it is greater than the computed minInterval of 365 days betwen the other years. + * be a valid interval because it is greater than the computed minInterval of 365 days between the other years. */ minInterval?: number; } @@ -453,7 +453,7 @@ export interface SeriesScales { * The x axis scale type * @defaultValue `ordinal` {@link (ScaleType:type) | ScaleType.Ordinal} */ - xScaleType: XScaleType | ScaleConfig; + xScaleType: XScaleType | APIScale; /** * If using a ScaleType.Time this timezone identifier is required to * compute a nice set of xScale ticks. Can be any IANA zone supported by @@ -465,7 +465,7 @@ export interface SeriesScales { * The y axis scale type * @defaultValue `linear` {@link (ScaleType:type) | ScaleType.Linear} */ - yScaleType: ScaleContinuousType | ScaleConfig; + yScaleType: ScaleContinuousType | APIScale; /** * if true, the min y value is set to the minimum domain value, 0 otherwise * @deprecated use `domain.fit` instead diff --git a/src/mocks/xy/domains.ts b/src/mocks/xy/domains.ts new file mode 100644 index 0000000000..46feb87133 --- /dev/null +++ b/src/mocks/xy/domains.ts @@ -0,0 +1,68 @@ +/* + * 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 { XDomain, YDomain } from '../../chart_types/xy_chart/domains/types'; +import { getXAPIScale, getYAPIScale } from '../../chart_types/xy_chart/scales/get_api_scales'; +import { X_SCALE_DEFAULT, Y_SCALE_DEFAULT } from '../../chart_types/xy_chart/scales/scale_defaults'; +import { DEFAULT_GLOBAL_ID, XScaleType } from '../../chart_types/xy_chart/utils/specs'; +import { ScaleContinuousType } from '../../scales'; +import { ScaleType } from '../../scales/constants'; +import { mergePartial, RecursivePartial } from '../../utils/common'; + +export class MockXDomain { + private static readonly base: XDomain = { + ...X_SCALE_DEFAULT, + isBandScale: X_SCALE_DEFAULT.type !== ScaleType.Ordinal, + minInterval: 0, + timeZone: undefined, + domain: [0, 1], + }; + + static default(partial?: RecursivePartial) { + return mergePartial(MockXDomain.base, partial, { mergeOptionalPartialValues: true }); + } + + static fromScaleType(scaleType: XScaleType, partial?: RecursivePartial) { + return mergePartial(MockXDomain.base, partial, { mergeOptionalPartialValues: true }, [ + { + ...getXAPIScale(scaleType), + }, + ]); + } +} + +export class MockYDomain { + private static readonly base: YDomain = { + ...Y_SCALE_DEFAULT, + isBandScale: false, + groupId: DEFAULT_GLOBAL_ID, + domain: [0, 1], + }; + + static default(partial?: RecursivePartial) { + return mergePartial(MockYDomain.base, partial, { mergeOptionalPartialValues: true }); + } + + static fromScaleType(scaleType: ScaleContinuousType, partial?: RecursivePartial) { + return mergePartial(MockYDomain.base, partial, { mergeOptionalPartialValues: true }, [ + { + ...getYAPIScale(scaleType), + }, + ]); + } +} diff --git a/src/scales/scale_continuous.test.ts b/src/scales/scale_continuous.test.ts index 6305260c56..1c66b51ed3 100644 --- a/src/scales/scale_continuous.test.ts +++ b/src/scales/scale_continuous.test.ts @@ -20,9 +20,8 @@ import { DateTime, Settings } from 'luxon'; import { ScaleContinuous, ScaleBand } from '.'; -import { XDomain } from '../chart_types/xy_chart/domains/types'; -import { getXScaleConfig } from '../chart_types/xy_chart/scales/get_scale_config'; import { computeXScale } from '../chart_types/xy_chart/utils/scales'; +import { MockXDomain } from '../mocks/xy/domains'; import { ContinuousDomain, Range } from '../utils/domain'; import { LOG_MIN_ABS_DOMAIN, ScaleType } from './constants'; import { limitLogScaleDomain } from './scale_continuous'; @@ -102,13 +101,11 @@ describe('Scale Continuous', () => { }); test('invert with step x value on linear band scale', () => { const data = [0, 1, 2]; - const xDomain: XDomain = { + const xDomain = MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0, 2], isBandScale: true, minInterval: 1, - scaleConfig: getXScaleConfig(ScaleType.Linear), - type: 'xDomain', - }; + }); const scaleLinear = computeXScale({ xDomain, totalBarsInCluster: 1, range: [0, 119], barsPadding: 0 }); expect(scaleLinear.bandwidth).toBe(119 / 3); @@ -143,13 +140,11 @@ describe('Scale Continuous', () => { test('can get the right x value on linear scale with band', () => { const data = [0, 10, 20, 50, 90]; - const xDomain: XDomain = { + const xDomain = MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0, 100], isBandScale: true, minInterval: 10, - scaleConfig: getXScaleConfig(ScaleType.Linear), - type: 'xDomain', - }; + }); // we tweak the maxRange removing the bandwidth to correctly compute // a band linear scale in computeXScale const scaleLinear = computeXScale({ xDomain, totalBarsInCluster: 1, range: [0, 109], barsPadding: 0 }); diff --git a/src/scales/scale_continuous.ts b/src/scales/scale_continuous.ts index 67d457a26f..bf04b9b6b3 100644 --- a/src/scales/scale_continuous.ts +++ b/src/scales/scale_continuous.ts @@ -278,7 +278,6 @@ export class ScaleContinuous implements Scale { logBase, logMinLimit, } = mergePartial(defaultScaleOptions, options, { mergeOptionalPartialValues: true }); - this.d3Scale = SCALES[type](); if (type === ScaleType.Log) { diff --git a/stories/axes/8_custom_domain.tsx b/stories/axes/8_custom_domain.tsx index 3fe1134241..83ff8b0623 100644 --- a/stories/axes/8_custom_domain.tsx +++ b/stories/axes/8_custom_domain.tsx @@ -20,73 +20,145 @@ import { boolean, number } from '@storybook/addon-knobs'; import React from 'react'; -import { Axis, BarSeries, Chart, LineSeries, Position, ScaleType, Settings } from '../../src'; +import { Axis, BarSeries, Chart, LIGHT_THEME, LineSeries, Position, ScaleType, Settings } from '../../src'; export const Example = () => { - const leftDomain = { - min: number('left min', 0), - max: number('left max', 7), + const options = { + range: true, + min: 0, + max: 20, + step: 0.1, + }; + const barDomain = { + min: number('Bar min', 0, options, 'Bar'), + max: number('Bar max', 7, options, 'Bar'), }; - const rightDomain1 = { - min: number('right1 min', 0), - max: number('right1 max', 10), + const lineDomain = { + min: number('Line min', 0, options, 'Line'), + max: number('Line max', 10, options, 'Line'), }; - const rightDomain2 = { - min: number('right2 min', 0), - max: number('right2 max', 10), + const ticksOptions = { + range: true, + min: 1, + max: 15, + step: 1, }; + const barTicks = number('Bar ticks', 4, ticksOptions, 'Bar'); + const lineTicks = number('Line ticks', 10, ticksOptions, 'Line'); + const xOptions = { + range: true, + min: 0, + max: 6, + step: 1, + }; const xDomain = { - min: number('xDomain min', 0), - max: number('xDomain max', 3), + min: number('X min', 0, xOptions, 'X axis'), + max: number('X max', 3, xOptions, 'X axis'), }; + + const showBars = boolean('show bars', false, 'Bar'); + const nice = boolean('nice domain', true); return ( - - + Number(d).toFixed(2)} - domain={leftDomain} - hide={boolean('hide left axis', false)} + id="bottom" + position={Position.Bottom} + title="X axis" + // showOverlappingTicks + style={{ + // tickLabel: { + // padding: 5, + // }, + tickLine: { + visible: true, + }, + }} /> Number(d).toFixed(2)} - domain={rightDomain1} + domain={barDomain} + hide={boolean('Hide bar axis', false, 'Bar')} + ticks={barTicks} + style={{ + axisLine: { + stroke: LIGHT_THEME.colors.vizColors[0], + strokeWidth: 1.5, + }, + axisTitle: { + fill: LIGHT_THEME.colors.vizColors[0], + }, + tickLabel: { + fill: LIGHT_THEME.colors.vizColors[0], + }, + tickLine: { + stroke: LIGHT_THEME.colors.vizColors[0], + }, + }} /> Number(d).toFixed(2)} - domain={rightDomain2} - /> - + {!showBars && ( + + )} { data={[ { x: 0, y: 2, obj: { from: 10, to: 20 }, sObj: 'from 10 to 20' }, { x: 1, y: 7, obj: { from: 20, to: 30 }, sObj: 'from 20 to 30' }, - { x: 2, y: 3, obj: { from: 30, to: 40 }, sObj: 'from 30 to 40' }, + { x: 2, y: -3, obj: { from: 30, to: 40 }, sObj: 'from 30 to 40' }, { x: 3, y: 6, obj: { from: 40, to: 50 }, sObj: 'from 40 to 50' }, ]} /> diff --git a/stories/line/2_w_axis.tsx b/stories/line/2_w_axis.tsx index 3b09ddfed7..de32940c61 100644 --- a/stories/line/2_w_axis.tsx +++ b/stories/line/2_w_axis.tsx @@ -19,7 +19,7 @@ import React from 'react'; -import { Axis, Chart, LineSeries, niceTimeFormatByDay, Position, ScaleType, timeFormatter } from '../../src'; +import { Axis, Chart, LineSeries, niceTimeFormatByDay, Position, ScaleType, Settings, timeFormatter } from '../../src'; import { KIBANA_METRICS } from '../../src/utils/data_samples/test_dataset_kibana'; import { SB_SOURCE_PANEL } from '../utils/storybook'; @@ -27,6 +27,7 @@ const dateFormatter = timeFormatter(niceTimeFormatByDay(1)); export const Example = () => ( + ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 5)} + data={[ + [0, 0], + [1, 1], + [2, 10], + [3, 3], + ]} /> ); From 5aa72fc4b669115f3b13510f694384f9e74e892e Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Thu, 1 Apr 2021 14:59:53 +0200 Subject: [PATCH 04/11] test: fix wrong test change --- src/chart_types/xy_chart/state/utils/utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chart_types/xy_chart/state/utils/utils.test.ts b/src/chart_types/xy_chart/state/utils/utils.test.ts index 35294519ff..e62c26ce2d 100644 --- a/src/chart_types/xy_chart/state/utils/utils.test.ts +++ b/src/chart_types/xy_chart/state/utils/utils.test.ts @@ -130,7 +130,7 @@ describe('Chart State utils', () => { const apiScaleConfig = getAPIScaleConfigs([], [spec1, spec2], MockGlobalSpec.settings()); const domains = computeSeriesDomains([spec1, spec2], apiScaleConfig); expect(domains.xDomain).toEqual( - MockXDomain.fromScaleType(ScaleType.Log, { + MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0, 3], isBandScale: false, minInterval: 1, From 6ec6b3e9c60b62df92b6245066cfdb5a5e8607be Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Tue, 6 Apr 2021 15:04:51 +0200 Subject: [PATCH 05/11] fix: reduce global scale correctly --- .../state/selectors/get_api_scale_configs.ts | 46 +++++++------------ stories/line/2_w_axis.tsx | 7 +-- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts index 276c845b00..c0205d1579 100644 --- a/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts +++ b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts @@ -31,14 +31,7 @@ import { X_SCALE_DEFAULT, Y_SCALE_DEFAULT } from '../../scales/scale_defaults'; import { APIScale } from '../../scales/types'; import { isHorizontalAxis, isVerticalAxis } from '../../utils/axis_type_utils'; import { groupBy } from '../../utils/group_data_series'; -import { - AxisSpec, - BasicSeriesSpec, - CustomXDomain, - DEFAULT_GLOBAL_ID, - XScaleType, - YDomainRange, -} from '../../utils/specs'; +import { AxisSpec, BasicSeriesSpec, CustomXDomain, XScaleType, YDomainRange } from '../../utils/specs'; import { isHorizontalRotation } from '../utils/common'; import { getAxisSpecsSelector, getSeriesSpecsSelector } from './get_specs'; import { mergeYCustomDomainsByGroupId } from './merge_y_custom_domains'; @@ -99,27 +92,20 @@ export function getAPIScaleConfigs( const customDomainByGroupId = mergeYCustomDomainsByGroupId(axisSpecs, settingsSpec.rotation); const yAxes = axisSpecs.filter((d) => isHorizontalChart === isVerticalAxis(d.position)); - const y = Object.keys(scaleTypeByGroupId).reduce( - (acc, groupId) => { - const axis = yAxes.find((yAxis) => yAxis.groupId === groupId); - const ticks = axis?.ticks ?? Y_SCALE_DEFAULT.ticks; - const apiScale = scaleTypeByGroupId[groupId]; - const customDomain = customDomainByGroupId.get(groupId); - if (!acc[groupId]) { - acc[groupId] = { - customDomain, - ...apiScale, - ticks, - }; - } - acc[groupId].ticks = Math.min(acc[groupId].ticks, ticks); - acc[groupId].customDomain = customDomain; - return acc; - }, - { - [DEFAULT_GLOBAL_ID]: Y_SCALE_DEFAULT, - }, - ); - + const y = Object.keys(scaleTypeByGroupId).reduce((acc, groupId) => { + const axis = yAxes.find((yAxis) => yAxis.groupId === groupId); + const ticks = axis?.ticks ?? Y_SCALE_DEFAULT.ticks; + const apiScale = scaleTypeByGroupId[groupId]; + const customDomain = customDomainByGroupId.get(groupId); + if (!acc[groupId]) { + acc[groupId] = { + customDomain, + ...apiScale, + ticks, + }; + } + acc[groupId].ticks = Math.min(acc[groupId].ticks, ticks); + return acc; + }, {}); return { x, y }; } diff --git a/stories/line/2_w_axis.tsx b/stories/line/2_w_axis.tsx index de32940c61..36c4c67cac 100644 --- a/stories/line/2_w_axis.tsx +++ b/stories/line/2_w_axis.tsx @@ -41,12 +41,7 @@ export const Example = () => ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={[ - [0, 0], - [1, 1], - [2, 10], - [3, 3], - ]} + data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 5)} /> ); From 5d2f1a88fd63d3726c45430e58a39dfe2f010b2d Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Tue, 6 Apr 2021 16:45:40 +0200 Subject: [PATCH 06/11] fix: stories --- ...m-domain-visually-looks-correct-1-snap.png | Bin 24604 -> 17887 bytes .../state/selectors/get_api_scale_configs.ts | 6 +++--- stories/interactions/1_bar_clicks.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-custom-domain-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-custom-domain-visually-looks-correct-1-snap.png index f161bace35bd6452178b65e7950e5eed3824c796..d3845a3dab12b939d904d4c4037ea9f4f5030ff7 100644 GIT binary patch literal 17887 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
x-i*qQOy34*n_i_Fu(Nn`tesS%U9s@4(Qq^}9$ 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 Hx*z@z4<)Ur literal 24604 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 isHorizontalChart === isHorizontalAxis(d.position)); - const xTicks = xAxes.reduce((acc, curr) => { - return Math.min(acc, curr.ticks ?? acc); + const xTicks = xAxes.reduce((acc, { ticks = X_SCALE_DEFAULT.ticks }) => { + return Math.max(acc, ticks); }, X_SCALE_DEFAULT.ticks); const xScaleConfig = convertXScaleTypes(seriesSpecs); @@ -104,7 +104,7 @@ export function getAPIScaleConfigs( ticks, }; } - acc[groupId].ticks = Math.min(acc[groupId].ticks, ticks); + acc[groupId].ticks = Math.max(acc[groupId].ticks, ticks); return acc; }, {}); return { x, y }; diff --git a/stories/interactions/1_bar_clicks.tsx b/stories/interactions/1_bar_clicks.tsx index f06e100fb4..5ae5bf0c75 100644 --- a/stories/interactions/1_bar_clicks.tsx +++ b/stories/interactions/1_bar_clicks.tsx @@ -70,7 +70,7 @@ export const Example = () => { data={[ { x: 0, y: 2, obj: { from: 10, to: 20 }, sObj: 'from 10 to 20' }, { x: 1, y: 7, obj: { from: 20, to: 30 }, sObj: 'from 20 to 30' }, - { x: 2, y: -3, obj: { from: 30, to: 40 }, sObj: 'from 30 to 40' }, + { x: 2, y: 3, obj: { from: 30, to: 40 }, sObj: 'from 30 to 40' }, { x: 3, y: 6, obj: { from: 40, to: 50 }, sObj: 'from 40 to 50' }, ]} /> From 08527a0e9741ec2d665c5cbe039d9e8afe160398 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 7 Apr 2021 14:25:38 +0200 Subject: [PATCH 07/11] chore: fix lint and api docs --- api/charts.api.md | 12 ++++++++++-- src/chart_types/specs.ts | 2 ++ .../xy_chart/scales/get_api_scales.ts | 1 + .../xy_chart/scales/scale_defaults.ts | 1 + src/chart_types/xy_chart/scales/types.ts | 1 + .../state/selectors/compute_axes_geometries.ts | 1 + .../state/selectors/get_api_scale_configs.ts | 1 + .../state/selectors/get_data_domain.ts | 18 ------------------ src/mocks/xy/domains.ts | 3 +++ 9 files changed, 20 insertions(+), 20 deletions(-) delete mode 100644 src/chart_types/xy_chart/state/selectors/get_data_domain.ts diff --git a/api/charts.api.md b/api/charts.api.md index e85da28288..ed92228406 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -71,6 +71,14 @@ export const AnnotationType: Readonly<{ // @public (undocumented) export type AnnotationType = $Values; +// @public (undocumented) +export interface APIScale { + // (undocumented) + nice: boolean; + // (undocumented) + type: T; +} + // @public (undocumented) export interface ArcSeriesStyle { // (undocumented) @@ -1670,10 +1678,10 @@ export type SeriesNameFn = (series: XYChartSeriesIdentifier, isTooltip: boolean) // @public (undocumented) export interface SeriesScales { timeZone?: string; - xScaleType: XScaleType; + xScaleType: XScaleType | APIScale; // @deprecated yScaleToDataExtent?: boolean; - yScaleType: ScaleContinuousType; + yScaleType: ScaleContinuousType | APIScale; } // @public (undocumented) diff --git a/src/chart_types/specs.ts b/src/chart_types/specs.ts index ef094975bc..030b49379a 100644 --- a/src/chart_types/specs.ts +++ b/src/chart_types/specs.ts @@ -28,6 +28,8 @@ export { RectAnnotation, } from './xy_chart/specs'; +export { APIScale } from './xy_chart/scales/types'; + export * from './xy_chart/utils/specs'; export { Partition } from './partition_chart/specs'; diff --git a/src/chart_types/xy_chart/scales/get_api_scales.ts b/src/chart_types/xy_chart/scales/get_api_scales.ts index 765b814162..ec8796f0a1 100644 --- a/src/chart_types/xy_chart/scales/get_api_scales.ts +++ b/src/chart_types/xy_chart/scales/get_api_scales.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { ScaleContinuousType } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { BasicSeriesSpec, XScaleType } from '../utils/specs'; diff --git a/src/chart_types/xy_chart/scales/scale_defaults.ts b/src/chart_types/xy_chart/scales/scale_defaults.ts index ef0e6d4134..1c3949b356 100644 --- a/src/chart_types/xy_chart/scales/scale_defaults.ts +++ b/src/chart_types/xy_chart/scales/scale_defaults.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { ScaleType } from '../../../scales/constants'; /** @internal */ diff --git a/src/chart_types/xy_chart/scales/types.ts b/src/chart_types/xy_chart/scales/types.ts index 18054995c4..dc51bbc915 100644 --- a/src/chart_types/xy_chart/scales/types.ts +++ b/src/chart_types/xy_chart/scales/types.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { ScaleType } from '../../../scales/constants'; /** @public */ diff --git a/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts index ecfbcff7f1..dbbefb6494 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import createCachedSelector from 're-reselect'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; diff --git a/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts index 2e282c77a1..c4ed8f4220 100644 --- a/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts +++ b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import createCachedSelector from 're-reselect'; import { ScaleContinuousType } from '../../../../scales'; diff --git a/src/chart_types/xy_chart/state/selectors/get_data_domain.ts b/src/chart_types/xy_chart/state/selectors/get_data_domain.ts deleted file mode 100644 index 460a858581..0000000000 --- a/src/chart_types/xy_chart/state/selectors/get_data_domain.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ diff --git a/src/mocks/xy/domains.ts b/src/mocks/xy/domains.ts index 46feb87133..656d99979d 100644 --- a/src/mocks/xy/domains.ts +++ b/src/mocks/xy/domains.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { XDomain, YDomain } from '../../chart_types/xy_chart/domains/types'; import { getXAPIScale, getYAPIScale } from '../../chart_types/xy_chart/scales/get_api_scales'; import { X_SCALE_DEFAULT, Y_SCALE_DEFAULT } from '../../chart_types/xy_chart/scales/scale_defaults'; @@ -24,6 +25,7 @@ import { ScaleContinuousType } from '../../scales'; import { ScaleType } from '../../scales/constants'; import { mergePartial, RecursivePartial } from '../../utils/common'; +/** @internal */ export class MockXDomain { private static readonly base: XDomain = { ...X_SCALE_DEFAULT, @@ -46,6 +48,7 @@ export class MockXDomain { } } +/** @internal */ export class MockYDomain { private static readonly base: YDomain = { ...Y_SCALE_DEFAULT, From b9db40c4442908e52c9f4c7961fc41daa2ba6138 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 7 Apr 2021 22:11:32 +0200 Subject: [PATCH 08/11] fix: review comments --- src/chart_types/heatmap/specs/heatmap.ts | 3 ++- .../heatmap/specs/scale_defaults.ts | 27 +++++++++++++++++++ .../state/selectors/get_heatmap_table.ts | 10 +++++-- .../xy_chart/scales/get_api_scales.ts | 2 +- .../selectors/compute_axes_geometries.ts | 1 - stories/axes/8_custom_domain.tsx | 4 +-- 6 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 src/chart_types/heatmap/specs/scale_defaults.ts diff --git a/src/chart_types/heatmap/specs/heatmap.ts b/src/chart_types/heatmap/specs/heatmap.ts index 0cdd896fe1..d399c46c46 100644 --- a/src/chart_types/heatmap/specs/heatmap.ts +++ b/src/chart_types/heatmap/specs/heatmap.ts @@ -29,6 +29,7 @@ import { Accessor, AccessorFn } from '../../../utils/accessor'; import { Color, Datum, RecursivePartial } from '../../../utils/common'; import { config } from '../layout/config/config'; import { Config } from '../layout/types/config_types'; +import { X_SCALE_DEFAULT } from './scale_defaults'; const defaultProps = { chartType: ChartType.Heatmap, @@ -38,7 +39,7 @@ const defaultProps = { colorScale: ScaleType.Linear, xAccessor: ({ x }: { x: string | number }) => x, yAccessor: ({ y }: { y: string | number }) => y, - xScaleType: ScaleType.Ordinal, + xScaleType: X_SCALE_DEFAULT.type, valueAccessor: ({ value }: { value: string | number }) => value, valueFormatter: (value: number) => `${value}`, xSortPredicate: Predicate.AlphaAsc, diff --git a/src/chart_types/heatmap/specs/scale_defaults.ts b/src/chart_types/heatmap/specs/scale_defaults.ts new file mode 100644 index 0000000000..f413d5ef87 --- /dev/null +++ b/src/chart_types/heatmap/specs/scale_defaults.ts @@ -0,0 +1,27 @@ +/* + * 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 { ScaleType } from '../../../scales/constants'; + +/** @internal */ +export const X_SCALE_DEFAULT = { + type: ScaleType.Ordinal, + nice: false, + ticks: 10, +}; diff --git a/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts b/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts index b61573a3bd..52597b15d1 100644 --- a/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts +++ b/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts @@ -25,7 +25,8 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { getAccessorValue } from '../../../../utils/accessor'; import { mergeXDomain } from '../../../xy_chart/domains/x_domain'; -import { getXAPIScale } from '../../../xy_chart/scales/get_api_scales'; +import { getDefaultAPIScale } from '../../../xy_chart/scales/get_api_scales'; +import { X_SCALE_DEFAULT } from '../../specs/scale_defaults'; import { HeatmapTable } from './compute_chart_dimensions'; import { getHeatmapSpecSelector } from './get_heatmap_spec'; @@ -75,7 +76,12 @@ export const getHeatmapTableSelector = createCachedSelector( ); resultData.xDomain = mergeXDomain( - { ...getXAPIScale(spec.xScaleType), isBandScale: false, ticks: 10, customDomain: xDomain }, + { + ...getDefaultAPIScale(spec.xScaleType, X_SCALE_DEFAULT), + isBandScale: false, + ticks: X_SCALE_DEFAULT.ticks, + customDomain: xDomain, + }, resultData.xValues, ); diff --git a/src/chart_types/xy_chart/scales/get_api_scales.ts b/src/chart_types/xy_chart/scales/get_api_scales.ts index ec8796f0a1..d598c7e224 100644 --- a/src/chart_types/xy_chart/scales/get_api_scales.ts +++ b/src/chart_types/xy_chart/scales/get_api_scales.ts @@ -34,7 +34,7 @@ export function getYAPIScale(scaleType: BasicSeriesSpec['yScaleType']): APIScale } /** @internal */ -function getDefaultAPIScale(type: T | APIScale, defaults: APIScale): APIScale { +export function getDefaultAPIScale(type: T | APIScale, defaults: APIScale): APIScale { if (typeof type === 'object') { return type; } diff --git a/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts index dbbefb6494..7c68a34da9 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts @@ -62,7 +62,6 @@ export const computeAxesGeometriesSelector = createCachedSelector( barsPadding, seriesSpecs, smScales, - // apiScaleConfigs, ): AxisGeometry[] => { const fallBackTickFormatter = seriesSpecs.find(({ tickFormat }) => tickFormat)?.tickFormat ?? defaultTickFormatter; const { xDomain, yDomains } = seriesDomainsAndData; diff --git a/stories/axes/8_custom_domain.tsx b/stories/axes/8_custom_domain.tsx index 83ff8b0623..bc0c146d1f 100644 --- a/stories/axes/8_custom_domain.tsx +++ b/stories/axes/8_custom_domain.tsx @@ -59,7 +59,7 @@ export const Example = () => { max: number('X max', 3, xOptions, 'X axis'), }; - const showBars = boolean('show bars', false, 'Bar'); + const showBars = boolean('show bars', true, 'Bar'); const nice = boolean('nice domain', true); return ( @@ -131,7 +131,7 @@ export const Example = () => { }, }} /> - {!showBars && ( + {showBars && ( Date: Thu, 8 Apr 2021 14:16:58 +0200 Subject: [PATCH 09/11] refactor: rename internal ticks in desiredTickCount --- .../heatmap/layout/viewmodel/viewmodel.ts | 2 +- src/chart_types/heatmap/specs/scale_defaults.ts | 2 +- .../heatmap/state/selectors/get_heatmap_table.ts | 2 +- src/chart_types/xy_chart/domains/types.ts | 4 ++-- src/chart_types/xy_chart/domains/x_domain.ts | 4 ++-- src/chart_types/xy_chart/domains/y_domain.ts | 4 ++-- src/chart_types/xy_chart/scales/scale_defaults.ts | 4 ++-- .../state/selectors/get_api_scale_configs.ts | 14 +++++++------- src/chart_types/xy_chart/utils/scales.ts | 10 +++++----- src/scales/scale_continuous.ts | 12 ++++++------ 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts b/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts index 06c6ac2c5a..8fbb8d6ef8 100644 --- a/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts +++ b/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts @@ -119,7 +119,7 @@ export function shapeViewModel( nice: false, }, { - ticks: getTicks(chartDimensions.width, config.xAxisLabel), + desiredTickCount: getTicks(chartDimensions.width, config.xAxisLabel), timeZone: config.timeZone, }, ) diff --git a/src/chart_types/heatmap/specs/scale_defaults.ts b/src/chart_types/heatmap/specs/scale_defaults.ts index f413d5ef87..4299532599 100644 --- a/src/chart_types/heatmap/specs/scale_defaults.ts +++ b/src/chart_types/heatmap/specs/scale_defaults.ts @@ -23,5 +23,5 @@ import { ScaleType } from '../../../scales/constants'; export const X_SCALE_DEFAULT = { type: ScaleType.Ordinal, nice: false, - ticks: 10, + desiredTickCount: 10, }; diff --git a/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts b/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts index 52597b15d1..6e08a22676 100644 --- a/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts +++ b/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts @@ -79,7 +79,7 @@ export const getHeatmapTableSelector = createCachedSelector( { ...getDefaultAPIScale(spec.xScaleType, X_SCALE_DEFAULT), isBandScale: false, - ticks: X_SCALE_DEFAULT.ticks, + desiredTickCount: X_SCALE_DEFAULT.desiredTickCount, customDomain: xDomain, }, resultData.xValues, diff --git a/src/chart_types/xy_chart/domains/types.ts b/src/chart_types/xy_chart/domains/types.ts index cefcb13605..37ccffc3c1 100644 --- a/src/chart_types/xy_chart/domains/types.ts +++ b/src/chart_types/xy_chart/domains/types.ts @@ -34,7 +34,7 @@ export type XDomain = Pick & /** if x domain is time, we should also specify the timezone */ timeZone?: string; domain: OrdinalDomain | ContinuousDomain; - ticks: number; + desiredTickCount: number; }; /** @internal */ @@ -43,5 +43,5 @@ export type YDomain = LogScaleOptions & isBandScale: false; groupId: GroupId; domain: ContinuousDomain; - ticks: number; + desiredTickCount: number; }; diff --git a/src/chart_types/xy_chart/domains/x_domain.ts b/src/chart_types/xy_chart/domains/x_domain.ts index fc25e63db2..cc70b3bacd 100644 --- a/src/chart_types/xy_chart/domains/x_domain.ts +++ b/src/chart_types/xy_chart/domains/x_domain.ts @@ -40,7 +40,7 @@ export function mergeXDomain( xValues: Set, fallbackScale?: APIScale, ): XDomain { - const { type, nice, isBandScale, timeZone, ticks, customDomain } = apiScaleConfig; + const { type, nice, isBandScale, timeZone, desiredTickCount, customDomain } = apiScaleConfig; const values = [...xValues.values()]; let seriesXComputedDomains; let minInterval = 0; @@ -119,7 +119,7 @@ export function mergeXDomain( minInterval, timeZone, logBase: customDomain && 'logBase' in customDomain ? customDomain.logBase : undefined, - ticks, + desiredTickCount, }; } diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts index bd019e4829..d1ad1ce3c4 100644 --- a/src/chart_types/xy_chart/domains/y_domain.ts +++ b/src/chart_types/xy_chart/domains/y_domain.ts @@ -70,7 +70,7 @@ function mergeYDomainForGroup( const [{ stackMode, spec }] = dataSeries; const groupId = getSpecDomainGroupId(spec); - const { customDomain, type, nice, ticks } = yAPIScaleConfig[groupId]; + const { customDomain, type, nice, desiredTickCount } = yAPIScaleConfig[groupId]; let domain: ContinuousDomain; if (stackMode === StackMode.Percentage) { @@ -121,7 +121,7 @@ function mergeYDomainForGroup( domain, logBase: customDomain?.logBase, logMinLimit: customDomain?.logMinLimit, - ticks, + desiredTickCount, }; } diff --git a/src/chart_types/xy_chart/scales/scale_defaults.ts b/src/chart_types/xy_chart/scales/scale_defaults.ts index 1c3949b356..7eb54d56c2 100644 --- a/src/chart_types/xy_chart/scales/scale_defaults.ts +++ b/src/chart_types/xy_chart/scales/scale_defaults.ts @@ -23,12 +23,12 @@ import { ScaleType } from '../../../scales/constants'; export const X_SCALE_DEFAULT = { type: ScaleType.Ordinal, nice: false, - ticks: 10, + desiredTickCount: 10, }; /** @internal */ export const Y_SCALE_DEFAULT = { type: ScaleType.Linear, nice: false, - ticks: 10, + desiredTickCount: 10, }; diff --git a/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts index c4ed8f4220..b45805f3fa 100644 --- a/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts +++ b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts @@ -39,7 +39,7 @@ import { mergeYCustomDomainsByGroupId } from './merge_y_custom_domains'; /** @internal */ export type APIScaleConfigBase = APIScale & { - ticks: number; + desiredTickCount: number; customDomain?: D; }; type APIXScaleConfigBase = APIScaleConfigBase; @@ -70,15 +70,15 @@ export function getAPIScaleConfigs( // x axis const xAxes = axisSpecs.filter((d) => isHorizontalChart === isHorizontalAxis(d.position)); - const xTicks = xAxes.reduce((acc, { ticks = X_SCALE_DEFAULT.ticks }) => { + const xTicks = xAxes.reduce((acc, { ticks = X_SCALE_DEFAULT.desiredTickCount }) => { return Math.max(acc, ticks); - }, X_SCALE_DEFAULT.ticks); + }, X_SCALE_DEFAULT.desiredTickCount); const xScaleConfig = convertXScaleTypes(seriesSpecs); const x: APIScaleConfigs['x'] = { customDomain: settingsSpec.xDomain, ...xScaleConfig, - ticks: xTicks, + desiredTickCount: xTicks, }; // y axes @@ -95,17 +95,17 @@ export function getAPIScaleConfigs( const yAxes = axisSpecs.filter((d) => isHorizontalChart === isVerticalAxis(d.position)); const y = Object.keys(scaleTypeByGroupId).reduce((acc, groupId) => { const axis = yAxes.find((yAxis) => yAxis.groupId === groupId); - const ticks = axis?.ticks ?? Y_SCALE_DEFAULT.ticks; + const desiredTickCount = axis?.ticks ?? Y_SCALE_DEFAULT.desiredTickCount; const apiScale = scaleTypeByGroupId[groupId]; const customDomain = customDomainByGroupId.get(groupId); if (!acc[groupId]) { acc[groupId] = { customDomain, ...apiScale, - ticks, + desiredTickCount, }; } - acc[groupId].ticks = Math.max(acc[groupId].ticks, ticks); + acc[groupId].desiredTickCount = Math.max(acc[groupId].desiredTickCount, desiredTickCount); return acc; }, {}); return { x, y }; diff --git a/src/chart_types/xy_chart/utils/scales.ts b/src/chart_types/xy_chart/utils/scales.ts index b834238af2..3c9125e89d 100644 --- a/src/chart_types/xy_chart/utils/scales.ts +++ b/src/chart_types/xy_chart/utils/scales.ts @@ -57,7 +57,7 @@ interface XScaleOptions { */ export function computeXScale(options: XScaleOptions): Scale { const { xDomain, totalBarsInCluster, range, barsPadding, enableHistogramMode, integersOnly } = options; - const { type, nice, minInterval, domain, isBandScale, timeZone, logBase, ticks } = xDomain; + const { type, nice, minInterval, domain, isBandScale, timeZone, logBase, desiredTickCount } = xDomain; const rangeDiff = Math.abs(range[1] - range[0]); const isInverse = range[1] < range[0]; if (type === ScaleType.Ordinal) { @@ -90,7 +90,7 @@ export function computeXScale(options: XScaleOptions): Scale { timeZone, totalBarsInCluster, barsPadding, - ticks, + desiredTickCount, isSingleValueHistogram, logBase, }, @@ -104,7 +104,7 @@ export function computeXScale(options: XScaleOptions): Scale { timeZone, totalBarsInCluster, barsPadding, - ticks, + desiredTickCount, integersOnly, logBase, }, @@ -123,7 +123,7 @@ interface YScaleOptions { */ export function computeYScales(options: YScaleOptions): Map { const { yDomains, range, integersOnly } = options; - return yDomains.reduce((yScales, { type, nice, ticks, domain, groupId, logBase, logMinLimit }) => { + return yDomains.reduce((yScales, { type, nice, desiredTickCount, domain, groupId, logBase, logMinLimit }) => { const yScale = new ScaleContinuous( { type, @@ -132,7 +132,7 @@ export function computeYScales(options: YScaleOptions): Map { nice, }, { - ticks, + desiredTickCount, integersOnly, logBase, logMinLimit, diff --git a/src/scales/scale_continuous.ts b/src/scales/scale_continuous.ts index 8da1a03818..c265ee150e 100644 --- a/src/scales/scale_continuous.ts +++ b/src/scales/scale_continuous.ts @@ -209,7 +209,7 @@ type ScaleOptions = Required & { * The approximated number of ticks. * @defaultValue 10 */ - ticks: number; + desiredTickCount: number; /** * true if the scale was adjusted to fit one single value histogram */ @@ -226,7 +226,7 @@ const defaultScaleOptions: ScaleOptions = { timeZone: 'utc', totalBarsInCluster: 1, barsPadding: 0, - ticks: 10, + desiredTickCount: 10, isSingleValueHistogram: false, integersOnly: false, logBase: LogBase.Common, @@ -275,7 +275,7 @@ export class ScaleContinuous implements Scale { timeZone, totalBarsInCluster, barsPadding, - ticks, + desiredTickCount, isSingleValueHistogram, integersOnly, logBase, @@ -292,7 +292,7 @@ export class ScaleContinuous implements Scale { this.d3Scale.domain(this.domain); if (nice && type !== ScaleType.Time) { - (this.d3Scale as ScaleContinuousNumeric).domain(this.domain).nice(ticks); + (this.d3Scale as ScaleContinuousNumeric).domain(this.domain).nice(desiredTickCount); this.domain = this.d3Scale.domain(); } @@ -318,7 +318,7 @@ export class ScaleContinuous implements Scale { const shiftedDomainMax = endDomain.add(offset, 'minutes').valueOf(); const tzShiftedScale = scaleUtc().domain([shiftedDomainMin, shiftedDomainMax]); - const rawTicks = tzShiftedScale.ticks(ticks); + const rawTicks = tzShiftedScale.ticks(desiredTickCount); const timePerTick = (shiftedDomainMax - shiftedDomainMin) / rawTicks.length; const hasHourTicks = timePerTick < 1000 * 60 * 60 * 12; @@ -334,7 +334,7 @@ export class ScaleContinuous implements Scale { const intervalCount = Math.floor((this.domain[1] - this.domain[0]) / this.minInterval); this.tickValues = new Array(intervalCount + 1).fill(0).map((_, i) => this.domain[0] + i * this.minInterval); } else { - this.tickValues = this.getTicks(ticks, integersOnly); + this.tickValues = this.getTicks(desiredTickCount, integersOnly); } } } From c0060d0e550a7f11925cb1d957aa9c18de9b9010 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Mon, 12 Apr 2021 12:34:21 +0200 Subject: [PATCH 10/11] refactor: split type and nice into separate props --- api/charts.api.md | 14 +-- .../state/selectors/get_heatmap_table.ts | 5 +- src/chart_types/specs.ts | 2 - .../crosshair_utils.linear_snap.test.ts | 12 +- .../crosshair_utils.ordinal_snap.test.ts | 12 +- src/chart_types/xy_chart/domains/types.ts | 39 ++++--- .../xy_chart/domains/x_domain.test.ts | 110 +++++++++++------- src/chart_types/xy_chart/domains/x_domain.ts | 31 +++-- src/chart_types/xy_chart/domains/y_domain.ts | 13 ++- src/chart_types/xy_chart/legend/legend.ts | 8 +- .../xy_chart/scales/get_api_scales.ts | 25 ++-- src/chart_types/xy_chart/scales/types.ts | 26 ----- .../state/selectors/compute_series_domains.ts | 8 +- .../state/selectors/get_api_scale_configs.ts | 42 ++++--- .../xy_chart/state/utils/utils.test.ts | 10 +- src/chart_types/xy_chart/state/utils/utils.ts | 8 +- src/chart_types/xy_chart/utils/series.ts | 5 +- src/chart_types/xy_chart/utils/specs.ts | 15 ++- src/mocks/xy/domains.ts | 13 ++- stories/axes/8_custom_domain.tsx | 18 +-- 20 files changed, 207 insertions(+), 209 deletions(-) delete mode 100644 src/chart_types/xy_chart/scales/types.ts diff --git a/api/charts.api.md b/api/charts.api.md index 68246a6667..114e5ffa34 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -71,14 +71,6 @@ export const AnnotationType: Readonly<{ // @public (undocumented) export type AnnotationType = $Values; -// @public (undocumented) -export interface APIScale { - // (undocumented) - nice: boolean; - // (undocumented) - type: T; -} - // @public (undocumented) export interface ArcSeriesStyle { // (undocumented) @@ -1677,10 +1669,12 @@ export type SeriesNameFn = (series: XYChartSeriesIdentifier, isTooltip: boolean) // @public (undocumented) export interface SeriesScales { timeZone?: string; - xScaleType: XScaleType | APIScale; + xNice?: boolean; + xScaleType: XScaleType; + yNice?: boolean; // @deprecated yScaleToDataExtent?: boolean; - yScaleType: ScaleContinuousType | APIScale; + yScaleType: ScaleContinuousType; } // @public (undocumented) diff --git a/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts b/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts index 6e08a22676..63e3c2c829 100644 --- a/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts +++ b/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts @@ -25,7 +25,7 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { getAccessorValue } from '../../../../utils/accessor'; import { mergeXDomain } from '../../../xy_chart/domains/x_domain'; -import { getDefaultAPIScale } from '../../../xy_chart/scales/get_api_scales'; +import { getXNiceFromSpec, getXScaleTypeFromSpec } from '../../../xy_chart/scales/get_api_scales'; import { X_SCALE_DEFAULT } from '../../specs/scale_defaults'; import { HeatmapTable } from './compute_chart_dimensions'; import { getHeatmapSpecSelector } from './get_heatmap_spec'; @@ -77,7 +77,8 @@ export const getHeatmapTableSelector = createCachedSelector( resultData.xDomain = mergeXDomain( { - ...getDefaultAPIScale(spec.xScaleType, X_SCALE_DEFAULT), + type: getXScaleTypeFromSpec(spec.xScaleType), + nice: getXNiceFromSpec(), isBandScale: false, desiredTickCount: X_SCALE_DEFAULT.desiredTickCount, customDomain: xDomain, diff --git a/src/chart_types/specs.ts b/src/chart_types/specs.ts index 030b49379a..ef094975bc 100644 --- a/src/chart_types/specs.ts +++ b/src/chart_types/specs.ts @@ -28,8 +28,6 @@ export { RectAnnotation, } from './xy_chart/specs'; -export { APIScale } from './xy_chart/scales/types'; - export * from './xy_chart/utils/specs'; export { Partition } from './partition_chart/specs'; 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 fb441de6db..311ef21748 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 @@ -23,7 +23,7 @@ import { MockXDomain } from '../../../mocks/xy/domains'; import { ScaleType } from '../../../scales/constants'; import { SpecType } from '../../../specs/constants'; import { Dimensions } from '../../../utils/dimensions'; -import { getAPIScaleConfigs } from '../state/selectors/get_api_scale_configs'; +import { getScaleConfigsFromSpecs } from '../state/selectors/get_api_scale_configs'; import { computeSeriesDomains } from '../state/utils/utils'; import { computeXScale } from '../utils/scales'; import { BasicSeriesSpec, SeriesType } from '../utils/specs'; @@ -103,31 +103,31 @@ describe('Crosshair utils linear scale', () => { const barSeries = [barSeries1]; const barSeriesDomains = computeSeriesDomains( barSeries, - getAPIScaleConfigs([], barSeries, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], barSeries, MockGlobalSpec.settings()), ); const multiBarSeries = [barSeries1, barSeries2]; const multiBarSeriesDomains = computeSeriesDomains( multiBarSeries, - getAPIScaleConfigs([], multiBarSeries, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], multiBarSeries, MockGlobalSpec.settings()), ); const lineSeries = [lineSeries1]; const lineSeriesDomains = computeSeriesDomains( lineSeries, - getAPIScaleConfigs([], lineSeries, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], lineSeries, MockGlobalSpec.settings()), ); const multiLineSeries = [lineSeries1, lineSeries2]; const multiLineSeriesDomains = computeSeriesDomains( multiLineSeries, - getAPIScaleConfigs([], multiLineSeries, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], multiLineSeries, MockGlobalSpec.settings()), ); const mixedLinesBars = [lineSeries1, lineSeries2, barSeries1, barSeries2]; const mixedLinesBarsSeriesDomains = computeSeriesDomains( mixedLinesBars, - getAPIScaleConfigs([], mixedLinesBars, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], mixedLinesBars, MockGlobalSpec.settings()), ); const barSeriesScale = computeXScale({ 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 d7b4e4f3df..7cd861b1d9 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 @@ -21,7 +21,7 @@ import { ChartType } from '../..'; import { MockGlobalSpec } from '../../../mocks/specs/specs'; import { ScaleType } from '../../../scales/constants'; import { SpecType } from '../../../specs/constants'; -import { getAPIScaleConfigs } from '../state/selectors/get_api_scale_configs'; +import { getScaleConfigsFromSpecs } from '../state/selectors/get_api_scale_configs'; import { computeSeriesDomains } from '../state/utils/utils'; import { computeXScale } from '../utils/scales'; import { BasicSeriesSpec, SeriesType } from '../utils/specs'; @@ -102,31 +102,31 @@ describe('Crosshair utils ordinal scales', () => { const barSeriesDomains = computeSeriesDomains( barSeries, - getAPIScaleConfigs([], barSeries, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], barSeries, MockGlobalSpec.settings()), ); const multiBarSeries = [barSeries1, barSeries2]; const multiBarSeriesDomains = computeSeriesDomains( multiBarSeries, - getAPIScaleConfigs([], multiBarSeries, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], multiBarSeries, MockGlobalSpec.settings()), ); const lineSeries = [lineSeries1]; const lineSeriesDomains = computeSeriesDomains( lineSeries, - getAPIScaleConfigs([], lineSeries, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], lineSeries, MockGlobalSpec.settings()), ); const multiLineSeries = [lineSeries1, lineSeries2]; const multiLineSeriesDomains = computeSeriesDomains( multiLineSeries, - getAPIScaleConfigs([], multiLineSeries, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], multiLineSeries, MockGlobalSpec.settings()), ); const mixedLinesBars = [lineSeries1, lineSeries2, barSeries1, barSeries2]; const mixedLinesBarsSeriesDomains = computeSeriesDomains( mixedLinesBars, - getAPIScaleConfigs([], mixedLinesBars, MockGlobalSpec.settings()), + getScaleConfigsFromSpecs([], mixedLinesBars, MockGlobalSpec.settings()), ); const barSeriesScale = computeXScale({ diff --git a/src/chart_types/xy_chart/domains/types.ts b/src/chart_types/xy_chart/domains/types.ts index 37ccffc3c1..0819ab61ae 100644 --- a/src/chart_types/xy_chart/domains/types.ts +++ b/src/chart_types/xy_chart/domains/types.ts @@ -21,27 +21,28 @@ import { ScaleContinuousType } from '../../../scales'; import { LogScaleOptions } from '../../../scales/scale_continuous'; import { OrdinalDomain, ContinuousDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; -import { APIScale } from '../scales/types'; import { XScaleType } from '../utils/specs'; /** @internal */ -export type XDomain = Pick & - APIScale & { - /* if the scale needs to be a band scale: used when displaying bars */ - isBandScale: boolean; - /* the minimum interval of the scale if not-ordinal band-scale */ - minInterval: number; - /** if x domain is time, we should also specify the timezone */ - timeZone?: string; - domain: OrdinalDomain | ContinuousDomain; - desiredTickCount: number; - }; +export type XDomain = Pick & { + type: XScaleType; + nice: boolean; + /* if the scale needs to be a band scale: used when displaying bars */ + isBandScale: boolean; + /* the minimum interval of the scale if not-ordinal band-scale */ + minInterval: number; + /** if x domain is time, we should also specify the timezone */ + timeZone?: string; + domain: OrdinalDomain | ContinuousDomain; + desiredTickCount: number; +}; /** @internal */ -export type YDomain = LogScaleOptions & - APIScale & { - isBandScale: false; - groupId: GroupId; - domain: ContinuousDomain; - desiredTickCount: number; - }; +export type YDomain = LogScaleOptions & { + type: ScaleContinuousType; + nice: boolean; + isBandScale: false; + groupId: GroupId; + domain: ContinuousDomain; + desiredTickCount: number; +}; 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 2ad796038d..6ab4bd962a 100644 --- a/src/chart_types/xy_chart/domains/x_domain.test.ts +++ b/src/chart_types/xy_chart/domains/x_domain.test.ts @@ -22,8 +22,8 @@ import { MockGlobalSpec, MockSeriesSpec, MockSeriesSpecs } from '../../../mocks/ import { ScaleType } from '../../../scales/constants'; import { SpecType, Direction, BinAgg } from '../../../specs/constants'; import { Logger } from '../../../utils/logger'; -import { getXAPIScale } from '../scales/get_api_scales'; -import { getAPIScaleConfigs } from '../state/selectors/get_api_scale_configs'; +import { getXNiceFromSpec, getXScaleTypeFromSpec } from '../scales/get_api_scales'; +import { getScaleConfigsFromSpecs } from '../state/selectors/get_api_scale_configs'; import { getDataSeriesFromSpecs } from '../utils/series'; import { BasicSeriesSpec, SeriesType } from '../utils/specs'; import { convertXScaleTypes, findMinInterval, mergeXDomain } from './x_domain'; @@ -50,7 +50,8 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - ...getXAPIScale(ScaleType.Linear), + type: getXScaleTypeFromSpec(ScaleType.Linear), + nice: getXNiceFromSpec(), isBandScale: true, }); }); @@ -64,7 +65,8 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - ...getXAPIScale(ScaleType.Ordinal), + type: getXScaleTypeFromSpec(ScaleType.Ordinal), + nice: getXNiceFromSpec(), isBandScale: true, }); }); @@ -78,7 +80,8 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - ...getXAPIScale(ScaleType.Linear), + type: getXScaleTypeFromSpec(ScaleType.Linear), + nice: getXNiceFromSpec(), isBandScale: false, }); }); @@ -92,7 +95,8 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - ...getXAPIScale(ScaleType.Time), + type: getXScaleTypeFromSpec(ScaleType.Time), + nice: getXNiceFromSpec(), isBandScale: false, timeZone: 'utc-3', }); @@ -112,7 +116,8 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - ...getXAPIScale(ScaleType.Time), + type: getXScaleTypeFromSpec(ScaleType.Time), + nice: getXNiceFromSpec(), isBandScale: false, timeZone: 'utc-3', }); @@ -132,7 +137,8 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - ...getXAPIScale(ScaleType.Time), + type: getXScaleTypeFromSpec(ScaleType.Time), + nice: getXNiceFromSpec(), isBandScale: false, timeZone: 'utc', }); @@ -151,7 +157,8 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - ...getXAPIScale(ScaleType.Ordinal), + type: getXScaleTypeFromSpec(ScaleType.Ordinal), + nice: getXNiceFromSpec(), isBandScale: false, }); }); @@ -168,7 +175,8 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - ...getXAPIScale(ScaleType.Ordinal), + type: getXScaleTypeFromSpec(ScaleType.Ordinal), + nice: getXNiceFromSpec(), isBandScale: true, }); }); @@ -186,7 +194,8 @@ describe('X Domain', () => { ]; const mainXScale = convertXScaleTypes(seriesSpecs); expect(mainXScale).toEqual({ - ...getXAPIScale(ScaleType.Linear), + type: getXScaleTypeFromSpec(ScaleType.Linear), + nice: getXNiceFromSpec(), isBandScale: true, }); }); @@ -225,9 +234,9 @@ describe('X Domain', () => { ], }; const specDataSeries: BasicSeriesSpec[] = [ds1, ds2]; - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + const scalesConfig = getScaleConfigsFromSpecs([], specDataSeries, MockGlobalSpec.settings()); const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); + const mergedDomain = mergeXDomain(scalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 7]); }); test('Should merge bar series correctly', () => { @@ -264,9 +273,9 @@ describe('X Domain', () => { ], }; const specDataSeries = [ds1, ds2]; - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + const scalesConfig = getScaleConfigsFromSpecs([], specDataSeries, MockGlobalSpec.settings()); const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); + const mergedDomain = mergeXDomain(scalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 7]); }); test('Should merge multi bar series correctly', () => { @@ -303,9 +312,9 @@ describe('X Domain', () => { ], }; const specDataSeries = [ds1, ds2]; - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + const scalesConfig = getScaleConfigsFromSpecs([], specDataSeries, MockGlobalSpec.settings()); const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); + const mergedDomain = mergeXDomain(scalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 7]); }); test('Should merge multi bar series correctly - 2', () => { @@ -344,9 +353,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + const scalesConfig = getScaleConfigsFromSpecs([], specDataSeries, MockGlobalSpec.settings()); - const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); + const mergedDomain = mergeXDomain(scalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 7]); }); test('Should merge multi bar linear/bar ordinal series correctly', () => { @@ -383,9 +392,9 @@ describe('X Domain', () => { ], }; const specDataSeries = [ds1, ds2]; - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + const scalesConfig = getScaleConfigsFromSpecs([], specDataSeries, MockGlobalSpec.settings()); const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); + const mergedDomain = mergeXDomain(scalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 1, 2, 5, 7]); }); @@ -430,9 +439,13 @@ describe('X Domain', () => { }; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings({ xDomain: customDomain })); + const scalesConfig = getScaleConfigsFromSpecs( + [], + specDataSeries, + MockGlobalSpec.settings({ xDomain: customDomain }), + ); - const getResult = () => mergeXDomain(apiScalesConfig.x, xValues, { type: ScaleType.Ordinal, nice: false }); + const getResult = () => mergeXDomain(scalesConfig.x, xValues, ScaleType.Ordinal); expect(getResult).not.toThrow(); @@ -477,9 +490,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + const scalesConfig = getScaleConfigsFromSpecs([], specDataSeries, MockGlobalSpec.settings()); - const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); + const mergedDomain = mergeXDomain(scalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 1, 2, 5, 7]); }); test('Should merge multi bar/line time series correctly', () => { @@ -518,9 +531,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + const scalesConfig = getScaleConfigsFromSpecs([], specDataSeries, MockGlobalSpec.settings()); - const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); + const mergedDomain = mergeXDomain(scalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 1, 2, 5, 7]); }); test('Should merge multi lines series correctly', () => { @@ -559,9 +572,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + const scalesConfig = getScaleConfigsFromSpecs([], specDataSeries, MockGlobalSpec.settings()); - const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); + const mergedDomain = mergeXDomain(scalesConfig.x, xValues); expect(mergedDomain.domain).toEqual([0, 1, 2, 5, 7]); }); @@ -594,9 +607,9 @@ describe('X Domain', () => { const specDataSeries = [ds1, ds2]; const { xValues } = getDataSeriesFromSpecs(specDataSeries); - const apiScalesConfig = getAPIScaleConfigs([], specDataSeries, MockGlobalSpec.settings()); + const scalesConfig = getScaleConfigsFromSpecs([], specDataSeries, MockGlobalSpec.settings()); - const mergedDomain = mergeXDomain(apiScalesConfig.x, xValues); + const mergedDomain = mergeXDomain(scalesConfig.x, xValues); expect(mergedDomain.domain.length).toEqual(maxValues); }); test('should compute minInterval an ordered list of numbers', () => { @@ -633,14 +646,14 @@ describe('X Domain', () => { const specs = [MockSeriesSpec.line({ xScaleType: ScaleType.Linear })]; const basicMergedDomain = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain })).x, xValues, ); expect(basicMergedDomain.domain).toEqual([0, 3]); const arrayXDomain = [1, 2]; let { domain } = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: arrayXDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain: arrayXDomain })).x, xValues, ); expect(domain).toEqual([1, 5]); @@ -651,7 +664,7 @@ describe('X Domain', () => { const invalidXDomain = { min: 10, max: 0 }; domain = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, xValues, ).domain; expect(domain).toEqual([1, 5]); @@ -663,12 +676,15 @@ describe('X Domain', () => { const xDomain = { min: 0 }; const specs = [MockSeriesSpec.line({ xScaleType: ScaleType.Linear })]; - const mergedDomain = mergeXDomain(getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, xValues); + const mergedDomain = mergeXDomain( + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain })).x, + xValues, + ); expect(mergedDomain.domain).toEqual([0, 5]); const invalidXDomain = { min: 10 }; const { domain } = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, xValues, ); expect(domain).toEqual([1, 5]); @@ -682,12 +698,15 @@ describe('X Domain', () => { const xDomain = { max: 3 }; const specs = [MockSeriesSpec.line({ xScaleType: ScaleType.Linear })]; - const mergedDomain = mergeXDomain(getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, xValues); + const mergedDomain = mergeXDomain( + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain })).x, + xValues, + ); expect(mergedDomain.domain).toEqual([1, 3]); const invalidXDomain = { max: -1 }; const { domain } = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, xValues, ); expect(domain).toEqual([1, 5]); @@ -701,14 +720,14 @@ describe('X Domain', () => { const xDomain = ['a', 'b', 'c']; const specs = [MockSeriesSpec.bar({ xScaleType: ScaleType.Ordinal })]; const basicMergedDomain = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain })).x, xValues, ); expect(basicMergedDomain.domain).toEqual(['a', 'b', 'c']); const objectXDomain = { max: 10, min: 0 }; const { domain } = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: objectXDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain: objectXDomain })).x, xValues, ); expect(domain).toEqual(['a', 'b', 'c', 'd']); @@ -723,14 +742,17 @@ describe('X Domain', () => { test('with valid minInterval', () => { const xDomain = { minInterval: 0.5 }; - const mergedDomain = mergeXDomain(getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, xValues); + const mergedDomain = mergeXDomain( + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain })).x, + xValues, + ); expect(mergedDomain.minInterval).toEqual(0.5); }); test('with valid minInterval greater than computed minInterval for single datum set', () => { const xDomain = { minInterval: 10 }; const mergedDomain = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain })).x, new Set([5]), ); expect(mergedDomain.minInterval).toEqual(10); @@ -739,7 +761,7 @@ describe('X Domain', () => { test('with invalid minInterval greater than computed minInterval for multi data set', () => { const invalidXDomain = { minInterval: 10 }; const { minInterval } = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, xValues, ); expect(minInterval).toEqual(1); @@ -751,7 +773,7 @@ describe('X Domain', () => { test('with invalid minInterval less than 0', () => { const invalidXDomain = { minInterval: -1 }; const { minInterval } = mergeXDomain( - getAPIScaleConfigs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, + getScaleConfigsFromSpecs([], specs, MockGlobalSpec.settings({ xDomain: invalidXDomain })).x, xValues, ); expect(minInterval).toEqual(1); diff --git a/src/chart_types/xy_chart/domains/x_domain.ts b/src/chart_types/xy_chart/domains/x_domain.ts index cc70b3bacd..97521a733b 100644 --- a/src/chart_types/xy_chart/domains/x_domain.ts +++ b/src/chart_types/xy_chart/domains/x_domain.ts @@ -23,9 +23,8 @@ import { ScaleType } from '../../../scales/constants'; import { compareByValueAsc, identity } from '../../../utils/common'; import { computeContinuousDataDomain, computeOrdinalDataDomain } from '../../../utils/domain'; import { Logger } from '../../../utils/logger'; -import { getXAPIScale } from '../scales/get_api_scales'; -import { APIScale } from '../scales/types'; -import { APIScaleConfigs } from '../state/selectors/get_api_scale_configs'; +import { getXNiceFromSpec, getXScaleTypeFromSpec } from '../scales/get_api_scales'; +import { ScaleConfigs } from '../state/selectors/get_api_scale_configs'; import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_type_utils'; import { BasicSeriesSpec, SeriesType, XScaleType } from '../utils/specs'; import { areAllNiceDomain } from './nice'; @@ -36,16 +35,15 @@ import { XDomain } from './types'; * @internal */ export function mergeXDomain( - apiScaleConfig: APIScaleConfigs['x'], + { type, nice, isBandScale, timeZone, desiredTickCount, customDomain }: ScaleConfigs['x'], xValues: Set, - fallbackScale?: APIScale, + fallbackScale?: XScaleType, ): XDomain { - const { type, nice, isBandScale, timeZone, desiredTickCount, customDomain } = apiScaleConfig; const values = [...xValues.values()]; let seriesXComputedDomains; let minInterval = 0; - if (type === ScaleType.Ordinal || fallbackScale?.type === ScaleType.Ordinal) { + if (type === ScaleType.Ordinal || fallbackScale === ScaleType.Ordinal) { if (type !== ScaleType.Ordinal) { Logger.warn(`Each X value in a ${type} x scale needs be be a number. Using ordinal x scale as fallback.`); } @@ -55,7 +53,7 @@ export function mergeXDomain( if (Array.isArray(customDomain)) { seriesXComputedDomains = customDomain; } else { - if (fallbackScale?.type === ScaleType.Ordinal) { + if (fallbackScale === ScaleType.Ordinal) { Logger.warn(`xDomain ignored for fallback ordinal scale. Options to resolve: 1) Correct data to match ${type} scale type (see previous warning) @@ -110,10 +108,8 @@ export function mergeXDomain( } return { - ...(fallbackScale ?? { - type, - nice, - }), + type: fallbackScale ?? type, + nice, isBandScale, domain: seriesXComputedDomains, minInterval, @@ -177,8 +173,10 @@ export function findMinInterval(xValues: number[]): number { * @internal */ export function convertXScaleTypes( - specs: Optional, 'seriesType'>[], -): APIScale & { + specs: Optional, 'seriesType'>[], +): { + type: XScaleType; + nice: boolean; isBandScale: boolean; timeZone?: string; } { @@ -187,10 +185,9 @@ export function convertXScaleTypes( const timeZones = new Set(); const niceDomainConfigs: Array = []; specs.forEach((spec) => { - const scaleConfig = getXAPIScale(spec.xScaleType); - niceDomainConfigs.push(scaleConfig.nice); + niceDomainConfigs.push(getXNiceFromSpec(spec.xNice)); seriesTypes.add(spec.seriesType); - scaleTypes.add(scaleConfig.type); + scaleTypes.add(getXScaleTypeFromSpec(spec.xScaleType)); if (spec.timeZone) { timeZones.add(spec.timeZone.toLowerCase()); } diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts index d1ad1ce3c4..8053adf3ed 100644 --- a/src/chart_types/xy_chart/domains/y_domain.ts +++ b/src/chart_types/xy_chart/domains/y_domain.ts @@ -23,8 +23,7 @@ import { identity } from '../../../utils/common'; import { computeContinuousDataDomain, ContinuousDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; -import { APIScale } from '../scales/types'; -import { APIScaleConfigs } from '../state/selectors/get_api_scale_configs'; +import { ScaleConfigs } from '../state/selectors/get_api_scale_configs'; import { getSpecDomainGroupId } from '../state/utils/spec'; import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_type_utils'; import { groupBy } from '../utils/group_data_series'; @@ -40,7 +39,7 @@ export type YBasicSeriesSpec = Pick< > & { stackMode?: StackMode; enableHistogramMode?: boolean }; /** @internal */ -export function mergeYDomain(dataSeries: DataSeries[], yScaleAPIConfig: APIScaleConfigs['y']): YDomain[] { +export function mergeYDomain(dataSeries: DataSeries[], yScaleAPIConfig: ScaleConfigs['y']): YDomain[] { const dataSeriesByGroupId = groupBy(dataSeries, ({ spec }) => getSpecDomainGroupId(spec), true); return dataSeriesByGroupId.reduce((acc, groupedDataSeries) => { @@ -61,7 +60,7 @@ function mergeYDomainForGroup( stacked: DataSeries[], nonStacked: DataSeries[], hasZeroBaselineSpecs: boolean, - yAPIScaleConfig: APIScaleConfigs['y'], + yScaleConfig: ScaleConfigs['y'], ): YDomain | null { const dataSeries = [...stacked, ...nonStacked]; if (dataSeries.length === 0) { @@ -70,7 +69,7 @@ function mergeYDomainForGroup( const [{ stackMode, spec }] = dataSeries; const groupId = getSpecDomainGroupId(spec); - const { customDomain, type, nice, desiredTickCount } = yAPIScaleConfig[groupId]; + const { customDomain, type, nice, desiredTickCount } = yScaleConfig[groupId]; let domain: ContinuousDomain; if (stackMode === StackMode.Percentage) { @@ -221,7 +220,9 @@ export function isStackedSpec(spec: YBasicSeriesSpec, histogramEnabled: boolean) * @returns {ScaleContinuousType} * @internal */ -export function coerceYScaleTypes(scales: APIScale[]): APIScale { +export function coerceYScaleTypes( + scales: Array<{ type: ScaleContinuousType; nice: boolean }>, +): { type: ScaleContinuousType; nice: boolean } { const scaleCollection = scales.reduce<{ types: Set; nice: Array; diff --git a/src/chart_types/xy_chart/legend/legend.ts b/src/chart_types/xy_chart/legend/legend.ts index fea8da7083..a09da6d93c 100644 --- a/src/chart_types/xy_chart/legend/legend.ts +++ b/src/chart_types/xy_chart/legend/legend.ts @@ -24,7 +24,7 @@ import { SortSeriesByConfig, TickFormatterOptions } from '../../../specs'; import { Color } from '../../../utils/common'; import { BandedAccessorType } from '../../../utils/geometry'; import { getLegendCompareFn, SeriesCompareFn } from '../../../utils/series_sort'; -import { getXAPIScale } from '../scales/get_api_scales'; +import { getXScaleTypeFromSpec } from '../scales/get_api_scales'; import { getAxesSpecForSpecId, getSpecsById } from '../state/utils/spec'; import { LastValues } from '../state/utils/types'; import { Y0_ACCESSOR_POSTFIX, Y1_ACCESSOR_POSTFIX } from '../tooltip/tooltip'; @@ -138,7 +138,7 @@ export function computeLegend( const lastValue = lastValues.get(key); const seriesIdentifier = getSeriesIdentifierFromDataSeries(series); - const scaleConf = getXAPIScale(spec.xScaleType); + const xScaleType = getXScaleTypeFromSpec(spec.xScaleType); legendItems.push({ color, label: labelY1, @@ -147,7 +147,7 @@ export function computeLegend( isSeriesHidden, isItemHidden: hideInLegend, isToggleable: true, - defaultExtra: getLegendExtra(showLegendExtra, scaleConf.type, formatter, 'y1', lastValue), + defaultExtra: getLegendExtra(showLegendExtra, xScaleType, formatter, 'y1', lastValue), path: [{ index: 0, value: seriesIdentifier.key }], keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()], }); @@ -161,7 +161,7 @@ export function computeLegend( isSeriesHidden, isItemHidden: hideInLegend, isToggleable: true, - defaultExtra: getLegendExtra(showLegendExtra, scaleConf.type, formatter, 'y0', lastValue), + defaultExtra: getLegendExtra(showLegendExtra, xScaleType, formatter, 'y0', lastValue), path: [{ index: 0, value: seriesIdentifier.key }], keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()], }); diff --git a/src/chart_types/xy_chart/scales/get_api_scales.ts b/src/chart_types/xy_chart/scales/get_api_scales.ts index d598c7e224..f43713c2c2 100644 --- a/src/chart_types/xy_chart/scales/get_api_scales.ts +++ b/src/chart_types/xy_chart/scales/get_api_scales.ts @@ -18,28 +18,25 @@ */ import { ScaleContinuousType } from '../../../scales'; -import { ScaleType } from '../../../scales/constants'; import { BasicSeriesSpec, XScaleType } from '../utils/specs'; import { X_SCALE_DEFAULT, Y_SCALE_DEFAULT } from './scale_defaults'; -import { APIScale } from './types'; /** @internal */ -export function getXAPIScale(scaleType: BasicSeriesSpec['xScaleType']): APIScale { - return getDefaultAPIScale(scaleType, { type: X_SCALE_DEFAULT.type, nice: X_SCALE_DEFAULT.nice }); +export function getXScaleTypeFromSpec(type?: BasicSeriesSpec['xScaleType']): XScaleType { + return type ?? X_SCALE_DEFAULT.type; } /** @internal */ -export function getYAPIScale(scaleType: BasicSeriesSpec['yScaleType']): APIScale { - return getDefaultAPIScale(scaleType, { type: Y_SCALE_DEFAULT.type, nice: Y_SCALE_DEFAULT.nice }); +export function getXNiceFromSpec(nice?: BasicSeriesSpec['xNice']): boolean { + return nice ?? X_SCALE_DEFAULT.nice; } /** @internal */ -export function getDefaultAPIScale(type: T | APIScale, defaults: APIScale): APIScale { - if (typeof type === 'object') { - return type; - } - return { - ...defaults, - type, - }; +export function getYScaleTypeFromSpec(type?: BasicSeriesSpec['yScaleType']): ScaleContinuousType { + return type ?? Y_SCALE_DEFAULT.type; +} + +/** @internal */ +export function getYNiceFromSpec(nice?: BasicSeriesSpec['yNice']): boolean { + return nice ?? Y_SCALE_DEFAULT.nice; } diff --git a/src/chart_types/xy_chart/scales/types.ts b/src/chart_types/xy_chart/scales/types.ts deleted file mode 100644 index dc51bbc915..0000000000 --- a/src/chart_types/xy_chart/scales/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { ScaleType } from '../../../scales/constants'; - -/** @public */ -export interface APIScale { - type: T; - nice: boolean; -} diff --git a/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts b/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts index 2f83ac4dab..f9ba585acf 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_series_domains.ts @@ -24,7 +24,7 @@ import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { SeriesDomainsAndData } from '../utils/types'; import { computeSeriesDomains } from '../utils/utils'; -import { getAPIScaleConfigsSelector } from './get_api_scale_configs'; +import { getScaleConfigsFromSpecsSelector } from './get_api_scale_configs'; import { getSeriesSpecsSelector, getSmallMultiplesIndexOrderSelector } from './get_specs'; const getDeselectedSeriesSelector = (state: GlobalChartState) => state.interactions.deselectedDataSeries; @@ -36,12 +36,12 @@ export const computeSeriesDomainsSelector = createCachedSelector( getDeselectedSeriesSelector, getSettingsSpecSelector, getSmallMultiplesIndexOrderSelector, - getAPIScaleConfigsSelector, + getScaleConfigsFromSpecsSelector, ], - (seriesSpecs, deselectedDataSeries, settingsSpec, smallMultiples, apiScaleConfigs): SeriesDomainsAndData => { + (seriesSpecs, deselectedDataSeries, settingsSpec, smallMultiples, scaleConfigs): SeriesDomainsAndData => { return computeSeriesDomains( seriesSpecs, - apiScaleConfigs, + scaleConfigs, deselectedDataSeries, settingsSpec.orderOrdinalBinsBy, smallMultiples, diff --git a/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts index b45805f3fa..c66aa1077d 100644 --- a/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts +++ b/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts @@ -27,9 +27,8 @@ import { getSettingsSpecSelector } from '../../../../state/selectors/get_setting import { GroupId } from '../../../../utils/ids'; import { convertXScaleTypes } from '../../domains/x_domain'; import { coerceYScaleTypes } from '../../domains/y_domain'; -import { getYAPIScale } from '../../scales/get_api_scales'; +import { getYNiceFromSpec, getYScaleTypeFromSpec } from '../../scales/get_api_scales'; import { X_SCALE_DEFAULT, Y_SCALE_DEFAULT } from '../../scales/scale_defaults'; -import { APIScale } from '../../scales/types'; import { isHorizontalAxis, isVerticalAxis } from '../../utils/axis_type_utils'; import { groupBy } from '../../utils/group_data_series'; import { AxisSpec, BasicSeriesSpec, CustomXDomain, XScaleType, YDomainRange } from '../../utils/specs'; @@ -38,34 +37,36 @@ import { getAxisSpecsSelector, getSeriesSpecsSelector } from './get_specs'; import { mergeYCustomDomainsByGroupId } from './merge_y_custom_domains'; /** @internal */ -export type APIScaleConfigBase = APIScale & { +export type ScaleConfigBase = { + type: T; + nice: boolean; desiredTickCount: number; customDomain?: D; }; -type APIXScaleConfigBase = APIScaleConfigBase; -type APIYScaleConfigBase = APIScaleConfigBase; +type XScaleConfigBase = ScaleConfigBase; +type YScaleConfigBase = ScaleConfigBase; /** @internal */ -export interface APIScaleConfigs { - x: APIXScaleConfigBase & { +export interface ScaleConfigs { + x: XScaleConfigBase & { isBandScale: boolean; timeZone?: string; }; - y: Record; + y: Record; } /** @internal */ -export const getAPIScaleConfigsSelector = createCachedSelector( +export const getScaleConfigsFromSpecsSelector = createCachedSelector( [getAxisSpecsSelector, getSeriesSpecsSelector, getSettingsSpecSelector], - getAPIScaleConfigs, + getScaleConfigsFromSpecs, )(getChartIdSelector); /** @internal */ -export function getAPIScaleConfigs( +export function getScaleConfigsFromSpecs( axisSpecs: AxisSpec[], seriesSpecs: BasicSeriesSpec[], settingsSpec: SettingsSpec, -): APIScaleConfigs { +): ScaleConfigs { const isHorizontalChart = isHorizontalRotation(settingsSpec.rotation); // x axis @@ -75,17 +76,20 @@ export function getAPIScaleConfigs( }, X_SCALE_DEFAULT.desiredTickCount); const xScaleConfig = convertXScaleTypes(seriesSpecs); - const x: APIScaleConfigs['x'] = { + const x: ScaleConfigs['x'] = { customDomain: settingsSpec.xDomain, ...xScaleConfig, desiredTickCount: xTicks, }; // y axes - const scaleTypeByGroupId = groupBy(seriesSpecs, ['groupId'], true).reduce< - Record> + const scaleConfigsByGroupId = groupBy(seriesSpecs, ['groupId'], true).reduce< + Record >((acc, series) => { - const yScaleTypes = series.map(({ yScaleType }) => getYAPIScale(yScaleType)); + const yScaleTypes = series.map(({ yScaleType, yNice }) => ({ + nice: getYNiceFromSpec(yNice), + type: getYScaleTypeFromSpec(yScaleType), + })); acc[series[0].groupId] = coerceYScaleTypes(yScaleTypes); return acc; }, {}); @@ -93,15 +97,15 @@ export function getAPIScaleConfigs( const customDomainByGroupId = mergeYCustomDomainsByGroupId(axisSpecs, settingsSpec.rotation); const yAxes = axisSpecs.filter((d) => isHorizontalChart === isVerticalAxis(d.position)); - const y = Object.keys(scaleTypeByGroupId).reduce((acc, groupId) => { + const y = Object.keys(scaleConfigsByGroupId).reduce((acc, groupId) => { const axis = yAxes.find((yAxis) => yAxis.groupId === groupId); const desiredTickCount = axis?.ticks ?? Y_SCALE_DEFAULT.desiredTickCount; - const apiScale = scaleTypeByGroupId[groupId]; + const scaleConfig = scaleConfigsByGroupId[groupId]; const customDomain = customDomainByGroupId.get(groupId); if (!acc[groupId]) { acc[groupId] = { customDomain, - ...apiScale, + ...scaleConfig, desiredTickCount, }; } diff --git a/src/chart_types/xy_chart/state/utils/utils.test.ts b/src/chart_types/xy_chart/state/utils/utils.test.ts index 99a29480bf..f167579b91 100644 --- a/src/chart_types/xy_chart/state/utils/utils.test.ts +++ b/src/chart_types/xy_chart/state/utils/utils.test.ts @@ -36,7 +36,7 @@ import { getSeriesIndex, XYChartSeriesIdentifier } from '../../utils/series'; import { BasicSeriesSpec, HistogramModeAlignments, SeriesColorAccessorFn } from '../../utils/specs'; import { computeSeriesDomainsSelector } from '../selectors/compute_series_domains'; import { computeSeriesGeometriesSelector } from '../selectors/compute_series_geometries'; -import { getAPIScaleConfigs } from '../selectors/get_api_scale_configs'; +import { getScaleConfigsFromSpecs } from '../selectors/get_api_scale_configs'; import { computeSeriesDomains, computeXScaleOffset, @@ -79,8 +79,8 @@ describe('Chart State utils', () => { yAccessors: ['y'], data: BARCHART_1Y0G, }); - const apiScaleConfig = getAPIScaleConfigs([], [spec1, spec2], MockGlobalSpec.settings()); - const domains = computeSeriesDomains([spec1, spec2], apiScaleConfig); + const scaleConfig = getScaleConfigsFromSpecs([], [spec1, spec2], MockGlobalSpec.settings()); + const domains = computeSeriesDomains([spec1, spec2], scaleConfig); expect(domains.xDomain).toEqual( MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0, 3], @@ -128,8 +128,8 @@ describe('Chart State utils', () => { stackAccessors: ['x'], data: BARCHART_1Y1G, }); - const apiScaleConfig = getAPIScaleConfigs([], [spec1, spec2], MockGlobalSpec.settings()); - const domains = computeSeriesDomains([spec1, spec2], apiScaleConfig); + const scaleConfig = getScaleConfigsFromSpecs([], [spec1, spec2], MockGlobalSpec.settings()); + const domains = computeSeriesDomains([spec1, spec2], scaleConfig); expect(domains.xDomain).toEqual( MockXDomain.fromScaleType(ScaleType.Linear, { domain: [0, 3], diff --git a/src/chart_types/xy_chart/state/utils/utils.ts b/src/chart_types/xy_chart/state/utils/utils.ts index 6f6c45cb7a..6adeac5fb3 100644 --- a/src/chart_types/xy_chart/state/utils/utils.ts +++ b/src/chart_types/xy_chart/state/utils/utils.ts @@ -64,7 +64,7 @@ import { isBubbleSeriesSpec, } from '../../utils/specs'; import { SmallMultipleScales } from '../selectors/compute_small_multiple_scales'; -import { APIScaleConfigs } from '../selectors/get_api_scale_configs'; +import { ScaleConfigs } from '../selectors/get_api_scale_configs'; import { SmallMultiplesGroupBy } from '../selectors/get_specs'; import { isHorizontalRotation } from './common'; import { getSpecsById, getAxesSpecForSpecId, getSpecDomainGroupId } from './spec'; @@ -120,7 +120,7 @@ export function getCustomSeriesColors(dataSeries: DataSeries[]): Map; smVValues: Set; smHValues: Set; - fallbackScale?: APIScale; + fallbackScale?: XScaleType; } { let globalDataSeries: DataSeries[] = []; const mutatedXValueSums = new Map(); @@ -457,7 +456,7 @@ export function getDataSeriesFromSpecs( // keep the user order for ordinal scales xValues, ...smallMultipleUniqueValues, - fallbackScale: !isOrdinalScale && !isNumberArray ? X_SCALE_DEFAULT : undefined, + fallbackScale: !isOrdinalScale && !isNumberArray ? X_SCALE_DEFAULT.type : undefined, }; } diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index f649356d4d..313651a868 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -44,7 +44,6 @@ import { } from '../../../utils/themes/theme'; import { PrimitiveValue } from '../../partition_chart/layout/utils/group_by_rollup'; import { AnnotationTooltipFormatter, CustomAnnotationTooltip } from '../annotations/types'; -import { APIScale } from '../scales/types'; import { XYChartSeriesIdentifier, DataSeriesDatum } from './series'; /** @public */ @@ -462,7 +461,12 @@ export interface SeriesScales { * The x axis scale type * @defaultValue `ordinal` {@link (ScaleType:type) | ScaleType.Ordinal} */ - xScaleType: XScaleType | APIScale; + xScaleType: XScaleType; + /** + * Extends the x domain so that it starts and ends on nice round values. + * @defaultValue `false` + */ + xNice?: boolean; /** * If using a ScaleType.Time this timezone identifier is required to * compute a nice set of xScale ticks. Can be any IANA zone supported by @@ -474,7 +478,12 @@ export interface SeriesScales { * The y axis scale type * @defaultValue `linear` {@link (ScaleType:type) | ScaleType.Linear} */ - yScaleType: ScaleContinuousType | APIScale; + yScaleType: ScaleContinuousType; + /** + * Extends the y domain so that it starts and ends on nice round values. + * @defaultValue `false` + */ + yNice?: boolean; /** * if true, the min y value is set to the minimum domain value, 0 otherwise * @deprecated use `domain.fit` instead diff --git a/src/mocks/xy/domains.ts b/src/mocks/xy/domains.ts index 656d99979d..5d3f752d36 100644 --- a/src/mocks/xy/domains.ts +++ b/src/mocks/xy/domains.ts @@ -18,7 +18,12 @@ */ import { XDomain, YDomain } from '../../chart_types/xy_chart/domains/types'; -import { getXAPIScale, getYAPIScale } from '../../chart_types/xy_chart/scales/get_api_scales'; +import { + getXNiceFromSpec, + getXScaleTypeFromSpec, + getYNiceFromSpec, + getYScaleTypeFromSpec, +} from '../../chart_types/xy_chart/scales/get_api_scales'; import { X_SCALE_DEFAULT, Y_SCALE_DEFAULT } from '../../chart_types/xy_chart/scales/scale_defaults'; import { DEFAULT_GLOBAL_ID, XScaleType } from '../../chart_types/xy_chart/utils/specs'; import { ScaleContinuousType } from '../../scales'; @@ -42,7 +47,8 @@ export class MockXDomain { static fromScaleType(scaleType: XScaleType, partial?: RecursivePartial) { return mergePartial(MockXDomain.base, partial, { mergeOptionalPartialValues: true }, [ { - ...getXAPIScale(scaleType), + type: getXScaleTypeFromSpec(scaleType), + nice: getXNiceFromSpec(), }, ]); } @@ -64,7 +70,8 @@ export class MockYDomain { static fromScaleType(scaleType: ScaleContinuousType, partial?: RecursivePartial) { return mergePartial(MockYDomain.base, partial, { mergeOptionalPartialValues: true }, [ { - ...getYAPIScale(scaleType), + type: getYScaleTypeFromSpec(scaleType), + nice: getYNiceFromSpec(), }, ]); } diff --git a/stories/axes/8_custom_domain.tsx b/stories/axes/8_custom_domain.tsx index bc0c146d1f..a4914b1e96 100644 --- a/stories/axes/8_custom_domain.tsx +++ b/stories/axes/8_custom_domain.tsx @@ -135,10 +135,8 @@ export const Example = () => { { )} Date: Mon, 12 Apr 2021 19:30:05 +0200 Subject: [PATCH 11/11] test: update VRT with negative values --- ...m-domain-visually-looks-correct-1-snap.png | Bin 17887 -> 18317 bytes stories/axes/8_custom_domain.tsx | 28 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-custom-domain-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-custom-domain-visually-looks-correct-1-snap.png index d3845a3dab12b939d904d4c4037ea9f4f5030ff7..570325f0d9b4c12401582988ec52eb2d0a7b077c 100644 GIT binary patch literal 18317 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�SKT+n|gOZln8hYTt(V`Ja58yzq^SM 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 S)CSLnhzUyx<-gJT^1lFELw6tm literal 17887 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
x-i*qQOy34*n_i_Fu(Nn`tesS%U9s@4(Qq^}9$ z=S>VZZ*48ML%}oG>Y3((C6PmT93LqYHV1fdEw}JdSA$e3M#x z;1wy5#0O{TZe#QgwYg+r{v)3lMolB1lL1Yo|NcTP4c-wa0c!(=^S6#-W=lWWd@BBN z-3iAcJUHm}R4|@*BoQ{vzyBO_CO%+Z6d)c^_;h-yl~QiRqNGZ{Fh)(#{Pp@$QCBZ- zYqPsK!`0DcLtQi!Ofm<-2BB{z=axOr{3OYur;~D)k31APH(i)1MeseK@bya?x%XA{?Hk)lFl-^_31f5PB zdZM3;{D|66G9Rxvc zwK!)JP7lg)mnPbeFvPj&JFgL6jje*{SHp06e^|Y*uf(ehPZk-hcwlvmsvNlaIXOBY zz#L)7cGUUFanHUS*B7(pDGFX|=qzPsfWxIgbdq+O1WKmtWlO6G;F9-pMfFm%D9H!D zA;b;swzHGt{3G8f;~(X+3m&?%3|DA~EU4c6>iNE6#yqUTtS-#PKeRC3d{|RtApyZm zg{3V`+CBt}2zvK)Wb|Ulq_OnI2Csz?W}TbMmTar25Pp-3@sZafV7Qj1fAAr;X(Xca zT_~*Ob(>-C@cWS>4(p>O-C=MkMcgL&jV}-bHMQ7Lc`EI6(+@v0@)Bc=@>>H3B z5u{X@Cr~}@&V|!}`7H2V6)L6Y8^NLo6NpEEnWPo3O^+{`Q?&1V!=0@7i-U*Eesb=b z|E;OwSoudO-#&$|EGWE|Jskcq(IT#ZQ!2_z0Ad(HPu@0du?j^B7d!CyBF%-X+kEOh zIA6X-yC*us8~VxBx~yj$YKF4;x3mhFr8q#n3tH`#82L8`wc-Mc7~cWXF^ z!ux}hxJZkUF=KFEyjbeaW}~)1@>27Vx?ey5J%TeaEDg9Eb!sS2@(qcrZ<>So8q_=o z;*9jTzZYl7-};LHrfmM6crS;PWQa zk7jI@<;Wp|Y}^sq38*rR)i*GH{VnYh7-o*v?%p0WfVFCQ?Kylb=pr6an33gNF~AJN zSp)-YZS{wgW^HCYG1vVnSsrhJrJ4`BItwnz8=B4&ioiPeq@9wwKpJ+(gJYFx!RPb6 z{W_$4v#j=Uq&QDH2E#;TCDP?iYa3?k@SYvimBcS5Sa=;;3Nl}_H9Yo}1|0 z=Vc%Te)#6Bcdo^mW45PeSe)@&)wQpaQw_G?3}b|0Y`(gLkD^8>_EY77%5L6UWzEyX zQFH!o^1|6X(I{QsN5RP%WCSW@iX)s`JZ6+xU73xL)P}4lmVg8Uw4feTdg*@z?XMe+#fa?Z(`F5( z1m7PRo?PZ)Q)#cw9~Zn9H@$nRVWF-0&`9aMZ~^9BZeqR#hdmof6fADy$8b?hqs3@d z|L7RO!jlm-s)&MvbzK#abR>IA@oC^N%LgnvDqJr7ckxoJ?D~CF#s7n9M9co~R3k0Q zX2avHT2~IfDJgu!v%pIwWc>)7Z<}ddxxR1iyp<4@V5#jT*xAWgBr9ddm<`HSUFJCbTV?#SB!O^J?-qg z2=)Db)i>}>&BH>a)0}K{%BU;*)Npfc zx0798+HAj@QL@2{(Ws*>H@kAv2Im7E;$TSD;SJ8OP(frw1e+K`HNU{%8&M*~M64Lm zG-~Cns-z2qB}>7f`bMl;6ZmDyFnb=i9oot_{5u;~f%Oy#w#VnEBWfJ=DKlAO(TH1! z2jsr#09fWh`xi&liS@3gc*5gr52ACZLc%3X=+}oDojww75(_MlbwQ2LeSNP&t;gKi z>AQhsph%w-KzicOg%=>ycQ9n`k6>=C-#4ESx;lK&rk#i}^zKN;lsv8(vs!F#PtA@MtBPbbeYwSL(KBK>r zE>F%rh^ae#*gWA#)gh&+s4R}h{d)2RMzv9VQ1G+Ll;SK!sx09RkzwQeThd>ucyV+j zm}_+$Kj+1#uwn6Lv)z;?wh79t0VeLpK0(G34%YqQsX1SG$SwgXq-igY33kpez>}~4r19c& z+5VMpiBVn7UiGeirmlzr7rtM?)!i3bY!@gEV@oqGgQoc>Vv>X-AC{p!Y_{XL81H@43hIE+wck-Oqu#v%M_NFG zk*qMl3LZ56cpW_sZue|Rok<}*(OgNAC?DU##XLtnLbx)gp9I5q5O`W?Jtc<&C-tVs zg=?@+!`k?drWJVH#4V#MT{+FzpXec{htLVL(h4?uCor|CWffdJd|d|Mmk zTyCY?s?v%OlR$uagYUKT#;^Y+qZ0AEmkCI12WA{PSym$T?DVeVEj64rHs`l?PT(}T z+z0h~l z+XA8f{wSm#pDvcPFRQ9lu6?zm?*Uf~e%XfZ8$v*pCYv{2ZnQiVp+LM;uoqsvD@kO1 zkKPrun0XvqFsHGZx-2OwWxBx~-x{?Ma1mGBSq}3b-B+A4 zPm6SPfrK;_um_Gr9F;1@;2ujrc9B<@ucL0@6*xw5e)e$e*z%iGNilfJ`!~=%3?W{o zVDd-bIyZx#&t!K)WDlM;LV4b^7U)GgHUQhJkpvCjKx+}r&N4D6=%|vtMaSM+%pVhK zIw!}N_hz4O2LH40PYT(`1oyS;mKNHePtC70{NX-@=^Cb$mS4Mr72^rL z_y9xkWta4M`SAU%R&E!%7Y&8xNr`T?z=jh@AmMiJmAfnV=(M3K5>>#!xS2al^T~iO zfLaHtPpy;gYfZS(Of3$ft^%2chr5jhr{9uGEZgAU?<)1@!`@1t#st_M=@GGsx8f_L1lW|5)Gx9^jse$b^TCV`$Z3aVVf* z+ak<`&#pM4xh&|)S7B9F4NOe-?NWhvoSS{hP4BiEVTu}kjnl?i@kQvC=P*O+!+&_Q zmcnbT6R>4|DTKXOivvyAX!6X~&>D>a_4ZTtc%g~RSumMuD+x@su)caewI{{**fOA0 zOL~#k-sU0-70wHIQtMxbWS%OI;q3g~3-p)ix7W12bT`VAnyo`e4Xi~&xs1WcZBv6l zjEI_i*Y+Yx^{Ph|nl^p2{bP#f{KxSdk-Dlc zGJ%y_Mc8nz?yffWv|oI7-Au40Oqs0 zxwLy1j{gIAZBQ|R{|&s7Px~Y(=GXwjDmeNK8;DPU{3Hm`tAkh9jr{-rrvse*I9!P- zf_SsgVcfigC*7y6Gv5qSapAnIuYi~pw}Zvar^LZatoN)Ina#VYWS}pKil-m?8Q`goFA$Nl0`s(2Wvj&DsqkcyD*QF=YP~x zo=;i+!_EQ!zdLW2<|f3VD)8?Dg+BNX4PD@6oywmH7`UxXLJx7lzp4I#xs!5biGs!c zqacb6)Ba7_VO*-jfb2yODY7Z@+nz5g#HP!>)t&p8EhSml7*h%B7gJBnWe zk&(dUI|X$lAkepmy(0yC4_yD9cArNO0w&k_g!>t~ZahMOo7G!rY5qoX|S{jGR zE@!ui_>=_g6ndQgN~FzO;V&)v>E%y}f}sI+7?0vx#y=~#nfwFx^oFHIEb0HZ1M|`c zw=qq|)f3LOzdR_KNkyU(toyO$koG@xLc%mk(K{D*Nar@)(ep_q1f} ztE$%qvw8anVpaK&Fsk3$X<3tvt6O5_^J|>E^k>O5I6j;Dq^_BuPK-hYeK6bj;l2%ptXj=XIDkLxBk?Z(B&zH=K*t%y3o^9QGA4L$B{#AfcbB<}0&q_#HofK)6rS%P^+ zN#U$@FEJdisY#@^UPi!Pw>jgHgkFztM#!s0xzBUf5m#jY{26pEG5Nc4Rss{;i-_As zqAK`t0srPZJREb?TQK6Q)K^Q=y^y{gp+Nidr`oB2jnPGeEjJ^pjnCZLeXujNLsG`* z+-r1NTdlWBEh%IkJv7-|*sbsAu2EE>af0x%lrQIAs#NU|%Kw@(8s^{i+jhxFAM*Sr zE@fntu|>*F^;c^}k?^*)CE4bGZCj5#K$jFI17hVy2}bqj=FNmuNlus$(oLH5$>gU3 zIl?O1+SlCACY<%G8r@_h`koH28yKGFOR2K>^PVL)=Gz4HIpOZ^eBBRSCklG}bsURH#gVg#+ zUPM7b!En(GQod$YYGx+Y;laU=A3wyMY9`mjcOI~_v%4K_#9448822UTb$3g-y1Q!- zqmj&PjF-3_tb|d}f-m9b=HA=iFX4Jj^@6zqwpq?|n<^_lo)DBZ8UX=qM@PqE*rueU zWHApe-%Asdtc!~aMS1zuiwh4)o~xqhXx!G87V&Tp*^h~d^C~O30u>r@4vvqzrl)nn z6?Jr&6M8aBOB-2-zCa*%-Xs1l09&Af9{GGF8pHn1tT3Z$;mhy^8?K>|5o23&moI81 z#YN1dd$NOGaCy@KBQP_ii#i~N%*|TT3S&fDbws@-g@7{@r zvpKFQO31%_3DJp$c*V`m>Xnt3oAt!AD~r8H;r2W$CE;`VHZ!AZIab)X#uYn5vs&1q zBdcb+cE6!u&Z)+6shglDj#VryEKIrWJ&G+)l3kT%PHwJ(y?x0;9Gtw6kElWOOoc5K z#0QOyLK5B=pV^*1ZRiw-P<`HC?&~rxp=DD-u$A!zrWh^ zyqqGts!FZ?@+hJ8GepV(0|R4iZB5?Esl2wf_Gbt(*52AMy+nA|;=p8$^J79nGh1>g z2l&>lE{~HPAtfcHwe|JE?l{&muS>Ul-4?-t-@m_^kLG9B*XytvcfVCsj4UoG$qTvD zT&2mhIaVa>RO5Lt(R#YSOwZ3hIcK6CZODnNGT^~ac927*u z$*F?rFN}gogdfwF#Os`|udlzbzRt_Ej|{ou|BJstX0See`c&<*n;Olik?D(qwYJa^ z!DF`&(A3U81M(8`a&rC7XlI{s zs3XI|zGu9NF+1H`?Cj|oK3pHAlprQ1mJ}C%`|8yP9$wzV!*vzzBpGfZOsz^Ap@Fe^ zCQ3?5Nm*GHP0d7kdHGV?`PU$XEgwv*!D@tn_Z6R&`CUlRqJblLGAWeQ)btb-BHL5Y z2#P$dnj`;t=Pfv`j*bp^%ov!MYl~emW|o#p8X5`V5fO_HJi}vSKS1zL*19Tu|NcEq z@b;CNuI|flHqSE$5XSFXzdW8OHVOnD=e0$S1QBeJ;ep_1=H!@_etR=8+wAYnRaaiF zOh7=OUTpXoc>M6h1f9g%<|Z+5L_z}ZncLyo@ZRz9HLPlGem*KPGX3bt=`k)Y9W}KW zILGbs?C`pT0@6?~0vyTL!NDOYS(urLNf|t&d~ImX+1Xh=@%7QB#$!CZ+?pDV8s}|g zQPEGSsi_na(_44&Nk~S%3;R<@C}c_jTrlkXrXn;n6xcEgSTHv=^%W{As@wijaBmXt zaY@X5$O)1pxcR0Jg2A+mjKlM7p;6J%nRyx&2;JH(6zGa?qD9BqStg zpAGT3ejknr@%xdVul(jsU{h05sq4P6ggm$wu=()xbiv%*-1EMV{{HlWf;vt$T4JFj z!x7Z7kDff?J@W!*ATlBm(N9ZQIW0Z?n?PK~0jT@2qM91FaoyLi4^B@`egK;r85sdP zE6K=&6c-o&vYv(bJg=lC2I0tp&I|sK3dBbb#MS@1H!l=r>9Ui`ILB%5r4R{G8Ik-~ Hx*z@z4<)Ur diff --git a/stories/axes/8_custom_domain.tsx b/stories/axes/8_custom_domain.tsx index a4914b1e96..06c083ec9b 100644 --- a/stories/axes/8_custom_domain.tsx +++ b/stories/axes/8_custom_domain.tsx @@ -23,14 +23,17 @@ import React from 'react'; import { Axis, BarSeries, Chart, LIGHT_THEME, LineSeries, Position, ScaleType, Settings } from '../../src'; export const Example = () => { + const customXDomain = boolean('customize X domain', true, 'X axis'); + const customBarYDomain = boolean('customize Y domain', true, 'Bar'); + const customLineYDomain = boolean('customize Y domain', true, 'Line'); const options = { range: true, - min: 0, + min: -10, max: 20, step: 0.1, }; const barDomain = { - min: number('Bar min', 0, options, 'Bar'), + min: number('Bar min', -5, options, 'Bar'), max: number('Bar max', 7, options, 'Bar'), }; @@ -60,23 +63,20 @@ export const Example = () => { }; const showBars = boolean('show bars', true, 'Bar'); - const nice = boolean('nice domain', true); + const niceDomainBar = boolean('nice domain', true, 'Bar'); + const niceDomainLine = boolean('nice domain', true, 'Line'); return ( { title="Bar" position={Position.Right} tickFormat={(d) => Number(d).toFixed(2)} - domain={barDomain} + domain={customBarYDomain ? barDomain : undefined} hide={boolean('Hide bar axis', false, 'Bar')} ticks={barTicks} style={{ @@ -112,7 +112,7 @@ export const Example = () => { groupId="group2" position={Position.Left} tickFormat={(d) => Number(d).toFixed(2)} - domain={lineDomain} + domain={customLineYDomain ? lineDomain : undefined} hide={boolean('Hide line axis', false, 'Line')} ticks={lineTicks} style={{ @@ -136,13 +136,13 @@ export const Example = () => { id="bars" xScaleType={ScaleType.Linear} yScaleType={ScaleType.Linear} - yNice={nice} + yNice={niceDomainBar} xAccessor="x" yAccessors={['y']} data={[ { x: 0, y: 2 }, { x: 1, y: 7 }, - { x: 2, y: 3 }, + { x: 2, y: -3 }, { x: 3, y: 6 }, ]} /> @@ -150,9 +150,9 @@ export const Example = () => {