Skip to content

Commit

Permalink
AG-10444 Increase axis ticks on zoom
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobp100 committed Jan 6, 2025
1 parent a6f9151 commit 4bc5241
Show file tree
Hide file tree
Showing 37 changed files with 90 additions and 34 deletions.
43 changes: 31 additions & 12 deletions packages/ag-charts-community/src/chart/axis/axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { AxisLabel } from './axisLabel';
import { AxisLine } from './axisLine';
import { AxisTick, type TickInterval } from './axisTick';
import { AxisTitle } from './axisTitle';
import type { AxisLineDatum } from './axisUtil';
import { type AxisLineDatum, NiceMode } from './axisUtil';

export interface LabelNodeDatum {
tickId: string;
Expand Down Expand Up @@ -474,29 +474,47 @@ export abstract class Axis<
this.animatable = animatable;
}

_niceDomainRange: number = NaN;
_scaleNiceDomainRange: number = NaN;
calculateLayout(initialPrimaryTickCount?: number) {
const { scale, label, visibleRange } = this;
const { scale, label, visibleRange, nice } = this;

const { rotation, parallelFlipRotation, regularFlipRotation } = this.calculateRotations();
const sideFlag = this.label.getSideFlag();

this.updateScale();
const { niceDomain, primaryTickCount, ticks, visibleTicks, fractionDigits, bbox } = this.calculateTickLayout(
this.dataDomain.domain,
visibleRange,
initialPrimaryTickCount
);

const range = findRangeExtent(this.range);

const domain = this.dataDomain.domain;
let tickLayoutDomain: D[] | undefined;
if (visibleRange[0] === 0 && visibleRange[1] === 1) {
this.scale.domain = niceDomain;
} else if (this._niceDomainRange !== range) {
this.scale.domain = this.calculateTickLayout(this.dataDomain.domain, [0, 1]).niceDomain;
tickLayoutDomain = undefined;
} else if (!nice) {
tickLayoutDomain = domain;
} else if (this._scaleNiceDomainRange === range) {
tickLayoutDomain = this.scale.domain;
} else {
tickLayoutDomain = this.calculateTickLayout(domain, NiceMode.TickAndDomain, [0, 1]).niceDomain;
}

let niceMode: NiceMode;
if (!nice) {
niceMode = NiceMode.Off;
} else if (tickLayoutDomain == null) {
niceMode = NiceMode.TickAndDomain;
} else {
niceMode = NiceMode.TicksOnly;
}
const { niceDomain, primaryTickCount, ticks, visibleTicks, fractionDigits, bbox } = this.calculateTickLayout(
tickLayoutDomain ?? domain,
niceMode,
visibleRange,
initialPrimaryTickCount
);

this.scale.domain = niceDomain;

this._niceDomainRange = range;
this._scaleNiceDomainRange = nice ? range : NaN;

const specifier = label.format;
this.labelFormatter =
Expand Down Expand Up @@ -531,6 +549,7 @@ export abstract class Axis<

abstract calculateTickLayout(
domain: D[],
niceMode: NiceMode,
visibleRange: [number, number],
primaryTickCount?: number
): {
Expand Down
44 changes: 34 additions & 10 deletions packages/ag-charts-community/src/chart/axis/axisTickGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { TextSizeProperties } from '../../scene/shape/text';
import { type PlacedLabelDatum, axisLabelsOverlap } from '../../scene/util/labelPlacement';
import { normalizeAngle360, toRadians } from '../../util/angle';
import { arraysEqual } from '../../util/array';
import { countFractionDigits, findMinMax, findRangeExtent, round } from '../../util/number';
import { countFractionDigits, findMinMax, findRangeExtent } from '../../util/number';
import { calculateNiceSecondaryAxis } from '../../util/secondaryAxisTicks';
import { createIdsGenerator } from '../../util/tempUtils';
import { CachedTextMeasurerPool, TextUtils } from '../../util/textMeasurer';
Expand All @@ -16,6 +16,7 @@ import { calculateLabelRotation, createLabelData, getLabelSpacing, getTextAlign,
import type { AxisInterval } from './axisInterval';
import type { TickInterval } from './axisTick';
import type { TickDatum } from './axisUtil';
import { NiceMode } from './axisUtil';

export interface TickData<D = any> {
rawTicks: D[];
Expand All @@ -30,6 +31,7 @@ export interface TickGenerationParams<D = any> {
domain: D[];
primaryTickCount: number | undefined;
visibleRange: [number, number];
niceMode: NiceMode;
parallelFlipRotation: number;
regularFlipRotation: number;
labelX: number;
Expand Down Expand Up @@ -81,9 +83,9 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {
constructor(private readonly axis: IAxis<S, D>) {}

private estimateTickCount(visibleRange: [number, number], minSpacing: number, maxSpacing: number) {
const rangeWithBleed = round(findRangeExtent(this.axis.range) / findRangeExtent(visibleRange), 2);
return estimateTickCount(
rangeWithBleed,
findRangeExtent(this.axis.range),
findRangeExtent(visibleRange),
minSpacing,
maxSpacing,
ContinuousScale.defaultTickCount,
Expand All @@ -102,6 +104,7 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {
domain,
primaryTickCount,
visibleRange,
niceMode,
parallelFlipRotation,
regularFlipRotation,
labelX,
Expand Down Expand Up @@ -163,7 +166,7 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {
autoRotation = 0;
textAlign = getTextAlign(parallel, configuredRotation, 0, sideFlag, regularFlipFlag);

const tickStrategies = this.getTickStrategies({ domain, secondaryAxis, index });
const tickStrategies = this.getTickStrategies({ domain, niceMode, secondaryAxis, index });

for (const strategy of tickStrategies) {
({ tickData, index, autoRotation, terminate } = strategy({
Expand Down Expand Up @@ -198,10 +201,12 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {

private getTickStrategies({
domain,
niceMode,
index: iteration,
secondaryAxis,
}: {
domain: D[];
niceMode: NiceMode;
index: number;
secondaryAxis: boolean;
}): TickStrategy[] {
Expand Down Expand Up @@ -231,7 +236,16 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {
visibleRange,
terminate,
}: TickStrategyParams) =>
this.createTickData(domain, tickGenerationType, index, tickData, terminate, primaryTickCount, visibleRange);
this.createTickData(
domain,
niceMode,
tickGenerationType,
index,
tickData,
terminate,
primaryTickCount,
visibleRange
);

strategies.push(tickGenerationStrategy);

Expand All @@ -245,6 +259,7 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {
}: TickStrategyParams) =>
this.createTickData(
domain,
niceMode,
TickGenerationType.FILTER,
index,
tickData,
Expand All @@ -270,6 +285,7 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {

private createTickData(
domain: D[],
niceMode: NiceMode,
tickGenerationType: TickGenerationType,
index: number,
tickData: TickData,
Expand Down Expand Up @@ -297,6 +313,7 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {

tickData = this.getTicks({
domain,
niceMode,
tickGenerationType,
previousTicks,
minTickCount,
Expand All @@ -317,6 +334,7 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {

private getTicks({
domain,
niceMode,
tickGenerationType,
previousTicks,
tickCount,
Expand All @@ -325,6 +343,7 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {
primaryTickCount,
}: {
domain: D[];
niceMode: NiceMode;
tickGenerationType: TickGenerationType;
previousTicks: TickDatum[];
tickCount: number;
Expand All @@ -333,18 +352,23 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {
primaryTickCount?: number;
}): TickData {
const { axis } = this;
const { nice, range, scale, visibleRange, interval } = axis;
const { range, scale, visibleRange, interval } = axis;
const idGenerator = createIdsGenerator();

const tickParams: ScaleTickParams<any> = {
nice,
const domainParams: ScaleTickParams<any> = {
nice: niceMode === NiceMode.TickAndDomain,
interval: interval.step,
tickCount,
minTickCount,
maxTickCount,
};

let niceDomain = nice ? scale.niceDomain(tickParams, domain) : domain;
const tickParams = {
...domainParams,
nice: niceMode === NiceMode.TickAndDomain || niceMode === NiceMode.TicksOnly,
};

let niceDomain = niceMode === NiceMode.TickAndDomain ? scale.niceDomain(domainParams, domain) : domain;

let rawTicks: any[];

Expand Down Expand Up @@ -373,7 +397,7 @@ export class AxisTickGenerator<S extends Scale<D, number, TickInterval<S>>, D> {
niceDomain = secondaryAxisTicks.domain.map((d) => scaleStopTsComplaining.toDomain(d));
} else {
// AG-10654 Just use normal ticks for categorical axes.
rawTicks = scaleStopTsComplaining.ticks?.(tickParams, niceDomain, visibleRange) ?? [];
rawTicks = scaleStopTsComplaining.ticks(tickParams, niceDomain, visibleRange) ?? [];
}
break;
case TickGenerationType.FILTER:
Expand Down
13 changes: 7 additions & 6 deletions packages/ag-charts-community/src/chart/axis/axisTicks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Selection } from '../../scene/selection';
import { Text } from '../../scene/shape/text';
import { formatValue } from '../../util/format.util';
import { createId } from '../../util/id';
import { countFractionDigits, findMinMax, findRangeExtent, round } from '../../util/number';
import { countFractionDigits, findMinMax, findRangeExtent } from '../../util/number';
import { createIdsGenerator } from '../../util/tempUtils';
import { CachedTextMeasurerPool } from '../../util/textMeasurer';
import { estimateTickCount } from '../../util/ticks';
Expand Down Expand Up @@ -50,7 +50,7 @@ interface LabelNodeDatum {

export class AxisTicks {
static readonly DefaultTickCount = 5;
static readonly DefaultMinSpacing = 50;
static readonly DefaultMinSpacing = 10;

readonly id = createId(this);

Expand Down Expand Up @@ -146,17 +146,17 @@ export class AxisTicks {

private generateTicks() {
const { minSpacing, maxSpacing } = this.interval;
const extentWithBleed = round(findRangeExtent(this.scale.range), 2);
const { maxTickCount, minTickCount, tickCount } = estimateTickCount(
extentWithBleed,
findRangeExtent(this.scale.range),
1,
minSpacing,
maxSpacing,
AxisTicks.DefaultTickCount,
AxisTicks.DefaultMinSpacing
);

const tickData = this.getTicksData({
nice: false,
nice: true,
interval: this.interval.step,
tickCount,
minTickCount,
Expand All @@ -182,7 +182,8 @@ export class AxisTicks {

private getTicksData(tickParams: ScaleTickParams<any>) {
const ticks: TickDatum[] = [];
const rawTicks = this.scale.ticks(tickParams);
const niceDomain = tickParams.nice ? this.scale.niceDomain(tickParams) : this.scale.domain;
const rawTicks = this.scale.ticks(tickParams, niceDomain);
const fractionDigits = rawTicks.reduce((max, tick) => Math.max(max, countFractionDigits(tick)), 0);
const idGenerator = createIdsGenerator();

Expand Down
6 changes: 6 additions & 0 deletions packages/ag-charts-community/src/chart/axis/axisUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import type { RotatableText, TransformableText } from '../../scene/shape/text';
import type { TranslatableType } from '../../scene/transformable';
import { findMinMax } from '../../util/number';

export enum NiceMode {
TickAndDomain,
TicksOnly,
Off,
}

export interface TickDatum {
tickLabel: string;
tick: any;
Expand Down
4 changes: 3 additions & 1 deletion packages/ag-charts-community/src/chart/axis/cartesianAxis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ChartAxisDirection } from '../chartAxisDirection';
import type { AnimationManager } from '../interaction/animationManager';
import { Axis, AxisGroupZIndexMap, type LabelNodeDatum, TranslatableLine } from './axis';
import { AxisTickGenerator, type TickGenerationResult } from './axisTickGenerator';
import type { AxisLabelDatum, TickDatum } from './axisUtil';
import type { AxisLabelDatum, NiceMode, TickDatum } from './axisUtil';
import {
prepareAxisAnimationContext,
prepareAxisAnimationFunctions,
Expand Down Expand Up @@ -161,6 +161,7 @@ export abstract class CartesianAxis<S extends Scale<D, number, any> = Scale<any,

override calculateTickLayout(
domain: D[],
niceMode: NiceMode,
visibleRange: [number, number],
initialPrimaryTickCount?: number
): {
Expand All @@ -178,6 +179,7 @@ export abstract class CartesianAxis<S extends Scale<D, number, any> = Scale<any,

const tickGenerationResult = this.tickGenerator.generateTicks({
domain,
niceMode,
visibleRange,
primaryTickCount: initialPrimaryTickCount,
parallelFlipRotation,
Expand Down
10 changes: 6 additions & 4 deletions packages/ag-charts-community/src/util/ticks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Logger } from './logger';
import { clamp, countFractionDigits } from './number';
import { clamp, countFractionDigits, round } from './number';
import { numberFormat, parseFormat } from './numberFormat';
import { day } from './time/day';
import {
Expand Down Expand Up @@ -227,18 +227,20 @@ export function niceTicksDomain(start: number, end: number) {

export function estimateTickCount(
rangeExtent: number,
zoomExtent: number,
minSpacing: number,
maxSpacing: number,
defaultTickCount: number,
defaultMinSpacing: number
) {
const zoomedRangeExtent = round(rangeExtent / zoomExtent, 2);
defaultMinSpacing = Math.max(defaultMinSpacing, rangeExtent / (defaultTickCount + 1));

if (isNaN(minSpacing)) {
minSpacing = defaultMinSpacing;
}
if (isNaN(maxSpacing)) {
maxSpacing = rangeExtent;
maxSpacing = 2 * minSpacing;
}
if (minSpacing > maxSpacing) {
if (minSpacing === defaultMinSpacing) {
Expand All @@ -248,8 +250,8 @@ export function estimateTickCount(
}
}

const maxTickCount = Math.max(1, Math.floor(rangeExtent / minSpacing));
const minTickCount = Math.min(maxTickCount, Math.ceil(rangeExtent / maxSpacing));
const maxTickCount = Math.max(1, Math.floor(zoomedRangeExtent / minSpacing));
const minTickCount = Math.min(maxTickCount, Math.ceil(zoomedRangeExtent / maxSpacing));
const tickCount = clamp(minTickCount, defaultTickCount, maxTickCount);

return { minTickCount, maxTickCount, tickCount };
Expand Down
2 changes: 2 additions & 0 deletions packages/ag-charts-enterprise/src/axes/radius/radiusAxis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export abstract class RadiusAxis<

override calculateTickLayout(
domain: D[],
niceMode: _ModuleSupport.NiceMode,
_visibleRange: [number, number],
initialPrimaryTickCount?: number
): {
Expand All @@ -107,6 +108,7 @@ export abstract class RadiusAxis<

const tickGenerationResult = this.tickGenerator.generateTicks({
domain,
niceMode,
visibleRange,
primaryTickCount: initialPrimaryTickCount,
parallelFlipRotation,
Expand Down
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.
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 @@ -23,7 +23,7 @@ export const GradientLegendModule: _ModuleSupport.LegendModule = {
fontFamily: { ref: 'fontFamily' },
},
interval: {
minSpacing: 1,
minSpacing: 15,
},
},
gradient: {
Expand Down

0 comments on commit 4bc5241

Please sign in to comment.