Search

Impact Analysis

Layouts

Reveal the dependencies and significance of nodes.

Impact Analysis
View live example →

In most networks it’s important to know which nodes have the greatest impact, and consider what the effects would be if they failed.

In the Impact view, all nodes are sized based on how many nodes depend on them. In the Alerts view, only nodes with warnings or errors are sized by their dependencies.

By interacting with the chart, you can focus on a single node and examine its dependencies.

Styling in KeyLines can also improve the flow of the chart. For example, gradients on links matching the node colours on each end can smooth the transitions between areas on the graph and curved links applied to sequential layout enhance the directionality of links.

Key functions used:

import KeyLines from "keylines";
import {
  data,
  fontIcons,
  errorColour,
  warningColour,
  dimmedColour,
  bgColour,
  annotationsState,
} from "./data.js";

// declaring global vars
let chart;
let impactAlertsSelection = document.querySelector(
  'input[name="dependencyView"]:checked',
).value;
let showNodes = null;
let selectedNodeId = null;
const squareNodeText = {};
const rectangleNodeText = {};
const initialNodeStyles = [];
const alertsItems = [];
let hoveredGlyphId = null;
let hoveredAnnotationGlyphId = null;

const graph = KeyLines.getGraphEngine();
graph.load(data);

const impactAlertsRadios = Array.from(
  document.querySelectorAll('input[name="dependencyView"]'),
);
const alertsList = document.getElementById("alerts");
const resetButton = document.getElementById("reset");

function getScaleFactor(numOfDeps) {
  return numOfDeps ** (1 / 3);
}

function highlightErrorsAndWarnings() {
  const colourItems = [];
  const warnings = [];
  let errors = [];

  chart.each({ type: "node" }, (item) => {
    if (item.d.logLevel === "error") {
      errors.push(item.id);
      let neighbours = graph.neighbours(item.id, {
        direction: "from",
        hops: 5,
      }).nodes;
      errors = errors.concat(neighbours);
    } else if (item.d.logLevel !== "warning") {
      colourItems.push({ id: item.id, c: dimmedColour });
    }
  });
  chart.each({ type: "node" }, (item) => {
    if (item.d.logLevel === "warning") {
      warnings.push(item.id);
      let neighbours = graph.neighbours(item.id, {
        direction: "from",
        hops: 5,
      }).nodes;
      neighbours.forEach((id) => {
        if (!errors.includes(id)) {
          warnings.push(id);
        }
      });
    }
  });
  errors.forEach((id) => colourItems.push({ id, c: errorColour }));
  warnings.forEach((id) => colourItems.push({ id, c: warningColour }));

  chart.each({ type: "link" }, (item) => {
    if (errors.includes(item.id2)) {
      colourItems.push({ id: item.id, c: errorColour, c2: errorColour });
    } else if (warnings.includes(item.id2)) {
      colourItems.push({ id: item.id, c: warningColour, c2: warningColour });
    } else {
      colourItems.push({ id: item.id, c: dimmedColour, c2: dimmedColour });
    }
  });
  chart.setProperties(colourItems);
  applyAnnotationStateUpdateToChart();
}

function applyAnnotationStateUpdateToChart() {
  const annotations = [];
  for (const [_, value] of Object.entries(annotationsState)) {
    if (value.hidden) continue;
    annotations.push(value.annotation);
  }
  chart.merge(annotations);
}

function removeAnnotationFromChart(id) {
  chart.removeItem(id);
}

function constructTable() {
  const tableBody = document.querySelector(".alertsTableBody");
  alertsItems.forEach((item) => {
    const label = document.createElement("td");
    const labelText = document.createTextNode(item.label);
    label.appendChild(labelText);

    const type = document.createElement("td");
    const typeText = document.createTextNode(
      item.type.charAt(0).toUpperCase() + item.type.slice(1),
    );
    type.appendChild(typeText);

    const description = document.createElement("td");
    const descriptionText = document.createTextNode(item.description);
    description.appendChild(descriptionText);

    const row = document.createElement("tr");
    row.id = item.nodeId;
    row.setAttribute("name", "alertRow");
    row.appendChild(label);
    row.appendChild(type);
    row.appendChild(description);
    row.onclick = function () {
      setSelection({ id: item.nodeId });
    };

    tableBody.appendChild(row);
  });
}

function clearTableStyling() {
  const alertsTableRows = document.querySelectorAll('tr[name="alertRow"]');
  alertsTableRows.forEach((row) => {
    row.classList.remove("selected-row");
  });
}

function showHideAlertsList() {
  impactAlertsSelection = document.querySelector(
    'input[name="dependencyView"]:checked',
  ).value;

  if (impactAlertsSelection === "impact") {
    chart.each({ type: "annotation" }, (annotation) => {
      chart.removeItem(annotation.id);
    });
    chart.setProperties(initialNodeStyles);
    alertsList.classList.add("hidden");
  } else {
    highlightErrorsAndWarnings();
    alertsList.classList.remove("hidden");
  }
}

async function applyFilters() {
  chart.lock(true);
  const result = await chart.filter(
    (item) => {
      if (item.type === "node" && showNodes) {
        return showNodes[item.id] !== undefined;
      }
      return true;
    },
    { time: 300 },
  );
  chart.lock(false);
  return result;
}

async function doLayout() {
  const layoutOptions = {
    consistent: true,
    mode: "adaptive",
  };
  let layoutName;
  // A node needs to be selected for the selected layout to be run
  if (selectedNodeId) {
    // Set the top node to be the selected one
    layoutName = "sequential";

    // Increase the space between levels for gentler curves in the links
    // but reduce the space between nodes within the same level
    layoutOptions.stretch = 2;
    layoutOptions.tightness = 8;
    layoutOptions.orientation = "right";
    layoutOptions.linkShape = "curved";
  } else {
    layoutName = "organic";
    layoutOptions.tightness = 6;
  }
  await chart.layout(layoutName, layoutOptions);
}

function hoverReaction({ id, subItem }) {
  const item = chart.getItem(id);
  const isAnnotation = id && id.includes("annotation");
  const changes = [];

  // Clear any existing hover styling
  if (hoveredGlyphId) {
    const node = chart.getItem(hoveredGlyphId);
    chart.setProperties({
      id: hoveredGlyphId,
      g: [
        {
          ...node.g[0],
          border: {
            width: 1.5,
            colour: bgColour,
          },
        },
      ],
    });
    hoveredGlyphId = null;
  }

  if (hoveredAnnotationGlyphId) {
    const annotation = chart.getItem(hoveredAnnotationGlyphId);
    if (annotation) {
      changes.push({
        id: hoveredAnnotationGlyphId,
        g: [
          annotation.g[0],
          {
            ...annotation.g[1],
            border: {
              width: 1.5,
              colour: bgColour,
            },
          },
        ],
      });
    }
    hoveredAnnotationGlyphId = null;
  }

  // Add any new glyph hover styling
  if (subItem.type === "glyph" && !isAnnotation) {
    hoveredGlyphId = id;
    chart.setProperties({
      id,
      g: [
        {
          ...item.g[0],
          border: {
            width: 1.5,
            colour: "white",
          },
        },
      ],
    });
  }

  if (isAnnotation && subItem.type === "glyph" && subItem.index === 1) {
    chart.setProperties({
      id,
      g: [
        item.g[0],
        {
          ...item.g[1],
          border: {
            width: 1.5,
            colour: "white",
          },
        },
      ],
    });
    hoveredAnnotationGlyphId = id;
    return;
  }

  // Handle node + node's tree hover styling and clearing
  const tree =
    item && item.type === "node"
      ? graph.distances(id, { direction: "from" })
      : [];
  chart.each({ type: "node" }, (node) => {
    // don't overwrite selected node styling
    if (node.id !== selectedNodeId) {
      const highlight = node.id in tree && !hoveredGlyphId;
      const borderWidth = node.w === 300 ? node.w / 40 : node.w / 20;
      changes.push({
        id: node.id,
        b: highlight ? "white" : undefined,
        bw: highlight ? borderWidth : 0,
      });
    }
  });

  chart.setProperties(changes);
}

async function setSelection({ id, subItem }) {
  const isAnnotation = id && id.includes("annotation");
  if (id && subItem?.type === "glyph" && !isAnnotation) {
    annotationsState[id].hidden = false;
    applyAnnotationStateUpdateToChart();
    chart.setProperties({ id, g: [] });
    return;
  }

  if (isAnnotation && subItem?.type === "glyph" && subItem.index === 1) {
    const subjectId = chart.getItem(id).subject;
    annotationsState[subjectId].hidden = true;
    removeAnnotationFromChart(id);
    chart.setProperties({
      id: subjectId,
      g: [annotationsState[subjectId].glyphStyling],
    });
    return;
  }

  if (isAnnotation) {
    // annotations are not selectable
    return;
  }

  clearTableStyling();
  // Update the selected node with the one passed in
  if (id === null) {
    const prevSelectedNodeId = selectedNodeId;
    selectedNodeId = null;
    resetButton.disabled = true;
    setNodeStyling("square");

    // click on chart background or network button - reshow hidden items
    showNodes = null;
    if (prevSelectedNodeId) {
      const result = await applyFilters();
      // Only layout if any nodes were shown or hidden
      if (result.shown.nodes.length > 0 || result.hidden.nodes.length > 0) {
        doLayout();
      }
    }
  } else {
    selectedNodeId = id;
    const item = chart.getItem(id);
    chart.selection(id);
    if (item && item.type === "node") {
      resetButton.disabled = false;
      const alertTableRow = document.getElementById(id);
      if (alertTableRow) {
        alertTableRow.classList.add("selected-row");
      }

      showNodes = {
        ...graph.distances(id, { direction: "to" }),
        ...graph.distances(id, { direction: "from" }),
      };
      if (item.d.dependencies.length) {
        item.d.dependencies.forEach((id) => (showNodes[id] = 1));
      }
      await applyFilters();
      setNodeStyling("rectangle");
      doLayout();
    }
  }
}

function storeNodeStyling() {
  const fontColour = bgColour;
  const backgroundColour = "rgba(0,0,0,0)";
  // set and store label styling for square and rectanglular nodes
  data.items.forEach((item) => {
    if (item.type === "node") {
      squareNodeText[item.id] = {
        t: [
          {
            fi: {
              t: fontIcons[item.d.type],
            },
            position: {
              vertical: "top",
            },
            fc: fontColour,
            fbc: backgroundColour,
          },
          {
            t: item.t.replace("-", "\n"),
            position: {
              vertical: "middle",
            },
            fc: fontColour,
            fbc: backgroundColour,
            fb: true,
          },
        ],
      };
      rectangleNodeText[item.id] = {
        fi: null,
        t: [
          {
            fi: {
              t: fontIcons[item.d.type],
            },
            position: {
              vertical: "middle",
              horizontal: "left",
            },
            fc: fontColour,
            fbc: backgroundColour,
            fs: 30,
            padding: {
              left: 13,
            },
          },
          {
            t: item.t,
            fc: fontColour,
            fbc: backgroundColour,
            position: {
              vertical: "top",
              horizontal: 45,
            },
            padding: {
              top: item.d.logMessage ? 19 : 24,
            },
            fb: true,
          },
          {
            fc: fontColour,
            fbc: backgroundColour,
            t: item.d.logMessage,
            fs: 9,
          },
        ],
      };
      initialNodeStyles.push({ id: item.id, c: item.c });
    } else if (item.type === "link") {
      initialNodeStyles.push({ id: item.id, c: item.c, c2: item.c2 });
    }
  });
}

function setNodeStyling(shape) {
  const isRectangle = shape === "rectangle";
  const styles = [];

  chart.each({ type: "node" }, (node) => {
    const id = node.id;
    const logLevel = node.d.logLevel;
    const b = id === selectedNodeId ? "white" : node.c;
    const bw = 6;
    let scaleFactor = 1;
    let w = 40;
    let h = 50;
    let borderRadius = 10;
    let t;
    let g = [];

    const dependencies = Object.keys(
      graph.distances(id, { direction: "from" }),
    ).length;

    if (impactAlertsSelection === "impact" || logLevel) {
      if (dependencies > 0) {
        scaleFactor = getScaleFactor(dependencies);
      }
    }

    if (isRectangle) {
      t = [...rectangleNodeText[node.id].t];
      w = 300;
      h = 60;

      t[0].fs = 48;
      t[0].padding = { right: 30, left: 15 };
      t[1].margin = { left: 14 };
      if (t[2]) {
        t[2].margin = { left: 14 };
      }
    } else {
      t = [...squareNodeText[node.id].t];
      w = h *= scaleFactor;
      borderRadius *= scaleFactor;
      t[0].fs = 22 * scaleFactor;
      t[0].padding = { top: 7 * scaleFactor };
      t[1].fs = 5 * scaleFactor;
      t[1].padding = { top: 17 * scaleFactor };
    }

    if (impactAlertsSelection === "alerts" && logLevel) {
      const icon = fontIcons.exclamationTriangle;
      const iconSize = isRectangle ? 2 : 1.2 * scaleFactor;
      const glyphStyling = {
        c: "white",
        p: "ne",
        fi: {
          t: icon,
          c: bgColour,
        },
        e: iconSize,
        border: {
          width: 1.5,
          colour: bgColour,
        },
      };

      if (annotationsState[id].hidden) {
        g.push(glyphStyling);
      }
      annotationsState[id].glyphStyling = glyphStyling;
    }

    styles.push({ id, t, w, h, b, bw, borderRadius, g });
  });
  chart.setProperties(styles);
}

function initialiseInteractions() {
  // On hover, foreground connected nodes
  chart.on("hover", hoverReaction);
  // On click, focus on the selected node
  chart.on("click", setSelection);
  // Hide/show alerts list based on radio selection
  impactAlertsRadios.forEach((radio) => {
    radio.addEventListener("click", () => {
      showHideAlertsList();
      const isAnnotation =
        selectedNodeId && selectedNodeId.includes("annotation");
      setSelection({ id: isAnnotation ? null : selectedNodeId });
      doLayout();
    });
  });
  resetButton.addEventListener("click", () => setSelection({ id: null }));
}

function initialiseTableData() {
  data.items.forEach((item) => {
    if (item.type == "node" && item.d.logLevel) {
      alertsItems.push({
        label: item.t,
        type: item.d.logLevel,
        description: item.d.logMessage,
        nodeId: item.id,
      });
    }
  });
}

async function startKeyLines() {
  const imageAlignment = {
    "fas fa-server": { e: 0.75 },
    "fas fa-desktop": { e: 0.75 },
    "fas fa-exclamation-triangle": { dy: -11 },
  };

  const options = {
    arrows: "large",
    backColour: bgColour,
    navigation: false,
    overview: false,
    drag: {
      links: false,
    },
    hover: 0,
    handMode: true,
    minZoom: 0.02,
    iconFontFamily: "Font Awesome 5 Free",
    imageAlignment,
    // Disable chart selection styles
    selectedNode: {},
    selectedLink: {},
  };

  chart = await KeyLines.create({
    container: "klchart",
    options,
  });

  chart.load(data);
  initialiseInteractions();
  initialiseTableData();
  constructTable();
  storeNodeStyling();
  setNodeStyling("square");
  doLayout();
}

function loadKeyLines() {
  document.fonts.load('24px "Font Awesome 5 Free"').then(startKeyLines);
}

window.addEventListener("DOMContentLoaded", loadKeyLines);
import KeyLines from "keylines";
export const fontIcons = {
  switch: "fas fa-exchange-alt",
  server: "fas fa-server ",
  vhost: "fas fa-server",
  service: "fas fa-desktop",
  exclamationTriangle: "fas fa-exclamation-triangle",
  eyeSlash: "fas fa-eye-slash",
};

export const errorColour = "#FF6052";
export const warningColour = "rgba(254,221,0,1)";
export const dimmedColour = "#969696";
export const bgColour = "#212121";

const virtualHostColour = "#75D8FF";
const serverColour = "#2CE0C2";
const serviceColour = "#4598F7";
const switchColour = "#56F580";

const errorColourLight = "rgb(255, 140, 130)";
const warningColourLight = "rgb(236, 223, 138)";

export const data = {
  type: "LinkChart",
  items: [
    {
      type: "link",
      id: "svr-a-plg-c",
      id1: "svr-a",
      id2: "plg-c",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-b-plg-d",
      id1: "svr-b",
      id2: "plg-d",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-c-plg-b",
      id1: "svr-c",
      id2: "plg-b",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-c-plg-d",
      id1: "svr-c",
      id2: "plg-d",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-d-plg-c",
      id1: "svr-d",
      id2: "plg-c",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-d-plg-b",
      id1: "svr-d",
      id2: "plg-b",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-e-plg-c",
      id1: "svr-e",
      id2: "plg-c",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-f-plg-d",
      id1: "svr-f",
      id2: "plg-d",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-g-plg-a",
      id1: "svr-g",
      id2: "plg-a",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-h-plg-c",
      id1: "svr-h",
      id2: "plg-c",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-i-plg-a",
      id1: "svr-i",
      id2: "plg-a",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-j-plg-a",
      id1: "svr-j",
      id2: "plg-a",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-k-plg-a",
      id1: "svr-k",
      id2: "plg-a",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "svr-k-plg-c",
      id1: "svr-k",
      id2: "plg-c",
      a1: true,
      w: 4,
      off: 0,
      c: serverColour,
      c2: switchColour,
    },
    {
      type: "link",
      id: "vh-a-svr-e",
      id1: "vh-a",
      id2: "svr-e",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-a-svr-b",
      id1: "vh-a",
      id2: "svr-b",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-a-svr-f",
      id1: "vh-a",
      id2: "svr-f",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-b-svr-i",
      id1: "vh-b",
      id2: "svr-i",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-c-svr-c",
      id1: "vh-c",
      id2: "svr-c",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-c-svr-j",
      id1: "vh-c",
      id2: "svr-j",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-d-svr-k",
      id1: "vh-d",
      id2: "svr-k",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-d-svr-a",
      id1: "vh-d",
      id2: "svr-a",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-e-svr-i",
      id1: "vh-e",
      id2: "svr-i",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-e-svr-d",
      id1: "vh-e",
      id2: "svr-d",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-f-svr-c",
      id1: "vh-f",
      id2: "svr-c",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-f-svr-b",
      id1: "vh-f",
      id2: "svr-b",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-g-svr-b",
      id1: "vh-g",
      id2: "svr-b",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-h-svr-d",
      id1: "vh-h",
      id2: "svr-d",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-i-svr-c",
      id1: "vh-i",
      id2: "svr-c",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-j-svr-e",
      id1: "vh-j",
      id2: "svr-e",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-k-svr-j",
      id1: "vh-k",
      id2: "svr-j",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-l-svr-e",
      id1: "vh-l",
      id2: "svr-e",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-l-svr-f",
      id1: "vh-l",
      id2: "svr-f",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-l-svr-h",
      id1: "vh-l",
      id2: "svr-h",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-m-svr-j",
      id1: "vh-m",
      id2: "svr-j",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-n-svr-h",
      id1: "vh-n",
      id2: "svr-h",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-n-svr-e",
      id1: "vh-n",
      id2: "svr-e",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-o-svr-b",
      id1: "vh-o",
      id2: "svr-b",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-p-svr-e",
      id1: "vh-p",
      id2: "svr-e",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-p-svr-a",
      id1: "vh-p",
      id2: "svr-a",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-q-svr-b",
      id1: "vh-q",
      id2: "svr-b",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-q-svr-g",
      id1: "vh-q",
      id2: "svr-g",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-r-svr-c",
      id1: "vh-r",
      id2: "svr-c",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-r-svr-k",
      id1: "vh-r",
      id2: "svr-k",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-s-svr-f",
      id1: "vh-s",
      id2: "svr-f",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-s-svr-k",
      id1: "vh-s",
      id2: "svr-k",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-t-svr-g",
      id1: "vh-t",
      id2: "svr-g",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-u-svr-f",
      id1: "vh-u",
      id2: "svr-f",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-u-svr-e",
      id1: "vh-u",
      id2: "svr-e",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-v-svr-h",
      id1: "vh-v",
      id2: "svr-h",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-w-svr-i",
      id1: "vh-w",
      id2: "svr-i",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-w-svr-c",
      id1: "vh-w",
      id2: "svr-c",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-x-svr-d",
      id1: "vh-x",
      id2: "svr-d",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-y-svr-j",
      id1: "vh-y",
      id2: "svr-j",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-y-svr-b",
      id1: "vh-y",
      id2: "svr-b",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-z-svr-k",
      id1: "vh-z",
      id2: "svr-k",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-z-svr-e",
      id1: "vh-z",
      id2: "svr-e",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-ab-svr-c",
      id1: "vh-ab",
      id2: "svr-c",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-ab-svr-j",
      id1: "vh-ab",
      id2: "svr-j",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-ac-svr-f",
      id1: "vh-ac",
      id2: "svr-f",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-ac-svr-d",
      id1: "vh-ac",
      id2: "svr-d",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-ad-svr-j",
      id1: "vh-ad",
      id2: "svr-j",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-ae-svr-h",
      id1: "vh-ae",
      id2: "svr-h",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-af-svr-c",
      id1: "vh-af",
      id2: "svr-c",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-af-svr-i",
      id1: "vh-af",
      id2: "svr-i",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-ag-svr-f",
      id1: "vh-ag",
      id2: "svr-f",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "vh-ah-svr-i",
      id1: "vh-ah",
      id2: "svr-i",
      a1: true,
      w: 4,
      off: 0,
      c: virtualHostColour,
      c2: serverColour,
    },
    {
      type: "link",
      id: "srvc-a-vh-q",
      id1: "srvc-a",
      id2: "vh-q",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-b-vh-j",
      id1: "srvc-b",
      id2: "vh-j",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-c-vh-f",
      id1: "srvc-c",
      id2: "vh-f",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-c-vh-m",
      id1: "srvc-c",
      id2: "vh-m",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-d-vh-c",
      id1: "srvc-d",
      id2: "vh-c",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-e-vh-af",
      id1: "srvc-e",
      id2: "vh-af",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-e-vh-v",
      id1: "srvc-e",
      id2: "vh-v",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-f-vh-h",
      id1: "srvc-f",
      id2: "vh-h",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-f-vh-m",
      id1: "srvc-f",
      id2: "vh-m",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-g-vh-k",
      id1: "srvc-g",
      id2: "vh-k",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-g-vh-d",
      id1: "srvc-g",
      id2: "vh-d",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-h-vh-d",
      id1: "srvc-h",
      id2: "vh-d",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-i-vh-d",
      id1: "srvc-i",
      id2: "vh-d",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-i-vh-i",
      id1: "srvc-i",
      id2: "vh-i",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-j-vh-c",
      id1: "srvc-j",
      id2: "vh-c",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-j-vh-j",
      id1: "srvc-j",
      id2: "vh-j",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-k-vh-v",
      id1: "srvc-k",
      id2: "vh-v",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-l-vh-ab",
      id1: "srvc-l",
      id2: "vh-ab",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-m-vh-e",
      id1: "srvc-m",
      id2: "vh-e",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-n-vh-s",
      id1: "srvc-n",
      id2: "vh-s",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-n-vh-i",
      id1: "srvc-n",
      id2: "vh-i",
      a1: true,
      w: 4,
      off: 0,
      c: serviceColour,
      c2: virtualHostColour,
    },
    {
      type: "link",
      id: "srvc-o-vh-o",
      id1: "srvc-o",
      id2: "vh-o",
      a1: true,

// ...truncated 2848 lines
<!doctype html>
<html lang="en" style="background-color: #2d383f">
  <head>
    <meta charset="utf-8" />
    <title>Impact 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" href="./style.css" />
  </head>
  <body>
    <div id="klchart" class="klchart"></div>
    <script type="module" src="./code.js"></script>
  </body>
</html>
#nodeDependantWarning {
  background-color: #fcf8e3;
  padding-left: 4px;
  border-style: solid;
  border-width: 1px;
  border-radius: 2px;
}

.radio {
  padding-left: 25px;
}

table {
  border: 1px solid #969696;
  border-collapse: collapse;
  font-size: 14px;
  width: 100%;
  height: 30vh;
}

table thead:hover {
  background-color: #969696;
}

table thead tr {
  display: block;
  width: 100%;
  background-color: #969696;
}

table thead th {
  color: #212121;
  background-color: #969696;
  position: sticky;
  top: 0;
  text-align: left;
}

table tbody {
  display: block;
  overflow-y: scroll;
  height: 100%;
  background-color: white;
}

table tbody::-webkit-scrollbar {
  width: 8px;
}

table tbody::-webkit-scrollbar-thumb {
  background: #969696;
  border-radius: 5px;
}

table tbody tr {
  border-radius: 30px;
  cursor: pointer;
  display: table-row;
  transition: all 0.3s ease;
}

.selected-row,
#alerts .selected-row:hover {
  background-color: #212121;
  color: white;
}

table tr *:nth-child(1) {
  width: 130px;
}

table tr *:nth-child(2) {
  text-align: center;
  width: 100px;
}

table tr *:nth-child(3) {
  width: 170px;
}

table th,
table td {
  padding: 0.5em;
}

#reset {
  text-align: center;
  margin: 12px 0px;
  border-style: solid;
  border-width: 1px;
}

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.