Skip to content

Commit

Permalink
Split view via unified coordinates and search control (#706) - GUI, B…
Browse files Browse the repository at this point in the history
…rowser tab: coordinates input enhancements
  • Loading branch information
rodichenko committed Dec 10, 2021
1 parent 51e3aa1 commit 5610619
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import baseController from '../../../../shared/baseController';
import {stringParseInt} from '../../../utils/Int';
import parseCoordinates, {getCoordinatesText} from './parse-coordinates';
import $ from 'jquery';
import angular from 'angular';
import SearchResults from './ngbCoordinates.search.results';
import angular from 'angular';
import baseController from '../../../../shared/baseController';

export default class ngbCoordinatesController extends baseController {

Expand Down Expand Up @@ -258,14 +258,36 @@ export default class ngbCoordinatesController extends baseController {
}
}

chromosomeKeyUp(event) {
if (!event) {
return;
}
if (/^enter$/i.test(event.key)) {
this.didTypeChromosome();
} else if (/^(esc|escape)$/i.test(event.key)) {
this.discardEdit();
}
}

coordinatesKeyUp(event) {
if (!event) {
return;
}
if (/^enter$/i.test(event.key)) {
this.didTypeCoordinates();
} else if (/^(esc|escape)$/i.test(event.key)) {
this.discardEdit();
}
}

didTypeCoordinates() {
(async()=> {
const parseResult = this._parseCoordinates();
if (!parseResult) {
const parseResult = await this._parseCoordinates();
if (parseResult) {
this._createTitle();
this.isChromosomeEditingMode = false;
this.isCoordinatesEditingMode = false;
}
this.isChromosomeEditingMode = false;
this.isCoordinatesEditingMode = false;
})();
}

Expand Down Expand Up @@ -360,84 +382,41 @@ export default class ngbCoordinatesController extends baseController {
this._chr = obj;
}

_parseCoordinates() {
let chrName = null;
let start = null;
let end = null;

async _parseCoordinates() {
//1. '' - should open specified chromosome and range
if (!this.coordinatesText) {
if (this.chromosome) {
[start, end] = [1, this.chromosome.size];
const [start, end] = [1, this.chromosome.size];
this.projectContext.changeState({viewport: {end, start}});
return true;
}
return false;
}

// 'x : start-stop' - default
//'5 : 217 - 7726'.match(/(([0-9a-zA-Z]*)\D*\:)\D*(\d*)\D*\-\D*(\d*)/)
//['5 : 217 - 7726', '5 :', '5', '217', '7726']
const regexp_1 = /(([0-9a-zA-Z\D]*)\D*\:)\D*([0-9,. ]*)\D*\-\D*([0-9,. ]*)/;

//2. 'start-stop' - should open specified range on current chromosome
const regexp_2 = /^([0-9,. ]*)\D*\-\D*([0-9,. ]*)$/;

//3. 'chr:start' - should open specified chromosome and range from start-50bp to start+50bp
const regexp_3 = /^([0-9a-zA-Z\D]*)\D*\:\D*([0-9,. ]*)$/;

//4. 'start' - should open range from start-50bp to start+50bp on current chromosome
const regexp_4 = /^[0-9,. ]+$/;

//5. 'chr:' - should open specified chromosome and range from 1 to end of chromosome
const regexp_5 = /^([0-9a-zA-Z\D]*)\D*:$/;

if (regexp_1.test(this.coordinatesText)) {
[, , chrName, start, end] = this.coordinatesText.match(regexp_1);
} else if (regexp_2.test(this.coordinatesText)) {
[, start, end] = this.coordinatesText.match(regexp_2);
} else if (regexp_5.test(this.coordinatesText)) {
[, chrName] = this.coordinatesText.match(regexp_5);
} else if (regexp_3.test(this.coordinatesText)) {
[, chrName, start] = this.coordinatesText.match(regexp_3);
} else if (regexp_4.test(this.coordinatesText)) {
[start] = this.coordinatesText.match(regexp_4);
}

if (chrName) {
chrName = chrName.trim();
}

const chr = (!chrName || (this.chromosome && this.chromosome.name === chrName))
? this.chromosome
: this.projectContext.getChromosome({name: chrName});

if (!chr) {
return false;
}

if (start) {
start = stringParseInt(start);
if (start < 1 || (end !== null && start > end) || start > chr.size) {
start = 1;
}

if (end) {
end = stringParseInt(end);
if (end > chr.size) {
end = chr.size;
const [
currentBrowser,
splitViewBrowser
] = await parseCoordinates(
this.coordinatesText,
this.projectContext,
this.chromosome ? this.chromosome.name : undefined,
this.contextMenuItems,
this.projectDataService,
this.projectContext.reference ? this.projectContext.reference.id : undefined
) || [];
if (currentBrowser) {
this.coordinatesText = getCoordinatesText(currentBrowser);
this.projectContext.changeState(
currentBrowser,
false,
() => {
if (splitViewBrowser && splitViewBrowser.chromosome && this.dispatcher) {
this.dispatcher.emitSimpleEvent('browser:open:split:view', splitViewBrowser);
}
}
}
} else {
start = 1;
end = chr.size;
);
return true;
}

const viewport = (start && end) ? {end, start} : null;
const position = (start && !end) ? start : null;

this.projectContext.changeState({chromosome: {name: chr.name}, position, viewport});
return true;
return false;
}
}

Expand All @@ -446,4 +425,4 @@ function createFilterFor(query) {
return function filterFn(chromosome) {
return (angular.lowercase(chromosome.name).indexOf(lowercaseQuery) === 0);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<input type="text"
aria-label="chromosome input"
ng-model="$ctrl.chromosomeText"
ng-key-bind="{ enter: '$ctrl.didTypeChromosome($event)', esc: '$ctrl.discardEdit()' }"
ng-keyup="$ctrl.chromosomeKeyUp($event)"
ng-mousedown="$event.stopImmediatePropagation();"
ng-click="$event.stopImmediatePropagation()"
ng-blur="$ctrl.discardEdit();"
Expand All @@ -27,7 +27,7 @@
<input type="text"
aria-label="coordinates input"
ng-model="$ctrl.coordinatesText"
ng-key-bind="{ enter: '$ctrl.didTypeCoordinates($event)', esc: '$ctrl.discardEdit()' }"
ng-keyup="$ctrl.coordinatesKeyUp($event)"
ng-mousedown="$event.stopImmediatePropagation();"
ng-click="$event.stopImmediatePropagation()"
ng-blur="$ctrl.discardEdit();"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// "chr x: start - end"
const CHR_START_END_regExp = /^\s*([^:]+):\s*(\d+)\s*-\s*(\d+)\s*/;
// "chr: position"
const CHR_POSITION_regExp = /^\s*([^:]+):\s*(\d+)\s*/;
// "chr:"
const CHR_regExp = /^\s*([^:]+):\s*/;
// "start - end"
const START_END_regExp = /^\s*(\d+)\s*-\s*(\d+)\s*/;
// "position"
const POSITION_regExp = /^\s*(\d+)\s*/;
// "feature"
const FEATURE_regExp = /^\s*([^\s]+)\s*/;

function getChromosome(name, projectContext) {
if (!projectContext) {
return undefined;
}
return projectContext.getChromosome({name});
}

function correctPosition (chromosome, position) {
if (!chromosome || Number.isNaN(Number(position))) {
return undefined;
}
return Math.max(
1,
Math.min(
chromosome.size,
Number(position)
)
);
}

export function getCoordinatesText(navigation) {
if (
!navigation ||
!navigation.chromosome ||
!navigation.chromosome.name
) {
return undefined;
}
const {
chromosome,
position,
viewport = {}
} = navigation;
if (position) {
return `${chromosome.name}: ${position}`;
}
if (viewport && viewport.start && viewport.end) {
return `${chromosome.name}: ${viewport.start} - ${viewport.end}`;
}
return undefined;
}

function findBestFeatureFn (featureName) {
return function filter (o) {
return (o.featureId || '').toLowerCase() === featureName.toLowerCase() ||
(o.name || '').toLowerCase() === featureName.toLowerCase() ||
(o.featureName || '').toLowerCase() === featureName.toLowerCase();
};
}

async function findFeature(featureName, cache = [], projectDataService, referenceId) {
if (!featureName) {
return undefined;
}
const find = findBestFeatureFn(featureName);
const [o] = cache.filter(cacheItem => cacheItem.tag && find(cacheItem));
let feature = o && o.tag ? o.tag : undefined;
if (!feature && projectDataService && referenceId) {
const result = await projectDataService.searchGenes(referenceId, featureName);
if (result.entries && result.entries.length > 0) {
const [best] = result.entries.filter(find);
feature = best;
}
}
if (
feature &&
feature.chromosome &&
(feature.chromosome.name || feature.chromosome.id) &&
feature.startIndex &&
feature.endIndex
) {
const {
chromosome,
startIndex,
endIndex
} = feature;
return {
chromosome,
viewport: {
start: startIndex,
end: endIndex
}
};
}
return undefined;
}

export default async function parseCoordinates (
string,
projectContext,
currentChromosomeName,
searchResultsCache = [],
projectDataService,
referenceId
) {
if (!string) {
return [];
}
let rest = string.slice();
const coordinates = [];
let currentChromosome = getChromosome(currentChromosomeName, projectContext);
do {
const chrStartEnd = CHR_START_END_regExp.exec(rest);
const chrPosition = CHR_POSITION_regExp.exec(rest);
const chr = CHR_regExp.exec(rest);
const startEnd = START_END_regExp.exec(rest);
const position = POSITION_regExp.exec(rest);
const feature = FEATURE_regExp.exec(rest);
const matchedRegExp = chrStartEnd ||
chrPosition ||
chr ||
startEnd ||
position ||
feature;
if (chrStartEnd) {
const [, chrName, start, end] = chrStartEnd;
const chromosome = getChromosome(chrName, projectContext);
const viewport = {
start: correctPosition(chromosome, start),
end: correctPosition(chromosome, end)
};
if (chromosome && viewport.start && viewport.end) {
coordinates.push({chromosome, viewport});
currentChromosome = chromosome;
}
} else if (chrPosition) {
const [, chrName, _position] = chrPosition;
const chromosome = getChromosome(chrName, projectContext);
const positionPayload = correctPosition(chromosome, _position);
if (chromosome && positionPayload) {
coordinates.push({chromosome, position: positionPayload});
currentChromosome = chromosome;
}
} else if (chr) {
const [, chrName] = chr;
const chromosome = getChromosome(chrName, projectContext);
if (chromosome) {
const viewport = {
start: 1,
end: currentChromosome.size
};
coordinates.push({chromosome, viewport});
currentChromosome = chromosome;
}
} else if (startEnd && currentChromosome) {
const [, start, end] = startEnd;
const viewport = {
start: correctPosition(currentChromosome, start),
end: correctPosition(currentChromosome, end)
};
if (viewport.start && viewport.end) {
coordinates.push({chromosome: currentChromosome, viewport});
}
} else if (position && currentChromosome) {
const [, _position] = position;
const positionPayload = correctPosition(currentChromosome, _position);
if (positionPayload) {
coordinates.push({chromosome: currentChromosome, position: positionPayload});
}
} else if (feature) {
const featureNavigation = await findFeature(
feature[1],
searchResultsCache,
projectDataService,
referenceId
);
if (featureNavigation) {
coordinates.push(featureNavigation);
}
}
if (matchedRegExp) {
rest = rest.slice(matchedRegExp.index + matchedRegExp[0].length);
} else {
rest = undefined;
}
} while (rest);
return coordinates;
}
Loading

0 comments on commit 5610619

Please sign in to comment.