Search

PDF Reports

Export

Export your charts in highly customised PDF reports.

PDF Reports
View live example →

PDF is a popular format for creating documents and reports as it's highly customisable and preserves important details.

This demo shows how to use chart.export() to create PDF reports with custom layouts, fonts and embedded KeyLines charts in vector quality.

Tailor-made layouts

Different documents may have different layout or text requirements, which is why the doc option gives you full control over the appearance of the exported PDF.

Passing an instance of PDFDocument to doc allows you to customise the page layout using the methods provided by PDFKit, such as text(), font() and list().

Customised fonts

The fonts option tackles the limited default font support in PDF by letting you embed your own fonts. Optionally, you can also use the specify separate font files for 'regular' and 'bold' versions using the weight property.

To minimise the size of the output file the export will only embed fonts that were used in the exported image.

Before you start

KeyLines uses external dependencies to run PDF export:

See Adding dependencies for more details and guidance.

Note that some viewers and in-browser viewers may not fully support viewing PDF. For example, we do not recommend using Firefox built-in PDF viewer.

Key functions used:

import KeyLines from "keylines";
import { data, defaultStyle, lorem, initZoomIds } from "./data.js";

const margin = 0.4 * 72; // 72dpi so this is 0.4inches
const iconFonts = {
  "Material Icons": { src: "/fonts/MaterialIcons/MaterialIcons-Regular.ttf" },
};
const ralewayFontSrcs = [
  { name: "Raleway-Bold", src: "/fonts/Raleway/Raleway-Bold.ttf" },
  { name: "Raleway-Regular", src: "/fonts/Raleway/Raleway-Regular.ttf" },
  { name: "Raleway-Italic", src: "/fonts/Raleway/Raleway-Italic.ttf" },
];
const timesNewFont = { regular: "Times-Roman", bold: "Times-Bold", italic: "Times-Italic" };
const helveticaFont = { regular: "Helvetica", bold: "Helvetica-Bold", italic: "Helvetica-Oblique" };
const ralewayFont = { regular: "Raleway-Regular", bold: "Raleway-Bold", italic: "Raleway-Italic" };

let chart;

function downloadPdf(pdfUrl) {
  // Create the link to download the image
  const snapshotLink = document.createElement("a");
  snapshotLink.download = `chart-export.pdf`;
  snapshotLink.href = pdfUrl;
  snapshotLink.click();
  // Important - remember to revoke the url afterwards to free it from browser memory
  URL.revokeObjectURL(pdfUrl);
}

async function addExportedChartAndCaption(width, height, fontFamily, captionText, doc) {
  doc.font(fontFamily.italic, 10);
  const captionHeight = doc.heightOfString(captionText);

  // add chart
  await chart.export({
    type: "pdf",
    doc,
    fonts: iconFonts,
    extents: "view",
    fitTo: {
      width,
      height: height - captionHeight,
    },
  });

  // add caption
  doc.moveDown().text(captionText, { align: "center", width });
}

async function loadAndRegisterFonts(fontSrcs, doc) {
  let error = false;
  for (let i = 0; i < fontSrcs.length; i++) {
    const font = fontSrcs[i];
    const response = await fetch(font.src);
    if (!response.ok) {
      console.warn(`Unable to load font ${font.name}`);
      error = true;
    }
    const fontBuffer = await response.arrayBuffer();
    doc.registerFont(font.name, fontBuffer);
  }
  return !error;
}

async function generateLandscapeReport(doc, docTitle, captionText) {
  const fontsLoaded = await loadAndRegisterFonts(ralewayFontSrcs, doc);
  const fontFamily = fontsLoaded ? ralewayFont : helveticaFont;
  // Add title
  doc.font(fontFamily.bold, 35).text(docTitle, { align: "center" });
  // Allowing a margin separation between them,
  // calculate the remaining space for the chart & bullet points
  const chartWidth = ((doc.page.width - 3 * margin) * 2) / 3;
  const contentHeight = doc.page.height - margin - doc.y;
  const chartHeight = (contentHeight * 4) / 5;
  // Split the padding equally above and below the main content
  doc.y += (contentHeight - chartHeight) / 2;
  const contentTop = doc.y;
  await addExportedChartAndCaption(chartWidth, chartHeight, fontFamily, captionText, doc);
  // Add the bullet points to the right of the chart
  doc.x += chartWidth + margin;
  doc.y = contentTop;
  doc.font(fontFamily.regular, 16);
  for (let i = 0; i < 4; i++) {
    doc.list([lorem[4]]).moveDown();
  }
}

async function generatePortraitReport(doc, docTitle, captionText) {
  const fontFamily = timesNewFont;
  // Add title
  doc.font(fontFamily.bold, 20).text(docTitle, { align: "center" });
  const contentWidth = doc.page.width - 2 * margin;
  const chartHeight = (doc.page.height - margin - doc.y) * 0.5;
  await addExportedChartAndCaption(contentWidth, chartHeight, fontFamily, captionText, doc);
  doc.moveDown(2);
  // Add columns of text
  doc.font(fontFamily.regular, 13);
  doc.text(lorem[3], { align: "justify", columns: 2 });
}

async function exportReport() {
  const orientation = document.querySelector('input[name="orientation"]:checked').value;
  const docTitle = document.getElementById("title").value;
  const captionText = document.getElementById("caption").value;
  // Create the PDF documnet
  const doc = new PDFDocument({ layout: orientation, margin });
  const reportGenerator =
    orientation === "landscape" ? generateLandscapeReport : generatePortraitReport;
  await reportGenerator(doc, docTitle, captionText);

  const docData = [];
  doc.on("data", docData.push.bind(docData));
  doc.on("end", () => {
    downloadPdf(URL.createObjectURL(new Blob(docData, { type: "application/pdf" })));
  });
  doc.end();
}

async function startKeyLines() {
  const options = {
    backColour: "#F0F8FF",
    selectedNode: { b: "#111", bw: 5, fbc: "#333", fc: "white" },
    iconFontFamily: "Material Icons",
    imageAlignment: defaultStyle.imageAlignment,
    handMode: true,
    logo: { u: "/images/Logo.png" },
  };

  chart = await KeyLines.create({ container: "klchart", options });
  data.items.forEach((item) => {
    if (item.type === "node") {
      const kind = item.d.kind;
      const icon = defaultStyle.kindIcons[kind];
      item.fi = {
        t: icon,
        c: defaultStyle.nodeColours[kind],
      };
    }
  });
  await chart.load(data);
  await chart.layout("organic", { fit: false });
  chart.zoom("fit", { animate: true, time: 500, ids: initZoomIds });

  document.getElementById("exportDoc").addEventListener("click", exportReport);
}

async function loadFontsAndStart() {
  // wait for Material icons to load before starting keylines
  document.fonts.load("24px 'Material Icons'").then(startKeyLines);
}

window.addEventListener("DOMContentLoaded", loadFontsAndStart);
export const data = {
  type: "LinkChart",
  items: [
    {
      type: "link",
      id: "252-250",
      id1: "252",
      id2: "250",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "293-291",
      id1: "293",
      id2: "291",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "252-249",
      id1: "252",
      id2: "249",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "293-290",
      id1: "293",
      id2: "290",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "253-252",
      id1: "253",
      id2: "252",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "294-293",
      id1: "294",
      id2: "293",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "447-294",
      id1: "447",
      id2: "294",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "476-253",
      id1: "476",
      id2: "253",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "663-253",
      id1: "663",
      id2: "253",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "447-3",
      id1: "447",
      id2: "3",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "476-3",
      id1: "476",
      id2: "3",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "663-3",
      id1: "663",
      id2: "3",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "445-447",
      id1: "445",
      id2: "447",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "446-447",
      id1: "446",
      id2: "447",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "469-476",
      id1: "469",
      id2: "476",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "470-476",
      id1: "470",
      id2: "476",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "471-476",
      id1: "471",
      id2: "476",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "472-476",
      id1: "472",
      id2: "476",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "473-476",
      id1: "473",
      id2: "476",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "474-476",
      id1: "474",
      id2: "476",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "475-476",
      id1: "475",
      id2: "476",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "655-663",
      id1: "655",
      id2: "663",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "656-663",
      id1: "656",
      id2: "663",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "657-663",
      id1: "657",
      id2: "663",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "658-663",
      id1: "658",
      id2: "663",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "659-663",
      id1: "659",
      id2: "663",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "660-663",
      id1: "660",
      id2: "663",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "661-663",
      id1: "661",
      id2: "663",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "662-663",
      id1: "662",
      id2: "663",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "447-292",
      id1: "447",
      id2: "292",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "476-251",
      id1: "476",
      id2: "251",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "663-251",
      id1: "663",
      id2: "251",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "447-437",
      id1: "447",
      id2: "437",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "476-460",
      id1: "476",
      id2: "460",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "476-464",
      id1: "476",
      id2: "464",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "663-654",
      id1: "663",
      id2: "654",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "447-441",
      id1: "447",
      id2: "441",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "447-444",
      id1: "447",
      id2: "444",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "476-468",
      id1: "476",
      id2: "468",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-445",
      id1: "3",
      id2: "445",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-446",
      id1: "3",
      id2: "446",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-469",
      id1: "3",
      id2: "469",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-470",
      id1: "3",
      id2: "470",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-471",
      id1: "3",
      id2: "471",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-472",
      id1: "3",
      id2: "472",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-473",
      id1: "3",
      id2: "473",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-474",
      id1: "3",
      id2: "474",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-475",
      id1: "3",
      id2: "475",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-655",
      id1: "3",
      id2: "655",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-656",
      id1: "3",
      id2: "656",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-657",
      id1: "3",
      id2: "657",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-658",
      id1: "3",
      id2: "658",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-659",
      id1: "3",
      id2: "659",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-660",
      id1: "3",
      id2: "660",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-661",
      id1: "3",
      id2: "661",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "garage-damages-3-662",
      id1: "3",
      id2: "662",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-252-460",
      id1: "252",
      id2: "460",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-252-464",
      id1: "252",
      id2: "464",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-252-468",
      id1: "252",
      id2: "468",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-252-654",
      id1: "252",
      id2: "654",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-293-437",
      id1: "293",
      id2: "437",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-293-441",
      id1: "293",
      id2: "441",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-293-444",
      id1: "293",
      id2: "444",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-437-441",
      id1: "437",
      id2: "441",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-437-444",
      id1: "437",
      id2: "444",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-441-444",
      id1: "441",
      id2: "444",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-460-464",
      id1: "460",
      id2: "464",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-460-468",
      id1: "460",
      id2: "468",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "people-464-468",
      id1: "464",
      id2: "468",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "person-garage-3-252",
      id1: "3",
      id2: "252",
      w: 3,
      off: 0,
    },
    {
      type: "link",
      id: "person-garage-3-293",
      id1: "3",
      id2: "293",
      w: 3,
      off: 0,
    },
    {
      type: "node",
      id: "3",
      t: "Torphy - Ebert",
      d: {
        kind: "garage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(0,107,95,1)",
      x: -1497.4758377075195,
      y: -1202.4347877502441,
    },
    {
      type: "node",
      id: "249",
      t: "0065 Cordia Throughway, Vivienneton",
      d: {
        kind: "address",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(164,39,104,1)",
      x: -1466.7487869262695,
      y: -454.68124771118164,
    },
    {
      type: "node",
      id: "250",
      t: "055 9497 2435",
      d: {
        kind: "telephone",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(164,39,104,1)",
      x: -1550.6120681762695,
      y: -432.18075942993164,
    },
    {
      type: "node",
      id: "251",
      t: "QWE09EE",
      d: {
        kind: "vehicle",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(127,203,104,1)",
      x: -1475.888557434082,
      y: -870.048038482666,
    },
    {
      type: "node",
      id: "252",
      t: "Ellen Larkin",
      d: {
        kind: "person",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(166,116,186,1)",
      x: -1564.0012283325195,
      y: -686.4468421936035,
    },
    {
      type: "node",
      id: "253",
      t: "Policy 910",
      d: {
        kind: "policy",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(31,120,180,1)",
      x: -1450.9795608520508,
      y: -805.0684852600098,
    },
    {
      type: "node",
      id: "290",
      t: "2253 Desiree Drive, Lake Cadeside",
      d: {
        kind: "address",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(164,39,104,1)",
      x: -1216.8798904418945,
      y: -1862.4563331604004,
    },
    {
      type: "node",
      id: "291",
      t: "0313 652 3599",
      d: {
        kind: "telephone",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(164,39,104,1)",
      x: -1267.6861038208008,
      y: -1925.0965309143066,
    },
    {
      type: "node",
      id: "292",
      t: "ASD984R",
      d: {
        kind: "vehicle",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(127,203,104,1)",
      x: -1759.520393371582,
      y: -1844.9278297424316,
    },
    {
      type: "node",
      id: "293",
      t: "Ahmad Lemke",
      d: {
        kind: "person",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(166,116,186,1)",
      x: -1414.8723831176758,
      y: -1728.5325050354004,
    },
    {
      type: "node",
      id: "294",
      t: "Policy 961",
      d: {
        kind: "policy",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(31,120,180,1)",
      x: -1520.4905471801758,
      y: -1879.7627296447754,
    },
    {
      type: "node",
      id: "437",
      t: "August Fay DVM",
      d: {
        kind: "person",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(166,116,186,1)",
      x: -1417.4956741333008,
      y: -1924.5281105041504,
    },
    {
      type: "node",
      id: "441",
      t: "Lora Nitzsche",
      d: {
        kind: "person",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(166,116,186,1)",
      x: -1518.036994934082,
      y: -1976.313877105713,
    },
    {
      type: "node",
      id: "444",
      t: "Yesenia Schinner",
      d: {
        kind: "person",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(166,116,186,1)",
      x: -1610.619026184082,
      y: -1909.568515777588,
    },
    {
      type: "node",
      id: "445",
      t: "Damage 129",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1532.5708084106445,
      y: -1479.2611122131348,
    },
    {
      type: "node",
      id: "446",
      t: "Damage 294",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1633.2661209106445,
      y: -1465.8401832580566,
    },
    {
      type: "node",
      id: "447",
      t: "Claim 451",
      d: {
        kind: "claim",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,47,63,1)",
      x: -1573.300666809082,
      y: -1687.7504615783691,
    },
    {
      type: "node",
      id: "460",
      t: "Nya Reilly",
      d: {
        kind: "person",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(166,116,186,1)",
      x: -1756.694465637207,
      y: -634.4872779846191,
    },
    {
      type: "node",
      id: "464",
      t: "Linda Kub",
      d: {
        kind: "person",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(166,116,186,1)",
      x: -1857.9077224731445,
      y: -635.5139503479004,
    },
    {
      type: "node",
      id: "468",
      t: "Lori Lueilwitz",
      d: {
        kind: "person",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(166,116,186,1)",
      x: -1722.832649230957,
      y: -535.9418678283691,
    },
    {
      type: "node",
      id: "469",
      t: "Damage 468",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1617.355110168457,
      y: -1022.916675567627,
    },
    {
      type: "node",
      id: "470",
      t: "Damage 586",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1537.0869216918945,
      y: -971.7761421203613,
    },
    {
      type: "node",
      id: "471",
      t: "Damage 638",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1706.604377746582,
      y: -1131.728801727295,
    },
    {
      type: "node",
      id: "472",
      t: "Damage 581",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1773.801399230957,
      y: -1051.3435401916504,
    },
    {
      type: "node",
      id: "473",
      t: "Damage 602",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1769.793586730957,
      y: -1156.489854812622,
    },
    {
      type: "node",
      id: "474",
      t: "Damage 771",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1695.019905090332,
      y: -1060.3865242004395,
    },
    {
      type: "node",
      id: "475",
      t: "Damage 927",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1814.770149230957,
      y: -1105.2457389831543,
    },
    {
      type: "node",
      id: "476",
      t: "Claim 503",
      d: {
        kind: "claim",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,47,63,1)",
      x: -1709.5419998168945,
      y: -877.073184967041,
    },
    {
      type: "node",
      id: "654",
      t: "Marietta Turner Jr.",
      d: {
        kind: "person",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(166,116,186,1)",
      x: -1324.6194534301758,
      y: -770.9541358947754,
    },
    {
      type: "node",
      id: "655",
      t: "Damage 712",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1364.6671829223633,
      y: -1311.3736000061035,
    },
    {
      type: "node",
      id: "656",
      t: "Damage 708",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1211.3150100708008,
      y: -1058.8240699768066,
    },
    {
      type: "node",
      id: "657",
      t: "Damage 233",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1188.2261428833008,
      y: -1203.4983215332031,
    },
    {
      type: "node",
      id: "658",
      t: "Damage 912",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1270.8091506958008,
      y: -1193.0131721496582,
    },
    {
      type: "node",
      id: "659",
      t: "Damage 500",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1183.8325271606445,
      y: -1130.365379333496,
    },
    {
      type: "node",
      id: "660",
      t: "Damage 120",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1288.408576965332,
      y: -1298.7427940368652,
    },
    {
      type: "node",
      id: "661",
      t: "Damage 855",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1262.979133605957,
      y: -972.7531471252441,
    },
    {
      type: "node",
      id: "662",
      t: "Damage 280",
      d: {
        kind: "damage",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,134,21,1)",
      x: -1227.943977355957,
      y: -1265.8992881774902,
    },
    {
      type: "node",
      id: "663",
      t: "Claim 411",
      d: {
        kind: "claim",
      },
      c: "rgba(255,255,255,1)",
      b: "rgba(255,47,63,1)",
      x: -1336.150276184082,
      y: -1056.543529510498,
    },
  ],
};

export const defaultStyle = {
  nodeColours: {
    telephone: "#A42768",
    address: "#A42768",
    person: "#A674BA",
    policy: "#1F78B4",
    claim: "#FF2F3F",
    vehicle: "#7FCB68",
    garage: "#006B5F",
    damage: "#FF8615",
  },
  kindIcons: {
    person: "person",
    telephone: "phone",
    address: "home",
    policy: "folder",
    claim: "receipt",
    vehicle: "directions_car",
    garage: "build",
    damage: "taxi_alert",
  },
  imageAlignment: {
    directions_car: { dy: -5 },
    receipt: { e: 0.9 },
    taxi_alert: { dy: -5, dx: 5, e: 0.8 },
    phone: { e: 0.8 },
    build: { e: 0.8 },
  },
};

export const initZoomIds = [
  "662",
  "656",
  "661",
  "660",
  "659",
  "663",
  "655",
  "658",
  "657",
  "3",
  "475",
  "474",
  "473",
  "471",
  "469",
  "470",
  "476",
  "472",
  "251",
  "253",
  "654",
  "252",
  "464",
  "460",
  "468",
];

export const lorem = [];
lorem[0] =
  "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
lorem[1] = `${lorem[0]}\n\n${lorem[0]}`;
lorem[2] = `${lorem[0]}\n\n${lorem[0]}\n\n${lorem[0]}`;
lorem[3] = `${lorem[0]}\n\n${lorem[0]}\n\n${lorem[0]}\n\n${lorem[0]}`;
lorem[4] = lorem[0].slice(0, 100);
<!doctype html>
<html lang="en" style="background-color: #2d383f">
  <head>
    <meta charset="utf-8" />
    <title>PDF Reports</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" href="./style.css" />
  </head>
  <body>
    <div id="klchart" class="klchart"></div>
    <script src="pdfkit/js/pdfkit.standalone.js" defer type="text/javascript"></script>
    <script src="svg-to-pdfkit" defer type="text/javascript"></script>
    <script type="module" src="./code.js"></script>
  </body>
</html>
#caption {
  border: 1px solid rgba(179, 179, 179, 0.705);
  padding-left: 7px;
  padding-top: 8px;
  resize: none;
}

[type="radio"] + svg {
  background-color: rgba(179, 179, 179, 0.521);
  border: 1px solid rgba(179, 179, 179, 0.705);
  margin-left: -18px;
  margin-right: 10px;
}

[type="text"] {
  font-family: muli;
  width: calc(100% - 6px);
  margin: 8px 3px 10px 3px;
}

[type="radio"]:checked + svg {
  border: 2px solid #009968;
}

[type="radio"]:hover + svg {
  border: 2px solid #00c980;
}

Terms of use

These terms do not alter or supersede any existing agreements between you (or your employer) and us.

By accessing or using any Content you agree to be bound by these Terms of Use. Please review these terms carefully before using the website.

The contents of this website, including but not limited to any text, code samples, API references, schemas, interactive tools, and other materials (collectively, the 'Content'), are made available for informational and internal evaluation purposes only. All intellectual property rights in the Content are reserved. No licence is granted to use the Content for any commercial purpose, or to copy, distribute, modify, reverse-engineer, or incorporate any part of the Content into any product or service, without our prior written consent.

This Content is provided “as is” and “as available,” without any representations, warranties, or guarantees of any kind, whether express or implied, including but not limited to implied warranties of merchantability, fitness for a particular purpose, non-infringement, or accuracy. To the fullest extent permitted by applicable law, we expressly exclude and disclaim all implied warranties, conditions, and other terms that might otherwise be implied.

We disclaim all liability for any loss or damage, whether direct, indirect, incidental, consequential, or otherwise, arising from any reliance placed on the Content or from your use of it, to the fullest extent permitted by applicable law. By continuing to access or use the Content, you acknowledge and agree to these terms.