Skip to content

Commit

Permalink
Merge branch 'release/0.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
abought committed Dec 13, 2019
2 parents dbcc3e5 + edb0b03 commit b0b53bf
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
lts/carbon
lts/erbium
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "localzoom",
"version": "0.4.0",
"version": "0.5.0",
"license": "MIT",
"main": "lib/LzTabix.umd.min.js",
"scripts": {
Expand All @@ -16,7 +16,7 @@
"bootstrap": "^4.1.3",
"bootstrap-vue": "^2.0.0-rc.11",
"gwas-credible-sets": "^0.1.0",
"locuszoom": "0.10.0-beta.2",
"locuszoom": "0.10.0",
"lodash": "^4.17.11",
"tabix-reader": "https://github.com/abought/tabix-reader",
"tabulator-tables": "^4.1.4",
Expand Down
64 changes: 64 additions & 0 deletions src/components/BatchScroller.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script>
/**
* Show a "scroll through a list of regions" toolbar
*/
export default {
name: 'BatchScroller',
props: ['regions'],
data() {
return { current_index: null };
},
methods: {
cancelNavigation() {
this.$emit('cancel');
},
goToItem(increment) {
if (this.current_index === null) {
this.current_index = 0;
} else {
this.current_index += increment;
}
this.$emit('navigate', this.current_region);
},
},
computed: {
current_region() {
return this.current_index === null ? null : this.regions[this.current_index];
},
current_region_display() {
const region = this.current_region;
if (!region) {
return '(none)';
}
const { chr, start, end } = region;
return `${chr}: ${start.toLocaleString()}-${end.toLocaleString()}`;
},
},
};
</script>


<template>
<div class="alert-success d-flex justify-content-between align-items-center">
<button class="btn btn-link"
@click="goToItem(-1)"
:disabled="this.current_index <= 0"
>&lt; Prev</button>
<span>
Current Locus: {{current_region_display}} -
<span v-if="this.current_index !==null">
({{this.current_index + 1}} of {{this.regions.length}}) -
</span>
<button @click="cancelNavigation"
class="btn btn-link p-0 border-0 align-bottom"
title="Cancel navigation"
>cancel batch mode</button>
</span>
<button class="btn btn-link"
@click="goToItem(+1)"
:disabled="this.current_index !== null && this.current_index >= this.regions.length - 1"
>Next &gt;</button>
</div>
</template>

<style scoped></style>
86 changes: 86 additions & 0 deletions src/components/BatchSpec.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<script>
/**
* Allows user to designate a list of regions to plot. Handles parsing and selection of regions,
* which will then be consumed by another widget that handles cycling between those choices.
*/
import { BDropdown } from 'bootstrap-vue/esm/';
import { parseRegion } from '../util/entity-helpers';
export default {
name: 'BatchSpec',
props: { max_range: Number },
data() {
return {
region_text: '',
show_loader: false,
message: null, // display validation errors
};
},
methods: {
getRegionsFromTextBox() {
// Regions are one per line, and eliminate empty lines
const text = this.region_text.trim().split(/\r?\n/).filter(value => !!value);
return text.map(item => parseRegion(item, { region_size: this.max_range }));
},
validateRegions(items) {
// There must be at least one region selected. Can add other checks in the future.
return !!items.length;
},
updateRegions(content) { // List of [chr,start, end] entries, one per region
// Receive a list of regions, and store them in the textbox
// Sometimes we may want to handle fetching items from a network request; wrap in a
// promise to be sure this handles async behavior or values, consistently
this.show_loader = true;
Promise.resolve(content)
.then(items => items.map(({ chr, start, end }) => `${chr}:${start}-${end}`).join('\n'))
.then((result) => { this.region_text = result; })
.catch((e) => { this.message = 'Unable to retrieve items'; })
.finally(() => { this.show_loader = false; });
},
sendRegions() {
// Fetch, parse, and send the list of regions
let items;
try {
items = this.getRegionsFromTextBox();
} catch (e) {
this.message = e.toString();
return;
}
if (!items.length) {
this.message = 'Must specify at least one region';
return;
}
this.message = '';
this.$emit('ready', items);
this.$refs.dropdown.hide();
},
},
components: { BDropdown },
};
</script>

<template>
<div>
<b-dropdown ref="dropdown" text="Batch view" variant="info">
<div class="px-3">
<label for="batch-region-list">Specify regions to plot (one per line):</label>
<textarea id="batch-region-list" v-model="region_text"
rows="10" placeholder="chr:start-end or chr:pos"></textarea>
<div v-if="message" class="text-danger">{{message}}</div>
<div class="d-flex justify-content-end">
<div v-if="show_loader" class="spinner-border text-warning" role="status">
<span class="sr-only">Loading...</span>
</div>
<!-- Optional spot for a button (like "fetch presets") -->
<slot name="preset-button" :updateRegions="updateRegions"></slot>
<button @click="sendRegions" class="btn btn-success ml-1">Go</button>
</div>
</div>
</b-dropdown>
</div>
</template>

<style scoped>
</style>
69 changes: 52 additions & 17 deletions src/components/GwasToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,33 @@
import { BDropdown } from 'bootstrap-vue/esm/';
import AdderWizard from './AdderWizard.vue';
import BatchSpec from './BatchSpec.vue';
import BatchScroller from './BatchScroller.vue';
import RegionPicker from './RegionPicker.vue';
import TabixFile from './TabixFile.vue';
import TabixUrl from './TabixUrl.vue';
const MAX_REGION_SIZE = 1000000;
export default {
name: 'gwas-toolbar',
props: {
// Limit how many studies can be added (due to browser performance)
max_studies: { type: Number, default: 3 },
max_studies: {
type: Number,
default: 3,
},
// Toolbar can optionally consider a list of studies already on plot
study_names: { type: [Array, null], default: null },
study_names: {
type: [Array, null],
default: null,
},
},
data() {
return {
// make constant available
max_region_size: MAX_REGION_SIZE,
// Whether to show the "add a gwas" UI
show_modal: false,
num_studies_added: 0,
Expand All @@ -32,6 +45,10 @@ export default {
has_catalog: false,
has_credible_sets: false,
build: 'GRCh37',
// Controls for "batch view" mode
batch_mode_active: false,
batch_mode_regions: [],
};
},
computed: {
Expand All @@ -44,6 +61,8 @@ export default {
// Reset state in the component
this.file_reader = null;
this.display_name = null;
this.batch_mode_active = false;
this.batch_mode_regions = [];
this.showMessage('', '');
},
showMessage(message, style = 'text-danger') {
Expand All @@ -59,6 +78,10 @@ export default {
this.display_name = name;
this.show_modal = true;
},
activateBatchMode(regions) {
this.batch_mode_active = true;
this.batch_mode_regions = regions;
},
sendConfig(parser_options, state) {
// This particular app captures reader options for display, then relays them to the plot
this.num_studies_added += 1;
Expand Down Expand Up @@ -89,6 +112,8 @@ export default {
components: {
BDropdown,
AdderWizard,
BatchScroller,
BatchSpec,
RegionPicker,
TabixFile,
TabixUrl,
Expand All @@ -99,31 +124,35 @@ export default {
<template>
<div>
<div class="row">
<div class="col-sm-6">
<div v-if="!batch_mode_active" class="col-sm-6">
<div v-if="study_count < max_studies">
<tabix-file class="mr-1"
@ready="connectReader" @fail="showMessage" />
@ready="connectReader" @fail="showMessage"/>
<b-dropdown text="Add from URL" variant="success">
<div class="px-3">
<tabix-url @ready="connectReader" @fail="showMessage" />
<tabix-url @ready="connectReader" @fail="showMessage"/>
</div>
</b-dropdown>
<adder-wizard v-if="show_modal"
:file_reader="file_reader"
:file_name.sync="display_name"
@ready="sendConfig"
@close="show_modal = false" />
@close="show_modal = false"/>
</div>
</div>
<div class="col-sm-6">
<region-picker v-if="study_count"
@ready="selectRange"
@fail="showMessage" class="float-right"
:build="build"
:max_range="1000000"
search_url="https://portaldev.sph.umich.edu/api/v1/annotation/omnisearch/" />
<div v-if="!batch_mode_active" class="col-sm-6">
<div v-if="study_count" class="d-flex justify-content-end">
<region-picker @ready="selectRange"
@fail="showMessage"
:build="build"
:max_range="max_region_size"
search_url="https://portaldev.sph.umich.edu/api/v1/annotation/omnisearch/"/>
<batch-spec class="ml-1"
:max_range="max_region_size"
@ready="activateBatchMode"/>
</div>
<b-dropdown v-else text="Plot options" variant="info"
class="float-right">
class="float-right">
<div class="px-3">
<strong>Annotations</strong><br>
<div class="form-check form-check-inline">
Expand All @@ -150,9 +179,15 @@ export default {
</div>
</b-dropdown>
</div>
</div>
<div class="row" v-if="message">
<div class="col-sm-12"><span :class="[message_class]">{{message}}</span></div>

<div v-if="batch_mode_active" class="col-md-12">
<batch-scroller :regions="batch_mode_regions"
@navigate="selectRange"
@cancel="batch_mode_active = false"/>
</div>
<div class="row" v-if="message">
<div class="col-sm-12"><span :class="[message_class]">{{message}}</span></div>
</div>
</div>
</div>
</template>
Expand Down
34 changes: 11 additions & 23 deletions src/components/RegionPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
import { debounce } from 'lodash';
import VueBootstrapTypeahead from 'vue-bootstrap-typeahead/src/components/VueBootstrapTypeahead.vue';
import { REGEX_REGION } from '../util/constants';
import { REGEX_MARKER } from '../gwas/parser_utils';
import { parseRegion, positionToRange } from '../util/entity-helpers';
export default {
name: 'region-picker',
Expand All @@ -41,17 +40,12 @@ export default {
};
},
methods: {
positionToRange(pos) {
const bounds = Math.floor(this.max_range / 2);
return [pos - bounds, pos + bounds];
},
selectRegion() {
const { max_range } = this;
if (!this.region) {
this.$emit('fail', 'Please specify a region');
return;
}
const range_match = this.region.match(REGEX_REGION);
const single_match = this.region.match(REGEX_MARKER);
let chr;
let start;
Expand All @@ -61,24 +55,18 @@ export default {
// For genes and rsids, the suggested range is often too narrow.
// Pick a region centered on the midpoint of the range.
({ chrom: chr, start, end } = search_result);
[start, end] = this.positionToRange((start + end) / 2);
} else if (range_match) {
[chr, start, end] = range_match.slice(1);
// Ensure that returned values are numeric
start = +start;
end = +end;
[start, end] = positionToRange((start + end) / 2, { region_size: max_range });
} else {
try {
({ chr, start, end } = parseRegion(this.region, { region_size: max_range }));
} catch (e) {
this.$emit('fail', 'Could not parse specified range');
return;
}
if ((end - start) > this.max_range) {
this.$emit('fail', `Maximum allowable range is ${this.max_range.toLocaleString()}`);
this.$emit('fail', `Maximum allowable range is ${max_range.toLocaleString()}`);
return;
}
} else if (single_match) {
let pos;
[chr, pos] = single_match.slice(1);
pos = +pos;
[start, end] = this.positionToRange(pos);
} else {
this.$emit('fail', 'Could not parse specified range');
return;
}
this.$emit('ready', { chr, start, end });
},
Expand Down
8 changes: 4 additions & 4 deletions src/util/constants.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const PORTAL_API_BASE_URL = 'https://portaldev.sph.umich.edu/api/v1/';
const PORTAL_DEV_API_BASE_URL = 'https://portaldev.sph.umich.edu/api_internal_dev/v1/';
const LD_SERVER_BASE_URL = 'https://portaldev.sph.umich.edu/ld/';

const REGEX_REGION = /(?:chr)?(.+):(\d+)-(\d+)/;
const REGEX_POSITION = /(?:chr)?(\w+)\s*:\s*(\d+)/;
const REGEX_REGION = /(?:chr)?(\w+)\s*:\s*(\d+)-(\d+)/;


export {
REGEX_REGION,
PORTAL_API_BASE_URL, PORTAL_DEV_API_BASE_URL, LD_SERVER_BASE_URL,
REGEX_REGION, REGEX_POSITION,
PORTAL_API_BASE_URL, LD_SERVER_BASE_URL,
};
Loading

0 comments on commit b0b53bf

Please sign in to comment.