Search

Combo Tightness

Combine Nodes

Control the space between items inside combo arrangements.

Combo Tightness
View live example →

Click on nodes to explore their connections and use the slider to change the tightness of arrangement inside the open combos.

This story shows a fictional IT infrastructure, showing the network connections between physical devices and highlighting security risks.

Combos can be created when you load data by defining the combine prop in your initial state.

See also

import React, { useState } from "react";
import { createRoot } from "react-dom/client";

import pickBy from "lodash/pickBy";
import values from "lodash/values";
import { Chart } from "regraph";

import data from "./data";
import { style } from "@ci/theme/rg/js/storyStyles";

import "@fortawesome/fontawesome-free/css/fontawesome.css";
import "@fortawesome/fontawesome-free/css/solid.css";

const initialTightness = 3;

function ComboTightness(props) {
  const { items } = props;

  const [state, setState] = useState({
    animate: false,
    closedCombos: {},
    tightness: initialTightness,
    combine: {
      shape: "rectangle",
      level: 2,
      properties: ["ip", "network"],
      tightness: initialTightness,
    },
    layout: {
      name: "sequential",
      linkShape: "curved",
      top: "_combonode_Central Server\n181.13.51.159",
    },
  });

  const getGlyph = (value) => {
    if (
      values(items).some((item) => {
        const { data: d } = item;
        return d && (d.network === value || d.ip === value) && d.alert;
      })
    ) {
      return [
        {
          ...style.selectMain,
          border: { color: style.colors.red3 },
          label: { text: "!", color: "#fff" },
          position: "ne",
          size: 1.1,
        },
      ];
    }
    return null;
  };

  const buildComboLabels = ({ location, locIp, ip, isNetwork, nodes }) => {
    const numDevices = Object.keys(nodes).length;

    const networkLabels = [
      {
        backgroundColor: style.colors.teal0,
        minWidth: "stretch",
        minHeight: 40,
        position: { vertical: "top", horizontal: "left" },
      },
      {
        text: location,
        position: { vertical: "top", horizontal: "left" },
        fontSize: 20,
        bold: true,
        minHeight: 40,
        padding: { left: 10 },
        backgroundColor: style.colors.transparent,
      },
      {
        text: locIp,
        position: { vertical: "top", horizontal: "center" },
        minHeight: 40,
        fontSize: 20,
        margin: "0 10",
        fontFamily: "monospace",
        backgroundColor: style.colors.transparent,
      },
      {
        text: `Devices: ${numDevices}`,
        position: { vertical: "top", horizontal: "right" },
        minHeight: 40,
        bold: true,
        fontSize: 20,
        margin: { right: 10 },
        backgroundColor: style.colors.transparent,
      },
    ];

    const nonNetworkLabels = [
      {
        backgroundColor: style.colors.teal0,
        minWidth: "stretch",
        minHeight: 40,
        position: { vertical: "top", horizontal: "left" },
      },
      {
        text: ip,
        fontFamily: "monospace",
        position: { vertical: "top", horizontal: "center" },
        minHeight: 40,
        fontSize: 20,
        backgroundColor: style.colors.transparent,
      },
    ];
    return isNetwork ? networkLabels : nonNetworkLabels;
  };

  const buildClosedComboLabels = ({ location, locIp, ip, isNetwork }) => {
    const networkLabels = [
      {
        text: location,
        position: "s",
        bold: true,
      },
      {
        text: locIp,
        fontFamily: "monospace",
      },
    ];

    const nonNetworkLabels = [
      {
        position: "s",
        text: ip,
        fontFamily: "monospace",
      },
    ];

    return isNetwork ? networkLabels : nonNetworkLabels;
  };

  const combineNodesHandler = ({ setStyle, combo, id, nodes }) => {
    const { ip, network } = combo;
    const { closedCombos } = state;
    const isNetwork = !ip;

    const [location, locIp] = isNetwork ? network.split("\n") : [];

    setStyle({
      open: !closedCombos[id],
      color: style.combo.color,
      border: isNetwork ? {} : { lineStyle: "dashed", width: 4 },
      fontIcon: {
        text: "fas fa-sitemap",
        color: style.primary1.color,
      },
      arrange: {
        name: "grid",
        tightness: isNetwork ? 5 : state.tightness,
      },
      label: buildComboLabels({ location, locIp, ip, isNetwork, nodes }),
      closedStyle: {
        color: style.colors.charcoal,
        label: buildClosedComboLabels({ location, locIp, ip, isNetwork }),
        shape: "box",
        glyphs: getGlyph(ip || network),
      },
    });
  };

  const combineLinksHandler = ({ setStyle, links }) => {
    const contents = pickBy(links, (link) => link.data.alert);
    setStyle({
      label: {},
      flow: false,
      width: 3,
      glyphs: [],
      contents,
    });
  };

  const doubleClickHandler = ({ id, itemType }) => {
    // if it is a combo, toggle its open state
    if (itemType === "combo") {
      setState((current) => {
        return {
          ...current,
          animate: true,
          combine: { ...current.combine },
          closedCombos: { ...current.closedCombos, [id]: !current.closedCombos[id] },
        };
      });
    }
  };

  const setTightness = (e) => {
    setState((current) => ({
      ...current,
      animate: false,
      tightness: parseFloat(e.target.value),
    }));
  };

  const layout = () => {
    setState((current) => ({
      ...current,
      combine: {
        ...current.combine,
        tightness: current.tightness,
      },
      layout: { ...current.layout },
    }));
  };

  return (
    <div className="story">
      <div className="options">
        <span style={{ paddingLeft: 20, paddingRight: 20 }}>Nested combo tightness</span>
        <input
          type="range"
          tabIndex="0"
          style={{ width: 200 }}
          min={0}
          max={10}
          step={1}
          value={state.tightness}
          onInput={setTightness}
          onChange={layout}
        />
        <span style={{ paddingLeft: 20 }}>{state.tightness}</span>
      </div>
      <div className="chart-wrapper">
        <Chart
          items={items(state.combine.shape)}
          combine={state.combine}
          layout={state.layout}
          options={{
            selection: {
              color: style.colors.teal0,
              labelColor: "#000",
            },
            iconFontFamily: "Font Awesome 5 Free",
            imageAlignment: {
              "fas fa-sitemap": { dy: -8, size: 0.75 },
              "fas fa-print": { size: 0.8 },
              "fas fa-laptop": { size: 0.75 },
              "fas fa-phone": { size: 0.8 },
              "fas fa-server": { size: 0.8 },
            },
          }}
          animation={{ animate: state.animate, time: 800 }}
          onCombineNodes={combineNodesHandler}
          onCombineLinks={combineLinksHandler}
          onDoubleClick={doubleClickHandler}
        />
      </div>
    </div>
  );
}

const FontReadyChart = React.lazy(() =>
  document.fonts.load("900 24px 'Font Awesome 5 Free'").then(() => ({
    default: () => <ComboTightness items={data} />,
  }))
);

export function Demo() {
  return (
    <React.Suspense fallback="">
      <FontReadyChart />
    </React.Suspense>
  );
}

const root = createRoot(document.getElementById("regraph"));
root.render(<Demo />);
import has from "lodash/has";
import mapValues from "lodash/mapValues";
import merge from "lodash/merge";
import { style } from "@ci/theme/rg/js/storyStyles";

function styleItem(item, shape) {
  const isCircle = shape === "circle";
  const glyphs = {};
  const hasAlert = item.data && item.data.alert;
  const isNode = !has(item, "id1");

  if (!isNode) {
    if (hasAlert) {
      glyphs.glyphs = [
        {
          ...style.selectMain,
          border: { color: style.colors.red3 },
          label: { ...style.secondaryLabel2, text: "!" },
          size: 1.5,
        },
      ];
    }
    return {
      ...item,
      ...glyphs,
      color: hasAlert ? style.secondary1.color : style.link.color,
      width: 4,
    };
  }

  if (hasAlert) {
    glyphs.glyphs = [
      {
        ...style.selectMain,
        border: { color: style.colors.red3 },
        label: { ...style.secondaryLabel2, text: "!" },
        position: "ne",
        size: 1.1,
      },
    ];
  }

  return merge(
    item,
    {
      size: 1.3,
      shape: isCircle ? "circle" : { width: 130, height: 80 },
      color: style.colors.charcoal,
      border: { color: null },
      label: [
        {
          ...item.label[0],
          fontFamily: "Font Awesome 5 Free",
          backgroundColor: "rgba(0, 0, 0, 0)",
          color: style.colors.teal1,
          fontSize: 42,
          position: {
            vertical: isCircle ? "middle" : 15,
          },
        },
        {
          ...item.label[1],
          backgroundColor: "rgba(0, 0, 0, 0)",
          color: isCircle ? "black" : "white",
          fontSize: 15,
          position: isCircle ? "s" : { vertical: 55 },
        },
      ],
    },
    glyphs
  );
}

function data(shape) {
  const items = {
    "140.211.152.43::192.168.247.240": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.247.240" }],
      data: { type: "win", network: "Cambridge\n140.211.152.43", ip: "192.168.247.0" },
    },
    "140.211.152.43::192.168.16.232": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.16.232" }],
      data: { type: "win", network: "Cambridge\n140.211.152.43", ip: "192.168.16.0" },
    },
    "140.211.152.43::192.168.16.194": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.16.194" }],
      data: { type: "win", network: "Cambridge\n140.211.152.43", ip: "192.168.16.0" },
    },
    "140.211.152.43::192.168.16.209": {
      label: [{ fontIcon: { text: "fas fa-phone" } }, { text: "192.168.16.209" }],
      data: { type: "win", network: "Cambridge\n140.211.152.43", ip: "192.168.16.0" },
    },
    "140.211.152.43::192.168.247.168": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.247.168" }],
      data: { type: "win", network: "Cambridge\n140.211.152.43", ip: "192.168.247.0" },
    },
    "140.211.152.43::192.168.247.162": {
      label: [{ fontIcon: { text: "fas fa-print" } }, { text: "192.168.247.162" }],
      data: { type: "printer", network: "Cambridge\n140.211.152.43", ip: "192.168.247.0" },
    },
    "140.211.152.43::192.168.247.242": {
      label: [{ fontIcon: { text: "fas fa-phone" } }, { text: "192.168.247.242" }],
      data: { type: "win", network: "Cambridge\n140.211.152.43", ip: "192.168.247.0" },
    },
    "56.35.38.237::192.168.51.101": {
      label: [{ fontIcon: { text: "fas fa-print" } }, { text: "192.168.51.101" }],
      data: { type: "printer", network: "London\n56.35.38.237", ip: "192.168.51.0" },
    },
    "56.35.38.237::192.168.51.158": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.51.158" }],
      data: { type: "mac", network: "London\n56.35.38.237", ip: "192.168.51.0" },
    },
    "56.35.38.237::192.168.51.108": {
      label: [{ fontIcon: { text: "fas fa-print" } }, { text: "192.168.51.108" }],
      data: { type: "printer", network: "London\n56.35.38.237", ip: "192.168.51.0" },
    },
    "56.35.38.237::192.168.131.80": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.131.80" }],
      data: { type: "mac", network: "London\n56.35.38.237", ip: "192.168.131.0" },
    },
    "56.35.38.237::192.168.131.57": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.131.57" }],
      data: { type: "mac", network: "London\n56.35.38.237", ip: "192.168.131.0" },
    },
    "56.35.38.237::192.168.131.11": {
      label: [{ fontIcon: { text: "fas fa-print" } }, { text: "192.168.131.11" }],
      data: { type: "printer", network: "London\n56.35.38.237", ip: "192.168.131.0" },
    },
    "56.35.38.237::192.168.51.194": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.51.194" }],
      data: {
        type: "mac",
        network: "London\n56.35.38.237",
        ip: "192.168.51.0",
        alert: "Blacklisted application",
      },
    },
    "56.35.38.237::192.168.131.120": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.131.120" }],
      data: { type: "mac", network: "London\n56.35.38.237", ip: "192.168.131.0" },
    },
    "56.35.38.237::192.168.131.151": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "192.168.131.151" }],
      data: { type: "mac", network: "London\n56.35.38.237", ip: "192.168.131.0" },
    },
    "56.35.38.237::192.168.131.167": {
      label: [{ fontIcon: { text: "fas fa-print" } }, { text: "192.168.131.167" }],
      data: { type: "printer", network: "London\n56.35.38.237", ip: "192.168.131.0" },
    },
    "134.58.218.94::10.0.71.179": {
      label: [{ fontIcon: { text: "fas fa-print" } }, { text: "10.0.71.179" }],
      data: { type: "printer", network: "Paris\n134.58.218.94", ip: "10.0.71.0" },
    },
    "134.58.218.94::10.0.203.8": {
      label: [{ fontIcon: { text: "fas fa-print" } }, { text: "10.0.203.8" }],
      data: { type: "printer", network: "Paris\n134.58.218.94", ip: "10.0.203.0" },
    },
    "134.58.218.94::10.0.203.56": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "10.0.203.56" }],
      data: { type: "win", network: "Paris\n134.58.218.94", ip: "10.0.203.0" },
    },
    "134.58.218.94::10.0.71.224": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "10.0.71.224" }],
      data: { type: "win", network: "Paris\n134.58.218.94", ip: "10.0.71.0" },
    },
    "134.58.218.94::10.0.203.249": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "10.0.203.249" }],
      data: { type: "win", network: "Paris\n134.58.218.94", ip: "10.0.203.0" },
    },
    "134.58.218.94::10.0.203.101": {
      label: [{ fontIcon: { text: "fas fa-phone" } }, { text: "10.0.203.101" }],
      data: { type: "phone", network: "Paris\n134.58.218.94", ip: "10.0.203.0" },
    },
    "134.58.218.94::10.0.71.134": {
      label: [{ fontIcon: { text: "fas fa-laptop" } }, { text: "10.0.71.134" }],
      data: { type: "win", network: "Paris\n134.58.218.94", ip: "10.0.71.0" },
    },
    "181.13.51.159::192.168.235.94": {
      label: [{ fontIcon: { text: "fas fa-server" } }, { text: "192.168.235.94" }],
      data: { type: "server", network: "Central Server\n181.13.51.159", ip: "192.168.235.0" },
    },
    "181.13.51.159::192.168.235.235": {
      label: [{ fontIcon: { text: "fas fa-server" } }, { text: "192.168.235.235" }],
      data: { type: "server", network: "Central Server\n181.13.51.159", ip: "192.168.235.0" },
    },
    "181.13.51.159::192.168.235.111": {
      label: [{ fontIcon: { text: "fas fa-server" } }, { text: "192.168.235.111" }],
      data: { type: "server", network: "Central Server\n181.13.51.159", ip: "192.168.235.0" },
    },
    "181.13.51.159::192.168.223.223": {
      label: [{ fontIcon: { text: "fas fa-server" } }, { text: "192.168.223.223" }],
      data: { type: "server", network: "Central Server\n181.13.51.159", ip: "192.168.223.0" },
    },
    "181.13.51.159::192.168.223.34": {
      label: [{ fontIcon: { text: "fas fa-server" } }, { text: "192.168.223.34" }],
      data: { type: "server", network: "Central Server\n181.13.51.159", ip: "192.168.223.0" },
    },
    "181.13.51.159::192.168.223.76": {
      label: [{ fontIcon: { text: "fas fa-server" } }, { text: "192.168.223.76" }],
      data: { type: "server", network: "Central Server\n181.13.51.159", ip: "192.168.223.0" },
    },
    "181.13.51.159::192.168.235.34": {
      label: [{ fontIcon: { text: "fas fa-server" } }, { text: "192.168.235.34" }],
      data: { type: "server", network: "Central Server\n181.13.51.159", ip: "192.168.235.0" },
    },
    "140.211.152.43::192.168.16.209-140.211.152.43::192.168.247.242": {
      flow: { velocity: 3 },
      id1: "140.211.152.43::192.168.16.209",
      id2: "140.211.152.43::192.168.247.242",
      data: { port1: 8080, port2: 80 },
    },
    "140.211.152.43::192.168.16.209-140.211.152.43::192.168.16.194": {
      flow: { velocity: 1 },
      id1: "140.211.152.43::192.168.16.194",
      id2: "140.211.152.43::192.168.16.209",
      data: { port1: 8080, port2: 80 },
    },
    "140.211.152.43::192.168.247.240-140.211.152.43::192.168.247.162": {
      flow: { velocity: 2 },
      id1: "140.211.152.43::192.168.247.240",
      id2: "140.211.152.43::192.168.247.162",
      data: { port1: 443, port2: 443 },
    },
    "140.211.152.43::192.168.247.168-140.211.152.43::192.168.16.194": {
      flow: { velocity: 1 },
      id1: "140.211.152.43::192.168.247.168",
      id2: "140.211.152.43::192.168.16.194",
      data: { port1: 80, port2: 8080 },
    },
    "140.211.152.43::192.168.247.162-140.211.152.43::192.168.247.168": {
      flow: { velocity: -1 },
      id1: "140.211.152.43::192.168.247.162",
      id2: "140.211.152.43::192.168.247.168",
      data: { port1: 8080, port2: 80 },
    },
    "140.211.152.43::192.168.247.242-140.211.152.43::192.168.247.162": {
      flow: { velocity: -1 },
      id1: "140.211.152.43::192.168.247.242",
      id2: "140.211.152.43::192.168.247.162",
      data: { port1: 20, port2: 8080 },
    },
    "56.35.38.237::192.168.51.101-56.35.38.237::192.168.131.167": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.51.101",
      id2: "56.35.38.237::192.168.131.167",
      data: { port1: 80, port2: 20 },
    },
    "56.35.38.237::192.168.51.101-56.35.38.237::192.168.51.194": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.51.101",
      id2: "56.35.38.237::192.168.51.194",
      data: { port1: 443, port2: 8080 },
    },
    "56.35.38.237::192.168.51.158-56.35.38.237::192.168.131.80": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.51.158",
      id2: "56.35.38.237::192.168.131.80",
      data: { port1: 80, port2: 20 },
    },
    "56.35.38.237::192.168.51.108-56.35.38.237::192.168.131.80": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.51.108",
      id2: "56.35.38.237::192.168.131.80",
      data: { port1: 8080, port2: 20 },
    },
    "56.35.38.237::192.168.51.108-56.35.38.237::192.168.51.108": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.51.108",
      id2: "56.35.38.237::192.168.51.108",
      data: { port1: 22, port2: 80 },
    },
    "56.35.38.237::192.168.131.80-56.35.38.237::192.168.131.120": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.131.80",
      id2: "56.35.38.237::192.168.131.120",
      data: { port1: 20, port2: 20 },
    },
    "56.35.38.237::192.168.131.80-56.35.38.237::192.168.131.167": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.131.80",
      id2: "56.35.38.237::192.168.131.167",
      data: { port1: 22, port2: 443 },
    },
    "56.35.38.237::192.168.131.80-56.35.38.237::192.168.51.158": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.131.80",
      id2: "56.35.38.237::192.168.51.158",
      data: { port1: 443, port2: 443 },
    },
    "56.35.38.237::192.168.131.57-56.35.38.237::192.168.51.158": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.131.57",
      id2: "56.35.38.237::192.168.51.158",
      data: { port1: 443, port2: 80 },
    },
    "56.35.38.237::192.168.131.57-56.35.38.237::192.168.51.101": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.131.57",
      id2: "56.35.38.237::192.168.51.101",
      data: { port1: 22, port2: 20 },
    },
    "56.35.38.237::192.168.131.11-56.35.38.237::192.168.131.80": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.131.11",
      id2: "56.35.38.237::192.168.131.80",
      data: { port1: 20, port2: 8080 },
    },
    "56.35.38.237::192.168.131.11-56.35.38.237::192.168.131.151": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.131.11",
      id2: "56.35.38.237::192.168.131.151",
      data: { port1: 20, port2: 443 },
    },
    "56.35.38.237::192.168.131.11-56.35.38.237::192.168.131.167": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.131.11",
      id2: "56.35.38.237::192.168.131.167",
      data: { port1: 8080, port2: 443 },
    },
    "56.35.38.237::192.168.51.194-56.35.38.237::192.168.51.158": {
      flow: { velocity: -3 },
      id1: "56.35.38.237::192.168.51.194",
      id2: "56.35.38.237::192.168.51.158",
      data: { port1: 443, port2: 20 },
    },
    "56.35.38.237::192.168.51.194-56.35.38.237::192.168.131.80": {
      flow: { velocity: 3 },
      id1: "56.35.38.237::192.168.51.194",
      id2: "56.35.38.237::192.168.131.80",
      data: { port1: 8080, port2: 80 },
    },
    "56.35.38.237::192.168.131.151-56.35.38.237::192.168.131.11": {
      flow: { velocity: 3 },
      id1: "56.35.38.237::192.168.131.151",
      id2: "56.35.38.237::192.168.131.11",
      data: { port1: 443, port2: 20 },
    },
    "56.35.38.237::192.168.131.151-56.35.38.237::192.168.51.194": {
      flow: { velocity: 3 },
      id1: "56.35.38.237::192.168.131.151",
      id2: "56.35.38.237::192.168.51.194",
      data: { port1: 443, port2: 443 },
    },
    "56.35.38.237::192.168.131.151-56.35.38.237::192.168.131.80": {
      flow: { velocity: 3 },
      id1: "56.35.38.237::192.168.131.151",
      id2: "56.35.38.237::192.168.131.80",
      data: { port1: 22, port2: 20 },
    },
    "56.35.38.237::192.168.131.167-56.35.38.237::192.168.51.194": {
      flow: { velocity: 3 },
      id1: "56.35.38.237::192.168.131.57",
      id2: "56.35.38.237::192.168.131.167",
      data: { port1: 20, port2: 20 },
    },
    "56.35.38.237::192.168.131.167-56.35.38.237::192.168.131.151": {
      flow: { velocity: 3 },
      id1: "56.35.38.237::192.168.131.167",
      id2: "56.35.38.237::192.168.131.151",
      data: { port1: 22, port2: 20 },
    },
    "56.35.38.237::192.168.131.167-56.35.38.237::192.168.131.11": {
      flow: { velocity: 3 },
      id1: "56.35.38.237::192.168.131.167",
      id2: "56.35.38.237::192.168.131.11",
      data: { port1: 8080, port2: 443 },
    },
    "10.0.203.56-134.58.218.94::10.0.203.8": {
      flow: { velocity: 1 },
      id1: "134.58.218.94::10.0.203.56",
      id2: "134.58.218.94::10.0.203.8",
      data: { port1: 21, port2: 24 },
    },
    "10.0.203.249-134.58.218.94::10.0.203.8": {
      flow: { velocity: 1 },
      id1: "134.58.218.94::10.0.203.249",
      id2: "134.58.218.94::10.0.203.8",
      data: { port1: 22, port2: 18 },
    },
    "134.58.218.94::10.0.71.179-134.58.218.94::10.0.203.101": {
      flow: { velocity: 3 },
      id1: "134.58.218.94::10.0.71.179",
      id2: "134.58.218.94::10.0.203.101",
      data: { port1: 22, port2: 80 },
    },
    "134.58.218.94::10.0.71.179-134.58.218.94::10.0.203.56": {
      flow: { velocity: 3 },
      id1: "134.58.218.94::10.0.71.179",
      id2: "134.58.218.94::10.0.203.56",
      data: { port1: 443, port2: 22 },
    },
    "134.58.218.94::10.0.71.179-134.58.218.94::10.0.203.249": {
      flow: { velocity: 3 },
      id1: "134.58.218.94::10.0.71.179",
      id2: "134.58.218.94::10.0.203.249",
      data: { port1: 8080, port2: 22 },
    },
    "134.58.218.94::10.0.203.56-134.58.218.94::10.0.203.101": {
      flow: { velocity: 2 },
      id1: "134.58.218.94::10.0.203.56",
      id2: "134.58.218.94::10.0.203.101",
      data: { port1: 80, port2: 22 },
    },
    "134.58.218.94::10.0.71.224-134.58.218.94::10.0.71.134": {
      flow: { velocity: 2 },
      id1: "134.58.218.94::10.0.71.224",
      id2: "134.58.218.94::10.0.71.134",
      data: { port1: 20, port2: 443 },
    },
    "134.58.218.94::10.0.203.101-134.58.218.94::10.0.71.224": {
      flow: { velocity: -1 },
      id1: "134.58.218.94::10.0.203.101",
      id2: "134.58.218.94::10.0.71.224",
      data: { port1: 8080, port2: 80 },
    },
    "134.58.218.94::10.0.203.101-134.58.218.94::10.0.71.179": {
      flow: { velocity: -1 },
      id1: "134.58.218.94::10.0.203.101",
      id2: "134.58.218.94::10.0.71.179",
      data: { port1: 443, port2: 80 },
    },
    "181.13.51.159::192.168.235.94-56.35.38.237::192.168.131.57": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.94",
      id2: "56.35.38.237::192.168.131.57",
      data: { port1: 80, port2: 20, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.94-140.211.152.43::192.168.247.240": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.94",
      id2: "140.211.152.43::192.168.247.240",
      data: { port1: 443, port2: 8080, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.235-56.35.38.237::192.168.51.158": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.235",
      id2: "56.35.38.237::192.168.51.158",
      data: { port1: 8080, port2: 22, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.235-56.35.38.237::192.168.131.167": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.235",
      id2: "56.35.38.237::192.168.131.167",
      data: { port1: 20, port2: 443, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.235-140.211.152.43::192.168.247.240": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.235",
      id2: "140.211.152.43::192.168.247.240",
      data: { port1: 443, port2: 443, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.76-134.58.218.94::10.0.71.134": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.223.76",
      id2: "134.58.218.94::10.0.71.134",
      data: { port1: 443, port2: 443, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.111-140.211.152.43::192.168.247.162": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.111",
      id2: "140.211.152.43::192.168.247.162",
      data: { port1: 443, port2: 22, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.111-56.35.38.237::192.168.131.167": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.111",
      id2: "56.35.38.237::192.168.131.167",
      data: { port1: 8080, port2: 20, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.111-56.35.38.237::192.168.51.108": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.111",
      id2: "56.35.38.237::192.168.51.108",
      data: { port1: 22, port2: 22, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.111-134.58.218.94::10.0.71.134": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.111",
      id2: "134.58.218.94::10.0.71.134",
      data: { port1: 20, port2: 80, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.34-140.211.152.43::192.168.16.194": {
      flow: { velocity: 5 },
      id1: "181.13.51.159::192.168.235.34",
      id2: "140.211.152.43::192.168.16.194",
      data: { port1: 80, port2: 8080, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.34-134.58.218.94::10.0.71.224": {
      flow: { velocity: -5 },
      id1: "181.13.51.159::192.168.235.34",
      id2: "134.58.218.94::10.0.71.224",
      data: { port1: 8080, port2: 8080, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.34-56.35.38.237::192.168.131.120": {
      flow: { velocity: -5 },
      id1: "181.13.51.159::192.168.235.34",
      id2: "56.35.38.237::192.168.131.120",
      data: { port1: 22, port2: 22, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.223-134.58.218.94::10.0.71.224": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.223.223",
      id2: "134.58.218.94::10.0.71.224",
      data: { port1: 22, port2: 20, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.223-56.35.38.237::192.168.51.194": {
      flow: { velocity: -2 },
      id1: "181.13.51.159::192.168.223.223",
      id2: "56.35.38.237::192.168.51.194",
      data: { port1: 443, port2: 8080, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.34-56.35.38.237::192.168.131.167": {
      flow: { velocity: -2 },
      id1: "181.13.51.159::192.168.223.34",
      id2: "56.35.38.237::192.168.131.167",
      data: { port1: 22, port2: 8080, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.34-140.211.152.43::192.168.16.232": {
      flow: { velocity: -2 },
      id1: "181.13.51.159::192.168.223.34",
      id2: "140.211.152.43::192.168.16.232",
      data: { port1: 80, port2: 22, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.34-134.58.218.94::10.0.203.56": {
      flow: { velocity: -2 },
      id1: "181.13.51.159::192.168.223.34",
      id2: "134.58.218.94::10.0.203.56",
      data: { port1: 20, port2: 443, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.34-56.35.38.237::192.168.51.158": {
      flow: { velocity: -2 },
      id1: "181.13.51.159::192.168.223.34",
      id2: "56.35.38.237::192.168.51.158",
      data: { port1: 443, port2: 80, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.34-134.58.218.94::10.0.203.249": {
      flow: { velocity: -2 },
      id1: "181.13.51.159::192.168.223.34",
      id2: "134.58.218.94::10.0.203.249",
      data: { port1: 80, port2: 443, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.294-134.58.218.94::10.0.71.134": {
      flow: { velocity: 6 },
      id1: "181.13.51.159::192.168.235.94",
      id2: "134.58.218.94::10.0.71.134",
      data: { port1: 8080, port2: 443, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.76-56.35.38.237::192.168.51.101": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.223.76",
      id2: "56.35.38.237::192.168.51.101",
      data: { port1: 443, port2: 8080, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.76-56.35.38.237::192.168.51.158": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.223.76",
      id2: "56.35.38.237::192.168.51.158",
      data: { port1: 80, port2: 80, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.76-134.58.218.94::10.0.203.56": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.223.76",
      id2: "134.58.218.94::10.0.203.56",
      data: { port1: 22, port2: 8080, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.223-140.211.152.43::192.168.16.194": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.223.223",
      id2: "140.211.152.43::192.168.16.194",
      data: { port1: 80, port2: 20, crossNetwork: true },
    },
    "181.13.51.159::192.168.223.223-140.211.152.43::192.168.247.240": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.223.223",
      id2: "140.211.152.43::192.168.247.240",
      data: { port1: 80, port2: 20, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.34-134.58.218.94::10.0.71.179": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.34",
      id2: "134.58.218.94::10.0.71.179",
      data: { port1: 20, port2: 22, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.34-56.35.38.237::192.168.51.101": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.34",
      id2: "56.35.38.237::192.168.51.101",
      data: { port1: 8080, port2: 8080, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.34-56.35.38.237::192.168.51.158": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.34",
      id2: "56.35.38.237::192.168.51.158",
      data: { port1: 22, port2: 80, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.34-140.211.152.43::192.168.16.232": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.34",
      id2: "140.211.152.43::192.168.16.232",
      data: { port1: 80, port2: 20, crossNetwork: true },
    },
    "181.13.51.159::192.168.235.34-56.35.38.237::192.168.131.11": {
      flow: { velocity: 2 },
      id1: "181.13.51.159::192.168.235.34",
      id2: "56.35.38.237::192.168.131.11",
      data: { port1: 20, port2: 443, crossNetwork: true },
    },
    "140.211.152.43::192.168.16.194-134.58.218.94::10.0.71.134": {
      flow: { velocity: 2 },
      id1: "140.211.152.43::192.168.16.194",
      id2: "134.58.218.94::10.0.71.134",
      data: { port1: 443, port2: 22, crossNetwork: true, alert: "Unencrypted data" },
    },
  };

  return mapValues(items, (item, key) => {
    return styleItem(item, shape);
  });
}

export default data;
<!doctype html>
<html>
  <body>
    <div id="regraph" style="height: 100vh"></div>
    <script type="module" src="./code.jsx"></script>
  </body>
</html>

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.