Skip to content

Commit 22f8faf

Browse files
authored
Merge pull request #9 from yamada-lab/develop
Develop
2 parents a947198 + a305cbd commit 22f8faf

File tree

5 files changed

+133
-66
lines changed

5 files changed

+133
-66
lines changed

functree/static/src/js/functree.js

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ const FuncTree = class {
134134
}
135135

136136
update(source=this.root) {
137+
137138
const nodes = this.tree.nodes(this.root);
138139
const links = this.tree.links(nodes);
139140
const depth = d3.max(
@@ -142,6 +143,7 @@ const FuncTree = class {
142143
})
143144
);
144145

146+
145147
const getLayerMax = (nodes, maxFunc) => {
146148
return Array.from(new Array(depth + 1))
147149
.map((v, i) => {
@@ -156,6 +158,7 @@ const FuncTree = class {
156158
const maxValue = getLayerMax(nodes, (x) => {
157159
return x.value;
158160
});
161+
159162
const maxSumOfValues = getLayerMax(nodes, (x) => {
160163
return d3.sum(x.values);
161164
});
@@ -170,15 +173,23 @@ const FuncTree = class {
170173
this._updateRounds(nodes, source, depth, maxValue);
171174
this._updateLabels(nodes, source, maxValue, maxSumOfValues);
172175

173-
for (const node of nodes) {
176+
for (var node of nodes) {
174177
node.x0 = node.x;
175178
node.y0 = node.y;
176179
}
177-
178-
// Enable showing tooltip by Bootstrap
179-
$('[data-toggle="tooltip"]').tooltip({
180-
container: 'body',
181-
placement: 'top'
180+
// Lazy initialize Bootstrap tooltip
181+
// FIXME for <g> elements this does not work as children trigger the event but they lack the data-original-title attribute
182+
$('[data-toggle="tooltip"]').on({
183+
'mouseenter': (x) => {
184+
$(x.target).tooltip({
185+
container: 'body',
186+
placement: 'top',
187+
trigger: 'manual'
188+
}).tooltip('show');
189+
},
190+
'mouseleave': (x) => {
191+
$(x.target).tooltip('hide');
192+
}
182193
});
183194
}
184195

@@ -314,16 +325,14 @@ const FuncTree = class {
314325
.attr('stroke-width', 0.25)
315326
.attr('cursor', 'pointer')
316327
.attr('data-toggle', 'tooltip')
317-
.attr('data-original-title', (d) => {
318-
return '[' + d.entry + '] ' + d.name;
319-
})
328+
.attr('data-original-title', this._makeNodeTitle)
320329
.on('click', (d) => {
321330
this._collapseChildren(d);
322331
this.update(d);
323332
})
324333
.on('mouseover', (d) => {
325334
d3.select(d3.event.target)
326-
.style('r', 10)
335+
.style('r', 1)
327336
.style('fill', '#000')
328337
.style('opacity', 0.5);
329338
this._highlightLinks(d);
@@ -460,10 +469,6 @@ const FuncTree = class {
460469
_updateBars(nodes, source, depth, maxSumOfValues, maxMaxOfValues) {
461470
const self = this;
462471
const data = nodes
463-
// .filter((d) => {
464-
// const excludes = ['root'];
465-
// return !~excludes.indexOf(d.layer);
466-
// })
467472
.filter((d) => {
468473
return d.depth > 0;
469474
});
@@ -479,9 +484,7 @@ const FuncTree = class {
479484
.append('g')
480485
.attr('transform', 'rotate(' + (source.x0 - 90) + '),translate(' + source.y0 + '),rotate(-90)')
481486
.attr('data-toggle', 'tooltip')
482-
.attr('data-original-title', function(d) {
483-
return '[' + d.entry + '] ' + d.name;
484-
})
487+
.attr('data-original-title', this._makeNodeTitle)
485488
.on('click', (d) => {
486489
this._collapseChildren(d);
487490
this.update(d);
@@ -511,7 +514,7 @@ const FuncTree = class {
511514
.duration(this.config.duration)
512515
.attr('transform', 'rotate(' + (source.x - 90) + '),translate(' + source.y + '),rotate(-90)')
513516
.remove();
514-
517+
515518
const bar = chart
516519
.selectAll('rect')
517520
.data((d) => {
@@ -592,10 +595,6 @@ const FuncTree = class {
592595

593596
_updateRounds(nodes, source, depth, max) {
594597
const data = nodes
595-
// .filter((d) => {
596-
// const excludes = ['root'];
597-
// return !~excludes.indexOf(d.layer);
598-
// })
599598
.filter((d) => {
600599
return d.depth > 0;
601600
});
@@ -632,9 +631,7 @@ const FuncTree = class {
632631
.attr('stroke-width', 0.5)
633632
.attr('opacity', 0.75)
634633
.attr('data-toggle', 'tooltip')
635-
.attr('data-original-title', (d) => {
636-
return '[' + d.entry + '] ' + d.name;
637-
})
634+
.attr('data-original-title', this._makeNodeTitle)
638635
.on('click', (d) => {
639636
this._collapseChildren(d);
640637
this.update(d);
@@ -664,9 +661,18 @@ const FuncTree = class {
664661
.attr('r', (d) => {
665662
const r_ = 25;
666663
if (this.config.normalize) {
667-
return d.value / max[d.depth] * r_ || 0;
664+
var radius = null;
665+
// map to Area
666+
if(this.config.circleMapToArea){
667+
var scaledArea = d.value / max[d.depth]
668+
radius = Math.sqrt(scaledArea/Math.PI)
669+
// map to radius
670+
} else {
671+
radius = d.value / max[d.depth];
672+
}
673+
return radius * r_ || 0;
668674
} else {
669-
return d.value;
675+
return this.config.circleMapToArea? Math.sqrt(d.value/Math.PI) : d.value;
670676
}
671677
})
672678
.attr('transform', (d) => {
@@ -768,7 +774,17 @@ const FuncTree = class {
768774
);
769775
}
770776

771-
777+
/**
778+
* Returns a clean title for the tooltip with PATH, BR information stripped from modules names
779+
*/
780+
_makeNodeTitle(d){
781+
var title = d.name.replace(/ \[PATH:.*\]$/, '');
782+
if(d.entry != d.name) {
783+
title = '[' + d.entry + '] ' + title
784+
}
785+
return title;
786+
}
787+
772788
search(word) {
773789
const node = d3.select('#nodes')
774790
.selectAll('circle');
@@ -777,11 +793,12 @@ const FuncTree = class {
777793
return d.entry === word;
778794
});
779795
hits.style('fill', '#f00')
796+
.style('stroke', '#f00')
780797
.style('opacity', 0.5)
781798
.transition()
782799
.duration(1000)
783800
.style('r', 50)
784-
.style('stroke-width', 0)
801+
.style('stroke-width', 50)
785802
.style('opacity', 0)
786803
.each('end', function() {
787804
d3.select(this)

functree/static/src/js/pathways.js

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,30 @@ const visualize = (profile, columns, column, cutoff, color, opacity, width, map)
44

55
/** iPath */
66
{
7-
const seriesData = profile.map((x) => {
8-
x.name = x.entry;
9-
x.y = x.values[column];
10-
return x;
11-
});
12-
13-
const selection = seriesData.filter((x) => {
7+
// select the
8+
var seriesData = profile.filter((x) => {
149
// let layers = ['pathway', 'module', 'ko'];
1510
let layers = ['ko'];
1611
// Match KO layer and ensure that the value is above > cutoff
17-
return ~layers.indexOf(x.layer) && x.y > cutoff;
18-
}).map((x) => {
19-
return x.name + ' ' + color + ' W' + (width) + ' ' + opacity
12+
return ~layers.indexOf(x.layer) && x.values[column] > cutoff;
13+
})
14+
15+
// a function that always return the same color
16+
var colorGradient = (x) => {return color}
17+
// if color is null then define a colorGradient
18+
if(color == null){
19+
var seriesValues = seriesData.map((x) => {return x.values[column]})
20+
colorGradient = d3.scale.linear()
21+
.domain(d3.extent(seriesValues))
22+
.range([d3.rgb(255, 128, 14), d3.rgb(171, 171, 171)])
23+
.interpolate(d3.interpolateHcl);
24+
}
25+
// generate the selection for iPath
26+
var selection = seriesData.map((x) => {
27+
return x.entry + ' ' + colorGradient(x.values[column]) + ' W' + (width) + ' ' + opacity
2028
}).join('\n');
21-
22-
const params = {
29+
// add default parameters
30+
var params = {
2331
'selection': selection,
2432
'default_opacity': 1,
2533
'default_width': 3,
@@ -29,11 +37,13 @@ const visualize = (profile, columns, column, cutoff, color, opacity, width, map)
2937
'tax_filter': '',
3038
'map': map
3139
};
32-
33-
const form = $('<form/>', {'action': 'https://pathways.embl.de/ipath3.cgi', 'method': 'POST', 'target': 'ipath'}).hide();
34-
for (name in params) {
40+
// attach a form
41+
var form = $('<form/>', {'action': 'https://pathways.embl.de/ipath3.cgi', 'method': 'POST', 'target': 'ipath'}).hide();
42+
// append form parameters
43+
for (var name in params) {
3544
form.append($('<input/>', {'type': 'hidden', 'name': name, 'value': params[name]}));
3645
}
46+
// submit the form
3747
form.appendTo(document.body).submit().remove();
3848
}
3949
};

functree/templates/about.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ <h3>Publications</h3>
2525
<hr />
2626
<h3>Change log</h3>
2727
<br />
28+
<h5>Version 0.8.2.0 (2019-03-13)</h5>
29+
<ul>
30+
<li>Performance improvement
31+
<ul>
32+
<li>Loading of the viewer is reduced from 10 seconds to 2.5 seconds on average (4 fold speedup).
33+
<li>Visualization of the BRITE hierarchy at KO level does not crash the browser anymore (still not fast though).
34+
</ul>
35+
<li>Addition of mapping to a color gradient when submitting to iPath.
36+
<li>Addition of mapping values to circle area. The default is still to radius.
37+
<li>Simplification of tooltip text: removal of duplicated names and hierarchy information.
38+
<li>Fix of the search match highlight in Firefox.
39+
<li>UX improvement: the size of highlighted nodes on mouseover is reduced to avoid hiding neighboring nodes.
40+
</ul>
2841
<h5>Version 0.8.1.0 (2019-03-08)</h5>
2942
<ul>
3043
<li>Enhancement of the search box for map elements.

functree/templates/functree.html

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@
99
{{super()}}
1010

1111

12-
<form id="form-search" class="collapse in" v-on:submit.prevent="submit">
12+
<div id="form-search" class="collapse in">
1313
<div class="form-group">
1414
<div id="map-element-search" class="input-group">
1515
<input type="text" class="form-control input-sm typeahead" placeholder="Search map elements" v-model.lazy.trim="searchWord">
1616
<span class="input-group-btn" style="vertical-align: top;">
17-
<button class="btn btn-primary btn-sm" type="submit">
17+
<button class="btn btn-primary btn-sm" v-on:click="search">
1818
<i class='fa fa-search fa-fw'></i>
1919
</button>
2020
</span>
2121
</div>
2222
</div>
23-
</form>
23+
</div>
2424
<hr>
2525
<div id="zoom-control">
2626
<label class="control-label">Zoom </label>&nbsp;&nbsp;
@@ -63,6 +63,12 @@ <h6>Choose a column to map</h6>
6363
<option value="">None</option>
6464
<option v-for="(option, index) in options[1]" v-bind:value="index">{% raw %}{{option}}{% endraw %}</option>
6565
</select>
66+
<div class="checkbox">
67+
<label>
68+
<input id="circleMapToArea" type="checkbox" v-model="circleMapToArea">
69+
Map values to circle area
70+
</label>
71+
</div>
6672
<h6>Color coding</h6>
6773
<select id="color-coding" class="form-control input-sm" v-model="colorizeBy">
6874
<option title="Layer: Gives the same color to nodes of the samle layer (e.g. by Pathway, by Module, ...)" value="layer">Layer</option>
@@ -177,7 +183,7 @@ <h4 class="modal-title">Entry detail: <code v-if="entry">{% raw %}{{entry}}{% en
177183
visualize(res0.data[0], res1.data[0]);
178184
}))
179185
.then(function() {
180-
$('#loading').remove();
186+
$('#loading').remove();
181187
})
182188
.catch(function(error) {
183189
$('#loading > div').html('<p><i class="fa fa-exclamation-circle fa-4x fa-fw"></i></p><p>Ajax error</p>');
@@ -200,7 +206,6 @@ <h4 class="modal-title">Entry detail: <code v-if="entry">{% raw %}{{entry}}{% en
200206

201207
// bind zoom to click action
202208
d3.selectAll(".zoomcontrol").on('click', zoomClick);
203-
204209

205210
$(window).on('beforeunload', function() {
206211
return null;
@@ -209,7 +214,7 @@ <h4 class="modal-title">Entry detail: <code v-if="entry">{% raw %}{{entry}}{% en
209214
function visualize(tree, profile) {
210215
var funcTree = new FuncTree(tree.tree, "{{ url_for('route_get_entry') }}");
211216
if (rootEntry) {
212-
const roots = funcTree.nodes.filter(function(x) {
217+
var roots = funcTree.nodes.filter(function(x) {
213218
return x.entry === rootEntry;
214219
});
215220
if (roots.length) {
@@ -218,10 +223,9 @@ <h4 class="modal-title">Entry detail: <code v-if="entry">{% raw %}{{entry}}{% en
218223
alert('No entry found: ' + rootEntry);
219224
}
220225
}
221-
funcTree
222-
.create()
223-
.update();
224226

227+
funcTree.create().update();
228+
225229
var layers = funcTree.nodes.reduce(function(x, y) {
226230
if (!~x.indexOf(y.layer)) {
227231
x.push(y.layer);
@@ -235,7 +239,7 @@ <h4 class="modal-title">Entry detail: <code v-if="entry">{% raw %}{{entry}}{% en
235239
'searchWord': ''
236240
},
237241
'methods': {
238-
'submit': function() {
242+
'search': function() {
239243
funcTree.search(this.searchWord);
240244
}
241245
}
@@ -249,6 +253,7 @@ <h4 class="modal-title">Entry detail: <code v-if="entry">{% raw %}{{entry}}{% en
249253
'columnsMultiple': '',
250254
'normalize': true,
251255
'percentage': false,
256+
'circleMapToArea': false,
252257
'colorizeBy': 'layer',
253258
'options': [
254259
profile.series,
@@ -287,6 +292,7 @@ <h4 class="modal-title">Entry detail: <code v-if="entry">{% raw %}{{entry}}{% en
287292
funcTree
288293
.configure({
289294
'normalize': self.normalize,
295+
'circleMapToArea': self.circleMapToArea,
290296
'colorizeBy': self.colorizeBy,
291297
'percentage': self.percentage,
292298
'colorSet': profile.colors.reduce(function(a, b) {
@@ -314,20 +320,14 @@ <h4 class="modal-title">Entry detail: <code v-if="entry">{% raw %}{{entry}}{% en
314320
}
315321
});
316322

323+
var MODULE_UNDEFINED_PATTERN = '*'
317324
// create an array of nodes to search
318-
const nodes = funcTree.nodes.map(function(x) {
325+
var nodes = funcTree.nodes.map(function(x) {
319326
return x.entry;
320-
}).filter(function(x, i, self) {
321-
return self.indexOf(x) === i;
322327
}).filter(function(x) {
323-
return !x.startsWith('*');
324-
}).sort(function(a, b) {
325-
a = a.toString().toLowerCase();
326-
b = b.toString().toLowerCase();
327-
if (a > b) return 1;
328-
if (a < b) return -1;
329-
return 0;
328+
return !x.startsWith(MODULE_UNDEFINED_PATTERN);
330329
})
330+
331331
// initialize a typeahead for the nodes
332332
$('#map-element-search .typeahead').typeahead({
333333
hint: true,

0 commit comments

Comments
 (0)