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