Download Image Example
This example demonstrates how to export the current flow as an image using Angular features and the html-to-image library.
import '@angular/compiler';import { Component, ElementRef, viewChild, type Signal } from '@angular/core';import { initializeModel, NgDiagramBackgroundComponent, NgDiagramComponent, NgDiagramModelService, provideNgDiagram, type NgDiagramConfig,} from 'ng-diagram';import { GenerateImageService } from './generate-image.service';import { NavBarComponent } from './nav-bar/nav-bar.component';
@Component({ imports: [NgDiagramComponent, NavBarComponent, NgDiagramBackgroundComponent], template: ` <div class="not-content diagram"> <ng-diagram #ngDiagram [model]="model" [config]="config"> <ng-diagram-background /> </ng-diagram> <div class="nav-bar"> <nav-bar [diagramRef]="diagramElementRef()"></nav-bar> </div> </div> `, styleUrl: './diagram.component.scss', providers: [GenerateImageService, NgDiagramModelService, provideNgDiagram()],})export class DiagramComponent { diagramElementRef: Signal<ElementRef<HTMLElement> | undefined> = viewChild( 'ngDiagram', { read: ElementRef } );
config = { zoom: { max: 3, zoomToFit: { onInit: true, }, }, } satisfies NgDiagramConfig;
model = initializeModel({ nodes: [ { id: 'MAIN ROOT', position: { x: 500, y: 50 }, data: {}, size: { width: 140, height: 50 }, }, { id: 'child1', position: { x: 250, y: 100 }, data: {}, size: { width: 120, height: 44 }, }, { id: 'child2', position: { x: 250, y: 0 }, data: {}, size: { width: 120, height: 44 }, }, { id: 'child3', position: { x: 750, y: 0 }, data: {}, size: { width: 120, height: 44 }, }, { id: 'leaf1', position: { x: 0, y: 150 }, data: {}, size: { width: 100, height: 38 }, }, { id: 'leaf2', position: { x: 0, y: 50 }, data: {}, size: { width: 100, height: 38 }, }, { id: 'leaf3', position: { x: 1000, y: 50 }, data: {}, size: { width: 100, height: 38 }, }, { id: 'leaf4', position: { x: 1000, y: -50 }, data: {}, size: { width: 100, height: 38 }, }, { id: 'leaf5', position: { x: 1000, y: -150 }, data: {}, size: { width: 100, height: 38 }, }, { id: 'leaf6', position: { x: 1000, y: 150 }, data: {}, size: { width: 100, height: 38 }, }, ], edges: [ { id: 'e1', data: {}, source: 'MAIN ROOT', target: 'child1', sourcePort: 'port-left', targetPort: 'port-right', }, { id: 'e2', data: {}, source: 'MAIN ROOT', target: 'child2', sourcePort: 'port-left', targetPort: 'port-right', }, { id: 'e3', data: {}, source: 'MAIN ROOT', target: 'child3', sourcePort: 'port-right', targetPort: 'port-left', }, { id: 'e4', data: {}, source: 'child1', target: 'leaf1', sourcePort: 'port-left', targetPort: 'port-right', }, { id: 'e5', data: {}, source: 'child1', target: 'leaf2', sourcePort: 'port-left', targetPort: 'port-right', }, { id: 'e6', data: {}, source: 'child3', target: 'leaf3', sourcePort: 'port-right', targetPort: 'port-left', }, { id: 'e7', data: {}, source: 'child3', target: 'leaf4', sourcePort: 'port-right', targetPort: 'port-left', }, { id: 'e8', data: {}, source: 'child3', target: 'leaf5', sourcePort: 'port-right', targetPort: 'port-left', }, { id: 'e9', data: {}, source: 'child3', target: 'leaf6', sourcePort: 'port-right', targetPort: 'port-left', }, ], });}28 collapsed lines
import { type Node } from 'ng-diagram';
export const downloadImage = (data: string | Blob, fileName: string) => { try { const anchor = document.createElement('a'); let url: string | null = null;
if (typeof data === 'string') { anchor.href = data; } else if (data instanceof Blob) { url = URL.createObjectURL(data); anchor.href = url; } else { throw new Error('Unsupported data type for download'); }
anchor.download = fileName; document.body.appendChild(anchor); anchor.click(); document.body.removeChild(anchor);
if (url) { URL.revokeObjectURL(url); } } catch (e) { console.error(e); }};
export const calculateBoundingBox = (nodes: Node[], margin: number) => { if (nodes.length === 0) { return { width: 0, height: 0, top: 0, left: 0, }; } let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
for (const node of nodes) { const nodeWidth = node.size?.width || 0; const nodeHeight = node.size?.height || 0;
minX = Math.min(minX, node.position.x); minY = Math.min(minY, node.position.y); maxX = Math.max(maxX, node.position.x + nodeWidth); maxY = Math.max(maxY, node.position.y + nodeHeight); }
return { width: maxX - minX + margin * 2, height: maxY - minY + margin * 2, left: minX - margin, top: minY - margin, };};7 collapsed lines
import { Injectable } from '@angular/core';import { toPng } from 'html-to-image';import type { Options } from 'html-to-image/lib/types';import { type Node } from 'ng-diagram';import { calculateBoundingBox } from './generate-image.helper';
@Injectable()export class GenerateImageService { private IMAGE_MARGIN: number = 50;
async generateImageFile(nodes: Node[], element: HTMLElement) { const size = calculateBoundingBox(nodes, this.IMAGE_MARGIN); const backgroundColor = getComputedStyle(element).backgroundColor || '#fff';
const options: Options = { backgroundColor, width: size.width, height: size.height, cacheBust: false, skipFonts: false, pixelRatio: 2, fetchRequestInit: { mode: 'cors' as RequestMode }, style: { transform: `translate(${Math.abs(size.left)}px, ${Math.abs(size.top)}px) scale(1)`, }, };
const diagramCanvasElement = element.getElementsByTagName( 'ng-diagram-canvas' )[0] as HTMLElement;
return await toPng(diagramCanvasElement, options); }}4 collapsed lines
import { Component, ElementRef, inject, input } from '@angular/core';import { NgDiagramModelService } from 'ng-diagram';import { downloadImage } from '../generate-image.helper';import { GenerateImageService } from '../generate-image.service';
@Component({ selector: 'nav-bar', template: `<button (click)="download()">Download Image</button>`,})export class NavBarComponent { private readonly generateImageService = inject(GenerateImageService); private readonly modelService = inject(NgDiagramModelService);
diagramRef = input<ElementRef<HTMLElement> | undefined>();
async download(): Promise<void> { const diagramRef = this.diagramRef(); if (diagramRef) { const file = await this.generateImageService.generateImageFile( this.modelService.getModel().getNodes(), diagramRef.nativeElement );
downloadImage(file, `flow-diagram-${Date.now()}.png`); } }}.diagram { position: relative; height: var(--ng-diagram-height); border: var(--ng-diagram-border); margin-top: 0;}
.nav-bar { position: absolute; top: 1rem; right: 1rem; display: flex; gap: 0.5px; align-items: flex-end; justify-content: end; padding: 1rem; background-color: var(--ngd-node-bg-primary-default); border: var(--ng-diagram-border);
button { margin-top: 0; }}Additional Explanation
Section titled “Additional Explanation”Key Concepts
Section titled “Key Concepts”- Export: Downloads the flow as a PNG image.
- Bounding Box Calculation: Ensures the exported image includes all nodes with proper margins.
Implementation Details
Section titled “Implementation Details”-
Generate Image Service: Handles the logic for exporting the flow as an image, using the
html-to-imagelibrary and bounding box calculation. -
Helper Functions: Manage the download process and calculate the bounding box for the flow.
-
NavBar Component: Provides a button to trigger the download action. Calculates the bounding box for the entire flow and passes the flow element reference to the service.
Actions
Section titled “Actions”- Download: Exports the current flow as a PNG image using the bounding box and margin settings.