Search

Social Network Analysis

Analysis

Investigate social structures and relations in complex email data.

Social Network Analysis
View live example →

This email data was collected by US federal investigators in the wake of the Enron collapse. It is a useful real data set that has been studied by many researchers.

When looking at email data from an organisation a common task is to try to work out individual roles and organisational hierarchies just from the metadata of email traffic directions.

Sub-teams within the organisation stand out as clusters of connected nodes in the chart, but identifying important individuals requires a little more analysis.

Clicking on Email Volumes sizes link widths based on the number of emails sent between individuals. This highlights a prominent individual - Bill Williams - in a group on the edge of the chart. Bill is a prolific emailer, and probably the manager of a team. The team don't seem to be engaging with him though, as hardly any of them write back.

You can follow a link from Bill to Timothy who is more central. This is probably Bill's boss. Following the thicker links from Timothy, we see that his boss was probably either John, Louise or Kevin.

Centrality Analysis

KeyLines has some useful network algorithms that can be used to score the email accounts in different ways. We can use these scores to modify the size of nodes, highlighting influential individuals.

See the Graph Centrality documentation for more detail on different centrality measures.

Degrees counts the number of directly connected nodes each node has.

Closeness measures how may steps each node is from every other node in the graph. It helps identify 'broadcasters', or people who have good influence over the network.

Betweenness counts the number of shortest paths all nodes are on, identifying important 'bridges' in the network. In sociology, this has been shown to correlate with seniority in organisations.

Eigenvector centrality is a measure of influence that takes into account the number of links each person has and the number of links their connections have, and so on throughout the network. It is an effective measure of influence in social networks and malware propagation.

PageRank identifies important nodes by counting incoming links and weighting according to the relative scores of their originating nodes. It helps identify nodes which are indirectly influential to the network.

Key functions used:

import KeyLines from "keylines";
import { data, colours } from "./data.js";

let chart;
let miniChart;
let restoreIds = [];

// Track UI state
const state = {
  sizeBy: "same",
  volume: "off",
  direction: "any",
};

const colourMap = {};

/**
 * debounce - This function delays execution of the passed "fn" until "timeToWait" milliseconds
 * have passed since the last time it was called.  This ensures that the function
 * runs at the end of a particular action to keep performance high.
 */
function debounce(fn, timeToWait = 100) {
  let timeoutId;
  return function debouncedFn(...args) {
    const timeoutFn = () => {
      timeoutId = undefined;
      fn.apply(this, args);
    };
    if (timeoutId !== undefined) {
      clearTimeout(timeoutId);
    }
    timeoutId = setTimeout(timeoutFn, timeToWait);
  };
}

// ensure that this doesn't get called too often when dragging the selection
// marquee
const loadMiniChart = debounce((items) => {
  miniChart.load({
    type: "LinkChart",
    items,
  });
  miniChart.layout("organic", { consistent: true });
});

function isLink(id) {
  return id.match("-");
}

function updateHighlight(itemIds) {
  const props = [];

  // Remove previous styles
  if (restoreIds) {
    props.push(
      ...restoreIds.map((id) => {
        const colour =
          colourMap[id] || (isLink(id) ? colours.link : colours.node);
        return {
          id,
          c: colour,
          b: colour,
          ha0: null,
        };
      }),
    );
    restoreIds = [];
  }

  // Add new styles
  if (itemIds.length) {
    // Find the neighbours of the provided items
    const toHighlight = [];
    itemIds.forEach((id) => {
      if (isLink(id)) {
        const link = chart.getItem(id);
        toHighlight.push(id, link.id1, link.id2);
      } else {
        const neighbours = chart.graph().neighbours(id);
        toHighlight.push(id, ...neighbours.nodes, ...neighbours.links);
      }
    });

    // For each neighbouring item, add some styling
    toHighlight.forEach((id) => {
      // Cache the existing styles
      restoreIds.push(id);

      // Generate new styles
      const style = {
        id,
      };
      if (isLink(id)) {
        // For links, just set the colour
        style.c = colours.selected;
      } else {
        // For nodes, add a halo
        style.ha0 = {
          c: colours.selected,
          r: 34,
          w: 6,
        };
      }
      props.push(style);
    });
  }

  chart.setProperties(props);
}

async function runLayout(inconsistent, mode) {
  const packing = mode === "adaptive" ? "adaptive" : "circle";
  return chart.layout("organic", {
    time: 500,
    tightness: 4,
    consistent: !inconsistent,
    packing,
    mode,
  });
}

function klReady(charts) {
  [chart, miniChart] = charts;

  chart.load(data);
  chart.zoom("fit", { animate: false }).then(runLayout);

  // On selection change:
  //   1) add styles to the targeted and neighbouring items
  //   2) copy the selected item and its neighbours to the miniChart
  chart.on("selection-change", () => {
    const ids = chart.selection();

    updateHighlight(ids);

    const miniChartItems = [];

    if (ids.length > 0) {
      const { nodes, links } = chart.graph().neighbours(ids);

      chart.each({ type: "node" }, (node) => {
        if (ids.includes(node.id) || nodes.includes(node.id)) {
          // Clear hover styling and position
          node.x = 0;
          node.y = 0;
          delete node.ha0;
          miniChartItems.push(node);
        }
      });

      chart.each({ type: "link" }, (link) => {
        if (ids.includes(link.id) || links.includes(link.id)) {
          link.c = colours.link;
          miniChartItems.push(link);
        }
      });
    }
    loadMiniChart(miniChartItems);
  });
}

// this function picks a colour from a range of colours based on the value
function colourPicker(value) {
  const { bands, node } = colours;
  if (value > 0.75) {
    return bands[2];
  }
  if (value > 0.5) {
    return bands[1];
  }
  if (value > 0.25) {
    return bands[0];
  }
  return node;
}

function normalize(max, min, value) {
  if (max === min) {
    return min;
  }

  return (value - min) / (max - min);
}

function miniChartFilter(items) {
  return items.filter(({ id }) => miniChart.getItem(id));
}

async function animateValues(values, links) {
  const valuesArray = Object.values(values);

  const max = Math.max(...valuesArray);
  const min = Math.min(...valuesArray);

  const items = Object.entries(values)
    .map(([id, value]) => {
      // Normalize the value in the range 0 -> 1
      const normalized = normalize(max, min, value);

      // enlarge nodes with higher values
      const e = Math.max(1, normalized * 5);

      // Choose a colour (use bands if there is a range of values)
      const c = max !== min ? colourPicker(normalized) : colours.node;
      colourMap[id] = c;

      return { id, e, c, b: c };
    })
    .concat(links);

  const miniItems = miniChartFilter(items);

  // Update the main chart and miniChart concurrently
  return Promise.all([
    chart
      .animateProperties(items, { time: 500 })
      .then(() => runLayout(undefined, "adaptive")),
    miniChart
      .animateProperties(miniItems, { time: 500 })
      .then(() => miniChart.layout("organic", { consistent: true })),
  ]);
}

function same() {
  return new Promise((resolve) => {
    const sizes = {};
    chart.each({ type: "node" }, (node) => {
      sizes[node.id] = 0;
    });
    resolve(sizes);
  });
}

function wrapCallback(fn) {
  return (options) => new Promise((resolve) => resolve(fn(options)));
}

function getAnalysisFunction(name) {
  if (name.match(/^(degrees|pageRank|eigenCentrality)$/)) {
    return wrapCallback(chart.graph()[name]);
  }
  if (name.match(/^(closeness|betweenness)$/)) {
    return chart.graph()[name];
  }
  return same;
}

async function analyseChart() {
  const { sizeBy, volume } = state;

  const options = {};
  // Configure weighting
  if (volume === "on") {
    if (sizeBy.match(/^(betweenness|closeness)$/)) {
      options.weights = true;
    }
    options.value = "count";
  }
  // Configure direction options
  if (sizeBy.match(/^(betweenness|pageRank)$/)) {
    options.directed = state.direction !== "any";
  } else {
    options.direction = state.direction;
  }

  const analyse = getAnalysisFunction(sizeBy);
  const values = await analyse(options);
  const linkWidths = calculateLinkWidths(volume === "on");
  return animateValues(values, linkWidths);
}

function calculateLinkWidths(showValue) {
  const links = [];
  chart.each({ type: "link" }, (link) => {
    const linkcount = link.d.count;
    let width = 1;
    if (showValue) {
      if (linkcount > 300) {
        width = 36;
      } else if (linkcount > 200) {
        width = 27;
      } else if (linkcount > 100) {
        width = 18;
      } else if (linkcount > 50) {
        width = 9;
      }
    }
    links.push({ id: link.id, w: width });
  });
  return links;
}

function doZoom(name) {
  chart.zoom(name, { animate: true, time: 350 });
}

function registerClickHandler(id, fn) {
  document.getElementById(id).addEventListener("click", fn);
}

function updateActiveState(nodes, activeValue) {
  nodes.forEach((node) => {
    if (node.value === activeValue) {
      node.classList.add("active");
    } else {
      node.classList.remove("active");
    }
  });
}

function registerButtonGroup(className, handler) {
  const nodes = document.querySelectorAll(`.${className}`);
  nodes.forEach((node) => {
    node.addEventListener("click", () => {
      const { value } = node;
      updateActiveState(nodes, value);

      handler(value);
    });
  });
}

function initUI() {
  // Chart overlay
  registerClickHandler("home", () => {
    doZoom("fit");
  });
  registerClickHandler("zoomIn", () => {
    doZoom("in");
  });
  registerClickHandler("zoomOut", () => {
    doZoom("out");
  });
  registerClickHandler("changeMode", () => {
    const hand = !!chart.options().handMode; // be careful with undefined
    chart.options({ handMode: !hand });

    const icon = document.getElementById("iconMode");
    icon.classList.toggle("fa-arrows-alt");
    icon.classList.toggle("fa-edit");
  });
  registerClickHandler("layout", () => {
    runLayout(true, "full");
  });

  // Right hand menu
  registerButtonGroup("volume", (volume) => {
    state.volume = volume;
    analyseChart();
  });

  registerButtonGroup("size", (sizeBy) => {
    state.sizeBy = sizeBy;
    analyseChart();
  });

  registerButtonGroup("direction", (direction) => {
    state.direction = direction;
    analyseChart();
  });
}

async function loadKeyLines() {
  initUI();

  const baseOpts = {
    arrows: "normal",
    handMode: true,
    navigation: { shown: false },
    overview: { icon: false },
    selectedNode: {
      c: colours.selected,
      b: colours.selected,
      fbc: colours.selected,
    },
    selectedLink: {
      c: colours.selected,
    },
  };

  const mainChartConfig = {
    container: "klchart",
    options: Object.assign({}, baseOpts, {
      drag: {
        links: false,
      },
      logo: { u: "/public/images/Logo.png" },
    }),
  };

  const miniChartConfig = {
    container: "minikl",
    options: baseOpts,
  };

  const charts = await KeyLines.create([mainChartConfig, miniChartConfig]);
  klReady(charts);
}

window.addEventListener("DOMContentLoaded", loadKeyLines);
export const colours = {
  node: "#ffa726",
  link: "#bcced8",
  bands: ["#f57c00", "#ef5350", "#e91e63"],
  hover: "#62efff",
  selected: "#42a5f5",
};

export const data = {
  type: "LinkChart",
  items: [
    {
      id: "343116-343246",
      type: "link",
      id1: "343116",
      id2: "343246",
      d: {
        count: 200,
      },
      a2: true,
    },
    {
      id: "343116-343244",
      type: "link",
      id1: "343116",
      id2: "343244",
      d: {
        count: 199,
      },
      a2: true,
    },
    {
      id: "343116-343229",
      type: "link",
      id1: "343116",
      id2: "343229",
      d: {
        count: 175,
      },
      a2: true,
    },
    {
      id: "343116-343213",
      type: "link",
      id1: "343116",
      id2: "343213",
      d: {
        count: 196,
      },
      a2: true,
    },
    {
      id: "343116-343212",
      type: "link",
      id1: "343116",
      id2: "343212",
      d: {
        count: 47,
      },
      a2: true,
    },
    {
      id: "343116-343178",
      type: "link",
      id1: "343116",
      id2: "343178",
      d: {
        count: 175,
      },
      a2: true,
    },
    {
      id: "343116-343175",
      type: "link",
      id1: "343116",
      id2: "343175",
      d: {
        count: 203,
      },
      a2: true,
    },
    {
      id: "343116-343149",
      type: "link",
      id1: "343116",
      id2: "343149",
      d: {
        count: 191,
      },
      a2: true,
    },
    {
      id: "343116-343200",
      type: "link",
      id1: "343116",
      id2: "343200",
      d: {
        count: 171,
      },
      a2: true,
    },
    {
      id: "343116-343115",
      type: "link",
      id1: "343116",
      id2: "343115",
      d: {
        count: 127,
      },
      a2: true,
    },
    {
      id: "343121-343116",
      type: "link",
      id1: "343121",
      id2: "343116",
      d: {
        count: 39,
      },
      a2: true,
    },
    {
      id: "343213-343116",
      type: "link",
      id1: "343213",
      id2: "343116",
      d: {
        count: 25,
      },
      a2: true,
    },
    {
      id: "343121-343216",
      type: "link",
      id1: "343121",
      id2: "343216",
      d: {
        count: 38,
      },
      a2: true,
    },
    {
      id: "343121-343146",
      type: "link",
      id1: "343121",
      id2: "343146",
      d: {
        count: 46,
      },
      a2: true,
    },
    {
      id: "343121-343120",
      type: "link",
      id1: "343121",
      id2: "343120",
      d: {
        count: 43,
      },
      a2: true,
    },
    {
      id: "343121-343253",
      type: "link",
      id1: "343121",
      id2: "343253",
      d: {
        count: 39,
      },
      a2: true,
    },
    {
      id: "343121-343241",
      type: "link",
      id1: "343121",
      id2: "343241",
      d: {
        count: 40,
      },
      a2: true,
    },
    {
      id: "343121-343130",
      type: "link",
      id1: "343121",
      id2: "343130",
      d: {
        count: 60,
      },
      a2: true,
    },
    {
      id: "343121-343112",
      type: "link",
      id1: "343121",
      id2: "343112",
      d: {
        count: 50,
      },
      a2: true,
    },
    {
      id: "343241-343146",
      type: "link",
      id1: "343241",
      id2: "343146",
      d: {
        count: 39,
      },
      a2: true,
    },
    {
      id: "343134-343150",
      type: "link",
      id1: "343134",
      id2: "343150",
      d: {
        count: 29,
      },
      a2: true,
    },
    {
      id: "343119-343263",
      type: "link",
      id1: "343119",
      id2: "343263",
      d: {
        count: 54,
      },
      a2: true,
    },
    {
      id: "343119-343126",
      type: "link",
      id1: "343119",
      id2: "343126",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343129",
      type: "link",
      id1: "343134",
      id2: "343129",
      d: {
        count: 21,
      },
      a2: true,
    },
    {
      id: "343134-343236",
      type: "link",
      id1: "343134",
      id2: "343236",
      d: {
        count: 22,
      },
      a2: true,
    },
    {
      id: "343134-343263",
      type: "link",
      id1: "343134",
      id2: "343263",
      d: {
        count: 31,
      },
      a2: true,
    },
    {
      id: "343134-343258",
      type: "link",
      id1: "343134",
      id2: "343258",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343250",
      type: "link",
      id1: "343134",
      id2: "343250",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343249",
      type: "link",
      id1: "343134",
      id2: "343249",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343238",
      type: "link",
      id1: "343134",
      id2: "343238",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343231",
      type: "link",
      id1: "343134",
      id2: "343231",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343119",
      type: "link",
      id1: "343134",
      id2: "343119",
      d: {
        count: 26,
      },
      a2: true,
    },
    {
      id: "343134-343221",
      type: "link",
      id1: "343134",
      id2: "343221",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343201",
      type: "link",
      id1: "343134",
      id2: "343201",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343192",
      type: "link",
      id1: "343134",
      id2: "343192",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343191",
      type: "link",
      id1: "343134",
      id2: "343191",
      d: {
        count: 29,
      },
      a2: true,
    },
    {
      id: "343134-343144",
      type: "link",
      id1: "343134",
      id2: "343144",
      d: {
        count: 29,
      },
      a2: true,
    },
    {
      id: "343134-343184",
      type: "link",
      id1: "343134",
      id2: "343184",
      d: {
        count: 29,
      },
      a2: true,
    },
    {
      id: "343134-343126",
      type: "link",
      id1: "343134",
      id2: "343126",
      d: {
        count: 29,
      },
      a2: true,
    },
    {
      id: "343134-343157",
      type: "link",
      id1: "343134",
      id2: "343157",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343154",
      type: "link",
      id1: "343134",
      id2: "343154",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343132",
      type: "link",
      id1: "343134",
      id2: "343132",
      d: {
        count: 29,
      },
      a2: true,
    },
    {
      id: "343134-343143",
      type: "link",
      id1: "343134",
      id2: "343143",
      d: {
        count: 29,
      },
      a2: true,
    },
    {
      id: "343134-343142",
      type: "link",
      id1: "343134",
      id2: "343142",
      d: {
        count: 30,
      },
      a2: true,
    },
    {
      id: "343134-343131",
      type: "link",
      id1: "343134",
      id2: "343131",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343133",
      type: "link",
      id1: "343134",
      id2: "343133",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343134-343128",
      type: "link",
      id1: "343134",
      id2: "343128",
      d: {
        count: 30,
      },
      a2: true,
    },
    {
      id: "343134-343125",
      type: "link",
      id1: "343134",
      id2: "343125",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343230-343177",
      type: "link",
      id1: "343230",
      id2: "343177",
      d: {
        count: 24,
      },
      a2: true,
    },
    {
      id: "343222-343265",
      type: "link",
      id1: "343222",
      id2: "343265",
      d: {
        count: 26,
      },
      a2: true,
    },
    {
      id: "343222-343124",
      type: "link",
      id1: "343222",
      id2: "343124",
      d: {
        count: 100,
      },
      a2: true,
    },
    {
      id: "343222-343243",
      type: "link",
      id1: "343222",
      id2: "343243",
      d: {
        count: 166,
      },
      a2: true,
    },
    {
      id: "343222-343119",
      type: "link",
      id1: "343222",
      id2: "343119",
      d: {
        count: 27,
      },
      a2: true,
    },
    {
      id: "343222-343230",
      type: "link",
      id1: "343222",
      id2: "343230",
      d: {
        count: 22,
      },
      a2: true,
    },
    {
      id: "343222-343218",
      type: "link",
      id1: "343222",
      id2: "343218",
      d: {
        count: 34,
      },
      a2: true,
    },
    {
      id: "343222-343215",
      type: "link",
      id1: "343222",
      id2: "343215",
      d: {
        count: 32,
      },
      a2: true,
    },
    {
      id: "343222-343206",
      type: "link",
      id1: "343222",
      id2: "343206",
      d: {
        count: 25,
      },
      a2: true,
    },
    {
      id: "343222-343226",
      type: "link",
      id1: "343222",
      id2: "343226",
      d: {
        count: 31,
      },
      a2: true,
    },
    {
      id: "343222-343193",
      type: "link",
      id1: "343222",
      id2: "343193",
      d: {
        count: 55,
      },
      a2: true,
    },
    {
      id: "343222-343183",
      type: "link",
      id1: "343222",
      id2: "343183",
      d: {
        count: 109,
      },
      a2: true,
    },
    {
      id: "343222-343176",
      type: "link",
      id1: "343222",
      id2: "343176",
      d: {
        count: 29,
      },
      a2: true,
    },
    {
      id: "343222-343145",
      type: "link",
      id1: "343222",
      id2: "343145",
      d: {
        count: 100,
      },
      a2: true,
    },
    {
      id: "343222-343123",
      type: "link",
      id1: "343222",
      id2: "343123",
      d: {
        count: 129,
      },
      a2: true,
    },
    {
      id: "343130-343169",
      type: "link",
      id1: "343130",
      id2: "343169",
      d: {
        count: 28,
      },
      a2: true,
    },
    {
      id: "343130-343112",
      type: "link",
      id1: "343130",
      id2: "343112",
      d: {
        count: 113,
      },
      a2: true,
    },
    {
      id: "343130-343148",
      type: "link",
      id1: "343130",
      id2: "343148",
      d: {
        count: 32,
      },
      a2: true,
    },
    {
      id: "343130-343119",
      type: "link",
      id1: "343130",
      id2: "343119",
      d: {
        count: 41,
      },
      a2: true,
    },
    {
      id: "343130-343121",
      type: "link",
      id1: "343130",
      id2: "343121",
      d: {
        count: 37,
      },
      a2: true,
    },
    {
      id: "343130-343124",
      type: "link",
      id1: "343130",
      id2: "343124",
      d: {
        count: 67,
      },
      a2: true,
    },
    {
      id: "343130-343140",
      type: "link",
      id1: "343130",
      id2: "343140",
      d: {
        count: 27,
      },
      a2: true,
    },
    {
      id: "343130-343260",
      type: "link",
      id1: "343130",
      id2: "343260",
      d: {
        count: 27,
      },
      a2: true,
    },
    {
      id: "343130-343141",
      type: "link",
      id1: "343130",
      id2: "343141",
      d: {
        count: 80,
      },
      a2: true,
    },
    {
      id: "343130-343266",
      type: "link",
      id1: "343130",
      id2: "343266",
      d: {
        count: 23,
      },
      a2: true,
    },
    {
      id: "343130-343203",
      type: "link",
      id1: "343130",
      id2: "343203",
      d: {
        count: 34,
      },
      a2: true,
    },
    {
      id: "343130-343155",
      type: "link",
      id1: "343130",
      id2: "343155",
      d: {
        count: 21,
      },
      a2: true,
    },
    {
      id: "343112-343130",
      type: "link",
      id1: "343112",
      id2: "343130",
      d: {
        count: 91,
      },
      a2: true,
    },
    {
      id: "343148-343251",
      type: "link",
      id1: "343148",
      id2: "343251",
      d: {
        count: 21,
      },
      a2: true,
    },
    {
      id: "343188-343112",
      type: "link",
      id1: "343188",
      id2: "343112",
      d: {
        count: 21,
      },
      a2: true,
    },
    {
      id: "343188-343254",
      type: "link",
      id1: "343188",
      id2: "343254",
      d: {
        count: 88,
      },
      a2: true,
    },
    {
      id: "343188-343247",
      type: "link",
      id1: "343188",
      id2: "343247",
      d: {
        count: 71,
      },
      a2: true,
    },
    {
      id: "343188-343233",
      type: "link",
      id1: "343188",
      id2: "343233",
      d: {
        count: 76,
      },
      a2: true,
    },
    {
      id: "343188-343198",
      type: "link",
      id1: "343188",
      id2: "343198",
      d: {
        count: 80,
      },
      a2: true,
    },
    {
      id: "343188-343186",
      type: "link",
      id1: "343188",
      id2: "343186",
      d: {
        count: 84,
      },
      a2: true,
    },
    {
      id: "343188-343164",
      type: "link",
      id1: "343188",
      id2: "343164",
      d: {
        count: 74,
      },
      a2: true,
    },
    {
      id: "343188-343156",
      type: "link",
      id1: "343188",
      id2: "343156",
      d: {
        count: 64,
      },
      a2: true,
    },
    {
      id: "343188-343111",
      type: "link",
      id1: "343188",
      id2: "343111",
      d: {
        count: 85,
      },
      a2: true,
    },
    {
      id: "343119-343112",
      type: "link",
      id1: "343119",
      id2: "343112",
      d: {
        count: 62,
      },
      a2: true,
    },
    {
      id: "343112-343119",
      type: "link",
      id1: "343112",
      id2: "343119",
      d: {
        count: 87,
      },
      a2: true,
    },
    {
      id: "343112-343148",
      type: "link",
      id1: "343112",
      id2: "343148",
      d: {
        count: 43,
      },
      a2: true,
    },
    {
      id: "343114-343148",
      type: "link",
      id1: "343114",
      id2: "343148",
      d: {
        count: 26,
      },
      a2: true,
    },
    {
      id: "343119-343130",
      type: "link",
      id1: "343119",
      id2: "343130",
      d: {
        count: 44,
      },
      a2: true,
    },
    {
      id: "343119-343121",
      type: "link",
      id1: "343119",
      id2: "343121",
      d: {
        count: 33,
      },
      a2: true,
    },
    {
      id: "343112-343121",
      type: "link",
      id1: "343112",
      id2: "343121",
      d: {
        count: 69,
      },
      a2: true,
    },
    {
      id: "343112-343141",
      type: "link",
      id1: "343112",
      id2: "343141",
      d: {
        count: 36,
      },
      a2: true,
    },
    {
      id: "343141-343130",
      type: "link",
      id1: "343141",
      id2: "343130",
      d: {
        count: 170,
      },
      a2: true,
    },
    {
      id: "343141-343112",
      type: "link",
      id1: "343141",
      id2: "343112",
      d: {
        count: 31,
      },
      a2: true,
    },
    {
      id: "343203-343130",
      type: "link",
      id1: "343203",
      id2: "343130",
      d: {
        count: 50,
      },
      a2: true,
    },
    {
      id: "343112-343140",
      type: "link",
      id1: "343112",
      id2: "343140",
      d: {
        count: 30,
      },
      a2: true,
    },
    {
      id: "343112-343129",
      type: "link",
      id1: "343112",
      id2: "343129",
      d: {
        count: 23,
      },
      a2: true,

// ...truncated 4062 lines
<!doctype html>
<html lang="en" style="background-color: #2d383f">
  <head>
    <meta charset="utf-8" />
    <title>Social Network Analysis</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" type="text/css" href="font-awesome/css/font-awesome.css" />
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div id="klchart" class="klchart">
      <div class="controloverlay">
        <ul>
          <li>
            <a id="home" rel="tooltip" title="Home"><i class="fa fa-home"></i></a>
          </li>
          <li>
            <a id="layout" rel="tooltip" title="Layout chart"><i class="fa fa-random"></i></a>
          </li>
          <li>
            <a id="changeMode" rel="tooltip" title="Drag mode"
              ><i class="fa fa-arrows-alt" id="iconMode"></i
            ></a>
          </li>
          <li>
            <a id="zoomIn" rel="tooltip" title="Zoom in"><i class="fa fa-plus-square"></i></a>
          </li>
          <li>
            <a id="zoomOut" rel="tooltip" title="Zoom out"><i class="fa fa-minus-square"></i></a>
          </li>
        </ul>
      </div>
    </div>
    <script type="module" src="./code.js"></script>
  </body>
</html>
#miniContainer {
  background-color: #fff;
  margin: 10px;
}
#miniContainer canvas {
  margin: 0 auto;
}
#minikl {
  height: 232px;
}
.controloverlay {
  position: absolute;
  left: 12px;
  top: 10px;
  padding: 0;
  margin: 0;
  font-size: 28px;
  z-index: 9001;

  background-color: rgba(250, 250, 250, 0.8);

  border: solid 1px #ededed;
  box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
}

.controloverlay ul {
  list-style-type: none;
  margin: 0;
  padding: 6px;
}

.controloverlay li {
  margin-bottom: 3px;
  text-align: center;
  font-size: 24px;
}

.controloverlay li:last-child {
  margin-bottom: 0;
}

.controloverlay a i {
  color: #2d383f;
  text-decoration: none;
  cursor: pointer;
}

.btn-row {
  overflow: auto;
  display: flex;
  justify-content: space-between;
  gap: 5px;
}
.size-btn button.btn,
.size-btn button.btn.active {
  min-width: 80px;
  margin-top: 4px;
}

.btn-group.size-btn {
  width: 100%;
  display: flex;
  justify-content: end;
  flex-wrap: wrap;
  gap: 5px;
}

#analysis-control {
  margin-top: 10px;
  width: 100%;
}

@media (max-width: 1023px) {
  .btn-group button.size {
    width: calc((100% / 2) + 2px);
  }
}

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.