Skip to content

Migrate charts from Chart.js to Apache ECharts#9

Merged
tyom merged 18 commits into
mainfrom
echarts
May 22, 2026
Merged

Migrate charts from Chart.js to Apache ECharts#9
tyom merged 18 commits into
mainfrom
echarts

Conversation

@tyom
Copy link
Copy Markdown
Owner

@tyom tyom commented May 22, 2026

Summary

Replaces Chart.js with Apache ECharts (v6) across the dashboard, moving from per-canvas imperative chart construction to a single reusable echart Svelte action (div mount → setOption → auto-resize → dispose).

What changed

  • echart action (actions.ts): one home for the canvas→div mount model — init with the shared "repo" theme, re-apply options on update, ResizeObserver-driven resize, dispose on unmount.
  • Modular registration (echarts.ts): only the series/components actually rendered are pulled in (line, bar, pie, treemap, scatter + grid/tooltip/legend), mirroring the old chart.ts tree-shaking discipline.
  • Shared theme (theme.ts): registerEchartsTheme() replaces the old Chart.defaults block — palette, fonts, axis/grid colours read from CSS custom properties. Tooltips use the dark --bg-popover surface so they match the body-portaled popovers.
  • Charts ported: weekly-commits stacked area, commit-share pie (was doughnut), added-vs-deleted bar, net-lines-per-commit bar, contributor sparklines, and a per-contributor commit-pattern punch card.
  • New languages-by-contributor treemap (two-level: contributor → language mix).
  • Punch-card cells open the commit-bucket popover via a zrender click + pixel→data coordinate conversion (click anywhere in the grid, snapped to the nearest cell).

Commit-pattern punch card

The separate "Commit time patterns (hour of day)" bar chart and "Day of week patterns" bar chart are merged into a single scatter punch card per contributor: x = hour of day (0–23), y = weekday (Mon on top), dot size = commit count scaled per-card to that author's busiest cell. Clicking a cell opens the commit-bucket popover labelled by the full cell (e.g. Friday 22:00–22:59).

  • The punch card needs the joint hour×weekday distribution, which the old per-axis marginals can't reconstruct — it's built client-side in one pass over the commit list (buildPunchPoints, reusing the timezone-safe isoBuckets).
  • The now-unused hourlyData / dowData are dropped from the Python output, types.ts, and the mock data, trimming dead payload from every generated report.
  • The two pattern sections + nav links collapse into one ("Commit patterns").

Review fixes

A best-practice review of the migration surfaced these, now applied:

  1. Debounce ResizeObserverchart.resize() through requestAnimationFrame (avoids resize-loop warnings/thrash).
  2. Guard the private getZr().storage.getDisplayList() treemap-cursor access so a future ECharts bump degrades gracefully instead of throwing.
  3. Drop the unused TitleComponent registration + dead title theme block (titles are CSS overlays).
  4. Treemap tooltip returns undefined (not "") for container nodes to fully suppress the empty tooltip box.

Test plan

  • bun run check → 0 errors
  • make format clean
  • make build regenerates dist/repo-intel (committed; pre-commit hook keeps it in sync)
  • Punch-card click → popover verified end-to-end (correct hour/weekday bucket + label)

Summary by CodeRabbit

  • New Features

    • Dashboard charts migrated to Apache ECharts.
    • Repository file stats added (file count, largest files, disk usage) surfaced in UI.
  • Enhancement

    • Commit-time views unified into a punch-card "Commit time patterns" with improved popover interactions and responsive declarative charts that preserve legend/filter state.
    • Treemap and conditional chart cards added for repo insights.
  • Style

    • Chart title overlays, sizing, and layout improvements.
  • Documentation

    • README updated to reflect the frontend charting library change.
  • Chore

    • CI review config adjusted to skip ESLint.

Review Change Stack

tyom added 5 commits May 22, 2026 17:55
Replace chart.js with Apache ECharts (modular build) to unlock chart
types chart.js can't do natively — starting with a contributor→language
treemap, with graph/sunburst/heatmap available for future work.

- echarts.ts: tree-shaken registration (line/bar/pie/treemap + grid,
  tooltip, legend, title, canvas) mirroring the old chart.ts discipline
- theme.ts: registerEchartsTheme("repo") built from the CSS-var palette,
  replacing the Chart.defaults block
- actions.ts: reusable `echart` Svelte action (init/setOption/resize/
  dispose, onReady hands the instance to components)
- Port all six charts: weekly stacked-area line, commit-share pie,
  add/del + net bars, contributor sparkline, hour/dow pattern bars
  (click→popover via convertToPixel). Legend reset re-implemented with
  legendselectchanged + dispatchAction(legendAllSelect)
- Non-interactive series use cursor:"default"; line series also silent
  (its area ignores cursor); pattern bars pointer only when links on
- Remove chart.js; update README

Tradeoff: the single-file artifact grows 454 KB → 850 KB (raw, since it
is embedded un-gzipped). Accepted for the broader chart capability.
Title strips:
- Move each chart's title out of the ECharts canvas into a blurred HTML
  overlay strip pinned to the top of the card (OverallCharts + PatternCard),
  so the canvas can scroll/zoom underneath and the title stays readable.
- Shared .chart-title style (backdrop-filter blur, contributor colour for
  the pattern cards); shift chart grid/legend offsets to clear the strip.

Languages-by-contributor treemap (treemap-show-parent layout):
- Contributors are top-level containers: a full-width header strip in the
  contributor colour (centred name) with the colour also framing the
  container border and the gaps between language tiles.
- Language tiles carry their own brand colour, with a half-transparent dark
  inner border for contrast against the contributor-coloured gaps.
- Per-tile/header label colour flips near-white/near-black for contrast
  (new theme.ts contrastText helper + bgPrimary export).
- Root background painted the card colour (was default white).
- Tooltip only fires on language tiles (no ": 300%" on the root/containers).
- Disable hover emphasis (no header re-layout jump) and override the
  treemap's forced pointer cursor to default (ECharts ignores series.cursor
  for treemap nodes).

Rebuild dist/repo-intel.
- Pattern cards: drop the dashed axisPointer line on hover, make the
  whole column clickable (zrender + convertFromPixel) so the commit
  popover opens consistently rather than only on a pixel-perfect bar
  hit, and move the pointer cursor to the chart container.
- Widen pattern cards to 380px so the hour/dow bars have more room.
- Hide the "Languages by contributor" treemap when no contributor has
  any language data instead of rendering an empty chart.
- Debounce ResizeObserver resize via requestAnimationFrame (avoids resize
  loop warnings and thrash)
- Guard private zrender display-list access in the treemap cursor hack so
  an ECharts upgrade degrades gracefully instead of throwing
- Drop unused TitleComponent registration and dead title theme block
  (titles are CSS .chart-title overlays, not ECharts title:)
- Treemap tooltip returns undefined (not "") for container nodes to fully
  suppress the empty tooltip box

Rebuild dist/repo-intel.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9845126a-b8b7-4746-95a4-2d8cefd35d26

📥 Commits

Reviewing files that changed from the base of the PR and between 6dd8d43 and ba70e13.

⛔ Files ignored due to path filters (1)
  • dist/repo-intel is excluded by !**/dist/**
📒 Files selected for processing (6)
  • repo-intel.py
  • web/src/lib/components/Header.svelte
  • web/src/lib/components/OverallCharts.svelte
  • web/src/lib/components/PatternCard.svelte
  • web/src/styles/app.css
  • web/src/types.ts

📝 Walkthrough

Walkthrough

Replaces Chart.js with selective Apache ECharts setup, adds a reusable Svelte echart action and theme, migrates chart components to ECharts, updates styles/docs/package and mock data, and extends repo-intel to collect and emit file/branch/disk statistics consumed by the UI.

Changes

ECharts Migration & repo data

Layer / File(s) Summary
ECharts dependency & theme registration
web/package.json, web/src/lib/echarts.ts, web/src/lib/theme.ts, web/src/App.svelte
Adds echarts dependency, registers needed series/components, exports configured echarts, and provides/calls registerEchartsTheme() at app startup.
ECharts Svelte action
web/src/lib/actions.ts
New EchartParams and echart action: initializes charts with the "repo" theme, sets options, supports onReady, auto-resizes via ResizeObserver + rAF debounce, and cleans up on destroy.
Component migrations
web/src/lib/components/ContributorCard.svelte, web/src/lib/components/OverallCharts.svelte, web/src/lib/components/PatternCard.svelte, web/src/lib/components/CommitPopover.svelte
Migrates canvases/Chart.js lifecycles to declarative ECharts option + use:echart divs; wires legend/treemap/click interactions and connects chart clicks to commit popovers.
Popovers and punch-card data
web/src/lib/popovers.ts, web/src/lib/popover-store.svelte.ts
Adds PunchPoint and buildPunchPoints, changes popover API to commitsInCell(email,hour,dow), and updates documentation comments for reactive popover usage.
Types, format helper, and mock data
web/src/types.ts, web/src/lib/format.ts, web/public/mock-data.json, web/src/lib/components/Header.svelte
Adds FileSize/FileSizes, extends RepoData with branchCount, fileCount, largestFiles, diskByPath; adds fmtBytes; updates mock data (removes hourly/dow fields, expands commits); Header shows file count when present.
repo-intel file-size & branch payloads
repo-intel.py
Adds helpers to compute HEAD largest files and history disk-by-path, branch counting, plumbs these into extras and build_data(), removes hourly/dow buckets, and adds Markdown rendering for size tables.
Styles, README, CI review config
web/src/styles/app.css, README.md, .coderabbit.yaml
Adds .chart-title overlay, .ec canvas sizing rule, adjusts .scroll-row/.pattern-card sizing; README now states the bundle includes Apache ECharts; adds .coderabbit.yaml disabling ESLint in review tooling.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Client
  participant App as App.svelte
  participant Theme as web/src/lib/theme.ts
  participant EChartsMod as web/src/lib/echarts.ts
  participant Action as web/src/lib/actions.ts (echart)
  participant Component as Chart Component (Contributor/Overall/Pattern)
  Browser->>App: load
  App->>Theme: registerEchartsTheme()
  Theme->>EChartsMod: register theme on echarts instance
  Component->>Action: mount via use:echart(option, onReady)
  Action->>EChartsMod: init chart (theme="repo"), setOption
  Component->>Action: zrender events (click/legend) -> commit popover request
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • tyom/repo-intel#6: Earlier Chart.js→ECharts migration work and initial popover/timeline logic that this PR continues and adjusts.

Poem

I'm a rabbit with a tiny charting broom,
Swapped canvases for divs and gave plots room.
I registered themes at morning light,
Counted branches and bytes late at night.
🥕📊 ECharts hop — the dashboard's looking bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Migrate charts from Chart.js to Apache ECharts' accurately and concisely summarizes the primary change—replacing Chart.js with Apache ECharts across the dashboard—which is the main focus of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch echarts

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

tyom added 6 commits May 22, 2026 20:42
This repo has no ESLint setup (Bun workspace uses svelte-check +
Prettier), so disabling the tool suppresses CodeRabbit's recurring
"ESLint skipped" warning.
Surface repo composition alongside contributor stats:
- File count at HEAD in the header subtitle, markdown report, and JSON
- "Largest files (at HEAD)" treemap of uncompressed blob sizes
- "Disk usage by path (full history)" treemap of compressed on-disk
  sizes summed per path, showing what fills the .git object store

Both file-size views are computed on the local/bare-clone path; the
remote GraphQL path has no per-file tree, so the fields stay null and
the charts/markdown sections hide. Treemap chosen over sunburst because
the size distribution is heavily skewed toward a few paths.
Swap the separate hour-of-day and day-of-week bar cards for a single
ECharts scatter punch card per contributor: x=hour, y=weekday (Mon on
top), dot size = commit count scaled per-card. Clicking a cell opens the
commit-bucket popover labelled by the full cell (e.g. Friday 22:00-22:59).

The punch card needs the joint hour x weekday distribution, which the old
per-axis marginals can't reconstruct, so build it client-side from the
commit list (buildPunchPoints, reusing isoBuckets). Drop the now-unused
hourlyData/dowData from the Python output, types, and mock data; register
ScatterChart; consolidate the two App sections and nav links into one.
Set the shared repo theme's tooltip background to --bg-popover (the same
surface the body-portaled popovers use) so the default white tooltip box
doesn't glare against the dark dashboard.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
repo-intel.py (1)

801-829: 💤 Low value

Potential memory pressure on very large repositories.

For repositories with massive histories (millions of objects), piping rev-list --objects --all into cat-file --batch-check loads the entire stdout into memory twice (revs.stdout and cat.stdout). This is already O(history) as noted, but could exhaust memory on extremely large monorepos.

Consider streaming the pipe if this becomes an issue, though for most repositories this approach is fine.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@repo-intel.py` around lines 801 - 829, The current history_disk_by_path
implementation captures the full output of `git rev-list ... --all` and `git
cat-file --batch-check` into memory (revs.stdout and cat.stdout), which can OOM
on very large repos; change this to stream the pipeline instead: replace the two
subprocess.run calls with subprocess.Popen (or subprocess.run with stdin as a
file-like stream) so that `git rev-list --objects --all` is started with
stdout=PIPE and its stdout is passed directly as stdin to `git cat-file
--batch-check`, then iterate over the resulting cat-file stdout as a file
iterator to parse lines incrementally rather than storing cat.stdout; update
references to revs, cat, and the line-parsing loop in history_disk_by_path
accordingly and preserve the existing error handling behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@repo-intel.py`:
- Around line 801-829: The current history_disk_by_path implementation captures
the full output of `git rev-list ... --all` and `git cat-file --batch-check`
into memory (revs.stdout and cat.stdout), which can OOM on very large repos;
change this to stream the pipeline instead: replace the two subprocess.run calls
with subprocess.Popen (or subprocess.run with stdin as a file-like stream) so
that `git rev-list --objects --all` is started with stdout=PIPE and its stdout
is passed directly as stdin to `git cat-file --batch-check`, then iterate over
the resulting cat-file stdout as a file iterator to parse lines incrementally
rather than storing cat.stdout; update references to revs, cat, and the
line-parsing loop in history_disk_by_path accordingly and preserve the existing
error handling behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 39bc6b95-7dab-47f4-837c-076b640eadb5

📥 Commits

Reviewing files that changed from the base of the PR and between 91a2b69 and 6dd8d43.

⛔ Files ignored due to path filters (1)
  • dist/repo-intel is excluded by !**/dist/**
📒 Files selected for processing (16)
  • repo-intel.py
  • web/public/mock-data.json
  • web/src/App.svelte
  • web/src/lib/actions.ts
  • web/src/lib/components/CommitPopover.svelte
  • web/src/lib/components/ContributorCard.svelte
  • web/src/lib/components/Header.svelte
  • web/src/lib/components/OverallCharts.svelte
  • web/src/lib/components/PatternCard.svelte
  • web/src/lib/echarts.ts
  • web/src/lib/format.ts
  • web/src/lib/popover-store.svelte.ts
  • web/src/lib/popovers.ts
  • web/src/lib/theme.ts
  • web/src/styles/app.css
  • web/src/types.ts
💤 Files with no reviewable changes (1)
  • web/src/lib/components/ContributorCard.svelte
✅ Files skipped from review due to trivial changes (2)
  • web/src/lib/popover-store.svelte.ts
  • web/src/lib/components/CommitPopover.svelte

tyom added 7 commits May 22, 2026 23:28
Combine the HEAD and full-history "largest files" treemaps into a single
"Largest files" card with a HEAD / Full history tab toggle, keeping the
nested container-with-header layout.

Clicking a cell drills into its folder by re-rooting (dispatchAction
treemapRootToNode), which preserves the nested view while changing the
view root. A Svelte breadcrumb overlay (All › folder) and a top-left
Reset button navigate back; both are hidden at the root. The breadcrumb
is HTML rather than ECharts' built-in one so it can hide at root, sit
flush at the bottom, and avoid the canvas hit-region offset.

Also scope an override for the global `canvas { max-height: 300px }` rule
(.ec canvas { max-height: none }): it was crushing the 320px treemaps to
300px, so ECharts laid out hit-regions taller than the displayed canvas
and every hover/click landed below its tile.
- Colour file tiles by a single log-scaled grey ramp (brighter = larger)
  instead of the per-directory palette; folder headers are fixed dark
  chrome with a visible border so they read against the grey tiles.
- Clicking drills one level toward the tile (a file zooms into its folder
  first); a click on a fully-zoomed leaf steps back out. Files are inert
  (default cursor); only folders carry the pointer.
- Drop the redundant Reset button (click-to-zoom-out replaces it); keep
  the breadcrumb.
- Inset the treemap below the title overlay so a zoomed-in folder's name
  strip isn't clipped.
Count distinct branches (union of local + remote-tracking heads, deduped)
on local/bare paths and via GraphQL refs.totalCount on the remote path,
thread it through build_data as branchCount, and render it in the header
subtitle and the markdown report.
history_disk_by_path buffered the full rev-list object list and the
cat-file output in memory, which can OOM on large repos. Pipe rev-list
straight into cat-file and iterate the result line by line so neither
the object list nor the size table is held in full at once.
head_file_sizes and history_disk_by_path decoded git output as strict
UTF-8 under core.quotePath=false, so a single non-UTF-8 filename raised
UnicodeDecodeError (not an OSError, so uncaught) and aborted the whole
run. Read bytes and decode leniently, mirroring _head_first_line.

The file-size treemap's grey scale included the 'N more files' remainder
aggregate, pinning the bright end to a sum-of-many and washing every
real file tile toward the dark floor. Exclude the remainder from the
scale and clamp t so its off-scale tile still yields valid hex.
@tyom tyom merged commit 4adf216 into main May 22, 2026
2 checks passed
@tyom tyom deleted the echarts branch May 22, 2026 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant