genoverse 3.2.0 → 4.0.0-beta1

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.
Files changed (216) hide show
  1. package/.eslintrc.js +93 -162
  2. package/.github/workflows/test.yml +9 -10
  3. package/.github/workflows/update-gh-pages.yml +33 -0
  4. package/LICENSE.TXT +2 -2
  5. package/README.md +176 -3
  6. package/{i → assets}/sort_handle.png +0 -0
  7. package/babel.config.js +19 -0
  8. package/dist/129.css +334 -0
  9. package/dist/129.css.map +1 -0
  10. package/dist/129.genoverse.js +2 -0
  11. package/dist/129.genoverse.js.map +1 -0
  12. package/dist/15d98c18221c8bcb2334.ttf +0 -0
  13. package/dist/166.css +2 -0
  14. package/dist/166.genoverse.js +1 -0
  15. package/dist/216.css +20 -0
  16. package/dist/216.css.map +1 -0
  17. package/dist/232.css +114 -0
  18. package/dist/232.css.map +1 -0
  19. package/dist/232.genoverse.js +2 -0
  20. package/dist/232.genoverse.js.map +1 -0
  21. package/dist/2e659e443f3e98569e9f.png +0 -0
  22. package/dist/394.css +114 -0
  23. package/dist/394.css.map +1 -0
  24. package/dist/394.genoverse.js +2 -0
  25. package/dist/394.genoverse.js.map +1 -0
  26. package/dist/469.css +24 -0
  27. package/dist/469.css.map +1 -0
  28. package/dist/469.genoverse.js +2 -0
  29. package/dist/469.genoverse.js.map +1 -0
  30. package/dist/4896d4b04430cc3dfb06.woff2 +0 -0
  31. package/dist/530.css +39 -0
  32. package/dist/530.css.map +1 -0
  33. package/dist/530.genoverse.js +2 -0
  34. package/dist/530.genoverse.js.map +1 -0
  35. package/dist/547.css +469 -0
  36. package/dist/547.css.map +1 -0
  37. package/dist/547.genoverse.js +1 -0
  38. package/dist/729.css +315 -0
  39. package/dist/729.css.map +1 -0
  40. package/dist/79da213423ac0def2058.ttf +0 -0
  41. package/dist/804.genoverse.js +2 -0
  42. package/dist/804.genoverse.js.map +1 -0
  43. package/dist/842.genoverse.js +2 -0
  44. package/dist/842.genoverse.js.map +1 -0
  45. package/dist/893.genoverse.js +2 -0
  46. package/dist/893.genoverse.js.map +1 -0
  47. package/dist/949.css +315 -0
  48. package/dist/949.css.map +1 -0
  49. package/dist/949.genoverse.js +2 -0
  50. package/dist/949.genoverse.js.map +1 -0
  51. package/dist/952.css +315 -0
  52. package/dist/952.css.map +1 -0
  53. package/dist/952.genoverse.js +2 -0
  54. package/dist/952.genoverse.js.map +1 -0
  55. package/dist/d79c2ec96ab9ff1161a2.woff2 +0 -0
  56. package/dist/genoverse.js +2 -0
  57. package/dist/genoverse.js.map +1 -0
  58. package/index.html +13 -14
  59. package/jest.config.js +5 -0
  60. package/jest.setup.js +13 -0
  61. package/package.json +29 -12
  62. package/{css → src/css}/controlPanel.css +0 -0
  63. package/{css → src/css}/fileDrop.css +0 -0
  64. package/src/css/fontawesome.css +3 -0
  65. package/{css → src/css}/fullscreen.css +0 -0
  66. package/{css → src/css}/genoverse.css +1 -1
  67. package/{css → src/css}/karyotype.css +2 -0
  68. package/{css → src/css}/resizer.css +0 -0
  69. package/{css → src/css}/tooltips.css +0 -0
  70. package/{css → src/css}/trackControls.css +0 -0
  71. package/src/js/Genoverse.js +1747 -0
  72. package/{js → src/js}/Track/Controller/Sequence.js +6 -4
  73. package/src/js/Track/Controller/Stranded.js +83 -0
  74. package/{js → src/js}/Track/Controller.js +201 -160
  75. package/src/js/Track/Model/File/BAM.js +47 -0
  76. package/src/js/Track/Model/File/BED.js +122 -0
  77. package/src/js/Track/Model/File/GFF.js +42 -0
  78. package/src/js/Track/Model/File/VCF.js +109 -0
  79. package/src/js/Track/Model/File/WIG.js +82 -0
  80. package/src/js/Track/Model/File.js +36 -0
  81. package/src/js/Track/Model/Gene/Ensembl.js +24 -0
  82. package/{js → src/js}/Track/Model/Gene.js +3 -1
  83. package/src/js/Track/Model/Sequence/Ensembl.js +6 -0
  84. package/{js → src/js}/Track/Model/Sequence/Fasta.js +24 -17
  85. package/{js → src/js}/Track/Model/Sequence.js +10 -7
  86. package/{js → src/js}/Track/Model/SequenceVariation.js +17 -11
  87. package/{js → src/js}/Track/Model/Stranded.js +11 -8
  88. package/src/js/Track/Model/Transcript/Ensembl.js +73 -0
  89. package/{js → src/js}/Track/Model/Transcript.js +3 -1
  90. package/{js → src/js}/Track/Model.js +128 -97
  91. package/{js → src/js}/Track/View/Gene/Ensembl.js +6 -4
  92. package/src/js/Track/View/Gene.js +8 -0
  93. package/{js → src/js}/Track/View/Sequence.js +18 -22
  94. package/src/js/Track/View/SequenceVariation.js +117 -0
  95. package/src/js/Track/View/Transcript/Ensembl.js +17 -0
  96. package/src/js/Track/View/Transcript.js +32 -0
  97. package/{js → src/js}/Track/View.js +200 -159
  98. package/{js → src/js}/Track/library/Chromosome.js +18 -13
  99. package/src/js/Track/library/File/BAM.js +34 -0
  100. package/src/js/Track/library/File/BED.js +27 -0
  101. package/src/js/Track/library/File/BIGBED.js +51 -0
  102. package/src/js/Track/library/File/BIGWIG.js +54 -0
  103. package/src/js/Track/library/File/GFF.js +10 -0
  104. package/{js → src/js}/Track/library/File/VCF.js +29 -22
  105. package/src/js/Track/library/File/WIG.js +8 -0
  106. package/{js → src/js}/Track/library/File.js +4 -2
  107. package/src/js/Track/library/Gene.js +44 -0
  108. package/src/js/Track/library/Graph/Bar.js +263 -0
  109. package/src/js/Track/library/Graph/Line.js +335 -0
  110. package/{js → src/js}/Track/library/Graph.js +137 -114
  111. package/{js → src/js}/Track/library/HighlightRegion.js +118 -93
  112. package/src/js/Track/library/Legend.js +258 -0
  113. package/{js → src/js}/Track/library/Scalebar.js +69 -49
  114. package/{js → src/js}/Track/library/Scaleline.js +29 -27
  115. package/src/js/Track/library/Static.js +82 -0
  116. package/{js → src/js}/Track/library/dbSNP.js +47 -50
  117. package/src/js/Track.js +649 -0
  118. package/{js → src/js}/genomes/grch37.js +52 -52
  119. package/{js → src/js}/genomes/grch38.js +52 -52
  120. package/src/js/lib/BWReader.js +562 -0
  121. package/src/js/lib/VCFReader.js +296 -0
  122. package/src/js/lib/dalliance/bam.js +517 -0
  123. package/src/js/lib/dalliance/bin.js +317 -0
  124. package/src/js/lib/dalliance/jszlib-inflate.js +2159 -0
  125. package/src/js/lib/dalliance/lh3utils.js +105 -0
  126. package/src/js/lib/dalliance/sha1.js +334 -0
  127. package/src/js/lib/import-tracks.js +42 -0
  128. package/{js/lib → src/js/lib/jquery-plugins}/jquery.mousehold.js +0 -0
  129. package/{js/lib → src/js/lib/jquery-plugins}/jquery.mousewheel.js +0 -0
  130. package/{js/lib → src/js/lib/jquery-plugins}/jquery.tipsy.js +0 -0
  131. package/src/js/lib/jquery.js +26 -0
  132. package/src/js/lib/polyfills.js +11 -0
  133. package/src/js/lib/wrap-functions.js +88 -0
  134. package/src/js/plugins/controlPanel.js +388 -0
  135. package/src/js/plugins/fileDrop.js +81 -0
  136. package/src/js/plugins/focusRegion.js +13 -0
  137. package/{js → src/js}/plugins/fullscreen.js +18 -14
  138. package/{js → src/js}/plugins/karyotype.js +51 -45
  139. package/src/js/plugins/resizer.js +52 -0
  140. package/{js → src/js}/plugins/tooltips.js +31 -29
  141. package/src/js/plugins/trackControls.js +159 -0
  142. package/test/View/render-legends.test.js +1 -1
  143. package/test/create-and-destroy.test.js +2 -2
  144. package/test/track-ordering.test.js +3 -2
  145. package/test/track_config/config-settings.test.js +1 -1
  146. package/test/utils.js +4 -2
  147. package/webpack.config.js +103 -34
  148. package/css/font-awesome.css +0 -3
  149. package/expanded.html +0 -120
  150. package/fontawesome/css/fontawesome.min.css +0 -5
  151. package/fontawesome/css/regular.min.css +0 -5
  152. package/fontawesome/css/solid.min.css +0 -5
  153. package/fontawesome/webfonts/fa-brands-400.ttf +0 -0
  154. package/fontawesome/webfonts/fa-brands-400.woff +0 -0
  155. package/fontawesome/webfonts/fa-brands-400.woff2 +0 -0
  156. package/fontawesome/webfonts/fa-regular-400.ttf +0 -0
  157. package/fontawesome/webfonts/fa-regular-400.woff +0 -0
  158. package/fontawesome/webfonts/fa-regular-400.woff2 +0 -0
  159. package/fontawesome/webfonts/fa-solid-900.ttf +0 -0
  160. package/fontawesome/webfonts/fa-solid-900.woff +0 -0
  161. package/fontawesome/webfonts/fa-solid-900.woff2 +0 -0
  162. package/help.pdf +0 -0
  163. package/index.js +0 -83
  164. package/js/Genoverse.js +0 -1681
  165. package/js/Track/Controller/Stranded.js +0 -73
  166. package/js/Track/Model/File/BAM.js +0 -44
  167. package/js/Track/Model/File/BED.js +0 -116
  168. package/js/Track/Model/File/GFF.js +0 -40
  169. package/js/Track/Model/File/VCF.js +0 -101
  170. package/js/Track/Model/File/WIG.js +0 -67
  171. package/js/Track/Model/File.js +0 -36
  172. package/js/Track/Model/Gene/Ensembl.js +0 -22
  173. package/js/Track/Model/Sequence/Ensembl.js +0 -4
  174. package/js/Track/Model/Transcript/Ensembl.js +0 -67
  175. package/js/Track/View/Gene.js +0 -6
  176. package/js/Track/View/Sequence/Variation.js +0 -115
  177. package/js/Track/View/Transcript/Ensembl.js +0 -12
  178. package/js/Track/View/Transcript.js +0 -28
  179. package/js/Track/library/File/BAM.js +0 -30
  180. package/js/Track/library/File/BED.js +0 -24
  181. package/js/Track/library/File/BIGBED.js +0 -47
  182. package/js/Track/library/File/BIGWIG.js +0 -52
  183. package/js/Track/library/File/GFF.js +0 -9
  184. package/js/Track/library/File/WIG.js +0 -5
  185. package/js/Track/library/Gene.js +0 -37
  186. package/js/Track/library/Graph/Bar.js +0 -235
  187. package/js/Track/library/Graph/Line.js +0 -296
  188. package/js/Track/library/Legend.js +0 -224
  189. package/js/Track/library/Static.js +0 -78
  190. package/js/Track.js +0 -632
  191. package/js/genoverse.min.js +0 -2
  192. package/js/genoverse.min.js.map +0 -1
  193. package/js/lib/BWReader.js +0 -578
  194. package/js/lib/Base.js +0 -145
  195. package/js/lib/VCFReader.js +0 -286
  196. package/js/lib/dalliance/js/bam.js +0 -494
  197. package/js/lib/dalliance/js/bin.js +0 -185
  198. package/js/lib/dalliance/js/das.js +0 -749
  199. package/js/lib/dalliance/js/utils.js +0 -370
  200. package/js/lib/dalliance-lib.js +0 -3594
  201. package/js/lib/dalliance-lib.min.js +0 -68
  202. package/js/lib/jDataView.js +0 -2
  203. package/js/lib/jParser.js +0 -192
  204. package/js/lib/jquery-ui.js +0 -8
  205. package/js/lib/jquery.js +0 -2
  206. package/js/lib/rtree.js +0 -1
  207. package/js/plugins/controlPanel.js +0 -395
  208. package/js/plugins/fileDrop.js +0 -62
  209. package/js/plugins/focusRegion.js +0 -12
  210. package/js/plugins/resizer.js +0 -45
  211. package/js/plugins/trackControls.js +0 -143
  212. package/utils/expandedTemplate.html +0 -46
  213. package/utils/git-hooks/post-commit +0 -9
  214. package/utils/git-hooks/pre-commit +0 -7
  215. package/utils/git-hooks/setup +0 -6
  216. package/utils/makeExpanded.js +0 -19
@@ -1,4 +1,8 @@
1
- Genoverse.Track.HighlightRegion = Genoverse.Track.extend({
1
+ import Track, { Model, View } from '../../Track';
2
+ import StrandedController from '../Controller/Stranded';
3
+ import StrandedModel from '../Model/Stranded';
4
+
5
+ export default Track.extend({
2
6
  id : 'highlights',
3
7
  unsortable : true,
4
8
  fixedOrder : true,
@@ -17,45 +21,47 @@ Genoverse.Track.HighlightRegion = Genoverse.Track.extend({
17
21
  featureMargin : { top: 13, right: 0, bottom: 0, left: 0 },
18
22
  margin : 0,
19
23
 
20
- constructor: function () {
24
+ constructor: function (...args) {
21
25
  this.colorIndex = 0;
22
- return this.base.apply(this, arguments);
26
+
27
+ return this.base(...args);
23
28
  },
24
29
 
25
30
  addHighlights: function (highlights) {
26
- for (var i = 0; i < highlights.length; i++) {
27
- this.model.insertFeature($.extend({ label: (highlights[i].start + '-' + highlights[i].end) }, highlights[i]));
28
- }
31
+ highlights.forEach(
32
+ highlight => this.model.insertFeature({ label: `${highlight.start}-${highlight.end}`, ...highlight })
33
+ );
29
34
 
30
35
  this.reset();
31
36
  },
32
37
 
33
38
  removeHighlights: function (highlights) {
34
- var featuresByChr = this.prop('featuresByChr');
35
- var featuresById = this.prop('featuresById');
36
- var features, bounds, h;
39
+ const featuresByChr = this.prop('featuresByChr');
40
+ const featuresById = this.prop('featuresById');
37
41
 
38
- highlights = highlights || $.map(featuresById, function (f) { return f; });
42
+ highlights = highlights || Object.values(featuresById);
39
43
 
40
- for (var i = 0; i < highlights.length; i++) {
41
- if (highlights[i].removable === false) {
42
- continue;
43
- }
44
+ highlights.forEach(
45
+ (highlight) => {
46
+ if (highlight.removable === false) {
47
+ return;
48
+ }
44
49
 
45
- features = featuresByChr[highlights[i].chr];
46
- bounds = { x: highlights[i].start, y: 0, w: highlights[i].end - highlights[i].start + 1, h: 1 };
50
+ const features = featuresByChr[highlight.chr];
51
+ const bounds = { x: highlight.start, y: 0, w: highlight.end - highlight.start + 1, h: 1 };
47
52
 
48
- // RTree.remove only works if the second argument (the object to be removed) === the object found in the tree.
49
- // Here, while highlight is effectively the same object as the one in the tree, it has been cloned, so the === check fails.
50
- // To fix this, search for the feature to remove in the location of highlight.
51
- h = $.grep(features.search(bounds), function (item) { return item.id === highlights[i].id; }); // eslint-disable-line no-loop-func
53
+ // RTree.remove only works if the second argument (the object to be removed) === the object found in the tree.
54
+ // Here, while highlight is effectively the same object as the one in the tree, it has been cloned, so the === check fails.
55
+ // To fix this, search for the feature to remove in the location of highlight.
56
+ const h = features.search(bounds).filter(item => item.id === highlight.id);
52
57
 
53
- if (h.length) {
54
- features.remove(bounds, h[0]);
55
- }
58
+ if (h.length) {
59
+ features.remove(bounds, h[0]);
60
+ }
56
61
 
57
- delete featuresById[highlights[i].id];
58
- }
62
+ delete featuresById[highlight.id];
63
+ }
64
+ );
59
65
 
60
66
  if (this.prop('strand') === 1) {
61
67
  this.prop('reverseTrack').removeHighlights(highlights);
@@ -64,7 +70,7 @@ Genoverse.Track.HighlightRegion = Genoverse.Track.extend({
64
70
  this.reset();
65
71
  },
66
72
 
67
- controller: Genoverse.Track.Controller.Stranded.extend({
73
+ controller: StrandedController.extend({
68
74
  setDefaults: function () {
69
75
  if (this.prop('strand') === -1) {
70
76
  this.prop('labels', false);
@@ -91,8 +97,10 @@ Genoverse.Track.HighlightRegion = Genoverse.Track.extend({
91
97
  params.background = 'gv-full-height';
92
98
  }
93
99
 
94
- var rtn = this.base(params);
100
+ const rtn = this.base(params);
101
+
95
102
  params.container.addClass(params.background);
103
+
96
104
  return rtn;
97
105
  },
98
106
 
@@ -107,59 +115,65 @@ Genoverse.Track.HighlightRegion = Genoverse.Track.extend({
107
115
  },
108
116
 
109
117
  populateMenu: function (features) {
110
- var menu = [];
111
- var location, m;
118
+ const menu = [];
112
119
 
113
120
  if (features.length > 1) {
114
121
  menu.push({ title: 'Highlights' });
115
122
  }
116
123
 
117
- for (var i = 0; i < features.length; i++) {
118
- location = features[i].start + '-' + features[i].end;
119
- m = {
120
- title : features[i].label ? features[i].label[0] : location,
121
- start : false
122
- };
124
+ features.forEach(
125
+ (feature) => {
126
+ const location = `${feature.start}-${feature.end}`;
127
+ const m = {
128
+ title : feature.label ? feature.label[0] : location,
129
+ start : false,
130
+ };
123
131
 
124
- m[m.title === location ? 'title' : 'Location'] = features[i].chr + ':' + location;
125
- m['<a class="gv-focus-highlight" href="#" data-chr="' + features[i].chr + '" data-start="' + features[i].start + '" data-end="' + features[i].end + '">Focus here</a>'] = '';
132
+ m[m.title === location ? 'title' : 'Location'] = `${feature.chr}:${location}`;
133
+ m[`<a class="gv-focus-highlight" href="#" data-chr="${feature.chr}" data-start="${feature.start}" data-end="${feature.end}">Focus here</a>`] = '';
126
134
 
127
- if (features[i].removable !== false) {
128
- m['<a class="gv-remove-highlight" href="#" data-id="' + features[i].id + '">Remove this highlight</a>'] = '';
129
- m['<a class="gv-remove-highlights" href="#">Remove all highlights</a>'] = '';
130
- }
135
+ if (feature.removable !== false) {
136
+ m[`<a class="gv-remove-highlight" href="#" data-id="${feature.id}">Remove this highlight</a>`] = '';
137
+ m['<a class="gv-remove-highlights" href="#">Remove all highlights</a>'] = '';
138
+ }
131
139
 
132
- menu.push(m);
133
- }
140
+ menu.push(m);
141
+ }
142
+ );
134
143
 
135
144
  return menu;
136
145
  },
137
146
 
138
- click: function () {
147
+ click: function (...args) {
148
+ const jQuery = this.browser.jQuery;
149
+
139
150
  if (this.prop('strand') !== 1) {
140
151
  return;
141
152
  }
142
153
 
143
- var menuEl = this.base.apply(this, arguments);
154
+ const menuEl = this.base(...args);
144
155
 
145
156
  if (menuEl && !menuEl.data('highlightEvents')) {
146
- var track = this.track;
157
+ const track = this.track;
147
158
 
148
159
  menuEl.find('.gv-remove-highlight').on('click', function () {
149
- var id = $(this).data('id');
150
- track.removeHighlights($.grep(menuEl.data('feature'), function (f) { return f.id === id; }));
160
+ const id = jQuery(this).data('id');
161
+
162
+ track.removeHighlights(menuEl.data('feature').filter(f => f.id === id));
163
+
151
164
  return false;
152
165
  });
153
166
 
154
- menuEl.find('.gv-remove-highlights').on('click', function () {
167
+ menuEl.find('.gv-remove-highlights').on('click', () => {
155
168
  track.removeHighlights();
169
+
156
170
  return false;
157
171
  });
158
172
 
159
173
  menuEl.find('.gv-focus-highlight').on('click', function () {
160
- var data = $(this).data();
161
- var length = data.end - data.start + 1;
162
- var context = Math.max(Math.round(length / 4), 25);
174
+ const data = jQuery(this).data();
175
+ const length = data.end - data.start + 1;
176
+ const context = Math.max(Math.round(length / 4), 25);
163
177
 
164
178
  track.browser.moveTo(data.chr, data.start - context, data.end + context, true);
165
179
 
@@ -171,38 +185,39 @@ Genoverse.Track.HighlightRegion = Genoverse.Track.extend({
171
185
  },
172
186
 
173
187
  getClickedFeatures: function (x, y) {
174
- var seen = {};
175
- var scale = this.scale;
176
- var features = $.grep(
177
- // feature positions
178
- this.featurePositions.search({ x: x, y: y, w: 1, h: 1 }).concat(
179
- // plus label positions where the labels are visible
180
- $.grep(this.labelPositions.search({ x: x, y: y, w: 1, h: 1 }), function (f) {
181
- return f.position[scale].label.visible !== false;
182
- })
183
- ),
184
- function (f) {
188
+ const seen = {};
189
+ const scale = this.scale;
190
+ const features =
191
+ this.featurePositions.search({ x: x, y: y, w: 1, h: 1 }).concat( // feature positions
192
+ this.labelPositions.search({ x: x, y: y, w: 1, h: 1 }).filter(// plus label positions where the labels are visible
193
+ f => f.position[scale].label.visible !== false
194
+ )
195
+ ).filter(
196
+ (f) => {
185
197
  // with duplicates removed
186
- var rtn = !seen[f.id];
198
+ const rtn = !seen[f.id];
199
+
187
200
  seen[f.id] = true;
201
+
188
202
  return rtn;
189
203
  }
190
204
  );
191
205
 
192
206
  return features.length ? [ this.model.sortFeatures(features) ] : false;
193
- }
207
+ },
194
208
  }),
195
209
 
196
- model: Genoverse.Track.Model.Stranded.extend({
210
+ model: StrandedModel.extend({
197
211
  url: false,
198
212
 
199
213
  insertFeature: function (feature) {
200
- feature.id = feature.chr + ':' + feature.start + '-' + feature.end;
214
+ feature.id = `${feature.chr}:${feature.start}-${feature.end}`;
201
215
  feature.sort = feature.start;
202
216
 
203
217
  if (!feature.color) {
204
- var colors = this.prop('colors');
205
- var i = this.prop('colorIndex');
218
+ const colors = this.prop('colors');
219
+
220
+ let i = this.prop('colorIndex');
206
221
 
207
222
  feature.color = colors[i++];
208
223
 
@@ -214,14 +229,14 @@ Genoverse.Track.HighlightRegion = Genoverse.Track.extend({
214
229
  }
215
230
  },
216
231
 
217
- findFeatures: function () {
218
- return Genoverse.Track.Model.prototype.findFeatures.apply(this, arguments);
219
- }
232
+ findFeatures: function (...args) {
233
+ return Model.prototype.findFeatures.call(this, ...args);
234
+ },
220
235
  }),
221
236
 
222
- view: Genoverse.Track.View.extend({
237
+ view: View.extend({
223
238
  positionFeatures: function (features, params) {
224
- var rtn = this.base.apply(this, arguments);
239
+ const rtn = this.base(features, params);
225
240
 
226
241
  // featureMargin.top gets used to define params.featureHeight, which is used to determine canvas height.
227
242
  // Since featureMargin.top = 13 on forward strand, the canvas has a 13px space at the bottom, meaning there is a gap before the background starts.
@@ -241,30 +256,40 @@ Genoverse.Track.HighlightRegion = Genoverse.Track.extend({
241
256
  },
242
257
 
243
258
  drawBackground: function (features, context, params) {
259
+ const jQuery = this.browser.jQuery;
260
+
244
261
  if (this.prop('strand') === -1) {
245
262
  return;
246
263
  }
247
264
 
248
- for (var i = 0; i < features.length; i++) {
249
- context.fillStyle = features[i].color;
250
-
251
- this.drawFeature($.extend(true, {}, features[i], {
252
- x : features[i].position[params.scale].X,
253
- y : 0,
254
- width : features[i].position[params.scale].width,
255
- height : context.canvas.height,
256
- color : this.shadeColor(context.fillStyle, 0.8),
257
- border : features[i].color,
258
- label : false,
259
- decorations : true
260
- }), context, false, params.scale);
261
- }
265
+ features.forEach(
266
+ (feature) => {
267
+ context.fillStyle = feature.color;
268
+
269
+ this.drawFeature(
270
+ jQuery.extend(true, {}, feature, {
271
+ x : feature.position[params.scale].X,
272
+ y : 0,
273
+ width : feature.position[params.scale].width,
274
+ height : context.canvas.height,
275
+ color : this.shadeColor(context.fillStyle, 0.8),
276
+ border : feature.color,
277
+ label : false,
278
+ decorations : true,
279
+ }),
280
+ context,
281
+ false,
282
+ params.scale
283
+ );
284
+ }
285
+ );
262
286
  },
263
287
 
264
288
  decorateFeature: function (feature, context) {
265
- var x1 = feature.x + 0.5;
266
- var x2 = x1 + feature.width;
267
- var draw = false;
289
+ const x1 = feature.x + 0.5;
290
+ const x2 = x1 + feature.width;
291
+
292
+ let draw = false;
268
293
 
269
294
  context.strokeStyle = feature.border;
270
295
  context.lineWidth = 2;
@@ -287,6 +312,6 @@ Genoverse.Track.HighlightRegion = Genoverse.Track.extend({
287
312
  }
288
313
 
289
314
  context.lineWidth = 1;
290
- }
291
- })
315
+ },
316
+ }),
292
317
  });
@@ -0,0 +1,258 @@
1
+ import Static, { Controller as StaticController, Model as StaticModel, View as StaticView } from './Static';
2
+
3
+ const Controller = StaticController.extend({
4
+ init: function () {
5
+ this.base();
6
+
7
+ this.container.addClass('gv-track-container-legend');
8
+
9
+ this.browser.legends[this.track.id] = this.track;
10
+
11
+ this.track.setTracks();
12
+ },
13
+
14
+ destroy: function () {
15
+ delete this.browser.legends[this.prop('id')];
16
+ this.base();
17
+ },
18
+ });
19
+
20
+ const Model = StaticModel.extend({
21
+ findFeatures: function () {
22
+ const bounds = { x: this.browser.scaledStart, y: 0, w: this.width };
23
+ const features = this.track.tracks.flatMap(
24
+ (track) => {
25
+ const featurePositions = track.prop('featurePositions');
26
+ const $bounds = { ...bounds, h: track.prop('height') };
27
+
28
+ return featurePositions ? featurePositions.search($bounds).concat(track.prop('labelPositions').search($bounds)) : [];
29
+ }
30
+ ).reduce(
31
+ (acc, feature) => {
32
+ if (Array.isArray(feature.legend)) {
33
+ feature.legend.forEach((legend) => { acc[legend.label] = legend.color; });
34
+ } else if (feature.legend) {
35
+ acc[feature.legend] = feature.legendColor || feature.color;
36
+ }
37
+
38
+ return acc;
39
+ },
40
+ {}
41
+ );
42
+
43
+ return this.sortFeatures(
44
+ Object.entries(features).map(([ text, color ]) => [ text, color ])
45
+ );
46
+ },
47
+
48
+ sortFeatures: function (features) {
49
+ // sort legend alphabetically
50
+ return features.sort((a, b) => a[0].localeCompare(b[0]));
51
+ },
52
+ });
53
+
54
+ const View = StaticView.extend({
55
+ textColor : '#000000',
56
+ labels : 'overlay',
57
+ featureHeight : 12,
58
+
59
+ positionFeatures: function (features, params) {
60
+ if (params.positioned) {
61
+ return features;
62
+ }
63
+
64
+ const cols = 2;
65
+ const pad = 5;
66
+ const w = 20;
67
+
68
+ let x = 0;
69
+ let y = 0;
70
+
71
+ const xScale = this.width / cols;
72
+ const yScale = this.fontHeight + pad;
73
+ const xOffest = params.xOffset || 0;
74
+ const $features = [];
75
+
76
+ features.forEach(
77
+ ([ text, color ]) => {
78
+ const xPos = (x * xScale) + pad;
79
+ const yPos = (y * yScale) + pad;
80
+ const labelWidth = this.context.measureText(text).width;
81
+
82
+ $features.push(
83
+ { x: xPos + xOffest, y: yPos, width: w, height: this.featureHeight, color: color },
84
+ { x: xPos + xOffest + pad + w, y: yPos, width: labelWidth + 1, height: this.featureHeight, color: false, labelColor: this.textColor, labelWidth: labelWidth, label: text }
85
+ );
86
+
87
+ if (++x === cols) {
88
+ x = 0;
89
+ y++;
90
+ }
91
+ }
92
+ );
93
+
94
+ params.height = this.prop('height', features.length ? ((y + (x ? 1 : 0)) * yScale) + pad : 0);
95
+ params.width = this.width;
96
+ params.positioned = true;
97
+
98
+ return this.base($features, params);
99
+ },
100
+ });
101
+
102
+ export default Static.extend({
103
+ isLegend : true, // For duck-typing
104
+ unsortable : true,
105
+ lockToTrack : true, // Always put the legend just below the last track that the legend is for
106
+ removable : false,
107
+
108
+ controller : Controller,
109
+ model : Model,
110
+ view : View,
111
+
112
+ setDefaults: function () {
113
+ this.order = typeof this.order !== 'undefined' ? this.order : 9e99;
114
+ this.id = this.id || 'legend';
115
+ this.type = this.type || 'legend';
116
+ this.base();
117
+ },
118
+
119
+ setEvents: function () {
120
+ this.browser.on({
121
+ 'afterAddTracks afterRemoveTracks': function () {
122
+ if (this.destroying) {
123
+ return;
124
+ }
125
+
126
+ Object.values(this.legends).forEach(
127
+ legend => legend.setTracks()
128
+ );
129
+
130
+ this.sortTracks();
131
+ },
132
+ afterRemoveTracks: function (tracks) {
133
+ if (this.destroying) {
134
+ return;
135
+ }
136
+
137
+ tracks.forEach(
138
+ (track) => {
139
+ if (track.legendTrack && track.legendTrack.tracks.length === 0) {
140
+ track.legendTrack.remove();
141
+ }
142
+ }
143
+ );
144
+
145
+ Object.values(this.legends).forEach(
146
+ legend => legend.controller.makeImage({})
147
+ );
148
+ },
149
+ afterUpdateTrackOrder: function (e, ui) {
150
+ const track = ui.item.data('track');
151
+ const legendTrack = this.legends[track.id] || track.legendTrack;
152
+
153
+ // If a legend track, or a track with a sortable legend has been reordered, its lockToTrack status is ignored from now on.
154
+ // This allows a legend to initially be locked to a track, but then to be reordered once the browser has been initialized
155
+ if (legendTrack && legendTrack.lockToTrack && legendTrack.unsortable === false) {
156
+ legendTrack.lockToTrack = false;
157
+ }
158
+
159
+ Object.values(this.legends).forEach(
160
+ legend => legend.updateOrder()
161
+ );
162
+
163
+ this.sortTracks();
164
+ },
165
+ });
166
+
167
+ this.browser.on(
168
+ {
169
+ afterPositionFeatures: function (features, params) {
170
+ const legend = this.prop('legendTrack');
171
+
172
+ if (legend) {
173
+ setTimeout(() => { legend.controller.makeImage(params); }, 1);
174
+ }
175
+ },
176
+ afterResize: function (height, userResize) {
177
+ const legend = this.prop('legendTrack');
178
+
179
+ if (legend && userResize === true) {
180
+ legend.controller.makeImage({});
181
+ }
182
+ },
183
+ afterCheckHeight: function () {
184
+ const legend = this.prop('legendTrack');
185
+
186
+ if (legend) {
187
+ legend.controller.makeImage({});
188
+ }
189
+ },
190
+ afterSetMVC: function () {
191
+ const legend = this.prop('legendTrack');
192
+
193
+ if (legend && legend.tracks.length) {
194
+ legend.disable();
195
+
196
+ if (this.legend !== false) {
197
+ legend.enable();
198
+ }
199
+ }
200
+ },
201
+ },
202
+ this
203
+ );
204
+ },
205
+
206
+ setTracks: function () {
207
+ const type = this.type;
208
+
209
+ this.tracks = this.browser.tracks.filter(
210
+ (track) => {
211
+ if (track.legendType === type) {
212
+ track.legendTrack = track.legendTrack || this;
213
+
214
+ return true;
215
+ }
216
+
217
+ return false;
218
+ }
219
+ ).flatMap(
220
+ track => [ track ].concat(
221
+ track.prop('childTracks'),
222
+ track.prop('parentTrack')
223
+ ).filter(
224
+ t => t && t !== this && !t.prop('disabled')
225
+ )
226
+ );
227
+
228
+ this.updateOrder();
229
+
230
+ if (typeof this.controller === 'object') {
231
+ this[this.tracks.length ? 'enable' : 'disable']();
232
+ } else {
233
+ this.disabled = !this.tracks.length;
234
+ }
235
+ },
236
+
237
+ updateOrder: function () {
238
+ if (this.lockToTrack) {
239
+ const tracks = this.tracks.filter(t => !t.prop('parentTrack'));
240
+
241
+ if (tracks.length) {
242
+ this.order = tracks[tracks.length - 1].order + 0.1;
243
+ }
244
+ }
245
+ },
246
+
247
+ enable: function () {
248
+ this.base();
249
+ this.controller.makeImage({});
250
+ },
251
+
252
+ disable: function () {
253
+ delete this.controller.stringified;
254
+ this.base();
255
+ },
256
+ });
257
+
258
+ export { Controller, Model, View };