Skip to content

Commit 3cd8874

Browse files
committed
latest changes
1 parent 54ed9ab commit 3cd8874

File tree

7 files changed

+245
-66
lines changed

7 files changed

+245
-66
lines changed

css/style2.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ svg text {
2727

2828
#tooltip {
2929
width:175px;
30-
background:white;
30+
background:#f6f6f6;
3131
margin-bottom:10px;
3232
box-shadow: 0 0 10px #555;
3333
border-radius:3px;
@@ -119,3 +119,6 @@ svg text {
119119
font-size: 12px;
120120
fill:#6590b4;
121121
}
122+
.link:hover {
123+
stroke-width:2px;
124+
}

js2/components/link.js

Lines changed: 215 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,21 @@ class Link {
66
this.links = [];
77
this.reltype = reltype;
88

9-
if (this.trigger) { this.trigger.links.push(this); }
10-
this.arguments.forEach(arg => arg.anchor.links.push(this));
9+
this.slot = 0;
10+
11+
if (this.trigger) {
12+
this.trigger.links.push(this);
13+
this.slot = this.trigger.slot;
14+
}
15+
this.arguments.forEach(arg => {
16+
arg.anchor.links.push(this)
17+
if (arg.anchor.slot > this.slot) {
18+
this.slot = arg.anchor.slot;
19+
}
20+
});
21+
22+
this.slot += 1;
23+
this.endpoints = this.getEndpoints();
1124

1225
this.mainSVG = null;
1326
this.svg = null;
@@ -16,38 +29,50 @@ class Link {
1629
this.svgTexts = [];
1730
}
1831

19-
init(svg) {
32+
init(svg, words) {
2033
this.mainSVG = svg;
2134
this.svg = svg.group().addClass('link');
2235

36+
this.recalculateSlots(words);
37+
2338
// init handles
2439
const s = 4;
40+
41+
// draw trigger
2542
if (this.trigger) {
2643
// draw a diamond at the location of the trigger
27-
let x = this.trigger.cx;
44+
let offset = this.trigger.links.indexOf(this);
45+
let x = this.trigger.cx + 8 * offset;
2846
let y = this.trigger.absoluteY;
47+
2948
let handle = this.svg.path(`M${s},0L0,${s}L${-s},0L0,${-s}Z`)
3049
.x(x - s)
31-
.y(y);
32-
this.handles.push({ anchor: this.trigger, handle, x, y });
50+
.y(y - s);
51+
this.handles.push({ anchor: this.trigger, handle, x, y, offset });
3352
}
53+
54+
// draw arguments
3455
this.arguments.forEach(arg => {
3556
// draw a triangle at the location of the argument
36-
let x = arg.anchor.cx;
57+
let offset = arg.anchor.links.indexOf(this);
58+
let x = arg.anchor.cx + 8 * offset;
3759
let y = arg.anchor.absoluteY;
3860

3961
let handle = this.svg.path(`M${[s, -s/2]}L${[-s, -s/2]}L0,${s}`)
4062
.x(x - s)
41-
.y(y);
42-
this.handles.push({ anchor: arg.anchor, handle, x, y });
63+
.y(y - s);
64+
this.handles.push({ anchor: arg.anchor, handle, x, y, offset });
4365

66+
// draw svgText for each trigger-argument relation
4467
if (this.trigger) {
4568
let text = this.svg.text(arg.type)
4669
.y(-7)
4770
.addClass('link-text');
4871
this.svgTexts.push(text);
4972
}
5073
});
74+
75+
// draw svgText for a non-trigger relation
5176
if (this.reltype) {
5277
let text = this.svg.text(this.reltype)
5378
.y(-7)
@@ -58,81 +83,159 @@ class Link {
5883
this.line = this.svg.path()
5984
.addClass('polyline');
6085
this.draw();
61-
62-
//TODO: sort links and offset according to # of links the anchor has
6386
}
6487

6588
draw(anchor) {
6689
const s = 4;
90+
// redraw handles if word or link was moved
6791
this.handles.forEach(h => {
6892
if (anchor === h.anchor) {
69-
h.x = anchor.cx;
93+
h.x = anchor.cx + 8 * h.offset;
7094
h.y = anchor.absoluteY;
7195
h.handle
7296
.x(h.x - s)
73-
.y(h.y);
97+
.y(h.y - s);
7498
}
75-
})
99+
});
100+
101+
// redraw line if it exists
76102
if (this.line) {
103+
let width = this.mainSVG.width();
77104
let d = '';
105+
106+
// draw a polyline between the trigger and each of its arguments
78107
if (this.trigger) {
79-
// draw a polyline between the trigger and each of its arguments
108+
let y = this.getY(this.handles[1]);
109+
let rowCrossed = false;
110+
80111
for (let i = 0, il = this.arguments.length; i < il; ++i) {
81112
let leftOfTrigger = this.arguments[i].anchor.idx < this.trigger.idx;
82113
let dx = leftOfTrigger ? 5 : -5;
83114
let textlen = leftOfTrigger ? this.svgTexts[i].length() : -this.svgTexts[i].length();
84115

116+
let handle1 = this.handles[i + 1];
117+
85118
// draw a line from the prev arrow segment
86119
if (i > 0) {
120+
// check if crossing over a row
121+
if (rowCrossed) {
122+
rowCrossed = false;
123+
d += 'L' + [width, y] + 'M0,';
124+
y = this.getY(handle1);
125+
d += y;
126+
}
87127
if (leftOfTrigger) {
88-
d += 'L' + [this.handles[i + 1].x + dx, this.handles[i + 1].y - 10]
128+
d += 'L' + [handle1.x + dx, y];
89129
}
90130
else {
91-
d += 'L' + [this.handles[i + 1].x + dx + textlen, this.handles[i + 1].y - 10]
131+
d += 'L' + [handle1.x + dx + textlen, y];
92132
}
93133
}
94134
else if (!leftOfTrigger) {
95135
// start drawing from the trigger
136+
y = this.getY(this.handles[0]);
96137
d += 'M' + [this.handles[0].x, this.handles[0].y]
97-
+ 'c' + [0, -10, 0, -10, -dx, -10]
98-
+ 'L' + [this.handles[1].x + dx + textlen, this.handles[1].y - 10];
138+
+ 'C' + [this.handles[0].x, y, this.handles[0].x, y, this.handles[0].x - dx, y];
139+
140+
// check if crossing over a row
141+
if (this.handles[0].anchor.row.idx < this.handles[1].anchor.row.idx) {
142+
d += 'L' + [width, y] + 'M0,';
143+
y = this.getY(this.handles[1]);
144+
d += y;
145+
}
146+
d += 'L' + [this.handles[1].x + dx + textlen, y];
99147
}
100148

149+
// draw the text svg
150+
this.svgTexts[i]
151+
.x(handle1.x + dx + textlen / 2)
152+
.y(y - 10);
153+
154+
let handlePrecedesTrigger = leftOfTrigger && (i + 2 > il || this.arguments[i + 1].anchor.idx >= this.trigger.idx);
155+
156+
// check if crossing over a row
157+
rowCrossed = (handlePrecedesTrigger && this.handles[0].anchor.row.idx != handle1.anchor.row.idx) || (!handlePrecedesTrigger && i + 1 < il && this.handles[i + 2].anchor.row.idx != handle1.anchor.row.idx);
158+
101159
// draw an arrow segment coming from each argument
102-
d += 'M' + [this.handles[i + 1].x, this.handles[i + 1].y]
103-
+ 'c' + [0, -10, 0, -10, dx, -10];
104-
if (leftOfTrigger) {
105-
d += 'm' + [textlen, 0];
160+
if (handlePrecedesTrigger && rowCrossed) {
161+
// if row is crossed
162+
let tempY = this.getY(handle1);
163+
y = this.getY(this.handles[0]);
164+
165+
d += 'M' + [handle1.x, handle1.y]
166+
+ 'C' + [handle1.x, tempY, handle1.x, tempY, handle1.x + dx, tempY]
167+
+ 'm' + [textlen, 0]
168+
+ 'L' + [width, tempY]
169+
+ 'M' + [0,y];
170+
rowCrossed = false;
171+
172+
this.svgTexts[i].y(tempY - 10);
173+
}
174+
else {
175+
d += 'M' + [handle1.x, handle1.y]
176+
+ 'C' + [handle1.x, y, handle1.x, y, handle1.x + dx, y];
177+
if (leftOfTrigger) {
178+
d += 'm' + [textlen, 0];
179+
}
106180
}
107181

108-
if (leftOfTrigger && (i + 2 > il || this.arguments[i + 1].anchor.idx >= this.trigger.idx)) {
182+
if (handlePrecedesTrigger) {
109183
// draw trigger to the right of the arrow segment
110-
d += 'L' + [this.handles[0].x - dx, this.handles[0].y - 10]
111-
+ 'c' + [dx, 0, dx, 0, dx, 10];
184+
d += 'L' + [this.handles[0].x - dx, y]
185+
+ 'c' + [dx, 0, dx, 0, dx, this.handles[0].y - y];
112186
if (i + 1 < il) {
113-
d += 'c' + [0, -10, 0, -10, dx, -10];
187+
rowCrossed = this.handles[i + 2].anchor.row.idx != this.handles[0].anchor.row.idx;
188+
d += 'C' + [this.handles[0].x, y, this.handles[0].x, y, this.handles[0].x + dx, y];
114189
}
115190
}
116-
117-
// draw the text svg
118-
this.svgTexts[i]
119-
.x(this.handles[i + 1].x + dx + textlen / 2)
120-
.y(this.handles[i + 1].y - 10 - 7);
121191
}
122192
}
123193
else if (this.reltype) {
124-
let avg = this.arguments.reduce((acc, a) => acc + a.anchor.cx, 0) / this.arguments.length;
194+
195+
// draw lines between a non-trigger relationship
196+
let y = this.getY(this.handles[0]);
197+
let endHandle = this.handles[this.handles.length - 1];
198+
let textlen = this.svgTexts[0].length();
199+
let avg;
200+
201+
if (this.handles[0].anchor.row.idx === endHandle.anchor.row.idx) {
202+
avg = this.arguments.reduce((acc, a) => acc + a.anchor.cx, 0) / this.arguments.length;
203+
d = 'M' + [this.handles[0].x, this.handles[0].y]
204+
+ 'C' + [this.handles[0].x, y, this.handles[0].x, y, this.handles[0].x + 5, y]
205+
+ 'L' + [avg - textlen / 2, y]
206+
+ 'm' + [textlen, 0]
207+
+ 'L' + [endHandle.x - 5, y]
208+
+ 'C' + [endHandle.x, y, endHandle.x, y, endHandle.x, endHandle.y];
209+
}
210+
else {
211+
avg = (this.handles[0].x + width) / 2;
212+
d = 'M' + [this.handles[0].x, this.handles[0].y]
213+
+ 'C' + [this.handles[0].x, y, this.handles[0].x, y, this.handles[0].x + 5, y]
214+
+ 'L' + [avg - textlen / 2, y]
215+
+ 'm' + [textlen, 0]
216+
+ 'L' + [width, y];
217+
218+
let tempY = this.getY(endHandle);
219+
d += 'M0,' + tempY
220+
+ 'L' + [endHandle.x - 5, tempY]
221+
+ 'C' + [endHandle.x, tempY, endHandle.x, tempY, endHandle.x, endHandle.y];
222+
}
125223
this.svgTexts[0].x(avg)
126-
.y(this.arguments[0].anchor.absoluteY - 10 - 7);
127-
// d = 'M' + [this.arguments[0].anchor.cx, this.arguments[0].anchor.absoluteY]
128-
// + 'L' + [this.arguments[1].anchor.cx, this.arguments[1].anchor.absoluteY];
224+
.y(y - 10);
225+
129226
}
130227
this.line.plot(d);
131228
}
132229

133230
this.links.forEach(l => l.draw(this));
134231
}
135232

233+
// helper function to calculate line-height in draw()
234+
getY(handle) {
235+
let r = handle.anchor.row;
236+
return r.rh + r.ry - 45 - 15 * this.slot;
237+
}
238+
136239
remove() {
137240
this.svg.remove();
138241

@@ -150,38 +253,91 @@ class Link {
150253
this.arguments.forEach(arg => detachLink(arg.anchor));
151254
}
152255

153-
hasAnchor(a) {
154-
if (this.trigger && a === this.trigger) { return true; }
155-
return this.arguments.find(arg => arg.anchor === a);
156-
}
256+
recalculateSlots(words) {
257+
// reorganize slots
258+
let self = this;
259+
let ep = this.endpoints;
260+
let wordArray = words.slice(this.endpoints[0].idx, this.endpoints[1].idx + 1);
157261

158-
get idx() {
159-
if (this.trigger) {
160-
return this.trigger.idx;
262+
// recursively increase the slots of l
263+
function incrementSlot(l) {
264+
l.slot += 1;
265+
l.links.forEach(incrementSlot);
161266
}
162-
return this.arguments.reduce((acc, arg) => acc + arg.anchor.idx, 0) / this.arguments.length;
267+
268+
// recursively check for collisions
269+
function checkCollision(l) {
270+
if (l !== self && l.slot === self.slot) {
271+
if (l.endpoints[0] <= ep[0] && l.endpoints[1] >= ep[0]) {
272+
incrementSlot(l);
273+
l.recalculateSlots(words);
274+
}
275+
else if (l.endpoints[0] >= ep[0] && l.endpoints[0] <= ep[1]) {
276+
// TODO: increase the slots of self
277+
incrementSlot(self);
278+
}
279+
l.links.forEach(checkCollision);
280+
}
281+
}
282+
283+
wordArray.forEach(w => {
284+
// get relevant links
285+
w.links.forEach(checkCollision);
286+
});
287+
163288
}
164289

165-
get cx() {
290+
getEndpoints() {
291+
let minWord = null;
292+
let maxWord = null;
293+
166294
if (this.trigger) {
167-
// if (this.trigger.links.length > 1) {
168-
// let offset = this.trigger.links.indexOf(this) / (this.trigger.links.length - 1) - 0.5;
169-
// return this.trigger.cx + offset * 200;
170-
// }
171-
return this.trigger.cx;
295+
minWord = maxWord = this.trigger;
172296
}
173-
if (this.arguments.length > 0) {
174-
return this.arguments.reduce((acc, arg) => acc + arg.anchor.cx, 0) / this.arguments.length;
297+
298+
this.arguments.forEach(arg => {
299+
if (arg.anchor instanceof Link) {
300+
let endpts = arg.anchor.getEndpoints();
301+
if (!minWord || minWord.idx > endpts[0].idx) {
302+
minWord = endpts[0];
303+
}
304+
if (!maxWord || maxWord.idx < endpts[1].idx) {
305+
maxWord = endpts[1];
306+
}
307+
}
308+
else { // word or wordcluster
309+
if (!minWord || minWord.idx > arg.anchor.idx) {
310+
minWord = arg.anchor;
311+
}
312+
if (!maxWord || maxWord.idx < arg.anchor.idx) {
313+
maxWord = arg.anchor;
314+
}
315+
}
316+
});
317+
return [minWord, maxWord];
318+
}
319+
320+
get rootWord() {
321+
if (this.trigger) { return this.trigger; }
322+
if (this.arguments[0].anchor instanceof Word) {
323+
return this.arguments[0].anchor;
175324
}
176-
return 0;
325+
return this.arguments[0].anchor.rootWord;
177326
}
178-
get absoluteY() {
179-
if (this.trigger) { return this.trigger.absoluteY - 15; }
180-
if (this.arguments[0]) { return this.arguments[0].absoluteY - 15; }
181-
return 0;
327+
328+
get idx() {
329+
return this.rootWord.idx;
330+
}
331+
332+
get row() {
333+
return this.rootWord.row;
182334
}
183335

184-
get val() {
185-
return this.trigger && this.trigger.val;
336+
get cx() {
337+
return this.rootWord.cx;
338+
}
339+
340+
get absoluteY() {
341+
return this.rootWord.row.rh + this.rootWord.row.ry - 45 - 15 * this.slot;
186342
}
187343
}

0 commit comments

Comments
 (0)