phyloio 2.1.1 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phyloio",
3
- "version": "2.1.1",
3
+ "version": "2.2.3",
4
4
  "description": "JS library for visualising and comparing phylogenetic trees",
5
5
  "scripts": {
6
6
  "test": "npm run-script build-jest; jest --silent=false",
@@ -0,0 +1,297 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+
6
+ const PhyloIO = require("./dist-jest/phylo.js").PhyloIO;
7
+ const utils = require('./src/utils.js')
8
+
9
+ var data = '((C,D)1,(A,(B,X)3)2,E);'
10
+
11
+ // get string made of concatenation of leaves name from node.leaves
12
+ const getSortedLeaves = (node) => {
13
+ return node.leaves.map(leaf => leaf.name).sort();
14
+ }
15
+
16
+ const verify_boostrap_default = (node, children) => {
17
+
18
+ var con_leaves = getSortedLeaves(node)
19
+
20
+ if ( con_leaves.length <= 1) {
21
+ return;
22
+ }
23
+
24
+ var str_leaves = JSON.stringify(con_leaves);
25
+
26
+ switch (str_leaves){
27
+ case JSON.stringify(['A', 'B', 'X']):
28
+ expect(node.extended_informations.Data).toBe("2");
29
+ break;
30
+ case JSON.stringify(['B', 'X']):
31
+ expect(node.extended_informations.Data).toBe("3");
32
+ break;
33
+ case JSON.stringify(['C', 'D']):
34
+ expect(node.extended_informations.Data).toBe("1");
35
+ break;
36
+ case JSON.stringify(['A', 'B', 'C', 'D', 'E', 'X']):
37
+ expect(node.extended_informations.Data).toBeUndefined();
38
+ break;
39
+ default:
40
+ expect(true).toBe(false);
41
+ break
42
+ }
43
+ }
44
+
45
+ test('test reroot on X' , () => {
46
+
47
+ const phylo = PhyloIO.init();
48
+
49
+ // >>> test with internal label for branches
50
+ var m1 = phylo._create_model(data, {'data_type' : 'nhx'})
51
+ m1.settings.edge_related_data.push('Data');
52
+ m1.traverse(m1.data,verify_boostrap_default);
53
+
54
+ // reroot on X
55
+ var x_node = m1.data.leaves.find(leaf => leaf.name === 'X');
56
+ m1.reroot(x_node);
57
+ m1.traverse(m1.data,function(node, children) {
58
+
59
+ var con_leaves = getSortedLeaves(node)
60
+
61
+ if ( con_leaves.length <= 1) {
62
+ return;
63
+ }
64
+
65
+ var str_leaves = JSON.stringify(con_leaves);
66
+
67
+ switch (str_leaves){
68
+ case JSON.stringify(['C', 'D', 'E']):
69
+ expect(node.extended_informations.Data).toBe("2");
70
+ break;
71
+ case JSON.stringify(['C', 'D']):
72
+ expect(node.extended_informations.Data).toBe("1");
73
+ break;
74
+ case JSON.stringify(['A', 'B', 'C', 'D', 'E', 'X']):
75
+ expect(node.extended_informations.Data).toBeNull();
76
+ break;
77
+ case JSON.stringify(['A', 'C', 'D', 'E']):
78
+ expect(node.extended_informations.Data).toBe("3");
79
+ break;
80
+ case JSON.stringify(['A', 'B', 'C', 'D', 'E']):
81
+ expect(node.extended_informations.Data).toBeUndefined();
82
+ break;
83
+ default:
84
+ expect(true).toBe(false);
85
+ break
86
+ }
87
+ });
88
+
89
+ // >>> test without internal label for branches
90
+ var m2 = phylo._create_model(data, {'data_type' : 'nhx'})
91
+ m2.traverse(m2.data,verify_boostrap_default);
92
+
93
+ // reroot on X
94
+ var x_node2 = m2.data.leaves.find(leaf => leaf.name === 'X');
95
+ m2.reroot(x_node2);
96
+ m2.traverse(m2.data,function(node, children) {
97
+
98
+ var con_leaves = getSortedLeaves(node)
99
+
100
+ if ( con_leaves.length <= 1) {
101
+ return;
102
+ }
103
+
104
+ var str_leaves = JSON.stringify(con_leaves);
105
+
106
+ switch (str_leaves){
107
+ case JSON.stringify(['C', 'D', 'E']):
108
+ expect(node.extended_informations.Data).toBeUndefined()
109
+ break;
110
+ case JSON.stringify(['C', 'D']):
111
+ expect(node.extended_informations.Data).toBe("1");
112
+ break;
113
+ case JSON.stringify(['A', 'B', 'C', 'D', 'E', 'X']):
114
+ expect(node.extended_informations.Data).toBeUndefined();
115
+ break;
116
+ case JSON.stringify(['A', 'C', 'D', 'E']):
117
+ expect(node.extended_informations.Data).toBe("2");
118
+ break;
119
+ case JSON.stringify(['A', 'B', 'C', 'D', 'E']):
120
+ expect(node.extended_informations.Data).toBe("3");
121
+ break;
122
+ default:
123
+ expect(true).toBe(false);
124
+ break
125
+ }
126
+ });
127
+
128
+ })
129
+
130
+ test('test reroot on B/X > C/D > B/X ' , () => {
131
+
132
+ const phylo = PhyloIO.init();
133
+
134
+ // >>> test with internal label for branches
135
+ var m1 = phylo._create_model(data, {'data_type' : 'nhx'})
136
+ m1.settings.edge_related_data.push('Data');
137
+ m1.traverse(m1.data,verify_boostrap_default);
138
+
139
+ // reroot on BX
140
+ var bx_node = null;
141
+ m1.traverse(m1.data, function(node, children) {
142
+
143
+ var con_leaves = getSortedLeaves(node)
144
+
145
+ if ( con_leaves.length <= 1) {
146
+ return;
147
+ }
148
+
149
+ var str_leaves = JSON.stringify(con_leaves);
150
+
151
+ if (str_leaves === JSON.stringify(['B', 'X'])) {
152
+ bx_node = node;
153
+ }
154
+ });
155
+ m1.reroot(bx_node);
156
+ m1.traverse(m1.data,function(node, children) {
157
+
158
+ var con_leaves = getSortedLeaves(node)
159
+
160
+ if ( con_leaves.length <= 1) {
161
+ return;
162
+ }
163
+
164
+ var str_leaves = JSON.stringify(con_leaves);
165
+
166
+ switch (str_leaves){
167
+ case JSON.stringify(['C', 'D', 'E']):
168
+ expect(node.extended_informations.Data).toBe("2");
169
+ break;
170
+ case JSON.stringify(['C', 'D']):
171
+ expect(node.extended_informations.Data).toBe("1");
172
+ break;
173
+ case JSON.stringify(['A', 'B', 'C', 'D', 'E', 'X']):
174
+ expect(node.extended_informations.Data).toBeNull();
175
+ break;
176
+ case JSON.stringify(['A', 'C', 'D', 'E']):
177
+ expect(node.extended_informations.Data).toBe("3");
178
+ break;
179
+ case JSON.stringify(['B', 'X']):
180
+ expect(node.extended_informations.Data).toBe("3");
181
+ break;
182
+ default:
183
+ expect(true).toBe(false);
184
+ break
185
+ }
186
+ });
187
+
188
+ // reroot on CD
189
+ var cd_node = null;
190
+ m1.traverse(m1.data, function(node, children) {
191
+
192
+ var con_leaves = getSortedLeaves(node)
193
+
194
+ if ( con_leaves.length <= 1) {
195
+ return;
196
+ }
197
+
198
+ var str_leaves = JSON.stringify(con_leaves);
199
+
200
+ if (str_leaves === JSON.stringify(['C', 'D'])) {
201
+ cd_node = node;
202
+ }
203
+ });
204
+ m1.reroot(cd_node);
205
+ m1.traverse(m1.data,function(node, children) {
206
+
207
+ var con_leaves = getSortedLeaves(node)
208
+
209
+ if ( con_leaves.length <= 1) {
210
+ return;
211
+ }
212
+
213
+ var str_leaves = JSON.stringify(con_leaves);
214
+
215
+ switch (str_leaves){
216
+ case JSON.stringify(['A', 'B', 'X']):
217
+ expect(node.extended_informations.Data).toBe("2");
218
+ break;
219
+ case JSON.stringify(['C', 'D']):
220
+ expect(node.extended_informations.Data).toBe("1");
221
+ break;
222
+ case JSON.stringify(['A', 'B', 'C', 'D', 'E', 'X']):
223
+ expect(node.extended_informations.Data).toBeNull();
224
+ break;
225
+ case JSON.stringify(['A', 'B', 'E', 'X']):
226
+ expect(node.extended_informations.Data).toBe("1");
227
+ break;
228
+ case JSON.stringify(['B', 'X']):
229
+ expect(node.extended_informations.Data).toBe("3");
230
+ break;
231
+ default:
232
+ expect(true).toBe(false);
233
+ break
234
+ }
235
+ });
236
+
237
+ // reroot on BX
238
+ var bx2_node = null;
239
+ m1.traverse(m1.data, function(node, children) {
240
+
241
+ var con_leaves = getSortedLeaves(node)
242
+
243
+ if ( con_leaves.length <= 1) {
244
+ return;
245
+ }
246
+
247
+ var str_leaves = JSON.stringify(con_leaves);
248
+
249
+ console.log(str_leaves)
250
+
251
+ if (str_leaves === JSON.stringify(['B', 'X'])) {
252
+
253
+ bx2_node = node;
254
+ }
255
+ });
256
+ m1.reroot(bx2_node);
257
+ m1.traverse(m1.data,function(node, children) {
258
+
259
+ var con_leaves = getSortedLeaves(node)
260
+
261
+ if ( con_leaves.length <= 1) {
262
+ return;
263
+ }
264
+
265
+ var str_leaves = JSON.stringify(con_leaves);
266
+
267
+ switch (str_leaves){
268
+ case JSON.stringify(['C', 'D', 'E']):
269
+ expect(node.extended_informations.Data).toBe("2");
270
+ break;
271
+ case JSON.stringify(['C', 'D']):
272
+ expect(node.extended_informations.Data).toBe("1");
273
+ break;
274
+ case JSON.stringify(['A', 'B', 'C', 'D', 'E', 'X']):
275
+ expect(node.extended_informations.Data).toBeNull();
276
+ break;
277
+ case JSON.stringify(['A', 'C', 'D', 'E']):
278
+ expect(node.extended_informations.Data).toBe("3");
279
+ break;
280
+ case JSON.stringify(['B', 'X']):
281
+ expect(node.extended_informations.Data).toBe("3");
282
+ break;
283
+ default:
284
+ expect(true).toBe(false);
285
+ break
286
+ }
287
+ });
288
+
289
+
290
+ })
291
+
292
+
293
+
294
+
295
+
296
+
297
+
package/src/container.js CHANGED
@@ -557,7 +557,7 @@ export default class Container {
557
557
 
558
558
  }
559
559
 
560
- monocolored_check_and_collapse(colors,child ){
560
+ monocolored_check_and_collapse(colors, child, data_type){
561
561
 
562
562
  var model = this.models[this.current_model]
563
563
 
@@ -569,8 +569,11 @@ export default class Container {
569
569
  this.viewer.apply_collapse_from_data_to_d3(child.data, child)
570
570
  break;
571
571
  default:
572
- // compute the all pair of colorDifference in colors. If one above 10% return
573
- var colorArray = Array.from(colors)
572
+ if (data_type !== "num") {
573
+ break; // more than one color, categorial or explicit color, do not colapse
574
+ }
575
+ // compute the all pairs of colorDifference in colors. If one above 5% do not collapse
576
+ let colorArray = Array.from(colors)
574
577
  for (let i = 0; i < colorArray.length; i++) {
575
578
  for (let j = i + 1; j < colorArray.length; j++) {
576
579
  if (colorDifference(colorArray[i], colorArray[j]) > 0.05) {
@@ -627,13 +630,10 @@ export default class Container {
627
630
  if (!leaf.renderedColor) {
628
631
  continue
629
632
  }
630
-
631
633
  colors.add(leaf.renderedColor)
632
634
  }
633
-
634
-
635
-
636
- that.monocolored_check_and_collapse(colors,child )
635
+ const data_type = model.settings.extended_data_type[model.settings.style.color_accessor['leaf']];
636
+ that.monocolored_check_and_collapse(colors, child, data_type)
637
637
 
638
638
  viewer.apply_collapse_from_data_to_d3(child.data, child)
639
639
 
@@ -690,9 +690,8 @@ export default class Container {
690
690
 
691
691
 
692
692
  }
693
-
694
- that.monocolored_check_and_collapse(child.colors,child )
695
-
693
+ const data_type = model.settings.extended_informations[model.settings.style.color_accessor['node']];
694
+ that.monocolored_check_and_collapse(child.colors, child, data_type);
696
695
 
697
696
  }
698
697
 
@@ -740,12 +739,11 @@ export default class Container {
740
739
  child.colors = new Set()
741
740
  return
742
741
  }
743
-
744
-
745
-
746
742
  }
747
-
748
- that.monocolored_check_and_collapse(child.colors,child )
743
+ const data_type_node = model.settings.extended_informations[model.settings.style.color_accessor['node']];
744
+ const data_type_leaf = model.settings.extended_informations[model.settings.style.color_accessor['leaf']];
745
+ const data_type = (data_type_node === data_type_leaf) ? data_type_node : 'cat'
746
+ that.monocolored_check_and_collapse(child.colors, child, data_type);
749
747
 
750
748
  }
751
749
 
package/src/interface.js CHANGED
@@ -2792,7 +2792,7 @@ export default class Interface {
2792
2792
  this.container_object.collapse_node_not_colored()
2793
2793
  this.viewer.build_d3_cluster()
2794
2794
  this.viewer.render(this.viewer.hierarchy)
2795
- this.viewer.maximise_zoom()
2795
+ //this.viewer.maximise_zoom()
2796
2796
  })
2797
2797
  .style('margin', '12px 0px')
2798
2798
  .style('flex-grow', '1')
@@ -2852,7 +2852,7 @@ export default class Interface {
2852
2852
  this.container_object.collapse_node_same_color()
2853
2853
  this.viewer.build_d3_cluster()
2854
2854
  this.viewer.render(this.viewer.hierarchy)
2855
- this.viewer.maximise_zoom()
2855
+ //this.viewer.maximise_zoom()
2856
2856
  })
2857
2857
  .style('margin', '12px 0px')
2858
2858
  .style('flex-grow', '1')
@@ -3115,7 +3115,7 @@ export default class Interface {
3115
3115
 
3116
3116
 
3117
3117
  if (!(acc in this.viewer.model.settings.style.number_domain)) {
3118
- this.viewer.model.settings.style.number_domain[acc] = 3
3118
+ this.viewer.model.settings.style.number_domain[acc] = 2
3119
3119
  }
3120
3120
 
3121
3121
  this.create_color_scheme_picker(type)
@@ -3217,6 +3217,8 @@ export default class Interface {
3217
3217
  this.viewer.model.set_color_scale(type, this.api);
3218
3218
  }
3219
3219
  this.viewer.render(this.viewer.hierarchy)
3220
+ this.remove_color_legend(type)
3221
+ this.add_color_legend(type)
3220
3222
 
3221
3223
  })
3222
3224
 
@@ -3528,9 +3530,8 @@ export default class Interface {
3528
3530
  var number = this.viewer.model.settings.style.number_domain[acc];
3529
3531
 
3530
3532
  if (!(acc in this.viewer.model.settings.style.color_domain)) {
3531
- this.viewer.model.settings.style.color_domain[acc] = this.viewer.model.settings.style.color_domain_default
3532
-
3533
- var default_color = this.viewer.model.settings.style.color_domain_default
3533
+ //this.viewer.model.settings.style.color_domain[acc] = this.viewer.model.settings.style.color_domain_default
3534
+ var default_color = this.viewer.model.settings.style.color_domain_default[acc] || this.viewer.model.settings.style.color_domain_default["DEFAULT"];
3534
3535
 
3535
3536
  var w = Math.round(135/number);
3536
3537
  var color = []
package/src/model.js CHANGED
@@ -53,8 +53,16 @@ export default class Model {
53
53
  'color_extent_min': {'leaf' : {}, 'node': {"Topology":0}, 'circle': {} },
54
54
  'color_extent_max':{'leaf' : {}, 'node': {"Topology":1}, 'circle': {}},
55
55
  'number_domain':{ 'Topology': 5, 'Length': 5},
56
- 'color_domain':{'Topology' : ['#253494', '#2C7FB8', '#41B6C4', '#C7E9B4', '#FFFFCC'], 'Length': ['#253494', '#2C7FB8', '#41B6C4', '#C7E9B4', '#FFFFCC']},
57
- 'color_domain_default': ['#253494', '#2C7FB8', '#41B6C4', '#C7E9B4', '#FFFFCC'],
56
+ 'color_domain': {
57
+ 'Topology' : ["#a50026", "#f46d43", "#ffffbf", "#74add1", "#313695"],
58
+ 'Length': ['#253494', '#2C7FB8', '#41B6C4', '#C7E9B4', '#FFFFCC']
59
+ },
60
+ 'color_domain_default': {
61
+ "Topology": ["#a50026", "#f46d43", "#ffffbf", "#74add1", "#313695"],
62
+ "Length": ['#253494', '#2C7FB8', '#41B6C4', '#C7E9B4', '#FFFFCC'],
63
+ "DEFAULT": ['#C60101', '#FB6A4A', '#F7F7F7', '#6BAED6', '#253494']
64
+ }
65
+
58
66
  },
59
67
  'tree': {
60
68
  'node_vertical_size' : 30,
@@ -794,10 +802,6 @@ export default class Model {
794
802
  parent.branch_length = old_distance /2
795
803
  parent.extended_informations['Length'] = old_distance/2
796
804
 
797
-
798
-
799
- // ((C,D)1,(A,(B,X)3)2,E); to test
800
-
801
805
  // Until we reach the old root reverse child/parent order
802
806
  var child = root
803
807
  var stack = []
@@ -812,29 +816,26 @@ export default class Model {
812
816
  parent.values_before_reverse = {}
813
817
  parent.branch_length_before_reverse = parent.branch_length
814
818
 
815
- for (var key of this.settings.edge_related_data) {
816
-
817
- var value = key;
819
+ for (var value of this.settings.edge_related_data) {
818
820
 
819
821
  parent.values_before_reverse[value] = parent.extended_informations[value]
820
822
 
823
+ if (value=== 'Length'){
824
+ parent.branch_length = child.branch_length_before_reverse || child.branch_length;
825
+ parent.extended_informations['Length'] = parent.branch_length;
821
826
 
822
- if (value=== 'Length'){
823
- parent.branch_length = child.branch_length_before_reverse || child.branch_length;
824
- parent.extended_informations['Length'] = parent.branch_length;
825
-
827
+ }
828
+ else{
829
+ if ( child.values_before_reverse && value in child.values_before_reverse){
830
+ parent.extended_informations[value] = child.values_before_reverse[value]
826
831
  }
827
832
  else{
828
- if ( child.values_before_reverse && value in child.values_before_reverse){
829
- parent.extended_informations[value] = child.values_before_reverse[value]
830
- }
831
- else{
832
- parent.extended_informations[value] = child.extended_informations[value]
833
- child.extended_informations[value] = null
834
- }
835
-
833
+ parent.extended_informations[value] = child.extended_informations[value]
834
+ child.extended_informations[value] = null
836
835
  }
837
836
 
837
+ }
838
+
838
839
 
839
840
 
840
841
  }
@@ -890,7 +891,6 @@ export default class Model {
890
891
 
891
892
  for (var childy of root.children) {
892
893
 
893
- console.log(childy, childy.extended_informations[key], root.extended_informations[key] )
894
894
  childy.extended_informations[key] = root.extended_informations[key]
895
895
 
896
896
  }
@@ -909,6 +909,8 @@ export default class Model {
909
909
 
910
910
  this.traverse(root, function(n,c){
911
911
  n.leaves = this.get_leaves(n)
912
+ n.values_before_reverse = {}
913
+ n.branch_length_before_reverse = undefined
912
914
  })
913
915
 
914
916
 
package/src/utils.js CHANGED
@@ -980,7 +980,14 @@ function check_if_color(query){
980
980
  }
981
981
  }
982
982
 
983
- function hexToRgb(hex) {
983
+ function hexToRgb(hex){
984
+ if (typeof hex === 'object' && hex.r !== undefined) {
985
+ return hex;
986
+ }
987
+ if (typeof hex === 'string' && hex.startsWith('rgb')) {
988
+ const values = hex.match(/\d+/g).map(Number);
989
+ return { r: values[0], g: values[1], b: values[2] };
990
+ }
984
991
  const bigint = parseInt(hex.slice(1), 16);
985
992
  const r = (bigint >> 16) & 255;
986
993
  const g = (bigint >> 8) & 255;
@@ -996,7 +1003,7 @@ function colorDifference(hex1, hex2) {
996
1003
  const bDiff = color1.b - color2.b;
997
1004
  const distance = Math.sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff);
998
1005
  const maxDistance = Math.sqrt(255 * 255 * 3);
999
- return (distance / maxDistance) * 100;
1006
+ return (distance / maxDistance);
1000
1007
  }
1001
1008
 
1002
1009
  module.exports = {colorDifference, check_if_color, prepare_and_run_distance, build_table, reroot_hierarchy, screen_shot, parse_nhx, save_file_as, compute_RF_Euc, get_intersection_leaves, filter_leaves_hierarchy, remove_duplicated_and_unnamed_leaves_hierarchy};
package/src/viewer.js CHANGED
@@ -548,6 +548,7 @@ export default class Viewer {
548
548
  var acc = this.model.settings.style.color_accessor['circle']
549
549
  var type_acc = this.model.settings.extended_data_type[acc]
550
550
  var g = d.data.extended_informations[acc]
551
+ if (typeof(g) === 'undefined' || !g) return "rgba(140,140,140,0.6)";
551
552
 
552
553
  if (type_acc == 'cat'){
553
554
 
@@ -781,7 +782,8 @@ export default class Viewer {
781
782
 
782
783
  color_triangle(node_){
783
784
 
784
- var mean = array => array.reduce((a, b) => a + b) / array.length;
785
+ const mean = array => array.reduce((a, b) => a + b) / array.length;
786
+ const default_color = '#666'
785
787
 
786
788
  function mostFrequentElement(array) {
787
789
  var frequencyMap = {};
@@ -824,6 +826,7 @@ export default class Viewer {
824
826
  })
825
827
 
826
828
  metrics = removeFalsyValues(metrics)
829
+ if (metrics.length === 0) {return default_color;}
827
830
 
828
831
  var type_l = this.model.settings.extended_data_type[acc_l]
829
832
 
@@ -854,6 +857,7 @@ export default class Viewer {
854
857
  })
855
858
 
856
859
  metrics = removeFalsyValues(metrics)
860
+ if (metrics.length === 0) {return default_color;}
857
861
 
858
862
  var type_l = this.model.settings.extended_data_type[acc_l]
859
863
 
@@ -871,7 +875,7 @@ export default class Viewer {
871
875
  break
872
876
  }
873
877
 
874
- return '#666'
878
+ return default_color;
875
879
  }
876
880
 
877
881
  render_edges(source, duration){