This page covers live diagram exports in JavaScript, Angular, React and Vue, plus server-side SVG output; Qwik is outside its scope. Use it when you need a downloadable image or PDF from a live diagram, or a static SVG from a server process with no DOM. The server-side path uses the framework-independent static renderer.
For how to mount a diagram and obtain its live instance in JavaScript and the framework bindings, see Build runnable workflows; this page adds image and PDF downloads plus DOM-free SVG rendering.
Download an image from a live diagram
Mount a real diagram first, then call the instance's async export() method from a user action. PNG returns an image data URL; SVG returns its source string, which you can wrap in an SVG data URL for download. The export includes the diagram's rendered labels, edges and styling; PNG at scale: 2 requests a 2× raster.
- Mount a diagram with at least one node and edge, then call
export()from a user action. The examples use the same three-node flow; their buttons downloaddiagram.pngordiagram.svgfrom the mounted canvas.
jsimport { render } from '@grafloria/element';
const host = document.createElement('div');
host.style.height = '400px';
const pngButton = document.createElement('button');
pngButton.textContent = 'Download PNG';
const svgButton = document.createElement('button');
svgButton.textContent = 'Download SVG';
const status = document.createElement('span');
document.body.append(pngButton, svgButton, status, host);
const instance = render({
nodes: [
{ id: 'ingest', label: 'Ingest', position: { x: 40, y: 80 }, size: { width: 150, height: 66 } },
{ id: 'transform', label: 'Transform', position: { x: 260, y: 80 }, size: { width: 150, height: 66 } },
{ id: 'publish', label: 'Publish', position: { x: 480, y: 80 }, size: { width: 150, height: 66 } },
],
edges: [
{ id: 'e1', source: 'ingest', target: 'transform', label: 'rows' },
{ id: 'e2', source: 'transform', target: 'publish' },
],
}, host);
instance.renderNow();
function download(href, name) {
const anchor = document.createElement('a');
anchor.href = href;
anchor.download = name;
anchor.click();
}
async function downloadPng() {
const image = await instance.export('png', { scale: 2 });
download(image, 'diagram.png');
status.textContent = 'PNG download started.';
}
async function downloadSvg() {
const svg = await instance.export('svg');
download(`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`, 'diagram.svg');
status.textContent = 'SVG download started.';
}
pngButton.addEventListener('click', downloadPng);
svgButton.addEventListener('click', downloadSvg);
tsimport { Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
@Component({
standalone: true,
imports: [DiagramCanvasComponent],
template: `
<button type="button" (click)="downloadPng()">Download PNG</button>
<button type="button" (click)="downloadSvg()">Download SVG</button>
<span>{{ note }}</span>
<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges"
style="display:block; height:400px" />
`,
})
export class DiagramExportComponent {
readonly canvas = viewChild.required(DiagramCanvasComponent);
note = 'Choose an image format to download.';
nodes: NodeSpec[] = [
{ id: 'ingest', label: 'Ingest', position: { x: 40, y: 80 }, size: { width: 150, height: 66 } },
{ id: 'transform', label: 'Transform', position: { x: 260, y: 80 }, size: { width: 150, height: 66 } },
{ id: 'publish', label: 'Publish', position: { x: 480, y: 80 }, size: { width: 150, height: 66 } },
];
edges: EdgeSpec[] = [
{ id: 'e1', source: 'ingest', target: 'transform', label: 'rows' },
{ id: 'e2', source: 'transform', target: 'publish' },
];
async downloadPng(): Promise<void> {
const href = await this.canvas().exportDiagram('png', { scale: 2 });
this.download(href, 'diagram.png');
this.note = 'PNG download started.';
}
async downloadSvg(): Promise<void> {
const svg = await this.canvas().exportDiagram('svg');
this.download(`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`, 'diagram.svg');
this.note = 'SVG download started.';
}
private download(href: string, name: string): void {
const anchor = document.createElement('a');
anchor.href = href;
anchor.download = name;
anchor.click();
}
}
vue<script setup lang="ts"> import { GrafloriaFlow, type DiagramInstance } from '@grafloria/vue'; import type { EdgeSpec, NodeSpec } from '@grafloria/renderer'; import { ref } from 'vue'; const nodes: NodeSpec[] = [ { id: 'ingest', label: 'Ingest', position: { x: 40, y: 80 }, size: { width: 150, height: 66 } }, { id: 'transform', label: 'Transform', position: { x: 260, y: 80 }, size: { width: 150, height: 66 } }, { id: 'publish', label: 'Publish', position: { x: 480, y: 80 }, size: { width: 150, height: 66 } }, ]; const edges: EdgeSpec[] = [ { id: 'e1', source: 'ingest', target: 'transform', label: 'rows' }, { id: 'e2', source: 'transform', target: 'publish' }, ]; const note = ref('Choose an image format to download.'); let instance: DiagramInstance | null = null; async function downloadImage(format: 'png' | 'svg'): Promise<void> { const live = instance; if (!live) return; const output = format === 'png' ? await live.export('png', { scale: 2 }) : await live.export('svg'); const href = format === 'svg' ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(output)}` : output; const anchor = document.createElement('a'); anchor.href = href; anchor.download = `diagram.${format}`; anchor.click(); note.value = `${format.toUpperCase()} download started.`; } function onInit(live: DiagramInstance): void { instance = live; } </script> <template> <button type="button" @click="downloadImage('png')">Download PNG</button> <button type="button" @click="downloadImage('svg')">Download SVG</button> <span>{{ note }}</span> <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" style="display:block; height:400px" @init="onInit" /> </template>
After either button, the page reports that the PNG or SVG download started, and the browser downloads the selected file. The PNG contains a rasterized export; the SVG is vector markup you can open in a text editor or scale without rasterizing.
Open the live image-export demo or view its source.
Download a PDF
- Add a PDF action to the mounted diagram and call
export('pdf'). The result is a PDF data URL, so the browser can download it with the same anchor pattern. This output is a vector PDF: node paths remain paths and labels remain selectable text.
jsimport { render } from '@grafloria/element';
const host = document.createElement('div');
host.style.height = '400px';
const pdfButton = document.createElement('button');
pdfButton.textContent = 'Download PDF';
const status = document.createElement('span');
document.body.append(pdfButton, status, host);
const instance = render({
nodes: [
{ id: 'requirements', label: 'Requirements', position: { x: 40, y: 80 }, size: { width: 170, height: 66 } },
{ id: 'design', label: 'Design', position: { x: 280, y: 80 }, size: { width: 170, height: 66 } },
{ id: 'ship', label: 'Ship', position: { x: 520, y: 80 }, size: { width: 170, height: 66 } },
],
edges: [
{ id: 'e1', source: 'requirements', target: 'design' },
{ id: 'e2', source: 'design', target: 'ship' },
],
}, host);
instance.renderNow();
async function downloadPdf() {
const href = await instance.export('pdf');
const anchor = document.createElement('a');
anchor.href = href;
anchor.download = 'diagram.pdf';
anchor.click();
status.textContent = 'PDF download started.';
}
pdfButton.addEventListener('click', () => void downloadPdf());
tsimport { Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
@Component({
standalone: true,
imports: [DiagramCanvasComponent],
template: `
<button type="button" (click)="downloadPdf()">Download PDF</button>
<span>{{ note }}</span>
<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges"
style="display:block; height:400px" />
`,
})
export class PdfExportComponent {
readonly canvas = viewChild.required(DiagramCanvasComponent);
note = 'Choose PDF to download.';
nodes: NodeSpec[] = [
{ id: 'requirements', label: 'Requirements', position: { x: 40, y: 80 }, size: { width: 170, height: 66 } },
{ id: 'design', label: 'Design', position: { x: 280, y: 80 }, size: { width: 170, height: 66 } },
{ id: 'ship', label: 'Ship', position: { x: 520, y: 80 }, size: { width: 170, height: 66 } },
];
edges: EdgeSpec[] = [
{ id: 'e1', source: 'requirements', target: 'design' },
{ id: 'e2', source: 'design', target: 'ship' },
];
async downloadPdf(): Promise<void> {
const href = await this.canvas().exportDiagram('pdf');
const anchor = document.createElement('a');
anchor.href = href;
anchor.download = 'diagram.pdf';
anchor.click();
this.note = 'PDF download started.';
}
}
tsximport { useRef, useState } from 'react';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const nodes: NodeSpec[] = [
{ id: 'requirements', label: 'Requirements', position: { x: 40, y: 80 }, size: { width: 170, height: 66 } },
{ id: 'design', label: 'Design', position: { x: 280, y: 80 }, size: { width: 170, height: 66 } },
{ id: 'ship', label: 'Ship', position: { x: 520, y: 80 }, size: { width: 170, height: 66 } },
];
const edges: EdgeSpec[] = [
{ id: 'e1', source: 'requirements', target: 'design' },
{ id: 'e2', source: 'design', target: 'ship' },
];
export default function PdfExport() {
const instanceRef = useRef<DiagramInstance | null>(null);
const [note, setNote] = useState('Choose PDF to download.');
async function downloadPdf(): Promise<void> {
const instance = instanceRef.current;
if (!instance) return;
const href = await instance.export('pdf');
const anchor = document.createElement('a');
anchor.href = href;
anchor.download = 'diagram.pdf';
anchor.click();
setNote('PDF download started.');
}
return (
<div>
<button type="button" onClick={() => void downloadPdf()}>Download PDF</button>
<span>{note}</span>
<GrafloriaFlow
defaultNodes={nodes}
defaultEdges={edges}
style={{ display: 'block', height: '400px' }}
onInit={(instance) => { instanceRef.current = instance; }}
/>
</div>
);
}
vue<script setup lang="ts"> import { GrafloriaFlow, type DiagramInstance } from '@grafloria/vue'; import type { EdgeSpec, NodeSpec } from '@grafloria/renderer'; import { ref } from 'vue'; const nodes: NodeSpec[] = [ { id: 'requirements', label: 'Requirements', position: { x: 40, y: 80 }, size: { width: 170, height: 66 } }, { id: 'design', label: 'Design', position: { x: 280, y: 80 }, size: { width: 170, height: 66 } }, { id: 'ship', label: 'Ship', position: { x: 520, y: 80 }, size: { width: 170, height: 66 } }, ]; const edges: EdgeSpec[] = [ { id: 'e1', source: 'requirements', target: 'design' }, { id: 'e2', source: 'design', target: 'ship' }, ]; const note = ref('Choose PDF to download.'); let instance: DiagramInstance | null = null; async function downloadPdf(): Promise<void> { const live = instance; if (!live) return; const href = await live.export('pdf'); const anchor = document.createElement('a'); anchor.href = href; anchor.download = 'diagram.pdf'; anchor.click(); note.value = 'PDF download started.'; } function onInit(live: DiagramInstance): void { instance = live; } </script> <template> <button type="button" @click="downloadPdf">Download PDF</button> <span>{{ note }}</span> <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" style="display:block; height:400px" @init="onInit" /> </template>
The page reports that the PDF download started, and the browser downloads diagram.pdf. Select and copy a node label in a PDF reader to confirm that the text is selectable rather than part of a screenshot.
Open the live PDF-export demo or view its source.
Render an SVG on the server
- For a server-rendered artifact, call
renderStaticwith plain node and edge specs. It returnssvgand the stylesheetcsswithout mounting a canvas or needing a DOM. Inline the stylesheet into the SVG before writing the file so the artifact carries its styles.
This Node.js ESM example writes a self-contained diagram.svg; render the same specs again to produce the same SVG and CSS.
tsimport { writeFile } from 'node:fs/promises';
import { renderStatic } from '@grafloria/element';
const result = renderStatic({
nodes: [
{ id: 'extract', label: 'Extract', position: { x: 40, y: 40 }, size: { width: 150, height: 66 } },
{ id: 'load', label: 'Load', position: { x: 260, y: 40 }, size: { width: 150, height: 66 } },
{ id: 'model', label: 'Model', position: { x: 150, y: 170 }, size: { width: 150, height: 66 } },
],
edges: [
{ id: 'e1', source: 'extract', target: 'load' },
{ id: 'e2', source: 'extract', target: 'model' },
],
width: 520,
height: 300,
standalone: true,
instanceId: 'pipeline-diagram',
});
const artifact = result.svg.replace(
/^(<svg[^>]*>)/,
`$1<style>${result.css}</style>`,
);
async function writeArtifact(): Promise<void> {
await writeFile('diagram.svg', artifact, 'utf8');
}
void writeArtifact();
The saved SVG contains the Extract, Load and Model nodes with their edges; the call returns { html, svg, css, snapshot }, and only svg plus its inlined CSS goes into this standalone file. renderStatic() supports SVG-rendered diagram content, not framework custom-node components.
Open the live server-side export demo or view its source.
Options and caveats
The instance's export() returns a Promise<string>: SVG source for svg, and a data: URL for PNG, JPEG, WebP and PDF. It waits for asynchronous custom-node painters before capturing them. Angular exposes the equivalent async path as exportDiagram(format, options) on the canvas component.
| Option | Type | Default | What it does |
|---|---|---|---|
scale | number | 1 | Sets raster image scale. |
quality | number from 0 to 1 | 0.92 | Sets JPEG or WebP quality. |
backgroundColor | string | transparent | Sets the export background color. |
viewport | Rectangle | content bounds | Selects a world-space rectangle to export. |
padding | number | 20 | Adds margin in world units around content bounds; ignored when viewport is explicit. |
embedModel | boolean | not specified | Embeds the source model in SVG metadata or a PNG text chunk for editable round-trips; JPEG and WebP ignore it. |
onWarnings | (warnings: string[]) => void | not specified | Receives fidelity warnings for the export. |
export() needs a painted live canvas; call it in response to an action after mounting, not before the first paint. For SVG downloads, wrap the returned markup in an SVG data URL as the examples do. External images can depend on browser fetch/CORS access; check onWarnings when an asset or custom-node capture does not appear. The synchronous exportSvgString() and exportPdf() instance methods are alternatives when you need immediate results; they return result objects with warnings and do not wait for asynchronous painters or fetch external assets.
Was this page helpful?