Socket
Socket
Sign inDemoInstall

3d-force-graph

Package Overview
Dependencies
Maintainers
1
Versions
318
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

3d-force-graph - npm Package Compare versions

Comparing version 0.5.3 to 1.0.0

4

package.json
{
"name": "3d-force-graph",
"version": "0.5.3",
"version": "1.0.0",
"description": "UI component for a 3D force-directed graph using ThreeJS and ngraph.forcelayout3d layout engine",

@@ -34,4 +34,6 @@ "main": "dist/3d-force-graph.js",

"dependencies": {
"ngraph.forcelayout": "~0.1.2",
"ngraph.forcelayout3d": "~0.0.16",
"ngraph.graph": "~0.0.12",
"qwest": "^4.4",
"swc": "^0.1",

@@ -38,0 +40,0 @@ "three": "~0.84"

@@ -35,3 +35,3 @@ # 3D Force-Directed Graph

```
<script src="/path/to/dist/3d-force-graph.js"></script>
<script src="//unpkg.com/3d-force-graph/dist/3d-force-graph.js"></script>
```

@@ -50,11 +50,17 @@ then

.height(<px>)
.jsonUrl(<URL of JSON file to load graph data directly from, as an alternative to specifying graphData directly>)
.graphData(<data>)
.numDimensions(<number of dimensions, between [1,3]. default: 3>)
.nodeRelSize(<(number) node volume per value unit>)
.lineOpacity(<between [0,1]>)
.valAccessor(<function(node) to extract numeric value. default: node.val>)
.nameAccessor(<function(node) to extract name string. default: node.name>)
.colorAccessor(<function(node) to extract color hex number. default: node.color>)
.warmUpTicks(<number of layout engine cycles to run before start rendering. default: 0>)
.coolDownTicks(<# frames to stop engine. default: Infinity>)
.coolDownTime(<ms to stop engine. default: 15000>)
.autoColorBy(<node object accessor property name, only affects nodes without a color field>)
.idField(<node object accessor property name. default: 'id'>)
.valField(<node object accessor property name. default: 'val'>)
.nameField(<node object accessor property name. default: 'name'>)
.colorField(<node object accessor property name. default: 'color'>)
.linkSourceField(<link object accessor property name. default: 'source'>)
.linkTargetField(<link object accessor property name. default: 'target'>)
.warmupTicks(<number of layout engine cycles to run before start rendering. default: 0>)
.cooldownTicks(<# frames to stop engine. default: Infinity>)
.cooldownTime(<ms to stop engine. default: 15000>)
.resetProps()

@@ -67,8 +73,10 @@ ```

{
nodes: {
id1: {
nodes: [
{
id: 'id1',
name: "name1",
val: 1
},
id2: {
{
id: 'id2',
name: "name2",

@@ -78,5 +86,8 @@ val: 10

(...)
},
],
links: [
['id1', 'id2'], // [from, to]
{
source: 'id1',
target: 'id2'
},
(...)

@@ -83,0 +94,0 @@ ]

@@ -7,4 +7,5 @@ import './3d-force-graph.css';

import graph from 'ngraph.graph';
import forcelayout from 'ngraph.forcelayout';
import forcelayout3d from 'ngraph.forcelayout3d';
const ngraph = { graph, forcelayout3d };
const ngraph = { graph, forcelayout, forcelayout3d };

@@ -22,14 +23,20 @@ import * as SWC from 'swc';

new SWC.Prop('height', window.innerHeight),
new SWC.Prop('jsonUrl'),
new SWC.Prop('graphData', {
nodes: {},
links: [] // [from, to]
nodes: [],
links: []
}),
new SWC.Prop('numDimensions', 3),
new SWC.Prop('nodeRelSize', 4), // volume per val unit
new SWC.Prop('lineOpacity', 0.2),
new SWC.Prop('valAccessor', node => node.val),
new SWC.Prop('nameAccessor', node => node.name),
new SWC.Prop('colorAccessor', node => node.color),
new SWC.Prop('warmUpTicks', 0), // how many times to tick the force engine at init before starting to render
new SWC.Prop('coolDownTicks', Infinity),
new SWC.Prop('coolDownTime', 15000) // ms
new SWC.Prop('autoColorBy'),
new SWC.Prop('idField', 'id'),
new SWC.Prop('valField', 'val'),
new SWC.Prop('nameField', 'name'),
new SWC.Prop('colorField', 'color'),
new SWC.Prop('linkSourceField', 'source'),
new SWC.Prop('linkTargetField', 'target'),
new SWC.Prop('warmupTicks', 0), // how many times to tick the force engine at init before starting to render
new SWC.Prop('cooldownTicks', Infinity),
new SWC.Prop('cooldownTime', 15000) // ms
],

@@ -42,6 +49,6 @@

// Add nav info section
const navInfo = document.createElement('div');
navInfo.classList.add('graph-nav-info');
navInfo.innerHTML = "MOVE mouse &amp; press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan";
domNode.appendChild(navInfo);
let navInfo;
domNode.appendChild(navInfo = document.createElement('div'));
navInfo.className = 'graph-nav-info';
navInfo.textContent = "MOVE mouse & press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan";

@@ -80,14 +87,14 @@ // Setup tooltip

// Setup renderer
state.renderer = new THREE.WebGLRenderer();
domNode.appendChild(state.renderer.domElement);
// Setup scene
const scene = new THREE.Scene();
scene.add(state.graphScene = new THREE.Group());
// Setup camera
state.camera = new THREE.PerspectiveCamera();
state.camera.far = 20000;
state.camera.position.z = 1000;
// Setup scene
state.scene = new THREE.Scene();
// Setup renderer
state.renderer = new THREE.WebGLRenderer();
domNode.appendChild(state.renderer.domElement);
// Add camera interaction

@@ -104,8 +111,9 @@ const tbControls = new THREE.TrackballControls(state.camera, state.renderer.domElement);

raycaster.setFromCamera(mousePos, state.camera);
const intersects = raycaster.intersectObjects(state.scene.children);
toolTipElem.innerHTML = intersects.length ? intersects[0].object.name || '' : '';
const intersects = raycaster.intersectObjects(state.graphScene.children)
.filter(o => o.object.name); // Check only objects with labels
toolTipElem.textContent = intersects.length ? intersects[0].object.name : '';
// Frame cycle
tbControls.update();
state.renderer.render(state.scene, state.camera);
state.renderer.render(scene, state.camera);
requestAnimationFrame(animate);

@@ -115,53 +123,62 @@ })();

update: state => {
update: function updateFn(state) {
resizeCanvas();
state.onFrame = null; // Clear previous frame hook
state.scene = new THREE.Scene(); // Clear the place
state.onFrame = null; // Pause simulation
// Build graph with data
const graph = ngraph.graph();
for (let nodeId in state.graphData.nodes) {
graph.addNode(nodeId, state.graphData.nodes[nodeId]);
if (!state.fetchingJson && state.jsonUrl && !state.graphData.nodes.length && !state.graphData.links.length) {
// (Re-)load data
state.fetchingJson = true;
qwest.get(state.jsonUrl).then((_, json) => {
state.fetchingJson = false;
state.graphData = json;
updateFn(state); // Force re-update
});
}
for (let link of state.graphData.links) {
graph.addLink(...link, {});
}
// Auto add color to uncolored nodes
autoColorNodes(state.graphData.nodes, state.autoColorBy, state.colorField);
// parse links
state.graphData.links.forEach(link => {
link.source = link[state.linkSourceField];
link.target = link[state.linkTargetField];
});
// Add WebGL objects
graph.forEachNode(node => {
const nodeMaterial = new THREE.MeshBasicMaterial({ color: state.colorAccessor(node.data) || 0xffffaa, transparent: true });
while (state.graphScene.children.length) { state.graphScene.remove(state.graphScene.children[0]) } // Clear the place
state.graphData.nodes.forEach(node => {
const nodeMaterial = new THREE.MeshBasicMaterial({ color: node[state.colorField] || 0xffffaa, transparent: true });
nodeMaterial.opacity = 0.75;
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(Math.cbrt(state.valAccessor(node.data) || 1) * state.nodeRelSize, 8, 8),
new THREE.SphereGeometry(Math.cbrt(node[state.valField] || 1) * state.nodeRelSize, 8, 8),
nodeMaterial
);
sphere.name = state.nameAccessor(node.data) || '';
state.scene.add(node.data.sphere = sphere)
sphere.name = node[state.nameField]; // Add label
state.graphScene.add(node.__sphere = sphere);
});
const lineMaterial = new THREE.MeshBasicMaterial({ color: 0xf0f0f0, transparent: true });
const lineMaterial = new THREE.LineBasicMaterial({ color: 0xf0f0f0, transparent: true });
lineMaterial.opacity = state.lineOpacity;
graph.forEachLink(link => {
const line = new THREE.Line(new THREE.Geometry(), lineMaterial),
fromName = getNodeName(link.fromId),
toName = getNodeName(link.toId);
if (fromName && toName) { line.name = `${fromName} > ${toName}`; }
state.graphData.links.forEach(link => {
const line = new THREE.Line(new THREE.Geometry(), lineMaterial);
line.geometry.vertices=[new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,0)];
state.scene.add(link.data.line = line);
function getNodeName(nodeId) {
return state.nameAccessor(graph.getNode(nodeId).data);
}
state.graphScene.add(link.__line = line);
});
state.camera.lookAt(state.scene.position);
state.camera.position.z = Math.cbrt(Object.keys(state.graphData.nodes).length) * CAMERA_DISTANCE2NODES_FACTOR;
state.camera.lookAt(state.graphScene.position);
state.camera.position.z = Math.cbrt(state.graphData.nodes.length) * CAMERA_DISTANCE2NODES_FACTOR;
// Add force-directed layout
const layout = ngraph.forcelayout3d(graph);
const graph = ngraph.graph();
state.graphData.nodes.forEach(node => { graph.addNode(node[state.idField]); });
state.graphData.links.forEach(link => { graph.addLink(link.source, link.target); });
const layout = ngraph['forcelayout' + (state.numDimensions === 2 ? '' : '3d')](graph);
for (let i=0; i<state.warmUpTicks; i++) { layout.step(); } // Initial ticks before starting to render
for (let i=0; i<state.warmupTicks; i++) { layout.step(); } // Initial ticks before starting to render

@@ -183,3 +200,3 @@ let cntTicks = 0;

function layoutTick() {
if (cntTicks++ > state.coolDownTicks || (new Date()) - startTickTime > state.coolDownTime) {
if (cntTicks++ > state.cooldownTicks || (new Date()) - startTickTime > state.cooldownTime) {
state.onFrame = null; // Stop ticking graph

@@ -191,19 +208,18 @@ }

// Update nodes position
graph.forEachNode(node => {
const sphere = node.data.sphere,
pos = layout.getNodePosition(node.id);
state.graphData.nodes.forEach(node => {
const sphere = node.__sphere,
pos = layout.getNodePosition(node[state.idField]);
sphere.position.x = pos.x;
sphere.position.y = pos.y;
sphere.position.z = pos.z;
sphere.position.y = pos.y || 0;
sphere.position.z = pos.z || 0;
});
// Update links position
graph.forEachLink(link => {
const line = link.data.line,
pos = layout.getLinkPosition(link.id);
state.graphData.links.forEach(link => {
const line = link.__line,
pos = layout.getLinkPosition(graph.getLink(link.source, link.target).id);
line.geometry.vertices = [
new THREE.Vector3(pos.from.x, pos.from.y, pos.from.z),
new THREE.Vector3(pos.to.x, pos.to.y, pos.to.z)
new THREE.Vector3(pos.from.x, pos.from.y || 0, pos.from.z || 0),
new THREE.Vector3(pos.to.x, pos.to.y || 0, pos.to.z || 0)
];

@@ -215,4 +231,21 @@

}
function autoColorNodes(nodes, colorBy, colorField) {
if (!colorBy) return;
// Color brewer paired set
const colors = ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928'];
const uncoloredNodes = nodes.filter(node => !node[colorField]),
nodeGroups = {};
uncoloredNodes.forEach(node => { nodeGroups[node[colorBy]] = null });
Object.keys(nodeGroups).forEach((group, idx) => { nodeGroups[group] = idx });
uncoloredNodes.forEach(node => {
node[colorField] = parseInt(colors[nodeGroups[node[colorBy]] % colors.length].slice(1), 16);
});
}
}
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc