genoverse 3.2.0

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 (148) hide show
  1. package/.eslintrc.js +197 -0
  2. package/.github/workflows/test.yml +24 -0
  3. package/LICENSE.TXT +24 -0
  4. package/README.md +11 -0
  5. package/css/controlPanel.css +200 -0
  6. package/css/fileDrop.css +22 -0
  7. package/css/font-awesome.css +3 -0
  8. package/css/fullscreen.css +19 -0
  9. package/css/genoverse.css +466 -0
  10. package/css/karyotype.css +85 -0
  11. package/css/resizer.css +36 -0
  12. package/css/tooltips.css +26 -0
  13. package/css/trackControls.css +111 -0
  14. package/expanded.html +120 -0
  15. package/fontawesome/css/fontawesome.min.css +5 -0
  16. package/fontawesome/css/regular.min.css +5 -0
  17. package/fontawesome/css/solid.min.css +5 -0
  18. package/fontawesome/webfonts/fa-brands-400.ttf +0 -0
  19. package/fontawesome/webfonts/fa-brands-400.woff +0 -0
  20. package/fontawesome/webfonts/fa-brands-400.woff2 +0 -0
  21. package/fontawesome/webfonts/fa-regular-400.ttf +0 -0
  22. package/fontawesome/webfonts/fa-regular-400.woff +0 -0
  23. package/fontawesome/webfonts/fa-regular-400.woff2 +0 -0
  24. package/fontawesome/webfonts/fa-solid-900.ttf +0 -0
  25. package/fontawesome/webfonts/fa-solid-900.woff +0 -0
  26. package/fontawesome/webfonts/fa-solid-900.woff2 +0 -0
  27. package/help.pdf +0 -0
  28. package/i/sort_handle.png +0 -0
  29. package/index.html +68 -0
  30. package/index.js +83 -0
  31. package/jest.config.js +4 -0
  32. package/js/Genoverse.js +1681 -0
  33. package/js/Track/Controller/Sequence.js +17 -0
  34. package/js/Track/Controller/Stranded.js +73 -0
  35. package/js/Track/Controller.js +620 -0
  36. package/js/Track/Model/File/BAM.js +44 -0
  37. package/js/Track/Model/File/BED.js +116 -0
  38. package/js/Track/Model/File/GFF.js +40 -0
  39. package/js/Track/Model/File/VCF.js +101 -0
  40. package/js/Track/Model/File/WIG.js +67 -0
  41. package/js/Track/Model/File.js +36 -0
  42. package/js/Track/Model/Gene/Ensembl.js +22 -0
  43. package/js/Track/Model/Gene.js +5 -0
  44. package/js/Track/Model/Sequence/Ensembl.js +4 -0
  45. package/js/Track/Model/Sequence/Fasta.js +60 -0
  46. package/js/Track/Model/Sequence.js +50 -0
  47. package/js/Track/Model/SequenceVariation.js +41 -0
  48. package/js/Track/Model/Stranded.js +28 -0
  49. package/js/Track/Model/Transcript/Ensembl.js +67 -0
  50. package/js/Track/Model/Transcript.js +5 -0
  51. package/js/Track/Model.js +303 -0
  52. package/js/Track/View/Gene/Ensembl.js +46 -0
  53. package/js/Track/View/Gene.js +6 -0
  54. package/js/Track/View/Sequence/Variation.js +115 -0
  55. package/js/Track/View/Sequence.js +63 -0
  56. package/js/Track/View/Transcript/Ensembl.js +12 -0
  57. package/js/Track/View/Transcript.js +28 -0
  58. package/js/Track/View.js +566 -0
  59. package/js/Track/library/Chromosome.js +145 -0
  60. package/js/Track/library/File/BAM.js +30 -0
  61. package/js/Track/library/File/BED.js +24 -0
  62. package/js/Track/library/File/BIGBED.js +47 -0
  63. package/js/Track/library/File/BIGWIG.js +52 -0
  64. package/js/Track/library/File/GFF.js +9 -0
  65. package/js/Track/library/File/VCF.js +71 -0
  66. package/js/Track/library/File/WIG.js +5 -0
  67. package/js/Track/library/File.js +10 -0
  68. package/js/Track/library/Gene.js +37 -0
  69. package/js/Track/library/Graph/Bar.js +235 -0
  70. package/js/Track/library/Graph/Line.js +296 -0
  71. package/js/Track/library/Graph.js +355 -0
  72. package/js/Track/library/HighlightRegion.js +292 -0
  73. package/js/Track/library/Legend.js +224 -0
  74. package/js/Track/library/Scalebar.js +227 -0
  75. package/js/Track/library/Scaleline.js +91 -0
  76. package/js/Track/library/Static.js +78 -0
  77. package/js/Track/library/dbSNP.js +142 -0
  78. package/js/Track.js +632 -0
  79. package/js/genomes/grch37.js +990 -0
  80. package/js/genomes/grch38.js +990 -0
  81. package/js/genoverse.min.js +2 -0
  82. package/js/genoverse.min.js.map +1 -0
  83. package/js/lib/BWReader.js +578 -0
  84. package/js/lib/Base.js +145 -0
  85. package/js/lib/VCFReader.js +286 -0
  86. package/js/lib/dalliance/js/bam.js +494 -0
  87. package/js/lib/dalliance/js/bin.js +185 -0
  88. package/js/lib/dalliance/js/das.js +749 -0
  89. package/js/lib/dalliance/js/utils.js +370 -0
  90. package/js/lib/dalliance-lib.js +3594 -0
  91. package/js/lib/dalliance-lib.min.js +68 -0
  92. package/js/lib/jDataView.js +2 -0
  93. package/js/lib/jParser.js +192 -0
  94. package/js/lib/jquery-ui.js +8 -0
  95. package/js/lib/jquery.js +2 -0
  96. package/js/lib/jquery.mousehold.js +53 -0
  97. package/js/lib/jquery.mousewheel.js +84 -0
  98. package/js/lib/jquery.tipsy.js +258 -0
  99. package/js/lib/rtree.js +1 -0
  100. package/js/plugins/controlPanel.js +395 -0
  101. package/js/plugins/fileDrop.js +62 -0
  102. package/js/plugins/focusRegion.js +12 -0
  103. package/js/plugins/fullscreen.js +77 -0
  104. package/js/plugins/karyotype.js +210 -0
  105. package/js/plugins/resizer.js +45 -0
  106. package/js/plugins/tooltips.js +94 -0
  107. package/js/plugins/trackControls.js +143 -0
  108. package/package.json +43 -0
  109. package/test/View/__snapshots__/render-bar-graph.test.js.snap +111 -0
  110. package/test/View/__snapshots__/render-blocks.test.js.snap +105 -0
  111. package/test/View/__snapshots__/render-chromosome.test.js.snap +5 -0
  112. package/test/View/__snapshots__/render-highlights.test.js.snap +73 -0
  113. package/test/View/__snapshots__/render-insert-variants.test.js.snap +9 -0
  114. package/test/View/__snapshots__/render-labels.test.js.snap +241 -0
  115. package/test/View/__snapshots__/render-legends.test.js.snap +13 -0
  116. package/test/View/__snapshots__/render-line-graph.test.js.snap +349 -0
  117. package/test/View/__snapshots__/render-scalebar.test.js.snap +49 -0
  118. package/test/View/__snapshots__/render-scaleline.test.js.snap +31 -0
  119. package/test/View/__snapshots__/render-sequence.test.js.snap +23 -0
  120. package/test/View/__snapshots__/render-stranded.test.js.snap +5 -0
  121. package/test/View/__snapshots__/render-transcripts.test.js.snap +193 -0
  122. package/test/View/render-bar-graph.test.js +87 -0
  123. package/test/View/render-blocks.test.js +171 -0
  124. package/test/View/render-chromosome.test.js +40 -0
  125. package/test/View/render-highlights.test.js +67 -0
  126. package/test/View/render-insert-variants.test.js +11 -0
  127. package/test/View/render-labels.test.js +266 -0
  128. package/test/View/render-legends.test.js +31 -0
  129. package/test/View/render-line-graph.test.js +169 -0
  130. package/test/View/render-scalebar.test.js +36 -0
  131. package/test/View/render-scaleline.test.js +28 -0
  132. package/test/View/render-sequence.test.js +49 -0
  133. package/test/View/render-stranded.test.js +10 -0
  134. package/test/View/render-transcripts.test.js +165 -0
  135. package/test/create-and-destroy.test.js +63 -0
  136. package/test/track-ordering.test.js +514 -0
  137. package/test/track_config/__snapshots__/config-settings.test.js.snap +23 -0
  138. package/test/track_config/config-settings.test.js +321 -0
  139. package/test/track_config/zoom-level-settings.test.js +98 -0
  140. package/test/utils.js +80 -0
  141. package/utils/createGenome.js +52 -0
  142. package/utils/devServer.js +36 -0
  143. package/utils/expandedTemplate.html +46 -0
  144. package/utils/git-hooks/post-commit +9 -0
  145. package/utils/git-hooks/pre-commit +7 -0
  146. package/utils/git-hooks/setup +6 -0
  147. package/utils/makeExpanded.js +19 -0
  148. package/webpack.config.js +39 -0
@@ -0,0 +1,620 @@
1
+ Genoverse.Track.Controller = Base.extend({
2
+ scrollBuffer : 1.2, // Number of widths, if left or right closer to the edges of viewpoint than the buffer, start making more images
3
+ threshold : Infinity, // Length above which the track is not drawn
4
+ clickTolerance : 0, // pixels of tolerance added to a click position when finding features for popup menus, when scale < 1
5
+ messages : undefined,
6
+
7
+ constructor: function (properties) {
8
+ $.extend(this, properties);
9
+ Genoverse.wrapFunctions(this);
10
+ this.init();
11
+ },
12
+
13
+ init: function () {
14
+ this.setDefaults();
15
+ this.addDomElements();
16
+ this.addUserEventHandlers();
17
+
18
+ this.deferreds = []; // tracks deferreds so they can be stopped if the track is destroyed
19
+ },
20
+
21
+ setDefaults: function () {
22
+ this.imgRange = {};
23
+ this.scrollRange = {};
24
+ this.messages = this.messages || {
25
+ error : 'ERROR: ',
26
+ threshold : 'Data for this track is not displayed in regions greater than ',
27
+ resize : 'Some features are currently hidden, <a class="gv-resize">resize to see all</a>'
28
+ };
29
+ },
30
+
31
+ reset: function () {
32
+ this.abort();
33
+ this.setDefaults();
34
+ this.resetImages();
35
+ this.browser.closeMenus(this);
36
+
37
+ if (arguments[0] !== 'resizing') {
38
+ this.setScale();
39
+ this.makeFirstImage();
40
+ }
41
+ },
42
+
43
+ resetImages: function () {
44
+ this.scrollContainer.empty();
45
+ this.resetImageRanges();
46
+ },
47
+
48
+ resetImageRanges: function () {
49
+ var browser = this.browser;
50
+
51
+ this.left = 0;
52
+ this.scrollStart = [ 'ss', browser.chr, browser.start, browser.end ].join('-');
53
+
54
+ this.imgRange[this.scrollStart] = this.imgRange[this.scrollStart] || { left: this.width * -2, right: this.width * 2 };
55
+ this.scrollRange[this.scrollStart] = this.scrollRange[this.scrollStart] || { start: Math.max(browser.start - browser.length, 1), end: Math.min(browser.end + browser.length, browser.chromosomeSize) };
56
+ },
57
+
58
+ setName: function (name, configName) {
59
+ this.track.name = name;
60
+ this.labelName = this.labelName || $('<span class="gv-name">').appendTo(this.label);
61
+
62
+ this.labelName.attr('title', name).html(
63
+ configName && configName.length
64
+ ? configName.map(function (part) { return '<span class="gv-name-part">' + part + '</span>'; })
65
+ : name
66
+ );
67
+
68
+ this.minLabelHeight = this.label.parents('body').length ? Math.max(this.labelName.outerHeight(true), this.labelName.outerHeight()) : 0;
69
+
70
+ this.setLabelHeight(true);
71
+
72
+ if (name && !this.track._constructing && this.track.height < this.minLabelHeight) {
73
+ this.resize(this.minLabelHeight);
74
+ }
75
+ },
76
+
77
+ addDomElements: function () {
78
+ var name = this.track.name || '';
79
+
80
+ this.menus = $();
81
+ this.container = $('<div class="gv-track-container">').appendTo(this.browser.wrapper);
82
+ this.scrollContainer = $('<div class="gv-scroll-container">').appendTo(this.container);
83
+ this.imgContainer = $('<div class="gv-image-container">').width(this.width).addClass(this.prop('invert') ? 'gv-invert' : '');
84
+ this.messageContainer = $('<div class="gv-message-container"><div class="gv-messages"></div><i class="gv-control gv-collapse fas fa-angle-double-left"></i><i class="gv-control gv-expand fas fa-angle-double-right"></i></div>').appendTo(this.container);
85
+ this.label = $('<li>').appendTo(this.browser.labelContainer).height(this.prop('height')).data('track', this.track);
86
+ this.context = $('<canvas>')[0].getContext('2d');
87
+
88
+ if (this.prop('border')) {
89
+ $('<div class="gv-track-border">').appendTo(this.container);
90
+ }
91
+
92
+ if (this.prop('unsortable')) {
93
+ this.label.addClass('gv-unsortable');
94
+ } else {
95
+ $('<div class="gv-handle">').appendTo(this.label);
96
+ }
97
+
98
+ if (this.prop('children')) {
99
+ this.superContainer = $('<div class="gv-track-container gv-track-super-container">').insertAfter(this.container);
100
+ this.container.appendTo(this.superContainer);
101
+ } else if (this.prop('parentTrack')) {
102
+ this.superContainer = this.prop('parentTrack').prop('superContainer');
103
+
104
+ this.container.appendTo(this.superContainer);
105
+ this.label.remove();
106
+
107
+ this.label = this.prop('parentTrack').prop('label');
108
+ }
109
+
110
+ this.setName(name, this.track.configName);
111
+
112
+ this.container.height(this.prop('disabled') ? 0 : Math.max(this.prop('height'), this.minLabelHeight));
113
+ },
114
+
115
+ addUserEventHandlers: function () {
116
+ var controller = this;
117
+ var browser = this.browser;
118
+
119
+ this.container.on('mouseup', '.gv-image-container', function (e) {
120
+ if ((e.which && e.which !== 1) || (typeof browser.dragStart === 'number' && browser.start !== browser.dragStart) || (browser.dragAction === 'select' && browser.selector.outerWidth(true) > 2)) {
121
+ return; // Only show menus on left click when not dragging and not selecting
122
+ }
123
+
124
+ controller.click(e);
125
+ });
126
+
127
+ this.messageContainer.children().on('click', function () {
128
+ var collapsed = controller.messageContainer.children('.gv-messages').is(':visible') ? ' gv-collapsed' : '';
129
+ var code = controller.messageContainer.find('.gv-msg').data('code');
130
+
131
+ controller.messageContainer.attr('class', 'gv-message-container' + collapsed);
132
+ controller.checkHeight();
133
+
134
+ if (code !== 'error') {
135
+ document.cookie = [ 'gv_msg', code, controller.prop('id') ].join('_') + '=1; expires=' + (collapsed ? 'Tue, 19 Jan 2038' : 'Thu, 01 Jan 1970') + ' 00:00:00 GMT; path=/';
136
+ }
137
+ });
138
+ },
139
+
140
+ click: function (e) {
141
+ var target = $(e.target);
142
+ var x = e.pageX - this.container.parent().offset().left + this.browser.scaledStart;
143
+ var y = e.pageY - target.offset().top;
144
+
145
+ if (this.imgContainer.hasClass('gv-invert')) {
146
+ y = target.height() - y;
147
+ }
148
+
149
+ return this.browser.makeMenu(this.getClickedFeatures(x, y, target), e, this.track);
150
+ },
151
+
152
+ getClickedFeatures: function (x, y, target) {
153
+ var bounds = { x: x, y: y, w: 1, h: 1 };
154
+ var scale = this.scale;
155
+ var tolerance = scale < 1 ? this.clickTolerance : 0;
156
+
157
+ if (tolerance) {
158
+ bounds.x -= tolerance / 2;
159
+ bounds.w += tolerance;
160
+ }
161
+
162
+ var features = this[target && target.hasClass('gv-labels') ? 'labelPositions' : 'featurePositions'].search(bounds);
163
+
164
+ if (tolerance) {
165
+ return features.filter(function (f) {
166
+ var featureBounds = f.position[scale].bounds;
167
+ var center = featureBounds.x + (featureBounds.w / 2);
168
+ var minX = Math.min(featureBounds.x, center - (tolerance / 2));
169
+ var maxX = Math.max(featureBounds.x + featureBounds.w, center + (tolerance / 2));
170
+
171
+ return x >= minX && x <= maxX;
172
+ }).sort(function (a, b) {
173
+ return Math.abs(a.position[scale].start - x) - Math.abs(b.position[scale].start - x);
174
+ });
175
+ }
176
+
177
+ return this.model.sortFeatures(features);
178
+ },
179
+
180
+ // FIXME: messages are now hidden/shown instead of removed/added. This will cause a problem if a new message arrives with the same code as one that already exists.
181
+ showMessage: function (code, additionalText) {
182
+ var messages = this.messageContainer.children('.gv-messages');
183
+
184
+ if (!messages.children('.gv-' + code).show().length) {
185
+ var msg = $('<div class="gv-msg gv-' + code + '">' + this.messages[code] + (additionalText || '') + '</div>').data('code', code).prependTo(messages);
186
+
187
+ if (code === 'resize') {
188
+ msg.children('a.gv-resize').on('click', $.proxy(function () {
189
+ this.resize(this.fullVisibleHeight);
190
+ }, this));
191
+ }
192
+
193
+ this.messageContainer[document.cookie.match([ 'gv_msg', code, this.prop('id') ].join('_') + '=1') ? 'addClass' : 'removeClass']('gv-collapsed');
194
+ }
195
+
196
+ var height = this.messageContainer.show().outerHeight(true);
197
+
198
+ if (height > this.prop('height')) {
199
+ this.resize(height, undefined, false);
200
+ }
201
+
202
+ messages = null;
203
+ },
204
+
205
+ hideMessage: function (code) {
206
+ var messages = this.messageContainer.find('.gv-msg');
207
+
208
+ if (code) {
209
+ messages = messages.filter('.gv-' + code).hide();
210
+
211
+ if (messages.length && !messages.siblings().filter(function () { return this.style.display !== 'none'; }).length) {
212
+ this.messageContainer.hide();
213
+ }
214
+ } else {
215
+ messages.hide();
216
+ this.messageContainer.hide();
217
+ }
218
+
219
+ messages = null;
220
+ },
221
+
222
+ showError: function (error) {
223
+ this.showMessage('error', error);
224
+ },
225
+
226
+ checkHeight: function () {
227
+ if (this.browser.length > this.threshold) {
228
+ if (this.thresholdMessage) {
229
+ this.showMessage('threshold', this.thresholdMessage);
230
+ this.fullVisibleHeight = Math.max(this.messageContainer.outerHeight(true), this.minLabelHeight);
231
+ } else {
232
+ this.fullVisibleHeight = 0;
233
+ }
234
+ } else if (this.thresholdMessage) {
235
+ this.hideMessage('threshold');
236
+ }
237
+
238
+ if (!this.prop('resizable')) {
239
+ return;
240
+ }
241
+
242
+ var autoHeight;
243
+
244
+ if (this.browser.length > this.threshold) {
245
+ autoHeight = this.prop('autoHeight');
246
+ this.prop('autoHeight', true);
247
+ } else {
248
+ this.fullVisibleHeight = this.visibleFeatureHeight() || (this.messageContainer.is(':visible') ? this.messageContainer.outerHeight(true) : this.prop('hideEmpty') ? 0 : this.minLabelHeight);
249
+ }
250
+
251
+ this.autoResize();
252
+
253
+ if (typeof autoHeight !== 'undefined') {
254
+ this.prop('autoHeight', autoHeight);
255
+ }
256
+ },
257
+
258
+ visibleFeatureHeight: function () {
259
+ var bounds = { x: this.browser.scaledStart, w: this.width, y: 0, h: 9e99 };
260
+ var scale = this.scale;
261
+ var features = this.featurePositions.search(bounds);
262
+ var minHeight = this.prop('hideEmpty') ? 0 : this.minLabelHeight;
263
+ var height = Math.max.apply(Math, $.map(features, function (feature) { return feature.position[scale].bottom; }).concat(minHeight));
264
+
265
+ if (this.prop('labels') === 'separate') {
266
+ this.labelTop = height;
267
+ height += Math.max.apply(Math, $.map(this.labelPositions.search(bounds).concat(this.prop('repeatLabels') ? features : []), function (feature) { return feature.position[scale].label.bottom; }).concat(minHeight));
268
+ }
269
+
270
+ return height;
271
+ },
272
+
273
+ autoResize: function () {
274
+ var autoHeight = this.prop('autoHeight');
275
+
276
+ if (autoHeight || this.prop('labels') === 'separate') {
277
+ this.resize(autoHeight ? this.fullVisibleHeight : this.prop('height'), this.labelTop, false);
278
+ } else {
279
+ this.toggleExpander(false);
280
+ }
281
+ },
282
+
283
+ resize: function (height, arg, saveConfig) {
284
+ height = this.track.setHeight(height, arg);
285
+
286
+ if (typeof arg === 'number') {
287
+ this.imgContainers.children('.gv-labels').css('top', arg);
288
+ }
289
+
290
+ this.container.height(height)[height ? 'show' : 'hide']();
291
+ this.setLabelHeight();
292
+ this.toggleExpander();
293
+
294
+ if (saveConfig !== false) {
295
+ this.browser.saveConfig();
296
+ }
297
+ },
298
+
299
+ toggleExpander: function (saveConfig) {
300
+ if (this.prop('resizable') !== true) {
301
+ return;
302
+ }
303
+
304
+ var featureMargin = this.prop('featureMargin');
305
+ var height = this.prop('height');
306
+
307
+ // Note: fullVisibleHeight - featureMargin.top - featureMargin.bottom is not actually the correct value to test against, but it's the easiest best guess to obtain.
308
+ // fullVisibleHeight is the maximum bottom position of the track's features in the region, which includes margin at the bottom of each feature and label
309
+ // Therefore fullVisibleHeight includes this margin for the bottom-most feature.
310
+ // The correct value (for a track using the default positionFeatures code) is:
311
+ // fullVisibleHeight - ([there are labels in this region] ? (labels === 'separate' ? 0 : featureMargin.bottom + 1) + 2 : featureMargin.bottom)
312
+ if (this.fullVisibleHeight - featureMargin.top - featureMargin.bottom > height && !this.prop('disabled')) {
313
+ this.showMessage('resize');
314
+
315
+ var controller = this;
316
+ var h = this.messageContainer.outerHeight(true);
317
+
318
+ if (h > height) {
319
+ this.resize(h, undefined, saveConfig);
320
+ }
321
+
322
+ this.expander = (this.expander || $('<div class="gv-expander gv-static">').width(this.width).appendTo(this.container).on('click', function () {
323
+ controller.resize(controller.fullVisibleHeight);
324
+ }))[this.prop('height') === 0 ? 'hide' : 'show']();
325
+ } else if (this.expander) {
326
+ this.hideMessage('resize');
327
+ this.expander.hide();
328
+ }
329
+ },
330
+
331
+ setLabelHeight: function (enforceMinHeight) {
332
+ var parent = this.prop('parentTrack');
333
+
334
+ if (parent) {
335
+ return parent.controller.setLabelHeight();
336
+ }
337
+
338
+ var tracks = [ this ].concat(this.prop('childTracks') || []);
339
+ var height = tracks.reduce(function (h, track) { return h + (track.prop('disabled') ? 0 : track.prop('height')); }, 0);
340
+
341
+ this.label.height(this.prop('disabled') ? 0 : enforceMinHeight && this.minLabelHeight ? Math.max(height, this.minLabelHeight) : height);
342
+
343
+ if (tracks.length > 1) {
344
+ var top = tracks[0].prop('height');
345
+
346
+ tracks.slice(1).forEach(function (track) {
347
+ var h = track.prop('height');
348
+
349
+ track.prop('labelName').css('top', top)[h ? 'removeClass' : 'addClass']('gv-hide');
350
+ top += h;
351
+ });
352
+ }
353
+ },
354
+
355
+ setWidth: function (width) {
356
+ var track = this.track;
357
+
358
+ $.each([ this, track, track.model, track.view ], function () {
359
+ this.width = width;
360
+ });
361
+
362
+ this.imgContainer.add(this.expander).width(width);
363
+ },
364
+
365
+ setScale: function () {
366
+ var controller = this;
367
+
368
+ this.scale = this.browser.scale;
369
+
370
+ this.track.setMVC();
371
+ this.resetImageRanges();
372
+
373
+ var labels = this.prop('labels');
374
+
375
+ if (labels && labels !== 'overlay') {
376
+ this.model.setLabelBuffer(this.browser.labelBuffer);
377
+ }
378
+
379
+ if (this.threshold !== Infinity && this.prop('resizable') !== 'auto') {
380
+ this.thresholdMessage = this.view.formatLabel(this.threshold);
381
+ }
382
+
383
+ $.each(this.view.setScaleSettings(this.scale), function (k, v) { controller[k] = v; });
384
+
385
+ this.hideMessage();
386
+ },
387
+
388
+ move: function (delta) {
389
+ this.left += delta;
390
+ this.scrollContainer.css('left', this.left);
391
+
392
+ var scrollStart = this.scrollStart;
393
+
394
+ if (this.imgRange[scrollStart] && this.imgRange[scrollStart].left + this.left > -this.scrollBuffer * this.width) {
395
+ var end = this.scrollRange[scrollStart].start - 1;
396
+
397
+ this.makeImage({
398
+ scale : this.scale,
399
+ chr : this.browser.chr,
400
+ start : end - this.browser.length + 1,
401
+ end : end,
402
+ left : this.imgRange[scrollStart].left,
403
+ cls : scrollStart
404
+ });
405
+
406
+ (this.imgRange[scrollStart] || {}).left -= this.width;
407
+ (this.scrollRange[scrollStart] || {}).start -= this.browser.length;
408
+ }
409
+
410
+ if (this.imgRange[scrollStart] && this.imgRange[scrollStart].right + this.left < this.scrollBuffer * this.width) {
411
+ var start = this.scrollRange[scrollStart].end + 1;
412
+
413
+ this.makeImage({
414
+ scale : this.scale,
415
+ chr : this.browser.chr,
416
+ start : start,
417
+ end : start + this.browser.length - 1,
418
+ left : this.imgRange[scrollStart].right,
419
+ cls : scrollStart
420
+ });
421
+
422
+ (this.imgRange[scrollStart] || {}).right += this.width;
423
+ (this.scrollRange[scrollStart] || {}).end += this.browser.length;
424
+ }
425
+ },
426
+
427
+ moveTo: function (chr, start, end, delta) {
428
+ var scrollRange = this.scrollRange[this.scrollStart];
429
+ var scrollStart = [ 'ss', chr, start, end ].join('-');
430
+
431
+ if (this.scrollRange[scrollStart] || start > scrollRange.end || end < scrollRange.start) {
432
+ this.resetImageRanges();
433
+ this.makeFirstImage(scrollStart);
434
+ } else {
435
+ this.move(typeof delta === 'number' ? delta : (start - this.browser.start) * this.scale);
436
+ this.checkHeight();
437
+ }
438
+ },
439
+
440
+ makeImage: function (params) {
441
+ params.scaledStart = params.scaledStart || params.start * params.scale;
442
+ params.width = params.width || this.width;
443
+ params.height = params.height || this.prop('height');
444
+ params.featureHeight = params.featureHeight || 0;
445
+ params.labelHeight = params.labelHeight || 0;
446
+
447
+ var deferred;
448
+ var controller = this;
449
+ var tooLarge = this.browser.length > this.threshold;
450
+ var div = this.imgContainer.clone().addClass((params.cls + ' gv-loading').replace('.', '_')).css({ left: params.left, display: params.cls === this.scrollStart ? 'block' : 'none' });
451
+ var bgImage = params.background ? $('<img class="gv-bg">').hide().addClass(params.background).data(params).prependTo(div) : false;
452
+ var image = $('<img class="gv-data">').hide().data(params).appendTo(div).on('load', function () {
453
+ $(this).fadeIn('fast').parent().removeClass('gv-loading');
454
+ $(this).siblings('.gv-bg').show();
455
+ });
456
+
457
+ params.container = div;
458
+
459
+ this.imgContainers.push(div[0]);
460
+ this.scrollContainer.append(this.imgContainers);
461
+
462
+ if (!tooLarge && !this.model.checkDataRange(params.chr, params.start, params.end)) {
463
+ var buffer = this.prop('dataBuffer');
464
+
465
+ params.start -= buffer.start;
466
+ params.end += buffer.end;
467
+ deferred = this.model.getData(params.chr, params.start, params.end);
468
+ }
469
+
470
+ if (!deferred) {
471
+ deferred = $.Deferred();
472
+ setTimeout($.proxy(deferred.resolve, this), 1); // This defer makes scrolling A LOT smoother, pushing render call to the end of the exec queue
473
+ }
474
+
475
+ this.deferreds.push(deferred);
476
+
477
+ return deferred.done(function () {
478
+ var features = tooLarge ? [] : controller.model.findFeatures(params.chr, params.start, params.end);
479
+ controller.render(features, image);
480
+
481
+ if (bgImage) {
482
+ controller.renderBackground(features, bgImage);
483
+ }
484
+ }).fail(function (e) {
485
+ controller.showError(e);
486
+ });
487
+ },
488
+
489
+ makeFirstImage: function (moveTo) {
490
+ var deferred = $.Deferred();
491
+
492
+ if (this.scrollContainer.children().hide().filter('.' + (moveTo || this.scrollStart)).show().length) {
493
+ this.scrollContainer.css('left', 0);
494
+ this.checkHeight();
495
+
496
+ return deferred.resolve();
497
+ }
498
+
499
+ var controller = this;
500
+ var chr = this.browser.chr;
501
+ var start = this.browser.start;
502
+ var end = this.browser.end;
503
+ var length = this.browser.length;
504
+ var scale = this.scale;
505
+ var cls = this.scrollStart;
506
+ var images = [{ chr: chr, start: start, end: end, scale: scale, cls: cls, left: 0 }];
507
+ var left = 0;
508
+ var width = this.width;
509
+
510
+ if (!this.browser.isStatic) {
511
+ if (start > 1) {
512
+ images.push({ chr: chr, start: start - length, end: start - 1, scale: scale, cls: cls, left: -this.width });
513
+ left = -this.width;
514
+ width += this.width;
515
+ }
516
+
517
+ if (end < this.browser.getChromosomeSize(chr)) {
518
+ images.push({ chr: chr, start: end + 1, end: end + length, scale: scale, cls: cls, left: this.width });
519
+ width += this.width;
520
+ }
521
+ }
522
+
523
+ var loading = this.imgContainer.clone().addClass('gv-loading').css({ left: left, width: width }).prependTo(this.scrollContainer.css('left', 0));
524
+
525
+ function makeImages() {
526
+ $.when.apply($, images.map(function (image) {
527
+ return controller.makeImage(image);
528
+ })).done(deferred.resolve);
529
+
530
+ loading.remove();
531
+ }
532
+
533
+ if (length > this.threshold || this.model.checkDataRange(chr, start, end)) {
534
+ makeImages();
535
+ } else {
536
+ var buffer = this.prop('dataBuffer');
537
+
538
+ this.model.getData(chr, start - buffer.start - length, end + buffer.end + length).done(makeImages).fail(function (e) {
539
+ controller.showError(e);
540
+ });
541
+ }
542
+
543
+ return deferred;
544
+ },
545
+
546
+ render: function (features, img) {
547
+ var params = img.data();
548
+
549
+ // positionFeatures alters params.featureHeight, so this must happen before the canvases are created
550
+ features = this.view.positionFeatures(this.view.scaleFeatures(features, params.scale), params);
551
+
552
+ var featureCanvas = $('<canvas>').attr({ width: params.width, height: params.featureHeight || 1 });
553
+ var labelCanvas = this.prop('labels') === 'separate' && params.labelHeight ? featureCanvas.clone().attr('height', params.labelHeight) : featureCanvas;
554
+ var featureContext = featureCanvas[0].getContext('2d');
555
+ var labelContext = labelCanvas[0].getContext('2d');
556
+
557
+ featureContext.font = labelContext.font = this.prop('font');
558
+
559
+ switch (this.prop('labels')) {
560
+ case false : break;
561
+ case 'overlay' : labelContext.textAlign = 'center'; labelContext.textBaseline = 'middle'; break;
562
+ default : labelContext.textAlign = 'left'; labelContext.textBaseline = 'top'; break;
563
+ }
564
+
565
+ this.view.draw(features, featureContext, labelContext, params.scale);
566
+
567
+ img.attr('src', featureCanvas[0].toDataURL());
568
+
569
+ if (labelContext !== featureContext) {
570
+ img.clone(true).attr({ 'class': 'gv-labels', src: labelCanvas[0].toDataURL() }).insertAfter(img);
571
+ }
572
+
573
+ this.checkHeight();
574
+
575
+ featureCanvas = labelCanvas = img = null;
576
+ },
577
+
578
+ renderBackground: function (features, img, height) {
579
+ var canvas = $('<canvas>').attr({ width: this.width, height: height || 1 })[0];
580
+ this.view.drawBackground(features, canvas.getContext('2d'), img.data());
581
+ img.attr('src', canvas.toDataURL());
582
+ canvas = img = null;
583
+ },
584
+
585
+ populateMenu: function (feature) {
586
+ var f = $.extend(true, {}, feature);
587
+ var menu = {
588
+ title : f.label ? f.label[0] : f.id,
589
+ Location : f.chr + ':' + f.start + '-' + f.end
590
+ };
591
+
592
+ delete f.chr;
593
+ delete f.start;
594
+ delete f.end;
595
+ delete f.sort;
596
+
597
+ for (var i in f) {
598
+ if (typeof f[i] === 'object' || menu.title === f[i]) {
599
+ delete f[i];
600
+ }
601
+ }
602
+
603
+ return $.extend(menu, f);
604
+ },
605
+
606
+ abort: function () {
607
+ for (var i = 0; i < this.deferreds.length; i++) {
608
+ if (this.deferreds[i].state() === 'pending') {
609
+ this.deferreds[i].reject();
610
+ }
611
+ }
612
+
613
+ this.deferreds = [];
614
+ },
615
+
616
+ destroy: function () {
617
+ this.abort();
618
+ this.container.add(this.label).add(this.menus).remove();
619
+ }
620
+ });
@@ -0,0 +1,44 @@
1
+ Genoverse.Track.Model.File.BAM = Genoverse.Track.Model.File.extend({
2
+ getData: function (chr, start, end) {
3
+ var model = this;
4
+ var deferred = $.Deferred();
5
+
6
+ if (!this.bamFile) {
7
+ if (this.url) {
8
+ this.bamFile = new dallianceLib.URLFetchable(this.url);
9
+ this.baiFile = new dallianceLib.URLFetchable(this.url + this.prop('indexExt'));
10
+ } else if (this.dataFile && this.indexFile) {
11
+ this.bamFile = new dallianceLib.BlobFetchable(this.dataFile);
12
+ this.baiFile = new dallianceLib.BlobFetchable(this.indexFile);
13
+ } else {
14
+ return deferred.rejectWith(model, [ 'BAM files must be accompanied by a .bai index file' ]);
15
+ }
16
+ }
17
+
18
+ dallianceLib.makeBam(this.bamFile, this.baiFile, null, function (bam, makeBamError) {
19
+ if (makeBamError) {
20
+ console.error(makeBamError); // eslint-disable-line no-console
21
+ } else {
22
+ bam.fetch(chr, start, end, function (features, fetchBamError) {
23
+ if (fetchBamError) {
24
+ console.error(fetchBamError); // eslint-disable-line no-console
25
+ } else {
26
+ model.receiveData(features, chr, start, end);
27
+ deferred.resolveWith(model);
28
+ }
29
+ });
30
+ }
31
+ });
32
+
33
+ return deferred;
34
+ },
35
+
36
+ insertFeature: function (feature) {
37
+ feature.id = feature.chr + ':' + feature.readName + ':' + feature.pos;
38
+ feature.start = feature.pos + 1;
39
+ feature.end = feature.start + feature.seq.length;
40
+ feature.sequence = feature.seq;
41
+
42
+ return this.base(feature);
43
+ }
44
+ });