Search

Basic Link Aggregation

Aggregate Links

Organize links into aggregate links to reduce clutter in the chart.

Basic Link Aggregation
View live example →

Use the buttons above the chart to see different sets of aggregated links.

This chart shows a supply chain where goods are transported using three transport methods - road freight, air freight and rail freight.

Aggregate By Transport uses the aggregateBy property to aggregate links showing the same method of transport, while All aggregates all the links at each step of the supply chain.

You can also add styling or summary information on aggregated links using the setStyle function passed to the onLinkAggregation event handler.

Link aggregation is currently in beta.

See also

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

import { Chart } from "regraph";

import { items } from "./data";

const aggregateLinksOptions = [false, true, { aggregateBy: "via" }];
const aggregateLinksButton = ["None", "All", "Aggregate By Transport"];
const layout = { name: "sequential", orientation: "right", tightness: 2 };

function Basics() {
  const [state, setState] = useState({
    openCombos: {},
    combine: {
      level: 2,
      properties: ["level1", "level2"],
      shape: "rectangle",
    },
    aggregateLinks: aggregateLinksOptions[2],
  });

  const doubleClickHandler = ({ id }) => {
    setState((current) => {
      return {
        ...current,
        openCombos: { ...current.openCombos, [id]: !current.openCombos[id] },
        combine: { ...current.combine },
      };
    });
  };

  const aggregateLinksHandler = (props) => {
    const { links: linkDict, aggregateByValue, setStyle } = props;
    const links = Object.values(linkDict);

    const color = aggregateByValue ? links[0].color : "grey";

    const glyph = links[0].glyphs[0];
    glyph.backgroundColor = color;

    const style = {
      width: links.length,
      color,
      glyphs: [
        aggregateByValue ? glyph : undefined,
        {
          label: { text: links.length, bold: true, color },
          color: "white",
          border: { color },
          size: 1.25,
        },
      ],
    };

    setStyle(style);
  };

  return (
    <div className="story">
      <div className="options">
        <span className="label">Link Aggregation </span>
        {aggregateLinksOptions.map((value, index) => {
          const valueStr = JSON.stringify(value);
          const aggregateLinksStr = JSON.stringify(state.aggregateLinks);
          return (
            <button
              key={index}
              type="button"
              className={valueStr === aggregateLinksStr ? "active" : ""}
              onClick={() => setState({ ...state, aggregateLinks: value })}
            >
              {aggregateLinksButton[index]}
            </button>
          );
        })}
      </div>
      <Chart
        items={items}
        options={{
          iconFontFamily: "Font Awesome 5 Free",
          navigation: false,
          overview: false,
          selection: false,
          links: { avoidLabels: false },
        }}
        layout={layout}
        aggregateLinks={state.aggregateLinks}
        onLinkAggregation={aggregateLinksHandler}
        onDoubleClick={doubleClickHandler}
        combine={state.combine}
        onCombineNodes={({ setStyle, id, nodes, combo }) => {
          const { closedStyle, openStyle } = createComboStyling(nodes, combo);
          setStyle({
            open: !!state.openCombos[id],
            closedStyle,
            ...openStyle,
          });
        }}
        onCombineLinks={({ setStyle }) => {
          setStyle({ contents: true, summary: false });
        }}
      />
    </div>
  );
}

function createComboStyling(nodes, combo) {
  const labelText = combo.level1 || combo.level2;
  const childNode = Object.values(nodes)[0];
  const backgroundColor = "#2dcda8";
  const color = "white";
  const padding = "4 6";
  const border = { radius: 10 };

  const closedStyle = {
    ...childNode,
    label: [
      childNode.label[0],
      {
        text: labelText,
        color,
        position: "s",
        backgroundColor,
        border,
        padding,
      },
    ],
  };
  const openStyle = {
    arrange: "grid",
    gridShape: { columns: 1 },
    border: { color: childNode.border.color },
    color: childNode.color,
    label: {
      text: labelText,
      backgroundColor,
      color,
      border,
      padding,
      margin: 5,
      position: "s",
    },
  };

  return { closedStyle, openStyle };
}

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

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

const root = createRoot(document.getElementById("regraph"));
root.render(<Demo />);
function createNode(icon, text, data) {
  return {
    border: { color: "#2dcda8" },
    color: "white",
    label: [
      {
        fontIcon: {
          text: icon,
          color: "#2dcda8",
        },
        fontSize: 40,
      },
      {
        text,
        color: "white",
        position: "s",
        backgroundColor: "#2dcda8",
        border: { radius: 10 },
        padding: "4 6",
      },
    ],
    data,
  };
}

function createLink(id1, id2, iconText, color, via) {
  return {
    id1,
    id2,
    color,
    glyphs: [
      {
        fontIcon: {
          text: iconText,
          color,
        },
        color: "white",
        border: { color },
        size: 1.25,
      },
    ],
    end2: { arrow: true },
    data: { via },
  };
}

function createPlaneLink(id1, id2) {
  return createLink(id1, id2, "fas fa-plane", "#3377ff", "air");
}

function createTruckLink(id1, id2) {
  return createLink(id1, id2, "fas fa-truck", "#dd3c3c", "road");
}

function createTrainLink(id1, id2) {
  return createLink(id1, id2, "fas fa-train", "#048170", "rail");
}

export const items = {
  // nodes

  //factory
  "assembly-line-1": createNode("fas fa-industry", "Assembly Line 1", { level1: "Factory" }),
  "assembly-line-2": createNode("fas fa-industry", "Assembly Line 2", { level1: "Factory" }),

  // storage
  "warehouse-1-container-1": createNode("fas fa-warehouse", "Container 1", {
    level2: "Warehouses",
    level1: "Warehouse 1",
  }),
  "warehouse-1-container-2": createNode("fas fa-warehouse", "Container 2", {
    level2: "Warehouses",
    level1: "Warehouse 1",
  }),
  "warehouse-1-container-3": createNode("fas fa-warehouse", "Container 3", {
    level2: "Warehouses",
    level1: "Warehouse 1",
  }),

  "warehouse-2-container-1": createNode("fas fa-warehouse", "Container 1", {
    level2: "Warehouses",
    level1: "Warehouse 2",
  }),
  "warehouse-2-container-2": createNode("fas fa-warehouse", "Container 2", {
    level2: "Warehouses",
    level1: "Warehouse 2",
  }),
  "warehouse-2-container-3": createNode("fas fa-warehouse", "Container 3", {
    level2: "Warehouses",
    level1: "Warehouse 2",
  }),
  "warehouse-2-container-4": createNode("fas fa-warehouse", "Container 4", {
    level2: "Warehouses",
    level1: "Warehouse 2",
  }),

  // store
  store: createNode("fas fa-store", "Retail Outlet"),

  // links

  // assembly lines to containers
  "assembly-line-1-warehouse-1-container-1": createPlaneLink(
    "assembly-line-1",
    "warehouse-1-container-1"
  ),
  "assembly-line-1-warehouse-1-container-2": createTruckLink(
    "assembly-line-1",
    "warehouse-1-container-2"
  ),
  "assembly-line-1-warehouse-1-container-3": createTruckLink(
    "assembly-line-1",
    "warehouse-1-container-3"
  ),

  "assembly-line-2-warehouse-2-container-1": createTrainLink(
    "assembly-line-2",
    "warehouse-2-container-1"
  ),
  "assembly-line-2-warehouse-2-container-2": createTrainLink(
    "assembly-line-2",
    "warehouse-2-container-2"
  ),
  "assembly-line-2-warehouse-2-container-3": createTruckLink(
    "assembly-line-2",
    "warehouse-2-container-3"
  ),
  "assembly-line-2-warehouse-2-container-4": createTruckLink(
    "assembly-line-2",
    "warehouse-2-container-4"
  ),

  // containers to store
  "warehouse-1-container-1-store": createTruckLink("warehouse-1-container-1", "store"),
  "warehouse-1-container-2-store": createTrainLink("warehouse-1-container-2", "store"),
  "warehouse-1-container-3-store": createTrainLink("warehouse-1-container-3", "store"),

  "warehouse-2-container-1-store": createTruckLink("warehouse-2-container-1", "store"),
  "warehouse-2-container-2-store": createTruckLink("warehouse-2-container-2", "store"),
  "warehouse-2-container-3-store": createTrainLink("warehouse-2-container-3", "store"),
  "warehouse-2-container-4-store": createTrainLink("warehouse-2-container-4", "store"),
};
<!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.