Skip to content

feat(web): print or download a single chart - #502

Merged
exploreriii merged 2 commits into
mainfrom
print-charts
Sep 30, 2026
Merged

exploreriii merged 2 commits into
mainfrom
print-charts

Conversation

@danielmarv

Copy link
Copy Markdown
Contributor

Description:

Print or download any single chart on the dashboard. Each chart gets a Print action that opens a preview of the chart as a page (A4 or Letter, portrait or landscape). The preview can be printed, or saved as PDF, PNG or JPG without the browser's print dialog.

  • Add a Print action to every interactive chart, beside Download CSV and Expand, and a print preview that draws the chart at its printed size and scales it down on screen, so the preview is the printout
  • Build the printed page from the chart's own switches, legend and render function, so the chart type and every selection carry over, with nothing duplicated:
    • period, chart style, scale, date span
    • hidden series, cohort, search, focus, show-all
  • State the selection on the page: title, card and organisation, filters, date range, a legend of what is drawn, and the population and window notes
  • Print values on the marks:
    • stack totals on bars
    • point values on short lines and areas
    • the latest value on long series
  • Size each chart kind to the page:
    • charts over time fill it
    • rankings and timelines fit their rows
    • heatmaps fit every column to the page width
    • network graphs and matrices scale into it
  • Choose the best-fit orientation per chart from its natural shape. The reader can override it, and the paper size is remembered.
  • Let a selection too long for one page run onto further pages, marked in the preview and counted, instead of clipping it
  • Show "Nothing to print for this selection" instead of an empty preview when the selection has no data
  • Print the page alone: hide the dashboard, navigation and controls, and give the page no margin, so the browser's own header and footer (date, title, URL) have nowhere to print
  • Add Download as PDF, PNG or JPG: the page rendered in the browser at 288 dpi with modern-screenshot (loaded only when a file is saved), and a small PDF writer (one losslessly compressed image per page, via CompressionStream, no PDF library)
  • Keep the printed page light in dark mode
  • Make reading notes, methodology and the printed date optional, off by default, so the chart gets the room
  • Scope the tab-print rules to tab printing (:where(), no specificity change), so Print tab behaves exactly as before
  • Cover the feature with Vitest and Playwright, and document it in docs/printing.md

Related issue(s):

Fixes #501

Notes for reviewer:

  • Design and impact
    • One renderer. ChartShell hands its own view's switches, legend and chart() render function to ChartPrintDialog. Views take an optional sheet size and lay themselves out for paper. The alternative, re-rendering the chart from a copy of its URL state, would duplicate every view's derivation and drift.
    • Scope. While the preview is open, <html data-print-scope="chart"> and a per-paper @page rule are set, and PrintProvider skips tab print mode.
    • No API, pipeline or output-contract changes.
  • Content Security Policy is unchanged.
    • The page is rendered via SVG foreignObject onto a canvas. Only data: images and same-origin fonts are used, which the existing policy already allows.
    • GitHub avatars cannot be read under connect-src 'self', so saved files show their initials instead.
    • The PDF is an image of the page, so its text is not selectable; Print keeps the text.
  • Minifier. Lightning CSS folds translate: none into the transform shorthand, which left the dialog's centring offset on paper in the production build. print.css resets Tailwind's --tw-translate-* variables instead.
  • Dependency: modern-screenshot (MIT), 22 kB, in its own chunk, loaded only when a file is saved.
  • Checked on the real data API (both orgs): every chart on every tab was previewed and printed to PDF in Chromium. That covers bars, stacked bars, lines, areas, rankings, funnel, meter, heatmaps with and without avatars, network graph, adjacency matrix and release timeline.
    • Each fits one page with nothing past the edges, and the preview's page count matched the PDF.
    • Also checked: filter combinations, A4 and Letter in both orientations, dark mode, phone width, and PDF/PNG/JPG downloads (A4 landscape → 3368×2381 px, one-page PDF at 841.89×595.28 pt).
    • Safari's native print was not verified.
  • Tests:
    • npm test: 180 tests, 13 new.
    • npm run lint: existing warnings only, none added.
    • npm run format:check and npm run build pass.
    • Playwright: 27 tests in each of Chromium and Firefox, 6 new, including real downloads parsed for page count and size.
  • Screenshots of the preview and a downloaded PDF to follow in a comment.

Checklist

  • I claimed the linked issue with /assign before starting (see contributing guide)
  • uv run pytest and uv run ruff check src tests pass locally — not applicable, no Python changes (npm test, npm run lint and npm run build pass in web/)
  • Tests added/updated for the change (mirroring the src/ layout)
  • Commits are signed and signed-off: git commit -S -s
  • Docs updated if relevant

Every interactive chart gets a Print action that opens a preview of the
chart laid out as a page (A4 or Letter, portrait or landscape, best fit by
default), which can be printed or saved as PDF, PNG or JPG.

* Add a print preview drawn at printed size from the chart's own switches,
  legend and render function, so chart type and selections carry over
* State title, filters, date range and legend on the page, and print
  values on the marks
* Fit each chart kind to the page; long selections run onto further pages
  instead of being clipped
* Show a message instead of an empty preview when no data is selected
* Print the page alone, with no page margin for the browser's own header
  and footer
* Add PDF, PNG and JPG downloads rendered in the browser at 288 dpi
* Scope tab-print rules so Print tab is unchanged
* Cover with Vitest and Playwright; document in docs/printing.md

Fixes #501

Signed-off-by: Ntege Daniel <danientege785@gmail.com>
@danielmarv danielmarv added enhancement New feature or request typescript Touches TypeScript/React code (web/) labels Sep 29, 2026
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

📊 Dashboard preview

The dashboard was built for this PR.

➡️ Download dashboard-preview-502, unzip, then serve it with python3 -m http.server -d . and open the URL it prints. (The app fetches its data over HTTP, so opening index.html directly shows an empty page.)

The artifact is built from this PR — treat it as contributor-authored code and glance at the diff before opening it. It expires after 7 days and is replaced on each new push.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 30 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: hiero-hackers/analytics/.coderabbit.yml

Review profile: CHILL

Plan: Advanced

Run ID: e8078c7a-a420-4b38-b5fb-270c0c87545f

📥 Commits

Reviewing files that changed from the base of the PR and between 2c91aaf and 9f24406.

📒 Files selected for processing (8)
  • docs/printing.md
  • web/e2e/chartPrint.spec.ts
  • web/src/chartExport.ts
  • web/src/chartPrint.ts
  • web/src/components/charts/ChartPrintDialog.tsx
  • web/src/components/charts/EventsView.tsx
  • web/src/components/charts/SeriesView.tsx
  • web/src/test/chartPrint.test.tsx

Walkthrough

Interactive charts now have a print preview with chart-specific layout, selection details, and paper settings. The preview can print the chart sheet or download it as PDF, PNG, or JPG. Chart-specific rendering and tests cover paper sizing, pagination, empty selections, and separation from dashboard printing.

Changes

Chart Printing

Layer / File(s) Summary
Paper geometry and file export
web/src/chartPrint.ts, web/src/chartExport.ts, web/package.json
Adds A4 and Letter sizing, orientation selection, regional defaults, and PDF, PNG, and JPG export.
Preview dialog and print scope
web/src/components/charts/ChartShell.tsx, web/src/components/charts/ChartPrintDialog.tsx, web/src/components/ChartSectionCard.tsx, web/src/components/PrintFilter.tsx, web/src/components/VariantTabs.tsx, web/src/components/charts/leading.ts, web/src/printContext.ts, web/src/app.css, web/src/print.css, web/src/printing.tsx
Adds a chart print dialog with filters, optional methodology and date, and print and download actions. Chart print styling isolates the sheet from dashboard print styles.
Chart-specific sheet layouts
web/src/components/charts/EventsView.tsx, web/src/components/charts/MatrixView.tsx, web/src/components/charts/NetworkView.tsx, web/src/components/charts/SeriesView.tsx, web/src/components/charts/AdjacencyMatrix.tsx, web/src/components/ContributorCell.tsx, web/src/components/charts/format.ts, web/src/hooks/use-element-size.ts
Chart renderers pass print filters and sizing to the preview. Print layouts adjust chart dimensions and hide selected interactive controls.
Print and export verification
web/src/test/chartPrint.test.tsx, web/src/test/printComponents.test.tsx, web/e2e/chartPrint.spec.ts, docs/printing.md
Adds unit and browser tests for preview, print scope, pagination, empty selections, and downloads. Documents printing and export behavior and verification.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🟡 Moderate · up to 2c91a

Multi-page chart PDFs can split rows, labels, and bars across pages. Preserve complete rows in downloaded PDFs before merging; the existing page-count checks do not catch this problem.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 2c91a

Downloads are generated locally and exclude external avatar images. A delayed print may fall back to printing the dashboard if its chart preview is closed first. Browser print confirmation limits the impact, while some rendering behavior remains unverified.

Retained concerns

  • Low · reliability · inferred: If font readiness is pending, closing the preview after clicking Print removes chart scope but does not stop the awaiting print operation. Its later window.print call can enter dashboard printing instead, broadening the output beyond the requested chart. Browser confirmation mitigates this inferred output-containment failure.
Security review details

Security Blast Radius

  • inferred — The demonstrated output boundary is the current browser tab: chart export reads its supplied sheet, while loss of chart print scope can activate dashboard printing in that same tab. The inspected paths do not establish access to another user's or tenant's data.

Security Findings and Attack Paths

  • inferred — A pending font-ready wait followed by closing the preview can leave native print initiation alive after its chart-only scope has been removed. This can present dashboard content to the browser print flow instead of the intended chart; it is an interrupted local transition, not an established attacker-controlled privilege bypass.

Trust Boundaries and Controls

  • observed — The source CSP retains same-origin script and connection restrictions. GitHub image permissions already exist in the inspected predecessor. Contributor image URLs use validated, encoded login values and no-referrer policy; this PR changes compact preview avatars to eager loading, not arbitrary-origin loading.

Hardening Proposals

  • proposed — Bind asynchronous print initiation to an active preview generation and recheck its ownership after font readiness. Apply equivalent lifetime handling to export completion so close, reopen and interruption cannot complete operations under stale preview identity.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.27% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 44 functions across 21 files. (4 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding single-chart printing and downloads in the web application.
Description check ✅ Passed The description directly explains the chart printing and download features, supported formats, preview behavior, testing, and documentation changes.
Linked Issues check ✅ Passed The PR implements the coding requirements in directly linked issue #501. ChartShell adds a Print action, preserves the current chart renderer and selections, handles empty selections, and passes pri…
Out of Scope Changes check ✅ Passed The changes stay within issue #501. The added chart export, print layout, chart renderer adaptations, print context, styling, compact contributor rendering, date formatting, documentation, and automat…
Full details: Docstring Coverage

Explanation

Docstring coverage is 52.27% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 44 functions across 21 files. (4 skipped: 4 unsupported.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: hiero-hackers/analytics/.coderabbit.yml

Review profile: CHILL

Plan: Advanced

Run ID: 78fc3ce6-0664-4044-9cb6-0b9b62ee8af2

📥 Commits

Reviewing files that changed from the base of the PR and between d2f92ff and 2c91aaf.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (25)
  • docs/printing.md
  • web/e2e/chartPrint.spec.ts
  • web/package.json
  • web/src/app.css
  • web/src/chartExport.ts
  • web/src/chartPrint.ts
  • web/src/components/ChartSectionCard.tsx
  • web/src/components/ContributorCell.tsx
  • web/src/components/PrintFilter.tsx
  • web/src/components/VariantTabs.tsx
  • web/src/components/charts/AdjacencyMatrix.tsx
  • web/src/components/charts/ChartPrintDialog.tsx
  • web/src/components/charts/ChartShell.tsx
  • web/src/components/charts/EventsView.tsx
  • web/src/components/charts/MatrixView.tsx
  • web/src/components/charts/NetworkView.tsx
  • web/src/components/charts/SeriesView.tsx
  • web/src/components/charts/format.ts
  • web/src/components/charts/leading.ts
  • web/src/hooks/use-element-size.ts
  • web/src/print.css
  • web/src/printContext.ts
  • web/src/printing.tsx
  • web/src/test/chartPrint.test.tsx
  • web/src/test/printComponents.test.tsx

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread web/src/chartExport.ts
A downloaded PDF that ran past one page was cut at fixed heights, so a
page could end through a ranking bar, its label or a heatmap row.

* Find the safe breaks on the sheet: under its header, footer notes and
  heatmap rows, and midway between two rows of a ranking or timeline
* End each PDF page at the last break that fits, leaving white space
  rather than splitting a row
* Draw the preview's page-break guides from the same breaks, recomputed
  as the charts inside redraw
* Check in Chromium and Firefox that a long ranking's page break falls
  between two rows

Signed-off-by: Ntege Daniel <danientege785@gmail.com>
@exploreriii
exploreriii merged commit d2ce076 into main Sep 30, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request typescript Touches TypeScript/React code (web/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Print or download a single chart from the dashboard

2 participants