//
// Copyright © 2011-2025 Cambridge Intelligence Limited.
// All rights reserved.
//
// Sample Code
//
//! Measure how well KeyLines performs.
import KeyLines from "keylines";
function fpsCounter(blend = 0.33) {
var tick = Date.now();
var mean = -1;
(function next() {
requestAnimationFrame((tock) => {
var durr = tock - tick;
mean = mean >= 0 ? mean * blend + durr * (1 - blend) : durr;
tick = tock;
next();
});
})();
return { get: () => Math.round(1000 / mean) };
}
var width = 638;
var height = 470;
var numNodes;
var numLinks;
var spacing;
var zooming = false;
var zoomHow = "out";
var animating = false;
var showLabels = false;
var chart;
function pushData(loadedChart) {
chart = loadedChart;
function linkIds() {
var obj = {
id1: "id" + Math.floor(Math.random() * numNodes),
id2: "id" + Math.floor(Math.random() * numNodes),
};
if (obj.id1 === obj.id2) {
return linkIds(numNodes); // try again
}
return obj;
}
function randomX() {
return Math.floor(Math.random() * width * spacing);
}
function randomY() {
return Math.floor(Math.random() * height * spacing);
}
function random(nNodes, nLinks) {
cancelAnimations();
numNodes = nNodes;
numLinks = nLinks;
spacing = numNodes < 150 ? 1 : Math.sqrt(numNodes / 150);
var nodes = (function () {
var ret = [];
for (var i = 0; i < numNodes; i++) {
ret.push({
id: "id" + i,
type: "node",
t: showLabels ? i : null,
d: { label: i },
x: randomX(),
y: randomY(),
u: "/images/icons/telephone.png",
});
}
return ret;
})();
var links = (function () {
var ret = [];
for (var i = 0; i < numLinks; i++) {
var ids = linkIds();
ret.push({
id: "linkid" + i,
type: "link",
t: showLabels ? i : null,
d: { label: i },
id1: ids.id1,
id2: ids.id2,
c: "rgb(50, 50, 50)",
w: 1,
});
}
return ret;
})();
var items = nodes.concat(links);
chart.load({
type: "LinkChart",
items: items,
});
chart.zoom("fit");
}
async function startAnimation() {
if (animating) {
var items = [];
for (var i = 0; i < numNodes; i++) {
items.push({ id: "id" + i, x: randomX(), y: randomY() });
}
await chart.animateProperties(items, { time: 3000 });
startAnimation();
}
}
function swapButtons(type, isActive) {
// cast the isActive because undefined is different from false in this case
$("#" + type + "On").toggleClass("active btn-kl", !!isActive);
$("#" + type + "Off").toggleClass("active btn-kl", !isActive);
}
$("#animationOff").click(function (evt) {
animating = false;
swapButtons("animation", false);
evt.preventDefault();
});
$("#animationOn").click(function (evt) {
if (numNodes > 0 && animating === false) {
swapButtons("animation", true);
animating = true;
startAnimation();
}
evt.preventDefault();
});
async function startZooming() {
if (zooming) {
// Invert the current zoom
zoomHow = zoomHow === "in" ? "out" : "in";
// Run the zoom
await chart.zoom(zoomHow, { animate: true });
startZooming();
}
}
$("#zoomOff").click(function (evt) {
zooming = false;
swapButtons("zoom", false);
evt.preventDefault();
});
$("#zoomOn").click(function (evt) {
if (numNodes > 0 && zooming === false) {
zooming = true;
swapButtons("zoom", true);
startZooming();
}
evt.preventDefault();
});
function toggleLabels() {
var items = [];
chart.each({ type: "all" }, function (item) {
items.push({ id: item.id, t: showLabels ? item.d.label : null });
});
chart.setProperties(items);
}
$("#labelsOff").click(function (evt) {
showLabels = false;
swapButtons("labels", false);
toggleLabels();
evt.preventDefault();
});
$("#labelsOn").click(function (evt) {
if (numNodes > 0) {
showLabels = true;
swapButtons("labels", true);
toggleLabels();
}
evt.preventDefault();
});
function cancelAnimations() {
// Cancel nodes animation
animating = false;
swapButtons("animation", false);
// Cancel zoom
zooming = false;
swapButtons("zoom", false);
}
$("#generate").click(function (evt) {
var option = $("#nodelink").val();
if (option) {
$("#animationOn").removeClass("disabled");
$("#zoomOn").removeClass("disabled");
$("#labelsOn").removeClass("disabled");
var parts = option.split(",");
random(Number(parts[0]), Number(parts[1]));
}
evt.preventDefault();
});
$("#layout").click(async function (evt) {
cancelAnimations();
evt.preventDefault();
var start = new Date().getTime();
await chart.layout("organic", { animate: false, packing: "circle" });
var endtime = new Date().getTime();
var secs = (endtime - start) / 1000;
$("#layoutStats").text(secs + "s");
});
// display frame rate
setInterval(
function (fps) {
$("#fps").text(fps.get() + " Frames per Second");
},
400,
fpsCounter()
);
}
$(function () {
var options = {
logo: { u: "/images/Logo.png" },
minZoom: 0.02,
};
KeyLines.create({ container: "klchart", options: options }).then(pushData);
});
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
<head>
<meta charset="UTF-8">
<title>Test Performance</title>
<link rel="stylesheet" type="text/css" href="/css/keylines.css">
<link rel="stylesheet" type="text/css" href="/css/minimalsdk.css">
<link rel="stylesheet" type="text/css" href="/css/sdk-layout.css">
<link rel="stylesheet" type="text/css" href="/css/demo.css">
<style>
h5 {
margin-bottom: 0;
}
</style>
<script src="/vendor/jquery.js" defer type="text/javascript"></script>
<script id="keylines" src="/public/keylines.umd.cjs" defer type="text/javascript"></script>
<script src="/performance.js" defer type="text/javascript"></script>
</head>
<body>
<div class="chart-wrapper demo-cols">
<div class="tab-content-panel flex1" id="lhs" data-tab-group="rhs">
<div class="toggle-content is-visible" id="lhsVisual" style="width:100%; height: 100%;">
<div class="klchart" id="klchart">
</div>
</div>
</div>
<div class="rhs citext closed" id="demorhscontent">
<div class="title-bar">
<svg viewBox="0 0 360 60" style="max-width: 360px; max-height: 60px; font-size: 24px; font-family: Raleway; flex: 1;">
<text x="0" y="38" style="width: 100%;">Test Performance</text>
</svg>
</div>
<div class="tab-content-panel" data-tab-group="rhs">
<div class="toggle-content is-visible tabcontent" id="controlsTab">
<form autocomplete="off" onsubmit="return false" id="rhsForm">
<div class="cicontent">
<h3 id="fps">0 Frames per Second</h3>
<select class="medium" id="nodelink">
<option value="10,10">10 Nodes, 10 Links</option>
<option value="50,50">50 Nodes, 50 Links</option>
<option value="100,100">100 Nodes, 100 Links</option>
<option value="200,200">200 Nodes, 200 Links</option>
<option value="500,500" selected="true">500 Nodes, 500 Links</option>
<option value="1000,1000">1,000 Nodes, 1,000 Links</option>
<option value="2000,2000">2,000 Nodes, 2,000 Links</option>
<option value="5000,5000">5,000 Nodes, 5,000 Links</option>
<option value="10000,10000">10,000 Nodes, 10,000 Links</option>
<option value="20000,20000">20,000 Nodes, 20,000 Links</option>
</select>
<p>
<input class="btn" type="button" value="Generate" id="generate">
</p>
<p>
<input class="btn" type="button" value="Layout" id="layout"><span id="layoutStats" style="margin-left:10px;"></span>
</p>
<p>
<fieldset>
<legend>Options</legend>
<h5>Animate node positions:</h5>
<div class="btn-group">
<input class="btn disabled" type="button" value="On" id="animationOn">
<input class="btn btn-kl active" type="button" value="Off" id="animationOff">
</div>
<h5>Zoom the chart in and out:</h5>
<div class="btn-group">
<input class="btn disabled" type="button" value="On" id="zoomOn">
<input class="btn btn-kl active" type="button" value="Off" id="zoomOff">
</div>
<h5>Show labels:</h5>
<div class="btn-group">
<input class="btn disabled" type="button" value="On" id="labelsOn">
<input class="btn btn-kl active" type="button" value="Off" id="labelsOff">
</div>
</fieldset>
</p>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="moreParent">
<div id="moreContainer">
</div>
</div>
<div id="lazyScripts">
</div>
</body>
</html>
Loading source
