This demo demonstrates how the clusters function of KeyLines can be used to find communities in a graph. By moving the slider, it is possible to change the sensitivity of clusters. Higher values create a larger number of smaller clusters, while lower values produce fewer, larger clusters. In this demo we assign each cluster a random colour.
Note also this demo uses the 'Lens' layout - see Layouts.
The data used in this demo shows companies and their relationship to other companies by ownership. The dataset authors are Kim Norlen, Gabriel Lucas, Mike Gebbie and John Chuang. EVA: Extraction, Visualization and Analysis of the Telecommunications and Media Ownership Network. Proceedings of the International Telecommunications Society's 14th Biennial Conference, Seoul Korea, August 2002.
Key functions used:
import KeyLines from "keylines";
import { data } from "./data.js";
let chart;
const clustersSlider = document.getElementById("clustersSlider");
// Generate a random hex colour
function randomColour() {
return `#${`${Math.random().toString(16)}0000000`.slice(2, 8)}`;
}
// Generate a new colour for each cluster:
// for each colour created save it inside an array:
// this works well with the stability factor of the cluster algo
const coloursList = [];
function getColour(i) {
// Cache a colour list and generate a new colour if not already present.
if (!coloursList[i]) {
coloursList[i] = randomColour();
}
return coloursList[i];
}
function runClusters() {
// Don't bother running unless the value has changed
const newValue = clustersSlider.value;
// Update the label
document.getElementById("clustersVal").innerText = newValue;
// Get the clusters - a 2d array of nodes
const result = chart.graph().clusters({
factor: newValue,
consistent: true,
});
// Get the changes - the first array is of groups, with the contents being the nodes in each
// group. As we're looking for changes, we use flatMap to go through all groups and merge
// them into a single array.
const changes = [];
result.forEach((inner, k1) => {
inner.forEach((id) => {
changes.push({ id, c: getColour(k1) });
});
});
// Apply the changes
chart.setProperties(changes);
}
async function startKeyLines() {
const options = {
logo: { u: "/public/images/Logo.png" },
linkEnds: { avoidLabels: false },
handMode: true,
};
chart = await KeyLines.create({
container: "klchart",
options,
});
chart.load(data);
clustersSlider.addEventListener("input", runClusters);
chart.zoom("fit");
runClusters();
}
window.addEventListener("DOMContentLoaded", startKeyLines); export const data = {
type: "LinkChart",
items: [
{
type: "link",
id: "link0",
id1: "node1",
id2: "node37",
},
{
type: "link",
id: "link1",
id1: "node1",
id2: "node38",
},
{
type: "link",
id: "link2",
id1: "node1",
id2: "node39",
},
{
type: "link",
id: "link3",
id1: "node1",
id2: "node40",
},
{
type: "link",
id: "link4",
id1: "node1",
id2: "node41",
},
{
type: "link",
id: "link5",
id1: "node1",
id2: "node42",
},
{
type: "link",
id: "link6",
id1: "node1",
id2: "node43",
},
{
type: "link",
id: "link7",
id1: "node1",
id2: "node44",
},
{
type: "link",
id: "link8",
id1: "node1",
id2: "node45",
},
{
type: "link",
id: "link9",
id1: "node1",
id2: "node46",
},
{
type: "link",
id: "link10",
id1: "node46",
id2: "node47",
},
{
type: "link",
id: "link11",
id1: "node1",
id2: "node48",
},
{
type: "link",
id: "link12",
id1: "node1",
id2: "node49",
},
{
type: "link",
id: "link13",
id1: "node1",
id2: "node50",
},
{
type: "link",
id: "link14",
id1: "node1",
id2: "node51",
},
{
type: "link",
id: "link15",
id1: "node1",
id2: "node52",
},
{
type: "link",
id: "link16",
id1: "node1",
id2: "node53",
},
{
type: "link",
id: "link17",
id1: "node53",
id2: "node54",
},
{
type: "link",
id: "link18",
id1: "node53",
id2: "node55",
},
{
type: "link",
id: "link19",
id1: "node53",
id2: "node56",
},
{
type: "link",
id: "link20",
id1: "node53",
id2: "node57",
},
{
type: "link",
id: "link21",
id1: "node53",
id2: "node58",
},
{
type: "link",
id: "link22",
id1: "node53",
id2: "node59",
},
{
type: "link",
id: "link23",
id1: "node53",
id2: "node60",
},
{
type: "link",
id: "link24",
id1: "node53",
id2: "node61",
},
{
type: "link",
id: "link25",
id1: "node53",
id2: "node62",
},
{
type: "link",
id: "link26",
id1: "node1",
id2: "node63",
},
{
type: "link",
id: "link27",
id1: "node1",
id2: "node64",
},
{
type: "link",
id: "link28",
id1: "node64",
id2: "node65",
},
{
type: "link",
id: "link29",
id1: "node64",
id2: "node66",
},
{
type: "link",
id: "link30",
id1: "node64",
id2: "node67",
},
{
type: "link",
id: "link31",
id1: "node64",
id2: "node68",
},
{
type: "link",
id: "link32",
id1: "node64",
id2: "node69",
},
{
type: "link",
id: "link33",
id1: "node64",
id2: "node70",
},
{
type: "link",
id: "link34",
id1: "node64",
id2: "node71",
},
{
type: "link",
id: "link35",
id1: "node64",
id2: "node73",
},
{
type: "link",
id: "link36",
id1: "node64",
id2: "node74",
},
{
type: "link",
id: "link37",
id1: "node64",
id2: "node75",
},
{
type: "link",
id: "link38",
id1: "node64",
id2: "node76",
},
{
type: "link",
id: "link39",
id1: "node64",
id2: "node77",
},
{
type: "link",
id: "link40",
id1: "node64",
id2: "node78",
},
{
type: "link",
id: "link41",
id1: "node64",
id2: "node79",
},
{
type: "link",
id: "link42",
id1: "node64",
id2: "node80",
},
{
type: "link",
id: "link43",
id1: "node64",
id2: "node81",
},
{
type: "link",
id: "link44",
id1: "node64",
id2: "node82",
},
{
type: "link",
id: "link45",
id1: "node64",
id2: "node83",
},
{
type: "link",
id: "link46",
id1: "node64",
id2: "node84",
},
{
type: "link",
id: "link47",
id1: "node64",
id2: "node85",
},
{
type: "link",
id: "link48",
id1: "node85",
id2: "node86",
},
{
type: "link",
id: "link49",
id1: "node64",
id2: "node3864",
},
{
type: "link",
id: "link50",
id1: "node64",
id2: "node87",
},
{
type: "link",
id: "link51",
id1: "node1",
id2: "node88",
},
{
type: "link",
id: "link52",
id1: "node88",
id2: "node89",
},
{
type: "link",
id: "link53",
id1: "node88",
id2: "node90",
},
{
type: "link",
id: "link54",
id1: "node88",
id2: "node72",
},
{
type: "link",
id: "link55",
id1: "node88",
id2: "node91",
},
{
type: "link",
id: "link56",
id1: "node88",
id2: "node3870",
},
{
type: "link",
id: "link57",
id1: "node88",
id2: "node92",
},
{
type: "link",
id: "link58",
id1: "node88",
id2: "node93",
},
{
type: "link",
id: "link59",
id1: "node88",
id2: "node94",
},
{
type: "link",
id: "link60",
id1: "node88",
id2: "node95",
},
{
type: "link",
id: "link61",
id1: "node88",
id2: "node96",
},
{
type: "link",
id: "link62",
id1: "node88",
id2: "node97",
},
{
type: "link",
id: "link63",
id1: "node88",
id2: "node98",
},
{
type: "link",
id: "link64",
id1: "node88",
id2: "node99",
},
{
type: "link",
id: "link65",
id1: "node88",
id2: "node100",
},
{
type: "link",
id: "link66",
id1: "node1",
id2: "node177",
},
{
type: "link",
id: "link67",
id1: "node1",
id2: "node102",
},
{
type: "link",
id: "link68",
id1: "node1",
id2: "node103",
},
{
type: "link",
id: "link69",
id1: "node1",
id2: "node104",
},
{
type: "link",
id: "link70",
id1: "node1",
id2: "node3850",
},
{
type: "link",
id: "link71",
id1: "node3850",
id2: "node3851",
},
{
type: "link",
id: "link72",
id1: "node3850",
id2: "node3852",
},
{
type: "link",
id: "link73",
id1: "node3850",
id2: "node3853",
},
{
type: "link",
id: "link74",
id1: "node3850",
id2: "node3854",
},
{
type: "link",
id: "link75",
id1: "node3850",
id2: "node3855",
},
{
type: "link",
id: "link76",
id1: "node3850",
id2: "node3856",
},
{
type: "link",
id: "link77",
id1: "node3850",
id2: "node3857",
},
{
type: "link",
id: "link78",
id1: "node3850",
id2: "node3858",
},
{
type: "link",
id: "link79",
id1: "node3850",
id2: "node3859",
},
{
type: "link",
id: "link80",
id1: "node1",
id2: "node3860",
},
{
type: "link",
id: "link81",
id1: "node3860",
id2: "node3849",
},
{
type: "link",
id: "link82",
id1: "node3860",
id2: "node103",
},
{
type: "link",
id: "link83",
id1: "node3860",
id2: "node3862",
},
{
type: "link",
id: "link84",
id1: "node3860",
id2: "node3863",
},
{
type: "link",
id: "link85",
id1: "node1",
id2: "node3865",
},
{
type: "link",
id: "link86",
id1: "node1",
id2: "node3867",
},
{
type: "link",
id: "link87",
id1: "node1",
id2: "node3868",
},
{
type: "link",
id: "link88",
id1: "node1",
id2: "node3869",
},
{
type: "link",
id: "link89",
id1: "node1",
id2: "node3871",
},
{
type: "link",
id: "link90",
id1: "node3872",
id2: "node107",
},
{
type: "link",
id: "link91",
id1: "node3872",
id2: "node108",
},
{
type: "link",
id: "link92",
id1: "node3872",
id2: "node109",
},
{
type: "link",
id: "link93",
id1: "node3872",
id2: "node110",
},
{
type: "link",
id: "link94",
id1: "node3872",
id2: "node111",
},
{
type: "link",
id: "link95",
id1: "node3872",
id2: "node112",
},
{
type: "link",
id: "link96",
id1: "node3872",
id2: "node113",
},
{
type: "link",
id: "link97",
id1: "node3872",
id2: "node114",
},
{
type: "link",
id: "link98",
id1: "node3872",
id2: "node115",
},
{
type: "link",
id: "link99",
id1: "node3872",
id2: "node116",
},
{
type: "link",
id: "link100",
id1: "node3872",
id2: "node117",
},
{
type: "link",
id: "link101",
id1: "node3872",
id2: "node118",
},
{
type: "link",
id: "link102",
id1: "node3872",
id2: "node119",
},
{
type: "link",
id: "link103",
id1: "node3872",
id2: "node120",
},
{
type: "link",
id: "link104",
id1: "node3872",
id2: "node121",
},
{
type: "link",
id: "link105",
id1: "node3872",
id2: "node122",
},
{
type: "link",
id: "link106",
id1: "node3872",
id2: "node123",
},
{
type: "link",
id: "link107",
id1: "node3872",
id2: "node3875",
},
{
type: "link",
id: "link108",
id1: "node3872",
id2: "node125",
},
{
type: "link",
id: "link109",
id1: "node3872",
id2: "node3876",
},
{
type: "link",
id: "link110",
id1: "node3872",
id2: "node127",
},
{
type: "link",
id: "link111",
id1: "node3872",
id2: "node128",
},
{
type: "link",
id: "link112",
id1: "node3872",
id2: "node3877",
},
{
type: "link",
id: "link113",
id1: "node3872",
id2: "node3878",
},
{
type: "link",
id: "link114",
id1: "node3872",
id2: "node129",
},
{
type: "link",
id: "link115",
id1: "node129",
id2: "node130",
},
{
type: "link",
id: "link116",
id1: "node129",
id2: "node131",
},
{
type: "link",
id: "link117",
id1: "node3872",
id2: "node132",
},
{
type: "link",
id: "link118",
id1: "node3872",
id2: "node133",
},
{
type: "link",
id: "link119",
id1: "node3872",
id2: "node134",
},
{
type: "link",
id: "link120",
id1: "node3872",
id2: "node135",
},
{
type: "link",
id: "link121",
id1: "node3872",
id2: "node136",
},
{
type: "link",
id: "link122",
id1: "node3872",
id2: "node137",
},
{
type: "link",
id: "link123",
id1: "node3872",
id2: "node138",
},
{
type: "link",
id: "link124",
id1: "node3872",
id2: "node139",
},
{
type: "link",
id: "link125",
id1: "node139",
id2: "node140",
},
{
type: "link",
id: "link126",
id1: "node139",
id2: "node141",
},
{
type: "link",
id: "link127",
id1: "node139",
id2: "node142",
},
{
type: "link",
id: "link128",
id1: "node3872",
id2: "node143",
},
{
type: "link",
id: "link129",
id1: "node4",
id2: "node3879",
},
{
type: "link",
id: "link130",
id1: "node4",
id2: "node3880",
},
{
type: "link",
id: "link131",
id1: "node4",
id2: "node3881",
},
{
type: "link",
id: "link132",
id1: "node4",
id2: "node3882",
},
{
type: "link",
id: "link133",
id1: "node4",
id2: "node3884",
},
{
type: "link",
id: "link134",
id1: "node4",
id2: "node3885",
},
{
type: "link",
id: "link135",
id1: "node4",
id2: "node3886",
},
{
type: "link",
id: "link136",
id1: "node4",
id2: "node3887",
},
{
type: "link",
id: "link137",
id1: "node4",
id2: "node3888",
},
{
type: "link",
id: "link138",
id1: "node4",
id2: "node3889",
},
{
type: "link",
id: "link139",
id1: "node4",
id2: "node3890",
},
{
type: "link",
id: "link140",
id1: "node4",
id2: "node170",
},
{
type: "link",
id: "link141",
id1: "node4",
id2: "node3891",
},
{
type: "link",
id: "link142",
id1: "node4",
id2: "node1297",
},
{
type: "link",
id: "link143",
id1: "node4",
id2: "node164",
},
{
type: "link",
id: "link144",
id1: "node4",
id2: "node167",
},
{
type: "link",
id: "link145",
id1: "node4",
id2: "node3892",
},
{
type: "link",
id: "link146",
id1: "node170",
id2: "node177",
},
{
type: "link",
id: "link147",
id1: "node171",
id2: "node174",
},
{
type: "link",
id: "link148",
id1: "node171",
id2: "node173",
},
{
type: "link",
id: "link149",
id1: "node171",
id2: "node3893",
},
{
type: "link",
id: "link150",
id1: "node171",
id2: "node3894",
},
{
type: "link",
id: "link151",
id1: "node170",
id2: "node3951",
},
{
type: "link",
id: "link152",
id1: "node170",
id2: "node175",
},
{
type: "link",
id: "link153",
id1: "node170",
id2: "node3896",
},
{
type: "link",
id: "link154",
id1: "node170",
id2: "node3897",
},
{
type: "link",
id: "link155",
id1: "node170",
id2: "node220",
},
{
type: "link",
id: "link156",
id1: "node171",
id2: "node3898",
},
{
type: "link",
id: "link157",
id1: "node171",
id2: "node3899",
},
{
type: "link",
id: "link158",
id1: "node171",
id2: "node3900",
},
{
type: "link",
id: "link159",
id1: "node171",
id2: "node3901",
},
{
type: "link",
id: "link160",
id1: "node171",
id2: "node3902",
},
{
type: "link",
id: "link161",
id1: "node170",
id2: "node3903",
},
{
type: "link",
id: "link162",
id1: "node170",
id2: "node238",
},
{
type: "link",
id: "link163",
id1: "node170",
id2: "node239",
},
{
type: "link",
id: "link164",
id1: "node170",
id2: "node246",
},
{
type: "link",
id: "link165",
id1: "node170",
id2: "node247",
},
{
// ...truncated 13097 lines <!doctype html>
<html lang="en" style="background-color: #2d383f">
<head>
<meta charset="utf-8" />
<title>Clusters</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 type="module" src="./code.js"></script>
</body>
</html> .sliderScale {
float: left;
line-height: 33px;
}
.sliderContainer {
float: left;
width: calc(100% - 30px);
}
#clustersSlider {
margin: 10px;
width: calc(100% - 20px);
}