Skip to content

Instantly share code, notes, and snippets.

@zachguo
Created December 17, 2014 00:38
Show Gist options
  • Save zachguo/7037141c11a233327e34 to your computer and use it in GitHub Desktop.
Save zachguo/7037141c11a233327e34 to your computer and use it in GitHub Desktop.
Meteor JS: Reactive D3 Force Layout Graph (minimum example)
<head>
<title>d3test</title>
</head>
<body>
<h3>Meteor JS: Reactive D3 Force Layout Graph (minimum example)</h3>
<h4>Add few nodes and links first, or visualization won't show up.</h4>
<span>New Node</span>
<form id="newnode">
<input type="text" name="title" placeholder="Node title here..." />
<select name="nodetype" id="nodetype">
<option value="type1">type1</option>
<option value="type2">type2</option>
</select>
<button type="submit">Create node!</button>
</form>
<span>New Link</span>
<form id="newlink">
<select name="sourceid" id="sourceid">
{{#each nodes}} {{> nodeoption}} {{/each}}
</select>
<select name="targetid" id="targetid">
{{#each nodes}} {{> nodeoption}} {{/each}}
</select>
<select name="linktype" id="linktype">
<option value="type1">type1</option>
<option value="type2">type2</option>
</select>
<button type="submit">Create link!</button>
</form>
<span>Delete Link</span>
<form id="dellink">
<select name="linkitem" id="linkitem">
{{#each links}} {{> linkoption}} {{/each}}
</select>
<button type="submit">Delete link!</button>
</form>
<span>Delete Node</span>
<form id="delnode">
<select name="nodeitem" id="nodeitem">
{{#each nodes}} {{> nodeoption}} {{/each}}
</select>
<button type="submit">Delete node!</button>
</form>
{{> graphvis}}
</body>
<template name="nodeoption">
<option value="{{_id}}">{{_id}} - {{title}} - {{type}}</option>
</template>
<template name="linkoption">
<option value="{{_id}}">{{_id}} - {{source}} - {{target}} - {{type}}</option>
</template>
<template name="graphvis">
<svg id="graphvis"></svg>
</template>
Nodes = new Mongo.Collection("nodes");
Links = new Mongo.Collection("links");
var LinksToD3Array = function(linksCol, nodesCol) {
var nodes = {};
nodesCol.forEach(function(node) {
nodes[node._id] = node;
});
var result = [];
linksCol.forEach(function(link) {
var tmp = {
source: nodes[link.source],
target: nodes[link.target],
type: link.type,
_id: link._id
};
result.push(tmp);
});
return result;
};
if (Meteor.isClient) {
Template.body.helpers({
nodes: function() {
return Nodes.find({});
},
links: function() {
return Links.find({});
}
});
Template.body.events({
"submit #newnode": function(event) {
Nodes.insert({
title: event.target.title.value,
type: event.target.nodetype.value
});
},
"submit #newlink": function(event) {
Links.insert({
source: event.target.sourceid.value,
target: event.target.targetid.value,
type: event.target.linktype.value
});
},
"submit #delnode": function(event) {
Nodes.remove({
_id: event.target.nodeitem.value
});
},
"submit #dellink": function(event) {
Links.remove({
_id: event.target.linkitem.value
});
}
});
Template.graphvis.rendered = function() {
var width = window.innerWidth,
height = window.innerHeight;
var svg = d3.select("#graphvis")
.attr("width", width)
.attr("height", height);
var render = function(svg, nodesCol, linksCol) {
var nodes = nodesCol,
links = LinksToD3Array(linksCol, nodesCol);
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(100)
.charge(-600)
.start();
// add links
var line = svg.selectAll("line")
.data(force.links())
.enter().append("line")
// differentiate link color by type
.attr('stroke', function(d) {
if (d.type === 'type2') {
return 'gray';
} else {
return 'black';
}
})
// dash link for 'type2' link type
.attr('stroke-dasharray', function(d) {
if (d.type === 'type2') {
return '0,2 2';
}
});
// add nodes
var circle = svg.selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.attr('fill', 'black')
.call(force.drag);
// tick
force.on("tick", function() {
circle.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
});
line.attr('x1', function(d) {
return d.source.x;
})
.attr('y1', function(d) {
return d.source.y;
})
.attr('x2', function(d) {
return d.target.x;
})
.attr('y2', function(d) {
return d.target.y;
});
});
};
Deps.autorun(function() {
// fetch() may cause memory overhead when collection is deep and large
var nodesCol = Nodes.find({}).fetch();
var linksCol = Links.find({}).fetch();
render(svg, nodesCol, linksCol);
});
};
}
if (Meteor.isServer) {
Meteor.startup(function() {
// code to run on server at startup
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment