fix(admin): KeyViz heatmap honours devicePixelRatio#684
Conversation
Phase 2-B follow-up to PR #680. Claude bot's round-1 review flagged that the canvas buffer was sized at CSS-pixel dimensions, leaving every cell edge blurry on 2x displays. Scale the buffer to physical pixels via window.devicePixelRatio, keep the CSS style at logical pixels, and reset the transform with setTransform on every render so repeated useEffect runs do not stack scales. The DPR is clamped at 4 so a browser reporting an absurd ratio (e.g. zoom-aware DPR > 8) cannot balloon the canvas buffer beyond the rendering budget; at the maximum matrix size 4x DPR is already 16384 x 16384 px.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces High DPI support for the KeyViz heatmap canvas by scaling the backing buffer based on the device pixel ratio, capped at 4. Feedback suggests using the actual ratio of physical to logical pixels in the transform to avoid sub-pixel blur on fractional DPR displays and notes that the implementation is not reactive to changes in display density or browser zoom.
| const dpr = Math.min(window.devicePixelRatio || 1, 4); | ||
| canvas.width = Math.max(1, Math.floor(width * dpr)); | ||
| canvas.height = Math.max(1, Math.floor(height * dpr)); | ||
| const ctx = canvas.getContext("2d"); | ||
| if (!ctx) return; | ||
| // setTransform (not scale) so the matrix is reset cleanly on | ||
| // every render — repeated useEffect runs would otherwise stack | ||
| // scales on top of the previous one. | ||
| ctx.setTransform(dpr, 0, 0, dpr, 0, 0); | ||
| ctx.clearRect(0, 0, width, height); | ||
| if (matrix.rows.length === 0 || matrix.column_unix_ms.length === 0) return; |
There was a problem hiding this comment.
Using the raw dpr for the transform while using Math.floor for the buffer dimensions can lead to sub-pixel clipping or blur at the edges on displays with fractional pixel ratios (e.g., 1.25x or 1.5x). For example, if width is 101 and dpr is 1.5, the buffer becomes 151px, but a 1.5x transform will attempt to draw at 151.5px. It is more precise to use the actual ratio of physical pixels to logical pixels.
Additionally, window.devicePixelRatio is not a reactive value. If a user moves the window between displays with different pixel densities or changes the browser zoom, the canvas will not update until the next data refresh. Consider implementing a listener using window.matchMedia to trigger a re-render when the resolution changes.
| const dpr = Math.min(window.devicePixelRatio || 1, 4); | |
| canvas.width = Math.max(1, Math.floor(width * dpr)); | |
| canvas.height = Math.max(1, Math.floor(height * dpr)); | |
| const ctx = canvas.getContext("2d"); | |
| if (!ctx) return; | |
| // setTransform (not scale) so the matrix is reset cleanly on | |
| // every render — repeated useEffect runs would otherwise stack | |
| // scales on top of the previous one. | |
| ctx.setTransform(dpr, 0, 0, dpr, 0, 0); | |
| ctx.clearRect(0, 0, width, height); | |
| if (matrix.rows.length === 0 || matrix.column_unix_ms.length === 0) return; | |
| const dpr = Math.min(window.devicePixelRatio || 1, 4); | |
| canvas.width = Math.max(1, Math.floor(width * dpr)); | |
| canvas.height = Math.max(1, Math.floor(height * dpr)); | |
| if (matrix.rows.length === 0 || matrix.column_unix_ms.length === 0) return; | |
| const ctx = canvas.getContext("2d"); | |
| if (!ctx) return; | |
| // Use the actual ratio of physical to logical pixels for the transform. | |
| // This ensures that the logical dimensions map exactly to the backing | |
| // buffer even on fractional DPR displays, avoiding sub-pixel blur or | |
| // clipping at the edges. setTransform also resets the matrix so | |
| // repeated runs do not stack scales. | |
| ctx.setTransform(canvas.width / width, 0, 0, canvas.height / height, 0, 0); | |
| ctx.clearRect(0, 0, width, height); |
Two items from Gemini round-1 on PR #684: - Use the actual buffer/logical ratio for setTransform rather than the raw devicePixelRatio. On a 1.25x or 1.5x display, the buffer is floored (Math.floor(width * dpr)) but the transform was the raw dpr, so the renderer would draw at fractional pixel coordinates the buffer could not represent. That left sub-pixel blur at every cell edge on fractional-DPR displays. Computing sx = canvas.width / width (and sy similarly) makes the transform exactly the ratio of physical to logical pixels, eliminating the residual. - Listen for DPR changes via window.matchMedia. devicePixelRatio is not reactive, so dragging the window between displays of different densities or zooming the browser used to leave the canvas at the old resolution until the next data refresh. A matchMedia listener bumps a tick that re-runs the canvas effect.
|
@claude review Round-1 follow-ups (b10a0ae) addressing both Gemini items on PR #684:
|
|
Claude finished @bootjp's task in 1m 38s —— View job Review of
|
Claude bot round-2 cosmetic nit: the width > 0 ternary in setTransform was never reachable in practice (the JSX only renders the canvas when matrix.rows.length > 0, which implies width > 0). Hoist the empty-matrix early return ahead of the transform call so the live path collapses to a single ctx.setTransform expression.
|
@claude review Round-2 cosmetic follow-up (7a77b57): hoisted the empty-matrix early return ahead of
|
|
Claude finished @bootjp's task in 1m 2s —— View job Review of round-2 follow-up (7a77b57)
The round-2 changes are correct and, notably, more than cosmetic — see below. 1. Hoisted empty-matrix guard (
|
Summary
Phase 2-B follow-up to PR #680. Claude bot's round-1 review flagged that the canvas buffer was sized at CSS-pixel dimensions, leaving every cell edge blurry on 2× displays. Fix:
window.devicePixelRatio, keep the CSSstyleat logical pixels.ctx.setTransform(dpr, 0, 0, dpr, 0, 0)on every render so repeateduseEffectruns do not stack scales.Five-lens self-review
dpr²(≤ 16× at the cap), butfillRectcount is unchanged — we still issue one rect per non-zero cell at logical-pixel coordinates. Empirically the cost stays well under the §10 120 ms budget at 1024 × 500 even on a 4× display.CanvasRenderingContext2D); manual verification on a retina display is the gate.Test plan
tsc -b --noEmitcleanvite buildclean/keyvizon a retina display; cell edges crisp instead of blurry