Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better symlog scale ticks #2245

Merged
merged 24 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6b3376f
better symlog scale ticks
cab404 Sep 1, 2023
3bc762d
Fix for rounding quirks
cab404 Sep 1, 2023
ee94aca
changeset
cab404 Sep 1, 2023
8eb90c6
stop using lodash round
cab404 Sep 1, 2023
efb58e7
bye lodash
cab404 Sep 1, 2023
bcf1f28
better name for a hacky function
cab404 Sep 1, 2023
9580fc4
apparently it was caused by lodash.range?
cab404 Sep 1, 2023
d3d79ac
Update packages/components/src/lib/d3/patchedScales.ts
cab404 Sep 2, 2023
257d34f
zero check for tick count
cab404 Sep 3, 2023
b63e1e4
remove redundant constant null check
cab404 Sep 3, 2023
5341326
better shifts for off-zero ranges, better name for expShift
cab404 Sep 3, 2023
0cbcb1e
===
cab404 Sep 3, 2023
beef455
now prettifying to significant digit in 1/2/5
cab404 Sep 5, 2023
86905a6
fall back to linear if exponent window is too small
cab404 Sep 10, 2023
96e1896
better threshold
cab404 Sep 10, 2023
c85d178
remove upper clipping
cab404 Sep 10, 2023
3992136
renicing lower and upper on main track
cab404 Sep 11, 2023
ab7d814
prettier lower/upper bounds on linear mode
cab404 Sep 11, 2023
91c02d0
rounding correctly everywhere (and a nicer function for 1/2/5 selection)
cab404 Sep 11, 2023
fdd2e6b
more rounding fixes
cab404 Sep 11, 2023
834bcd3
weird midnight typo, I guess?
cab404 Sep 11, 2023
bc49c72
detached digit limiting proved itself worthless on weird intervals
cab404 Sep 11, 2023
267d47a
more weird mistakes
cab404 Sep 11, 2023
2720d9b
Merge remote-tracking branch 'origin/main' into cab/symlog-ticks
cab404 Sep 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pretty-bulldogs-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@quri/squiggle-components": minor
---

Better symlog scale ticks
96 changes: 95 additions & 1 deletion packages/components/src/lib/d3/patchedScales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,101 @@ function patchLinearishTickFormat<
}

function patchSymlogTickFormat(scale: ScaleSymLog): ScaleSymLog {
return patchLinearishTickFormat(scale);
// copy-pasted from https://github.com/d3/d3-scale/blob/83555bd759c7314420bd4240642beda5e258db9e/src/linear.js#L14
scale.tickFormat = (count, specifier) => {
const d = scale.domain();
return tickFormatWithCustom(d[0], d[d.length - 1], count ?? 10, specifier);
};
// UPSTREAM-ME: Patching symlog tick generator for better experience
scale.ticks = (count?: number) => {
if (count === 0) return [];

const [lower, upper] = scale.domain();
const c = scale.constant();

// We can't reliably use invert and transform from scale
// It converts relative to screen, and we would instead like it to be relative to 0
// const transform = scale
// const invert = scale.invert

function transform(x: number): number {
return Math.sign(x) * Math.log1p(Math.abs(x / c));
}

function invert(x: number): number {
return Math.sign(x) * Math.expm1(Math.abs(x)) * c;
}

/**
* @param [rounding=Math.round] Rounding method
* @returns Closest number with a single significant digit being 1, 2 or 5
cab404 marked this conversation as resolved.
Show resolved Hide resolved
*/
function roundToNice(x: number, rounding = Math.round): number {
if (x === 0) return 0;

const base = Math.floor(Math.log10(Math.abs(x)));
const zeros = Math.pow(10, base);
const mult = Math.abs(rounding(x / zeros));

// https://oeis.org/A002522
// It works for our range.
const niceMult =
mult == 0
? 0
: Math.pow(
Math.abs(rounding(Math.sign(x) * Math.sqrt(mult - 1))),
2
) + 1;

// There's also https://oeis.org/A051109, but I don't have whole day

return niceMult * zeros * Math.sign(x);
}

function closestNice(x: number) {
return roundToNice(x);
}

const normCount = count ?? 10;

// Sometimes users don't know what they want -- and they actually don't want symlog
const expSize = Math.abs(transform(lower) - transform(upper));

// If exponent window is too small, then let's try linear scale instead
if (expSize / normCount < 0.5) {
const reqPrecision = Math.pow(
10,
Math.floor(Math.log10(upper - lower) - 1)
);
const pLower = Math.ceil(lower / reqPrecision) * reqPrecision;
const pUpper = Math.floor(upper / reqPrecision) * reqPrecision;

const linSize = pUpper - pLower;

// Alternative linear route.
const digits = Math.floor(Math.log10(linSize));
let tickNumber = linSize / Math.pow(10, digits);
while (tickNumber * 1.5 < normCount) tickNumber *= 2;
return d3.range(pLower, pUpper, linSize / tickNumber).concat([pUpper]);
}

const tLower = transform(roundToNice(lower, Math.ceil));
const tUpper = transform(roundToNice(upper, Math.floor));
const expStep = (tUpper - tLower) / normCount;
const tLowerAdjusted = !(tUpper > 0 && tLower < 0)
? tLower + expStep / 2
: Math.ceil(tLower / expStep) * expStep;
const tickRange = d3.range(tLowerAdjusted, tUpper, expStep);

const ticks = [roundToNice(lower, Math.ceil)].concat(
tickRange.map(invert).map(closestNice),
[roundToNice(upper, Math.floor)]
);

return ticks;
};

return scale;
}

function patchLogarithmicTickFormat(scale: ScaleLogarithmic): ScaleLogarithmic {
Expand Down