Users can sometimes get lost when viewing networks, particularly when charts are large. Lists and tables already have established sorting and filtering techniques, but networks can be harder to navigate.
A simple search box can be a great solution.
Once the item has been found, it is usually a good idea to select the item and then zoom or pan to it so the user can locate it quickly.
Advanced
This demo only searches for node labels. A more advanced search could iterate over custom data properties which you can hold on the d property of items.
Key functions used:
import KeyLines from "keylines";
import { data } from "./data.js";
let chart;
const formElement = document.getElementById("rhsForm");
const searchInputElement = document.getElementById("name-search-input");
// Select all nodes with labels containing the search
async function selectMatchingNodes() {
const searchText = searchInputElement.value;
// If there is no search value, clear the selection and return
if (!searchText) {
chart.selection([]);
return;
}
const matchingNodes = [];
// Iterate over every node and test if it matches the search criterion
await chart.each({ type: "node" }, (item) => {
// make both lowercase so we can match better
if (item.t.toLowerCase().includes(searchText.toLowerCase())) {
matchingNodes.push(item.id);
}
});
// Set chart selection to matching nodes
chart.selection(matchingNodes);
}
// Fit the current selection to view and ping selected items
function centreAndPingSelection() {
chart.zoom("selection", { animate: true, time: 300 });
chart.ping(chart.selection());
}
// Search and ping nodes when search is submitted
async function searchAndPing(event) {
event.preventDefault();
await selectMatchingNodes();
centreAndPingSelection();
}
// Attach submit listener for search, and change listener for input field
function initialiseInteractions() {
formElement.addEventListener("submit", searchAndPing);
searchInputElement.addEventListener("input", selectMatchingNodes);
}
async function loadKeyLines() {
const options = {
handMode: true,
logo: { u: "/images/Logo.png" },
linkStyle: { inline: true },
};
chart = await KeyLines.create({ container: "klchart", options });
chart.load(data);
chart.zoom("fit");
initialiseInteractions();
}
window.addEventListener("DOMContentLoaded", loadKeyLines); export const data = {
type: "LinkChart",
items: [
{
id: "ab",
id1: "a",
id2: "b",
type: "link",
},
{
id: "ac",
id1: "a",
id2: "c",
type: "link",
},
{
id: "ad",
id1: "a",
id2: "d",
type: "link",
},
{
id: "ae",
id1: "a",
id2: "e",
type: "link",
},
{
id: "bc",
id1: "b",
id2: "c",
type: "link",
},
{
id: "bd",
id1: "b",
id2: "d",
type: "link",
},
{
id: "cj",
id1: "c",
id2: "j",
type: "link",
},
{
id: "ci",
id1: "c",
id2: "i",
type: "link",
},
{
id: "eg",
id1: "e",
id2: "g",
type: "link",
},
{
id: "hi",
id1: "h",
id2: "i",
type: "link",
},
{
id: "ge",
id1: "g",
id2: "e",
type: "link",
},
{
id: "ij",
id1: "i",
id2: "j",
type: "link",
},
{
id: "a",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "Bob",
d: {
fn: "Robert",
ln: "Smith",
},
x: 75.76026518005943,
y: -83.46634235119183,
},
{
id: "b",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "Diane",
d: {
fn: "Diane",
ln: "Caliva",
},
x: -21.316204675743222,
y: 22.70114230209424,
},
{
id: "c",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "Debbie",
d: {
fn: "Debbie",
ln: "Denise",
},
x: -100.90629851699214,
y: -106.8638179262436,
},
{
id: "d",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "Sean",
d: {
fn: "Sean",
ln: "Young",
},
x: 111.4919118130037,
y: 57.76324466477354,
},
{
id: "e",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "Tim",
d: {
fn: "Tim",
ln: "Angulo",
},
x: 235.02898787052658,
y: -88.42576308481992,
},
{
id: "f",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "Bud",
d: {
fn: "Bud",
ln: "Alper",
},
x: -368.6071671777256,
y: 195,
},
{
id: "g",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "Morris",
d: {
fn: "Morris",
ln: "Chapnick",
},
x: 365.7934441633303,
y: -51.145368880462485,
},
{
id: "h",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "Joe",
d: {
fn: "Joe",
ln: "Turkel",
},
x: -368.8944961700126,
y: 38.76191087970028,
},
{
id: "j",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "David",
d: {
fn: "David",
ln: "Snyder",
},
x: -227.32361697339707,
y: -193.04421213667794,
},
{
id: "i",
type: "node",
c: "#008282",
fbc: "rgba(0, 0, 0, 0)",
fc: "white",
t: "Michael",
d: {
fn: "Michael",
ln: "Deeley",
},
x: -263.2245888367452,
y: -56.58911339133482,
},
],
combos: {
nodes: [],
links: [],
},
viewSettings: {
width: 768,
height: 565,
zoom: 0.8185140073081608,
offsetX: 729.4129110840438,
offsetY: 527.2356881851401,
},
}; <!doctype html>
<html lang="en" style="background-color: #2d383f">
<head>
<meta charset="utf-8" />
<title>Search for Text</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" />
</head>
<body>
<div id="klchart" class="klchart"></div>
<script type="module" src="./code.js"></script>
</body>
</html>