Back

Category hierarchy layout mode

The 'categoryHierarchy' layout mode differs from 'hierarchy' by sorting nodes based on 'hierarchyCategory' strings, instead of their relationship hierarchy. This way, you can display nodes at the same level regardless of their actual relationships - useful for flow charts, scheduling or resource management.

Documentation Open in JSFiddle
Start Free Trial Purchase

HTML

HTML
<script src="https://cdn.zoomcharts-cloud.com/1/nightly/zoomcharts.js"></script>
<div id="demo"></div>

CSS

CSS
//No CSS for this example 

JavaScript

JavaScript

var data = {
  "nodes": [ 
    { "id": "a1", "loaded": true, "style": { "label": "Node Label A1", "hierarchyCategory": "Category Applesauce", "fillColor": "red" } },
    { "id": "a2", "loaded": true, "style": { "label": "Node Label A2", "hierarchyCategory": "Category Applesauce", "fillColor": "red" } },
    { "id": "a3", "loaded": true, "style": { "label": "Node Label A3", "hierarchyCategory": "Category Applesauce", "fillColor": "red" } },
    { "id": "a4", "loaded": true, "style": { "label": "Node Label A4", "hierarchyCategory": "Category Applesauce", "fillColor": "red" } },
    { "id": "a5", "loaded": true, "style": { "label": "Node Label A5", "hierarchyCategory": "Category Applesauce", "fillColor": "red" } },
    { "id": "b1", "loaded": true, "style": { "label": "Node Label B1", "hierarchyCategory": "Category Biscuit", "fillColor": "green" } },
    { "id": "b2", "loaded": true, "style": { "label": "Node Label B2", "hierarchyCategory": "Category Biscuit", "fillColor": "green" } },
    { "id": "b3", "loaded": true, "style": { "label": "Node Label B3", "hierarchyCategory": "Category Biscuit", "fillColor": "green" } },
    { "id": "b4", "loaded": true, "style": { "label": "Node Label B4", "hierarchyCategory": "Category Biscuit", "fillColor": "green" } },
    { "id": "c1", "loaded": true, "style": { "label": "Node Label C1", "hierarchyCategory": "Category Cucumber", "fillColor": "blue" } },
    { "id": "c2", "loaded": true, "style": { "label": "Node Label C2", "hierarchyCategory": "Category Cucumber", "fillColor": "blue" } },
    { "id": "c3", "loaded": true, "style": { "label": "Node Label C3", "hierarchyCategory": "Category Cucumber", "fillColor": "blue" } },
    { "id": "c4", "loaded": true, "style": { "label": "Node Label C4", "hierarchyCategory": "Category Cucumber", "fillColor": "blue" } },
    { "id": "c5", "loaded": true, "style": { "label": "Node Label C5", "hierarchyCategory": "Category Cucumber", "fillColor": "blue" } },
    { "id": "d2", "loaded": true, "style": { "label": "Node Label D2", "hierarchyCategory": "Category Dessert", "fillColor": "yellow" } },
    { "id": "d3", "loaded": true, "style": { "label": "Node Label D3", "hierarchyCategory": "Category Dessert", "fillColor": "yellow" } },
    { "id": "d4", "loaded": true, "style": { "label": "Node Label D4", "hierarchyCategory": "Category Dessert", "fillColor": "yellow" } },
    { "id": "d5", "loaded": true, "style": { "label": "Node Label D5", "hierarchyCategory": "Category Dessert", "fillColor": "yellow" } },
    { "id": "d6", "loaded": true, "style": { "label": "Node Label D6", "hierarchyCategory": "Category Dessert", "fillColor": "yellow" } },
    { "id": "e4", "loaded": true, "style": { "label": "Node Label E4", "hierarchyCategory": "Category Egg", "fillColor": "orange" } },
    { "id": "e5", "loaded": true, "style": { "label": "Node Label E5", "hierarchyCategory": "Category Egg", "fillColor": "orange" } },
    { "id": "e6", "loaded": true, "style": { "label": "Node Label E6", "hierarchyCategory": "Category Egg", "fillColor": "orange" } },
  ],
  "links": [
    { "id": "l1", "from": "a1", "to": "a2" },
    { "id": "l2", "from": "a2", "to": "a3" },
    { "id": "l3", "from": "a3", "to": "a4" },
    { "id": "l4", "from": "a4", "to": "a5" },
    { "id": "l5", "from": "a1", "to": "b1" },
    { "id": "l6", "from": "b1", "to": "a2" },
    { "id": "l7", "from": "b2", "to": "b3" },
    { "id": "l8", "from": "b3", "to": "b4" },
    { "id": "l9", "from": "a3", "to": "b2" },
    { "id": "l10", "from": "b3", "to": "a5" },
    { "id": "l11", "from": "b1", "to": "b2" },
    { "id": "l12", "from": "c2", "to": "b2" },
    { "id": "l13", "from": "b2", "to": "d6" },
    { "id": "l14", "from": "a5", "to": "e6" },
    { "id": "l15", "from": "e6", "to": "b4" },
    { "id": "l16", "from": "c1", "to": "c2" },
    { "id": "l17", "from": "c2", "to": "c3" },
    { "id": "l18", "from": "c3", "to": "c4" },
    { "id": "l19", "from": "c4", "to": "c5" },
    { "id": "l20", "from": "d3", "to": "c3" },
    { "id": "l21", "from": "c3", "to": "d4" },
    { "id": "l22", "from": "d4", "to": "c4" },
    { "id": "l23", "from": "c5", "to": "d6" },
    { "id": "l24", "from": "d2", "to": "d3" },
    { "id": "l25", "from": "d3", "to": "d4" },
    { "id": "l26", "from": "d4", "to": "d5" },
    { "id": "l27", "from": "d5", "to": "d6" },
    { "id": "l28", "from": "a2", "to": "d3" },
    { "id": "l29", "from": "e4", "to": "e5" },
    { "id": "l30", "from": "e5", "to": "e6" },
    { "id": "l31", "from": "d3", "to": "e4" },
    { "id": "l32", "from": "e4", "to": "d4" },
    { "id": "l33", "from": "d5", "to": "e5" },
    { "id": "l34", "from": "e5", "to": "d6" },
    { "id": "l35", "from": "d6", "to": "e6" },
    { "id": "l36", "from": "d6", "to": "a1" },
    { "id": "l37", "from": "d6", "to": "c4" },
  ]
};

const cycleColors = ["red", "orange", "yellow", "green", "blue", "magenta"];
const cycleIDColorMap = {};
let nextCycleIndex = 0;
var chart = new NetChart({
  container: document.getElementById("demo"),
  data: { preloaded: data },
  layout: {
    mode: "categoryHierarchy",
    rotation: -90,
    scaleY: -1,
    // Nodes can be spaced to avoid overlap using these settings
    nodeSpacing: 30,
    rowSpacing: 60,
    categoryLabelStyle: {
      // Labels respect alignment settings
      align: "left",
      side: "right",
      positionX: "left",
      textStyle: {
        font: "12px Arial"
      },
      backgroundStyle: {
        fillColor: "white"
      }
    },
    onCycleDetect: (backlink, cycle) => {
      // This is naively marking every link involved in every cycle
      // detected. Can also e.g. only mark the backlinks or have
      // some method of handling overlapping cycles, or have special
      // additional styling for backlinks, etc. etc.

      const cycleID = backlink.id;
      for(let i = 0; i < cycle.length; i++) {
        let link = cycle[i];

        if(!link.extra) {
          link.extra = {};
        }
        link.extra.cycleID = cycleID;
      }

      if(!backlink.extra) {
        backlink.extra = {};
      }
      backlink.extra.isBacklink = true;

      if(!cycleIDColorMap.hasOwnProperty(cycleID)) {
        cycleIDColorMap[cycleID] = cycleColors[nextCycleIndex % cycleColors.length];
        nextCycleIndex++;
      }
    }
  },
  style:{
    multilinkAutoCurve: 1.0,
    multilinkSpacing: 10,
    node: {
      // Label should be inside the node with roundtext setting
      display: "roundtext",
    },
    link: { toDecoration: "arrow" },
    linkStyleFunction: (link) => {
      link.radius = 1;
      // Simple example of styling links that are part of a cycle
      if(link.extra && link.extra.cycleID) {
        link.fillColor = cycleIDColorMap[link.extra.cycleID];
        link.radius = 2;
      }
      if(link.extra && link.extra.isBacklink) {
        link.radius = 5;
      }
    },
    nodeStyleFunction: (node) => {
      // Example of applying styles to the node labels
      node.labelStyle.textStyle.fillColor = "black"
      node.labelStyle.textStyle.font = "12px Arial"
    }
  }
});

Data

Data
//No separate data for this example