grnsight 7.1.1 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/node.js.yml +35 -0
- package/README.md +1 -1
- package/database/network-database/schema.sql +2 -2
- package/database/protein-protein-database/schema.sql +4 -4
- package/package.json +3 -2
- package/server/controllers/demo-workbooks.js +2 -2
- package/server/controllers/importers/sif.js +9 -8
- package/server/controllers/sif-constants.js +7 -0
- package/server/dals/expression-dal.js +4 -4
- package/server/dals/grnsetting-dal.js +2 -2
- package/server/dals/network-dal.js +6 -8
- package/server/dals/protein-dal.js +2 -2
- package/test/additional-sheet-parser-tests.js +1 -1
- package/test/import-sif-tests.js +13 -0
- package/web-client/public/js/constants.js +1 -0
- package/web-client/public/js/graph.js +18 -25
- package/web-client/public/js/grnstate.js +1 -0
- package/web-client/public/js/setup-handlers.js +48 -29
- package/web-client/public/js/setup-load-and-import-handlers.js +1 -1
- package/web-client/public/js/update-app.js +41 -17
- package/web-client/public/js/upload.js +3 -3
- package/web-client/public/js/warnings.js +25 -0
- package/web-client/views/upload.pug +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
+
|
|
4
|
+
name: Node.js CI
|
|
5
|
+
|
|
6
|
+
on: [push]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: [18.x, 20.x, 22.x]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
# install system dependencies needed by the 'canvas' package
|
|
20
|
+
- name: Install dependencies for canvas
|
|
21
|
+
run: |
|
|
22
|
+
sudo apt-get update
|
|
23
|
+
sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg62 libgif-dev librsvg2-dev
|
|
24
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
25
|
+
uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: ${{ matrix.node-version }}
|
|
28
|
+
cache: 'npm'
|
|
29
|
+
|
|
30
|
+
- run: npm ci
|
|
31
|
+
- run: npm run lint
|
|
32
|
+
- run: npm run build --if-present
|
|
33
|
+
- run: npm test
|
|
34
|
+
|
|
35
|
+
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
GRNsight
|
|
2
2
|
========
|
|
3
3
|
[](https://zenodo.org/badge/latestdoi/16195791)
|
|
4
|
-
[](https://github.com/dondi/GRNsight/actions/workflows/node.js.yml)
|
|
5
5
|
[](https://coveralls.io/github/dondi/GRNsight?branch=master)
|
|
6
6
|
|
|
7
7
|
http://dondi.github.io/GRNsight/
|
|
@@ -20,6 +20,6 @@ CREATE TABLE gene_regulatory_network.network (
|
|
|
20
20
|
time_stamp TIMESTAMP WITH TIME ZONE,
|
|
21
21
|
source VARCHAR,
|
|
22
22
|
FOREIGN KEY (regulator_gene_id, taxon_id) REFERENCES gene_regulatory_network.gene(gene_id, taxon_id),
|
|
23
|
-
FOREIGN KEY (target_gene_id, taxon_id) REFERENCES
|
|
24
|
-
FOREIGN KEY (time_stamp, source) REFERENCES
|
|
23
|
+
FOREIGN KEY (target_gene_id, taxon_id) REFERENCES gene_regulatory_network.gene(gene_id, taxon_id),
|
|
24
|
+
FOREIGN KEY (time_stamp, source) REFERENCES gene_regulatory_network.source(time_stamp, source)
|
|
25
25
|
);
|
|
@@ -20,7 +20,7 @@ CREATE TABLE protein_protein_interactions.protein (
|
|
|
20
20
|
molecular_weight FLOAT,
|
|
21
21
|
PI FLOAT,
|
|
22
22
|
taxon_id VARCHAR,
|
|
23
|
-
FOREIGN KEY (gene_systematic_name, taxon_id) REFERENCES
|
|
23
|
+
FOREIGN KEY (gene_systematic_name, taxon_id) REFERENCES protein_protein_interactions.gene(gene_id, taxon_id)
|
|
24
24
|
);
|
|
25
25
|
|
|
26
26
|
CREATE TABLE protein_protein_interactions.physical_interactions (
|
|
@@ -30,8 +30,8 @@ CREATE TABLE protein_protein_interactions.physical_interactions (
|
|
|
30
30
|
experiment_name VARCHAR,
|
|
31
31
|
time_stamp TIMESTAMP WITH TIME ZONE,
|
|
32
32
|
source VARCHAR,
|
|
33
|
-
FOREIGN KEY (protein1) REFERENCES
|
|
34
|
-
FOREIGN KEY (protein2) REFERENCES
|
|
35
|
-
FOREIGN KEY (time_stamp, source) REFERENCES
|
|
33
|
+
FOREIGN KEY (protein1) REFERENCES protein_protein_interactions.protein(standard_name),
|
|
34
|
+
FOREIGN KEY (protein2) REFERENCES protein_protein_interactions.protein(standard_name),
|
|
35
|
+
FOREIGN KEY (time_stamp, source) REFERENCES protein_protein_interactions.source(time_stamp, source),
|
|
36
36
|
CONSTRAINT unique_physical_interaction UNIQUE (protein1, protein2, interaction_detection_methods_identifier, experiment_name, time_stamp, source)
|
|
37
37
|
);
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grnsight",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "Web app and service for visualizing models of gene regulatory networks",
|
|
5
5
|
"directories": {
|
|
6
6
|
"test": "test"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"body-parser": "1.18.2",
|
|
10
|
-
"
|
|
10
|
+
"canvas": "^2.11.2",
|
|
11
|
+
"canvg": "3.0.10",
|
|
11
12
|
"cors": "2.8.1",
|
|
12
13
|
"cytoscape": "2.7.14",
|
|
13
14
|
"d3-v4-grid": "2.0.1",
|
|
@@ -515,7 +515,7 @@ var demoWorkbook1 = function (path, res, app) {
|
|
|
515
515
|
},
|
|
516
516
|
meta: {
|
|
517
517
|
data: {
|
|
518
|
-
alpha: 0.
|
|
518
|
+
alpha: 0.02,
|
|
519
519
|
kk_max: 1,
|
|
520
520
|
MaxIter: 100000000,
|
|
521
521
|
TolFun: 0.000001,
|
|
@@ -2679,7 +2679,7 @@ var demoWorkbook2 = function (path, res, app) {
|
|
|
2679
2679
|
},
|
|
2680
2680
|
meta: {
|
|
2681
2681
|
data: {
|
|
2682
|
-
alpha: 0.
|
|
2682
|
+
alpha: 0.02,
|
|
2683
2683
|
kk_max: 1,
|
|
2684
2684
|
MaxIter: 100000000,
|
|
2685
2685
|
TolFun: 0.000001,
|
|
@@ -61,14 +61,15 @@ module.exports = function (sif) {
|
|
|
61
61
|
if (allNumbers) {
|
|
62
62
|
workbookType = constants.NETWORK_GRN_MODE;
|
|
63
63
|
} else {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
let stringRelationships = relationships.filter(function (relationship) {
|
|
65
|
+
return !isNumber(relationship);
|
|
66
|
+
});
|
|
67
|
+
if (stringRelationships.every(rel => rel === "pp")) {
|
|
68
|
+
workbookType = constants.NETWORK_PPI_MODE;
|
|
69
|
+
} else if (stringRelationships.every(rel => rel === "pd")) {
|
|
70
|
+
workbookType = constants.NETWORK_GRN_MODE;
|
|
71
|
+
} else if (stringRelationships.every(rel => rel === "pp" || rel === "pd")) {
|
|
72
|
+
errors.push(sifConstants.errors.SIF_MIXED_RELATIONSHIP_TYPE_ERROR);
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -32,5 +32,12 @@ module.exports = {
|
|
|
32
32
|
possibleCause: "GRNsight has detected stray data and/or extraneous blank rows in your SIF file. ",
|
|
33
33
|
suggestedFix: "Please review the data and delete extraneous data from the file."
|
|
34
34
|
},
|
|
35
|
+
|
|
36
|
+
SIF_MIXED_RELATIONSHIP_TYPE_ERROR: {
|
|
37
|
+
errorCode: "SIF_MIXED_RELATIONSHIP_TYPE_ERROR",
|
|
38
|
+
possibleCause: "The SIF importer detects a SIF file with mixed relationship types.",
|
|
39
|
+
suggestedFix: ["GRNsight only supports networks with a single relationship type.",
|
|
40
|
+
"Please review the relationships for consistency."].join(" ")
|
|
41
|
+
},
|
|
35
42
|
},
|
|
36
43
|
};
|
|
@@ -4,8 +4,8 @@ var env = process.env.NODE_ENV || "development";
|
|
|
4
4
|
var config = require("../config/config")[env];
|
|
5
5
|
var sequelize = new Sequelize(
|
|
6
6
|
config.databaseName,
|
|
7
|
-
process.env.
|
|
8
|
-
process.env.
|
|
7
|
+
process.env.DB_USERNAME,
|
|
8
|
+
process.env.DB_PASSWORD,
|
|
9
9
|
{
|
|
10
10
|
host: config.databaseHost,
|
|
11
11
|
dialect: config.databaseDialect,
|
|
@@ -20,7 +20,7 @@ var sequelize = new Sequelize(
|
|
|
20
20
|
const buildExpressionGenesQuery = function (geneString) {
|
|
21
21
|
let genes = "";
|
|
22
22
|
let geneList = geneString.split(",");
|
|
23
|
-
geneList.forEach(x => genes += ( `(gene_expression.gene.display_gene_id
|
|
23
|
+
geneList.forEach(x => genes += ( `(LOWER(gene_expression.gene.display_gene_id) = LOWER(\'${x}\')) OR `));
|
|
24
24
|
return genes.substring(0, genes.length - 4);
|
|
25
25
|
};
|
|
26
26
|
|
|
@@ -62,7 +62,7 @@ const buildExpressionQuery = function (query) {
|
|
|
62
62
|
const listExpressionGeneData = function (gene, totalOutput) {
|
|
63
63
|
let listOfData = [];
|
|
64
64
|
totalOutput.forEach(function (x) {
|
|
65
|
-
if (x.display_gene_id === gene) {
|
|
65
|
+
if (x.display_gene_id.toLowerCase() === gene.toLowerCase()) {
|
|
66
66
|
listOfData.push(Number(x.expression));
|
|
67
67
|
}
|
|
68
68
|
});
|
|
@@ -4,8 +4,8 @@ var env = process.env.NODE_ENV || "development";
|
|
|
4
4
|
var config = require("../config/config")[env];
|
|
5
5
|
var sequelize = new Sequelize(
|
|
6
6
|
config.databaseName,
|
|
7
|
-
process.env.
|
|
8
|
-
process.env.
|
|
7
|
+
process.env.DB_USERNAME,
|
|
8
|
+
process.env.DB_PASSWORD,
|
|
9
9
|
{
|
|
10
10
|
host: config.databaseHost,
|
|
11
11
|
dialect: config.databaseDialect,
|
|
@@ -4,8 +4,8 @@ var env = process.env.NODE_ENV || "development";
|
|
|
4
4
|
var config = require("../config/config")[env];
|
|
5
5
|
var sequelize = new Sequelize(
|
|
6
6
|
config.databaseName,
|
|
7
|
-
process.env.
|
|
8
|
-
process.env.
|
|
7
|
+
process.env.DB_USERNAME,
|
|
8
|
+
process.env.DB_PASSWORD,
|
|
9
9
|
{
|
|
10
10
|
host: config.databaseHost,
|
|
11
11
|
dialect: config.databaseDialect,
|
|
@@ -21,12 +21,10 @@ const buildNetworkSourceQuery = function () {
|
|
|
21
21
|
return "SELECT * FROM gene_regulatory_network.source ORDER BY time_stamp DESC;";
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
const buildNetworkGeneFromSourceQuery = function (gene
|
|
24
|
+
const buildNetworkGeneFromSourceQuery = function (gene) {
|
|
25
25
|
return `SELECT DISTINCT gene_id, display_gene_id FROM
|
|
26
|
-
gene_regulatory_network.
|
|
27
|
-
|
|
28
|
-
(gene.gene_id ='${gene}' OR gene.display_gene_id ='${gene}') AND
|
|
29
|
-
(gene.gene_id = network.regulator_gene_id OR gene.gene_id = network.target_gene_id);`;
|
|
26
|
+
gene_regulatory_network.gene WHERE (gene.gene_id ='${gene}'
|
|
27
|
+
OR gene.display_gene_id ='${gene}')`;
|
|
30
28
|
};
|
|
31
29
|
|
|
32
30
|
const buildNetworkGenesQuery = function (geneString) {
|
|
@@ -49,7 +47,7 @@ const buildGenerateNetworkQuery = function (genes, source, timestamp) {
|
|
|
49
47
|
const buildQueryByType = function (queryType, query) {
|
|
50
48
|
const networkQueries = {
|
|
51
49
|
"NetworkSource": () => buildNetworkSourceQuery(),
|
|
52
|
-
"NetworkGeneFromSource": () => buildNetworkGeneFromSourceQuery(query.gene
|
|
50
|
+
"NetworkGeneFromSource": () => buildNetworkGeneFromSourceQuery(query.gene),
|
|
53
51
|
"GenerateNetwork": () => buildGenerateNetworkQuery(query.genes, query.source, query.timestamp)
|
|
54
52
|
};
|
|
55
53
|
if (Object.keys(networkQueries).includes(query.type)) {
|
|
@@ -4,8 +4,8 @@ var env = process.env.NODE_ENV || "development";
|
|
|
4
4
|
var config = require("../config/config")[env];
|
|
5
5
|
var sequelize = new Sequelize(
|
|
6
6
|
config.databaseName,
|
|
7
|
-
process.env.
|
|
8
|
-
process.env.
|
|
7
|
+
process.env.DB_USERNAME,
|
|
8
|
+
process.env.DB_PASSWORD,
|
|
9
9
|
{
|
|
10
10
|
host: config.databaseHost,
|
|
11
11
|
dialect: config.databaseDialect,
|
package/test/import-sif-tests.js
CHANGED
|
@@ -316,6 +316,13 @@ var sifWithSemanticErrorOnly = [
|
|
|
316
316
|
"E"
|
|
317
317
|
].join("\r\n");
|
|
318
318
|
|
|
319
|
+
var sifWithMixedRelationshipTypes = [
|
|
320
|
+
[ "A", "pd", "A" ].join("\t"),
|
|
321
|
+
[ "B", "pd", "A", "C" ].join("\t"),
|
|
322
|
+
[ "C", "pd", "B" ].join("\t"),
|
|
323
|
+
[ "D", "pp", "D" ].join("\t"),
|
|
324
|
+
].join("\r\n");
|
|
325
|
+
|
|
319
326
|
describe("Import from SIF", function () {
|
|
320
327
|
it("should import unweighted workbooks from SIF correctly", function () {
|
|
321
328
|
expect(
|
|
@@ -514,4 +521,10 @@ describe("Import from SIF syntactic checker", function () {
|
|
|
514
521
|
).to.deep.equal(expectedUnweightedGRNWorkbook);
|
|
515
522
|
});
|
|
516
523
|
|
|
524
|
+
it("should throw an error for SIF files with mixed relationship types", function () {
|
|
525
|
+
expect(
|
|
526
|
+
importController.sifToGrnsight(sifWithMixedRelationshipTypes).errors[0].errorCode
|
|
527
|
+
).to.equal("SIF_MIXED_RELATIONSHIP_TYPE_ERROR");
|
|
528
|
+
});
|
|
529
|
+
|
|
517
530
|
});
|
|
@@ -55,6 +55,7 @@ export const MIN_EDGE_WEIGHT_NORMALIZATION = 0.0001;
|
|
|
55
55
|
export const MAX_EDGE_WEIGHT_NORMALIZATION = 1000;
|
|
56
56
|
|
|
57
57
|
export const DEFAULT_ZOOM_VALUE = 100;
|
|
58
|
+
export const BOUNDARY_MARGIN = 5;
|
|
58
59
|
|
|
59
60
|
export const GREY_EDGE_THRESHOLD_MENU = "#gray-edge-threshold-menu";
|
|
60
61
|
export const GREY_EDGE_THRESHOLD_SLIDER_SIDEBAR = "#grayThresholdInput";
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import Grid from "d3-v4-grid";
|
|
2
2
|
import { grnState } from "./grnstate";
|
|
3
|
-
import { modifyChargeParameter, modifyLinkDistanceParameter, valueValidator } from "./update-app";
|
|
4
3
|
import {
|
|
5
|
-
|
|
4
|
+
modifyChargeParameter,
|
|
5
|
+
modifyLinkDistanceParameter,
|
|
6
|
+
valueValidator,
|
|
7
|
+
adjustGeneNameForExpression,
|
|
8
|
+
hasExpressionData,
|
|
9
|
+
} from "./update-app";
|
|
10
|
+
import {
|
|
6
11
|
VIEWPORT_FIT,
|
|
7
12
|
ZOOM_INPUT,
|
|
8
13
|
ZOOM_PERCENT,
|
|
@@ -11,7 +16,8 @@ import {
|
|
|
11
16
|
ZOOM_DISPLAY_MAXIMUM_VALUE,
|
|
12
17
|
ZOOM_DISPLAY_MIDDLE,
|
|
13
18
|
ZOOM_ADAPTIVE_MAX_SCALE,
|
|
14
|
-
NETWORK_GRN_MODE
|
|
19
|
+
NETWORK_GRN_MODE,
|
|
20
|
+
BOUNDARY_MARGIN
|
|
15
21
|
} from "./constants";
|
|
16
22
|
|
|
17
23
|
/* globals d3 */
|
|
@@ -456,8 +462,6 @@ export var drawGraph = function (workbook) {
|
|
|
456
462
|
center();
|
|
457
463
|
}
|
|
458
464
|
updateAppBasedOnZoomValue(); // Update zoom value within bounds
|
|
459
|
-
// Refresh the graph so that nodes and paths are adjusted to fit in viewport
|
|
460
|
-
tick();
|
|
461
465
|
};
|
|
462
466
|
|
|
463
467
|
d3.select("#restrict-graph-to-viewport").on("click", function () {
|
|
@@ -1087,20 +1091,20 @@ export var drawGraph = function (workbook) {
|
|
|
1087
1091
|
.selectAll(".coloring")
|
|
1088
1092
|
.data(function () {
|
|
1089
1093
|
if (grnState.workbook.expression[dataset]) {
|
|
1094
|
+
const geneName = adjustGeneNameForExpression(p);
|
|
1090
1095
|
if (
|
|
1091
|
-
grnState.workbook.expression[dataset].data[
|
|
1096
|
+
grnState.workbook.expression[dataset].data[geneName]
|
|
1092
1097
|
) {
|
|
1093
1098
|
const result = getExpressionData(
|
|
1094
|
-
|
|
1099
|
+
geneName,
|
|
1095
1100
|
dataset,
|
|
1096
1101
|
average
|
|
1097
1102
|
);
|
|
1098
1103
|
timePoints = result.timePoints;
|
|
1099
|
-
return result.data;
|
|
1100
|
-
} else {
|
|
1101
|
-
return 0;
|
|
1104
|
+
return result.data || [];
|
|
1102
1105
|
}
|
|
1103
1106
|
}
|
|
1107
|
+
return [];
|
|
1104
1108
|
})
|
|
1105
1109
|
.attr("class", "coloring")
|
|
1106
1110
|
.enter().append("rect")
|
|
@@ -1118,6 +1122,9 @@ export var drawGraph = function (workbook) {
|
|
|
1118
1122
|
.attr("stroke-width", "0px")
|
|
1119
1123
|
.style("fill", function (d) {
|
|
1120
1124
|
d = d || 0; // missing values are changed to 0
|
|
1125
|
+
if (d === 0) {
|
|
1126
|
+
return "white";
|
|
1127
|
+
}
|
|
1121
1128
|
var scale = d3.scaleLinear()
|
|
1122
1129
|
.domain([-logFoldChangeMaxValue, logFoldChangeMaxValue])
|
|
1123
1130
|
.range([0, 1]);
|
|
@@ -1227,15 +1234,6 @@ export var drawGraph = function (workbook) {
|
|
|
1227
1234
|
}
|
|
1228
1235
|
};
|
|
1229
1236
|
|
|
1230
|
-
const hasExpressionData = sheets => {
|
|
1231
|
-
for (var property in sheets) {
|
|
1232
|
-
if (property.match(ENDS_IN_EXPRESSION_REGEXP)) {
|
|
1233
|
-
return true;
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
return false;
|
|
1237
|
-
};
|
|
1238
|
-
|
|
1239
1237
|
if (!$.isEmptyObject(workbook.expression) && hasExpressionData(workbook.expression) &&
|
|
1240
1238
|
grnState.nodeColoring.topDataset !== undefined) {
|
|
1241
1239
|
updaters.renderNodeColoring();
|
|
@@ -1383,8 +1381,6 @@ export var drawGraph = function (workbook) {
|
|
|
1383
1381
|
}
|
|
1384
1382
|
};
|
|
1385
1383
|
|
|
1386
|
-
const BOUNDARY_MARGIN = 5;
|
|
1387
|
-
|
|
1388
1384
|
function viewportBoundsMoveDrag (graphZoom, dx, dy) {
|
|
1389
1385
|
updateZoomContainerInfo();
|
|
1390
1386
|
flexibleContainer = calcFlexiBox();
|
|
@@ -1526,7 +1522,6 @@ export var drawGraph = function (workbook) {
|
|
|
1526
1522
|
try {
|
|
1527
1523
|
node.attr("x", function (d) {
|
|
1528
1524
|
var selfReferringEdge = getSelfReferringEdge(d);
|
|
1529
|
-
|
|
1530
1525
|
var selfReferringEdgeWidth = (selfReferringEdge ? getSelfReferringRadius(selfReferringEdge) +
|
|
1531
1526
|
selfReferringEdge.strokeWidth + 2 : 0);
|
|
1532
1527
|
var rightBoundary = width - (d.textWidth + OFFSET_VALUE) - BOUNDARY_MARGIN - selfReferringEdgeWidth;
|
|
@@ -1541,9 +1536,7 @@ export var drawGraph = function (workbook) {
|
|
|
1541
1536
|
}
|
|
1542
1537
|
// currentXPos bounds the graph when toggle to !adaptive and moves each of the nodes to be in bounds
|
|
1543
1538
|
var currentXPos = Math.max(getLeftXBoundaryMargin(), Math.min(rightBoundary, d.x));
|
|
1544
|
-
if (
|
|
1545
|
-
adaptive &&
|
|
1546
|
-
width < MAX_WIDTH &&
|
|
1539
|
+
if (adaptive && width < MAX_WIDTH &&
|
|
1547
1540
|
(currentXPos === getLeftXBoundaryMargin() ||
|
|
1548
1541
|
currentXPos === rightBoundary)
|
|
1549
1542
|
) {
|
|
@@ -94,13 +94,11 @@ export const setupHandlers = grnState => {
|
|
|
94
94
|
window.document.body.appendChild(emptySvg);
|
|
95
95
|
var emptySvgDeclarationComputed = getComputedStyle(emptySvg);
|
|
96
96
|
|
|
97
|
-
const traverse =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (node && node.hasChildNodes()) {
|
|
103
|
-
var child = node.firstChild;
|
|
97
|
+
const traverse = (node) => {
|
|
98
|
+
const tree = [];
|
|
99
|
+
const visit = (currentNode) => {
|
|
100
|
+
if (currentNode && currentNode.hasChildNodes()) {
|
|
101
|
+
let child = currentNode.firstChild;
|
|
104
102
|
while (child) {
|
|
105
103
|
if (child.nodeType === 1 && child.nodeName !== "SCRIPT") {
|
|
106
104
|
tree.push(child);
|
|
@@ -110,38 +108,39 @@ export const setupHandlers = grnState => {
|
|
|
110
108
|
}
|
|
111
109
|
}
|
|
112
110
|
};
|
|
113
|
-
visit(
|
|
111
|
+
visit(node);
|
|
114
112
|
return tree;
|
|
115
113
|
};
|
|
116
114
|
|
|
117
115
|
const explicitlySetStyle = element => {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
for (i = 0, len = cSSStyleDeclarationComputed.length; i < len; i++) {
|
|
126
|
-
key = cSSStyleDeclarationComputed[i];
|
|
127
|
-
value = cSSStyleDeclarationComputed.getPropertyValue(key);
|
|
116
|
+
const cssStyleDeclarationComputed = window.getComputedStyle(element);
|
|
117
|
+
const computedStyleObj = {};
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < cssStyleDeclarationComputed.length; i++) {
|
|
121
|
+
const key = cssStyleDeclarationComputed[i];
|
|
122
|
+
const value = cssStyleDeclarationComputed.getPropertyValue(key);
|
|
128
123
|
if (value !== emptySvgDeclarationComputed.getPropertyValue(key)) {
|
|
129
124
|
// Don't set computed style of width and height. Makes SVG elmements disappear.
|
|
130
125
|
if ((key !== "height") && (key !== "width")) {
|
|
131
|
-
|
|
126
|
+
computedStyleObj[key] = value;
|
|
132
127
|
}
|
|
133
128
|
|
|
134
129
|
}
|
|
135
130
|
}
|
|
136
|
-
|
|
131
|
+
|
|
132
|
+
if (element.classList.contains("weight")) {
|
|
133
|
+
computedStyleObj["visibility"] = "hidden";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (computedStyleObj) {
|
|
137
|
+
Object.assign(element.style, computedStyleObj);
|
|
138
|
+
}
|
|
137
139
|
};
|
|
138
140
|
|
|
139
141
|
// hardcode computed css styles inside svg
|
|
140
142
|
var allElements = traverse(svg);
|
|
141
|
-
|
|
142
|
-
while (i--) {
|
|
143
|
-
explicitlySetStyle(allElements[i]);
|
|
144
|
-
}
|
|
143
|
+
allElements.forEach(explicitlySetStyle);
|
|
145
144
|
};
|
|
146
145
|
|
|
147
146
|
const sourceAttributeSetter = (svg) => {
|
|
@@ -160,7 +159,7 @@ export const setupHandlers = grnState => {
|
|
|
160
159
|
};
|
|
161
160
|
|
|
162
161
|
const exportSVG = (svgElement, name) => {
|
|
163
|
-
let source = svgElement;
|
|
162
|
+
let source = svgElement.cloneNode(true);
|
|
164
163
|
|
|
165
164
|
sourceAttributeSetter(source);
|
|
166
165
|
setInlineStyles(source);
|
|
@@ -185,10 +184,30 @@ export const setupHandlers = grnState => {
|
|
|
185
184
|
const imgData = canvas.toDataURL("image/png");
|
|
186
185
|
|
|
187
186
|
const pdf = new jsPDF("l", "mm", "letter");
|
|
188
|
-
const
|
|
189
|
-
const
|
|
187
|
+
const pdfWidth = pdf.internal.pageSize.getWidth();
|
|
188
|
+
const pdfHeight = pdf.internal.pageSize.getHeight();
|
|
189
|
+
|
|
190
|
+
const svgWidth = canvas.width;
|
|
191
|
+
const svgHeight = canvas.height;
|
|
192
|
+
|
|
193
|
+
const aspectRatio = svgWidth / svgHeight;
|
|
194
|
+
let scaledWidth = pdfWidth;
|
|
195
|
+
// ratio = width/height --> height = width/ratio
|
|
196
|
+
let scaledHeight = scaledWidth / aspectRatio;
|
|
197
|
+
|
|
198
|
+
if (scaledHeight > pdfHeight) {
|
|
199
|
+
scaledHeight = pdfHeight;
|
|
200
|
+
scaledWidth = pdfHeight * aspectRatio;
|
|
201
|
+
}
|
|
190
202
|
|
|
191
|
-
pdf.addImage(
|
|
203
|
+
pdf.addImage(
|
|
204
|
+
imgData,
|
|
205
|
+
"PNG",
|
|
206
|
+
(pdfWidth - scaledWidth) / 2,
|
|
207
|
+
(pdfHeight - scaledHeight) / 2,
|
|
208
|
+
scaledWidth,
|
|
209
|
+
scaledHeight
|
|
210
|
+
);
|
|
192
211
|
pdf.save(name);
|
|
193
212
|
};
|
|
194
213
|
|
|
@@ -209,7 +228,7 @@ export const setupHandlers = grnState => {
|
|
|
209
228
|
$(EXPORT_TO_PNG).click(() => {
|
|
210
229
|
var svgContainer = document.getElementById("exportContainer");
|
|
211
230
|
var editedName = grnState.name.replace(determineFileType(grnState.name), "") + ".png";
|
|
212
|
-
saveSvgAsPng(svgContainer, editedName);
|
|
231
|
+
saveSvgAsPng(svgContainer, editedName, { backgroundColor: "white"});
|
|
213
232
|
});
|
|
214
233
|
|
|
215
234
|
$(EXPORT_TO_SVG).click(() => {
|
|
@@ -171,8 +171,8 @@ export const setupLoadAndImportHandlers = (grnState) => {
|
|
|
171
171
|
* for helping to resolve this.
|
|
172
172
|
*/
|
|
173
173
|
|
|
174
|
-
// $(".upload").change(uploadHandler(loadGrn));
|
|
175
174
|
$("body").on("change", ".upload", uploadHandler(loadGrn));
|
|
175
|
+
|
|
176
176
|
const loadDemo = (url, value) => {
|
|
177
177
|
$("#demoSourceDropdown option[value='" + value.substring(1) + "']").prop(
|
|
178
178
|
"selected",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { drawGraph, updaters } from "./graph";
|
|
2
2
|
import { uploadState } from "./upload";
|
|
3
|
-
import { displayWarnings } from "./warnings";
|
|
3
|
+
import { displayWarnings, displayPPINodeColorWarning } from "./warnings";
|
|
4
4
|
import { max } from "d3-array";
|
|
5
5
|
import { grnState } from "./grnstate";
|
|
6
6
|
|
|
@@ -109,7 +109,7 @@ import {
|
|
|
109
109
|
NETWORK_MODE_GRN,
|
|
110
110
|
EXPORT_TO_UNWEIGHTED_GML_MENU,
|
|
111
111
|
NETWORK_GRN_MODE,
|
|
112
|
-
NETWORK_PPI_MODE
|
|
112
|
+
NETWORK_PPI_MODE,
|
|
113
113
|
// EXPRESSION_SOURCE,
|
|
114
114
|
} from "./constants";
|
|
115
115
|
|
|
@@ -384,6 +384,14 @@ const enableNodeColoringUI = function () {
|
|
|
384
384
|
$(LOG_FOLD_CHANGE_MAX_VALUE_HEADER).removeClass("hidden");
|
|
385
385
|
};
|
|
386
386
|
|
|
387
|
+
const adjustGeneNameForExpression = function (gene) {
|
|
388
|
+
const geneName = gene.name;
|
|
389
|
+
return grnState.workbook.meta.data.workbookType === NETWORK_PPI_MODE &&
|
|
390
|
+
geneName.endsWith("p")
|
|
391
|
+
? geneName.slice(0, -1)
|
|
392
|
+
: geneName;
|
|
393
|
+
};
|
|
394
|
+
|
|
387
395
|
const loadExpressionDatabase = function (isTopDataset) {
|
|
388
396
|
const dataset = isTopDataset ? grnState.nodeColoring.topDataset : grnState.nodeColoring.bottomDataset;
|
|
389
397
|
startLoadingIcon();
|
|
@@ -394,7 +402,9 @@ const loadExpressionDatabase = function (isTopDataset) {
|
|
|
394
402
|
queryExpressionDatabase({
|
|
395
403
|
type:"ExpressionData",
|
|
396
404
|
dataset,
|
|
397
|
-
genes
|
|
405
|
+
genes: grnState.workbook.genes
|
|
406
|
+
.map(adjustGeneNameForExpression)
|
|
407
|
+
.join(","),
|
|
398
408
|
timepoints: timepointsResponse[dataset]
|
|
399
409
|
}).then(function (response) {
|
|
400
410
|
if (isTopDataset) {
|
|
@@ -502,6 +512,10 @@ const toggleLayout = (on, off) => {
|
|
|
502
512
|
}
|
|
503
513
|
};
|
|
504
514
|
|
|
515
|
+
export const hasExpressionData = (sheets) => {
|
|
516
|
+
return Object.keys(sheets).some(property => property.match(ENDS_IN_EXPRESSION_REGEXP));
|
|
517
|
+
};
|
|
518
|
+
|
|
505
519
|
const updatetoForceGraph = () => {};
|
|
506
520
|
|
|
507
521
|
const updatetoGridLayout = () => {};
|
|
@@ -528,7 +542,6 @@ const isNewWorkbook = (name) => {
|
|
|
528
542
|
};
|
|
529
543
|
|
|
530
544
|
// Workbook Mode Functions
|
|
531
|
-
|
|
532
545
|
const updateModeViews = () =>{
|
|
533
546
|
// Select correct dropdown item
|
|
534
547
|
$(`${NETWORK_MODE_DROPDOWN} option`).removeAttr("selected");
|
|
@@ -543,14 +556,18 @@ const updateModeViews = () =>{
|
|
|
543
556
|
};
|
|
544
557
|
|
|
545
558
|
const checkWorkbookModeSettings = () => {
|
|
546
|
-
|
|
559
|
+
const hasExpression = hasExpressionData(grnState.workbook.expression);
|
|
560
|
+
|
|
561
|
+
if (grnState.mode === NETWORK_PPI_MODE || !hasExpression) {
|
|
547
562
|
grnState.nodeColoring.nodeColoringEnabled = false;
|
|
563
|
+
grnState.nodeColoring.showMenu = true;
|
|
548
564
|
grnState.colorOptimal = false;
|
|
549
|
-
|
|
565
|
+
showNodeColoringMenus();
|
|
550
566
|
hideEdgeWeightOptions();
|
|
551
567
|
updateModeViews();
|
|
552
568
|
} else if (grnState.mode === NETWORK_GRN_MODE) {
|
|
553
569
|
grnState.nodeColoring.nodeColoringEnabled = true;
|
|
570
|
+
grnState.nodeColoring.showMenu = true;
|
|
554
571
|
grnState.colorOptimal = true;
|
|
555
572
|
showNodeColoringMenus();
|
|
556
573
|
showEdgeWeightOptions();
|
|
@@ -587,15 +604,6 @@ const shortenExpressionSheetName = (name) => {
|
|
|
587
604
|
(name.slice(0, MAX_NUM_CHARACTERS_DROPDOWN) + "...") : name;
|
|
588
605
|
};
|
|
589
606
|
|
|
590
|
-
const hasExpressionData = (sheets) => {
|
|
591
|
-
for (var property in sheets) {
|
|
592
|
-
if (property.match(ENDS_IN_EXPRESSION_REGEXP)) {
|
|
593
|
-
return true;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
return false;
|
|
597
|
-
};
|
|
598
|
-
|
|
599
607
|
const updateSpeciesMenu = () => {
|
|
600
608
|
$(SPECIES_DISPLAY).val(grnState.genePageData.species);
|
|
601
609
|
$(SPECIES_BUTTON_CRESS + " span").removeClass("glyphicon-ok");
|
|
@@ -878,6 +886,10 @@ export const updateApp = grnState => {
|
|
|
878
886
|
$(NODE_COLORING_SIDEBAR_BODY).removeClass("hidden");
|
|
879
887
|
$(NODE_COLORING_MENU).removeClass("hidden");
|
|
880
888
|
$(NODE_COLORING_NAVBAR_OPTIONS).removeClass("hidden");
|
|
889
|
+
if (grnState.mode === NETWORK_PPI_MODE) {
|
|
890
|
+
displayPPINodeColorWarning(grnState.ppiNodeColorWarningDisplayed);
|
|
891
|
+
grnState.ppiNodeColorWarningDisplayed = true;
|
|
892
|
+
}
|
|
881
893
|
if (grnState.database.expressionDatasets.includes(grnState.nodeColoring.topDataset) &&
|
|
882
894
|
grnState.workbook.expression[grnState.nodeColoring.topDataset] === undefined) {
|
|
883
895
|
if ($(NODE_COLORING_TOGGLE_SIDEBAR).prop("checked")) {
|
|
@@ -905,6 +917,12 @@ export const updateApp = grnState => {
|
|
|
905
917
|
grnState.nodeColoring.topDataset : "Dahlquist_2018_wt";
|
|
906
918
|
grnState.nodeColoring.bottomDataset = grnState.nodeColoring.bottomDataset ?
|
|
907
919
|
grnState.nodeColoring.bottomDataset : "Dahlquist_2018_wt";
|
|
920
|
+
$(NODE_COLORING_TOGGLE_SIDEBAR).prop("checked", true);
|
|
921
|
+
$(`${NODE_COLORING_TOGGLE_MENU} span`).addClass("glyphicon-ok");
|
|
922
|
+
$(NODE_COLORING_SIDEBAR_BODY).removeClass("hidden");
|
|
923
|
+
$(NODE_COLORING_MENU).removeClass("hidden");
|
|
924
|
+
$(NODE_COLORING_NAVBAR_OPTIONS).removeClass("hidden");
|
|
925
|
+
$(LOG_FOLD_CHANGE_MAX_VALUE_CLASS).val(DEFAULT_MAX_LOG_FOLD_CHANGE);
|
|
908
926
|
$(LOG_FOLD_CHANGE_MAX_VALUE_CLASS).addClass("hidden");
|
|
909
927
|
$(LOG_FOLD_CHANGE_MAX_VALUE_SIDEBAR_BUTTON).addClass("hidden");
|
|
910
928
|
$(LOG_FOLD_CHANGE_MAX_VALUE_HEADER).addClass("hidden");
|
|
@@ -931,7 +949,10 @@ export const updateApp = grnState => {
|
|
|
931
949
|
// Investigate why a timeout is required in order for node coloring to take place
|
|
932
950
|
// successfully in this case.
|
|
933
951
|
setTimeout(() => updaters.renderNodeColoring(), 250);
|
|
934
|
-
|
|
952
|
+
}
|
|
953
|
+
if (grnState.mode === NETWORK_PPI_MODE) {
|
|
954
|
+
displayPPINodeColorWarning(grnState.ppiNodeColorWarningDisplayed);
|
|
955
|
+
grnState.ppiNodeColorWarningDisplayed = true;
|
|
935
956
|
}
|
|
936
957
|
}
|
|
937
958
|
} else if (grnState.workbook !== null && !grnState.nodeColoring.nodeColoringEnabled) {
|
|
@@ -940,6 +961,9 @@ export const updateApp = grnState => {
|
|
|
940
961
|
$(NODE_COLORING_NAVBAR_OPTIONS).addClass("hidden");
|
|
941
962
|
$(`${NODE_COLORING_TOGGLE_MENU} span`).removeClass("glyphicon-ok");
|
|
942
963
|
$(NODE_COLORING_TOGGLE_SIDEBAR).prop("checked", false);
|
|
964
|
+
if (grnState.mode === NETWORK_PPI_MODE) {
|
|
965
|
+
grnState.ppiNodeColorWarningDisplayed = false;
|
|
966
|
+
}
|
|
943
967
|
}
|
|
944
968
|
|
|
945
969
|
if (grnState.workbook !== null && grnState.workbook.sheetType === "weighted") {
|
|
@@ -1010,4 +1034,4 @@ export const updateApp = grnState => {
|
|
|
1010
1034
|
};
|
|
1011
1035
|
|
|
1012
1036
|
|
|
1013
|
-
export { stopLoadingIcon, startLoadingIcon};
|
|
1037
|
+
export { stopLoadingIcon, startLoadingIcon, adjustGeneNameForExpression};
|
|
@@ -120,7 +120,7 @@ export const upload = function () {
|
|
|
120
120
|
const updateOptimizationParameters = (finalExportSheets) => {
|
|
121
121
|
let optimizationParameters = {
|
|
122
122
|
data: grnState.mode === NETWORK_GRN_MODE ? {
|
|
123
|
-
alpha: 0.
|
|
123
|
+
alpha: 0.02,
|
|
124
124
|
"kk_max": 1,
|
|
125
125
|
MaxIter: 100000000,
|
|
126
126
|
TolFun: 0.000001,
|
|
@@ -261,7 +261,7 @@ export const upload = function () {
|
|
|
261
261
|
} else if (sheet === "network") {
|
|
262
262
|
finalExportSheets.networks[sheet] = grnState.workbook.network;
|
|
263
263
|
} else if (sheet === "network_weights") {
|
|
264
|
-
finalExportSheets.networks[sheet] = grnState.workbook.
|
|
264
|
+
finalExportSheets.networks[sheet] = grnState.workbook.network; // network_weights is identical to network
|
|
265
265
|
} else if (sheet === "optimization_diagnostics") { // Get the additional Sheets
|
|
266
266
|
finalExportSheets[sheet] = grnState.workbook.meta2;
|
|
267
267
|
} else if (sheet === "optimization_parameters") {
|
|
@@ -430,7 +430,7 @@ export const upload = function () {
|
|
|
430
430
|
let networks = [
|
|
431
431
|
[grnState.workbook.network !== undefined, "network"],
|
|
432
432
|
[grnState.workbook.networkOptimizedWeights !== undefined, "network_optimized_weights"],
|
|
433
|
-
[grnState.workbook.
|
|
433
|
+
[grnState.workbook.network !== undefined, "network_weights"]]; // network_weights is always available if network is available
|
|
434
434
|
// networks = networks.filter(x => x !== false);
|
|
435
435
|
let additionalsheets = grnState.workbook.twoColumnSheets ? [
|
|
436
436
|
...Object.keys(grnState.workbook.twoColumnSheets),
|
|
@@ -58,3 +58,28 @@ export var displayWarnings = function (warnings) {
|
|
|
58
58
|
|
|
59
59
|
$("#warningsModal").modal("show");
|
|
60
60
|
};
|
|
61
|
+
|
|
62
|
+
export var displayPPINodeColorWarning = function (warningDisplayed) {
|
|
63
|
+
if (warningDisplayed) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
$("#warningIntro").html("Protein-protein interaction node coloring warning.");
|
|
68
|
+
$("#warningsList").html(
|
|
69
|
+
["You are displaying mRNA-level expression data on a protein-protein interaction network.",
|
|
70
|
+
"Please note that this may not be the most appropriate representation of the data."].join(" ")
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
var screenHeight = $(window).height();
|
|
74
|
+
var MIN_SCREEN_HEIGHT = 600;
|
|
75
|
+
var BORDER = 425;
|
|
76
|
+
var setPanel = screenHeight - BORDER + "px";
|
|
77
|
+
var minPanel = MIN_SCREEN_HEIGHT - BORDER + "px";
|
|
78
|
+
if (screenHeight > MIN_SCREEN_HEIGHT) {
|
|
79
|
+
$("#list-frame").css({ height: setPanel });
|
|
80
|
+
} else {
|
|
81
|
+
$("#list-frame").css({ height: minPanel });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$("#warningsModal").modal("show");
|
|
85
|
+
};
|
|
@@ -513,7 +513,7 @@ html
|
|
|
513
513
|
p(id='error')
|
|
514
514
|
div(class='modal-footer')
|
|
515
515
|
input(type='button' class='btn btn-default' data-dismiss='modal' value='Close')
|
|
516
|
-
input(type='button' id='launchFileOpen' class='btn btn-primary' data-dismiss='modal' value='Select New File' onclick="$('
|
|
516
|
+
input(type='button' id='launchFileOpen' class='btn btn-primary' data-dismiss='modal' value='Select New File' onclick="$('#upload-network').click()")
|
|
517
517
|
|
|
518
518
|
div(id='warningsModal' class='modal fade' tab-index='-1' role='dialog' aria-labelledby='mySmallModalLabel' aria-hidden='true' data-backdrop="static")
|
|
519
519
|
div(class='modal-dialog')
|