Skip to content

feat(charts): export chart as SVG - #720

Merged
alfredo1996 merged 1 commit into
devfrom
feat/issue-706-svg-export
May 14, 2026
Merged

feat(charts): export chart as SVG#720
alfredo1996 merged 1 commit into
devfrom
feat/issue-706-svg-export

Conversation

@alfredo1996

@alfredo1996 alfredo1996 commented May 11, 2026

Copy link
Copy Markdown
Owner

Summary

Add "Export SVG" option to the widget actions dropdown for ECharts-based charts. Creates a vector SVG file download — resolution-independent, perfect for docs, presentations, and embedding.

How it works

  1. Registers ECharts SVG renderer alongside Canvas renderer
  2. On export: creates an offscreen ECharts instance with SVG renderer, copies current chart options, serializes the SVG DOM, triggers browser download
  3. Menu item only appears for widgets with isECharts: true capability

Supported chart types

Bar, Line, Pie, Gauge, Radar, Sankey, Treemap, Sunburst, Single Value

Test plan

  • Unit: triggerSvgDownload helper, SVG filename extension
  • 1240 component tests pass (SVGRenderer mock added)
  • 1927 app tests pass
  • 274 E2E tests pass (fresh Docker)
  • TypeScript type-check clean

Closes #706

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Widget charts can be exported as SVG; an "Export SVG" action appears for supported (ECharts) chart types alongside existing CSV exports.
    • Exports produce themed, high-quality vector SVGs suitable for sharing and printing; filenames include the .svg extension and follow existing naming rules.
  • Tests

    • Test setup and suites updated to support SVG rendering and verify SVG filename generation and download behavior.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

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: edbb90a7-856a-47ec-a274-ef0e503f2b5a

📥 Commits

Reviewing files that changed from the base of the PR and between 8e4b989 and 83c9b03.

📒 Files selected for processing (8)
  • app/src/components/dashboard-container.tsx
  • component/src/charts/__tests__/echarts-mock.ts
  • component/src/charts/base-chart.tsx
  • component/src/charts/index.ts
  • component/src/lib/__tests__/export-utils.test.ts
  • component/src/lib/export-utils.ts
  • component/src/utils/index.ts
  • component/vitest.setup.ts
✅ Files skipped from review due to trivial changes (2)
  • component/src/utils/index.ts
  • component/vitest.setup.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • component/src/charts/tests/echarts-mock.ts
  • component/src/charts/index.ts
  • app/src/components/dashboard-container.tsx
  • component/src/lib/tests/export-utils.test.ts
  • component/src/lib/export-utils.ts
  • component/src/charts/base-chart.tsx

Walkthrough

Adds ECharts SVG rendering and chart→SVG serialization, a triggerSvgDownload helper, and a conditional "Export SVG" widget action in DashboardContainer with corresponding tests and test-mock updates.

Changes

SVG Chart Export

Layer / File(s) Summary
ECharts SVG Renderer Setup
component/src/charts/base-chart.tsx, component/src/charts/__tests__/echarts-mock.ts, component/vitest.setup.ts
SVGRenderer is registered alongside CanvasRenderer in ECharts production setup and test mocks.
Chart-to-SVG Export Function
component/src/charts/base-chart.tsx, component/src/charts/index.ts
exportChartToSvg(container) re-renders an existing chart using SVG mode, serializes the DOM to a string, cleans up, and is re-exported from the charts barrel.
SVG Download Utility
component/src/lib/export-utils.ts, component/src/utils/index.ts
triggerSvgDownload(svgString, filename) delegates to triggerDownload with image/svg+xml MIME type and is re-exported from utils.
Dashboard SVG Export Action
app/src/components/dashboard-container.tsx
Widget actions menu conditionally shows "Export SVG" when the chart type's getChartConfig().capabilities.isECharts is true; clicking invokes exportWidgetSvg(widget) which locates the chart DOM, exports it, builds a filename, and triggers the download.
Test Coverage
component/src/lib/__tests__/export-utils.test.ts
Tests verify triggerSvgDownload exists and is a function; buildExportFilename with "svg" extension generates correctly formatted filenames.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DashboardContainer
  participant exportWidgetSvg
  participant exportChartToSvg
  participant triggerSvgDownload
  User->>DashboardContainer: click Export SVG
  DashboardContainer->>exportWidgetSvg: widget
  exportWidgetSvg->>exportChartToSvg: chart container
  exportChartToSvg->>exportWidgetSvg: svg string
  exportWidgetSvg->>triggerSvgDownload: svg string, filename
  triggerSvgDownload->>User: starts download
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • alfredo1996/neoboard#428: Related to chart-config capability refactor that exposes capabilities.isECharts, used to conditionally show Export SVG.
  • alfredo1996/neoboard#66: Related changes to ECharts test/mocking setup; both add renderer mocks used in tests.

Suggested labels

enhancement, pkg:component, pkg:app, area:charts, testing

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% 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 clearly summarizes the main feature added: SVG export capability for charts.
Linked Issues check ✅ Passed All acceptance criteria from #706 are met: SVG export option added to menu, clean .svg files downloaded, works for all ECharts-capable types.
Out of Scope Changes check ✅ Passed All changes directly support SVG export functionality: ECharts SVG renderer registration, export helper functions, DOM serialization logic, and test coverage.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-706-svg-export

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 and usage tips.

@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

🤖 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.

Inline comments:
In `@component/src/charts/base-chart.tsx`:
- Around line 290-327: In exportChartToSvg, ensure the SVG-mode ECharts instance
(svgInstance) is always disposed to avoid leaks: move creation of svgInstance
and any calls that can throw (echarts.init and svgInstance.setOption) into a try
block and call svgInstance.dispose() in a finally block (or track svgInstance
and dispose it in the existing finally before removing the offscreen element);
reference svgInstance and the exportChartToSvg function to locate where to add
the guaranteed dispose.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 640b1ea0-16e6-4493-aaae-a9728b296873

📥 Commits

Reviewing files that changed from the base of the PR and between 9c4e291 and ba00d1d.

📒 Files selected for processing (8)
  • app/src/components/dashboard-container.tsx
  • component/src/charts/__tests__/echarts-mock.ts
  • component/src/charts/base-chart.tsx
  • component/src/charts/index.ts
  • component/src/lib/__tests__/export-utils.test.ts
  • component/src/lib/export-utils.ts
  • component/src/utils/index.ts
  • component/vitest.setup.ts

Comment on lines +290 to +327
function exportChartToSvg(container: HTMLElement): string | null {
const instance = echarts.getInstanceByDom(container);
if (!instance) return null;

const options = instance.getOption();
const { width, height } = instance.getDom().getBoundingClientRect();

// Create an offscreen container for the SVG renderer
const offscreen = document.createElement("div");
offscreen.style.width = `${width}px`;
offscreen.style.height = `${height}px`;
offscreen.style.position = "absolute";
offscreen.style.left = "-9999px";
document.body.appendChild(offscreen);

try {
const themeName = isDarkMode() ? THEME_DARK : THEME_LIGHT;
const svgInstance = echarts.init(offscreen, themeName, {
renderer: "svg",
width,
height,
});
svgInstance.setOption(options);

// Extract the rendered SVG from the offscreen container
const svgEl = offscreen.querySelector("svg");
if (!svgEl) {
svgInstance.dispose();
return null;
}

const svgString = new XMLSerializer().serializeToString(svgEl);
svgInstance.dispose();
return svgString;
} finally {
document.body.removeChild(offscreen);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Dispose the ECharts instance in all code paths to prevent memory leaks.

If svgInstance.setOption(options) (line 312) or any subsequent operation throws an exception, svgInstance.dispose() won't be called, leaving the ECharts instance alive in memory. Each failed export attempt will leak an instance.

🔧 Proposed fix to ensure disposal in all paths
   try {
     const themeName = isDarkMode() ? THEME_DARK : THEME_LIGHT;
     const svgInstance = echarts.init(offscreen, themeName, {
       renderer: "svg",
       width,
       height,
     });
-    svgInstance.setOption(options);
-
-    // Extract the rendered SVG from the offscreen container
-    const svgEl = offscreen.querySelector("svg");
-    if (!svgEl) {
+    try {
+      svgInstance.setOption(options);
+
+      // Extract the rendered SVG from the offscreen container
+      const svgEl = offscreen.querySelector("svg");
+      if (!svgEl) {
+        return null;
+      }
+
+      return new XMLSerializer().serializeToString(svgEl);
+    } finally {
       svgInstance.dispose();
-      return null;
     }
-
-    const svgString = new XMLSerializer().serializeToString(svgEl);
-    svgInstance.dispose();
-    return svgString;
   } finally {
     document.body.removeChild(offscreen);
   }
🤖 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 `@component/src/charts/base-chart.tsx` around lines 290 - 327, In
exportChartToSvg, ensure the SVG-mode ECharts instance (svgInstance) is always
disposed to avoid leaks: move creation of svgInstance and any calls that can
throw (echarts.init and svgInstance.setOption) into a try block and call
svgInstance.dispose() in a finally block (or track svgInstance and dispose it in
the existing finally before removing the offscreen element); reference
svgInstance and the exportChartToSvg function to locate where to add the
guaranteed dispose.

@alfredo1996
alfredo1996 force-pushed the feat/issue-706-svg-export branch 2 times, most recently from defa723 to 8e4b989 Compare May 12, 2026 16:10
- Add "Export SVG" option to widget actions menu for ECharts-based charts
- Register SVG renderer alongside Canvas in base-chart
- exportChartToSvg() creates offscreen instance with SVG renderer,
  copies current chart options, serializes SVG, triggers download
- Only appears for widgets with isECharts capability (bar, line, pie,
  gauge, radar, sankey, treemap, sunburst, single-value)
- Add triggerSvgDownload() helper to export-utils

Closes #706

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@alfredo1996
alfredo1996 force-pushed the feat/issue-706-svg-export branch from 8e4b989 to 83c9b03 Compare May 13, 2026 13:30
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
35.3% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@alfredo1996
alfredo1996 merged commit f353e7c into dev May 14, 2026
12 of 13 checks passed
@alfredo1996
alfredo1996 deleted the feat/issue-706-svg-export branch May 16, 2026 17:47
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.

feat: export chart as SVG

2 participants