igv 2.13.4 → 2.13.6

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/dist/igv.js CHANGED
@@ -16472,6 +16472,376 @@
16472
16472
  }
16473
16473
  }
16474
16474
 
16475
+ const appleCrayonRGBPalette = {
16476
+ cantaloupe: {
16477
+ r: 255,
16478
+ g: 206,
16479
+ b: 110
16480
+ },
16481
+ honeydew: {
16482
+ r: 206,
16483
+ g: 250,
16484
+ b: 110
16485
+ },
16486
+ spindrift: {
16487
+ r: 104,
16488
+ g: 251,
16489
+ b: 208
16490
+ },
16491
+ sky: {
16492
+ r: 106,
16493
+ g: 207,
16494
+ b: 255
16495
+ },
16496
+ lavender: {
16497
+ r: 210,
16498
+ g: 120,
16499
+ b: 255
16500
+ },
16501
+ carnation: {
16502
+ r: 255,
16503
+ g: 127,
16504
+ b: 211
16505
+ },
16506
+ licorice: {
16507
+ r: 0,
16508
+ g: 0,
16509
+ b: 0
16510
+ },
16511
+ snow: {
16512
+ r: 255,
16513
+ g: 255,
16514
+ b: 255
16515
+ },
16516
+ salmon: {
16517
+ r: 255,
16518
+ g: 114,
16519
+ b: 110
16520
+ },
16521
+ banana: {
16522
+ r: 255,
16523
+ g: 251,
16524
+ b: 109
16525
+ },
16526
+ flora: {
16527
+ r: 104,
16528
+ g: 249,
16529
+ b: 110
16530
+ },
16531
+ ice: {
16532
+ r: 104,
16533
+ g: 253,
16534
+ b: 255
16535
+ },
16536
+ orchid: {
16537
+ r: 110,
16538
+ g: 118,
16539
+ b: 255
16540
+ },
16541
+ bubblegum: {
16542
+ r: 255,
16543
+ g: 122,
16544
+ b: 255
16545
+ },
16546
+ lead: {
16547
+ r: 30,
16548
+ g: 30,
16549
+ b: 30
16550
+ },
16551
+ mercury: {
16552
+ r: 232,
16553
+ g: 232,
16554
+ b: 232
16555
+ },
16556
+ tangerine: {
16557
+ r: 255,
16558
+ g: 136,
16559
+ b: 2
16560
+ },
16561
+ lime: {
16562
+ r: 131,
16563
+ g: 249,
16564
+ b: 2
16565
+ },
16566
+ sea_foam: {
16567
+ r: 3,
16568
+ g: 249,
16569
+ b: 135
16570
+ },
16571
+ aqua: {
16572
+ r: 0,
16573
+ g: 140,
16574
+ b: 255
16575
+ },
16576
+ grape: {
16577
+ r: 137,
16578
+ g: 49,
16579
+ b: 255
16580
+ },
16581
+ strawberry: {
16582
+ r: 255,
16583
+ g: 41,
16584
+ b: 135
16585
+ },
16586
+ tungsten: {
16587
+ r: 58,
16588
+ g: 58,
16589
+ b: 58
16590
+ },
16591
+ silver: {
16592
+ r: 208,
16593
+ g: 208,
16594
+ b: 208
16595
+ },
16596
+ maraschino: {
16597
+ r: 255,
16598
+ g: 33,
16599
+ b: 1
16600
+ },
16601
+ lemon: {
16602
+ r: 255,
16603
+ g: 250,
16604
+ b: 3
16605
+ },
16606
+ spring: {
16607
+ r: 5,
16608
+ g: 248,
16609
+ b: 2
16610
+ },
16611
+ turquoise: {
16612
+ r: 0,
16613
+ g: 253,
16614
+ b: 255
16615
+ },
16616
+ blueberry: {
16617
+ r: 0,
16618
+ g: 46,
16619
+ b: 255
16620
+ },
16621
+ magenta: {
16622
+ r: 255,
16623
+ g: 57,
16624
+ b: 255
16625
+ },
16626
+ iron: {
16627
+ r: 84,
16628
+ g: 84,
16629
+ b: 83
16630
+ },
16631
+ magnesium: {
16632
+ r: 184,
16633
+ g: 184,
16634
+ b: 184
16635
+ },
16636
+ mocha: {
16637
+ r: 137,
16638
+ g: 72,
16639
+ b: 0
16640
+ },
16641
+ fern: {
16642
+ r: 69,
16643
+ g: 132,
16644
+ b: 1
16645
+ },
16646
+ moss: {
16647
+ r: 1,
16648
+ g: 132,
16649
+ b: 72
16650
+ },
16651
+ ocean: {
16652
+ r: 0,
16653
+ g: 74,
16654
+ b: 136
16655
+ },
16656
+ eggplant: {
16657
+ r: 73,
16658
+ g: 26,
16659
+ b: 136
16660
+ },
16661
+ maroon: {
16662
+ r: 137,
16663
+ g: 22,
16664
+ b: 72
16665
+ },
16666
+ steel: {
16667
+ r: 110,
16668
+ g: 110,
16669
+ b: 110
16670
+ },
16671
+ aluminum: {
16672
+ r: 160,
16673
+ g: 159,
16674
+ b: 160
16675
+ },
16676
+ cayenne: {
16677
+ r: 137,
16678
+ g: 17,
16679
+ b: 0
16680
+ },
16681
+ aspargus: {
16682
+ r: 136,
16683
+ g: 133,
16684
+ b: 1
16685
+ },
16686
+ clover: {
16687
+ r: 2,
16688
+ g: 132,
16689
+ b: 1
16690
+ },
16691
+ teal: {
16692
+ r: 0,
16693
+ g: 134,
16694
+ b: 136
16695
+ },
16696
+ midnight: {
16697
+ r: 0,
16698
+ g: 24,
16699
+ b: 136
16700
+ },
16701
+ plum: {
16702
+ r: 137,
16703
+ g: 30,
16704
+ b: 136
16705
+ },
16706
+ tin: {
16707
+ r: 135,
16708
+ g: 134,
16709
+ b: 135
16710
+ },
16711
+ nickel: {
16712
+ r: 136,
16713
+ g: 135,
16714
+ b: 135
16715
+ }
16716
+ };
16717
+ function appleCrayonRGB(name) {
16718
+ const {
16719
+ r,
16720
+ g,
16721
+ b
16722
+ } = appleCrayonRGBPalette[name];
16723
+ return `rgb(${r},${g},${b})`;
16724
+ }
16725
+ function appleCrayonRGBA(name, alpha) {
16726
+ const {
16727
+ r,
16728
+ g,
16729
+ b
16730
+ } = appleCrayonRGBPalette[name];
16731
+ return `rgba(${r},${g},${b},${alpha})`;
16732
+ }
16733
+ const colorPalettes = {
16734
+ Set1: ["rgb(228,26,28)", "rgb(55,126,184)", "rgb(77,175,74)", "rgb(166,86,40)", "rgb(152,78,163)", "rgb(255,127,0)", "rgb(247,129,191)", "rgb(153,153,153)", "rgb(255,255,51)"],
16735
+ Dark2: ["rgb(27,158,119)", "rgb(217,95,2)", "rgb(117,112,179)", "rgb(231,41,138)", "rgb(102,166,30)", "rgb(230,171,2)", "rgb(166,118,29)", "rgb(102,102,102)"],
16736
+ Set2: ["rgb(102, 194,165)", "rgb(252,141,98)", "rgb(141,160,203)", "rgb(231,138,195)", "rgb(166,216,84)", "rgb(255,217,47)", "rgb(229,196,148)", "rgb(179,179,179)"],
16737
+ Set3: ["rgb(141,211,199)", "rgb(255,255,179)", "rgb(190,186,218)", "rgb(251,128,114)", "rgb(128,177,211)", "rgb(253,180,98)", "rgb(179,222,105)", "rgb(252,205,229)", "rgb(217,217,217)", "rgb(188,128,189)", "rgb(204,235,197)", "rgb(255,237,111)"],
16738
+ Pastel1: ["rgb(251,180,174)", "rgb(179,205,227)", "rgb(204,235,197)", "rgb(222,203,228)", "rgb(254,217,166)", "rgb(255,255,204)", "rgb(229,216,189)", "rgb(253,218,236)"],
16739
+ Pastel2: ["rgb(173,226,207)", "rgb(253,205,172)", "rgb(203,213,232)", "rgb(244,202,228)", "rgb(230,245,201)", "rgb(255,242,174)", "rgb(243,225,206)"],
16740
+ Accent: ["rgb(127,201,127)", "rgb(190,174,212)", "rgb(253,192,134)", "rgb(255,255,153)", "rgb(56,108,176)", "rgb(240,2,127)", "rgb(191,91,23)"]
16741
+ };
16742
+ class PaletteColorTable {
16743
+ constructor(palette) {
16744
+ this.colors = colorPalettes[palette];
16745
+ if (!Array.isArray(this.colors)) this.colors = [];
16746
+ this.colorTable = {};
16747
+ this.nextIdx = 0;
16748
+ this.colorGenerator = new RandomColorGenerator();
16749
+ }
16750
+ getColor(key) {
16751
+ if (!this.colorTable.hasOwnProperty(key)) {
16752
+ if (this.nextIdx < this.colors.length) {
16753
+ this.colorTable[key] = this.colors[this.nextIdx];
16754
+ } else {
16755
+ this.colorTable[key] = this.colorGenerator.get();
16756
+ }
16757
+ this.nextIdx++;
16758
+ }
16759
+ return this.colorTable[key];
16760
+ }
16761
+ }
16762
+ class ColorTable {
16763
+ constructor(colors) {
16764
+ this.colorTable = colors || {};
16765
+ this.nextIdx = 0;
16766
+ this.colorGenerator = new RandomColorGenerator();
16767
+ }
16768
+ getColor(key) {
16769
+ if (!this.colorTable.hasOwnProperty(key)) {
16770
+ if (this.colorTable.hasOwnProperty("*")) {
16771
+ return this.colorTable["*"];
16772
+ }
16773
+ this.colorTable[key] = this.colorGenerator.get();
16774
+ }
16775
+ return this.colorTable[key];
16776
+ }
16777
+ }
16778
+
16779
+ // Random color generator from https://github.com/sterlingwes/RandomColor/blob/master/rcolor.js
16780
+ // Free to use & distribute under the MIT license
16781
+ // Wes Johnson (@SterlingWes)
16782
+ //
16783
+ // inspired by http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
16784
+ function RandomColorGenerator() {
16785
+ this.hue = Math.random();
16786
+ this.goldenRatio = 0.618033988749895;
16787
+ this.hexwidth = 2;
16788
+ }
16789
+ RandomColorGenerator.prototype.hsvToRgb = function (h, s, v) {
16790
+ var h_i = Math.floor(h * 6),
16791
+ f = h * 6 - h_i,
16792
+ p = v * (1 - s),
16793
+ q = v * (1 - f * s),
16794
+ t = v * (1 - (1 - f) * s),
16795
+ r = 255,
16796
+ g = 255,
16797
+ b = 255;
16798
+ switch (h_i) {
16799
+ case 0:
16800
+ r = v, g = t, b = p;
16801
+ break;
16802
+ case 1:
16803
+ r = q, g = v, b = p;
16804
+ break;
16805
+ case 2:
16806
+ r = p, g = v, b = t;
16807
+ break;
16808
+ case 3:
16809
+ r = p, g = q, b = v;
16810
+ break;
16811
+ case 4:
16812
+ r = t, g = p, b = v;
16813
+ break;
16814
+ case 5:
16815
+ r = v, g = p, b = q;
16816
+ break;
16817
+ }
16818
+ return [Math.floor(r * 256), Math.floor(g * 256), Math.floor(b * 256)];
16819
+ };
16820
+ RandomColorGenerator.prototype.padHex = function (str) {
16821
+ if (str.length > this.hexwidth) return str;
16822
+ return new Array(this.hexwidth - str.length + 1).join('0') + str;
16823
+ };
16824
+ RandomColorGenerator.prototype.get = function (saturation, value) {
16825
+ this.hue += this.goldenRatio;
16826
+ this.hue %= 1;
16827
+ if (typeof saturation !== "number") saturation = 0.5;
16828
+ if (typeof value !== "number") value = 0.95;
16829
+ var rgb = this.hsvToRgb(this.hue, saturation, value);
16830
+ return "#" + this.padHex(rgb[0].toString(16)) + this.padHex(rgb[1].toString(16)) + this.padHex(rgb[2].toString(16));
16831
+ };
16832
+ const randomColorGenerator = new RandomColorGenerator();
16833
+ function randomColor() {
16834
+ return randomColorGenerator.get();
16835
+ }
16836
+ function randomRGB$1(min, max) {
16837
+ min = IGVMath.clamp(min, 0, 255);
16838
+ max = IGVMath.clamp(max, 0, 255);
16839
+ const r = Math.round(Math.random() * (max - min) + min).toString(10);
16840
+ const g = Math.round(Math.random() * (max - min) + min).toString(10);
16841
+ const b = Math.round(Math.random() * (max - min) + min).toString(10);
16842
+ return `rgb(${r},${g},${b})`;
16843
+ }
16844
+
16475
16845
  /*
16476
16846
  * The MIT License (MIT)
16477
16847
  *
@@ -16699,6 +17069,18 @@
16699
17069
  if (fill) {
16700
17070
  ctx.fill();
16701
17071
  }
17072
+ },
17073
+ drawRandomColorVerticalLines: ctx => {
17074
+ for (let x = 0; x < ctx.canvas.width; x++) {
17075
+ IGVGraphics.fillRect(ctx, x, 0, 1, ctx.canvas.height, {
17076
+ fillStyle: randomRGB$1(100, 250)
17077
+ });
17078
+ }
17079
+ },
17080
+ labelTransformWithContext: (ctx, exe) => {
17081
+ ctx.translate(exe, 0);
17082
+ ctx.scale(-1, 1);
17083
+ ctx.translate(-exe, 0);
16702
17084
  }
16703
17085
  };
16704
17086
  function doPath(ctx, x, y) {
@@ -18787,7 +19169,7 @@
18787
19169
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
18788
19170
  * THE SOFTWARE.
18789
19171
  */
18790
- const knownFileExtensions = new Set(["narrowpeak", "broadpeak", "regionpeak", "peaks", "bedgraph", "wig", "gff3", "gff", "gtf", "fusionjuncspan", "refflat", "seg", "aed", "bed", "vcf", "bb", "bigbed", "biginteract", "biggenepred", "bignarrowpeak", "bw", "bigwig", "bam", "tdf", "refgene", "genepred", "genepredext", "bedpe", "bp", "snp", "rmsk", "cram", "gwas", "maf", "mut", "tsv", "hiccups"]);
19172
+ const knownFileExtensions = new Set(["narrowpeak", "broadpeak", "regionpeak", "peaks", "bedgraph", "wig", "gff3", "gff", "gtf", "fusionjuncspan", "refflat", "seg", "aed", "bed", "vcf", "bb", "bigbed", "biginteract", "biggenepred", "bignarrowpeak", "bw", "bigwig", "bam", "tdf", "refgene", "genepred", "genepredext", "bedpe", "bp", "snp", "rmsk", "cram", "gwas", "maf", "mut", "tsv", "hiccups", "fasta", "fa", "fna"]);
18791
19173
 
18792
19174
  /**
18793
19175
  * Return a custom format object with the given name.
@@ -18842,6 +19224,10 @@
18842
19224
  return "bigwig";
18843
19225
  case "bb":
18844
19226
  return "bigbed";
19227
+ case "fasta":
19228
+ case "fa":
19229
+ case "fna":
19230
+ return "fasta";
18845
19231
  default:
18846
19232
  if (knownFileExtensions.has(ext)) {
18847
19233
  return ext;
@@ -18901,6 +19287,8 @@
18901
19287
  case "biggenepred":
18902
19288
  case "bignarrowpeak":
18903
19289
  return "bedtype";
19290
+ case "fasta":
19291
+ return "sequence";
18904
19292
  default:
18905
19293
  return "annotation";
18906
19294
  }
@@ -18982,6 +19370,14 @@
18982
19370
  return comp;
18983
19371
  }
18984
19372
 
19373
+ class Chromosome {
19374
+ constructor(name, order, bpLength) {
19375
+ this.name = name;
19376
+ this.order = order;
19377
+ this.bpLength = bpLength;
19378
+ }
19379
+ }
19380
+
18985
19381
  /*
18986
19382
  * The MIT License (MIT)
18987
19383
  *
@@ -19006,318 +19402,160 @@
19006
19402
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19007
19403
  * THE SOFTWARE.
19008
19404
  */
19009
- const defaultSequenceTrackOrder = Number.MIN_SAFE_INTEGER;
19010
- const translationDict = {
19011
- 'TTT': 'F',
19012
- 'TTC': 'F',
19013
- 'TTA': 'L',
19014
- 'TTG': 'L',
19015
- 'CTT': 'L',
19016
- 'CTC': 'L',
19017
- 'CTA': 'L',
19018
- 'CTG': 'L',
19019
- 'ATT': 'I',
19020
- 'ATC': 'I',
19021
- 'ATA': 'I',
19022
- 'ATG': 'M',
19023
- 'GTT': 'V',
19024
- 'GTC': 'V',
19025
- 'GTA': 'V',
19026
- 'GTG': 'V',
19027
- 'TCT': 'S',
19028
- 'TCC': 'S',
19029
- 'TCA': 'S',
19030
- 'TCG': 'S',
19031
- 'CCT': 'P',
19032
- 'CCC': 'P',
19033
- 'CCA': 'P',
19034
- 'CCG': 'P',
19035
- 'ACT': 'T',
19036
- 'ACC': 'T',
19037
- 'ACA': 'T',
19038
- 'ACG': 'T',
19039
- 'GCT': 'A',
19040
- 'GCC': 'A',
19041
- 'GCA': 'A',
19042
- 'GCG': 'A',
19043
- 'TAT': 'Y',
19044
- 'TAC': 'Y',
19045
- 'TAA': 'STOP',
19046
- 'TAG': 'STOP',
19047
- 'CAT': 'H',
19048
- 'CAC': 'H',
19049
- 'CAA': 'Q',
19050
- 'CAG': 'Q',
19051
- 'AAT': 'N',
19052
- 'AAC': 'N',
19053
- 'AAA': 'K',
19054
- 'AAG': 'K',
19055
- 'GAT': 'D',
19056
- 'GAC': 'D',
19057
- 'GAA': 'E',
19058
- 'GAG': 'E',
19059
- 'TGT': 'C',
19060
- 'TGC': 'C',
19061
- 'TGA': 'STOP',
19062
- 'TGG': 'W',
19063
- 'CGT': 'R',
19064
- 'CGC': 'R',
19065
- 'CGA': 'R',
19066
- 'CGG': 'R',
19067
- 'AGT': 'S',
19068
- 'AGC': 'S',
19069
- 'AGA': 'R',
19070
- 'AGG': 'R',
19071
- 'GGT': 'G',
19072
- 'GGC': 'G',
19073
- 'GGA': 'G',
19074
- 'GGG': 'G'
19075
- };
19076
- const complement = {};
19077
- const t1 = ['A', 'G', 'C', 'T', 'Y', 'R', 'W', 'S', 'K', 'M', 'D', 'V', 'H', 'B', 'N', 'X'];
19078
- const t2 = ['T', 'C', 'G', 'A', 'R', 'Y', 'W', 'S', 'M', 'K', 'H', 'B', 'D', 'V', 'N', 'X'];
19079
- for (let i = 0; i < t1.length; i++) {
19080
- complement[t1[i]] = t2[i];
19081
- complement[t1[i].toLowerCase()] = t2[i].toLowerCase();
19082
- }
19083
- const DEFAULT_HEIGHT = 25;
19084
- const TRANSLATED_HEIGHT = 115;
19085
- const SEQUENCE_HEIGHT = 15;
19086
- const FRAME_HEIGHT = 25;
19087
- const FRAME_BORDER = 5;
19088
- const BP_PER_PIXEL_THRESHOLD = 1 / 10;
19089
- class SequenceTrack {
19090
- constructor(config, browser) {
19091
- this.type = "sequence";
19092
- this.browser = browser;
19093
- this.removable = false;
19405
+ const splitLines$4 = splitLines$5;
19406
+ const reservedProperties$1 = new Set(['fastaURL', 'indexURL', 'cytobandURL', 'indexed']);
19407
+ class NonIndexedFasta {
19408
+ constructor(reference) {
19409
+ this.fastaURL = reference.fastaURL;
19410
+ this.withCredentials = reference.withCredentials;
19411
+ this.chromosomeNames = [];
19412
+ this.chromosomes = {};
19413
+ this.sequences = new Map();
19414
+
19415
+ // Build a track-like config object from the referenceObject
19416
+ const config = {};
19417
+ for (let key in reference) {
19418
+ if (reference.hasOwnProperty(key) && !reservedProperties$1.has(key)) {
19419
+ config[key] = reference[key];
19420
+ }
19421
+ }
19094
19422
  this.config = config;
19095
- this.name = "Sequence";
19096
- this.id = "sequence";
19097
- this.sequenceType = config.sequenceType || "dna"; // dna | rna | prot
19098
- this.disableButtons = false;
19099
- this.order = config.order || defaultSequenceTrackOrder;
19100
- this.ignoreTrackMenu = false;
19101
- this.reversed = config.reversed === true;
19102
- this.frameTranslate = config.frameTranslate === true;
19103
- this.height = this.frameTranslate ? TRANSLATED_HEIGHT : DEFAULT_HEIGHT;
19104
19423
  }
19105
- menuItemList() {
19106
- return [{
19107
- name: this.reversed ? "Forward" : "Reverse",
19108
- click: () => {
19109
- this.reversed = !this.reversed;
19110
- this.trackView.repaintViews();
19111
- }
19112
- }, {
19113
- name: this.frameTranslate ? "Close Translation" : "Three-frame Translate",
19114
- click: () => {
19115
- this.frameTranslate = !this.frameTranslate;
19116
- if (this.frameTranslate) {
19117
- for (let vp of this.trackView.viewports) {
19118
- vp.setContentHeight(TRANSLATED_HEIGHT);
19119
- }
19120
- this.trackView.setTrackHeight(TRANSLATED_HEIGHT);
19121
- } else {
19122
- for (let vp of this.trackView.viewports) {
19123
- vp.setContentHeight(DEFAULT_HEIGHT);
19124
- }
19125
- this.trackView.setTrackHeight(DEFAULT_HEIGHT);
19126
- }
19127
- this.trackView.repaintViews();
19128
- }
19129
- }];
19424
+ async init() {
19425
+ return this.loadAll();
19130
19426
  }
19131
- contextMenuItemList(clickState) {
19132
- const viewport = clickState.viewport;
19133
- if (viewport.referenceFrame.bpPerPixel <= 1) {
19134
- const pixelWidth = viewport.getWidth();
19135
- const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
19136
- const chr = viewport.referenceFrame.chr;
19137
- const start = Math.floor(viewport.referenceFrame.start);
19138
- const end = Math.ceil(start + bpWindow);
19139
- const items = [{
19140
- label: this.reversed ? 'View visible sequence (reversed)...' : 'View visible sequence...',
19141
- click: async () => {
19142
- let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
19143
- if (!seq) {
19144
- seq = "Unknown sequence";
19145
- } else if (this.reversed) {
19146
- seq = reverseComplementSequence(seq);
19147
- }
19148
- this.browser.alert.present(seq);
19149
- }
19150
- }];
19151
- if (isSecureContext()) {
19152
- items.push({
19153
- label: 'Copy visible sequence',
19154
- click: async () => {
19155
- let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
19156
- if (!seq) {
19157
- seq = "Unknown sequence";
19158
- } else if (this.reversed) {
19159
- seq = reverseComplementSequence(seq);
19160
- }
19161
- try {
19162
- await navigator.clipboard.writeText(seq);
19163
- } catch (e) {
19164
- console.error(e);
19165
- this.browser.alert.present(`error copying sequence to clipboard ${e}`);
19166
- }
19167
- }
19168
- });
19169
- }
19170
- items.push('<hr/>');
19171
- return items;
19172
- } else {
19427
+ async getSequence(chr, start, end) {
19428
+ if (!this.sequences.has(chr)) {
19173
19429
  return undefined;
19174
19430
  }
19175
- }
19176
- translateSequence(seq) {
19177
- const threeFrame = [[], [], []];
19178
- for (let fNum of [0, 1, 2]) {
19179
- let idx = fNum;
19180
- while (seq.length - idx >= 3) {
19181
- let st = seq.slice(idx, idx + 3);
19182
- if (this.reversed) {
19183
- st = st.split('').reverse().join('');
19184
- }
19185
- const aa = translationDict[st.toUpperCase()] || "";
19186
- threeFrame[fNum].push({
19187
- codons: st,
19188
- aminoA: aa
19189
- });
19190
- idx += 3;
19431
+ let seqSlice = this.sequences.get(chr).find(ss => ss.contains(start, end));
19432
+ if (!seqSlice) {
19433
+ seqSlice = this.sequences.get(chr).find(ss => ss.overlaps(start, end));
19434
+ if (!seqSlice) {
19435
+ return undefined;
19191
19436
  }
19192
19437
  }
19193
- return threeFrame;
19194
- }
19195
- async getFeatures(chr, start, end, bpPerPixel) {
19196
- start = Math.floor(start);
19197
- end = Math.floor(end);
19198
- if (bpPerPixel && bpPerPixel > 1) {
19199
- return null;
19200
- } else {
19201
- const sequence = await this.browser.genome.sequence.getSequence(chr, start, end);
19202
- return {
19203
- bpStart: start,
19204
- sequence: sequence
19205
- };
19438
+ start -= seqSlice.offset;
19439
+ end -= seqSlice.offset;
19440
+ let prefix = "";
19441
+ if (start < 0) {
19442
+ for (let i = start; i < Math.min(end, 0); i++) {
19443
+ prefix += "*";
19444
+ }
19445
+ }
19446
+ if (end <= 0) {
19447
+ return Promise.resolve(prefix);
19206
19448
  }
19449
+ const seq = seqSlice.sequence;
19450
+ const seqEnd = Math.min(end, seq.length);
19451
+ return prefix + seq.substring(start, seqEnd);
19207
19452
  }
19208
- draw(options) {
19209
- const ctx = options.context;
19210
- if (options.features) {
19211
- let sequence = options.features.sequence;
19212
- if (!sequence) {
19213
- return;
19214
- }
19215
- if (this.reversed) {
19216
- sequence = sequence.split('').map(function (cv) {
19217
- return complement[cv];
19218
- }).join('');
19453
+ async loadAll() {
19454
+ let data;
19455
+ if (isDataURL(this.fastaURL)) {
19456
+ let bytes = decodeDataURI$1(this.fastaURL);
19457
+ data = "";
19458
+ for (let b of bytes) {
19459
+ data += String.fromCharCode(b);
19219
19460
  }
19220
- const sequenceBpStart = options.features.bpStart; // genomic position at start of sequence
19221
- const bpEnd = 1 + options.bpStart + options.pixelWidth * options.bpPerPixel;
19222
- for (let bp = Math.floor(options.bpStart); bp <= bpEnd; bp++) {
19223
- const seqIdx = Math.floor(bp - sequenceBpStart);
19224
- if (seqIdx >= 0 && seqIdx < sequence.length) {
19225
- const baseLetter = sequence[seqIdx];
19226
- const offsetBP = bp - options.bpStart;
19227
- const aPixel = offsetBP / options.bpPerPixel;
19228
- const pixelWidth = 1 / options.bpPerPixel;
19229
- const color = this.fillColor(baseLetter);
19230
- if (options.bpPerPixel > BP_PER_PIXEL_THRESHOLD) {
19231
- IGVGraphics.fillRect(ctx, aPixel, FRAME_BORDER, pixelWidth, SEQUENCE_HEIGHT - FRAME_BORDER, {
19232
- fillStyle: color
19233
- });
19234
- } else {
19235
- let textPixel = aPixel + 0.5 * (pixelWidth - ctx.measureText(baseLetter).width);
19236
- IGVGraphics.strokeText(ctx, baseLetter, textPixel, SEQUENCE_HEIGHT, {
19237
- strokeStyle: color
19238
- });
19239
- }
19461
+ } else {
19462
+ data = await igvxhr.load(this.fastaURL, buildOptions(this.config));
19463
+ }
19464
+ const chrNameSet = new Set();
19465
+ const lines = splitLines$4(data);
19466
+ const len = lines.length;
19467
+ let lineNo = 0;
19468
+ let order = 0;
19469
+ let nextLine;
19470
+ let current = {};
19471
+ while (lineNo < len) {
19472
+ nextLine = lines[lineNo++].trim();
19473
+ if (nextLine.startsWith("#") || nextLine.length === 0) ; else if (nextLine.startsWith(">")) {
19474
+ // Start the next sequence
19475
+ if (current && current.seq) {
19476
+ pushChromosome.call(this, current, order++);
19240
19477
  }
19241
- }
19242
- if (this.frameTranslate) {
19243
- let y = SEQUENCE_HEIGHT + 2 * FRAME_BORDER;
19244
- const translatedSequence = this.translateSequence(sequence);
19245
- for (let fNum = 0; fNum < translatedSequence.length; fNum++) {
19246
- // == 3, 1 for each frame
19247
-
19248
- const aaSequence = translatedSequence[fNum]; // AA sequence for this frame
19249
-
19250
- for (let idx = 0; idx < aaSequence.length; idx++) {
19251
- let color = 0 === idx % 2 ? 'rgb(160,160,160)' : 'rgb(224,224,224)';
19252
- const cv = aaSequence[idx];
19253
- const bpPos = sequenceBpStart + fNum + idx * 3;
19254
- const bpOffset = bpPos - options.bpStart;
19255
- const p0 = Math.floor(bpOffset / options.bpPerPixel);
19256
- const p1 = Math.floor((bpOffset + 3) / options.bpPerPixel);
19257
- const pc = Math.round((p0 + p1) / 2);
19258
- if (p1 < 0) {
19259
- continue; // off left edge
19260
- } else if (p0 > options.pixelWidth) {
19261
- break; // off right edge
19262
- }
19478
+ const parts = nextLine.substr(1).split(/\s+/);
19263
19479
 
19264
- let aaLabel = cv.aminoA;
19265
- if (cv.aminoA.indexOf('STOP') > -1) {
19266
- color = 'rgb(255, 0, 0)';
19267
- aaLabel = 'STOP'; //Color blind accessible
19268
- } else if (cv.aminoA === 'M') {
19269
- color = 'rgb(0, 153, 0)';
19270
- aaLabel = 'START'; //Color blind accessible
19271
- }
19480
+ // Check for samtools style locus string. This is not perfect, and could fail on weird sequence names
19481
+ const nameParts = parts[0].split(':');
19482
+ current.chr = nameParts[0];
19483
+ current.seq = "";
19484
+ current.offset = 0;
19485
+ if (nameParts.length > 1 && nameParts[1].indexOf('-') > 0) {
19486
+ const locusParts = nameParts[1].split('-');
19487
+ if (locusParts.length === 2 && /^[0-9]+$/.test(locusParts[0]) && /^[0-9]+$/.test(locusParts[1])) ;
19488
+ const from = Number.parseInt(locusParts[0]);
19489
+ const to = Number.parseInt(locusParts[1]);
19490
+ if (to > from) {
19491
+ // TODO this should be an error
19492
+ current.offset = from - 1;
19493
+ }
19272
19494
 
19273
- IGVGraphics.fillRect(ctx, p0, y, p1 - p0, FRAME_HEIGHT, {
19274
- fillStyle: color
19275
- });
19276
- if (options.bpPerPixel <= 1 / 10) {
19277
- IGVGraphics.strokeText(ctx, aaLabel, pc - ctx.measureText(aaLabel).width / 2, y + 15);
19495
+ // Check for chromosome length token
19496
+ if (parts.length > 1 && parts[1].startsWith("@len=")) {
19497
+ try {
19498
+ current.length = parseInt(parts[1].trim().substring(5));
19499
+ } catch (e) {
19500
+ current.length = undefined;
19501
+ console.error(`Error parsing sequence length for ${nextLine}`);
19278
19502
  }
19503
+ } else {
19504
+ current.length = undefined;
19279
19505
  }
19280
- y += FRAME_HEIGHT + FRAME_BORDER;
19281
19506
  }
19507
+ } else {
19508
+ current.seq += nextLine;
19509
+ }
19510
+ // add last seq
19511
+ if (current && current.seq) {
19512
+ pushChromosome.call(this, current, order);
19513
+ }
19514
+ }
19515
+ function pushChromosome(current, order) {
19516
+ const length = current.length || current.offset + current.seq.length;
19517
+ if (!chrNameSet.has(current.chr)) {
19518
+ this.chromosomeNames.push(current.chr);
19519
+ this.sequences.set(current.chr, []);
19520
+ this.chromosomes[current.chr] = new Chromosome(current.chr, order, length);
19521
+ chrNameSet.add(current.chr);
19522
+ } else {
19523
+ const c = this.chromosomes[current.chr];
19524
+ c.bpLength = Math.max(c.bpLength, length);
19282
19525
  }
19526
+ this.sequences.get(current.chr).push(new SequenceSlice(current.offset, current.seq));
19283
19527
  }
19284
19528
  }
19285
- get supportsWholeGenome() {
19286
- return false;
19529
+ }
19530
+ class SequenceSlice {
19531
+ constructor(offset, sequence) {
19532
+ this.offset = offset;
19533
+ this.sequence = sequence;
19287
19534
  }
19288
- computePixelHeight(ignore) {
19289
- this.height = this.frameTranslate ? TRANSLATED_HEIGHT : DEFAULT_HEIGHT;
19290
- return this.height;
19535
+ contains(start, end) {
19536
+ return this.offset <= start && this.end >= end;
19291
19537
  }
19292
- fillColor(index) {
19293
- if (this.color) {
19294
- return this.color;
19295
- } else if ("dna" === this.sequenceType) {
19296
- return this.browser.nucleotideColors[index] || 'gray';
19297
- } else {
19298
- return 'rgb(0, 0, 150)';
19299
- }
19538
+ overlaps(start, end) {
19539
+ return this.offset < end && this.end > start;
19300
19540
  }
19301
-
19302
- /**
19303
- * Return the current state of the track. Used to create sessions and bookmarks.
19304
- *
19305
- * @returns {*|{}}
19306
- */
19307
- getState() {
19308
- const config = {
19309
- type: "sequence"
19310
- };
19311
- if (this.order !== defaultSequenceTrackOrder) {
19312
- config.order = this.order;
19313
- }
19314
- if (this.reversed) {
19315
- config.revealed = true;
19316
- }
19317
- return config;
19541
+ get end() {
19542
+ return this.offset + this.sequence.length;
19318
19543
  }
19319
19544
  }
19320
19545
 
19546
+ const GenomicInterval = function (chr, start, end, features) {
19547
+ this.chr = chr;
19548
+ this.start = start;
19549
+ this.end = end;
19550
+ this.features = features;
19551
+ };
19552
+ GenomicInterval.prototype.contains = function (chr, start, end) {
19553
+ return this.chr === chr && this.start <= start && this.end >= end;
19554
+ };
19555
+ GenomicInterval.prototype.containsRange = function (range) {
19556
+ return this.chr === range.chr && this.start <= range.start && this.end >= range.end;
19557
+ };
19558
+
19321
19559
  /*
19322
19560
  * The MIT License (MIT)
19323
19561
  *
@@ -19342,87 +19580,947 @@
19342
19580
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19343
19581
  * THE SOFTWARE.
19344
19582
  */
19345
- class Viewport {
19346
- constructor(trackView, viewportColumn, referenceFrame, width) {
19347
- this.guid = guid$2();
19348
- this.trackView = trackView;
19349
- this.referenceFrame = referenceFrame;
19350
- this.browser = trackView.browser;
19351
- this.$viewport = $$1('<div class="igv-viewport">');
19352
- viewportColumn.appendChild(this.$viewport.get(0));
19353
- if (trackView.track.height) {
19354
- this.$viewport.get(0).style.height = `${trackView.track.height}px`;
19355
- }
19356
-
19357
- // Create an alert dialog for the sequence track to copy ref sequence to.
19358
- if (trackView.track instanceof SequenceTrack) {
19359
- this.alert = new AlertDialog(this.$viewport.get(0));
19360
- }
19361
- this.contentTop = 0;
19362
- this.contentHeight = this.$viewport.height();
19363
- this.$viewport.width(width);
19364
- this.initializationHelper();
19365
- }
19366
- initializationHelper() {}
19367
- showMessage(message) {
19368
- if (!this.messageDiv) {
19369
- this.messageDiv = document.createElement('div');
19370
- this.messageDiv.className = 'igv-viewport-message';
19371
- //this.contentDiv.append(this.messageDiv)
19372
- this.$viewport.append($$1(this.messageDiv));
19373
- }
19374
- this.messageDiv.textContent = message;
19375
- this.messageDiv.style.display = 'inline-block';
19376
- }
19377
- hideMessage(message) {
19378
- if (this.messageDiv) this.messageDiv.style.display = 'none';
19379
- }
19380
- setTrackLabel(label) {}
19381
- startSpinner() {}
19382
- stopSpinner() {}
19383
- checkZoomIn() {
19384
- return true;
19385
- }
19386
- shift() {}
19387
- setTop(contentTop) {
19388
- this.contentTop = contentTop;
19389
- this.$viewport.height();
19390
-
19391
- //this.$content.css('top', `${contentTop}px`)
19392
- //
19393
- // if (undefined === this.canvasVerticalRange || this.canvasVerticalRange.bottom < viewBottom || this.canvasVerticalRange.top > viewTop) {
19394
- // console.log("Repaint " + this.canvasVerticalRange)
19395
- // this.repaint()
19396
- // }
19397
- }
19583
+ const splitLines$3 = splitLines$5;
19584
+ const reservedProperties = new Set(['fastaURL', 'indexURL', 'compressedIndexURL', 'cytobandURL', 'indexed']);
19585
+ class FastaSequence {
19586
+ constructor(reference) {
19587
+ this.file = reference.fastaURL;
19588
+ this.indexFile = reference.indexURL || reference.indexFile || this.file + ".fai";
19589
+ this.compressedIndexFile = reference.compressedIndexURL || false;
19590
+ this.withCredentials = reference.withCredentials;
19591
+ this.chromosomeNames = [];
19592
+ this.chromosomes = {};
19593
+ this.sequences = {};
19594
+ this.offsets = {};
19398
19595
 
19399
- async loadFeatures() {
19400
- return undefined;
19401
- }
19402
- clearCache() {}
19403
- async repaint() {}
19404
- draw(drawConfiguration, features, roiFeatures) {
19405
- console.log('Viewport - draw(drawConfiguration, features, roiFeatures)');
19406
- }
19407
- checkContentHeight(features) {
19408
- let track = this.trackView.track;
19409
- features = features || this.cachedFeatures;
19410
- if ("FILL" === track.displayMode) {
19411
- this.setContentHeight(this.$viewport.height());
19412
- } else if (typeof track.computePixelHeight === 'function') {
19413
- if (features && features.length > 0) {
19414
- let requiredContentHeight = track.computePixelHeight(features);
19415
- //let currentContentHeight = this.$content.height()
19416
- let currentContentHeight = this.contentHeight;
19417
- if (requiredContentHeight !== currentContentHeight) {
19418
- this.setContentHeight(requiredContentHeight);
19419
- }
19596
+ // Build a track-like config object from the referenceObject
19597
+ const config = {};
19598
+ for (let key in reference) {
19599
+ if (reference.hasOwnProperty(key) && !reservedProperties.has(key)) {
19600
+ config[key] = reference[key];
19420
19601
  }
19421
19602
  }
19603
+ this.config = config;
19422
19604
  }
19423
- getContentHeight() {
19424
- //return this.$content.height()
19425
- return this.contentHeight;
19605
+ async init() {
19606
+ return this.getIndex();
19607
+ }
19608
+ async getSequence(chr, start, end) {
19609
+ const hasCachedSquence = this.interval && this.interval.contains(chr, start, end);
19610
+ if (!hasCachedSquence) {
19611
+ // Expand query, to minimum of 50kb
19612
+ let qstart = start;
19613
+ let qend = end;
19614
+ if (end - start < 50000) {
19615
+ const w = end - start;
19616
+ const center = Math.round(start + w / 2);
19617
+ qstart = Math.max(0, center - 25000);
19618
+ qend = center + 25000;
19619
+ }
19620
+ const seqBytes = await this.readSequence(chr, qstart, qend);
19621
+ this.interval = new GenomicInterval(chr, qstart, qend, seqBytes);
19622
+ }
19623
+ const offset = start - this.interval.start;
19624
+ const n = end - start;
19625
+ const seq = this.interval.features ? this.interval.features.substr(offset, n) : null;
19626
+ return seq;
19627
+ }
19628
+ async getIndex() {
19629
+ if (this.index) {
19630
+ return this.index;
19631
+ } else {
19632
+ const data = await igvxhr.load(this.indexFile, buildOptions(this.config));
19633
+ const lines = splitLines$3(data);
19634
+ const len = lines.length;
19635
+ let lineNo = 0;
19636
+ let order = 0;
19637
+ this.index = {};
19638
+ while (lineNo < len) {
19639
+ const tokens = lines[lineNo++].split("\t");
19640
+ const nTokens = tokens.length;
19641
+ if (nTokens === 5) {
19642
+ // Parse the index line.
19643
+ const chr = tokens[0];
19644
+ const size = parseInt(tokens[1]);
19645
+ const position = parseInt(tokens[2]);
19646
+ const basesPerLine = parseInt(tokens[3]);
19647
+ const bytesPerLine = parseInt(tokens[4]);
19648
+ const indexEntry = {
19649
+ size: size,
19650
+ position: position,
19651
+ basesPerLine: basesPerLine,
19652
+ bytesPerLine: bytesPerLine
19653
+ };
19654
+ this.chromosomeNames.push(chr);
19655
+ this.index[chr] = indexEntry;
19656
+ this.chromosomes[chr] = new Chromosome(chr, order++, size);
19657
+ }
19658
+ }
19659
+ return this.index;
19660
+ }
19661
+ }
19662
+
19663
+ //Code is losely based on https://github.com/GMOD/bgzf-filehandle
19664
+ //Reworked however in orde to work with the igvxhr interface for loading files
19665
+ //Additionally, replaced calls to the Long.js interface with standard JS calls for ArrayBuffers and the associated views
19666
+ //
19667
+ //The compressed index is an array of blocks, with each block being a pair: compressed-position & uncompressed-position (both in bytes)
19668
+ async getCompressedIndex() {
19669
+ const GZI_NUM_BYTES_OFFSET = 8;
19670
+ const GZI_NUM_BYTES_BLOCK = 8;
19671
+ if (this.compressedIndex) {
19672
+ return this.compressedIndex;
19673
+ }
19674
+ if (!this.compressedIndexFile) {
19675
+ this.compressedIndex = [];
19676
+ return this.compressedIndex;
19677
+ }
19678
+ //In contrast to the 'normal' reference (for which the index is chromosome based), this index is block-based
19679
+ //As such there is not need to make it a hash. An array is sufficient.
19680
+ this.compressedIndex = [];
19681
+ const gziData = await igvxhr.loadArrayBuffer(this.compressedIndexFile, buildOptions(this.config));
19682
+ const givenFileSize = gziData.byteLength;
19683
+ if (givenFileSize < GZI_NUM_BYTES_OFFSET) {
19684
+ console.log("Cannot parse GZI index file: length (" + givenFileSize + " bytes) is insufficient to determine content of index.");
19685
+ return this.compressedIndex;
19686
+ }
19687
+ //First 8 bytes are a little endian unsigned bigint (64bit), indicating the number of blocks in the index.
19688
+ const numBlocksBuffer = gziData.slice(0, GZI_NUM_BYTES_OFFSET);
19689
+ const numBlocks = Number(new DataView(numBlocksBuffer).getBigUint64(0, true));
19690
+ //The remainder of the gzi content are pairs of little endian unsigned bigint (64bit) numbers.
19691
+ //The first of the pair is the compressed position of a block
19692
+ //The second of the pair is the uncompressed position of a block
19693
+
19694
+ //Sanity check:
19695
+ //Is the size of the array-buffer (of the entire file) correct with regards to the number of blocks detailled by the first 8 bytes of the file?
19696
+ //Total file-size should be:
19697
+ // 8 + 2*(num_entries*8) bytes, with the first 8 bytes indicating the number of entries
19698
+ const expectedFileSize = GZI_NUM_BYTES_OFFSET + numBlocks * 2 * GZI_NUM_BYTES_BLOCK;
19699
+ if (givenFileSize != expectedFileSize) {
19700
+ console.log("Incorrect file size of reference genome index. Expected : " + expectedFileSize + ". Received : " + givenFileSize);
19701
+ return this.compressedIndex;
19702
+ }
19703
+
19704
+ //Push the first block to the index: the first block always has positions 0 for both the compressed and uncompressed file
19705
+ this.compressedIndex.push([0, 0]);
19706
+
19707
+ //Further process all the blocks of the GZI index, and keep them in memory
19708
+ for (let blockNumber = 0; blockNumber < numBlocks; blockNumber++) {
19709
+ const bufferBlockStart = GZI_NUM_BYTES_OFFSET + blockNumber * 2 * GZI_NUM_BYTES_BLOCK;
19710
+ const bufferBlockEnd = GZI_NUM_BYTES_OFFSET + blockNumber * 2 * GZI_NUM_BYTES_BLOCK + 2 * GZI_NUM_BYTES_BLOCK;
19711
+ const bufferBlock = gziData.slice(bufferBlockStart, bufferBlockEnd);
19712
+ const viewBlock = new DataView(bufferBlock);
19713
+ const compressedPosition = Number(viewBlock.getBigUint64(0, true)); //First 8 bytes
19714
+ const uncompressedPosition = Number(viewBlock.getBigUint64(GZI_NUM_BYTES_BLOCK, true)); //Last 8 bytes
19715
+ this.compressedIndex.push([compressedPosition, uncompressedPosition]);
19716
+ }
19717
+ return this.compressedIndex;
19718
+ }
19719
+
19720
+ //The Fasta-index gives a byte-position of the chromosomal sequences within the FASTA file.
19721
+ //These locations need to be remapped to the locations within the zipped reference genome, using the GZI index
19722
+ //This function provides this functionality by
19723
+ //1) taking the indicated start/stop byte locations within the UNCOMPRESSED FASTA file
19724
+ //2) remapping these byte locations to the correct blocks (and associated positions) within the COMPRESSED FASTA file
19725
+ //Subsequently, the calling method can then extract the correct blocks from the compressed FASTA files and uncompressed the data
19726
+ async getRelevantCompressedBlockNumbers(queryPositionStart, queryPositionEnd) {
19727
+ const UNCOMPRESSED_POSITION = 1;
19728
+ //Fallback for impossible values
19729
+ if (queryPositionStart < 0 || queryPositionEnd < 0 || queryPositionEnd < queryPositionStart) {
19730
+ console.log("Incompatible query positions for reference-genome. Start:" + queryPositionStart + " | End:" + queryPositionEnd);
19731
+ return [];
19732
+ }
19733
+ //Ensure compressed index is loaded
19734
+ await this.getCompressedIndex();
19735
+ let result = [];
19736
+ //Now search for the correct block-numbers (going from 0 to length(compressed-index)) which overlap with the provided byte-positions
19737
+ const lowestBlockNumber = 0;
19738
+ const highestBlockNumber = this.compressedIndex.length - 1;
19739
+ //Failsafe if for some reason the compressed index wasn't loaded or doesn't contain any data
19740
+ if (this.compressedIndex.length == 0) {
19741
+ console.log("Compressed index does not contain any content");
19742
+ return [];
19743
+ }
19744
+ //Failsafe: if the queryPositionStart is greater than the uncompressed-position of the final block,
19745
+ //then this final block is the only possible result
19746
+ if (queryPositionStart > this.compressedIndex[highestBlockNumber][UNCOMPRESSED_POSITION]) {
19747
+ return [highestBlockNumber];
19748
+ }
19749
+
19750
+ //Rather than doing a linear search over all blocks, a binary search is done for speed considerations
19751
+ //We are searching for the highest block number for which its position is smaller than the query start position
19752
+ //Afterwards we will simply expand the blocks until the entire query range is covered
19753
+ let searchLow = lowestBlockNumber;
19754
+ let searchHigh = highestBlockNumber;
19755
+ let searchPosition = Math.floor(this.compressedIndex.length / 2);
19756
+ let maxIterations = this.compressedIndex.length + 1;
19757
+ let solutionFound = false;
19758
+ //instead of doing a while(true), this for-loop prevents eternal loops in case of issues
19759
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
19760
+ const searchUncompressedPosition = this.compressedIndex[searchPosition][UNCOMPRESSED_POSITION];
19761
+ const nextSearchUncompressedPosition = searchPosition < this.compressedIndex.length - 1 ? this.compressedIndex[searchPosition + 1][UNCOMPRESSED_POSITION] : Infinity;
19762
+ //The query position lies within the current search block
19763
+ if (searchUncompressedPosition <= queryPositionStart && nextSearchUncompressedPosition > queryPositionStart) {
19764
+ solutionFound = true;
19765
+ break; //searchPosition is the correct block number index
19766
+ }
19767
+ //Current block lies before the query position
19768
+ else if (searchUncompressedPosition < queryPositionStart) {
19769
+ searchLow = searchPosition + 1;
19770
+ }
19771
+ //Current block lies after the query position
19772
+ else {
19773
+ searchHigh = searchPosition - 1;
19774
+ }
19775
+ searchPosition = Math.ceil((searchHigh - searchLow) / 2) + searchLow;
19776
+ }
19777
+ //If for some reason the binary search did not reveal a correct block index, then we return the empty result
19778
+ if (!solutionFound) {
19779
+ console.log("No blocks within compressed index found that correspond with query positions " + queryPositionStart + "," + queryPositionEnd);
19780
+ console.log(this.compressedIndex);
19781
+ return [];
19782
+ }
19783
+
19784
+ //Now extend the result by adding additional blocks until the entire query range is covered
19785
+ result.push(searchPosition);
19786
+ for (let blockIndex = searchPosition + 1; blockIndex < this.compressedIndex.length; blockIndex++) {
19787
+ result.push(blockIndex);
19788
+ const blockUncompressedPosition = this.compressedIndex[blockIndex][UNCOMPRESSED_POSITION];
19789
+ if (blockUncompressedPosition >= queryPositionEnd) {
19790
+ break;
19791
+ }
19792
+ }
19793
+
19794
+ //It is possible that the query end position lies AFTER the start of the final block
19795
+ //If this is the case, we add a 'fake' negative index which will be interpreted by the loadAndUncompressBlocks method as an indicator
19796
+ //to read until the end of the file
19797
+ const finalRelevantBlock = result[result.length - 1];
19798
+ const finalIndexBlock = this.compressedIndex.length - 1;
19799
+ if (finalRelevantBlock === finalIndexBlock && this.compressedIndex[finalRelevantBlock][UNCOMPRESSED_POSITION] < queryPositionEnd) {
19800
+ result.push(-1);
19801
+ }
19802
+ return result;
19803
+ }
19804
+
19805
+ //Load the content from the blockIndices.
19806
+ //This is done on a per-block basis
19807
+ //Content of the first block will be trimmed in order to match the expected offset
19808
+ async loadAndUncompressBlocks(blockIndices, startByte) {
19809
+ const COMPRESSED_POSITION = 0;
19810
+ const UNCOMPRESSED_POSITION = 1;
19811
+ //Normally the compressed index should already exist, we're just makeing sure here
19812
+ await this.getCompressedIndex();
19813
+ if (blockIndices.length == 0) {
19814
+ return "";
19815
+ }
19816
+
19817
+ //Storing data in seperate array with indices in order to assert order due to async behaviour of loops
19818
+ let resultCache = Array(blockIndices.length - 1);
19819
+ for (let i = 0; i < blockIndices.length - 1; i++) {
19820
+ const currentBlockNumber = blockIndices[i];
19821
+ const currentBlockInfo = this.compressedIndex[currentBlockNumber];
19822
+ const currentBlockCompressedPosition = currentBlockInfo[COMPRESSED_POSITION];
19823
+ const nextBlockNumber = blockIndices[i + 1];
19824
+ let compressedBytes = [];
19825
+ if (nextBlockNumber != -1) {
19826
+ //default : read current entire block only
19827
+ const nextBlockInfo = this.compressedIndex[nextBlockNumber];
19828
+ const nextBlockCompressedPosition = nextBlockInfo[COMPRESSED_POSITION];
19829
+ const compressedLength = nextBlockCompressedPosition - currentBlockCompressedPosition;
19830
+ compressedBytes = await igvxhr.loadArrayBuffer(this.file, buildOptions(this.config, {
19831
+ range: {
19832
+ start: currentBlockCompressedPosition,
19833
+ size: compressedLength
19834
+ }
19835
+ }));
19836
+ } else {
19837
+ // special case for query within final block: read until the end of the file
19838
+ compressedBytes = await igvxhr.loadArrayBuffer(this.file, buildOptions(this.config, {
19839
+ range: {
19840
+ start: currentBlockCompressedPosition
19841
+ }
19842
+ }));
19843
+ }
19844
+ //now unzip the compressed bytes, and store them in the resultCache
19845
+ const uncompressedBytes = await unbgzf(compressedBytes);
19846
+ resultCache[i] = uncompressedBytes;
19847
+ }
19848
+
19849
+ //Iterate over the result cache, create sequences from the data, and create a full sequence string from the data
19850
+ let result = "";
19851
+ for (let i = 0; i < resultCache.length; i++) {
19852
+ for (let j = 0; j < resultCache[i].length; j++) {
19853
+ const c = String.fromCharCode(resultCache[i][j]);
19854
+ result = result + c;
19855
+ }
19856
+ }
19857
+
19858
+ //postprocess this data: because entire blocks are read we need to remove the first N bases of the first used block,
19859
+ //which are not included in the original query positions
19860
+ const firstBlockInfo = this.compressedIndex[blockIndices[0]];
19861
+ const offset = startByte - firstBlockInfo[UNCOMPRESSED_POSITION];
19862
+ result = result.substring(offset);
19863
+ return result;
19864
+ }
19865
+ async readSequence(chr, qstart, qend) {
19866
+ await this.getIndex();
19867
+ await this.getCompressedIndex(); //This will work even if no compressed index file is set
19868
+
19869
+ const idxEntry = this.index[chr];
19870
+ if (!idxEntry) {
19871
+ console.log("No index entry for chr: " + chr);
19872
+ // Tag interval with null so we don't try again
19873
+ this.interval = new GenomicInterval(chr, qstart, qend, null);
19874
+ return null;
19875
+ }
19876
+ const start = Math.max(0, qstart); // qstart should never be < 0
19877
+ const end = Math.min(idxEntry.size, qend);
19878
+ const bytesPerLine = idxEntry.bytesPerLine;
19879
+ const basesPerLine = idxEntry.basesPerLine;
19880
+ const position = idxEntry.position;
19881
+ const nEndBytes = bytesPerLine - basesPerLine;
19882
+ const startLine = Math.floor(start / basesPerLine);
19883
+ const endLine = Math.floor(end / basesPerLine);
19884
+ const base0 = startLine * basesPerLine; // Base at beginning of start line
19885
+ const offset = start - base0;
19886
+ const startByte = position + startLine * bytesPerLine + offset;
19887
+ const base1 = endLine * basesPerLine;
19888
+ const offset1 = end - base1;
19889
+ const endByte = position + endLine * bytesPerLine + offset1 - 1;
19890
+ const byteCount = endByte - startByte + 1;
19891
+ if (byteCount <= 0) {
19892
+ console.error("No sequence for " + chr + ":" + qstart + "-" + qend);
19893
+ return null;
19894
+ }
19895
+
19896
+ //If the compressed index file is set, then we are dealing with a compressed genome sequence
19897
+ //The selection of startByte/endByte is done for the non-compressed genome sequence.
19898
+ //These need to be 'converted' to the correct byte positions in the compressed genome sequence,
19899
+ //by making use of the compressed index (GZI file)
19900
+ let allBytes;
19901
+ if (!this.compressedIndexFile) {
19902
+ allBytes = await igvxhr.load(this.file, buildOptions(this.config, {
19903
+ range: {
19904
+ start: startByte,
19905
+ size: byteCount
19906
+ }
19907
+ }));
19908
+ } else {
19909
+ let relevantBlockIndices = await this.getRelevantCompressedBlockNumbers(startByte, endByte);
19910
+ if (relevantBlockIndices.length === 0) {
19911
+ console.log("No blocks in the compressed index that correspond with the requested byte positions (" + startByte + "," + endByte + ")");
19912
+ return null;
19913
+ }
19914
+ allBytes = await this.loadAndUncompressBlocks(relevantBlockIndices, startByte);
19915
+ }
19916
+ if (!allBytes) {
19917
+ return null;
19918
+ }
19919
+ let nBases,
19920
+ seqBytes = "",
19921
+ srcPos = 0,
19922
+ allBytesLength = allBytes.length;
19923
+ if (offset > 0) {
19924
+ nBases = Math.min(end - start, basesPerLine - offset);
19925
+ seqBytes += allBytes.substr(srcPos, nBases);
19926
+ srcPos += nBases + nEndBytes;
19927
+ }
19928
+ while (srcPos < allBytesLength) {
19929
+ nBases = Math.min(basesPerLine, allBytesLength - srcPos);
19930
+ seqBytes += allBytes.substr(srcPos, nBases);
19931
+ srcPos += nBases + nEndBytes;
19932
+ }
19933
+ return seqBytes;
19934
+ }
19935
+ }
19936
+
19937
+ /*
19938
+ * The MIT License (MIT)
19939
+ *
19940
+ * Copyright (c) 2014 Broad Institute
19941
+ *
19942
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
19943
+ * of this software and associated documentation files (the "Software"), to deal
19944
+ * in the Software without restriction, including without limitation the rights
19945
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19946
+ * copies of the Software, and to permit persons to whom the Software is
19947
+ * furnished to do so, subject to the following conditions:
19948
+ *
19949
+ * The above copyright notice and this permission notice shall be included in
19950
+ * all copies or substantial portions of the Software.
19951
+ *
19952
+ *
19953
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19954
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19955
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19956
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19957
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19958
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19959
+ * THE SOFTWARE.
19960
+ */
19961
+ const splitLines$2 = splitLines$5;
19962
+ class ChromSizes {
19963
+ constructor(url) {
19964
+ this.url = url;
19965
+ this.chromosomeNames = [];
19966
+ this.chromosomes = {};
19967
+ }
19968
+ async init() {
19969
+ return this.loadAll();
19970
+ }
19971
+ async getSequence(chr, start, end) {
19972
+ return undefined; // TODO -- return array of "N"s?
19973
+ }
19974
+
19975
+ async loadAll() {
19976
+ let data;
19977
+ if (isDataURL(this.url)) {
19978
+ let bytes = decodeDataURI$1(this.fastaURL);
19979
+ data = "";
19980
+ for (let b of bytes) {
19981
+ data += String.fromCharCode(b);
19982
+ }
19983
+ } else {
19984
+ data = await igvxhr.load(this.url, {});
19985
+ }
19986
+ this.chromosomeNames = [];
19987
+ this.chromosomes = {};
19988
+ const lines = splitLines$2(data);
19989
+ let order = 0;
19990
+ for (let nextLine of lines) {
19991
+ const tokens = nextLine.split('\t');
19992
+ this.chromosomeNames.push(tokens[0]);
19993
+ const chrLength = Number.parseInt(tokens[1]);
19994
+ const chromosome = new Chromosome(tokens[0], order++, chrLength);
19995
+ this.chromosomes[tokens[0]] = chromosome;
19996
+ }
19997
+ }
19998
+ }
19999
+
20000
+ async function loadFasta(reference) {
20001
+ let fasta;
20002
+ if ("chromsizes" === reference.format) {
20003
+ fasta = new ChromSizes(reference.fastaURL);
20004
+ } else if (isDataURL(reference.fastaURL) || reference.indexed === false) {
20005
+ fasta = new NonIndexedFasta(reference);
20006
+ } else {
20007
+ fasta = new FastaSequence(reference);
20008
+ }
20009
+ await fasta.init();
20010
+ return fasta;
20011
+ }
20012
+
20013
+ const defaultNucleotideColors = {
20014
+ "A": "rgb( 0, 200, 0)",
20015
+ "C": "rgb( 0,0,200)",
20016
+ "T": "rgb(255,0,0)",
20017
+ "G": "rgb(209,113, 5)",
20018
+ "N": "rgb(80,80,80)"
20019
+ };
20020
+
20021
+ /*
20022
+ * The MIT License (MIT)
20023
+ *
20024
+ * Copyright (c) 2014 Broad Institute
20025
+ *
20026
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
20027
+ * of this software and associated documentation files (the "Software"), to deal
20028
+ * in the Software without restriction, including without limitation the rights
20029
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20030
+ * copies of the Software, and to permit persons to whom the Software is
20031
+ * furnished to do so, subject to the following conditions:
20032
+ *
20033
+ * The above copyright notice and this permission notice shall be included in
20034
+ * all copies or substantial portions of the Software.
20035
+ *
20036
+ *
20037
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20038
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20039
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20040
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20041
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20042
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20043
+ * THE SOFTWARE.
20044
+ */
20045
+ const defaultSequenceTrackOrder = Number.MIN_SAFE_INTEGER;
20046
+ const translationDict = {
20047
+ 'TTT': 'F',
20048
+ 'TTC': 'F',
20049
+ 'TTA': 'L',
20050
+ 'TTG': 'L',
20051
+ 'CTT': 'L',
20052
+ 'CTC': 'L',
20053
+ 'CTA': 'L',
20054
+ 'CTG': 'L',
20055
+ 'ATT': 'I',
20056
+ 'ATC': 'I',
20057
+ 'ATA': 'I',
20058
+ 'ATG': 'M',
20059
+ 'GTT': 'V',
20060
+ 'GTC': 'V',
20061
+ 'GTA': 'V',
20062
+ 'GTG': 'V',
20063
+ 'TCT': 'S',
20064
+ 'TCC': 'S',
20065
+ 'TCA': 'S',
20066
+ 'TCG': 'S',
20067
+ 'CCT': 'P',
20068
+ 'CCC': 'P',
20069
+ 'CCA': 'P',
20070
+ 'CCG': 'P',
20071
+ 'ACT': 'T',
20072
+ 'ACC': 'T',
20073
+ 'ACA': 'T',
20074
+ 'ACG': 'T',
20075
+ 'GCT': 'A',
20076
+ 'GCC': 'A',
20077
+ 'GCA': 'A',
20078
+ 'GCG': 'A',
20079
+ 'TAT': 'Y',
20080
+ 'TAC': 'Y',
20081
+ 'TAA': 'STOP',
20082
+ 'TAG': 'STOP',
20083
+ 'CAT': 'H',
20084
+ 'CAC': 'H',
20085
+ 'CAA': 'Q',
20086
+ 'CAG': 'Q',
20087
+ 'AAT': 'N',
20088
+ 'AAC': 'N',
20089
+ 'AAA': 'K',
20090
+ 'AAG': 'K',
20091
+ 'GAT': 'D',
20092
+ 'GAC': 'D',
20093
+ 'GAA': 'E',
20094
+ 'GAG': 'E',
20095
+ 'TGT': 'C',
20096
+ 'TGC': 'C',
20097
+ 'TGA': 'STOP',
20098
+ 'TGG': 'W',
20099
+ 'CGT': 'R',
20100
+ 'CGC': 'R',
20101
+ 'CGA': 'R',
20102
+ 'CGG': 'R',
20103
+ 'AGT': 'S',
20104
+ 'AGC': 'S',
20105
+ 'AGA': 'R',
20106
+ 'AGG': 'R',
20107
+ 'GGT': 'G',
20108
+ 'GGC': 'G',
20109
+ 'GGA': 'G',
20110
+ 'GGG': 'G'
20111
+ };
20112
+ const complement = {};
20113
+ const t1 = ['A', 'G', 'C', 'T', 'Y', 'R', 'W', 'S', 'K', 'M', 'D', 'V', 'H', 'B', 'N', 'X'];
20114
+ const t2 = ['T', 'C', 'G', 'A', 'R', 'Y', 'W', 'S', 'M', 'K', 'H', 'B', 'D', 'V', 'N', 'X'];
20115
+ for (let i = 0; i < t1.length; i++) {
20116
+ complement[t1[i]] = t2[i];
20117
+ complement[t1[i].toLowerCase()] = t2[i].toLowerCase();
20118
+ }
20119
+ const DEFAULT_HEIGHT = 25;
20120
+ const TRANSLATED_HEIGHT = 115;
20121
+ const SEQUENCE_HEIGHT = 15;
20122
+ const FRAME_HEIGHT = 25;
20123
+ const FRAME_BORDER = 5;
20124
+ const BP_PER_PIXEL_THRESHOLD = 1 / 10;
20125
+ const bppFeatureFetchThreshold = 10;
20126
+ class SequenceTrack {
20127
+ constructor(config, browser) {
20128
+ this.config = config;
20129
+ this.browser = browser;
20130
+ this.type = "sequence";
20131
+ this.removable = config.removable === undefined ? true : config.removable; // Defaults to true
20132
+ this.name = config.name;
20133
+ this.id = config.id;
20134
+ this.sequenceType = config.sequenceType || "dna"; // dna | rna | prot
20135
+ this.disableButtons = false;
20136
+ this.order = config.order || defaultSequenceTrackOrder;
20137
+ this.ignoreTrackMenu = false;
20138
+ this.reversed = config.reversed === true;
20139
+ this.frameTranslate = config.frameTranslate === true;
20140
+ this.height = this.frameTranslate ? TRANSLATED_HEIGHT : DEFAULT_HEIGHT;
20141
+
20142
+ // Hack for backward compatibility
20143
+ if (config.url) {
20144
+ config.fastaURL = config.url;
20145
+ }
20146
+ if (!config.fastaURL) {
20147
+ // Mark this as the genome reference sequence ==> backward compatibility convention
20148
+ this.id = config.id || "sequence";
20149
+ }
20150
+ }
20151
+ menuItemList() {
20152
+ return [{
20153
+ name: this.reversed ? "Forward" : "Reverse",
20154
+ click: () => {
20155
+ this.reversed = !this.reversed;
20156
+ this.trackView.repaintViews();
20157
+ }
20158
+ }, {
20159
+ name: this.frameTranslate ? "Close Translation" : "Three-frame Translate",
20160
+ click: () => {
20161
+ this.frameTranslate = !this.frameTranslate;
20162
+ if (this.frameTranslate) {
20163
+ for (let vp of this.trackView.viewports) {
20164
+ vp.setContentHeight(TRANSLATED_HEIGHT);
20165
+ }
20166
+ this.trackView.setTrackHeight(TRANSLATED_HEIGHT);
20167
+ } else {
20168
+ for (let vp of this.trackView.viewports) {
20169
+ vp.setContentHeight(DEFAULT_HEIGHT);
20170
+ }
20171
+ this.trackView.setTrackHeight(DEFAULT_HEIGHT);
20172
+ }
20173
+ this.trackView.repaintViews();
20174
+ }
20175
+ }];
20176
+ }
20177
+ contextMenuItemList(clickState) {
20178
+ const viewport = clickState.viewport;
20179
+ if (viewport.referenceFrame.bpPerPixel <= 1) {
20180
+ const pixelWidth = viewport.getWidth();
20181
+ const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20182
+ const chr = viewport.referenceFrame.chr;
20183
+ const start = Math.floor(viewport.referenceFrame.start);
20184
+ const end = Math.ceil(start + bpWindow);
20185
+ const items = [{
20186
+ label: this.reversed ? 'View visible sequence (reversed)...' : 'View visible sequence...',
20187
+ click: async () => {
20188
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20189
+ if (!seq) {
20190
+ seq = "Unknown sequence";
20191
+ } else if (this.reversed) {
20192
+ seq = reverseComplementSequence(seq);
20193
+ }
20194
+ this.browser.alert.present(seq);
20195
+ }
20196
+ }];
20197
+ if (isSecureContext()) {
20198
+ items.push({
20199
+ label: 'Copy visible sequence',
20200
+ click: async () => {
20201
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20202
+ if (!seq) {
20203
+ seq = "Unknown sequence";
20204
+ } else if (this.reversed) {
20205
+ seq = reverseComplementSequence(seq);
20206
+ }
20207
+ try {
20208
+ await navigator.clipboard.writeText(seq);
20209
+ } catch (e) {
20210
+ console.error(e);
20211
+ this.browser.alert.present(`error copying sequence to clipboard ${e}`);
20212
+ }
20213
+ }
20214
+ });
20215
+ }
20216
+ items.push('<hr/>');
20217
+ return items;
20218
+ } else {
20219
+ return undefined;
20220
+ }
20221
+ }
20222
+ translateSequence(seq) {
20223
+ const threeFrame = [[], [], []];
20224
+ for (let fNum of [0, 1, 2]) {
20225
+ let idx = fNum;
20226
+ while (seq.length - idx >= 3) {
20227
+ let st = seq.slice(idx, idx + 3);
20228
+ if (this.reversed) {
20229
+ st = st.split('').reverse().join('');
20230
+ }
20231
+ const aa = translationDict[st.toUpperCase()] || "";
20232
+ threeFrame[fNum].push({
20233
+ codons: st,
20234
+ aminoA: aa
20235
+ });
20236
+ idx += 3;
20237
+ }
20238
+ }
20239
+ return threeFrame;
20240
+ }
20241
+
20242
+ /**
20243
+ * Return the source for sequence. If an explicit fasta url is defined, use it, otherwise fetch sequence
20244
+ * from the current genome
20245
+ * *
20246
+ * @returns {Promise<WrappedFasta|*>}
20247
+ */
20248
+ async getSequenceSource() {
20249
+ if (this.config.fastaURL) {
20250
+ if (!this.fasta) {
20251
+ this.fasta = new WrappedFasta(this.config, this.browser.genome);
20252
+ await this.fasta.init();
20253
+ }
20254
+ return this.fasta;
20255
+ } else {
20256
+ return this.browser.genome.sequence;
20257
+ }
20258
+ }
20259
+ async getFeatures(chr, start, end, bpPerPixel) {
20260
+ start = Math.floor(start);
20261
+ end = Math.floor(end);
20262
+ if (bpPerPixel && bpPerPixel > bppFeatureFetchThreshold) {
20263
+ return null;
20264
+ } else {
20265
+ const sequenceSource = await this.getSequenceSource();
20266
+ const sequence = await sequenceSource.getSequence(chr, start, end);
20267
+ return {
20268
+ bpStart: start,
20269
+ sequence: sequence
20270
+ };
20271
+ }
20272
+ }
20273
+ draw(options) {
20274
+ const ctx = options.context;
20275
+ if (options.features) {
20276
+ let sequence = options.features.sequence;
20277
+ if (!sequence) {
20278
+ return;
20279
+ }
20280
+ if (this.reversed) {
20281
+ sequence = sequence.split('').map(function (cv) {
20282
+ return complement[cv];
20283
+ }).join('');
20284
+ }
20285
+ const sequenceBpStart = options.features.bpStart; // genomic position at start of sequence
20286
+ const bpEnd = 1 + options.bpStart + options.pixelWidth * options.bpPerPixel;
20287
+ for (let bp = Math.floor(options.bpStart); bp <= bpEnd; bp++) {
20288
+ const seqIdx = Math.floor(bp - sequenceBpStart);
20289
+ if (seqIdx >= 0 && seqIdx < sequence.length) {
20290
+ const offsetBP = bp - options.bpStart;
20291
+ const aPixel = offsetBP / options.bpPerPixel;
20292
+ const pixelWidth = 1 / options.bpPerPixel;
20293
+ const baseLetter = sequence[seqIdx];
20294
+ const color = this.fillColor(baseLetter.toUpperCase());
20295
+ if (options.bpPerPixel > BP_PER_PIXEL_THRESHOLD) {
20296
+ IGVGraphics.fillRect(ctx, aPixel, FRAME_BORDER, pixelWidth, SEQUENCE_HEIGHT - FRAME_BORDER, {
20297
+ fillStyle: color
20298
+ });
20299
+ } else {
20300
+ const textPixel = aPixel + 0.5 * (pixelWidth - ctx.measureText(baseLetter).width);
20301
+ if ('y' === options.axis) {
20302
+ ctx.save();
20303
+ IGVGraphics.labelTransformWithContext(ctx, textPixel);
20304
+ IGVGraphics.strokeText(ctx, baseLetter, textPixel, SEQUENCE_HEIGHT, {
20305
+ strokeStyle: color
20306
+ });
20307
+ ctx.restore();
20308
+ } else {
20309
+ IGVGraphics.strokeText(ctx, baseLetter, textPixel, SEQUENCE_HEIGHT, {
20310
+ strokeStyle: color
20311
+ });
20312
+ }
20313
+ }
20314
+ }
20315
+ }
20316
+ if (this.frameTranslate) {
20317
+ let y = SEQUENCE_HEIGHT + 2 * FRAME_BORDER;
20318
+ const translatedSequence = this.translateSequence(sequence);
20319
+ for (let fNum = 0; fNum < translatedSequence.length; fNum++) {
20320
+ // == 3, 1 for each frame
20321
+
20322
+ const aaSequence = translatedSequence[fNum]; // AA sequence for this frame
20323
+
20324
+ for (let idx = 0; idx < aaSequence.length; idx++) {
20325
+ let color = 0 === idx % 2 ? 'rgb(160,160,160)' : 'rgb(224,224,224)';
20326
+ const cv = aaSequence[idx];
20327
+ const bpPos = sequenceBpStart + fNum + idx * 3;
20328
+ const bpOffset = bpPos - options.bpStart;
20329
+ const p0 = Math.floor(bpOffset / options.bpPerPixel);
20330
+ const p1 = Math.floor((bpOffset + 3) / options.bpPerPixel);
20331
+ const pc = Math.round((p0 + p1) / 2);
20332
+ if (p1 < 0) {
20333
+ continue; // off left edge
20334
+ } else if (p0 > options.pixelWidth) {
20335
+ break; // off right edge
20336
+ }
20337
+
20338
+ let aaLabel = cv.aminoA;
20339
+ if (cv.aminoA.indexOf('STOP') > -1) {
20340
+ color = 'rgb(255, 0, 0)';
20341
+ aaLabel = 'STOP'; //Color blind accessible
20342
+ } else if (cv.aminoA === 'M') {
20343
+ color = 'rgb(0, 153, 0)';
20344
+ aaLabel = 'START'; //Color blind accessible
20345
+ }
20346
+
20347
+ IGVGraphics.fillRect(ctx, p0, y, p1 - p0, FRAME_HEIGHT, {
20348
+ fillStyle: color
20349
+ });
20350
+ if (options.bpPerPixel <= 1 / 10) {
20351
+ IGVGraphics.strokeText(ctx, aaLabel, pc - ctx.measureText(aaLabel).width / 2, y + 15);
20352
+ }
20353
+ }
20354
+ y += FRAME_HEIGHT + FRAME_BORDER;
20355
+ }
20356
+ }
20357
+ }
20358
+ }
20359
+ get supportsWholeGenome() {
20360
+ return false;
20361
+ }
20362
+ computePixelHeight(ignore) {
20363
+ this.height = this.frameTranslate ? TRANSLATED_HEIGHT : DEFAULT_HEIGHT;
20364
+ return this.height;
20365
+ }
20366
+ fillColor(index) {
20367
+ if (this.color) {
20368
+ return this.color;
20369
+ } else if ("dna" === this.sequenceType) {
20370
+ // return this.browser.nucleotideColors[index] || 'gray'
20371
+ return defaultNucleotideColors[index] || 'gray';
20372
+ } else {
20373
+ return 'rgb(0, 0, 150)';
20374
+ }
20375
+ }
20376
+
20377
+ /**
20378
+ * Return the current state of the track. Used to create sessions and bookmarks.
20379
+ *
20380
+ * @returns {*|{}}
20381
+ */
20382
+ getState() {
20383
+ const config = {
20384
+ type: "sequence"
20385
+ };
20386
+ if (this.order !== defaultSequenceTrackOrder) {
20387
+ config.order = this.order;
20388
+ }
20389
+ if (this.reversed) {
20390
+ config.revealed = true;
20391
+ }
20392
+ return config;
20393
+ }
20394
+ }
20395
+
20396
+ /**
20397
+ * Wrapper for a Fasta object that does chr name alias translation. This is not neccessary for the genome fasta,
20398
+ * as it defines the reference name, but can be neccessary if loading an additional fasta as a track
20399
+ *
20400
+ */
20401
+ class WrappedFasta {
20402
+ constructor(config, genome) {
20403
+ this.config = config;
20404
+ this.genome = genome;
20405
+ }
20406
+ async init() {
20407
+ this.fasta = await loadFasta(this.config);
20408
+ this.chrNameMap = new Map();
20409
+ for (let name of this.fasta.chromosomeNames) {
20410
+ this.chrNameMap.set(this.genome.getChromosomeName(name), name);
20411
+ }
20412
+ }
20413
+ async getSequence(chr, start, end) {
20414
+ const chrName = this.chrNameMap.has(chr) ? this.chrNameMap.get(chr) : chr;
20415
+ return this.fasta.getSequence(chrName, start, end);
20416
+ }
20417
+ }
20418
+
20419
+ /*
20420
+ * The MIT License (MIT)
20421
+ *
20422
+ * Copyright (c) 2014 Broad Institute
20423
+ *
20424
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
20425
+ * of this software and associated documentation files (the "Software"), to deal
20426
+ * in the Software without restriction, including without limitation the rights
20427
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20428
+ * copies of the Software, and to permit persons to whom the Software is
20429
+ * furnished to do so, subject to the following conditions:
20430
+ *
20431
+ * The above copyright notice and this permission notice shall be included in
20432
+ * all copies or substantial portions of the Software.
20433
+ *
20434
+ *
20435
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20436
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20437
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20438
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20439
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20440
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20441
+ * THE SOFTWARE.
20442
+ */
20443
+ class Viewport {
20444
+ constructor(trackView, viewportColumn, referenceFrame, width) {
20445
+ this.guid = guid$2();
20446
+ this.trackView = trackView;
20447
+ this.referenceFrame = referenceFrame;
20448
+ this.browser = trackView.browser;
20449
+ this.$viewport = $$1('<div class="igv-viewport">');
20450
+ viewportColumn.appendChild(this.$viewport.get(0));
20451
+ if (trackView.track.height) {
20452
+ this.$viewport.get(0).style.height = `${trackView.track.height}px`;
20453
+ }
20454
+
20455
+ // Create an alert dialog for the sequence track to copy ref sequence to.
20456
+ if (trackView.track instanceof SequenceTrack) {
20457
+ this.alert = new AlertDialog(this.$viewport.get(0));
20458
+ }
20459
+ this.contentTop = 0;
20460
+ this.contentHeight = this.$viewport.height();
20461
+ this.$viewport.width(width);
20462
+ this.initializationHelper();
20463
+ }
20464
+ initializationHelper() {}
20465
+ showMessage(message) {
20466
+ if (!this.messageDiv) {
20467
+ this.messageDiv = document.createElement('div');
20468
+ this.messageDiv.className = 'igv-viewport-message';
20469
+ //this.contentDiv.append(this.messageDiv)
20470
+ this.$viewport.append($$1(this.messageDiv));
20471
+ }
20472
+ this.messageDiv.textContent = message;
20473
+ this.messageDiv.style.display = 'inline-block';
20474
+ }
20475
+ hideMessage(message) {
20476
+ if (this.messageDiv) this.messageDiv.style.display = 'none';
20477
+ }
20478
+ setTrackLabel(label) {}
20479
+ startSpinner() {}
20480
+ stopSpinner() {}
20481
+ checkZoomIn() {
20482
+ return true;
20483
+ }
20484
+ shift() {}
20485
+ setTop(contentTop) {
20486
+ this.contentTop = contentTop;
20487
+ this.$viewport.height();
20488
+
20489
+ //this.$content.css('top', `${contentTop}px`)
20490
+ //
20491
+ // if (undefined === this.canvasVerticalRange || this.canvasVerticalRange.bottom < viewBottom || this.canvasVerticalRange.top > viewTop) {
20492
+ // console.log("Repaint " + this.canvasVerticalRange)
20493
+ // this.repaint()
20494
+ // }
20495
+ }
20496
+
20497
+ async loadFeatures() {
20498
+ return undefined;
20499
+ }
20500
+ clearCache() {}
20501
+ async repaint() {}
20502
+ draw(drawConfiguration, features, roiFeatures) {
20503
+ console.log('Viewport - draw(drawConfiguration, features, roiFeatures)');
20504
+ }
20505
+ checkContentHeight(features) {
20506
+ let track = this.trackView.track;
20507
+ features = features || this.cachedFeatures;
20508
+ if ("FILL" === track.displayMode) {
20509
+ this.setContentHeight(this.$viewport.height());
20510
+ } else if (typeof track.computePixelHeight === 'function') {
20511
+ if (features && features.length > 0) {
20512
+ let requiredContentHeight = track.computePixelHeight(features);
20513
+ //let currentContentHeight = this.$content.height()
20514
+ let currentContentHeight = this.contentHeight;
20515
+ if (requiredContentHeight !== currentContentHeight) {
20516
+ this.setContentHeight(requiredContentHeight);
20517
+ }
20518
+ }
20519
+ }
20520
+ }
20521
+ getContentHeight() {
20522
+ //return this.$content.height()
20523
+ return this.contentHeight;
19426
20524
  }
19427
20525
  setContentHeight(contentHeight) {
19428
20526
  this.contentHeight = contentHeight;
@@ -20096,1372 +21194,732 @@
20096
21194
  y: y
20097
21195
  }));
20098
21196
  }
20099
- /**
20100
- * rotates the current element
20101
- */
20102
- rotate(angle) {
20103
- var degrees = angle * 180 / Math.PI;
20104
- this.__addTransform(format("rotate({angle},{cx},{cy})", {
20105
- angle: degrees,
20106
- cx: 0,
20107
- cy: 0
20108
- }));
20109
- }
20110
- /**
20111
- * translates the current element
20112
- */
20113
- translate(x, y) {
20114
- this.__addTransform(format("translate({x},{y})", {
20115
- x: x,
20116
- y: y
20117
- }));
20118
- }
20119
- /**
20120
- * applies a transform to the current element
20121
- */
20122
- transform(a, b, c, d, e, f) {
20123
- this.__addTransform(format("matrix({a},{b},{c},{d},{e},{f})", {
20124
- a: a,
20125
- b: b,
20126
- c: c,
20127
- d: d,
20128
- e: e,
20129
- f: f
20130
- }));
20131
- }
20132
- /**
20133
- * Create a new Path Element
20134
- */
20135
- beginPath() {
20136
- var path, parent;
20137
-
20138
- // Note that there is only one current default path, it is not part of the drawing state.
20139
- // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path
20140
- this.__currentDefaultPath = "";
20141
- this.__currentPosition = {};
20142
- path = this.__createElement("path", {}, true);
20143
- parent = this.__closestGroupOrSvg();
20144
- parent.appendChild(path);
20145
- this.__currentElement = path;
20146
- }
20147
- /**
20148
- * Helper function to apply currentDefaultPath to current path element
20149
- * @private
20150
- */
20151
- __applyCurrentDefaultPath() {
20152
- var currentElement = this.__currentElement;
20153
- if (currentElement.nodeName === "path") {
20154
- currentElement.setAttribute("d", this.__currentDefaultPath);
20155
- } else {
20156
- console.error("Attempted to apply path command to node", currentElement.nodeName);
20157
- }
20158
- }
20159
- /**
20160
- * Helper function to add path command
20161
- * @private
20162
- */
20163
- __addPathCommand(command) {
20164
- this.__currentDefaultPath += " ";
20165
- this.__currentDefaultPath += command;
20166
- }
20167
- /**
20168
- * Adds the move command to the current path element,
20169
- * if the currentPathElement is not empty create a new path element
20170
- */
20171
- moveTo(x, y) {
20172
- if (this.__currentElement.nodeName !== "path") {
20173
- this.beginPath();
20174
- }
20175
-
20176
- // creates a new subpath with the given point
20177
- this.__currentPosition = {
20178
- x: x,
20179
- y: y
20180
- };
20181
- this.__addPathCommand(format("M {x} {y}", {
20182
- x: x,
20183
- y: y
20184
- }));
20185
- }
20186
- /**
20187
- * Closes the current path
20188
- */
20189
- closePath() {
20190
- if (this.__currentDefaultPath) {
20191
- this.__addPathCommand("Z");
20192
- }
20193
- }
20194
- /**
20195
- * Adds a line to command
20196
- */
20197
- lineTo(x, y) {
20198
- this.__currentPosition = {
20199
- x: x,
20200
- y: y
20201
- };
20202
- if (this.__currentDefaultPath && this.__currentDefaultPath.indexOf('M') > -1) {
20203
- this.__addPathCommand(format("L {x} {y}", {
20204
- x: x,
20205
- y: y
20206
- }));
20207
- } else {
20208
- this.__addPathCommand(format("M {x} {y}", {
20209
- x: x,
20210
- y: y
20211
- }));
20212
- }
20213
- }
20214
- /**
20215
- * Add a bezier command
20216
- */
20217
- bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
20218
- this.__currentPosition = {
20219
- x: x,
20220
- y: y
20221
- };
20222
- this.__addPathCommand(format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", {
20223
- cp1x: cp1x,
20224
- cp1y: cp1y,
20225
- cp2x: cp2x,
20226
- cp2y: cp2y,
20227
- x: x,
20228
- y: y
20229
- }));
20230
- }
20231
- /**
20232
- * Adds a quadratic curve to command
20233
- */
20234
- quadraticCurveTo(cpx, cpy, x, y) {
20235
- this.__currentPosition = {
20236
- x: x,
20237
- y: y
20238
- };
20239
- this.__addPathCommand(format("Q {cpx} {cpy} {x} {y}", {
20240
- cpx: cpx,
20241
- cpy: cpy,
20242
- x: x,
20243
- y: y
20244
- }));
20245
- }
20246
- /**
20247
- * Adds the arcTo to the current path
20248
- *
20249
- * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto
20250
- */
20251
- arcTo(x1, y1, x2, y2, radius) {
20252
- // Let the point (x0, y0) be the last point in the subpath.
20253
- var x0 = this.__currentPosition && this.__currentPosition.x;
20254
- var y0 = this.__currentPosition && this.__currentPosition.y;
20255
-
20256
- // First ensure there is a subpath for (x1, y1).
20257
- if (typeof x0 == "undefined" || typeof y0 == "undefined") {
20258
- return;
20259
- }
20260
-
20261
- // Negative values for radius must cause the implementation to throw an IndexSizeError exception.
20262
- if (radius < 0) {
20263
- throw new Error("IndexSizeError: The radius provided (" + radius + ") is negative.");
20264
- }
20265
-
20266
- // If the point (x0, y0) is equal to the point (x1, y1),
20267
- // or if the point (x1, y1) is equal to the point (x2, y2),
20268
- // or if the radius radius is zero,
20269
- // then the method must add the point (x1, y1) to the subpath,
20270
- // and connect that point to the previous point (x0, y0) by a straight line.
20271
- if (x0 === x1 && y0 === y1 || x1 === x2 && y1 === y2 || radius === 0) {
20272
- this.lineTo(x1, y1);
20273
- return;
20274
- }
20275
-
20276
- // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line,
20277
- // then the method must add the point (x1, y1) to the subpath,
20278
- // and connect that point to the previous point (x0, y0) by a straight line.
20279
- var unit_vec_p1_p0 = normalize$1([x0 - x1, y0 - y1]);
20280
- var unit_vec_p1_p2 = normalize$1([x2 - x1, y2 - y1]);
20281
- if (unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === unit_vec_p1_p0[1] * unit_vec_p1_p2[0]) {
20282
- this.lineTo(x1, y1);
20283
- return;
20284
- }
20285
-
20286
- // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius,
20287
- // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1),
20288
- // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2).
20289
- // The points at which this circle touches these two lines are called the start and end tangent points respectively.
20290
-
20291
- // note that both vectors are unit vectors, so the length is 1
20292
- var cos = unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1];
20293
- var theta = Math.acos(Math.abs(cos));
20294
-
20295
- // Calculate origin
20296
- var unit_vec_p1_origin = normalize$1([unit_vec_p1_p0[0] + unit_vec_p1_p2[0], unit_vec_p1_p0[1] + unit_vec_p1_p2[1]]);
20297
- var len_p1_origin = radius / Math.sin(theta / 2);
20298
- var x = x1 + len_p1_origin * unit_vec_p1_origin[0];
20299
- var y = y1 + len_p1_origin * unit_vec_p1_origin[1];
20300
-
20301
- // Calculate start angle and end angle
20302
- // rotate 90deg clockwise (note that y axis points to its down)
20303
- var unit_vec_origin_start_tangent = [-unit_vec_p1_p0[1], unit_vec_p1_p0[0]];
20304
- // rotate 90deg counter clockwise (note that y axis points to its down)
20305
- var unit_vec_origin_end_tangent = [unit_vec_p1_p2[1], -unit_vec_p1_p2[0]];
20306
- var getAngle = function (vector) {
20307
- // get angle (clockwise) between vector and (1, 0)
20308
- var x = vector[0];
20309
- var y = vector[1];
20310
- if (y >= 0) {
20311
- // note that y axis points to its down
20312
- return Math.acos(x);
20313
- } else {
20314
- return -Math.acos(x);
20315
- }
20316
- };
20317
- var startAngle = getAngle(unit_vec_origin_start_tangent);
20318
- var endAngle = getAngle(unit_vec_origin_end_tangent);
20319
-
20320
- // Connect the point (x0, y0) to the start tangent point by a straight line
20321
- this.lineTo(x + unit_vec_origin_start_tangent[0] * radius, y + unit_vec_origin_start_tangent[1] * radius);
20322
-
20323
- // Connect the start tangent point to the end tangent point by arc
20324
- // and adding the end tangent point to the subpath.
20325
- this.arc(x, y, radius, startAngle, endAngle);
20326
- }
20327
- /**
20328
- * Sets the stroke property on the current element
20329
- */
20330
- stroke() {
20331
- if (this.__currentElement.nodeName === "path") {
20332
- this.__currentElement.setAttribute("paint-order", "fill stroke markers");
20333
- }
20334
- this.__applyCurrentDefaultPath();
20335
- this.__applyStyleToCurrentElement("stroke");
20336
- }
20337
- /**
20338
- * Sets fill properties on the current element
20339
- */
20340
- fill() {
20341
- if (this.__currentElement.nodeName === "path") {
20342
- this.__currentElement.setAttribute("paint-order", "stroke fill markers");
20343
- }
20344
- this.__applyCurrentDefaultPath();
20345
- this.__applyStyleToCurrentElement("fill");
20346
- }
20347
- /**
20348
- * Adds a rectangle to the path.
20349
- */
20350
- rect(x, y, width, height) {
20351
- if (this.__currentElement.nodeName !== "path") {
20352
- this.beginPath();
20353
- }
20354
- this.moveTo(x, y);
20355
- this.lineTo(x + width, y);
20356
- this.lineTo(x + width, y + height);
20357
- this.lineTo(x, y + height);
20358
- this.lineTo(x, y);
20359
- this.closePath();
20360
- }
20361
- /**
20362
- * adds a rectangle element
20363
- */
20364
- fillRect(x, y, width, height) {
20365
- if (height < 0) {
20366
- y += height;
20367
- height = -height;
20368
- }
20369
- if (width < 0) {
20370
- x += width;
20371
- width = -width;
20372
- }
20373
- // See if rect intersects current viewbox
20374
- var r2 = {
20375
- x: x,
20376
- y: y,
20377
- width: width,
20378
- height: height
20379
- };
20380
- if (this.viewbox) {
20381
- if (!intersectRect(this.viewbox, r2)) {
20382
- return;
20383
- }
20384
- }
20385
- var rect, parent;
20386
- rect = this.__createElement("rect", r2, true);
20387
- parent = this.__closestGroupOrSvg();
20388
- parent.appendChild(rect);
20389
- this.__currentElement = rect;
20390
- this.__applyStyleToCurrentElement("fill");
20391
- }
20392
- /**
20393
- * Draws a rectangle with no fill
20394
- * @param x
20395
- * @param y
20396
- * @param width
20397
- * @param height
20398
- */
20399
- strokeRect(x, y, width, height) {
20400
- var rect, parent;
20401
- rect = this.__createElement("rect", {
20402
- x: x,
20403
- y: y,
20404
- width: width,
20405
- height: height
20406
- }, true);
20407
- parent = this.__closestGroupOrSvg();
20408
- parent.appendChild(rect);
20409
- this.__currentElement = rect;
20410
- this.__applyStyleToCurrentElement("stroke");
20411
- }
20412
- // stroke ellipse
20413
- strokeEllipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW) {
20414
- this.__ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW, 'stroke');
20415
- }
20416
-
20417
- // fill ellipse
20418
- fillEllipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW) {
20419
- this.__ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW, 'fill');
20420
- }
20421
-
20422
- // ellipse helper
20423
- __ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW, style) {
20424
- const config = {
20425
- cx,
20426
- cy,
20427
- rx,
20428
- ry
20429
- };
20430
- const element = this.__createElement('ellipse', config, true);
20431
- const parent = this.__closestGroupOrSvg();
20432
- parent.appendChild(element);
20433
- this.__currentElement = element;
20434
- this.__applyStyleToCurrentElement(style);
20435
- }
20436
-
20437
- /**
20438
- * Clear entire canvas:
20439
- * 1. save current transforms
20440
- * 2. remove all the childNodes of the root g element
20441
- */
20442
- __clearCanvas() {
20443
- var current = this.__closestGroupOrSvg(),
20444
- transform = current.getAttribute("transform");
20445
- var rootGroup = this.__root.childNodes[1];
20446
- var childNodes = rootGroup.childNodes;
20447
- for (var i = childNodes.length - 1; i >= 0; i--) {
20448
- if (childNodes[i]) {
20449
- rootGroup.removeChild(childNodes[i]);
20450
- }
20451
- }
20452
- this.__currentElement = rootGroup;
20453
- //reset __groupStack as all the child group nodes are all removed.
20454
- this.__groupStack = [];
20455
- if (transform) {
20456
- this.__addTransform(transform);
20457
- }
20458
- }
20459
- /**
20460
- * "Clears" a canvas by just drawing a white rectangle in the current group.
20461
- */
20462
- clearRect(x, y, width, height) {
20463
- //clear entire canvas
20464
- if (x === 0 && y === 0 && width === this.width && height === this.height) {
20465
- this.__clearCanvas();
20466
- return;
20467
- }
20468
- var rect,
20469
- parent = this.__closestGroupOrSvg();
20470
- rect = this.__createElement("rect", {
20471
- x: x,
20472
- y: y,
20473
- width: width,
20474
- height: height,
20475
- fill: "#FFFFFF"
20476
- }, true);
20477
- parent.appendChild(rect);
21197
+ /**
21198
+ * rotates the current element
21199
+ */
21200
+ rotate(angle) {
21201
+ var degrees = angle * 180 / Math.PI;
21202
+ this.__addTransform(format("rotate({angle},{cx},{cy})", {
21203
+ angle: degrees,
21204
+ cx: 0,
21205
+ cy: 0
21206
+ }));
20478
21207
  }
20479
21208
  /**
20480
- * Adds a linear gradient to a defs tag.
20481
- * Returns a canvas gradient object that has a reference to it's parent def
21209
+ * translates the current element
20482
21210
  */
20483
- createLinearGradient(x1, y1, x2, y2) {
20484
- var grad = this.__createElement("linearGradient", {
20485
- id: randomString(this.__ids),
20486
- x1: x1 + "px",
20487
- x2: x2 + "px",
20488
- y1: y1 + "px",
20489
- y2: y2 + "px",
20490
- "gradientUnits": "userSpaceOnUse"
20491
- }, false);
20492
- this.__defs.appendChild(grad);
20493
- return new CanvasGradient(grad, this);
21211
+ translate(x, y) {
21212
+ this.__addTransform(format("translate({x},{y})", {
21213
+ x: x,
21214
+ y: y
21215
+ }));
20494
21216
  }
20495
21217
  /**
20496
- * Adds a radial gradient to a defs tag.
20497
- * Returns a canvas gradient object that has a reference to it's parent def
21218
+ * applies a transform to the current element
20498
21219
  */
20499
- createRadialGradient(x0, y0, r0, x1, y1, r1) {
20500
- var grad = this.__createElement("radialGradient", {
20501
- id: randomString(this.__ids),
20502
- cx: x1 + "px",
20503
- cy: y1 + "px",
20504
- r: r1 + "px",
20505
- fx: x0 + "px",
20506
- fy: y0 + "px",
20507
- "gradientUnits": "userSpaceOnUse"
20508
- }, false);
20509
- this.__defs.appendChild(grad);
20510
- return new CanvasGradient(grad, this);
21220
+ transform(a, b, c, d, e, f) {
21221
+ this.__addTransform(format("matrix({a},{b},{c},{d},{e},{f})", {
21222
+ a: a,
21223
+ b: b,
21224
+ c: c,
21225
+ d: d,
21226
+ e: e,
21227
+ f: f
21228
+ }));
20511
21229
  }
20512
21230
  /**
20513
- * Parses the font string and returns svg mapping
20514
- * @private
21231
+ * Create a new Path Element
20515
21232
  */
20516
- __parseFont() {
20517
- var regex = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\'\"\sa-z0-9]+?)\s*$/i;
20518
- var fontPart = regex.exec(this.font);
20519
- var data = {
20520
- style: fontPart[1] || 'normal',
20521
- size: fontPart[4] || '10px',
20522
- family: fontPart[6] || 'sans-serif',
20523
- weight: fontPart[3] || 'normal',
20524
- decoration: fontPart[2] || 'normal',
20525
- href: null
20526
- };
20527
-
20528
- //canvas doesn't support underline natively, but we can pass this attribute
20529
- if (this.__fontUnderline === "underline") {
20530
- data.decoration = "underline";
20531
- }
21233
+ beginPath() {
21234
+ var path, parent;
20532
21235
 
20533
- //canvas also doesn't support linking, but we can pass this as well
20534
- if (this.__fontHref) {
20535
- data.href = this.__fontHref;
20536
- }
20537
- return data;
21236
+ // Note that there is only one current default path, it is not part of the drawing state.
21237
+ // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path
21238
+ this.__currentDefaultPath = "";
21239
+ this.__currentPosition = {};
21240
+ path = this.__createElement("path", {}, true);
21241
+ parent = this.__closestGroupOrSvg();
21242
+ parent.appendChild(path);
21243
+ this.__currentElement = path;
20538
21244
  }
20539
21245
  /**
20540
- * Helper to link text fragments
20541
- * @param font
20542
- * @param element
20543
- * @return {*}
21246
+ * Helper function to apply currentDefaultPath to current path element
20544
21247
  * @private
20545
21248
  */
20546
- __wrapTextLink(font, element) {
20547
- if (font.href) {
20548
- var a = this.__createElement("a");
20549
- a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", font.href);
20550
- a.appendChild(element);
20551
- return a;
21249
+ __applyCurrentDefaultPath() {
21250
+ var currentElement = this.__currentElement;
21251
+ if (currentElement.nodeName === "path") {
21252
+ currentElement.setAttribute("d", this.__currentDefaultPath);
21253
+ } else {
21254
+ console.error("Attempted to apply path command to node", currentElement.nodeName);
20552
21255
  }
20553
- return element;
20554
21256
  }
20555
21257
  /**
20556
- * Fills or strokes text
20557
- * @param text
20558
- * @param x
20559
- * @param y
20560
- * @param action - stroke or fill
21258
+ * Helper function to add path command
20561
21259
  * @private
20562
21260
  */
20563
- __applyText(text, x, y, action) {
20564
- var font = this.__parseFont(),
20565
- parent = this.__closestGroupOrSvg(),
20566
- textElement = this.__createElement("text", {
20567
- "font-family": font.family,
20568
- "font-size": font.size,
20569
- "font-style": font.style,
20570
- "font-weight": font.weight,
20571
- "text-decoration": font.decoration,
20572
- "x": x,
20573
- "y": y,
20574
- "text-anchor": getTextAnchor(this.textAlign),
20575
- "dominant-baseline": getDominantBaseline(this.textBaseline)
20576
- }, true);
20577
- textElement.appendChild(this.__document.createTextNode(text));
20578
- this.__currentElement = textElement;
20579
- this.__applyStyleToCurrentElement(action);
20580
- parent.appendChild(this.__wrapTextLink(font, textElement));
21261
+ __addPathCommand(command) {
21262
+ this.__currentDefaultPath += " ";
21263
+ this.__currentDefaultPath += command;
20581
21264
  }
20582
21265
  /**
20583
- * Creates a text element
20584
- * @param text
20585
- * @param x
20586
- * @param y
21266
+ * Adds the move command to the current path element,
21267
+ * if the currentPathElement is not empty create a new path element
20587
21268
  */
20588
- fillText(text, x, y) {
20589
- this.__applyText(text, x, y, "fill");
21269
+ moveTo(x, y) {
21270
+ if (this.__currentElement.nodeName !== "path") {
21271
+ this.beginPath();
21272
+ }
21273
+
21274
+ // creates a new subpath with the given point
21275
+ this.__currentPosition = {
21276
+ x: x,
21277
+ y: y
21278
+ };
21279
+ this.__addPathCommand(format("M {x} {y}", {
21280
+ x: x,
21281
+ y: y
21282
+ }));
20590
21283
  }
20591
21284
  /**
20592
- * Strokes text
20593
- * @param text
20594
- * @param x
20595
- * @param y
21285
+ * Closes the current path
20596
21286
  */
20597
- strokeText(text, x, y) {
20598
- this.__applyText(text, x, y, "stroke");
21287
+ closePath() {
21288
+ if (this.__currentDefaultPath) {
21289
+ this.__addPathCommand("Z");
21290
+ }
20599
21291
  }
20600
21292
  /**
20601
- * No need to implement this for svg.
20602
- * @param text
20603
- * @return {TextMetrics}
21293
+ * Adds a line to command
20604
21294
  */
20605
- measureText(text) {
20606
- this.__ctx.font = this.font;
20607
- return this.__ctx.measureText(text);
21295
+ lineTo(x, y) {
21296
+ this.__currentPosition = {
21297
+ x: x,
21298
+ y: y
21299
+ };
21300
+ if (this.__currentDefaultPath && this.__currentDefaultPath.indexOf('M') > -1) {
21301
+ this.__addPathCommand(format("L {x} {y}", {
21302
+ x: x,
21303
+ y: y
21304
+ }));
21305
+ } else {
21306
+ this.__addPathCommand(format("M {x} {y}", {
21307
+ x: x,
21308
+ y: y
21309
+ }));
21310
+ }
20608
21311
  }
20609
21312
  /**
20610
- * Arc command!
21313
+ * Add a bezier command
20611
21314
  */
20612
- arc(x, y, radius, startAngle, endAngle, counterClockwise) {
20613
- // in canvas no circle is drawn if no angle is provided.
20614
- if (startAngle === endAngle) {
20615
- return;
20616
- }
20617
- startAngle = startAngle % (2 * Math.PI);
20618
- endAngle = endAngle % (2 * Math.PI);
20619
- if (startAngle === endAngle) {
20620
- //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle)
20621
- endAngle = (endAngle + 2 * Math.PI - 0.001 * (counterClockwise ? -1 : 1)) % (2 * Math.PI);
20622
- }
20623
- var endX = x + radius * Math.cos(endAngle),
20624
- endY = y + radius * Math.sin(endAngle),
20625
- startX = x + radius * Math.cos(startAngle),
20626
- startY = y + radius * Math.sin(startAngle),
20627
- sweepFlag = counterClockwise ? 0 : 1,
20628
- largeArcFlag = 0,
20629
- diff = endAngle - startAngle;
20630
-
20631
- // https://github.com/gliffy/canvas2svg/issues/4
20632
- if (diff < 0) {
20633
- diff += 2 * Math.PI;
20634
- }
20635
- if (counterClockwise) {
20636
- largeArcFlag = diff > Math.PI ? 0 : 1;
20637
- } else {
20638
- largeArcFlag = diff > Math.PI ? 1 : 0;
20639
- }
20640
- this.lineTo(startX, startY);
20641
- this.__addPathCommand(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", {
20642
- rx: radius,
20643
- ry: radius,
20644
- xAxisRotation: 0,
20645
- largeArcFlag: largeArcFlag,
20646
- sweepFlag: sweepFlag,
20647
- endX: endX,
20648
- endY: endY
20649
- }));
21315
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
20650
21316
  this.__currentPosition = {
20651
- x: endX,
20652
- y: endY
21317
+ x: x,
21318
+ y: y
20653
21319
  };
21320
+ this.__addPathCommand(format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", {
21321
+ cp1x: cp1x,
21322
+ cp1y: cp1y,
21323
+ cp2x: cp2x,
21324
+ cp2y: cp2y,
21325
+ x: x,
21326
+ y: y
21327
+ }));
20654
21328
  }
20655
21329
  /**
20656
- * The ellipse() method creates an elliptical arc centered at (x, y) with the radii radiusX and radiusY. The path
20657
- * starts at startAngle and ends at endAngle, and travels in the direction given by counterclockwise (defaulting to clockwise).
20658
- */
20659
- // ellipse (x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
20660
- // // TODO -- implement
20661
- // }
20662
-
20663
- /**
20664
- * Generates a ClipPath from the clip command.
21330
+ * Adds a quadratic curve to command
20665
21331
  */
20666
- clip() {
20667
- var group = this.__closestGroupOrSvg(),
20668
- clipPath = this.__createElement("clipPath"),
20669
- id = randomString(this.__ids),
20670
- newGroup = this.__createElement("g");
20671
- this.__applyCurrentDefaultPath();
20672
- group.removeChild(this.__currentElement);
20673
- clipPath.setAttribute("id", id);
20674
- clipPath.appendChild(this.__currentElement);
20675
- this.__defs.appendChild(clipPath);
20676
-
20677
- //set the clip path to this group
20678
- group.setAttribute("clip-path", format("url(#{id})", {
20679
- id: id
21332
+ quadraticCurveTo(cpx, cpy, x, y) {
21333
+ this.__currentPosition = {
21334
+ x: x,
21335
+ y: y
21336
+ };
21337
+ this.__addPathCommand(format("Q {cpx} {cpy} {x} {y}", {
21338
+ cpx: cpx,
21339
+ cpy: cpy,
21340
+ x: x,
21341
+ y: y
20680
21342
  }));
20681
-
20682
- //clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations
20683
- // to this path
20684
- group.appendChild(newGroup);
20685
- this.__currentElement = newGroup;
20686
21343
  }
20687
21344
  /**
20688
- * Draws a canvas, image or mock context to this canvas.
20689
- * Note that all svg dom manipulation uses node.childNodes rather than node.children for IE support.
20690
- * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage
21345
+ * Adds the arcTo to the current path
21346
+ *
21347
+ * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto
20691
21348
  */
20692
- drawImage() {
20693
- //convert arguments to a real array
20694
- var args = Array.prototype.slice.call(arguments),
20695
- image = args[0],
20696
- dx,
20697
- dy,
20698
- dw,
20699
- dh,
20700
- sx = 0,
20701
- sy = 0,
20702
- sw,
20703
- sh,
20704
- parent,
20705
- svg,
20706
- defs,
20707
- group,
20708
- svgImage,
20709
- canvas,
20710
- context,
20711
- id;
20712
- if (args.length === 3) {
20713
- dx = args[1];
20714
- dy = args[2];
20715
- sw = image.width;
20716
- sh = image.height;
20717
- dw = sw;
20718
- dh = sh;
20719
- } else if (args.length === 5) {
20720
- dx = args[1];
20721
- dy = args[2];
20722
- dw = args[3];
20723
- dh = args[4];
20724
- sw = image.width;
20725
- sh = image.height;
20726
- } else if (args.length === 9) {
20727
- sx = args[1];
20728
- sy = args[2];
20729
- sw = args[3];
20730
- sh = args[4];
20731
- dx = args[5];
20732
- dy = args[6];
20733
- dw = args[7];
20734
- dh = args[8];
20735
- } else {
20736
- throw new Error("Invalid number of arguments passed to drawImage: " + arguments.length);
21349
+ arcTo(x1, y1, x2, y2, radius) {
21350
+ // Let the point (x0, y0) be the last point in the subpath.
21351
+ var x0 = this.__currentPosition && this.__currentPosition.x;
21352
+ var y0 = this.__currentPosition && this.__currentPosition.y;
21353
+
21354
+ // First ensure there is a subpath for (x1, y1).
21355
+ if (typeof x0 == "undefined" || typeof y0 == "undefined") {
21356
+ return;
20737
21357
  }
20738
- parent = this.__closestGroupOrSvg();
20739
- this.__currentElement;
20740
- var translateDirective = "translate(" + dx + ", " + dy + ")";
20741
- if (image instanceof ctx) {
20742
- //canvas2svg mock canvas context. In the future we may want to clone nodes instead.
20743
- //also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context.
20744
- svg = image.getSvg().cloneNode(true);
20745
- if (svg.childNodes && svg.childNodes.length > 1) {
20746
- defs = svg.childNodes[0];
20747
- while (defs.childNodes.length) {
20748
- id = defs.childNodes[0].getAttribute("id");
20749
- this.__ids[id] = id;
20750
- this.__defs.appendChild(defs.childNodes[0]);
20751
- }
20752
- group = svg.childNodes[1];
20753
- if (group) {
20754
- //save original transform
20755
- var originTransform = group.getAttribute("transform");
20756
- var transformDirective;
20757
- if (originTransform) {
20758
- transformDirective = originTransform + " " + translateDirective;
20759
- } else {
20760
- transformDirective = translateDirective;
20761
- }
20762
- group.setAttribute("transform", transformDirective);
20763
- parent.appendChild(group);
20764
- }
20765
- }
20766
- } else if (image.nodeName === "CANVAS" || image.nodeName === "IMG") {
20767
- //canvas or image
20768
- svgImage = this.__createElement("image");
20769
- svgImage.setAttribute("width", dw);
20770
- svgImage.setAttribute("height", dh);
20771
- svgImage.setAttribute("preserveAspectRatio", "none");
20772
- if (sx || sy || sw !== image.width || sh !== image.height) {
20773
- //crop the image using a temporary canvas
20774
- canvas = this.__document.createElement("canvas");
20775
- canvas.width = dw;
20776
- canvas.height = dh;
20777
- context = canvas.getContext("2d");
20778
- context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh);
20779
- image = canvas;
20780
- }
20781
- svgImage.setAttribute("transform", translateDirective);
20782
- svgImage.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src"));
20783
- parent.appendChild(svgImage);
21358
+
21359
+ // Negative values for radius must cause the implementation to throw an IndexSizeError exception.
21360
+ if (radius < 0) {
21361
+ throw new Error("IndexSizeError: The radius provided (" + radius + ") is negative.");
20784
21362
  }
20785
- }
20786
- /**
20787
- * Generates a pattern tag
20788
- */
20789
- createPattern(image, repetition) {
20790
- let pattern = this.__document.__createElement("pattern");
20791
- let id = randomString(this.__ids);
20792
- let img;
20793
- pattern.setAttribute("id", id);
20794
- pattern.setAttribute("width", image.width);
20795
- pattern.setAttribute("height", image.height);
20796
- if (image.nodeName === "CANVAS" || image.nodeName === "IMG") {
20797
- img = this.__createElement("image");
20798
- img.setAttribute("width", image.width);
20799
- img.setAttribute("height", image.height);
20800
- img.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src"));
20801
- pattern.appendChild(img);
20802
- this.__defs.appendChild(pattern);
20803
- } else if (image instanceof ctx) {
20804
- pattern.appendChild(image.__root.childNodes[1]);
20805
- this.__defs.appendChild(pattern);
21363
+
21364
+ // If the point (x0, y0) is equal to the point (x1, y1),
21365
+ // or if the point (x1, y1) is equal to the point (x2, y2),
21366
+ // or if the radius radius is zero,
21367
+ // then the method must add the point (x1, y1) to the subpath,
21368
+ // and connect that point to the previous point (x0, y0) by a straight line.
21369
+ if (x0 === x1 && y0 === y1 || x1 === x2 && y1 === y2 || radius === 0) {
21370
+ this.lineTo(x1, y1);
21371
+ return;
20806
21372
  }
20807
- return new CanvasPattern(pattern, this);
20808
- }
20809
- setLineDash(dashArray) {
20810
- if (dashArray && dashArray.length > 0) {
20811
- this.lineDash = dashArray.join(",");
20812
- } else {
20813
- this.lineDash = null;
21373
+
21374
+ // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line,
21375
+ // then the method must add the point (x1, y1) to the subpath,
21376
+ // and connect that point to the previous point (x0, y0) by a straight line.
21377
+ var unit_vec_p1_p0 = normalize$1([x0 - x1, y0 - y1]);
21378
+ var unit_vec_p1_p2 = normalize$1([x2 - x1, y2 - y1]);
21379
+ if (unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === unit_vec_p1_p0[1] * unit_vec_p1_p2[0]) {
21380
+ this.lineTo(x1, y1);
21381
+ return;
20814
21382
  }
20815
- }
20816
- /**
20817
- * Not yet implemented
20818
- */
20819
- drawFocusRing() {}
20820
- createImageData() {}
20821
- getImageData() {}
20822
- putImageData() {}
20823
- globalCompositeOperation() {}
20824
- setTransform() {}
20825
- }
20826
21383
 
20827
- class Chromosome {
20828
- constructor(name, order, bpLength) {
20829
- this.name = name;
20830
- this.order = order;
20831
- this.bpLength = bpLength;
20832
- }
20833
- }
21384
+ // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius,
21385
+ // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1),
21386
+ // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2).
21387
+ // The points at which this circle touches these two lines are called the start and end tangent points respectively.
20834
21388
 
20835
- /*
20836
- * The MIT License (MIT)
20837
- *
20838
- * Copyright (c) 2014 Broad Institute
20839
- *
20840
- * Permission is hereby granted, free of charge, to any person obtaining a copy
20841
- * of this software and associated documentation files (the "Software"), to deal
20842
- * in the Software without restriction, including without limitation the rights
20843
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20844
- * copies of the Software, and to permit persons to whom the Software is
20845
- * furnished to do so, subject to the following conditions:
20846
- *
20847
- * The above copyright notice and this permission notice shall be included in
20848
- * all copies or substantial portions of the Software.
20849
- *
20850
- *
20851
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20852
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20853
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20854
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20855
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20856
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20857
- * THE SOFTWARE.
20858
- */
20859
- const splitLines$4 = splitLines$5;
20860
- const reservedProperties$1 = new Set(['fastaURL', 'indexURL', 'cytobandURL', 'indexed']);
20861
- class NonIndexedFasta {
20862
- constructor(reference) {
20863
- this.fastaURL = reference.fastaURL;
20864
- this.withCredentials = reference.withCredentials;
20865
- this.chromosomeNames = [];
20866
- this.chromosomes = {};
20867
- this.sequences = new Map();
21389
+ // note that both vectors are unit vectors, so the length is 1
21390
+ var cos = unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1];
21391
+ var theta = Math.acos(Math.abs(cos));
20868
21392
 
20869
- // Build a track-like config object from the referenceObject
20870
- const config = {};
20871
- for (let key in reference) {
20872
- if (reference.hasOwnProperty(key) && !reservedProperties$1.has(key)) {
20873
- config[key] = reference[key];
21393
+ // Calculate origin
21394
+ var unit_vec_p1_origin = normalize$1([unit_vec_p1_p0[0] + unit_vec_p1_p2[0], unit_vec_p1_p0[1] + unit_vec_p1_p2[1]]);
21395
+ var len_p1_origin = radius / Math.sin(theta / 2);
21396
+ var x = x1 + len_p1_origin * unit_vec_p1_origin[0];
21397
+ var y = y1 + len_p1_origin * unit_vec_p1_origin[1];
21398
+
21399
+ // Calculate start angle and end angle
21400
+ // rotate 90deg clockwise (note that y axis points to its down)
21401
+ var unit_vec_origin_start_tangent = [-unit_vec_p1_p0[1], unit_vec_p1_p0[0]];
21402
+ // rotate 90deg counter clockwise (note that y axis points to its down)
21403
+ var unit_vec_origin_end_tangent = [unit_vec_p1_p2[1], -unit_vec_p1_p2[0]];
21404
+ var getAngle = function (vector) {
21405
+ // get angle (clockwise) between vector and (1, 0)
21406
+ var x = vector[0];
21407
+ var y = vector[1];
21408
+ if (y >= 0) {
21409
+ // note that y axis points to its down
21410
+ return Math.acos(x);
21411
+ } else {
21412
+ return -Math.acos(x);
20874
21413
  }
20875
- }
20876
- this.config = config;
21414
+ };
21415
+ var startAngle = getAngle(unit_vec_origin_start_tangent);
21416
+ var endAngle = getAngle(unit_vec_origin_end_tangent);
21417
+
21418
+ // Connect the point (x0, y0) to the start tangent point by a straight line
21419
+ this.lineTo(x + unit_vec_origin_start_tangent[0] * radius, y + unit_vec_origin_start_tangent[1] * radius);
21420
+
21421
+ // Connect the start tangent point to the end tangent point by arc
21422
+ // and adding the end tangent point to the subpath.
21423
+ this.arc(x, y, radius, startAngle, endAngle);
20877
21424
  }
20878
- async init() {
20879
- return this.loadAll();
21425
+ /**
21426
+ * Sets the stroke property on the current element
21427
+ */
21428
+ stroke() {
21429
+ if (this.__currentElement.nodeName === "path") {
21430
+ this.__currentElement.setAttribute("paint-order", "fill stroke markers");
21431
+ }
21432
+ this.__applyCurrentDefaultPath();
21433
+ this.__applyStyleToCurrentElement("stroke");
20880
21434
  }
20881
- async getSequence(chr, start, end) {
20882
- if (!this.sequences.has(chr)) {
20883
- return undefined;
21435
+ /**
21436
+ * Sets fill properties on the current element
21437
+ */
21438
+ fill() {
21439
+ if (this.__currentElement.nodeName === "path") {
21440
+ this.__currentElement.setAttribute("paint-order", "stroke fill markers");
20884
21441
  }
20885
- let seqSlice = this.sequences.get(chr).find(ss => ss.contains(start, end));
20886
- if (!seqSlice) {
20887
- seqSlice = this.sequences.get(chr).find(ss => ss.overlaps(start, end));
20888
- if (!seqSlice) {
20889
- return undefined;
20890
- }
21442
+ this.__applyCurrentDefaultPath();
21443
+ this.__applyStyleToCurrentElement("fill");
21444
+ }
21445
+ /**
21446
+ * Adds a rectangle to the path.
21447
+ */
21448
+ rect(x, y, width, height) {
21449
+ if (this.__currentElement.nodeName !== "path") {
21450
+ this.beginPath();
20891
21451
  }
20892
- start -= seqSlice.offset;
20893
- end -= seqSlice.offset;
20894
- let prefix = "";
20895
- if (start < 0) {
20896
- for (let i = start; i < Math.min(end, 0); i++) {
20897
- prefix += "*";
20898
- }
21452
+ this.moveTo(x, y);
21453
+ this.lineTo(x + width, y);
21454
+ this.lineTo(x + width, y + height);
21455
+ this.lineTo(x, y + height);
21456
+ this.lineTo(x, y);
21457
+ this.closePath();
21458
+ }
21459
+ /**
21460
+ * adds a rectangle element
21461
+ */
21462
+ fillRect(x, y, width, height) {
21463
+ if (height < 0) {
21464
+ y += height;
21465
+ height = -height;
20899
21466
  }
20900
- if (end <= 0) {
20901
- return Promise.resolve(prefix);
21467
+ if (width < 0) {
21468
+ x += width;
21469
+ width = -width;
20902
21470
  }
20903
- const seq = seqSlice.sequence;
20904
- const seqEnd = Math.min(end, seq.length);
20905
- return prefix + seq.substring(start, seqEnd);
20906
- }
20907
- async loadAll() {
20908
- let data;
20909
- if (isDataURL(this.fastaURL)) {
20910
- let bytes = decodeDataURI$1(this.fastaURL);
20911
- data = "";
20912
- for (let b of bytes) {
20913
- data += String.fromCharCode(b);
21471
+ // See if rect intersects current viewbox
21472
+ var r2 = {
21473
+ x: x,
21474
+ y: y,
21475
+ width: width,
21476
+ height: height
21477
+ };
21478
+ if (this.viewbox) {
21479
+ if (!intersectRect(this.viewbox, r2)) {
21480
+ return;
20914
21481
  }
20915
- } else {
20916
- data = await igvxhr.load(this.fastaURL, buildOptions(this.config));
20917
21482
  }
20918
- const chrNameSet = new Set();
20919
- const lines = splitLines$4(data);
20920
- const len = lines.length;
20921
- let lineNo = 0;
20922
- let order = 0;
20923
- let nextLine;
20924
- let current = {};
20925
- while (lineNo < len) {
20926
- nextLine = lines[lineNo++].trim();
20927
- if (nextLine.startsWith("#") || nextLine.length === 0) ; else if (nextLine.startsWith(">")) {
20928
- // Start the next sequence
20929
- if (current && current.seq) {
20930
- pushChromosome.call(this, current, order++);
20931
- }
20932
- const parts = nextLine.substr(1).split(/\s+/);
21483
+ var rect, parent;
21484
+ rect = this.__createElement("rect", r2, true);
21485
+ parent = this.__closestGroupOrSvg();
21486
+ parent.appendChild(rect);
21487
+ this.__currentElement = rect;
21488
+ this.__applyStyleToCurrentElement("fill");
21489
+ }
21490
+ /**
21491
+ * Draws a rectangle with no fill
21492
+ * @param x
21493
+ * @param y
21494
+ * @param width
21495
+ * @param height
21496
+ */
21497
+ strokeRect(x, y, width, height) {
21498
+ var rect, parent;
21499
+ rect = this.__createElement("rect", {
21500
+ x: x,
21501
+ y: y,
21502
+ width: width,
21503
+ height: height
21504
+ }, true);
21505
+ parent = this.__closestGroupOrSvg();
21506
+ parent.appendChild(rect);
21507
+ this.__currentElement = rect;
21508
+ this.__applyStyleToCurrentElement("stroke");
21509
+ }
21510
+ // stroke ellipse
21511
+ strokeEllipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW) {
21512
+ this.__ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW, 'stroke');
21513
+ }
20933
21514
 
20934
- // Check for samtools style locus string. This is not perfect, and could fail on weird sequence names
20935
- const nameParts = parts[0].split(':');
20936
- current.chr = nameParts[0];
20937
- current.seq = "";
20938
- current.offset = 0;
20939
- if (nameParts.length > 1 && nameParts[1].indexOf('-') > 0) {
20940
- const locusParts = nameParts[1].split('-');
20941
- if (locusParts.length === 2 && /^[0-9]+$/.test(locusParts[0]) && /^[0-9]+$/.test(locusParts[1])) ;
20942
- const from = Number.parseInt(locusParts[0]);
20943
- const to = Number.parseInt(locusParts[1]);
20944
- if (to > from) {
20945
- // TODO this should be an error
20946
- current.offset = from - 1;
20947
- }
21515
+ // fill ellipse
21516
+ fillEllipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW) {
21517
+ this.__ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW, 'fill');
21518
+ }
20948
21519
 
20949
- // Check for chromosome length token
20950
- if (parts.length > 1 && parts[1].startsWith("@len=")) {
20951
- try {
20952
- current.length = parseInt(parts[1].trim().substring(5));
20953
- } catch (e) {
20954
- current.length = undefined;
20955
- console.error(`Error parsing sequence length for ${nextLine}`);
20956
- }
20957
- } else {
20958
- current.length = undefined;
20959
- }
20960
- }
20961
- } else {
20962
- current.seq += nextLine;
20963
- }
20964
- // add last seq
20965
- if (current && current.seq) {
20966
- pushChromosome.call(this, current, order);
21520
+ // ellipse helper
21521
+ __ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, isCCW, style) {
21522
+ const config = {
21523
+ cx,
21524
+ cy,
21525
+ rx,
21526
+ ry
21527
+ };
21528
+ const element = this.__createElement('ellipse', config, true);
21529
+ const parent = this.__closestGroupOrSvg();
21530
+ parent.appendChild(element);
21531
+ this.__currentElement = element;
21532
+ this.__applyStyleToCurrentElement(style);
21533
+ }
21534
+
21535
+ /**
21536
+ * Clear entire canvas:
21537
+ * 1. save current transforms
21538
+ * 2. remove all the childNodes of the root g element
21539
+ */
21540
+ __clearCanvas() {
21541
+ var current = this.__closestGroupOrSvg(),
21542
+ transform = current.getAttribute("transform");
21543
+ var rootGroup = this.__root.childNodes[1];
21544
+ var childNodes = rootGroup.childNodes;
21545
+ for (var i = childNodes.length - 1; i >= 0; i--) {
21546
+ if (childNodes[i]) {
21547
+ rootGroup.removeChild(childNodes[i]);
20967
21548
  }
20968
21549
  }
20969
- function pushChromosome(current, order) {
20970
- const length = current.length || current.offset + current.seq.length;
20971
- if (!chrNameSet.has(current.chr)) {
20972
- this.chromosomeNames.push(current.chr);
20973
- this.sequences.set(current.chr, []);
20974
- this.chromosomes[current.chr] = new Chromosome(current.chr, order, length);
20975
- chrNameSet.add(current.chr);
20976
- } else {
20977
- const c = this.chromosomes[current.chr];
20978
- c.bpLength = Math.max(c.bpLength, length);
20979
- }
20980
- this.sequences.get(current.chr).push(new SequenceSlice(current.offset, current.seq));
21550
+ this.__currentElement = rootGroup;
21551
+ //reset __groupStack as all the child group nodes are all removed.
21552
+ this.__groupStack = [];
21553
+ if (transform) {
21554
+ this.__addTransform(transform);
20981
21555
  }
20982
21556
  }
20983
- }
20984
- class SequenceSlice {
20985
- constructor(offset, sequence) {
20986
- this.offset = offset;
20987
- this.sequence = sequence;
20988
- }
20989
- contains(start, end) {
20990
- return this.offset <= start && this.end >= end;
21557
+ /**
21558
+ * "Clears" a canvas by just drawing a white rectangle in the current group.
21559
+ */
21560
+ clearRect(x, y, width, height) {
21561
+ //clear entire canvas
21562
+ if (x === 0 && y === 0 && width === this.width && height === this.height) {
21563
+ this.__clearCanvas();
21564
+ return;
21565
+ }
21566
+ var rect,
21567
+ parent = this.__closestGroupOrSvg();
21568
+ rect = this.__createElement("rect", {
21569
+ x: x,
21570
+ y: y,
21571
+ width: width,
21572
+ height: height,
21573
+ fill: "#FFFFFF"
21574
+ }, true);
21575
+ parent.appendChild(rect);
20991
21576
  }
20992
- overlaps(start, end) {
20993
- return this.offset < end && this.end > start;
21577
+ /**
21578
+ * Adds a linear gradient to a defs tag.
21579
+ * Returns a canvas gradient object that has a reference to it's parent def
21580
+ */
21581
+ createLinearGradient(x1, y1, x2, y2) {
21582
+ var grad = this.__createElement("linearGradient", {
21583
+ id: randomString(this.__ids),
21584
+ x1: x1 + "px",
21585
+ x2: x2 + "px",
21586
+ y1: y1 + "px",
21587
+ y2: y2 + "px",
21588
+ "gradientUnits": "userSpaceOnUse"
21589
+ }, false);
21590
+ this.__defs.appendChild(grad);
21591
+ return new CanvasGradient(grad, this);
20994
21592
  }
20995
- get end() {
20996
- return this.offset + this.sequence.length;
21593
+ /**
21594
+ * Adds a radial gradient to a defs tag.
21595
+ * Returns a canvas gradient object that has a reference to it's parent def
21596
+ */
21597
+ createRadialGradient(x0, y0, r0, x1, y1, r1) {
21598
+ var grad = this.__createElement("radialGradient", {
21599
+ id: randomString(this.__ids),
21600
+ cx: x1 + "px",
21601
+ cy: y1 + "px",
21602
+ r: r1 + "px",
21603
+ fx: x0 + "px",
21604
+ fy: y0 + "px",
21605
+ "gradientUnits": "userSpaceOnUse"
21606
+ }, false);
21607
+ this.__defs.appendChild(grad);
21608
+ return new CanvasGradient(grad, this);
20997
21609
  }
20998
- }
20999
-
21000
- const GenomicInterval = function (chr, start, end, features) {
21001
- this.chr = chr;
21002
- this.start = start;
21003
- this.end = end;
21004
- this.features = features;
21005
- };
21006
- GenomicInterval.prototype.contains = function (chr, start, end) {
21007
- return this.chr === chr && this.start <= start && this.end >= end;
21008
- };
21009
- GenomicInterval.prototype.containsRange = function (range) {
21010
- return this.chr === range.chr && this.start <= range.start && this.end >= range.end;
21011
- };
21610
+ /**
21611
+ * Parses the font string and returns svg mapping
21612
+ * @private
21613
+ */
21614
+ __parseFont() {
21615
+ var regex = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\'\"\sa-z0-9]+?)\s*$/i;
21616
+ var fontPart = regex.exec(this.font);
21617
+ var data = {
21618
+ style: fontPart[1] || 'normal',
21619
+ size: fontPart[4] || '10px',
21620
+ family: fontPart[6] || 'sans-serif',
21621
+ weight: fontPart[3] || 'normal',
21622
+ decoration: fontPart[2] || 'normal',
21623
+ href: null
21624
+ };
21012
21625
 
21013
- /*
21014
- * The MIT License (MIT)
21015
- *
21016
- * Copyright (c) 2014 Broad Institute
21017
- *
21018
- * Permission is hereby granted, free of charge, to any person obtaining a copy
21019
- * of this software and associated documentation files (the "Software"), to deal
21020
- * in the Software without restriction, including without limitation the rights
21021
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21022
- * copies of the Software, and to permit persons to whom the Software is
21023
- * furnished to do so, subject to the following conditions:
21024
- *
21025
- * The above copyright notice and this permission notice shall be included in
21026
- * all copies or substantial portions of the Software.
21027
- *
21028
- *
21029
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21030
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21031
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21032
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21033
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21034
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21035
- * THE SOFTWARE.
21036
- */
21037
- const splitLines$3 = splitLines$5;
21038
- const reservedProperties = new Set(['fastaURL', 'indexURL', 'compressedIndexURL', 'cytobandURL', 'indexed']);
21039
- class FastaSequence {
21040
- constructor(reference) {
21041
- this.file = reference.fastaURL;
21042
- this.indexFile = reference.indexURL || reference.indexFile || this.file + ".fai";
21043
- this.compressedIndexFile = reference.compressedIndexURL || false;
21044
- this.withCredentials = reference.withCredentials;
21045
- this.chromosomeNames = [];
21046
- this.chromosomes = {};
21047
- this.sequences = {};
21048
- this.offsets = {};
21626
+ //canvas doesn't support underline natively, but we can pass this attribute
21627
+ if (this.__fontUnderline === "underline") {
21628
+ data.decoration = "underline";
21629
+ }
21049
21630
 
21050
- // Build a track-like config object from the referenceObject
21051
- const config = {};
21052
- for (let key in reference) {
21053
- if (reference.hasOwnProperty(key) && !reservedProperties.has(key)) {
21054
- config[key] = reference[key];
21055
- }
21631
+ //canvas also doesn't support linking, but we can pass this as well
21632
+ if (this.__fontHref) {
21633
+ data.href = this.__fontHref;
21056
21634
  }
21057
- this.config = config;
21058
- }
21059
- async init() {
21060
- return this.getIndex();
21635
+ return data;
21061
21636
  }
21062
- async getSequence(chr, start, end) {
21063
- const hasCachedSquence = this.interval && this.interval.contains(chr, start, end);
21064
- if (!hasCachedSquence) {
21065
- // Expand query, to minimum of 50kb
21066
- let qstart = start;
21067
- let qend = end;
21068
- if (end - start < 50000) {
21069
- const w = end - start;
21070
- const center = Math.round(start + w / 2);
21071
- qstart = Math.max(0, center - 25000);
21072
- qend = center + 25000;
21073
- }
21074
- const seqBytes = await this.readSequence(chr, qstart, qend);
21075
- this.interval = new GenomicInterval(chr, qstart, qend, seqBytes);
21637
+ /**
21638
+ * Helper to link text fragments
21639
+ * @param font
21640
+ * @param element
21641
+ * @return {*}
21642
+ * @private
21643
+ */
21644
+ __wrapTextLink(font, element) {
21645
+ if (font.href) {
21646
+ var a = this.__createElement("a");
21647
+ a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", font.href);
21648
+ a.appendChild(element);
21649
+ return a;
21076
21650
  }
21077
- const offset = start - this.interval.start;
21078
- const n = end - start;
21079
- const seq = this.interval.features ? this.interval.features.substr(offset, n) : null;
21080
- return seq;
21651
+ return element;
21081
21652
  }
21082
- async getIndex() {
21083
- if (this.index) {
21084
- return this.index;
21085
- } else {
21086
- const data = await igvxhr.load(this.indexFile, buildOptions(this.config));
21087
- const lines = splitLines$3(data);
21088
- const len = lines.length;
21089
- let lineNo = 0;
21090
- let order = 0;
21091
- this.index = {};
21092
- while (lineNo < len) {
21093
- const tokens = lines[lineNo++].split("\t");
21094
- const nTokens = tokens.length;
21095
- if (nTokens === 5) {
21096
- // Parse the index line.
21097
- const chr = tokens[0];
21098
- const size = parseInt(tokens[1]);
21099
- const position = parseInt(tokens[2]);
21100
- const basesPerLine = parseInt(tokens[3]);
21101
- const bytesPerLine = parseInt(tokens[4]);
21102
- const indexEntry = {
21103
- size: size,
21104
- position: position,
21105
- basesPerLine: basesPerLine,
21106
- bytesPerLine: bytesPerLine
21107
- };
21108
- this.chromosomeNames.push(chr);
21109
- this.index[chr] = indexEntry;
21110
- this.chromosomes[chr] = new Chromosome(chr, order++, size);
21111
- }
21112
- }
21113
- return this.index;
21114
- }
21653
+ /**
21654
+ * Fills or strokes text
21655
+ * @param text
21656
+ * @param x
21657
+ * @param y
21658
+ * @param action - stroke or fill
21659
+ * @private
21660
+ */
21661
+ __applyText(text, x, y, action) {
21662
+ var font = this.__parseFont(),
21663
+ parent = this.__closestGroupOrSvg(),
21664
+ textElement = this.__createElement("text", {
21665
+ "font-family": font.family,
21666
+ "font-size": font.size,
21667
+ "font-style": font.style,
21668
+ "font-weight": font.weight,
21669
+ "text-decoration": font.decoration,
21670
+ "x": x,
21671
+ "y": y,
21672
+ "text-anchor": getTextAnchor(this.textAlign),
21673
+ "dominant-baseline": getDominantBaseline(this.textBaseline)
21674
+ }, true);
21675
+ textElement.appendChild(this.__document.createTextNode(text));
21676
+ this.__currentElement = textElement;
21677
+ this.__applyStyleToCurrentElement(action);
21678
+ parent.appendChild(this.__wrapTextLink(font, textElement));
21115
21679
  }
21116
-
21117
- //Code is losely based on https://github.com/GMOD/bgzf-filehandle
21118
- //Reworked however in orde to work with the igvxhr interface for loading files
21119
- //Additionally, replaced calls to the Long.js interface with standard JS calls for ArrayBuffers and the associated views
21120
- //
21121
- //The compressed index is an array of blocks, with each block being a pair: compressed-position & uncompressed-position (both in bytes)
21122
- async getCompressedIndex() {
21123
- const GZI_NUM_BYTES_OFFSET = 8;
21124
- const GZI_NUM_BYTES_BLOCK = 8;
21125
- if (this.compressedIndex) {
21126
- return this.compressedIndex;
21127
- }
21128
- if (!this.compressedIndexFile) {
21129
- this.compressedIndex = [];
21130
- return this.compressedIndex;
21680
+ /**
21681
+ * Creates a text element
21682
+ * @param text
21683
+ * @param x
21684
+ * @param y
21685
+ */
21686
+ fillText(text, x, y) {
21687
+ this.__applyText(text, x, y, "fill");
21688
+ }
21689
+ /**
21690
+ * Strokes text
21691
+ * @param text
21692
+ * @param x
21693
+ * @param y
21694
+ */
21695
+ strokeText(text, x, y) {
21696
+ this.__applyText(text, x, y, "stroke");
21697
+ }
21698
+ /**
21699
+ * No need to implement this for svg.
21700
+ * @param text
21701
+ * @return {TextMetrics}
21702
+ */
21703
+ measureText(text) {
21704
+ this.__ctx.font = this.font;
21705
+ return this.__ctx.measureText(text);
21706
+ }
21707
+ /**
21708
+ * Arc command!
21709
+ */
21710
+ arc(x, y, radius, startAngle, endAngle, counterClockwise) {
21711
+ // in canvas no circle is drawn if no angle is provided.
21712
+ if (startAngle === endAngle) {
21713
+ return;
21131
21714
  }
21132
- //In contrast to the 'normal' reference (for which the index is chromosome based), this index is block-based
21133
- //As such there is not need to make it a hash. An array is sufficient.
21134
- this.compressedIndex = [];
21135
- const gziData = await igvxhr.loadArrayBuffer(this.compressedIndexFile, buildOptions(this.config));
21136
- const givenFileSize = gziData.byteLength;
21137
- if (givenFileSize < GZI_NUM_BYTES_OFFSET) {
21138
- console.log("Cannot parse GZI index file: length (" + givenFileSize + " bytes) is insufficient to determine content of index.");
21139
- return this.compressedIndex;
21715
+ startAngle = startAngle % (2 * Math.PI);
21716
+ endAngle = endAngle % (2 * Math.PI);
21717
+ if (startAngle === endAngle) {
21718
+ //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle)
21719
+ endAngle = (endAngle + 2 * Math.PI - 0.001 * (counterClockwise ? -1 : 1)) % (2 * Math.PI);
21140
21720
  }
21141
- //First 8 bytes are a little endian unsigned bigint (64bit), indicating the number of blocks in the index.
21142
- const numBlocksBuffer = gziData.slice(0, GZI_NUM_BYTES_OFFSET);
21143
- const numBlocks = Number(new DataView(numBlocksBuffer).getBigUint64(0, true));
21144
- //The remainder of the gzi content are pairs of little endian unsigned bigint (64bit) numbers.
21145
- //The first of the pair is the compressed position of a block
21146
- //The second of the pair is the uncompressed position of a block
21721
+ var endX = x + radius * Math.cos(endAngle),
21722
+ endY = y + radius * Math.sin(endAngle),
21723
+ startX = x + radius * Math.cos(startAngle),
21724
+ startY = y + radius * Math.sin(startAngle),
21725
+ sweepFlag = counterClockwise ? 0 : 1,
21726
+ largeArcFlag = 0,
21727
+ diff = endAngle - startAngle;
21147
21728
 
21148
- //Sanity check:
21149
- //Is the size of the array-buffer (of the entire file) correct with regards to the number of blocks detailled by the first 8 bytes of the file?
21150
- //Total file-size should be:
21151
- // 8 + 2*(num_entries*8) bytes, with the first 8 bytes indicating the number of entries
21152
- const expectedFileSize = GZI_NUM_BYTES_OFFSET + numBlocks * 2 * GZI_NUM_BYTES_BLOCK;
21153
- if (givenFileSize != expectedFileSize) {
21154
- console.log("Incorrect file size of reference genome index. Expected : " + expectedFileSize + ". Received : " + givenFileSize);
21155
- return this.compressedIndex;
21729
+ // https://github.com/gliffy/canvas2svg/issues/4
21730
+ if (diff < 0) {
21731
+ diff += 2 * Math.PI;
21156
21732
  }
21157
-
21158
- //Push the first block to the index: the first block always has positions 0 for both the compressed and uncompressed file
21159
- this.compressedIndex.push([0, 0]);
21160
-
21161
- //Further process all the blocks of the GZI index, and keep them in memory
21162
- for (let blockNumber = 0; blockNumber < numBlocks; blockNumber++) {
21163
- const bufferBlockStart = GZI_NUM_BYTES_OFFSET + blockNumber * 2 * GZI_NUM_BYTES_BLOCK;
21164
- const bufferBlockEnd = GZI_NUM_BYTES_OFFSET + blockNumber * 2 * GZI_NUM_BYTES_BLOCK + 2 * GZI_NUM_BYTES_BLOCK;
21165
- const bufferBlock = gziData.slice(bufferBlockStart, bufferBlockEnd);
21166
- const viewBlock = new DataView(bufferBlock);
21167
- const compressedPosition = Number(viewBlock.getBigUint64(0, true)); //First 8 bytes
21168
- const uncompressedPosition = Number(viewBlock.getBigUint64(GZI_NUM_BYTES_BLOCK, true)); //Last 8 bytes
21169
- this.compressedIndex.push([compressedPosition, uncompressedPosition]);
21733
+ if (counterClockwise) {
21734
+ largeArcFlag = diff > Math.PI ? 0 : 1;
21735
+ } else {
21736
+ largeArcFlag = diff > Math.PI ? 1 : 0;
21170
21737
  }
21171
- return this.compressedIndex;
21738
+ this.lineTo(startX, startY);
21739
+ this.__addPathCommand(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", {
21740
+ rx: radius,
21741
+ ry: radius,
21742
+ xAxisRotation: 0,
21743
+ largeArcFlag: largeArcFlag,
21744
+ sweepFlag: sweepFlag,
21745
+ endX: endX,
21746
+ endY: endY
21747
+ }));
21748
+ this.__currentPosition = {
21749
+ x: endX,
21750
+ y: endY
21751
+ };
21172
21752
  }
21753
+ /**
21754
+ * The ellipse() method creates an elliptical arc centered at (x, y) with the radii radiusX and radiusY. The path
21755
+ * starts at startAngle and ends at endAngle, and travels in the direction given by counterclockwise (defaulting to clockwise).
21756
+ */
21757
+ // ellipse (x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
21758
+ // // TODO -- implement
21759
+ // }
21173
21760
 
21174
- //The Fasta-index gives a byte-position of the chromosomal sequences within the FASTA file.
21175
- //These locations need to be remapped to the locations within the zipped reference genome, using the GZI index
21176
- //This function provides this functionality by
21177
- //1) taking the indicated start/stop byte locations within the UNCOMPRESSED FASTA file
21178
- //2) remapping these byte locations to the correct blocks (and associated positions) within the COMPRESSED FASTA file
21179
- //Subsequently, the calling method can then extract the correct blocks from the compressed FASTA files and uncompressed the data
21180
- async getRelevantCompressedBlockNumbers(queryPositionStart, queryPositionEnd) {
21181
- const UNCOMPRESSED_POSITION = 1;
21182
- //Fallback for impossible values
21183
- if (queryPositionStart < 0 || queryPositionEnd < 0 || queryPositionEnd < queryPositionStart) {
21184
- console.log("Incompatible query positions for reference-genome. Start:" + queryPositionStart + " | End:" + queryPositionEnd);
21185
- return [];
21186
- }
21187
- //Ensure compressed index is loaded
21188
- await this.getCompressedIndex();
21189
- let result = [];
21190
- //Now search for the correct block-numbers (going from 0 to length(compressed-index)) which overlap with the provided byte-positions
21191
- const lowestBlockNumber = 0;
21192
- const highestBlockNumber = this.compressedIndex.length - 1;
21193
- //Failsafe if for some reason the compressed index wasn't loaded or doesn't contain any data
21194
- if (this.compressedIndex.length == 0) {
21195
- console.log("Compressed index does not contain any content");
21196
- return [];
21197
- }
21198
- //Failsafe: if the queryPositionStart is greater than the uncompressed-position of the final block,
21199
- //then this final block is the only possible result
21200
- if (queryPositionStart > this.compressedIndex[highestBlockNumber][UNCOMPRESSED_POSITION]) {
21201
- return [highestBlockNumber];
21202
- }
21203
-
21204
- //Rather than doing a linear search over all blocks, a binary search is done for speed considerations
21205
- //We are searching for the highest block number for which its position is smaller than the query start position
21206
- //Afterwards we will simply expand the blocks until the entire query range is covered
21207
- let searchLow = lowestBlockNumber;
21208
- let searchHigh = highestBlockNumber;
21209
- let searchPosition = Math.floor(this.compressedIndex.length / 2);
21210
- let maxIterations = this.compressedIndex.length + 1;
21211
- let solutionFound = false;
21212
- //instead of doing a while(true), this for-loop prevents eternal loops in case of issues
21213
- for (let iteration = 0; iteration < maxIterations; iteration++) {
21214
- const searchUncompressedPosition = this.compressedIndex[searchPosition][UNCOMPRESSED_POSITION];
21215
- const nextSearchUncompressedPosition = searchPosition < this.compressedIndex.length - 1 ? this.compressedIndex[searchPosition + 1][UNCOMPRESSED_POSITION] : Infinity;
21216
- //The query position lies within the current search block
21217
- if (searchUncompressedPosition <= queryPositionStart && nextSearchUncompressedPosition > queryPositionStart) {
21218
- solutionFound = true;
21219
- break; //searchPosition is the correct block number index
21220
- }
21221
- //Current block lies before the query position
21222
- else if (searchUncompressedPosition < queryPositionStart) {
21223
- searchLow = searchPosition + 1;
21224
- }
21225
- //Current block lies after the query position
21226
- else {
21227
- searchHigh = searchPosition - 1;
21228
- }
21229
- searchPosition = Math.ceil((searchHigh - searchLow) / 2) + searchLow;
21230
- }
21231
- //If for some reason the binary search did not reveal a correct block index, then we return the empty result
21232
- if (!solutionFound) {
21233
- console.log("No blocks within compressed index found that correspond with query positions " + queryPositionStart + "," + queryPositionEnd);
21234
- console.log(this.compressedIndex);
21235
- return [];
21236
- }
21761
+ /**
21762
+ * Generates a ClipPath from the clip command.
21763
+ */
21764
+ clip() {
21765
+ var group = this.__closestGroupOrSvg(),
21766
+ clipPath = this.__createElement("clipPath"),
21767
+ id = randomString(this.__ids),
21768
+ newGroup = this.__createElement("g");
21769
+ this.__applyCurrentDefaultPath();
21770
+ group.removeChild(this.__currentElement);
21771
+ clipPath.setAttribute("id", id);
21772
+ clipPath.appendChild(this.__currentElement);
21773
+ this.__defs.appendChild(clipPath);
21237
21774
 
21238
- //Now extend the result by adding additional blocks until the entire query range is covered
21239
- result.push(searchPosition);
21240
- for (let blockIndex = searchPosition + 1; blockIndex < this.compressedIndex.length; blockIndex++) {
21241
- result.push(blockIndex);
21242
- const blockUncompressedPosition = this.compressedIndex[blockIndex][UNCOMPRESSED_POSITION];
21243
- if (blockUncompressedPosition >= queryPositionEnd) {
21244
- break;
21245
- }
21246
- }
21775
+ //set the clip path to this group
21776
+ group.setAttribute("clip-path", format("url(#{id})", {
21777
+ id: id
21778
+ }));
21247
21779
 
21248
- //It is possible that the query end position lies AFTER the start of the final block
21249
- //If this is the case, we add a 'fake' negative index which will be interpreted by the loadAndUncompressBlocks method as an indicator
21250
- //to read until the end of the file
21251
- const finalRelevantBlock = result[result.length - 1];
21252
- const finalIndexBlock = this.compressedIndex.length - 1;
21253
- if (finalRelevantBlock === finalIndexBlock && this.compressedIndex[finalRelevantBlock][UNCOMPRESSED_POSITION] < queryPositionEnd) {
21254
- result.push(-1);
21255
- }
21256
- return result;
21780
+ //clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations
21781
+ // to this path
21782
+ group.appendChild(newGroup);
21783
+ this.__currentElement = newGroup;
21257
21784
  }
21258
-
21259
- //Load the content from the blockIndices.
21260
- //This is done on a per-block basis
21261
- //Content of the first block will be trimmed in order to match the expected offset
21262
- async loadAndUncompressBlocks(blockIndices, startByte) {
21263
- const COMPRESSED_POSITION = 0;
21264
- const UNCOMPRESSED_POSITION = 1;
21265
- //Normally the compressed index should already exist, we're just makeing sure here
21266
- await this.getCompressedIndex();
21267
- if (blockIndices.length == 0) {
21268
- return "";
21785
+ /**
21786
+ * Draws a canvas, image or mock context to this canvas.
21787
+ * Note that all svg dom manipulation uses node.childNodes rather than node.children for IE support.
21788
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage
21789
+ */
21790
+ drawImage() {
21791
+ //convert arguments to a real array
21792
+ var args = Array.prototype.slice.call(arguments),
21793
+ image = args[0],
21794
+ dx,
21795
+ dy,
21796
+ dw,
21797
+ dh,
21798
+ sx = 0,
21799
+ sy = 0,
21800
+ sw,
21801
+ sh,
21802
+ parent,
21803
+ svg,
21804
+ defs,
21805
+ group,
21806
+ svgImage,
21807
+ canvas,
21808
+ context,
21809
+ id;
21810
+ if (args.length === 3) {
21811
+ dx = args[1];
21812
+ dy = args[2];
21813
+ sw = image.width;
21814
+ sh = image.height;
21815
+ dw = sw;
21816
+ dh = sh;
21817
+ } else if (args.length === 5) {
21818
+ dx = args[1];
21819
+ dy = args[2];
21820
+ dw = args[3];
21821
+ dh = args[4];
21822
+ sw = image.width;
21823
+ sh = image.height;
21824
+ } else if (args.length === 9) {
21825
+ sx = args[1];
21826
+ sy = args[2];
21827
+ sw = args[3];
21828
+ sh = args[4];
21829
+ dx = args[5];
21830
+ dy = args[6];
21831
+ dw = args[7];
21832
+ dh = args[8];
21833
+ } else {
21834
+ throw new Error("Invalid number of arguments passed to drawImage: " + arguments.length);
21269
21835
  }
21270
-
21271
- //Storing data in seperate array with indices in order to assert order due to async behaviour of loops
21272
- let resultCache = Array(blockIndices.length - 1);
21273
- for (let i = 0; i < blockIndices.length - 1; i++) {
21274
- const currentBlockNumber = blockIndices[i];
21275
- const currentBlockInfo = this.compressedIndex[currentBlockNumber];
21276
- const currentBlockCompressedPosition = currentBlockInfo[COMPRESSED_POSITION];
21277
- const nextBlockNumber = blockIndices[i + 1];
21278
- let compressedBytes = [];
21279
- if (nextBlockNumber != -1) {
21280
- //default : read current entire block only
21281
- const nextBlockInfo = this.compressedIndex[nextBlockNumber];
21282
- const nextBlockCompressedPosition = nextBlockInfo[COMPRESSED_POSITION];
21283
- const compressedLength = nextBlockCompressedPosition - currentBlockCompressedPosition;
21284
- compressedBytes = await igvxhr.loadArrayBuffer(this.file, buildOptions(this.config, {
21285
- range: {
21286
- start: currentBlockCompressedPosition,
21287
- size: compressedLength
21288
- }
21289
- }));
21290
- } else {
21291
- // special case for query within final block: read until the end of the file
21292
- compressedBytes = await igvxhr.loadArrayBuffer(this.file, buildOptions(this.config, {
21293
- range: {
21294
- start: currentBlockCompressedPosition
21836
+ parent = this.__closestGroupOrSvg();
21837
+ this.__currentElement;
21838
+ var translateDirective = "translate(" + dx + ", " + dy + ")";
21839
+ if (image instanceof ctx) {
21840
+ //canvas2svg mock canvas context. In the future we may want to clone nodes instead.
21841
+ //also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context.
21842
+ svg = image.getSvg().cloneNode(true);
21843
+ if (svg.childNodes && svg.childNodes.length > 1) {
21844
+ defs = svg.childNodes[0];
21845
+ while (defs.childNodes.length) {
21846
+ id = defs.childNodes[0].getAttribute("id");
21847
+ this.__ids[id] = id;
21848
+ this.__defs.appendChild(defs.childNodes[0]);
21849
+ }
21850
+ group = svg.childNodes[1];
21851
+ if (group) {
21852
+ //save original transform
21853
+ var originTransform = group.getAttribute("transform");
21854
+ var transformDirective;
21855
+ if (originTransform) {
21856
+ transformDirective = originTransform + " " + translateDirective;
21857
+ } else {
21858
+ transformDirective = translateDirective;
21295
21859
  }
21296
- }));
21860
+ group.setAttribute("transform", transformDirective);
21861
+ parent.appendChild(group);
21862
+ }
21297
21863
  }
21298
- //now unzip the compressed bytes, and store them in the resultCache
21299
- const uncompressedBytes = await unbgzf(compressedBytes);
21300
- resultCache[i] = uncompressedBytes;
21301
- }
21302
-
21303
- //Iterate over the result cache, create sequences from the data, and create a full sequence string from the data
21304
- let result = "";
21305
- for (let i = 0; i < resultCache.length; i++) {
21306
- for (let j = 0; j < resultCache[i].length; j++) {
21307
- const c = String.fromCharCode(resultCache[i][j]);
21308
- result = result + c;
21864
+ } else if (image.nodeName === "CANVAS" || image.nodeName === "IMG") {
21865
+ //canvas or image
21866
+ svgImage = this.__createElement("image");
21867
+ svgImage.setAttribute("width", dw);
21868
+ svgImage.setAttribute("height", dh);
21869
+ svgImage.setAttribute("preserveAspectRatio", "none");
21870
+ if (sx || sy || sw !== image.width || sh !== image.height) {
21871
+ //crop the image using a temporary canvas
21872
+ canvas = this.__document.createElement("canvas");
21873
+ canvas.width = dw;
21874
+ canvas.height = dh;
21875
+ context = canvas.getContext("2d");
21876
+ context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh);
21877
+ image = canvas;
21309
21878
  }
21879
+ svgImage.setAttribute("transform", translateDirective);
21880
+ svgImage.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src"));
21881
+ parent.appendChild(svgImage);
21310
21882
  }
21311
-
21312
- //postprocess this data: because entire blocks are read we need to remove the first N bases of the first used block,
21313
- //which are not included in the original query positions
21314
- const firstBlockInfo = this.compressedIndex[blockIndices[0]];
21315
- const offset = startByte - firstBlockInfo[UNCOMPRESSED_POSITION];
21316
- result = result.substring(offset);
21317
- return result;
21318
21883
  }
21319
- async readSequence(chr, qstart, qend) {
21320
- await this.getIndex();
21321
- await this.getCompressedIndex(); //This will work even if no compressed index file is set
21322
-
21323
- const idxEntry = this.index[chr];
21324
- if (!idxEntry) {
21325
- console.log("No index entry for chr: " + chr);
21326
- // Tag interval with null so we don't try again
21327
- this.interval = new GenomicInterval(chr, qstart, qend, null);
21328
- return null;
21329
- }
21330
- const start = Math.max(0, qstart); // qstart should never be < 0
21331
- const end = Math.min(idxEntry.size, qend);
21332
- const bytesPerLine = idxEntry.bytesPerLine;
21333
- const basesPerLine = idxEntry.basesPerLine;
21334
- const position = idxEntry.position;
21335
- const nEndBytes = bytesPerLine - basesPerLine;
21336
- const startLine = Math.floor(start / basesPerLine);
21337
- const endLine = Math.floor(end / basesPerLine);
21338
- const base0 = startLine * basesPerLine; // Base at beginning of start line
21339
- const offset = start - base0;
21340
- const startByte = position + startLine * bytesPerLine + offset;
21341
- const base1 = endLine * basesPerLine;
21342
- const offset1 = end - base1;
21343
- const endByte = position + endLine * bytesPerLine + offset1 - 1;
21344
- const byteCount = endByte - startByte + 1;
21345
- if (byteCount <= 0) {
21346
- console.error("No sequence for " + chr + ":" + qstart + "-" + qend);
21347
- return null;
21348
- }
21349
-
21350
- //If the compressed index file is set, then we are dealing with a compressed genome sequence
21351
- //The selection of startByte/endByte is done for the non-compressed genome sequence.
21352
- //These need to be 'converted' to the correct byte positions in the compressed genome sequence,
21353
- //by making use of the compressed index (GZI file)
21354
- let allBytes;
21355
- if (!this.compressedIndexFile) {
21356
- allBytes = await igvxhr.load(this.file, buildOptions(this.config, {
21357
- range: {
21358
- start: startByte,
21359
- size: byteCount
21360
- }
21361
- }));
21362
- } else {
21363
- let relevantBlockIndices = await this.getRelevantCompressedBlockNumbers(startByte, endByte);
21364
- if (relevantBlockIndices.length === 0) {
21365
- console.log("No blocks in the compressed index that correspond with the requested byte positions (" + startByte + "," + endByte + ")");
21366
- return null;
21367
- }
21368
- allBytes = await this.loadAndUncompressBlocks(relevantBlockIndices, startByte);
21369
- }
21370
- if (!allBytes) {
21371
- return null;
21372
- }
21373
- let nBases,
21374
- seqBytes = "",
21375
- srcPos = 0,
21376
- allBytesLength = allBytes.length;
21377
- if (offset > 0) {
21378
- nBases = Math.min(end - start, basesPerLine - offset);
21379
- seqBytes += allBytes.substr(srcPos, nBases);
21380
- srcPos += nBases + nEndBytes;
21381
- }
21382
- while (srcPos < allBytesLength) {
21383
- nBases = Math.min(basesPerLine, allBytesLength - srcPos);
21384
- seqBytes += allBytes.substr(srcPos, nBases);
21385
- srcPos += nBases + nEndBytes;
21884
+ /**
21885
+ * Generates a pattern tag
21886
+ */
21887
+ createPattern(image, repetition) {
21888
+ let pattern = this.__document.__createElement("pattern");
21889
+ let id = randomString(this.__ids);
21890
+ let img;
21891
+ pattern.setAttribute("id", id);
21892
+ pattern.setAttribute("width", image.width);
21893
+ pattern.setAttribute("height", image.height);
21894
+ if (image.nodeName === "CANVAS" || image.nodeName === "IMG") {
21895
+ img = this.__createElement("image");
21896
+ img.setAttribute("width", image.width);
21897
+ img.setAttribute("height", image.height);
21898
+ img.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src"));
21899
+ pattern.appendChild(img);
21900
+ this.__defs.appendChild(pattern);
21901
+ } else if (image instanceof ctx) {
21902
+ pattern.appendChild(image.__root.childNodes[1]);
21903
+ this.__defs.appendChild(pattern);
21386
21904
  }
21387
- return seqBytes;
21388
- }
21389
- }
21390
-
21391
- /*
21392
- * The MIT License (MIT)
21393
- *
21394
- * Copyright (c) 2014 Broad Institute
21395
- *
21396
- * Permission is hereby granted, free of charge, to any person obtaining a copy
21397
- * of this software and associated documentation files (the "Software"), to deal
21398
- * in the Software without restriction, including without limitation the rights
21399
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21400
- * copies of the Software, and to permit persons to whom the Software is
21401
- * furnished to do so, subject to the following conditions:
21402
- *
21403
- * The above copyright notice and this permission notice shall be included in
21404
- * all copies or substantial portions of the Software.
21405
- *
21406
- *
21407
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21408
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21409
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21410
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21411
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21412
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21413
- * THE SOFTWARE.
21414
- */
21415
- const splitLines$2 = splitLines$5;
21416
- class ChromSizes {
21417
- constructor(url) {
21418
- this.url = url;
21419
- this.chromosomeNames = [];
21420
- this.chromosomes = {};
21421
- }
21422
- async init() {
21423
- return this.loadAll();
21424
- }
21425
- async getSequence(chr, start, end) {
21426
- return undefined; // TODO -- return array of "N"s?
21905
+ return new CanvasPattern(pattern, this);
21427
21906
  }
21428
-
21429
- async loadAll() {
21430
- let data;
21431
- if (isDataURL(this.url)) {
21432
- let bytes = decodeDataURI$1(this.fastaURL);
21433
- data = "";
21434
- for (let b of bytes) {
21435
- data += String.fromCharCode(b);
21436
- }
21907
+ setLineDash(dashArray) {
21908
+ if (dashArray && dashArray.length > 0) {
21909
+ this.lineDash = dashArray.join(",");
21437
21910
  } else {
21438
- data = await igvxhr.load(this.url, {});
21439
- }
21440
- this.chromosomeNames = [];
21441
- this.chromosomes = {};
21442
- const lines = splitLines$2(data);
21443
- let order = 0;
21444
- for (let nextLine of lines) {
21445
- const tokens = nextLine.split('\t');
21446
- this.chromosomeNames.push(tokens[0]);
21447
- const chrLength = Number.parseInt(tokens[1]);
21448
- const chromosome = new Chromosome(tokens[0], order++, chrLength);
21449
- this.chromosomes[tokens[0]] = chromosome;
21911
+ this.lineDash = null;
21450
21912
  }
21451
21913
  }
21452
- }
21453
-
21454
- async function loadFasta(reference) {
21455
- let fasta;
21456
- if ("chromsizes" === reference.format) {
21457
- fasta = new ChromSizes(reference.fastaURL);
21458
- } else if (isDataURL(reference.fastaURL) || reference.indexed === false) {
21459
- fasta = new NonIndexedFasta(reference);
21460
- } else {
21461
- fasta = new FastaSequence(reference);
21462
- }
21463
- await fasta.init();
21464
- return fasta;
21914
+ /**
21915
+ * Not yet implemented
21916
+ */
21917
+ drawFocusRing() {}
21918
+ createImageData() {}
21919
+ getImageData() {}
21920
+ putImageData() {}
21921
+ globalCompositeOperation() {}
21922
+ setTransform() {}
21465
21923
  }
21466
21924
 
21467
21925
  const Cytoband = function (start, end, name, typestain) {
@@ -21481,7 +21939,7 @@
21481
21939
  }
21482
21940
  };
21483
21941
 
21484
- const _version = "2.13.4";
21942
+ const _version = "2.13.6";
21485
21943
  function version$1() {
21486
21944
  return _version;
21487
21945
  }
@@ -21960,7 +22418,7 @@
21960
22418
  return visibilityWindow !== undefined && visibilityWindow > 0 && this.referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow;
21961
22419
  }
21962
22420
  };
21963
- if (this.trackView.track && "sequence" === this.trackView.track.type && this.referenceFrame.bpPerPixel > 1) {
22421
+ if (this.trackView.track && "sequence" === this.trackView.track.type && this.referenceFrame.bpPerPixel > bppFeatureFetchThreshold) {
21964
22422
  $$1(this.canvas).remove();
21965
22423
  this.canvas = undefined;
21966
22424
  //this.featureCache = undefined
@@ -23166,6 +23624,9 @@
23166
23624
  feature.attributes = {};
23167
23625
  for (let kv of attributeKVs) {
23168
23626
  feature.attributes[kv[0]] = kv[1];
23627
+ if (header.nameField != undefined && kv[0] === header.nameField) {
23628
+ feature.name = kv[1];
23629
+ }
23169
23630
  }
23170
23631
  }
23171
23632
  if (!feature.name) {
@@ -29402,25 +29863,12 @@
29402
29863
  var FunctionPrototype$2 = Function.prototype;
29403
29864
  var call$2 = FunctionPrototype$2.call;
29404
29865
  var uncurryThisWithBind = functionBindNative && FunctionPrototype$2.bind.bind(call$2, call$2);
29405
- var functionUncurryThisRaw = functionBindNative ? uncurryThisWithBind : function (fn) {
29866
+ var functionUncurryThis = functionBindNative ? uncurryThisWithBind : function (fn) {
29406
29867
  return function () {
29407
29868
  return call$2.apply(fn, arguments);
29408
29869
  };
29409
29870
  };
29410
29871
 
29411
- var toString$1 = functionUncurryThisRaw({}.toString);
29412
- var stringSlice = functionUncurryThisRaw(''.slice);
29413
- var classofRaw = function (it) {
29414
- return stringSlice(toString$1(it), 8, -1);
29415
- };
29416
-
29417
- var functionUncurryThis = function (fn) {
29418
- // Nashorn bug:
29419
- // https://github.com/zloirock/core-js/issues/1128
29420
- // https://github.com/zloirock/core-js/issues/1130
29421
- if (classofRaw(fn) === 'Function') return functionUncurryThisRaw(fn);
29422
- };
29423
-
29424
29872
  // we can't use just `it == null` since of `document.all` special case
29425
29873
  // https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot-aec
29426
29874
  var isNullOrUndefined = function (it) {
@@ -29643,19 +30091,19 @@
29643
30091
  (module.exports = function (key, value) {
29644
30092
  return sharedStore[key] || (sharedStore[key] = value !== undefined ? value : {});
29645
30093
  })('versions', []).push({
29646
- version: '3.26.0',
30094
+ version: '3.26.1',
29647
30095
  mode: 'global',
29648
30096
  copyright: '© 2014-2022 Denis Pushkarev (zloirock.ru)',
29649
- license: 'https://github.com/zloirock/core-js/blob/v3.26.0/LICENSE',
30097
+ license: 'https://github.com/zloirock/core-js/blob/v3.26.1/LICENSE',
29650
30098
  source: 'https://github.com/zloirock/core-js'
29651
30099
  });
29652
30100
  });
29653
30101
 
29654
30102
  var id = 0;
29655
30103
  var postfix = Math.random();
29656
- var toString = functionUncurryThis(1.0.toString);
30104
+ var toString$1 = functionUncurryThis(1.0.toString);
29657
30105
  var uid = function (key) {
29658
- return 'Symbol(' + (key === undefined ? '' : key) + ')_' + toString(++id + postfix, 36);
30106
+ return 'Symbol(' + (key === undefined ? '' : key) + ')_' + toString$1(++id + postfix, 36);
29659
30107
  };
29660
30108
 
29661
30109
  var WellKnownSymbolsStore = shared('wks');
@@ -30659,368 +31107,6 @@
30659
31107
  }
30660
31108
  }
30661
31109
 
30662
- const appleCrayonRGBPalette = {
30663
- cantaloupe: {
30664
- r: 255,
30665
- g: 206,
30666
- b: 110
30667
- },
30668
- honeydew: {
30669
- r: 206,
30670
- g: 250,
30671
- b: 110
30672
- },
30673
- spindrift: {
30674
- r: 104,
30675
- g: 251,
30676
- b: 208
30677
- },
30678
- sky: {
30679
- r: 106,
30680
- g: 207,
30681
- b: 255
30682
- },
30683
- lavender: {
30684
- r: 210,
30685
- g: 120,
30686
- b: 255
30687
- },
30688
- carnation: {
30689
- r: 255,
30690
- g: 127,
30691
- b: 211
30692
- },
30693
- licorice: {
30694
- r: 0,
30695
- g: 0,
30696
- b: 0
30697
- },
30698
- snow: {
30699
- r: 255,
30700
- g: 255,
30701
- b: 255
30702
- },
30703
- salmon: {
30704
- r: 255,
30705
- g: 114,
30706
- b: 110
30707
- },
30708
- banana: {
30709
- r: 255,
30710
- g: 251,
30711
- b: 109
30712
- },
30713
- flora: {
30714
- r: 104,
30715
- g: 249,
30716
- b: 110
30717
- },
30718
- ice: {
30719
- r: 104,
30720
- g: 253,
30721
- b: 255
30722
- },
30723
- orchid: {
30724
- r: 110,
30725
- g: 118,
30726
- b: 255
30727
- },
30728
- bubblegum: {
30729
- r: 255,
30730
- g: 122,
30731
- b: 255
30732
- },
30733
- lead: {
30734
- r: 30,
30735
- g: 30,
30736
- b: 30
30737
- },
30738
- mercury: {
30739
- r: 232,
30740
- g: 232,
30741
- b: 232
30742
- },
30743
- tangerine: {
30744
- r: 255,
30745
- g: 136,
30746
- b: 2
30747
- },
30748
- lime: {
30749
- r: 131,
30750
- g: 249,
30751
- b: 2
30752
- },
30753
- sea_foam: {
30754
- r: 3,
30755
- g: 249,
30756
- b: 135
30757
- },
30758
- aqua: {
30759
- r: 0,
30760
- g: 140,
30761
- b: 255
30762
- },
30763
- grape: {
30764
- r: 137,
30765
- g: 49,
30766
- b: 255
30767
- },
30768
- strawberry: {
30769
- r: 255,
30770
- g: 41,
30771
- b: 135
30772
- },
30773
- tungsten: {
30774
- r: 58,
30775
- g: 58,
30776
- b: 58
30777
- },
30778
- silver: {
30779
- r: 208,
30780
- g: 208,
30781
- b: 208
30782
- },
30783
- maraschino: {
30784
- r: 255,
30785
- g: 33,
30786
- b: 1
30787
- },
30788
- lemon: {
30789
- r: 255,
30790
- g: 250,
30791
- b: 3
30792
- },
30793
- spring: {
30794
- r: 5,
30795
- g: 248,
30796
- b: 2
30797
- },
30798
- turquoise: {
30799
- r: 0,
30800
- g: 253,
30801
- b: 255
30802
- },
30803
- blueberry: {
30804
- r: 0,
30805
- g: 46,
30806
- b: 255
30807
- },
30808
- magenta: {
30809
- r: 255,
30810
- g: 57,
30811
- b: 255
30812
- },
30813
- iron: {
30814
- r: 84,
30815
- g: 84,
30816
- b: 83
30817
- },
30818
- magnesium: {
30819
- r: 184,
30820
- g: 184,
30821
- b: 184
30822
- },
30823
- mocha: {
30824
- r: 137,
30825
- g: 72,
30826
- b: 0
30827
- },
30828
- fern: {
30829
- r: 69,
30830
- g: 132,
30831
- b: 1
30832
- },
30833
- moss: {
30834
- r: 1,
30835
- g: 132,
30836
- b: 72
30837
- },
30838
- ocean: {
30839
- r: 0,
30840
- g: 74,
30841
- b: 136
30842
- },
30843
- eggplant: {
30844
- r: 73,
30845
- g: 26,
30846
- b: 136
30847
- },
30848
- maroon: {
30849
- r: 137,
30850
- g: 22,
30851
- b: 72
30852
- },
30853
- steel: {
30854
- r: 110,
30855
- g: 110,
30856
- b: 110
30857
- },
30858
- aluminum: {
30859
- r: 160,
30860
- g: 159,
30861
- b: 160
30862
- },
30863
- cayenne: {
30864
- r: 137,
30865
- g: 17,
30866
- b: 0
30867
- },
30868
- aspargus: {
30869
- r: 136,
30870
- g: 133,
30871
- b: 1
30872
- },
30873
- clover: {
30874
- r: 2,
30875
- g: 132,
30876
- b: 1
30877
- },
30878
- teal: {
30879
- r: 0,
30880
- g: 134,
30881
- b: 136
30882
- },
30883
- midnight: {
30884
- r: 0,
30885
- g: 24,
30886
- b: 136
30887
- },
30888
- plum: {
30889
- r: 137,
30890
- g: 30,
30891
- b: 136
30892
- },
30893
- tin: {
30894
- r: 135,
30895
- g: 134,
30896
- b: 135
30897
- },
30898
- nickel: {
30899
- r: 136,
30900
- g: 135,
30901
- b: 135
30902
- }
30903
- };
30904
- function appleCrayonRGB(name) {
30905
- const {
30906
- r,
30907
- g,
30908
- b
30909
- } = appleCrayonRGBPalette[name];
30910
- return `rgb(${r},${g},${b})`;
30911
- }
30912
- function appleCrayonRGBA(name, alpha) {
30913
- const {
30914
- r,
30915
- g,
30916
- b
30917
- } = appleCrayonRGBPalette[name];
30918
- return `rgba(${r},${g},${b},${alpha})`;
30919
- }
30920
- const colorPalettes = {
30921
- Set1: ["rgb(228,26,28)", "rgb(55,126,184)", "rgb(77,175,74)", "rgb(166,86,40)", "rgb(152,78,163)", "rgb(255,127,0)", "rgb(247,129,191)", "rgb(153,153,153)", "rgb(255,255,51)"],
30922
- Dark2: ["rgb(27,158,119)", "rgb(217,95,2)", "rgb(117,112,179)", "rgb(231,41,138)", "rgb(102,166,30)", "rgb(230,171,2)", "rgb(166,118,29)", "rgb(102,102,102)"],
30923
- Set2: ["rgb(102, 194,165)", "rgb(252,141,98)", "rgb(141,160,203)", "rgb(231,138,195)", "rgb(166,216,84)", "rgb(255,217,47)", "rgb(229,196,148)", "rgb(179,179,179)"],
30924
- Set3: ["rgb(141,211,199)", "rgb(255,255,179)", "rgb(190,186,218)", "rgb(251,128,114)", "rgb(128,177,211)", "rgb(253,180,98)", "rgb(179,222,105)", "rgb(252,205,229)", "rgb(217,217,217)", "rgb(188,128,189)", "rgb(204,235,197)", "rgb(255,237,111)"],
30925
- Pastel1: ["rgb(251,180,174)", "rgb(179,205,227)", "rgb(204,235,197)", "rgb(222,203,228)", "rgb(254,217,166)", "rgb(255,255,204)", "rgb(229,216,189)", "rgb(253,218,236)"],
30926
- Pastel2: ["rgb(173,226,207)", "rgb(253,205,172)", "rgb(203,213,232)", "rgb(244,202,228)", "rgb(230,245,201)", "rgb(255,242,174)", "rgb(243,225,206)"],
30927
- Accent: ["rgb(127,201,127)", "rgb(190,174,212)", "rgb(253,192,134)", "rgb(255,255,153)", "rgb(56,108,176)", "rgb(240,2,127)", "rgb(191,91,23)"]
30928
- };
30929
- class PaletteColorTable {
30930
- constructor(palette) {
30931
- this.colors = colorPalettes[palette];
30932
- if (!Array.isArray(this.colors)) this.colors = [];
30933
- this.colorTable = {};
30934
- this.nextIdx = 0;
30935
- this.colorGenerator = new RandomColorGenerator();
30936
- }
30937
- getColor(key) {
30938
- if (!this.colorTable.hasOwnProperty(key)) {
30939
- if (this.nextIdx < this.colors.length) {
30940
- this.colorTable[key] = this.colors[this.nextIdx];
30941
- } else {
30942
- this.colorTable[key] = this.colorGenerator.get();
30943
- }
30944
- this.nextIdx++;
30945
- }
30946
- return this.colorTable[key];
30947
- }
30948
- }
30949
- class ColorTable {
30950
- constructor(colors) {
30951
- this.colorTable = colors || {};
30952
- this.nextIdx = 0;
30953
- this.colorGenerator = new RandomColorGenerator();
30954
- }
30955
- getColor(key) {
30956
- if (!this.colorTable.hasOwnProperty(key)) {
30957
- if (this.colorTable.hasOwnProperty("*")) {
30958
- return this.colorTable["*"];
30959
- }
30960
- this.colorTable[key] = this.colorGenerator.get();
30961
- }
30962
- return this.colorTable[key];
30963
- }
30964
- }
30965
-
30966
- // Random color generator from https://github.com/sterlingwes/RandomColor/blob/master/rcolor.js
30967
- // Free to use & distribute under the MIT license
30968
- // Wes Johnson (@SterlingWes)
30969
- //
30970
- // inspired by http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
30971
- function RandomColorGenerator() {
30972
- this.hue = Math.random();
30973
- this.goldenRatio = 0.618033988749895;
30974
- this.hexwidth = 2;
30975
- }
30976
- RandomColorGenerator.prototype.hsvToRgb = function (h, s, v) {
30977
- var h_i = Math.floor(h * 6),
30978
- f = h * 6 - h_i,
30979
- p = v * (1 - s),
30980
- q = v * (1 - f * s),
30981
- t = v * (1 - (1 - f) * s),
30982
- r = 255,
30983
- g = 255,
30984
- b = 255;
30985
- switch (h_i) {
30986
- case 0:
30987
- r = v, g = t, b = p;
30988
- break;
30989
- case 1:
30990
- r = q, g = v, b = p;
30991
- break;
30992
- case 2:
30993
- r = p, g = v, b = t;
30994
- break;
30995
- case 3:
30996
- r = p, g = q, b = v;
30997
- break;
30998
- case 4:
30999
- r = t, g = p, b = v;
31000
- break;
31001
- case 5:
31002
- r = v, g = p, b = q;
31003
- break;
31004
- }
31005
- return [Math.floor(r * 256), Math.floor(g * 256), Math.floor(b * 256)];
31006
- };
31007
- RandomColorGenerator.prototype.padHex = function (str) {
31008
- if (str.length > this.hexwidth) return str;
31009
- return new Array(this.hexwidth - str.length + 1).join('0') + str;
31010
- };
31011
- RandomColorGenerator.prototype.get = function (saturation, value) {
31012
- this.hue += this.goldenRatio;
31013
- this.hue %= 1;
31014
- if (typeof saturation !== "number") saturation = 0.5;
31015
- if (typeof value !== "number") value = 0.95;
31016
- var rgb = this.hsvToRgb(this.hue, saturation, value);
31017
- return "#" + this.padHex(rgb[0].toString(16)) + this.padHex(rgb[1].toString(16)) + this.padHex(rgb[2].toString(16));
31018
- };
31019
- const randomColorGenerator = new RandomColorGenerator();
31020
- function randomColor() {
31021
- return randomColorGenerator.get();
31022
- }
31023
-
31024
31110
  /*
31025
31111
  * The MIT License (MIT)
31026
31112
  *
@@ -33546,6 +33632,12 @@
33546
33632
  f: f$3
33547
33633
  };
33548
33634
 
33635
+ var toString = functionUncurryThis({}.toString);
33636
+ var stringSlice = functionUncurryThis(''.slice);
33637
+ var classofRaw = function (it) {
33638
+ return stringSlice(toString(it), 8, -1);
33639
+ };
33640
+
33549
33641
  var $Object = Object;
33550
33642
  var split = functionUncurryThis(''.split);
33551
33643
 
@@ -33803,7 +33895,14 @@
33803
33895
  return call.apply(apply, arguments);
33804
33896
  });
33805
33897
 
33806
- var bind = functionUncurryThis(functionUncurryThis.bind);
33898
+ var functionUncurryThisClause = function (fn) {
33899
+ // Nashorn bug:
33900
+ // https://github.com/zloirock/core-js/issues/1128
33901
+ // https://github.com/zloirock/core-js/issues/1130
33902
+ if (classofRaw(fn) === 'Function') return functionUncurryThis(fn);
33903
+ };
33904
+
33905
+ var bind = functionUncurryThisClause(functionUncurryThisClause.bind);
33807
33906
 
33808
33907
  // optional / simple context binding
33809
33908
  var functionBindContext = function (fn, that) {
@@ -53173,7 +53272,14 @@
53173
53272
  const lastLabelX = options.rowLastLabelX[feature.row] || -Number.MAX_SAFE_INTEGER;
53174
53273
  if (options.labelAllFeatures || xleft > lastLabelX || gtexSelection) {
53175
53274
  options.rowLastLabelX[feature.row] = xright;
53176
- IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
53275
+ if ('y' === options.axis) {
53276
+ ctx.save();
53277
+ IGVGraphics.labelTransformWithContext(ctx, centerX);
53278
+ IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
53279
+ ctx.restore();
53280
+ } else {
53281
+ IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
53282
+ }
53177
53283
  }
53178
53284
  } finally {
53179
53285
  ctx.restore();
@@ -58609,14 +58715,6 @@
58609
58715
  });
58610
58716
  }
58611
58717
 
58612
- const defaultNucleotideColors = {
58613
- "A": "rgb( 0, 200, 0)",
58614
- "C": "rgb( 0,0,200)",
58615
- "T": "rgb(255,0,0)",
58616
- "G": "rgb(209,113, 5)",
58617
- "N": "rgb(80,80,80)"
58618
- };
58619
-
58620
58718
  const DEFAULT_SEARCH_CONFIG = {
58621
58719
  timeout: 5000,
58622
58720
  type: "plain",
@@ -58919,8 +59017,8 @@
58919
59017
  constructor(browser) {
58920
59018
  this.browser = browser;
58921
59019
  }
58922
- navbarDidResize(width, isWholeGenomeView) {
58923
- this.updateNavbar(this.createResponsiveClassSchedule(width, isWholeGenomeView));
59020
+ navbarDidResize(width) {
59021
+ this.updateNavbar(this.createResponsiveClassSchedule(width));
58924
59022
  }
58925
59023
  updateNavbar(responsiveClassSchedule) {
58926
59024
  this.browser.$toggle_button_container.removeClass();
@@ -58928,9 +59026,10 @@
58928
59026
  $$1(this.browser.zoomWidget.zoomContainer).removeClass();
58929
59027
  $$1(this.browser.zoomWidget.zoomContainer).addClass(responsiveClassSchedule.zoomContainer);
58930
59028
  }
58931
- createResponsiveClassSchedule(navbarWidth, isWholeGenomeView) {
59029
+ createResponsiveClassSchedule(navbarWidth) {
58932
59030
  let candidates = {};
58933
- if (isWholeGenomeView) {
59031
+ const isWGV = this.browser.isMultiLocusWholeGenomeView() || this.browser.referenceFrameList && GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr);
59032
+ if (isWGV) {
58934
59033
  this.browser.windowSizePanel.hide();
58935
59034
  } else {
58936
59035
  this.browser.windowSizePanel.show();
@@ -58949,7 +59048,7 @@
58949
59048
  candidates.zoomContainer = 'igv-zoom-widget-900';
58950
59049
  this.browser.windowSizePanel.hide();
58951
59050
  }
58952
- if (isWholeGenomeView) {
59051
+ if (isWGV) {
58953
59052
  candidates['zoomContainer'] = 'igv-zoom-widget-hidden';
58954
59053
  }
58955
59054
  return candidates;
@@ -59118,40 +59217,33 @@
59118
59217
  } = translateMouseCoordinates$1(event, this.columnContainer);
59119
59218
  this.horizontalGuide.style.top = `${y}px`;
59120
59219
  const target = document.elementFromPoint(event.clientX, event.clientY);
59121
- let viewport = undefined;
59122
- if (target.parentElement) {
59123
- if (target.parentElement.classList.contains('igv-viewport-content')) {
59124
- viewport = target.parentElement.parentElement;
59125
- } else if (target.parentElement.classList.contains('igv-viewport') && target.classList.contains('igv-viewport-content')) {
59126
- viewport = target.parentElement;
59127
- }
59128
- if (viewport && browser.getRulerTrackView()) {
59129
- this.verticalGuide.style.left = `${x}px`;
59130
- const columns = browser.root.querySelectorAll('.igv-column');
59131
- let index = undefined;
59132
- const viewportParent = viewport.parentElement;
59133
- for (let i = 0; i < columns.length; i++) {
59134
- if (undefined === index && viewportParent === columns[i]) {
59135
- index = i;
59136
- }
59137
- }
59138
- const rulerViewport = browser.getRulerTrackView().viewports[index];
59139
- const result = rulerViewport.mouseMove(event);
59140
- if (result) {
59141
- const {
59220
+ const viewport = findAncestorOfClass(target, 'igv-viewport');
59221
+ if (viewport && browser.getRulerTrackView()) {
59222
+ this.verticalGuide.style.left = `${x}px`;
59223
+ const columns = browser.root.querySelectorAll('.igv-column');
59224
+ let index = undefined;
59225
+ const viewportParent = viewport.parentElement;
59226
+ for (let i = 0; i < columns.length; i++) {
59227
+ if (undefined === index && viewportParent === columns[i]) {
59228
+ index = i;
59229
+ }
59230
+ }
59231
+ const rulerViewport = browser.getRulerTrackView().viewports[index];
59232
+ const result = rulerViewport.mouseMove(event);
59233
+ if (result) {
59234
+ const {
59235
+ start,
59236
+ bp,
59237
+ end
59238
+ } = result;
59239
+ const interpolant = (bp - start) / (end - start);
59240
+ if (this.customMouseHandler) {
59241
+ this.customMouseHandler({
59142
59242
  start,
59143
59243
  bp,
59144
- end
59145
- } = result;
59146
- const interpolant = (bp - start) / (end - start);
59147
- if (this.customMouseHandler) {
59148
- this.customMouseHandler({
59149
- start,
59150
- bp,
59151
- end,
59152
- interpolant
59153
- });
59154
- }
59244
+ end,
59245
+ interpolant
59246
+ });
59155
59247
  }
59156
59248
  }
59157
59249
  }
@@ -59182,6 +59274,23 @@
59182
59274
  }
59183
59275
  }
59184
59276
 
59277
+ /**
59278
+ * Walk up the tree until a parent is found with the given classname. If no ancestor is found return undefined.
59279
+ * @param target
59280
+ * @param classname
59281
+ * @returns {*}
59282
+ */
59283
+ function findAncestorOfClass(target, classname) {
59284
+ while (target.parentElement) {
59285
+ if (target.parentElement.classList.contains(classname)) {
59286
+ return target.parentElement;
59287
+ } else {
59288
+ target = target.parentElement;
59289
+ }
59290
+ }
59291
+ return undefined;
59292
+ }
59293
+
59185
59294
  /*
59186
59295
  * The MIT License (MIT)
59187
59296
  *
@@ -61116,7 +61225,12 @@
61116
61225
 
61117
61226
  // Track gear column
61118
61227
  createColumn(this.columnContainer, 'igv-gear-menu-column');
61119
- const genomeConfig = await GenomeUtils.expandReference(this.alert, session.reference || session.genome);
61228
+ const genomeOrReference = session.reference || session.genome;
61229
+ if (!genomeOrReference) {
61230
+ console.warn("No genome or reference object specified");
61231
+ return;
61232
+ }
61233
+ const genomeConfig = await GenomeUtils.expandReference(this.alert, genomeOrReference);
61120
61234
  await this.loadReference(genomeConfig, session.locus);
61121
61235
  this.centerLineList = this.createCenterLineList(this.columnContainer);
61122
61236
 
@@ -61171,8 +61285,8 @@
61171
61285
  const genomeTracks = genomeConfig.tracks || [];
61172
61286
  const trackConfigurations = session.tracks ? genomeTracks.concat(session.tracks) : genomeTracks;
61173
61287
 
61174
- // Insure that we always have a sequence track
61175
- const pushSequenceTrack = trackConfigurations.filter(track => track.type === 'sequence').length === 0;
61288
+ // Insure that we always have a sequence track with no explicit URL (=> the reference genome sequence track)
61289
+ const pushSequenceTrack = trackConfigurations.filter(track => 'sequence' === track.type && !track.url && !track.fastaURL).length === 0;
61176
61290
  if (pushSequenceTrack /*&& false !== this.config.showSequence*/) {
61177
61291
  trackConfigurations.push({
61178
61292
  type: "sequence",
@@ -61500,13 +61614,15 @@
61500
61614
  */
61501
61615
  async createTrack(config) {
61502
61616
  // Resolve function and promise urls
61503
- let url = await resolveURL(config.url);
61617
+ let url = await resolveURL(config.url || config.fastaURL);
61504
61618
  if (isString$2(url)) {
61505
61619
  url = url.trim();
61506
61620
  }
61507
61621
  if (url) {
61508
61622
  if (config.format) {
61509
61623
  config.format = config.format.toLowerCase();
61624
+ } else if (config.fastaURL) {
61625
+ config.format = "fasta"; // by definition
61510
61626
  } else {
61511
61627
  let filename = config.filename;
61512
61628
  if (!filename) {
@@ -61518,7 +61634,7 @@
61518
61634
  } else if (format) {
61519
61635
  config.format = format;
61520
61636
  } else {
61521
- if (config.sourceType === undefined || config.sourceType === "htsget") {
61637
+ if (config.sourceType === "htsget") {
61522
61638
  // Check for htsget URL. This is a longshot
61523
61639
  await HtsgetReader.inferFormat(config);
61524
61640
  }
@@ -62570,8 +62686,7 @@
62570
62686
  } else {
62571
62687
  await browser.loadSessionObject(config);
62572
62688
  }
62573
- const isWGV = browser.isMultiLocusWholeGenomeView() || GenomeUtils.isWholeGenomeView(browser.referenceFrameList[0].chr);
62574
- browser.navbarManager.navbarDidResize(browser.$navigation.width(), isWGV);
62689
+ browser.navbarManager.navbarDidResize(browser.$navigation.width());
62575
62690
  return browser;
62576
62691
  }
62577
62692
  function removeBrowser(browser) {
@@ -62728,7 +62843,7 @@
62728
62843
  }
62729
62844
 
62730
62845
  function embedCSS() {
62731
- var css = '.igv-navbar {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n width: 100%;\n color: #444;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n line-height: 32px;\n padding-left: 8px;\n padding-right: 8px;\n margin-top: 2px;\n margin-bottom: 6px;\n height: 32px;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: #f3f3f3;\n}\n.igv-navbar .igv-navbar-left-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-left-container .igv-logo {\n width: 34px;\n height: 32px;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-left-container .igv-current-genome {\n height: 32px;\n margin-left: 4px;\n margin-right: 4px;\n user-select: none;\n line-height: 32px;\n vertical-align: middle;\n text-align: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n height: 100%;\n width: 125px;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container select {\n display: block;\n cursor: pointer;\n width: 100px;\n height: 75%;\n outline: none;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n margin-left: 8px;\n height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 210px;\n height: 22px;\n line-height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container input.igv-search-input {\n cursor: text;\n width: 85%;\n height: 22px;\n line-height: 22px;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n text-align: left;\n padding-left: 8px;\n margin-right: 8px;\n outline: none;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: white;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container .igv-search-icon-container {\n cursor: pointer;\n height: 16px;\n width: 16px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-windowsize-panel-container {\n margin-left: 4px;\n user-select: none;\n}\n.igv-navbar .igv-navbar-right-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container div {\n margin-left: 0;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container div:last-child {\n margin-left: 0;\n margin-right: 0;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container-750 {\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:first-child {\n height: 24px;\n width: 24px;\n margin-left: unset;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:last-child {\n height: 24px;\n width: 24px;\n margin-left: 8px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:nth-child(even) {\n display: block;\n height: fit-content;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget input {\n display: block;\n width: 125px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:first-child {\n height: 24px;\n width: 24px;\n margin-left: unset;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:last-child {\n height: 24px;\n width: 24px;\n margin-left: 8px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:nth-child(even) {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 input {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-hidden {\n display: none;\n}\n\n.igv-navbar-button {\n display: block;\n box-sizing: unset;\n padding-left: 6px;\n padding-right: 6px;\n height: 18px;\n text-transform: capitalize;\n user-select: none;\n line-height: 18px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 11px;\n font-weight: 200;\n color: #737373;\n background-color: #f3f3f3;\n border-color: #737373;\n border-style: solid;\n border-width: thin;\n border-radius: 6px;\n}\n\n.igv-navbar-button-clicked {\n color: white;\n background-color: #737373;\n}\n\n.igv-navbar-button:hover {\n cursor: pointer;\n}\n\n.igv-zoom-in-notice-container {\n z-index: 1024;\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translate(-50%, 0%);\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n background-color: white;\n}\n.igv-zoom-in-notice-container > div {\n padding-left: 4px;\n padding-right: 4px;\n padding-top: 2px;\n padding-bottom: 2px;\n width: 100%;\n height: 100%;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: #3f3f3f;\n}\n\n.igv-zoom-in-notice {\n position: absolute;\n top: 10px;\n left: 50%;\n}\n.igv-zoom-in-notice div {\n position: relative;\n left: -50%;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #3f3f3f;\n background-color: rgba(255, 255, 255, 0.51);\n z-index: 64;\n}\n\n.igv-container-spinner {\n position: absolute;\n top: 90%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1024;\n width: 24px;\n height: 24px;\n pointer-events: none;\n color: #737373;\n}\n\n.igv-multi-locus-close-button {\n position: absolute;\n top: 2px;\n right: 0;\n padding-left: 2px;\n padding-right: 2px;\n width: 18px;\n height: 18px;\n color: #666666;\n background-color: white;\n z-index: 1000;\n}\n.igv-multi-locus-close-button > svg {\n vertical-align: top;\n}\n\n.igv-multi-locus-close-button:hover {\n cursor: pointer;\n color: #434343;\n}\n\n.igv-multi-locus-ruler-label {\n z-index: 64;\n position: absolute;\n top: 2px;\n left: 0;\n width: 100%;\n height: 14px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-multi-locus-ruler-label div {\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n background-color: white;\n}\n\n.igv-multi-locus-ruler-label-square-dot {\n z-index: 64;\n position: absolute;\n left: 50%;\n top: 5%;\n transform: translate(-50%, 0%);\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-multi-locus-ruler-label-square-dot > div:first-child {\n width: 14px;\n height: 14px;\n}\n.igv-multi-locus-ruler-label-square-dot > div:last-child {\n margin-left: 16px;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n}\n\n.igv-multi-locus-ruler-label:hover {\n cursor: pointer;\n}\n\n.igv-ruler-sweeper {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 26px;\n bottom: 0;\n left: 0;\n width: 0;\n z-index: 99999;\n background-color: rgba(68, 134, 247, 0.25);\n}\n\n.igv-ruler-tooltip {\n pointer-events: none;\n z-index: 128;\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 32px;\n background-color: transparent;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ruler-tooltip > div {\n pointer-events: none;\n width: 128px;\n height: auto;\n padding: 1px;\n color: #373737;\n font-size: 10px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: #373737;\n}\n\n.igv-track-label {\n position: absolute;\n left: 8px;\n top: 8px;\n width: auto;\n height: auto;\n max-width: 200px;\n padding-left: 4px;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n text-align: center;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-color: #444;\n border-radius: 2px;\n border-style: solid;\n border-width: thin;\n background-color: white;\n z-index: 128;\n cursor: pointer;\n}\n\n.igv-track-label:hover,\n.igv-track-label:focus,\n.igv-track-label:active {\n background-color: #e8e8e8;\n}\n\n.igv-track-label-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-top: 4px;\n}\n\n.igv-center-line {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-left-style: dashed;\n border-left-width: thin;\n border-right-style: dashed;\n border-right-width: thin;\n}\n\n.igv-center-line-wide {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(127, 127, 127, 0.51);\n}\n\n.igv-center-line-thin {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(0, 0, 0, 0);\n}\n\n.igv-cursor-guide-horizontal {\n display: none;\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 1px;\n z-index: 1;\n margin-left: 50px;\n margin-right: 54px;\n border-top-style: dotted;\n border-top-width: thin;\n border-top-color: rgba(127, 127, 127, 0.76);\n}\n\n.igv-cursor-guide-vertical {\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n z-index: 1;\n border-left-style: dotted;\n border-left-width: thin;\n border-left-color: rgba(127, 127, 127, 0.76);\n display: none;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-generic-dialog-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 300px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-generic-dialog-container .igv-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input div {\n width: 30%;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div {\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-generic-container {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-container div:first-child {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-generic-container div:first-child i {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-menu-popup {\n position: absolute;\n top: 0;\n left: 0;\n width: max-content;\n z-index: 4096;\n cursor: pointer;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background: white;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-end;\n text-align: left;\n}\n.igv-menu-popup > div:not(:first-child) {\n width: 100%;\n}\n.igv-menu-popup > div:not(:first-child) > div {\n background: white;\n}\n.igv-menu-popup > div:not(:first-child) > div.context-menu {\n padding-left: 4px;\n padding-right: 4px;\n}\n.igv-menu-popup > div:not(:first-child) > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-menu-popup > div:not(:first-child) > div:hover {\n background: #efefef;\n}\n\n.igv-menu-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-bottom: 1px;\n padding-top: 1px;\n}\n\n.igv-menu-popup-header {\n position: relative;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-menu-popup-header div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-menu-popup-header div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-menu-popup-check-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 20px;\n margin-right: 4px;\n background-color: transparent;\n}\n.igv-menu-popup-check-container div {\n padding-top: 2px;\n padding-left: 8px;\n}\n.igv-menu-popup-check-container div:first-child {\n position: relative;\n width: 12px;\n height: 12px;\n}\n.igv-menu-popup-check-container div:first-child svg {\n position: absolute;\n width: 12px;\n height: 12px;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-loading-spinner-container {\n z-index: 1024;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 32px;\n height: 32px;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-loading-spinner-container > div {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 4px solid rgba(128, 128, 128, 0.5);\n border-top-color: rgb(255, 255, 255);\n animation: spin 1s ease-in-out infinite;\n -webkit-animation: spin 1s ease-in-out infinite;\n}\n\n@keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-webkit-keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n.igv-roi-menu-next-gen {\n position: absolute;\n z-index: 512;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background-color: white;\n width: 192px;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu-next-gen > div:first-child {\n height: 24px;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-roi-menu-next-gen > div:first-child > div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-roi-menu-next-gen > div:first-child > div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-menu-next-gen > div:last-child {\n background-color: white;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n text-align: start;\n vertical-align: middle;\n cursor: pointer;\n}\n.igv-roi-menu-next-gen > div:last-child > div {\n height: 24px;\n padding-left: 4px;\n border-bottom-style: solid;\n border-bottom-width: thin;\n border-bottom-color: #7f7f7f;\n}\n.igv-roi-menu-next-gen > div:last-child > div:not(:first-child):hover {\n background-color: rgba(127, 127, 127, 0.1);\n}\n.igv-roi-menu-next-gen > div:last-child div:first-child {\n cursor: default;\n font-style: italic;\n text-align: center;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-menu-next-gen > div:last-child > div:last-child {\n border-bottom-width: 0;\n border-bottom-color: transparent;\n}\n\n.igv-roi-placeholder {\n font-style: normal;\n color: rgba(75, 75, 75, 0.6);\n}\n\n.igv-roi-table {\n position: absolute;\n z-index: 1024;\n width: fit-content;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-table > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table > div:first-child > div:first-child {\n text-align: center;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: center;\n margin-left: 4px;\n margin-right: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(1) {\n width: 20%;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(2) {\n width: 15%;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(3) {\n width: 15%;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(4) {\n width: 30%;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(5) {\n width: 20%;\n}\n.igv-roi-table > .igv-roi-table-row-container {\n resize: both;\n overflow: scroll;\n min-width: 512px;\n min-height: 72px;\n height: 144px;\n max-height: 480px;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: center;\n margin-left: 4px;\n margin-right: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(1) {\n width: 20%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(2) {\n width: 15%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(3) {\n width: 15%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(4) {\n width: 30%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(5) {\n width: 20%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table > div:last-child {\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-four-column {\n position: absolute;\n z-index: 1024;\n width: fit-content;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-table-four-column > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table-four-column > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table-four-column > div:first-child > div:first-child {\n text-align: center;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table-four-column > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table-four-column > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table-four-column > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: center;\n margin-left: 4px;\n margin-right: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div:nth-child(1) {\n width: 25%;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div:nth-child(2) {\n width: 20%;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div:nth-child(3) {\n width: 20%;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div:nth-child(4) {\n width: 35%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container {\n resize: both;\n overflow: scroll;\n min-width: 512px;\n min-height: 72px;\n height: 144px;\n max-height: 480px;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: center;\n margin-left: 4px;\n margin-right: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(1) {\n width: 25%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(2) {\n width: 20%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(3) {\n width: 20%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(4) {\n width: 35%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table-four-column > div:last-child {\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-row-selected {\n background-color: rgba(0, 0, 0, 0.125);\n}\n\n.igv-roi-table-button {\n height: 20px;\n user-select: none;\n line-height: 20px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 13px;\n font-weight: 400;\n color: black;\n padding-left: 6px;\n padding-right: 6px;\n background-color: rgb(239, 239, 239);\n border-color: black;\n border-style: solid;\n border-width: thin;\n border-radius: 3px;\n}\n\n.igv-roi-table-button:hover {\n cursor: pointer;\n font-weight: 400;\n background-color: rgba(0, 0, 0, 0.13);\n}\n\n.igv-roi-region {\n z-index: 64;\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n overflow: visible;\n margin-top: 44px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-region > div {\n position: relative;\n width: 100%;\n height: 8px;\n cursor: pointer;\n pointer-events: auto;\n}\n\n.igv-roi-menu {\n position: absolute;\n z-index: 1024;\n width: 144px;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu > div:not(:last-child) {\n border-bottom-color: rgba(128, 128, 128, 0.5);\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-menu > div:first-child {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-color: transparent;\n border-top-style: solid;\n border-top-width: 0;\n}\n.igv-roi-menu > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n}\n\n.igv-roi-menu-row {\n height: 24px;\n padding-left: 8px;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n background-color: white;\n}\n\n.igv-roi-menu-row-edit-description {\n width: -webkit-fill-available;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n background-color: white;\n padding-left: 4px;\n padding-right: 4px;\n padding-bottom: 4px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-menu-row-edit-description > label {\n margin-left: 2px;\n margin-bottom: 0;\n display: block;\n width: -webkit-fill-available;\n}\n.igv-roi-menu-row-edit-description > input {\n display: block;\n margin-left: 2px;\n margin-right: 2px;\n margin-bottom: 1px;\n width: -webkit-fill-available;\n}\n\n.igv-container {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n padding-top: 4px;\n user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n}\n\n.igv-viewport {\n position: relative;\n margin-top: 5px;\n overflow-x: hidden;\n overflow-y: hidden;\n}\n\n.igv-viewport-content {\n position: relative;\n width: 100%;\n}\n.igv-viewport-content > canvas {\n position: relative;\n display: block;\n}\n\n.igv-column-container {\n position: relative;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n width: 100%;\n}\n\n.igv-column-shim {\n width: 1px;\n margin-left: 2px;\n margin-right: 2px;\n background-color: #545453;\n}\n\n.igv-column {\n position: relative;\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-axis-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 50px;\n}\n.igv-axis-column > div {\n margin-top: 5px;\n width: 100%;\n}\n\n.igv-sample-name-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-scrollbar-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 14px;\n}\n.igv-scrollbar-column > div {\n position: relative;\n margin-top: 5px;\n width: 14px;\n}\n.igv-scrollbar-column > div > div {\n cursor: pointer;\n position: absolute;\n top: 0;\n left: 2px;\n width: 8px;\n border-width: 1px;\n border-style: solid;\n border-color: #c4c4c4;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-scrollbar-column > div > div:hover {\n background-color: #c4c4c4;\n}\n\n.igv-track-drag-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 12px;\n background-color: white;\n}\n.igv-track-drag-column > .igv-track-drag-handle {\n z-index: 512;\n position: relative;\n cursor: pointer;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n background-color: #c4c4c4;\n}\n.igv-track-drag-column .igv-track-drag-handle-hover {\n background-color: #787878;\n}\n.igv-track-drag-column > .igv-track-drag-shim {\n position: relative;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n}\n\n.igv-gear-menu-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 28px;\n}\n.igv-gear-menu-column > div {\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n margin-top: 5px;\n width: 100%;\n background: white;\n}\n.igv-gear-menu-column > div > div {\n position: relative;\n margin-top: 4px;\n width: 16px;\n height: 16px;\n color: #7F7F7F;\n}\n.igv-gear-menu-column > div > div:hover {\n cursor: pointer;\n color: #444;\n}\n\n/*# sourceMappingURL=dom.css.map */\n';
62846
+ var css = '.igv-navbar {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n width: 100%;\n color: #444;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n line-height: 32px;\n padding-left: 8px;\n padding-right: 8px;\n margin-top: 2px;\n margin-bottom: 6px;\n height: 32px;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: #f3f3f3;\n}\n.igv-navbar .igv-navbar-left-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-left-container .igv-logo {\n width: 34px;\n height: 32px;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-left-container .igv-current-genome {\n height: 32px;\n margin-left: 4px;\n margin-right: 4px;\n user-select: none;\n line-height: 32px;\n vertical-align: middle;\n text-align: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n height: 100%;\n width: 125px;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container select {\n display: block;\n cursor: pointer;\n width: 100px;\n height: 75%;\n outline: none;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n margin-left: 8px;\n height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 210px;\n height: 22px;\n line-height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container input.igv-search-input {\n cursor: text;\n width: 85%;\n height: 22px;\n line-height: 22px;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n text-align: left;\n padding-left: 8px;\n margin-right: 8px;\n outline: none;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: white;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container .igv-search-icon-container {\n cursor: pointer;\n height: 16px;\n width: 16px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-windowsize-panel-container {\n margin-left: 4px;\n user-select: none;\n}\n.igv-navbar .igv-navbar-right-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container div {\n margin-left: 0;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container div:last-child {\n margin-left: 0;\n margin-right: 0;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container-750 {\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:first-child {\n height: 24px;\n width: 24px;\n margin-left: unset;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:last-child {\n height: 24px;\n width: 24px;\n margin-left: 8px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:nth-child(even) {\n display: block;\n height: fit-content;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget input {\n display: block;\n width: 125px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:first-child {\n height: 24px;\n width: 24px;\n margin-left: unset;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:last-child {\n height: 24px;\n width: 24px;\n margin-left: 8px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:nth-child(even) {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 input {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-hidden {\n display: none;\n}\n\n.igv-navbar-button {\n display: block;\n box-sizing: unset;\n padding-left: 6px;\n padding-right: 6px;\n height: 18px;\n text-transform: capitalize;\n user-select: none;\n line-height: 18px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 11px;\n font-weight: 200;\n color: #737373;\n background-color: #f3f3f3;\n border-color: #737373;\n border-style: solid;\n border-width: thin;\n border-radius: 6px;\n}\n\n.igv-navbar-button-clicked {\n color: white;\n background-color: #737373;\n}\n\n.igv-navbar-button:hover {\n cursor: pointer;\n}\n\n.igv-zoom-in-notice-container {\n z-index: 1024;\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translate(-50%, 0%);\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n background-color: white;\n}\n.igv-zoom-in-notice-container > div {\n padding-left: 4px;\n padding-right: 4px;\n padding-top: 2px;\n padding-bottom: 2px;\n width: 100%;\n height: 100%;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: #3f3f3f;\n}\n\n.igv-zoom-in-notice {\n position: absolute;\n top: 10px;\n left: 50%;\n}\n.igv-zoom-in-notice div {\n position: relative;\n left: -50%;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #3f3f3f;\n background-color: rgba(255, 255, 255, 0.51);\n z-index: 64;\n}\n\n.igv-container-spinner {\n position: absolute;\n top: 90%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1024;\n width: 24px;\n height: 24px;\n pointer-events: none;\n color: #737373;\n}\n\n.igv-multi-locus-close-button {\n position: absolute;\n top: 2px;\n right: 0;\n padding-left: 2px;\n padding-right: 2px;\n width: 18px;\n height: 18px;\n color: #666666;\n background-color: white;\n z-index: 1000;\n}\n.igv-multi-locus-close-button > svg {\n vertical-align: top;\n}\n\n.igv-multi-locus-close-button:hover {\n cursor: pointer;\n color: #434343;\n}\n\n.igv-multi-locus-ruler-label {\n z-index: 64;\n position: absolute;\n top: 2px;\n left: 0;\n width: 100%;\n height: 14px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-multi-locus-ruler-label div {\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n background-color: white;\n}\n\n.igv-multi-locus-ruler-label-square-dot {\n z-index: 64;\n position: absolute;\n left: 50%;\n top: 5%;\n transform: translate(-50%, 0%);\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-multi-locus-ruler-label-square-dot > div:first-child {\n width: 14px;\n height: 14px;\n}\n.igv-multi-locus-ruler-label-square-dot > div:last-child {\n margin-left: 16px;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n}\n\n.igv-multi-locus-ruler-label:hover {\n cursor: pointer;\n}\n\n.igv-ruler-sweeper {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 26px;\n bottom: 0;\n left: 0;\n width: 0;\n z-index: 99999;\n background-color: rgba(68, 134, 247, 0.25);\n}\n\n.igv-ruler-tooltip {\n pointer-events: none;\n z-index: 128;\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 32px;\n background-color: transparent;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ruler-tooltip > div {\n pointer-events: none;\n width: 128px;\n height: auto;\n padding: 1px;\n color: #373737;\n font-size: 10px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: #373737;\n}\n\n.igv-track-label {\n position: absolute;\n left: 8px;\n top: 8px;\n width: auto;\n height: auto;\n max-width: 50%;\n padding-left: 4px;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n text-align: center;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-color: #444;\n border-radius: 2px;\n border-style: solid;\n border-width: thin;\n background-color: white;\n z-index: 128;\n cursor: pointer;\n}\n\n.igv-track-label:hover,\n.igv-track-label:focus,\n.igv-track-label:active {\n background-color: #e8e8e8;\n}\n\n.igv-track-label-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-top: 4px;\n}\n\n.igv-center-line {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-left-style: dashed;\n border-left-width: thin;\n border-right-style: dashed;\n border-right-width: thin;\n}\n\n.igv-center-line-wide {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(127, 127, 127, 0.51);\n}\n\n.igv-center-line-thin {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(0, 0, 0, 0);\n}\n\n.igv-cursor-guide-horizontal {\n display: none;\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 1px;\n z-index: 1;\n margin-left: 50px;\n margin-right: 54px;\n border-top-style: dotted;\n border-top-width: thin;\n border-top-color: rgba(127, 127, 127, 0.76);\n}\n\n.igv-cursor-guide-vertical {\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n z-index: 1;\n border-left-style: dotted;\n border-left-width: thin;\n border-left-color: rgba(127, 127, 127, 0.76);\n display: none;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-generic-dialog-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 300px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-generic-dialog-container .igv-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input div {\n width: 30%;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div {\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-generic-container {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-container div:first-child {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-generic-container div:first-child i {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-menu-popup {\n position: absolute;\n top: 0;\n left: 0;\n width: max-content;\n z-index: 4096;\n cursor: pointer;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background: white;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-end;\n text-align: left;\n}\n.igv-menu-popup > div:not(:first-child) {\n width: 100%;\n}\n.igv-menu-popup > div:not(:first-child) > div {\n background: white;\n}\n.igv-menu-popup > div:not(:first-child) > div.context-menu {\n padding-left: 4px;\n padding-right: 4px;\n}\n.igv-menu-popup > div:not(:first-child) > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-menu-popup > div:not(:first-child) > div:hover {\n background: #efefef;\n}\n\n.igv-menu-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-bottom: 1px;\n padding-top: 1px;\n}\n\n.igv-menu-popup-header {\n position: relative;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-menu-popup-header div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-menu-popup-header div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-menu-popup-check-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 20px;\n margin-right: 4px;\n background-color: transparent;\n}\n.igv-menu-popup-check-container div {\n padding-top: 2px;\n padding-left: 8px;\n}\n.igv-menu-popup-check-container div:first-child {\n position: relative;\n width: 12px;\n height: 12px;\n}\n.igv-menu-popup-check-container div:first-child svg {\n position: absolute;\n width: 12px;\n height: 12px;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-loading-spinner-container {\n z-index: 1024;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 32px;\n height: 32px;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-loading-spinner-container > div {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 4px solid rgba(128, 128, 128, 0.5);\n border-top-color: rgb(255, 255, 255);\n animation: spin 1s ease-in-out infinite;\n -webkit-animation: spin 1s ease-in-out infinite;\n}\n\n@keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-webkit-keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n.igv-roi-menu-next-gen {\n position: absolute;\n z-index: 512;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background-color: white;\n width: 192px;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu-next-gen > div:first-child {\n height: 24px;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-roi-menu-next-gen > div:first-child > div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-roi-menu-next-gen > div:first-child > div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-menu-next-gen > div:last-child {\n background-color: white;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n text-align: start;\n vertical-align: middle;\n cursor: pointer;\n}\n.igv-roi-menu-next-gen > div:last-child > div {\n height: 24px;\n padding-left: 4px;\n border-bottom-style: solid;\n border-bottom-width: thin;\n border-bottom-color: #7f7f7f;\n}\n.igv-roi-menu-next-gen > div:last-child > div:not(:first-child):hover {\n background-color: rgba(127, 127, 127, 0.1);\n}\n.igv-roi-menu-next-gen > div:last-child div:first-child {\n cursor: default;\n font-style: italic;\n text-align: center;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-menu-next-gen > div:last-child > div:last-child {\n border-bottom-width: 0;\n border-bottom-color: transparent;\n}\n\n.igv-roi-placeholder {\n font-style: normal;\n color: rgba(75, 75, 75, 0.6);\n}\n\n.igv-roi-table {\n position: absolute;\n z-index: 1024;\n width: fit-content;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-table > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table > div:first-child > div:first-child {\n text-align: center;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: center;\n margin-left: 4px;\n margin-right: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(1) {\n width: 20%;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(2) {\n width: 15%;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(3) {\n width: 15%;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(4) {\n width: 30%;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:nth-child(5) {\n width: 20%;\n}\n.igv-roi-table > .igv-roi-table-row-container {\n resize: both;\n overflow: scroll;\n min-width: 512px;\n min-height: 72px;\n height: 144px;\n max-height: 480px;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: center;\n margin-left: 4px;\n margin-right: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(1) {\n width: 20%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(2) {\n width: 15%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(3) {\n width: 15%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(4) {\n width: 30%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(5) {\n width: 20%;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table > div:last-child {\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-four-column {\n position: absolute;\n z-index: 1024;\n width: fit-content;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-table-four-column > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table-four-column > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table-four-column > div:first-child > div:first-child {\n text-align: center;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table-four-column > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table-four-column > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table-four-column > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: center;\n margin-left: 4px;\n margin-right: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div:nth-child(1) {\n width: 25%;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div:nth-child(2) {\n width: 20%;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div:nth-child(3) {\n width: 20%;\n}\n.igv-roi-table-four-column > .igv-roi-table-column-titles > div:nth-child(4) {\n width: 35%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container {\n resize: both;\n overflow: scroll;\n min-width: 512px;\n min-height: 72px;\n height: 144px;\n max-height: 480px;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: center;\n margin-left: 4px;\n margin-right: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(1) {\n width: 25%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(2) {\n width: 20%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(3) {\n width: 20%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row > div:nth-child(4) {\n width: 35%;\n}\n.igv-roi-table-four-column > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table-four-column > div:last-child {\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-row-selected {\n background-color: rgba(0, 0, 0, 0.125);\n}\n\n.igv-roi-table-button {\n height: 20px;\n user-select: none;\n line-height: 20px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 13px;\n font-weight: 400;\n color: black;\n padding-left: 6px;\n padding-right: 6px;\n background-color: rgb(239, 239, 239);\n border-color: black;\n border-style: solid;\n border-width: thin;\n border-radius: 3px;\n}\n\n.igv-roi-table-button:hover {\n cursor: pointer;\n font-weight: 400;\n background-color: rgba(0, 0, 0, 0.13);\n}\n\n.igv-roi-region {\n z-index: 64;\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n overflow: visible;\n margin-top: 44px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-region > div {\n position: relative;\n width: 100%;\n height: 8px;\n cursor: pointer;\n pointer-events: auto;\n}\n\n.igv-roi-menu {\n position: absolute;\n z-index: 1024;\n width: 144px;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu > div:not(:last-child) {\n border-bottom-color: rgba(128, 128, 128, 0.5);\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-menu > div:first-child {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-color: transparent;\n border-top-style: solid;\n border-top-width: 0;\n}\n.igv-roi-menu > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n}\n\n.igv-roi-menu-row {\n height: 24px;\n padding-left: 8px;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n background-color: white;\n}\n\n.igv-roi-menu-row-edit-description {\n width: -webkit-fill-available;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n background-color: white;\n padding-left: 4px;\n padding-right: 4px;\n padding-bottom: 4px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-menu-row-edit-description > label {\n margin-left: 2px;\n margin-bottom: 0;\n display: block;\n width: -webkit-fill-available;\n}\n.igv-roi-menu-row-edit-description > input {\n display: block;\n margin-left: 2px;\n margin-right: 2px;\n margin-bottom: 1px;\n width: -webkit-fill-available;\n}\n\n.igv-container {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n padding-top: 4px;\n user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n}\n\n.igv-viewport {\n position: relative;\n margin-top: 5px;\n line-height: 1;\n overflow-x: hidden;\n overflow-y: hidden;\n}\n\n.igv-viewport-content {\n position: relative;\n width: 100%;\n}\n.igv-viewport-content > canvas {\n position: relative;\n display: block;\n}\n\n.igv-column-container {\n position: relative;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n width: 100%;\n}\n\n.igv-column-shim {\n width: 1px;\n margin-left: 2px;\n margin-right: 2px;\n background-color: #545453;\n}\n\n.igv-column {\n position: relative;\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-axis-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 50px;\n}\n.igv-axis-column > div {\n margin-top: 5px;\n width: 100%;\n}\n\n.igv-sample-name-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-scrollbar-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 14px;\n}\n.igv-scrollbar-column > div {\n position: relative;\n margin-top: 5px;\n width: 14px;\n}\n.igv-scrollbar-column > div > div {\n cursor: pointer;\n position: absolute;\n top: 0;\n left: 2px;\n width: 8px;\n border-width: 1px;\n border-style: solid;\n border-color: #c4c4c4;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-scrollbar-column > div > div:hover {\n background-color: #c4c4c4;\n}\n\n.igv-track-drag-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 12px;\n background-color: white;\n}\n.igv-track-drag-column > .igv-track-drag-handle {\n z-index: 512;\n position: relative;\n cursor: pointer;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n background-color: #c4c4c4;\n}\n.igv-track-drag-column .igv-track-drag-handle-hover {\n background-color: #787878;\n}\n.igv-track-drag-column > .igv-track-drag-shim {\n position: relative;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n}\n\n.igv-gear-menu-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 28px;\n}\n.igv-gear-menu-column > div {\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n margin-top: 5px;\n width: 100%;\n background: white;\n}\n.igv-gear-menu-column > div > div {\n position: relative;\n margin-top: 4px;\n width: 16px;\n height: 16px;\n color: #7F7F7F;\n}\n.igv-gear-menu-column > div > div:hover {\n cursor: pointer;\n color: #444;\n}\n\n/*# sourceMappingURL=dom.css.map */\n';
62732
62847
  var style = document.createElement('style');
62733
62848
  style.setAttribute('type', 'text/css');
62734
62849
  style.innerHTML = css;
@@ -62759,7 +62874,8 @@
62759
62874
  version: version$1,
62760
62875
  setApiKey,
62761
62876
  doAutoscale,
62762
- TrackView
62877
+ TrackView,
62878
+ GenomeUtils
62763
62879
  };
62764
62880
 
62765
62881
  return index;