Skip to content

Commit cc3b618

Browse files
committed
re-implement tree
1 parent eea7d06 commit cc3b618

File tree

3 files changed

+184
-68
lines changed

3 files changed

+184
-68
lines changed

css/style.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ header > button:hover {
123123
display:block;
124124
}
125125
.modal svg {
126-
background:blue;
127126
width:100%;
128127
height:100%;
129128
position:absolute;

js/components/link.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,6 @@ class Link {
522522
}
523523

524524
get val() {
525-
return this.reltype || this.trigger.val;
525+
return this.reltype || this.trigger.reltype || (this.trigger.tag && this.trigger.tag.val) || this.trigger.val;
526526
}
527527
}

js/treelayout.js

Lines changed: 183 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -56,30 +56,18 @@ const TreeLayout = (function() {
5656
return data;
5757
};
5858

59-
// tree layout function
60-
const tree = d3.tree()
61-
.nodeSize([30,80])
62-
.separation((a,b) => {
63-
let separation = a.parent == b.parent ? 1 : 2;
64-
function reduce(acc, val) {
65-
if (val.expanded) { return acc + val.size; }
66-
return acc + 1;
67-
}
68-
separation += Math.max(b.data.incoming.reduce(reduce, 0), a.data.incoming.reduce(reduce, 0)) / 2;
69-
return separation;
70-
});
71-
7259
class TreeLayout {
7360
constructor(el) {
7461
// container element
7562
this.svg = d3.select(el);
76-
this.g = this.svg.append('g');
63+
this.draggable = this.svg.append('g');
64+
this.g = this.draggable.append('g');
7765

7866
// add zoom/pan events
7967
this.svg.call(d3.zoom()
8068
.scaleExtent([1 / 2, 4])
8169
.on("zoom", () => {
82-
this.g.attr('transform', d3.event.transform);
70+
this.draggable.attr('transform', d3.event.transform);
8371
}))
8472
.on("dblclick.zoom", null);
8573

@@ -88,6 +76,8 @@ const TreeLayout = (function() {
8876
this.maxDepth = 20; // default value for max dist from root
8977
}
9078
resize() {
79+
// let bounds = this.svg.node().getBoundingClientRect();
80+
// this.g.attr('transform', `translate(${bounds.width / 2}, 30)`);// ${bounds.height / 2})`);
9181
}
9282
clear() {
9383
this.word = null;
@@ -98,27 +88,191 @@ const TreeLayout = (function() {
9888
* construct a set of hierarchies from an array of
9989
* Word or Link "root" nodes
10090
*/
101-
graph(word) {
102-
this.word = word;
91+
graph(selected) {
92+
this.resize();
10393

10494
maxDepth = this.maxDepth;
105-
let sum = 0;
106-
this.incoming = [];
107-
this.data = (function() {
108-
const seed = addNode(word, 0, null);
10995

110-
const root = d3.hierarchy(seed);
96+
let data = [];
97+
98+
function addNode(node, source = null, depth = 0) {
99+
let data = {
100+
node,
101+
depth,
102+
children: [],
103+
siblings: []
104+
};
105+
106+
if (depth < maxDepth) {
107+
let links = node.links.filter(l => l.top);
108+
let args = [];
109+
let corefs = links.filter(x => !x.trigger)
110+
.map(coref => {
111+
return {
112+
type: coref.reltype,
113+
args: coref.arguments.filter(x => x.anchor !== node && x.anchor !== source)
114+
.map(x => addNode(x.anchor, node, depth))
115+
};
116+
});
111117

112-
const data = {
113-
root,
114-
tree: tree(root),
115-
offset: sum
116-
};
118+
if (node instanceof Word) {
119+
args = links.filter(x => x.trigger === node);
120+
}
121+
else if (node instanceof Link) {
122+
args = node.arguments.map(x => x.anchor);
123+
}
117124

118-
return data;
125+
data.children = args.map(arg => addNode(arg, data, depth + 1));
126+
data.siblings = corefs;
127+
}
128+
129+
return data;
130+
}
131+
132+
let hierarchy = addNode(selected);
133+
134+
let [nodes, links] = (function() {
135+
let nodes = [];
136+
let links = [];
137+
138+
function flatten(node) {
139+
nodes.push(node);
140+
node.siblings.forEach(sibling => {
141+
sibling.args.forEach(arg => {
142+
flatten(arg);
143+
links.push({
144+
type: 'sibling',
145+
label: sibling.type,
146+
source: node,
147+
target: arg
148+
});
149+
});
150+
});
151+
node.children.forEach(child => {
152+
flatten(child);
153+
links.push({
154+
type: 'child',
155+
source: node,
156+
target: child
157+
})
158+
});
159+
}
160+
flatten(hierarchy);
161+
162+
return [nodes, links];
119163
})();
120164

121-
this.updateGraph();
165+
166+
let maxWidth = 0;
167+
let layers = [];
168+
nodes.forEach(node => {
169+
layers[node.depth] = layers[node.depth] || [];
170+
layers[node.depth].push(node);
171+
});
172+
173+
function shiftSubtree(node, dx, root) {
174+
node.offset += dx;
175+
if (node.offset > maxWidth) { maxWidth = node.offset; }
176+
if (!root) {
177+
node.siblings.forEach(node => shiftSubtree(node, dx))
178+
}
179+
node.children.forEach(node => shiftSubtree(node, dx));
180+
}
181+
for (let i = layers.length - 1; i >= 0; --i) {
182+
layers[i].forEach((node, j) => {
183+
// 1st pass: assign an initial offset according to children
184+
if (node.children.length > 0) {
185+
let leftChild = node.children[0];
186+
let rightChild = node.children[node.children.length - 1];
187+
node.offset = (leftChild.offset + rightChild.offset) / 2;
188+
}
189+
else if (j > 0) {
190+
node.offset = layers[i][j - 1].offset;
191+
}
192+
else {
193+
node.offset = 0;
194+
}
195+
});
196+
197+
// 2nd pass: check that subtree doesn't collide with left tree
198+
function computeWidth(word, svg) {
199+
let text = svg.append('text').text(word);
200+
let length = text.node().getComputedTextLength();
201+
text.remove();
202+
return length;
203+
}
204+
205+
layers[i].forEach((node, j) => {
206+
node.width = computeWidth(node.node.val, this.svg);
207+
if (j > 0) {
208+
const prev = layers[i][j - 1];
209+
const separation = prev.siblings.some(sibling => sibling.args.indexOf(node) > -1) ? 50 : 20; // TODO: make more universal
210+
211+
let dx = prev.offset + prev.width / 2 + node.width / 2 - node.offset + separation;
212+
if (dx > 0) {
213+
// shift subtree and right-ward trees by dx
214+
for (let k = j; k < layers[i].length; ++k) {
215+
shiftSubtree(layers[i][k], dx, true);
216+
}
217+
}
218+
}
219+
if (node.offset > maxWidth) { maxWidth = node.offset; }
220+
});
221+
}// end for
222+
223+
224+
let nodeSVG = this.g.selectAll('.node')
225+
.data(nodes, d => d.node);
226+
227+
let edgeSVG = this.g.selectAll('.edge')
228+
.data(links, d => d.parent);
229+
230+
//layout constants
231+
const rh = 50; // row height
232+
nodeSVG.exit().remove();
233+
nodeSVG.enter().append('text')
234+
.attr('class','node')
235+
.attr('text-anchor', 'middle')
236+
.attr('transform', d => 'translate(' + [d.offset, d.depth * rh] + ')')
237+
.merge(nodeSVG)
238+
.text(d => d.node.val)
239+
.transition()
240+
.attr('transform', d => 'translate(' + [d.offset, d.depth * rh] + ')');
241+
242+
// resize
243+
let bounds = this.svg.node().getBoundingClientRect();
244+
this.g.attr('transform', 'translate(' + [bounds.width / 2 - maxWidth / 2, bounds.height / 2 - layers.length * rh / 2] + ')');
245+
246+
edgeSVG.exit().remove();
247+
edgeSVG.enter().append('path')
248+
.attr('class', 'edge')
249+
.attr('stroke', 'grey')
250+
.attr('stroke-width', '1px')
251+
.attr('fill','none')
252+
.merge(edgeSVG)
253+
.attr('d', d => {
254+
if (d.type === 'sibling') {
255+
let x1, x2;
256+
if (d.target.offset > d.source.offset) {
257+
x1 = d.source.offset + d.source.width / 2;
258+
x2 = d.target.offset - d.target.width / 2;
259+
}
260+
else {
261+
x1 = d.target.offset + d.target.width / 2;
262+
x2 = d.source.offset - d.source.width / 2;
263+
}
264+
return 'M' + [x1 - 10, d.source.depth * rh + 5]
265+
+ 'v7h' + (x2 - x1 + 20) + 'v-7';
266+
}
267+
else if (d.type === 'child') {
268+
return 'M' + [d.source.offset, d.source.depth * rh + 5]
269+
+ 'C' + [
270+
d.source.offset, d.source.depth * rh + 25,
271+
d.target.offset, d.target.depth * rh - 40,
272+
d.target.offset, d.target.depth * rh - 15
273+
];
274+
}
275+
})
122276
}
123277

124278
updateIncoming(data, index) {
@@ -144,43 +298,6 @@ const TreeLayout = (function() {
144298
node.y += dy;
145299
});
146300

147-
/* console.log('----- range',d3.extent(this.data[index].root.descendants(), d => d.x));
148-
console.log('graft range',d3.extent(root.descendants(), d => d.x));
149-
console.log(data.anchor.x, dx);
150-
*/
151-
// -------- in progress
152-
// test case : Pos_reg --> graft "outside"
153-
// test case : Promotes --> graft "inside"
154-
// test case : Ubiquitination --> graft left
155-
// test case : Phosphorylation --> two
156-
/* let graftLeftOfRoot = data.anchor.x < this.data[index].root.x;
157-
console.log(root.descendants());
158-
// rearrange old tree to not interfere with graft
159-
let range = d3.extent(root.leaves().concat(data.anchor), d => d.x);
160-
console.log(range);
161-
console.log(this.data[index].root.descendants().map(d => d.x));
162-
let children = data.anchor.descendants();
163-
let offset = Number.MIN_SAFE_INTEGER;
164-
this.data[index].root.descendants().forEach(node => {
165-
// not a shared branch
166-
if (children.indexOf(node) < 0) {
167-
if (node.x <= range[1] && node.x >= range[0]) {
168-
offset = Math.max(offset, node.x);
169-
}
170-
}
171-
});
172-
offset = data.anchor.x - offset;
173-
console.log(offset);
174-
this.data[index].root.descendants().forEach(node => {
175-
if (children.indexOf(node) < 0) {
176-
if (node.x <= range[1] && node.x >= range[0]) {
177-
node.x -= offset;
178-
}
179-
}
180-
})
181-
*/
182-
// ------ end testing
183-
184301
this.data.push({
185302
index,
186303
root,

0 commit comments

Comments
 (0)