From f784b2b1752b3ce3fc6e883f6f6391dead793c1a Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 3 Mar 2020 10:31:22 -0800 Subject: [PATCH 01/25] fix: add helper function to remove duplicate tickLabels from axis --- .../xy_chart/utils/axis_utils.test.ts | 34 +++++++++++++++++++ src/chart_types/xy_chart/utils/axis_utils.ts | 24 +++++++++---- 2 files changed, 51 insertions(+), 7 deletions(-) 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 2fdab9840a..038a0d7b8c 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -30,6 +30,7 @@ import { getAxisTickLabelPadding, isVerticalGrid, isHorizontalGrid, + removeDupeTickLabels, } from './axis_utils'; import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { SvgTextBBoxCalculator } from '../../../utils/bbox/svg_text_bbox_calculator'; @@ -37,6 +38,7 @@ import { niceTimeFormatter } from '../../../utils/data/formatters'; import { mergeYCustomDomainsByGroupId } from '../state/selectors/merge_y_custom_domains'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; +import { DateTime } from 'luxon'; describe('Axis computational utils', () => { const mockedRect = { @@ -523,6 +525,38 @@ describe('Axis computational utils', () => { ]; expect(visibleOverlappingTicksAndLabels).toEqual(expectedVisibleOverlappingTicksAndLabels); }); + test('should remove duplicate tick labels when tick values repeat', () => { + const tickValues = [ + 1546329600000, + 1546329600000, + 1546329600000, + 1546329600000, + 1546416000000, + 1546502400000, + 1546588800000, + 1546675200000, + 1546761600000, + 1546848000000, + 1546934400000, + 1547020800000, + ]; + const tickLabels = [ + '2019-01-01 00:00:00', + '2019-01-02 00:00:00', + '2019-01-03 00:00:00', + '2019-01-04 00:00:00', + '2019-01-05 00:00:00', + '2019-01-06 00:00:00', + '2019-01-07 00:00:00', + '2019-01-08 00:00:00', + '2019-01-09 00:00:00', + ]; + const tickFormat = (d: number) => { + return DateTime.fromMillis(d).toFormat('yyyy-MM-dd HH:mm:ss'); + }; + + expect(removeDupeTickLabels(tickValues, tickFormat)).toEqual(tickLabels); + }); test('should compute min max range for on 0 deg bottom', () => { const minMax = getMinMaxRange(Position.Bottom, 0, { width: 100, diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index b514d2eb59..c5ff8cfe2c 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -69,7 +69,6 @@ export function computeAxisTicksDimensions( if (axisSpec.hide) { return null; } - const scale = getScaleForAxisSpec( axisSpec, xDomain, @@ -207,7 +206,7 @@ export const getMaxBboxDimensions = ( }; }; -function computeTickDimensions( +export function computeTickDimensions( scale: Scale, tickFormat: TickFormatter, bboxCalculator: BBoxCalculator, @@ -217,10 +216,7 @@ function computeTickDimensions( tickFormatOptions?: TickFormatterOptions, ) { const tickValues = scale.ticks(); - const tickLabels = tickValues.map((d) => { - return tickFormat(d, tickFormatOptions); - }); - + const tickLabels = removeDupeTickLabels(tickValues, tickFormat, tickFormatOptions); const { tickLabelStyle: { fontFamily, fontSize }, } = axisConfig; @@ -234,7 +230,6 @@ function computeTickDimensions( getMaxBboxDimensions(bboxCalculator, fontSize, fontFamily, tickLabelRotation, tickLabelPadding), { maxLabelBboxWidth: 0, maxLabelBboxHeight: 0, maxLabelTextWidth: 0, maxLabelTextHeight: 0 }, ); - return { tickValues, tickLabels, @@ -245,6 +240,21 @@ function computeTickDimensions( }; } +export function removeDupeTickLabels( + tickValues: Array, + tickFormat: TickFormatter, + tickFormatOptions?: TickFormatterOptions, +) { + return ( + tickValues + // @ts-ignore + .filter((value: any, index: number) => tickValues.indexOf(value) === index) + .map((d: any) => { + return tickFormat(d, tickFormatOptions); + }) + ); +} + /** * Gets the computed x/y coordinates & alignment properties for an axis tick label. * @param isVerticalAxis if the axis is vertical (in contrast to horizontal) From e71ac1874483e7d266ca4e98b3dc4d0306f33848 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 3 Mar 2020 14:08:14 -0800 Subject: [PATCH 02/25] test: set utc time for test --- .../xy_chart/utils/axis_utils.test.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) 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 038a0d7b8c..06d9b340f6 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -38,7 +38,7 @@ import { niceTimeFormatter } from '../../../utils/data/formatters'; import { mergeYCustomDomainsByGroupId } from '../state/selectors/merge_y_custom_domains'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; -import { DateTime } from 'luxon'; +import { DateTime, Settings } from 'luxon'; describe('Axis computational utils', () => { const mockedRect = { @@ -541,17 +541,18 @@ describe('Axis computational utils', () => { 1547020800000, ]; const tickLabels = [ - '2019-01-01 00:00:00', - '2019-01-02 00:00:00', - '2019-01-03 00:00:00', - '2019-01-04 00:00:00', - '2019-01-05 00:00:00', - '2019-01-06 00:00:00', - '2019-01-07 00:00:00', - '2019-01-08 00:00:00', - '2019-01-09 00:00:00', + '2019-01-01 08:00:00', + '2019-01-02 08:00:00', + '2019-01-03 08:00:00', + '2019-01-04 08:00:00', + '2019-01-05 08:00:00', + '2019-01-06 08:00:00', + '2019-01-07 08:00:00', + '2019-01-08 08:00:00', + '2019-01-09 08:00:00', ]; const tickFormat = (d: number) => { + Settings.defaultZoneName = 'utc'; return DateTime.fromMillis(d).toFormat('yyyy-MM-dd HH:mm:ss'); }; From f4eafa5670026aaa7d256312763648ae86b80104 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 4 Mar 2020 07:08:30 -0800 Subject: [PATCH 03/25] refactor: clean up removeDupeTickLabels --- src/chart_types/xy_chart/utils/axis_utils.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index c5ff8cfe2c..283fcdc53b 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -245,14 +245,11 @@ export function removeDupeTickLabels( tickFormat: TickFormatter, tickFormatOptions?: TickFormatterOptions, ) { - return ( - tickValues - // @ts-ignore - .filter((value: any, index: number) => tickValues.indexOf(value) === index) - .map((d: any) => { - return tickFormat(d, tickFormatOptions); - }) - ); + return tickValues + .filter((value: number | string, index: number) => tickValues.indexOf(value) === index) + .map((d) => { + return tickFormat(d, tickFormatOptions); + }); } /** From f7a1aa0c2bee1ff7b8e337dc1c3ccb4a779d8c70 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 4 Mar 2020 13:20:39 -0800 Subject: [PATCH 04/25] refactor: add filter after formatting --- .../xy_chart/utils/axis_utils.test.ts | 70 +++++++++---------- src/chart_types/xy_chart/utils/axis_utils.ts | 23 +++--- src/chart_types/xy_chart/utils/scales.ts | 2 + src/chart_types/xy_chart/utils/specs.ts | 3 + 4 files changed, 49 insertions(+), 49 deletions(-) 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 06d9b340f6..9631ab780b 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -30,7 +30,6 @@ import { getAxisTickLabelPadding, isVerticalGrid, isHorizontalGrid, - removeDupeTickLabels, } from './axis_utils'; import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { SvgTextBBoxCalculator } from '../../../utils/bbox/svg_text_bbox_calculator'; @@ -38,7 +37,6 @@ import { niceTimeFormatter } from '../../../utils/data/formatters'; import { mergeYCustomDomainsByGroupId } from '../state/selectors/merge_y_custom_domains'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; -import { DateTime, Settings } from 'luxon'; describe('Axis computational utils', () => { const mockedRect = { @@ -186,7 +184,7 @@ describe('Axis computational utils', () => { expect(axisDimensions).toEqual(axis1Dims); const computeScalelessSpec = () => { - computeAxisTicksDimensions(ungroupedAxisSpec, xDomain, [yDomain], 1, bboxCalculator, 0, axes); + computeAxisTicksDimensions(ungroupedAxisSpec, xDomain, [yDomain], 1, bboxCalculator, 0, axes, undefined, false); }; const ungroupedAxisSpec = { ...verticalAxisSpec, groupId: 'foo' }; @@ -525,39 +523,39 @@ describe('Axis computational utils', () => { ]; expect(visibleOverlappingTicksAndLabels).toEqual(expectedVisibleOverlappingTicksAndLabels); }); - test('should remove duplicate tick labels when tick values repeat', () => { - const tickValues = [ - 1546329600000, - 1546329600000, - 1546329600000, - 1546329600000, - 1546416000000, - 1546502400000, - 1546588800000, - 1546675200000, - 1546761600000, - 1546848000000, - 1546934400000, - 1547020800000, - ]; - const tickLabels = [ - '2019-01-01 08:00:00', - '2019-01-02 08:00:00', - '2019-01-03 08:00:00', - '2019-01-04 08:00:00', - '2019-01-05 08:00:00', - '2019-01-06 08:00:00', - '2019-01-07 08:00:00', - '2019-01-08 08:00:00', - '2019-01-09 08:00:00', - ]; - const tickFormat = (d: number) => { - Settings.defaultZoneName = 'utc'; - return DateTime.fromMillis(d).toFormat('yyyy-MM-dd HH:mm:ss'); - }; - - expect(removeDupeTickLabels(tickValues, tickFormat)).toEqual(tickLabels); - }); + // test('should remove duplicate tick labels when tick values repeat', () => { + // const tickValues = [ + // 1546329600000, + // 1546329600000, + // 1546329600000, + // 1546329600000, + // 1546416000000, + // 1546502400000, + // 1546588800000, + // 1546675200000, + // 1546761600000, + // 1546848000000, + // 1546934400000, + // 1547020800000, + // ]; + // const tickLabels = [ + // '2019-01-01 08:00:00', + // '2019-01-02 08:00:00', + // '2019-01-03 08:00:00', + // '2019-01-04 08:00:00', + // '2019-01-05 08:00:00', + // '2019-01-06 08:00:00', + // '2019-01-07 08:00:00', + // '2019-01-08 08:00:00', + // '2019-01-09 08:00:00', + // ]; + // const tickFormat = (d: number) => { + // Settings.defaultZoneName = 'utc'; + // return DateTime.fromMillis(d).toFormat('yyyy-MM-dd HH:mm:ss'); + // }; + + // expect(removeDupeTickLabels(tickValues, tickFormat)).toEqual(tickLabels); + // }); test('should compute min max range for on 0 deg bottom', () => { const minMax = getMinMaxRange(Position.Bottom, 0, { width: 100, diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 283fcdc53b..82787a1291 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -93,6 +93,7 @@ export function computeAxisTicksDimensions( axisConfig, tickLabelPadding, axisSpec.tickLabelRotation, + axisSpec.duplicateTicks, { timeZone: xDomain.timeZone, }, @@ -138,6 +139,7 @@ export function getScaleForAxisSpec( range, ticks: axisSpec.ticks, integersOnly: axisSpec.integersOnly, + duplicateTicks: axisSpec.duplicateTicks, }); if (yScales.has(axisSpec.groupId)) { return yScales.get(axisSpec.groupId)!; @@ -152,6 +154,7 @@ export function getScaleForAxisSpec( enableHistogramMode, ticks: axisSpec.ticks, integersOnly: axisSpec.integersOnly, + duplicateTicks: axisSpec.duplicateTicks, }); } } @@ -213,10 +216,16 @@ export function computeTickDimensions( axisConfig: AxisConfig, tickLabelPadding: number, tickLabelRotation = 0, + duplicateTicks: boolean = false, tickFormatOptions?: TickFormatterOptions, ) { const tickValues = scale.ticks(); - const tickLabels = removeDupeTickLabels(tickValues, tickFormat, tickFormatOptions); + const duplicateTickLabels = tickValues.map((d) => { + return tickFormat(d, tickFormatOptions); + }); + const tickLabels = duplicateTicks + ? duplicateTickLabels + : duplicateTickLabels.filter((value, index) => duplicateTickLabels.indexOf(value) === index); const { tickLabelStyle: { fontFamily, fontSize }, } = axisConfig; @@ -240,18 +249,6 @@ export function computeTickDimensions( }; } -export function removeDupeTickLabels( - tickValues: Array, - tickFormat: TickFormatter, - tickFormatOptions?: TickFormatterOptions, -) { - return tickValues - .filter((value: number | string, index: number) => tickValues.indexOf(value) === index) - .map((d) => { - return tickFormat(d, tickFormatOptions); - }); -} - /** * Gets the computed x/y coordinates & alignment properties for an axis tick label. * @param isVerticalAxis if the axis is vertical (in contrast to horizontal) diff --git a/src/chart_types/xy_chart/utils/scales.ts b/src/chart_types/xy_chart/utils/scales.ts index 78ff50fd3f..c0a2a3b86b 100644 --- a/src/chart_types/xy_chart/utils/scales.ts +++ b/src/chart_types/xy_chart/utils/scales.ts @@ -60,6 +60,7 @@ interface XScaleOptions { enableHistogramMode?: boolean; ticks?: number; integersOnly?: boolean; + duplicateTicks?: boolean; } /** @@ -122,6 +123,7 @@ interface YScaleOptions { range: [number, number]; ticks?: number; integersOnly?: boolean; + duplicateTicks?: boolean; } /** * Compute the y scales, one per groupId for the y axis. diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 97008027ec..b4f4dcc46f 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -507,11 +507,14 @@ export interface AxisSpec extends Spec { style?: AxisStyle; /** Show only integar values **/ integersOnly?: boolean; + /** Remove duplicate ticks, default is false*/ + duplicateTicks?: boolean; } export type TickFormatterOptions = { timeZone?: string; }; + export type TickFormatter = (value: any, options?: TickFormatterOptions) => string; export interface AxisStyle { From a0c8839ffe69281d603b7e6060eacd6d56bc5107 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 5 Mar 2020 15:09:51 -0800 Subject: [PATCH 05/25] feat: adding duplicate ticks story --- src/chart_types/xy_chart/utils/axis_utils.ts | 1 - src/chart_types/xy_chart/utils/scales.ts | 16 ++++++-- src/scales/scale_continuous.ts | 3 ++ stories/line/10_duplicate_ticks.tsx | 40 ++++++++++++++++++++ stories/line/line.stories.tsx | 1 + 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 stories/line/10_duplicate_ticks.tsx diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 82787a1291..d4a47c7b74 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -229,7 +229,6 @@ export function computeTickDimensions( const { tickLabelStyle: { fontFamily, fontSize }, } = axisConfig; - const { maxLabelBboxWidth, maxLabelBboxHeight, diff --git a/src/chart_types/xy_chart/utils/scales.ts b/src/chart_types/xy_chart/utils/scales.ts index c0a2a3b86b..b7d95badcf 100644 --- a/src/chart_types/xy_chart/utils/scales.ts +++ b/src/chart_types/xy_chart/utils/scales.ts @@ -70,7 +70,16 @@ interface XScaleOptions { * @param axisLength the length of the x axis */ export function computeXScale(options: XScaleOptions): Scale { - const { xDomain, totalBarsInCluster, range, barsPadding, enableHistogramMode, ticks, integersOnly } = options; + const { + xDomain, + totalBarsInCluster, + range, + barsPadding, + enableHistogramMode, + ticks, + integersOnly, + duplicateTicks, + } = options; const { scaleType, minInterval, domain, isBandScale, timeZone } = xDomain; const rangeDiff = Math.abs(range[1] - range[0]); const isInverse = range[1] < range[0]; @@ -112,7 +121,7 @@ export function computeXScale(options: XScaleOptions): Scale { } else { return new ScaleContinuous( { type: scaleType, domain, range }, - { bandwidth: 0, minInterval, timeZone, totalBarsInCluster, barsPadding, ticks, integersOnly }, + { bandwidth: 0, minInterval, timeZone, totalBarsInCluster, barsPadding, ticks, integersOnly, duplicateTicks }, ); } } @@ -132,7 +141,7 @@ interface YScaleOptions { */ export function computeYScales(options: YScaleOptions): Map { const yScales: Map = new Map(); - const { yDomains, range, ticks, integersOnly } = options; + const { yDomains, range, ticks, integersOnly, duplicateTicks } = options; yDomains.forEach(({ scaleType: type, domain, groupId }) => { const yScale = new ScaleContinuous( { @@ -143,6 +152,7 @@ export function computeYScales(options: YScaleOptions): Map { { ticks, integersOnly, + duplicateTicks, }, ); yScales.set(groupId, yScale); diff --git a/src/scales/scale_continuous.ts b/src/scales/scale_continuous.ts index 9e7108c466..28f8526866 100644 --- a/src/scales/scale_continuous.ts +++ b/src/scales/scale_continuous.ts @@ -121,6 +121,8 @@ interface ScaleOptions { isSingleValueHistogram: boolean; /** Show only integer values **/ integersOnly?: boolean; + /** Show duplicate tick values default to false */ + duplicateTicks?: boolean; } const defaultScaleOptions: ScaleOptions = { bandwidth: 0, @@ -131,6 +133,7 @@ const defaultScaleOptions: ScaleOptions = { ticks: 10, isSingleValueHistogram: false, integersOnly: false, + duplicateTicks: false, }; export class ScaleContinuous implements Scale { readonly bandwidth: number; diff --git a/stories/line/10_duplicate_ticks.tsx b/stories/line/10_duplicate_ticks.tsx new file mode 100644 index 0000000000..a782381e23 --- /dev/null +++ b/stories/line/10_duplicate_ticks.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Axis, Chart, LineSeries, Position, ScaleType, niceTimeFormatter } from '../../src/'; +import { KIBANA_METRICS } from '../../src/utils/data_samples/test_dataset_kibana'; +import { boolean } from '@storybook/addon-knobs'; +import { DateTime } from 'luxon'; + +export const example = () => { + const now = DateTime.fromISO('2019-01-11T00:00:00.000') + .setZone('utc+1') + .toMillis(); + const oneDay = 1000 * 60 * 60 * 24; + const formatter = niceTimeFormatter([now, now + oneDay * 10]); + const duplicateTicksInAxis = boolean('Hide duplicate ticks in x axis', false); + return ( + + + `${Number(d).toFixed(1)}`} + /> + + + ); +}; diff --git a/stories/line/line.stories.tsx b/stories/line/line.stories.tsx index 970c206e38..6b242cd9b6 100644 --- a/stories/line/line.stories.tsx +++ b/stories/line/line.stories.tsx @@ -16,3 +16,4 @@ export { example as curvedWithAxisAndLegend } from './6_curved'; export { example as multipleWithAxisAndLegend } from './7_multiple'; export { example as stackedWithAxisAndLegend } from './8_stacked'; export { example as multiSeriesWithLogValues } from './9_multi_series'; +export { example as duplicateTicksDates } from './10_duplicate_ticks'; From d6fb645785e072b81555dcb8e782fe5cd676fe7c Mon Sep 17 00:00:00 2001 From: rshen91 Date: Fri, 6 Mar 2020 04:50:01 -0800 Subject: [PATCH 06/25] test: add missing snapshot --- ...icks-dates-visually-looks-correct-1-snap.png | Bin 0 -> 17572 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-line-chart-duplicate-ticks-dates-visually-looks-correct-1-snap.png diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-line-chart-duplicate-ticks-dates-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-line-chart-duplicate-ticks-dates-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..c2637c23cf35e325b9f669662215954ed2d4417a GIT binary patch literal 17572 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ΤHs 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 OWTh1E72Yv;@!tSoA3MJQ literal 0 HcmV?d00001 From d5abfd7675e0d057a3a0b4dd69f3bd533bccbace Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 9 Mar 2020 07:53:20 -0600 Subject: [PATCH 07/25] fix: add license heading to story file --- stories/line/10_duplicate_ticks.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/stories/line/10_duplicate_ticks.tsx b/stories/line/10_duplicate_ticks.tsx index a782381e23..578d1e299e 100644 --- a/stories/line/10_duplicate_ticks.tsx +++ b/stories/line/10_duplicate_ticks.tsx @@ -1,3 +1,21 @@ +/* + * 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 React from 'react'; import { Axis, Chart, LineSeries, Position, ScaleType, niceTimeFormatter } from '../../src/'; import { KIBANA_METRICS } from '../../src/utils/data_samples/test_dataset_kibana'; From 591c603a5f3afb14831c1c7ccd7426ee66309afb Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 9 Mar 2020 08:02:19 -0600 Subject: [PATCH 08/25] docs: correct x axis format for duplicates --- stories/line/10_duplicate_ticks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/line/10_duplicate_ticks.tsx b/stories/line/10_duplicate_ticks.tsx index 578d1e299e..991468110f 100644 --- a/stories/line/10_duplicate_ticks.tsx +++ b/stories/line/10_duplicate_ticks.tsx @@ -27,7 +27,7 @@ export const example = () => { .setZone('utc+1') .toMillis(); const oneDay = 1000 * 60 * 60 * 24; - const formatter = niceTimeFormatter([now, now + oneDay * 10]); + const formatter = niceTimeFormatter([now, now + oneDay * 31]); const duplicateTicksInAxis = boolean('Hide duplicate ticks in x axis', false); return ( From dc9a38fe6453d8720b67796ecf177a3481e09858 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Mon, 9 Mar 2020 08:35:20 -0600 Subject: [PATCH 09/25] test: update snapshot for x axis --- ...ks-dates-visually-looks-correct-1-snap.png | Bin 17572 -> 17677 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-line-chart-duplicate-ticks-dates-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-line-chart-duplicate-ticks-dates-visually-looks-correct-1-snap.png index c2637c23cf35e325b9f669662215954ed2d4417a..3e67a94d96989b58da8b71af4a50b4a1fa024d8d 100644 GIT binary patch literal 17677 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 z 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ΤHs z+vBOmWF9{36b#GB&wnb&fa+Yzy?}S_ASyY{HZTE{@csH-x7%kWNZ)L<-VM^Jin8)M z7LJP-Z}Haux)ayL1?41Agh;lQmk+k7nWB>IetzV{BTdch$7LoQ?CkfUd>XJU!dwaZ zoC4a=_vgpWbs?ke} zi`L)^x7Y~#HwwDbjs(o9^H3b?3|5bH$}y+f(VwX+s%)&`NBH8Qo@EzXRbC}tFP~rc zkpsX{ht+j(a1ij>^Mo1dZd3#ZpAL9+7?oV6<-L&H-v4Os7&y8eC6!0xxRMVsytK5` zO0-UsOF?KHDyQ#HA^2YR2<(_;lG(sFV%ZLCF{siz&G-8&H` zx%A5penAyUaE>h-S1pKdRu4iIwk@Icl$BGu(qvk(SQ(Wbsq{3Nuzql6{9jKafa^HS zqVtYRM>+np`fH`+$dG;JEgyQxL5O_u!MF(?^dnu}0wB=mC%=Z{d$Pi8s>l5m zRXZV?zOoHevg~^2MYI|chmil{)dJ5PpdWX9PF6&8wCT3avuD+#?(^J!uU-iYKylnY z>*wAe2<+*dI_4Q(yJ>vZ0J91!(0ED83 zT40^HI##5b){ymivg7}BqS`|v}>grJvyX&fy6cp?N0*_pA&e240BgU=M zw;}A8_tp?aLbfNDhdN-z^w|owCQe;HKL@Eje3(3w=+S=m{b?k_m-ki!v1!_~tajgZ zuMPD>*hOD>D4w@*N992jm;Ij#hfqHR80EI)i}aFi-9GWdj_iQH5o{cNpDSzlu9k!4 z6Kt!n zwD}IB_WE9OB+UibrwKeKl{(~vLh|JgI=!Lyxvz@vlZg0|A>E!IS0zMh_4^2f*jBKG z$ipC01c_K&4^*_+>Uv3>=^aD>#%Cm>n@PLr7EIgdWY`XVJ?48zORYO7FwkjzR#{C= zO?P!m+otC8r4+z_Lz8Mu7EO!fvNj}S=y?%(@fT z5hxiMd6<}(0#1lH<0F8=nZo0T76yQwgPupG+uC%_U-a5TE35nw9F8IJ7^;&{U+x%m z?o+DZC6*D`np(cP2ku5jY0Y6wMU6XCykz8%1S;GK7Hx^*+Q1FjZp3{1CJ%FIZECv9 z$jBJ@`t?KbtK;O13%|rrzJ(vJewFI}nZKKBZq_;S-B4QQaOY0 zetdj6HND{@G`_q%mdOXz)?tt&*ND%i>FKQs>vgD+s+}p2y6pAzbhwV95*`Ctu|bj4FM9s`lu5~~%-zDC z_SEpJxrL9Ecxa9pAf|l$`2EkHKiBbjMJ)pZ8azBaWT(zCFciQ!@7h=)<*iTl*{<>d z6t4_u#=^paf{rd1Dhh6$>W5H@=H}+AKt(bA?S0SB?YX|Zfxq&jA0q(ZCS%jc_~-&4 zkBuXv*{kcS_Nr$%fz&8)pZQ>uP>_?Ee1z{<-2>uFa*6B;%Yl}*Hig8*#Km`W*H4hq zKQuHm<_%~V(;0xsZ?}Nusc2YT64%VMXTHAC_jVrqw_8!$8;J>HRBTsCKQ*UYQ z(|vHxgf^ZE6reo&n+8TITZlbt{p2PTTASEX~W9La~jcyDE1LqP|tZHsu z0SyAPUB4$14X!cgKBYzcL6u>*%-H6wl-74O=%qLRX;i4erkuN;ULp&U8?qD=9G*dS zw&0wduX$nBp>>;&$&4*F#-tGuQ?hJOl zTpx(bQ8#PyGz`zYdV5mgR2Z~% z*qfb+dH@&x(gJ8gNY<)2HUXYg{`Bj)1Ti-iFXDPv{7pvz$LVhyUrbLz{wQyM-hFwv z&MUE+ui0S*C!y=sbP;8C4P1VExv>ZiQ3!)*|3*!GJUt}5S^o<*cF%4xL^x{)AO zI9X56I%C?}7?%a*l!e^02x-JM3QRWTiTZo1)NSwiXVB;d&n*Y;$EB0G%hDvMS^7ZB zT&ujPuUmt$G~F7ovq+_hZ=xYZ3EvBP;12c7;xce0m0=eyT&Qqe)`NOSM1leJfr2c8 zYH)Ose_6*5#=9}TcfpD@3>y*bdG@qpo6iy72qw|Zz=iS?PJ zl1Id9hesD3pFOlqOFMKcYv5<(`t6s;Wzl4eV%fgFsADID+UNjXToo493=`kb+-5n7 za(ewU_A2X)G5l_AT4Gm#RZI?82j%;&RNSa!=0UIV*@eL}pn1}39A_#ut~8aP$&&NF zaB-AkhE4ajNLXLVn78;bJf?Ya2sKe|LE0RI$K%UAHf^@Hw=I8scpm!Am|af9x7`XF z$HQ?X$J|{s?r272!ml(h8gZcvs9d7g5f}rQ%6%ELx!%OI_Yi{oTNd&CowZtk5<{#{ zWzy1*%*qGm=7Bvgtd==HFq4E=T?30uKi#AXrlqBg`k?^$EL}m=2Im#M!j4 zgF|9kwf~9cfrDNp$yH9oQ@4$Nt}nG!A-`7q07zFwB^4@6kU%^@BdB|EuuNuuyK#RW zu&!^^0}kQBNm7sK&3eA()w+@@WET{_ETNaCrZT*?H|9WgfKHEzp)r2?G+VpMhK&3K zs=D+Oz6B13!t%yVDBK`=t$*vhMq+}T=2$q9-(s!}6&t=x&$OoX*jRu9hRZJ?Kvaw# z^+RP_j>LL-!!36;-a6QLyse5BPVtXhh=KH;-3FWo0lwR76UPRT1$H&ZXMYa?owfB5 zFDA5-T;;!R-NY}>WqdV!M{N48Ef4{LXtsP|6gO2fli0I9*W=uyAoR7VDGlR58;H|I z(aeEfpTf1K7+NsY|3J4M^EI8;MS+nsqm)DA)&`ne7>;4&w-Rf^=@Sidqm5=-roD@!$*!F zb+1B_gf6t$7SEM416AYn>de;7O6zkTuAAz2o<%?;IOubCp`eB)h=!_bXmE;)>pqC) z=^N1ZvEJTEzWs8&`vEV6uV%wbqI9ebw>*SZb2dbEvU&*K+Ts$`KsxL`u zf7Ba=S#)zwuyIKEg7I&)LI4%A9%6xTsE{C{225P{mv_v(TSVxN77Yyzsrx-HdEy6W zlU)|6Cor3%oV-P?KF8w!I|)$26Ea5MH4#;3J=kDmpFxy>WHwdste)`+2%xC)Kq!2m z+V25!Z*pQ{;ujw!B_+NqEr4ZitwS>UFV>IeCc-I`QuFlkcZj9o@fO^g#kVB)w>qXE ztrXKk7^#l3?MN)Ke)lZ1R^^l-q$0)w*_7R2*w@*k3wpARU=S;xj@DLH0A8tSX;X7^ zgVTJ}q_uq(YCsxrQMDRlpPHBeIim+q4_c`Y@7;R^1**WaUq5b)NuqQVdaT@fLb)#K zNyMIOhd`L@RbHLW`?&NT9QaOTQVm|)_{FNR{;|CcAz77&58v+1`0UBNJbuQ)G1<5^ z3Q_z#fBxKkX9Wu?roYgi71Q?ib{@!T+BQik)HU!`b(OiLg^HWl)8n5Sc+v|?lpAr8 zxvJo%m9E*0Oagb%h>`+47Y+@z%ab>~9sXgeyv>>@)MEB$RK+G zb&5@gJ?1llxeHxd|z=4NS}+e`w|9x&-hEI4w4OqeSIHWi!+ur|I`uJ(Uo zWEKHN{s$uyb;7?#+v8_;^ZMz&_&s}cI@^Rao^81QYipbRfXIhU8EH;;LcwAjodgc?||C+wJb?XDb)7)GOf|2i{&%ZoA{rWb zJ{KhP;5=!?N9M2{59?jr)!I99F@7NJg2r6Xc^N^BtEN1Vd*^mjrK7Y6(A9%15%s;+RaGUMbI+p z#fujL-aF4Q8$*_*np|>na;280(bGKAy9fpk?s8JmR+<{(*;{jRRK25_zUS3@h7MO2 z*4;tDZjQr(?y5Jv`2H^6l1L2=0}aEImC5(+c1sas2y0HrF&QNGwC&)mdaAVTca@Viw3DdCXHxUV~!0Cilf0bPO@yA9XVmq6IIc`-s-t5hBw&YL>7lC9;BqC)Gjw{PtMKd0B>YH zR4yCNrqr+y5)x0zimMR}DG{Jt%U?aGKo-;;L2CPXwBT5y>>(&OV{x`7y_65T6FT}; zYbyIzmu!~gsvmwUnx}o@Bme@KI_2)T+pPZeJSyy;Vd(MSp$BKnccbnr(d4~639?P7oh2>TanJh&TTPtR6Do+BG3NY`=Ft;J z;wNSK7cvDK(t!=_n$dbXsu@td6m;0ai{4!Fic@cGN~7q;xiKOtT6o4G3Z3{^rMFz+ ziFf5{yBhm}-@tnRve66(()`~)AU404s1%|kSzj212Q?`U7LjlgW@&PD4ni!rGz@|} zH`K9_dFNmWPKsbZ8X!iaDa`MBSN6{s=OveZs?*hV?0eW9*L9Y4_a{y3=O^Cte%GQv;dTrufKZ(ou&-TutX}(mI8Au< z{3LuE^cg5cRP}2ReaJ+u{~Kg;HWZ5D7+0oifsBYk6@GZc>NVG$A>zKC4MjGz2L&wq zxu7`E`=5ItqbWCsRJuv?IIR&}>?iEug%1-+hwzG zEPK|FMN-X^8?l+285B{CA@15qMU{5Ma{&50&ctHGj1{ znE+&K?C@8c7OSi}T4BAtrqdgkgwQWQKkiFr9Lr4u1tK9xf9DQ7S7}Wx6HJo={GK(g zR_*WP#bOT7Z>Z8b4*bRBTTF^!lmAUFY@SCCVSF5e4hmwV`=e;aKcsO(2z&xX_B0l4 zbH$C-6vV}pXK9`SK@36qc4nNc(92uoN&$Lr5h?!nrbqnH$wiJJ{Kxm89r=HzasTUw zUlG>x|K$%SOq_g|fk#x+(u!?Q6wgCyO~l}0HvgSoLsXaprrGHY5)Vco+}IMI-DzlO z&=C7K8AhV#s%(Kyjf%6gGegWdc-l-KWbKBAh9oCXR^zYzt4|1=@5wqrCTDlunF?SswsjIhGROs20-iJ3Oi@(oJ zk2`j7wL#h;5D@{Ok~#km?mzs0;v4_vRSg^I6Tb`LHUYO@y(UzWNP)JyIfiV3?tc=A zeapcHx3>BE z8yj0%TSc$1z_Xo)_E<CgFWWHty`K6#Yt{vW@f9z zX8fk`vUA@|B1}QN?7Et45}P5LqZ*-pzGGfmgJeLTjw$dgmjgc1W2+G zk_9MbClGDt0pH<*vw@C|_g1UE#p>kO(9i`w(1sCzyd+;AG)>|F=J4akkLdKkKoZRU z-ZpWetOG(x3h+@C_)1CW7lzOYwse&+Ov`nwKv1awLKehv_z%yI;Kv#pp_X2(T@)Jg zRE3v-<64HzTDxS-RD*Jf$@AB9_(4{Axj;>IHT&hup9+Uv{0H_T6SosYUGY#nJp#_u zWqGs!SY?oD4LJ|DdyZ-Yfuf$TnF3-baP0?B8b{RCc?8)G&?atkCT#NLf((Oc@7D<+ zqCkexyfp06zcwm$f}F88uc#pxbO^_eA4lHgJ{q1rwlgh+q)`=BRix^^U{n3^Kc;{* zlyRJDN|+Zz3#VC2BVBwwwyB=>s}9bYN+SI24@@Y&F!EUwftUZiWgkLs`B1 zle*EY==EaD*^XVH+wx_@uT;p?yG*@a(*vV{Nd$pih3C{BJu1H%q#C`BROyH~4(K%? zlaQW>92#jj!|o%xekm!b1M2bVmp{A+eHzF{MsTRI$RY$5&|FCC%XQ340}j81nOP^C zJ7<89IjO04kme?Is*8U9d;-_3I6?IRJ^Qru%%oLMPfsf*eG16$z>c)8R!(PS=hdi8 zgR)&4E|Oj4;6QF@F?#z z9RAcEg>wq4nQIQEs3yfLmmb%*}I_Fq?XWg+W!MT?7E1l7WF+wsPPO#4!*}7Q!t! zFn(LC;Dhn;aYSXau`qZLrNP>Ob6rR51Mb|t%PLr4(-wbI75XP7#+B)LlQR@^I*n#AOK17Tz!KeB-!pvg}BM-=}d@+<+C+R5}wul zg}NSX$U*k7wY41{130Tqh-qvcZ1({D<=ftMa{aq^?*{FSH`1AkEXyj)knr;If-WNm z!nHye4jx078#W!s*)^RRDlw9YW2~FhnGu(9tXxY>yE*K}3$i70v0uI;Be51PmOkev zds)4H{`n?W!mAc;;6!n2XMzpQK&FI_-MbbK8`chLNo31Gp;?2s7XS6(2xLGNNQtAK zYYM|F;CJqm?__#GiUDNrEHp$rLxe`zp{{^qH;a|rv#*m>^x%gMmD#6PrvUqPwnTDP z`0RV1VUpMhtU7=Gd>R07S@iYm*UO#f)gh}WpdSGj7QpC!U!~{!C&+Sw;L}K-0aT}$ z$^CqWIg9s;UQ0zABFNjIKM2U2Ft)!tCgQVK5AKDKjL`L|y6Pb0$5@-rAB(Y-*wQO|LvUCrIyU7K-3 zkUwsv-W51Fq(Rq{rQ~{F*0pj?4?^GGZ>(fXU!NLsPMbq64B)X#!7~`-mMG9=>Skuy zOVtx(!%q|}g?rpOEGlx-)9)clGBoRhNx1O|C+R9X`sHisRT#;+yL&iEP~PNhn5dj zUUIy3%a>(P&q@O_XknP-o+bd9+@hk!Nw$XX-!+HFu)(xxZE-@DU*2A7`SPU``eeg1 z&nrIkT_4^a(O-0SvOhKgw|LW}L%Iuf>jd1_%@Ty|?;znFSJMd15yZXVc5Uglhj#2% z-M*P_9t|j(XW?e7D(KaKPG`-#4Sl|pO;!|EYu%Uu?}r_2}DwJ&{x$q=D6 zD+t#~-JU)BzPro)m7bE)cv~jjv?CEw7Xuet^h}%EZ7HL&aRUnqjg<+koDymsp(7j@%# zp*F8Gu6*|_fo5Yqy*ftAC=0n&IFG!0l!01JhEoH?O(72n3Z7QmM) z^-J_UcaZ+-<7VE+!uU}W;RL}1$F~UBDviRJ&?osl!z``gb zI%^fB1+iJm(uup5kNUuvb#<0-Wz1!NNCGGi zZMu!*^yyyl%@Jk@9s2vkfy&{nCC6pB>V4?Qad!AA!d4@4PK{|eD!UU*`<<^&o#)}W zRbXH7tqrpg=dpwv8Ch^&Vjv7LflU#7AqawZyx@;-GvXjje8((f0jkLYo;k33Yg=0% zoa#srg+Z>7Re_7mPV)s-mG~9W9=D>B5@iUeTGe(bEOT%Z59x3$HST*8mdD1H4$fMT zq`>jx$KhsOp#t@7kzuzEEEc=5iAYSLT&&!_rax(QFeV%rGg$n=oofpKjV=;+(yG4P ze+LdTw&^hmB3>oWYjiktulZ{HC;JhE@@r zNOe%Hq);Co4BtwHCiU=yq{HOv#WnEn$|OdRocZ(((+FBbNDyb4@A}+&x6sO^yH!&) z;|zzNQ(s@-`ozXbR}|`~H1h8Pd@ugEL>US4;R4+{b%;F`9vaa;eX#@H;HrzMnMT;q z!MzgVk`J~ifIP3Ta4E3r8sIXdtUr79tV==q4d`KXrAi^1iMx728&9KRO;TH1THtsf z831&Ua;5u?Mwa^9V9(0^QP{)Cq$K@3pZku@YkIJZ^vr>e0L%L7Ty5(b_UquxY<@Qi zGvC|ULb^AI)Ku4Jmr%enyI~R)EPjBS@EVP!;(Oqbpd!dpJBAzmMosHB3gXgk=(w{8 z?)o}_#cyz1L9PQ)@fUS8|Mlxb>)#Vr>V+VW3~xqm0N3jY$3bTY`hn$|y6OsuryZvEIeYRkWgGdA`47$1+>Kn%iDT{%%*7@?d2_ zT-*hajj8QnOG>USE-!P&wdIzTtqsII^zmsN8XnfQ?X|fHx^49vaeX*k0{Uw@i@Ax( zbkX*4pI8r!QB=dIPH1qjnWg0eUFhUZkYD(^Yil{iy$Mx%Aaj5DA*o1@vw^3um zLJv4|SE+(5%+0%g{PQ=;So{V4?Ko%Oj4?&Qoq9(b3dp)zxNl8R(gkRz@bK zww9JadwT5JcV)fpUd-(DC=H&`nK#x_67O8ZC1BglfbHt;Mnf3cb5pWRBk_CjbEVqf z-`~O=ZJD{Y=BTTur`~>HqiK9R#i1sCdU{$c344>yH7*BJr0ke OWTh1E72Yv;@!tSoA3MJQ From 60664b976e2f2595738fcceb202f4388831b2ed0 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 10 Mar 2020 09:57:31 -0600 Subject: [PATCH 10/25] feat: implement duplicate tick functionality --- src/chart_types/xy_chart/state/utils.ts | 9 ++++- src/chart_types/xy_chart/utils/axis_utils.ts | 40 ++++++++++++++++---- stories/line/10_duplicate_ticks.tsx | 2 +- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/chart_types/xy_chart/state/utils.ts b/src/chart_types/xy_chart/state/utils.ts index a6d23e07d7..383f242d10 100644 --- a/src/chart_types/xy_chart/state/utils.ts +++ b/src/chart_types/xy_chart/state/utils.ts @@ -295,9 +295,14 @@ export function computeSeriesGeometries( // compute how many series are clustered const { stackedBarsInCluster, totalBarsInCluster } = countBarsInCluster(stacked, nonStacked); - // compute scales - const xScale = computeXScale({ xDomain, totalBarsInCluster, range: [0, width], barsPadding, enableHistogramMode }); + const xScale = computeXScale({ + xDomain, + totalBarsInCluster, + range: [0, width], + barsPadding, + enableHistogramMode, + }); const yScales = computeYScales({ yDomains: yDomain, range: [height, 0] }); // compute colors diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index b269dc68ac..4459a89d74 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -111,7 +111,6 @@ export function computeAxisTicksDimensions( axisConfig, tickLabelPadding, axisSpec.tickLabelRotation, - axisSpec.duplicateTicks, { timeZone: xDomain.timeZone, }, @@ -234,16 +233,13 @@ export function computeTickDimensions( axisConfig: AxisConfig, tickLabelPadding: number, tickLabelRotation = 0, - duplicateTicks: boolean = false, + // duplicateTicks: boolean = false, tickFormatOptions?: TickFormatterOptions, ) { const tickValues = scale.ticks(); - const duplicateTickLabels = tickValues.map((d) => { + const tickLabels = tickValues.map((d) => { return tickFormat(d, tickFormatOptions); }); - const tickLabels = duplicateTicks - ? duplicateTickLabels - : duplicateTickLabels.filter((value, index) => duplicateTickLabels.indexOf(value) === index); const { tickLabelStyle: { fontFamily, fontSize }, } = axisConfig; @@ -447,14 +443,44 @@ export function getAvailableTicks( return [firstTick, lastTick]; } - return ticks.map((tick) => { + + // want to go through the distinct tick labels and populate a tick for each of them + return getDuplicateTicks(axisSpec, scale, offset, tickFormatOptions); +} +export function getDuplicateTicks( + axisSpec: AxisSpec, + scale: Scale, + offset: number, + tickFormatOptions?: TickFormatterOptions, +) { + const ticks = scale.ticks(); + const labels = [...new Set(ticks.map((tick) => axisSpec.tickFormat(tick, tickFormatOptions)))]; + const allTicks = ticks.map((tick) => { return { value: tick, label: axisSpec.tickFormat(tick, tickFormatOptions), position: scale.scale(tick) + offset, }; }); + + if (axisSpec.duplicateTicks === false) { + // eslint-disable-next-line prefer-const + let uniqueTickLabels: { value: any; label: string; position: number }[] = []; + allTicks.filter((value, index) => { + for (let i = 0; i < labels.length; i++) { + if (labels[i] === allTicks[index].label) { + uniqueTickLabels.push(allTicks[index]); + // once uniqueTickLabels has the unique label, remove the label from the labels array so it doesnt create a duplicate + labels.splice(i, 1); + } + } + }); + return uniqueTickLabels; + } else { + return allTicks; + } } + export function getVisibleTicks(allTicks: AxisTick[], axisSpec: AxisSpec, axisDim: AxisTicksDimensions): AxisTick[] { // We sort the ticks by position so that we can incrementally compute previousOccupiedSpace allTicks.sort((a: AxisTick, b: AxisTick) => a.position - b.position); diff --git a/stories/line/10_duplicate_ticks.tsx b/stories/line/10_duplicate_ticks.tsx index 991468110f..712f283716 100644 --- a/stories/line/10_duplicate_ticks.tsx +++ b/stories/line/10_duplicate_ticks.tsx @@ -28,7 +28,7 @@ export const example = () => { .toMillis(); const oneDay = 1000 * 60 * 60 * 24; const formatter = niceTimeFormatter([now, now + oneDay * 31]); - const duplicateTicksInAxis = boolean('Hide duplicate ticks in x axis', false); + const duplicateTicksInAxis = boolean('Show duplicate ticks in x axis', false); return ( From 935e64af5e2b5d64f28c6a1654b370769990540e Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 10 Mar 2020 11:39:27 -0600 Subject: [PATCH 11/25] style: clean up comments --- .../xy_chart/utils/axis_utils.test.ts | 33 ------------------- src/chart_types/xy_chart/utils/axis_utils.ts | 6 ++-- 2 files changed, 2 insertions(+), 37 deletions(-) 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 a8fe194e62..e8d6aea0b4 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -541,39 +541,6 @@ describe('Axis computational utils', () => { ]; expect(visibleOverlappingTicksAndLabels).toEqual(expectedVisibleOverlappingTicksAndLabels); }); - // test('should remove duplicate tick labels when tick values repeat', () => { - // const tickValues = [ - // 1546329600000, - // 1546329600000, - // 1546329600000, - // 1546329600000, - // 1546416000000, - // 1546502400000, - // 1546588800000, - // 1546675200000, - // 1546761600000, - // 1546848000000, - // 1546934400000, - // 1547020800000, - // ]; - // const tickLabels = [ - // '2019-01-01 08:00:00', - // '2019-01-02 08:00:00', - // '2019-01-03 08:00:00', - // '2019-01-04 08:00:00', - // '2019-01-05 08:00:00', - // '2019-01-06 08:00:00', - // '2019-01-07 08:00:00', - // '2019-01-08 08:00:00', - // '2019-01-09 08:00:00', - // ]; - // const tickFormat = (d: number) => { - // Settings.defaultZoneName = 'utc'; - // return DateTime.fromMillis(d).toFormat('yyyy-MM-dd HH:mm:ss'); - // }; - - // expect(removeDupeTickLabels(tickValues, tickFormat)).toEqual(tickLabels); - // }); test('should compute min max range for on 0 deg bottom', () => { const minMax = getMinMaxRange(Position.Bottom, 0, { width: 100, diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 4459a89d74..634ca124fa 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -443,10 +443,9 @@ export function getAvailableTicks( return [firstTick, lastTick]; } - - // want to go through the distinct tick labels and populate a tick for each of them return getDuplicateTicks(axisSpec, scale, offset, tickFormatOptions); } + export function getDuplicateTicks( axisSpec: AxisSpec, scale: Scale, @@ -464,8 +463,7 @@ export function getDuplicateTicks( }); if (axisSpec.duplicateTicks === false) { - // eslint-disable-next-line prefer-const - let uniqueTickLabels: { value: any; label: string; position: number }[] = []; + const uniqueTickLabels: { value: any; label: string; position: number }[] = []; allTicks.filter((value, index) => { for (let i = 0; i < labels.length; i++) { if (labels[i] === allTicks[index].label) { From 4976be7598a91b0e0780f67f5c0b1c84d4d5a7b8 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 11 Mar 2020 09:18:45 -0600 Subject: [PATCH 12/25] test: add unit tests --- .../xy_chart/utils/axis_utils.test.ts | 126 +++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) 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 e8d6aea0b4..881732030f 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -22,7 +22,7 @@ import { AxisSpec, DomainRange, AxisStyle } from './specs'; import { Position } from '../../../utils/commons'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { AxisId, GroupId } from '../../../utils/ids'; -import { ScaleType } from '../../../scales'; +import { ScaleType, Scale } from '../../../scales'; import { AxisTick, AxisTicksDimensions, @@ -48,6 +48,7 @@ import { getAxisTickLabelPadding, isVerticalGrid, isHorizontalGrid, + getDuplicateTicks, } from './axis_utils'; import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { SvgTextBBoxCalculator } from '../../../utils/bbox/svg_text_bbox_calculator'; @@ -55,6 +56,8 @@ import { niceTimeFormatter } from '../../../utils/data/formatters'; import { mergeYCustomDomainsByGroupId } from '../state/selectors/merge_y_custom_domains'; import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; +import { DateTime } from 'luxon'; +import { computeXScale } from './scales'; describe('Axis computational utils', () => { const mockedRect = { @@ -1436,4 +1439,125 @@ describe('Axis computational utils', () => { expect(getAxisTickLabelPadding(axisConfigTickLabelPadding, axisSpecStyle)).toEqual(2); }); + test('should show unique tick labels if duplicateTicks is set to false', () => { + const now = DateTime.fromISO('2019-01-11T00:00:00.000') + .setZone('utc+1') + .toMillis(); + const oneDay = 1000 * 60 * 60 * 24; + const formatter = niceTimeFormatter([now, now + oneDay * 31]); + const axisSpec: AxisSpec = { + id: 'bottom', + position: 'bottom', + duplicateTicks: false, + chartType: 'xy_axis', + specType: 'axis', + groupId: '__global__', + hide: false, + showOverlappingLabels: false, + showOverlappingTicks: false, + tickSize: 10, + tickPadding: 10, + tickLabelRotation: 0, + tickFormat: formatter, + }; + const xDomainTime: XDomain = { + type: 'xDomain', + isBandScale: false, + domain: [1547190000000, 1547622000000], + minInterval: 86400000, + scaleType: ScaleType.Time, + }; + const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); + // xDomain: { type: 'xDomain', scaleType: 'time', isBandScale: false }, + // + // minInterval: + // timeZone: undefined, + // totalBarsInCluster: 0, + // range: [0, 603.5], + // barsPadding: 0.25, + // enableHistogramMode: false, + // ticks: undefined, + // integersOnly: undefined, + // duplicateTicks: false, + // = { + // domain: [], + // barsPadding: 0.25, + // bandwidth: 0, + // type: 'time', + // range: [0, 603.5], + // minInterval: 86400000, + // isInverted: false, + // tickValues: [ + // 1547208000000, + // 1547251200000, + // 1547294400000, + // 1547337600000, + // 1547380800000, + // 1547424000000, + // 1547467200000, + // 1547510400000, + // 1547553600000, + // 1547596800000, + // ], + // isSingleValue: () => false, + // totalBarsInCluster: 0, + // bandwidthPadding: 0, + // step: 0, + // timeZone: 'utc', + // isSingleValueHistogram: false, + // }; + const offset = 0; + const tickFormatOption = { timeZone: undefined }; + expect(getDuplicateTicks(axisSpec, scale, offset, tickFormatOption)).toEqual([ + { value: 1547208000000, label: '2019-01-11', position: 25.145833333333332 }, + { value: 1547294400000, label: '2019-01-12', position: 145.84583333333333 }, + { value: 1547380800000, label: '2019-01-13', position: 266.54583333333335 }, + { value: 1547467200000, label: '2019-01-14', position: 387.24583333333334 }, + { value: 1547553600000, label: '2019-01-15', position: 507.9458333333333 }, + ]); + }); + test('should show duplicate tick labels if duplicateTicks is set to true', () => { + const now = DateTime.fromISO('2019-01-11T00:00:00.000') + .setZone('utc+1') + .toMillis(); + const oneDay = 1000 * 60 * 60 * 24; + const formatter = niceTimeFormatter([now, now + oneDay * 31]); + const axisSpec: AxisSpec = { + id: 'bottom', + position: 'bottom', + duplicateTicks: true, + chartType: 'xy_axis', + specType: 'axis', + groupId: '__global__', + hide: false, + showOverlappingLabels: false, + showOverlappingTicks: false, + tickSize: 10, + tickPadding: 10, + tickLabelRotation: 0, + tickFormat: formatter, + }; + const xDomainTime: XDomain = { + type: 'xDomain', + isBandScale: false, + domain: [1547190000000, 1547622000000], + minInterval: 86400000, + scaleType: ScaleType.Time, + }; + const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); + const offset = 0; + const tickFormatOption = { timeZone: undefined }; + expect(getDuplicateTicks(axisSpec, scale, offset, tickFormatOption)).toEqual([ + { value: 1547208000000, label: '2019-01-11', position: 25.145833333333332 }, + { value: 1547251200000, label: '2019-01-11', position: 85.49583333333334 }, + { value: 1547294400000, label: '2019-01-12', position: 145.84583333333333 }, + { value: 1547337600000, label: '2019-01-12', position: 206.19583333333333 }, + { value: 1547380800000, label: '2019-01-13', position: 266.54583333333335 }, + { value: 1547424000000, label: '2019-01-13', position: 326.8958333333333 }, + { value: 1547467200000, label: '2019-01-14', position: 387.24583333333334 }, + { value: 1547510400000, label: '2019-01-14', position: 447.59583333333336 }, + { value: 1547553600000, label: '2019-01-15', position: 507.9458333333333 }, + { value: 1547596800000, label: '2019-01-15', position: 568.2958333333333 }, + ]); + }); }); From 1b76c6981f4b3bb62bfeadf96612351246437afe Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 11 Mar 2020 09:35:33 -0600 Subject: [PATCH 13/25] test: set timezone to clean up tests --- .../xy_chart/utils/axis_utils.test.ts | 61 ++++--------------- 1 file changed, 12 insertions(+), 49 deletions(-) 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 881732030f..4ece042beb 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -1468,52 +1468,15 @@ describe('Axis computational utils', () => { scaleType: ScaleType.Time, }; const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); - // xDomain: { type: 'xDomain', scaleType: 'time', isBandScale: false }, - // - // minInterval: - // timeZone: undefined, - // totalBarsInCluster: 0, - // range: [0, 603.5], - // barsPadding: 0.25, - // enableHistogramMode: false, - // ticks: undefined, - // integersOnly: undefined, - // duplicateTicks: false, - // = { - // domain: [], - // barsPadding: 0.25, - // bandwidth: 0, - // type: 'time', - // range: [0, 603.5], - // minInterval: 86400000, - // isInverted: false, - // tickValues: [ - // 1547208000000, - // 1547251200000, - // 1547294400000, - // 1547337600000, - // 1547380800000, - // 1547424000000, - // 1547467200000, - // 1547510400000, - // 1547553600000, - // 1547596800000, - // ], - // isSingleValue: () => false, - // totalBarsInCluster: 0, - // bandwidthPadding: 0, - // step: 0, - // timeZone: 'utc', - // isSingleValueHistogram: false, - // }; const offset = 0; - const tickFormatOption = { timeZone: undefined }; + const tickFormatOption = { timeZone: 'utc+1' }; expect(getDuplicateTicks(axisSpec, scale, offset, tickFormatOption)).toEqual([ { value: 1547208000000, label: '2019-01-11', position: 25.145833333333332 }, - { value: 1547294400000, label: '2019-01-12', position: 145.84583333333333 }, - { value: 1547380800000, label: '2019-01-13', position: 266.54583333333335 }, - { value: 1547467200000, label: '2019-01-14', position: 387.24583333333334 }, - { value: 1547553600000, label: '2019-01-15', position: 507.9458333333333 }, + { value: 1547251200000, label: '2019-01-12', position: 85.49583333333334 }, + { value: 1547337600000, label: '2019-01-13', position: 206.19583333333333 }, + { value: 1547424000000, label: '2019-01-14', position: 326.8958333333333 }, + { value: 1547510400000, label: '2019-01-15', position: 447.59583333333336 }, + { value: 1547596800000, label: '2019-01-16', position: 568.2958333333333 }, ]); }); test('should show duplicate tick labels if duplicateTicks is set to true', () => { @@ -1546,18 +1509,18 @@ describe('Axis computational utils', () => { }; const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; - const tickFormatOption = { timeZone: undefined }; + const tickFormatOption = { timeZone: 'utc+1' }; expect(getDuplicateTicks(axisSpec, scale, offset, tickFormatOption)).toEqual([ { value: 1547208000000, label: '2019-01-11', position: 25.145833333333332 }, - { value: 1547251200000, label: '2019-01-11', position: 85.49583333333334 }, + { value: 1547251200000, label: '2019-01-12', position: 85.49583333333334 }, { value: 1547294400000, label: '2019-01-12', position: 145.84583333333333 }, - { value: 1547337600000, label: '2019-01-12', position: 206.19583333333333 }, + { value: 1547337600000, label: '2019-01-13', position: 206.19583333333333 }, { value: 1547380800000, label: '2019-01-13', position: 266.54583333333335 }, - { value: 1547424000000, label: '2019-01-13', position: 326.8958333333333 }, + { value: 1547424000000, label: '2019-01-14', position: 326.8958333333333 }, { value: 1547467200000, label: '2019-01-14', position: 387.24583333333334 }, - { value: 1547510400000, label: '2019-01-14', position: 447.59583333333336 }, + { value: 1547510400000, label: '2019-01-15', position: 447.59583333333336 }, { value: 1547553600000, label: '2019-01-15', position: 507.9458333333333 }, - { value: 1547596800000, label: '2019-01-15', position: 568.2958333333333 }, + { value: 1547596800000, label: '2019-01-16', position: 568.2958333333333 }, ]); }); }); From 5037110da8c22457d0b628ddc00cb571be1bef5b Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 11 Mar 2020 11:57:35 -0600 Subject: [PATCH 14/25] refactor: update naming and remove from scales spec --- .../xy_chart/utils/axis_utils.test.ts | 10 +++++----- src/chart_types/xy_chart/utils/axis_utils.ts | 9 +++------ src/chart_types/xy_chart/utils/scales.ts | 18 +++--------------- src/chart_types/xy_chart/utils/specs.ts | 2 +- src/scales/scale_continuous.ts | 3 --- stories/line/10_duplicate_ticks.tsx | 7 ++++++- 6 files changed, 18 insertions(+), 31 deletions(-) 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 4ece042beb..5afb598692 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -48,7 +48,7 @@ import { getAxisTickLabelPadding, isVerticalGrid, isHorizontalGrid, - getDuplicateTicks, + enableDuplicatedTicks, } from './axis_utils'; import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { SvgTextBBoxCalculator } from '../../../utils/bbox/svg_text_bbox_calculator'; @@ -1448,7 +1448,7 @@ describe('Axis computational utils', () => { const axisSpec: AxisSpec = { id: 'bottom', position: 'bottom', - duplicateTicks: false, + enableDuplicatedTicks: false, chartType: 'xy_axis', specType: 'axis', groupId: '__global__', @@ -1470,7 +1470,7 @@ describe('Axis computational utils', () => { const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; const tickFormatOption = { timeZone: 'utc+1' }; - expect(getDuplicateTicks(axisSpec, scale, offset, tickFormatOption)).toEqual([ + expect(enableDuplicatedTicks(axisSpec, scale, offset, tickFormatOption)).toEqual([ { value: 1547208000000, label: '2019-01-11', position: 25.145833333333332 }, { value: 1547251200000, label: '2019-01-12', position: 85.49583333333334 }, { value: 1547337600000, label: '2019-01-13', position: 206.19583333333333 }, @@ -1488,7 +1488,7 @@ describe('Axis computational utils', () => { const axisSpec: AxisSpec = { id: 'bottom', position: 'bottom', - duplicateTicks: true, + enableDuplicatedTicks: true, chartType: 'xy_axis', specType: 'axis', groupId: '__global__', @@ -1510,7 +1510,7 @@ describe('Axis computational utils', () => { const scale: Scale = computeXScale({ xDomain: xDomainTime, totalBarsInCluster: 0, range: [0, 603.5] }); const offset = 0; const tickFormatOption = { timeZone: 'utc+1' }; - expect(getDuplicateTicks(axisSpec, scale, offset, tickFormatOption)).toEqual([ + expect(enableDuplicatedTicks(axisSpec, scale, offset, tickFormatOption)).toEqual([ { value: 1547208000000, label: '2019-01-11', position: 25.145833333333332 }, { value: 1547251200000, label: '2019-01-12', position: 85.49583333333334 }, { value: 1547294400000, label: '2019-01-12', position: 145.84583333333333 }, diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 634ca124fa..7316fd92b9 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -156,7 +156,6 @@ export function getScaleForAxisSpec( range, ticks: axisSpec.ticks, integersOnly: axisSpec.integersOnly, - duplicateTicks: axisSpec.duplicateTicks, }); if (yScales.has(axisSpec.groupId)) { return yScales.get(axisSpec.groupId)!; @@ -171,7 +170,6 @@ export function getScaleForAxisSpec( enableHistogramMode, ticks: axisSpec.ticks, integersOnly: axisSpec.integersOnly, - duplicateTicks: axisSpec.duplicateTicks, }); } } @@ -233,7 +231,6 @@ export function computeTickDimensions( axisConfig: AxisConfig, tickLabelPadding: number, tickLabelRotation = 0, - // duplicateTicks: boolean = false, tickFormatOptions?: TickFormatterOptions, ) { const tickValues = scale.ticks(); @@ -443,10 +440,10 @@ export function getAvailableTicks( return [firstTick, lastTick]; } - return getDuplicateTicks(axisSpec, scale, offset, tickFormatOptions); + return enableDuplicatedTicks(axisSpec, scale, offset, tickFormatOptions); } -export function getDuplicateTicks( +export function enableDuplicatedTicks( axisSpec: AxisSpec, scale: Scale, offset: number, @@ -462,7 +459,7 @@ export function getDuplicateTicks( }; }); - if (axisSpec.duplicateTicks === false) { + if (axisSpec.enableDuplicatedTicks === false) { const uniqueTickLabels: { value: any; label: string; position: number }[] = []; allTicks.filter((value, index) => { for (let i = 0; i < labels.length; i++) { diff --git a/src/chart_types/xy_chart/utils/scales.ts b/src/chart_types/xy_chart/utils/scales.ts index 0c64244fac..9c6a76ac21 100644 --- a/src/chart_types/xy_chart/utils/scales.ts +++ b/src/chart_types/xy_chart/utils/scales.ts @@ -78,7 +78,6 @@ interface XScaleOptions { enableHistogramMode?: boolean; ticks?: number; integersOnly?: boolean; - duplicateTicks?: boolean; } /** @@ -88,16 +87,7 @@ interface XScaleOptions { * @param axisLength the length of the x axis */ export function computeXScale(options: XScaleOptions): Scale { - const { - xDomain, - totalBarsInCluster, - range, - barsPadding, - enableHistogramMode, - ticks, - integersOnly, - duplicateTicks, - } = options; + const { xDomain, totalBarsInCluster, range, barsPadding, enableHistogramMode, ticks, integersOnly } = options; const { scaleType, minInterval, domain, isBandScale, timeZone } = xDomain; const rangeDiff = Math.abs(range[1] - range[0]); const isInverse = range[1] < range[0]; @@ -139,7 +129,7 @@ export function computeXScale(options: XScaleOptions): Scale { } else { return new ScaleContinuous( { type: scaleType, domain, range }, - { bandwidth: 0, minInterval, timeZone, totalBarsInCluster, barsPadding, ticks, integersOnly, duplicateTicks }, + { bandwidth: 0, minInterval, timeZone, totalBarsInCluster, barsPadding, ticks, integersOnly }, ); } } @@ -150,7 +140,6 @@ interface YScaleOptions { range: [number, number]; ticks?: number; integersOnly?: boolean; - duplicateTicks?: boolean; } /** * Compute the y scales, one per groupId for the y axis. @@ -159,7 +148,7 @@ interface YScaleOptions { */ export function computeYScales(options: YScaleOptions): Map { const yScales: Map = new Map(); - const { yDomains, range, ticks, integersOnly, duplicateTicks } = options; + const { yDomains, range, ticks, integersOnly } = options; yDomains.forEach(({ scaleType: type, domain, groupId }) => { const yScale = new ScaleContinuous( { @@ -170,7 +159,6 @@ export function computeYScales(options: YScaleOptions): Map { { ticks, integersOnly, - duplicateTicks, }, ); yScales.set(groupId, yScale); diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 50b911f0d0..ed2b241c83 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -526,7 +526,7 @@ export interface AxisSpec extends Spec { /** Show only integar values **/ integersOnly?: boolean; /** Remove duplicate ticks, default is false*/ - duplicateTicks?: boolean; + enableDuplicatedTicks?: boolean; } export type TickFormatterOptions = { diff --git a/src/scales/scale_continuous.ts b/src/scales/scale_continuous.ts index f5ce21885f..154bf3b3fc 100644 --- a/src/scales/scale_continuous.ts +++ b/src/scales/scale_continuous.ts @@ -139,8 +139,6 @@ interface ScaleOptions { isSingleValueHistogram: boolean; /** Show only integer values **/ integersOnly?: boolean; - /** Show duplicate tick values default to false */ - duplicateTicks?: boolean; } const defaultScaleOptions: ScaleOptions = { bandwidth: 0, @@ -151,7 +149,6 @@ const defaultScaleOptions: ScaleOptions = { ticks: 10, isSingleValueHistogram: false, integersOnly: false, - duplicateTicks: false, }; export class ScaleContinuous implements Scale { readonly bandwidth: number; diff --git a/stories/line/10_duplicate_ticks.tsx b/stories/line/10_duplicate_ticks.tsx index 712f283716..2b71b6b8d9 100644 --- a/stories/line/10_duplicate_ticks.tsx +++ b/stories/line/10_duplicate_ticks.tsx @@ -31,7 +31,12 @@ export const example = () => { const duplicateTicksInAxis = boolean('Show duplicate ticks in x axis', false); return ( - + Date: Wed, 11 Mar 2020 13:36:59 -0600 Subject: [PATCH 15/25] refactor: add timeZone prop to story --- stories/line/10_duplicate_ticks.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/stories/line/10_duplicate_ticks.tsx b/stories/line/10_duplicate_ticks.tsx index 2b71b6b8d9..c81302c328 100644 --- a/stories/line/10_duplicate_ticks.tsx +++ b/stories/line/10_duplicate_ticks.tsx @@ -57,6 +57,7 @@ export const example = () => { { x: now + oneDay * 4, y: 8 }, { x: now + oneDay * 5, y: 6 }, ]} + timeZone="local" /> ); From c5ac664cf4610904f7ca962ad9bb2fa3f250d9ba Mon Sep 17 00:00:00 2001 From: rshen91 Date: Thu, 12 Mar 2020 08:55:52 -0600 Subject: [PATCH 16/25] refactor: improve filter() with a set and return allTicks earlier --- src/chart_types/xy_chart/utils/axis_utils.ts | 26 +++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 7316fd92b9..70e902569e 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -450,7 +450,6 @@ export function enableDuplicatedTicks( tickFormatOptions?: TickFormatterOptions, ) { const ticks = scale.ticks(); - const labels = [...new Set(ticks.map((tick) => axisSpec.tickFormat(tick, tickFormatOptions)))]; const allTicks = ticks.map((tick) => { return { value: tick, @@ -459,21 +458,20 @@ export function enableDuplicatedTicks( }; }); - if (axisSpec.enableDuplicatedTicks === false) { - const uniqueTickLabels: { value: any; label: string; position: number }[] = []; - allTicks.filter((value, index) => { - for (let i = 0; i < labels.length; i++) { - if (labels[i] === allTicks[index].label) { - uniqueTickLabels.push(allTicks[index]); - // once uniqueTickLabels has the unique label, remove the label from the labels array so it doesnt create a duplicate - labels.splice(i, 1); - } - } - }); - return uniqueTickLabels; - } else { + if (axisSpec.enableDuplicatedTicks === true) { return allTicks; } + + const uniqueTickLabels: Set = new Set(); + return allTicks.filter((value) => { + const { label } = value; + if (uniqueTickLabels.has(label)) { + return false; + } else { + uniqueTickLabels.add(label); + return true; + } + }); } export function getVisibleTicks(allTicks: AxisTick[], axisSpec: AxisSpec, axisDim: AxisTicksDimensions): AxisTick[] { From 4e88447fd072ce2164c55bf9275805b27e2edb14 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Fri, 13 Mar 2020 08:57:37 -0600 Subject: [PATCH 17/25] refactor: refactor to reduce() implementation --- src/chart_types/xy_chart/utils/axis_utils.ts | 30 +++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 70e902569e..812f6fe3a1 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -450,7 +450,7 @@ export function enableDuplicatedTicks( tickFormatOptions?: TickFormatterOptions, ) { const ticks = scale.ticks(); - const allTicks = ticks.map((tick) => { + const allTicks: AxisTick[] = ticks.map((tick) => { return { value: tick, label: axisSpec.tickFormat(tick, tickFormatOptions), @@ -462,16 +462,24 @@ export function enableDuplicatedTicks( return allTicks; } - const uniqueTickLabels: Set = new Set(); - return allTicks.filter((value) => { - const { label } = value; - if (uniqueTickLabels.has(label)) { - return false; - } else { - uniqueTickLabels.add(label); - return true; - } - }); + return allTicks.reduce<{ + filteredTicks: AxisTick[]; + uniqueTickLabels: Set; + }>( + (acc, currentValue) => { + const { label } = currentValue; + if (acc.uniqueTickLabels.has(label)) { + return acc; + } + acc.uniqueTickLabels.add(label); + acc.filteredTicks.push(currentValue); + return acc; + }, + { + filteredTicks: [], + uniqueTickLabels: new Set(), + }, + ).filteredTicks; } export function getVisibleTicks(allTicks: AxisTick[], axisSpec: AxisSpec, axisDim: AxisTicksDimensions): AxisTick[] { From 55bf6ee7d922a1d19f7dc05f88783e5fb1c9a471 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 17 Mar 2020 08:11:49 -0600 Subject: [PATCH 18/25] refactor: use moment library for time calculations --- .../xy_chart/utils/axis_utils.test.ts | 9 +++++---- stories/interactions/11_brush_time.tsx | 17 +++++++++-------- stories/interactions/12_brush_time_hist.tsx | 11 ++++++----- stories/line/10_duplicate_ticks.tsx | 15 ++++++++------- 4 files changed, 28 insertions(+), 24 deletions(-) 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 5afb598692..dba5a94784 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -58,6 +58,7 @@ import { ChartTypes } from '../..'; import { SpecTypes } from '../../../specs/settings'; import { DateTime } from 'luxon'; import { computeXScale } from './scales'; +import moment from 'moment-timezone'; describe('Axis computational utils', () => { const mockedRect = { @@ -1443,8 +1444,8 @@ describe('Axis computational utils', () => { const now = DateTime.fromISO('2019-01-11T00:00:00.000') .setZone('utc+1') .toMillis(); - const oneDay = 1000 * 60 * 60 * 24; - const formatter = niceTimeFormatter([now, now + oneDay * 31]); + const oneDay = moment.duration(1, 'day'); + const formatter = niceTimeFormatter([now, oneDay.add(now).asMilliseconds() * 31]); const axisSpec: AxisSpec = { id: 'bottom', position: 'bottom', @@ -1483,8 +1484,8 @@ describe('Axis computational utils', () => { const now = DateTime.fromISO('2019-01-11T00:00:00.000') .setZone('utc+1') .toMillis(); - const oneDay = 1000 * 60 * 60 * 24; - const formatter = niceTimeFormatter([now, now + oneDay * 31]); + const oneDay = moment.duration(1, 'day'); + const formatter = niceTimeFormatter([now, oneDay.add(now).asMilliseconds() * 31]); const axisSpec: AxisSpec = { id: 'bottom', position: 'bottom', diff --git a/stories/interactions/11_brush_time.tsx b/stories/interactions/11_brush_time.tsx index 2eb74384ae..45bbd53987 100644 --- a/stories/interactions/11_brush_time.tsx +++ b/stories/interactions/11_brush_time.tsx @@ -23,13 +23,14 @@ import { Axis, BarSeries, Chart, LineSeries, niceTimeFormatter, Position, ScaleT import { boolean } from '@storybook/addon-knobs'; import { DateTime } from 'luxon'; import { getChartRotationKnob } from '../utils/knobs'; +import moment from 'moment-timezone'; export const example = () => { const now = DateTime.fromISO('2019-01-11T00:00:00.000') .setZone('utc+1') .toMillis(); - const oneDay = 1000 * 60 * 60 * 24; - const formatter = niceTimeFormatter([now, now + oneDay * 5]); + const oneDay = moment.duration(1, 'day'); + const formatter = niceTimeFormatter([now, oneDay.add(now).asMilliseconds() * 5]); return ( { timeZone="Europe/Rome" data={[ { x: now, y: 2 }, - { x: now + oneDay, y: 7 }, - { x: now + oneDay * 2, y: 3 }, - { x: now + oneDay * 5, y: 6 }, + { x: oneDay.add(now).asMilliseconds(), y: 7 }, + { x: oneDay.add(now).asMilliseconds() * 2, y: 3 }, + { x: oneDay.add(now).asMilliseconds() * 5, y: 6 }, ]} /> { timeZone="Europe/Rome" data={[ { x: now, y: 2 }, - { x: now + oneDay, y: 7 }, - { x: now + oneDay * 2, y: 3 }, - { x: now + oneDay * 5, y: 6 }, + { x: oneDay.add(now).asMilliseconds(), y: 7 }, + { x: oneDay.add(now).asMilliseconds() * 2, y: 3 }, + { x: oneDay.add(now).asMilliseconds() * 5, y: 6 }, ]} /> diff --git a/stories/interactions/12_brush_time_hist.tsx b/stories/interactions/12_brush_time_hist.tsx index 6ff4133acb..445290dcb9 100644 --- a/stories/interactions/12_brush_time_hist.tsx +++ b/stories/interactions/12_brush_time_hist.tsx @@ -23,13 +23,14 @@ import { Axis, Chart, niceTimeFormatter, Position, ScaleType, Settings, Histogra import { boolean } from '@storybook/addon-knobs'; import { DateTime } from 'luxon'; import { getChartRotationKnob } from '../utils/knobs'; +import moment from 'moment-timezone'; export const example = () => { const now = DateTime.fromISO('2019-01-11T00:00:00.000') .setZone('utc+1') .toMillis(); - const oneDay = 1000 * 60 * 60 * 24; - const formatter = niceTimeFormatter([now, now + oneDay * 5]); + const oneDay = moment.duration(1, 'day'); + const formatter = niceTimeFormatter([now, oneDay.add(now).asMilliseconds() * 5]); return ( { timeZone="Europe/Rome" data={[ { x: now, y: 2 }, - { x: now + oneDay, y: 7 }, - { x: now + oneDay * 2, y: 3 }, - { x: now + oneDay * 5, y: 6 }, + { x: oneDay.add(now).asMilliseconds(), y: 7 }, + { x: oneDay.add(now).asMilliseconds() * 2, y: 3 }, + { x: oneDay.add(now).asMilliseconds() * 5, y: 6 }, ]} /> diff --git a/stories/line/10_duplicate_ticks.tsx b/stories/line/10_duplicate_ticks.tsx index c81302c328..f4b77d9c95 100644 --- a/stories/line/10_duplicate_ticks.tsx +++ b/stories/line/10_duplicate_ticks.tsx @@ -21,13 +21,14 @@ import { Axis, Chart, LineSeries, Position, ScaleType, niceTimeFormatter } from import { KIBANA_METRICS } from '../../src/utils/data_samples/test_dataset_kibana'; import { boolean } from '@storybook/addon-knobs'; import { DateTime } from 'luxon'; +import moment from 'moment-timezone'; export const example = () => { const now = DateTime.fromISO('2019-01-11T00:00:00.000') .setZone('utc+1') .toMillis(); - const oneDay = 1000 * 60 * 60 * 24; - const formatter = niceTimeFormatter([now, now + oneDay * 31]); + const oneDay = moment.duration(1, 'day'); + const formatter = niceTimeFormatter([now, oneDay.add(now).asMilliseconds() * 31]); const duplicateTicksInAxis = boolean('Show duplicate ticks in x axis', false); return ( @@ -51,11 +52,11 @@ export const example = () => { yAccessors={['y']} data={[ { x: now, y: 2 }, - { x: now + oneDay, y: 3 }, - { x: now + oneDay * 2, y: 3 }, - { x: now + oneDay * 3, y: 4 }, - { x: now + oneDay * 4, y: 8 }, - { x: now + oneDay * 5, y: 6 }, + { x: oneDay.add(now).asMilliseconds(), y: 3 }, + { x: oneDay.add(now).asMilliseconds() * 2, y: 3 }, + { x: oneDay.add(now).asMilliseconds() * 3, y: 4 }, + { x: oneDay.add(now).asMilliseconds() * 4, y: 8 }, + { x: oneDay.add(now).asMilliseconds() * 5, y: 6 }, ]} timeZone="local" /> From 6ba675167232e2a0503519d5268908dcf21cc89d Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 17 Mar 2020 08:26:08 -0600 Subject: [PATCH 19/25] refactor: change config prop name to show vs enable --- src/chart_types/xy_chart/utils/axis_utils.test.ts | 4 ++-- src/chart_types/xy_chart/utils/axis_utils.ts | 2 +- src/chart_types/xy_chart/utils/specs.ts | 2 +- stories/line/10_duplicate_ticks.tsx | 7 +------ 4 files changed, 5 insertions(+), 10 deletions(-) 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 dba5a94784..60beaf4710 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -1449,7 +1449,7 @@ describe('Axis computational utils', () => { const axisSpec: AxisSpec = { id: 'bottom', position: 'bottom', - enableDuplicatedTicks: false, + showDuplicatedTicks: false, chartType: 'xy_axis', specType: 'axis', groupId: '__global__', @@ -1489,7 +1489,7 @@ describe('Axis computational utils', () => { const axisSpec: AxisSpec = { id: 'bottom', position: 'bottom', - enableDuplicatedTicks: true, + showDuplicatedTicks: true, chartType: 'xy_axis', specType: 'axis', groupId: '__global__', diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 812f6fe3a1..1f5615b855 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -458,7 +458,7 @@ export function enableDuplicatedTicks( }; }); - if (axisSpec.enableDuplicatedTicks === true) { + if (axisSpec.showDuplicatedTicks === true) { return allTicks; } diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index ed2b241c83..5c8ce8ba3c 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -526,7 +526,7 @@ export interface AxisSpec extends Spec { /** Show only integar values **/ integersOnly?: boolean; /** Remove duplicate ticks, default is false*/ - enableDuplicatedTicks?: boolean; + showDuplicatedTicks?: boolean; } export type TickFormatterOptions = { diff --git a/stories/line/10_duplicate_ticks.tsx b/stories/line/10_duplicate_ticks.tsx index f4b77d9c95..6c860aaf3e 100644 --- a/stories/line/10_duplicate_ticks.tsx +++ b/stories/line/10_duplicate_ticks.tsx @@ -32,12 +32,7 @@ export const example = () => { const duplicateTicksInAxis = boolean('Show duplicate ticks in x axis', false); return ( - + Date: Tue, 17 Mar 2020 08:45:49 -0600 Subject: [PATCH 20/25] fix: fix moment in duplicate ticks --- stories/line/10_duplicate_ticks.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/stories/line/10_duplicate_ticks.tsx b/stories/line/10_duplicate_ticks.tsx index 6c860aaf3e..c9531c6dac 100644 --- a/stories/line/10_duplicate_ticks.tsx +++ b/stories/line/10_duplicate_ticks.tsx @@ -27,8 +27,13 @@ export const example = () => { const now = DateTime.fromISO('2019-01-11T00:00:00.000') .setZone('utc+1') .toMillis(); - const oneDay = moment.duration(1, 'day'); - const formatter = niceTimeFormatter([now, oneDay.add(now).asMilliseconds() * 31]); + const oneDay = moment.duration(1, 'd'); + const twoDays = moment.duration(2, 'd'); + const oneMonth = moment.duration(31, 'd'); + const threeDays = moment.duration(3, 'd'); + const fourDays = moment.duration(4, 'd'); + const fiveDays = moment.duration(5, 'd'); + const formatter = niceTimeFormatter([now, oneMonth.add(now).asMilliseconds()]); const duplicateTicksInAxis = boolean('Show duplicate ticks in x axis', false); return ( @@ -48,10 +53,10 @@ export const example = () => { data={[ { x: now, y: 2 }, { x: oneDay.add(now).asMilliseconds(), y: 3 }, - { x: oneDay.add(now).asMilliseconds() * 2, y: 3 }, - { x: oneDay.add(now).asMilliseconds() * 3, y: 4 }, - { x: oneDay.add(now).asMilliseconds() * 4, y: 8 }, - { x: oneDay.add(now).asMilliseconds() * 5, y: 6 }, + { x: twoDays.add(now).asMilliseconds(), y: 3 }, + { x: threeDays.add(now).asMilliseconds(), y: 4 }, + { x: fourDays.add(now).asMilliseconds(), y: 8 }, + { x: fiveDays.add(now).asMilliseconds(), y: 6 }, ]} timeZone="local" /> From 4952e6b2f168cafbeaffcaac154600d8a4fdcca3 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 17 Mar 2020 10:29:26 -0600 Subject: [PATCH 21/25] refactor: revert moment changes in brush stories --- stories/interactions/11_brush_time.tsx | 19 +++++++++++-------- stories/interactions/12_brush_time_hist.tsx | 11 +++++------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/stories/interactions/11_brush_time.tsx b/stories/interactions/11_brush_time.tsx index 45bbd53987..e2b72cc470 100644 --- a/stories/interactions/11_brush_time.tsx +++ b/stories/interactions/11_brush_time.tsx @@ -29,8 +29,11 @@ export const example = () => { const now = DateTime.fromISO('2019-01-11T00:00:00.000') .setZone('utc+1') .toMillis(); - const oneDay = moment.duration(1, 'day'); - const formatter = niceTimeFormatter([now, oneDay.add(now).asMilliseconds() * 5]); + const oneDay = 1000 * 60 * 60 * 24; + const oneDays = moment.duration(1, 'd'); + const twoDays = moment.duration(2, 'd'); + const fiveDays = moment.duration(5, 'd'); + const formatter = niceTimeFormatter([now, fiveDays.add(now).asMilliseconds()]); return ( { timeZone="Europe/Rome" data={[ { x: now, y: 2 }, - { x: oneDay.add(now).asMilliseconds(), y: 7 }, - { x: oneDay.add(now).asMilliseconds() * 2, y: 3 }, - { x: oneDay.add(now).asMilliseconds() * 5, y: 6 }, + { x: oneDays.add(now).asMilliseconds(), y: 7 }, + { x: twoDays.add(now).asMilliseconds(), y: 3 }, + { x: now + oneDay * 5, y: 6 }, ]} /> { timeZone="Europe/Rome" data={[ { x: now, y: 2 }, - { x: oneDay.add(now).asMilliseconds(), y: 7 }, - { x: oneDay.add(now).asMilliseconds() * 2, y: 3 }, - { x: oneDay.add(now).asMilliseconds() * 5, y: 6 }, + { x: now + oneDay, y: 7 }, + { x: now + oneDay * 2, y: 3 }, + { x: now + oneDay * 5, y: 6 }, ]} /> diff --git a/stories/interactions/12_brush_time_hist.tsx b/stories/interactions/12_brush_time_hist.tsx index 445290dcb9..6ff4133acb 100644 --- a/stories/interactions/12_brush_time_hist.tsx +++ b/stories/interactions/12_brush_time_hist.tsx @@ -23,14 +23,13 @@ import { Axis, Chart, niceTimeFormatter, Position, ScaleType, Settings, Histogra import { boolean } from '@storybook/addon-knobs'; import { DateTime } from 'luxon'; import { getChartRotationKnob } from '../utils/knobs'; -import moment from 'moment-timezone'; export const example = () => { const now = DateTime.fromISO('2019-01-11T00:00:00.000') .setZone('utc+1') .toMillis(); - const oneDay = moment.duration(1, 'day'); - const formatter = niceTimeFormatter([now, oneDay.add(now).asMilliseconds() * 5]); + const oneDay = 1000 * 60 * 60 * 24; + const formatter = niceTimeFormatter([now, now + oneDay * 5]); return ( { timeZone="Europe/Rome" data={[ { x: now, y: 2 }, - { x: oneDay.add(now).asMilliseconds(), y: 7 }, - { x: oneDay.add(now).asMilliseconds() * 2, y: 3 }, - { x: oneDay.add(now).asMilliseconds() * 5, y: 6 }, + { x: now + oneDay, y: 7 }, + { x: now + oneDay * 2, y: 3 }, + { x: now + oneDay * 5, y: 6 }, ]} /> From 594c6f6725a317703867c47f9b90166b698ff16d Mon Sep 17 00:00:00 2001 From: rshen91 Date: Tue, 17 Mar 2020 14:10:48 -0600 Subject: [PATCH 22/25] feat: refactor getUniqueValues to be reusable --- src/chart_types/xy_chart/utils/axis_utils.ts | 26 ++++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 1f5615b855..7dd06c7384 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -63,6 +63,10 @@ export interface TickLabelProps { verticalAlign: string; } +export interface Duplicates { + filteredTicks: any[]; + uniqueValues: Set; +} /** * Compute the ticks values and identify max width and height of the labels * so we can compute the max space occupied by the axis component. @@ -461,23 +465,29 @@ export function enableDuplicatedTicks( if (axisSpec.showDuplicatedTicks === true) { return allTicks; } + return getUniqueValues( + allTicks, + allTicks.map((tick) => tick.label), + ); +} - return allTicks.reduce<{ - filteredTicks: AxisTick[]; - uniqueTickLabels: Set; - }>( +function getUniqueValues(fullArray: any[], uniqueProperty: (string | number)[]) { + return fullArray.reduce( (acc, currentValue) => { - const { label } = currentValue; - if (acc.uniqueTickLabels.has(label)) { + // need to find the correct value from currentValue (uniqueProperty points to the value in fullArray not the key) + const oneObject = Object.values(fullArray)[0]; + const uniqueKey = Object.keys(oneObject).find((key) => oneObject[key] === uniqueProperty[0]); + const uniqueProp: string = currentValue[uniqueKey!]; + if (acc.uniqueValues.has(uniqueProp)) { return acc; } - acc.uniqueTickLabels.add(label); + acc.uniqueValues.add(uniqueProp); acc.filteredTicks.push(currentValue); return acc; }, { filteredTicks: [], - uniqueTickLabels: new Set(), + uniqueValues: new Set(), }, ).filteredTicks; } From 9ed79f06f19c6929f6309836103cd0a37a8ccfe4 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 18 Mar 2020 11:47:06 -0600 Subject: [PATCH 23/25] refactor: refactored generics in getUniqueValues --- src/chart_types/xy_chart/utils/axis_utils.ts | 33 ++------------------ src/utils/commons.ts | 22 +++++++++++++ 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 7dd06c7384..b2c2a9f8f9 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -29,7 +29,7 @@ import { AxisStyle, TickFormatterOptions, } from './specs'; -import { Position, Rotation } from '../../../utils/commons'; +import { Position, Rotation, getUniqueValues } from '../../../utils/commons'; import { AxisConfig, Theme } from '../../../utils/themes/theme'; import { Dimensions, Margins } from '../../../utils/dimensions'; import { AxisId } from '../../../utils/ids'; @@ -63,10 +63,6 @@ export interface TickLabelProps { verticalAlign: string; } -export interface Duplicates { - filteredTicks: any[]; - uniqueValues: Set; -} /** * Compute the ticks values and identify max width and height of the labels * so we can compute the max space occupied by the axis component. @@ -447,6 +443,7 @@ export function getAvailableTicks( return enableDuplicatedTicks(axisSpec, scale, offset, tickFormatOptions); } +/** @internal */ export function enableDuplicatedTicks( axisSpec: AxisSpec, scale: Scale, @@ -465,31 +462,7 @@ export function enableDuplicatedTicks( if (axisSpec.showDuplicatedTicks === true) { return allTicks; } - return getUniqueValues( - allTicks, - allTicks.map((tick) => tick.label), - ); -} - -function getUniqueValues(fullArray: any[], uniqueProperty: (string | number)[]) { - return fullArray.reduce( - (acc, currentValue) => { - // need to find the correct value from currentValue (uniqueProperty points to the value in fullArray not the key) - const oneObject = Object.values(fullArray)[0]; - const uniqueKey = Object.keys(oneObject).find((key) => oneObject[key] === uniqueProperty[0]); - const uniqueProp: string = currentValue[uniqueKey!]; - if (acc.uniqueValues.has(uniqueProp)) { - return acc; - } - acc.uniqueValues.add(uniqueProp); - acc.filteredTicks.push(currentValue); - return acc; - }, - { - filteredTicks: [], - uniqueValues: new Set(), - }, - ).filteredTicks; + return getUniqueValues(allTicks, 'label'); } export function getVisibleTicks(allTicks: AxisTick[], axisSpec: AxisSpec, axisDim: AxisTicksDimensions): AxisTick[] { diff --git a/src/utils/commons.ts b/src/utils/commons.ts index 4917523f65..e1bdeca373 100644 --- a/src/utils/commons.ts +++ b/src/utils/commons.ts @@ -191,6 +191,28 @@ export function isNumberArray(value: unknown): value is number[] { return Array.isArray(value) && value.every((element) => typeof element === 'number'); } +/** @internal */ +export function getUniqueValues(fullArray: T[], uniqueProperty: keyof T): T[] { + return fullArray.reduce<{ + filtered: T[]; + uniqueValues: Set; + }>( + (acc, currentValue) => { + const uniqueValue = currentValue[uniqueProperty]; + if (acc.uniqueValues.has(uniqueValue)) { + return acc; + } + acc.uniqueValues.add(uniqueValue); + acc.filtered.push(currentValue); + return acc; + }, + { + filtered: [], + uniqueValues: new Set(), + }, + ).filtered; +} + export type ValueFormatter = (value: number) => string; export type ValueAccessor = (d: Datum) => number; export type LabelAccessor = (value: PrimitiveValue) => string; From 506bf0bc341e365c3deacbe83f4e4df20cc33406 Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 18 Mar 2020 13:23:43 -0600 Subject: [PATCH 24/25] style: remove export from computeTickDimensions --- src/chart_types/xy_chart/utils/axis_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index b2c2a9f8f9..d14fc274e2 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -224,7 +224,7 @@ export const getMaxBboxDimensions = ( }; }; -export function computeTickDimensions( +function computeTickDimensions( scale: Scale, tickFormat: TickFormatter, bboxCalculator: BBoxCalculator, From d0ff3c8c8b0f084564cbe231105724ff2837c61c Mon Sep 17 00:00:00 2001 From: rshen91 Date: Wed, 18 Mar 2020 13:55:34 -0600 Subject: [PATCH 25/25] test: move story to axes stories and update vrt --- ...cate-ticks-visually-looks-correct-1-snap.png | Bin 0 -> 17164 bytes ...icks-dates-visually-looks-correct-1-snap.png | Bin 17677 -> 0 bytes .../12_duplicate_ticks.tsx} | 2 +- stories/axes/axes.stories.tsx | 1 + stories/line/line.stories.tsx | 1 - 5 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-duplicate-ticks-visually-looks-correct-1-snap.png delete mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-line-chart-duplicate-ticks-dates-visually-looks-correct-1-snap.png rename stories/{line/10_duplicate_ticks.tsx => axes/12_duplicate_ticks.tsx} (98%) diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-duplicate-ticks-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-axes-duplicate-ticks-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..2d503fd42071d0b733d5d776c3bb0b603cb71f54 GIT binary patch literal 17164 zcmcJ11yq!4+wK^MBEmKZ$*qD4NSBf(I3OV1El48`Lt5yTQY56iW28G20qJH0q(K@a zh8P$)_sh4xKhFPu|2pfOb=GpN(U~`%yzlG2uIury$MVu=$mqyWDAXDB!~04o)X_N< z>WJCN6Y$M^t;qs>9C1*RzKhCjXMkV+M4|8BQE`o49Cp`Hb*esC-5T-v^G}-97cVZq zO?*|d7B(QK_|ZzHAYY_y&mwim@t3fWvN8YqLUhEOW4o(-k4bZcMPSdPl)2fE6cbjh zcXyLhGve9Un^n&~u?-Q;9%b(3dXi){{F(YUMuJI3Z1uBxhoCuX%}-wU2eby& z@;QnT`Mm8A;s_tLM%STTD4+Vr-ACZlFCqCDd{Ey*lfuUh-(U*(&>&|;Zuv|rBLW`= zyw#?p+qpk~>QrrwxLcT+y^o8F`_&PL{r>$sbz@3O3Y)mNu27U58ylO9j11o;PPnI2 z;)ZJXsQZd|TLizfot@oawP%II{9*<7u-7C021xPPvj%uZnZ$&J|h@303Vb zSClX0R*YJUf+wm@XMk_PHwXBd9~=q;B#)gU!Hno zG`U$j-z1lllT&khyV~8mFIdcb(-lRW7d8AQ@7}#T_3Wn3iYNm`i7{^uFZtvP+oGZ( z5%JA&dgG}^R)$@5HRK!i=i=>*7wARN^FlE6BH1*&cpM#WCW8%*jmBnXqRLr zC*OCNX%A(w9>{+%RBZim*ao*aP#}oeFvI>z&(01pq$MLGTbm~yMWGyWZbs+mroD4g zZsnPdv6~P3aU8#~+Q+%iDpir~OIV+`arVX4^5;kPv+`}df4^w7VHZn%5E135 zzGs4Q?EOaZkhJhgLQ?x8)s>C0Q=~x`wpw`M{CBTVT)&R}o$%y2#e0RuBc2v!R=wX` z^mYGTsCQA#WYo zKf;Ldaug476h53AiGv~z-SHzwFlJNJsV3Vu58lGubR*+cqbD>{$S}J{>jFDBW@Jd3 za`uT+N(PB;u&Gp|IW9+1N!7C|S7%+=noY#Ug2_=YJGtTu5>{dwNAk}R*Vg5fbo2uA%-H#{7jN1EyE7@*Bc%+O7;ec+ zRxgqjOvZmS_(=jwNULcfjIHRHvwhhGVe)r9;p^VX?+99QZd)rd9PRv~=R}UEbq^aL zv29^@{MVM#Q;)>pBqMmBDalig!s`wnUl1{7Fy&-R1=SZh_a^cplMn zH~yY-)Ix?ZUFhct6UOTGMi?fV73Pkv^AS-S54&A^w%p(B{A1w1ZaB3Rc1?(u>Aa&S zZM0mmrkr(jD$4a*aY~6ZIG!qM3|&!!k^CG+TI><8Jt<|T<`_MoDDrKUUmjUOBm3g` zT{JF><_ga+#Xq~f+8Wz2WxLg<5Zn;Om{OM`>h)0JiC1DAi(Kc+D~%D1q>OKo^=6=H z8N{e$^EyeJ+7TVe@A_$Tc+*EFi^HkS-S+zA{pSN8I`3$})(wR%*}4^)q#`*$$djmE zl`lxr_?pL0fxKVh$a5_*^T-z-#MZ!9&fkzx|IBc&J9}$1G2UDH(X({5M{!x2U3QVb zH}&!1$12+3s{2UF3hU`}L8iN&TzPYea{m7GMctB3Ig{8M0;1;zk4$pe@GL{HcVm** z#zy&)t2f6dm`dkWCQ(gmg_NN`h`%gyT@!%7Bq(~W@)goRgNq$YM4eNHXD@6By<4J6 z^=9el*_gdVS3>YE3OW0U3kg7|8{@>uTOw1}d%h~3ht)IplYw7{blLSGN$67nYlt}9 zYsw^Y(E;u;2GV&!d zkRtt;X3WJar;nxZz42Ndcw6+l^D6fH7HM^xBY(a`y6xAX zm$7@U;S(b%Q~p$`tgdcMHDR73fKD*8ekQ-5AT%U|#oOEawXd&5XZ)S!rY7D?tSHoP z->I!|4tzsJYhc&(PrfIm5P~j2WVaxcn+$$|#u+tvAAE;2_vn#7W^-|1!a+VwIUyk- zcdPE}*C|I4Q9dSkNHDW4@o`XKWV&HO6^xt-ahFP+V`ky(r!QYF5s1m=YbCp%7*Ep} zG=**qSGZU%{>ZZy>F+U*nV+9OUEPPn=|8eUp~jv|E)V4*@}ZjMCB3aqS?uEoYZ!^h#3=!I>aI+oCww*otR(&0ON~*XYX3i|pb@r^o!|5&i z#NE({2sZS)w{JVAnm*ze3p(aQ)%CifZ#h(!7o0|+%=CM3oIO&-S^~qLJ4Z0?zdUrQ zF6MkVaMkpdEX6H8+@@%;`C@jp*x)!S-m{f3##zNaKk zN{K=Z4$Eqa3f#OI^1{Zh?wDPiRT;@)ROPJ;l zD0WJ%PfE@Hj*;xY^fbcM^LLnYcr1^oQb}E@AH9DCOf6$$`%TA=)UCp4_bq5B&D?Fb z3?$e5?i@h^#G+8tz=v`YB$VB*FuWGtZhK$j)kus})YT38^aPImWMRrBet6z(BsMHv zHX)K#i7q(r3}n#jep#}+;+A!wyFu!nUu3!v@qOv$G!Do+e}NUBcDlCLBc3IEKs^Sn$| zu*b^TVtBk^2uYZ=mO|817VhW|v9#_f(CZ`ACDu!F4pT7ch(Yo-3TtpJe0eGVw*;iiCAyxp zIHRzd)7 zNTPUGQz|p}oqvB$-j&wCqkaS0YKhnQgv}lAOA>MSr3}Oln^tOuT^vmT48k`1%u;2g z8sojmNY__ke%!Qv+^XP;6Wu?vIn5?|oR>%(VT*JDHgcw=w5*+cxSa<((zA!Asj8_nd5fzLt9S(+5NUT923i zcq3|y>A)%pSs+7`Qjv@#y5GZ7v71yJ%Vb%K*PwY>;iG^1XW)4zK#SjgBPJ*1+C`f6j7b>#l2&m*TG*4^~^%Y zfM}uBASXOfOLZ52vUQd0jhCcbr0?rjQH)JlfA@U2uW4Rpc>4>Pb(~AwfTEAkfL-Q8 z1&?C1GAVh#ZEoq{;!V9eWaOV2UZZgon%pISJRe;_b)F${oZ}$d#lD75G+153f_0dO z-1GMr(!nCj@I~i};Ex{#?Iync*TQqnf&vK(!(Dr44tf*wlm!x2Pz6aSnv~RpJAQn^ zYzNuONVMye7dtv>>Fl%8{Qdi5H2SYt*NIaDOU|k)Dup*wA)H^lcu}kLSwd!3R?(BD za}eggcXV{T`UdFoObuaAxh+60K0Zl#eQULAUD?zOw)pbs+Vfm`+9dA^kIaEQu6L02 zp|%yU?7y)?AT-;QK+#%Q&pR+M0L5`Iq=LKm?g^T8{y9B0_31q|$|rYxg>a}I5RM_q>z%&uD(ym9~6 zRnC@yw6lh1F$b=JbZ72uk<+Kd1;svb{-&mBii|nbf8TL9^&kmCdV~MPq`oZmk~!zt z^sKCkG3|Tz>gv5N!kEyd2&!V9EWEkk=x8qP>4EDUIhiEw(KQHAU z=g*%H4Gm3+jHC+<59i?KR#;tK%^8^~`}*}O*EaPZe~b~iDNv}>WmoWwRUUY&*SqU3 zOyzG@&Ron5S__=TzjVhFzElxz9;8;7C@lZTHoR1YCypmU)OAeXE1<&Ma!+H4 zVv?!U>AF(|8=YM?)G-Um#O0Zg%zF0MlrfmEPO#OUF@+5bLzz>E?NFeeA2uc}Vt0!j zP(r{&v*7L zUPleSJ^XevDn?s?UqoJ+APwoqhxD-GQ*3d_SLx}DcRycQp~~yC+4wpMru&YlJk?yB zG{tNjzpbDsnU5cmXXE|s`upZb6#6Zo?0}qI`@jgQF&{2pV}M`x3eK0fHbp|H}J#x>N8F{3cf)G`S6cTEsWs3QHTaB-)G?$vF|C2aDlP61%_G# znIn|nE>?FgvR6{3=LDc{lG~&R`DS2f%Pby`Fb+^$LpM5~mp;1y*ABXr-|6M8~c3s)HnW8u~*4PzWlDlKWAILlN!h*&d$utjP3@4FYCy$6NNXq zfc~1Bo68@tl0l;tm6VjErAbh=3;k0}D&qrW2#`eX&z58LT%D-5E-!fCN3E+wi!Psu zbr*Q@{k@u`zLr*|zdt#rPU+*mzCNw|Cw~ExHZ?Qj(lm1dReJ-#)m$i1-;B6O0$nyZ zV$D96rIusS6XTYYtpCv2xfrUVsg)IOs6)=s2|T=Y>lWQD`zIA~^g!x#8qh|rkIOKY zt)GSX{`sv?=mzuN7-{YSHa)#lbfq%s)cLE|B_#A7Ja{lz=~iIczR@n(mZOn(*SSLX zij@CjsJT2mqof^0V#v;%ajIgbroNiiGZ*RrQ{g+e5N19Z4dvyL+Pi})-QH^-<4X4r z@*0_XRMmA;X5zd@@Od=)9*S=$nX9LG9DcPbnwh0TZHEu?-n*w=Y}Hk0-t$k6(AapA zBxrR*;s~l#gVJi}b`68@gS@J*uJmQ+)~=$R#w(XQI}j6^Oi8q!D8zuNbsf|u{m8y()!(Q9e6o3<==$jpJ@GC zGWd@<%nOtBD*~cqD8IGRWLYIu)lfcuey6o5G=2dG6r9|=(&Q!Jxo^Akvea{Vqsv@~ zS+G(yemn?u%rK|k`?<5czGOQRfy9p`6DVCV(~%eI-vDeO@ps?rI zoDwC*jTG2dM^W({n48vBUJ|B!B#gBr1L9R*96H3)qN5pb?Oe|8J-Ww_A9odoY^eL) z!Cg}QuEr~+6K9YRB40Zh91H0O*5hnakVG&`)!teQ()*1hX5#mSaTIEnY(z3P>0Tfo zCxc)Y5++Us$zmQnc-;`hj2#;@fGqYQkXcf1rE)zGS9b)}IlyQi+wb7chfm88MgqvS ziPc9K$esW#rWO{mK7ZyB-(4)Qu(Go1@jDLJ53^vkQvpXGt-Z&DO%7rdRjY7WQ3Gn- zwDnVhabqAGq(q#KgPi-+F;sjHQspYyR<=MC5lQeHd7&6Qzp#KGf5V)akZ=ds{k8S= zbyK09&m>n+V@l%r_$zHp3~9GszCj}3wW-xoKNeu~IY116okz!G;FJ0&qT7TFVr%$( z&~7mY7mNQ3im^mDo$k|QWEH-N@*@dUv?}tqR==*Th!qd06T~kt1=bNs@Av}z|gF|R! zWTxF@Ly@!uP(v#RHoF=KEg5jqj|aKs>m(s-bhv*1k38cl zrcQFJFDFrES*)6(PQ}$~HM9;;FQObT(G)3Q|07)D5I^BL3J&kwPrU6v-8V8C03V4*71PO*~>c_=5%L5y+ z#U3k65HV+8K=kR236k8`;84r{-JlC2$o5FT*ipOaIbZ z!8-1`&*m3|b70c=RDEYFh#{@zD-bm#w`ux+ani4)yKvdL*N*`9VJURBmA>7_7*UH*_8$I#N9y7 zLCpFVl5C@XH&grTp3&wximJBUZkDuM*(04=nLNDlI87h~J|+i=y*K-}Ce_G9d?+3u zV1!Dtt zD{^xQUlDPd520X|fZ&&D%3QNtzEF7%4| zj&u;3>Svr6e~gDdX7>2~yx3|W@nEmxAY`~rmMiUl)5q4D-HxD;1?b#f(E&TH{1!YY z3tOE@AYdLyOLN@3`D5YvJ3l{&qFixmxryAIoT^e%zSLYg>GSgzoSOOfPn|l2>_iMT z>h_lXx^&h?|AzL3*_Kf@RO_@I+Sbad7om%~Wx3tN{S9fgN^D|ms_<%{Se1!i6&2M+ zqtRwcV1`0j#az^e? zWT;ZwlCa)gBuUwn3J&#hGutR&#XQVl9E^1;)*|@mWG&Lh)-F{s}D?uLJ z->o^&mOR+$h-!;^^Xk?2@n@wYE;kwIgse28I+T@^x>F~Q9y>PV5eG_&32;(7L!Nkp ziRs{EegBATed~kXDHYUj&LG+Hy79+GMv2ICU&CWe3*1;ALW5&H*3h`!ntePL@29_q zzzGmPL38V>eSQof08?bc*>By_R8>`NZfWUxL2`P~BevReSMT?y=4KhS*(33O18$ib zz=yFHjiqNUESP-n%o?b0TzT6?SmBmbeuR~!A2^P(g~T@Mxizd+<5lJ1=}Gm+ADv7Q zT0d&MYaq%CxGd{DxPRYlZK?^gIq=k^WJvI7JAi9$7fHEBOw8wUFLvb- z)_ds%ZWNXPRK-6V#CR%sJi2oC%7$&Yf}kiLs`LjrWw^g>*)%oPg$v(7ECozq?mRvl zDQH;>FU{{-1 zmBDyno*@dxWGsZWfQsLHRCKz##}T*Kq7 zh1R;MAc5Cvy<_hveYev;QE6&?V|<^{!Se#jkp-sDkQTF$Q&Q-$V^2jxv#>K0htsyL zN{QfqI{zzbXj8#YO&TJ_`VyCT0t^^5Pu0!3DC?6xQVw*X;)mTTk5|LJ;#)r*a<_J#vJ()}n-SufubOpsN-oom{&(5OfY5p*C7DLVQ z4eE6Fba(3<=xb=qVrug86hU790K>Y~a|HD$+=&p9^@&D3RuSfMIZ&ZnAG<_{yL7~d zs>to93E=M;;$COMy(0T*8SE=QdZ5Y5D-_6r%KkATPUAD_BOq6uKn>rGQty`br@e$0 zc{b+rwXUwO#@ib$=bpT^GOE8d?4rNRavW7yq1>k`S1_&L5w0)HBgLO+jPFrA-&=F- zG&QQT3o`OXzqV~3ko%4>FU9Itd-8%Q;zhsjx6{fRnmp)v5T~!dZO$*sfO2#|ra>4K zkwQxx*X!4>&9{G#kF=Vw%ZT{o&i5o@8#UH~GPhhBG^F?&j-tYlh`H8u4FOCzJ$E@u z$-U1Iwz<$+g=QaYDwc|!PYrZIjx2wdA31M|-E@F2U*14B_xq=7h>NY0 zogpU|5@kY-odc|S?a`Qqea-AfvL{7gkUdRius zDY5LHlvz_R{gh=v6a<~I2T(@o9}sZw0sCD=>sLIyeOyM zE!R)tm?trWfideEcU)%6?)^w;d}pB8$nZ2$I0Cwfhy1R!zrVi}+M)vCvi3&cAf5GI zyUmUByAK4H8qQH=&Bp7QHXcJc{sCZMdv%f(edo@d@_oXBQ{5S1v#+mCt*x$_hx?cU zEj0V^O>|l)0Y5VRCfX!&A3h-z&l^7VCqb28fxD6Y>9%g_xxX_5sp$g?etB4%|7m+K z(v0NfWam?ZA7Hizhx#WxQm7gF8eSxU=5~E&p{k{E>!N|P0B{UC8ytaZfaLl4l|gLm z1(>}|*nWHV%0|@ao)i>tdZ!OpOl6y<`cR?PK3?dr^Mo^xVGpHB*IOWb!zvhx928lH z=N#Du1yvD9kw+a$n>{YDECg!O2k74tuP(Me;ul#B43+ND}81FiS)3y?C4+>A*s_M(`i=jcXWkr0gjnWGJ-@J~2^9L#dh?DBTB?DowfVAucB~!M#p4$WOofdr-^x3m#G2^dk z1dP7Dm8B@O=wml|`ZUXBWrWZbpchb!m6G}F_Da? z7^i^nne_`cdT`>zLK8=7GtN}q#4|?ew0+T~gSYU3NIj7C`$n<5vEWpy`W*uR{}i+g zm#dqVn0Ohg$~oY@waOhcSlIsj^EQAlgjdq@Sgn_(a2&EO$$Mu*Wr@4f%0&w*c{X5w^0e=%0il_3YuZ?)R6$L#;-fdy zFqTw0uveUL$a5m>q6nlX=7cA)UD1^4^B=zD!K$-_ zeR^BC|JMm6C{!cQjzWZFnFD{?M_3z)iHRFfUh3I@s;m@4$g_Ze07N?>;1{lCWN6_2 zHOoU{Eg|jdx=j+4k(pUOi#-z4_$zTT_o8v#`}W?xIl}XMDmrdb%rlINgKePAo@E@{ zs_4cO62vhct)W^ji}7^iFJDlgcyyy=F;AW(g9ji9SwqZT+X;YUb%(gAsi-ob%noNT zB@*x=ZfnUfP)0^Z2&P4MgWL_lArbZ_`{m1bKVDPlFX)iqs2I+@hmg2DQX>`vhLimio-FA zxlfaezu$b0Frti-lb!kI!cmN;P(H=BU$%cm+0Fwzbg7C94o(~;F1v_$?Unv&i%Qch zFrAxAi?b<-XZ(wkwjvPAkRsD&TfWV`<$te9anvSs#<-y9=Kdqkm=_V^%VA_39cBjc zTP0zDmyKKt6$sMIp2^(R+jozVQuH#q{7mriK`s5ryBFt0$VgR)8<}_=Ai^PonF&*i zo*Ddb)wxy-=-^9M=`XM0h{R8|Ue308v zK8=SmTh7Y+q*m2dhv>AZfV}OZ@p8n?$&r*?aR*(J3dSQD&cozLvP~#|w*$H!vfWW* zs)xp|{;}*V)p^{N8yP#&f(fH2R_S`STbPrgSa>?up)M6-0ATnW4z7{*Z9>?0^C5MMjs$1zh%aY*T@RT7yKkV zmz-Ip1p$g4Z_t3B+x-wtmpBofgWYxZXFW)7*qp;&)Ysp*c%H!m5Fe26hpKPYQlMH$ zMT5p>XsCy1!#T#RScz&R!J=kE4u`FSd;Lj*&=j+x#hqN1vIdQ-czQE?KDa&iIr4(4 zvP@r0$}jH~;{*f*+!Yf*gM%RkJIg;B zSPTtxoDTNd^H-*?;ra!Niy&O2=OvDm87LIkH0CW>J*CkfEQ>0O^X~M(HR|{vA;8Hf z6r}iyt^rw$obgMg8m?(%Ud3*FEz)A@Kt|qY8g8ETXB7v-S;kHovNV>i`GxE`u_*)w zs-8R?{}?jX;ed9fZkJeg=YTvF9FF4_22k~cfM75$H9Vr-KX}&v! zuJMh!yY2{FLZS5TqFw4_SgXdYUjH4|B}6qlt%Somnf4DmcUu%1Xy}X{RmCLEJ$Sw5 z;FMYorQ|(q0Un3=HSt85ahyu<+OC`JeUEl(EL?OS<#*Qc92c4ky1%N|@ z`?2~pdO)iHUUl5v0gslcF%QTFppsaw6T`#9O9*AEjg%Js@i#rax38m>_BGQeL}U;P zA5@Cg>p&r*d}IxUMQdHGM=CY1$wwmt4oO3Y8DY`>wNcsujM7B}p?n2zYxeI>jJrEc zm&JMP8AuQlJYzGTHjj{y(7bvLA@s$I7dk^+fG^a54j(Lc%KDpE@I9 zwwLZKQE~@-H_$-=u^!{}Fj6SJClSFmtUGBfPiTEl;1NO+zAV4g(w3}!$Myh))q9V8 z;rbQSIru~&1SafVM6v)`@c+cG9|A_k|0|>a|CbvN-4-&kvdTbXHa9nKOnp>fzkWTv zZ?e0m2PP*3QZE!H6!J)?3z=nek{j#m=k|A3)E_>4nAI2(7RC-r*=ZWC-j|dT>2Lx; z0sQogpU%P6X8N##6%`f9C@CdZ{#%QMUw&yK>G}N=zDp2(Ri#w;fCb~9UfKsXOv-r+lRI&VN3ANXQrn= ze)jA$h@t}C`yT(Z6y`1n=M7TG>&%=^KZFOxi$ia;Nf1F9WjcQWaqpmRyCAafzdXS4 z|7BkP$0H;EuWvjInXva;!nwnNSOz?~T)8ffpY2NeuQB-1Lj+b7Qo_>l-X4ELC+eI{ z&8e0A_U%dQq2io+UYI8^`*eadi647OIS(SYGI(B3nIlna(=a2r+WWvu*J}rO;MK`S zOOSs!1Dn9e3Z*L4P;hW0^Uli?Y}c-(B_%@YI zcfkuiW#ucNci&>50PAh>O)eIemZy7A7>hIPOs7l&Z#cyw%M^(-tbKHt>SohOz>53+27nXI7vhd<@+@dh{sx@hCRijI16%em|ht-onSK8!Q#b zkP##5V$mGJHkfDphO1(c53$Vr4-zm^9B4DWhYc*hI`(B3$a`R>$DGwKIa&iPOGZu} zGHesUcH7aXp+O34u)jS<&-C^8ueo>%z!B|8{{Io|b}3 zYmE?>-~Y>IYQQa`tP$RjQNlw55*m;uh|KNb;nCaK84oFP0T%N7KtbB!q6XzU7~xcr zQ$64q?gp)Rz8qWkhSGAl+zChmWfheWn;N3?&h~cU%^3hC_;%5ibkJl$!R-U)f&~qR za>UMtXd$3cK#5w`_H%H@!QOrY;%HWBsfNq2Q@B(B-E^C+YWU=wvA&{W0(h7>KvJD- z48pYuk1m#DVYAFZ&82J%2S((h(=D-8{Y$riJ^eQd75u$F+)QiQHw)`AiN0jva-)|&mn>nzKCQ`E6 zB{r%UOgSArJ=NK>2}3p&a)yS6!D8NeHVeipbL&lITDt?X3JN$V-Vg&kx*MJ^Xx`0& z^{Ock&h1@iF`sFVk&%;Ag^u`AGUx8B&$#c)@rwKfR&{z{r=&bRt5$I}`+aaQj)%OC z2SA=p<<74tUqf0cHMOwS@S44Przn_LdN7DLz)&v`G+?k-dzBR~0*?!qH9(;ru(7d0 zJ!5HQB|y!sXLW&!>f)I*XBzyttIei<{i1`5c6N58V5V1LAJIQ;k2V)9s92}>HH1V- z2NX#I41E9?Lv*HGl6QY^}VKr3Trcq!Tu;BIX-8=t!KN@=2wo(uvRzWGe z2sd)JyUh2p;q1Wi(Ewzbb#H$^oo^Sx7?ax{Xt`v0M;3ONP$0uCBU)cW6%`d)1xaF| zqK=Lg{4*P3L*L1)>NWq!3Rmp>ONvV7H?)f=j2p;yi z{a%|eLNIfNw7!b!>Q8TI=6|~h?0Mtp$K?r7#v3w})8GPsFwfB`h~wdC8O>JZUIV>t zsh3)Qe!f50TI!Z=iF-=i!o7~gg>&oM+|Vv=-TqxiDNb~D5r&+62A%-qFCDl` z_{k?HV#9|BKivO*eX;eBsnyTYRio-N!%FJ&ZZHE46ZL)pu&C>J$dj_O7mcb}rP40C zT;waxLjt!lSiuDIr#X3RSSjI zHu5M>p6q;o>E6`B&>r(U`B=gZ`@M>5WX3u8d%NRj8|oKjL^AI0#2^4eX7{DBnp09a7SUnxL!MCmv9VKxtC zQ{(^C#01F6E{hv& z1L6vvuz;=bY`-I`n=k|Ca^Q@~BcPQMf%AOe2^1%dlVptPRb30M=SUnpcm6z_F`6X& zC~J%kYBTKt(?t&0qOt(Ff;TpF*lT=LRbz)QT$pLsi%2jhY|J5CDrRFg;rTJr$h998 zux84^^Hl{gs~q&5>6MigtvfqX+DvhXg$Y~*n7Nej3)Ix9z`~aVRi#1i2?HHosmML6 z4fKfWJ@(~g73M$}B5Q(>&Cm4Rm739|nW44qpRSVu7|_D-Ur%#i7ZOr~CCNzmfe2Gz z=rM}gC!rW6?5%e=feH->xnpUj3jd>ICT<6uV3z;TKe~XIE3)XzRPzq&O3vD-1e^f~ zV7^guKM$ETb$w4&u(Xr{-VrO0lUMD=VfDEyhDWR(Ohk=hfZM-b$D_4Zyd)3jlbA5G zm(blH%{zi|Ob}|jh^Mh1dL0#UpLmTUiNB_w-E4QSnHli{3X_KDl*no;zIp!Yqs~{S z&aHvH14bOD4(9 znf=8=XDg^k7LMkFdjzc%#3b%W#N$ecQC)3Mo<3cGB-C?^Qf#dJM@P@hw5?4~Sfk$B z;?B3>rQuT)6wbs0#P$di5_fP~I(*Iz;tW_*J_7lTc%I=s;I4KPTkD5S3bW%h@$D3G zj8xK0h&c>YUIbr)@-PU_g%jkoV+7esK?J0MF%Tkik$KP6w&+{RFb*}__1rp*w7v=8 zl0d?wevKExSO02_=$ZG7b$53S5@y~7e)xT&cp2oMH4 z+iB_>W_Sr>Bcs8+A0-G#fpx;EUHk~rE)JBnC{G)tO=S*g;7&_q6m_1>Z{ZSM@dT_2 z2k7KMc0+zcd>2$yDe39gbzz(3PKpsDEEbHtf3;qOq{${GrUNg7Bi;RQ9?R{^kdr!G zOn?xO>bCw%a97qEgmETuZdthl6{ZUpSU^64U1&M+?JtN~SIZnT;m5ap;L*{9W6>j% zmiN7#48FHcO*g%&q|{z`6;Ly5BgjB8?H2o}C&Wb4qGU*t*NJ&0rl>}TYu-^rB(7u4 zQ-97;3yxP7TemprE^WD|D6y$#^^G}hj(AAmjPf3fF{!60DduT}T&PanV$xM-552oW zGHNYEUzWQ&{Uk{(E93F?*=IM+cxaEOaRf-+YkI=BF(ASeG>^xui76CYXX#-{C9jTP z(qey-4Y@4F-yusA+B?5J^67FCAWd_TEFJ)Vb{Y9;vkMlV3tug}1c-`NsVt YyxGZJ+SXyl@FNQSK>mL2U4s|@3*rD}X8-^I literal 0 HcmV?d00001 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-line-chart-duplicate-ticks-dates-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-line-chart-duplicate-ticks-dates-visually-looks-correct-1-snap.png deleted file mode 100644 index 3e67a94d96989b58da8b71af4a50b4a1fa024d8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17677 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 z 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