Skip to content

Instantly share code, notes, and snippets.

@mlent
Last active September 26, 2015 16:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mlent/4a08236e3d07514357c3 to your computer and use it in GitHub Desktop.
Save mlent/4a08236e3d07514357c3 to your computer and use it in GitHub Desktop.
var parsetree = function(selector, options) {
this.el = document.querySelector(selector);
this.options = options || {};
if (this.el == null)
console.log("Could not find DOM object");
return this;
};
// For our purposes, I'll hardcode our data in.
parsetree.prototype.init = function() {
words = [
{ id: 1, head: 3, relation: "OBJ", value: "ταῦτα" },
{ id: 2, head: 3, relation: "AuxY", value: "γὰρ" },
{ id: 3, head: 0, relation: "PRED", value: "εἶχον", },
{ id: 4, head: 3, relation: "SBJ", value: "Ἀθηναῖοι" },
{ id: 5, head: 1, relation: "ATR", value: "Πελοποννησίων" },
{ id: 6, head: 0, relation: "AuxK", value: "." }
];
// We'll convert our flat word object into hierarchical data -- read on to find out how!
this.data = this.convertData(words);
this.render();
return this;
};
parsetree.prototype.convertData = function(words) {
// Create a root node
var rootNode = { 'id': 0, 'value': 'root', 'pos': 'root' };
words.push(rootNode);
var dataMap = words.reduce(function(map, node) {
map[node.id] = node;
return map;
}, {});
var treeData = [];
words.forEach(function(node) {
var head = dataMap[node.head];
// Then, create the hierarchical data d3 needs
if (head)
(head.children || (head.children = [])).push(node);
else
treeData.push(node);
});
return treeData;
};
parsetree.prototype.render = function () {
// To keep multiple instances from stomping on each other's data/d3 references
this.tree = d3.layout.tree().nodeSize([100, 50]);
// Tell our tree how to decide how to separate the nodes
this.tree.separation(function (a, b) {
var w1 = (a.value.length > a.relation.length) ? a.value.length : a.relation.length;
var w2 = (b.value.length > b.relation.length) ? b.value.length : b.relation.length;
var scale = 0.13;
return Math.ceil((w1 * scale) + (w2 * scale) / 2);
});
// Create our SVG elements
// this.svg is our reference to the parent SVG element
this.svg = d3.select(this.el).append('svg')
.attr('class', 'svg-container')
.style('width', 700)
.style('overflow', 'auto');
// this.canvas is the group () that the actual tree goes into
this.canvas = this.svg.append('g')
.attr('class', 'canvas');
// and we nest another one inside to allow zooming and panning
this.canvas.append('g')
.attr('transform', 'translate(' + (this.options.width || 500) + ', ' + (this.options.marginTop || 10) + ') scale(' + (this.options.initialScale || .8) + ')');
// And at last, we tell the tree to consider our data.
this.root = this.data[0];
// this.update is called whenever our data changes
this.update(this.root);
return this;
};
parsetree.prototype.update = function (source) {
// This function tells our tree to be oriented vertically instead of horizontally
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x, d.y];
});
var nodes = this.tree(this.root).reverse(),
links = this.tree.links(nodes);
nodes.forEach(function (d) {
d.y = d.depth * 100;
});
var node = this.svg.select('.canvas g')
.selectAll('g.node')
.data(nodes, function (d, i) {
return d.id || (d.id = ++i);
});
var nodeEnter = node.enter()
.append('g')
.attr('class', 'node')
.attr('transform', function (d) {
return 'translate(' + source.x + ', ' + source.y + ')';
});
nodeEnter.append('circle')
.attr('r', 10)
.style('stroke', '#000')
.style('stroke-width', '3px')
.style('fill', '#FFF');
// Our Greek Word
nodeEnter.append('text')
.attr('y', function (d, i) {
return (d.pos == 'root') ? -30 : 15;
})
.attr('dy', '14px')
.attr('text-anchor', 'middle')
.text(function (d) {
return d.value;
})
.style('fill', function (d, i) {
return (d.pos == 'root') ? '#CCC' : '#333';
})
.style('font-family', 'Cambria, Serif')
.style('letter-spacing', '2px')
.style('font-size', '18px')
.style('fill-opacity', 1);
// Relation of Node to Parent
nodeEnter.append('text')
.attr('y', function (d, i) {
return (d.pos == 'root') ? 0 : -30;
})
.attr('dy', '12px')
.attr('text-anchor', 'middle')
.attr('class', 'label')
.style('font-family', 'sans-serif')
.style('font-size', '12px')
.style('font-weight', 500)
.style('letter-spacing', '1px')
.style('fill', '#666')
.text(function (d) {
return d.relation;
});
var nodeUpdate = node.transition()
.duration(this.options.duration || 500)
.attr('transform', function (d) {
return 'translate(' + d.x + ', ' + d.y + ')';
});
var link = this.svg.select('.canvas g')
.selectAll('path.link')
.data(links, function (d) {
return d.target.id;
});
link.enter()
.insert('path', 'g')
.attr('class', 'link')
.style('stroke', '#CCC')
.style('stroke-width', '2px')
.style('fill', 'none')
.attr('d', function (d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
});
link.transition()
.duration(this.options.duration || 500)
.attr('d', diagonal);
nodes.forEach(function (d, i) {
d.x0 = d.x;
d.y0 = d.y;
});
};
new parsetree('div[data-toggle="parsetree"]').init();
<body>
<div data-toggle="parsetree"></div>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment