How to export the diagram along with other information to PDF? This CodePen does so with the help of the great jsPDF library and the export to canvas feature of JointJS.
Last active
November 14, 2024 09:58
-
-
Save plugn/80597a214cfc6b0d9d32dfccc1a39490 to your computer and use it in GitHub Desktop.
JointJS+: PDF Export
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="app"> | |
<div id="paper-container"></div> | |
</div> | |
<div id="toolbar-container"></div> | |
<a target="_blank" href="https://www.jointjs.com"> | |
<img id="logo" src="https://assets.codepen.io/7589991/jointjs-logo.svg" width="200" height="50"></img> | |
</a> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { jsPDF } from "https://cdn.skypack.dev/jspdf"; | |
const { dia, ui, shapes, util, format } = joint; | |
const backgroundColor = "#F3F7F6"; | |
// Paper | |
const graph = new dia.Graph({}, { cellNamespace: shapes }); | |
const paper = new dia.Paper({ | |
model: graph, | |
cellViewNamespace: shapes, | |
async: true, | |
sorting: dia.Paper.sorting.APPROX, | |
defaultConnectionPoint: { name: "boundary" }, | |
background: { color: backgroundColor }, | |
clickThreshold: 10 | |
}); | |
const scroller = new ui.PaperScroller({ | |
paper, | |
autoResizePaper: true, | |
baseWidth: 100, | |
baseHeight: 100, | |
padding: 100, | |
contentOptions: { | |
useModelGeometry: true, | |
padding: 100, | |
allowNewOrigin: "any" | |
} | |
}); | |
document.getElementById("paper-container").appendChild(scroller.el); | |
paper.on("blank:pointerdown", (evt) => scroller.startPanning(evt)); | |
// Example | |
graph.fromJSON({ | |
cells: [ | |
{ | |
id: "r3", | |
presentationOrder: 1, | |
type: "standard.Rectangle", | |
position: { x: 200, y: 80 }, | |
size: { width: 100, height: 60 }, | |
attrs: { | |
body: { | |
rx: 20, | |
ry: 20 | |
}, | |
label: { | |
text: "Start" | |
} | |
} | |
}, | |
{ | |
id: "p2", | |
presentationOrder: 2, | |
type: "standard.Path", | |
position: { x: 200, y: 230 }, | |
size: { width: 100, height: 60 }, | |
attrs: { | |
body: { | |
d: "M 20 0 H calc(w) L calc(w-20) calc(h) H 0 Z" | |
}, | |
label: { | |
text: "Input" | |
} | |
} | |
}, | |
{ | |
id: "p1", | |
presentationOrder: 3, | |
type: "standard.Path", | |
position: { x: 200, y: 400 }, | |
size: { width: 100, height: 100 }, | |
attrs: { | |
body: { | |
d: | |
"M 0 calc(0.5 * h) calc(0.5 * w) 0 calc(w) calc(0.5 * h) calc(0.5 * w) calc(h) Z" | |
}, | |
label: { | |
text: "Decision" | |
} | |
} | |
}, | |
{ | |
id: "r4", | |
type: "standard.Rectangle", | |
presentationOrder: 4, | |
position: { x: 200, y: 600 }, | |
size: { width: 100, height: 60 }, | |
attrs: { | |
label: { | |
text: "Process" | |
} | |
} | |
}, | |
{ | |
id: "e1", | |
presentationOrder: 5, | |
type: "standard.Ellipse", | |
position: { x: 220, y: 750 }, | |
size: { width: 60, height: 60 }, | |
attrs: { | |
label: { | |
text: "End" | |
} | |
} | |
}, | |
{ | |
id: "l1", | |
type: "standard.Link", | |
source: { id: "r3" }, | |
target: { id: "p2" } | |
}, | |
{ | |
id: "l2", | |
type: "standard.Link", | |
source: { id: "p2" }, | |
target: { id: "p1" } | |
}, | |
{ | |
id: "l3", | |
type: "standard.Link", | |
source: { id: "p1" }, | |
target: { id: "r4" }, | |
labels: [{ attrs: { text: { text: "Yes" } } }] | |
}, | |
{ | |
id: "l4", | |
type: "standard.Link", | |
source: { id: "p1" }, | |
target: { id: "p2" }, | |
vertices: [ | |
{ x: 400, y: 450 }, | |
{ x: 400, y: 260 } | |
], | |
labels: [{ attrs: { text: { text: "No" } } }] | |
}, | |
{ | |
id: "l5", | |
type: "standard.Link", | |
source: { id: "r4" }, | |
target: { id: "e1" } | |
} | |
] | |
}); | |
scroller.centerContent({ useModelGeometry: true }); | |
// Toolbar | |
const toolbar = new ui.Toolbar({ | |
theme: "modern", | |
tools: [ | |
{ | |
type: "button", | |
text: "PDF", | |
name: "pdf" | |
}, | |
{ | |
type: "button", | |
text: "PNG", | |
name: "png" | |
}, | |
{ | |
type: "button", | |
text: "SVG", | |
name: "svg" | |
}, | |
{ | |
type: "button", | |
text: "JSON", | |
name: "json" | |
} | |
] | |
}); | |
document.getElementById("toolbar-container").appendChild(toolbar.render().el); | |
const exportOptions = { | |
padding: 20, | |
useComputedStyles: false, | |
backgroundColor, | |
size: "2x" | |
}; | |
toolbar.on("pdf:pointerclick", () => { | |
const pageWidth = 595.28; | |
const pageHeight = 841.89; | |
const pageMargin = 20; | |
const doc = new jsPDF("p", "pt", [pageWidth, pageHeight]); | |
const imageMargin = 2; | |
const y = 50; | |
doc.text("JointJS Diagram", pageWidth / 2, y / 2, { align: "center" }); | |
format.toCanvas( | |
paper, | |
(canvas) => { | |
// Page 1. | |
const imageRect = new g.Rect(0, 0, canvas.width, canvas.height); | |
const maxImageRect = new g.Rect( | |
0, | |
0, | |
pageWidth - 2 * pageMargin, | |
pageHeight - y - pageMargin | |
); | |
const scale = maxImageRect.maxRectUniformScaleToFit( | |
imageRect, | |
new g.Point(0, 0) | |
); | |
const width = canvas.width * scale; | |
const height = canvas.height * scale; | |
const x = (pageWidth - width) / 2; | |
// draw a rectangle around the diagram | |
doc.setDrawColor(211, 211, 211); | |
doc.setFillColor(backgroundColor); | |
doc.setLineWidth(1); | |
doc.roundedRect( | |
x - imageMargin, | |
y - imageMargin, | |
width + 2 * imageMargin, | |
height + 2 * imageMargin, | |
2, | |
2, | |
"FD" | |
); | |
// draw the diagram from the canvas | |
doc.addImage(canvas, "JPEG", x, y, width, height); | |
// Page 2. | |
doc.addPage(); | |
doc.text("Shapes Table", pageWidth / 2, y / 2, { align: "center" }); | |
const getElementRows = (start) => { | |
const result = []; | |
let i = 1; | |
graph.search(start, (el) => { | |
const { x, y } = el.position().toJSON(); | |
result.push({ | |
id: i++, | |
name: el.attr(["label", "text"]), | |
type: el.get("type"), | |
x: String(x), | |
y: String(y) | |
}); | |
}); | |
return result; | |
}; | |
const headerNames = ["name", "type", "x", "y"]; | |
const headerWidths = [300, 300, 70, 70]; | |
const headers = headerNames.map((key, index) => { | |
return { | |
id: key, | |
name: key, | |
prompt: key, | |
width: headerWidths[index], | |
align: "center" | |
}; | |
}); | |
const [start] = graph.getSources(); | |
doc.table(pageMargin, y, getElementRows(start), headers, { | |
headerBackgroundColor: "#557AC5", | |
headerTextColor: "#FFFFFF" | |
}); | |
// download the PDF | |
doc.save("diagram.pdf"); | |
}, | |
{ ...exportOptions } | |
); | |
}); | |
// PNG | |
toolbar.on("png:pointerclick", () => { | |
format.toPNG( | |
paper, | |
(dataUri) => { | |
util.downloadDataUri(dataUri, "diagram.png"); | |
}, | |
exportOptions | |
); | |
}); | |
// SVG | |
toolbar.on("svg:pointerclick", () => { | |
format.toSVG( | |
paper, | |
(svg) => { | |
util.downloadDataUri( | |
`data:image/svg+xml,${encodeURIComponent(svg)}`, | |
"diagram.svg" | |
); | |
}, | |
exportOptions | |
); | |
}); | |
// JSON | |
toolbar.on("json:pointerclick", () => { | |
const str = JSON.stringify(graph.toJSON()); | |
const bytes = new TextEncoder().encode(str); | |
const blob = new Blob([bytes], { type: "application/json;charset=utf-8" }); | |
util.downloadBlob(blob, "diagram.json"); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.0/backbone-min.js"></script> | |
<script src="https://resources.jointjs.com/demos/rappid/build/package/rappid.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#paper-container { | |
position: absolute; | |
right: 0; | |
top: 0; | |
left: 0; | |
bottom: 0; | |
font-family: sans-serif; | |
.joint-paper { | |
border: 1px solid lightgray; | |
} | |
.selection-wrapper[data-selection-length="1"] { | |
display: block; | |
~ .selection-box { | |
display: none; | |
} | |
} | |
} | |
#toolbar-container { | |
position: absolute; | |
top: 20px; | |
left: 20px; | |
.joint-toolbar { | |
margin: 0; | |
padding: 4px; | |
button { | |
font-family: sans-serif; | |
font-size: 1.2em; | |
padding: 10px; | |
} | |
} | |
} | |
#logo { | |
position: absolute; | |
bottom: 20px; | |
right: 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<link href="https://resources.jointjs.com/demos/rappid/build/package/rappid.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment