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>