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
package/js/Track.js ADDED
@@ -0,0 +1,632 @@
1
+ Genoverse.Track = Base.extend({
2
+ height : 12, // The height of the gv-track-container div
3
+ margin : 2, // The spacing between this track and the next
4
+ resizable : true, // Is the track resizable - can be true, false or 'auto'. Auto means the track will automatically resize to show all features, but the user cannot resize it themselves.
5
+ border : true, // Does the track have a bottom border
6
+ unsortable : false, // Is the track unsortable by the user
7
+ fixedOrder : false, // Is the track unsortable by the user or automatically - use for tracks which always need to go at the top/bottom
8
+ invert : false, // If true, features are drawn from the bottom of the track, rather than from the top. This is actually achieved by performing a CSS transform on the gv-image-container div
9
+ legend : false, // Does the track have a legend - can be true, false, or a Genoverse.Track.Legend extension/child class.
10
+ children : undefined, // Does the track have any child tracks - can be one or an array of Genoverse.Track extension/child classes.
11
+ name : undefined, // The name of the track, which appears in its label
12
+ autoHeight : undefined, // Does the track automatically resize so that all the features are visible
13
+ hideEmpty : undefined, // If the track automatically resizes, should it be hidden when there are no features, or should an empty track still be shown
14
+
15
+ constructor: function (config) {
16
+ if (this.stranded || config.stranded) {
17
+ this.controller = this.controller || Genoverse.Track.Controller.Stranded;
18
+ this.model = this.model || Genoverse.Track.Model.Stranded;
19
+ }
20
+
21
+ this.models = {};
22
+ this.views = {};
23
+
24
+ this.setInterface();
25
+ this.extend(config);
26
+ this.setDefaults();
27
+ this.setEvents();
28
+
29
+ Genoverse.wrapFunctions(this);
30
+
31
+ this.setLengthMap();
32
+ this.setMVC();
33
+
34
+ if (this.browser.scale > 0) {
35
+ this.controller.setScale();
36
+ this.controller.makeFirstImage();
37
+ }
38
+
39
+ if (this.children) {
40
+ this.addChildTracks();
41
+ }
42
+
43
+ if (this.legend) {
44
+ this.addLegend();
45
+ }
46
+ },
47
+
48
+ setEvents: $.noop,
49
+
50
+ setDefaults: function () {
51
+ this.config = this.config || {};
52
+ this.configSettings = this.configSettings || {};
53
+ this.defaultConfig = this.defaultConfig || {};
54
+ this.controls = this.controls || [];
55
+ this.defaultName = this.name;
56
+ this.configName = [];
57
+ this.defaultHeight = this.height;
58
+ this.defaultAutoHeight = this.autoHeight;
59
+ this.autoHeight = typeof this.autoHeight !== 'undefined' ? this.autoHeight : this.browser.trackAutoHeight;
60
+ this.hideEmpty = typeof this.hideEmpty !== 'undefined' ? this.hideEmpty : this.browser.hideEmptyTracks;
61
+ this.height += this.margin;
62
+ this.initialHeight = this.height;
63
+
64
+ if (this.resizable === 'auto') {
65
+ this.autoHeight = true;
66
+ }
67
+
68
+ this.setDefaultConfig();
69
+ },
70
+
71
+ setDefaultConfig: function () {
72
+ for (var i in this.defaultConfig) {
73
+ if (typeof this.config[i] === 'undefined') {
74
+ this.config[i] = this.defaultConfig[i];
75
+ }
76
+ }
77
+
78
+ this._setCurrentConfig();
79
+ },
80
+
81
+ setInterface: function () {
82
+ var mvc = [ 'Controller', 'Model', 'View', 'controller', 'model', 'view' ];
83
+ var prop;
84
+
85
+ this._interface = {};
86
+
87
+ for (var i = 0; i < 3; i++) {
88
+ for (prop in Genoverse.Track[mvc[i]].prototype) {
89
+ if (!/^(constructor|init|reset|setDefaults|base|extend|lengthMap)$/.test(prop)) {
90
+ this._interface[prop] = mvc[i + 3];
91
+ }
92
+ }
93
+ }
94
+ },
95
+
96
+ setMVC: function () {
97
+ if (this.model && typeof this.model.abort === 'function') { // TODO: don't abort unless model is changed?
98
+ this.model.abort();
99
+ }
100
+
101
+ this._defaults = this._defaults || {};
102
+
103
+ var settings = $.extend(true, {}, this.constructor.prototype, this.getSettingsForLength()[1]); // model, view, options
104
+ var controllerSettings = { prop: {}, func: {} };
105
+ var trackSettings = {};
106
+ var i;
107
+
108
+ settings.controller = settings.controller || this.controller || Genoverse.Track.Controller;
109
+
110
+ for (i in settings) {
111
+ if (!/^(constructor|init|reset|setDefaults|base|extend|lengthMap)$/.test(i) && isNaN(i)) {
112
+ if (this._interface[i] === 'controller') {
113
+ controllerSettings[typeof settings[i] === 'function' ? 'func' : 'prop'][i] = settings[i];
114
+ } else if (!Genoverse.Track.prototype.hasOwnProperty(i) && !/^(controller|models|views|config|disabled)$/.test(i)) { // If we allow trackSettings to overwrite the MVC properties, we will potentially lose of information about instantiated objects that the track needs to perform future switching correctly.
115
+ if (typeof this._defaults[i] === 'undefined') {
116
+ this._defaults[i] = this[i];
117
+ }
118
+
119
+ trackSettings[i] = settings[i];
120
+ }
121
+ }
122
+ }
123
+
124
+ for (i in this._defaults) {
125
+ if (typeof trackSettings[i] === 'undefined') {
126
+ trackSettings[i] = this._defaults[i];
127
+ }
128
+ }
129
+
130
+ // If there are configSettings for the track, ensure that any properties in _currentConfig are set for the model/view/controller/track as appropriate.
131
+ // Functions in _currentConfig are accessed via Genoverse.functionWrap, so nothing needs to be done with them here.
132
+ if (!$.isEmptyObject(this._currentConfig)) {
133
+ var changed = {};
134
+ var type;
135
+
136
+ for (i in this._currentConfig.prop) {
137
+ type = this._interface[i];
138
+
139
+ if (/model|view/.test(type)) {
140
+ if (trackSettings[type][i] !== this._currentConfig.prop[i]) {
141
+ trackSettings[type][i] = this._currentConfig.prop[i];
142
+ changed[type] = true;
143
+ }
144
+ } else if (type === 'controller') {
145
+ controllerSettings.prop[i] = this._currentConfig.prop[i];
146
+ } else {
147
+ trackSettings[i] = this._currentConfig.prop[i];
148
+ }
149
+ }
150
+
151
+ for (type in changed) {
152
+ trackSettings[type].setDefaults(true);
153
+ }
154
+ }
155
+
156
+ /*
157
+ * Abandon all hope! If you've tracked a bug to this line of code, be afraid.
158
+ * It will almost certainly be due to the wonderful way the javascript objects work.
159
+ *
160
+ * Consider the following:
161
+ *
162
+ * var Obj = function () {};
163
+ *
164
+ * Obj.prototype = {
165
+ * scalar : 1,
166
+ * array : [ 1, 2, 3 ],
167
+ * hash : { a: 1, b : 2 }
168
+ * };
169
+ *
170
+ * var x = new Obj();
171
+ *
172
+ * x.scalar = 10;
173
+ * x.array[0] = 10;
174
+ * x.hash.a = 10;
175
+ *
176
+ * var y = new Obj();
177
+ *
178
+ * y is now { scalar: 1, array: [ 10, 2, 3 ], hash: { a: 10, b : 2 } }, since memory locations of objects in prototypes are shared.
179
+ *
180
+ * This has been the cause of numerous Genoverse bugs in the past, due to property sharing between different tracks, models, views, and controllers.
181
+ */
182
+ this.extend(trackSettings);
183
+
184
+ this.model.setChrProps(); // make sure the data stores for the current chromsome are being used
185
+
186
+ if (!this.controller || typeof this.controller === 'function') {
187
+ this.controller = this.newMVC(settings.controller, controllerSettings.func, $.extend(controllerSettings.prop, { model: this.model, view: this.view }));
188
+ } else {
189
+ controllerSettings.prop.threshold = controllerSettings.prop.threshold || this.controller.constructor.prototype.threshold;
190
+ $.extend(this.controller, controllerSettings.prop, { model: this.model, view: this.view });
191
+ }
192
+ },
193
+
194
+ newMVC: function (object, functions, properties) {
195
+ return new (object.extend(
196
+ $.extend(true, {}, object.prototype, functions, {
197
+ prop: $.proxy(this.prop, this)
198
+ })
199
+ ))(
200
+ $.extend(properties, {
201
+ browser : this.browser,
202
+ width : this.width,
203
+ track : this
204
+ })
205
+ );
206
+ },
207
+
208
+ setLengthMap: function () {
209
+ var mv = [ 'model', 'view' ];
210
+ var lengthMap = [];
211
+ var models = {};
212
+ var views = {};
213
+ var settings, value, deepCopy, prevLengthMap, mvSettings, type, prevType, i, j;
214
+
215
+ function compare(a, b) {
216
+ var checked = { browser: true, width: true, track: true }; // Properties set in newMVC should be ignored, as they will be missing if comparing an object with a prototype
217
+ var key;
218
+
219
+ for (key in a) {
220
+ if (checked[key]) {
221
+ continue;
222
+ }
223
+
224
+ checked[key] = true;
225
+
226
+ if (typeof a[key] !== typeof b[key]) {
227
+ return false;
228
+ }
229
+
230
+ if (typeof a[key] === 'function' && typeof b[key] === 'function') {
231
+ if (a[key].toString() !== b[key].toString()) {
232
+ return false;
233
+ }
234
+ } else if (typeof a[key] === 'object' && !(a[key] instanceof $) && !compare(a[key], b[key])) {
235
+ return false;
236
+ } else if (a[key] !== b[key]) {
237
+ return false;
238
+ }
239
+ }
240
+
241
+ for (key in b) {
242
+ if (!checked[key]) {
243
+ return false;
244
+ }
245
+ }
246
+
247
+ return true;
248
+ }
249
+
250
+ // Find all scale-map like keys
251
+ for (var key in this) {
252
+ if (!isNaN(key)) {
253
+ key = parseInt(key, 10);
254
+ value = this[key];
255
+
256
+ lengthMap.push([ key, value === false ? { threshold: key, resizable: 'auto', featureHeight: 0, model: Genoverse.Track.Model, view: Genoverse.Track.View } : $.extend(true, {}, value) ]);
257
+ }
258
+ }
259
+
260
+ // Force at least one lengthMap entry to exist, containing the base model and view. lengthMap entries above -1 without a model or view will inherit from -1.
261
+ lengthMap.push([ -1, { view: this.view || Genoverse.Track.View, model: this.model || Genoverse.Track.Model }]);
262
+
263
+ lengthMap = lengthMap.sort(function (a, b) { return b[0] - a[0]; });
264
+
265
+ for (i = 0; i < lengthMap.length; i++) {
266
+ if (lengthMap[i][1].model && lengthMap[i][1].view) {
267
+ continue;
268
+ }
269
+
270
+ deepCopy = {};
271
+
272
+ if (lengthMap[i][0] !== -1) {
273
+ for (j in lengthMap[i][1]) {
274
+ if (this._interface[j]) {
275
+ deepCopy[this._interface[j]] = true;
276
+ }
277
+
278
+ if (deepCopy.model && deepCopy.view) {
279
+ break;
280
+ }
281
+ }
282
+ }
283
+
284
+ // Ensure that every lengthMap entry has a model and view property, copying them from entries with smaller lengths if needed.
285
+ for (j = i + 1; j < lengthMap.length; j++) {
286
+ if (!lengthMap[i][1].model && lengthMap[j][1].model) {
287
+ lengthMap[i][1].model = deepCopy.model ? Genoverse.Track.Model.extend($.extend(true, {}, lengthMap[j][1].model.prototype)) : lengthMap[j][1].model;
288
+ }
289
+
290
+ if (!lengthMap[i][1].view && lengthMap[j][1].view) {
291
+ lengthMap[i][1].view = deepCopy.view ? Genoverse.Track.View.extend($.extend(true, {}, lengthMap[j][1].view.prototype)) : lengthMap[j][1].view;
292
+ }
293
+
294
+ if (lengthMap[i][1].model && lengthMap[i][1].view) {
295
+ break;
296
+ }
297
+ }
298
+ }
299
+
300
+ // Now every lengthMap entry has a model and a view class, create instances of those classes.
301
+ for (i = 0; i < lengthMap.length; i++) {
302
+ prevLengthMap = lengthMap[i - 1] ? lengthMap[i - 1][1] : {};
303
+ settings = $.extend(true, {}, this.constructor.prototype, lengthMap[i][1]);
304
+ mvSettings = { model: { prop: {}, func: {} }, view: { prop: {}, func: {} } };
305
+
306
+ // Work out which settings belong to models or views
307
+ for (j in settings) {
308
+ if (j !== 'constructor' && mvSettings[this._interface[j]]) {
309
+ mvSettings[this._interface[j]][typeof settings[j] === 'function' ? 'func' : 'prop'][j] = settings[j];
310
+ }
311
+ }
312
+
313
+ // Create models and views, if settings.model or settings.view is a class rather than an instance
314
+ for (j = 0; j < mv.length; j++) {
315
+ type = mv[j];
316
+
317
+ if (typeof settings[type] === 'function') {
318
+ prevType = this[mv[j] + 's'];
319
+
320
+ // If the previous lengthMap contains an instance of the class in settings, it can be reused.
321
+ // This allows sharing of models and views between lengthMap entries if they are the same, stopping the need to fetch identical data or draw identical images more than once
322
+ if (prevLengthMap[type] instanceof settings[type]) {
323
+ settings[type] = prevLengthMap[type];
324
+ } else {
325
+ // Make an instance of the model/view, based on the settings[type] class but with a prototype that contains the functions in mvSettings[type].func
326
+ settings[type] = this.newMVC(settings[type], mvSettings[type].func, mvSettings[type].prop);
327
+
328
+ // If the track already has this.models/this.views and the prototype of the new model/view is the same as the value of this.models/this.views for the same length key, reuse that value.
329
+ // This can happen if the track has configSettings and the user changes config but that only affects one of the model and view.
330
+ // Again, reusing the old value stops the need to fetch identical data or draw identical images more than once.
331
+ if (prevType[lengthMap[i][0]] && compare(prevType[lengthMap[i][0]].constructor.prototype, $.extend({}, settings[type].constructor.prototype, mvSettings[type].prop))) {
332
+ settings[type] = prevType[lengthMap[i][0]];
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ models[lengthMap[i][0]] = lengthMap[i][1].model = settings.model;
339
+ views[lengthMap[i][0]] = lengthMap[i][1].view = settings.view;
340
+ }
341
+
342
+ this.lengthMap = lengthMap;
343
+ this.models = models;
344
+ this.views = views;
345
+ },
346
+
347
+ getSettingsForLength: function () {
348
+ var length = this.browser.length || (this.browser.end - this.browser.start + 1);
349
+
350
+ for (var i = 0; i < this.lengthMap.length; i++) {
351
+ if (length > this.lengthMap[i][0] || (length === 1 && this.lengthMap[i][0] === 1) || (length < 0 && this.lengthMap[i][0] < 0)) {
352
+ return this.lengthMap[i];
353
+ }
354
+ }
355
+
356
+ return [];
357
+ },
358
+
359
+ prop: function (key, value) {
360
+ var mvc = [ 'controller', 'model', 'view' ];
361
+ var obj;
362
+
363
+ if (this._interface[key]) {
364
+ obj = this[this._interface[key]];
365
+ } else {
366
+ for (var i = 0; i < 3; i++) {
367
+ if (this[mvc[i]] && typeof this[mvc[i]][key] !== 'undefined') {
368
+ obj = this[mvc[i]];
369
+ break;
370
+ }
371
+ }
372
+
373
+ obj = obj || this;
374
+ }
375
+
376
+ if (typeof value !== 'undefined') {
377
+ if (value === null) {
378
+ delete obj[key];
379
+ } else {
380
+ obj[key] = value;
381
+ }
382
+ }
383
+
384
+ return obj ? obj[key] : undefined;
385
+ },
386
+
387
+ setHeight: function (height, forceShow) {
388
+ if (this.disabled || (forceShow !== true && height < this.prop('featureHeight')) || (this.prop('threshold') && !this.prop('thresholdMessage') && this.browser.length > this.prop('threshold'))) {
389
+ height = 0;
390
+ } else {
391
+ height = Math.max(height, this.prop('minLabelHeight'));
392
+ }
393
+
394
+ this.height = height;
395
+
396
+ return height;
397
+ },
398
+
399
+ resetHeight: function () {
400
+ if (this.resizable === true) {
401
+ var resizer = this.prop('resizer');
402
+
403
+ this.autoHeight = !!([ this.defaultAutoHeight, this.browser.trackAutoHeight ].sort(function (a, b) {
404
+ return (typeof a !== 'undefined' && a !== null ? 0 : 1) - (typeof b !== 'undefined' && b !== null ? 0 : 1);
405
+ })[0]);
406
+
407
+ this.controller.resize(this.autoHeight ? this.prop('fullVisibleHeight') : this.defaultHeight + this.margin + (resizer ? resizer.height() : 0));
408
+ this.initialHeight = this.height;
409
+ }
410
+ },
411
+
412
+ setConfig: function (config) {
413
+ if (typeof config === 'string' && arguments.length === 2) {
414
+ var _config = {};
415
+ _config[config] = arguments[1];
416
+ config = _config;
417
+ }
418
+
419
+ var configChanged = false;
420
+ var conf;
421
+
422
+ for (var type in config) {
423
+ conf = config[type];
424
+
425
+ if (typeof this.configSettings[type] === 'undefined' || typeof this.configSettings[type][conf] === 'undefined' || this.config[type] === conf) {
426
+ continue;
427
+ }
428
+
429
+ this.config[type] = conf;
430
+
431
+ configChanged = true;
432
+ }
433
+
434
+ if (configChanged) {
435
+ var features = this.prop('featuresById');
436
+
437
+ for (var i in features) {
438
+ delete features[i].menuEl;
439
+ }
440
+
441
+ this._setCurrentConfig();
442
+
443
+ if (!this.disabled) {
444
+ this.reset.apply(this, configChanged ? [ 'config', config ] : []);
445
+ }
446
+
447
+ (this.prop('childTracks') || []).forEach(function (track) {
448
+ track.setConfig(config);
449
+ });
450
+
451
+ this.browser.saveConfig();
452
+ }
453
+ },
454
+
455
+ _setCurrentConfig: function () {
456
+ var settings = [];
457
+ var featureFilters = [];
458
+ var configName = [];
459
+ var controls = (Array.isArray(this.controls) ? this.controls : []).reduce(function (acc, control) { return acc.add(control); }, $());
460
+ var conf, i;
461
+
462
+ this._currentConfig = { prop: {}, func: {} };
463
+
464
+ for (i in this.configSettings) {
465
+ conf = this.getConfig(i);
466
+
467
+ if (conf) {
468
+ settings.push(conf);
469
+
470
+ if (conf.featureFilter) {
471
+ featureFilters.push(conf.featureFilter);
472
+ }
473
+
474
+ configName.push(
475
+ conf.hasOwnProperty('name')
476
+ ? typeof conf.name === 'function' ? conf.name.call(this) : conf.name
477
+ : conf.featureFilter === false
478
+ ? false
479
+ : controls.filter('[data-control="' + i + '"]').find('[value="' + this.config[i] + '"]').html()
480
+ );
481
+ }
482
+ }
483
+
484
+ if (settings.length) {
485
+ configName = configName.filter(Boolean);
486
+
487
+ settings = $.extend.apply($, [ true, {}].concat(
488
+ settings,
489
+ {
490
+ featureFilters : featureFilters,
491
+ name : this.defaultName + (configName.length ? ' - ' + configName.join(', ') : ''),
492
+ configName : [ this.defaultName ].concat(configName)
493
+ }
494
+ ));
495
+
496
+ delete settings.featureFilter;
497
+ }
498
+
499
+ for (i in settings) {
500
+ this._currentConfig[typeof settings[i] === 'function' && !/^(before|after)/.test(i) ? 'func' : 'prop'][i] = settings[i];
501
+ }
502
+
503
+ if (settings.name) {
504
+ this.updateName(settings.name, settings.configName);
505
+ }
506
+ },
507
+
508
+ getConfig: function (type) {
509
+ return this.configSettings[type][this.config[type]];
510
+ },
511
+
512
+ addChildTracks: function () {
513
+ if (!this.children) {
514
+ return;
515
+ }
516
+
517
+ var track = this;
518
+ var browser = this.browser;
519
+ var children = (Array.isArray(this.children) ? this.children : [ this.children ]).filter(function (child) { return child.prototype instanceof Genoverse.Track; });
520
+ var config = {
521
+ parentTrack : this,
522
+ controls : 'off',
523
+ threshold : this.prop('threshold')
524
+ };
525
+
526
+ setTimeout(function () {
527
+ track.childTracks = children.map(function (child) {
528
+ if (child.prototype instanceof Genoverse.Track.Legend || child === Genoverse.Track.Legend) {
529
+ track.addLegend(child.extend(config), true);
530
+ return track.legendTrack;
531
+ }
532
+
533
+ return browser.addTrack(child.extend(config));
534
+ });
535
+
536
+ track.controller.setLabelHeight();
537
+ }, 1);
538
+ },
539
+
540
+ addLegend: function (constructor, now) {
541
+ if (!(constructor || this.legend)) {
542
+ return;
543
+ }
544
+
545
+ constructor = constructor || (this.legend.prototype instanceof Genoverse.Track.Legend ? this.legend : Genoverse.Track.Legend);
546
+
547
+ var track = this;
548
+ var legendType = constructor.prototype.shared === true ? Genoverse.getTrackNamespace(constructor) : constructor.prototype.shared || this.id;
549
+ var config = {
550
+ id : legendType + 'Legend',
551
+ name : constructor.prototype.name || (this.defaultName + ' Legend'),
552
+ type : legendType
553
+ };
554
+
555
+ this.legendType = legendType;
556
+
557
+ function makeLegendTrack() {
558
+ track.legendTrack = track.browser.legends[config.id] || track.browser.addTrack(constructor.extend(config));
559
+ return track.legendTrack;
560
+ }
561
+
562
+ if (now === true) {
563
+ makeLegendTrack();
564
+ } else {
565
+ setTimeout(makeLegendTrack, 1);
566
+ }
567
+ },
568
+
569
+ changeChr: function () {
570
+ for (var i in this.models) {
571
+ this.models[i].setChrProps();
572
+ }
573
+ },
574
+
575
+ updateName: function (name, configName) { // For ease of use in external code
576
+ if (this.controller && typeof this.controller !== 'function') {
577
+ this.controller.setName(name, configName);
578
+ } else {
579
+ this.name = name;
580
+ this.configName = configName || [];
581
+ }
582
+ },
583
+
584
+ enable: function () {
585
+ if (this.disabled === true) {
586
+ this.disabled = false;
587
+ this.controller.resize(this.initialHeight);
588
+ this.reset();
589
+ }
590
+ },
591
+
592
+ disable: function () {
593
+ if (!this.disabled) {
594
+ this.disabled = true;
595
+ this.controller.resize(0);
596
+ }
597
+ },
598
+
599
+ reset: function () {
600
+ var i;
601
+
602
+ this.setLengthMap();
603
+
604
+ for (i in this.models) {
605
+ if (this.models[i].url !== false) {
606
+ this.models[i].init(true);
607
+ }
608
+ }
609
+
610
+ for (i in this.views) {
611
+ this.views[i].init();
612
+ }
613
+
614
+ this.controller.reset.apply(this.controller, arguments);
615
+ },
616
+
617
+ remove: function () {
618
+ this.browser.removeTrack(this);
619
+ },
620
+
621
+ destructor: function () {
622
+ this.controller.destroy();
623
+
624
+ var objs = [ this.view, this.model, this.controller, this ];
625
+
626
+ for (var obj in objs) {
627
+ for (var key in obj) {
628
+ delete obj[key];
629
+ }
630
+ }
631
+ }
632
+ });