//
// Copyright © 2011-2025 Cambridge Intelligence Limited.
// All rights reserved.
//
// Sample Code
//
//! Use combinations of filters to simplify networks.
import KeyLines from "keylines";
import { data } from "./filters-data.js";
let chart;
let kCoresResult;
const graphEngine = KeyLines.getGraphEngine();
const kCoresEl = document.getElementById("slider");
let filterCountries;
let k;
// Runs an organic layout
async function doLayout() {
await chart.layout("organic", {
time: 400,
easing: "linear",
mode: "adaptive",
});
}
// Append a new checkbox with label for each country
function createCheckboxMarkup(element, countries) {
countries.forEach((country) => {
const newCheckBoxItem = document.createElement("label");
const countryName = country.country;
const countryCount = country.count;
newCheckBoxItem.innerHTML = `
<input type='checkbox' id=${countryName} disabled=true checked>
<img src='/images/flags/${countryName}.png' style='width: 16px; height: 16px')>
${countryName} (${countryCount})`;
element.append(newCheckBoxItem);
});
}
function displayCheckboxItems(items) {
const checkboxColumn1El = document.getElementById("checkboxColumn1");
const checkboxColumn2El = document.getElementById("checkboxColumn2");
// Count occurrences of each country
const countries = {};
items.forEach((item) => {
if (item.type === "node") {
const country = item.d.country;
countries[country] = (countries[country] || 0) + 1;
}
});
// Convert into array and sort countries in descending order of occurrences
const countryCounts = Object.keys(countries).map((country) => ({
country,
count: countries[country],
}));
countryCounts.sort((a, b) => b.count - a.count);
// Show each country as a checkbox in two columns
createCheckboxMarkup(checkboxColumn1El, countryCounts.slice(0, 13));
createCheckboxMarkup(checkboxColumn2El, countryCounts.slice(13));
}
// Model the chart state from original data and user selection
// We include all links as the load function will disregard links that don't have both ends present
function getChartModel() {
const items = data.items.filter(
(item) => item.type === "link" || filterCountries.includes(item.d.country)
);
return { type: "LinkChart", items };
}
// Returns a new kCores result
function getNewkCores() {
graphEngine.load(getChartModel());
return graphEngine.kCores();
}
// Sets the value of filters and kCore values
function setFilters() {
k = kCoresEl.value;
filterCountries = [
...document.querySelectorAll("input[type=checkbox]:checked"),
].map((checkbox) => checkbox.id);
kCoresResult = getNewkCores();
}
function matchesFilters(item) {
const countryIsSelected = filterCountries.includes(item.d.country);
const kCoreIsValid = kCoresResult.values[item.id] >= k;
return kCoreIsValid && countryIsSelected;
}
function toggleInteraction() {
const inputItems = document.querySelectorAll("input");
// Filter the chart when any checkbox is changed
inputItems.forEach((inputItem) => {
inputItem.disabled = !inputItem.disabled;
});
// focus back on the slider to stop Edge requiring 2 taps
kCoresEl.focus();
}
// Filter the chart based on user selections
async function doFiltering() {
setFilters();
toggleInteraction();
// Filter the chart and run a layout
await chart.filter(matchesFilters, { type: "node" });
await doLayout();
toggleInteraction();
}
function initialiseInteractions() {
toggleInteraction();
const checkBoxes = [...document.querySelectorAll("input[type=checkbox]")];
// Set the slider max value from kCoresResultAll
kCoresEl.setAttribute("max", kCoresResult.maximumK);
// Filter the chart when any checkbox is changed
checkBoxes.forEach((checkbox) => {
checkbox.addEventListener("click", doFiltering);
});
// Filter the chart when all countries are selected
document.getElementById("selectAll").addEventListener("click", () => {
checkBoxes.forEach((checkbox) => {
checkbox.checked = "checked";
});
doFiltering();
});
// Filter the chart when all countries are cleared
document.getElementById("clearAll").addEventListener("click", () => {
checkBoxes.forEach((checkbox) => {
checkbox.checked = false;
});
doFiltering();
});
// Filter the chart when slider is changed
kCoresEl.addEventListener("change", () => {
const kCoresValue = kCoresEl.value;
document.getElementById("kCoresValue").innerHTML = ` ${kCoresValue}`;
doFiltering();
});
}
async function loadKeyLines() {
const options = {
logo: { u: "/images/Logo.png" },
handMode: true,
};
chart = await KeyLines.create({ container: "klchart", options });
displayCheckboxItems(data.items);
chart.load(data);
// Calculate the initial core values
kCoresResult = chart.graph().kCores();
await doLayout();
initialiseInteractions();
}
window.addEventListener("DOMContentLoaded", loadKeyLines);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
<head>
<meta charset="UTF-8">
<title>Filtering and kCores</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">
<link rel="stylesheet" type="text/css" href="/filters.css">
<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="/filters.js" crossorigin="use-credentials" defer type="module"></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%;">Filtering and kCores</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">
<fieldset>
<legend>Select countries: </legend>
<div class="row">
<div id="checkboxColumn1"></div>
<div id="checkboxColumn2"></div>
</div>
<div class="row"></div>
</fieldset>
<fieldset>
<div class="span">
<input class="btn btn-kl btn-spaced" type="button" value="Select All" disabled id="selectAll">
<input class="btn btn-kl btn-spaced" type="button" value="Clear All" disabled id="clearAll">
</div>
</fieldset>
<fieldset>
<legend>Select kCore value: <span id="kCoresValue"> 0</span></legend>
<label id="slider-container">
<input id="slider" type="range" min="0" max="10" value="0" style="display: block;" disabled>
</label>
</fieldset>
</form>
</div>
</div>
</div>
</div>
<div id="moreParent">
<div id="moreContainer">
</div>
</div>
<div id="lazyScripts">
</div>
</body>
</html>
#slider-container {
width: 100%;
}
input[type=range] {
width: 90%;
margin-bottom: 10px;
}
fieldset {
margin-top: 10px;
}
form label:not(.checkbox) {
margin: 5px 0px;
}
#checkboxColumn1 label.checkbox,
#checkboxColumn2 label.checkbox {
width: 100%
}
#checkboxColumn1,
#checkboxColumn2 {
width: 49%;
display: inline-block;
vertical-align: top;
}
Loading source
