//
//     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