//
//     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