Skip to content
  1. Examples

Multilevel Grouping new

This example shows how to use the nodeGrouping transformation to create three levels of grouping in a graph.

ts
import Ogma, { NodeList } from '@linkurious/ogma';

const ogma = new Ogma<NodeData>({
  container: 'graph-container'
});

interface NodeData {
  name: string;
  lang: string;
  size: number;
  country: string;
  level?: number;
}

ogma.styles.setTheme({
  nodeAttributes: {
    innerStroke: {
      color: 'white',
      width: 1,
      minVisibleSize: 30
    },
    outerStroke: {
      color: 'black',
      width: 2,
      scalingMethod: 'scaled',
      minVisibleSize: 10
    },
    text: {
      backgroundColor: null,
      size: 14,
      minVisibleSize: 0,
      padding: 4
    },
    radius: 7
  },
  selectedNodeAttributes: {
    outline: false,
    outerStroke: {
      color: '#222',
      scalingMethod: 'fixed',
      width: 6
    },
    innerStroke: {
      color: 'white',
      scalingMethod: 'fixed',
      width: 6,
      minVisibleSize: 0
    },
    text: {
      backgroundColor: 'white'
    }
  },
  hoveredNodeAttributes: {
    outline: false,
    outerStroke: {
      color: '#222',
      scalingMethod: 'fixed',
      width: 4
    },
    innerStroke: {
      color: 'white',
      scalingMethod: 'fixed',
      width: 4,
      minVisibleSize: 0
    },
    text: {
      color: '#fff',
      backgroundColor: '#222'
    }
  },
  edgeAttributes: {},
  hoveredEdgeAttributes: {
    color: '#222',
    width: 3,
    stroke: {
      color: '#fff',
      width: 2
    },
    text: { color: '#fff' }
  },
  selectedEdgeAttributes: {
    color: '#222',
    width: 4,
    stroke: {
      color: '#fff',
      width: 2
    }
  }
});

// three bright pastel colors for the group nodes
const colors = ['#f9a03f', '#f9e03f'];
ogma.styles.addNodeRule({
  color: node =>
    !node.isVirtual()
      ? '#333'
      : node.getData('level') === 1
        ? colors[0]
        : colors[1],

  text: {
    content: node =>
      node.isVirtual() ? node.getData('label') : node.getData('name'),
    minVisibleSize: node => (node.isVirtual() ? 30 : 12),
    size: 10, // node => (node.isVirtual() ? 12 : 12),
    color: '#fff',
    backgroundColor: node => '#333'
  }
});
ogma.styles.createClass({
  name: 'hovered-group',
  edgeAttributes: {
    color: '#fff',
    width: 2
  }
});

const layer = ogma.layers.addLayer(`<div class='loader'>
<svg version="1.1" id="L3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
<circle fill="none" stroke="#fff" stroke-width="4" cx="50" cy="50" r="44" style="opacity:1;"/>
<circle fill="#222"  cx="8" cy="54" r="6" >
  <animateTransform
    attributeName="transform"
    dur="2s"
    type="rotate"
    from="0 50 48"
    to="360 50 52"
    repeatCount="indefinite" />
</circle>
</svg>
</div>`);

const graph = await Ogma.parse.jsonFromUrl<NodeData>('files/github.json');
graph.nodes = graph.nodes
  .filter(node => node.data && node.data.country)
  .slice(1000, 1200)
  .map(node => ({
    ...node,
    attributes: undefined
  }));
graph.edges = graph.edges.map(edge => ({
  ...edge,
  attributes: undefined
}));
await ogma.setGraph(graph, { ignoreInvalid: true });
await ogma.layouts.force({ gpu: true, locate: true });
await ogma.transformations
  .addNodeGrouping({
    groupIdFunction: node =>
      `${node.getData('lang')}_${node.getData('country')}`,
    nodeGenerator: (nodes, groupId) => {
      const [lang, country] = groupId.split('_');
      return {
        data: {
          level: 1,
          lang,
          label: lang,
          country
        }
      };
    },
    padding: 20,
    showContents: true
  })
  .whenApplied();
await Promise.all(
  ogma
    .getNodes()
    .filter(n => n.isVirtual())
    .map(group =>
      ogma.layouts.hierarchical({
        nodes: group.getSubNodes() as NodeList,
        duration: 0
      })
    )
);
await ogma.transformations
  .addNodeGrouping({
    selector: node => node.getData('level') === 1,
    groupIdFunction: node => `${node.getData('country')}`,
    nodeGenerator: (nodes, groupId) => {
      return {
        data: {
          level: 2,
          country: groupId,
          label: groupId
        }
      };
    },
    padding: 20,
    showContents: true
  })
  .whenApplied();
await Promise.all(
  ogma
    .getNodes()
    .filter(n => n.getData('level') === 2)
    .map(group =>
      ogma.layouts.force({
        nodes: group.getSubNodes() as NodeList,
        charge: 40,
        radiusRatio: 2,
        gravity: 0.01,
        edgeStrength: 0.25,
        duration: 0
      })
    )
);
await ogma.layouts.force({
  nodes: ogma.getNodes().filter(n => n.getData('level') === 2),
  theta: 0.9,
  edgeStrength: 0.25,
  gravity: 0.01,
  locate: true
});
layer.element.classList.add('fadeout');
setTimeout(() => layer.hide(), 1000);
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>
    <div id="info" class="info"></div>
    <script type="module" src="index.ts"></script>
  </body>
</html>
css
#graph-container {
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: 0;
  overflow: hidden;
}
.loader {
  position: absolute;
  left: 0px;
  top: 0px;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  background: #ddd;
  opacity: 0.9;
}
.loader > svg{
  width: 25%;
  opacity: 1;
}

.fadeout{
  animation: fadeout 1s;
}

@keyframes fadeout {
  from { opacity: 1; }
  to   { opacity: 0; }
}