Skip to content

Download Image

This example demonstrates how to export the current flow as an image using Angular features and the html-to-image library.

The download functionality allows users to export the flow as a image, capturing the current state and layout.

  • Export: Downloads the flow as a PNG image.
  • Bounding Box Calculation: Ensures the exported image includes all nodes with proper margins.

The GenerateImageService handles the logic for exporting the flow as an image. It uses the html-to-image library and calculates the bounding box to ensure all nodes are included:

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);
}
}

Helper functions manage the download process and calculate the bounding box for the flow:

29 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,
};
};

The navigation bar provides a button to trigger the download action.

To accurately calculate the bounding box for the entire flow, all nodes are pulled and passed to the calculation. Also it passes the flow element reference to the service.

5 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: `
<div class="nav-bar">
<button (click)="download()">Download</button>
</div>
`,
styleUrls: ['./nav-bar-component.scss'],
})
export class NavBarComponent {
private readonly generateImageService = inject(GenerateImageService);
private readonly modelService = inject(NgDiagramModelService);
elementRef = input<ElementRef>();
async download(): Promise<void> {
if (this.elementRef()) {
const file = await this.generateImageService.generateImageFile(
this.modelService.getModel().getNodes(),
this.elementRef()?.nativeElement
);
downloadImage(file, `flow-diagram-${Date.now()}.png`);
}
}
}
  • Download: Exports the current flow as a PNG image using the bounding box and margin settings.
import '@angular/compiler';
import { Component } from '@angular/core';
import { provideNgDiagram } from 'ng-diagram';
import { DownloadImageComponent } from './download-image.component';
import { GenerateImageService } from './generate-image.service';
@Component({
selector: 'download-image-wrapper',
imports: [DownloadImageComponent],
template: ` <download-image></download-image> `,
styles: [
`
:host {
flex: 1;
display: flex;
position: relative;
flex-direction: column;
}
`,
],
providers: [GenerateImageService, provideNgDiagram()],
})
export class DownloadImageWrapperComponent {}