Chart export can be useful for sharing, printing or keeping records of your charts.
In this demo you can adjust the content and size of images to best suit your needs using the extents and fitTo options, and export to a range of common formats.
The chart.export() function can be used to create raster images in high resolution, and vector images in SVG or PDF, preserving the important details of your KeyLines charts in all of the available output types.
For SVG, the export is optimised for viewing at full zoom, ensuring all details are crisp, even on the largest charts.
While here we show how to create a simple PDF with a full page chart image, it is also possible to incorporate charts into more content rich reports with PDF. See our PDF Reports demo for more information.
For further details read our documentation on Chart Export.
Saving image vs. saving chart
This demo saves an image of a chart and does not contain any other information or meta-data about the state of the chart.
If you want to be able to reload the state of the chart, use chart.serialize() as seen in the Save & Load Charts demo.
Key functions used:
import KeyLines from "keylines";
import PDFDocument from "pdfkit/js/pdfkit.standalone";
import SVGtoPDF from "svg-to-pdfkit";
import { data } from "./data";
// KeyLines' PDF export uses PDFKit and SVG-to-PDFKit from the global scope
window.PDFDocument = PDFDocument;
window.SVGtoPDF = SVGtoPDF;
let chart;
let type = "png";
const widthSlider = document.getElementById("widthSlider");
const widthLabel = document.getElementById("widthLabel");
const exportButton = document.getElementById("exportButton");
function initializeUI() {
const typeButtons = {};
function setExportType(newType) {
typeButtons[type].classList.remove("active", "btn-kl");
typeButtons[type].disabled = false;
typeButtons[newType].classList.add("active", "btn-kl");
typeButtons[newType].disabled = true;
type = newType;
widthSlider.disabled = type === "pdf" || type === "svg";
}
function setWidthLabel() {
const width = Math.round(widthSlider.value / 100) / 10;
widthLabel.innerText = `${width}`;
}
["png", "jpeg", "svg", "pdf"].forEach((exportType) => {
typeButtons[exportType] = document.getElementById(exportType);
typeButtons[exportType].addEventListener("click", () =>
setExportType(exportType),
);
});
widthSlider.addEventListener("input", setWidthLabel);
exportButton.addEventListener("click", exportChartImage);
}
function downloadImage(chartImage, format) {
// Create the link to download the image
const snapshotLink = document.createElement("a");
snapshotLink.download = `chart-export.${format}`;
snapshotLink.href = chartImage.url;
snapshotLink.click();
// Important - remember to revoke the url afterwards to free it from browser memory
URL.revokeObjectURL(chartImage.url);
}
async function exportChartImage() {
const extents = document.querySelector(
`input[type="radio"][name="extents"]:checked`,
).value;
let fitTo;
if (type === "pdf") {
// always fit to page for PDF export
fitTo = "page";
} else {
fitTo = {
width:
type === "svg"
? chart.viewOptions().width
: parseInt(widthSlider.value),
};
}
const fonts = {
"Font Awesome 5 Free Solid": {
src: "/fonts/fontAwesome5/fa-solid-900.woff",
},
};
const doc = {
size: "letter",
layout: "landscape",
margin: 36, // 0.5 inches
};
const chartImage = await chart.export({
type,
extents,
fitTo,
// only add fonts for SVG and PDF
...(type === "svg" || type === "pdf" ? { fonts } : {}),
...(type === "pdf" ? { doc } : {}),
});
downloadImage(chartImage, type);
}
// List of icons to realign
const imageAlignment = {
"fas fa-user": { dy: -10, e: 0.9 },
};
async function startKeyLines() {
const options = {
backColour: "rgb(250, 250, 250)",
selectedNode: { b: "#111", bw: 5, fbc: "#333", fc: "white" },
iconFontFamily: "Font Awesome 5 Free",
handMode: true,
logo: { u: "/public/images/Logo.png" },
// Font icons and images can often be poorly aligned.
// Set offsets to the icons to ensure they are centred correctly.
imageAlignment,
};
chart = await KeyLines.create({ container: "klchart", options });
chart.load(data.chartData);
chart.zoom("fit", { animate: false, ids: data.zoomNodes });
initializeUI();
}
async function loadFontsAndStart() {
// be sure to include the CSS file in the page with the @font-face definition
document.fonts.load("24px 'Font Awesome 5 Free'").then(startKeyLines);
}
window.addEventListener("DOMContentLoaded", loadFontsAndStart);export const data = {
chartData: {
type: "LinkChart",
items: [
{
id1: "N1",
id2: "N10",
id: "N1/p/N10",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N14",
id: "N1/p/N14",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N15",
id: "N1/p/N15",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N152",
id: "N1/p/N152",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N16",
id: "N1/p/N16",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N357",
id: "N1/p/N357",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N363",
id: "N1/p/N363",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N43",
id: "N1/p/N43",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N5",
id: "N1/p/N5",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N6",
id: "N1/p/N6",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N7",
id: "N1/p/N7",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N1",
id2: "N8",
id: "N1/p/N8",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N10",
id2: "N2",
id: "N10/p/N2",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N10",
id2: "N4",
id: "N10/p/N4",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N10",
id2: "N6",
id: "N10/p/N6",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N104",
id2: "N149",
id: "N104/p/N149",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N104",
id2: "N54",
id: "N104/p/N54",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N107",
id2: "N120",
id: "N107/p/N120",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N108",
id2: "N105",
id: "N108/p/N105",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N108",
id2: "N27",
id: "N108/p/N27",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N108",
id2: "N29",
id: "N108/p/N29",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N109",
id2: "N29",
id: "N109/p/N29",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N11",
id2: "N2",
id: "N11/p/N2",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N11",
id2: "N21",
id: "N11/p/N21",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N11",
id2: "N34",
id: "N11/p/N34",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N11",
id2: "N6",
id: "N11/p/N6",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N110",
id2: "N29",
id: "N110/p/N29",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N112",
id2: "N29",
id: "N112/p/N29",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N115",
id2: "N111",
id: "N115/p/N111",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N115",
id2: "N113",
id: "N115/p/N113",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N117",
id2: "N113",
id: "N117/p/N113",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N118",
id2: "N108",
id: "N118/p/N108",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N12",
id2: "N2",
id: "N12/p/N2",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N12",
id2: "N6",
id: "N12/p/N6",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N120",
id2: "N1",
id: "N120/p/N1",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N124",
id2: "N126",
id: "N124/p/N126",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N125",
id2: "N126",
id: "N125/p/N126",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N13",
id2: "N2",
id: "N13/p/N2",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N13",
id2: "N21",
id: "N13/p/N21",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N13",
id2: "N23",
id: "N13/p/N23",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N13",
id2: "N25",
id: "N13/p/N25",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N13",
id2: "N34",
id: "N13/p/N34",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N13",
id2: "N6",
id: "N13/p/N6",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N138",
id2: "N142",
id: "N138/p/N142",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N140",
id2: "N139",
id: "N140/p/N139",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N141",
id2: "N143",
id: "N141/p/N143",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N141",
id2: "N147",
id: "N141/p/N147",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N141",
id2: "N148",
id: "N141/p/N148",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N147",
id2: "N148",
id: "N147/p/N148",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N149",
id2: "N54",
id: "N149/p/N54",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N151",
id2: "N195",
id: "N151/p/N195",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N153",
id2: "N155",
id: "N153/p/N155",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N153",
id2: "N167",
id: "N153/p/N167",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N153",
id2: "N175",
id: "N153/p/N175",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N153",
id2: "N179",
id: "N153/p/N179",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N153",
id2: "N195",
id: "N153/p/N195",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N153",
id2: "N196",
id: "N153/p/N196",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N154",
id2: "N1",
id: "N154/p/N1",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N154",
id2: "N41",
id: "N154/p/N41",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N154",
id2: "N43",
id: "N154/p/N43",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N155",
id2: "N161",
id: "N155/p/N161",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N155",
id2: "N175",
id: "N155/p/N175",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N155",
id2: "N180",
id: "N155/p/N180",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N156",
id2: "N151",
id: "N156/p/N151",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N156",
id2: "N152",
id: "N156/p/N152",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N156",
id2: "N167",
id: "N156/p/N167",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N156",
id2: "N175",
id: "N156/p/N175",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N156",
id2: "N191",
id: "N156/p/N191",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N157",
id2: "N153",
id: "N157/p/N153",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N157",
id2: "N158",
id: "N157/p/N158",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N158",
id2: "N161",
id: "N158/p/N161",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N161",
id2: "N167",
id: "N161/p/N167",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N161",
id2: "N175",
id: "N161/p/N175",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N161",
id2: "N179",
id: "N161/p/N179",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N161",
id2: "N195",
id: "N161/p/N195",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N161",
id2: "N196",
id: "N161/p/N196",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N165",
id2: "N156",
id: "N165/p/N156",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N166",
id2: "N173",
id: "N166/p/N173",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N166",
id2: "N175",
id: "N166/p/N175",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N174",
id2: "N180",
id: "N174/p/N180",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N175",
id2: "N179",
id: "N175/p/N179",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N175",
id2: "N195",
id: "N175/p/N195",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N175",
id2: "N196",
id: "N175/p/N196",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N175",
id2: "N6",
id: "N175/p/N6",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N177",
id2: "N180",
id: "N177/p/N180",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N178",
id2: "N173",
id: "N178/p/N173",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N184",
id2: "N153",
id: "N184/p/N153",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N185",
id2: "N153",
id: "N185/p/N153",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N19",
id2: "N355",
id: "N19/p/N355",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N196",
id2: "N151",
id: "N196/p/N151",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N2",
id2: "N21",
id: "N2/p/N21",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N2",
id2: "N26",
id: "N2/p/N26",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N2",
id2: "N27",
id: "N2/p/N27",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N2",
id2: "N30",
id: "N2/p/N30",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N2",
id2: "N33",
id: "N2/p/N33",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N2",
id2: "N357",
id: "N2/p/N357",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N2",
id2: "N5",
id: "N2/p/N5",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N2",
id2: "N6",
id: "N2/p/N6",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N2",
id2: "N8",
id: "N2/p/N8",
c: "rgb(150,150,150)",
type: "link",
w: 1,
off: 0,
g: null,
},
{
id1: "N202",
id2: "N306",
id: "N202/p/N306",
c: "rgb(150,150,150)",
type: "link",
// ...truncated 18533 lines<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Export Chart</title>
<link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/keylines.css">
<link
rel="stylesheet"
type="text/css"
href="@ci/theme/kl/css/minimalsdk.css"
>
<link
rel="stylesheet"
type="text/css"
href="@ci/theme/kl/css/sdk-layout.css"
>
<link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/demo.css">
<link
rel="stylesheet"
type="text/css"
href="@ci/theme/kl/css/examples-chrome.css"
>
<link
rel="stylesheet"
type="text/css"
href="@fortawesome/[email protected]/css/fontawesome.css"
>
<link
rel="stylesheet"
type="text/css"
href="@fortawesome/[email protected]/css/solid.css"
>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="chart-wrapper demo-cols">
<div id="lhs" class="flex1">
<div
id="lhsVisual"
class="toggle-content is-visible"
style="width: 100%; height: 100%"
>
<div id="klchart" class="klchart"></div>
</div>
</div>
<div class="rhs citext" id="demorhscontent">
<div class="tab-content-panel">
<div id="controlsTab" class="toggle-content is-visible tabcontent">
<p>
Export charts in high resolution or vector graphic and customise
the output to fit your use.
</p>
<form id="rhsForm" autocomplete="off" onsubmit="return false;">
<div class="cicontent">
<div class="media-body">
<h4>Type</h4>
<div class="btn-group" id="exportTypes">
<input
class="btn btn-kl active"
type="button"
value="PNG"
disabled
id="png"
><input class="btn" type="button" value="JPEG" id="jpeg">
<input class="btn" type="button" value="SVG" id="svg">
<input class="btn" type="button" value="PDF" id="pdf">
</div>
<h4 id="exportContent">Content extents</h4>
<label class="radio inline"
><input
type="radio"
name="extents"
value="view"
checked
>Current view</label
><label class="radio inline"
><input type="radio" name="extents" value="chart">Whole
chart</label
>
<h4>
Image resolution: <span id="widthLabel">3</span>k pixels
</h4>
<div class="sliderGroup">
<div class="sliderScale">1k</div>
<div class="sliderContainer">
<input
id="widthSlider"
type="range"
min="1000"
max="5000"
value="3000"
>
</div>
<div class="sliderScale">5k</div>
</div>
<input
class="btn"
type="button"
value="Export Image"
style="width: 100%; margin-top: 10px"
id="exportButton"
>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<script type="module" src="./code.js"></script>
</body>
</html>/* --- export.css --- */
.klchart {
background-color: rgb(250, 250, 250);
}
.sliderGroup {
width: 100%;
height: 40px;
}
.sliderScale {
float: left;
line-height: 10px;
padding: 5px;
}
.sliderContainer {
float: left;
width: calc(100% - 200px);
}
#imageWidthSlider {
margin: 10px;
width: calc(100% - 20px);
}