|
a |
|
b/exseek/templates/igv/main.html |
|
|
1 |
|
|
|
2 |
<!DOCTYPE html> |
|
|
3 |
<html lang="en"> |
|
|
4 |
|
|
|
5 |
<head> |
|
|
6 |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
|
|
7 |
<meta charset="utf-8"> |
|
|
8 |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|
|
9 |
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
|
10 |
<meta name="description" content=""> |
|
|
11 |
<meta name="author" content=""> |
|
|
12 |
<link rel="shortcut icon" href="https://igv.org/web/img/favicon.ico"> |
|
|
13 |
<title>Integrative Genomics Viewer</title> |
|
|
14 |
|
|
|
15 |
<!-- Bootstrap 4 Dependancies - jQuery | Popper --> |
|
|
16 |
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> |
|
|
17 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> |
|
|
18 |
|
|
|
19 |
<!-- Latest compiled and minified CSS --> |
|
|
20 |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> |
|
|
21 |
|
|
|
22 |
<!-- Optional theme --> |
|
|
23 |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> |
|
|
24 |
|
|
|
25 |
<!-- Latest compiled and minified JavaScript --> |
|
|
26 |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> |
|
|
27 |
|
|
|
28 |
<!-- Global styles --> |
|
|
29 |
<style type="text/css"> |
|
|
30 |
body { |
|
|
31 |
font-size: 11px; |
|
|
32 |
} |
|
|
33 |
p.panel-title { |
|
|
34 |
font-size: 14px; |
|
|
35 |
font-weight: bold; |
|
|
36 |
margin-bottom: 0.5em; |
|
|
37 |
} |
|
|
38 |
#sample-list { |
|
|
39 |
list-style-type: none; |
|
|
40 |
margin: 0; |
|
|
41 |
padding: 0; |
|
|
42 |
} |
|
|
43 |
#sample-list li{ |
|
|
44 |
margin: 0; |
|
|
45 |
} |
|
|
46 |
</style> |
|
|
47 |
|
|
|
48 |
<!-- IGV JS --> |
|
|
49 |
<script src="https://igv.org/web/release/2.1.0/dist/igv.min.js"></script> |
|
|
50 |
|
|
|
51 |
</head> |
|
|
52 |
|
|
|
53 |
<body> |
|
|
54 |
|
|
|
55 |
<script type="text/javascript"> |
|
|
56 |
var dataTrackTypes = {"wig": 0, "alignment": 0}; |
|
|
57 |
var tracks = {{ tracks_json }}; |
|
|
58 |
|
|
|
59 |
var groups = [ |
|
|
60 |
{% for group_name, group in groups.items() %} |
|
|
61 |
{ |
|
|
62 |
"name": "{{ group_name }}", |
|
|
63 |
"color": "{{ group.color }}", |
|
|
64 |
"show": "{{ group.show }}" |
|
|
65 |
} {% if not loop.last %},{% endif %} |
|
|
66 |
{% endfor %} |
|
|
67 |
]; |
|
|
68 |
var showStrand = {"+": true, "-": true}; |
|
|
69 |
var showGroup = {}; |
|
|
70 |
for(let i = 0; i < groups.length; i ++) |
|
|
71 |
showGroup[groups[i].name] = groups[i].show; |
|
|
72 |
var sampleIds = []; |
|
|
73 |
var showSample = {}; |
|
|
74 |
for(name in tracks){ |
|
|
75 |
if(tracks[name].type in dataTrackTypes){ |
|
|
76 |
if(!(tracks[name].sample_id in showSample)) |
|
|
77 |
sampleIds.push(tracks[name].sample_id); |
|
|
78 |
showSample[tracks[name].sample_id] = tracks[name].show; |
|
|
79 |
} |
|
|
80 |
} |
|
|
81 |
|
|
|
82 |
applyForAllTracks = function(func){ |
|
|
83 |
for(name in tracks){ |
|
|
84 |
if(tracks[name].type in dataTrackTypes){ |
|
|
85 |
func(tracks[name]); |
|
|
86 |
} |
|
|
87 |
} |
|
|
88 |
}; |
|
|
89 |
|
|
|
90 |
findGroup = function(name){ |
|
|
91 |
var found = []; |
|
|
92 |
groups.forEach(function(group){ |
|
|
93 |
if(group.name == name) |
|
|
94 |
found.push(group); |
|
|
95 |
}); |
|
|
96 |
if(found.length > 0) |
|
|
97 |
return found[0]; |
|
|
98 |
} |
|
|
99 |
|
|
|
100 |
createTrackNavigator = function(){ |
|
|
101 |
var trackNav = document.getElementById("track-nav"); |
|
|
102 |
onToggleTrack = function(event){ |
|
|
103 |
tracks[event.target.name].show = event.target.checked; |
|
|
104 |
toggleTracks(); |
|
|
105 |
}; |
|
|
106 |
var checkbox, label, ul, li; |
|
|
107 |
var trackNavCheckBoxes = {}; |
|
|
108 |
ul = document.createElement("ul"); |
|
|
109 |
ul.setAttribute("id", "sample-list"); |
|
|
110 |
for(name in tracks){ |
|
|
111 |
li = document.createElement("li"); |
|
|
112 |
checkbox = document.createElement("input"); |
|
|
113 |
checkbox.setAttribute("type", "checkbox"); |
|
|
114 |
checkbox.setAttribute("name", name); |
|
|
115 |
if(tracks[name].show) |
|
|
116 |
checkbox.setAttribute("checked", ""); |
|
|
117 |
checkbox.addEventListener("change", onToggleTrack); |
|
|
118 |
li.appendChild(checkbox); |
|
|
119 |
trackNavCheckBoxes[name] = checkbox; |
|
|
120 |
|
|
|
121 |
label = document.createElement("label"); |
|
|
122 |
label.setAttribute("for", name); |
|
|
123 |
label.appendChild(document.createTextNode(name)); |
|
|
124 |
li.appendChild(label); |
|
|
125 |
ul.appendChild(li); |
|
|
126 |
} |
|
|
127 |
trackNav.appendChild(ul); |
|
|
128 |
return trackNavCheckBoxes; |
|
|
129 |
}; |
|
|
130 |
|
|
|
131 |
createTrackGroupSelector = function(){ |
|
|
132 |
var tr, td, checkbox, label, color; |
|
|
133 |
var groupTable = document.getElementById("track-groups-table"); |
|
|
134 |
onShowTrackGroupChecked = function(event){ |
|
|
135 |
groups[event.target.name].show = event.target.checked; |
|
|
136 |
showTrackGroup(event.target); |
|
|
137 |
} |
|
|
138 |
onTrackColorGroupChanged = function(event){ |
|
|
139 |
groups[event.target.name].show = event.target.value; |
|
|
140 |
setTrackColorGroup(event.target); |
|
|
141 |
} |
|
|
142 |
for(let i = 0; i < groups.length; i ++){ |
|
|
143 |
var group = groups[i]; |
|
|
144 |
tr = document.createElement("tr"); |
|
|
145 |
|
|
|
146 |
checkbox = document.createElement("input"); |
|
|
147 |
checkbox.setAttribute("type", "checkbox"); |
|
|
148 |
checkbox.setAttribute("name", "show-group-" + i); |
|
|
149 |
if(group.show) |
|
|
150 |
checkbox.setAttribute("checked", ""); |
|
|
151 |
checkbox.addEventListener("change", showTrackGroup); |
|
|
152 |
|
|
|
153 |
label = document.createElement("label"); |
|
|
154 |
label.setAttribute("for", "show-group-" + i); |
|
|
155 |
label.innerHTML = group.name; |
|
|
156 |
|
|
|
157 |
td = document.createElement("td"); |
|
|
158 |
td.appendChild(checkbox); |
|
|
159 |
td.appendChild(label); |
|
|
160 |
tr.appendChild(td); |
|
|
161 |
|
|
|
162 |
color = document.createElement("input"); |
|
|
163 |
color.setAttribute("type", "color"); |
|
|
164 |
color.setAttribute("name", "track-color-group-" + i); |
|
|
165 |
color.setAttribute("value", group.color); |
|
|
166 |
color.addEventListener("change", setTrackColorGroup); |
|
|
167 |
td = document.createElement("td"); |
|
|
168 |
td.appendChild(color); |
|
|
169 |
tr.appendChild(td); |
|
|
170 |
|
|
|
171 |
groupTable.appendChild(tr); |
|
|
172 |
} |
|
|
173 |
}; |
|
|
174 |
|
|
|
175 |
updateTrackViews = function(items){ |
|
|
176 |
for(name in tracks){ |
|
|
177 |
if(!(tracks[name].type in dataTrackTypes)) |
|
|
178 |
continue; |
|
|
179 |
igv.browser.findTracks("name", name).forEach(function(track){ |
|
|
180 |
for(let i = 0; i < items.length; i ++){ |
|
|
181 |
if(items[i] == "height") |
|
|
182 |
track.trackView.setTrackHeight(tracks[name].height); |
|
|
183 |
else if(items[i] == "data_range"){ |
|
|
184 |
track.trackView.setDataRange(tracks[name].min, tracks[name].max, tracks[name].autoscale); |
|
|
185 |
if(tracks[name].autoscale) |
|
|
186 |
track.trackView.updateViews(); |
|
|
187 |
}else if(items[i] == "color"){ |
|
|
188 |
track.trackView.setColor(tracks[name].color); |
|
|
189 |
}else{ |
|
|
190 |
track.trackView.track.logScale = tracks[name].logScale; |
|
|
191 |
track.trackView.updateViews(); |
|
|
192 |
} |
|
|
193 |
} |
|
|
194 |
}); |
|
|
195 |
} |
|
|
196 |
}; |
|
|
197 |
|
|
|
198 |
// show or remove tracks according to track config |
|
|
199 |
toggleTracks = function(){ |
|
|
200 |
var currentTrack; |
|
|
201 |
var showTrack; |
|
|
202 |
var track; |
|
|
203 |
for(name in tracks){ |
|
|
204 |
track = tracks[name]; |
|
|
205 |
currentTrack = igv.browser.findTracks("name", name); |
|
|
206 |
if(track.type in dataTrackTypes){ |
|
|
207 |
showTrack = track.show && showGroup[track.group]; |
|
|
208 |
if(track.type == "wig"){ |
|
|
209 |
showTrack = showTrack && showStrand[track.strand]; |
|
|
210 |
} |
|
|
211 |
}else{ |
|
|
212 |
showTrack = track.show; |
|
|
213 |
} |
|
|
214 |
if(showTrack && (currentTrack.length == 0)){ |
|
|
215 |
igv.browser.loadTrack(track); |
|
|
216 |
}else if(!showTrack && (currentTrack.length > 0)){ |
|
|
217 |
igv.browser.removeTrackByName(name); |
|
|
218 |
} |
|
|
219 |
} |
|
|
220 |
}; |
|
|
221 |
|
|
|
222 |
showTracksWithStrand = function(element){ |
|
|
223 |
var name, group; |
|
|
224 |
var strand = (element.name == "show-plus-track")? "+" : "-"; |
|
|
225 |
showStrand[strand] = element.checked; |
|
|
226 |
toggleTracks(); |
|
|
227 |
}; |
|
|
228 |
|
|
|
229 |
showTrackGroup = function(element){ |
|
|
230 |
var c = element.name.split("-"); |
|
|
231 |
var group = groups[parseInt(c[c.length - 1])]; |
|
|
232 |
showGroup[group.name] = element.checked; |
|
|
233 |
group.show = element.checked; |
|
|
234 |
toggleTracks(); |
|
|
235 |
}; |
|
|
236 |
|
|
|
237 |
setTrackColorGroup = function(element){ |
|
|
238 |
var c = element.name.split("-"); |
|
|
239 |
var group = groups[parseInt(c[c.length - 1])].name; |
|
|
240 |
var i; |
|
|
241 |
var trackView; |
|
|
242 |
for(name in tracks){ |
|
|
243 |
if(tracks[name].group == group){ |
|
|
244 |
tracks[name].color = element.value; |
|
|
245 |
} |
|
|
246 |
} |
|
|
247 |
updateTrackViews(["color"]); |
|
|
248 |
}; |
|
|
249 |
</script> |
|
|
250 |
|
|
|
251 |
<div id="left-panel" style="position: fixed; margin-left: 5px; width: 300px; height: 100%; overflow: scroll"> |
|
|
252 |
<div id="config-panel" style="border:1px solid lightgray; padding-left: 10px; padding-top: 10px;padding-bottom: 10px"> |
|
|
253 |
<p class="panel-title">Track configuation</p> |
|
|
254 |
<table id="table-config"> |
|
|
255 |
<tr><td>Minimum value: </td><td><input type="text" id="config-data-min" value="0" style="width: 50px"></td></tr> |
|
|
256 |
<tr><td>Maximum value: </td><td><input type="text" id="config-data-max" value="100" style="width: 50px"></td></tr> |
|
|
257 |
<tr><td>Autoscale: </td><td><input type="checkbox" id="config-autoscale" checked></td></tr> |
|
|
258 |
<tr><td>Track height: </td><td><input type="text" id="config-height" value="25" style="width: 50px"></td></tr> |
|
|
259 |
</table> |
|
|
260 |
</div> |
|
|
261 |
<div id="sequence-panel" style="border:1px solid lightgray; padding-left: 10px; padding-top: 10px;padding-bottom: 10px; margin-top: 5px"> |
|
|
262 |
<p class="panel-title">Sequence</p> |
|
|
263 |
<p id="sequence-locus"></p> |
|
|
264 |
<textarea id="sequence-box" rows="4" cols="40" readonly></textarea><br> |
|
|
265 |
<input type="checkbox" id="reverse-sequence" name="reverse-sequence" onchange="showSequence()"> |
|
|
266 |
<label for="reverse-sequence">Reverse complement</label><br> |
|
|
267 |
<input type="button" id="show-sequence" value="Show sequence"> |
|
|
268 |
<input type="button" id="copy-sequence" value="Copy sequence"> |
|
|
269 |
</div> |
|
|
270 |
<div id="track-selection" style="border:1px solid lightgray; padding-left: 10px; padding-top: 10px;padding-bottom: 10px; margin-top: 5px"> |
|
|
271 |
<p class="panel-title">Track selection</p> |
|
|
272 |
<input type="checkbox" onchange="showTracksWithStrand(this)" name="show-plus-track" checked> |
|
|
273 |
<label for="show-plus-track">Show tracks in + strand</label><br> |
|
|
274 |
<input type="checkbox" onchange="showTracksWithStrand(this)" name="show-minus-strand" checked> |
|
|
275 |
<label for="show-minus-track">Show tracks in - strand</label><br> |
|
|
276 |
</div> |
|
|
277 |
<div id="track-groups" style="border:1px solid lightgray; padding-left: 10px; padding-top: 10px;padding-bottom: 10px; margin-top: 5px"> |
|
|
278 |
<p class="panel-title">Track groups</p> |
|
|
279 |
<table id="track-groups-table" border="0"> |
|
|
280 |
{% for name, group in groups.items() %} |
|
|
281 |
<tr> |
|
|
282 |
<td><input type="checkbox" onchange="showTrackGroup(this)" name="show-group-{{ loop.index0 }}" checked> |
|
|
283 |
<label for="show-group-{{ loop.index0 }}">{{ name }}</label></td> |
|
|
284 |
<td><input type="color" onchange="setTrackColorGroup(this)" name="track-color-group-{{ loop.index0 }}" value="{{ group.color }}"></td> |
|
|
285 |
</tr> |
|
|
286 |
{% endfor %} |
|
|
287 |
</table> |
|
|
288 |
</div> |
|
|
289 |
<div id="track-nav" style="border:1px solid lightgray; padding-left: 10px; padding-top: 10px;padding-bottom: 10px; margin-top: 5px; overflow: scroll; height:400px;"> |
|
|
290 |
<p class="panel-title">Track navigator</p> |
|
|
291 |
</div> |
|
|
292 |
</div> |
|
|
293 |
|
|
|
294 |
<div id="igv-div" style="margin-left: 310px; padding-top: 10px;padding-bottom: 10px; border:1px solid lightgray"></div> |
|
|
295 |
|
|
|
296 |
<script type="text/javascript"> |
|
|
297 |
document.addEventListener("DOMContentLoaded", function () { |
|
|
298 |
|
|
|
299 |
var options = {{ options_json }}; |
|
|
300 |
|
|
|
301 |
if(window.location.hash.length > 1){ |
|
|
302 |
options["locus"] = window.location.hash.substr(1); |
|
|
303 |
} |
|
|
304 |
|
|
|
305 |
var igvDiv = document.getElementById("igv-div"); |
|
|
306 |
|
|
|
307 |
igv.createBrowser(igvDiv, options) |
|
|
308 |
.then(function (browser) { |
|
|
309 |
console.log("Created IGV browser"); |
|
|
310 |
for(name in tracks){ |
|
|
311 |
if(tracks[name].show){ |
|
|
312 |
browser.loadTrack(tracks[name]); |
|
|
313 |
} |
|
|
314 |
} |
|
|
315 |
}); |
|
|
316 |
|
|
|
317 |
|
|
|
318 |
createTrackNavigator(); |
|
|
319 |
|
|
|
320 |
showSequence = function(){ |
|
|
321 |
var complement = {'A': 'T', 'C': 'G', 'G': 'C', 'T': 'A'}; |
|
|
322 |
var referenceFrame; |
|
|
323 |
|
|
|
324 |
referenceFrame = igv.browser.genomicStateList[0].referenceFrame; |
|
|
325 |
var start = referenceFrame.start; |
|
|
326 |
var chrName = referenceFrame.chrName; |
|
|
327 |
var end = referenceFrame.start + |
|
|
328 |
referenceFrame.bpPerPixel * (igv.browser.viewportContainerWidth() / igv.browser.genomicStateList.length); |
|
|
329 |
end = Math.floor(end); |
|
|
330 |
var locus; |
|
|
331 |
if((end - start) > 100000){ |
|
|
332 |
alert("Sequence too long"); |
|
|
333 |
return; |
|
|
334 |
} |
|
|
335 |
|
|
|
336 |
igv.browser.genome.sequence.getSequence(chrName, start, end).then(function(seq){ |
|
|
337 |
seq = seq.toUpperCase(); |
|
|
338 |
locus = "Locus: " + chrName + ":" + start + "-" + end; |
|
|
339 |
document.getElementById("sequence-locus").innerHTML = locus; |
|
|
340 |
if(document.getElementById("reverse-sequence").checked){ |
|
|
341 |
seq = seq.split("").map(function(cv){ |
|
|
342 |
return complement[cv]; |
|
|
343 |
}).join(""); |
|
|
344 |
seq = seq.split("").reverse().join(""); |
|
|
345 |
} |
|
|
346 |
document.getElementById("sequence-box").value = seq; |
|
|
347 |
}); |
|
|
348 |
} |
|
|
349 |
|
|
|
350 |
|
|
|
351 |
document.getElementById("show-sequence").addEventListener("click", function(event){ |
|
|
352 |
showSequence(); |
|
|
353 |
}); |
|
|
354 |
|
|
|
355 |
document.getElementById("copy-sequence").addEventListener("click", function(event){ |
|
|
356 |
showSequence(); |
|
|
357 |
document.getElementById("sequence-box").select(); |
|
|
358 |
document.execCommand('copy'); |
|
|
359 |
}); |
|
|
360 |
|
|
|
361 |
document.getElementById("config-data-min").addEventListener("change", function(event){ |
|
|
362 |
var value = parseFloat(event.target.value); |
|
|
363 |
if(isNaN(value)){ |
|
|
364 |
alert("Error: minimum value should be a number"); |
|
|
365 |
}else{ |
|
|
366 |
applyForAllTracks(function(track){ track.min = value; }); |
|
|
367 |
updateTrackViews(["data_range"]); |
|
|
368 |
} |
|
|
369 |
}); |
|
|
370 |
|
|
|
371 |
document.getElementById("config-data-max").addEventListener("change", function(event){ |
|
|
372 |
var value = parseFloat(event.target.value); |
|
|
373 |
if(isNaN(value)){ |
|
|
374 |
alert("Error: maximum value should be a number"); |
|
|
375 |
}else{ |
|
|
376 |
applyForAllTracks(function(track){ track.max = value; }); |
|
|
377 |
updateTrackViews(["data_range"]); |
|
|
378 |
} |
|
|
379 |
}); |
|
|
380 |
|
|
|
381 |
document.getElementById("config-autoscale").addEventListener("change", function(event){ |
|
|
382 |
applyForAllTracks(function(track){ |
|
|
383 |
if(!track.autoscale){ |
|
|
384 |
track.min = parseFloat(document.getElementById("config-data-min").value); |
|
|
385 |
track.max = parseFloat(document.getElementById("config-data-max").value); |
|
|
386 |
} |
|
|
387 |
track.autoscale = event.target.checked; |
|
|
388 |
}); |
|
|
389 |
updateTrackViews(["data_range"]); |
|
|
390 |
}); |
|
|
391 |
|
|
|
392 |
document.getElementById("config-height").addEventListener("change", function(event){ |
|
|
393 |
var value = parseInt(event.target.value); |
|
|
394 |
if(isNaN(value)){ |
|
|
395 |
alert("Error: track height should be a number"); |
|
|
396 |
}else { |
|
|
397 |
applyForAllTracks(function(track){ track.height = event.target.value; }); |
|
|
398 |
updateTrackViews(["height"]); |
|
|
399 |
} |
|
|
400 |
}); |
|
|
401 |
|
|
|
402 |
}); |
|
|
403 |
|
|
|
404 |
</script> |
|
|
405 |
|
|
|
406 |
</body> |
|
|
407 |
|
|
|
408 |
</html> |