Skip to content
  1. Examples

Group nodes

This example shows how to use the groupNodes transformation to group nodes depending on a criteria. In this case, we will group nodes by their location data property.

js
import Ogma from '@linkurious/ogma';

const LOCATION = ['France', 'Russia', 'USA'];

const FLAGS = {
  France: 'flags/fr.svg',
  Russia: 'flags/ru.svg',
  USA: 'flags/us.svg'
};

const duration = 300;

const randomGraph = (N, E) => {
  const g = { nodes: [], edges: [] };

  for (let i = 0; i < N; i++) {
    const location = LOCATION[i % LOCATION.length];
    g.nodes.push({
      id: 'n' + i,
      attributes: {
        x: Math.random() * 100,
        y: Math.random() * 100,
        text: 'Node ' + i + ' - ' + location,
        image: {
          url: FLAGS[location]
        }
      },
      data: {
        location
      }
    });
  }

  for (let i = 0; i < E; i++) {
    g.edges.push({
      id: 'e' + i,
      source: 'n' + ((Math.random() * N) | 0),
      target: 'n' + ((Math.random() * N) | 0)
    });
  }

  return g;
};

const g = randomGraph(10, 10);

const ogma = new Ogma({
  graph: g,
  container: 'graph-container'
});

ogma.styles.addNodeRule({
  innerStroke: {
    color: '#999'
  },
  badges: {
    bottomRight: {
      stroke: {
        color: '#999'
      }
    }
  }
});

let transformation = null;

document.getElementById('group-btn').addEventListener('click', () => {
  if (!transformation) {
    // Groups all active nodes that have the same `location` data property
    // into a single node that will have the combined size of all nodes,
    // and be at the center of the grouped nodes.
    transformation = ogma.transformations.addNodeGrouping({
      groupIdFunction: node => node.getData('location'),
      nodeGenerator: (nodes, groupId) => {
        return {
          id: 'special group ' + groupId,
          data: {
            groupId: groupId,
            subNodes: nodes
          },
          attributes: {
            radius: nodes.reduce((acc, node) => {
              return acc + node.getAttribute('radius');
            }, 0),
            text: groupId,
            badges: {
              bottomRight: {
                text: nodes.size
              }
            },
            image: FLAGS[groupId]
          }
        };
      },
      duration: duration
    });
  } else {
    // Toggle the grouping
    transformation.toggle(duration);
  }
  ogma.transformations.afterNextUpdate().then(() => {
    ogma.layouts.force({ locate: true });
  });
});

ogma.events.on('click', evt => {
  if (evt.target && evt.target.isNode) {
    const subNodes = evt.target.getData('subNodes');
    console.log(subNodes && subNodes.getId());
  }
});

// UI update
const onToggle = ({ target }) => {
  const enabled = target.isEnabled();
  const buttonText = enabled ? 'Ungroup nodes' : 'Group nodes';
  document.getElementById('group-btn').textContent = buttonText;
};
ogma.events.on('transformationEnabled', onToggle);
ogma.events.on('transformationDisabled', onToggle);
html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />

    <link type="text/css" rel="stylesheet" href="styles.css">
  </head>
  <body>
    <div id="graph-container"></div>
    <button id="group-btn">Group nodes</button>
    <script src="index.js"></script>
  </body>
</html>
css
#graph-container {
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: 0;
  overflow: hidden;
}
button {
  position: absolute;
  top: 10px;
  left: 10px;
}