Search

Node Styles

Styling

Explore ways to style nodes and make them stand out.

Node Styles
View live example →

Styled nodes make data visualisations clearer and more meaningful. This demo shows some of the most popular options.

For even more examples of advanced node styling, see also our Advanced Node Styles demo.

Style guidelines

Below are some general styling tips to help users understand the data quickly and effectively:

  • Use intuitive styles that are instantly recognisable.
  • Avoid cluttering charts with too much styling.
  • Don’t overload labels with too much information.
  • Use colours in moderation.
  • Use emphasis and animation in moderation.

When creating your data model, consider the best ways to represent different values. For example, you could use glyphs for properties, halos for events, and donuts for relative data.

Object types used:

Key functions used:

import KeyLines from "keylines";
import { data } from "./data.js";

let chart;

// Puts the selected item and its group into foreground, and everything else into the background
function highlightSection(id) {
  // if the user hasn't clicked an element resize back to fit
  if (!id) {
    chart.foreground(() => true);
    return;
  }
  // if there is a clicked item set it to be foreground
  const clickedItem = chart.getItem(id);
  if (clickedItem) {
    chart.foreground((item) => clickedItem.id === item.id);
  }
}

function formatPrintString(key, value) {
  if (key === "x" || key === "y" || key === "bg") {
    return undefined;
  }
  // prints the borderRadius array as a single value if all directions are set to the same value
  if (key === "borderRadius") {
    const set = new Set(value);
    if (set.size === 1) {
      return value[0];
    }
  }
  return value;
}

// Formats the code snippets neatly in the display box on the right-hand side
function prettyPrint(id) {
  const item = chart.getItem(id);
  if (item) {
    const json = JSON.stringify(item, formatPrintString, 1);
    document.getElementById("display").innerHTML = json.replace(
      /"(t)":\s?"(KeyLines\.getFontIcon[^"]+)"/g,
      '"$1": $2'
    );
  } else {
    document.getElementById("display").innerHTML = "No node selected";
  }
}

// Starts and stops the glyph animation
let animated = false;
function startStopAnimation() {
  animated = !animated;
  // get the node to be animated
  const animatedNode = chart.getItem("g3");
  // update the first glyph of the selected item by toggling the animation option on/off
  animatedNode.g[0].a = !animatedNode.g[0].a;

  chart.setProperties(animatedNode);

  // Ensure that the displayed source code reflects the current state of the animated element.
  if (chart.selection()[0] === "g3") {
    prettyPrint("g3");
  }
}

// Called when KeyLines is created
function initializeInteractions() {
  // Calls highlighting and pretty printing when a selection is made
  chart.on("selection-change", () => {
    const id = chart.selection()[0];
    highlightSection(id);
    prettyPrint(id);
  });

  // Zoom the view to its original position when double-clicking the background
  chart.on("double-click", ({ id, preventDefault }) => {
    if (!chart.getItem(id)) {
      chart.zoom("fit", { animate: true, time: 500 });
    }
    // Default behaviour is to zoom in - we preventDefault to override this
    preventDefault();
  });

  // prevent nodes from being dragged
  chart.on("drag-start", ({ preventDefault, type }) => {
    if (type === "node") {
      preventDefault();
    }
  });

  // Listen to the checkbox change event so that the startStopAnimation function
  // updates the animation property of the glyph.
  document.getElementById("animateGlyph").addEventListener("change", startStopAnimation);
}

// Custom image alignments for fontawesome nodes
const imageAlignment = {
  "fas fa-user": { dy: -8 },
  "far fa-user": { dy: -8 },
  "fas fa-exclamation-triangle": { dy: -10 },
};

async function startKeyLines() {
  const options = {
    logo: { u: "/images/Logo.png" },
    handMode: true,
    selectedNode: { b: "#FF9933" },
    iconFontFamily: "Font Awesome 5 Free",
    imageAlignment,
    overview: { icon: false },
    backColour: "rgb(238,238,238)",
  };

  chart = await KeyLines.create({ container: "klchart", options });
  chart.load(data);
  chart.zoom("fit");
  initializeInteractions();
}

function loadFontsAndStart() {
  document.fonts.load('24px "Font Awesome 5 Free"').then(startKeyLines);
}

window.addEventListener("DOMContentLoaded", loadFontsAndStart);
import KeyLines from "keylines";
const primaryColour = "#7AC1E2";
const blueAccent = "rgb(45,105,160)";
const greenAccent = "rgb(87, 167, 115)";
const pinkAccent = "rgb(216, 30, 91)";
const transparent = "rgba(0,0,0,0)";

const yPosition = [0, 130, 260, 390, 520];
const imageURL = "/images/icons/pearlgirl.jpg";
const imageURL2 = "/images/starry-night.jpg";
const flagImageURL = "/images/flags/Britain.png";
const userImage = "/im/people/Person8.png";
const userIcon = "fas fa-user";

const labelAttributes = {
  type: "node",
  c: transparent,
  fs: 20,
  fb: true,
  fc: blueAccent,
  fbc: transparent,
};

export const data = {
  type: "LinkChart",
  items: [
    {
      // Increasing diameter node style
      id: "l1",
      x: -70,
      y: yPosition[0],
      t: { t: "Basic Nodes", textWrap: "normal" },
      ...labelAttributes,
    },
    {
      // Increasing diameter and altering background colour nodes
      id: "dw2",
      type: "node",
      c: primaryColour,
      x: 40,
      y: yPosition[0],
      e: 0.3,
    },
    {
      id: "dw3",
      type: "node",
      c: pinkAccent,
      x: 100,
      y: yPosition[0],
      e: 0.6,
    },
    {
      id: "dw4",
      type: "node",
      c: greenAccent,
      x: 185,
      y: yPosition[0],
      e: 1,
    },
    {
      id: "dw5",
      type: "node",
      c: blueAccent,
      x: 290,
      y: yPosition[0],
      e: 1.4,
    },
    {
      // Different border style nodes
      id: "b1",
      type: "node",
      b: blueAccent,
      x: 410,
      y: yPosition[0],
      c: primaryColour,
      bw: 5,
    },
    {
      id: "b2",
      type: "node",
      b: blueAccent,
      x: 510,
      y: yPosition[0],
      c: primaryColour,
      bw: 10,
    },
    {
      id: "b3",
      type: "node",
      b: blueAccent,
      x: 610,
      y: yPosition[0],
      c: primaryColour,
      bs: "dashed",
    },
    {
      // Font icons in nodes
      id: "f1",
      type: "node",
      fi: { t: userIcon, c: "white" },
      c: primaryColour,
      x: 710,
      y: yPosition[0],
    },
    {
      id: "f2",
      type: "node",
      fi: { t: userIcon, c: primaryColour },
      c: transparent,
      b: primaryColour,
      bw: 4,
      x: 810,
      y: yPosition[0],
    },
    {
      id: "f3",
      type: "node",
      fi: { t: userIcon, c: primaryColour },
      x: 900,
      y: yPosition[0],
    },
    {
      // image nodes
      id: "l2",
      x: 470,
      y: yPosition[1],
      t: { t: "Image Nodes", textWrap: "normal" },
      ...labelAttributes,
    },
    {
      id: "i1",
      type: "node",
      u: imageURL,
      ci: true,
      x: 550,
      y: yPosition[1],
    },
    {
      id: "i2",
      x: 640,
      y: yPosition[1],
      type: "node",
      u: imageURL,
      b: blueAccent,
      ci: true,
      c: "#7AC1E2",
      fbc: transparent,
      fs: 8,
    },
    {
      id: "i3",
      type: "node",
      w: 91,
      h: 57,
      u: imageURL2,
      x: 750,
      y: yPosition[1],
    },
    {
      id: "i4",
      x: 870,
      y: yPosition[1],
      type: "node",
      w: 91,
      h: 57,
      u: imageURL2,
      b: pinkAccent,
    },
    {
      // Glyphs
      id: "l3",
      x: -70,
      y: yPosition[1],
      t: "Glyphs",
      ...labelAttributes,
    },
    {
      id: "g1",
      type: "node",
      c: primaryColour,
      x: 50,
      y: yPosition[1],
      g: [
        { u: flagImageURL, p: "sw", e: 2 },
        {
          p: "ne",
          e: 1.5,
          c: greenAccent,
          t: "1",
          border: { colour: "rgb(238,238,238)", width: 1.5 },
        },
      ],
    },
    {
      id: "g2",
      type: "node",
      c: primaryColour,
      x: 150,
      y: yPosition[1],
      g: [
        {
          fi: { t: "fas fa-exclamation-triangle", c: "white" },
          border: { colour: pinkAccent, width: 0.5 },
          c: greenAccent,
          p: 30,
          e: 1.5,
        },
        {
          fi: { t: "fas fa-exclamation-triangle", c: "white" },
          border: { colour: blueAccent, width: 1 },
          c: pinkAccent,
          p: 150,
          e: 1.5,
        },
        {
          fi: { t: "fas fa-info", c: "white" },
          border: { colour: greenAccent, width: 1.5 },
          c: blueAccent,
          p: 270,
          e: 1.5,
        },
      ],
    },
    {
      id: "g3",
      type: "node",
      c: primaryColour,
      x: 250,
      y: yPosition[1],
      g: [
        { t: "30°", b: "rgba(0, 0, 0, 0)", c: greenAccent, p: 30, e: 1.7 },
        { t: "120°", b: "rgba(0, 0, 0, 0)", c: greenAccent, p: 120, e: 1.3 },
        { t: "240°", b: "rgba(0, 0, 0, 0)", c: greenAccent, p: 240, e: 0.9 },
      ],
    },
    {
      id: "g4",
      type: "node",
      sh: "box",
      c: primaryColour,
      x: 350,
      y: yPosition[1],
      g: [
        { p: "ne", e: 1.5, c: pinkAccent, w: true, t: "ne", b: null },
        { p: "se", e: 1.5, c: pinkAccent, w: true, t: "se", b: null },
        { p: "sw", e: 1.5, c: pinkAccent, w: true, t: "sw", b: null },
        { p: "nw", e: 1.5, c: pinkAccent, w: true, t: "nw", b: null },
      ],
    },
    {
      // Basic Labels
      id: "l4",
      x: -70,
      y: yPosition[2],
      t: { t: "Basic Labels", textWrap: "normal" },
      ...labelAttributes,
    },
    {
      id: "lb1",
      type: "node",
      c: primaryColour,
      fbc: primaryColour,
      x: 50,
      y: yPosition[2],
      t: "label",
      fs: 16,
      fc: "white",
      ff: "Serif",
    },
    {
      id: "lb3",
      type: "node",
      c: primaryColour,
      x: 160,
      y: yPosition[2],
      t: [{ t: "Sans-Serif 12pt", position: "s" }],
      ff: "Sans-Serif",
    },
    {
      id: "lb4",
      type: "node",
      c: primaryColour,
      x: 270,
      y: yPosition[2],
      fbc: transparent,
      t: [{ t: "Sans-Serif 16pt", position: "n" }],
      fs: 16,
      ff: "Sans-Serif",
      fc: pinkAccent,
    },
    {
      id: "lb5",
      type: "node",
      c: primaryColour,
      x: 380,
      y: yPosition[2],
      tc: false,
      t: "😀😂👻🔥",
      fs: 16,
      fbc: transparent,
    },
    {
      id: "lb6",
      type: "node",
      c: primaryColour,
      x: 490,
      y: yPosition[2],
      fbc: transparent,
      t: [{ t: "East Position", position: "e" }],
      fs: 16,
      ff: "Sans-Serif",
    },
    {
      // Halos
      id: "l5",
      x: 710,
      y: yPosition[2],
      t: "Halos",
      ...labelAttributes,
    },
    {
      id: "h1",
      type: "node",
      e: 0.85,
      c: primaryColour,
      x: 825,
      y: yPosition[2],
      t: "",
      fbc: transparent,
      fc: "white",
      g: [{ p: 20, r: 50, t: "1", c: pinkAccent, fc: "white", b: null, e: 0.8 }],
      ha0: { c: blueAccent, r: 30, w: 5 },
      ha1: { c: greenAccent, r: 40, w: 5 },
      ha2: { c: pinkAccent, r: 50, w: 5 },
    },
    {
      // Donuts
      id: "l6",
      x: 600,
      y: yPosition[3],
      t: "Donuts",
      ...labelAttributes,
    },
    {
      id: "d1",
      type: "node",
      c: primaryColour,
      x: 710,
      y: yPosition[3],
      t: "",
      fbc: transparent,
      fc: "white",
      donut: {
        c: [pinkAccent, blueAccent, greenAccent],
        v: [1, 3, 5],
        b: "rgb(255, 255, 255)",
        bw: 2,
        w: 10,
      },
    },
    {
      id: "d2",
      type: "node",
      x: 850,
      y: yPosition[3],
      fi: { t: "fas fa-download", c: primaryColour },
      donut: {
        w: 9,
        b: "black",
        bw: 1,
        c: [
          "RGB(255,255,255)",
          "RGB(242,249,252)",
          "RGB(230,243,250)",
          "RGB(218,236,248)",
          "RGB(206,231,245)",
          "RGB(194,226,243)",
          "RGB(182,221,241)",
          "RGB(170,215,238)",
          "RGB(158,209,235)",
          "RGB(146,205,232)",
          "RGB(134,199,229)",
          "RGB(122,193,226)",
          "RGB(45,105,160)",
          "RGB(43,102,155)",
          "RGB(41,98,150)",
          "RGB(39,94,145)",
          "RGB(37,90,140)",
          "RGB(35,86,135)",
          "RGB(33,82,130)",
          "RGB(31,78,125)",
          "RGB(30,74,120)",
          "RGB(28,70,114)",
          "RGB(26,66,108)",
          "RGB(25,62,102)",
          "RGB(23,59,96)",
          "RGB(22,55,90)",
          "RGB(20,51,84)",
          "RGB(19,47,78)",
          "RGB(18,43,72)",
          "RGB(16,39,66)",
          "RGB(14,35,60)",
          "RGB(13,31,54)",
          "RGB(11,27,48)",
          "RGB(10,24,42)",
          "RGB(8,20,36)",
          "RGB(6,16,30)",
          "RGB(5,12,24)",
          "RGB(3,8,18)",
          "RGB(2,4,12)",
          "RGB(0,0,6)",
        ],
        // Create a new array with 40 items where each item has the value 1
        v: [...Array(40)].map(() => 1),
      },
    },
    {
      // complex node labels
      id: "l7",
      x: -70,
      y: yPosition[3],
      t: { t: "Complex Labels", textWrap: "normal" },
      ...labelAttributes,
    },
    {
      id: "lc1",
      type: "node",
      c: primaryColour,
      x: 50,
      y: yPosition[3],
      fbc: transparent,
      fc: "white",
      t: [
        {
          fi: {
            t: userIcon,
          },
          fs: 24,
          position: { vertical: 5 },
        },
        { t: "multi-line text", textWrap: "normal", position: { vertical: 25 }, fs: 10 },
      ],
    },
    {
      id: "lc2",
      type: "node",
      c: primaryColour,
      x: 220,
      y: yPosition[3],
      b: "white",
      fi: { t: userIcon, c: "white" },
      fbc: transparent,
      t: [
        { t: "Big Bold Text", fb: true, position: "s" },
        { t: "small pink text", fs: 10, fc: pinkAccent },
      ],
    },
    {
      id: "lc3",
      type: "node",
      c: primaryColour,
      x: 390,
      y: yPosition[3],
      b: "white",
      fi: { t: userIcon, c: "white" },
      fbc: "white",
      t: [
        {
          t: "Rounded Labels",
          minWidth: 110,
          borderRadius: "8 8 0 0",
          padding: "8 8 0 8",
          position: "s",
        },
        {
          t: "with padding",
          minWidth: 110,
          fs: 10,
          borderRadius: "0 0 8 8",
          padding: "0 8 8 8",
        },
      ],
    },
    {
      // rectangular nodes
      id: "l8",
      x: -70,
      y: yPosition[4],
      t: { t: "Rectangular Nodes", textWrap: "normal" },
      ...labelAttributes,
    },
    {
      id: "r1",
      type: "node",
      borderRadius: 6,
      c: primaryColour,
      t: [
        {
          fbc: transparent,
          fs: 25,
          fc: "white",
          padding: 8,
          position: {
            vertical: "top",
          },
          fi: {
            t: userIcon,
          },
        },
        {
          fbc: transparent,
          fs: 10,
          fb: true,
          fc: "white",
          t: "Rectangle Node",
          padding: 8,
          position: {
            vertical: "bottom",
          },
          textWrap: "normal",
        },
      ],
      h: 60,
      w: 75,
      x: 50,
      y: 520,
    },
    {
      id: "r2",
      type: "node",
      b: blueAccent,
      bw: 2,
      c: primaryColour,
      fbc: transparent,
      fc: "white",
      t: [
        {
          fs: 24,
          padding: {
            top: 6,
            right: 0,
            bottom: 0,
            left: 8,
          },
          position: {
            vertical: "top",
            horizontal: "left",
          },
          fi: {
            t: userIcon,
          },
        },
        {
          fs: 15,
          fb: true,
          t: "Rectangle Node",
          padding: {
            top: 8,
            right: 8,
            bottom: 0,
            left: 8,
          },
          position: {
            vertical: "inherit",
          },
        },
        {
          fs: 8,
          t: "Multi-line and border",
          padding: {
            top: 4,
            right: 2,
            bottom: 0,
            left: 8,
          },
        },
        {
          fs: 8,
          t: "Left Align",
          padding: {
            top: 2,
            right: 2,
            bottom: 0,
            left: 8,
          },
        },
      ],
      x: 240,
      y: 520,
      w: "auto",
      h: "auto",
    },
    {
      id: "r3",
      type: "node",
      borderRadius: 6,
      c: primaryColour,
      fbc: transparent,
      fc: "white",
      t: [
        {
          u: userImage,
          maxHeight: 50,
          maxWidth: 50,
          position: {
            vertical: 8,
            horizontal: "left",
          },
          margin: [0, 0, 0, 10],
          b: blueAccent,
          borderRadius: 100,
        },
        {
          fs: 15,
          fb: true,
          t: "Rectangle Node",
          padding: {
            top: 12,
            right: 12,
            bottom: 0,
            left: 10,
          },
          position: {
            vertical: "top",
            horizontal: "right",
          },
        },
        {
          fs: 8,
          t: "Rounded Corners",
          padding: {
            top: 1,
            right: 12,
            bottom: 0,
            left: 0,
          },
        },
        {
          fs: 8,
          t: "Image Label",
          padding: {
            top: 1,
            right: 12,
            bottom: 0,
            left: 0,
          },
        },
        {
          fs: 8,
          t: "Right Align",
          padding: {
            top: 0,
            right: 12,
            bottom: 0,
            left: 0,
          },
        },
      ],
      x: 479,
      y: 520,
      w: "auto",
      h: "auto",
    },
    {
      id: "r4",
      type: "node",
      bg: false,
      borderRadius: { bottomRight: 16 },
      c: primaryColour,
      fbc: transparent,
      fc: "white",
      t: [
        {
          fs: 41,
          padding: {
            top: 0,
            right: 0,
            bottom: 0,
            left: 8,
          },
          position: {
            vertical: 12,
            horizontal: "left",
          },
          fi: {
            t: userIcon,
          },
        },
        {
          fs: 15,
          t: "Rectangle Node",
          padding: {
            top: 12,
            right: 12,
            bottom: 0,
            left: 10,
          },
          position: {
            vertical: "top",
            horizontal: "right",
          },
        },
        {
          fs: 8,
          t: "Single Rounded Corner",
          padding: {
            top: 1,
            right: 13,
            bottom: 0,
            left: 10,
          },
        },
        {
          fs: 8,
          t: "with Glyph",
          padding: {
            top: 0,
            right: 13,
            bottom: 0,
            left: 0,
          },
        },
      ],
      g: [
        {
          e: 1.2,
          border: {
            colour: "rgb(238,238,238)",
            width: 1,
          },
          fc: "white",
          t: "1",
          c: pinkAccent,
          p: "ne",
        },
      ],
      x: 723,
      y: 520,
      w: "auto",
      h: "auto",
    },
  ],
};
<!doctype html>
<html lang="en" style="background-color: #2d383f">
  <head>
    <meta charset="utf-8" />
    <title>Node Styles</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="@fortawesome/[email protected]/css/fontawesome.css"
    />
    <link
      rel="stylesheet"
      type="text/css"
      href="@fortawesome/[email protected]/css/solid.css"
    />
  </head>
  <body>
    <div id="klchart" class="klchart"></div>
    <script type="module" src="./code.js"></script>
  </body>
</html>

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.