mgraph.hde

High dimensional embedding of a graph

mgraph.hde

Modern high-dimensional graph embedding for JavaScript — Fast initial layouts for large graphs

npm version License

About This Project

mgraph.hde is a modern ES module refactoring of ngraph.hde, originally developed by Andrei Kashcha. This package implements high dimensional graph layout with O(m*(|V| + |E|)) time complexity, providing excellent initial configurations for force-directed layouts.

This project is not affiliated with or endorsed by Andrei Kashcha, and any modifications are the responsibility of the maintainers of mgraph.hde.

What is High-Dimensional Embedding?

While high-dimensional layouts don't necessarily look appealing for all graphs, this package provides amazing initial configurations for subsequent refinement by mgraph.forcelayout or d3-force.

Since force-based layout convergence speed depends on initial configuration, this library can provide a significant boost for large graph layouts.

High-dimensional embedding demo

How It Works

The package follows the [Graph Drawing by High-Dimensional Embedding](http://www.wisdom.weizmann.ac.il/~harel/papers/highdimensionalGD.pdf or https://jgaa.info/index.php/jgaa/article/view/paper89/2878) paper by David Harel and Yehuda Koren:

  1. Project to M-dimensional space: The graph is first projected into M-dimensional space (default: 50 dimensions) where adjacent nodes are close to each other
  2. PCA reduction: From this M-dimensional space, we reduce back to 2D, 3D, or any D < M dimensions using Principal Component Analysis (PCA)
  3. Use as initial layout: The resulting D-dimensional coordinates serve as excellent starting positions for force-based layouts

Installation

Via npm

npm install mgraph.hde
Via CDN
html<script src="https://cdn.jsdelivr.net/npm/mgraph.hde/dist/mgraph.hde.umd.min.js"></script>
When loaded from a CDN, the library is available as the global variable mgraphHDE.
Usage
ES Modules (Recommended)
javascriptimport createLayout from 'mgraph.hde';
import createGraph from 'mgraph.graph';

// Create your graph
const graph = createGraph();
graph.addLink(1, 2);
graph.addLink(2, 3);
graph.addLink(1, 3);

// Create high-dimensional layout
const layout = createLayout(graph);

// Get 2D positions (default)
const position = layout.getNodePosition(1); // returns [0.39, -0.72]
CommonJS
javascriptconst createLayout = require('mgraph.hde').default;
const createGraph = require('mgraph.graph').default;
TypeScript
typescriptimport createLayout from 'mgraph.hde';
import createGraph from 'mgraph.graph';
import type { Graph } from 'mgraph.graph';

const graph: Graph = createGraph();
graph.addLink(1, 2);
graph.addLink(2, 3);

const layout = createLayout(graph, {
  pivotCount: 50,
  dimensions: 2
});

const position: number[] = layout.getNodePosition(1);
Configuration Options
javascriptconst layout = createLayout(graph, {
  // Number of dimensions in M-dimensional space
  // If larger than node count, node count is used instead
  pivotCount: 50,

  // Number of output dimensions (2D, 3D, etc.)
  // This affects what getNodePosition() returns
  dimensions: 2
});
Integration with Force Layouts
With mgraph.forcelayout
javascriptimport createHDELayout from 'mgraph.hde';
import createForceLayout from 'mgraph.forcelayout';
import createGraph from 'mgraph.graph';

const graph = createGraph();
// ... add nodes and links

// Step 1: Get initial positions from HDE
const hdeLayout = createHDELayout(graph, { dimensions: 2 });

// Step 2: Use HDE positions as starting point for force layout
const forceLayout = createForceLayout(graph);

// Set initial positions from HDE
graph.forEachNode(node => {
  const [x, y] = hdeLayout.getNodePosition(node.id);
  forceLayout.setNodePosition(node.id, x, y);
});

// Step 3: Run force simulation
for (let i = 0; i < 100; i++) {
  forceLayout.step();
}
With D3-Force
javascriptimport * as d3 from 'd3-force';
import createHDELayout from 'mgraph.hde';

const hdeLayout = createHDELayout(graph);
const nodes = [];

graph.forEachNode(node => {
  const [x, y] = hdeLayout.getNodePosition(node.id);
  nodes.push({ id: node.id, x, y });
});

const simulation = d3.forceSimulation(nodes)
  .force('link', d3.forceLink().id(d => d.id))
  .force('charge', d3.forceManyBody())
  .force('center', d3.forceCenter(width / 2, height / 2));
Framework Integration
React
jsximport { useEffect, useState } from 'react';
import createHDELayout from 'mgraph.hde';
import createForceLayout from 'mgraph.forcelayout';

function GraphVisualization({ graph }) {
  const [positions, setPositions] = useState(new Map());
  
  useEffect(() => {
    if (!graph) return;
    
    // Get initial layout from HDE
    const hdeLayout = createHDELayout(graph, { dimensions: 2 });
    const forceLayout = createForceLayout(graph);
    
    // Set HDE positions as starting points
    graph.forEachNode(node => {
      const [x, y] = hdeLayout.getNodePosition(node.id);
      forceLayout.setNodePosition(node.id, x, y);
    });
    
    // Animate force simulation
    const animate = () => {
      const stable = forceLayout.step();
      
      const newPositions = new Map();
      graph.forEachNode(node => {
        const pos = forceLayout.getNodePosition(node.id);
        newPositions.set(node.id, pos);
      });
      setPositions(newPositions);
      
      if (!stable) {
        requestAnimationFrame(animate);
      }
    };
    
    animate();
  }, [graph]);
  
  return (
    <svg width={800} height={600}>
      {Array.from(positions.entries()).map(([nodeId, pos]) => (
        <circle
          key={nodeId}
          cx={pos.x + 400}
          cy={pos.y + 300}
          r={5}
          fill="blue"
        />
      ))}
    </svg>
  );
}
Vue
vue<template>
  <svg width="800" height="600">
    <circle
      v-for="[nodeId, pos] in positions"
      :key="nodeId"
      :cx="pos.x + 400"
      :cy="pos.y + 300"
      r="5"
      fill="blue"
    />
  </svg>
</template>

<script setup>
import { ref, watch } from 'vue';
import createHDELayout from 'mgraph.hde';
import createForceLayout from 'mgraph.forcelayout';

const props = defineProps(['graph']);
const positions = ref(new Map());

watch(() => props.graph, (graph) => {
  if (!graph) return;
  
  const hdeLayout = createHDELayout(graph);
  const forceLayout = createForceLayout(graph);
  
  graph.forEachNode(node => {
    const [x, y] = hdeLayout.getNodePosition(node.id);
    forceLayout.setNodePosition(node.id, x, y);
  });
  
  const animate = () => {
    const stable = forceLayout.step();
    
    const newPositions = new Map();
    graph.forEachNode(node => {
      const pos = forceLayout.getNodePosition(node.id);
      newPositions.set(node.id, pos);
    });
    positions.value = newPositions;
    
    if (!stable) {
      requestAnimationFrame(animate);
    }
  };
  
  animate();
}, { immediate: true });
</script>
Angular
typescriptimport { Component, Input, OnChanges } from '@angular/core';
import createHDELayout from 'mgraph.hde';
import createForceLayout from 'mgraph.forcelayout';
import { Graph } from 'mgraph.graph';

@Component({
  selector: 'app-graph-hde',
  template: `
    <svg width="800" height="600">
      <circle
        *ngFor="let item of positions | keyvalue"
        [attr.cx]="item.value.x + 400"
        [attr.cy]="item.value.y + 300"
        r="5"
        fill="blue">
      </circle>
    </svg>
  `
})
export class GraphHDEComponent implements OnChanges {
  @Input() graph: Graph | null = null;
  positions: Map<any, {x: number, y: number}> = new Map();

  ngOnChanges() {
    if (!this.graph) return;
    
    const hdeLayout = createHDELayout(this.graph);
    const forceLayout = createForceLayout(this.graph);
    
    this.graph.forEachNode(node => {
      const [x, y] = hdeLayout.getNodePosition(node.id);
      forceLayout.setNodePosition(node.id, x, y);
    });
    
    this.animate(forceLayout);
  }
  
  private animate(forceLayout: any) {
    const step = () => {
      const stable = forceLayout.step();
      
      const newPositions = new Map();
      this.graph!.forEachNode(node => {
        const pos = forceLayout.getNodePosition(node.id);
        newPositions.set(node.id, pos);
      });
      this.positions = newPositions;
      
      if (!stable) {
        requestAnimationFrame(step);
      }
    };
    
    step();
  }
}
Large Graph Optimization
For very large graphs, consider using connected components:
javascriptimport createHDELayout from 'mgraph.hde';
import createGraph from 'mgraph.graph';

function findLargestComponent(graph) {
  // Implementation to find largest connected component
  // See: https://github.com/anvaka/ngraph.hde/blob/main/demo/src/lib/findLargestComponent.js
}

const graph = createGraph();
// ... load large graph

// Option 1: Use largest component only
const largestComponent = findLargestComponent(graph);
const layout = createHDELayout(largestComponent);

// Option 2: Process components separately
const components = findAllComponents(graph);
const layouts = components.map(component => createHDELayout(component));
Performance Tips

Adjust pivot count: For graphs with < 50 nodes, reduce pivotCount
Use appropriate dimensions: 2D for visualization, 3D for WebGL/Three.js
Connected components: Process large graphs by components
Initial layout only: Use HDE as starting point, not final layout

javascript// Optimized for different graph sizes
const getOptimalPivotCount = (nodeCount) => {
  if (nodeCount < 20) return Math.min(nodeCount, 10);
  if (nodeCount < 100) return 25;
  if (nodeCount < 1000) return 50;
  return 100;
};

const layout = createHDELayout(graph, {
  pivotCount: getOptimalPivotCount(graph.getNodesCount()),
  dimensions: 2
});
API Reference
createLayout(graph, options?)
Parameters:

graph (Graph) - mgraph.graph instance
options (object, optional) - Configuration options

Options:

pivotCount (number, default: 50) - Dimensions in M-dimensional space
dimensions (number, default: 2) - Output dimensions (2D, 3D, etc.)

Returns:

Layout instance with getNodePosition(nodeId) method

layout.getNodePosition(nodeId)
Parameters:

nodeId (any) - Node identifier

Returns:

number[] - Position array [x, y] for 2D, [x, y, z] for 3D, etc.

Limitations

Connected graphs only: Current version doesn't support multiple disconnected components
Memory usage: High-dimensional embedding uses O(N*M) memory where N is nodes and M is pivotCount
Computation time: O(m*(|V| + |E|)) where m is the number of iterations

Part of the mgraph Ecosystem

mgraph.graph - Core graph data structure
mgraph.events - Event system
mgraph.forcelayout - Force-directed layouts
mgraph.generators - Graph generators
mgraph.fromdot - DOT file parser
mgraph.fromjson - JSON parser
mgraph.hde - High-dimensional embedding ← You are here
mgraph.merge - Object merging utility
mgraph.random - Seeded random numbers

Research
Based on the paper: Graph Drawing by High-Dimensional Embedding by David Harel and Yehuda Koren.
License
This project is released under the BSD 3-Clause License, in compliance with the original ngraph.hde licensing terms. See LICENSE for details.
Contributing
Issues and pull requests are welcome on GitHub.
Credits
Original ngraph.hde by Andrei Kashcha.
Modern mgraph.hde maintained by Michael Feldman.
Support
If you find this library useful, please consider:

⭐ Starring the repository
🐛 Reporting issues
💡 Contributing improvements
📖 Improving documentation


This README provides:
- ✅ Modern branding and attribution
- ✅ Comprehensive usage examples
- ✅ Framework integration (React, Vue, Angular)
- ✅ TypeScript support
- ✅ Performance optimization tips
- ✅ Integration with force layouts
- ✅ Complete API documentation
- ✅ Ecosystem integration
- ✅ Research background
- ✅ Proper licensing information
## Citation

If you use this library in academic work, please cite:

**Algorithm:**
```bibtex
@article{harel2002graph,
  title={Graph drawing by high-dimensional embedding},
  author={Harel, David and Koren, Yehuda},
  journal={Information Visualization},
  volume={1},
  number={1},
  pages={73--88},
  year={2002},
  publisher={SAGE Publications}
}
@software{mgraph_hde,
  title={mgraph.hde: High-dimensional graph embedding for JavaScript},
  author={Feldman, Michael},
  year={2025},
  url={https://github.com/mfeldman143/mgraph.hde}
}

Legal Notes

✅ Research papers: Algorithms from academic papers can be implemented freely
✅ Original code: Properly attributed to Andrei Kashcha
✅ Derivative work: Your modernization is properly credited
✅ License compatibility: BSD 3-Clause is compatible with MIT (original ngraph.hde)