From e8c89ec0588f829acdbdf169a223f96dffb067a2 Mon Sep 17 00:00:00 2001 From: Rachel Shen Date: Wed, 18 Mar 2020 14:25:11 -0600 Subject: [PATCH] feat: remove duplicate tick labels from axis (#577) Closes #445 --- ...te-ticks-visually-looks-correct-1-snap.png | Bin 0 -> 17164 bytes src/chart_types/xy_chart/state/utils.ts | 9 +- .../xy_chart/utils/axis_utils.test.ts | 92 +++++++++++++++++- src/chart_types/xy_chart/utils/axis_utils.ts | 25 +++-- src/chart_types/xy_chart/utils/specs.ts | 3 + src/utils/commons.ts | 22 +++++ stories/axes/12_duplicate_ticks.tsx | 65 +++++++++++++ stories/axes/axes.stories.tsx | 1 + stories/interactions/11_brush_time.tsx | 10 +- 9 files changed, 214 insertions(+), 13 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 create mode 100644 stories/axes/12_duplicate_ticks.tsx 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 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 YyxGZJ+SXyl@FNQSK>mL2U4s|@3*rD}X8-^I literal 0 HcmV?d00001 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.test.ts b/src/chart_types/xy_chart/utils/axis_utils.test.ts index b736577218..60beaf4710 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, + enableDuplicatedTicks, } from './axis_utils'; import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { SvgTextBBoxCalculator } from '../../../utils/bbox/svg_text_bbox_calculator'; @@ -55,6 +56,9 @@ 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'; +import moment from 'moment-timezone'; describe('Axis computational utils', () => { const mockedRect = { @@ -202,7 +206,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' }; @@ -1436,4 +1440,88 @@ 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 = moment.duration(1, 'day'); + const formatter = niceTimeFormatter([now, oneDay.add(now).asMilliseconds() * 31]); + const axisSpec: AxisSpec = { + id: 'bottom', + position: 'bottom', + showDuplicatedTicks: 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] }); + const offset = 0; + const tickFormatOption = { timeZone: 'utc+1' }; + 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 }, + { 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', () => { + 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 axisSpec: AxisSpec = { + id: 'bottom', + position: 'bottom', + showDuplicatedTicks: 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: 'utc+1' }; + 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 }, + { value: 1547337600000, label: '2019-01-13', position: 206.19583333333333 }, + { value: 1547380800000, label: '2019-01-13', position: 266.54583333333335 }, + { value: 1547424000000, label: '2019-01-14', position: 326.8958333333333 }, + { value: 1547467200000, label: '2019-01-14', position: 387.24583333333334 }, + { value: 1547510400000, label: '2019-01-15', position: 447.59583333333336 }, + { value: 1547553600000, label: '2019-01-15', position: 507.9458333333333 }, + { value: 1547596800000, label: '2019-01-16', position: 568.2958333333333 }, + ]); + }); }); diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index eb3a9a4267..d14fc274e2 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'; @@ -87,7 +87,6 @@ export function computeAxisTicksDimensions( if (axisSpec.hide) { return null; } - const scale = getScaleForAxisSpec( axisSpec, xDomain, @@ -238,11 +237,9 @@ function computeTickDimensions( const tickLabels = tickValues.map((d) => { return tickFormat(d, tickFormatOptions); }); - const { tickLabelStyle: { fontFamily, fontSize }, } = axisConfig; - const { maxLabelBboxWidth, maxLabelBboxHeight, @@ -252,7 +249,6 @@ function computeTickDimensions( getMaxBboxDimensions(bboxCalculator, fontSize, fontFamily, tickLabelRotation, tickLabelPadding), { maxLabelBboxWidth: 0, maxLabelBboxHeight: 0, maxLabelTextWidth: 0, maxLabelTextHeight: 0 }, ); - return { tickValues, tickLabels, @@ -444,14 +440,31 @@ export function getAvailableTicks( return [firstTick, lastTick]; } - return ticks.map((tick) => { + return enableDuplicatedTicks(axisSpec, scale, offset, tickFormatOptions); +} + +/** @internal */ +export function enableDuplicatedTicks( + axisSpec: AxisSpec, + scale: Scale, + offset: number, + tickFormatOptions?: TickFormatterOptions, +) { + const ticks = scale.ticks(); + const allTicks: AxisTick[] = ticks.map((tick) => { return { value: tick, label: axisSpec.tickFormat(tick, tickFormatOptions), position: scale.scale(tick) + offset, }; }); + + if (axisSpec.showDuplicatedTicks === true) { + return allTicks; + } + return getUniqueValues(allTicks, 'label'); } + 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/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index dd91b2f78f..5c8ce8ba3c 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -525,11 +525,14 @@ export interface AxisSpec extends Spec { style?: AxisStyle; /** Show only integar values **/ integersOnly?: boolean; + /** Remove duplicate ticks, default is false*/ + showDuplicatedTicks?: boolean; } export type TickFormatterOptions = { timeZone?: string; }; + export type TickFormatter = (value: any, options?: TickFormatterOptions) => string; export interface AxisStyle { 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; diff --git a/stories/axes/12_duplicate_ticks.tsx b/stories/axes/12_duplicate_ticks.tsx new file mode 100644 index 0000000000..d5acbc0228 --- /dev/null +++ b/stories/axes/12_duplicate_ticks.tsx @@ -0,0 +1,65 @@ +/* + * 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'; +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 = 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 ( + + + `${Number(d).toFixed(1)}`} + /> + + + ); +}; diff --git a/stories/axes/axes.stories.tsx b/stories/axes/axes.stories.tsx index 91ee8795b3..17ea145000 100644 --- a/stories/axes/axes.stories.tsx +++ b/stories/axes/axes.stories.tsx @@ -36,3 +36,4 @@ export { example as customDomain } from './8_custom_domain'; export { example as customMixed } from './9_custom_mixed_domain'; export { example as oneDomainBound } from './10_one_domain_bound'; export { example as fitDomain } from './11_fit_domain_extent'; +export { example as duplicateTicks } from './12_duplicate_ticks'; diff --git a/stories/interactions/11_brush_time.tsx b/stories/interactions/11_brush_time.tsx index 2eb74384ae..e2b72cc470 100644 --- a/stories/interactions/11_brush_time.tsx +++ b/stories/interactions/11_brush_time.tsx @@ -23,13 +23,17 @@ 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 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: now + oneDay, y: 7 }, - { x: now + oneDay * 2, y: 3 }, + { x: oneDays.add(now).asMilliseconds(), y: 7 }, + { x: twoDays.add(now).asMilliseconds(), y: 3 }, { x: now + oneDay * 5, y: 6 }, ]} />