Skip to content

Commit

Permalink
Merge branch 'feature/pathway_search' into feature/bar-chart-network
Browse files Browse the repository at this point in the history
  • Loading branch information
chrtannus committed Aug 24, 2023
2 parents 6dd2989 + 7bc3cc1 commit 7cdb763
Show file tree
Hide file tree
Showing 10 changed files with 672 additions and 472 deletions.
70 changes: 28 additions & 42 deletions src/client/components/network-editor/controller.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import EventEmitter from 'eventemitter3';
import Cytoscape from 'cytoscape'; // eslint-disable-line
import _ from 'lodash';
import MiniSearch from 'minisearch';

import { DEFAULT_PADDING } from '../defaults';
import { monkeyPatchMathRandom, restoreMathRandom } from '../../rng';
import { SearchController } from './search-contoller';

/**
* The network editor controller contains all high-level model operations that the network
Expand Down Expand Up @@ -34,12 +34,13 @@ export class NetworkEditorController {
/** @type {String} */
this.networkIDStr = cy.data('id');

this.searchController = new SearchController(cy, this.bus);

this.networkLoaded = false;
this.geneListIndexed = false;

this.bus.on('networkLoaded', () => {
this.networkLoaded = true;
this._indexGeneList();
this._fetchMinMaxRanks();
});
}

Expand All @@ -48,7 +49,19 @@ export class NetworkEditorController {
}

isGeneListIndexed() {
return this.geneListIndexed;
return this.searchController.isGeneListIndexed();
}

isPathwayListIndexed() {
return this.searchController.isPathwayListIndexed();
}

searchGenes(query) {
return this.searchController.searchGenes(query);
}

searchPathways(query) {
return this.searchController.searchPathways(query);
}

/**
Expand Down Expand Up @@ -107,45 +120,16 @@ export class NetworkEditorController {
this.bus.emit('deletedSelectedElements', deletedEls);
}

searchGenes(query) {
if (query && query.length > 0) {
return this.geneMiniSearch.search(query, { fields: ['gene'], prefix: true });
}

return [];
}

getGene(name) {
if (!this.isGeneListIndexed()) {
throw "The gene list hasn't been fecthed yet!";
}

const res = this.geneMiniSearch.search(name, { fields: ['gene'], prefix: true });

return res.length > 0 ? res[0] : {};
}

async _indexGeneList() {
const res = await this.fetchGeneList([]);
const genes = res ? res.genes : [];
this.minRank = res ? res.minRank : 0;
this.maxRank = res ? res.maxRank : 0;

if (genes && genes.length > 0) {
this.geneMiniSearch = new MiniSearch({
idField: 'gene',
fields: ['gene'], // fields to index for full-text search
storeFields: ['gene', 'rank'] // fields to return with search results
});
this.geneMiniSearch.addAll(genes);

this.lastGeneSet1 = res;
this.lastGeneSetNames1 = [];
this.geneListIndexed = true;
this.bus.emit('geneListIndexed');
}
async _fetchMinMaxRanks() {
const res = await fetch(`/api/${this.networkIDStr}/minmaxranks`);
const json = await res.json();
this.minRank = json ? json.minRank : 0;
this.maxRank = json ? json.maxRank : 0;
}

/**
* Still needed by the gene sidebar.
*/
async fetchGeneList(geneSetNames) {
geneSetNames = geneSetNames || [];
const nameSet = new Set(geneSetNames);
Expand All @@ -165,11 +149,12 @@ export class NetworkEditorController {
geneSets: geneSetNames
})
});

if (res.ok) {
const geneSet = await res.json();
const rankedGenes = geneSet.genes.filter(g => g.rank);
geneSet.genes = rankedGenes;

// We cache the last two queries because clicking on an
// edge queries for both source/target nodes.
this.lastGeneSet2 = this.lastGeneSet1;
Expand All @@ -181,4 +166,5 @@ export class NetworkEditorController {
return geneSet;
}
}

}
3 changes: 2 additions & 1 deletion src/client/components/network-editor/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,13 @@ export function Header({ controller, classes, showControlPanel, isMobile, onShow
<MobileMenu />
<SearchDialog
open={dialogName === SEARCH_DIALOG_ID}
controller={controller}
onClose={handleDialogClose}
/>
<ShareMenu
visible={menuName === SHARE_MENU_ID}
target={anchorEl}
controller={controller}
controller={controller}
onClose={handleMenuClose}
setSnackBarState={setSnackBarState}
/>
Expand Down
3 changes: 3 additions & 0 deletions src/client/components/network-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ async function loadNetwork(cy, controller, id) {

console.log('Successful load from DB');
console.log('End of editor sync initial phase');

// make the controller accessible from the chrome console for debugging purposes
window.controller = controller;
}


Expand Down
101 changes: 101 additions & 0 deletions src/client/components/network-editor/search-contoller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import EventEmitter from 'eventemitter3';
import Cytoscape from 'cytoscape'; // eslint-disable-line
import _ from 'lodash';
import MiniSearch from 'minisearch';

export class SearchController {

constructor(cy, bus) {
this.networkIDStr = cy.data('id');
this.bus = bus || new EventEmitter();

this.genesReady = false;
this.pathwaysReady = false;

this.bus.on('networkLoaded', () => {
this.fetchAllGenesInNetwork();
this.fetchAllPathwaysInNetwork();
});
}

isGeneListIndexed() {
return this.genesReady;
}

isPathwayListIndexed() {
return this.pathwaysReady;
}

/**
* Fetches all the genes in the network.
*/
async fetchAllGenesInNetwork() {
const res = await fetch(`/api/${this.networkIDStr}/genesforsearch`);

if (res.ok) {
this.geneMiniSearch = new MiniSearch({
idField: 'gene',
fields: ['gene'],
storeFields: ['gene', 'rank', 'pathwayNames']
});

const documents = await res.json();
this.geneMiniSearch.addAll(documents);

this.genesReady = true;
this.bus.emit('geneListIndexed');
}
}

/**
* Fetches all the genes in the network.
*/
async fetchAllPathwaysInNetwork() {
const res = await fetch(`/api/${this.networkIDStr}/pathwaysforsearch`);

if (res.ok) {
// TODO add 'description' and 'leadingEdge'
this.pathwayMiniSearch = new MiniSearch({
idField: 'name',
fields: ['name'],
storeFields: ['name', 'padj', 'NES', 'pval', 'size', 'genes']
});

const documents = await res.json();

documents.map(doc => {
const i = doc.name.indexOf('%');
if(i > 0) {
doc.name = doc.name.substring(0, i);
}
doc.name = doc.name.toLowerCase();
return doc;
});

this.pathwayMiniSearch.addAll(documents);

this.pathwaysReady = true;
this.bus.emit('pathwayListIndexed');
}
}

searchGenes(query) {
if (!this.isGeneListIndexed()) {
throw "The gene list hasn't been fecthed yet!";
}
if (query && query.length > 0) {
return this.geneMiniSearch.search(query, { fields: ['gene'], prefix: true });
}
return [];
}

searchPathways(query) {
if (!this.isPathwayListIndexed()) {
throw "The pathway list hasn't been fecthed yet!";
}
if (query && query.length > 0) {
return this.pathwayMiniSearch.search(query, { fields: ['name'], prefix: true });
}
return [];
}
}
20 changes: 12 additions & 8 deletions src/client/components/network-editor/search-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useRef, useState } from 'react';
import PropTypes from 'prop-types';

import theme from '../../theme';
import { NetworkEditorController } from './controller';

import { makeStyles } from '@material-ui/core/styles';

Expand All @@ -28,14 +29,16 @@ const useStyles = makeStyles((theme) => ({
}));


export const SearchDialog = ({ open, onClose }) => {
export const SearchDialog = ({ open, controller, onClose }) => {
const [searchValue, setSearchValue] = useState('');
const [searchResult, setSearchResult] = useState(null);
const [genes, setGenes] = useState(null);

const searchValueRef = useRef(searchValue);
searchValueRef.current = searchValue;

const cy = controller.cy;

const classes = useStyles();

const cancelSearch = () => {
Expand All @@ -46,13 +49,13 @@ export const SearchDialog = ({ open, onClose }) => {
const query = val.trim();

if (val.length > 0) {
// // Unselect Cy elements first
// const selectedEles = cy.elements().filter(':selected');
// selectedEles.unselect();
// // Now execute the search
// const res = controller.searchGenes(query);
// setSearchValue(val);
// setSearchResult(res);
// Unselect Cy elements first
const selectedEles = cy.elements().filter(':selected');
selectedEles.unselect();
// Now execute the search
const res = controller.searchGenes(query);
setSearchValue(val);
setSearchResult(res);
} else {
cancelSearch();
}
Expand Down Expand Up @@ -82,6 +85,7 @@ export const SearchDialog = ({ open, onClose }) => {

SearchDialog.propTypes = {
open: PropTypes.bool,
controller: PropTypes.instanceOf(NetworkEditorController).isRequired,
onClose: PropTypes.func.isRequired,
};

Expand Down
Loading

0 comments on commit 7cdb763

Please sign in to comment.