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.
@@ -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
  [![DOI](https://zenodo.org/badge/16195791.svg)](https://zenodo.org/badge/latestdoi/16195791)
4
- [![Build Status](https://app.travis-ci.com/dondi/GRNsight.svg?branch=master)](https://app.travis-ci.com/dondi/GRNsight)
4
+ [![Node.js CI](https://github.com/dondi/GRNsight/actions/workflows/node.js.yml/badge.svg)](https://github.com/dondi/GRNsight/actions/workflows/node.js.yml)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/dondi/GRNsight/badge.svg?branch=master)](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 gene_regulatory_network_testing.gene(gene_id, taxon_id),
24
- FOREIGN KEY (time_stamp, source) REFERENCES gene_regulatory_network_testing.source(time_stamp, source)
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 protein_protein_interactions_testing.gene(gene_id, taxon_id)
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 protein_protein_interactions_testing.protein(standard_name),
34
- FOREIGN KEY (protein2) REFERENCES protein_protein_interactions_testing.protein(standard_name),
35
- FOREIGN KEY (time_stamp, source) REFERENCES protein_protein_interactions_testing.source(time_stamp, source),
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.1.1",
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
- "canvg": "2.0.0",
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.002,
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.002,
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
- for (const relationship of relationships) {
65
- if (relationship === "pp") {
66
- workbookType = constants.NETWORK_PPI_MODE;
67
- break;
68
- } else if (relationship === "pd") {
69
- workbookType = constants.NETWORK_GRN_MODE;
70
- break;
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.EXPRESSION_DB_USERNAME,
8
- process.env.EXPRESSION_DB_PASSWORD,
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 =\'${x}\') OR `));
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.EXPRESSION_DB_USERNAME,
8
- process.env.EXPRESSION_DB_PASSWORD,
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.EXPRESSION_DB_USERNAME,
8
- process.env.EXPRESSION_DB_PASSWORD,
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, source, timestamp) {
24
+ const buildNetworkGeneFromSourceQuery = function (gene) {
25
25
  return `SELECT DISTINCT gene_id, display_gene_id FROM
26
- gene_regulatory_network.network, gene_regulatory_network.gene WHERE
27
- network.time_stamp='${timestamp}' AND network.source='${source}' AND
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, query.source, query.timestamp),
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.EXPRESSION_DB_USERNAME,
8
- process.env.EXPRESSION_DB_PASSWORD,
7
+ process.env.DB_USERNAME,
8
+ process.env.DB_PASSWORD,
9
9
  {
10
10
  host: config.databaseHost,
11
11
  dialect: config.databaseDialect,
@@ -62,7 +62,7 @@ describe("additional-sheet-parser", function () {
62
62
  var data = parseAdditionalSheets(workbook);
63
63
  /* eslint-disable */
64
64
  assert(data.meta, {
65
- alpha: 0.002,
65
+ alpha: 0.02,
66
66
  kk_max: 1,
67
67
  MaxIter: 100000000,
68
68
  TolFun: 0.000001,
@@ -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
- ENDS_IN_EXPRESSION_REGEXP,
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[p.name]
1096
+ grnState.workbook.expression[dataset].data[geneName]
1092
1097
  ) {
1093
1098
  const result = getExpressionData(
1094
- p.name,
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
  ) {
@@ -85,6 +85,7 @@ export const grnState = {
85
85
  lastDataset: null,
86
86
  bottomDataSameAsTop: true,
87
87
  nodeColoringOptions: [],
88
+ ppiNodeColorWarningDisplayed: false,
88
89
  },
89
90
 
90
91
 
@@ -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 = svg => {
98
- var tree = [];
99
- tree.push(svg);
100
- // implement DFS
101
- const visit = (node) => {
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(svg);
111
+ visit(node);
114
112
  return tree;
115
113
  };
116
114
 
117
115
  const explicitlySetStyle = element => {
118
- const cSSStyleDeclarationComputed = window.getComputedStyle(element);
119
- let i;
120
- let len;
121
- let key;
122
- let value;
123
- let computedStyleStr = "";
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
- computedStyleStr += key + ":" + value + ";";
126
+ computedStyleObj[key] = value;
132
127
  }
133
128
 
134
129
  }
135
130
  }
136
- element.setAttribute("style", computedStyleStr);
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
- var i = allElements.length;
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 width = pdf.internal.pageSize.getWidth();
189
- const height = pdf.internal.pageSize.getHeight();
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(imgData, "PNG", 0, 0, width, height);
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 : grnState.workbook.genes.map(gene => {return gene.name;}).join(","),
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
- if (grnState.mode === NETWORK_PPI_MODE) {
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
- disableNodeColoringMenus();
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.002,
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.networkWeights;
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.networkWeights !== undefined, "network_weights"]];
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="$('.upload').click()")
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')