Search

Visualising Big Data

Large Charts

Discover new ways to visualise large networks.

Visualising Big Data
View live example →

Displaying large networks and amounts of data all at once is tricky, but KeyLines offers features that can make even large data visualisations clear and effective.

Organic layout

This demo uses organic layout to make sense of the data's complex structure. This, combined with the enhanced rendering performance you can achieve with WebGL, creates a responsive, clear and engaging chart.

Organic also supports incrementally reporting progress events, which provide information about the layout's progress. This can be used for example to display a progress indicator and improve the user's experience while the layout is running.

Adaptive styling

Adaptive styling enlarges small nodes so that you can see them better, and fades out links and text when they're too closely packed or too small to read. This means that details are shown when you need them, but don't clutter the chart when they're unnecessary.

The data is taken from OpenFlights, available under the Open Database License (ODbL).

Chart Options used:

import KeyLines from "keylines";
let chart;
let dataSizeOverlay;

function getById(id) {
  return document.getElementById(id);
}

const progressBarContainer = getById("progressBarContainer");
const progressBar = getById("progressBar");
const progressFrame = getById("progressFrame");
const layoutButton = getById("layoutButton");
const fpsLabel = getById("fps");

function updateProgressBar(progress) {
  const percentage = (progress * 100).toFixed(2);
  progressBar.style.width = `${percentage}%`;
}

async function doLayout() {
  layoutButton.disabled = true;
  progressBarContainer.style.display = "block";
  await chart.layout("organic", { time: 1500 });
  progressBarContainer.style.display = "none";
  layoutButton.disabled = false;
}

function setupLayoutUX() {
  // Add progress event listener
  chart.on("progress", ({ progress, task }) => {
    if (task === "layout") {
      updateProgressBar(progress);
    }
  });
  layoutButton.addEventListener("click", () => doLayout());
}

function fpsCounter(blend = 0.33) {
  let tick = Date.now();
  let mean;

  (function next() {
    requestAnimationFrame((tock) => {
      const dt = tock - tick;
      mean = mean ? mean * blend + dt * (1 - blend) : dt;
      tick = tock;
      next();
    });
  })();

  return { get: () => Math.round(1000 / mean) };
}

function toggleSwitch(name, onOn, onOff) {
  const onElement = getById(`${name}On`);
  const offElement = getById(`${name}Off`);
  onElement.addEventListener("click", (evt) => {
    onElement.classList.add("active", "btn-kl");
    offElement.classList.remove("active", "btn-kl");
    offElement.disabled = false;
    onElement.disabled = true;
    onOn();
    evt.preventDefault();
  });
  offElement.addEventListener("click", (evt) => {
    onElement.classList.remove("active", "btn-kl");
    onElement.disabled = false;
    offElement.disabled = true;
    offElement.classList.add("active", "btn-kl");
    onOff();
    evt.preventDefault();
  });
}

async function loadAndStart() {
  dataSizeOverlay = document.getElementsByClassName("datasize")[0];
  const chartDefinition = {
    container: "klchart",
    options: {
      backColour: "#2d383f",
      controlTheme: "dark",
      drag: {
        links: false,
      },
      minZoom: 0.01,
      overview: {
        backColour: "#2d383f",
        borderColour: "grey",
      },
      handMode: true,
    },
  };
  chart = await KeyLines.create(chartDefinition);

  // display frame rate
  setInterval(
    (fps) => {
      fpsLabel.innerText = fps.get() + " Frames per Second";
    },
    400,
    fpsCounter()
  );

  function adaptiveStylingOn() {
    chart.options({ zoom: { adaptiveStyling: true } });
  }

  function adaptiveStylingOff() {
    chart.options({ zoom: { adaptiveStyling: false } });
  }

  function darkModeOn() {
    chart.options({
      backColour: "#2d383f",
      controlTheme: "dark",
      overview: {
        backColour: "#2d383f",
        borderColour: "grey",
      },
    });
    progressFrame.style["border-color"] = "white";
    dataSizeOverlay.classList.add("dark");
  }

  function darkModeOff() {
    chart.options({
      backColour: "white",
      controlTheme: "light",
      overview: {
        backColour: "white",
        borderColour: "rgb(243, 246, 247)",
      },
    });
    progressFrame.style["border-color"] = "black";
    dataSizeOverlay.classList.remove("dark");
  }

  function gradientLinksOn() {
    const links = [];
    chart.each({ type: "link" }, (link) => {
      links.push({ id: link.id, c: link.d.c, c2: link.d.c2 });
    });
    chart.setProperties(links);
  }

  function gradientLinksOff() {
    const links = [];
    chart.each({ type: "link" }, (link) => {
      links.push({ id: link.id, c: null, c2: null, d: { c: link.c, c2: link.c2 } });
    });
    chart.setProperties(links);
  }

  toggleSwitch("adaptiveStyling", adaptiveStylingOn, adaptiveStylingOff);
  toggleSwitch("darkMode", darkModeOn, darkModeOff);
  toggleSwitch("gradientLinks", gradientLinksOn, gradientLinksOff);

  const data = await (await fetch("/worldflights-data.json")).json();
  chart.load(data);

  setupLayoutUX();
  await doLayout();
}

window.addEventListener("DOMContentLoaded", loadAndStart);
<!doctype html>
<html lang="en" style="background-color: #2d383f">
  <head>
    <meta charset="utf-8" />
    <title>Visualising Big Data</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 id="progressBarContainer">
        <div class="progress" id="progressFrame"><div id="progressBar"></div></div>
      </div>
      <div class="datasize dark" id="dataSize">
        <h5 id="fps">0 Frames per Second</h5>
        <p>Nodes: 2875</p>
        <p>Links: 13139</p>
      </div>
    </div>
    <script type="module" src="./code.js"></script>
  </body>
</html>
.klchart {
  background-color: #2d383f;
}

.datasize {
  position: absolute;
  bottom: 0px;
  height: 100px;
  line-height: 16px;
  left: 16px;
  color: #2d383f;
  background-color: transparent;
}

.datasize > h5 {
  font-size: 20px;
}

.datasize.dark,
.datasize.dark p {
  color: #eee;
}

.progress {
  position: absolute;
  top: calc(50% - 22.5px);
  left: 30%;
  width: 40%;
  height: 45px;
  padding: 4px;
  opacity: 0.9;
  border: white 3px solid;
}

.progress > div {
  background-color: #009968;
  width: 0%;
  height: 100%;
  transition: width 1s ease-in;
}

Terms of use

These terms do not alter or supersede any existing agreements between you (or your employer) and us.

By accessing or using any Content you agree to be bound by these Terms of Use. Please review these terms carefully before using the website.

The contents of this website, including but not limited to any text, code samples, API references, schemas, interactive tools, and other materials (collectively, the 'Content'), are made available for informational and internal evaluation purposes only. All intellectual property rights in the Content are reserved. No licence is granted to use the Content for any commercial purpose, or to copy, distribute, modify, reverse-engineer, or incorporate any part of the Content into any product or service, without our prior written consent.

This Content is provided “as is” and “as available,” without any representations, warranties, or guarantees of any kind, whether express or implied, including but not limited to implied warranties of merchantability, fitness for a particular purpose, non-infringement, or accuracy. To the fullest extent permitted by applicable law, we expressly exclude and disclaim all implied warranties, conditions, and other terms that might otherwise be implied.

We disclaim all liability for any loss or damage, whether direct, indirect, incidental, consequential, or otherwise, arising from any reliance placed on the Content or from your use of it, to the fullest extent permitted by applicable law. By continuing to access or use the Content, you acknowledge and agree to these terms.