Making sense of densely-connected data can be challenging and time-consuming. This demo shows how combo features can reveal hidden patterns quickly and easily, even in complex networks.
The dataset contains details of suspected terrorists and their country and region of origin. The initial chart presents a tangled web of connections, and it’s difficult to spot key relationships.
Combos reduce clutter
When you combine nodes by region and country, it creates a high-level view and the visualisation starts to make sense:
- Double-click a region to drill down further. The nested combos represent suspects from each country.
- Double-click a country to see its network of individual suspects.
- Links can be weighted to visually represent the number of connections between combos.
Combos let you move around the chart more easily without losing any of the detail you'd get from an uncombined view.
Revealing suspects
A chart of combos isn’t just for high-level analysis.
Double-click a country and click on a suspect to reveal their primary connections across the chart. Nodes linked to them are brought to the foreground, so it’s easier to analyse key suspects without distraction. You can select multiple suspects to foreground a subset of interesting data.
See also
Documentation: Combos and Combos Concepts
Demos: Combo Options and Combo Dragging
Key functions used:
import KeyLines from "keylines";
import {
data,
getRegion,
theme,
comboArrangement,
countryAliases,
regionMapping,
getIcon,
getComboSelectionStyling,
getNonComboSelectionStyling,
getCombineNodeStyle,
getGlyphStyling,
getNodeStyle,
getComboStyle,
} from "./data.js";
let chart;
let graphEngine;
// State variables
let combinedByCountry = false;
let combinedByRegion = false;
// set of comboids which are currently being opened/closed
const comboAnimations = {};
// Common helper Functions //
function isCombo(ids, type = "node") {
return chart.combo().isCombo(ids, { type });
}
function layout(mode) {
return chart.layout("organic", { mode });
}
function getNodeLinks(nodeId) {
return chart.graph().neighbours(nodeId).links;
}
function getAllComboIds() {
const comboIds = [];
chart.each({ type: "node", items: "all" }, ({ id }) => {
if (isCombo(id, "all")) {
comboIds.push(id);
}
});
return comboIds;
}
function formatCountry(country) {
let countryFormatted = country.toLowerCase().replace(/ /g, "-");
if (countryFormatted in countryAliases) {
countryFormatted = countryAliases[countryFormatted];
}
return countryFormatted;
}
function getCountryGlyph(item) {
if (!item.d.country || item.d.country === "Unknown") {
return null;
}
const countryFormatted = formatCountry(item.d.country);
const glyph = getGlyphStyling(countryFormatted);
return glyph;
}
function getAllNeighbours(ids) {
const nodeNeighbours = graphEngine.neighbours(
ids.filter((id) => !isCombo(id, "all")),
);
const comboNeighbours = chart
.graph()
.neighbours(ids.filter((id) => isCombo(id, "all")));
const allLinks = nodeNeighbours.links.concat(comboNeighbours.links);
const allNodes = nodeNeighbours.nodes.concat(comboNeighbours.nodes);
return {
links: Array.from(new Set(allLinks).values()),
nodes: Array.from(new Set(allNodes).values()),
};
}
function collectRootCombos(neighbours, selectedIds) {
const roots = new Set();
neighbours.links.forEach((linkId) => {
const link = chart.getItem(linkId);
let root1 = chart.combo().find(link.id1);
let root2 = chart.combo().find(link.id2);
if (root1 != null) roots.add(root1);
if (root2 != null) roots.add(root2);
});
neighbours.nodes.forEach((nodeId) => {
let rootId = chart.combo().find(nodeId);
if (rootId != null) roots.add(rootId);
});
selectedIds.forEach((nodeId) => {
let rootId = chart.combo().find(nodeId);
if (rootId != null) roots.add(rootId);
});
return roots;
}
/* END of helper functions */
/* This code controls the COMBINE action */
function enableInput(ids, enabled) {
ids.forEach((id) => {
const button = document.getElementById(id);
if (enabled) {
button.classList.remove("disabled");
button.removeAttribute("disabled");
} else {
button.classList.add("disabled");
button.setAttribute("disabled", "");
}
});
}
function afterCombine() {
layout("adaptive").then(() => {
enableInput(["openall", "combineRegion", "uncombine", "layout"], true);
enableInput(["combineRegion"], !combinedByRegion);
});
// reset foregrounded items when nodes are combined
updateSelection([]);
applyStyling(new Set());
}
// Helper functions for COMBINE action //
function getNodeSize(ids) {
let size = 0;
for (let i = 0; i < ids.length; i++) {
if (isCombo(ids[i])) {
size += chart.combo().info(ids[i]).nodes.length;
} else {
// regular node
size += 1;
}
}
return size;
}
function getLinkSize(id) {
if (isCombo(id, "link")) {
// set the link thickness
return 2 * Math.sqrt(chart.combo().info(id).links.length);
}
return 2;
}
function groupNodesBy(criteria) {
const groups = {};
chart.each({ type: "node", items: "toplevel" }, (item) => {
const group = criteria(item);
if (group) {
if (!groups[group]) {
groups[group] = [];
}
groups[group].push(item.id);
}
});
return groups;
}
function applyLinkTheme(comboIds) {
const props = getNodeLinks(comboIds).map((id) => ({
id,
w: getLinkSize(id),
}));
return chart.setProperties(props, false);
}
// Allows links to a closed combo from an open combo
function isLinkedToOpenCombo(linkId) {
const link = chart.getItem(linkId);
const [node1, node2] = chart.combo().find([link.id1, link.id2]);
// Filter out non-combo ends of the link
const linkEndCombos = [node1, node2].filter((id) => isCombo(id, "all"));
// Return true only if one of the links is an open combo
return linkEndCombos.some((comboId) => chart.combo().isOpen(comboId));
}
// END of combine helper functions //
function combineNodes(criteria) {
chart.zoom("fit", { animate: true, time: 500 });
const options = {
arrange: { name: comboArrangement, tightness: 6.2 },
animate: true,
time: 1500,
select: false,
};
const groups = groupNodesBy(criteria);
const toClose = [];
const combineArray = [];
Object.keys(groups).forEach((group) => {
if (group === "null") {
return;
}
toClose.push(...groups[group]);
const firstItem = chart.getItem(groups[group][0]);
const isRegion = firstItem.d.region !== undefined;
const region = isRegion
? firstItem.d.region
: getRegion(firstItem.d.country);
const nodeSize = Math.sqrt(getNodeSize(groups[group]));
const icon = getIcon(isRegion ? region : "default");
const countryGlyph = !isRegion ? getCountryGlyph(firstItem) : null;
const g = countryGlyph !== null ? [countryGlyph] : null;
const combineIds = {
ids: groups[group],
d: { region, isRegion },
glyph: null,
style: getCombineNodeStyle(firstItem, isRegion, region, icon, nodeSize, g)
.closedStyle,
openStyle: getCombineNodeStyle(firstItem, isRegion, region).openStyle,
};
combineArray.push(combineIds);
});
// close all groups before we combine
chart.combo().close(toClose, { animate: false });
return chart.combo().combine(combineArray, options).then(applyLinkTheme);
}
/* END of combine action controls */
function byCountry(item) {
return item.d.country || null;
}
function byRegion(item) {
return item.d.region || null;
}
function combineCountries() {
enableInput(
["combineCountry", "combineRegion", "uncombine", "openall", "layout"],
false,
);
combinedByCountry = true;
combineNodes(byCountry).then(afterCombine);
}
function combineRegions() {
enableInput(
["combineCountry", "combineRegion", "uncombine", "openall", "layout"],
false,
);
combinedByRegion = true;
if (combinedByCountry) {
combineNodes(byRegion).then(afterCombine);
} else {
combineNodes(byCountry).then(() => {
combineNodes(byRegion).then(afterCombine);
});
}
}
/* END of combine action */
function openOrCloseCombo(ids, open, cb) {
if (Object.keys(comboAnimations).length > 0) {
return false;
}
const action = open ? chart.combo().open : chart.combo().close;
let targets = Array.isArray(ids) ? ids : [ids];
targets = targets.filter((id) => {
if (!isCombo(id) || chart.combo().isOpen(id) === open) {
return false;
}
comboAnimations[id] = true;
return true;
});
action(targets, { adapt: "inCombo", time: 300 })
.then(() => (targets.length > 0 ? layout("adaptive") : null))
.then(() => {
targets.forEach((id) => {
delete comboAnimations[id];
});
if (cb) {
cb();
}
});
return targets.length > 0;
}
function uncombineAll() {
const combos = [];
chart.each({ type: "node", items: "toplevel" }, (node) => {
if (isCombo(node.id, "all")) {
combos.push(node.id);
}
});
if (combos.length) {
enableInput(["uncombine", "openall", "layout"], false);
chart
.combo()
.uncombine(combos, { full: true, select: false })
.then(() => {
layout("adaptive").then(() => {
combinedByCountry = false;
combinedByRegion = false;
enableInput(["combineCountry", "combineRegion", "layout"], true);
applyStyling(new Set());
});
});
}
}
function onSelection() {
// Get selection state for links and nodes
const selectedNodeIds = chart.selection();
const highlightedItemIds = updateSelection(selectedNodeIds);
applyStyling(highlightedItemIds);
}
function updateSelection(selectedIds) {
let highlightedItemIds;
// clear revealed items
chart.combo().reveal([]);
if (selectedIds.length > 0) {
highlightedItemIds = foregroundSelected(selectedIds);
} else {
// Nothing is selected, reset foregrounding so we see everything
chart.foreground(() => true, { type: "all" });
chart.combo().reveal([]);
highlightedItemIds = new Set();
}
return highlightedItemIds;
}
function foregroundSelected(selectedNodeIds) {
const highlightedItemIds = new Set();
const itemStyleUpdates = [];
const neighbours = getAllNeighbours(selectedNodeIds);
// Disaggregate links into combos
chart.combo().reveal(neighbours.links.filter(isLinkedToOpenCombo));
// Update links styles
neighbours.links.forEach((linkId) => {
highlightedItemIds.add(linkId);
itemStyleUpdates.push({ id: linkId, c: theme.selectedLinkColour });
});
// Update node styles
selectedNodeIds.forEach((id) => {
highlightedItemIds.add(id);
const item = chart.getItem(id);
const updatedStyle = isCombo(id, "all")
? getComboSelectionStyling(item)
: getNonComboSelectionStyling(item);
itemStyleUpdates.push(updatedStyle);
});
// Collect all the things that should be in the foreground
const itemsToForeground = new Set(
selectedNodeIds.concat(neighbours.links).concat(neighbours.nodes),
).union(collectRootCombos(neighbours, selectedNodeIds));
if (selectedNodeIds.every((id) => !isCombo(id, "node"))) {
// Where only nodes are selected, use the underlying items
chart.foreground((item) => itemsToForeground.has(item.id), {
type: "all",
items: "underlying",
});
} else {
// Either a mixture of combos and nodes or only combos selected, use the top level instead
chart.foreground((item) => itemsToForeground.has(item.id), {
type: "all",
items: "toplevel",
});
}
chart.setProperties(itemStyleUpdates);
return highlightedItemIds;
}
function setUpEventHandlers() {
chart.on("selection-change", onSelection);
chart.on("drag-start", ({ type, id, setDragOptions }) => {
if (
type === "node" &&
chart.combo().isOpen(id) &&
!chart.options().handMode
) {
setDragOptions({ type: "marquee" });
}
});
chart.on("click", ({ id, preventDefault }) => {
const item = chart.getItem(id);
if (item != null && item.type === "link") {
preventDefault();
}
});
chart.on("double-click", ({ id, preventDefault, button }) => {
if (id && button === 0) {
if (isCombo(id)) {
openOrCloseCombo(id, !chart.combo().isOpen(id));
}
preventDefault();
}
});
// buttons
document
.getElementById("combineCountry")
.addEventListener("click", combineCountries);
document
.getElementById("combineRegion")
.addEventListener("click", combineRegions);
document.getElementById("uncombine").addEventListener("click", uncombineAll);
document.getElementById("layout").addEventListener("click", () => layout());
document.getElementById("openall").addEventListener("click", () => {
openOrCloseCombo(getAllComboIds(), true);
});
}
function applyStyling(highlightedItemIds) {
const props = [];
chart.each({ items: "all" }, (item) => {
if (highlightedItemIds.has(item.id)) {
// An update is not required
return;
}
if (item.type === "node") {
if (!isCombo(item.id)) {
const countryGlyph = getCountryGlyph(item);
props.push(getNodeStyle(item, countryGlyph));
} else if (isCombo(item.id)) {
props.push(getComboStyle(item));
}
} else if (isCombo(item.id, "link")) {
props.push({ id: item.id, c: null });
} else if (item.type === "link") {
// non-combo link styles
props.push({ id: item.id, c: theme.linkColour, w: 3 });
}
});
chart.setProperties(props);
}
async function startKeyLines() {
function getImageAlignments() {
const imageAlignments = {
"fas fa-user": { dy: -2, e: 0.7 },
"fas fa-users": { dy: 0, e: 0.6 },
"fas fa-globe": { dy: 3, e: 1.5 },
"fas fa-earth-americas": { dy: 3, e: 1.5 },
"fas fa-earth-asia": { dy: 3, e: 1.5 },
"fas fa-earth-africa": { dy: 3, e: 1.5 },
"fas fa-earth-europe": { dy: 3, e: 1.5 },
};
const countries = Object.keys(regionMapping);
const countriesFormatted = countries.map((country) =>
formatCountry(country),
);
// Set image alignment for country glyphs
countriesFormatted.forEach(
(countryFormatted) =>
(imageAlignments[`/public/im/flag-icons/${countryFormatted}.svg`] = {
e: 1.3,
}),
);
return imageAlignments;
}
graphEngine = KeyLines.getGraphEngine();
chart = await KeyLines.create({
container: "klchart",
options: {
drag: {
links: false,
},
marqueeLinkSelection: "off",
truncateLabels: { maxLength: 15 },
imageAlignment: getImageAlignments(),
selectedNode: theme.selectedNode,
selectedLink: theme.selectedLink,
logo: { u: "/public/images/Logo.png" },
fontFamily: "Inter",
iconFontFamily: "Font Awesome 6 Free",
linkEnds: { avoidLabels: false },
minZoom: 0.02,
handMode: true,
},
});
window.chart = chart;
await chart.load(data);
graphEngine.load(chart.serialize());
layout();
setUpEventHandlers();
// set up the initial look
onSelection();
}
function loadKeyLines() {
// load FontAwesome for the node icons
document.fonts.load('900 24px "Font Awesome 6 Free"').then(startKeyLines);
}
window.addEventListener("DOMContentLoaded", loadKeyLines); export const theme = {
selectedNode: {},
selectedLink: {},
linkColour: "rgba(150,150,150,0.8)",
selectedLinkColour: "#222",
countryFontSize: 26,
regionFontSize: 20,
};
/* styling chart items */
export function getNodeStyle(item, glyph) {
const g = glyph !== null ? [glyph] : [];
const rTheme =
item.d.region !== undefined
? getRegionTheme(item.d.region)
: getRegionTheme(getRegion(item.d.country));
return {
id: item.id,
u: null,
g,
b: undefined,
c: rTheme.iconColour,
t: [{ ...item.t[0], fbc: undefined, fc: "#222" }],
fi: { t: "fas fa-user", c: "white" },
bw: 3,
};
}
export function getComboStyle(item) {
const rTheme =
item.d.region !== undefined
? getRegionTheme(item.d.region)
: getRegionTheme(getRegion(item.d.country));
return {
id: item.id,
b: undefined,
t: [{ ...item.t[0], fbc: undefined, fc: "#222" }],
g: [{ ...item.g[0], c: "white", border: { colour: "white", width: 1 } }],
ha0: { ...item.ha0, c: rTheme.iconColour },
ha1: {
...item.ha1,
c: item.d.region !== undefined ? rTheme.iconColour : null,
},
oc: {
...item.oc,
t: [{ ...item.oc.t[0], fbc: undefined, fc: "#222" }],
b: rTheme.iconColour,
},
};
}
export function getGlyphStyling(country) {
return {
p: "ne",
u: `/public/im/flag-icons/${country}.svg`,
c: "rgb(255, 255, 255)",
border: { colour: "white", width: 1 },
e: 1,
};
}
export function getComboSelectionStyling(combo) {
return {
id: combo.id,
t: [{ ...combo.t[0], fbc: "#222", fc: "white" }],
g: [{ ...combo.g[0], c: "#222", border: { colour: "#222", width: 4 } }],
ha0: { ...combo.ha0, c: "#222" },
ha1: { ...combo.ha1, c: combo.d.region !== undefined ? "#222" : null },
oc: {
...combo.oc,
t: [{ ...combo.oc.t[0], fbc: "#222", fc: "white" }],
b: "#222",
},
};
}
export function getNonComboSelectionStyling(node) {
return {
id: node.id,
b: "#222",
bw: 3,
t: [{ ...node.t[0], fbc: "#222", fc: "white" }],
g: [{ ...node.g[0], c: "#222", border: { colour: "#222", width: 4 } }],
};
}
export function getCombineNodeStyle(
firstItem,
isRegion,
region,
icon,
nodeSize,
g,
) {
const rTheme = getRegionTheme(region);
const closedStyle = {
e: nodeSize,
c: isRegion ? "white" : rTheme.iconColour,
sh: "circle",
fi: {
t: icon,
c: isRegion ? rTheme.iconColour : "white",
},
g,
b: undefined,
t: [
{
t: `${isRegion ? firstItem.d.region : firstItem.d.country}`,
borderRadius: 20,
padding: [4, 8, 0, 8],
margin: isRegion ? [14, 0, 0, 0] : [5, 0, 0, 0],
fbc: "white with alpha",
fc: "#222",
fs: isRegion ? theme.regionFontSize : theme.countryFontSize,
},
],
ha0: {
c: rTheme.iconColour,
r: 30.5,
w: 3,
},
ha1: {
c: isRegion ? rTheme.iconColour : null,
r: isRegion ? 35 : null,
w: isRegion ? 3 : null,
},
};
const openStyle = {
c: isRegion ? rTheme.regionOCColour : rTheme.countryBgColour,
b: rTheme.iconColour,
bw: 3,
t: [
{
t: `${isRegion ? firstItem.d.region : firstItem.d.country}`,
borderRadius: 20,
padding: [4, 8, 0, 8],
margin: [0, 0, 0, 0],
fbc: undefined,
fc: "#222",
fs: isRegion ? theme.regionFontSize : theme.countryFontSize,
},
],
};
return { closedStyle, openStyle };
}
/* end of styling */
// aliases for country names to find images
export const countryAliases = {
usa: "united-states-of-america",
britain: "united-kingdom",
bosnia: "bosnia-and-herzegovina",
};
// arrangement used for country combos
export const comboArrangement = "lens";
// regional theme settings
const regionThemes = {
"Middle East": {
iconColour: "rgba(232, 131, 0, 1)",
countryBgColour: "rgba(232, 131, 0, 0.3)",
regionOCColour: "rgba(232, 131, 0, 0.2)",
},
Europe: {
iconColour: "rgba(13, 172, 71, 1)",
countryBgColour: "rgba(13, 172, 71, 0.3)",
regionOCColour: "rgba(13, 172, 71, 0.2)",
},
"North America": {
iconColour: "rgba(226, 62, 133, 1)",
countryBgColour: "rgba(226, 62, 133, 0.3)",
regionOCColour: "rgba(226, 62, 133, 0.2)",
},
"South East": {
iconColour: "rgba(146, 58, 205, 1)",
countryBgColour: "rgba(146, 58, 205, 0.3)",
regionOCColour: "rgba(146, 58, 205, 0.2)",
},
Africa: {
iconColour: "rgba(87, 157, 255, 1)",
countryBgColour: "rgba(87, 157, 255, 0.3)",
regionOCColour: "rgba(87, 157, 255, 0.2)",
},
default: {
iconColour: "rgba(101, 99, 144, 1)",
countryBgColour: "rgba(101, 99, 144, 0.3)",
regionOCColour: "rgba(101, 99, 144, 0.2)",
},
};
export function getRegionTheme(region) {
if (region === undefined || regionThemes[region] === undefined) {
return regionThemes.default;
}
return regionThemes[region];
}
const MIDDLE_EAST = "Middle East";
const EUROPE = "Europe";
const NORTHAMERICA = "North America";
const SOUTHEAST = "South East";
const AFRICA = "Africa";
export const regionMapping = {
Afghanistan: MIDDLE_EAST,
France: EUROPE,
"Saudi Arabia": MIDDLE_EAST,
Malaysia: SOUTHEAST,
Germany: EUROPE,
Morocco: AFRICA,
Britain: EUROPE,
Canada: NORTHAMERICA,
Tanzania: AFRICA,
Jordan: MIDDLE_EAST,
Sudan: AFRICA,
Australia: SOUTHEAST,
Belgium: EUROPE,
Italy: EUROPE,
Pakistan: MIDDLE_EAST,
Spain: EUROPE,
Bosnia: EUROPE,
Turkey: MIDDLE_EAST,
USA: NORTHAMERICA,
Kuwait: MIDDLE_EAST,
Algeria: AFRICA,
Indonesia: SOUTHEAST,
Singapore: SOUTHEAST,
Yemen: MIDDLE_EAST,
};
const iconMapping = {
"North America": "fas fa-earth-americas",
"Middle East": "fas fa-earth-asia",
Africa: "fas fa-earth-africa",
Europe: "fas fa-earth-europe",
"South East": "fas fa-earth-asia",
};
export function getRegion(country) {
if (!country) {
return null;
}
return regionMapping[country];
}
export function getIcon(place) {
if (place in iconMapping) {
return iconMapping[place];
}
return "fas fa-users";
}
export const data = {
type: "LinkChart",
items: [
{
id: "N1",
t: [{ t: "Hassan al Fadli", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N2",
t: [{ t: "Nabil ibn al-Salim", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N3",
t: [{ t: "Mustafa al-Bakr", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N4",
t: [{ t: "Ibrahim Abd Bakr", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N5",
t: [{ t: "Amir Abdal Faruq", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Pakistan.png",
d: { country: "Pakistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N6",
t: [{ t: "Omar Abu Omari", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Pakistan.png",
d: { country: "Pakistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N7",
t: [{ t: "Sami al Omari", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N8",
t: [{ t: "Yusuf al-Fadli", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N9",
t: [{ t: "Tariq ibn Bakr", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "",
d: { country: "Unknown" },
type: "node",
x: 0,
y: 0,
},
{
id: "N10",
t: [{ t: "Yusuf Abd Fadli", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N11",
t: [{ t: "Ahmed al Rahman", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N12",
t: [{ t: "Zaid Abdal Faruq", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N13",
t: [{ t: "Yusuf ibn Anwar", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N14",
t: [{ t: "Mustafa Abu Fadli", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Pakistan.png",
d: { country: "Pakistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N15",
t: [{ t: "Nabil Abd Najjar", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N16",
t: [
{ t: "Faisal ibn al-Qureshi", borderRadius: 20, padding: [4, 8, 0, 8] },
],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N17",
t: [{ t: "Faisal al Salim", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N18",
t: [{ t: "Tariq Abdal Zaman", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N19",
t: [{ t: "Karim al Najjar", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Pakistan.png",
d: { country: "Pakistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N20",
t: [{ t: "Zaid ibn Faruq", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N21",
t: [{ t: "Ahmed ibn al-Faruq", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Pakistan.png",
d: { country: "Pakistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N22",
t: [{ t: "Saad al-Omari", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N23",
t: [{ t: "Sami ibn Shafi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Jordan.png",
d: { country: "Jordan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N24",
t: [{ t: "Zaid al Hassan", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N25",
t: [{ t: "Ali al-Zaman", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N26",
t: [
{ t: "Faisal Abdal Ghamdi", borderRadius: 20, padding: [4, 8, 0, 8] },
],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N27",
t: [{ t: "Tariq al Bakr", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Sudan.png",
d: { country: "Sudan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N28",
t: [{ t: "Saad ibn Omari", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Kuwait.png",
d: { country: "Kuwait" },
type: "node",
x: 0,
y: 0,
},
{
id: "N29",
t: [{ t: "Bilal al Najjar", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N30",
t: [{ t: "Omar al-Omari", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N31",
t: [{ t: "Faisal ibn Tahir", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Britain.png",
d: { country: "Britain" },
type: "node",
x: 0,
y: 0,
},
{
id: "N32",
t: [{ t: "Ali ibn Harbi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N33",
t: [{ t: "Yusuf Abdal Mahdi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N34",
t: [{ t: "Mustafa Abu Najjar", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N35",
t: [{ t: "Ibrahim al-Rahman", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Yemen.png",
d: { country: "Yemen" },
type: "node",
x: 0,
y: 0,
},
{
id: "N36",
t: [{ t: "Yusuf Abd Hassan", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N37",
t: [{ t: "Hassan Abd Shehri", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N38",
t: [{ t: "Bilal Abd Salim", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/USA.png",
d: { country: "USA" },
type: "node",
x: 0,
y: 0,
},
{
id: "N39",
t: [{ t: "Omar ibn al-Salim", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N40",
t: [
{ t: "Faisal Abdal Qureshi", borderRadius: 20, padding: [4, 8, 0, 8] },
],
u: "/public/images/flags/USA.png",
d: { country: "USA" },
type: "node",
x: 0,
y: 0,
},
{
id: "N41",
t: [{ t: "Amir al Tahir", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N42",
t: [
{ t: "Mohammed al Qureshi", borderRadius: 20, padding: [4, 8, 0, 8] },
],
u: "/public/images/flags/Pakistan.png",
d: { country: "Pakistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N43",
t: [{ t: "Abdul Abdal Mahdi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N44",
t: [
{ t: "Zaid ibn al-Qureshi", borderRadius: 20, padding: [4, 8, 0, 8] },
],
u: "/public/images/flags/Pakistan.png",
d: { country: "Pakistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N45",
t: [{ t: "Abdul Abdal Harbi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N46",
t: [{ t: "Nabil Abdal Tahir", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N47",
t: [
{ t: "Mustafa ibn al-Karim", borderRadius: 20, padding: [4, 8, 0, 8] },
],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N48",
t: [{ t: "Zaid Abd Hussein", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N49",
t: [
{ t: "L'Houssaine Kherchtou", borderRadius: 20, padding: [4, 8, 0, 8] },
],
u: "/public/images/flags/Italy.png",
d: { country: "Italy" },
type: "node",
x: 0,
y: 0,
},
{
id: "N50",
t: [{ t: "Abdul Abd Fawaz", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N51",
t: [{ t: "Omar al Faruq", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Pakistan.png",
d: { country: "Pakistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N52",
t: [{ t: "Abdul Abd Fadli", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Tanzania.png",
d: { country: "Tanzania" },
type: "node",
x: 0,
y: 0,
},
{
id: "N53",
t: [{ t: "Saad Abd Shafi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Britain.png",
d: { country: "Britain" },
type: "node",
x: 0,
y: 0,
},
{
id: "N54",
t: [{ t: "Faisal bin Faruq", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N55",
t: [{ t: "Khalid ibn Hassan", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Afghanistan.png",
d: { country: "Afghanistan" },
type: "node",
x: 0,
y: 0,
},
{
id: "N56",
t: [{ t: "Amir ibn Mahdi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N57",
t: [{ t: "Amir ibn al-Harbi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N58",
t: [{ t: "Yusuf Abdal Rahman", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N59",
t: [{ t: "Zaid Abd Bakr", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N60",
t: [{ t: "Ali al Karim", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N61",
t: [{ t: "Nabil bin Mahdi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N62",
t: [{ t: "Amir Abdal Bakr", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N63",
t: [{ t: "Zaid al Harbi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N64",
t: [{ t: "Zaid ibn Hassan", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N65",
t: [{ t: "Amir al-Hassan", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N66",
t: [{ t: "Khalid al Salim", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N67",
t: [
{ t: "Hassan Abdal Hassan", borderRadius: 20, padding: [4, 8, 0, 8] },
],
u: "/public/images/flags/Germany.png",
d: { country: "Germany" },
type: "node",
x: 0,
y: 0,
},
{
id: "N68",
t: [{ t: "Jamal Abu Shehri", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/USA.png",
d: { country: "USA" },
type: "node",
x: 0,
y: 0,
},
{
id: "N69",
t: [{ t: "Nabil ibn al-Fadli", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N70",
t: [
{ t: "Karim ibn al-Shehri", borderRadius: 20, padding: [4, 8, 0, 8] },
],
d: { country: "Unknown" },
type: "node",
x: 0,
y: 0,
},
{
id: "N71",
t: [{ t: "Yusuf al-Karim", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N72",
t: [{ t: "Mustafa bin Faruq", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N73",
t: [{ t: "Ali Abd Shafi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N74",
t: [{ t: "Mohammed bin Anwar", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N75",
t: [{ t: "Khalid al-Hussein", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N76",
t: [{ t: "Amir Abd Omari", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N77",
t: [{ t: "Amir Abu Ghamdi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N78",
t: [{ t: "Yusuf al Shafi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N79",
t: [{ t: "Abdul Abdal Shehri", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N80",
t: [{ t: "Yusuf Abd Qureshi", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
x: 0,
y: 0,
},
{
id: "N81",
t: [{ t: "Mohammed Abu Fadli", borderRadius: 20, padding: [4, 8, 0, 8] }],
u: "/public/images/flags/Saudi Arabia.png",
d: { country: "Saudi Arabia" },
type: "node",
// ...truncated 3898 lines <!doctype html>
<html lang="en" style="background-color: #2d383f">
<head>
<meta charset="utf-8" />
<title>Combining Nodes</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>