igv 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/igv.esm.js CHANGED
@@ -17624,7 +17624,7 @@ function Node(interval) {
17624
17624
  * @constructor
17625
17625
  */
17626
17626
 
17627
- class FeatureCache {
17627
+ class FeatureCache$1 {
17628
17628
 
17629
17629
  constructor(featureList, genome, range) {
17630
17630
 
@@ -19091,7 +19091,7 @@ function createMenuElements$1(itemList, popover) {
19091
19091
 
19092
19092
  function embedCSS$2() {
19093
19093
 
19094
- var css = '.igv-ui-popover {\n cursor: default;\n position: absolute;\n z-index: 2048;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n background-color: white; }\n .igv-ui-popover > div:first-child {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: space-between;\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 .igv-ui-popover > div:first-child > div:first-child {\n margin-left: 4px; }\n .igv-ui-popover > div:first-child > div:last-child {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F; }\n .igv-ui-popover > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444; }\n .igv-ui-popover > div:last-child {\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 400px;\n max-width: 800px;\n background-color: white; }\n .igv-ui-popover > div:last-child > div {\n -webkit-user-select: all;\n /* Chrome/Safari */\n -moz-user-select: all;\n /* Firefox */\n margin-left: 4px;\n margin-right: 4px;\n min-width: 220px;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap; }\n .igv-ui-popover > div:last-child > div > span {\n font-weight: bolder; }\n .igv-ui-popover > div:last-child hr {\n width: 100%; }\n\n.igv-ui-alert-dialog-container {\n box-sizing: content-box;\n position: absolute;\n z-index: 2048;\n top: 50%;\n left: 50%;\n width: 400px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n outline: none;\n font-family: \"Open Sans\", sans-serif;\n font-size: 15px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center; }\n .igv-ui-alert-dialog-container > div:first-child {\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: 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 .igv-ui-alert-dialog-container > div:first-child div:first-child {\n padding-left: 8px; }\n .igv-ui-alert-dialog-container .igv-ui-alert-dialog-body {\n color: #373737;\n width: 100%;\n height: calc(100% - 24px - 64px);\n overflow-y: scroll; }\n .igv-ui-alert-dialog-container .igv-ui-alert-dialog-body .igv-ui-alert-dialog-body-copy {\n cursor: pointer;\n margin: 16px;\n width: auto;\n height: auto;\n overflow-wrap: break-word;\n word-break: break-word;\n -webkit-user-select: all;\n -moz-user-select: all;\n -ms-user-select: all;\n user-select: all;\n background-color: white;\n border: unset; }\n .igv-ui-alert-dialog-container > div:last-child {\n width: 100%;\n margin-bottom: 10px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center; }\n .igv-ui-alert-dialog-container > div:last-child div {\n margin: unset;\n width: 40px;\n height: 30px;\n line-height: 30px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n border-color: #2B81AF;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF; }\n .igv-ui-alert-dialog-container > div:last-child div:hover {\n cursor: pointer;\n border-color: #25597f;\n background-color: #25597f; }\n\n.igv-ui-color-swatch {\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 2px;\n border-color: white;\n border-radius: 4px; }\n\n.igv-ui-color-swatch:hover {\n border-color: dimgray; }\n\n.igv-ui-colorpicker-menu-close-button {\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: 32px;\n margin-top: 4px;\n margin-bottom: 4px;\n padding-right: 8px; }\n .igv-ui-colorpicker-menu-close-button i.fa {\n display: block;\n margin-left: 4px;\n margin-right: 4px;\n color: #5f5f5f; }\n .igv-ui-colorpicker-menu-close-button i.fa:hover,\n .igv-ui-colorpicker-menu-close-button i.fa:focus,\n .igv-ui-colorpicker-menu-close-button i.fa:active {\n cursor: pointer;\n color: #0f0f0f; }\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F; }\n .igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444; }\n .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px; }\n .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n font-size: 16px; }\n .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0; }\n .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4; }\n .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f; }\n .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f; }\n .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f; }\n\n.igv-ui-generic-container {\n box-sizing: content-box;\n position: absolute;\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 .igv-ui-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 .igv-ui-generic-container div:first-child div {\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.igv-ui-dialog {\n z-index: 2048;\n position: fixed;\n width: fit-content;\n height: fit-content;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n background-color: white;\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 .igv-ui-dialog .igv-ui-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 .igv-ui-dialog .igv-ui-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F; }\n .igv-ui-dialog .igv-ui-dialog-header div:hover {\n cursor: pointer;\n color: #444; }\n .igv-ui-dialog .igv-ui-dialog-one-liner {\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin: 8px;\n overflow-wrap: break-word;\n background-color: white;\n font-weight: bold; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel {\n width: 100%;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel div {\n margin: 16px;\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 .igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child {\n background-color: #5ea4e0; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child {\n background-color: #c4c4c4; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f; }\n .igv-ui-dialog .igv-ui-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 .igv-ui-dialog .igv-ui-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 .igv-ui-dialog .igv-ui-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f; }\n\n.igv-ui-panel, .igv-ui-panel-column, .igv-ui-panel-row {\n z-index: 2048;\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start; }\n\n.igv-ui-panel-column {\n display: flex;\n flex-direction: column; }\n\n.igv-ui-panel-row {\n display: flex;\n flex-direction: row; }\n\n.igv-ui-textbox {\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start; }\n\n/*# sourceMappingURL=igv-ui.css.map */\n';
19094
+ var css = '.igv-ui-popover {\n cursor: default;\n position: absolute;\n z-index: 2048;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n background-color: white; }\n .igv-ui-popover > div:first-child {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: space-between;\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 .igv-ui-popover > div:first-child > div:first-child {\n margin-left: 4px; }\n .igv-ui-popover > div:first-child > div:last-child {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F; }\n .igv-ui-popover > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444; }\n .igv-ui-popover > div:last-child {\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 400px;\n max-width: 800px;\n background-color: white; }\n .igv-ui-popover > div:last-child > div {\n -webkit-user-select: text;\n -moz-user-select: text;\n -ms-user-select: text;\n user-select: text;\n margin-left: 4px;\n margin-right: 4px;\n min-width: 220px;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap; }\n .igv-ui-popover > div:last-child > div > span {\n font-weight: bolder; }\n .igv-ui-popover > div:last-child hr {\n width: 100%; }\n\n.igv-ui-alert-dialog-container {\n box-sizing: content-box;\n position: absolute;\n z-index: 2048;\n top: 50%;\n left: 50%;\n width: 400px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n outline: none;\n font-family: \"Open Sans\", sans-serif;\n font-size: 15px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center; }\n .igv-ui-alert-dialog-container > div:first-child {\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: 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 .igv-ui-alert-dialog-container > div:first-child div:first-child {\n padding-left: 8px; }\n .igv-ui-alert-dialog-container .igv-ui-alert-dialog-body {\n -webkit-user-select: text;\n -moz-user-select: text;\n -ms-user-select: text;\n user-select: text;\n color: #373737;\n width: 100%;\n height: calc(100% - 24px - 64px);\n overflow-y: scroll; }\n .igv-ui-alert-dialog-container .igv-ui-alert-dialog-body .igv-ui-alert-dialog-body-copy {\n margin: 16px;\n width: auto;\n height: auto;\n overflow-wrap: break-word;\n word-break: break-word;\n background-color: white;\n border: unset; }\n .igv-ui-alert-dialog-container > div:last-child {\n width: 100%;\n margin-bottom: 10px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center; }\n .igv-ui-alert-dialog-container > div:last-child div {\n margin: unset;\n width: 40px;\n height: 30px;\n line-height: 30px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n border-color: #2B81AF;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF; }\n .igv-ui-alert-dialog-container > div:last-child div:hover {\n cursor: pointer;\n border-color: #25597f;\n background-color: #25597f; }\n\n.igv-ui-color-swatch {\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 2px;\n border-color: white;\n border-radius: 4px; }\n\n.igv-ui-color-swatch:hover {\n border-color: dimgray; }\n\n.igv-ui-colorpicker-menu-close-button {\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: 32px;\n margin-top: 4px;\n margin-bottom: 4px;\n padding-right: 8px; }\n .igv-ui-colorpicker-menu-close-button i.fa {\n display: block;\n margin-left: 4px;\n margin-right: 4px;\n color: #5f5f5f; }\n .igv-ui-colorpicker-menu-close-button i.fa:hover,\n .igv-ui-colorpicker-menu-close-button i.fa:focus,\n .igv-ui-colorpicker-menu-close-button i.fa:active {\n cursor: pointer;\n color: #0f0f0f; }\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F; }\n .igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444; }\n .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px; }\n .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n font-size: 16px; }\n .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0; }\n .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4; }\n .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f; }\n .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f; }\n .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-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 .igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f; }\n\n.igv-ui-generic-container {\n box-sizing: content-box;\n position: absolute;\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 .igv-ui-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 .igv-ui-generic-container > div:first-child > div {\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.igv-ui-dialog {\n z-index: 2048;\n position: fixed;\n width: fit-content;\n height: fit-content;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n background-color: white;\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 .igv-ui-dialog .igv-ui-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 .igv-ui-dialog .igv-ui-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F; }\n .igv-ui-dialog .igv-ui-dialog-header div:hover {\n cursor: pointer;\n color: #444; }\n .igv-ui-dialog .igv-ui-dialog-one-liner {\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin: 8px;\n overflow-wrap: break-word;\n background-color: white;\n font-weight: bold; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel {\n width: 100%;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel div {\n margin: 16px;\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 .igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child {\n background-color: #5ea4e0; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child {\n background-color: #c4c4c4; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f; }\n .igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f; }\n .igv-ui-dialog .igv-ui-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 .igv-ui-dialog .igv-ui-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 .igv-ui-dialog .igv-ui-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f; }\n\n.igv-ui-panel, .igv-ui-panel-column, .igv-ui-panel-row {\n z-index: 2048;\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start; }\n\n.igv-ui-panel-column {\n display: flex;\n flex-direction: column; }\n\n.igv-ui-panel-row {\n display: flex;\n flex-direction: row; }\n\n.igv-ui-textbox {\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start; }\n\n/*# sourceMappingURL=igv-ui.css.map */\n';
19095
19095
 
19096
19096
  var style = document.createElement('style');
19097
19097
  style.setAttribute('type', 'text/css');
@@ -19711,6 +19711,7 @@ const knownFileExtensions = new Set([
19711
19711
  "vcf",
19712
19712
  "bb",
19713
19713
  "bigbed",
19714
+ "biginteract",
19714
19715
  "bw",
19715
19716
  "bigwig",
19716
19717
  "bam",
@@ -19853,6 +19854,7 @@ function inferTrackType(config) {
19853
19854
  return "alignment"
19854
19855
  case "bedpe":
19855
19856
  case "bedpe-loop":
19857
+ case "biginteract":
19856
19858
  return "interact"
19857
19859
  case "bp":
19858
19860
  return "arc"
@@ -20111,6 +20113,38 @@ function isSecureContext() {
20111
20113
  return window.location.protocol === "https:" || window.location.hostname === "localhost"
20112
20114
  }
20113
20115
 
20116
+ const pairs =
20117
+ [
20118
+ ['A', 'T'],
20119
+ ['G', 'C'],
20120
+ ['Y', 'R'],
20121
+ ['W', 'S'],
20122
+ ['K', 'M'],
20123
+ ['D', 'H'],
20124
+ ['B', 'V']
20125
+ ];
20126
+
20127
+ const complements = new Map();
20128
+ for (let p of pairs) {
20129
+ const p1 = p[0];
20130
+ const p2 = p[1];
20131
+ complements.set(p1, p2);
20132
+ complements.set(p2, p1);
20133
+ complements.set(p1.toLowerCase(), p2.toLowerCase());
20134
+ complements.set(p2.toLowerCase(), p1.toLowerCase());
20135
+ }
20136
+
20137
+ function reverseComplementSequence(sequence) {
20138
+
20139
+ let comp = '';
20140
+ let idx = sequence.length;
20141
+ while (idx-- > 0) {
20142
+ const base = sequence[idx];
20143
+ comp += complements.has(base) ? complements.get(base) : base;
20144
+ }
20145
+ return comp
20146
+ }
20147
+
20114
20148
  /*
20115
20149
  * The MIT License (MIT)
20116
20150
  *
@@ -20235,7 +20269,6 @@ class SequenceTrack {
20235
20269
  }
20236
20270
 
20237
20271
  menuItemList() {
20238
-
20239
20272
  return [
20240
20273
  {
20241
20274
  name: this.reversed ? "Forward" : "Reverse",
@@ -20270,17 +20303,20 @@ class SequenceTrack {
20270
20303
  contextMenuItemList(clickState) {
20271
20304
  const viewport = clickState.viewport;
20272
20305
  if (viewport.referenceFrame.bpPerPixel <= 1) {
20306
+ const pixelWidth = viewport.getWidth();
20307
+ const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20308
+ const chr = viewport.referenceFrame.chr;
20309
+ const start = Math.floor(viewport.referenceFrame.start);
20310
+ const end = Math.ceil(start + bpWindow);
20273
20311
  const items = [
20274
20312
  {
20275
- label: 'View visible sequence...',
20313
+ label: this.reversed ? 'View visible sequence (reversed)...' : 'View visible sequence...',
20276
20314
  click: async () => {
20277
- const pixelWidth = viewport.getWidth();
20278
- const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20279
- const chr = viewport.referenceFrame.chr;
20280
- const start = viewport.referenceFrame.start;
20281
- const end = start + bpWindow;
20282
- const sequence = await this.browser.genome.sequence.getSequence(chr, start, end);
20283
- Alert.presentAlert(sequence);
20315
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20316
+ if (this.reversed) {
20317
+ seq = reverseComplementSequence(seq);
20318
+ }
20319
+ Alert.presentAlert(seq);
20284
20320
  }
20285
20321
  }
20286
20322
  ];
@@ -20288,14 +20324,18 @@ class SequenceTrack {
20288
20324
  items.push({
20289
20325
  label: 'Copy visible sequence',
20290
20326
  click: async () => {
20291
- const pixelWidth = viewport.getWidth();
20292
- const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20293
- const chr = viewport.referenceFrame.chr;
20294
- const start = viewport.referenceFrame.start;
20295
- const end = start + bpWindow;
20296
- const sequence = await this.browser.genome.sequence.getSequence(chr, start, end);
20297
- navigator.clipboard.writeText(sequence);
20327
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20328
+ if (this.reversed) {
20329
+ seq = reverseComplementSequence(seq);
20330
+ }
20331
+ try {
20332
+ await navigator.clipboard.writeText(seq);
20333
+ } catch (e) {
20334
+ console.error(e);
20335
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
20336
+ }
20298
20337
  }
20338
+
20299
20339
  });
20300
20340
  }
20301
20341
  items.push('<hr/>');
@@ -20439,7 +20479,7 @@ class SequenceTrack {
20439
20479
  }
20440
20480
  }
20441
20481
 
20442
- supportsWholeGenome() {
20482
+ get supportsWholeGenome() {
20443
20483
  return false
20444
20484
  }
20445
20485
 
@@ -20513,13 +20553,13 @@ class Viewport {
20513
20553
  this.$content.height(this.$viewport.height());
20514
20554
  this.contentDiv = this.$content.get(0);
20515
20555
 
20516
- this.$canvas = $$1('<canvas>');
20517
- this.$content.append(this.$canvas);
20518
-
20519
- this.canvas = this.$canvas.get(0);
20520
- this.ctx = this.canvas.getContext("2d");
20556
+ // this.$canvas = $('<canvas>')
20557
+ // this.$content.append(this.$canvas)
20558
+ //
20559
+ // this.canvas = this.$canvas.get(0)
20560
+ // this.ctx = this.canvas.getContext("2d")
20521
20561
 
20522
- this.setWidth(width);
20562
+ this.$viewport.width(width);
20523
20563
 
20524
20564
  this.initializationHelper();
20525
20565
 
@@ -20586,14 +20626,13 @@ class Viewport {
20586
20626
  console.log('Viewport - draw(drawConfiguration, features, roiFeatures)');
20587
20627
  }
20588
20628
 
20589
- checkContentHeight() {
20629
+ checkContentHeight(features) {
20590
20630
 
20591
20631
  let track = this.trackView.track;
20592
-
20632
+ features = features || this.cachedFeatures;
20593
20633
  if ("FILL" === track.displayMode) {
20594
20634
  this.setContentHeight(this.$viewport.height());
20595
20635
  } else if (typeof track.computePixelHeight === 'function') {
20596
- let features = this.cachedFeatures;
20597
20636
  if (features && features.length > 0) {
20598
20637
  let requiredContentHeight = track.computePixelHeight(features);
20599
20638
  let currentContentHeight = this.$content.height();
@@ -20609,12 +20648,10 @@ class Viewport {
20609
20648
  }
20610
20649
 
20611
20650
  setContentHeight(contentHeight) {
20651
+
20612
20652
  // Maximum height of a canvas is ~32,000 pixels on Chrome, possibly smaller on other platforms
20613
20653
  contentHeight = Math.min(contentHeight, 32000);
20614
-
20615
20654
  this.$content.height(contentHeight);
20616
-
20617
- if (this.tile) this.tile.invalidate = true;
20618
20655
  }
20619
20656
 
20620
20657
  isLoading() {
@@ -20631,8 +20668,6 @@ class Viewport {
20631
20668
 
20632
20669
  setWidth(width) {
20633
20670
  this.$viewport.width(width);
20634
- this.canvas.style.width = (`${width}px`);
20635
- this.canvas.setAttribute('width', width);
20636
20671
  }
20637
20672
 
20638
20673
  getWidth() {
@@ -20662,8 +20697,6 @@ class Viewport {
20662
20697
  this.popover.dispose();
20663
20698
  }
20664
20699
 
20665
- this.removeMouseHandlers();
20666
-
20667
20700
  this.$viewport.get(0).remove();
20668
20701
 
20669
20702
  // Null out all properties -- this should not be neccessary, but just in case there is a
@@ -22759,7 +22792,7 @@ const Cytoband = function (start, end, name, typestain) {
22759
22792
  }
22760
22793
  };
22761
22794
 
22762
- const _version = "2.11.0";
22795
+ const _version = "2.12.0";
22763
22796
  function version() {
22764
22797
  return _version
22765
22798
  }
@@ -23113,6 +23146,7 @@ class Genome {
23113
23146
  }
23114
23147
 
23115
23148
  async getSequence(chr, start, end) {
23149
+ chr = this.getChromosomeName(chr);
23116
23150
  return this.sequence.getSequence(chr, start, end)
23117
23151
  }
23118
23152
  }
@@ -23248,28 +23282,27 @@ class TrackViewport extends Viewport {
23248
23282
  this.$viewport.append(this.$spinner);
23249
23283
  this.$spinner.append($$1('<div>'));
23250
23284
 
23251
- const {track} = this.trackView;
23252
-
23285
+ const track = this.trackView.track;
23253
23286
  if ('sequence' !== track.type) {
23254
23287
  this.$zoomInNotice = this.createZoomInNotice(this.$content);
23255
23288
  }
23256
23289
 
23257
- if (track.name && "sequence" !== track.config.type) {
23258
-
23290
+ if (track.name && "sequence" !== track.id) {
23259
23291
  this.$trackLabel = $$1('<div class="igv-track-label">');
23260
23292
  this.$viewport.append(this.$trackLabel);
23261
23293
  this.setTrackLabel(track.name);
23262
-
23263
23294
  if (false === this.browser.trackLabelsVisible) {
23264
23295
  this.$trackLabel.hide();
23265
23296
  }
23266
-
23267
23297
  }
23268
23298
 
23269
23299
  this.stopSpinner();
23270
-
23271
23300
  this.addMouseHandlers();
23301
+ }
23272
23302
 
23303
+ setContentHeight(contentHeight) {
23304
+ super.setContentHeight(contentHeight);
23305
+ if (this.featureCache) this.featureCache.redraw = true;
23273
23306
  }
23274
23307
 
23275
23308
  setTrackLabel(label) {
@@ -23291,61 +23324,74 @@ class TrackViewport extends Viewport {
23291
23324
  }
23292
23325
  }
23293
23326
 
23327
+ /**
23328
+ * Test to determine if we are zoomed in far enough to see features. Applicable to tracks with visibility windows.
23329
+ *
23330
+ * As a side effect the viewports canvas is removed if zoomed out.
23331
+ *
23332
+ * @returns {boolean} true if we are zoomed in past visibility window, false otherwise
23333
+ */
23294
23334
  checkZoomIn() {
23295
23335
 
23296
- const showZoomInNotice = () => {
23297
- const referenceFrame = this.referenceFrame;
23298
- if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome()) {
23336
+ const zoomedOutOfWindow = () => {
23337
+ if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome) {
23299
23338
  return true
23300
23339
  } else {
23301
23340
  const visibilityWindow = this.trackView.track.visibilityWindow;
23302
23341
  return (
23303
23342
  visibilityWindow !== undefined && visibilityWindow > 0 &&
23304
- (referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow))
23343
+ (this.referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow))
23305
23344
  }
23306
23345
  };
23307
23346
 
23347
+ if (this.trackView.track && "sequence" === this.trackView.track.type && this.referenceFrame.bpPerPixel > 1) {
23348
+ $$1(this.canvas).remove();
23349
+ this.canvas = undefined;
23350
+ //this.featureCache = undefined
23351
+ return false
23352
+ }
23353
+
23308
23354
  if (!(this.viewIsReady())) {
23309
23355
  return false
23310
23356
  }
23311
23357
 
23312
- if (this.$zoomInNotice) {
23313
- if (showZoomInNotice()) {
23314
- // Out of visibility window
23315
- if (this.canvas) {
23316
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
23317
- this.tile = undefined;
23318
- }
23319
- this.$zoomInNotice.show();
23320
23358
 
23321
- if (this.trackView.track.autoHeight) {
23322
- const minHeight = this.trackView.minHeight || 0;
23323
- this.setContentHeight(minHeight);
23324
- }
23359
+ if (zoomedOutOfWindow()) {
23325
23360
 
23326
- return false
23327
- } else {
23361
+ // Out of visibility window
23362
+ if (this.canvas) {
23363
+ $$1(this.canvas).remove();
23364
+ this.canvas = undefined;
23365
+ //this.featureCache = undefined
23366
+ }
23367
+ if (this.trackView.track.autoHeight) {
23368
+ const minHeight = this.trackView.minHeight || 0;
23369
+ this.setContentHeight(minHeight);
23370
+ }
23371
+ if (this.$zoomInNotice) {
23372
+ this.$zoomInNotice.show();
23373
+ }
23374
+ return false
23375
+ } else {
23376
+ if (this.$zoomInNotice) {
23328
23377
  this.$zoomInNotice.hide();
23329
- return true
23330
23378
  }
23379
+ return true
23331
23380
  }
23332
23381
 
23333
- return true
23334
-
23335
-
23336
23382
  }
23337
23383
 
23384
+ /**
23385
+ * Adjust the canvas to the current genomic state.
23386
+ */
23338
23387
  shift() {
23339
- const self = this;
23340
- const referenceFrame = self.referenceFrame;
23341
-
23342
- if (self.canvas &&
23343
- self.tile &&
23344
- self.tile.chr === self.referenceFrame.chr &&
23345
- self.tile.bpPerPixel === referenceFrame.bpPerPixel) {
23346
-
23347
- const pixelOffset = Math.round((self.tile.startBP - referenceFrame.start) / referenceFrame.bpPerPixel);
23348
- self.canvas.style.left = pixelOffset + "px";
23388
+ const referenceFrame = this.referenceFrame;
23389
+ if (this.canvas &&
23390
+ this.canvas._data &&
23391
+ this.canvas._data.chr === this.referenceFrame.chr &&
23392
+ this.canvas._data.bpPerPixel === referenceFrame.bpPerPixel) {
23393
+ const pixelOffset = Math.round((this.canvas._data.startBP - referenceFrame.start) / referenceFrame.bpPerPixel);
23394
+ this.canvas.style.left = pixelOffset + "px";
23349
23395
  }
23350
23396
  }
23351
23397
 
@@ -23368,22 +23414,23 @@ class TrackViewport extends Viewport {
23368
23414
  this.startSpinner();
23369
23415
 
23370
23416
  try {
23371
- const features = await this.getFeatures(this.trackView.track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23417
+ const track = this.trackView.track;
23418
+ const features = await this.getFeatures(track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23372
23419
  let roiFeatures = [];
23373
- const roi = mergeArrays(this.browser.roi, this.trackView.track.roi);
23420
+ const roi = mergeArrays(this.browser.roi, track.roi);
23374
23421
  if (roi) {
23375
23422
  for (let r of roi) {
23376
- const f = await
23377
- r.getFeatures(chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23423
+ const f = await r.getFeatures(chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23378
23424
  roiFeatures.push({track: r, features: f});
23379
23425
  }
23380
23426
  }
23381
23427
 
23382
- this.tile = new Tile(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures);
23428
+ const mr = track && ("wig" === track.type || "merged" === track.type); // wig tracks are potentially multiresolution (e.g. bigwig)
23429
+ this.featureCache = new FeatureCache(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures, mr);
23383
23430
  this.loading = false;
23384
23431
  this.hideMessage();
23385
23432
  this.stopSpinner();
23386
- return this.tile
23433
+ return this.featureCache
23387
23434
  } catch (error) {
23388
23435
  // Track might have been removed during load
23389
23436
  if (this.trackView && this.trackView.disposed !== true) {
@@ -23397,32 +23444,29 @@ class TrackViewport extends Viewport {
23397
23444
  }
23398
23445
  }
23399
23446
 
23400
- async repaint() {
23447
+ /**
23448
+ * Repaint the canvas using the cached features
23449
+ *
23450
+ */
23451
+ repaint() {
23401
23452
 
23402
- if (undefined === this.tile) {
23453
+ if (undefined === this.featureCache) {
23403
23454
  return
23404
23455
  }
23405
23456
 
23406
- let {features, roiFeatures, bpPerPixel, startBP, endBP} = this.tile;
23457
+ let {features, roiFeatures} = this.featureCache;
23458
+ //this.tile.bpPerPixel = this.referenceFrame.bpPerPixel
23407
23459
 
23408
23460
  // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
23409
23461
  const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
23410
- let pixelWidth;
23411
-
23412
- if (isWGV) {
23413
- bpPerPixel = this.referenceFrame.end / this.$viewport.width();
23414
- startBP = 0;
23415
- endBP = this.referenceFrame.end;
23416
- pixelWidth = this.$viewport.width();
23417
- } else {
23418
- pixelWidth = Math.ceil((endBP - startBP) / bpPerPixel);
23419
- }
23420
23462
 
23463
+ // Canvas dimensions. There is no left-right panning for WGV so canvas width is viewport width.
23421
23464
  // For deep tracks we paint a canvas == 3*viewportHeight centered on the current vertical scroll position
23465
+ const pixelWidth = isWGV ? this.$viewport.width() : 3 * this.$viewport.width();
23422
23466
  const viewportHeight = this.$viewport.height();
23423
23467
  const contentHeight = this.getContentHeight();
23424
23468
  const minHeight = roiFeatures ? Math.max(contentHeight, viewportHeight) : contentHeight; // Need to fill viewport for ROIs.
23425
- let pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23469
+ const pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23426
23470
  if (0 === pixelWidth || 0 === pixelHeight) {
23427
23471
  if (this.canvas) {
23428
23472
  $$1(this.canvas).remove();
@@ -23431,30 +23475,25 @@ class TrackViewport extends Viewport {
23431
23475
  }
23432
23476
  const canvasTop = Math.max(0, -(this.$content.position().top) - viewportHeight);
23433
23477
 
23434
- // Always use high DPI if in compressed display mode, otherwise use preference setting;
23435
- let devicePixelRatio;
23436
- if ("FILL" === this.trackView.track.displayMode) {
23437
- devicePixelRatio = window.devicePixelRatio;
23438
- } else {
23439
- devicePixelRatio = (this.trackView.track.supportHiDPI === false) ? 1 : window.devicePixelRatio;
23440
- }
23441
-
23442
- const pixelXOffset = Math.round((startBP - this.referenceFrame.start) / this.referenceFrame.bpPerPixel);
23478
+ const bpPerPixel = this.referenceFrame.bpPerPixel;
23479
+ const startBP = this.referenceFrame.start - (isWGV ? 0 : pixelWidth / 3 * bpPerPixel);
23480
+ const endBP = this.referenceFrame.end + (isWGV ? 0 : pixelWidth / 3 * bpPerPixel);
23481
+ const pixelXOffset = Math.round((startBP - this.referenceFrame.start) / bpPerPixel);
23443
23482
 
23444
23483
  const newCanvas = $$1('<canvas class="igv-canvas">').get(0);
23445
- const ctx = newCanvas.getContext("2d");
23446
-
23447
23484
  newCanvas.style.width = pixelWidth + "px";
23448
23485
  newCanvas.style.height = pixelHeight + "px";
23486
+ newCanvas.style.left = pixelXOffset + "px";
23487
+ newCanvas.style.top = canvasTop + "px";
23449
23488
 
23489
+ // Always use high DPI if in "FILL" display mode, otherwise use track setting;
23490
+ const devicePixelRatio = ("FILL" === this.trackView.track.displayMode || this.trackView.track.supportHiDPI !== false) ?
23491
+ window.devicePixelRatio : 1;
23450
23492
  newCanvas.width = devicePixelRatio * pixelWidth;
23451
23493
  newCanvas.height = devicePixelRatio * pixelHeight;
23452
23494
 
23495
+ const ctx = newCanvas.getContext("2d");
23453
23496
  ctx.scale(devicePixelRatio, devicePixelRatio);
23454
-
23455
- newCanvas.style.left = pixelXOffset + "px";
23456
- newCanvas.style.top = canvasTop + "px";
23457
-
23458
23497
  ctx.translate(0, -canvasTop);
23459
23498
 
23460
23499
  const drawConfiguration =
@@ -23475,17 +23514,27 @@ class TrackViewport extends Viewport {
23475
23514
 
23476
23515
  this.draw(drawConfiguration, features, roiFeatures);
23477
23516
 
23478
- this.canvasVerticalRange = {top: canvasTop, bottom: canvasTop + pixelHeight};
23517
+ this.featureCache.canvasTop = canvasTop;
23518
+ this.featureCache.height = pixelHeight;
23479
23519
 
23480
- if (this.$canvas) {
23481
- this.$canvas.remove();
23520
+ if (this.canvas) {
23521
+ $$1(this.canvas).remove();
23482
23522
  }
23483
- this.$canvas = $$1(newCanvas);
23484
- this.$content.append(this.$canvas);
23523
+ newCanvas._data = {
23524
+ chr: this.featureCache.chr, bpPerPixel, startBP, endBP, pixelHeight, pixelTop: canvasTop
23525
+ };
23485
23526
  this.canvas = newCanvas;
23486
- this.ctx = ctx;
23527
+ this.$content.append($$1(newCanvas));
23528
+
23487
23529
  }
23488
23530
 
23531
+ /**
23532
+ * Draw the associated track.
23533
+ *
23534
+ * @param drawConfiguration
23535
+ * @param features
23536
+ * @param roiFeatures
23537
+ */
23489
23538
  draw(drawConfiguration, features, roiFeatures) {
23490
23539
 
23491
23540
  // console.log(`${ Date.now() } viewport draw(). track ${ this.trackView.track.type }. content-css-top ${ this.$content.css('top') }. canvas-top ${ drawConfiguration.pixelTop }.`)
@@ -23502,60 +23551,6 @@ class TrackViewport extends Viewport {
23502
23551
  }
23503
23552
  }
23504
23553
 
23505
- // TODO: Nolonger used. Will discard
23506
- async toSVG(tile) {
23507
-
23508
- // Nothing to do if zoomInNotice is active
23509
- if (this.$zoomInNotice && this.$zoomInNotice.is(":visible")) {
23510
- return
23511
- }
23512
-
23513
- const referenceFrame = this.referenceFrame;
23514
- const bpPerPixel = tile.bpPerPixel;
23515
- const features = tile.features;
23516
- const roiFeatures = tile.roiFeatures;
23517
- const pixelWidth = this.$viewport.width();
23518
- const pixelHeight = this.$viewport.height();
23519
- const bpStart = referenceFrame.start;
23520
- const bpEnd = referenceFrame.start + pixelWidth * referenceFrame.bpPerPixel;
23521
-
23522
- const ctx$1 = new ctx(
23523
- {
23524
- // svg
23525
- width: pixelWidth,
23526
- height: pixelHeight,
23527
- viewbox:
23528
- {
23529
- x: 0,
23530
- y: -this.$content.position().top,
23531
- width: pixelWidth,
23532
- height: pixelHeight
23533
- }
23534
-
23535
- });
23536
-
23537
- const drawConfiguration =
23538
- {
23539
- viewport: this,
23540
- context: ctx$1,
23541
- top: -this.$content.position().top,
23542
- pixelTop: 0, // for compatibility with canvas draw
23543
- pixelWidth,
23544
- pixelHeight,
23545
- bpStart,
23546
- bpEnd,
23547
- bpPerPixel,
23548
- referenceFrame: this.referenceFrame,
23549
- selection: this.selection,
23550
- viewportWidth: pixelWidth,
23551
- };
23552
-
23553
- this.draw(drawConfiguration, features, roiFeatures);
23554
-
23555
- return ctx$1.getSerializedSvg(true)
23556
-
23557
- }
23558
-
23559
23554
  containsPosition(chr, position) {
23560
23555
  if (this.referenceFrame.chr === chr && position >= this.referenceFrame.start) {
23561
23556
  return position <= this.referenceFrame.calculateEnd(this.getWidth())
@@ -23568,18 +23563,20 @@ class TrackViewport extends Viewport {
23568
23563
  return this.loading
23569
23564
  }
23570
23565
 
23571
- saveImage() {
23566
+ savePNG() {
23572
23567
 
23573
- if (!this.ctx) return
23568
+ if (!this.canvas) return
23574
23569
 
23575
- const canvasTop = this.canvasVerticalRange ? this.canvasVerticalRange.top : 0;
23570
+ const canvasMetadata = this.featureCache;
23571
+ const canvasTop = canvasMetadata ? canvasMetadata.canvasTop : 0;
23576
23572
  const devicePixelRatio = window.devicePixelRatio;
23577
23573
  const w = this.$viewport.width() * devicePixelRatio;
23578
23574
  const h = this.$viewport.height() * devicePixelRatio;
23579
23575
  const x = -$$1(this.canvas).position().left * devicePixelRatio;
23580
23576
  const y = (-this.$content.position().top - canvasTop) * devicePixelRatio;
23581
23577
 
23582
- const imageData = this.ctx.getImageData(x, y, w, h);
23578
+ const ctx = this.canvas.getContext("2d");
23579
+ const imageData = ctx.getImageData(x, y, w, h);
23583
23580
  const exportCanvas = document.createElement('canvas');
23584
23581
  const exportCtx = exportCanvas.getContext('2d');
23585
23582
  exportCanvas.width = imageData.width;
@@ -23702,32 +23699,53 @@ class TrackViewport extends Viewport {
23702
23699
  selection: this.selection
23703
23700
  };
23704
23701
 
23705
- const features = this.tile ? this.tile.features : [];
23706
- const roiFeatures = this.tile ? this.tile.roiFeatures : undefined;
23702
+ const features = this.featureCache ? this.featureCache.features : [];
23703
+ const roiFeatures = this.featureCache ? this.featureCache.roiFeatures : undefined;
23707
23704
  this.draw(config, features, roiFeatures);
23708
23705
 
23709
23706
  context.restore();
23710
23707
 
23711
23708
  }
23712
23709
 
23713
- getCachedFeatures() {
23714
- return this.tile ? this.tile.features : []
23710
+ get cachedFeatures() {
23711
+ return this.featureCache ? this.featureCache.features : []
23715
23712
  }
23716
23713
 
23717
23714
  async getFeatures(track, chr, start, end, bpPerPixel) {
23718
23715
 
23719
- if (this.tile && this.tile.containsRange(chr, start, end, bpPerPixel)) {
23720
- return this.tile.features
23716
+ if (this.featureCache && this.featureCache.containsRange(chr, start, end, bpPerPixel)) {
23717
+ return this.featureCache.features
23721
23718
  } else if (typeof track.getFeatures === "function") {
23722
23719
  const features = await track.getFeatures(chr, start, end, bpPerPixel, this);
23723
- this.cachedFeatures = features;
23724
- this.checkContentHeight();
23720
+ this.checkContentHeight(features);
23725
23721
  return features
23726
23722
  } else {
23727
23723
  return undefined
23728
23724
  }
23729
23725
  }
23730
23726
 
23727
+ needsRepaint() {
23728
+
23729
+ if (!this.canvas) return true
23730
+
23731
+ const data = this.canvas._data;
23732
+ return !data ||
23733
+ this.referenceFrame.start < data.startBP ||
23734
+ this.referenceFrame.end > data.endBP ||
23735
+ this.referenceFrame.chr !== data.chr ||
23736
+ this.referenceFrame.bpPerPixel != data.bpPerPixel
23737
+ }
23738
+
23739
+ needsReload() {
23740
+ if (!this.featureCache) return true
23741
+ const referenceFrame = this.referenceFrame;
23742
+ const chr = this.referenceFrame.chr;
23743
+ const start = referenceFrame.start;
23744
+ const end = start + referenceFrame.toBP($$1(this.contentDiv).width());
23745
+ const bpPerPixel = referenceFrame.bpPerPixel;
23746
+ return (!this.featureCache.containsRange(chr, start, end, bpPerPixel))
23747
+ }
23748
+
23731
23749
  createZoomInNotice($parent) {
23732
23750
 
23733
23751
  const $container = $$1('<div>', {class: 'igv-zoom-in-notice-container'});
@@ -23749,15 +23767,33 @@ class TrackViewport extends Viewport {
23749
23767
 
23750
23768
  addMouseHandlers() {
23751
23769
 
23752
- this.addViewportContextMenuHandler(this.$viewport.get(0));
23770
+ const viewport = this.$viewport.get(0);
23753
23771
 
23754
- this.addViewportMouseDownHandler(this.$viewport.get(0));
23772
+ this.addViewportContextMenuHandler(viewport);
23755
23773
 
23756
- this.addViewportTouchStartHandler(this.$viewport.get(0));
23757
-
23758
- this.addViewportMouseUpHandler(this.$viewport.get(0));
23774
+ const md = (event) => {
23775
+ this.enableClick = true;
23776
+ this.browser.mouseDownOnViewport(event, this);
23777
+ pageCoordinates$1(event);
23778
+ };
23779
+ viewport.addEventListener('mousedown', md);
23780
+ viewport.addEventListener('touchstart', md);
23781
+
23782
+ const mu = (event) => {
23783
+ // Any mouse up cancels drag and scrolling
23784
+ if (this.browser.dragObject || this.browser.isScrolling) {
23785
+ this.browser.cancelTrackPan();
23786
+ // event.preventDefault();
23787
+ // event.stopPropagation();
23788
+ this.enableClick = false; // Until next mouse down
23789
+ } else {
23790
+ this.browser.cancelTrackPan();
23791
+ this.browser.endTrackDrag();
23792
+ }
23793
+ };
23759
23794
 
23760
- this.addViewportTouchEndHandler(this.$viewport.get(0));
23795
+ viewport.addEventListener('mouseup', mu);
23796
+ viewport.addEventListener('touchend', mu);
23761
23797
 
23762
23798
  this.addViewportClickHandler(this.$viewport.get(0));
23763
23799
 
@@ -23767,32 +23803,9 @@ class TrackViewport extends Viewport {
23767
23803
 
23768
23804
  }
23769
23805
 
23770
- removeMouseHandlers() {
23771
-
23772
- this.removeViewportContextMenuHandler(this.$viewport.get(0));
23773
-
23774
- this.removeViewportMouseDownHandler(this.$viewport.get(0));
23775
-
23776
- this.removeViewportTouchStartHandler(this.$viewport.get(0));
23777
-
23778
- this.removeViewportMouseUpHandler(this.$viewport.get(0));
23779
-
23780
- this.removeViewportTouchEndHandler(this.$viewport.get(0));
23781
-
23782
- this.removeViewportClickHandler(this.$viewport.get(0));
23783
-
23784
- if (this.trackView.track.name && "sequence" !== this.trackView.track.config.type) {
23785
- this.removeTrackLabelClickHandler(this.$trackLabel.get(0));
23786
- }
23787
-
23788
- }
23789
-
23790
23806
  addViewportContextMenuHandler(viewport) {
23791
23807
 
23792
- this.boundContextMenuHandler = contextMenuHandler.bind(this);
23793
- viewport.addEventListener('contextmenu', this.boundContextMenuHandler);
23794
-
23795
- function contextMenuHandler(event) {
23808
+ viewport.addEventListener('contextmenu', (event) => {
23796
23809
 
23797
23810
  // Ignore if we are doing a drag. This can happen with touch events.
23798
23811
  if (this.browser.dragObject) {
@@ -23825,56 +23838,14 @@ class TrackViewport extends Viewport {
23825
23838
  menuItems.push({label: 'Save Image (SVG)', click: () => this.saveSVG()});
23826
23839
 
23827
23840
  this.browser.menuPopup.presentTrackContextMenu(event, menuItems);
23828
- }
23829
-
23830
- }
23831
-
23832
- removeViewportContextMenuHandler(viewport) {
23833
- viewport.removeEventListener('contextmenu', this.boundContextMenuHandler);
23834
- }
23835
-
23836
- addViewportMouseDownHandler(viewport) {
23837
- this.boundMouseDownHandler = mouseDownHandler.bind(this);
23838
- viewport.addEventListener('mousedown', this.boundMouseDownHandler);
23839
- }
23840
-
23841
- removeViewportMouseDownHandler(viewport) {
23842
- viewport.removeEventListener('mousedown', this.boundMouseDownHandler);
23843
- }
23844
-
23845
- addViewportTouchStartHandler(viewport) {
23846
- this.boundTouchStartHandler = mouseDownHandler.bind(this);
23847
- viewport.addEventListener('touchstart', this.boundTouchStartHandler);
23848
- }
23849
-
23850
- removeViewportTouchStartHandler(viewport) {
23851
- viewport.removeEventListener('touchstart', this.boundTouchStartHandler);
23852
- }
23853
-
23854
- addViewportMouseUpHandler(viewport) {
23855
- this.boundMouseUpHandler = mouseUpHandler.bind(this);
23856
- viewport.addEventListener('mouseup', this.boundMouseUpHandler);
23857
- }
23841
+ });
23858
23842
 
23859
- removeViewportMouseUpHandler(viewport) {
23860
- viewport.removeEventListener('mouseup', this.boundMouseUpHandler);
23861
23843
  }
23862
23844
 
23863
- addViewportTouchEndHandler(viewport) {
23864
- this.boundTouchEndHandler = mouseUpHandler.bind(this);
23865
- viewport.addEventListener('touchend', this.boundTouchEndHandler);
23866
- }
23867
-
23868
- removeViewportTouchEndHandler(viewport) {
23869
- viewport.removeEventListener('touchend', this.boundTouchEndHandler);
23870
- }
23871
23845
 
23872
23846
  addViewportClickHandler(viewport) {
23873
23847
 
23874
- this.boundClickHandler = clickHandler.bind(this);
23875
- viewport.addEventListener('click', this.boundClickHandler);
23876
-
23877
- function clickHandler(event) {
23848
+ viewport.addEventListener('click', (event) => {
23878
23849
 
23879
23850
  if (this.enableClick) {
23880
23851
  if (3 === event.which || event.ctrlKey) {
@@ -23959,21 +23930,12 @@ class TrackViewport extends Viewport {
23959
23930
  lastClickTime = time;
23960
23931
 
23961
23932
  }
23962
-
23963
- }
23964
-
23965
- }
23966
-
23967
- removeViewportClickHandler(viewport) {
23968
- viewport.removeEventListener('click', this.boundClickHandler);
23933
+ });
23969
23934
  }
23970
23935
 
23971
23936
  addTrackLabelClickHandler(trackLabel) {
23972
23937
 
23973
- this.boundTrackLabelClickHandler = clickHandler.bind(this);
23974
- trackLabel.addEventListener('click', this.boundTrackLabelClickHandler);
23975
-
23976
- function clickHandler(event) {
23938
+ trackLabel.addEventListener('click', (event) => {
23977
23939
 
23978
23940
  event.stopPropagation();
23979
23941
 
@@ -23993,48 +23955,18 @@ class TrackViewport extends Viewport {
23993
23955
  this.popover = new Popover(this.browser.columnContainer, (track.name || ''));
23994
23956
  this.popover.presentContentWithEvent(event, str);
23995
23957
  }
23996
- }
23997
- }
23998
-
23999
- removeTrackLabelClickHandler(trackLabel) {
24000
- trackLabel.removeEventListener('click', this.boundTrackLabelClickHandler);
23958
+ });
24001
23959
  }
24002
23960
 
24003
23961
  }
24004
23962
 
24005
- function mouseDownHandler(event) {
24006
- this.enableClick = true;
24007
- this.browser.mouseDownOnViewport(event, this);
24008
- pageCoordinates$1(event);
24009
- }
24010
-
24011
- function mouseUpHandler(event) {
24012
-
24013
- // Any mouse up cancels drag and scrolling
24014
- if (this.browser.dragObject || this.browser.isScrolling) {
24015
- this.browser.cancelTrackPan();
24016
- // event.preventDefault();
24017
- // event.stopPropagation();
24018
- this.enableClick = false; // Until next mouse down
24019
- } else {
24020
- this.browser.cancelTrackPan();
24021
- this.browser.endTrackDrag();
24022
- }
24023
- }
24024
-
24025
23963
  function createClickState(event, viewport) {
24026
23964
 
24027
23965
  const referenceFrame = viewport.referenceFrame;
24028
-
24029
23966
  const viewportCoords = translateMouseCoordinates$1(event, viewport.contentDiv);
24030
23967
  const canvasCoords = translateMouseCoordinates$1(event, viewport.canvas);
24031
-
24032
23968
  const genomicLocation = ((referenceFrame.start) + referenceFrame.toBP(viewportCoords.x));
24033
23969
 
24034
- if (undefined === genomicLocation || null === viewport.tile) {
24035
- return undefined
24036
- }
24037
-
24038
23970
  return {
24039
23971
  event,
24040
23972
  viewport,
@@ -24095,22 +24027,30 @@ function formatPopoverText(nameValues) {
24095
24027
  return rows.join('')
24096
24028
  }
24097
24029
 
24098
- var Tile = function (chr, tileStart, tileEnd, bpPerPixel, features, roiFeatures) {
24099
- this.chr = chr;
24100
- this.startBP = tileStart;
24101
- this.endBP = tileEnd;
24102
- this.bpPerPixel = bpPerPixel;
24103
- this.features = features;
24104
- this.roiFeatures = roiFeatures;
24105
- };
24030
+ class FeatureCache {
24106
24031
 
24107
- Tile.prototype.containsRange = function (chr, start, end, bpPerPixel) {
24108
- return this.bpPerPixel === bpPerPixel && start >= this.startBP && end <= this.endBP && chr === this.chr
24109
- };
24032
+ constructor(chr, tileStart, tileEnd, bpPerPixel, features, roiFeatures, multiresolution) {
24033
+ this.chr = chr;
24034
+ this.startBP = tileStart;
24035
+ this.endBP = tileEnd;
24036
+ this.bpPerPixel = bpPerPixel;
24037
+ this.features = features;
24038
+ this.roiFeatures = roiFeatures;
24039
+ this.multiresolution = multiresolution;
24040
+ }
24110
24041
 
24111
- Tile.prototype.overlapsRange = function (chr, start, end) {
24112
- return this.chr === chr && end >= this.startBP && start <= this.endBP
24113
- };
24042
+ containsRange(chr, start, end, bpPerPixel) {
24043
+
24044
+ // For multi-resolution tracks allow for a 2X change in bpPerPixel
24045
+ const r = this.multiresolution ? this.bpPerPixel / bpPerPixel : 1;
24046
+
24047
+ return start >= this.startBP && end <= this.endBP && chr === this.chr && r > 0.5 && r < 2
24048
+ }
24049
+
24050
+ overlapsRange(chr, start, end) {
24051
+ return this.chr === chr && end >= this.startBP && start <= this.endBP
24052
+ }
24053
+ }
24114
24054
 
24115
24055
 
24116
24056
  /**
@@ -24275,11 +24215,13 @@ class RulerSweeper {
24275
24215
 
24276
24216
  validateLocusExtent(this.rulerViewport.browser.genome.getChromosome(this.rulerViewport.referenceFrame.chr).bpLength, extent, this.rulerViewport.browser.minimumBases());
24277
24217
 
24278
- this.rulerViewport.referenceFrame.bpPerPixel = (Math.round(extent.end) - Math.round(extent.start)) / this.rulerViewport.contentDiv.clientWidth;
24279
- this.rulerViewport.referenceFrame.start = Math.round(extent.start);
24280
- this.rulerViewport.referenceFrame.end = Math.round(extent.end);
24218
+ const newStart = Math.round(extent.start);
24219
+ const newEnd = Math.round(extent.end);
24220
+ this.rulerViewport.referenceFrame.bpPerPixel = (newEnd - newStart) / this.rulerViewport.contentDiv.clientWidth;
24221
+ this.rulerViewport.referenceFrame.start = newStart;
24222
+ this.rulerViewport.referenceFrame.end = newEnd;
24281
24223
 
24282
- this.rulerViewport.browser.updateViews(this.rulerViewport.referenceFrame);
24224
+ this.rulerViewport.browser.updateViews();
24283
24225
  }
24284
24226
 
24285
24227
  }
@@ -24417,6 +24359,18 @@ class PairedAlignment {
24417
24359
  return true // By definition
24418
24360
  }
24419
24361
 
24362
+ isMateMapped() {
24363
+ return true // By definition
24364
+ }
24365
+
24366
+ isProperPair() {
24367
+ return this.firstAlignment.isProperPair()
24368
+ }
24369
+
24370
+ get fragmentLength() {
24371
+ return Math.abs(this.firstAlignment.fragmentLength)
24372
+ }
24373
+
24420
24374
  firstOfPairStrand() {
24421
24375
 
24422
24376
  if (this.firstAlignment.isFirstOfPair()) {
@@ -24758,7 +24712,10 @@ function packAlignmentRows(alignments, start, end, showSoftClips) {
24758
24712
 
24759
24713
 
24760
24714
  class AlignmentContainer {
24761
- constructor(chr, start, end, samplingWindowSize, samplingDepth, pairsSupported, alleleFreqThreshold) {
24715
+
24716
+ // this.config.samplingWindowSize, this.config.samplingDepth,
24717
+ // this.config.pairsSupported, this.config.alleleFreqThreshold)
24718
+ constructor(chr, start, end, {samplingWindowSize, samplingDepth, pairsSupported, alleleFreqThreshold}) {
24762
24719
 
24763
24720
  this.chr = chr;
24764
24721
  this.start = Math.floor(start);
@@ -24786,17 +24743,12 @@ class AlignmentContainer {
24786
24743
  return alignment.isMapped() && !alignment.isFailsVendorQualityCheck()
24787
24744
  };
24788
24745
 
24789
- this.pairedEndStats = new PairedEndStats();
24790
24746
  }
24791
24747
 
24792
24748
  push(alignment) {
24793
24749
 
24794
24750
  if (this.filter(alignment) === false) return
24795
24751
 
24796
- if (alignment.isPaired()) {
24797
- this.pairedEndStats.push(alignment);
24798
- }
24799
-
24800
24752
  this.coverageMap.incCounts(alignment); // Count coverage before any downsampling
24801
24753
 
24802
24754
  if (this.pairsSupported && this.downsampledReads.has(alignment.readName)) {
@@ -24828,8 +24780,6 @@ class AlignmentContainer {
24828
24780
 
24829
24781
  this.pairsCache = undefined;
24830
24782
  this.downsampledReads = undefined;
24831
-
24832
- this.pairedEndStats.compute();
24833
24783
  }
24834
24784
 
24835
24785
  contains(chr, start, end) {
@@ -25140,69 +25090,6 @@ class DownsampledInterval {
25140
25090
  }
25141
25091
  }
25142
25092
 
25143
- class PairedEndStats {
25144
-
25145
- constructor(lowerPercentile, upperPercentile) {
25146
- this.totalCount = 0;
25147
- this.frCount = 0;
25148
- this.rfCount = 0;
25149
- this.ffCount = 0;
25150
- this.sumF = 0;
25151
- this.sumF2 = 0;
25152
- //this.lp = lowerPercentile === undefined ? 0.005 : lowerPercentile;
25153
- //this.up = upperPercentile === undefined ? 0.995 : upperPercentile;
25154
- //this.digest = new Digest();
25155
- }
25156
-
25157
- push(alignment) {
25158
-
25159
- if (alignment.isProperPair()) {
25160
-
25161
- var fragmentLength = Math.abs(alignment.fragmentLength);
25162
- //this.digest.push(fragmentLength);
25163
- this.sumF += fragmentLength;
25164
- this.sumF2 += fragmentLength * fragmentLength;
25165
-
25166
- var po = alignment.pairOrientation;
25167
-
25168
- if (typeof po === "string" && po.length === 4) {
25169
- var tmp = '' + po.charAt(0) + po.charAt(2);
25170
- switch (tmp) {
25171
- case 'FF':
25172
- case 'RR':
25173
- this.ffCount++;
25174
- break
25175
- case "FR":
25176
- this.frCount++;
25177
- break
25178
- case"RF":
25179
- this.rfCount++;
25180
- }
25181
- }
25182
- this.totalCount++;
25183
- }
25184
- }
25185
-
25186
- compute() {
25187
-
25188
- if (this.totalCount > 100) {
25189
- if (this.ffCount / this.totalCount > 0.9) this.orienation = "ff";
25190
- else if (this.frCount / this.totalCount > 0.9) this.orienation = "fr";
25191
- else if (this.rfCount / this.totalCount > 0.9) this.orienation = "rf";
25192
-
25193
-
25194
- var fMean = this.sumF / this.totalCount;
25195
- var stdDev = Math.sqrt((this.totalCount * this.sumF2 - this.sumF * this.sumF) / (this.totalCount * this.totalCount));
25196
- this.lowerFragmentLength = fMean - 3 * stdDev;
25197
- this.upperFragmentLength = fMean + 3 * stdDev;
25198
-
25199
- //this.lowerFragmentLength = this.digest.percentile(this.lp);
25200
- //this.upperFragmentLength = this.digest.percentile(this.up);
25201
- //this.digest = undefined;
25202
- }
25203
- }
25204
- }
25205
-
25206
25093
  class SupplementaryAlignment {
25207
25094
 
25208
25095
  constructor(rec) {
@@ -25923,7 +25810,7 @@ const BamUtils = {
25923
25810
  const lseq = readInt(ba, offset + 20);
25924
25811
  const mateChrIdx = readInt(ba, offset + 24);
25925
25812
  const matePos = readInt(ba, offset + 28);
25926
- const tlen = readInt(ba, offset + 32);
25813
+ const fragmentLength = readInt(ba, offset + 32);
25927
25814
 
25928
25815
  let readName = [];
25929
25816
  for (let j = 0; j < nl - 1; ++j) {
@@ -25964,7 +25851,7 @@ const BamUtils = {
25964
25851
  alignment.readName = readName;
25965
25852
  alignment.cigar = cigar;
25966
25853
  alignment.lengthOnRef = lengthOnRef;
25967
- alignment.fragmentLength = tlen;
25854
+ alignment.fragmentLength = fragmentLength;
25968
25855
  alignment.mq = mq;
25969
25856
 
25970
25857
  BamUtils.bam_tag2cigar(ba, blockEnd, p, lseq, alignment, cigarArray);
@@ -26392,7 +26279,7 @@ class BamReaderNonIndexed {
26392
26279
  const header = this.header;
26393
26280
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
26394
26281
  const qAlignments = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
26395
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
26282
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
26396
26283
  for (let a of qAlignments) {
26397
26284
  alignmentContainer.push(a);
26398
26285
  }
@@ -26419,13 +26306,13 @@ class BamReaderNonIndexed {
26419
26306
  const alignments = [];
26420
26307
  this.header = BamUtils.decodeBamHeader(data);
26421
26308
  BamUtils.decodeBamRecords(data, this.header.size, alignments, this.header.chrNames);
26422
- this.alignmentCache = new FeatureCache(alignments, this.genome);
26309
+ this.alignmentCache = new FeatureCache$1(alignments, this.genome);
26423
26310
  }
26424
26311
 
26425
26312
  fetchAlignments(chr, bpStart, bpEnd) {
26426
26313
  const queryChr = this.header.chrAliasTable.hasOwnProperty(chr) ? this.header.chrAliasTable[chr] : chr;
26427
26314
  const features = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
26428
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported);
26315
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
26429
26316
  for (let feature of features) {
26430
26317
  alignmentContainer.push(feature);
26431
26318
  }
@@ -27416,9 +27303,7 @@ class BamReader {
27416
27303
  const chrToIndex = await this.getChrIndex();
27417
27304
  const queryChr = this.chrAliasTable.hasOwnProperty(chr) ? this.chrAliasTable[chr] : chr;
27418
27305
  const chrId = chrToIndex[queryChr];
27419
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd,
27420
- this.config.samplingWindowSize, this.config.samplingDepth,
27421
- this.config.pairsSupported, this.config.alleleFreqThreshold);
27306
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
27422
27307
 
27423
27308
  if (chrId === undefined) {
27424
27309
  return alignmentContainer
@@ -27550,7 +27435,7 @@ class ShardedBamReader {
27550
27435
  async readAlignments(chr, start, end) {
27551
27436
 
27552
27437
  if (!this.bamReaders.hasOwnProperty(chr)) {
27553
- return new AlignmentContainer(chr, start, end)
27438
+ return new AlignmentContainer(chr, start, end, this.config)
27554
27439
  } else {
27555
27440
 
27556
27441
  let reader = this.bamReaders[chr];
@@ -27641,7 +27526,7 @@ BamWebserviceReader.prototype.readAlignments = function (chr, bpStart, bpEnd) {
27641
27526
 
27642
27527
  header.chrToIndex[queryChr];
27643
27528
 
27644
- alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold);
27529
+ alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.config);
27645
27530
 
27646
27531
  BamUtils.decodeSamRecords(sam, alignmentContainer, queryChr, bpStart, bpEnd, self.filter);
27647
27532
 
@@ -27895,7 +27780,7 @@ class HtsgetBamReader extends HtsgetReader {
27895
27780
  const ba = unbgzf(compressedData.buffer);
27896
27781
 
27897
27782
  const chrIdx = this.header.chrToIndex[chr];
27898
- const alignmentContainer = new AlignmentContainer(chr, start, end, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
27783
+ const alignmentContainer = new AlignmentContainer(chr, start, end, this.config);
27899
27784
  BamUtils.decodeBamRecords(ba, this.header.size, alignmentContainer, this.header.chrNames, chrIdx, start, end);
27900
27785
  alignmentContainer.finish();
27901
27786
 
@@ -28061,8 +27946,7 @@ class CramReader {
28061
27946
  const header = await this.getHeader();
28062
27947
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
28063
27948
  const chrIdx = header.chrToIndex[queryChr];
28064
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd,
28065
- this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
27949
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
28066
27950
 
28067
27951
  if (chrIdx === undefined) {
28068
27952
  return alignmentContainer
@@ -28678,7 +28562,7 @@ Ga4ghAlignmentReader.prototype.readAlignments = function (chr, bpStart, bpEnd) {
28678
28562
  "pageSize": "10000"
28679
28563
  },
28680
28564
  decode: decodeGa4ghReads,
28681
- results: new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold)
28565
+ results: new AlignmentContainer(chr, bpStart, bpEnd, self.config)
28682
28566
  })
28683
28567
  })
28684
28568
 
@@ -29011,7 +28895,6 @@ class BamSource {
29011
28895
 
29012
28896
  this.config = config;
29013
28897
  this.genome = genome;
29014
- this.alignmentContainer = undefined;
29015
28898
 
29016
28899
  if (isDataURL(config.url)) {
29017
28900
  if ("cram" === config.format) {
@@ -29059,19 +28942,11 @@ class BamSource {
29059
28942
  }
29060
28943
 
29061
28944
  setViewAsPairs(bool) {
29062
-
29063
- if (this.viewAsPairs !== bool) {
29064
- this.viewAsPairs = bool;
29065
- // if (this.alignmentContainer) {
29066
- // this.alignmentContainer.setViewAsPairs(bool);
29067
- // }
29068
- }
28945
+ this.viewAsPairs = bool;
29069
28946
  }
29070
28947
 
29071
28948
  setShowSoftClips(bool) {
29072
- if (this.showSoftClips !== bool) {
29073
- this.showSoftClips = bool;
29074
- }
28949
+ this.showSoftClips = bool;
29075
28950
  }
29076
28951
 
29077
28952
  async getAlignments(chr, bpStart, bpEnd) {
@@ -29079,33 +28954,28 @@ class BamSource {
29079
28954
  const genome = this.genome;
29080
28955
  const showSoftClips = this.showSoftClips;
29081
28956
 
29082
- if (this.alignmentContainer && this.alignmentContainer.contains(chr, bpStart, bpEnd)) {
29083
- return this.alignmentContainer
28957
+ const alignmentContainer = await this.bamReader.readAlignments(chr, bpStart, bpEnd);
28958
+ let alignments = alignmentContainer.alignments;
28959
+ if (!this.viewAsPairs) {
28960
+ alignments = unpairAlignments([{alignments: alignments}]);
28961
+ }
28962
+ const hasAlignments = alignments.length > 0;
28963
+ alignmentContainer.packedAlignmentRows = packAlignmentRows(alignments, alignmentContainer.start, alignmentContainer.end, showSoftClips);
29084
28964
 
29085
- } else {
29086
- const alignmentContainer = await this.bamReader.readAlignments(chr, bpStart, bpEnd);
29087
- let alignments = alignmentContainer.alignments;
29088
- if (!this.viewAsPairs) {
29089
- alignments = unpairAlignments([{alignments: alignments}]);
29090
- }
29091
- const hasAlignments = alignments.length > 0;
29092
- alignmentContainer.packedAlignmentRows = packAlignmentRows(alignments, alignmentContainer.start, alignmentContainer.end, showSoftClips);
29093
- alignmentContainer.alignments = undefined; // Don't need to hold onto these anymore
29094
-
29095
- this.alignmentContainer = alignmentContainer;
29096
-
29097
- if (hasAlignments) {
29098
- const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
29099
- if (sequence) {
29100
- alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
29101
- alignmentContainer.sequence = sequence; // TODO -- fix this
29102
- return alignmentContainer
29103
- } else {
29104
- console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
29105
- }
28965
+ this.alignmentContainer = alignmentContainer;
28966
+
28967
+ if (hasAlignments) {
28968
+ const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
28969
+ if (sequence) {
28970
+ alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
28971
+ alignmentContainer.sequence = sequence; // TODO -- fix this
28972
+ return alignmentContainer
28973
+ } else {
28974
+ console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
29106
28975
  }
29107
- return alignmentContainer
29108
28976
  }
28977
+ return alignmentContainer
28978
+
29109
28979
  }
29110
28980
  }
29111
28981
 
@@ -29285,7 +29155,7 @@ class TrackBase {
29285
29155
  return state
29286
29156
  }
29287
29157
 
29288
- supportsWholeGenome() {
29158
+ get supportsWholeGenome() {
29289
29159
  return false
29290
29160
  }
29291
29161
 
@@ -29423,7 +29293,7 @@ class TrackBase {
29423
29293
 
29424
29294
  // We use the cached features rather than method to avoid async load. If the
29425
29295
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
29426
- if (!features) features = clickState.viewport.getCachedFeatures();
29296
+ if (!features) features = clickState.viewport.cachedFeatures;
29427
29297
 
29428
29298
  if (!features || features.length === 0) {
29429
29299
  return []
@@ -29927,8 +29797,7 @@ class Locus {
29927
29797
  }
29928
29798
 
29929
29799
  overlaps(locus) {
29930
- return locus.chr === this.chr
29931
- && !(locus.end < this.start || locus.start > this.end)
29800
+ return locus.chr === this.chr && !(locus.end < this.start || locus.start > this.end)
29932
29801
  }
29933
29802
 
29934
29803
  extend(l) {
@@ -31805,6 +31674,22 @@ function makeCircViewChromosomes(genome) {
31805
31674
  return regions
31806
31675
  }
31807
31676
 
31677
+ function sendChords(chords, track, refFrame, alpha) {
31678
+
31679
+ const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? track.color : getChrColor(refFrame.chr), alpha);
31680
+ const trackColor = IGVColor.addAlpha(track.color || 'rgb(0,0,255)', alpha);
31681
+
31682
+ // name the chord set to include locus and filtering information
31683
+ const encodedName = track.name.replaceAll(' ', '%20');
31684
+ const chordSetName = "all" === refFrame.chr ? encodedName :
31685
+ `${encodedName} ${refFrame.chr}:${refFrame.start}-${refFrame.end}`;
31686
+ track.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor});
31687
+
31688
+ // show circular view if hidden
31689
+ if(!track.browser.circularViewVisible) track.browser.circularViewVisible = true;
31690
+
31691
+ }
31692
+
31808
31693
 
31809
31694
  function createCircularView(el, browser) {
31810
31695
 
@@ -31814,42 +31699,105 @@ function createCircularView(el, browser) {
31814
31699
 
31815
31700
  const f1 = feature.data;
31816
31701
  const f2 = f1.mate;
31817
- const flanking = 2000;
31702
+ addFrameForFeature(f1);
31703
+ addFrameForFeature(f2);
31818
31704
 
31819
- const l1 = new Locus({chr: browser.genome.getChromosomeName(f1.refName), start: f1.start, end: f1.end});
31820
- const l2 = new Locus({chr: browser.genome.getChromosomeName(f2.refName), start: f2.start, end: f2.end});
31705
+ function addFrameForFeature(feature) {
31821
31706
 
31822
- let loci;
31707
+ feature.chr = browser.genome.getChromosomeName(feature.refName);
31708
+ let frameFound = false;
31709
+ for (let referenceFrame of browser.referenceFrameList) {
31710
+ const l = Locus.fromLocusString(referenceFrame.getLocusString());
31711
+ if (l.contains(feature)) {
31712
+ frameFound = true;
31713
+ break
31714
+ } else if (l.overlaps(feature)) {
31715
+ referenceFrame.extend(feature);
31716
+ frameFound = true;
31717
+ break
31718
+ }
31719
+ }
31720
+ if (!frameFound) {
31721
+ const flanking = 2000;
31722
+ const center = (feature.start + feature.end) / 2;
31723
+ browser.addMultiLocusPanel(feature.chr, center - flanking, center + flanking);
31823
31724
 
31824
- // If there is overlap with current loci
31725
+ }
31726
+ }
31727
+ }
31728
+ });
31825
31729
 
31826
- loci = browser.currentLoci().map(str => Locus.fromLocusString(str));
31730
+ return circularView
31731
+ }
31827
31732
 
31828
- if (loci.some(locus => locus.contains(l1)) || loci.some(locus => locus.contains(l2))) {
31829
- for (let l of [l1, l2]) {
31830
- if (!loci.some(locus => {
31831
- return locus.contains(l)
31832
- })) {
31833
- // add flanking
31834
- l.start = Math.max(0, l.start - flanking);
31835
- l.end += flanking;
31836
- loci.push(l);
31733
+ class PairedEndStats {
31734
+
31735
+ constructor(alignments, {minTLENPercentile, maxTLENPercentile}) {
31736
+ this.totalCount = 0;
31737
+ this.frCount = 0;
31738
+ this.rfCount = 0;
31739
+ this.ffCount = 0;
31740
+ this.sumF = 0;
31741
+ this.sumF2 = 0;
31742
+ this.lp = minTLENPercentile === undefined ? 0.1 : minTLENPercentile;
31743
+ this.up = maxTLENPercentile === undefined ? 99.5 : maxTLENPercentile;
31744
+ this.isizes = [];
31745
+ this.compute(alignments);
31746
+ }
31747
+
31748
+ compute(alignments) {
31749
+
31750
+ for (let alignment of alignments) {
31751
+ if (alignment.isProperPair()) {
31752
+ var tlen = Math.abs(alignment.fragmentLength);
31753
+ this.sumF += tlen;
31754
+ this.sumF2 += tlen * tlen;
31755
+ this.isizes.push(tlen);
31756
+
31757
+ var po = alignment.pairOrientation;
31758
+
31759
+ if (typeof po === "string" && po.length === 4) {
31760
+ var tmp = '' + po.charAt(0) + po.charAt(2);
31761
+ switch (tmp) {
31762
+ case 'FF':
31763
+ case 'RR':
31764
+ this.ffCount++;
31765
+ break
31766
+ case "FR":
31767
+ this.frCount++;
31768
+ break
31769
+ case"RF":
31770
+ this.rfCount++;
31837
31771
  }
31838
31772
  }
31839
- } else {
31840
- l1.start = Math.max(0, l1.start - flanking);
31841
- l1.end += flanking;
31842
- l2.start = Math.max(0, l2.start - flanking);
31843
- l2.end += flanking;
31844
- loci = [l1, l2];
31773
+ this.totalCount++;
31845
31774
  }
31846
-
31847
- const searchString = loci.map(l => l.getLocusString()).join(" ");
31848
- browser.search(searchString);
31849
31775
  }
31776
+
31777
+ if (this.ffCount / this.totalCount > 0.9) this.orienation = "ff";
31778
+ else if (this.frCount / this.totalCount > 0.9) this.orienation = "fr";
31779
+ else if (this.rfCount / this.totalCount > 0.9) this.orienation = "rf";
31780
+
31781
+ this.minTLEN = this.lp === 0 ? 0 : percentile(this.isizes, this.lp);
31782
+ this.maxTLEN = percentile(this.isizes, this.up);
31783
+
31784
+ // var fMean = this.sumF / this.totalCount
31785
+ // var stdDev = Math.sqrt((this.totalCount * this.sumF2 - this.sumF * this.sumF) / (this.totalCount * this.totalCount))
31786
+ // this.minTLEN = fMean - 3 * stdDev
31787
+ // this.maxTLEN = fMean + 3 * stdDev
31788
+
31789
+ }
31790
+ }
31791
+
31792
+ function percentile(array, p) {
31793
+
31794
+ if (array.length === 0) return undefined
31795
+ var k = Math.floor(array.length * (p / 100));
31796
+ array.sort(function (a, b) {
31797
+ return a - b
31850
31798
  });
31799
+ return array[k]
31851
31800
 
31852
- return circularView
31853
31801
  }
31854
31802
 
31855
31803
  /*
@@ -31916,8 +31864,6 @@ class BAMTrack extends TrackBase {
31916
31864
  this.showMismatches = false !== config.showMismatches;
31917
31865
  this.color = config.color;
31918
31866
  this.coverageColor = config.coverageColor;
31919
- this.minFragmentLength = config.minFragmentLength; // Optional, might be undefined
31920
- this.maxFragmentLength = config.maxFragmentLength || 1000;
31921
31867
 
31922
31868
  // The sort object can be an array in the case of multi-locus view, however if multiple sort positions
31923
31869
  // are present for a given reference frame the last one will take precedence
@@ -31945,12 +31891,24 @@ class BAMTrack extends TrackBase {
31945
31891
  return this._height
31946
31892
  }
31947
31893
 
31894
+ get minTemplateLength() {
31895
+ const configMinTLEN = this.config.minTLEN !== undefined ? this.config.minTLEN : this.config.minFragmentLength;
31896
+ return (configMinTLEN !== undefined) ? configMinTLEN :
31897
+ this._pairedEndStats ? this._pairedEndStats.minTLEN : 0
31898
+ }
31899
+
31900
+ get maxTemplateLength() {
31901
+ const configMaxTLEN = this.config.maxTLEN !== undefined ? this.config.maxTLEN : this.config.maxFragmentLength;
31902
+ return (configMaxTLEN !== undefined) ? configMaxTLEN :
31903
+ this._pairedEndStats ? this._pairedEndStats.maxTLEN : 1000
31904
+ }
31905
+
31948
31906
  sort(options) {
31949
31907
  options = this.assignSort(options);
31950
31908
 
31951
31909
  for (let vp of this.trackView.viewports) {
31952
31910
  if (vp.containsPosition(options.chr, options.position)) {
31953
- const alignmentContainer = vp.getCachedFeatures();
31911
+ const alignmentContainer = vp.cachedFeatures;
31954
31912
  if (alignmentContainer) {
31955
31913
  sortAlignmentRows(options, alignmentContainer);
31956
31914
  vp.repaint();
@@ -31985,14 +31943,13 @@ class BAMTrack extends TrackBase {
31985
31943
 
31986
31944
  const alignmentContainer = await this.featureSource.getAlignments(chr, bpStart, bpEnd);
31987
31945
 
31988
- if (alignmentContainer.alignments && alignmentContainer.alignments.length > 99) {
31989
- if (undefined === this.minFragmentLength) {
31990
- this.minFragmentLength = alignmentContainer.pairedEndStats.lowerFragmentLength;
31991
- }
31992
- if (undefined === this.maxFragmentLength) {
31993
- this.maxFragmentLength = alignmentContainer.pairedEndStats.upperFragmentLength;
31946
+ if (alignmentContainer.paired && !this._pairedEndStats && !this.config.maxFragmentLength) {
31947
+ const pairedEndStats = new PairedEndStats(alignmentContainer.alignments, this.config);
31948
+ if (pairedEndStats.totalCount > 99) {
31949
+ this._pairedEndStats = pairedEndStats;
31994
31950
  }
31995
31951
  }
31952
+ alignmentContainer.alignments = undefined; // Don't need to hold onto these anymore
31996
31953
 
31997
31954
  const sort = this.sortObject;
31998
31955
  if (sort) {
@@ -32089,7 +32046,7 @@ class BAMTrack extends TrackBase {
32089
32046
  if (this.alignmentTrack.hasPairs) {
32090
32047
  colorByMenuItems.push({key: 'firstOfPairStrand', label: 'first-of-pair strand'});
32091
32048
  colorByMenuItems.push({key: 'pairOrientation', label: 'pair orientation'});
32092
- colorByMenuItems.push({key: 'fragmentLength', label: 'insert size (TLEN)'});
32049
+ colorByMenuItems.push({key: 'tlen', label: 'insert size (TLEN)'});
32093
32050
  colorByMenuItems.push({key: 'unexpectedPair', label: 'pair orientation & insert size (TLEN)'});
32094
32051
  }
32095
32052
  const tagLabel = 'tag' + (this.alignmentTrack.colorByTag ? ' (' + this.alignmentTrack.colorByTag + ')' : '');
@@ -32195,32 +32152,17 @@ class BAMTrack extends TrackBase {
32195
32152
  });
32196
32153
  }
32197
32154
 
32198
- // Experimental JBrowse feature
32199
- if (this.browser.circularView && true === this.browser.circularViewVisible &&
32155
+ // Add chords to JBrowse circular view, if present
32156
+ if (this.browser.circularView &&
32200
32157
  (this.alignmentTrack.hasPairs || this.alignmentTrack.hasSupplemental)) {
32201
32158
  menuItems.push('<hr/>');
32202
32159
  if (this.alignmentTrack.hasPairs) {
32203
32160
  menuItems.push({
32204
32161
  label: 'Add discordant pairs to circular view',
32205
32162
  click: () => {
32206
- const maxFragmentLength = this.maxFragmentLength;
32207
- const inView = [];
32208
32163
  for (let viewport of this.trackView.viewports) {
32209
- for (let a of viewport.getCachedFeatures().allAlignments()) {
32210
- const referenceFrame = viewport.referenceFrame;
32211
- if (a.end >= referenceFrame.start
32212
- && a.start <= referenceFrame.end
32213
- && a.mate
32214
- && a.mate.chr
32215
- && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxFragmentLength)) {
32216
- inView.push(a);
32217
- }
32218
- }
32164
+ this.addPairedChordsForViewport(viewport);
32219
32165
  }
32220
- this.browser.circularViewVisible = true;
32221
- const chords = makePairedAlignmentChords(inView);
32222
- const color = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02);
32223
- this.browser.circularView.addChords(chords, {track: this.name, color: color});
32224
32166
  }
32225
32167
  });
32226
32168
  }
@@ -32228,19 +32170,9 @@ class BAMTrack extends TrackBase {
32228
32170
  menuItems.push({
32229
32171
  label: 'Add split reads to circular view',
32230
32172
  click: () => {
32231
- const inView = [];
32232
32173
  for (let viewport of this.trackView.viewports) {
32233
- for (let a of viewport.getCachedFeatures().allAlignments()) {
32234
- const referenceFrame = viewport.referenceFrame;
32235
- const sa = a.hasTag('SA');
32236
- if (a.end >= referenceFrame.start && a.start <= referenceFrame.end && sa) {
32237
- inView.push(a);
32238
- }
32239
- }
32174
+ this.addSplitChordsForViewport(viewport);
32240
32175
  }
32241
- const chords = makeSupplementalAlignmentChords(inView);
32242
- const color = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.1);
32243
- this.browser.circularView.addChords(chords, {track: this.name, color: color});
32244
32176
  }
32245
32177
  });
32246
32178
  }
@@ -32352,7 +32284,7 @@ class BAMTrack extends TrackBase {
32352
32284
  }
32353
32285
 
32354
32286
  getCachedAlignmentContainers() {
32355
- return this.trackView.viewports.map(vp => vp.getCachedFeatures())
32287
+ return this.trackView.viewports.map(vp => vp.cachedFeatures)
32356
32288
  }
32357
32289
 
32358
32290
  get dataRange() {
@@ -32378,6 +32310,62 @@ class BAMTrack extends TrackBase {
32378
32310
  set autoscale(autoscale) {
32379
32311
  this.coverageTrack.autoscale = autoscale;
32380
32312
  }
32313
+
32314
+ /**
32315
+ * Add chords to the circular view for the given viewport, represented by its reference frame
32316
+ * @param refFrame
32317
+ */
32318
+ addPairedChordsForViewport(viewport) {
32319
+
32320
+ const maxTemplateLength = this.maxTemplateLength;
32321
+ const inView = [];
32322
+ const refFrame = viewport.referenceFrame;
32323
+ for (let a of viewport.cachedFeatures.allAlignments()) {
32324
+ if (a.end >= refFrame.start
32325
+ && a.start <= refFrame.end
32326
+ && a.mate
32327
+ && a.mate.chr
32328
+ && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxTemplateLength)) {
32329
+ inView.push(a);
32330
+ }
32331
+ }
32332
+ const chords = makePairedAlignmentChords(inView);
32333
+ sendChords(chords, this, refFrame, 0.02);
32334
+
32335
+ // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
32336
+ // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
32337
+ //
32338
+ // // name the chord set to include track name and locus
32339
+ // const encodedName = this.name.replaceAll(' ', '%20')
32340
+ // const chordSetName = "all" === refFrame.chr ? encodedName :
32341
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
32342
+ // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
32343
+ }
32344
+
32345
+ addSplitChordsForViewport(viewport) {
32346
+
32347
+ const inView = [];
32348
+ const refFrame = viewport.referenceFrame;
32349
+ for (let a of viewport.cachedFeatures.allAlignments()) {
32350
+
32351
+ const sa = a.hasTag('SA');
32352
+ if (a.end >= refFrame.start && a.start <= refFrame.end && sa) {
32353
+ inView.push(a);
32354
+ }
32355
+ }
32356
+
32357
+ const chords = makeSupplementalAlignmentChords(inView);
32358
+ sendChords(chords, this, refFrame, 0.02);
32359
+
32360
+ // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
32361
+ // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
32362
+ //
32363
+ // // name the chord set to include track name and locus
32364
+ // const encodedName = this.name.replaceAll(' ', '%20')
32365
+ // const chordSetName = "all" === refFrame.chr ? encodedName :
32366
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
32367
+ // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
32368
+ }
32381
32369
  }
32382
32370
 
32383
32371
 
@@ -32498,7 +32486,7 @@ class CoverageTrack {
32498
32486
 
32499
32487
  getClickedObject(clickState) {
32500
32488
 
32501
- let features = clickState.viewport.getCachedFeatures();
32489
+ let features = clickState.viewport.cachedFeatures;
32502
32490
  if (!features || features.length === 0) return
32503
32491
 
32504
32492
  const genomicLocation = Math.floor(clickState.genomicLocation);
@@ -32574,8 +32562,8 @@ class AlignmentTrack {
32574
32562
  this.skippedColor = config.skippedColor || "rgb(150, 170, 170)";
32575
32563
  this.pairConnectorColor = config.pairConnectorColor;
32576
32564
 
32577
- this.smallFragmentLengthColor = config.smallFragmentLengthColor || "rgb(0, 0, 150)";
32578
- this.largeFragmentLengthColor = config.largeFragmentLengthColor || "rgb(200, 0, 0)";
32565
+ this.smallTLENColor = config.smallTLENColor || config.smallFragmentLengthColor || "rgb(0, 0, 150)";
32566
+ this.largeTLENColor = config.largeTLENColor || config.largeFragmentLengthColor || "rgb(200, 0, 0)";
32579
32567
 
32580
32568
  this.pairOrientation = config.pairOrienation || 'fr';
32581
32569
  this.pairColors = {};
@@ -32583,7 +32571,7 @@ class AlignmentTrack {
32583
32571
  this.pairColors["RR"] = config.rrColor || "rgb(20, 50, 200)";
32584
32572
  this.pairColors["LL"] = config.llColor || "rgb(0, 150, 150)";
32585
32573
 
32586
- this.colorBy = config.colorBy || "pairOrientation";
32574
+ this.colorBy = config.colorBy || "unexpectedPair";
32587
32575
  this.colorByTag = config.colorByTag ? config.colorByTag.toUpperCase() : undefined;
32588
32576
  this.bamColorTag = config.bamColorTag === undefined ? "YC" : config.bamColorTag;
32589
32577
 
@@ -32690,7 +32678,7 @@ class AlignmentTrack {
32690
32678
  for (let alignment of alignmentRow.alignments) {
32691
32679
 
32692
32680
  this.hasPairs = this.hasPairs || alignment.isPaired();
32693
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
32681
+ if (this.browser.circularView) {
32694
32682
  // This is an expensive check, only do it if needed
32695
32683
  this.hasSupplemental = this.hasSupplemental || alignment.hasTag('SA');
32696
32684
  }
@@ -32975,7 +32963,7 @@ class AlignmentTrack {
32975
32963
  direction: direction
32976
32964
  };
32977
32965
  this.parent.sortObject = newSortObject;
32978
- sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
32966
+ sortAlignmentRows(newSortObject, viewport.cachedFeatures);
32979
32967
  viewport.repaint();
32980
32968
  };
32981
32969
  list.push('<b>Sort by...</b>');
@@ -33005,7 +32993,7 @@ class AlignmentTrack {
33005
32993
  };
33006
32994
  this.sortByTag = tag;
33007
32995
  this.parent.sortObject = newSortObject;
33008
- sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
32996
+ sortAlignmentRows(newSortObject, viewport.cachedFeatures);
33009
32997
  viewport.repaint();
33010
32998
  }
33011
32999
  }
@@ -33032,7 +33020,11 @@ class AlignmentTrack {
33032
33020
  const referenceFrame = clickState.viewport.referenceFrame;
33033
33021
  if (this.browser.genome.getChromosome(clickedAlignment.mate.chr)) {
33034
33022
  this.highlightedAlignmentReadNamed = clickedAlignment.readName;
33035
- this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame);
33023
+ //this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame)
33024
+ const bpWidth = referenceFrame.end - referenceFrame.start;
33025
+ const frameStart = clickedAlignment.mate.position - bpWidth / 2;
33026
+ const frameEnd = clickedAlignment.mate.position + bpWidth / 2;
33027
+ this.browser.addMultiLocusPanel(clickedAlignment.mate.chr, frameStart, frameEnd, referenceFrame);
33036
33028
  } else {
33037
33029
  Alert.presentAlert(`Reference does not contain chromosome: ${clickedAlignment.mate.chr}`);
33038
33030
  }
@@ -33045,10 +33037,7 @@ class AlignmentTrack {
33045
33037
  list.push({
33046
33038
  label: 'View read sequence',
33047
33039
  click: () => {
33048
- const alignment = clickedAlignment;
33049
- if (!alignment) return
33050
-
33051
- const seqstring = alignment.seq; //.map(b => String.fromCharCode(b)).join("");
33040
+ const seqstring = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
33052
33041
  if (!seqstring || "*" === seqstring) {
33053
33042
  Alert.presentAlert("Read sequence: *");
33054
33043
  } else {
@@ -33060,11 +33049,16 @@ class AlignmentTrack {
33060
33049
  if (isSecureContext()) {
33061
33050
  list.push({
33062
33051
  label: 'Copy read sequence',
33063
- click: () => {
33064
- const alignment = clickedAlignment;
33065
- if (!alignment) return
33066
- const seqstring = alignment.seq; //.map(b => String.fromCharCode(b)).join("");
33067
- navigator.clipboard.writeText(seqstring);
33052
+ click: async () => {
33053
+ const seq = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
33054
+ try {
33055
+ //console.log(`seq: ${seq}`)
33056
+ await navigator.clipboard.writeText(seq);
33057
+ } catch (e) {
33058
+ console.error(e);
33059
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
33060
+ }
33061
+
33068
33062
  }
33069
33063
  });
33070
33064
  }
@@ -33074,25 +33068,12 @@ class AlignmentTrack {
33074
33068
  }
33075
33069
 
33076
33070
  // Experimental JBrowse feature
33077
- if (this.browser.circularView && true === this.browser.circularViewVisible
33078
- && (this.hasPairs || this.hasSupplemental)) {
33071
+ if (this.browser.circularView && (this.hasPairs || this.hasSupplemental)) {
33079
33072
  if (this.hasPairs) {
33080
33073
  list.push({
33081
33074
  label: 'Add discordant pairs to circular view',
33082
33075
  click: () => {
33083
- const maxFragmentLength = this.parent.maxFragmentLength;
33084
- const {referenceFrame} = viewport;
33085
- const inView = viewport.getCachedFeatures().allAlignments().filter(a => {
33086
- return a.end >= referenceFrame.start
33087
- && a.start <= referenceFrame.end
33088
- && a.mate
33089
- && a.mate.chr
33090
- && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxFragmentLength)
33091
- });
33092
- this.browser.circularViewVisible = true;
33093
- const chords = makePairedAlignmentChords(inView);
33094
- const color = IGVColor.addAlpha(this.parent.color || 'rgb(0,0,255)', 0.02);
33095
- this.browser.circularView.addChords(chords, {track: this.parent.name, color: color});
33076
+ this.parent.addPairedChordsForViewport(viewport);
33096
33077
  }
33097
33078
  });
33098
33079
  }
@@ -33100,17 +33081,7 @@ class AlignmentTrack {
33100
33081
  list.push({
33101
33082
  label: 'Add split reads to circular view',
33102
33083
  click: () => {
33103
- const inView = [];
33104
- for (let a of viewport.getCachedFeatures().allAlignments()) {
33105
- const referenceFrame = viewport.referenceFrame;
33106
- const sa = a.hasTag('SA');
33107
- if (a.end >= referenceFrame.start && a.start <= referenceFrame.end && sa) {
33108
- inView.push(a);
33109
- }
33110
- }
33111
- const chords = makeSupplementalAlignmentChords(inView);
33112
- const color = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.1);
33113
- this.browser.circularView.addChords(chords, {track: this.name, color: color});
33084
+ this.parent.addSplitChordsForViewport(viewport);
33114
33085
  }
33115
33086
  });
33116
33087
  }
@@ -33129,7 +33100,7 @@ class AlignmentTrack {
33129
33100
 
33130
33101
  const showSoftClips = this.parent.showSoftClips;
33131
33102
 
33132
- let features = viewport.getCachedFeatures();
33103
+ let features = viewport.cachedFeatures;
33133
33104
  if (!features || features.length === 0) return
33134
33105
 
33135
33106
  let packedAlignmentRows = features.packedAlignmentRows;
@@ -33220,14 +33191,17 @@ class AlignmentTrack {
33220
33191
  break
33221
33192
  }
33222
33193
 
33194
+ case "tlen":
33223
33195
  case "fragmentLength":
33224
33196
 
33225
- if (alignment.mate && alignment.isMateMapped() && alignment.mate.chr !== alignment.chr) {
33226
- color = getChrColor(alignment.mate.chr);
33227
- } else if (this.parent.minFragmentLength && Math.abs(alignment.fragmentLength) < this.parent.minFragmentLength) {
33228
- color = this.smallFragmentLengthColor;
33229
- } else if (this.parent.maxFragmentLength && Math.abs(alignment.fragmentLength) > this.parent.maxFragmentLength) {
33230
- color = this.largeFragmentLengthColor;
33197
+ if (alignment.mate && alignment.isMateMapped()) {
33198
+ if (alignment.mate.chr !== alignment.chr) {
33199
+ color = getChrColor(alignment.mate.chr);
33200
+ } else if (this.parent.minTemplateLength && Math.abs(alignment.fragmentLength) < this.parent.minTemplateLength) {
33201
+ color = this.smallTLENColor;
33202
+ } else if (this.parent.maxTemplateLength && Math.abs(alignment.fragmentLength) > this.parent.maxTemplateLength) {
33203
+ color = this.largeTLENColor;
33204
+ }
33231
33205
  }
33232
33206
  break
33233
33207
 
@@ -33269,11 +33243,6 @@ function sortAlignmentRows(options, alignmentContainer) {
33269
33243
  return true === direction ? i : -i
33270
33244
  });
33271
33245
 
33272
- // For debugging
33273
- // for(let r of alignmentContainer.packedAlignmentRows) {
33274
- // console.log(r.score);
33275
- // }
33276
-
33277
33246
  }
33278
33247
 
33279
33248
  function shadedBaseColor(qual, baseColor) {
@@ -33433,7 +33402,7 @@ class RulerViewport extends TrackViewport {
33433
33402
 
33434
33403
  this.$rulerLabel.click(async () => {
33435
33404
 
33436
- await this.browser.selectMultiLocusPanel(this.referenceFrame);
33405
+ await this.browser.gotoMultilocusPanel(this.referenceFrame);
33437
33406
 
33438
33407
  // const removals = this.browser.referenceFrameList.filter(r => this.referenceFrame !== r)
33439
33408
  // for (let referenceFrame of removals) {
@@ -33556,7 +33525,9 @@ class RulerViewport extends TrackViewport {
33556
33525
  currentViewport = this;
33557
33526
  this.$tooltip.show();
33558
33527
  } else if (currentViewport.guid !== this.guid) {
33559
- currentViewport.$tooltip.hide();
33528
+ if (currentViewport.$tooltip) {
33529
+ currentViewport.$tooltip.hide();
33530
+ }
33560
33531
  this.$tooltip.show();
33561
33532
  currentViewport = this;
33562
33533
  } else {
@@ -33583,7 +33554,9 @@ class RulerViewport extends TrackViewport {
33583
33554
 
33584
33555
  // hide tooltip when movement stops
33585
33556
  clearTimeout(timer);
33586
- timer = setTimeout(() => this.$tooltip.hide(), toolTipTimeout);
33557
+ timer = setTimeout(() => {
33558
+ if (this.$tooltip) this.$tooltip.hide();
33559
+ }, toolTipTimeout);
33587
33560
 
33588
33561
  }
33589
33562
 
@@ -33602,69 +33575,6 @@ class RulerViewport extends TrackViewport {
33602
33575
 
33603
33576
  }
33604
33577
 
33605
- const viewportColumnManager =
33606
- {
33607
- createColumns: (columnContainer, count) => {
33608
-
33609
- for (let i = 0; i < count; i++) {
33610
- if (0 === i) {
33611
- createColumn(columnContainer, 'igv-column');
33612
- } else {
33613
- columnContainer.appendChild(div$1({class: 'igv-column-shim'}));
33614
- createColumn(columnContainer, 'igv-column');
33615
- }
33616
- }
33617
-
33618
- },
33619
-
33620
- removeColumnAtIndex: (i, column) => {
33621
- const shim = 0 === i ? column.nextElementSibling : column.previousElementSibling;
33622
- column.remove();
33623
- shim.remove();
33624
- },
33625
-
33626
- insertAfter: referenceElement => {
33627
-
33628
- const shim = div$1({class: 'igv-column-shim'});
33629
- insertElementAfter(shim, referenceElement);
33630
-
33631
- const column = div$1({class: 'igv-column'});
33632
- insertElementAfter(column, shim);
33633
-
33634
- return column
33635
- },
33636
-
33637
- insertBefore: (referenceElement, count) => {
33638
-
33639
- for (let i = 0; i < count; i++) {
33640
-
33641
- const column = div$1({class: 'igv-column'});
33642
- insertElementBefore(column, referenceElement);
33643
-
33644
- if (count > 1 && i > 0) {
33645
- const columnShim = div$1({class: 'igv-column-shim'});
33646
- insertElementBefore(columnShim, column);
33647
- }
33648
-
33649
- }
33650
-
33651
- },
33652
-
33653
- indexOfColumn: (columnContainer, column) => {
33654
-
33655
- const allColumns = columnContainer.querySelectorAll('.igv-column');
33656
-
33657
- for (let i = 0; i < allColumns.length; i++) {
33658
- const c = allColumns[ i ];
33659
- if (c === column) {
33660
- return i
33661
- }
33662
- }
33663
-
33664
- return undefined
33665
- },
33666
- };
33667
-
33668
33578
  /*
33669
33579
  * The MIT License (MIT)
33670
33580
  *
@@ -33698,61 +33608,28 @@ class IdeogramViewport extends TrackViewport {
33698
33608
 
33699
33609
  initializationHelper() {
33700
33610
 
33701
- this.$ideogramCanvas = $$1('<canvas>', {class: 'igv-ideogram-canvas'});
33702
- this.$ideogramCanvas.insertBefore(this.$canvas);
33703
-
33704
- const canvas = this.$ideogramCanvas.get(0);
33705
- this.ideogram_ctx = canvas.getContext('2d');
33706
-
33707
- this.$canvas.remove();
33708
- this.canvas = undefined;
33709
- this.ctx = undefined;
33611
+ this.canvas = document.createElement('canvas');
33612
+ this.canvas.className = 'igv-ideogram-canvas';
33613
+ this.$content.append($$1(this.canvas));
33614
+ this.ideogram_ctx = this.canvas.getContext('2d');
33710
33615
 
33711
33616
  this.addMouseHandlers();
33712
-
33713
33617
  }
33714
33618
 
33715
33619
  addMouseHandlers() {
33716
- this.addBrowserObserver();
33717
33620
  this.addViewportClickHandler(this.$viewport.get(0));
33718
33621
  }
33719
33622
 
33720
- removeMouseHandlers() {
33721
- this.removeBrowserObserver();
33722
- this.removeViewportClickHandler(this.$viewport.get(0));
33723
-
33724
- }
33725
-
33726
- addBrowserObserver() {
33727
-
33728
- function observerHandler(referenceFrameList) {
33729
- const column = this.$viewport.get(0).parentElement;
33730
- if (null !== column) {
33731
- const index = viewportColumnManager.indexOfColumn(this.browser.columnContainer, column);
33732
- // console.log(`ideogram-viewport - locus-change-handler index(${ index }) ${ referenceFrameList[ index ].getLocusString() } ${ Date.now() } `)
33733
- this.update(this.ideogram_ctx, this.$viewport.width(), this.$viewport.height(), referenceFrameList[ index ]);
33734
- }
33735
- }
33736
-
33737
- this.boundObserverHandler = observerHandler.bind(this);
33738
- this.browser.on('locuschange', this.boundObserverHandler);
33739
- }
33740
-
33741
- removeBrowserObserver() {
33742
- this.browser.off('locuschange', this.boundObserverHandler);
33743
- }
33744
-
33745
33623
  addViewportClickHandler(viewport) {
33746
33624
 
33747
- function clickHandler(event) {
33625
+ this.boundClickHandler = clickHandler.bind(this);
33626
+ viewport.addEventListener('click', this.boundClickHandler);
33748
33627
 
33749
- const column = viewport.parentElement;
33750
- const index = viewportColumnManager.indexOfColumn(this.browser.columnContainer, column);
33751
- const referenceFrame = this.browser.referenceFrameList[ index ];
33628
+ function clickHandler(event) {
33752
33629
 
33753
33630
  const {xNormalized, width} = translateMouseCoordinates$1(event, this.ideogram_ctx.canvas);
33754
- const {bpLength} = this.browser.genome.getChromosome(referenceFrame.chr);
33755
- const locusLength = referenceFrame.bpPerPixel * width;
33631
+ const {bpLength} = this.browser.genome.getChromosome(this.referenceFrame.chr);
33632
+ const locusLength = this.referenceFrame.bpPerPixel * width;
33756
33633
  const chrCoveragePercentage = locusLength / bpLength;
33757
33634
 
33758
33635
  let xPercentage = xNormalized;
@@ -33767,21 +33644,14 @@ class IdeogramViewport extends TrackViewport {
33767
33644
  const ss = Math.round((xPercentage - (chrCoveragePercentage / 2.0)) * bpLength);
33768
33645
  const ee = Math.round((xPercentage + (chrCoveragePercentage / 2.0)) * bpLength);
33769
33646
 
33770
- referenceFrame.start = ss;
33771
- referenceFrame.end = ee;
33772
- referenceFrame.bpPerPixel = (ee - ss) / width;
33647
+ this.referenceFrame.start = ss;
33648
+ this.referenceFrame.end = ee;
33649
+ this.referenceFrame.bpPerPixel = (ee - ss) / width;
33773
33650
 
33774
- this.browser.updateViews(referenceFrame, this.browser.trackViews, true);
33651
+ this.browser.updateViews(this.referenceFrame, this.browser.trackViews, true);
33775
33652
 
33776
33653
  }
33777
33654
 
33778
- this.boundClickHandler = clickHandler.bind(this);
33779
- viewport.addEventListener('click', this.boundClickHandler);
33780
-
33781
- }
33782
-
33783
- removeViewportClickHandler(viewport) {
33784
- viewport.removeEventListener('click', this.boundClickHandler);
33785
33655
  }
33786
33656
 
33787
33657
  setWidth(width) {
@@ -33802,10 +33672,20 @@ class IdeogramViewport extends TrackViewport {
33802
33672
  context.restore();
33803
33673
  }
33804
33674
 
33805
- update(context, pixelWidth, pixelHeight, referenceFrame) {
33806
- this.$canvas.hide();
33807
- IGVGraphics.configureHighDPICanvas(context, pixelWidth, pixelHeight);
33808
- this.trackView.track.draw({ context, referenceFrame, pixelWidth, pixelHeight });
33675
+ repaint() {
33676
+ this.draw({referenceFrame: this.referenceFrame});
33677
+ }
33678
+
33679
+ draw({referenceFrame}) {
33680
+
33681
+ IGVGraphics.configureHighDPICanvas(this.ideogram_ctx, this.$viewport.width(), this.$viewport.height());
33682
+
33683
+ this.trackView.track.draw({
33684
+ context: this.ideogram_ctx,
33685
+ referenceFrame,
33686
+ pixelWidth: this.$viewport.width(),
33687
+ pixelHeight: this.$viewport.height()
33688
+ });
33809
33689
  }
33810
33690
 
33811
33691
  startSpinner() {
@@ -33846,7 +33726,7 @@ function createViewport(trackView, column, referenceFrame, width) {
33846
33726
 
33847
33727
  if ('ruler' === trackView.track.type) {
33848
33728
  return new RulerViewport(trackView, column, referenceFrame, width)
33849
- } else if ('ideogram' === trackView.track.type) {
33729
+ } else if ('ideogram' === trackView.track.id) {
33850
33730
  return new IdeogramViewport(trackView, column, referenceFrame, width)
33851
33731
  } else {
33852
33732
  return new TrackViewport(trackView, column, referenceFrame, width)
@@ -34369,15 +34249,10 @@ const colorPickerExclusionTypes = new Set(['ruler', 'sequence', 'ideogram']);
34369
34249
  class TrackView {
34370
34250
 
34371
34251
  constructor(browser, columnContainer, track) {
34372
-
34373
- this.namespace = `trackview-${guid$2()}`;
34374
-
34375
34252
  this.browser = browser;
34376
34253
  this.track = track;
34377
34254
  track.trackView = this;
34378
-
34379
34255
  this.addDOMToColumnContainer(browser, columnContainer, browser.referenceFrameList);
34380
-
34381
34256
  }
34382
34257
 
34383
34258
  /**
@@ -34401,7 +34276,7 @@ class TrackView {
34401
34276
  // Axis
34402
34277
  this.axis = this.createAxis(browser, this.track);
34403
34278
 
34404
- // Track Viewports
34279
+ // Create a viewport for each reference frame
34405
34280
  this.viewports = [];
34406
34281
  const viewportWidth = browser.calculateViewportWidth(referenceFrameList.length);
34407
34282
  const viewportColumns = columnContainer.querySelectorAll('.igv-column');
@@ -34474,7 +34349,6 @@ class TrackView {
34474
34349
 
34475
34350
  // Track Viewports
34476
34351
  for (let viewport of this.viewports) {
34477
- viewport.removeMouseHandlers();
34478
34352
  viewport.$viewport.remove();
34479
34353
  }
34480
34354
 
@@ -34540,19 +34414,14 @@ class TrackView {
34540
34414
  if (false === colorPickerExclusionTypes.has(this.track.type)) {
34541
34415
 
34542
34416
  const trackColors = [];
34543
-
34544
34417
  const color = this.track.color || this.track.defaultColor;
34545
-
34546
34418
  if (isString$3(color)) {
34547
34419
  trackColors.push(color);
34548
34420
  }
34549
-
34550
34421
  if (this.track.altColor && isString$3(this.track.altColor)) {
34551
34422
  trackColors.push(this.track.altColor);
34552
34423
  }
34553
-
34554
34424
  const defaultColors = trackColors.map(c => c.startsWith("#") ? c : c.startsWith("rgb(") ? IGVColor.rgbToHex(c) : IGVColor.colorNameToHex(c));
34555
-
34556
34425
  const colorHandlers =
34557
34426
  {
34558
34427
  color: color => {
@@ -34565,7 +34434,6 @@ class TrackView {
34565
34434
  }
34566
34435
 
34567
34436
  };
34568
-
34569
34437
  this.browser.genericColorPicker.configure(defaultColors, colorHandlers);
34570
34438
  this.browser.genericColorPicker.setActiveColorHandler(key);
34571
34439
  this.browser.genericColorPicker.show();
@@ -34579,7 +34447,6 @@ class TrackView {
34579
34447
  if (this.track.minHeight) {
34580
34448
  newHeight = Math.max(this.track.minHeight, newHeight);
34581
34449
  }
34582
-
34583
34450
  if (this.track.maxHeight) {
34584
34451
  newHeight = Math.min(this.track.maxHeight, newHeight);
34585
34452
  }
@@ -34599,9 +34466,8 @@ class TrackView {
34599
34466
 
34600
34467
  this.sampleNameViewport.viewport.style.height = `${newHeight}px`;
34601
34468
 
34602
- // If the track does not manage its own content height set it here
34469
+ // If the track does not manage its own content height set it equal to the viewport height here
34603
34470
  if (typeof this.track.computePixelHeight !== "function") {
34604
-
34605
34471
  for (let vp of this.viewports) {
34606
34472
  vp.setContentHeight(newHeight);
34607
34473
  }
@@ -34658,15 +34524,6 @@ class TrackView {
34658
34524
  }
34659
34525
  }
34660
34526
 
34661
- resize(viewportWidth) {
34662
-
34663
- for (let viewport of this.viewports) {
34664
- viewport.setWidth(viewportWidth);
34665
- }
34666
-
34667
- this.updateViews(true);
34668
- }
34669
-
34670
34527
  /**
34671
34528
  * Repaint all viewports without loading any new data. Use this for events that change visual aspect of data,
34672
34529
  * e.g. color, sort order, etc, but do not change the genomic state.
@@ -34674,7 +34531,9 @@ class TrackView {
34674
34531
  repaintViews() {
34675
34532
 
34676
34533
  for (let viewport of this.viewports) {
34677
- viewport.repaint();
34534
+ if (viewport.isVisible()) {
34535
+ viewport.repaint();
34536
+ }
34678
34537
  }
34679
34538
 
34680
34539
  if (typeof this.track.paintAxis === 'function') {
@@ -34683,7 +34542,6 @@ class TrackView {
34683
34542
 
34684
34543
  // Repaint sample names last
34685
34544
  this.repaintSamples();
34686
-
34687
34545
  }
34688
34546
 
34689
34547
  repaintSamples() {
@@ -34699,10 +34557,25 @@ class TrackView {
34699
34557
  this.viewports.forEach(viewport => viewport.setTrackLabel(name));
34700
34558
  }
34701
34559
 
34560
+ /**
34561
+ * Called in response to a window resize event, change in # of multilocus panels, or other event that changes
34562
+ * the width of the track view.
34563
+ *
34564
+ * @param viewportWidth The width of each viewport in this track view.
34565
+ */
34566
+ resize(viewportWidth) {
34567
+ for (let viewport of this.viewports) {
34568
+ viewport.setWidth(viewportWidth);
34569
+ }
34570
+ }
34571
+
34702
34572
  /**
34703
34573
  * Update viewports to reflect current genomic state, possibly loading additional data.
34574
+ *
34575
+ * @param force - if true, force a repaint even if no new data is loaded
34576
+ * @returns {Promise<void>}
34704
34577
  */
34705
- async updateViews(force) {
34578
+ async updateViews() {
34706
34579
 
34707
34580
  if (!(this.browser && this.browser.referenceFrameList)) return
34708
34581
 
@@ -34711,31 +34584,38 @@ class TrackView {
34711
34584
  // Shift viewports left/right to current genomic state (pans canvas)
34712
34585
  visibleViewports.forEach(viewport => viewport.shift());
34713
34586
 
34714
- const isDragging = this.browser.dragObject;
34715
-
34716
- if (isDragging) {
34587
+ // If dragging (panning) return
34588
+ if (this.browser.dragObject) {
34717
34589
  return
34718
34590
  }
34719
34591
 
34720
- // rpv: viewports whose image (canvas) does not fully cover current genomic range
34721
- const reloadableViewports = this.viewportsToReload(force);
34592
+ // Get viewports to repaint
34593
+ let viewportsToRepaint = (this.track.autoscale || this.track.autoscaleGroup || this.track.type === 'ruler') ?
34594
+ visibleViewports :
34595
+ visibleViewports.filter(vp => vp.needsRepaint());
34596
+
34597
+ // Filter zoomed out views. This has the side effect or turning off or no the zoomed out notice
34598
+ viewportsToRepaint = viewportsToRepaint.filter(viewport => viewport.checkZoomIn());
34599
+
34600
+ // Get viewports that require a data load
34601
+ const viewportsToReload = viewportsToRepaint.filter(viewport => viewport.needsReload());
34722
34602
 
34723
34603
  // Trigger viewport to load features needed to cover current genomic range
34724
34604
  // NOTE: these must be loaded synchronously, do not user Promise.all, not all file readers are thread safe
34725
- for (let viewport of reloadableViewports) {
34605
+ for (let viewport of viewportsToReload) {
34726
34606
  await viewport.loadFeatures();
34727
34607
  }
34728
-
34608
+
34729
34609
  if (this.disposed) return // Track was removed during load
34730
34610
 
34731
- // Very special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
34611
+ // Special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
34732
34612
  // section depends on data from all the views. We only need to adjust this however if any data was loaded
34733
34613
  // (i.e. reloadableViewports.length > 0)
34734
- if (this.track && typeof this.track.variantRowCount === 'function' && reloadableViewports.length > 0) {
34614
+ if (this.track && typeof this.track.variantRowCount === 'function' && viewportsToReload.length > 0) {
34735
34615
  let maxRow = 0;
34736
34616
  for (let viewport of this.viewports) {
34737
- if (viewport.tile && viewport.tile.features) {
34738
- maxRow = Math.max(maxRow, viewport.tile.features.reduce((a, f) => Math.max(a, f.row || 0), 0));
34617
+ if (viewport.featureCache && viewport.featureCache.features) {
34618
+ maxRow = Math.max(maxRow, viewport.featureCache.features.reduce((a, f) => Math.max(a, f.row || 0), 0));
34739
34619
  }
34740
34620
  }
34741
34621
  const current = this.track.nVariantRows;
@@ -34747,19 +34627,18 @@ class TrackView {
34747
34627
  }
34748
34628
  }
34749
34629
 
34750
-
34751
34630
  if (this.track.autoscale) {
34752
34631
  let allFeatures = [];
34753
34632
  for (let visibleViewport of visibleViewports) {
34754
34633
  const referenceFrame = visibleViewport.referenceFrame;
34755
34634
  const start = referenceFrame.start;
34756
34635
  const end = start + referenceFrame.toBP($$1(visibleViewport.contentDiv).width());
34757
- if (visibleViewport.tile && visibleViewport.tile.features) {
34758
- if (typeof visibleViewport.tile.features.getMax === 'function') {
34759
- const max = visibleViewport.tile.features.getMax(start, end);
34636
+ if (visibleViewport.featureCache && visibleViewport.featureCache.features) {
34637
+ if (typeof visibleViewport.featureCache.features.getMax === 'function') {
34638
+ const max = visibleViewport.featureCache.features.getMax(start, end);
34760
34639
  allFeatures.push({value: max});
34761
34640
  } else {
34762
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.tile.features, start, end));
34641
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.featureCache.features, start, end));
34763
34642
  }
34764
34643
  }
34765
34644
  }
@@ -34770,15 +34649,8 @@ class TrackView {
34770
34649
  }
34771
34650
  }
34772
34651
 
34773
- // Must repaint all viewports if autoscaling
34774
- if (!isDragging && (this.track.autoscale || this.track.autoscaleGroup)) {
34775
- for (let visibleViewport of visibleViewports) {
34776
- visibleViewport.repaint();
34777
- }
34778
- } else {
34779
- for (let vp of reloadableViewports) {
34780
- vp.repaint();
34781
- }
34652
+ for (let vp of viewportsToRepaint) {
34653
+ vp.repaint();
34782
34654
  }
34783
34655
 
34784
34656
  this.adjustTrackHeight();
@@ -34806,34 +34678,29 @@ class TrackView {
34806
34678
  }
34807
34679
 
34808
34680
  /**
34809
- * Return a promise to get all in-view features. Used for group autoscaling.
34681
+ * Return a promise to get all in-view features across all viewports. Used for group autoscaling.
34810
34682
  */
34811
- async getInViewFeatures(force) {
34683
+ async getInViewFeatures() {
34812
34684
 
34813
34685
  if (!(this.browser && this.browser.referenceFrameList)) {
34814
34686
  return []
34815
34687
  }
34816
34688
 
34817
- // List of viewports that need reloading
34818
- const rpV = this.viewportsToReload(force);
34819
- const promises = rpV.map(function (vp) {
34820
- return vp.loadFeatures()
34821
- });
34822
-
34823
- await Promise.all(promises);
34824
-
34825
34689
  let allFeatures = [];
34826
34690
  for (let vp of this.viewports) {
34827
- if (vp.tile && vp.tile.features) {
34691
+ if (vp.needsReload()) {
34692
+ await vp.loadFeatures();
34693
+ }
34694
+ if (vp.featureCache && vp.featureCache.features) {
34828
34695
  const referenceFrame = vp.referenceFrame;
34829
34696
  const start = referenceFrame.start;
34830
34697
  const end = start + referenceFrame.toBP($$1(vp.contentDiv).width());
34831
34698
 
34832
- if (typeof vp.tile.features.getMax === 'function') {
34833
- const max = vp.tile.features.getMax(start, end);
34699
+ if (typeof vp.featureCache.features.getMax === 'function') {
34700
+ const max = vp.featureCache.features.getMax(start, end);
34834
34701
  allFeatures.push({value: max});
34835
34702
  } else {
34836
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.tile.features, start, end));
34703
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.featureCache.features, start, end));
34837
34704
  }
34838
34705
  }
34839
34706
  }
@@ -34874,27 +34741,6 @@ class TrackView {
34874
34741
  }
34875
34742
  }
34876
34743
 
34877
- viewportsToReload(force) {
34878
-
34879
- // List of viewports that need reloading
34880
- const viewports = this.viewports.filter(viewport => {
34881
- if (!viewport.isVisible()) {
34882
- return false
34883
- }
34884
- if (!viewport.checkZoomIn()) {
34885
- return false
34886
- } else {
34887
- const referenceFrame = viewport.referenceFrame;
34888
- const chr = viewport.referenceFrame.chr;
34889
- const start = referenceFrame.start;
34890
- const end = start + referenceFrame.toBP($$1(viewport.contentDiv).width());
34891
- const bpPerPixel = referenceFrame.bpPerPixel;
34892
- return force || (!viewport.tile || viewport.tile.invalidate || !viewport.tile.containsRange(chr, start, end, bpPerPixel))
34893
- }
34894
- });
34895
- return viewports
34896
- }
34897
-
34898
34744
  createTrackScrollbar(browser) {
34899
34745
 
34900
34746
  const outerScroll = div$1();
@@ -35007,7 +34853,7 @@ class TrackView {
35007
34853
 
35008
34854
  addTrackDragMouseHandlers(browser) {
35009
34855
 
35010
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
34856
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
35011
34857
 
35012
34858
  let currentDragHandle = undefined;
35013
34859
 
@@ -35084,7 +34930,7 @@ class TrackView {
35084
34930
 
35085
34931
  removeTrackDragMouseHandlers() {
35086
34932
 
35087
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
34933
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
35088
34934
  this.dragHandle.removeEventListener('mousedown', this.boundTrackDragMouseDownHandler);
35089
34935
  document.removeEventListener('mouseup', this.boundDocumentTrackDragMouseUpHandler);
35090
34936
  this.dragHandle.removeEventListener('mouseup', this.boundTrackDragMouseEnterHandler);
@@ -39662,7 +39508,7 @@ class TextFeatureSource {
39662
39508
  this.sourceType = (config.sourceType === undefined ? "file" : config.sourceType);
39663
39509
  this.maxWGCount = config.maxWGCount || DEFAULT_MAX_WG_COUNT;
39664
39510
 
39665
- const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "tdf"]);
39511
+ const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "tdf"]);
39666
39512
 
39667
39513
  if (config.features && Array.isArray(config.features)) {
39668
39514
  // Explicit array of features
@@ -39672,7 +39518,7 @@ class TextFeatureSource {
39672
39518
  mapProperties(features, config.mappings);
39673
39519
  }
39674
39520
  this.queryable = false;
39675
- this.featureCache = new FeatureCache(features, genome);
39521
+ this.featureCache = new FeatureCache$1(features, genome);
39676
39522
  } else if (config.reader) {
39677
39523
  // Explicit reader implementation
39678
39524
  this.reader = config.reader;
@@ -39839,14 +39685,14 @@ class TextFeatureSource {
39839
39685
  }
39840
39686
 
39841
39687
  // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
39842
- this.featureCache = new FeatureCache(features, this.genome, genomicInterval);
39688
+ this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval);
39843
39689
 
39844
39690
  // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
39845
39691
  if (this.config.searchable || this.config.searchableFields) {
39846
39692
  this.addFeaturesToDB(features);
39847
39693
  }
39848
39694
  } else {
39849
- this.featureCache = new FeatureCache([], genomicInterval); // Empty cache
39695
+ this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
39850
39696
  }
39851
39697
  }
39852
39698
 
@@ -40084,9 +39930,9 @@ class BufferedReader {
40084
39930
 
40085
39931
  //table chromatinInteract
40086
39932
 
40087
- function getDecoder(definedFieldCount, fieldCount, autoSql) {
39933
+ function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
40088
39934
 
40089
- if (autoSql && 'chromatinInteract' === autoSql.table) {
39935
+ if (autoSql && 'chromatinInteract' === autoSql.table || "biginteract" === format) {
40090
39936
  return decodeInteract
40091
39937
  } else {
40092
39938
  const standardFieldCount = definedFieldCount - 3;
@@ -40233,6 +40079,7 @@ class BWReader {
40233
40079
 
40234
40080
  constructor(config, genome) {
40235
40081
  this.path = config.url;
40082
+ this.format = config.format || "bigwig";
40236
40083
  this.genome = genome;
40237
40084
  this.rpTreeCache = {};
40238
40085
  this.config = config;
@@ -40833,7 +40680,7 @@ function decodeWigData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, chr
40833
40680
  function getBedDataDecoder() {
40834
40681
 
40835
40682
  const minSize = 3 * 4 + 1; // Minimum # of bytes required for a bed record
40836
- const decoder = getDecoder(this.header.definedFieldCount, this.header.fieldCount, this.autoSql);
40683
+ const decoder = getDecoder(this.header.definedFieldCount, this.header.fieldCount, this.autoSql, this.format);
40837
40684
  return function (data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, chrDict) {
40838
40685
  const binaryParser = new BinaryParser(data);
40839
40686
  while (binaryParser.remLength() >= minSize) {
@@ -41589,7 +41436,7 @@ class TDFSource {
41589
41436
  return features
41590
41437
  }
41591
41438
 
41592
- supportsWholeGenome() {
41439
+ get supportsWholeGenome() {
41593
41440
  return true
41594
41441
  }
41595
41442
  }
@@ -41701,7 +41548,7 @@ function zoomLevelForScale(chr, bpPerPixel, genome) {
41701
41548
  function FeatureSource(config, genome) {
41702
41549
 
41703
41550
  const format = config.format ? config.format.toLowerCase() : undefined;
41704
- if ('bigwig' === format || 'bigbed' === format || 'bb' === format) {
41551
+ if ('bigwig' === format || 'bigbed' === format || 'bb' === format || "biginteract" === format) {
41705
41552
  return new BWSource(config, genome)
41706
41553
  } else if ("tdf" === format) {
41707
41554
  return new TDFSource(config, genome)
@@ -41710,38 +41557,6 @@ function FeatureSource(config, genome) {
41710
41557
  }
41711
41558
  }
41712
41559
 
41713
- const pairs =
41714
- [
41715
- ['A', 'T'],
41716
- ['G', 'C'],
41717
- ['Y', 'R'],
41718
- ['W', 'S'],
41719
- ['K', 'M'],
41720
- ['D', 'H'],
41721
- ['B', 'V']
41722
- ];
41723
-
41724
- const complements = new Map();
41725
- for (let p of pairs) {
41726
- const p1 = p[0];
41727
- const p2 = p[1];
41728
- complements.set(p1, p2);
41729
- complements.set(p2, p1);
41730
- complements.set(p1.toLowerCase(), p2.toLowerCase());
41731
- complements.set(p2.toLowerCase(), p1.toLowerCase());
41732
- }
41733
-
41734
- function reverseComplementSequence(sequence) {
41735
-
41736
- let comp = '';
41737
- let idx = sequence.length;
41738
- while (idx-- > 0) {
41739
- const base = sequence[idx];
41740
- comp += complements.has(base) ? complements.get(base) : base;
41741
- }
41742
- return comp
41743
- }
41744
-
41745
41560
  const GtexUtils = {
41746
41561
 
41747
41562
  getTissueInfo: function (datasetId, baseURL) {
@@ -41842,7 +41657,7 @@ function renderFeature(feature, bpStart, xScale, pixelHeight, ctx, options) {
41842
41657
  // single-exon transcript
41843
41658
  const xLeft = Math.max(0, coord.px);
41844
41659
  const xRight = Math.min(pixelWidth, coord.px1);
41845
- const width = Math.max(coord.pw, xRight - xLeft);
41660
+ const width = xRight - xLeft;
41846
41661
  ctx.fillRect(xLeft, py, width, h);
41847
41662
 
41848
41663
  // Arrows
@@ -42285,7 +42100,7 @@ class FeatureTrack extends TrackBase {
42285
42100
 
42286
42101
  }
42287
42102
 
42288
- supportsWholeGenome() {
42103
+ get supportsWholeGenome() {
42289
42104
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false
42290
42105
  }
42291
42106
 
@@ -42434,21 +42249,18 @@ class FeatureTrack extends TrackBase {
42434
42249
  const infoURL = this.infoURL || this.config.infoURL;
42435
42250
  for (let fd of featureData) {
42436
42251
  data.push(fd);
42437
- if (infoURL) {
42438
- if (fd.name &&
42439
- fd.name.toLowerCase() === "name" &&
42440
- fd.value &&
42441
- isString$3(fd.value) &&
42442
- !fd.value.startsWith("<")) {
42443
-
42444
-
42445
- const url = this.infoURL || this.config.infoURL;
42446
- const href = url.replace("$$", feature.name);
42447
- data.push({name: "Info", value: `<a target="_blank" href=${href}>${fd.value}</a>`});
42448
- }
42252
+ if (infoURL &&
42253
+ fd.name &&
42254
+ fd.name.toLowerCase() === "name" &&
42255
+ fd.value &&
42256
+ isString$3(fd.value) &&
42257
+ !fd.value.startsWith("<")) {
42258
+ const href = infoURL.replace("$$", feature.name);
42259
+ fd.value = `<a target=_blank href=${href}>${fd.value}</a>`;
42449
42260
  }
42450
42261
  }
42451
42262
 
42263
+
42452
42264
  //Array.prototype.push.apply(data, featureData);
42453
42265
 
42454
42266
  // If we have clicked over an exon number it.
@@ -42477,7 +42289,7 @@ class FeatureTrack extends TrackBase {
42477
42289
  }
42478
42290
 
42479
42291
  menuItemList() {
42480
-
42292
+
42481
42293
  const menuItems = [];
42482
42294
 
42483
42295
  if (this.render === renderSnp) {
@@ -42505,7 +42317,7 @@ class FeatureTrack extends TrackBase {
42505
42317
  menuItems.push(
42506
42318
  {
42507
42319
  object: $$1(createCheckbox$1(lut[displayMode], displayMode === this.displayMode)),
42508
- click: () => {
42320
+ click: () => {
42509
42321
  this.displayMode = displayMode;
42510
42322
  this.config.displayMode = displayMode;
42511
42323
  this.trackView.checkContentHeight();
@@ -42521,14 +42333,28 @@ class FeatureTrack extends TrackBase {
42521
42333
 
42522
42334
  contextMenuItemList(clickState) {
42523
42335
 
42524
- if (isSecureContext()) {
42525
- const features = this.clickedFeatures(clickState);
42526
- if (features.length > 1) {
42527
- features.sort((a, b) => (a.end - a.start) - (b.end - b.start));
42528
- }
42529
- const f = features[0]; // The longest feature
42530
- if ((f.end - f.start) <= 1000000) {
42531
- return [
42336
+ const features = this.clickedFeatures(clickState);
42337
+ if (features.length > 1) {
42338
+ features.sort((a, b) => (b.end - b.start) - (a.end - a.start));
42339
+ }
42340
+ const f = features[0]; // The shortest clicked feature
42341
+
42342
+ if ((f.end - f.start) <= 1000000) {
42343
+ const list = [{
42344
+ label: 'View feature sequence',
42345
+ click: async () => {
42346
+ let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
42347
+ if (f.strand === '-') {
42348
+ seq = reverseComplementSequence(seq);
42349
+ }
42350
+ if (!seq) seq = "Unknown sequence";
42351
+ Alert.presentAlert(seq);
42352
+
42353
+ }
42354
+ }];
42355
+
42356
+ if (isSecureContext() && navigator.clipboard !== undefined) {
42357
+ list.push(
42532
42358
  {
42533
42359
  label: 'Copy feature sequence',
42534
42360
  click: async () => {
@@ -42536,17 +42362,23 @@ class FeatureTrack extends TrackBase {
42536
42362
  if (f.strand === '-') {
42537
42363
  seq = reverseComplementSequence(seq);
42538
42364
  }
42539
- navigator.clipboard.writeText(seq);
42365
+ try {
42366
+ await navigator.clipboard.writeText(seq);
42367
+ } catch (e) {
42368
+ console.error(e);
42369
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
42370
+ }
42540
42371
  }
42541
- },
42542
- '<hr/>'
42543
- ]
42372
+ }
42373
+ );
42544
42374
  }
42545
- }
42375
+ list.push('<hr/>');
42376
+ return list
42377
+ } else {
42546
42378
 
42547
- // Either not a secure context (i.e. http: protocol), or feature is too long
42548
- return undefined
42379
+ return undefined
42549
42380
 
42381
+ }
42550
42382
  }
42551
42383
 
42552
42384
  description() {
@@ -42567,7 +42399,7 @@ class FeatureTrack extends TrackBase {
42567
42399
  desc += "</html>";
42568
42400
  return desc
42569
42401
  } else {
42570
- return super.description();
42402
+ return super.description()
42571
42403
  }
42572
42404
 
42573
42405
  };
@@ -42648,13 +42480,11 @@ class WigTrack extends TrackBase {
42648
42480
  this.paintAxis = paintAxis;
42649
42481
 
42650
42482
  const format = config.format ? config.format.toLowerCase() : config.format;
42483
+ this.flipAxis = config.flipAxis ? config.flipAxis : false;
42484
+ this.logScale = config.logScale ? config.logScale : false;
42651
42485
  if ("bigwig" === format) {
42652
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
42653
- this.logScale = config.logScale ? config.logScale : false;
42654
42486
  this.featureSource = new BWSource(config, this.browser.genome);
42655
42487
  } else if ("tdf" === format) {
42656
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
42657
- this.logScale = config.logScale ? config.logScale : false;
42658
42488
  this.featureSource = new TDFSource(config, this.browser.genome);
42659
42489
  } else {
42660
42490
  this.featureSource = FeatureSource(config, this.browser.genome);
@@ -42706,7 +42536,7 @@ class WigTrack extends TrackBase {
42706
42536
  let items = [];
42707
42537
  if (this.flipAxis !== undefined) {
42708
42538
  items.push({
42709
- label:"Flip y-axis",
42539
+ label: "Flip y-axis",
42710
42540
  click: () => {
42711
42541
  this.flipAxis = !this.flipAxis;
42712
42542
  this.trackView.repaintViews();
@@ -42890,7 +42720,7 @@ class WigTrack extends TrackBase {
42890
42720
  }
42891
42721
  }
42892
42722
 
42893
- supportsWholeGenome() {
42723
+ get supportsWholeGenome() {
42894
42724
  return !this.config.indexURL && this.config.supportsWholeGenome !== false
42895
42725
  }
42896
42726
 
@@ -43434,7 +43264,7 @@ class SegTrack extends TrackBase {
43434
43264
 
43435
43265
  const sortHandler = (sort) => {
43436
43266
  const viewport = clickState.viewport;
43437
- const features = viewport.getCachedFeatures();
43267
+ const features = viewport.cachedFeatures;
43438
43268
  this.sortSamples(sort.chr, sort.start, sort.end, sort.direction, features);
43439
43269
  };
43440
43270
 
@@ -43462,7 +43292,7 @@ class SegTrack extends TrackBase {
43462
43292
 
43463
43293
  }
43464
43294
 
43465
- supportsWholeGenome() {
43295
+ get supportsWholeGenome() {
43466
43296
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false
43467
43297
  }
43468
43298
 
@@ -43552,10 +43382,16 @@ const MUT_COLORS = {
43552
43382
  * THE SOFTWARE.
43553
43383
  */
43554
43384
 
43385
+ /**
43386
+ * Represents 2 or more wig tracks overlaid on a common viewport.
43387
+ */
43555
43388
  class MergedTrack extends TrackBase {
43556
43389
 
43557
43390
  constructor(config, browser) {
43558
43391
  super(config, browser);
43392
+ this.type = "merged";
43393
+ this.featureType = 'numeric';
43394
+ this.paintAxis = paintAxis;
43559
43395
  }
43560
43396
 
43561
43397
  init(config) {
@@ -43566,21 +43402,6 @@ class MergedTrack extends TrackBase {
43566
43402
  super.init(config);
43567
43403
  }
43568
43404
 
43569
- get height() {
43570
- return this._height
43571
- }
43572
-
43573
- set height(h) {
43574
- this._height = h;
43575
- if (this.tracks) {
43576
- for (let t of this.tracks) {
43577
- t.height = h;
43578
- t.config.height = h;
43579
- }
43580
- }
43581
- }
43582
-
43583
-
43584
43405
  async postInit() {
43585
43406
 
43586
43407
  this.tracks = [];
@@ -43600,11 +43421,55 @@ class MergedTrack extends TrackBase {
43600
43421
  }
43601
43422
  }
43602
43423
 
43603
- this.height = this.config.height || 100;
43424
+ this.flipAxis = this.config.flipAxis ? this.config.flipAxis : false;
43425
+ this.logScale = this.config.logScale ? this.config.logScale : false;
43426
+ this.autoscale = this.config.autoscale || this.config.max === undefined;
43427
+ if (!this.autoscale) {
43428
+ this.dataRange = {
43429
+ min: this.config.min || 0,
43430
+ max: this.config.max
43431
+ };
43432
+ }
43433
+ for (let t of this.tracks) {
43434
+ t.autoscale = false;
43435
+ t.dataRange = this.dataRange;
43436
+ }
43437
+
43438
+ this.height = this.config.height || 50;
43604
43439
 
43605
43440
  return Promise.all(p)
43606
43441
  }
43607
43442
 
43443
+ get height() {
43444
+ return this._height
43445
+ }
43446
+
43447
+ set height(h) {
43448
+ this._height = h;
43449
+ if (this.tracks) {
43450
+ for (let t of this.tracks) {
43451
+ t.height = h;
43452
+ t.config.height = h;
43453
+ }
43454
+ }
43455
+ }
43456
+
43457
+ menuItemList() {
43458
+ let items = [];
43459
+ if (this.flipAxis !== undefined) {
43460
+ items.push({
43461
+ label: "Flip y-axis",
43462
+ click: () => {
43463
+ this.flipAxis = !this.flipAxis;
43464
+ this.trackView.repaintViews();
43465
+ }
43466
+ });
43467
+ }
43468
+
43469
+ items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
43470
+
43471
+ return items
43472
+ }
43608
43473
 
43609
43474
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel) {
43610
43475
 
@@ -43614,44 +43479,26 @@ class MergedTrack extends TrackBase {
43614
43479
 
43615
43480
  draw(options) {
43616
43481
 
43617
- var i, len, mergedFeatures, trackOptions, dataRange;
43618
-
43619
- mergedFeatures = options.features; // Array of feature arrays, 1 for each track
43620
-
43621
- dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
43482
+ const mergedFeatures = options.features; // Array of feature arrays, 1 for each track
43622
43483
 
43623
- //IGVGraphics.fillRect(options.context, 0, options.pixelTop, options.pixelWidth, options.pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
43624
-
43625
- for (i = 0, len = this.tracks.length; i < len; i++) {
43484
+ if (this.autoscale) {
43485
+ this.dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
43486
+ }
43626
43487
 
43627
- trackOptions = Object.assign({}, options);
43488
+ for (let i = 0, len = this.tracks.length; i < len; i++) {
43489
+ const trackOptions = Object.assign({}, options);
43628
43490
  trackOptions.features = mergedFeatures[i];
43629
- this.tracks[i].dataRange = dataRange;
43491
+ this.tracks[i].dataRange = this.dataRange;
43492
+ this.tracks[i].flipAxis = this.flipAxis;
43493
+ this.tracks[i].logScale = this.logScale;
43494
+ this.tracks[i].graphType = this.graphType;
43630
43495
  this.tracks[i].draw(trackOptions);
43631
43496
  }
43632
-
43633
- }
43634
-
43635
- paintAxis(ctx, pixelWidth, pixelHeight) {
43636
-
43637
- var i, len, autoscale, track;
43638
-
43639
- autoscale = true; // Hardcoded for now
43640
-
43641
- for (i = 0, len = this.tracks.length; i < len; i++) {
43642
-
43643
- track = this.tracks[i];
43644
-
43645
- if (typeof track.paintAxis === 'function') {
43646
- track.paintAxis(ctx, pixelWidth, pixelHeight);
43647
- if (autoscale) break
43648
- }
43649
- }
43650
43497
  }
43651
43498
 
43652
43499
  popupData(clickState, features) {
43653
43500
 
43654
- const featuresArray = features || clickState.viewport.getCachedFeatures();
43501
+ const featuresArray = features || clickState.viewport.cachedFeatures;
43655
43502
 
43656
43503
  if (featuresArray && featuresArray.length === this.tracks.length) {
43657
43504
  // Array of feature arrays, 1 for each track
@@ -43668,42 +43515,23 @@ class MergedTrack extends TrackBase {
43668
43515
  }
43669
43516
 
43670
43517
 
43671
- supportsWholeGenome() {
43672
- const b = this.tracks.every(track => track.supportsWholeGenome());
43673
- return b
43518
+ get supportsWholeGenome() {
43519
+ return this.tracks.every(track => track.supportsWholeGenome())
43674
43520
  }
43675
43521
  }
43676
43522
 
43677
43523
  function autoscale(chr, featureArrays) {
43678
43524
 
43679
-
43680
- var min = 0,
43681
- max = -Number.MAX_VALUE;
43682
-
43683
- // if (chr === 'all') {
43684
- // allValues = [];
43685
- // featureArrays.forEach(function (features) {
43686
- // features.forEach(function (f) {
43687
- // if (!Number.isNaN(f.value)) {
43688
- // allValues.push(f.value);
43689
- // }
43690
- // });
43691
- // });
43692
- //
43693
- // min = Math.min(0, IGVMath.percentile(allValues, .1));
43694
- // max = IGVMath.percentile(allValues, 99.9);
43695
- //
43696
- // }
43697
- // else {
43698
- featureArrays.forEach(function (features, i) {
43699
- features.forEach(function (f) {
43525
+ let min = 0;
43526
+ let max = -Number.MAX_VALUE;
43527
+ for(let features of featureArrays) {
43528
+ for(let f of features) {
43700
43529
  if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
43701
43530
  min = Math.min(min, f.value);
43702
43531
  max = Math.max(max, f.value);
43703
43532
  }
43704
- });
43705
- });
43706
- // }
43533
+ }
43534
+ }
43707
43535
  return {min: min, max: max}
43708
43536
  }
43709
43537
 
@@ -43812,7 +43640,7 @@ class InteractionTrack extends TrackBase {
43812
43640
  return this
43813
43641
  }
43814
43642
 
43815
- supportsWholeGenome() {
43643
+ get supportsWholeGenome() {
43816
43644
  return true
43817
43645
  }
43818
43646
 
@@ -44198,7 +44026,7 @@ class InteractionTrack extends TrackBase {
44198
44026
  items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
44199
44027
  }
44200
44028
 
44201
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44029
+ if (this.browser.circularView) {
44202
44030
  items.push('<hr/>');
44203
44031
  items.push({
44204
44032
  label: 'Add interactions to circular view',
@@ -44216,7 +44044,7 @@ class InteractionTrack extends TrackBase {
44216
44044
  contextMenuItemList(clickState) {
44217
44045
 
44218
44046
  // Experimental JBrowse feature
44219
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44047
+ if (this.browser.circularView ) {
44220
44048
  const viewport = clickState.viewport;
44221
44049
  const list = [];
44222
44050
 
@@ -44245,21 +44073,22 @@ class InteractionTrack extends TrackBase {
44245
44073
 
44246
44074
  // inView features are simply features that have been drawn, i.e. have a drawState
44247
44075
  const inView = cachedFeatures.filter(f => f.drawState);
44248
- if(inView.length === 0) erturn;
44076
+ if(inView.length === 0) return;
44249
44077
 
44250
- this.browser.circularViewVisible = true;
44251
44078
  const chords = makeBedPEChords(inView);
44252
-
44253
- // for filtered set, distinguishing the chromosomes is more critical than tracks
44254
- const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.5);
44255
- const trackColor = IGVColor.addAlpha(this.color, 0.5);
44256
-
44257
- // name the chord set to include filtering information
44258
- const encodedName = this.name.replaceAll(' ', '%20');
44259
- const chordSetName = "all" === refFrame.chr ?
44260
- encodedName :
44261
- `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end} ; range:${this.dataRange.min}-${this.dataRange.max})`;
44262
- this.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor});
44079
+ sendChords(chords, this, refFrame, 0.5);
44080
+ //
44081
+ //
44082
+ // // for filtered set, distinguishing the chromosomes is more critical than tracks
44083
+ // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.5)
44084
+ // const trackColor = IGVColor.addAlpha(this.color, 0.5)
44085
+ //
44086
+ // // name the chord set to include locus and filtering information
44087
+ // const encodedName = this.name.replaceAll(' ', '%20')
44088
+ // const chordSetName = "all" === refFrame.chr ?
44089
+ // encodedName :
44090
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end} ; range:${this.dataRange.min}-${this.dataRange.max})`
44091
+ // this.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor})
44263
44092
  }
44264
44093
 
44265
44094
  doAutoscale(features) {
@@ -44324,7 +44153,7 @@ class InteractionTrack extends TrackBase {
44324
44153
 
44325
44154
  // We use the cached features rather than method to avoid async load. If the
44326
44155
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
44327
- const featureList = features || clickState.viewport.getCachedFeatures();
44156
+ const featureList = features || clickState.viewport.cachedFeatures;
44328
44157
  const candidates = [];
44329
44158
  if (featureList) {
44330
44159
  const proportional = (this.arcType === "proportional" || this.arcType === "inView" || this.arcType === "partialInView");
@@ -44658,7 +44487,7 @@ class VariantTrack extends TrackBase {
44658
44487
 
44659
44488
  }
44660
44489
 
44661
- supportsWholeGenome() {
44490
+ get supportsWholeGenome() {
44662
44491
  return this.config.indexed === false || this.config.supportsWholeGenome === true
44663
44492
  }
44664
44493
 
@@ -44850,7 +44679,7 @@ class VariantTrack extends TrackBase {
44850
44679
  }
44851
44680
 
44852
44681
  } else if (this._color) {
44853
- variantColor = (typeof this._color === "function") ? this._color(v) : this._color;
44682
+ variantColor = this.color;
44854
44683
  } else if ("NONVARIANT" === v.type) {
44855
44684
  variantColor = this.nonRefColor;
44856
44685
  } else if ("MIXED" === v.type) {
@@ -44861,6 +44690,10 @@ class VariantTrack extends TrackBase {
44861
44690
  return variantColor
44862
44691
  }
44863
44692
 
44693
+ get color() {
44694
+ return this._color ? ((typeof this._color === "function") ? this._color(v) : this._color) : this.defaultColor
44695
+ }
44696
+
44864
44697
  clickedFeatures(clickState, features) {
44865
44698
 
44866
44699
  let featureList = super.clickedFeatures(clickState, features);
@@ -45087,25 +44920,15 @@ class VariantTrack extends TrackBase {
45087
44920
  }
45088
44921
 
45089
44922
  // Experimental JBrowse circular view integration
45090
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44923
+ if (this.browser.circularView) {
45091
44924
 
45092
44925
  menuItems.push('<hr>');
45093
44926
  menuItems.push({
45094
44927
  label: 'Add SVs to circular view',
45095
44928
  click: () => {
45096
- const inView = [];
45097
44929
  for (let viewport of this.trackView.viewports) {
45098
- const refFrame = viewport.referenceFrame;
45099
- for (let f of viewport.getCachedFeatures()) {
45100
- if (f.end >= refFrame.start && f.start <= refFrame.end) {
45101
- inView.push(f);
45102
- }
45103
- }
44930
+ this.sendChordsForViewport(viewport);
45104
44931
  }
45105
-
45106
- const chords = makeVCFChords(inView);
45107
- const color = IGVColor.addAlpha(this._color || this.defaultColor, 0.5);
45108
- this.browser.circularView.addChords(chords, {track: this.name, color: color});
45109
44932
  }
45110
44933
  });
45111
44934
  }
@@ -45117,20 +44940,14 @@ class VariantTrack extends TrackBase {
45117
44940
  contextMenuItemList(clickState) {
45118
44941
 
45119
44942
  // Experimental JBrowse circular view integration
45120
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44943
+ if (this.browser.circularView) {
45121
44944
  const viewport = clickState.viewport;
45122
44945
  const list = [];
45123
44946
 
45124
44947
  list.push({
45125
44948
  label: 'Add SVs to Circular View',
45126
44949
  click: () => {
45127
- const refFrame = viewport.referenceFrame;
45128
- const inView = "all" === refFrame.chr ?
45129
- this.featureSource.getAllFeatures() :
45130
- this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end);
45131
- const chords = makeVCFChords(inView);
45132
- const color = IGVColor.addAlpha(this._color || this.defaultColor, 0.5);
45133
- this.browser.circularView.addChords(chords, {track: this.name, color: color});
44950
+ this.sendChordsForViewport(viewport);
45134
44951
  }
45135
44952
  });
45136
44953
 
@@ -45140,6 +44957,15 @@ class VariantTrack extends TrackBase {
45140
44957
  }
45141
44958
 
45142
44959
 
44960
+ sendChordsForViewport(viewport) {
44961
+ const refFrame = viewport.referenceFrame;
44962
+ const inView = "all" === refFrame.chr ?
44963
+ this.featureSource.getAllFeatures() :
44964
+ this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end);
44965
+ const chords = makeVCFChords(inView);
44966
+ sendChords(chords, this, refFrame, 0.5);
44967
+ }
44968
+
45143
44969
  /**
45144
44970
  * Create a "color by" checkbox menu item, optionally initially checked
45145
44971
  * @param menuItem
@@ -45436,7 +45262,7 @@ class EqtlTrack extends TrackBase {
45436
45262
  */
45437
45263
  popupData(clickState) {
45438
45264
 
45439
- let features = clickState.viewport.getCachedFeatures();
45265
+ let features = clickState.viewport.cachedFeatures;
45440
45266
  if (!features || features.length === 0) return []
45441
45267
 
45442
45268
  const tolerance = 3;
@@ -45655,7 +45481,7 @@ class GWASTrack extends TrackBase {
45655
45481
  }
45656
45482
 
45657
45483
 
45658
- supportsWholeGenome() {
45484
+ get supportsWholeGenome() {
45659
45485
  return true
45660
45486
  }
45661
45487
 
@@ -45766,7 +45592,7 @@ class GWASTrack extends TrackBase {
45766
45592
 
45767
45593
  let data = [];
45768
45594
  const track = clickState.viewport.trackView.track;
45769
- const features = clickState.viewport.getCachedFeatures();
45595
+ const features = clickState.viewport.cachedFeatures;
45770
45596
 
45771
45597
  if (features) {
45772
45598
  let count = 0;
@@ -46169,7 +45995,7 @@ class GCNVTrack extends TrackBase {
46169
45995
  return items
46170
45996
  }
46171
45997
 
46172
- supportsWholeGenome() {
45998
+ get supportsWholeGenome() {
46173
45999
  return false
46174
46000
  }
46175
46001
  }
@@ -46449,7 +46275,7 @@ class RNAFeatureSource {
46449
46275
 
46450
46276
  const data = await igvxhr.loadString(this.config.url, options);
46451
46277
 
46452
- this.featureCache = new FeatureCache(parseBP(data), genome);
46278
+ this.featureCache = new FeatureCache$1(parseBP(data), genome);
46453
46279
 
46454
46280
  return this.featureCache.queryFeatures(chr, start, end)
46455
46281
 
@@ -46556,21 +46382,18 @@ class RNAFeatureSource {
46556
46382
  * THE SOFTWARE.
46557
46383
  */
46558
46384
 
46385
+ /**
46386
+ * Class represents an ideogram of a chromsome cytobands. It is used for the header of a track panel.
46387
+ *
46388
+ */
46559
46389
  class IdeogramTrack {
46560
46390
  constructor(browser) {
46561
-
46562
46391
  this.browser = browser;
46563
-
46564
46392
  this.type = 'ideogram';
46565
- this.id = this.type;
46566
-
46567
46393
  this.height = 16;
46568
-
46569
46394
  this.order = Number.MIN_SAFE_INTEGER;
46570
-
46571
46395
  this.disableButtons = true;
46572
46396
  this.ignoreTrackMenu = true;
46573
-
46574
46397
  }
46575
46398
 
46576
46399
  async getFeatures(chr, start, end) {
@@ -46837,7 +46660,7 @@ class SpliceJunctionTrack extends TrackBase {
46837
46660
 
46838
46661
  }
46839
46662
 
46840
- supportsWholeGenome() {
46663
+ get supportsWholeGenome() {
46841
46664
  return false
46842
46665
  }
46843
46666
 
@@ -47731,6 +47554,15 @@ class ReferenceFrame {
47731
47554
  this.id = guid$2();
47732
47555
  }
47733
47556
 
47557
+ extend(locus) {
47558
+ const newStart = Math.min(locus.start, this.start);
47559
+ const newEnd = Math.max(locus.end, this.end);
47560
+ const ratio = (newEnd - newStart) / (this.end - this.start);
47561
+ this.start = newStart;
47562
+ this.end = newEnd;
47563
+ this.bpPerPixel *= ratio;
47564
+ }
47565
+
47734
47566
  calculateEnd(pixels) {
47735
47567
  return this.start + this.bpPerPixel * pixels
47736
47568
  }
@@ -47817,7 +47649,7 @@ class ReferenceFrame {
47817
47649
 
47818
47650
  const viewChanged = start !== this.start || bpPerPixel !== this.bpPerPixel;
47819
47651
  if (viewChanged) {
47820
- await browser.updateViews(this);
47652
+ await browser.updateViews(true);
47821
47653
  }
47822
47654
 
47823
47655
  }
@@ -47893,30 +47725,6 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
47893
47725
  })
47894
47726
  }
47895
47727
 
47896
- function adjustReferenceFrame(scaleFactor, referenceFrame, viewportWidth, alignmentStart, alignmentLength) {
47897
-
47898
- referenceFrame.bpPerPixel *= scaleFactor;
47899
-
47900
- const alignmentEE = alignmentStart + alignmentLength;
47901
- const alignmentCC = (alignmentStart + alignmentEE) / 2;
47902
-
47903
- referenceFrame.start = alignmentCC - (referenceFrame.bpPerPixel * (viewportWidth / 2));
47904
- referenceFrame.end = referenceFrame.start + (referenceFrame.bpPerPixel * viewportWidth);
47905
- referenceFrame.locusSearchString = referenceFrame.getLocusString();
47906
- }
47907
-
47908
- function createReferenceFrameWithAlignment(genome, chromosomeName, bpp, viewportWidth, alignmentStart, alignmentLength) {
47909
-
47910
- const alignmentEE = alignmentStart + alignmentLength;
47911
- const alignmentCC = (alignmentStart + alignmentEE) / 2;
47912
-
47913
- const ss = alignmentCC - (bpp * (viewportWidth / 2));
47914
- const ee = ss + (bpp * viewportWidth);
47915
-
47916
- return new ReferenceFrame(genome, chromosomeName, ss, ee, bpp)
47917
-
47918
- }
47919
-
47920
47728
  const defaultNucleotideColors = {
47921
47729
  "A": "rgb( 0, 200, 0)",
47922
47730
  "C": "rgb( 0,0,200)",
@@ -49016,6 +48824,69 @@ const SVGSaveControl = function (parent, browser) {
49016
48824
  button.addEventListener('click', () => browser.saveSVGtoFile({}));
49017
48825
  };
49018
48826
 
48827
+ const viewportColumnManager =
48828
+ {
48829
+ createColumns: (columnContainer, count) => {
48830
+
48831
+ for (let i = 0; i < count; i++) {
48832
+ if (0 === i) {
48833
+ createColumn(columnContainer, 'igv-column');
48834
+ } else {
48835
+ columnContainer.appendChild(div$1({class: 'igv-column-shim'}));
48836
+ createColumn(columnContainer, 'igv-column');
48837
+ }
48838
+ }
48839
+
48840
+ },
48841
+
48842
+ removeColumnAtIndex: (i, column) => {
48843
+ const shim = 0 === i ? column.nextElementSibling : column.previousElementSibling;
48844
+ column.remove();
48845
+ shim.remove();
48846
+ },
48847
+
48848
+ insertAfter: referenceElement => {
48849
+
48850
+ const shim = div$1({class: 'igv-column-shim'});
48851
+ insertElementAfter(shim, referenceElement);
48852
+
48853
+ const column = div$1({class: 'igv-column'});
48854
+ insertElementAfter(column, shim);
48855
+
48856
+ return column
48857
+ },
48858
+
48859
+ insertBefore: (referenceElement, count) => {
48860
+
48861
+ for (let i = 0; i < count; i++) {
48862
+
48863
+ const column = div$1({class: 'igv-column'});
48864
+ insertElementBefore(column, referenceElement);
48865
+
48866
+ if (count > 1 && i > 0) {
48867
+ const columnShim = div$1({class: 'igv-column-shim'});
48868
+ insertElementBefore(columnShim, column);
48869
+ }
48870
+
48871
+ }
48872
+
48873
+ },
48874
+
48875
+ indexOfColumn: (columnContainer, column) => {
48876
+
48877
+ const allColumns = columnContainer.querySelectorAll('.igv-column');
48878
+
48879
+ for (let i = 0; i < allColumns.length; i++) {
48880
+ const c = allColumns[ i ];
48881
+ if (c === column) {
48882
+ return i
48883
+ }
48884
+ }
48885
+
48886
+ return undefined
48887
+ },
48888
+ };
48889
+
49019
48890
  /*
49020
48891
  * The MIT License (MIT)
49021
48892
  *
@@ -49248,7 +49119,7 @@ class RulerTrack {
49248
49119
  }
49249
49120
  }
49250
49121
 
49251
- supportsWholeGenome() {
49122
+ get supportsWholeGenome() {
49252
49123
  return true
49253
49124
  };
49254
49125
 
@@ -49619,8 +49490,8 @@ class Browser {
49619
49490
  this.svgSaveControl = new SVGSaveControl($toggle_button_container.get(0), this);
49620
49491
  }
49621
49492
 
49622
- if(config.customButtons) {
49623
- for(let b of config.customButtons) {
49493
+ if (config.customButtons) {
49494
+ for (let b of config.customButtons) {
49624
49495
  new CustomButton($toggle_button_container.get(0), this, b);
49625
49496
  }
49626
49497
  }
@@ -49781,7 +49652,6 @@ class Browser {
49781
49652
  return undefined
49782
49653
  }
49783
49654
  }
49784
-
49785
49655
  }
49786
49656
  }
49787
49657
 
@@ -49826,7 +49696,9 @@ class Browser {
49826
49696
  // Create ideogram and ruler track. Really this belongs in browser initialization, but creation is
49827
49697
  // deferred because ideogram and ruler are treated as "tracks", and tracks require a reference frame
49828
49698
  if (false !== session.showIdeogram) {
49829
- this.trackViews.push(new TrackView(this, this.columnContainer, new IdeogramTrack(this)));
49699
+ const ideogramTrack = new IdeogramTrack(this);
49700
+ ideogramTrack.id = 'ideogram';
49701
+ this.trackViews.push(new TrackView(this, this.columnContainer, ideogramTrack));
49830
49702
  }
49831
49703
 
49832
49704
  if (false !== session.showRuler) {
@@ -49871,6 +49743,11 @@ class Browser {
49871
49743
 
49872
49744
  await this.loadTrackList(trackConfigurations);
49873
49745
 
49746
+ // The ruler and ideogram tracks are not explicitly loaded, but needs updated nonetheless.
49747
+ for (let rtv of this.trackViews.filter((tv) => tv.track.type === 'ruler' || tv.track.type === 'ideogram')) {
49748
+ rtv.updateViews();
49749
+ }
49750
+
49874
49751
  this.updateUIWithReferenceFrameList();
49875
49752
 
49876
49753
  }
@@ -50049,23 +49926,19 @@ class Browser {
50049
49926
 
50050
49927
  async loadTrackList(configList) {
50051
49928
 
50052
- try {
50053
- const promises = [];
50054
- for (let config of configList) {
50055
- promises.push(this.loadTrack(config, false));
50056
- }
49929
+ const promises = [];
49930
+ for (let config of configList) {
49931
+ promises.push(this.loadTrack(config));
49932
+ }
50057
49933
 
50058
- const loadedTracks = await Promise.all(promises);
50059
- const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
50060
- return trackView.track.autoscaleGroup
50061
- });
50062
- if (groupAutoscaleViews.length > 0) {
50063
- this.updateViews(groupAutoscaleViews);
50064
- }
50065
- return loadedTracks
50066
- } finally {
50067
- await this.resize();
49934
+ const loadedTracks = await Promise.all(promises);
49935
+ const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
49936
+ return trackView.track.autoscaleGroup
49937
+ });
49938
+ if (groupAutoscaleViews.length > 0) {
49939
+ this.updateViews();
50068
49940
  }
49941
+ return loadedTracks
50069
49942
  }
50070
49943
 
50071
49944
  async loadROI(config) {
@@ -50079,6 +49952,8 @@ class Browser {
50079
49952
  } else {
50080
49953
  this.roi.push(new ROI(config, this.genome));
50081
49954
  }
49955
+ // Force reload all views (force = true) to insure ROI features are loaded. Wasteful but this function is
49956
+ // rarely called.
50082
49957
  await this.updateViews(true);
50083
49958
  }
50084
49959
 
@@ -50090,14 +49965,14 @@ class Browser {
50090
49965
  }
50091
49966
  }
50092
49967
  for (let tv of this.trackViews) {
50093
- tv.updateViews(true);
49968
+ tv.repaintViews();
50094
49969
  }
50095
49970
  }
50096
49971
 
50097
49972
  clearROIs() {
50098
49973
  this.roi = [];
50099
49974
  for (let tv of this.trackViews) {
50100
- tv.updateViews(true);
49975
+ tv.repaintViews();
50101
49976
  }
50102
49977
  }
50103
49978
 
@@ -50109,24 +49984,12 @@ class Browser {
50109
49984
  /**
50110
49985
  * Return a promise to load a track.
50111
49986
  *
50112
- * Each track is associated with the following DOM elements
50113
- *
50114
- * leftHandGutter - div on the left for track controls and legend
50115
- * contentDiv - a div element wrapping all the track content. Height can be > viewportDiv height
50116
- * viewportDiv - a div element through which the track is viewed. This might have a vertical scrollbar
50117
- * canvas - canvas element upon which the track is drawn. Child of contentDiv
50118
- *
50119
- * The width of all elements should be equal. Height of the viewportDiv is controlled by the user, but never
50120
- * greater than the contentDiv height. Height of contentDiv and canvas are equal, and governed by the data
50121
- * loaded.
50122
- *
50123
- *
50124
49987
  * @param config
50125
49988
  * @param doResize - undefined by default
50126
49989
  * @returns {*}
50127
49990
  */
50128
49991
 
50129
- async loadTrack(config, doResize) {
49992
+ async loadTrack(config) {
50130
49993
 
50131
49994
 
50132
49995
  // config might be json
@@ -50194,11 +50057,6 @@ class Browser {
50194
50057
  }
50195
50058
  msg += (": " + config.url);
50196
50059
  Alert.presentAlert(new Error(msg), undefined);
50197
- } finally {
50198
- // TODO: If loadTrack() is called individually - not via loadTrackList() - call this.resize()
50199
- if (false === doResize) ; else {
50200
- await this.resize();
50201
- }
50202
50060
  }
50203
50061
  }
50204
50062
 
@@ -50422,44 +50280,11 @@ class Browser {
50422
50280
  this.navbarManager.navbarDidResize(this.$navigation.width(), isWGV);
50423
50281
  }
50424
50282
 
50425
- await this.resize();
50426
- }
50427
-
50428
- async resize() {
50429
-
50430
- const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
50431
-
50432
- for (let referenceFrame of this.referenceFrameList) {
50433
-
50434
- const index = this.referenceFrameList.indexOf(referenceFrame);
50435
-
50436
- const {chr, genome} = referenceFrame;
50437
-
50438
- const {bpLength} = genome.getChromosome(referenceFrame.chr);
50439
-
50440
- const viewportWidthBP = referenceFrame.toBP(viewportWidth);
50441
-
50442
- // viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
50443
- if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
50444
- // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
50445
- referenceFrame.bpPerPixel = bpLength / viewportWidth;
50446
- } else {
50447
- // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
50448
- referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
50449
- }
50450
-
50451
- for (let {viewports} of this.trackViews) {
50452
- viewports[index].setWidth(viewportWidth);
50453
- }
50454
-
50455
- }
50456
-
50457
- await this.updateViews(true);
50458
-
50459
- this.updateUIWithReferenceFrameList();
50283
+ resize.call(this);
50284
+ await this.updateViews();
50460
50285
  }
50461
50286
 
50462
- async updateViews(force) {
50287
+ async updateViews() {
50463
50288
 
50464
50289
  const trackViews = this.trackViews;
50465
50290
 
@@ -50472,7 +50297,7 @@ class Browser {
50472
50297
  // Don't autoscale while dragging.
50473
50298
  if (this.dragObject) {
50474
50299
  for (let trackView of trackViews) {
50475
- await trackView.updateViews(force);
50300
+ await trackView.updateViews();
50476
50301
  }
50477
50302
  } else {
50478
50303
  // Group autoscale
@@ -50517,14 +50342,14 @@ class Browser {
50517
50342
  for (let trackView of groupTrackViews) {
50518
50343
  trackView.track.dataRange = dataRange;
50519
50344
  trackView.track.autoscale = false;
50520
- p.push(trackView.updateViews(force));
50345
+ p.push(trackView.updateViews());
50521
50346
  }
50522
50347
  await Promise.all(p);
50523
50348
  }
50524
50349
 
50525
50350
  }
50526
50351
 
50527
- await Promise.all(otherTracks.map(tv => tv.updateViews(force)));
50352
+ await Promise.all(otherTracks.map(tv => tv.updateViews()));
50528
50353
  // for (let trackView of otherTracks) {
50529
50354
  // await trackView.updateViews(force);
50530
50355
  // }
@@ -50532,6 +50357,12 @@ class Browser {
50532
50357
 
50533
50358
  }
50534
50359
 
50360
+ repaintViews() {
50361
+ for (let trackView of this.trackViews) {
50362
+ trackView.repaintViews();
50363
+ }
50364
+ }
50365
+
50535
50366
  updateLocusSearchWidget() {
50536
50367
 
50537
50368
  const referenceFrameList = this.referenceFrameList;
@@ -50596,49 +50427,50 @@ class Browser {
50596
50427
  }
50597
50428
  }
50598
50429
 
50599
- async presentMultiLocusPanel(alignment, referenceFrameLeft) {
50430
+ /**
50431
+ * Add a new multi-locus panel for the specified region
50432
+ * @param chr
50433
+ * @param start
50434
+ * @param end
50435
+ * @param referenceFrameLeft - optional, if supplied new panel should be placed to the immediate right
50436
+ */
50437
+ async addMultiLocusPanel(chr, start, end, referenceFrameLeft) {
50600
50438
 
50601
50439
  // account for reduced viewport width as a result of adding right mate pair panel
50602
50440
  const viewportWidth = this.calculateViewportWidth(1 + this.referenceFrameList.length);
50603
-
50604
50441
  const scaleFactor = this.calculateViewportWidth(this.referenceFrameList.length) / this.calculateViewportWidth(1 + this.referenceFrameList.length);
50605
- adjustReferenceFrame(scaleFactor, referenceFrameLeft, viewportWidth, alignment.start, alignment.lengthOnRef);
50606
-
50607
- // create right mate pair reference frame
50608
- const mateChrName = this.genome.getChromosomeName(alignment.mate.chr);
50609
-
50610
- const referenceFrameRight = createReferenceFrameWithAlignment(this.genome, mateChrName, referenceFrameLeft.bpPerPixel, viewportWidth, alignment.mate.position, alignment.lengthOnRef);
50442
+ for (let refFrame of this.referenceFrameList) {
50443
+ refFrame.bpPerPixel *= scaleFactor;
50444
+ }
50611
50445
 
50612
- // add right mate panel beside left mate panel
50613
- const indexLeft = this.referenceFrameList.indexOf(referenceFrameLeft);
50614
- const indexRight = 1 + (this.referenceFrameList.indexOf(referenceFrameLeft));
50446
+ const bpp = (end - start) / viewportWidth;
50447
+ const newReferenceFrame = new ReferenceFrame(this.genome, chr, start, end, bpp);
50448
+ const indexLeft = referenceFrameLeft ? this.referenceFrameList.indexOf(referenceFrameLeft) : this.referenceFrameList.length - 1;
50449
+ const indexRight = 1 + indexLeft;
50615
50450
 
50451
+ // TODO -- this is really ugly
50616
50452
  const {$viewport} = this.trackViews[0].viewports[indexLeft];
50617
50453
  const viewportColumn = viewportColumnManager.insertAfter($viewport.get(0).parentElement);
50618
50454
 
50619
50455
  if (indexRight === this.referenceFrameList.length) {
50620
-
50621
- this.referenceFrameList.push(referenceFrameRight);
50622
-
50456
+ this.referenceFrameList.push(newReferenceFrame);
50623
50457
  for (let trackView of this.trackViews) {
50624
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
50458
+ const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
50625
50459
  trackView.viewports.push(viewport);
50626
50460
  }
50627
-
50628
50461
  } else {
50629
-
50630
- this.referenceFrameList.splice(indexRight, 0, referenceFrameRight);
50631
-
50462
+ this.referenceFrameList.splice(indexRight, 0, newReferenceFrame);
50632
50463
  for (let trackView of this.trackViews) {
50633
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
50464
+ const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
50634
50465
  trackView.viewports.splice(indexRight, 0, viewport);
50635
50466
  }
50636
-
50637
50467
  }
50638
50468
 
50469
+
50639
50470
  this.centerLineList = this.createCenterLineList(this.columnContainer);
50640
50471
 
50641
- await this.resize();
50472
+ resize.call(this);
50473
+ await this.updateViews(true);
50642
50474
  }
50643
50475
 
50644
50476
  async removeMultiLocusPanel(referenceFrame) {
@@ -50667,7 +50499,13 @@ class Browser {
50667
50499
 
50668
50500
  }
50669
50501
 
50670
- async selectMultiLocusPanel(referenceFrame) {
50502
+ /**
50503
+ * Goto the locus represented by the selected referenceFrame, discarding all other panels
50504
+ *
50505
+ * @param referenceFrame
50506
+ * @returns {Promise<void>}
50507
+ */
50508
+ async gotoMultilocusPanel(referenceFrame) {
50671
50509
 
50672
50510
  const referenceFrameIndex = this.referenceFrameList.indexOf(referenceFrame);
50673
50511
 
@@ -50721,7 +50559,7 @@ class Browser {
50721
50559
 
50722
50560
  this.updateUIWithReferenceFrameList();
50723
50561
 
50724
- await this.updateViews(true);
50562
+ await this.updateViews();
50725
50563
 
50726
50564
  }
50727
50565
 
@@ -50944,7 +50782,6 @@ class Browser {
50944
50782
  }
50945
50783
 
50946
50784
 
50947
-
50948
50785
  json["tracks"] = trackJson;
50949
50786
 
50950
50787
  return json // This is an object, not a json string
@@ -50963,16 +50800,6 @@ class Browser {
50963
50800
  return surl
50964
50801
  }
50965
50802
 
50966
- currentLoci() {
50967
- const loci = [];
50968
- const anyTrackView = this.trackViews[0];
50969
- for (let {referenceFrame} of anyTrackView.viewports) {
50970
- const locusString = referenceFrame.getLocusString();
50971
- loci.push(locusString);
50972
- }
50973
- return loci
50974
- }
50975
-
50976
50803
  /**
50977
50804
  * Record a mouse click on a specific viewport. This might be the start of a drag operation. Dragging
50978
50805
  * (panning) is handled here so that the mouse can move out of a specific viewport (e.g. stray into another
@@ -51010,12 +50837,22 @@ class Browser {
51010
50837
 
51011
50838
  }
51012
50839
 
50840
+ /**
50841
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
50842
+ *
50843
+ * @param trackView
50844
+ */
51013
50845
  startTrackDrag(trackView) {
51014
50846
 
51015
50847
  this.dragTrack = trackView;
51016
50848
 
51017
50849
  }
51018
50850
 
50851
+ /**
50852
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
50853
+ *
50854
+ * @param dragDestination
50855
+ */
51019
50856
  updateTrackDrag(dragDestination) {
51020
50857
 
51021
50858
  if (dragDestination && this.dragTrack) {
@@ -51090,12 +50927,9 @@ class Browser {
51090
50927
  }
51091
50928
 
51092
50929
  addWindowResizeHandler() {
51093
- this.boundWindowResizeHandler = windowResizeHandler.bind(this);
50930
+ // Create a copy of the prototype "resize" function bound to this instance. Neccessary to support removing.
50931
+ this.boundWindowResizeHandler = resize.bind(this);
51094
50932
  window.addEventListener('resize', this.boundWindowResizeHandler);
51095
-
51096
- function windowResizeHandler() {
51097
- this.resize();
51098
- }
51099
50933
  }
51100
50934
 
51101
50935
  removeWindowResizeHandler() {
@@ -51178,7 +51012,7 @@ class Browser {
51178
51012
  id: this.genome.id,
51179
51013
  chromosomes: makeCircViewChromosomes(this.genome)
51180
51014
  });
51181
- this.circularViewVisible = show === true;
51015
+ this.circularViewVisible = show;
51182
51016
 
51183
51017
  }
51184
51018
 
@@ -51194,6 +51028,50 @@ class Browser {
51194
51028
  }
51195
51029
  }
51196
51030
 
51031
+ /**
51032
+ * Function called win window is resized, or visibility changed (e.g. "show" from a tab). This is a function rather
51033
+ * than class method because it needs to be copied and bound to specific instances of browser to support listener
51034
+ * removal
51035
+ *
51036
+ * @returns {Promise<void>}
51037
+ */
51038
+ async function resize() {
51039
+
51040
+ const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
51041
+
51042
+ for (let referenceFrame of this.referenceFrameList) {
51043
+
51044
+ const index = this.referenceFrameList.indexOf(referenceFrame);
51045
+
51046
+ const {chr, genome} = referenceFrame;
51047
+
51048
+ const {bpLength} = genome.getChromosome(referenceFrame.chr);
51049
+
51050
+ const viewportWidthBP = referenceFrame.toBP(viewportWidth);
51051
+
51052
+ // viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
51053
+ if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
51054
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
51055
+ referenceFrame.bpPerPixel = bpLength / viewportWidth;
51056
+ } else {
51057
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
51058
+ referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
51059
+ }
51060
+
51061
+ for (let {viewports} of this.trackViews) {
51062
+ viewports[index].setWidth(viewportWidth);
51063
+ }
51064
+
51065
+ }
51066
+
51067
+ this.updateUIWithReferenceFrameList();
51068
+
51069
+ //TODO -- update view only if needed. Reducing size never needed. Increasing size maybe
51070
+
51071
+ await this.updateViews(true);
51072
+ }
51073
+
51074
+
51197
51075
  function handleMouseMove(e) {
51198
51076
 
51199
51077
  e.preventDefault();