Charts: Let a line chart show a period with no data - #52580
adamwoodnz merged 16 commits into
Conversation
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Code Coverage SummaryCoverage changed in 3 files.
2 files are newly checked for coverage.
|
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Review cycle summaryStatus: clean. Ready for human review, after #52522. Round reviewer: Codex, per this repo's preset. Two rounds.
Final review: the Claude app, one round. No blocking issues, and it agreed with both declines.
CI: 89 passing, no failures. Code coverage requirement stays pending: this PR's base is the CHARTS-281 feature branch, which has no coverage data. The coverage bot says that is expected, and it resolves once #52522 merges and this PR is retargeted to Inline review threads: none were opened. Human reviewer comments: none yet. Merge order: #52522 (bar chart), then this PR, then #52523 (Premium Analytics, WOOA7S-2181). |
c3c2884 to
a7b9cde
Compare
Bar and area charts each check a data point's value for validity with slightly different logic scattered inline. Pull the check and the tooltip "No data" formatting into one private module so the upcoming line chart change reuses the same rule instead of a third copy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
validateData rejected a null value outright. Allowing it lets visx break the line on its own: d3's linear scale maps null to undefined, which already fails the defined check that AreaSeries uses to skip a point. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
formatNumber( null ) printed 0, claiming a reading the bucket doesn't have. Use formatReading and sort missing readings after numeric ones instead of letting null coerce to 0 in the comparator. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
d3's linear scale maps a null bucket to undefined, not NaN, and the existing typeof y !== 'number' guard rejects undefined. With a leading or trailing null, LineChartGlyph took data.data[0] or the last point unconditionally, so its start or end glyph silently disappeared instead of sitting on the first or last real reading. Pick the first (find) or last (findLast) point whose scaled y is a finite number, and render nothing when no point has one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
visx's d3.extent skips null, undefined and NaN, so a window where every visible bucket is null (e.g. entirely before a site launched) left the y scale with no domain. Pin it to [0, 1] in that case, spread before stableYDomain so a hidden series' real range still wins over the empty-domain fallback when rescaleYOnVisibilityChange is false. A caller's own yScale.domain always wins last. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
Adds a BucketsWithNoData story routed through the shared Template so every control still reaches it, documents the null-value behavior in index.docs.mdx and index.api.mdx, and adds the changelog entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
Add the missing changelog entry for the line chart's translated "No data available" and "Invalid data" messages, replace the glyph predicate's disproven undefined-vs-NaN comment with what the code actually does, and reword the no-reading y-axis fallback docs to say no visible series has a reading (not "in view") and to note that a caller's own yScale domain or a pinned rescaleYOnVisibilityChange axis takes priority. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
The no-reading fallback used [ 0, 1 ] for every scale, but a log scale cannot map zero, so a caller using type 'log' got a y axis of NaN ticks once every visible bucket was null. It now falls back to [ 1, 10 ] there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
`isInvalidReading` took `unknown`, which switched off type checking at every call site, so a stray string would have passed validation. It now takes the value type the charts actually hold. The fallback comment said "window", inviting a zoom-filtered scan that would be wrong, and a docs bullet did not parse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
Matches the bar chart story: April is now a real zero, so the line starts there at 0 while the months before it draw nothing, which is the distinction between a period with none and a period with no record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
a7b9cde to
472a8cd
Compare
chihsuan
left a comment
There was a problem hiding this comment.
Thanks for closing the line chart half of this! @adamwoodnz Tested well.
Three inline notes. Not blocking this PR, but I'd love the pointer one settled before #52523.
A line chart scales its value axis to the readings alone, so when every reading is the same the domain collapses to one value and d3 draws the line halfway up the plot. Buckets with no reading make this common: a flat subscriber count after the null months drew its line mid-height beside the same data as full-height bars. Run a flat linear axis from zero to the value, as the bar chart already does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DxvpmYsGipgDaUwR3zeVe6
visx's nearest search adds the y distance to the x distance, and a null reading scales to NaN, so a pointer over a bucket where no series has a reading fired no pointer event. A drag-to-zoom starting there did nothing, and onPointerDown / onPointerUp never reached a consumer such as a drill-through. Find the nearest datum along x ourselves, preferring a series with a reading, as the bar chart already does for its bands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
The stable and fallback y domains each spelled out their own finite-number test. One type guard beside the other reading rules keeps them from drifting apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
The default tooltip had moved to a local row type once a value could be null, leaving the exported TooltipDatum describing rows the chart no longer produces. Widen it and use it again, before a consumer builds on the narrower shape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
The story had no zoomable control, so the pointer fix could not be tried where the null buckets are. Document that pointer handlers receive the null datum. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
Codex reviewThe replacement pointer selection changes existing numeric callback behavior and mishandles numeric values that cannot be placed on log scales. It also regresses pointer-move complexity from logarithmic to linear in all plotted points. Full review comments:
|
Scanning every point on every pointer move cost O(n) per series where visx bisected; the data is sorted by date, so bisect it the same way. A numeric reading a log scale cannot place scaled to a non-finite y and fell into the no-reading fallback, firing a callback for an invisible point; only a genuine null bucket takes that path now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
|
Responses to the Codex review of the pointer change: P1, match visx's numeric ordering: not applied. The tooltip does not use P2, log-invalid readings: fixed in 25ca546. Only a genuine P2, binary search: fixed in 25ca546. Charts tests: 1684 passing. Typecheck and ESLint: clean. In Storybook, a zoom drag from January and one from May both zoom, and hover tooltips still read |
Codex reviewP1 — AGREE / ACCEPTED-DECLINE: AreaSeries sends per-series nearest results to TooltipProvider, whose Euclidean calculation matches Math.hypot; the malformed POINTER_EVENTS_NEAREST branch in useEventHandlers does not determine this tooltip ordering. Full review comments:
|
|
Responses to the Codex follow-up: P1: agreed, closed. Log-invalid readings in the tooltip: declining for this PR. The tooltip path is visx's own ( Undated points and the bisect: declining for this PR. visx's |
Fixes CHARTS-284
Why
A line chart can now show a period that has no data. Before, one
nullvalue made the whole chart print "Invalid data". Now the line breaks at that period and starts at the first real reading, its tooltip says "No data", and the start and end markers sit on real readings. This is the line chart half of #52522 (merged), and Premium Analytics needs both before it can stop turning a missing subscriber count into zero (WOOA7S-2181, #52523), because its Subscriber summary switches between bar and line.Proposed changes
readings.tsholdsisReading,isInvalidReadingandformatReading. The bar and area charts move onto it with no change in behavior; the area chart still rejectsnull, because its stack layout cannot take a gap.null.validateDatalets it through and still rejectsundefinedandNaN, and its two error strings are now translated. No accessor change is needed: d3's linear scale mapsnulltoundefined, which visx'sAreaSeriesdefinedcheck already rejects, so the line and its fill break on their own.0, and sorts missing readings last. The exportedTooltipDatum.valuewidens tonumber | nullto match.NaNfor a null reading, so a pointer over a bucket where no series had one fired nothing: a drag-to-zoom could not start there, andonPointerDown/onPointerUpnever reached a drill-through. The chart now finds the nearest datum along x itself, preferring a series with a reading, as the bar chart'sBandTooltipdoes.nullmade them disappear.0to1. A caller's own domain, or an axis pinned byrescaleYOnVisibilityChange={ false }, still takes priority.0to1when the value is 0. A log scale is unchanged.BucketsWithNoDatastory, docs, and changelog entries.Screenshots
The
BucketsWithNoDatastory, matching the bar chart's: January to March have no reading, April is a real zero, and May to July climb.The line starts at April, at 0, with the start glyph on its first reading and nothing before it
A month with no reading: the tooltip says "No data", not 0
On a month with no reading the tooltip sits at the top of the plot, because there is no point to anchor it to. It stays over the right month and reads correctly.
A real zero: April's tooltip reads 0, anchored on its point
Related product discussion/links
Does this pull request change what data or activity we track or use?
No.
Testing instructions
In Storybook, open JS Packages / Charts Library / Charts / Line Chart / Buckets With No Data.
withStartGlyphsandwithEndGlyphs: the glyphs sit on April and July.No data, not0. The fourth press reaches April, which reads0.zoomableand drag from January to May: the chart zooms. Before this change, a drag starting on January did nothing.nullvalue.Verified:
pnpm run testinprojects/js-packages/charts: 103 suites, 1684 tests passing.0to1axis, level with its bars.pnpm run typecheck: clean. ESLint: clean on every changed file.🤖 Generated with Claude Code
https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp