Skip to content

Commit

Permalink
fix(axis): ticks generation for linear scale on bar charts (#1645)
Browse files Browse the repository at this point in the history
Reduce the number of ticks generated when rendering bar charts using a linear scale. It will render only a set of ticks at nicer rounded intervals
  • Loading branch information
markov00 authored Apr 14, 2022
1 parent 128114c commit 65d0e7d
Show file tree
Hide file tree
Showing 21 changed files with 69 additions and 20 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const e5 = Math.sqrt(10);
const e2 = Math.sqrt(2);

/** @internal */
export function getLinearTicks(start: number, stop: number, count: number, base: number = 2) {
export function getLinearTicks(start: number, stop: number, count: number, base: number = 2): number[] {
let reverse,
i = -1,
n,
Expand Down
57 changes: 40 additions & 17 deletions packages/charts/src/scales/scale_continuous.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,22 @@ export class ScaleContinuous implements Scale<number> {
const nicePaddedDomain = isPixelPadded && isNice ? (d3Scale.domain() as number[]) : paddedDomain;

this.tickValues =
// This case is for the xScale (minInterval is > 0) when we want to show bars (bandwidth > 0)
// we want to avoid displaying inner ticks between bars in a bar chart when using linear x scale
type === ScaleType.Time
? getTimeTicks(scaleOptions.desiredTickCount, scaleOptions.timeZone, nicePaddedDomain)
: scaleOptions.minInterval <= 0 || scaleOptions.bandwidth <= 0
? this.type === ScaleType.Linear
? getLinearTicks(
nicePaddedDomain[0],
nicePaddedDomain[nicePaddedDomain.length - 1],
scaleOptions.desiredTickCount,
this.linearBase,
)
: (d3Scale as D3ScaleNonTime).ticks(scaleOptions.desiredTickCount)
: new Array(Math.floor((nicePaddedDomain[1] - nicePaddedDomain[0]) / minInterval) + 1)
.fill(0)
.map((_, i) => nicePaddedDomain[0] + i * minInterval);
? getTimeTicks(
nicePaddedDomain,
scaleOptions.desiredTickCount,
scaleOptions.timeZone,
scaleOptions.bandwidth === 0 ? 0 : scaleOptions.minInterval,
)
: type === ScaleType.Linear
? getLinearNonDenserTicks(
nicePaddedDomain,
scaleOptions.desiredTickCount,
this.linearBase,
scaleOptions.bandwidth === 0 ? 0 : scaleOptions.minInterval,
)
: (d3Scale as D3ScaleNonTime).ticks(scaleOptions.desiredTickCount);

this.domain = nicePaddedDomain;
this.project = (d: number) => d3Scale(d);
this.inverseProject = (d: number) => d3Scale.invert(d);
Expand Down Expand Up @@ -243,14 +243,20 @@ export class ScaleContinuous implements Scale<number> {
handleDomainPadding() {}
}

function getTimeTicks(desiredTickCount: number, timeZone: string, domain: number[]) {
function getTimeTicks(domain: number[], desiredTickCount: number, timeZone: string, minInterval: number) {
const startDomain = getMomentWithTz(domain[0], timeZone);
const endDomain = getMomentWithTz(domain[1], timeZone);
const offset = startDomain.utcOffset();
const shiftedDomainMin = startDomain.add(offset, 'minutes').valueOf();
const shiftedDomainMax = endDomain.add(offset, 'minutes').valueOf();
const tzShiftedScale = scaleUtc().domain([shiftedDomainMin, shiftedDomainMax]);
const rawTicks = tzShiftedScale.ticks(desiredTickCount);
let currentCount = desiredTickCount;
let rawTicks = tzShiftedScale.ticks(desiredTickCount);
while (rawTicks.length > 2 && currentCount > 0 && rawTicks[1].valueOf() - rawTicks[0].valueOf() < minInterval) {
currentCount--;
rawTicks = tzShiftedScale.ticks(currentCount);
}

const timePerTick = (shiftedDomainMax - shiftedDomainMin) / rawTicks.length;
const hasHourTicks = timePerTick < 1000 * 60 * 60 * 12;
return rawTicks.map((d: Date) => {
Expand All @@ -260,6 +266,23 @@ function getTimeTicks(desiredTickCount: number, timeZone: string, domain: number
});
}

function getLinearNonDenserTicks(
domain: number[],
desiredTickCount: number,
base: number = 2,
minInterval: number,
): number[] {
const start = domain[0];
const stop = domain[domain.length - 1];
let currentCount = desiredTickCount;
let ticks = getLinearTicks(start, stop, desiredTickCount, base);
while (ticks.length > 2 && currentCount > 0 && ticks[1] - ticks[0] < minInterval) {
currentCount--;
ticks = getLinearTicks(start, stop, currentCount, base);
}
return ticks;
}

function isDegenerateDomain(domain: unknown[]): boolean {
return domain.every((v) => v === domain[0]);
}
Expand Down
26 changes: 26 additions & 0 deletions packages/charts/src/scales/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { getLinearTicks } from '../chart_types/xy_chart/utils/get_linear_ticks';

/** @internal */
export function getLinearNonDenserTicks(
start: number,
stop: number,
count: number,
base: number = 2,
minInterval: number,
): number[] {
let currentCount = count;
let ticks = getLinearTicks(start, stop, count, base);
while (ticks.length > 2 && currentCount > 0 && ticks[1] - ticks[0] < minInterval) {
currentCount--;
ticks = getLinearTicks(start, stop, currentCount, base);
}
return ticks;
}
2 changes: 1 addition & 1 deletion storybook/stories/bar/44_test_single_histogram.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const Example = () => {
<Series
id="series"
timeZone="local"
xScaleType={multiLayerAxis ? ScaleType.Time : ScaleType.Linear}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
Expand Down
2 changes: 1 addition & 1 deletion storybook/stories/interactions/17_png_export.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function renderXYAxisChart() {

<BarSeries
id="series bars chart"
xScaleType={ScaleType.Linear}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
Expand Down

0 comments on commit 65d0e7d

Please sign in to comment.