Skip to content

Commit 4623a17

Browse files
committed
Row: Better spacing when no Links are active
- Links: Distinguished between `enabled` and `visible` Links (the former being active but not necessarily drawn yet; the latter being active and drawn) - RowManager: Better checks for Row size/spacing when contents change or Rows are resized - Row: No longer reserve vertical space for Links that are not currently visible, allowing for more compact visualisations
1 parent b14154c commit 4623a17

File tree

8 files changed

+448
-279
lines changed

8 files changed

+448
-279
lines changed

demo/demo.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/tag/js/tag.js

Lines changed: 322 additions & 211 deletions
Large diffs are not rendered by default.

dist/tag/js/tag.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/js/components/link.js

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ class Link {
4545
// Is this Link currently visible in the visualisation?
4646
this.visible = false;
4747

48+
// Should this Link be drawn onto the visualisation?
49+
this.enabled = false;
50+
4851
// Slots are the y-intervals at which links may be drawn.
4952
// The main instance will need to provide the `.calculateSlot()` method
5053
// with the full set of Words in the data so that we can check for
@@ -246,31 +249,33 @@ class Link {
246249
* Toggles the visibility of this Link
247250
*/
248251
toggle() {
249-
if (this.visible) {
252+
if (this.enabled) {
250253
this.hide();
251254
} else {
252255
this.show();
253256
}
254-
255-
this.visible = !this.visible;
256257
}
257258

258259
/**
259-
* Shows this Link
260+
* Enables this Link and draws it onto the visualisation
260261
*/
261262
show() {
262-
if (this.svg) {
263+
this.enabled = true;
264+
265+
if (this.svg && !this.svg.visible()) {
263266
this.svg.show();
264-
this.draw();
265267
}
268+
this.draw();
266269
this.visible = true;
267270
}
268271

269272
/**
270-
* Hides this Link
273+
* Disables this Link and removes it from the visualisation
271274
*/
272275
hide() {
273-
if (this.svg) {
276+
this.enabled = false;
277+
278+
if (this.svg && this.svg.visible()) {
274279
this.svg.hide();
275280
}
276281
this.visible = false;
@@ -281,6 +286,7 @@ class Link {
281286
*/
282287
showMainLabel() {
283288
this.linkLabel.show();
289+
// Redraw the Link to make sure that the label ends up in the correct spot
284290
this.draw();
285291
}
286292

@@ -296,6 +302,7 @@ class Link {
296302
*/
297303
showArgLabels() {
298304
this.argLabels.forEach(label => label.show());
305+
// Redraw the Link to make sure that the label ends up in the correct spot
299306
this.draw();
300307
}
301308

@@ -314,7 +321,7 @@ class Link {
314321
* redraw. If not, the positions of all handles will be recalculated.
315322
*/
316323
draw(modAnchor) {
317-
if (!this.initialised) {
324+
if (!this.initialised || !this.enabled) {
318325
return;
319326
}
320327

@@ -326,6 +333,17 @@ class Link {
326333
}
327334
const changedHandles = [];
328335

336+
// One or more of our anchors might be nested Links. We need to make
337+
// sure that all of them are already drawn in, so that our offset
338+
// calculations and the like are accurate.
339+
for (let handle of calcHandles) {
340+
const anchor = handle.anchor;
341+
if (anchor instanceof Link && !anchor.visible) {
342+
anchor.show();
343+
}
344+
}
345+
346+
// Offset calculations
329347
for (let handle of calcHandles) {
330348
const anchor = handle.anchor;
331349
// Two possibilities: The anchor is a Word/WordCluster, or it is a
@@ -348,10 +366,6 @@ class Link {
348366
} else {
349367
// The anchor is a Link; the handle rests on another Link's line,
350368
// and the offset might extend to the next row and beyond.
351-
if (!anchor.visible) {
352-
// We need to draw in our anchor before proceeding with our own draw
353-
anchor.draw();
354-
}
355369
const baseLeft = anchor.leftHandle;
356370

357371
// First, make sure the offset doesn't overshoot the base row
@@ -414,10 +428,6 @@ class Link {
414428
// This Link has no trigger (Relation)
415429
this._drawAsRelation();
416430
}
417-
418-
this.visible = true;
419-
420-
this.links.forEach(l => l.draw(this));
421431
}
422432

423433
/**

src/js/components/row.js

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ class Row {
238238
redrawLinksAndClusters() {
239239
const elements = [];
240240
for (const word of this.words) {
241-
for (const link of word.links) {
241+
for (const link of word.passingLinks) {
242242
if (elements.indexOf(link) < 0) {
243243
elements.push(link);
244244
}
@@ -265,7 +265,7 @@ class Row {
265265
* @return {number}
266266
*/
267267
get ry2() {
268-
return this.ry + this.rh + this.descent;
268+
return this.ry + this.rh + this.minDescent;
269269
}
270270

271271
/**
@@ -352,41 +352,85 @@ class Row {
352352

353353
/**
354354
* Returns the minimum amount of height above the baseline needed to fit
355-
* all this Row's Words, top WordTags and top Links.
355+
* all this Row's Words, top WordTags and currently-visible top Links.
356356
* Includes vertical Row padding.
357357
* @return {number}
358358
*/
359359
get minHeight() {
360-
let height = this.wordHeight +
361-
this.maxSlot * this.config.linkSlotInterval +
362-
this.config.rowVerticalPadding;
360+
// Minimum height needed for Words + padding only
361+
let height = this.wordHeight + this.config.rowVerticalPadding;
363362

364-
// Because top Link labels are above the Link lines, we need to add
365-
// their height if any of the Words on this Row is an endpoint for a Link
366-
for (const word of this.words) {
367-
for (const link of word.links) {
368-
if (link.top) {
369-
// This Word anchors some top Link
370-
return height + this.config.rowExtraTopPadding;
363+
// Highest visible top Link
364+
let maxVisibleSlot = 0;
365+
366+
let checkWords = this.words;
367+
if (checkWords.length === 0 && this.lastRemovedWord !== null) {
368+
// We let all our Words go; what was the last one that mattered?
369+
checkWords = [this.lastRemovedWord];
370+
}
371+
372+
for (const word of checkWords) {
373+
for (const link of word.links.concat(word.passingLinks)) {
374+
if (link.top && link.visible) {
375+
maxVisibleSlot = Math.max(
376+
maxVisibleSlot,
377+
link.slot
378+
);
371379
}
372380
}
373381
}
374382

375-
// Still here?
383+
// Because top Link labels are above the Link lines, we need to add
384+
// their height if any of the Words on this Row is an endpoint for a Link
385+
if (maxVisibleSlot > 0) {
386+
return height +
387+
maxVisibleSlot * this.config.linkSlotInterval +
388+
this.config.rowExtraTopPadding;
389+
}
390+
391+
// Still here? No visible top Links on this row.
376392
return height;
377393
}
378394

379395
/**
380-
* Returns the amount of descent below the baseline needed to fit
381-
* all this Row's bottom WordTags and Links.
396+
* Returns the minimum amount of descent below the baseline needed to fit
397+
* all this Row's bottom WordTags and currently-visible bottom Links.
382398
* Includes vertical Row padding.
383-
* TODO: Have this account for whether or not bottom Links are visible.
384399
* @return {number}
385400
*/
386-
get descent() {
387-
return this.wordDescent +
388-
Math.abs(this.minSlot) * this.config.linkSlotInterval +
389-
this.config.rowVerticalPadding;
401+
get minDescent() {
402+
// Minimum height needed for WordTags + padding only
403+
let descent = this.wordDescent + this.config.rowVerticalPadding;
404+
405+
// Lowest visible bottom Link
406+
let minVisibleSlot = 0;
407+
408+
let checkWords = this.words;
409+
if (checkWords.length === 0 && this.lastRemovedWord !== null) {
410+
// We let all our Words go; what was the last one that mattered?
411+
checkWords = [this.lastRemovedWord];
412+
}
413+
414+
for (const word of checkWords) {
415+
for (const link of word.links.concat(word.passingLinks)) {
416+
if (!link.top && link.visible) {
417+
minVisibleSlot = Math.min(
418+
minVisibleSlot,
419+
link.slot
420+
);
421+
}
422+
}
423+
}
424+
425+
// Unlike in the `minHeight()` function, bottom Link labels do not
426+
// extend below the Link lines, so we don't need to add extra padding
427+
// for them.
428+
if (minVisibleSlot < 0) {
429+
return descent + Math.abs(minVisibleSlot) * this.config.linkSlotInterval;
430+
}
431+
432+
// Still here? No visible bottom Links on this row.
433+
return descent;
390434
}
391435

392436
/**

src/js/components/word.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,17 @@ class Word {
223223
this.initialised = true;
224224
}
225225

226+
/**
227+
* Redraw Links
228+
*/
226229
redrawLinks() {
227230
this.links.forEach(l => l.draw(this));
228231
this.redrawClusters();
229232
}
230233

234+
/**
235+
* Redraw all clusters (they should always be visible)
236+
*/
231237
redrawClusters() {
232238
this.clusters.forEach(cluster => {
233239
if (cluster.endpoints.indexOf(this) > -1) {

src/js/main.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ class Main {
195195
}
196196
});
197197

198+
// Now that Links are visible, make sure that all Rows have enough space
199+
this.rowManager.resizeAll();
200+
198201
// Change token colours based on the current taxonomy, if loaded
199202
this.taxonomyManager.colour(this.words);
200203
}
@@ -378,6 +381,9 @@ class Main {
378381
link.hide();
379382
}
380383
});
384+
385+
// Always resize when the set of visible Links may have changed
386+
this.rowManager.resizeAll();
381387
}
382388

383389
/**
@@ -407,6 +413,9 @@ class Main {
407413
link.hide();
408414
}
409415
});
416+
417+
// Always resize when the set of visible Links may have changed
418+
this.rowManager.resizeAll();
410419
}
411420

412421
/**

src/js/managers/rowmanager.js

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,37 @@ class RowManager {
2727
*
2828
* If called without a `dy`, simply ensures that the Row's height is at
2929
* least as large as its minimum height.
30-
* (Row descents are fixed and vary according to their bottom Links)
3130
*/
3231
resizeRow(i, dy = 0) {
3332
const row = this._rows[i];
34-
if (row === undefined) return;
33+
if (!row) return;
3534

36-
// Adjust height for the main Row
37-
// (Row descent is fixed, and depends on the number of bottom Links in
38-
// the Row)
35+
// Height adjustment
3936
const newHeight = Math.max(row.rh + dy, row.minHeight);
4037
if (row.rh !== newHeight) {
4138
row.height(newHeight);
4239
row.redrawLinksAndClusters();
4340
}
4441

45-
// Adjust the positions of all following Rows
42+
// Adjust position/height of all following Rows
4643
for (i = i + 1; i < this._rows.length; i++) {
4744
const prevRow = this._rows[i - 1];
4845
const thisRow = this._rows[i];
46+
47+
// Height check
48+
let changed = false;
49+
if (thisRow.rh < thisRow.minHeight) {
50+
thisRow.height(thisRow.minHeight);
51+
changed = true;
52+
}
53+
54+
// Position check
4955
if (thisRow.ry !== prevRow.ry2) {
5056
thisRow.move(prevRow.ry2);
57+
changed = true;
58+
}
59+
60+
if (changed) {
5161
thisRow.redrawLinksAndClusters();
5262
}
5363
}
@@ -73,14 +83,7 @@ class RowManager {
7383
}
7484
} else {
7585
// Redraw Words/Links that might have changed
76-
row.words.forEach(word => {
77-
word.links.forEach(function (l) {
78-
if (l.endpoints[1].row !== l.endpoints[0].row) {
79-
l.draw(word);
80-
}
81-
});
82-
word.redrawClusters();
83-
});
86+
row.redrawLinksAndClusters();
8487
}
8588
});
8689
}
@@ -329,20 +332,6 @@ class RowManager {
329332
this.addWordToRow(this._rows[index].removeLastWord(), nextRow, 0);
330333
}
331334

332-
//
333-
// getSlotRange(acc, anchor) {
334-
// if (anchor instanceof Link && !anchor.visible) {
335-
// return [acc[0], acc[1]];
336-
// }
337-
// if (anchor.links.length === 0) {
338-
// return [Math.min(acc[0], 0), Math.max(acc[1], 0)];
339-
// // return [Math.min(acc[0], anchor.slot), Math.max(acc[1],
340-
// anchor.slot)]; } let a = anchor.links.reduce((acc, val) =>
341-
// this.getSlotRange(acc, val), [0, 0]); return [Math.min(acc[0], a[0]),
342-
// Math.max(acc[1], a[1])]; } recalculateRowSlots(row) { [row.minSlot,
343-
// row.maxSlot] = row.words .reduce((acc, val) => this.getSlotRange(acc,
344-
// val), [0, 0]); }
345-
346335
/**
347336
* Returns the last Row managed by the RowManager
348337
* @return {*}

0 commit comments

Comments
 (0)