diff --git a/dev/shoebox/shoebox.html b/dev/shoebox/shoebox.html index 95b4bda57..7640860ff 100644 --- a/dev/shoebox/shoebox.html +++ b/dev/shoebox/shoebox.html @@ -26,10 +26,12 @@

Shoebox

locus: "chr1:1,001,575-1,002,408", tracks: [ { - "url": "https://www.dropbox.com/scl/fi/2tv6vnqqlfy6kxzahshwc/pseudobulk_14.bed?rlkey=ytrotr3v197k26pe2dettfgsr&dl=0", - // "name": "pseudobulk 14", - // "type": "shoebox", - // "format": "shoebox" + "url": "https://www.dropbox.com/scl/fi/ayu63q7raqi47yduvvte5/pseudobulk_0.bed.gz?rlkey=ci5oqo928iljje4igk4wwjoor&dl=0", + "indexURL": "https://www.dropbox.com/scl/fi/bxz53av4v0ri9snidxj8g/pseudobulk_0.bed.gz.tbi?rlkey=qvc7zk8si0ays89cqjp05fdw9&dl=0", + // visibilityWindow: 10000 + // "name": "pseudobulk 14", + // "type": "shoebox", + // "format": "shoebox" }, { "name": "Homo sapiens HepG2 H3K4me3 ", diff --git a/dev/wig/groupAutoscale.html b/dev/wig/groupAutoscale.html index 85d9c4cf0..ffbc05c13 100644 --- a/dev/wig/groupAutoscale.html +++ b/dev/wig/groupAutoscale.html @@ -38,6 +38,7 @@

Test tracks

import igv from "../../js/index.js"; const options = { + queryParametersSupported: true, "genome": "hg19", "locus": "myc egfr", "tracks": [ @@ -94,7 +95,12 @@

Test tracks

}) document.getElementById("bookmarkButton").addEventListener("click", - () => window.history.pushState({}, "IGV", browser.sessionURL())) + () => { + const path = window.location.href.slice() + const idx = path.indexOf("?") + const url = (idx > 0 ? path.substring(0, idx) : path) + "?sessionURL=blob:" + browser.compressedSession() + window.history.pushState({}, "IGV", url) + }) document.getElementById("svgButton").addEventListener("click", () => { let svg = browser.toSVG(); diff --git a/js/bigwig/bwReader.js b/js/bigwig/bwReader.js index c6438b45f..a7b4ea6b8 100644 --- a/js/bigwig/bwReader.js +++ b/js/bigwig/bwReader.js @@ -56,8 +56,9 @@ class BWReader { this.loader = isDataURL(this.path) ? new DataBuffer(this.path) : igvxhr - if (config.searchTrix) { - this._trix = new Trix(`${config.searchTrix}x`, config.searchTrix) + const trixURL = config.trixURL || config.searchTrix + if (trixURL) { + this._trix = new Trix(`${trixURL}x`, trixURL) } } diff --git a/js/browser.js b/js/browser.js index 7e55ca22e..be1484c61 100755 --- a/js/browser.js +++ b/js/browser.js @@ -1,8 +1,6 @@ import $ from "./vendor/jquery-3.3.1.slim.js" import {BGZip, FileUtils, igvxhr, StringUtils, URIUtils} from "../node_modules/igv-utils/src/index.js" import * as DOMUtils from "./ui/utils/dom-utils.js" -import {createIcon} from "./ui/utils/icons.js" -import SliderDialog from "./ui/components/sliderDialog.js" import InputDialog from "./ui/components/inputDialog.js" import GenericColorPicker from "./ui/components/genericColorPicker.js" import Alert from './ui/alert.js' @@ -20,19 +18,9 @@ import version from "./version.js" import FeatureSource from "./feature/featureSource.js" import {defaultNucleotideColors} from "./util/nucleotideColors.js" import search from "./search.js" -import {navbarDidResize} from "./responsiveNavbar.js" -import ChromosomeSelectWidget from "./ui/chromosomeSelectWidget.js" -import WindowSizePanel from "./windowSizePanel.js" -import CursorGuide from "./ui/cursorGuide.js" -import CursorGuideButton from "./ui/cursorGuideButton.js" -import CenterLineButton from './ui/centerLineButton.js' -import TrackLabelControl from "./ui/trackLabelControl.js" -import SampleNameControl from "./sample/sampleNameControl.js" -import SampleInfoControl from "./sample/sampleInfoControl.js" -import ZoomWidget from "./ui/zoomWidget.js" +import ResponsiveNavbar from "./responsiveNavbar.js" import DataRangeDialog from "./ui/dataRangeDialog.js" import HtsgetReader from "./htsget/htsgetReader.js" -import SaveImageControl from "./ui/saveImageControl.js" import MenuPopup from "./ui/menuPopup.js" import {viewportColumnManager} from './viewportColumnManager.js' import ViewportCenterLine from './ui/viewportCenterLine.js' @@ -40,16 +28,12 @@ import IdeogramTrack from "./ideogramTrack.js" import RulerTrack from "./rulerTrack.js" import CircularViewControl from "./ui/circularViewControl.js" import {createCircularView, makeCircViewChromosomes} from "./jbrowse/circularViewUtils.js" -import CustomButton from "./ui/customButton.js" import ROIManager from './roi/ROIManager.js' import TrackROISet from "./roi/trackROISet.js" -import ROITableControl from './roi/roiTableControl.js' import SampleInfo from "./sample/sampleInfo.js" import HicFile from "./hic/straw/hicFile.js" import {translateSession} from "./hic/shoeboxUtils.js" import Hub from "./ucsc/ucscHub.js" -import MultiTrackSelectButton from "./ui/multiTrackSelectButton.js" -import OverlayTrackButton from "./ui/overlayTrackButton.js" import MenuUtils from "./ui/menuUtils.js" import Genome from "./genome/genome.js" import {setDefaults} from "./igv-create.js" @@ -61,6 +45,22 @@ import {sampleInfoTileWidth, sampleInfoTileXShim} from "./sample/sampleInfoConst import QTLSelections from "./qtl/qtlSelections.js" import {inferFileFormat} from "./util/fileFormatUtils.js" import {convertToHubURL} from "./ucsc/ucscUtils.js" +import ChromosomeSelectWidget from "./ui/chromosomeSelectWidget.js" +import {createIcon} from "./ui/utils/icons.js" +import WindowSizePanel from "./windowSizePanel.js" +import OverlayTrackButton from "./ui/overlayTrackButton.js" +import MultiTrackSelectButton from "./ui/multiTrackSelectButton.js" +import CursorGuide from "./ui/cursorGuide.js" +import CursorGuideButton from "./ui/cursorGuideButton.js" +import CenterLineButton from "./ui/centerLineButton.js" +import TrackLabelControl from "./ui/trackLabelControl.js" +import ROITableControl from "./roi/roiTableControl.js" +import SampleInfoControl from "./sample/sampleInfoControl.js" +import SampleNameControl from "./sample/sampleNameControl.js" +import SaveImageControl from "./ui/saveImageControl.js" +import CustomButton from "./ui/customButton.js" +import ZoomWidget from "./ui/zoomWidget.js" +import SliderDialog from "./ui/components/sliderDialog.js" // css - $igv-scrollbar-outer-width: 14px; @@ -152,7 +152,6 @@ class Browser { this.sampleNameControl.setState(this.showSampleNames) this.sampleNameControl.hide() - this.layoutChange() } }) @@ -171,7 +170,7 @@ class Browser { this.sampleInfo = new SampleInfo(this) - this.setControls(config) + this.createStandardControls(config) // Region of interest this.roiManager = new ROIManager(this) @@ -229,121 +228,18 @@ class Browser { } } - setControls(config) { - - const $navBar = this.createStandardControls(config) - $navBar.insertBefore($(this.columnContainer)) - this.$navigation = $navBar - - if (false === config.showControls) { - $navBar.hide() - } - - } - createStandardControls(config) { - const $navBar = $('
', {class: 'igv-navbar'}) - this.$navigation = $navBar - - const $navbarLeftContainer = $('
', {class: 'igv-navbar-left-container'}) - $navBar.append($navbarLeftContainer) - - // IGV logo - const $logo = $('
', {class: 'igv-logo'}) - $navbarLeftContainer.append($logo) - - const logoSvg = logo() - logoSvg.css("width", "34px") - logoSvg.css("height", "32px") - $logo.append(logoSvg) - - this.$current_genome = $('
', {class: 'igv-current-genome'}) - $navbarLeftContainer.append(this.$current_genome) - this.$current_genome.text('') - - const $genomicLocation = $('
', {class: 'igv-navbar-genomic-location'}) - $navbarLeftContainer.append($genomicLocation) - - // chromosome select widget - this.chromosomeSelectWidget = new ChromosomeSelectWidget(this, $genomicLocation.get(0)) - if (config.showChromosomeWidget !== false) { - this.chromosomeSelectWidget.show() - } else { - this.chromosomeSelectWidget.hide() - } - - const $locusSizeGroup = $('
', {class: 'igv-locus-size-group'}) - $genomicLocation.append($locusSizeGroup) - - const $searchContainer = $('
', {class: 'igv-search-container'}) - $locusSizeGroup.append($searchContainer) - - // browser.$searchInput = $(''); - this.$searchInput = $('', {class: 'igv-search-input', type: 'text', placeholder: 'Locus Search'}) - $searchContainer.append(this.$searchInput) - // Stop event propagation to prevent feature track keyboard navigation - this.$searchInput[0].addEventListener('keyup', (event) => { - event.stopImmediatePropagation() - }) - - this.$searchInput.change(() => this.doSearch(this.$searchInput.val())) - - const searchIconContainer = DOMUtils.div({class: 'igv-search-icon-container'}) - $searchContainer.append($(searchIconContainer)) - - searchIconContainer.appendChild(createIcon("search")) - - searchIconContainer.addEventListener('click', () => this.doSearch(this.$searchInput.val())) - - this.windowSizePanel = new WindowSizePanel($locusSizeGroup.get(0), this) - - const $navbarRightContainer = $('
', {class: 'igv-navbar-right-container'}) - $navBar.append($navbarRightContainer) - - const $toggle_button_container = $('
') - $navbarRightContainer.append($toggle_button_container) - this.$toggle_button_container = $toggle_button_container - - this.overlayTrackButton = new OverlayTrackButton(this, $toggle_button_container.get(0)) - this.overlayTrackButton.setVisibility(false) - - this.multiTrackSelectButton = new MultiTrackSelectButton(this, $toggle_button_container.get(0)) - - this.cursorGuide = new CursorGuide(this.columnContainer, this) - - this.cursorGuideButton = new CursorGuideButton(this, $toggle_button_container.get(0)) - - this.centerLineButton = new CenterLineButton(this, $toggle_button_container.get(0)) - this.setTrackLabelVisibility(config.showTrackLabels) - this.trackLabelControl = new TrackLabelControl($toggle_button_container.get(0), this) - - // ROI Control - this.roiTableControl = new ROITableControl($toggle_button_container.get(0), this) - - this.sampleInfoControl = new SampleInfoControl($toggle_button_container.get(0), this) - - this.sampleNameControl = new SampleNameControl($toggle_button_container.get(0), this) - if (true === config.showSVGButton) { - this.saveImageControl = new SaveImageControl($toggle_button_container.get(0), this) - } - - if (config.customButtons) { - for (let b of config.customButtons) { - new CustomButton($toggle_button_container.get(0), this, b) - } - } + this.navbar = new ResponsiveNavbar(config, this) - this.zoomWidget = new ZoomWidget(this, $navbarRightContainer.get(0)) + this.navbar.$navigation.insertBefore($(this.columnContainer)) - if (false === config.showNavigation) { - this.$navigation.hide() + if (false === config.showControls) { + this.navbar.hide() } - - this.sliderDialog = new SliderDialog(this.root) - this.sliderDialog.container.id = `igv-slider-dialog-${DOMUtils.guid()}` + this.cursorGuide = new CursorGuide(this.columnContainer, this) this.inputDialog = new InputDialog(this.root) this.inputDialog.container.id = `igv-input-dialog-${DOMUtils.guid()}` @@ -354,7 +250,8 @@ class Browser { this.genericColorPicker = new GenericColorPicker({parent: this.columnContainer, width: 432}) this.genericColorPicker.container.id = `igv-track-color-picker-${DOMUtils.guid()}` - return $navBar + this.sliderDialog = new SliderDialog(this.root) + this.sliderDialog.container.id = `igv-slider-dialog-${DOMUtils.guid()}` } @@ -585,10 +482,10 @@ class Browser { session = await translateSession(session) } - this.sampleInfoControl.setButtonVisibility(false) + this.navbar.sampleInfoControl.setButtonVisibility(false) this.showSampleNames = session.showSampleNames || false - this.sampleNameControl.setState(this.showSampleNames === true) + this.navbar.sampleNameControl.setState(this.showSampleNames === true) if (session.sampleNameViewportWidth) { this.sampleNameViewportWidth = session.sampleNameViewportWidth @@ -708,9 +605,9 @@ class Browser { await rtv.updateViews() } - // If any tracks are selected show the selectino buttons + // If any tracks are selected show the selection buttons if (this.trackViews.some(tv => tv.track.selected)) { - this.multiTrackSelectButton.setMultiTrackSelection(true) + this.navbar.setEnableTrackSelection(true) } this.updateUIWithReferenceFrameList() @@ -751,7 +648,8 @@ class Browser { this.removeAllTracks() // Do this first, before new genome is set this.roiManager.clearROIs() - this.multiTrackSelectButton.setMultiTrackSelection(false) + + this.navbar.setEnableTrackSelection(false) let genome if (genomeConfig.gbkURL) { @@ -764,8 +662,7 @@ class Browser { this.genome = genome - this.updateNavbarDOMWithGenome(genome) - + this.navbar.updateGenome(genome) let locus = initialLocus || genome.initialLocus if (Array.isArray(locus)) { @@ -796,26 +693,6 @@ class Browser { } } - updateNavbarDOMWithGenome(genome) { - let genomeLabel = (genome.id && genome.id.length < 20 ? genome.id : `${genome.id.substring(0, 8)}...${genome.id.substring(genome.id.length - 8)}`) - this.$current_genome.text(genomeLabel) - this.$current_genome.attr('title', genome.description) - - // chromosome select widget -- Show this IFF its not explicitly hidden AND the genome has pre-loaded chromosomes - const showChromosomeWidget = - this.config.showChromosomeWidget !== false && - this.genome.showChromosomeWidget !== false && - genome.chromosomeNames && - genome.chromosomeNames.length > 1 - - if (showChromosomeWidget) { - this.chromosomeSelectWidget.update(genome) - this.chromosomeSelectWidget.show() - } else { - this.chromosomeSelectWidget.hide() - } - } - /** * Load a genome, defined by a string ID or a json-like configuration object. This includes a fasta reference * as well as optional cytoband and annotation tracks. @@ -900,16 +777,16 @@ class Browser { const isWGV = (this.isMultiLocusWholeGenomeView() || GenomeUtils.isWholeGenomeView(referenceFrameList[0].chr)) - navbarDidResize(this, this.$navigation.width(), isWGV) + this.navbar.navbarDidResize() toggleTrackLabels(this.trackViews, this.doShowTrackLabels) if (this.doShowCenterLine && GenomeUtils.isWholeGenomeView(referenceFrameList[0].chr)) { - this.centerLineButton.boundMouseClickHandler() + this.navbar.centerLineButton.boundMouseClickHandler() } if (this.doShowCursorGuide && GenomeUtils.isWholeGenomeView(referenceFrameList[0].chr)) { - this.cursorGuideButton.boundMouseClickHandler() + this.navbar.cursorGuideButton.boundMouseClickHandler() } this.setCenterLineAndCenterLineButtonVisibility(GenomeUtils.isWholeGenomeView(referenceFrameList[0].chr)) @@ -919,9 +796,9 @@ class Browser { setCenterLineAndCenterLineButtonVisibility(isWholeGenomeView) { if (isWholeGenomeView) { - this.centerLineButton.setVisibility(!isWholeGenomeView) + this.navbar.centerLineButton.setVisibility(false) } else { - this.centerLineButton.setVisibility(this.config.showCenterGuideButton) + this.navbar.centerLineButton.setVisibility(this.config.showCenterGuideButton) } for (let centerLine of this.centerLineList) { @@ -1127,7 +1004,7 @@ class Browser { this.reorderTracks() this.fireEvent('trackorderchanged', [this.getTrackOrder()]) - this.multiTrackSelectButton.setMultiTrackSelection(this.multiTrackSelectButton.enableMultiTrackSelection) + newTrack.trackView.enableTrackSelection(this.navbar.getEnableTrackSelection()) return newTrack @@ -1495,8 +1372,7 @@ class Browser { } if (this.referenceFrameList) { - const isWGV = this.isMultiLocusWholeGenomeView() || GenomeUtils.isWholeGenomeView(this.referenceFrameList[0].chr) - navbarDidResize(this, this.$navigation.width(), isWGV) + this.navbar.navbarDidResize() } resize.call(this) @@ -1578,13 +1454,11 @@ class Browser { referenceFrame.end = referenceFrame.start + referenceFrame.bpPerPixel * width } - if (this.chromosomeSelectWidget) { - this.chromosomeSelectWidget.select.value = referenceFrameList.length === 1 ? this.referenceFrameList[0].chr : '' - } + const chrName = referenceFrameList.length === 1 ? this.referenceFrameList[0].chr : '' const loc = this.referenceFrameList.map(rf => rf.getLocusString()).join(' ') - this.$searchInput.val(loc) + this.navbar.updateLocus(loc, chrName) this.fireEvent('locuschange', [this.referenceFrameList]) } @@ -1861,7 +1735,7 @@ class Browser { async loadSampleInfo(config) { await this.sampleInfo.loadSampleInfoFile(config.url) - + for (const {sampleInfoViewport} of this.trackViews) { sampleInfoViewport.setWidth(this.getSampleInfoColumnWidth()) } @@ -2317,7 +2191,7 @@ class Browser { createCircularView(container, show) { show = show === true // convert undefined to boolean this.circularView = createCircularView(container, this) - this.circularViewControl = new CircularViewControl(this.$toggle_button_container.get(0), this) + this.circularViewControl = new CircularViewControl(this.navbar.toggle_button_container, this) this.circularView.setAssembly({ name: this.genome.id, id: this.genome.id, @@ -2337,6 +2211,30 @@ class Browser { this.circularViewControl.setState(isVisible) } } + + + + // Navbar delegates + get sampleInfoControl() { + return this.navbar.sampleInfoControl + } + + get overlayTrackButton() { + return this.navbar.overlayTrackButton + } + + get roiTableControl() { + return this.navbar.roiTableControl + } + + get sampleInfoControl() { + return this.navbar.sampleInfoControl + } + + get sampleNameControl() { + return this.navbar.sampleNameControl + } + } function getFileExtension(input) { @@ -2546,21 +2444,6 @@ async function keyUpHandler(event) { } } - -function logo() { - - return $( - '' + - 'IGV' + - '' + - '' + - '' + - ';' + - '' + - ' ' - ) -} - function toggleTrackLabels(trackViews, isVisible) { for (let {viewports} of trackViews) { diff --git a/js/feature/mergedTrack.js b/js/feature/mergedTrack.js index 3eace8bc6..56c4315d7 100644 --- a/js/feature/mergedTrack.js +++ b/js/feature/mergedTrack.js @@ -152,6 +152,19 @@ class MergedTrack extends TrackBase { return this._autoscale } + set autoscaleGroup(g) { + if(this.tracks) { + for(let t of this.tracks) t.autoscaleGroup = g + } + } + + get autoscaleGroup() { + if(this.tracks && this.tracks.length > 0) { + const g = this.tracks[0].autoscaleGroup + return (this.tracks.some(t => g !== t.autoscaleGroup)) ? undefined : g + } + } + /** * Set the data range of all constitutive numeric tracks. This method is called from the menu item, i.e. an explicit * setting, so it should disable autoscale as well. diff --git a/js/igv-create.js b/js/igv-create.js index 0ae990176..dc9db472d 100644 --- a/js/igv-create.js +++ b/js/igv-create.js @@ -26,7 +26,6 @@ import {GoogleAuth, igvxhr} from '../node_modules/igv-utils/src/index.js' import Browser from "./browser.js" import GenomeUtils from "./genome/genomeUtils.js" -import {navbarDidResize} from "./responsiveNavbar.js" let allBrowsers = [] @@ -82,7 +81,7 @@ async function createBrowser(parentDiv, config) { } browser.stopSpinner() - navbarDidResize(browser, browser.$navigation.width()) + browser.navbar.navbarDidResize() return browser diff --git a/js/responsiveNavbar.js b/js/responsiveNavbar.js index 27d32994c..d461cd715 100644 --- a/js/responsiveNavbar.js +++ b/js/responsiveNavbar.js @@ -24,66 +24,270 @@ */ import $ from "./vendor/jquery-3.3.1.slim.js" -import {StringUtils} from "../node_modules/igv-utils/src/index.js" import GenomeUtils from "./genome/genomeUtils.js" +import ChromosomeSelectWidget from "./ui/chromosomeSelectWidget.js" +import * as DOMUtils from "./ui/utils/dom-utils.js" +import {createIcon} from "./ui/utils/icons.js" +import WindowSizePanel from "./windowSizePanel.js" +import OverlayTrackButton from "./ui/overlayTrackButton.js" +import MultiTrackSelectButton from "./ui/multiTrackSelectButton.js" +import CursorGuide from "./ui/cursorGuide.js" +import CursorGuideButton from "./ui/cursorGuideButton.js" +import CenterLineButton from "./ui/centerLineButton.js" +import TrackLabelControl from "./ui/trackLabelControl.js" +import ROITableControl from "./roi/roiTableControl.js" +import SampleInfoControl from "./sample/sampleInfoControl.js" +import SampleNameControl from "./sample/sampleNameControl.js" +import SaveImageControl from "./ui/saveImageControl.js" +import CustomButton from "./ui/customButton.js" +import ZoomWidget from "./ui/zoomWidget.js" import NavbarButton from "./ui/navbarButton.js" -const navbarResponsiveClasses = {} +class ResponsiveNavbar { + constructor(config, browser) { + + this.browser = browser + this.config = config + + this.currentClass = 'igv-navbar-text-button' + + // DOM element for + const $navBar = $('
', {class: 'igv-navbar'}) + this.$navigation = $navBar + + const $navbarLeftContainer = $('
', {class: 'igv-navbar-left-container'}) + $navBar.append($navbarLeftContainer) + this.navbarLeftContainer = $navbarLeftContainer.get(0) + + // IGV logo + const $logo = $('
', {class: 'igv-logo'}) + $navbarLeftContainer.append($logo) + + const logoSvg = logo() + logoSvg.css("width", "34px") + logoSvg.css("height", "32px") + $logo.append(logoSvg) + + this.$current_genome = $('
', {class: 'igv-current-genome'}) + $navbarLeftContainer.append(this.$current_genome) + this.$current_genome.text('') + + const $genomicLocation = $('
', {class: 'igv-navbar-genomic-location'}) + $navbarLeftContainer.append($genomicLocation) + + // chromosome select widget + this.chromosomeSelectWidget = new ChromosomeSelectWidget(browser, $genomicLocation.get(0)) + if (config.showChromosomeWidget !== false) { + this.chromosomeSelectWidget.show() + } else { + this.chromosomeSelectWidget.hide() + } + + const $locusSizeGroup = $('
', {class: 'igv-locus-size-group'}) + $genomicLocation.append($locusSizeGroup) + + const $searchContainer = $('
', {class: 'igv-search-container'}) + $locusSizeGroup.append($searchContainer) + + // browser.$searchInput = $(''); + this.$searchInput = $('', {class: 'igv-search-input', type: 'text', placeholder: 'Locus Search'}) + $searchContainer.append(this.$searchInput) + // Stop event propagation to prevent feature track keyboard navigation + this.$searchInput[0].addEventListener('keyup', (event) => { + event.stopImmediatePropagation() + }) + + this.$searchInput.change(() => browser.doSearch(this.$searchInput.val())) + + const searchIconContainer = DOMUtils.div({class: 'igv-search-icon-container'}) + $searchContainer.append($(searchIconContainer)) + searchIconContainer.appendChild(createIcon("search")) + searchIconContainer.addEventListener('click', () => browser.doSearch(this.$searchInput.val())) + + this.windowSizePanel = new WindowSizePanel($locusSizeGroup.get(0), browser) + + const $navbarRightContainer = $('
', {class: 'igv-navbar-right-container'}) + $navBar.append($navbarRightContainer) + this.navbarRightContainer = $navbarRightContainer.get(0) + + const $toggle_button_container = $('
') + $navbarRightContainer.append($toggle_button_container) + const toggleButtonContainer = $toggle_button_container.get(0) + this.toggle_button_container = toggleButtonContainer // TODO -- for circular view , refactor this + + this.overlayTrackButton = new OverlayTrackButton(toggleButtonContainer, browser) + this.overlayTrackButton.setVisibility(false) + + const showMultiSelect = config.showMultiSelectButton !== false + this.multiTrackSelectButton = new MultiTrackSelectButton(toggleButtonContainer, browser, this, showMultiSelect) + + this.cursorGuideButton = new CursorGuideButton(toggleButtonContainer, browser) + + this.centerLineButton = new CenterLineButton(toggleButtonContainer, browser) + + this.trackLabelControl = new TrackLabelControl(toggleButtonContainer, browser) + + // ROI Control + this.roiTableControl = new ROITableControl(toggleButtonContainer, browser) + + this.sampleInfoControl = new SampleInfoControl(toggleButtonContainer, browser) + + this.sampleNameControl = new SampleNameControl(toggleButtonContainer, browser) + + if (true === config.showSVGButton) { + this.saveImageControl = new SaveImageControl(toggleButtonContainer, browser) + } + + if (config.customButtons) { + for (let b of config.customButtons) { + new CustomButton(toggleButtonContainer, browser, b) + } + } + + this.zoomWidget = new ZoomWidget(config, browser, $navbarRightContainer.get(0)) + + if (false === config.showNavigation) { + this.$navigation.hide() + } -const responsiveThreshold = 8 -let textButtonContainerWidth = undefined -function navbarDidResize(browser, width) { - const currentClass = NavbarButton.currentNavbarButtonClass(browser) - if ('igv-navbar-text-button' === currentClass) { - textButtonContainerWidth = browser.$navigation.get(0).querySelector('.igv-navbar-right-container').getBoundingClientRect().width } - const responsiveClasses = getResponsiveClasses(browser, width) + navbarDidResize() { - $(browser.zoomWidget.zoomContainer).removeClass() - $(browser.zoomWidget.zoomContainer).addClass(responsiveClasses.zoomContainer) - browser.fireEvent('navbar-resize', [ responsiveClasses.navbarButton ]) -} + const navbarWidth = this.$navigation.width() + const currentClass = this.currentNavbarButtonClass() + if ('igv-navbar-text-button' === currentClass) { + this.textButtonContainerWidth = this.navbarRightContainer.getBoundingClientRect().width + } + const browser = this.browser + const isWGV = + (browser.isMultiLocusWholeGenomeView()) || + (browser.referenceFrameList && GenomeUtils.isWholeGenomeView(browser.referenceFrameList[0].chr)) + + isWGV ? this.windowSizePanel.hide() : this.windowSizePanel.show() -function getResponsiveClasses(browser, navbarWidth) { + const { + x: leftContainerX, + width: leftContainerWidth + } = this.navbarLeftContainer.getBoundingClientRect() + const leftContainerExtent = leftContainerX + leftContainerWidth + const {x: rightContainerX} = this.navbarRightContainer.getBoundingClientRect() - const isWGV = - (browser.isMultiLocusWholeGenomeView()) || - (browser.referenceFrameList && GenomeUtils.isWholeGenomeView(browser.referenceFrameList[0].chr)) + const delta = rightContainerX - leftContainerExtent + + let navbarButtonClass + const threshold = 8 + if ('igv-navbar-text-button' === currentClass && delta < threshold) { + navbarButtonClass = 'igv-navbar-icon-button' + } else if (this.textButtonContainerWidth && 'igv-navbar-icon-button' === currentClass) { + const length = navbarWidth - leftContainerExtent + if (length - this.textButtonContainerWidth > threshold) { + navbarButtonClass = 'igv-navbar-text-button' + } + } + // Update all the buttons (buttons are listeners) + if(navbarButtonClass && currentClass !== navbarButtonClass) { + this.currentClass = navbarButtonClass + this.browser.fireEvent('navbar-resize', [navbarButtonClass]) + } + + let zoomContainerClass + if (isWGV) { + zoomContainerClass = 'igv-zoom-widget-hidden' + } else { + zoomContainerClass = navbarWidth > 860 ? 'igv-zoom-widget' : 'igv-zoom-widget-900' + } + $(this.zoomWidget.zoomContainer).removeClass() + $(this.zoomWidget.zoomContainer).addClass(zoomContainerClass) + } + + + setCenterLineButtonVisibility(isWholeGenomeView) { + if (isWholeGenomeView) { + this.centerLineButton.setVisibility(!isWholeGenomeView) + } else { + this.centerLineButton.setVisibility(this.config.showCenterGuideButton) + } + } - isWGV ? browser.windowSizePanel.hide() : browser.windowSizePanel.show() + setCursorGuideVisibility(doShowCursorGuide) { + if (doShowCursorGuide) { + this.cursorGuide.show() + } else { + this.cursorGuide.hide() + } + } - const { x: leftContainerX, width: leftContainerWidth } = browser.$navigation.get(0).querySelector('.igv-navbar-left-container').getBoundingClientRect() - const leftContainerExtent = leftContainerX + leftContainerWidth - const { x:rightContainerX} = browser.$navigation.get(0).querySelector('.igv-navbar-right-container').getBoundingClientRect() + updateGenome(genome) { - const delta = rightContainerX - leftContainerExtent + let genomeLabel = (genome.id && genome.id.length < 20 ? genome.id : `${genome.id.substring(0, 8)}...${genome.id.substring(genome.id.length - 8)}`) + this.$current_genome.text(genomeLabel) + this.$current_genome.attr('title', genome.description) - const currentClass = NavbarButton.currentNavbarButtonClass(browser) + // chromosome select widget -- Show this IFF its not explicitly hidden AND the genome has pre-loaded chromosomes + const showChromosomeWidget = + this.config.showChromosomeWidget !== false && + genome.showChromosomeWidget !== false && + genome.chromosomeNames && + genome.chromosomeNames.length > 1 - // console.log(`Current class ${ currentClass } Delta: ${ StringUtils.numberFormatter(Math.floor(delta))}`) + if (showChromosomeWidget) { + this.chromosomeSelectWidget.update(genome) + this.chromosomeSelectWidget.show() + } else { + this.chromosomeSelectWidget.hide() + } + } - if ('igv-navbar-text-button' === currentClass && delta < responsiveThreshold) { - navbarResponsiveClasses.navbarButton = 'igv-navbar-icon-button' - } else if (textButtonContainerWidth && 'igv-navbar-icon-button' === currentClass) { - const length = navbarWidth - leftContainerExtent - if (length - textButtonContainerWidth > responsiveThreshold) { - navbarResponsiveClasses.navbarButton = 'igv-navbar-text-button' + updateLocus(loc, chrName) { + if(this.$searchInput) { + this.$searchInput.val(loc) } + if (this.chromosomeSelectWidget) { + this.chromosomeSelectWidget.select.value = chrName + } + } + currentNavbarButtonClass() { + return this.currentClass + //const el = this.$navigation.get(0).querySelector('.igv-navbar-text-button') + //return el ? 'igv-navbar-text-button' : 'igv-navbar-icon-button' } + setEnableTrackSelection(b) { + this.multiTrackSelectButton.setMultiTrackSelection(b) + } + getEnableTrackSelection() { + return this.multiTrackSelectButton.enableMultiTrackSelection + } - if (isWGV) { - navbarResponsiveClasses.zoomContainer = 'igv-zoom-widget-hidden' - } else { - navbarResponsiveClasses.zoomContainer = navbarWidth > 860 ? 'igv-zoom-widget' : 'igv-zoom-widget-900' + hide() { + this.$navigation.hide() + } + + show() { + this.$navigation.show() } - return navbarResponsiveClasses } -export { navbarDidResize, navbarResponsiveClasses } + +function logo() { + + return $( + '' + + 'IGV' + + '' + + '' + + '' + + ';' + + '' + + ' ' + ) +} + + +export default ResponsiveNavbar diff --git a/js/roi/roiTableControl.js b/js/roi/roiTableControl.js index 07170660c..877281e72 100644 --- a/js/roi/roiTableControl.js +++ b/js/roi/roiTableControl.js @@ -32,7 +32,7 @@ class ROITableControl extends NavbarButton { constructor(parent, browser) { - super(browser, parent, [ 'ROI', 'Regions of Interest Table' ], buttonLabel, roiImage, roiImageHover, false) + super(parent, browser, ['ROI', 'Regions of Interest Table'], buttonLabel, roiImage, roiImageHover, false) this.button.addEventListener('mouseenter', () => { if (false === browser.doShowROITable) { diff --git a/js/sample/sampleInfoControl.js b/js/sample/sampleInfoControl.js index 0163f4e71..d737e8c3a 100644 --- a/js/sample/sampleInfoControl.js +++ b/js/sample/sampleInfoControl.js @@ -32,7 +32,7 @@ class SampleInfoControl extends NavbarButton { constructor(parent, browser) { - super(browser, parent, 'Sample Info', buttonLabel, sampleInfoImage, sampleInfoImageHover, false) + super(parent, browser, 'Sample Info', buttonLabel, sampleInfoImage, sampleInfoImageHover, false) this.showSampleInfo = false diff --git a/js/sample/sampleNameControl.js b/js/sample/sampleNameControl.js index 9e5a8fbdb..5b006b4db 100644 --- a/js/sample/sampleNameControl.js +++ b/js/sample/sampleNameControl.js @@ -32,7 +32,7 @@ class SampleNameControl extends NavbarButton { constructor(parent, browser) { - super(browser, parent, 'Sample Names', sampleNameButtonLabel, sampleNameImage, sampleNameImageHover, browser.config.showSampleNames) + super(parent, browser, 'Sample Names', sampleNameButtonLabel, sampleNameImage, sampleNameImageHover, browser.config.showSampleNames) this.button.addEventListener('mouseenter', () => { if (false === browser.showSampleNames) { diff --git a/js/search.js b/js/search.js index 4391768d6..9b844580f 100644 --- a/js/search.js +++ b/js/search.js @@ -13,6 +13,7 @@ async function searchFeatures(browser, name) { const searchConfig = browser.searchConfig || DEFAULT_SEARCH_CONFIG let feature + name = name.toUpperCase() const searchableTracks = browser.tracks.filter(t => t.searchable) for (let track of searchableTracks) { const feature = await track.search(name) diff --git a/js/trackView.js b/js/trackView.js index 494c81c7c..3862ac55e 100644 --- a/js/trackView.js +++ b/js/trackView.js @@ -33,7 +33,7 @@ import {createIcon} from "./ui/utils/icons.js" import SampleInfoViewport from "./sample/sampleInfoViewport.js" import SampleNameViewport from './sample/sampleNameViewport.js' import MenuPopup from "./ui/menuPopup.js" -import { autoScaleGroupColorHash, multiTrackSelectExclusionTypes } from "./ui/menuUtils.js" +import {autoScaleGroupColorHash, multiTrackSelectExclusionTypes} from "./ui/menuUtils.js" import {colorPalettes, hexToRGB} from "./util/colorPalletes.js" import {isOverlayTrackCriteriaMet} from "./ui/overlayTrackButton.js" @@ -117,6 +117,8 @@ class TrackView { createAxis(browser, track) { const axis = DOMUtils.div() + this.axis = axis + browser.columnContainer.querySelector('.igv-axis-column').appendChild(axis) axis.dataset.tracktype = track.type @@ -134,12 +136,12 @@ class TrackView { if (false === multiTrackSelectExclusionTypes.has(this.track.type)) { - const trackSelectionContainer = DOMUtils.div() - axis.appendChild(trackSelectionContainer) + this.trackSelectionContainer = DOMUtils.div() + axis.appendChild(this.trackSelectionContainer) const html = `` const input = document.createRange().createContextualFragment(html).firstChild - trackSelectionContainer.appendChild(input) + this.trackSelectionContainer.appendChild(input) input.checked = this.track.selected || false input.addEventListener('change', event => { @@ -147,10 +149,10 @@ class TrackView { event.stopPropagation() this.track.selected = event.target.checked this.setDragHandleSelectionState(event.target.checked) - this.browser.overlayTrackButton.setVisibility( isOverlayTrackCriteriaMet(this.browser) ) + this.browser.overlayTrackButton.setVisibility(isOverlayTrackCriteriaMet(this.browser)) }) - this.setTrackSelectionState(axis, false) + this.enableTrackSelection(false) } @@ -966,13 +968,19 @@ class TrackView { return Math.max(...this.viewports.map(viewport => viewport.getContentHeight())) } - setTrackSelectionState(axis, doEnableMultiSelection) { + enableTrackSelection(doEnableMultiSelection) { + + const container = this.trackSelectionContainer - const container = axis.querySelector('div') + if (!container || multiTrackSelectExclusionTypes.has(this.track.type)) { + return + } if (false !== doEnableMultiSelection) { container.style.display = 'grid' } else { + // If disabling selection set track selection state to false + this.track.selected = false const trackSelectInput = container.querySelector('[name=track-select]') trackSelectInput.checked = this.track.selected @@ -998,13 +1006,11 @@ class TrackView { dragHandle.classList.remove('igv-track-drag-handle-selected-color') dragHandle.classList.add('igv-track-drag-handle-color') } - } } - function renderSVGAxis(context, track, axisCanvas, deltaX, deltaY) { if (typeof track.paintAxis === 'function') { diff --git a/js/ucsc/ucscHub.js b/js/ucsc/ucscHub.js index c015f0fcd..c2f72a7ef 100644 --- a/js/ucsc/ucscHub.js +++ b/js/ucsc/ucscHub.js @@ -332,7 +332,7 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1 config.searchIndex = t.getProperty("searchIndex") } if (t.hasProperty("searchTrix")) { - config.searchTrix = this.baseURL + t.getProperty("searchTrix") + config.trixURL = this.baseURL + t.getProperty("searchTrix") } if (t.hasProperty("group")) { diff --git a/js/ui/centerLineButton.js b/js/ui/centerLineButton.js index 925c9bd90..c0bd0acd6 100644 --- a/js/ui/centerLineButton.js +++ b/js/ui/centerLineButton.js @@ -31,9 +31,9 @@ import { buttonLabel } from "./navbarIcons/buttonLabel.js" class CenterLineButton extends NavbarButton { - constructor(browser, parent) { + constructor(parent, browser) { - super(browser, parent, 'Center Line', buttonLabel, centerlineImage, centerlineImageHover, browser.config.showCenterGuide) + super(parent, browser, 'Center Line', buttonLabel, centerlineImage, centerlineImageHover, browser.config.showCenterGuide) this.button.addEventListener('mouseenter', () => { if (false === browser.doShowCenterLine) { diff --git a/js/ui/cursorGuideButton.js b/js/ui/cursorGuideButton.js index 623a5a0c6..cd4824290 100644 --- a/js/ui/cursorGuideButton.js +++ b/js/ui/cursorGuideButton.js @@ -31,9 +31,9 @@ import { buttonLabel } from "./navbarIcons/buttonLabel.js" class CursorGuideButton extends NavbarButton { - constructor(browser, parent) { + constructor(parent, browser) { - super(browser, parent, 'Crosshairs', buttonLabel, cursorImage, cursorImageHover, browser.doShowCursorGuide) + super(parent, browser, 'Crosshairs', buttonLabel, cursorImage, cursorImageHover, browser.doShowCursorGuide) this.button.addEventListener('mouseenter', () => { if (false === browser.doShowCursorGuide) { diff --git a/js/ui/multiTrackSelectButton.js b/js/ui/multiTrackSelectButton.js index 365dd5ed2..285136378 100644 --- a/js/ui/multiTrackSelectButton.js +++ b/js/ui/multiTrackSelectButton.js @@ -1,15 +1,16 @@ import NavbarButton from "./navbarButton.js" -import {multiTrackSelectExclusionTypes} from './menuUtils.js' import {multiSelectImage, multiSelectImageHover} from "./navbarIcons/multiSelect.js" import {buttonLabel} from "./navbarIcons/buttonLabel.js" - +import {multiTrackSelectExclusionTypes} from './menuUtils.js' class MultiTrackSelectButton extends NavbarButton { - constructor(browser, parent, enableMultiTrackSelection) { + constructor(parent, browser, navbar, enableMultiTrackSelection, visibility = true) { - super(browser, parent, 'Select Tracks', buttonLabel, multiSelectImage, multiSelectImageHover, enableMultiTrackSelection = false) - this.enableMultiTrackSelection = enableMultiTrackSelection + super(parent, browser, 'Select Tracks', buttonLabel, multiSelectImage, multiSelectImageHover, false) + + this.navbar = navbar + this.enableMultiTrackSelection = false // Initial state this.button.addEventListener('mouseenter', event => { if (false === enableMultiTrackSelection) { this.setState(true) @@ -23,6 +24,7 @@ class MultiTrackSelectButton extends NavbarButton { }) const mouseClickHandler = () => { + // Toggle the selection state this.setMultiTrackSelection(!this.enableMultiTrackSelection) } @@ -30,29 +32,26 @@ class MultiTrackSelectButton extends NavbarButton { this.button.addEventListener('click', this.boundMouseClickHandler) - this.setVisibility(true) + this.setVisibility(visibility) } setMultiTrackSelection(enableMultiTrackSelection) { - this.enableMultiTrackSelection = enableMultiTrackSelection - for (const trackView of this.browser.trackViews) { - if (false === multiTrackSelectExclusionTypes.has(trackView.track.type)) { - trackView.setTrackSelectionState(trackView.axis, this.enableMultiTrackSelection) - // If closing the selection boxes set track selected property to false - if (!this.enableMultiTrackSelection) { - trackView.track.selected = false - } - } - } + this.enableMultiTrackSelection = enableMultiTrackSelection this.setState(this.enableMultiTrackSelection) - // If enableMultiTrackSelection is false hide Overlay button + // If enableMultiTrackSelection is false hide the Overly button if (false === this.enableMultiTrackSelection) { - this.browser.overlayTrackButton.setVisibility(false) + this.navbar.overlayTrackButton.setVisibility(false) } + + for (const trackView of this.browser.trackViews) { + trackView.enableTrackSelection(enableMultiTrackSelection) + } + } + } export default MultiTrackSelectButton diff --git a/js/ui/navbarButton.js b/js/ui/navbarButton.js index ce7d254cd..29218b883 100644 --- a/js/ui/navbarButton.js +++ b/js/ui/navbarButton.js @@ -28,7 +28,7 @@ import * as DOMUtils from "../ui/utils/dom-utils.js" class NavbarButton { - constructor(browser, parent, title, buttonLabel, imageSVG, imageHoverSVG, initialButtonState) { + constructor(parent, browser, title, buttonLabel, imageSVG, imageHoverSVG, initialButtonState) { this.browser = browser @@ -85,7 +85,7 @@ class NavbarButton { } configureTextButton(textContent) { - + console.log(`text ${this.title}`) this.button.classList.add('igv-navbar-text-button') const tempDiv = document.createElement('div') @@ -100,6 +100,7 @@ class NavbarButton { } configureIconButton() { + console.log(`icon ${this.title}`) this.button.classList.add('igv-navbar-icon-button') } @@ -139,11 +140,6 @@ class NavbarButton { this.hide() } } - - static currentNavbarButtonClass(browser) { - const el = browser.$navigation.get(0).querySelector('.igv-navbar-text-button') - return el ? 'igv-navbar-text-button' : 'igv-navbar-icon-button' - } } export default NavbarButton diff --git a/js/ui/overlayTrackButton.js b/js/ui/overlayTrackButton.js index 46f1722b4..31a909ef3 100644 --- a/js/ui/overlayTrackButton.js +++ b/js/ui/overlayTrackButton.js @@ -1,13 +1,13 @@ import NavbarButton from "./navbarButton.js" -import { overlayTrackImage, overlayTrackImageHover } from "./navbarIcons/overlayTrack.js" -import { buttonLabel } from "./navbarIcons/buttonLabel.js" +import {overlayTrackImage, overlayTrackImageHover} from "./navbarIcons/overlayTrack.js" +import {buttonLabel} from "./navbarIcons/buttonLabel.js" import MergedTrack from "../feature/mergedTrack.js" class OverlayTrackButton extends NavbarButton { - constructor(browser, parent) { + constructor(parent, browser) { - super(browser, parent, 'Overlay Tracks', buttonLabel, overlayTrackImage, overlayTrackImageHover, false) + super(parent, browser, 'Overlay Tracks', buttonLabel, overlayTrackImage, overlayTrackImageHover, false) this.button.addEventListener('mouseenter', () => this.setState(true)) this.button.addEventListener('mouseleave', () => this.setState(false)) @@ -30,15 +30,15 @@ function trackOverlayClickHandler(e) { if (true === isOverlayTrackCriteriaMet(this.browser)) { - const tracks = this.browser.getSelectedTrackViews().map(({ track }) => track) + const tracks = this.browser.getSelectedTrackViews().map(({track}) => track) for (const track of tracks) { track.selected = false } - // Flatten any merged tracks. Must do this before there removal + // Flatten any merged tracks. Must do this before their removal const flattenedTracks = [] - for(let t of tracks) { - if("merged" === t.type) { + for (let t of tracks) { + if ("merged" === t.type) { flattenedTracks.push(...t.tracks) } else { flattenedTracks.push(t) @@ -51,17 +51,20 @@ function trackOverlayClickHandler(e) { type: 'merged', autoscale: false, alpha: 0.5, //fudge * (1.0/tracks.length), - height: Math.max(...tracks.map(({ height }) => height)), - order: Math.min(...tracks.map(({ order }) => order)), + height: Math.max(...tracks.map(({height}) => height)), + order: Math.min(...tracks.map(({order}) => order)), } const mergedTrack = new MergedTrack(config, this.browser, flattenedTracks) for (const track of tracks) { - this.browser.removeTrack(track) + const idx = this.browser.trackViews.indexOf(track.trackView) + this.browser.trackViews.splice(idx, 1) + track.trackView.dispose() } this.browser.addTrack(config, mergedTrack) + mergedTrack.trackView.updateViews() } @@ -73,9 +76,9 @@ function isOverlayTrackCriteriaMet(browser) { if (selected && selected.length > 1) { - const criteriaSet = new Set([ 'wig', 'merged' ]) + const criteriaSet = new Set(['wig', 'merged']) - const list = selected.filter(({ track }) => criteriaSet.has(track.type)) + const list = selected.filter(({track}) => criteriaSet.has(track.type)) return list.length > 1 @@ -85,5 +88,5 @@ function isOverlayTrackCriteriaMet(browser) { } -export { isOverlayTrackCriteriaMet } +export {isOverlayTrackCriteriaMet} export default OverlayTrackButton diff --git a/js/ui/saveImageControl.js b/js/ui/saveImageControl.js index 785040a82..fd31ad5ee 100644 --- a/js/ui/saveImageControl.js +++ b/js/ui/saveImageControl.js @@ -33,7 +33,7 @@ import { buttonLabel } from "./navbarIcons/buttonLabel.js" class SaveImageControl extends NavbarButton { constructor(parent, browser) { - super(browser, parent, 'Save Image', buttonLabel, imageSaveImageSVG, imageSaveImageHoverSVG, false) + super(parent, browser, 'Save Image', buttonLabel, imageSaveImageSVG, imageSaveImageHoverSVG, false) this.button.addEventListener('mouseenter', () => this.setState(true)) diff --git a/js/ui/trackLabelControl.js b/js/ui/trackLabelControl.js index adc43cfab..e98973fd7 100644 --- a/js/ui/trackLabelControl.js +++ b/js/ui/trackLabelControl.js @@ -32,7 +32,7 @@ class TrackLabelControl extends NavbarButton { constructor(parent, browser) { - super(browser, parent, 'Track Labels', buttonLabel, trackLabelsImage, trackLabelsImageHover, browser.config.showTrackLabels) + super(parent, browser, 'Track Labels', buttonLabel, trackLabelsImage, trackLabelsImageHover, browser.config.showTrackLabels) this.button.addEventListener('mouseenter', () => { if (false === browser.doShowTrackLabels) { diff --git a/js/ui/zoomWidget.js b/js/ui/zoomWidget.js index 3019530e6..54e9dc3e8 100644 --- a/js/ui/zoomWidget.js +++ b/js/ui/zoomWidget.js @@ -5,146 +5,135 @@ const sliderMin = 0 let sliderMax = 23 let sliderValueRaw = 0 -const ZoomWidget = function (browser, parent) { +class ZoomWidget { + constructor(config, browser, parent) { - this.browser = browser + this.browser = browser - this.zoomContainer = DOMUtils.div({class: 'igv-zoom-widget'}) - parent.appendChild(this.zoomContainer) + this.zoomContainer = DOMUtils.div({class: 'igv-zoom-widget'}) + parent.appendChild(this.zoomContainer) - // zoom out - this.zoomOutButton = DOMUtils.div() - this.zoomContainer.appendChild(this.zoomOutButton) - this.zoomOutButton.appendChild(createIcon('minus-circle')) - this.zoomOutButton.addEventListener('click', () => { - // browser.zoomWithScaleFactor(2.0) - browser.zoomOut() - }) + // zoom out + this.zoomOutButton = DOMUtils.div() + this.zoomContainer.appendChild(this.zoomOutButton) + this.zoomOutButton.appendChild(createIcon('minus-circle')) + this.zoomOutButton.addEventListener('click', () => { + // browser.zoomWithScaleFactor(2.0) + browser.zoomOut() + }) - // Range slider - const el = DOMUtils.div() - this.zoomContainer.appendChild(el) - this.slider = document.createElement('input') - this.slider.type = 'range' + // Range slider (optional) + if (config.showZoomSlider !== false) { + const el = DOMUtils.div() + this.zoomContainer.appendChild(el) + this.slider = document.createElement('input') + this.slider.type = 'range' - this.slider.min = `${sliderMin}` - this.slider.max = `${sliderMax}` + this.slider.min = `${sliderMin}` + this.slider.max = `${sliderMax}` - el.appendChild(this.slider) + el.appendChild(this.slider) - this.slider.addEventListener('change', e => { + this.slider.addEventListener('change', e => { - e.preventDefault() - e.stopPropagation() + e.preventDefault() + e.stopPropagation() - const referenceFrame = browser.referenceFrameList[0] - const {bpLength} = referenceFrame.genome.getChromosome(referenceFrame.chr) - const {end, start} = referenceFrame + const referenceFrame = browser.referenceFrameList[0] + const {bpLength} = referenceFrame.genome.getChromosome(referenceFrame.chr) + const {end, start} = referenceFrame - const extent = end - start + const extent = end - start - // bpLength/(end - start) - const scaleFactor = Math.pow(2, e.target.valueAsNumber) + // bpLength/(end - start) + const scaleFactor = Math.pow(2, e.target.valueAsNumber) - // (end - start) = bpLength/scaleFactor - const zoomedExtent = bpLength / scaleFactor + // (end - start) = bpLength/scaleFactor + const zoomedExtent = bpLength / scaleFactor - // console.log(`zoom-widget - slider ${ e.target.value } scaleFactor ${ scaleFactor } extent-zoomed ${ StringUtils.numberFormatter(Math.round(zoomedExtent)) }`) + // console.log(`zoom-widget - slider ${ e.target.value } scaleFactor ${ scaleFactor } extent-zoomed ${ StringUtils.numberFormatter(Math.round(zoomedExtent)) }`) - browser.zoomWithScaleFactor(zoomedExtent / extent) + browser.zoomWithScaleFactor(zoomedExtent / extent) - }) - - // zoom in - this.zoomInButton = DOMUtils.div() - this.zoomContainer.appendChild(this.zoomInButton) - this.zoomInButton.appendChild(createIcon('plus-circle')) - this.zoomInButton.addEventListener('click', () => { - // browser.zoomWithScaleFactor(0.5) - browser.zoomIn() - }) - - browser.on('locuschange', (referenceFrameList) => { - - if (this.browser.isMultiLocusMode()) { - this.disable() - } else { - this.enable() - this.update(referenceFrameList) + }) } - }) - -} - -ZoomWidget.prototype.update = function (referenceFrameList) { - - const referenceFrame = referenceFrameList[0] - const {bpLength} = referenceFrame.genome.getChromosome(referenceFrame.chr) - const {start, end} = referenceFrame - - sliderMax = Math.ceil(Math.log2(bpLength / this.browser.minimumBases())) - - this.slider.max = `${sliderMax}` + // zoom in + this.zoomInButton = DOMUtils.div() + this.zoomContainer.appendChild(this.zoomInButton) + this.zoomInButton.appendChild(createIcon('plus-circle')) + this.zoomInButton.addEventListener('click', () => { + // browser.zoomWithScaleFactor(0.5) + browser.zoomIn() + }) - const scaleFactor = bpLength / (end - start) - sliderValueRaw = Math.log2(scaleFactor) - this.slider.value = `${Math.round(sliderValueRaw)}` + browser.on('locuschange', (referenceFrameList) => { - const extent = end - start + if (this.browser.isMultiLocusMode()) { + this.disable() + } else { + this.enable() + this.update(referenceFrameList) + } - const derivedScalefactor = Math.pow(2, sliderValueRaw) + }) - const derivedExtent = bpLength / derivedScalefactor + } - // referenceFrame.description('zoom.update') + update(referenceFrameList) { - // console.log(`${ Date.now() } update - slider ${ this.slider.value } scaleFactor ${ Math.round(scaleFactor) } extent ${ StringUtils.numberFormatter(Math.round(extent)) }`) + if(this.slider) { + const referenceFrame = referenceFrameList[0] + const {bpLength} = referenceFrame.genome.getChromosome(referenceFrame.chr) + const {start, end} = referenceFrame - // console.log(`update - sliderMin ${ sliderMin } sliderValue ${ this.slider.value } sliderMax ${ sliderMax } scaleFactor ${ scaleFactor.toFixed(3) } derived-scaleFactor ${ derivedScalefactor.toFixed(3) }`) + sliderMax = Math.ceil(Math.log2(bpLength / this.browser.minimumBases())) + this.slider.max = `${sliderMax}` -} + const scaleFactor = bpLength / (end - start) + sliderValueRaw = Math.log2(scaleFactor) + this.slider.value = `${Math.round(sliderValueRaw)}` + } + } -ZoomWidget.prototype.enable = function () { - // this.zoomInButton.style.color = appleCrayonPalette[ 'steel' ]; - // this.zoomInButton.style.pointerEvents = 'auto'; - // - // this.zoomOutButton.style.color = appleCrayonPalette[ 'steel' ]; - // this.zoomOutButton.style.pointerEvents = 'auto'; + enable() { - this.slider.disabled = false -} + // this.zoomInButton.style.color = appleCrayonPalette[ 'steel' ]; + // this.zoomInButton.style.pointerEvents = 'auto'; + // + // this.zoomOutButton.style.color = appleCrayonPalette[ 'steel' ]; + // this.zoomOutButton.style.pointerEvents = 'auto'; -ZoomWidget.prototype.disable = function () { + if (this.slider) this.slider.disabled = false + } - // this.zoomInButton.style.color = appleCrayonPalette[ 'silver' ]; - // this.zoomInButton.style.pointerEvents = 'none'; - // - // this.zoomOutButton.style.color = appleCrayonPalette[ 'silver' ]; - // this.zoomOutButton.style.pointerEvents = 'none'; + disable() { - this.slider.disabled = true -} + // this.zoomInButton.style.color = appleCrayonPalette[ 'silver' ]; + // this.zoomInButton.style.pointerEvents = 'none'; + // + // this.zoomOutButton.style.color = appleCrayonPalette[ 'silver' ]; + // this.zoomOutButton.style.pointerEvents = 'none'; -ZoomWidget.prototype.hide = function () { - this.zoomContainer.style.display = 'none' -} + if (this.slider) this.slider.disabled = true + } -ZoomWidget.prototype.show = function () { - this.zoomContainer.style.display = 'block' -} + hide() { + this.zoomContainer.style.display = 'none' + } -ZoomWidget.prototype.hideSlider = function () { - this.slider.style.display = 'none' -} + show() { + this.zoomContainer.style.display = 'block' + } -ZoomWidget.prototype.showSlider = function () { - this.slider.style.display = 'block' -} + hideSlider() { + if (this.slider) this.slider.style.display = 'none' + } -function lerpAlvyRaySmith(a, b, t) { - return a - t * (a - b) + showSlider() { + if (this.slider) this.slider.style.display = 'block' + } } export default ZoomWidget