Skip to content

Commit 930c8fa

Browse files
feat(logAxis): respect splitNumber for asinh/symlog tick density
logMappingCalcNiceTicks now accepts an optional splitNumber (default 5). When the number of power-of-base candidates exceeds the requested tick count, the function computes a stride k and uses base^k as the effective step between ticks. This keeps tick density manageable for small bases like 2, where the raw candidate count can be very large over wide data ranges. The splitNumber is threaded from the axis model through calcNiceForIntervalOrLogScale, consistent with how interval and standard log scales already work.
1 parent ce63b0a commit 930c8fa

3 files changed

Lines changed: 41 additions & 13 deletions

File tree

src/coord/axisNiceTicks.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function calcNiceForIntervalOrLogScale(
5959

6060
// For mapped-log axes (asinh/symlog), use the raw-space tick strategy.
6161
if (isTargetLogScale && (scale as LogScale).logMapping) {
62-
logMappingCalcNiceTicks(scale as LogScale);
62+
logMappingCalcNiceTicks(scale as LogScale, opt.splitNumber);
6363
return;
6464
}
6565

@@ -219,22 +219,41 @@ function logScaleCalcNiceTicks(
219219
* then set `intervalStub` extent to the transformed range so that
220220
* `normalize`/`scale` pixel mapping remains correct.
221221
*/
222-
export function logMappingCalcNiceTicks(scale: LogScale): void {
222+
export function logMappingCalcNiceTicks(
223+
scale: LogScale, splitNumber?: number | NullUndefined
224+
): void {
223225
const base = scale.base;
224226
const a0 = scale.linearWidth || 1;
225227
const [rawMin, rawMax] = scale.getExtent();
228+
const maxTicks = ensureValidSplitNumber(splitNumber, 5) + 1;
226229

227230
const forward = scale.logMapping === 'asinh'
228231
? (v: number) => asinhScaleForwardTick(v, a0)
229232
: (v: number) => symlogScaleForwardTick(v, a0);
230233

231-
// Candidates: 0, ±a0, ±b*a0, ±b^2*a0, ...
234+
// Count how many powers of `base` span the extent so we can decide
235+
// whether to step by base^1, base^2, … to stay within `splitNumber`.
232236
const absMax = Math.max(Math.abs(rawMin), Math.abs(rawMax));
237+
const totalSteps = absMax > a0 ? Math.ceil(Math.log(absMax / a0) / Math.log(base)) : 0;
238+
// Account for both positive and negative sides plus zero.
239+
const hasNeg = rawMin < 0;
240+
const hasPos = rawMax > 0;
241+
const sidesMultiplier = (hasNeg && hasPos) ? 2 : 1;
242+
const estimatedTicks = totalSteps * sidesMultiplier + 1; // +1 for zero
243+
244+
// Raise the effective base so tick count stays within splitNumber.
245+
let stride = 1;
246+
if (estimatedTicks > maxTicks && totalSteps > 0) {
247+
stride = Math.ceil(totalSteps * sidesMultiplier / (maxTicks - 1));
248+
}
249+
const effectiveBase = Math.pow(base, stride);
250+
251+
// Candidates: 0, ±a0, ±a0·effectiveBase, ±a0·effectiveBase², ...
233252
const candidates: number[] = [0];
234253
let v = a0;
235254
while (v <= absMax * 1.0001) {
236255
candidates.push(v, -v);
237-
v *= base;
256+
v *= effectiveBase;
238257
}
239258

240259
// Filter to data extent and sort ascending.

test/log-mapping.html

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/ut/spec/scale/log.test.ts

Lines changed: 17 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)