Commit 03821800 authored by Marek Ostaszewski's avatar Marek Ostaszewski
Browse files

Merge branch 'init_and_select' into 'master'

Init and select

See merge request starter-kit!1
parents 916d89e5 286e11bc
......@@ -24,6 +24,6 @@ deploy:
- npm install
- npm run build
- echo "mkdir plugins-www/starter-kit"|sftp -P 8022 gitlab-ci@10.240.6.160
- echo "put dist/plugin.js plugins-www/starter-kit/"|sftp -P 8022 gitlab-ci@10.240.6.160
- echo "mkdir plugins-www/gsea"|sftp -P 8022 gitlab-ci@10.240.6.160
- echo "put dist/plugin.js plugins-www/gsea/"|sftp -P 8022 gitlab-ci@10.240.6.160
# Minerva plugin creation with starter-kit
# Minerva GSEA plugin
Minerva plugin is a javascript file which manages its dedicated space in the Minerva page (right hand side panel). Since the plugin is a single Javascript file, all its content needs to be added/updated with JavaScript (no HTML pages). The same holds for CSS which either needs to be added via JavaSscript, or bundled using NPM (that is what starter-kit is doing), Gulp, Webpack or other similar technologies.
A plugin allowing to calculate the gene set enrichment based on the data overlays highlighted by the user.
## General comments
......
{
"name": "minerva-plugins-starter-kit",
"version": "1.0.0",
"name": "minerva-plugins-gsea",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
......@@ -10,7 +10,7 @@
"watch-js": "mkdirp dist && npm run build-css && watchify -v -t [ babelify --presets es2015 ] -t browserify-css src/js/index.js -o dist/plugin.js",
"clean": "rimraf dist"
},
"author": "David Hoksza",
"author": "David Hoksza and Marek Ostaszewski",
"license": "MIT",
"devDependencies": {
"babel-core": "^6.26.0",
......@@ -23,5 +23,8 @@
"rimraf": "latest",
"uglify-js": "^3.1.9",
"watchify": "^3.9.0"
},
"dependencies": {
"npm": "^6.4.1"
}
}
require('../css/styles.css');
const pluginName = 'starter-kit';
const pluginVersion = '1.0.1';
const pluginName = 'GSEA';
const pluginVersion = '0.0.1';
const minervaProxyServer = 'https://minerva-dev.lcsb.uni.lu/minerva-proxy/';
const globals = {
selected: [],
allBioEntities: [],
pickedRandomly: undefined
pathways: [],
groupedPathways: undefined,
quickAliases: []
};
// ******************************************************************************
......@@ -19,7 +21,7 @@ let minervaProxy;
let pluginContainer;
let pluginContainerId;
const register = function(_minerva) {
const register = function (_minerva) {
console.log('registering ' + pluginName + ' plugin');
......@@ -38,7 +40,12 @@ const register = function(_minerva) {
console.log('project id: ', minervaProxy.project.data.getProjectId());
console.log('model id: ', minervaProxy.project.data.getModels()[0].modelId);
globals.modelIds = minervaProxy.project.data.getModels();
initPlugin();
calculatePathways();
};
const unregister = function () {
......@@ -48,18 +55,18 @@ const unregister = function () {
return deHighlightAll();
};
const getName = function() {
const getName = function () {
return pluginName;
};
const getVersion = function() {
const getVersion = function () {
return pluginVersion;
};
/**
* Function provided by Minerva to register the plugin
*/
minervaDefine(function (){
minervaDefine(function () {
return {
register: register,
unregister: unregister,
......@@ -69,19 +76,10 @@ minervaDefine(function (){
}
});
function initPlugin () {
registerListeners();
function initPlugin() {
initMainPageStructure();
}
function registerListeners(){
minervaProxy.project.map.addListener({
dbOverlayName: "search",
type: "onSearch",
callback: searchListener
});
}
function unregisterListeners() {
minervaProxy.project.map.removeAllListeners();
}
......@@ -91,213 +89,202 @@ function unregisterListeners() {
// ****************************************************************************
function deHighlightAll(){
return minervaProxy.project.map.getHighlightedBioEntities().then( highlighted => minervaProxy.project.map.hideBioEntity(highlighted) );
function deHighlightAll() {
pluginContainer.find('.panel-events .panel-body').html('');
return minervaProxy.project.map.getHighlightedBioEntities().then(highlighted => minervaProxy.project.map.hideBioEntity(highlighted));
}
// ****************************************************************************
// ********************* PLUGIN STRUCTURE AND INTERACTION*********************
// ****************************************************************************
function initMainPageStructure(){
function initMainPageStructure() {
const container = $('<div class="' + pluginName + '-container"></div>').appendTo(pluginContainer);
container.append(`
<div class="panel panel-default panel-events">
<div class="panel-heading">Events (Select an element in the map)</div>
<div class="panel-heading">Gene Set Enrichment Analysis</div>
<div class="panel-body">
</div>
</div>
`);
container.append('<button type="button" class="btn-focus btn btn-primary btn-default btn-block">Focus</button>');
container.append('<button type="button" class="btn-highlight btn btn-primary btn-default btn-block">Highlight</button>');
container.append('<button type="button" class="btn-calc btn btn-primary btn-default btn-block">Show enriched pathways</button>');
container.append('<hr>');
container.append('<button type="button" class="btn-pick-random btn btn-primary btn-default btn-block">Retrieve random object from map</button>');
container.append(`
<div class="panel panel-default panel-randomly-picked">
<div class="panel-heading">Randomly picked object</div>
<div class="panel-body">
</div>
</div>
`);
container.append('<button type="button" class="btn-focus-random btn btn-primary btn-default btn-block">Focus</button>');
container.append('<button type="button" class="btn-highlight-random btn btn-primary btn-default btn-block">Highlight</button>');
container.append('<button type="button" class="btn-reset btn btn-primary btn-default btn-block">Reset</button>');
container.append(`<div>
<br><b>Plugin help:</b><br>
This is a basic Gene Set Enrichment plugin. It calculates enrichment of elements from selected overlay(s) in the pathways of the map (grey areas).<br><br>
<u>Show enriched pathways</u> button calculates the enrichment scores (Bonferroni-adjusted p-value), lists the significantly enriched pathways in the window "Gene Set Enrichment Analysis" window, and highlights them.<br><br>
<u>Reset</u> button removes the results and the enrichment.<br><br>
If multiple overlays are selected, the enrichment will be calculated for the combined gene sets.
</div>`);
container.find('.btn-reset').on('click', () => deHighlightAll());
}
container.append('<hr>');
container.append('<h4>Query UniProt API</h4>');
container.append('<button type="button" class="btn-uniprot btn btn-primary btn-default btn-block" ' +
'title="Queries UniProt using the element selected from the map">Retrieve from UniProt</button>');
container.append(`
<div class="panel panel-default panel-uniprot">
<div class="panel-heading">Uniprot records for the selected element</div>
<div class="panel-body">
<code></code>
</div>
</div>
`);
function calculatePathways() {
function populatePathways() {
const pathwayObjs = [];
const pgrObjects = [];
// For the time being, handle only the main map
// minervaProxy.project.data.getModels() registered earlier
globals.allBioEntities.forEach(e => {
var this_type = e.getType();
if (this_type == "Compartment" && e.getElementId().startsWith("art")) {
pathwayObjs.push(e)
}
else if (this_type == "Protein" || this_type == "RNA" || this_type == "Gene") {
pgrObjects.push(e)
}
});
globals.quickAliases = pgrObjects;
container.append('<hr>');
container.append('<h4>Query Minerva API</h4>');
container.append(`
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">Address</label>
<div class="col-sm-10">
<input class="input-minerva-address form-control" value="https://minerva-dev.lcsb.uni.lu/minerva">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Project ID</label>
<div class="col-sm-10">
<input class="input-minerva-projectid form-control" value="sample2">
</div>
</div>
</form>
<button type="button" class="btn-minerva btn btn-primary btn-default btn-block">Retrieve from Minerva</button>
<div class="panel panel-default panel-minerva">
<div class="panel-heading">Names of elements</div>
<div class="panel-body">
</div>
</div>
`);
globals.groupedPathways = [];
container.find('.btn-highlight').on('click', () => highlightSelected() );
container.find('.btn-focus').on('click', () => focusOnSelected() );
container.find('.btn-pick-random').on('click', () => pickRandom() );
container.find('.btn-highlight-random').on('click', () => highlightSelected(true) );
container.find('.btn-focus-random').on('click', () => focusOnSelected(true) );
container.find('.btn-uniprot').on('click', () => retrieveUniprot() );
container.find('.btn-minerva').on('click', () => retrieveMinerva() );
}
// Helper function for point in area (encoded as x,x+with,y,y+height)
function pointInRectangle(x, y, rectangle) {
return x >= rectangle[0] && x <= rectangle[1] &&
y >= rectangle[2] && y <= rectangle[3];
}
function searchListener(entites) {
globals.selected = entites[0];
pathwayObjs.forEach(function (e, i) {
const these_dims = new Array(e.getX(), e.getX() + e.getWidth(), e.getY(), e.getY() + e.getHeight());
const these_hits = [];
pgrObjects.forEach(g => {
// Comparison of all four corners of a species vs pathway area
if (pointInRectangle(g.getX(), g.getY(), these_dims) ||
pointInRectangle(g.getX() + g.getWidth(), g.getY(), these_dims) ||
pointInRectangle(g.getX(), g.getY() + g.getHeight(), these_dims) ||
pointInRectangle(g.getX() + g.getWidth(), g.getY() + g.getHeight(), these_dims)) {
these_hits.push(g);
}
});
e.pathwayAliases = these_hits;
let str = '';
if (globals.selected.length > 0) {
globals.selected.forEach(e => {if (e.constructor.name === 'Alias') str += `<div>${e.getName()} - ${e.getElementId()}</div>`});
}
pluginContainer.find('.panel-events .panel-body').html(str);
}
var hit = globals.groupedPathways.findIndex(fi => fi.name == e.getName());
function pickRandom() {
if (hit == -1) {
hit = globals.groupedPathways.length;
globals.groupedPathways.push({ name: e.getName(), aliases: [], elementAliases: new Set() });
}
globals.groupedPathways[hit].aliases.push(e);
globals.groupedPathways[hit].elementAliases = new Set([...globals.groupedPathways[hit].elementAliases, ...these_hits]);
});
function pick(){
globals.pickedRandomly = globals.allBioEntities[Math.floor(Math.random() * globals.allBioEntities.length)];
// Adding submaps
globals.modelIds.slice(1).forEach(m => {
var hit = globals.groupedPathways.length;
globals.groupedPathways.push({
name: "Submap: " + m.name, aliases: m,
elementAliases: new Set(pgrObjects.filter(o => o.getModelId() === m.modelId))
});
});
console.log(globals.groupedPathways);
let html = `${globals.pickedRandomly.constructor.name} - `;
if (globals.pickedRandomly.constructor.name === 'Alias') html += `${globals.pickedRandomly.getElementId()} - ${globals.pickedRandomly.getName()}`;
else html += `${globals.pickedRandomly.getReactionId()}`;
pluginContainer.find('.panel-randomly-picked .panel-body').html(html);
globals.pathways = pathwayObjs;
}
if (globals.allBioEntities.length > 0) {
pick();
populatePathways();
}
else {
minervaProxy.project.data.getAllBioEntities().then(function(bioEntities) {
minervaProxy.project.data.getAllBioEntities().then(function (bioEntities) {
globals.allBioEntities = bioEntities;
pick();
populatePathways();
});
}
// Only allow to press the button when the pathways have been loaded
pluginContainer.find('.btn-calc').on('click', () => calculateGSEA());
}
function highlightSelected(pickedRandomly = false) {
const highlightDefs = [];
if (pickedRandomly) {
if (globals.pickedRandomly) {
highlightDefs.push({
element: {
id: globals.pickedRandomly.id,
modelId: globals.pickedRandomly.getModelId(),
type: globals.pickedRandomly.constructor.name.toUpperCase()
},
type: "SURFACE",
options: {
color: '#00FF00',
opacity: 0.5
}
});
}
} else {
globals.selected.forEach(e => {
highlightDefs.push({
element: {
id: e.id,
modelId: e.getModelId(),
type: "ALIAS"
},
type: "ICON"
});
function calculateGSEA() {
//Names of all considered aliases
const allNames = new Set(globals.quickAliases.map(ref => ref.getReferences().filter(tref => (tref.getType() == "HGNC_SYMBOL")).map(ann => ann.getResource())));
deHighlightAll().then(function (){
return minervaProxy.project.map.getVisibleDataOverlays()
}).then(function (overlays) {
let gsea_html = '<table><tr><th style="width:80px;">p val (adj)</th><th>Enriched area</th></tr>';
var ovnames = new Set();
overlays.forEach(e => {
//const these_ovnames = new Set(e.getAliases().map(ref => ref.getReferences().filter(tref => (tref.getType() == "HGNC_SYMBOL")).map(ann => ann.getResource())));
// e is a LayoutAlias, needs to be mapped back to map aliases, to retrieve HGNC; defined as set, .has() function looks better in filtering
const these_ovaliases = new Set([...e.getAliases().map(g => g.getId())]);
// grab aliases matching the overlay -> get their references -> filter by HGNC_SYMBOL -> get value
const these_ovnames = new Set(globals.quickAliases.filter(h => these_ovaliases.has(h.getId())).flatMap(ref =>
ref.getReferences().filter(tref => (tref.getType() == "HGNC_SYMBOL")).map(ann => ann.getResource())));
ovnames = new Set([...ovnames, ...these_ovnames]);
});
}
minervaProxy.project.map.showBioEntity(highlightDefs);
}
function focusOnSelected(pickedRandomly = false) {
function focus(entity) {
if (entity.constructor.name === 'Alias') {
minervaProxy.project.map.fitBounds({
modelId: entity.getModelId(),
x1: entity.getX(),
y1: entity.getY(),
x2: entity.getX() + entity.getWidth(),
y2: entity.getY() + entity.getHeight()
});
} else {
minervaProxy.project.map.fitBounds({
modelId: entity.getModelId(),
x1: entity.getCenter().x,
y1: entity.getCenter().y,
x2: entity.getCenter().x,
y2: entity.getCenter().y
});
}
}
if (!pickedRandomly && globals.selected.length > 0) focus(globals.selected[0]);
if (pickedRandomly && globals.pickedRandomly) focus(globals.pickedRandomly);
}
const overlay_pvals = [];
globals.groupedPathways.forEach(g => {
// split into two steps for better clarity
// 1. get element aliases for a given key (pathway group), because it's a set, we use [...] notation to cast onto array
const these_pgaliases = [...g.elementAliases];
// 2. get element names for the alias array; define as set for unique values
const these_pgnames = new Set(these_pgaliases.flatMap(ref =>
ref.getReferences().filter(tref => (tref.getType() == "HGNC_SYMBOL")).map(ann => ann.getResource())));
//calculate intersect
const isect = [...ovnames].filter(value => these_pgnames.has(value));
var m = these_pgnames.size;
var k = isect.length;
var N = allNames.size;
var n = ovnames.size;
if (k > 0) {
//var px = (kcomb(m,k)*kcomb(N-m,n-k))/kcomb(N,n);
//Approximation using upper boud from Eq (2) in http://page.mi.fu-berlin.de/shagnik/notes/binomials.pdf
//Simplified, props to Ewa Smula (ewa.smula@uni.lu)
var this_pval = Math.pow(((m * (n - k)) / (k * (N - m))), k) * Math.pow(((n * (N - m)) / (N * (n - k))), n);
overlay_pvals.push({ pval: this_pval, pname: g.name })
};
});
function retrieveUniprot() {
let query = pluginContainer.find('.panel-events .panel-body').text();
query = query.substring(0,query.indexOf(' - '));
console.log(query);
$.ajax({
type: 'GET',
url: 'https://www.uniprot.org/uniprot/?query=' + query + '&sort=score&columns=id,entry%20name,reviewed,protein%20names,3d,genes,organism,length&format=tab&limit=10'
}).then(function(result){
pluginContainer.find('.panel-uniprot .panel-body code').text(result);
})
}
const highlightPathways = [];
overlay_pvals.sort(function (a, b) {
return a.pval - b.pval;
}).forEach(p => {
var adj_pval = Math.min(p.pval * globals.groupedPathways.length, 1).toFixed(4);
if (adj_pval < 0.1) {
if (adj_pval == 0) {
gsea_html += `<tr><td>< 0.0001</td><td>${p.pname}</td></tr>`;
} else {
gsea_html += `<tr><td style="padding-left: 10px;">${adj_pval}</td><td>${p.pname}</td></tr>`;
}
function retrieveMinerva() {
const address = pluginContainer.find('.input-minerva-address').val();
const projectId = pluginContainer.find('.input-minerva-projectid').val();
$.ajax({
type: 'GET',
url: `${minervaProxyServer}?url=${address}/api/projects/${projectId}/models/`,
dataType: 'json'
}).then((models) => {
console.log(`Retrived models from ${minervaProxyServer}`, models);
const firstModelId = models[0].idObject;
return $.ajax({
type: 'GET',
url: `${minervaProxyServer}?url=${address}/api/projects/${projectId}/models/${firstModelId}/bioEntities/elements/`,
dataType: 'json'
});
}).then((elements) => {
console.log(`Retrived elements from ${minervaProxyServer}`, elements);
let names = '';
elements.forEach(function (element) {
names += element.name + '<br/>';
var hit = globals.groupedPathways.findIndex(fi => fi.name == p.pname);
if (Array.isArray(globals.groupedPathways[hit].aliases)) { //check if this is not a submap
globals.groupedPathways[hit].aliases.forEach(al => {
highlightPathways.push({
element: {
id: al.getId(),
modelId: al.getModelId(),
type: al.constructor.name.toUpperCase()
},
type: "SURFACE",
options: {
color: '#00FF00',
opacity: 0.1
}
});
});
}
}
});
pluginContainer.find('.panel-minerva .panel-body').html(names);
minervaProxy.project.map.showBioEntity(highlightPathways);
gsea_html += '</table>'
pluginContainer.find('.panel-events .panel-body').html(gsea_html);
});
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment