From a7a4ecdd668818e6502c65e7a3f8abed75e3b226 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 21 Aug 2019 16:14:59 +0200 Subject: [PATCH] feat: customize number of axis ticks (#319) This commit add the possibility to customize the number of ticks displayed on each axis. A ticks parameter is added to the Axis component. The scale function computes the number of ticks that are uniformly spaced using human-readable values that are around the same number provided on that tick property. See https://github.com/d3/d3-scale#continuous_ticks --- .../annotations/annotation_marker.test.tsx | 6 +- .../annotations/annotation_utils.test.ts | 14 ++- .../crosshair_utils.linear_snap.test.ts | 30 +++++- .../crosshair_utils.ordinal_snap.test.ts | 30 +++++- .../rendering/rendering.areas.test.ts | 67 ++++++++++--- .../rendering/rendering.bands.test.ts | 32 ++++-- .../xy_chart/rendering/rendering.bars.test.ts | 40 ++++++-- .../rendering/rendering.lines.test.ts | 64 +++++++++--- .../store/chart_state.interactions.test.ts | 21 +++- .../xy_chart/store/chart_state.test.ts | 10 +- .../chart_types/xy_chart/store/utils.test.ts | 9 +- .../src/chart_types/xy_chart/store/utils.ts | 4 +- .../chart_types/xy_chart/utils/axis_utils.ts | 13 ++- .../chart_types/xy_chart/utils/scales.test.ts | 24 +++-- .../src/chart_types/xy_chart/utils/scales.ts | 76 ++++++++------- .../src/chart_types/xy_chart/utils/specs.ts | 2 + .../stacked_percent_series_utils.test.ts | 2 +- .../src/components/react_canvas/axis.tsx | 1 + .../src/utils/scales/scale_continuous.test.ts | 39 ++++---- .../src/utils/scales/scale_continuous.ts | 97 ++++++++++++------- .../src/utils/scales/scale_time.test.ts | 40 ++++++-- .../src/utils/scales/scales.test.ts | 28 +++--- packages/osd-charts/stories/axis.tsx | 47 +++++++-- 23 files changed, 488 insertions(+), 208 deletions(-) diff --git a/packages/osd-charts/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx b/packages/osd-charts/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx index 65c9ca2bb0d9..2958252a23a3 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx +++ b/packages/osd-charts/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx @@ -21,7 +21,11 @@ describe('annotation marker', () => { const maxRange = 100; const continuousData = [0, 10]; - const continuousScale = new ScaleContinuous(ScaleType.Linear, continuousData, [minRange, maxRange]); + const continuousScale = new ScaleContinuous({ + type: ScaleType.Linear, + domain: continuousData, + range: [minRange, maxRange], + }); const chartDimensions: Dimensions = { width: 10, diff --git a/packages/osd-charts/src/chart_types/xy_chart/annotations/annotation_utils.test.ts b/packages/osd-charts/src/chart_types/xy_chart/annotations/annotation_utils.test.ts index c22e24fddb52..450ebce0c3dc 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/annotations/annotation_utils.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/annotations/annotation_utils.test.ts @@ -50,7 +50,14 @@ describe('annotation utils', () => { const maxRange = 100; const continuousData = [0, 10]; - const continuousScale = new ScaleContinuous(ScaleType.Linear, continuousData, [minRange, maxRange], 0, 1); + const continuousScale = new ScaleContinuous( + { + type: ScaleType.Linear, + domain: continuousData, + range: [minRange, maxRange], + }, + { bandwidth: 0, minInterval: 1 }, + ); const ordinalData = ['a', 'b', 'c', 'd', 'a', 'b', 'c']; const ordinalScale = new ScaleBand(ordinalData, [minRange, maxRange]); @@ -1345,7 +1352,10 @@ describe('annotation utils', () => { const yScales: Map = new Map(); yScales.set(groupId, continuousScale); - const xScale: Scale = new ScaleContinuous(ScaleType.Linear, continuousData, [minRange, maxRange], 1, 1); + const xScale: Scale = new ScaleContinuous( + { type: ScaleType.Linear, domain: continuousData, range: [minRange, maxRange] }, + { bandwidth: 1, minInterval: 1 }, + ); const annotationRectangle: RectAnnotationSpec = { annotationId: getAnnotationId('rect'), diff --git a/packages/osd-charts/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts b/packages/osd-charts/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts index 2fbec6c39855..556732778ccb 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/crosshair/crosshair_utils.linear_snap.test.ts @@ -82,11 +82,31 @@ describe('Crosshair utils linear scale', () => { mixedLinesBarsMap.set(barSeries2SpecId, barSeries2); const mixedLinesBarsSeriesDomains = computeSeriesDomains(mixedLinesBarsMap, new Map()); - const barSeriesScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 120); - const multiBarSeriesScale = computeXScale(multiBarSeriesDomains.xDomain, multiBarSeriesMap.size, 0, 120); - const lineSeriesScale = computeXScale(lineSeriesDomains.xDomain, lineSeriesMap.size, 0, 120); - const multiLineSeriesScale = computeXScale(multiLineSeriesDomains.xDomain, multiLineSeriesMap.size, 0, 120); - const mixedLinesBarsSeriesScale = computeXScale(mixedLinesBarsSeriesDomains.xDomain, mixedLinesBarsMap.size, 0, 120); + const barSeriesScale = computeXScale({ + xDomain: barSeriesDomains.xDomain, + totalBarsInCluster: barSeriesMap.size, + range: [0, 120], + }); + const multiBarSeriesScale = computeXScale({ + xDomain: multiBarSeriesDomains.xDomain, + totalBarsInCluster: multiBarSeriesMap.size, + range: [0, 120], + }); + const lineSeriesScale = computeXScale({ + xDomain: lineSeriesDomains.xDomain, + totalBarsInCluster: lineSeriesMap.size, + range: [0, 120], + }); + const multiLineSeriesScale = computeXScale({ + xDomain: multiLineSeriesDomains.xDomain, + totalBarsInCluster: multiLineSeriesMap.size, + range: [0, 120], + }); + const mixedLinesBarsSeriesScale = computeXScale({ + xDomain: mixedLinesBarsSeriesDomains.xDomain, + totalBarsInCluster: mixedLinesBarsMap.size, + range: [0, 120], + }); /** * if we have lines on a linear scale, the snap position and band should diff --git a/packages/osd-charts/src/chart_types/xy_chart/crosshair/crosshair_utils.ordinal_snap.test.ts b/packages/osd-charts/src/chart_types/xy_chart/crosshair/crosshair_utils.ordinal_snap.test.ts index 16a85e617100..74edfdeb1f7c 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/crosshair/crosshair_utils.ordinal_snap.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/crosshair/crosshair_utils.ordinal_snap.test.ts @@ -81,11 +81,31 @@ describe('Crosshair utils ordinal scales', () => { mixedLinesBarsMap.set(barSeries2SpecId, barSeries2); const mixedLinesBarsSeriesDomains = computeSeriesDomains(mixedLinesBarsMap, new Map()); - const barSeriesScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 120); - const multiBarSeriesScale = computeXScale(multiBarSeriesDomains.xDomain, multiBarSeriesMap.size, 0, 120); - const lineSeriesScale = computeXScale(lineSeriesDomains.xDomain, lineSeriesMap.size, 0, 120); - const multiLineSeriesScale = computeXScale(multiLineSeriesDomains.xDomain, multiLineSeriesMap.size, 0, 120); - const mixedLinesBarsSeriesScale = computeXScale(mixedLinesBarsSeriesDomains.xDomain, mixedLinesBarsMap.size, 0, 120); + const barSeriesScale = computeXScale({ + xDomain: barSeriesDomains.xDomain, + totalBarsInCluster: barSeriesMap.size, + range: [0, 120], + }); + const multiBarSeriesScale = computeXScale({ + xDomain: multiBarSeriesDomains.xDomain, + totalBarsInCluster: multiBarSeriesMap.size, + range: [0, 120], + }); + const lineSeriesScale = computeXScale({ + xDomain: lineSeriesDomains.xDomain, + totalBarsInCluster: lineSeriesMap.size, + range: [0, 120], + }); + const multiLineSeriesScale = computeXScale({ + xDomain: multiLineSeriesDomains.xDomain, + totalBarsInCluster: multiLineSeriesMap.size, + range: [0, 120], + }); + const mixedLinesBarsSeriesScale = computeXScale({ + xDomain: mixedLinesBarsSeriesDomains.xDomain, + totalBarsInCluster: mixedLinesBarsMap.size, + range: [0, 120], + }); test('can snap position on scale ordinal bar', () => { let snappedPosition = getSnapPosition('a', barSeriesScale); diff --git a/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.areas.test.ts b/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.areas.test.ts index d262e1675a6c..d3b8f124077d 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.areas.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.areas.test.ts @@ -27,8 +27,15 @@ describe('Rendering points - areas', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ + yDomains: pointSeriesDomains.yDomain, + range: [100, 0], + }); let renderedArea: { areaGeometry: AreaGeometry; indexedGeometries: Map; @@ -76,8 +83,12 @@ describe('Rendering points - areas', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; indexedGeometries: Map; @@ -187,8 +198,12 @@ describe('Rendering points - areas', () => { pointSeriesMap.set(spec1Id, pointSeriesSpec1); pointSeriesMap.set(spec2Id, pointSeriesSpec2); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let firstLine: { areaGeometry: AreaGeometry; @@ -351,8 +366,12 @@ describe('Rendering points - areas', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; @@ -457,8 +476,12 @@ describe('Rendering points - areas', () => { pointSeriesMap.set(spec1Id, pointSeriesSpec1); pointSeriesMap.set(spec2Id, pointSeriesSpec2); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let firstLine: { areaGeometry: AreaGeometry; @@ -620,8 +643,12 @@ describe('Rendering points - areas', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; @@ -726,8 +753,12 @@ describe('Rendering points - areas', () => { pointSeriesMap.set(spec1Id, pointSeriesSpec1); pointSeriesMap.set(spec2Id, pointSeriesSpec2); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let firstLine: { areaGeometry: AreaGeometry; @@ -874,8 +905,12 @@ describe('Rendering points - areas', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 90); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 90], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; diff --git a/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.bands.test.ts b/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.bands.test.ts index 6e0418b1a452..f08c0fd3247a 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.bands.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.bands.test.ts @@ -28,8 +28,12 @@ describe('Rendering bands - areas', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; indexedGeometries: Map; @@ -78,8 +82,12 @@ describe('Rendering bands - areas', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; indexedGeometries: Map; @@ -216,8 +224,12 @@ describe('Rendering bands - areas', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedArea: { areaGeometry: AreaGeometry; indexedGeometries: Map; @@ -393,8 +405,12 @@ describe('Rendering bands - areas', () => { const barSeriesMap = new Map(); barSeriesMap.set(SPEC_ID, barSeriesSpec); const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 100); - const yScales = computeYScales(barSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: barSeriesDomains.xDomain, + totalBarsInCluster: barSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); test('Can render two bars', () => { const { barGeometries } = renderBars( diff --git a/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.bars.test.ts b/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.bars.test.ts index ebb70cf0f7e8..5c071c3370eb 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.bars.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.bars.test.ts @@ -27,8 +27,12 @@ describe('Rendering bars', () => { barSeriesMap.set(SPEC_ID, barSeriesSpec); const customDomain = [0, 1]; const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map(), customDomain); - const xScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 100); - const yScales = computeYScales(barSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: barSeriesDomains.xDomain, + totalBarsInCluster: barSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); test('Can render two bars within domain', () => { const { barGeometries } = renderBars( @@ -207,8 +211,12 @@ describe('Rendering bars', () => { barSeriesMap.set(spec1Id, barSeriesSpec1); barSeriesMap.set(spec2Id, barSeriesSpec2); const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 100); - const yScales = computeYScales(barSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: barSeriesDomains.xDomain, + totalBarsInCluster: barSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); test('can render first spec bars', () => { const { barGeometries } = renderBars( @@ -392,8 +400,12 @@ describe('Rendering bars', () => { const barSeriesMap = new Map(); barSeriesMap.set(SPEC_ID, barSeriesSpec); const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 100); - const yScales = computeYScales(barSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: barSeriesDomains.xDomain, + totalBarsInCluster: barSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); test('Can render two bars', () => { const { barGeometries } = renderBars( @@ -507,8 +519,12 @@ describe('Rendering bars', () => { barSeriesMap.set(spec1Id, barSeriesSpec1); barSeriesMap.set(spec2Id, barSeriesSpec2); const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 100); - const yScales = computeYScales(barSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: barSeriesDomains.xDomain, + totalBarsInCluster: barSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); test('can render first spec bars', () => { const { barGeometries } = renderBars( @@ -706,8 +722,12 @@ describe('Rendering bars', () => { barSeriesMap.set(spec1Id, barSeriesSpec1); barSeriesMap.set(spec2Id, barSeriesSpec2); const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const xScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 100); - const yScales = computeYScales(barSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: barSeriesDomains.xDomain, + totalBarsInCluster: barSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [100, 0] }); test('can render first spec bars', () => { const { barGeometries } = renderBars( diff --git a/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.lines.test.ts b/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.lines.test.ts index 2cb1b0e80eab..1e1de5e138da 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.lines.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/rendering/rendering.lines.test.ts @@ -28,8 +28,12 @@ describe('Rendering points - line', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedLine: { lineGeometry: LineGeometry; indexedGeometries: Map; @@ -74,8 +78,12 @@ describe('Rendering points - line', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedLine: { lineGeometry: LineGeometry; indexedGeometries: Map; @@ -180,8 +188,12 @@ describe('Rendering points - line', () => { pointSeriesMap.set(spec1Id, pointSeriesSpec1); pointSeriesMap.set(spec2Id, pointSeriesSpec2); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let firstLine: { lineGeometry: LineGeometry; @@ -342,8 +354,12 @@ describe('Rendering points - line', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedLine: { lineGeometry: LineGeometry; @@ -447,8 +463,12 @@ describe('Rendering points - line', () => { pointSeriesMap.set(spec1Id, pointSeriesSpec1); pointSeriesMap.set(spec2Id, pointSeriesSpec2); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let firstLine: { lineGeometry: LineGeometry; @@ -608,8 +628,12 @@ describe('Rendering points - line', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedLine: { lineGeometry: LineGeometry; @@ -713,8 +737,12 @@ describe('Rendering points - line', () => { pointSeriesMap.set(spec1Id, pointSeriesSpec1); pointSeriesMap.set(spec2Id, pointSeriesSpec2); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 100); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let firstLine: { lineGeometry: LineGeometry; @@ -861,8 +889,12 @@ describe('Rendering points - line', () => { const pointSeriesMap = new Map(); pointSeriesMap.set(SPEC_ID, pointSeriesSpec); const pointSeriesDomains = computeSeriesDomains(pointSeriesMap, new Map()); - const xScale = computeXScale(pointSeriesDomains.xDomain, pointSeriesMap.size, 0, 90); - const yScales = computeYScales(pointSeriesDomains.yDomain, 100, 0); + const xScale = computeXScale({ + xDomain: pointSeriesDomains.xDomain, + totalBarsInCluster: pointSeriesMap.size, + range: [0, 90], + }); + const yScales = computeYScales({ yDomains: pointSeriesDomains.yDomain, range: [100, 0] }); let renderedLine: { lineGeometry: LineGeometry; diff --git a/packages/osd-charts/src/chart_types/xy_chart/store/chart_state.interactions.test.ts b/packages/osd-charts/src/chart_types/xy_chart/store/chart_state.interactions.test.ts index 58762106440f..1a7daff38a5d 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/store/chart_state.interactions.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/store/chart_state.interactions.test.ts @@ -174,9 +174,16 @@ describe('Chart state pointer interactions', () => { }); test('can respond to tooltip types changes', () => { - store.xScale = new ScaleContinuous(ScaleType.Linear, [0, 1], [0, 100], 50, 0.5); + store.xScale = new ScaleContinuous( + { + type: ScaleType.Linear, + domain: [0, 1], + range: [0, 100], + }, + { bandwidth: 50, minInterval: 0.5 }, + ); store.yScales = new Map(); - store.yScales.set(GROUP_ID, new ScaleContinuous(ScaleType.Linear, [0, 1], [0, 100])); + store.yScales.set(GROUP_ID, new ScaleContinuous({ type: ScaleType.Linear, domain: [0, 1], range: [0, 100] })); store.geometriesIndex.set(0, [indexedGeom1Red]); store.geometriesIndexKeys.push(0); store.tooltipType.set(TooltipType.None); @@ -214,8 +221,12 @@ function mouseOverTestSuite(scaleType: ScaleType) { const barSeriesMap = new Map(); barSeriesMap.set(SPEC_ID, spec); const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map()); - const barSeriesScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 100); - const yScales = computeYScales(barSeriesDomains.yDomain, 0, 100); + const barSeriesScale = computeXScale({ + xDomain: barSeriesDomains.xDomain, + totalBarsInCluster: barSeriesMap.size, + range: [0, 100], + }); + const yScales = computeYScales({ yDomains: barSeriesDomains.yDomain, range: [0, 100] }); store.xScale = barSeriesScale; store.yScales = yScales; store.geometriesIndex.set(0, [indexedGeom1Red]); @@ -376,7 +387,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { const singleValueScale = store.xScale!.type === ScaleType.Ordinal ? new ScaleBand(['a'], [0, 0]) - : new ScaleContinuous(ScaleType.Linear, [1, 1], [0, 0]); + : new ScaleContinuous({ type: ScaleType.Linear, domain: [1, 1], range: [0, 0] }); store.xScale = singleValueScale; }); test('horizontal chart rotation', () => { diff --git a/packages/osd-charts/src/chart_types/xy_chart/store/chart_state.test.ts b/packages/osd-charts/src/chart_types/xy_chart/store/chart_state.test.ts index de7e3f7d8709..b2a4b9b757f4 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/store/chart_state.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/store/chart_state.test.ts @@ -681,7 +681,7 @@ describe('Chart Store', () => { test('can disable brush based on scale and listener', () => { store.xScale = undefined; expect(store.isBrushEnabled()).toBe(false); - store.xScale = new ScaleContinuous(ScaleType.Linear, [0, 100], [0, 100]); + store.xScale = new ScaleContinuous({ type: ScaleType.Linear, domain: [0, 100], range: [0, 100] }); store.onBrushEndListener = undefined; expect(store.isBrushEnabled()).toBe(false); store.setOnBrushEndListener(() => ({})); @@ -702,7 +702,7 @@ describe('Chart Store', () => { seriesKey: 'a', yAccessor: 'y', }; - store.xScale = new ScaleContinuous(ScaleType.Linear, [0, 100], [0, 100]); + store.xScale = new ScaleContinuous({ type: ScaleType.Linear, domain: [0, 100], range: [0, 100] }); store.cursorPosition.x = 1; store.cursorPosition.y = 1; store.tooltipType.set(TooltipType.Crosshairs); @@ -793,7 +793,7 @@ describe('Chart Store', () => { expect(clickListener.mock.calls[1][0]).toEqual([geom1.value, geom2.value]); }); test('can compute annotation tooltip state', () => { - const scale = new ScaleContinuous(ScaleType.Linear, [0, 100], [0, 100]); + const scale = new ScaleContinuous({ type: ScaleType.Linear, domain: [0, 100], range: [0, 100] }); store.rawCursorPosition.x = -1; store.rawCursorPosition.y = 0; @@ -901,7 +901,7 @@ describe('Chart Store', () => { }; beforeEach(() => { - store.xScale = new ScaleContinuous(ScaleType.Linear, [0, 100], [0, 100]); + store.xScale = new ScaleContinuous({ type: ScaleType.Linear, domain: [0, 100], range: [0, 100] }); }); test('when cursor is outside of chart bounds', () => { @@ -982,7 +982,7 @@ describe('Chart Store', () => { getPosition, })); - const scale = new ScaleContinuous(ScaleType.Linear, [0, 100], [0, 100]); + const scale = new ScaleContinuous({ type: ScaleType.Linear, domain: [0, 100], range: [0, 100] }); beforeEach(() => { // @ts-ignore store.setCursorPosition = jest.fn(); diff --git a/packages/osd-charts/src/chart_types/xy_chart/store/utils.test.ts b/packages/osd-charts/src/chart_types/xy_chart/store/utils.test.ts index 3def34ddc0eb..05a46ec0ce8e 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/store/utils.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/store/utils.test.ts @@ -1035,7 +1035,14 @@ describe('Chart State utils', () => { const range: [number, number] = [0, 100]; const bandwidth = 10; const barsPadding = 0.5; - const scale = new ScaleContinuous(ScaleType.Linear, domain, range, bandwidth, 0, 'utc', 1, barsPadding); + const scale = new ScaleContinuous( + { + type: ScaleType.Linear, + domain, + range, + }, + { bandwidth, minInterval: 0, timeZone: 'utc', totalBarsInCluster: 1, barsPadding }, + ); const histogramModeEnabled = true; const histogramModeDisabled = false; diff --git a/packages/osd-charts/src/chart_types/xy_chart/store/utils.ts b/packages/osd-charts/src/chart_types/xy_chart/store/utils.ts index f3a45bb76aec..87c7f8779ebb 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/store/utils.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/store/utils.ts @@ -215,8 +215,8 @@ export function computeSeriesGeometries( const { stackedBarsInCluster, totalBarsInCluster } = countBarsInCluster(stacked, nonStacked); // compute scales - const xScale = computeXScale(xDomain, totalBarsInCluster, 0, width, barsPadding, enableHistogramMode); - const yScales = computeYScales(yDomain, height, 0); + const xScale = computeXScale({ xDomain, totalBarsInCluster, range: [0, width], barsPadding, enableHistogramMode }); + const yScales = computeYScales({ yDomains: yDomain, range: [height, 0] }); // compute colors diff --git a/packages/osd-charts/src/chart_types/xy_chart/utils/axis_utils.ts b/packages/osd-charts/src/chart_types/xy_chart/utils/axis_utils.ts index f40705a0d4ef..31f698f2eb7f 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/utils/axis_utils.ts @@ -126,15 +126,22 @@ export function getScaleForAxisSpec( enableHistogramMode?: boolean, ): Scale | null { const axisIsYDomain = isYDomain(axisSpec.position, chartRotation); - + const range: [number, number] = [minRange, maxRange]; if (axisIsYDomain) { - const yScales = computeYScales(yDomain, minRange, maxRange); + const yScales = computeYScales({ yDomains: yDomain, range, ticks: axisSpec.ticks }); if (yScales.has(axisSpec.groupId)) { return yScales.get(axisSpec.groupId)!; } return null; } else { - return computeXScale(xDomain, totalBarsInCluster, minRange, maxRange, barsPadding, enableHistogramMode); + return computeXScale({ + xDomain, + totalBarsInCluster, + range, + barsPadding, + enableHistogramMode, + ticks: axisSpec.ticks, + }); } } diff --git a/packages/osd-charts/src/chart_types/xy_chart/utils/scales.test.ts b/packages/osd-charts/src/chart_types/xy_chart/utils/scales.test.ts index 9b621c540d1a..5c909d09ccfe 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/utils/scales.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/utils/scales.test.ts @@ -22,7 +22,7 @@ describe('Series scales', () => { }; test('should compute X Scale linear min, max with bands', () => { - const scale = computeXScale(xDomainLinear, 1, 0, 120); + const scale = computeXScale({ xDomain: xDomainLinear, totalBarsInCluster: 1, range: [0, 120] }); const expectedBandwidth = 120 / 4; expect(scale.bandwidth).toBe(expectedBandwidth); expect(scale.scale(0)).toBe(expectedBandwidth * 0); @@ -32,7 +32,7 @@ describe('Series scales', () => { }); test('should compute X Scale linear inverse min, max with bands', () => { - const scale = computeXScale(xDomainLinear, 1, 120, 0); + const scale = computeXScale({ xDomain: xDomainLinear, totalBarsInCluster: 1, range: [120, 0] }); const expectedBandwidth = 120 / 4; expect(scale.bandwidth).toBe(expectedBandwidth); expect(scale.scale(0)).toBe(expectedBandwidth * 3); @@ -56,7 +56,13 @@ describe('Series scales', () => { }; const enableHistogramMode = true; - const scale = computeXScale(xDomainSingleValue, 1, 0, maxRange, 0, enableHistogramMode); + const scale = computeXScale({ + xDomain: xDomainSingleValue, + totalBarsInCluster: 1, + range: [0, maxRange], + barsPadding: 0, + enableHistogramMode, + }); expect(scale.bandwidth).toBe(maxRange); expect(scale.domain).toEqual([singleDomainValue, singleDomainValue + minInterval]); expect(scale.range).toEqual([0, maxRange]); @@ -72,7 +78,13 @@ describe('Series scales', () => { }; const enableHistogramMode = false; - const scale = computeXScale(xDomainSingleValue, 1, 0, maxRange, 0, enableHistogramMode); + const scale = computeXScale({ + xDomain: xDomainSingleValue, + totalBarsInCluster: 1, + range: [0, maxRange], + barsPadding: 0, + enableHistogramMode, + }); expect(scale.bandwidth).toBe(maxRange); expect(scale.domain).toEqual([singleDomainValue, singleDomainValue]); expect(scale.range).toEqual([0, 0]); @@ -80,13 +92,13 @@ describe('Series scales', () => { }); test('should compute X Scale ordinal', () => { - const nonZeroGroupScale = computeXScale(xDomainOrdinal, 1, 120, 0); + const nonZeroGroupScale = computeXScale({ xDomain: xDomainOrdinal, totalBarsInCluster: 1, range: [120, 0] }); const expectedBandwidth = 60; expect(nonZeroGroupScale.bandwidth).toBe(expectedBandwidth); expect(nonZeroGroupScale.scale('a')).toBe(expectedBandwidth); expect(nonZeroGroupScale.scale('b')).toBe(0); - const zeroGroupScale = computeXScale(xDomainOrdinal, 0, 120, 0); + const zeroGroupScale = computeXScale({ xDomain: xDomainOrdinal, totalBarsInCluster: 0, range: [120, 0] }); expect(zeroGroupScale.bandwidth).toBe(expectedBandwidth); }); diff --git a/packages/osd-charts/src/chart_types/xy_chart/utils/scales.ts b/packages/osd-charts/src/chart_types/xy_chart/utils/scales.ts index acac6cfdef9f..afa529a9bdab 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/utils/scales.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/utils/scales.ts @@ -55,27 +55,30 @@ function getBandScaleRange( return { start, end }; } +interface XScaleOptions { + xDomain: XDomain; + totalBarsInCluster: number; + range: [number, number]; + barsPadding?: number; + enableHistogramMode?: boolean; + ticks?: number; +} + /** * 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 */ -export function computeXScale( - xDomain: XDomain, - totalBarsInCluster: number, - minRange: number, - maxRange: number, - barsPadding?: number, - enableHistogramMode?: boolean, -): Scale { +export function computeXScale(options: XScaleOptions): Scale { + const { xDomain, totalBarsInCluster, range, barsPadding, enableHistogramMode, ticks } = options; const { scaleType, minInterval, domain, isBandScale, timeZone } = xDomain; - const rangeDiff = Math.abs(maxRange - minRange); - const isInverse = maxRange < minRange; + const rangeDiff = Math.abs(range[1] - range[0]); + const isInverse = range[1] < range[0]; if (scaleType === ScaleType.Ordinal) { const dividend = totalBarsInCluster > 0 ? totalBarsInCluster : 1; const bandwidth = rangeDiff / (domain.length * dividend); - return new ScaleBand(domain, [minRange, maxRange], bandwidth, barsPadding); + return new ScaleBand(domain, range, bandwidth, barsPadding); } else { if (isBandScale) { const [domainMin, domainMax] = domain; @@ -87,47 +90,52 @@ export function computeXScale( const intervalCount = (adjustedDomain[1] - adjustedDomain[0]) / minInterval; const intervalCountOffest = isSingleValueHistogram ? 0 : 1; const bandwidth = rangeDiff / (intervalCount + intervalCountOffest); - - const { start, end } = getBandScaleRange(isInverse, isSingleValueHistogram, minRange, maxRange, bandwidth); + const { start, end } = getBandScaleRange(isInverse, isSingleValueHistogram, range[0], range[1], bandwidth); const scale = new ScaleContinuous( - scaleType, - adjustedDomain, - [start, end], - bandwidth / totalBarsInCluster, - minInterval, - timeZone, - totalBarsInCluster, - barsPadding, + { + type: scaleType, + domain: adjustedDomain, + range: [start, end], + }, + { bandwidth: bandwidth / totalBarsInCluster, minInterval, timeZone, totalBarsInCluster, barsPadding, ticks }, ); return scale; } else { return new ScaleContinuous( - scaleType, - domain, - [minRange, maxRange], - 0, - minInterval, - timeZone, - totalBarsInCluster, - barsPadding, + { type: scaleType, domain, range }, + { bandwidth: 0, minInterval, timeZone, totalBarsInCluster, barsPadding, ticks }, ); } } } +interface YScaleOptions { + yDomains: YDomain[]; + range: [number, number]; + ticks?: number; +} /** * Compute the y scales, one per groupId for the y axis. * @param yDomains the y domains * @param axisLength the axisLength of the y axis */ -export function computeYScales(yDomains: YDomain[], minRange: number, maxRange: number): Map { +export function computeYScales(options: YScaleOptions): Map { const yScales: Map = new Map(); - - yDomains.forEach((yDomain) => { - const yScale = new ScaleContinuous(yDomain.scaleType, yDomain.domain, [minRange, maxRange]); - yScales.set(yDomain.groupId, yScale); + const { yDomains, range, ticks } = options; + yDomains.forEach(({ scaleType: type, domain, groupId }) => { + const yScale = new ScaleContinuous( + { + type, + domain, + range, + }, + { + ticks, + }, + ); + yScales.set(groupId, yScale); }); return yScales; diff --git a/packages/osd-charts/src/chart_types/xy_chart/utils/specs.ts b/packages/osd-charts/src/chart_types/xy_chart/utils/specs.ts index 458375aa2e97..6f278f34c412 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/utils/specs.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/utils/specs.ts @@ -227,6 +227,8 @@ export interface AxisSpec { tickFormat: TickFormatter; /** The degrees of rotation of the tick labels */ tickLabelRotation?: number; + /** An approximate count of how many ticks will be generated */ + ticks?: number; /** The axis title */ title?: string; /** If specified, it constrains the domain for these values */ diff --git a/packages/osd-charts/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts b/packages/osd-charts/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts index 1716894e8bbf..c658ccc49705 100644 --- a/packages/osd-charts/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts +++ b/packages/osd-charts/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts @@ -163,7 +163,7 @@ describe('Stacked Series Utils', () => { }, ]; - describe.only('Format stacked dataset', () => { + describe('Format stacked dataset', () => { test('format data without nulls', () => { const formattedData = formatStackedDataSeriesValues(STANDARD_DATA_SET, false, true); const data0 = formattedData[0].data[0]; diff --git a/packages/osd-charts/src/components/react_canvas/axis.tsx b/packages/osd-charts/src/components/react_canvas/axis.tsx index 7483ce766d56..72157ce7deca 100644 --- a/packages/osd-charts/src/components/react_canvas/axis.tsx +++ b/packages/osd-charts/src/components/react_canvas/axis.tsx @@ -220,6 +220,7 @@ export class Axis extends React.PureComponent { stroke="black" strokeWidth={1} fill="violet" + opacity={0.2} /> )} diff --git a/packages/osd-charts/src/utils/scales/scale_continuous.test.ts b/packages/osd-charts/src/utils/scales/scale_continuous.test.ts index 8be8896afc95..a18e147ef0b5 100644 --- a/packages/osd-charts/src/utils/scales/scale_continuous.test.ts +++ b/packages/osd-charts/src/utils/scales/scale_continuous.test.ts @@ -11,7 +11,7 @@ describe('Scale Continuous', () => { const domain: Domain = [0, 2]; const minRange = 0; const maxRange = 100; - const scale = new ScaleContinuous(ScaleType.Linear, domain, [minRange, maxRange]); + const scale = new ScaleContinuous({ type: ScaleType.Linear, domain, range: [minRange, maxRange] }); expect(scale.invert(0)).toBe(0); expect(scale.invert(50)).toBe(1); expect(scale.invert(100)).toBe(2); @@ -23,7 +23,7 @@ describe('Scale Continuous', () => { const domain = [startTime.toMillis(), endTime.toMillis()]; const minRange = 0; const maxRange = 100; - const scale = new ScaleContinuous(ScaleType.Time, domain, [minRange, maxRange]); + const scale = new ScaleContinuous({ type: ScaleType.Time, domain, range: [minRange, maxRange] }); expect(scale.invert(0)).toBe(startTime.toMillis()); expect(scale.invert(50)).toBe(midTime.toMillis()); expect(scale.invert(100)).toBe(endTime.toMillis()); @@ -31,10 +31,10 @@ describe('Scale Continuous', () => { test('check if a scale is log scale', () => { const domain: Domain = [0, 2]; const range: [number, number] = [0, 100]; - const scaleLinear = new ScaleContinuous(ScaleType.Linear, domain, range); - const scaleLog = new ScaleContinuous(ScaleType.Log, domain, range); - const scaleTime = new ScaleContinuous(ScaleType.Time, domain, range); - const scaleSqrt = new ScaleContinuous(ScaleType.Sqrt, domain, range); + const scaleLinear = new ScaleContinuous({ type: ScaleType.Linear, domain, range }); + const scaleLog = new ScaleContinuous({ type: ScaleType.Log, domain, range }); + const scaleTime = new ScaleContinuous({ type: ScaleType.Time, domain, range }); + const scaleSqrt = new ScaleContinuous({ type: ScaleType.Sqrt, domain, range }); const scaleBand = new ScaleBand(domain, range); expect(isLogarithmicScale(scaleLinear)).toBe(false); expect(isLogarithmicScale(scaleLog)).toBe(true); @@ -46,7 +46,7 @@ describe('Scale Continuous', () => { const domain: Domain = [0, 2]; const data = [0, 0.5, 0.8, 2]; const range: [number, number] = [0, 2]; - const scaleLinear = new ScaleContinuous(ScaleType.Linear, domain, range); + const scaleLinear = new ScaleContinuous({ type: ScaleType.Linear, domain, range }); expect(scaleLinear.bandwidth).toBe(0); expect(scaleLinear.invertWithStep(0, data)).toBe(0); expect(scaleLinear.invertWithStep(0.1, data)).toBe(0); @@ -78,7 +78,7 @@ describe('Scale Continuous', () => { type: 'xDomain', }; - const scaleLinear = computeXScale(xDomain, 1, 0, 120, 0); + const scaleLinear = computeXScale({ xDomain, totalBarsInCluster: 1, range: [0, 120], barsPadding: 0 }); expect(scaleLinear.bandwidth).toBe(40); expect(scaleLinear.invertWithStep(0, data)).toBe(0); expect(scaleLinear.invertWithStep(40, data)).toBe(1); @@ -97,7 +97,10 @@ describe('Scale Continuous', () => { // we tweak the maxRange removing the bandwidth to correctly compute // a band linear scale in computeXScale const range: [number, number] = [0, 100 - 10]; - const scaleLinear = new ScaleContinuous(ScaleType.Linear, domain, range, 10, 10); + const scaleLinear = new ScaleContinuous( + { type: ScaleType.Linear, domain, range }, + { bandwidth: 10, minInterval: 10 }, + ); expect(scaleLinear.bandwidth).toBe(10); expect(scaleLinear.invertWithStep(0, data)).toBe(0); expect(scaleLinear.invertWithStep(10, data)).toBe(10); @@ -117,8 +120,8 @@ describe('Scale Continuous', () => { type: 'xDomain', }; - const scaleLinear = computeXScale(xDomain, 1, 0, 110, 0); - // const scaleLinear = new ScaleContinuous(ScaleType.Linear, domain, range, 10, 10); + const scaleLinear = computeXScale({ xDomain, totalBarsInCluster: 1, range: [0, 110], barsPadding: 0 }); + // const scaleLinear = new ScaleContinuous({type: ScaleType.Linear, domain, range}, 10, 10); expect(scaleLinear.bandwidth).toBe(10); expect(scaleLinear.invertWithStep(0, data)).toBe(0); @@ -142,15 +145,15 @@ describe('Scale Continuous', () => { describe('isSingleValue', () => { test('should return true for domain with fewer than 2 values', () => { - const scale = new ScaleContinuous(ScaleType.Linear, [], [0, 100]); + const scale = new ScaleContinuous({ type: ScaleType.Linear, domain: [], range: [0, 100] }); expect(scale.isSingleValue()).toBe(true); }); test('should return true for domain with equal min and max values', () => { - const scale = new ScaleContinuous(ScaleType.Linear, [1, 1], [0, 100]); + const scale = new ScaleContinuous({ type: ScaleType.Linear, domain: [1, 1], range: [0, 100] }); expect(scale.isSingleValue()).toBe(true); }); test('should return false for domain with differing min and max values', () => { - const scale = new ScaleContinuous(ScaleType.Linear, [1, 2], [0, 100]); + const scale = new ScaleContinuous({ type: ScaleType.Linear, domain: [1, 2], range: [0, 100] }); expect(scale.isSingleValue()).toBe(false); }); }); @@ -160,12 +163,8 @@ describe('Scale Continuous', () => { function getTicksForDomain(domainStart: number, domainEnd: number) { const scale = new ScaleContinuous( - ScaleType.Time, - [domainStart, domainEnd], - [0, 100], - 0, - 0, - Settings.defaultZoneName, + { type: ScaleType.Time, domain: [domainStart, domainEnd], range: [0, 100] }, + { bandwidth: 0, minInterval: 0, timeZone: Settings.defaultZoneName }, ); return scale.tickValues; } diff --git a/packages/osd-charts/src/utils/scales/scale_continuous.ts b/packages/osd-charts/src/utils/scales/scale_continuous.ts index dde9f6a56198..fbc6c03a55eb 100644 --- a/packages/osd-charts/src/utils/scales/scale_continuous.ts +++ b/packages/osd-charts/src/utils/scales/scale_continuous.ts @@ -1,7 +1,7 @@ import { bisectLeft } from 'd3-array'; import { scaleLinear, scaleLog, scaleSqrt, scaleUtc } from 'd3-scale'; import { DateTime } from 'luxon'; -import { clamp } from '../commons'; +import { clamp, mergePartial } from '../commons'; import { ScaleContinuousType, ScaleType, Scale } from './scales'; const SCALES = { @@ -60,6 +60,50 @@ export function limitLogScaleDomain(domain: any[]) { } return domain; } +interface ScaleData { + /** The Type of continuous scale */ + type: ScaleContinuousType; + /** The data input domain */ + domain: any[]; + /** The data output range */ + range: [number, number]; +} + +interface ScaleOptions { + /** + * The desidered bandwidth for a linear band scale. + * @default 0 + */ + bandwidth: number; + /** + * The min interval computed on the XDomain. Not available for yDomains. + * @default 0 + */ + minInterval: number; + /** + * A time zone identifier. Can be any IANA zone supported by he host environment, + * or a fixed-offset name of the form 'utc+3', or the strings 'local' or 'utc'. + * @default 'utc' + */ + timeZone: string; + /** + * The number of bars in the cluster. Used to correctly compute scales when + * using padding between bars. + * @default 1 + */ + totalBarsInCluster: number; + /** + * The proportion of the range that is reserved for blank space between bands + * A number between 0 and 1. + * @default 0 + */ + barsPadding: number; + /** + * The approximated number of ticks. + * @default 10 + */ + ticks: number; +} export class ScaleContinuous implements Scale { readonly bandwidth: number; @@ -76,39 +120,20 @@ export class ScaleContinuous implements Scale { readonly barsPadding: number; private readonly d3Scale: any; - constructor( - type: ScaleContinuousType, - domain: any[], - range: [number, number], - /** - * The desidered bandwidth for a linear band scale. - * @default 0 - */ - bandwidth: number = 0, - /** - * The min interval computed on the XDomain. Not available for yDomains. - * @default 0 - */ - minInterval: number = 0, - /** - * A time zone identifier. Can be any IANA zone supported by he host environment, - * or a fixed-offset name of the form 'utc+3', or the strings 'local' or 'utc'. - * @default 'utc' - */ - timeZone: string = 'utc', - /** - * The number of bars in the cluster. Used to correctly compute scales when - * using padding between bars. - * @default 1 - */ - totalBarsInCluster: number = 1, - /** - * The proportion of the range that is reserved for blank space between bands - * A number between 0 and 1. - * @default 0 - */ - barsPadding: number = 0, - ) { + constructor(scaleData: ScaleData, options?: Partial) { + const { type, domain, range } = scaleData; + const scaleOptions: ScaleOptions = mergePartial( + { + bandwidth: 0, + minInterval: 0, + timeZone: 'utc', + totalBarsInCluster: 1, + barsPadding: 0, + ticks: 10, + }, + options, + ); + const { bandwidth, minInterval, timeZone, totalBarsInCluster, barsPadding, ticks } = scaleOptions; this.d3Scale = SCALES[type](); if (type === ScaleType.Log) { this.domain = limitLogScaleDomain(domain); @@ -137,7 +162,7 @@ export class ScaleContinuous implements Scale { const shiftedDomainMax = endDomain.plus({ minutes: offset }).toMillis(); const tzShiftedScale = scaleUtc().domain([shiftedDomainMin, shiftedDomainMax]); - const rawTicks = tzShiftedScale.ticks(); + const rawTicks = tzShiftedScale.ticks(ticks); const timePerTick = (shiftedDomainMax - shiftedDomainMin) / rawTicks.length; const hasHourTicks = timePerTick < 1000 * 60 * 60 * 12; @@ -153,7 +178,7 @@ export class ScaleContinuous implements Scale { return this.domain[0] + i * this.minInterval; }); } else { - this.tickValues = this.d3Scale.ticks(); + this.tickValues = this.d3Scale.ticks(ticks); } } } diff --git a/packages/osd-charts/src/utils/scales/scale_time.test.ts b/packages/osd-charts/src/utils/scales/scale_time.test.ts index ed03e4b08156..d1fd1b45baad 100644 --- a/packages/osd-charts/src/utils/scales/scale_time.test.ts +++ b/packages/osd-charts/src/utils/scales/scale_time.test.ts @@ -74,7 +74,14 @@ describe('[Scale Time] - timezones', () => { const minRange = 0; const maxRange = 100; const minInterval = (endTime - startTime) / 2; - const scale = new ScaleContinuous(ScaleType.Time, domain, [minRange, maxRange], undefined, minInterval, 'local'); + const scale = new ScaleContinuous( + { + type: ScaleType.Time, + domain, + range: [minRange, maxRange], + }, + { bandwidth: undefined, minInterval, timeZone: 'local' }, + ); expect(scale.invert(0)).toBe(startTime); expect(scale.invert(50)).toBe(midTime); expect(scale.invert(100)).toBe(endTime); @@ -99,7 +106,10 @@ describe('[Scale Time] - timezones', () => { const minRange = 0; const maxRange = 100; const minInterval = (endTime - startTime) / 2; - const scale = new ScaleContinuous(ScaleType.Time, domain, [minRange, maxRange], undefined, minInterval, 'utc'); + const scale = new ScaleContinuous( + { type: ScaleType.Time, domain, range: [minRange, maxRange] }, + { bandwidth: undefined, minInterval, timeZone: 'utc' }, + ); expect(scale.invert(0)).toBe(startTime); expect(scale.invert(50)).toBe(midTime); expect(scale.invert(100)).toBe(endTime); @@ -124,7 +134,14 @@ describe('[Scale Time] - timezones', () => { const minRange = 0; const maxRange = 100; const minInterval = (endTime - startTime) / 2; - const scale = new ScaleContinuous(ScaleType.Time, domain, [minRange, maxRange], undefined, minInterval, 'utc+8'); + const scale = new ScaleContinuous( + { + type: ScaleType.Time, + domain, + range: [minRange, maxRange], + }, + { bandwidth: undefined, minInterval, timeZone: 'utc+8' }, + ); expect(scale.invert(0)).toBe(startTime); expect(scale.invert(50)).toBe(midTime); expect(scale.invert(100)).toBe(endTime); @@ -149,7 +166,14 @@ describe('[Scale Time] - timezones', () => { const minRange = 0; const maxRange = 100; const minInterval = (endTime - startTime) / 2; - const scale = new ScaleContinuous(ScaleType.Time, domain, [minRange, maxRange], undefined, minInterval, 'utc-8'); + const scale = new ScaleContinuous( + { + type: ScaleType.Time, + domain, + range: [minRange, maxRange], + }, + { bandwidth: undefined, minInterval, timeZone: 'utc-8' }, + ); expect(scale.invert(0)).toBe(startTime); expect(scale.invert(50)).toBe(midTime); expect(scale.invert(100)).toBe(endTime); @@ -179,12 +203,8 @@ describe('[Scale Time] - timezones', () => { const maxRange = 100; const minInterval = (endTime - startTime) / 2; const scale = new ScaleContinuous( - ScaleType.Time, - domain, - [minRange, maxRange], - undefined, - minInterval, - timezone, + { type: ScaleType.Time, domain, range: [minRange, maxRange] }, + { bandwidth: undefined, minInterval, timeZone: timezone }, ); const formatFunction = (d: number) => { return DateTime.fromMillis(d, { zone: timezone }).toISO(); diff --git a/packages/osd-charts/src/utils/scales/scales.test.ts b/packages/osd-charts/src/utils/scales/scales.test.ts index 97fac456989c..1530d78a896d 100644 --- a/packages/osd-charts/src/utils/scales/scales.test.ts +++ b/packages/osd-charts/src/utils/scales/scales.test.ts @@ -26,7 +26,7 @@ describe('Scale Test', () => { const data = [0, 10]; const minRange = 0; const maxRange = 100; - const linearScale = new ScaleContinuous(ScaleType.Linear, data, [minRange, maxRange]); + const linearScale = new ScaleContinuous({ type: ScaleType.Linear, domain: data, range: [minRange, maxRange] }); const { domain, range } = linearScale; expect(domain).toEqual([0, 10]); expect(range).toEqual([minRange, maxRange]); @@ -50,7 +50,7 @@ describe('Scale Test', () => { const data = [date1, date3]; const minRange = 0; const maxRange = 100; - const timeScale = new ScaleContinuous(ScaleType.Time, data, [minRange, maxRange]); + const timeScale = new ScaleContinuous({ type: ScaleType.Time, domain: data, range: [minRange, maxRange] }); const { domain, range } = timeScale; expect(domain).toEqual([date1, date3]); expect(range).toEqual([minRange, maxRange]); @@ -65,7 +65,7 @@ describe('Scale Test', () => { const data = [1, 10]; const minRange = 0; const maxRange = 100; - const logScale = new ScaleContinuous(ScaleType.Log, data, [minRange, maxRange]); + const logScale = new ScaleContinuous({ type: ScaleType.Log, domain: data, range: [minRange, maxRange] }); const { domain, range } = logScale; expect(domain).toEqual([1, 10]); expect(range).toEqual([minRange, maxRange]); @@ -78,7 +78,7 @@ describe('Scale Test', () => { const data = [0, 10]; const minRange = 0; const maxRange = 100; - const logScale = new ScaleContinuous(ScaleType.Log, data, [minRange, maxRange]); + const logScale = new ScaleContinuous({ type: ScaleType.Log, domain: data, range: [minRange, maxRange] }); const { domain, range } = logScale; expect(domain).toEqual([1, 10]); expect(range).toEqual([minRange, maxRange]); @@ -91,7 +91,7 @@ describe('Scale Test', () => { const data = [0, 10]; const minRange = 0; const maxRange = 100; - const sqrtScale = new ScaleContinuous(ScaleType.Sqrt, data, [minRange, maxRange]); + const sqrtScale = new ScaleContinuous({ type: ScaleType.Sqrt, domain: data, range: [minRange, maxRange] }); const { domain, range } = sqrtScale; expect(domain).toEqual([0, 10]); expect(range).toEqual([minRange, maxRange]); @@ -149,11 +149,8 @@ describe('Scale Test', () => { const maxRange = 120; const bandwidth = maxRange / 3; const linearScale = new ScaleContinuous( - ScaleType.Linear, - domainLinear, - [minRange, maxRange - bandwidth], // we currently limit the range like that a band linear scale - bandwidth, - 1, + { type: ScaleType.Linear, domain: domainLinear, range: [minRange, maxRange - bandwidth] }, // we currently limit the range like that a band linear scale + { bandwidth, minInterval: 1 }, ); const ordinalScale = new ScaleBand(domainOrdinal, [minRange, maxRange]); expect(ordinalScale.invertWithStep(0)).toBe(0); @@ -170,11 +167,12 @@ describe('Scale Test', () => { const maxRange = 100; const bandwidth = maxRange / 2; const linearScale = new ScaleContinuous( - ScaleType.Linear, - dataLinear, - [minRange, maxRange - bandwidth], // we currently limit the range like that a band linear scale - bandwidth, - 1, + { + type: ScaleType.Linear, + domain: dataLinear, + range: [minRange, maxRange - bandwidth], + }, // we currently limit the range like that a band linear scale + { bandwidth, minInterval: 1 }, ); const ordinalScale = new ScaleBand(dataOrdinal, [minRange, maxRange]); diff --git a/packages/osd-charts/stories/axis.tsx b/packages/osd-charts/stories/axis.tsx index e3730461dfec..c90b262974ed 100644 --- a/packages/osd-charts/stories/axis.tsx +++ b/packages/osd-charts/stories/axis.tsx @@ -17,7 +17,9 @@ import { Position, ScaleType, Settings, + niceTimeFormatter, } from '../src/'; +import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana'; function createThemeAction(title: string, min: number, max: number, value: number) { return number( @@ -55,9 +57,14 @@ function renderAxisWithOptions(position: Position, seriesGroup: string, show: bo storiesOf('Axis', module) .add('basic', () => { const customStyle = { - tickLabelPadding: number('Tick Label Padding', 0), + tickLabelPadding: number('Tick Label Padding', 0, { + range: true, + min: 2, + max: 30, + step: 1, + }), }; - + const data = KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 60); return ( @@ -65,8 +72,21 @@ storiesOf('Axis', module) id={getAxisId('bottom')} position={Position.Bottom} title={'Bottom axis'} - showOverlappingTicks={true} style={customStyle} + showOverlappingLabels={boolean('Bottom overlap labels', false, 'Bottom Axis')} + showOverlappingTicks={boolean('Bottom overlap ticks', true, 'Bottom Axis')} + ticks={number( + 'Number of ticks on bottom', + 10, + { + range: true, + min: 2, + max: 20, + step: 1, + }, + 'Bottom Axis', + )} + tickFormat={niceTimeFormatter([data[0][0], data[data.length - 1][0]])} /> Number(d).toFixed(2)} style={customStyle} + showOverlappingLabels={boolean('Left overlap labels', false, 'Left Axis')} + showOverlappingTicks={boolean('Left overlap ticks', true, 'Left Axis')} + ticks={number( + 'Number of ticks on left', + 10, + { + range: true, + min: 2, + max: 20, + step: 1, + }, + 'Left Axis', + )} /> );