Search

Advanced Node Gallery

Styles

Create custom shaped nodes with multiple labels and styles.

Advanced Node Gallery
View live example →

Click on a node to see its styling data in the overlay.

For more advanced design needs, ReGraph lets you create nodes as rectangles or custom font icons, and style your nodes with multiple labels, custom interactions and interaction styling.

See also

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

import isEmpty from "lodash/isEmpty";
import { Chart } from "regraph";

import {
  data,
  interactionStylePresets,
  invertedInteractiveNode,
  normalInteractiveNode,
  positions,
} from "./data";

import "@fortawesome/fontawesome-free/css/fontawesome.css";
import "@fortawesome/fontawesome-free/css/solid.css";
import "@ci/theme/rg/css/properties.css";

function NodeGallery() {
  const [selection, setSelection] = useState({});
  const [items, setItems] = useState(data);
  const [interactiveNodesState, setInteractiveNodeState] = useState({});
  const [buttons, setButtons] = useState({
    labelButtons: {},
    buttonGroupLabels: {},
  });
  const [chartClasses, setChartClasses] = useState([]);
  const currentSubItem = useRef(null);
  const chartRef = useRef();

  const { labelButtons, buttonGroupLabels } = buttons;

  // register any required label as a button or a button group
  useEffect(() => {
    const buttonsToRegister = {};
    const buttonGroupsToRegister = {};

    function registerLabelButton(
      itemId,
      labelIndex,
      onItemInteractionLabelStyle,
      onClick,
    ) {
      buttonsToRegister[itemId] = buttonsToRegister[itemId] || {};
      buttonsToRegister[itemId][labelIndex] = { onClick };
      buttonsToRegister[itemId][onItemInteractionLabelStyle] =
        onItemInteractionLabelStyle;
    }

    function registerLabelButtonGroup(
      itemId,
      labelIndexArray,
      onItemInteractionLabelStyle,
      onClick,
    ) {
      for (const labelIndex of labelIndexArray) {
        registerLabelButton(
          itemId,
          labelIndex,
          onItemInteractionLabelStyle,
          onClick,
        );
        buttonGroupsToRegister[itemId] = {
          ...buttonGroupsToRegister[itemId],
          [labelIndex]: labelIndexArray,
        };
      }
    }

    function toggleLabelButtons(itemId, labelIndex, style) {
      // track which buttons are toggled
      const currentNodeInteractiveState = interactiveNodesState[itemId] ?? [];

      currentNodeInteractiveState[labelIndex] =
        currentNodeInteractiveState[labelIndex] === undefined
          ? true
          : !currentNodeInteractiveState[labelIndex];

      setInteractiveNodeState({
        ...interactiveNodesState,
        [itemId]: currentNodeInteractiveState,
      });

      // make sure all buttons are styled correctly depending on their toggle state
      const currentStyle = items[itemId];
      const originalStyle = data[itemId];

      const toggleStyle = { ...currentStyle, label: [...currentStyle.label] };
      toggleStyle.label[labelIndex] = {
        ...toggleStyle.label[labelIndex],
        ...style,
      };

      currentNodeInteractiveState.forEach((toggleValue, index) => {
        const originalLabelStyle = { ...originalStyle.label[index] };
        const labelStyle = toggleValue ? style : originalLabelStyle;
        toggleStyle.label[index] = {
          ...toggleStyle.label[index],
          ...labelStyle,
        };
      });

      setItems({ ...items, [itemId]: toggleStyle });
    }

    function toggleNodeStyle(itemId) {
      const interactiveNodeState = interactiveNodesState[itemId];

      const nodeStyle = !interactiveNodeState
        ? invertedInteractiveNode
        : normalInteractiveNode;
      setItems({ ...items, [itemId]: nodeStyle });

      const toggleInteractiveNodeState =
        interactiveNodesState[itemId] === undefined
          ? true
          : !interactiveNodesState[itemId];
      setInteractiveNodeState({
        ...interactiveNodesState,
        [itemId]: toggleInteractiveNodeState,
      });
    }

    registerLabelButton(
      "t2",
      2,
      interactionStylePresets.interactiveNodeHover,
      () => {
        toggleNodeStyle("t2");
      },
    );

    registerLabelButtonGroup(
      "t2",
      [3, 4],
      interactionStylePresets.interactiveNodeHover,
      () => window.open("/docs#node-styling", "_blank"),
    );

    registerLabelButton(
      "outside-buttons-rectangle",
      3,
      interactionStylePresets.outsideLabelActive,
      () => {
        toggleLabelButtons(
          "outside-buttons-rectangle",
          3,
          interactionStylePresets.outsideLabelHover,
        );
      },
    );
    registerLabelButton(
      "outside-buttons-rectangle",
      5,
      interactionStylePresets.outsideLabelActive,
      () => {
        toggleLabelButtons(
          "outside-buttons-rectangle",
          5,
          interactionStylePresets.outsideLabelHover,
        );
      },
    );
    registerLabelButton(
      "outside-buttons-rectangle",
      7,
      interactionStylePresets.outsideLabelActive,
      () => {
        toggleLabelButtons(
          "outside-buttons-rectangle",
          7,
          interactionStylePresets.outsideLabelHover,
        );
      },
    );

    registerLabelButton(
      "outside-buttons-circle",
      3,
      interactionStylePresets.outsideLabelActive,
      () => {
        toggleLabelButtons(
          "outside-buttons-circle",
          3,
          interactionStylePresets.outsideLabelHover,
        );
      },
    );
    registerLabelButton(
      "outside-buttons-circle",
      4,
      interactionStylePresets.outsideLabelActive,
      () => {
        toggleLabelButtons(
          "outside-buttons-circle",
          4,
          interactionStylePresets.outsideLabelHover,
        );
      },
    );
    registerLabelButton(
      "outside-buttons-circle",
      5,
      interactionStylePresets.outsideLabelActive,
      () => {
        toggleLabelButtons(
          "outside-buttons-circle",
          5,
          interactionStylePresets.outsideLabelHover,
        );
      },
    );

    setButtons((current) => ({
      ...current,
      labelButtons: buttonsToRegister,
      buttonGroupLabels: buttonGroupsToRegister,
    }));
  }, [data, positions, items, interactiveNodesState]);

  const handleChartClick = ({ id, subItem, preventDefault }) => {
    if (subItem !== null && subItem.type === "label") {
      const labelIndex = subItem.index;
      if (labelButtons[id] && labelButtons[id][labelIndex]) {
        labelButtons[id][labelIndex].onClick();
        preventDefault();
      }
    }
  };

  const handleChartChange = (change) => {
    if (change.selection) {
      setSelection({ ...change.selection });
    }
  };

  const handleItemInteraction = ({
    id,
    subItem,
    setStyle,
    hovered,
    selected,
  }) => {
    const style = {};

    if (selected) {
      style.fade = false;
    }

    const labelIndex =
      subItem !== null && subItem.type === "label" ? subItem.index : -1;
    const isHoverableLabel =
      id !== null && labelButtons[id] && labelButtons[id][labelIndex];
    const isAButtonGroup =
      isHoverableLabel && !!buttonGroupLabels?.[id]?.[labelIndex];
    const hoverStyle = isHoverableLabel && labelButtons[id][style];

    if (hovered && isHoverableLabel && !isAButtonGroup) {
      const label = [...items[id].label];
      label[labelIndex] = {
        ...label[labelIndex],
        ...hoverStyle,
      };
      style.label = label;
    } else if (hovered && isAButtonGroup) {
      const label = [...items[id].label];

      for (const index of buttonGroupLabels[id][labelIndex]) {
        label[index] = {
          ...label[index],
          ...hoverStyle,
        };
      }

      style.label = label;
    }

    // apply hover styling for buttons that are not toggled, otherwise only apply fade to selected nodes
    if (
      id === "t2" ||
      (subItem && currentSubItem.current?.index !== subItem?.index)
    ) {
      setStyle({
        [id]: style,
      });
    } else if (selected) {
      setStyle({ [id]: { fade: false } });
    }
    currentSubItem.current = subItem;
  };

  const handleHover = ({ id, subItem }) => {
    // On a hover interaction over a label button, append a class to the chart to style the cursor differently
    const labelIndex =
      subItem !== null && subItem.type === "label" ? subItem.index : -1;
    const isHoverableLabel =
      id !== null && labelButtons[id] && labelButtons[id][labelIndex];
    setChartClasses((current) =>
      isHoverableLabel
        ? [...current, "cursor-pointer"]
        : current.filter((className) => !className.startsWith("cursor-")),
    );

    if (id == null) {
      currentSubItem.current = null;
    }
  };

  return (
    <div
      style={{ width: "100%", height: "100%" }}
      className={chartClasses.join(" ")}
    >
      <Chart
        items={items}
        ref={chartRef}
        positions={positions}
        onClick={handleChartClick}
        onItemInteraction={handleItemInteraction}
        onChange={handleChartChange}
        onHover={handleHover}
        options={{
          navigation: false,
          overview: false,
          iconFontFamily: "Font Awesome 5 Free",
          selection: { color: "#FF9933" },
          hoverDelay: 0,
        }}
        animation={{ animate: true, time: 450 }}
      />
      {isEmpty(selection) ? (
        <pre className="properties empty">
          Select a node to view its properties.
        </pre>
      ) : (
        <pre
          className="properties"
          style={{ fontFamily: 'monospace, "Material Icons"' }}
        >
          {JSON.stringify(selection, null, 2)}
        </pre>
      )}
    </div>
  );
}

const FontReadyChart = React.lazy(() =>
  Promise.all([
    document.fonts.load("24px 'Raleway'"),
    document.fonts.load("24px 'Montserrat'"),
    document.fonts.load("24px 'Varela Round'"),
    document.fonts.load("24px 'Material Icons'", "air"),
    document.fonts.load("900 24px 'Font Awesome 5 Free'"),
  ]).then(() => ({
    default: NodeGallery,
  })),
);

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

const root = createRoot(document.getElementById("regraph"));
root.render(<Demo />);
export const interactionStylePresets = {
  outsideLabelActive: { backgroundColor: "#d1fae5" },
  outsideLabelHover: { backgroundColor: "#6ee7b7" },
  interactiveNodeHover: { backgroundColor: "#9A9B9B" },
};

const lineSVG = `data:image/svg+xml;base64,${btoa(
  `<svg xmlns="http://www.w3.org/2000/svg" fill="#e2e8f0" width="256px" height="5px">
  <g id="SVGRepo_bgCarrier" stroke-width="0">
  </g>
  <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round">
  </g>
  <g id="SVGRepo_iconCarrier">
    <rect width="256" height="5" />
  </g>
</svg>`,
)}`;

function circleSVG(colour) {
  return `data:image/svg+xml;base64,${btoa(
    `<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg">
  <circle cx="128" cy="128" r="128" fill="${colour}"/>
</svg>`,
  )}`;
}

function hexagonSVG(colour) {
  return `data:image/svg+xml;base64,${btoa(
    `<svg fill="${colour}" height="256px" width="256px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 184.751 184.751" xml:space="preserve">
    <g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="4.803526"></g><g id="SVGRepo_iconCarrier"> <path d="M0,92.375l46.188-80h92.378l46.185,80l-46.185,80H46.188L0,92.375z"></path> </g>
    </svg>
`,
  )}`;
}

function squareSVG(colour) {
  return `data:image/svg+xml;base64,${btoa(
    `<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg">
  <rect width="256" height="256" fill="${colour}"/>
</svg>`,
  )}`;
}

export const positions = {
  "t1-a": {
    x: -475,
    y: -45,
  },
  "t1-b": {
    x: -350,
    y: -45,
  },
  "t1-c": {
    x: -597,
    y: -45,
  },
  t2: {
    x: 425,
    y: 200,
  },
  "j1-rectangle": {
    x: -105,
    y: -361,
  },
  "j2-rectangle": {
    x: -105,
    y: -255,
  },
  "j3-rectangle": {
    x: -105,
    y: -140,
  },
  "j4-circle": {
    x: 247,
    y: 0,
  },
  "j5-circle": {
    x: 120,
    y: -22,
  },
  "j6-circle": {
    x: 203,
    y: -122,
  },
  "j7-profile-card": {
    x: -500,
    y: -300,
  },
  "c1-circle": {
    x: -40,
    y: 75,
  },
  "c1-hexagon": {
    x: -105,
    y: 200,
  },
  "c1-square": {
    x: 45,
    y: 200,
  },
  c1: {
    x: 380,
    y: -334,
  },
  "node_Emanuela Cooper": {
    x: 480,
    y: -150,
  },
  "node_Vito Morello": {
    x: 480,
    y: -45,
  },
  node_people: {
    x: 480,
    y: 45,
  },
  "outside-buttons-rectangle": {
    x: -597,
    y: 180,
  },
  "outside-buttons-circle": {
    x: -400,
    y: 180,
  },
};

function makeInteractiveNode(backgroundColour, accentColour) {
  return {
    type: "node",
    border: {
      color: accentColour,
      radius: [0, 10, 20, 10],
    },
    shape: {
      height: 85,
      width: 305,
    },
    color: backgroundColour,
    label: [
      {
        color: accentColour,
        backgroundColor: "transparent",
        fontSize: 35,
        position: {
          horizontal: "left",
          vertical: "middle",
        },
        fontIcon: {
          text: "fas fa-desktop",
        },
        margin: {
          left: 17.5,
        },
      },
      {
        bold: true,
        color: accentColour,
        text: "Interactive Node",
        backgroundColor: "transparent",
        fontSize: 16,
        padding: {
          top: 0,
          right: 10,
          bottom: 0,
          left: 0,
        },
        textAlignment: {
          horizontal: "left",
        },
        position: {
          horizontal: 70,
          vertical: 15,
        },
      },
      {
        bold: true,
        color: backgroundColour,
        text: "Invert Style",
        backgroundColor: accentColour,
        border: {
          radius: [10, 10, 10, 10],
        },
        margin: {
          top: 10,
          right: 0,
          bottom: 10,
          left: 0,
        },
        minWidth: 60,
        padding: {
          top: 8,
          right: 10,
          bottom: 6,
          left: 10,
        },
        position: {
          horizontal: 70,
        },
      },
      {
        bold: true,
        color: backgroundColour,
        backgroundColor: accentColour,
        fontSize: 13,
        margin: {
          top: 10,
          right: 10,
          bottom: 0,
          left: 10,
        },
        border: {
          radius: [10, 0, 0, 10],
        },
        padding: {
          top: 9,
          right: 10,
          bottom: 9.75,
          left: 10,
        },
        position: {
          vertical: "inherit",
        },
        fontIcon: {
          text: "fas fa-external-link-alt",
        },
      },
      {
        bold: true,
        color: backgroundColour,
        text: "Go to Docs ",
        backgroundColor: accentColour,
        border: {
          radius: [0, 10, 10, 0],
        },
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 30,
        },
        minWidth: 60,
        padding: {
          top: 8,
          right: 10,
          bottom: 6,
          left: 1,
        },
        position: {
          horizontal: 170,
          vertical: "inherit",
        },
      },
    ],
  };
}

export const invertedInteractiveNode = makeInteractiveNode(
  "#3d405b",
  "#f4f3ee",
);

export const normalInteractiveNode = makeInteractiveNode("#f4f3ee", "#3d405b");

function makeProfileCardNode() {
  return {
    color: "#f1f5f9",
    border: {
      radius: [10, 10, 6, 6],
    },
    label: [
      {
        color: "rgb(81, 136, 168)",
        backgroundColor: "#f1f5f9",
        fontSize: 90,
        border: {
          color: "rgb(0, 85, 135)",
          width: 5,
          radius: [60, 60, 60, 60],
        },
        margin: {
          top: -50,
          right: 0,
          bottom: 0,
          left: 0,
        },
        minHeight: 100,
        minWidth: 100,
        position: {
          vertical: "top",
        },
        fontIcon: {
          text: "fas fa-user",
        },
      },
      {
        bold: true,
        color: "rgb(0, 85, 135)",
        text: "John Doe",
        backgroundColor: "transparent",
        fontSize: 30,
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 0,
        },
      },
      {
        color: "rgb(81, 136, 168)",
        backgroundColor: "transparent",
        fontSize: 12,
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 0,
        },
        fontIcon: {
          text: "fas fa-envelope",
        },
      },
      {
        color: "rgb(81, 136, 168)",
        text: "[email protected]",
        backgroundColor: "transparent",
        fontSize: 10,
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 0,
        },
        position: {
          vertical: "inherit",
        },
      },
      {
        color: "rgb(81, 136, 168)",
        backgroundColor: "transparent",
        fontSize: 12,
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 0,
        },
        fontIcon: {
          text: "fas fa-code",
        },
      },
      {
        color: "#334155",
        text: "Software Developer",
        backgroundColor: "transparent",
        fontSize: 10,
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 0,
        },
        position: {
          vertical: "inherit",
        },
      },
      {
        color: "white",
        text: "[email protected]",
        backgroundColor: "rgb(81, 136, 168)",
        fontSize: 10,
        border: {
          radius: [0, 0, 6, 6],
        },
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 0,
        },
        minWidth: 300,
        padding: {
          top: 10,
          right: 0,
          bottom: 10,
          left: 0,
        },
        position: {
          vertical: "bottom",
        },
      },
    ],
    shape: {
      height: "auto",
      width: 300,
    },
  };
}

function makeBusinessProcessNode() {
  return {
    color: "#ecfdf5",
    border: {
      color: "#e5e5e5",
      width: 2,
      radius: [10, 10, 10, 10],
    },
    label: [
      {
        color: "#334155",
        text: "Business Process 1",
        backgroundColor: "transparent",
        fontSize: 26,
        margin: {
          top: 8,
          right: 0,
          bottom: 0,
          left: 8,
        },
        position: {
          horizontal: "left",
          vertical: "top",
        },
      },
      {
        color: "#334155",
        text: "2140.0",
        backgroundColor: "transparent",
        fontSize: 26,
        margin: {
          top: 0,
          right: 0,
          bottom: 20,
          left: 8,
        },
        textAlignment: {
          vertical: "bottom",
        },
        position: {
          horizontal: "left",
          vertical: "bottom",
        },
      },
      {
        color: "#334155",
        text: "GBP",
        backgroundColor: "transparent",
        fontSize: 20,
        margin: {
          top: 0,
          right: 0,
          bottom: 21,
          left: 0,
        },
        minHeight: 36,
        textAlignment: {
          vertical: "bottom",
        },
        position: {
          vertical: "bottom",
        },
      },
      {
        margin: {
          top: 0,
          right: 2,
          bottom: 15,
          left: 0,
        },
        fontSize: 36,
        backgroundColor: "transparent",
        minHeight: 36,
        textAlignment: {
          vertical: "bottom",
        },
        position: {
          horizontal: "right",
          vertical: "bottom",
        },
        fontIcon: {
          text: "fas fa-caret-up",
          color: "#10b981",
        },
      },
      {
        color: "#10b981",
        text: "55.12%",
        backgroundColor: "transparent",
        fontSize: 20,
        minHeight: 36,
        textAlignment: {
          vertical: "bottom",
        },
        position: {
          vertical: "inherit",
        },
        margin: {
          top: 0,
          right: 8,
          bottom: 20,
          left: 0,
        },
      },
      {
        border: {
          radius: [0, 0, 9, 9],
        },
        minHeight: 18,
        minWidth: 402,
        backgroundColor: "#e5e5e5",
        fontSize: 0,
        padding: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        position: {
          horizontal: "center",
          vertical: 103,
        },
      },
      {
        border: {
          radius: [0, 0, 10, 10],
        },
        margin: {
          top: 1,
          right: 1,
          bottom: 1,
          left: 1,
        },
        fontSize: 10,
        backgroundColor: "#f1f5f9",
        minWidth: 394,
        position: {
          horizontal: "left",
          vertical: "bottom",
        },
      },
      {
        border: {
          radius: [0, 0, 0, 10],
        },
        margin: {
          top: 1,
          right: 1,
          bottom: 1,
          left: 1,
        },
        fontSize: 10,
        backgroundColor: "#10b981",
        minWidth: 230.5,
        position: {
          horizontal: "left",
          vertical: "bottom",
        },
      },
      {
        backgroundColor: "#f1f5f9",
        minWidth: 40,
        minHeight: 60,
        position: { vertical: "middle", horizontal: -46 },
        border: {
          radius: [5, 0, 0, 5],
          width: 2,
          color: "#e5e5e5",
        },
        fontSize: 26,
        fontIcon: {
          text: "fas fa-people-arrows",
          color: "#334155",
        },
      },
    ],
    shape: {
      height: 120,
      width: 400,
    },
  };
}

function makeShadowCircleNode(colour, icon, text) {
  return {
    type: "node",
    color: colour,
    border: {
      color: "#283618",
      width: 2,
    },
    size: 1.5,
    halos: [
      {
        color: "rgba(220,220,220,1)",
        radius: 29,
        width: 1,
      },
      {
        color: "rgba(220,220,220,0.8)",
        radius: 30,
        width: 1,
      },
      {
        color: "rgba(220,220,220,0.6)",
        radius: 31,
        width: 1,
      },
      {
        color: "rgba(220,220,220,0.4)",
        radius: 32,
        width: 1,
      },
      {
        color: "rgba(220,220,220,0.2)",
        radius: 33,
        width: 1,
      },
    ],
    label: [
      {
        color: "#283618",
        backgroundColor: "rgba(0, 0, 0, 0)",
        fontSize: 30,
        padding: {
          top: 6,
          right: 2,
          bottom: 0,
          left: 2,
        },
        position: {
          vertical: "top",
        },
        fontIcon: {
          text: icon,
          fontFamily: "Material Icons",
        },
      },
      {
        bold: true,
        color: "#283618",
        text: text,
        backgroundColor: "rgba(0, 0, 0, 0)",
        fontSize: 10,
        fontFamily: "Varela Round",
      },
    ],
  };
}

function makeMaterialIconLabel(icon, fontSize, colour) {
  return {
    position: {
      horizontal: "centre",
      vertical: "middle",
    },
    fontSize: fontSize,
    color: colour,
    backgroundColor: "transparent",
    fontIcon: {
      text: icon,
      fontFamily: "Material Icons",
    },
    padding: {
      top: 5,
      right: 2,
      bottom: 0,
      left: 2,
    },
  };
}

function makeCustomNodeLabel(image, size) {
  return {
    position: {
      horizontal: "centre",
      vertical: "middle",
    },
    backgroundColor: "transparent",
    image,
    padding: {
      top: 5,
      right: 2,
      bottom: 0,
      left: 2,
    },
    maxHeight: size,
    maxWidth: size,
  };
}

export const data = {
  t2: normalInteractiveNode,
  "t1-a": {
    color: "#A485D5",
    border: {
      color: "#69738C",
      width: 3,
      radius: [10, 10, 10, 10],
    },
    label: [
      {
        color: "white",
        backgroundColor: "transparent",
        fontSize: 50,
        position: {
          vertical: 24,
        },
        fontIcon: {
          text: "fas fa-server",
        },
      },
      {
        bold: true,
        color: "white",
        text: "Rack A",
        backgroundColor: "transparent",
        fontSize: 15,
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 0,
        },
      },
      {
        color: "white",
        text: "OK",
        backgroundColor: "transparent",
        fontSize: 11,
        margin: {
          top: 5,
          right: 0,
          bottom: 0,
          left: 0,
        },
        padding: {
          top: 0,
          right: 20,
          bottom: 0,
          left: 20,
        },
      },
      {
        border: {
          radius: [0, 0, 8.5, 8.5],
        },
        backgroundColor: "#69738C",
        margin: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        minHeight: 21.5,
        minWidth: 103,
        padding: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        position: {
          horizontal: -1.5,
          vertical: 130,
        },
      },
      {
        border: {
          radius: [0, 0, 0, 6.5],
        },
        margin: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        backgroundColor: "#FACA16",
        minHeight: 17,
        minWidth: 60,
        padding: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        position: {
          horizontal: 0,
          vertical: 133,
        },
      },
    ],
    shape: {
      height: 150,
      width: 100,
    },
  },
  "t1-b": {
    color: "#A485D5",
    border: {
      color: "#69738C",
      width: 3,
      radius: [10, 10, 10, 10],
    },
    label: [
      {
        color: "white",
        backgroundColor: "transparent",
        fontSize: 50,
        position: {
          vertical: 24,
        },
        fontIcon: {
          text: "fas fa-server",
        },
      },
      {
        bold: true,
        color: "white",
        text: "Rack B",
        backgroundColor: "transparent",
        fontSize: 15,
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 0,
        },
      },
      {
        color: "white",
        text: "High CPU usage",
        backgroundColor: "transparent",
        fontSize: 11,
        margin: {
          top: 5,
          right: 0,
          bottom: 0,
          left: 0,
        },
        padding: {
          top: 0,
          right: 20,
          bottom: 0,
          left: 20,
        },
      },
      {
        border: {
          radius: [0, 0, 8.5, 8.5],
        },
        backgroundColor: "#69738C",
        margin: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        minHeight: 21.5,
        minWidth: 103,
        padding: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        position: {
          horizontal: -1.5,
          vertical: 130,
        },
      },
      {
        border: {
          radius: [0, 0, 0, 6.5],
        },
        margin: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        backgroundColor: "#FF3C38",
        minHeight: 17,
        minWidth: 80,
        padding: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        position: {
          horizontal: 0,
          vertical: 133,
        },
      },
    ],
    shape: {
      height: 150,
      width: 100,
    },
  },
  "t1-c": {
    color: "#61A899",
    border: {
      color: "#69738C",
      width: 3,
      radius: [10, 10, 10, 10],
    },
    label: [
      {
        color: "white",
        backgroundColor: "transparent",
        fontSize: 50,
        position: {
          vertical: 24,
        },
        fontIcon: {
          text: "fas fa-hdd",
        },
      },
      {
        bold: true,
        color: "white",
        text: "Blade C",
        backgroundColor: "transparent",
        fontSize: 15,
        margin: {
          top: 10,
          right: 0,
          bottom: 0,
          left: 0,
        },
      },
      {
        color: "white",
        text: "OK",
        backgroundColor: "transparent",
        fontSize: 11,
        margin: {
          top: 5,
          right: 0,
          bottom: 0,
          left: 0,
        },
        padding: {
          top: 0,
          right: 20,
          bottom: 0,
          left: 20,
        },
      },
      {
        border: {
          radius: [0, 0, 8.5, 8.5],
        },
        backgroundColor: "#69738C",
        margin: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        minHeight: 21.5,
        minWidth: 103,
        padding: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        position: {
          horizontal: -1.5,
          vertical: 130,
        },
      },
      {
        margin: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        border: {
          radius: [0, 0, 0, 6.5],
        },
        backgroundColor: "#65B540",
        minHeight: 17,
        minWidth: 30,
        padding: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        position: {
          horizontal: 0,
          vertical: 133,
        },
      },
    ],
    shape: {
      height: 150,

// ...truncated 589 lines
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div id="regraph" style="height: 100vh"></div>
    <script type="module" src="./code.jsx"></script>
  </body>
</html>
/* Import for Google Fonts: Montserrat, Raleway and Varela Round */
@import url("https://fonts.googleapis.com/css2?family=Montserrat&family=Raleway&family=Varela+Round&display=swap");

/* Import for Google's Material Icons */
@import url("https://fonts.googleapis.com/icon?family=Material+Icons");

.cursor-pointer canvas {
  cursor: pointer !important;
}

pre {
  max-width: 60vh;
}

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.