grnsight 6.0.7 → 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.
Files changed (67) hide show
  1. package/.eslintrc.yml +4 -4
  2. package/.github/workflows/node.js.yml +35 -0
  3. package/README.md +1 -1
  4. package/database/README.md +218 -97
  5. package/database/constants.py +42 -0
  6. package/database/filter_update.py +168 -0
  7. package/database/grnsettings-database/README.md +52 -0
  8. package/database/grnsettings-database/schema.sql +4 -0
  9. package/database/loader.py +30 -0
  10. package/database/loader_update.py +36 -0
  11. package/database/network-database/scripts/generate_network.py +15 -23
  12. package/database/network-database/scripts/generate_new_network_version.py +17 -24
  13. package/database/protein-protein-database/README.md +71 -0
  14. package/database/protein-protein-database/schema.sql +37 -0
  15. package/database/protein-protein-database/scripts/generate_protein_network.py +227 -0
  16. package/database/protein-protein-database/scripts/remove_duplicates.sh +4 -0
  17. package/database/utils.py +418 -0
  18. package/package.json +3 -2
  19. package/server/app.js +2 -0
  20. package/server/config/config.js +4 -4
  21. package/server/controllers/additional-sheet-parser.js +2 -1
  22. package/server/controllers/constants.js +5 -0
  23. package/server/controllers/custom-workbook-controller.js +4 -3
  24. package/server/controllers/demo-workbooks.js +1462 -6
  25. package/server/controllers/export-constants.js +3 -2
  26. package/server/controllers/exporters/sif.js +6 -1
  27. package/server/controllers/exporters/xlsx.js +8 -3
  28. package/server/controllers/expression-sheet-parser.js +0 -6
  29. package/server/controllers/grnsettings-database-controller.js +17 -0
  30. package/server/controllers/importers/sif.js +30 -11
  31. package/server/controllers/network-database-controller.js +2 -2
  32. package/server/controllers/network-sheet-parser.js +54 -12
  33. package/server/controllers/protein-database-controller.js +18 -0
  34. package/server/controllers/sif-constants.js +11 -4
  35. package/server/controllers/spreadsheet-controller.js +44 -1
  36. package/server/controllers/workbook-constants.js +21 -4
  37. package/server/dals/expression-dal.js +4 -4
  38. package/server/dals/grnsetting-dal.js +49 -0
  39. package/server/dals/network-dal.js +14 -15
  40. package/server/dals/protein-dal.js +106 -0
  41. package/test/additional-sheet-parser-tests.js +1 -1
  42. package/test/export-tests.js +136 -9
  43. package/test/import-sif-tests.js +67 -13
  44. package/test/test.js +1 -1
  45. package/test-files/additional-sheet-test-files/optimization-parameters-default.xlsx +0 -0
  46. package/test-files/demo-files/18_proteins_81_edges_PPI.xlsx +0 -0
  47. package/test-files/expression-data-test-sheets/expression_sheet_missing_data_ok_export_exact.xlsx +0 -0
  48. package/web-client/config/config.js +4 -4
  49. package/web-client/public/js/api/grnsight-api.js +18 -3
  50. package/web-client/public/js/constants.js +27 -12
  51. package/web-client/public/js/generateNetwork.js +170 -72
  52. package/web-client/public/js/graph.js +424 -161
  53. package/web-client/public/js/grnsight.js +25 -4
  54. package/web-client/public/js/grnstate.js +4 -1
  55. package/web-client/public/js/iframe-coordination.js +3 -3
  56. package/web-client/public/js/setup-handlers.js +76 -61
  57. package/web-client/public/js/setup-load-and-import-handlers.js +32 -7
  58. package/web-client/public/js/update-app.js +119 -28
  59. package/web-client/public/js/upload.js +142 -85
  60. package/web-client/public/js/warnings.js +25 -0
  61. package/web-client/public/lib/bootstrap.file-input/bootstrap.file-input.js +0 -1
  62. package/web-client/public/stylesheets/grnsight.styl +40 -16
  63. package/web-client/views/components/demo.pug +7 -5
  64. package/web-client/views/upload.pug +64 -50
  65. package/database/network-database/scripts/filter_genes.py +0 -76
  66. package/database/network-database/scripts/loader.py +0 -79
  67. package/database/network-database/scripts/loader_updates.py +0 -99
@@ -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,6 +16,8 @@ import {
11
16
  ZOOM_DISPLAY_MAXIMUM_VALUE,
12
17
  ZOOM_DISPLAY_MIDDLE,
13
18
  ZOOM_ADAPTIVE_MAX_SCALE,
19
+ NETWORK_GRN_MODE,
20
+ BOUNDARY_MARGIN
14
21
  } from "./constants";
15
22
 
16
23
  /* globals d3 */
@@ -33,6 +40,7 @@ import {
33
40
  * Resize detection logic: to avoid "listener leaks," this is set up a single time here, with an assignable
34
41
  * updateFunction being set when needed.
35
42
  */
43
+
36
44
  let mutationCallback = null;
37
45
  const resizeObserver = new MutationObserver((mutationsList, observer) => {
38
46
  if (typeof(mutationCallback) === "function") {
@@ -50,6 +58,7 @@ export var updaters = {
50
58
  export var drawGraph = function (workbook) {
51
59
  /* eslint-enable no-unused-vars */
52
60
  var $container = $(".grnsight-container");
61
+ var CURSOR_CLASSES = "cursorGrab cursorGrabbing";
53
62
  d3.selectAll("svg").remove();
54
63
 
55
64
  $container.removeClass(CURSOR_CLASSES).addClass("cursorGrab"); // allow graph dragging right away
@@ -61,8 +70,6 @@ export var drawGraph = function (workbook) {
61
70
 
62
71
  var dashedLine = $("#dashedGrayLineButton").prop("checked");
63
72
 
64
- var CURSOR_CLASSES = "cursorGrab cursorGrabbing";
65
-
66
73
  $("#warningMessage").html(workbook.warnings.length !== 0 ? "Click here in order to view warnings." : "");
67
74
 
68
75
  var getNodeWidth = function (node) {
@@ -159,35 +166,40 @@ export var drawGraph = function (workbook) {
159
166
 
160
167
  var zoomDragPrevX = 0;
161
168
  var zoomDragPrevY = 0;
169
+ let graphZoom = 0;
170
+
162
171
  var zoomDragStarted = function () {
163
172
  zoomDragPrevX = d3.event.x;
164
173
  zoomDragPrevY = d3.event.y;
165
174
  $container.removeClass(CURSOR_CLASSES).addClass("cursorGrabbing");
166
- if (!adaptive) {
167
- $container.removeClass(CURSOR_CLASSES);
168
- }
169
175
  };
170
176
 
171
177
  var zoomDragged = function () {
172
- if (adaptive) {
173
- var scale = 1;
174
- if (zoomContainer.attr("transform")) {
175
- var string = zoomContainer.attr("transform");
176
- scale = 1 / +(string.match(/scale\(([^\)]+)\)/)[1]);
177
- }
178
- zoom.translateBy(zoomContainer, scale * (d3.event.x - zoomDragPrevX), scale * (d3.event.y - zoomDragPrevY));
179
- zoomDragPrevX = d3.event.x;
180
- zoomDragPrevY = d3.event.y;
178
+ var scale = 1;
179
+ if (zoomContainer.attr("transform")) {
180
+ var string = zoomContainer.attr("transform");
181
+ scale = 1 / +(string.match(/scale\(([^\)]+)\)/)[1]);
182
+ }
183
+
184
+ if (adaptive || (!adaptive &&
185
+ flexZoomInBounds(graphZoom) &&
186
+ viewportBoundsMoveDrag(graphZoom, d3.event.dx, d3.event.dy))
187
+ ) {
188
+ zoom.translateBy(
189
+ zoomContainer,
190
+ scale * (d3.event.x - zoomDragPrevX),
191
+ scale * (d3.event.y - zoomDragPrevY)
192
+ );
181
193
  }
194
+ zoomDragPrevX = d3.event.x;
195
+ zoomDragPrevY = d3.event.y;
182
196
  };
183
197
 
184
198
  var zoomDragEnded = function () {
185
199
  $container.removeClass(CURSOR_CLASSES).addClass("cursorGrab");
186
- if (!adaptive) {
187
- $container.removeClass(CURSOR_CLASSES);
188
- }
189
200
  };
190
201
 
202
+ // zoomDrag and all functions that it calls handles cursor dragging
191
203
  var zoomDrag = d3.drag()
192
204
  .on("start", zoomDragStarted)
193
205
  .on("drag", zoomDragged)
@@ -207,6 +219,21 @@ export var drawGraph = function (workbook) {
207
219
 
208
220
  var boundingBoxContainer = zoomContainer.append("g"); // appended another g here...
209
221
 
222
+ // This rectangle catches all of the mousewheel and pan events, without letting
223
+ // them bubble up to the body.
224
+ var boundingBoxRect = boundingBoxContainer.append("rect")
225
+ .attr("width", width)
226
+ .attr("height", height)
227
+ .style("fill", "none")
228
+ .style("pointer-events", "all")
229
+ .attr("stroke", "none" )
230
+ .attr("id", "boundingBoxRect");
231
+
232
+ var flexibleContainerRect = boundingBoxContainer.append("rect")
233
+ .attr("class", "boundingBox")
234
+ .attr("fill", "none")
235
+ .attr("id", "flexibleContainerRect");
236
+
210
237
  var zoom = d3.zoom()
211
238
  .scaleExtent([MIN_SCALE, ZOOM_ADAPTIVE_MAX_SCALE])
212
239
  .on("zoom", zoomed);
@@ -214,23 +241,14 @@ export var drawGraph = function (workbook) {
214
241
  svg.style("pointer-events", "all").call(zoomDrag)
215
242
  .style("font-family", "sans-serif");
216
243
 
217
-
244
+ // this allows zoomContainer to be zoomed, dragged
218
245
  function zoomed () {
219
246
  zoomContainer.attr("transform", d3.event.transform);
220
247
  }
221
248
 
222
249
  d3.select("svg").on("dblclick.zoom", null); // disables double click zooming
223
250
 
224
- // This rectangle catches all of the mousewheel and pan events, without letting
225
- // them bubble up to the body.
226
- boundingBoxContainer.append("rect")
227
- .attr("width", width)
228
- .attr("height", height)
229
- .style("fill", "none")
230
- .style("pointer-events", "all")
231
- .attr("stroke", "none" )
232
- .append("g");
233
-
251
+ // this controls the D-pad
234
252
  d3.selectAll(".scrollBtn").on("click", null); // Remove event handlers, if there were any.
235
253
  var arrowMovement = [ "Up", "Left", "Right", "Down" ];
236
254
  arrowMovement.forEach(function (direction) {
@@ -240,14 +258,39 @@ export var drawGraph = function (workbook) {
240
258
  });
241
259
  d3.select(".center").on("click", center);
242
260
 
261
+ let xTranslation = 0;
262
+ let yTranslation = 0;
263
+
264
+ function updateZoomContainerInfo () {
265
+ // transform attribute of zoomContainer contains translation info about graph
266
+ if (zoomContainer.attr("transform")) {
267
+ xTranslation = Number(
268
+ zoomContainer
269
+ .attr("transform")
270
+ .split("(")[1]
271
+ .split(",")[0]
272
+ );
273
+
274
+ yTranslation = Number(
275
+ zoomContainer
276
+ .attr("transform")
277
+ .split("(")[1]
278
+ .split(",")[1]
279
+ .split(")")[0]
280
+ );
281
+ }
282
+ }
283
+
284
+ // controls reading movement of zoomSlider and scaling graph to that zoomScale
243
285
  const setGraphZoom = zoomScale => {
244
286
  if (zoomScale < MIDDLE_SCALE) {
245
287
  $container.removeClass(CURSOR_CLASSES).addClass("cursorGrab");
246
- } else if (!adaptive && zoomScale >= MIDDLE_SCALE) {
247
- $container.removeClass(CURSOR_CLASSES);
248
288
  }
249
289
  var container = zoomContainer;
250
- zoom.scaleTo(container, zoomScale);
290
+ if (adaptive || (!adaptive && flexZoomInBounds(graphZoom))) {
291
+ zoom.scaleTo(container, zoomScale);
292
+ graphZoom = zoomScale;
293
+ }
251
294
  };
252
295
 
253
296
  // See setupZoomElements below to see how these are initialized. They are declared here because
@@ -255,15 +298,34 @@ export var drawGraph = function (workbook) {
255
298
  let sliderMidpoint;
256
299
  let zoomScaleSliderLeft;
257
300
  let zoomScaleSliderRight;
301
+ let prevGrnstateZoomVal;
302
+ let flexibleContainer = null;
258
303
 
259
304
  const updateAppBasedOnZoomValue = () => {
305
+ let zoomDisplay;
260
306
 
261
- if (!adaptive) {
262
- grnState.zoomValue = 100.0;
307
+ // If the zoom value is out of bounds, reset it to the previous value.
308
+ if (adaptive) {
309
+ zoomDisplay = grnState.zoomValue;
310
+ } else if (
311
+ !adaptive &&
312
+ flexZoomInBounds(
313
+ (grnState.zoomValue <= ZOOM_DISPLAY_MIDDLE
314
+ ? zoomScaleLeft
315
+ : zoomScaleRight)(grnState.zoomValue)
316
+ )
317
+ ) {
318
+ zoomDisplay = grnState.zoomValue;
319
+ } else {
320
+ grnState.zoomValue = prevGrnstateZoomVal;
321
+ zoomDisplay = grnState.zoomValue;
263
322
  }
264
323
 
265
- const zoomDisplay = grnState.zoomValue;
266
- setGraphZoom((zoomDisplay <= ZOOM_DISPLAY_MIDDLE ? zoomScaleLeft : zoomScaleRight)(zoomDisplay));
324
+ const calcGraphZoom = (zoomDisplay <= ZOOM_DISPLAY_MIDDLE
325
+ ? zoomScaleLeft
326
+ : zoomScaleRight)(zoomDisplay);
327
+
328
+ setGraphZoom(calcGraphZoom);
267
329
 
268
330
  const finalDisplay = grnState.zoomValue;
269
331
  $(ZOOM_PERCENT).text(`${finalDisplay}%`);
@@ -275,8 +337,21 @@ export var drawGraph = function (workbook) {
275
337
  $(ZOOM_INPUT).val(finalDisplay);
276
338
  }
277
339
 
278
- $(ZOOM_SLIDER).val((finalDisplay <= ZOOM_DISPLAY_MIDDLE ? zoomScaleSliderLeft : zoomScaleSliderRight)
279
- .invert(finalDisplay));
340
+ // This controls movement of slider and is where the zoomSlider can be restricted
341
+ if (adaptive || (!adaptive && flexZoomInBounds(calcGraphZoom))) {
342
+ if (!adaptive) {
343
+ // Recenter graph when zooming to ensure that graph stays in viewport
344
+ center();
345
+ updateZoomContainerInfo();
346
+ }
347
+
348
+ $(ZOOM_SLIDER).val(
349
+ (finalDisplay <= ZOOM_DISPLAY_MIDDLE
350
+ ? zoomScaleSliderLeft
351
+ : zoomScaleSliderRight
352
+ ).invert(finalDisplay)
353
+ );
354
+ }
280
355
  };
281
356
 
282
357
  /**
@@ -317,10 +392,15 @@ export var drawGraph = function (workbook) {
317
392
  }).blur(() => $(ZOOM_INPUT).val(grnState.zoomValue));
318
393
 
319
394
  d3.select(ZOOM_SLIDER).on("input", function () {
320
- const sliderValue = +$(this).val();
395
+ const sliderValue = $(this).val();
396
+ prevGrnstateZoomVal = grnState.zoomValue;
397
+
321
398
  grnState.zoomValue = Math.floor(
322
- (sliderValue <= sliderMidpoint ? zoomScaleSliderLeft : zoomScaleSliderRight)(sliderValue)
399
+ (sliderValue <= sliderMidpoint
400
+ ? zoomScaleSliderLeft
401
+ : zoomScaleSliderRight)(sliderValue)
323
402
  );
403
+
324
404
  updateAppBasedOnZoomValue();
325
405
  }).on("mousedown", function () {
326
406
  manualZoom = true;
@@ -359,20 +439,13 @@ export var drawGraph = function (workbook) {
359
439
  var restrictGraphToViewport = function (fixed) {
360
440
  if (!fixed) {
361
441
  $("#restrict-graph-to-viewport span").removeClass("glyphicon-ok");
362
- $(document).ready(function () {
363
- $(".scale-and-scroll").show();
364
- });
365
442
  $("input[name=viewport]").removeProp("checked");
366
- $container.addClass("cursorGrabbing");
367
443
  adaptive = true;
368
- d3.select("rect").attr("stroke", "none");
444
+ flexibleContainer = null;
369
445
  center();
370
- } else if (fixed) {
446
+ } else {
371
447
  $("#restrict-graph-to-viewport span").addClass("glyphicon-ok");
372
448
  $("input[name=viewport]").prop("checked", "checked");
373
- $(document).ready(function () {
374
- $(".scale-and-scroll").hide();
375
- });
376
449
  adaptive = false;
377
450
  $container.removeClass(CURSOR_CLASSES);
378
451
  if (grnState.zoomValue > ZOOM_DISPLAY_MIDDLE) {
@@ -382,9 +455,9 @@ export var drawGraph = function (workbook) {
382
455
  }
383
456
  width = $container.width();
384
457
  height = $container.height();
385
- d3.select("rect").attr("stroke", "#9A9A9A")
386
- .attr("width", width)
387
- .attr("height", height);
458
+ d3.select("rect")
459
+ .attr("width", width)
460
+ .attr("height", height);
388
461
  $(".boundingBox").attr("width", width).attr("height", height);
389
462
  center();
390
463
  }
@@ -407,10 +480,17 @@ export var drawGraph = function (workbook) {
407
480
  zoom.translateTo(zoomContainer, viewportWidth / 2, viewportHeight / 2);
408
481
  }
409
482
 
483
+ // move: Moves graph with D-pad
410
484
  function move (direction) {
411
- var width = direction === "left" ? -50 : (direction === "right" ? 50 : 0);
412
- var height = direction === "up" ? -50 : (direction === "down" ? 50 : 0);
413
- zoom.translateBy(zoomContainer, width, height);
485
+ var moveWidth = direction === "left" ? -50 : direction === "right" ? 50 : 0;
486
+ var moveHeight = direction === "up" ? -50 : direction === "down" ? 50 : 0;
487
+ if (adaptive) {
488
+ zoom.translateBy(zoomContainer, moveWidth, moveHeight);
489
+ } else if (!adaptive) {
490
+ if (viewportBoundsMoveDrag(graphZoom, moveWidth, moveHeight)) {
491
+ zoom.translateBy(zoomContainer, moveWidth, moveHeight);
492
+ }
493
+ }
414
494
  }
415
495
 
416
496
  var defs = boundingBoxContainer.append("defs");
@@ -595,70 +675,72 @@ export var drawGraph = function (workbook) {
595
675
  });
596
676
  } else {
597
677
  // Arrowheads
598
- if (d.strokeWidth === 2) {
599
- d.strokeWidth = 4;
600
- }
601
- defs.append("marker")
602
- .attr("id", "arrowhead" + selfRef + "_StrokeWidth" + d.strokeWidth + minimum)
603
- .attr("viewBox", "0 0 15 15")
604
- .attr("preserveAspectRatio", "xMinYMin meet")
605
- .attr("refX", function () {
606
- // Individual offsets for each possible stroke width
607
- return ((x1 === x2 && y1 === y2) ?
608
- {
609
- 2: 2, 3: 10.5, 4: 11, 5: 9, 6: 9, 7: 10,
610
- 8: 9.8, 9: 9.1, 10: 10, 11: 9.5, 12: 9, 13: 8.3,
611
- 14: 8.3
612
- } : {
613
- 2: 11.75, 3: 11, 4: 9.75, 5: 9.25, 6: 8.5, 7: 10,
614
- 8: 9.75, 9: 9.5, 10: 9, 11: 9.5, 12: 9.5, 13: 9.25,
615
- 14: 9
616
- }
617
- )[d.strokeWidth];
618
- })
619
- .attr("refY", function () {
620
- return ((x1 === x2 && y1 === y2) ?
621
- {
622
- 2: 6.7, 3: 5.45, 4: 5.3, 5: 5.5, 6: 5, 7: 5.4,
623
- 8: 5.65, 9: 6, 10: 5.7, 11: 5.5, 12: 5.9, 13: 6,
624
- 14: 6
625
- } : {
626
- 2: 5, 3: 5, 4: 4.8, 5: 5, 6: 5, 7: 4.98,
627
- 8: 4.9, 9: 5.2, 10: 4.85, 11: 4.7, 12: 5.15,
628
- 13: 5, 14: 5.3
629
- }
630
- )[d.strokeWidth];
631
- })
632
- .attr("markerUnits", "userSpaceOnUse")
633
- .attr("markerWidth", function () {
634
- return 12 + ((d.strokeWidth < 7) ? d.strokeWidth * 2.25 : d.strokeWidth * 3);
635
- })
636
- .attr("markerHeight", function () {
637
- return 5 + ((d.strokeWidth < 7) ? d.strokeWidth * 2.25 : d.strokeWidth * 3);
638
- })
639
- .attr("orient", function () {
640
- return (x1 === x2 && y1 === y2) ?
678
+ if (grnState.mode === NETWORK_GRN_MODE) {
679
+ if (d.strokeWidth === 2) {
680
+ d.strokeWidth = 4;
681
+ }
682
+ defs.append("marker")
683
+ .attr("id", "arrowhead" + selfRef + "_StrokeWidth" + d.strokeWidth + minimum)
684
+ .attr("viewBox", "0 0 15 15")
685
+ .attr("preserveAspectRatio", "xMinYMin meet")
686
+ .attr("refX", function () {
687
+ // Individual offsets for each possible stroke width
688
+ return ((x1 === x2 && y1 === y2) ?
641
689
  {
642
- 2: 270, 3: 270, 4: 268, 5: 264, 6: 268, 7: 252,
643
- 8: 248, 9: 243, 10: 240, 11: 240, 12: 235, 13: 233,
644
- 14: 232
645
- }[d.strokeWidth] : "auto";
646
- })
647
- .append("path")
648
- .attr("d", "M 0 0 L 14 5 L 0 10 Q 6 5 0 0")
649
- .attr("style", function () {
650
- if (unweighted || !grnState.colorOptimal) {
651
- color = "black";
652
- } else if ( normalize(d) <= grayThreshold) {
653
- color = "gray";
654
- } else {
655
- color = d.stroke;
656
- }
657
- return "stroke: " + color + "; fill: " + color;
658
- });
690
+ 2: 2, 3: 10.5, 4: 11, 5: 9, 6: 9, 7: 10,
691
+ 8: 9.8, 9: 9.1, 10: 10, 11: 9.5, 12: 9, 13: 8.3,
692
+ 14: 8.3
693
+ } : {
694
+ 2: 11.75, 3: 11, 4: 9.75, 5: 9.25, 6: 8.5, 7: 10,
695
+ 8: 9.75, 9: 9.5, 10: 9, 11: 9.5, 12: 9.5, 13: 9.25,
696
+ 14: 9
697
+ }
698
+ )[d.strokeWidth];
699
+ })
700
+ .attr("refY", function () {
701
+ return ((x1 === x2 && y1 === y2) ?
702
+ {
703
+ 2: 6.7, 3: 5.45, 4: 5.3, 5: 5.5, 6: 5, 7: 5.4,
704
+ 8: 5.65, 9: 6, 10: 5.7, 11: 5.5, 12: 5.9, 13: 6,
705
+ 14: 6
706
+ } : {
707
+ 2: 5, 3: 5, 4: 4.8, 5: 5, 6: 5, 7: 4.98,
708
+ 8: 4.9, 9: 5.2, 10: 4.85, 11: 4.7, 12: 5.15,
709
+ 13: 5, 14: 5.3
710
+ }
711
+ )[d.strokeWidth];
712
+ })
713
+ .attr("markerUnits", "userSpaceOnUse")
714
+ .attr("markerWidth", function () {
715
+ return 12 + ((d.strokeWidth < 7) ? d.strokeWidth * 2.25 : d.strokeWidth * 3);
716
+ })
717
+ .attr("markerHeight", function () {
718
+ return 5 + ((d.strokeWidth < 7) ? d.strokeWidth * 2.25 : d.strokeWidth * 3);
719
+ })
720
+ .attr("orient", function () {
721
+ return (x1 === x2 && y1 === y2) ?
722
+ {
723
+ 2: 270, 3: 270, 4: 268, 5: 264, 6: 268, 7: 252,
724
+ 8: 248, 9: 243, 10: 240, 11: 240, 12: 235, 13: 233,
725
+ 14: 232
726
+ }[d.strokeWidth] : "auto";
727
+ })
728
+ .append("path")
729
+ .attr("d", "M 0 0 L 14 5 L 0 10 Q 6 5 0 0")
730
+ .attr("style", function () {
731
+ if (unweighted || !grnState.colorOptimal) {
732
+ color = "black";
733
+ } else if ( normalize(d) <= grayThreshold) {
734
+ color = "gray";
735
+ } else {
736
+ color = d.stroke;
737
+ }
738
+ return "stroke: " + color + "; fill: " + color;
739
+ });
740
+ }
659
741
  }
742
+ return "url(#" + d.type + selfRef + "_StrokeWidth" + d.strokeWidth + minimum + ")";
660
743
  }
661
- return "url(#" + d.type + selfRef + "_StrokeWidth" + d.strokeWidth + minimum + ")";
662
744
  });
663
745
 
664
746
  if (workbook.sheetType === "weighted") {
@@ -1008,13 +1090,21 @@ export var drawGraph = function (workbook) {
1008
1090
  .append("g")
1009
1091
  .selectAll(".coloring")
1010
1092
  .data(function () {
1011
- if (grnState.workbook.expression[dataset].data[p.name]) {
1012
- const result = getExpressionData(p.name, dataset, average);
1013
- timePoints = result.timePoints;
1014
- return result.data;
1015
- } else {
1016
- return 0;
1093
+ if (grnState.workbook.expression[dataset]) {
1094
+ const geneName = adjustGeneNameForExpression(p);
1095
+ if (
1096
+ grnState.workbook.expression[dataset].data[geneName]
1097
+ ) {
1098
+ const result = getExpressionData(
1099
+ geneName,
1100
+ dataset,
1101
+ average
1102
+ );
1103
+ timePoints = result.timePoints;
1104
+ return result.data || [];
1105
+ }
1017
1106
  }
1107
+ return [];
1018
1108
  })
1019
1109
  .attr("class", "coloring")
1020
1110
  .enter().append("rect")
@@ -1032,6 +1122,9 @@ export var drawGraph = function (workbook) {
1032
1122
  .attr("stroke-width", "0px")
1033
1123
  .style("fill", function (d) {
1034
1124
  d = d || 0; // missing values are changed to 0
1125
+ if (d === 0) {
1126
+ return "white";
1127
+ }
1035
1128
  var scale = d3.scaleLinear()
1036
1129
  .domain([-logFoldChangeMaxValue, logFoldChangeMaxValue])
1037
1130
  .range([0, 1]);
@@ -1141,15 +1234,6 @@ export var drawGraph = function (workbook) {
1141
1234
  }
1142
1235
  };
1143
1236
 
1144
- const hasExpressionData = sheets => {
1145
- for (var property in sheets) {
1146
- if (property.match(ENDS_IN_EXPRESSION_REGEXP)) {
1147
- return true;
1148
- }
1149
- }
1150
- return false;
1151
- };
1152
-
1153
1237
  if (!$.isEmptyObject(workbook.expression) && hasExpressionData(workbook.expression) &&
1154
1238
  grnState.nodeColoring.topDataset !== undefined) {
1155
1239
  updaters.renderNodeColoring();
@@ -1297,6 +1381,120 @@ export var drawGraph = function (workbook) {
1297
1381
  }
1298
1382
  };
1299
1383
 
1384
+ function viewportBoundsMoveDrag (graphZoom, dx, dy) {
1385
+ updateZoomContainerInfo();
1386
+ flexibleContainer = calcFlexiBox();
1387
+
1388
+ if (
1389
+ flexibleContainer.x + flexibleContainer.width + dx >=
1390
+ -xTranslation / graphZoom +
1391
+ BOUNDARY_MARGIN / 2 +
1392
+ width / graphZoom -
1393
+ BOUNDARY_MARGIN
1394
+ ) {
1395
+ return false;
1396
+ }
1397
+
1398
+ if (flexibleContainer.x + dx <= getLeftXBoundaryMargin()) {
1399
+ return false;
1400
+ }
1401
+
1402
+ if (
1403
+ flexibleContainer.y + flexibleContainer.height + dy >=
1404
+ -yTranslation / graphZoom +
1405
+ BOUNDARY_MARGIN / 2 +
1406
+ height / graphZoom -
1407
+ BOUNDARY_MARGIN
1408
+ ) {
1409
+ return false;
1410
+ }
1411
+
1412
+ if (flexibleContainer.y + dy <= getTopYBoundaryMargin()) {
1413
+ return false;
1414
+ }
1415
+
1416
+ return true;
1417
+ }
1418
+
1419
+ function calcFlexiBox () {
1420
+ const nodes = simulation.nodes();
1421
+ let nodeWidth = 0;
1422
+ if (nodes.length > 0) {
1423
+ nodeWidth = nodes[0].textWidth + 8;
1424
+ }
1425
+
1426
+ const xValuesNodes = nodes.map((node) => node.x);
1427
+ const yValuesNodes = nodes.map((node) => node.y);
1428
+
1429
+ let minX = Math.min(...xValuesNodes);
1430
+ let maxX = Math.max(...xValuesNodes) + nodeWidth;
1431
+
1432
+ let minY = Math.min(...yValuesNodes);
1433
+ let maxY = Math.max(...yValuesNodes) + nodeHeight;
1434
+
1435
+ // Handle left x and top y boundaries to not exceed graph BOUNDARY_MARGINs
1436
+ const BOUNDARY_MARGIN_X_L = getLeftXBoundaryMargin();
1437
+ const BOUNDARY_MARGIN_Y_T = getTopYBoundaryMargin();
1438
+ minX = minX < BOUNDARY_MARGIN_X_L ? BOUNDARY_MARGIN_X_L : minX;
1439
+ minY = minY < BOUNDARY_MARGIN_Y_T ? BOUNDARY_MARGIN_Y_T : minY;
1440
+
1441
+ maxX =
1442
+ maxX > -xTranslation / graphZoom + BOUNDARY_MARGIN / 2 + width / graphZoom - BOUNDARY_MARGIN
1443
+ ? -xTranslation / graphZoom + BOUNDARY_MARGIN / 2 + width / graphZoom - BOUNDARY_MARGIN
1444
+ : maxX;
1445
+
1446
+ maxY =
1447
+ maxY > -yTranslation / graphZoom + BOUNDARY_MARGIN / 2 + height / graphZoom - BOUNDARY_MARGIN
1448
+ ? -yTranslation / graphZoom + BOUNDARY_MARGIN / 2 + height / graphZoom - BOUNDARY_MARGIN
1449
+ : maxY;
1450
+
1451
+ let flexiBoxWidth = maxX - minX;
1452
+ if (maxX < 0 && minX < 0) {
1453
+ flexiBoxWidth = Math.abs(maxX) - Math.abs(minX);
1454
+ }
1455
+
1456
+ let flexiBoxHeight = maxY - minY;
1457
+ if (maxY < 0 && minY < 0) {
1458
+ flexiBoxHeight = Math.abs(maxY) - Math.abs(minY);
1459
+ }
1460
+
1461
+ boundingBoxRect
1462
+ .attr("x", -xTranslation / graphZoom + BOUNDARY_MARGIN / 2)
1463
+ .attr("width", width / graphZoom - BOUNDARY_MARGIN)
1464
+ .attr("y", -yTranslation / graphZoom + BOUNDARY_MARGIN / 2)
1465
+ .attr("height", height / graphZoom - BOUNDARY_MARGIN);
1466
+
1467
+ flexibleContainerRect
1468
+ .attr("x", minX)
1469
+ .attr("y", minY)
1470
+ .attr("width", flexiBoxWidth)
1471
+ .attr("height", flexiBoxHeight);
1472
+ return {x: minX, y: minY, maxX: maxX, maxY: maxY, width: flexiBoxWidth, height: flexiBoxHeight};
1473
+ }
1474
+
1475
+ // Checks if zoomValue is in bounds when zoom in and out
1476
+ function flexZoomInBounds (zoomValue) {
1477
+ if (flexibleContainer) {
1478
+ updateZoomContainerInfo();
1479
+ flexibleContainer = calcFlexiBox();
1480
+ if (flexibleContainer.width * zoomValue > width ) {
1481
+ return false;
1482
+ } else if (flexibleContainer.height * zoomValue > height) {
1483
+ return false;
1484
+ }
1485
+ }
1486
+ return true;
1487
+ }
1488
+
1489
+ // Only calculate Left and Top boundary margins because calculate rightboundary and bottomboundary in tick
1490
+ function getLeftXBoundaryMargin () {
1491
+ return !adaptive && flexibleContainer ? -xTranslation / graphZoom + BOUNDARY_MARGIN / 2 : BOUNDARY_MARGIN;
1492
+ }
1493
+
1494
+ function getTopYBoundaryMargin () {
1495
+ return !adaptive && flexibleContainer ? -yTranslation / graphZoom + BOUNDARY_MARGIN / 2 : BOUNDARY_MARGIN;
1496
+ }
1497
+
1300
1498
  // Tick only runs while the graph physics are still running.
1301
1499
  // (I.e. when the graph is completely relaxed, tick stops running.)
1302
1500
  function tick () {
@@ -1310,38 +1508,53 @@ export var drawGraph = function (workbook) {
1310
1508
  var getSelfReferringRadius = function (edge) {
1311
1509
  return edge ? 17 + (getEdgeThickness(edge) / 2) : 0;
1312
1510
  };
1313
- var BOUNDARY_MARGIN = 5;
1511
+
1314
1512
  var SELF_REFERRING_Y_OFFSET = 6;
1315
1513
  var MAX_WIDTH = 5000;
1316
1514
  var MAX_HEIGHT = 5000;
1317
1515
  var OFFSET_VALUE = 5;
1318
1516
 
1517
+ if (!adaptive) {
1518
+ flexibleContainer = calcFlexiBox();
1519
+ }
1520
+
1521
+ // this controls movement and position of nodes, clamps the nodes to boundary
1319
1522
  try {
1320
1523
  node.attr("x", function (d) {
1321
1524
  var selfReferringEdge = getSelfReferringEdge(d);
1322
-
1323
1525
  var selfReferringEdgeWidth = (selfReferringEdge ? getSelfReferringRadius(selfReferringEdge) +
1324
1526
  selfReferringEdge.strokeWidth + 2 : 0);
1325
1527
  var rightBoundary = width - (d.textWidth + OFFSET_VALUE) - BOUNDARY_MARGIN - selfReferringEdgeWidth;
1326
- var currentXPos = Math.max(BOUNDARY_MARGIN, Math.min(rightBoundary, d.x));
1528
+ if (!adaptive) {
1529
+ rightBoundary =
1530
+ -xTranslation / graphZoom +
1531
+ BOUNDARY_MARGIN / 2 +
1532
+ width / graphZoom -
1533
+ BOUNDARY_MARGIN -
1534
+ (d.textWidth + OFFSET_VALUE) -
1535
+ selfReferringEdgeWidth;
1536
+ }
1537
+ // currentXPos bounds the graph when toggle to !adaptive and moves each of the nodes to be in bounds
1538
+ var currentXPos = Math.max(getLeftXBoundaryMargin(), Math.min(rightBoundary, d.x));
1327
1539
  if (adaptive && width < MAX_WIDTH &&
1328
- (currentXPos === BOUNDARY_MARGIN || currentXPos === rightBoundary)) {
1540
+ (currentXPos === getLeftXBoundaryMargin() ||
1541
+ currentXPos === rightBoundary)
1542
+ ) {
1329
1543
  if (!d3.select(this).classed("fixed")) {
1330
1544
  width += OFFSET_VALUE;
1331
1545
  boundingBoxContainer.attr("width", width);
1332
1546
 
1333
1547
  link
1334
- .attr("x1", function (d) {
1335
- return d.source.x;
1336
- })
1337
- .attr("x2", function (d) {
1338
- return d.target.x;
1339
- });
1548
+ .attr("x1", function (d) {
1549
+ return d.source.x;
1550
+ })
1551
+ .attr("x2", function (d) {
1552
+ return d.target.x;
1553
+ });
1340
1554
 
1341
- node
1342
- .attr("x", function (d) {
1343
- return d.x;
1344
- });
1555
+ node.attr("x", function (d) {
1556
+ return d.x;
1557
+ });
1345
1558
  }
1346
1559
  }
1347
1560
  return d.x = currentXPos;
@@ -1350,24 +1563,34 @@ export var drawGraph = function (workbook) {
1350
1563
  var selfReferringEdgeHeight = (selfReferringEdge ? getSelfReferringRadius(selfReferringEdge) +
1351
1564
  selfReferringEdge.strokeWidth + SELF_REFERRING_Y_OFFSET + 0.5 : 0);
1352
1565
  var bottomBoundary = height - nodeHeight - BOUNDARY_MARGIN - selfReferringEdgeHeight;
1353
- var currentYPos = Math.max(BOUNDARY_MARGIN, Math.min(bottomBoundary, d.y));
1566
+ if (!adaptive) {
1567
+ bottomBoundary =
1568
+ -yTranslation / graphZoom +
1569
+ BOUNDARY_MARGIN / 2 +
1570
+ height / graphZoom -
1571
+ BOUNDARY_MARGIN -
1572
+ nodeHeight -
1573
+ selfReferringEdgeHeight;
1574
+ }
1575
+ // currentYPos bounds the graph when toggle to !adaptive and moves each of the nodes to be in bounds
1576
+ var currentYPos = Math.max(getTopYBoundaryMargin(), Math.min(bottomBoundary, d.y));
1577
+
1354
1578
  if (adaptive && height < MAX_HEIGHT &&
1355
- (currentYPos === BOUNDARY_MARGIN || currentYPos === bottomBoundary)) {
1579
+ (currentYPos === getTopYBoundaryMargin() || currentYPos === bottomBoundary)) {
1356
1580
  if (!d3.select(this).classed("fixed")) {
1357
1581
  height += OFFSET_VALUE;
1358
1582
  boundingBoxContainer.attr("height", height);
1359
1583
  link
1360
- .attr("y1", function (d) {
1361
- return d.source.y;
1362
- })
1363
- .attr("y2", function (d) {
1364
- return d.target.y;
1365
- });
1584
+ .attr("y1", function (d) {
1585
+ return d.source.y;
1586
+ })
1587
+ .attr("y2", function (d) {
1588
+ return d.target.y;
1589
+ });
1366
1590
 
1367
- node
1368
- .attr("y", function (d) {
1369
- return d.y;
1370
- });
1591
+ node.attr("y", function (d) {
1592
+ return d.y;
1593
+ });
1371
1594
  }
1372
1595
  }
1373
1596
  return d.y = currentYPos;
@@ -1403,7 +1626,6 @@ export var drawGraph = function (workbook) {
1403
1626
  var ADDITIONAL_SHIFT = 0.07;
1404
1627
  var END_POINT_ADJUSTMENT = 1.2;
1405
1628
 
1406
-
1407
1629
  // Self edge.
1408
1630
  if (x1 === x2 && y1 === y2) {
1409
1631
  // Move the position of the loop.
@@ -1499,8 +1721,49 @@ export var drawGraph = function (workbook) {
1499
1721
  }
1500
1722
 
1501
1723
  function dragged (d) {
1502
- d.fx = d3.event.x;
1503
- d.fy = d3.event.y;
1724
+ if (!adaptive) {
1725
+ /* fx and fy stands for fixed x and y which is when node is fixed to a position or
1726
+ to prevent tick from adjusting position of node
1727
+ */
1728
+ // prevents nodes from being dragged outside of right boundary
1729
+ if (
1730
+ d3.event.x + d.textWidth <=
1731
+ -xTranslation / graphZoom + BOUNDARY_MARGIN / 2 +
1732
+ width / graphZoom -
1733
+ BOUNDARY_MARGIN
1734
+ ) {
1735
+ d.fx = d3.event.x;
1736
+ } else {
1737
+ d.fx =
1738
+ -xTranslation / graphZoom +
1739
+ BOUNDARY_MARGIN / 2 +
1740
+ width / graphZoom -
1741
+ BOUNDARY_MARGIN -
1742
+ d.textWidth;
1743
+ }
1744
+
1745
+ // prevent nodes from being dragged out of bottom boundary
1746
+ if (
1747
+ d3.event.y + nodeHeight <=
1748
+ -yTranslation / graphZoom +
1749
+ BOUNDARY_MARGIN / 2 +
1750
+ height / graphZoom -
1751
+ BOUNDARY_MARGIN
1752
+ ) {
1753
+ // fy stands for fixed y
1754
+ d.fy = d3.event.y;
1755
+ } else {
1756
+ d.fy =
1757
+ -yTranslation / graphZoom +
1758
+ BOUNDARY_MARGIN / 2 +
1759
+ height / graphZoom -
1760
+ BOUNDARY_MARGIN -
1761
+ nodeHeight;
1762
+ }
1763
+ } else {
1764
+ d.fx = d3.event.x;
1765
+ d.fy = d3.event.y;
1766
+ }
1504
1767
  }
1505
1768
 
1506
1769
  grnState.simulation = simulation;