gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
239 lines (212 loc) • 9.63 kB
HTML
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Generating PDF with multiple Diagrams</title>
<meta name="description" content="A demonstration of generating a PDF file showing multiple diagrams that are not visible on the page." />
<!-- Copyright 1998-2023 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="https://unpkg.com/gojs"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script src="https://unpkg.com/svg-to-pdfkit"></script>
<script id="code">
// Remember three separate models for the diagram
const allModels = new go.Map();
// This just creates and initializes a Diagram.
// The details do not really matter for this demo.
var myDiagram = null;
function init() {
const $ = go.GraphObject.make;
myDiagram =
new go.Diagram("myDiagramDiv",
{
"grid.visible": true,
"undoManager.isEnabled": true
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "RoundedRectangle",
{ strokeWidth: 0, fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 },
new go.Binding("text"))
);
// create and remember three models
allModels.add("one", new go.GraphLinksModel(
{
nodeDataArray: [
{ key: 1, text: "one", color: "#FFAAAA" },
{ key: 2, text: "one" + " 1", color: "#FFAAAA" },
{ key: 3, text: "one" + " 2", color: "#FFAAAA" }
],
linkDataArray: [
{ from: 1, to: 2 },
{ from: 1, to: 3 }
]
}));
allModels.add("two", new go.GraphLinksModel(
{
nodeDataArray: [
{ key: 1, text: "two", color: "#AAFFAA", loc: "0 0" },
{ key: 2, text: "two" + " 1", color: "#AAFFAA", loc: "150 -50" },
{ key: 3, text: "two" + " 2", color: "#AAFFAA", loc: "-50 150" },
{ key: 4, text: "two" + " 3", color: "#AAFFAA", loc: "1150 150" }
],
linkDataArray: [
{ from: 1, to: 2 },
{ from: 1, to: 3 },
{ from: 1, to: 4 }
]
}));
allModels.add("three", new go.GraphLinksModel(
{
nodeDataArray: [
{ key: 1, text: "three", color: "#AAAAFF", loc: "0 0" },
{ key: 2, text: "three" + " 1", color: "#AAAAFF", loc: "-150 -150" },
{ key: 3, text: "three" + " 2", color: "#AAAAFF", loc: "150 150" },
{ key: 4, text: "three" + " 3", color: "#AAAAFF", loc: "-150 150" },
{ key: 5, text: "three" + " 4", color: "#AAAAFF", loc: "150 -150" }
],
linkDataArray: [
{ from: 1, to: 2 },
{ from: 1, to: 3 },
{ from: 1, to: 4 },
{ from: 1, to: 5 }
]
}));
fetchModel("one");
}
// Load a model for a given NAME; can be used without CALLBACK,
// which would initialize myDiagram rather than using the hidden diagram/div
function fetchModel(name, callback) {
if (!callback) callback = m => myDiagram.model = m;
const m = allModels.get(name) || allModels.get("one");
callback(m);
}
// Use a hidden diagram/div to render a diagram
function useModel(model, callback) {
// this initializes the model for an existing visible Diagram on the page,
// or this will create and initialize a new Diagram
const div = document.getElementById("myHiddenDiagramDiv");
const olddiag = go.Diagram.fromDiv(div);
if (olddiag) olddiag.div = null;
const diagram = new go.Diagram(div);
diagram.nodeTemplate = myDiagram.nodeTemplate;
diagram.addDiagramListener("InitialLayoutCompleted", callback);
diagram.model = model;
}
// This common function is called both when showing the PDF in an iframe and when downloading a PDF file.
// The options include:
// "pageSize", either "A4" or "LETTER" (the default)
// "layout", either "portrait" (the default) or "landscape"
// "margin" for the uniform page margin on each page (default is 36 pt)
// "padding" instead of the Diagram.padding when adjusting the Diagram.documentBounds for the area to render
// "imgWidth", size of diagram image for one page; defaults to the page width minus margins
// "imgHeight", size of diagram image for one page; defaults to the page height minus margins
// "parts", "background", "showTemporary", "showGrid", all are passed to Diagram.makeSvg
function generatePdf(action, namelist, options) {
if (!options) options = {};
const pageSize = (options.pageSize || "LETTER").toUpperCase();
if (pageSize !== "LETTER" && pageSize !== "A4") throw new Error("unknown page size: " + pageSize);
// LETTER: 612x792 pt == 816x1056 CSS units
// A4: 595.28x841.89 pt == 793.71x1122.52 CSS units
const pageWidth = (pageSize === "LETTER" ? 612 : 595.28) * 96 / 72; // convert from pt to CSS units
const pageHeight = (pageSize === "LETTER" ? 792 : 841.89) * 96 / 72;
const layout = (options.layout || "portrait").toLowerCase();
if (layout !== "portrait" && layout !== "landscape") throw new Error("unknown layout: " + layout);
if (layout === "landscape") {
const temp = pageWidth;
pageWidth = pageHeight;
pageHeight = temp;
}
const margin = options.margin !== undefined ? options.margin : 36; // pt: 0.5 inch margin on each side
const padding = options.padding !== undefined ? options.padding : new go.Margin(0); // CSS units
const imgWidth = options.imgWidth !== undefined ? options.imgWidth : (pageWidth-margin/72*96*2); // CSS units
const imgHeight = options.imgHeight !== undefined ? options.imgHeight : (pageHeight-margin/72*96*2); // CSS units
const pageOptions = {
size: pageSize,
margin: margin, // unit is pt
layout: layout
};
const namelistlength = namelist.length;
function generateDiagram(namelist, doc, stream, callback) {
if (namelist.length > 0) {
if (namelist.length !== namelistlength) doc.addPage(pageOptions);
const name = namelist.shift();
fetchModel(name, m =>
useModel(m, e => {
const diagram = e.diagram;
const bnds = diagram.documentBounds;
// add some descriptive text
doc.text("Diagram: " + name);
const db = diagram.documentBounds.copy().subtractMargin(diagram.padding).addMargin(padding);
const p = db.position;
const makeOptions = {};
if (options.parts !== undefined) makeOptions.parts = options.parts;
if (options.background !== undefined) makeOptions.background = options.background;
if (options.showTemporary !== undefined) makeOptions.showTemporary = options.showTemporary;
if (options.showGrid !== undefined) makeOptions.showGrid = options.showGrid;
makeOptions.scale = Math.min(1, Math.min(imgWidth/db.width, imgHeight/db.height));
makeOptions.position = new go.Point(p.x, p.y);
makeOptions.size = new go.Size(db.width, db.height);
const svg = diagram.makeSvg(makeOptions);
SVGtoPDF(doc, svg, doc.x, doc.y, null);
// then do rest of list
generateDiagram(namelist, doc, stream, callback);
})
);
} else { // no more diagrams to generate, so just finish up the PDF
callback();
}
} // end generateDiagram
require(["blob-stream", "pdfkit"], (blobStream, PDFDocument) => {
// start the PDF
const doc = new PDFDocument(pageOptions);
const stream = doc.pipe(blobStream());
generateDiagram(namelist, doc, stream,
() => { // finish up the PDF
doc.end();
stream.on('finish', () => action(stream.toBlob('application/pdf')));
});
});
} // end generatePdf
const namelist = ["one", "two", "three"]; // this could be used to generate the <ul> myList
const pdfOptions =
{
showTemporary: true, // default is false
// layout: "landscape", // instead of "portrait"
// pageSize: "A4" // instead of "LETTER"
};
function downloadPdf() { // called by HTML button
generatePdf(blob => {
const datauri = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.style = "display: none";
a.href = datauri;
a.download = "myDiagram.pdf";
document.body.appendChild(a);
requestAnimationFrame(() => {
a.click();
window.URL.revokeObjectURL(datauri);
document.body.removeChild(a);
});
}, namelist, pdfOptions);
}
</script>
</head>
<body onload="init()">
<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
Models:
<ul id="myList">
<li><button onclick="fetchModel(event.target.textContent)">one</button></li>
<li><button onclick="fetchModel(event.target.textContent)">two</button></li>
<li><button onclick="fetchModel(event.target.textContent)">three</button></li>
</ul>
<button onclick="downloadPdf()">Download PDF of all three diagrams</button>
<div id="myHiddenDiagramDiv" style="display:none"></div>
</div>
</body>
</html>