Conversation
…rlap Recharts' own "nice" tick candidates aren't always uniformly spaced, and its minTickGap-based overlap thinning drops individual candidates without re-spacing the survivors - producing axes like 0/300/1k (gaps of 300 then 700) instead of 0/250/500/750/1k. Feed it a uniform candidate set instead: thinning a uniformly-spaced sequence for overlap always keeps every Nth candidate, so whatever survives stays evenly spaced. Tick count behavior is unchanged - this only changes which values are chosen, not how many survive. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 3e7dc0e The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@arj22 is attempting to deploy a commit to the HyperDX Team on Vercel. A member of the Team first needs to authorize it. |
|
Deep Review✅ No critical issues found. This PR is well-iterated (26 commits, 22 prior review rounds) and the previous-comments reviewer confirms every prior substantive thread — stacked-bar total clipping, 2.5-step density, the 🟡 P2 — recommended
🔵 P3 nitpicks (4)
Reviewers (10): correctness, adversarial, testing, maintainability, project-standards, performance, kieran-typescript, previous-comments, agent-native, learnings-researcher. Testing gaps:
No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond |
PR Review
5 finding(s): 🔴 0 critical · 🟠 0 major · 🔵 5 minor 5 posted as inline comment(s) on the changed lines. Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides. |
Three review findings addressed: Critical: the new default-domain max was computed from the largest individual series value, but stacked bars sum at each timestamp (all share stackId="1") - a 60+40 stack reached 100 but got a domain top of 63. Stacked bars now defer to Recharts entirely, as before this PR. Major: dividing an unrounded domain (max * 1.05, or max + padding) into even quarters produced fractional labels (341/683/1k) or, worse, duplicate rounded ones (a peak of 3 gave 0/1/2/2/3). Ported the Graphics Gems "nice number" rounding already used by the CLI's termchart (packages/cli/src/termchart/scale.ts's niceTicks) so the domain rounds to a clean step before ticks are chosen from it. Minor: consolidated the two near-identical min/max scans (default vs. selection/fit-to-data) into one scanYAxisValueRange, parameterized by a visibility predicate, and replaced a tautological test (asserting an arithmetic sequence has equal gaps, which can't fail) with real domain/tick assertions for the reported peaks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
scaling, and the 2.5 step
Four issues from this round's review:
Critical/P1 (duplicate reports): the stacked-bar guard only sat inside
the no-selection branch, so a stacked bar WITH an active legend
selection still fell through to per-series-max scanning. Moved the
guard to the top of computeYAxisBounds so it applies unconditionally.
Critical: getNiceYAxisBounds(0, 0) (all-zero data) or a negative max
past its max<=min guard produced a literal [0, 0] or inverted [0, -5]
domain instead of falling back - toYAxisBounds now falls back to each
branch's own no-data sentinel ([0,'auto'] default, ['auto','auto']
fit-to-data) whenever the guard fires.
Major: the max scan used the full lineData (up to 250 materialized
series) instead of visibleLineData (selection- and
HARD_LINES_LIMIT-filtered, i.e. what's actually drawn), so an
undrawn series could scale the axis and squash the real lines to the
bottom of the plot. Also let scanYAxisValueRange drop its now-unneeded
isVisible predicate, since visibleLineData is already filtered.
Major: the nice-step algorithm's 2.5 bucket produced ticks (e.g.
12.5) that formatAxisTick rounds to integers past its magnitude
threshold ("13"), breaking the even progression this PR exists to
fix. Dropped 2.5, matching the existing 1/2/5-only precedent in
components/TimelineChart/utils.ts's calculateInterval.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Missed the toPrecision(12) cleanup the CLI's niceTicks (which this was ported from) already does - a 0.1-ish step could emit a tick like 0.6000000000000001 or a domain endpoint with the same dust. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nice-rounding ticks by expanding the domain regressed Fit-Y-Axis-to-Data (halved its effective zoom), added dead space/lost ticks on the default axis, and went stale once an alert reference line silently extends the domain. Ticks are now chosen within the existing (unexpanded) domain instead, and are skipped in favor of Recharts' own when a reference line is present. Also restores 2.5 as a valid step everywhere except the one magnitude where it produces non-integer ticks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A selection with fit-to-data off degenerated to the unpinned fit fallback (['auto','auto']) instead of the zero-pinned default, since only fitting itself should opt out of the zero baseline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rounding to the nearest 1/2/2.5/5 step could pick one smaller than the raw target, overflowing past maxTicks (e.g. 6 ticks for a 0-1480 range). Now searches nearby nice steps and picks the smallest one whose actual generated ticks still fit the limit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Recharts widens an explicit domain to fit out-of-range data (allowDataOverflow defaults to false), so a domain pinned at [0, upperBound] with negative data actually renders wider than that, stranding a domain-only tick list in a sliver at the top of the axis. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
max * 1.05 made the upper bound more negative than max itself for an all-negative series (-900 * 1.05 = -945), excluding the data's own maximum from the domain and stranding the precomputed ticks once Recharts widened the rendered domain back up to it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Reference lines: an explicit numeric domain skips Recharts' own nice rounding even with ticks suppressed, reproducing the exact uneven spacing this PR fixes on every alert chart. Fall back to auto entirely when a reference line is present. - Selection branch: mirror the default branch's negative-data fix - the lower bound must follow negative data regardless of fit mode, since Recharts widens the domain to it either way. - Reject any candidate step whose formatted labels collide (e.g. 1500 and 2000 both rendering "2k"), not just the literal 2.5 step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cleanNumber's 12-significant-digit rounding can make t + step round back to t once |t| is large enough relative to step, so the loop's increment silently stopped advancing while still pushing to the ticks array - a frozen tab followed by OOM, reachable from the selection/fit branch's flat-high-baseline case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Grafana/Chronosphere never render two ticks with the same label - a narrow range at the configured decimal precision (e.g. a 2.9-3.4GB memory gauge at 0 decimals) collapsed every tick to "3 GB", or worse, a single tick with no scale at all. Tick selection now escalates past the configured mantissa (bypassing formatAxisTick's own forced-0 rule) when that's the only way to keep labels distinct, applied uniformly to the default and Fit-to-Data/ selection branches alike. A candidate step or expansion is now also rejected outright if it would leave fewer than 2 ticks - a one-tick axis is never an acceptable outcome, even though its label is trivially "distinct" from nothing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Duration formats had no escalation path at all - a colliding range (e.g. 3600-3700s all rendering "1h") fell back to null/undefined ticks unconditionally. Now escalates formatDurationMsCompact's fixed 2-3 significant digits the same way numeric formats do. - Numeric escalation started from "configured mantissa + 1", but formatAxisTick already forces mantissa to 0 above its magnitude threshold regardless of what's configured - so the collision was at 0, and escalating from e.g. 3 (mantissa 2 + 1) skipped a sufficient 1, producing needlessly wide labels that can overflow the axis width budget. Escalation now always starts at 1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Escalating precision to keep ticks distinct could render a label wider than the axis leaves room for; now rejected in favor of a coarser step, scoped to number/percent since byte/duration were already exempt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…en-tick-spacing # Conflicts: # packages/app/src/HDXMultiSeriesTimeChart.tsx # packages/app/src/__tests__/HDXMultiSeriesTimeChart.test.ts
Fixed-unit escalation no longer reintroduces the suffix formatAxisTick drops (P1); escalated numeric labels now trim trailing zeros like the base formatter; duration escalation now respects the label budget too, reusing a parameterized formatDurationMsCompact instead of a duplicate; the default-axis branch defers to Recharts fully (not a raw domain) when no step fits; yAxisBounds memoizes on a boolean, not the referenceLines node identity. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
b3bed8f to
f32ee39
Compare
computeYAxisBounds was leaving ticks undefined when its nice-step search came up empty, deferring to Recharts' raw domain-division default - which has no distinctness guard and can render duplicate labels. Falls back to getYAxisTicks's reduce-tick-count strategy instead, restoring behavior hyperdxio#3162 already shipped for this case before the merge replaced its wiring. Also trims a few comments that crept past 2 lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… step Full-precision fallback now respects the label budget like every other escalation path, falling through to a coarser step instead of rendering overflowing grouped numbers. Reference-line domains now dedup ticks via getYAxisTicks instead of dropping tick management entirely, restoring behavior alert charts had before this branch existed. The 2.5x10^n step exclusion now applies at every magnitude below 1 (0.25, 0.025, ...), not just the bare value - those round unevenly the same way. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@brandon-pereira @pulpdrew this is ready for review. Here's a brief overview of changes
Note: The following one is a pre-existing issue, just reachable through one more path now. Only surfaces on Fit-to-Data/legend-isolated tiles with near-flat data, e.g. a 99.995%–99.997% success-rate panel. |
…nce lines Mantissa-aware 2.5x10^n exclusion: only excluded below 1 when the format forces integers, restoring tick density for decimal gauges/ratios that have room to represent it exactly (e.g. mantissa 2 keeps a clean 0.25 step instead of falling back to a sparser 0.5 one). Reference lines: threaded real threshold value(s) from Alerts.tsx down through DBTimeChart/MemoChart into computeYAxisBounds (previously just a boolean), so a threshold that would widen the domain gets folded into the nice-step calculation up front instead of ticking a domain that goes stale once Recharts' ifOverflow="extendDomain" moves it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…erts getAlertReferenceLineValues mirrors getAlertReferenceLines' own branching so the values used to size the Y-axis domain can never include a thresholdMax that isn't actually rendered - previously any non-null thresholdMax was grabbed unconditionally, so a leftover value from an alert that used to be BETWEEN/NOT_BETWEEN (but was since changed to a simple type) could silently stretch the domain around an invisible line. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
getAlertReferenceLineValues omitted the implicit y1=0 edge that getAlertReferenceLines draws for BELOW/BELOW_OR_EQUAL, letting the Y-axis domain get computed too narrow and then silently widened. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>






Fixes uneven spacing between Y-axis ticks (e.g.
0, 300, 1kinstead of0, 250, 500, 750, 1k) on an otherwise linear axis.Recharts picks its own tick values, then drops some to avoid overlapping labels on shorter charts — but it drops them without re-spacing the rest, which is what causes the unevenness. This feeds it evenly-spaced candidates instead, so whatever survives stays evenly spaced. Same number of ticks as before, just positioned correctly.
🤖 Generated with Claude Code