//
// Copyright © 2011-2025 Cambridge Intelligence Limited.
// All rights reserved.
//
// Sample Code
//! Export charts in high resolution and multiple formats including SVG.
import KeyLines from "keylines";
import { data } from "./export-data.js";
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: "/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);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
<head>
<meta charset="UTF-8">
<title>Export Chart</title>
<link rel="stylesheet" type="text/css" href="/css/keylines.css">
<link rel="stylesheet" type="text/css" href="/css/minimalsdk.css">
<link rel="stylesheet" type="text/css" href="/css/sdk-layout.css">
<link rel="stylesheet" type="text/css" href="/css/demo.css">
<link rel="stylesheet" type="text/css" href="/export.css">
<link rel="stylesheet" type="text/css" href="/css/fontawesome5/fontawesome.css">
<link rel="stylesheet" type="text/css" href="/css/fontawesome5/solid.css">
<script src="/vendor/jquery.js" defer type="text/javascript"></script>
<script id="keylines" src="/public/keylines.umd.cjs" defer type="text/javascript"></script>
<script src="/vendor/pdfkit.standalone.js" defer type="text/javascript"></script>
<script src="/vendor/svg-to-pdfkit.js" defer type="text/javascript"></script>
<script src="/export.js" crossorigin="use-credentials" type="module"></script>
</head>
<body>
<div class="chart-wrapper demo-cols">
<div class="tab-content-panel flex1" id="lhs" data-tab-group="rhs">
<div class="toggle-content is-visible" id="lhsVisual" style="width:100%; height: 100%;">
<div class="klchart" id="klchart">
</div>
</div>
</div>
<div class="rhs citext closed" id="demorhscontent">
<div class="title-bar">
<svg viewBox="0 0 360 60" style="max-width: 360px; max-height: 60px; font-size: 24px; font-family: Raleway; flex: 1;">
<text x="0" y="38" style="width: 100%;">Export Chart</text>
</svg>
</div>
<div class="tab-content-panel" data-tab-group="rhs">
<div class="toggle-content is-visible tabcontent" id="controlsTab">
<p>Export charts in high resolution or vector graphic and customise the output to fit your use.</p>
<form autocomplete="off" onsubmit="return false" id="rhsForm">
<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>
<div id="moreParent">
<div id="moreContainer">
</div>
</div>
<div id="lazyScripts">
</div>
</body>
</html>
.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);
}
Loading source
