63 |
64 |
69 |
70 |
75 |
--------------------------------------------------------------------------------
/app/components/charts/chart.css:
--------------------------------------------------------------------------------
1 | /* #vis { text-align: center; width: 700px;}
2 | #vis svg { width: 100%; height: 500px; } */
3 |
4 | #vis svg {
5 | position: absolute;
6 | top: 0;
7 | bottom: 0;
8 | right: 0;
9 | width: 100%;
10 | height: 100%;
11 |
12 | text-rendering: optimizeLegibility;
13 | shape-rendering: geometricPrecision;
14 |
15 | font-family: Helvetica, Arial, sans-serif;
16 | }
17 |
18 | .axis {
19 | stroke: #000;
20 | stroke-width: 1.5px;
21 | }
22 |
23 | .node {
24 | fill: #ccc;
25 | fill-opacity: 1;
26 | stroke: #333;
27 | stroke-width: 1px;
28 | cursor: pointer;
29 | }
30 |
31 | .node text {
32 | stroke: none;
33 | fill: #333;
34 | stroke-width: 1px;
35 | font-size: 10px;
36 | cursor: pointer;
37 | display: none;
38 | /* shape-rendering: crispEdges;
39 | writing-mode: tb;
40 | glyph-orientation-vertical: 0;
41 | kerning: -3; */
42 | }
43 |
44 | svg .label {
45 | cursor: pointer;
46 | }
47 |
48 | svg.labels .node text {
49 | display: inherit;
50 | }
51 |
52 | .node:hover text, .node.hover text, .node.fixed text {
53 | font-size: 18px !important;
54 | }
55 |
56 | .node.fixed circle, .node.fixed rect {
57 | stroke: red !important;
58 | stroke-width: 3px !important;
59 | }
60 |
61 | .link {
62 | fill: none;
63 | stroke: #666;
64 | stroke-width: 1.5px;
65 | opacity: 0.6;
66 | cursor: pointer;
67 | }
68 |
69 | svg.fixed .link {
70 | stroke: #666;
71 | opacity: 0.3;
72 | }
73 |
74 | svg.hover .link {
75 | stroke: #666 !important;
76 | opacity: 0.1 !important;
77 | }
78 |
79 | svg.hover .link.hover, .link:hover {
80 | stroke: blue !important;
81 | opacity: 1 !important;
82 | }
83 |
84 | svg.hover .node {
85 | opacity: 0.2 !important;
86 | }
87 |
88 | svg.hover .node.hover, svg.hover .node:hover {
89 | opacity: 1 !important;
90 | }
91 |
92 | .link.fixed {
93 | stroke: red !important;
94 | opacity: 1 !important;
95 | }
96 |
97 | .target-arrow {
98 | stroke-width: 1px;
99 | stroke: #666;
100 | fill: none;
101 | opacity: 1;
102 | }
103 |
104 | #legend {
105 | margin: 0 30px;
106 | display: inline-block;
107 | }
108 |
109 | #legend .rotate {
110 | height:30px;
111 | line-height: 30px;
112 | }
113 |
114 | #legend .node {
115 | display: inline-block;
116 | border: 0;
117 | width: 16px;
118 | height: 16px;
119 | margin-top: 0;
120 | border-radius: 8px;
121 | }
122 |
123 | #legend .node.square {
124 | border-radius: 0;
125 | }
126 |
127 | #legend .node.receptor {
128 | background-color: #3349ff;
129 | }
130 |
131 | #legend .node.ligand {
132 | background-color: #ed1940;
133 | }
134 |
135 | #legend .node.both {
136 | background-color: #a650e2;
137 | }
138 |
139 | .d3-tip {
140 | line-height: 1;
141 | font-weight: bold;
142 | padding: 12px;
143 | background: rgba(0, 0, 0, 0.8);
144 | color: #fff;
145 | border-radius: 2px;
146 | pointer-events:none !important;
147 | z-index: 100;
148 | position: absolute;
149 | top: 40px !important;
150 | right: 20px !important;
151 | width: 400px !important;
152 | left: auto !important;
153 | }
154 |
--------------------------------------------------------------------------------
/app/components/ui/main-directives.js:
--------------------------------------------------------------------------------
1 |
2 | (function() {
3 | 'use strict';
4 |
5 | angular.module('lrSpaApp')
6 |
7 | .filter('percentage', ['$filter', function($filter) {
8 | return function(input, decimals) {
9 | return $filter('number')(input*100, decimals)+'%';
10 | };
11 | }])
12 |
13 | .filter('min', function() {
14 | return function(input) {
15 | var out;
16 | if (input) {
17 | for (var i in input) {
18 | if (input[i] < out || out === undefined || out === null) {
19 | out = input[i];
20 | }
21 | }
22 | }
23 | return out;
24 | };
25 | })
26 |
27 | .filter('max', function() {
28 | return function(input) {
29 | var out;
30 | if (input) {
31 | for (var i in input) {
32 | if (input[i] > out || out === undefined || out === null) {
33 | out = input[i];
34 | }
35 | }
36 | }
37 | return out;
38 | };
39 | })
40 |
41 | .directive('graphItem', function() {
42 | return {
43 | scope: {
44 | item: '=graphItem',
45 | data: '=',
46 | graphData: '='
47 | },
48 | templateUrl: 'components/ui/item.html'
49 | };
50 | })
51 |
52 | .directive('neighborsList', function() {
53 | return {
54 | scope: {
55 | list: '=neighborsList',
56 | array: '=',
57 | title: '&',
58 | key: '&'
59 | },
60 | templateUrl: 'components/ui/neighbors-list-template.html',
61 | link: function (scope, element, attrs) {
62 | scope.limit = 3;
63 | scope.key = attrs.key;
64 |
65 | scope.hover = function(item, __) {
66 | if (item.ticked) {
67 | item.hover = __;
68 | }
69 | };
70 |
71 | scope.click = function(item) {
72 | item.ticked = !item.ticked;
73 | };
74 |
75 | scope.expand = function() {
76 | scope.limit = (scope.limit + 10 < scope.list.length) ? scope.limit + 10 : scope.list.length;
77 | };
78 |
79 | }
80 | };
81 | })
82 |
83 | .directive('expressionList', function() {
84 | return {
85 | scope: {
86 | list: '=expressionList',
87 | array: '=',
88 | key: '&'
89 | },
90 | templateUrl: 'components/ui/gene-list-template.html',
91 | link: function (scope, element, attrs) {
92 | scope.limit = 3;
93 |
94 | attrs.key = attrs.key || 'gene'; // todo: change
95 | scope.key = attrs.key;
96 |
97 | scope.get = function(_) {
98 | var __ = _[attrs.key];
99 |
100 | if (typeof __ === 'number' && attrs.array) { // this is an id
101 | return scope.array[__];
102 | } else {
103 | return __;
104 | }
105 | };
106 |
107 | scope.hover = function(item, __) {
108 | if (item.ticked) {
109 | item.hover = __;
110 | }
111 | };
112 |
113 | scope.click = function(item) {
114 | item.ticked = !item.ticked;
115 | };
116 |
117 | scope.expand = function() {
118 | scope.limit = (scope.limit + 10 < scope.list.length) ? scope.limit + 10 : scope.list.length;
119 | };
120 |
121 | }
122 | };
123 | });
124 |
125 | })();
126 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.10/config/configuration-file.html
3 |
4 | module.exports = function(config) {
5 | config.set({
6 | // base path, that will be used to resolve files and exclude
7 | basePath: 'app',
8 |
9 | // testing framework to use (jasmine/mocha/qunit/...)
10 | frameworks: ['jasmine'],
11 |
12 | // list of files / patterns to load in the browser
13 | files: [
14 | 'bower_components/jquery/dist/jquery.js',
15 | 'bower_components/angular/angular.js',
16 | 'bower_components/angular-mocks/angular-mocks.js',
17 | 'bower_components/_F/_F.js',
18 | 'bower_components/d3/d3.js',
19 | 'components/charts/core.js',
20 | 'bower_components/angular-sanitize/angular-sanitize.js',
21 | 'bower_components/angular-loading-bar/src/loading-bar.js',
22 | 'bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
23 | 'bower_components/angular-ui-router/release/angular-ui-router.js',
24 | 'bower_components/ng-debounce/angular-debounce.js',
25 | //'bower_components/d3-plugins/hive/hive.js',
26 | //'bower_components/FileSaver/FileSaver.js',
27 | 'bower_components/angular-downloadsvg-directive/angular-downloadsvg-directive.js',
28 | 'bower_components/angular-growl/build/angular-growl.js',
29 | 'bower_components/angular-animate/angular-animate.js',
30 | 'bower_components/angular-slider-widget/angular-slider-widget.js',
31 | 'bower_components/angular-local-storage/angular-local-storage.js',
32 | 'bower_components/_F/_F.js',
33 | //'bower_components/lodash/dist/lodash.compat.js',
34 | 'bower_components/angular-dsv/angular-dsv.js',
35 | 'bower_components/angular-sanitize/angular-sanitize.js',
36 | 'bower_components/angular-bindonce/bindonce.js',
37 | 'bower_components/ng-context-menu/dist/ng-context-menu.js',
38 | 'bower_components/ng-grid/build/ng-grid.js',
39 | //'bower_components/marked/lib/marked.js',
40 | 'bower_components/angular-marked/angular-marked.js',
41 | //'bower_components/Blob.js/Blob.js',
42 | //'bower_components/chosen/chosen.jquery.js',
43 | 'bower_components/angular-chosen-localytics/chosen.js',
44 | 'bower_components/zeroclipboard/dist/ZeroClipboard.js',
45 | 'bower_components/ng-clip/dest/ng-clip.min.js',
46 | 'bower_components/ng-grid/plugins/ng-grid-flexible-height.js',
47 | 'components/*.js',
48 | 'components/**/*.js',
49 | //'test/mock/**/*.js',
50 | '../test/spec/**/*.js',
51 | {pattern: 'data/*.txt', watched: true, served: true, included: false}
52 | ],
53 |
54 | // list of files / patterns to exclude
55 | exclude: [],
56 |
57 | reporters: ['mocha'],
58 |
59 | // web server port
60 | port: 8080,
61 |
62 | // level of logging
63 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
64 | logLevel: config.LOG_INFO,
65 |
66 | // enable / disable watching file and executing tests whenever any file changes
67 | autoWatch: false,
68 |
69 | // Start these browsers, currently available:
70 | // - Chrome
71 | // - ChromeCanary
72 | // - Firefox
73 | // - Opera
74 | // - Safari (only Mac)
75 | // - PhantomJS
76 | // - IE (only Windows)
77 | browsers: ['PhantomJS'],
78 |
79 | // Continuous Integration mode
80 | // if true, it capture browsers, run tests and exit
81 | singleRun: false
82 | });
83 | };
84 |
--------------------------------------------------------------------------------
/app/data/ontology.txt:
--------------------------------------------------------------------------------
1 | Cell Ontology
2 | Adipocyte Breast mesenchymal
3 | Adipocyte Omental mesenchymal
4 | Adipocyte Perirenal mesenchymal
5 | Adipocyte Subcutaneous mesenchymal
6 | Alveolar Epithelial epithelial
7 | Amniotic Epithelial epithelial
8 | Amniotic Membrane epithelial
9 | Annulus Pulposus other
10 | Astrocyte Cerebellum nervous system
11 | Astrocyte Cerebral Cortex nervous system
12 | Basophils hematopoietic
13 | Bronchial Epithelial epithelial
14 | Cardiac Myocyte mesenchymal
15 | CD133+ Haematopoietic stem cell hematopoietic
16 | CD14-CD16+ Monocytes hematopoietic
17 | CD14+ Monocyte derived Endothelial progenitor hematopoietic
18 | CD14+ Monocytes hematopoietic
19 | CD14+CD16- Monocytes hematopoietic
20 | CD14+CD16+ Monocytes hematopoietic
21 | CD19+ B cells hematopoietic
22 | CD34+ Haematopoietic stem cell hematopoietic
23 | CD34+ progenitor hematopoietic
24 | CD4+ T cells hematopoietic
25 | CD4+CD25-CD45RA- memory conventional T cells hematopoietic
26 | CD4+CD25-CD45RA+ naive conventional T cells hematopoietic
27 | CD4+CD25+CD45RA- memory regulatory T cells hematopoietic
28 | CD4+CD25+CD45RA+ naive regulatory T cells hematopoietic
29 | CD8+ T cells hematopoietic
30 | Chondroblast mesenchymal
31 | Chondrocyte mesenchymal
32 | Chorionic Membrane epithelial
33 | Ciliary Epithelial epithelial
34 | Corneal Epithelial epithelial
35 | Dendritic Monocyte Immature derived hematopoietic
36 | Dendritic Plasmacytoid hematopoietic
37 | ES cells other
38 | Endothelial Aortic endothelial
39 | Endothelial Artery endothelial
40 | Endothelial Lymphatic endothelial
41 | Endothelial Microvascular endothelial
42 | Endothelial Thoracic endothelial
43 | Endothelial Umbilical Vein endothelial
44 | Endothelial Vein endothelial
45 | Eosinophils hematopoietic
46 | Esophageal Epithelial epithelial
47 | Fibroblast Aortic Adventitial mesenchymal
48 | Fibroblast Cardiac mesenchymal
49 | Fibroblast Choroid Plexus mesenchymal
50 | Fibroblast Conjunctival mesenchymal
51 | Fibroblast Dermal mesenchymal
52 | Fibroblast Gingival mesenchymal
53 | Fibroblast Lymphatic mesenchymal
54 | Fibroblast Periodontal Ligament mesenchymal
55 | Fibroblast Pulmonary Artery mesenchymal
56 | Fibroblast Skin Normal mesenchymal
57 | Gingival Epithelial epithelial
58 | Granulocyte Macrophage progenitor hematopoietic
59 | Hair Follicle Dermal Papilla epithelial
60 | Hair Follicle Outer Root Sheath epithelial
61 | Hepatic Sinusoidal Endothelial endothelial
62 | Hepatic Stellate mesenchymal
63 | Hepatocyte epithelial
64 | Immature Langerhans hematopoietic
65 | Intestinal Epithelial (polarized) epithelial
66 | Iris Pigment Epithelial epithelial
67 | Keratinocyte Epidermal epithelial
68 | Keratinocyte Oral epithelial
69 | Keratocytes epithelial
70 | Lens Epithelial epithelial
71 | Macrophage Monocyte derived hematopoietic
72 | Mallassez derived epithelial
73 | Mammary Epithelial epithelial
74 | Mast cells hematopoietic
75 | Mast cells stimulated hematopoietic
76 | Mature Adipocyte mesenchymal
77 | Melanocyte epithelial
78 | Meningeal other
79 | Mesenchymal precursor Adipose mesenchymal
80 | Mesenchymal precursor Bone Marrow mesenchymal
81 | Mesenchymal precursor Cardiac mesenchymal
82 | Mesenchymal stem cell Adipose mesenchymal
83 | Mesenchymal stem cell Amniotic Membrane mesenchymal
84 | Mesenchymal stem cell Bone Marrow mesenchymal
85 | Mesenchymal stem cell Hepatic mesenchymal
86 | Mesenchymal stem cell Umbilical mesenchymal
87 | Mesenchymal stem cell Wharton's Jelly mesenchymal
88 | Mesothelial mesenchymal
89 | Migratory Langerhans hematopoietic
90 | Multipot. Cord Blood Unrestrict. Somatic stem cell hematopoietic
91 | Myoblast mesenchymal
92 | Myotube mesenchymal
93 | Nasal Epithelial epithelial
94 | NK cells hematopoietic
95 | Neural stem cell nervous system
96 | Neurons nervous system
97 | Neutrophils hematopoietic
98 | Nucleus Pulposus mesenchymal
99 | Olfactory Epithelial epithelial
100 | Osteoblast mesenchymal
101 | Osteocyte mesenchymal
102 | Pancreatic Stromal mesenchymal
103 | Pericytes mesenchymal
104 | Placental Epithelial epithelial
105 | Preadipocyte Breast mesenchymal
106 | Preadipocyte Omental mesenchymal
107 | Preadipocyte Perirenal mesenchymal
108 | Preadipocyte Subcutaneous mesenchymal
109 | Preadipocyte Visceral mesenchymal
110 | Prostate Epithelial epithelial
111 | Prostate Stromal mesenchymal
112 | Renal Cortical Epithelial epithelial
113 | Renal Epithelial epithelial
114 | Renal Glomerular Endothelial endothelial
115 | Renal Mesangial mesenchymal
116 | Renal Proximal Tubular Epithelial epithelial
117 | Reticulocyte hematopoietic
118 | Retinal Pigment Epithelial epithelial
119 | Salivary Acinar epithelial
120 | Sebocyte epithelial
121 | Sertoli other
122 | Skeletal Muscle mesenchymal
123 | Skeletal Muscle Satellite mesenchymal
124 | Small Airway Epithelial epithelial
125 | Smooth Muscle Aortic mesenchymal
126 | Smooth Muscle Brachiocephalic mesenchymal
127 | Smooth Muscle Brain Vascular mesenchymal
128 | Smooth Muscle Bronchial mesenchymal
129 | Smooth Muscle Carotid mesenchymal
130 | Smooth Muscle Colonic mesenchymal
131 | Smooth Muscle Coronary Artery mesenchymal
132 | Smooth Muscle Esophageal mesenchymal
133 | Smooth Muscle Internal Thoracic Artery mesenchymal
134 | Smooth Muscle Prostate mesenchymal
135 | Smooth Muscle Pulmonary Artery mesenchymal
136 | Smooth Muscle Subclavian Artery mesenchymal
137 | Smooth Muscle Tracheal mesenchymal
138 | Smooth Muscle Umbilical Artery mesenchymal
139 | Smooth Muscle Umbilical Vein mesenchymal
140 | Smooth Muscle Uterine mesenchymal
141 | Synoviocyte mesenchymal
142 | Tenocyte mesenchymal
143 | Trabecular Meshwork other
144 | Tracheal Epithelial epithelial
145 | Urothelial epithelial
146 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # connectome
2 |
3 | Web tool for exploring cell-cell ligand-receptor mediated communication networks
4 |
5 | ## Introduction
6 |
7 | In Ramilowski et al. ‘A draft network of ligand-receptor mediated multicellular signaling in human’ 2015 [doi:10.1038/ncomms8866](http://dx.doi.org/10.1038/ncomms8866) we present the first large-scale map of cell-to-cell communication between 144 human primary cell types using 2,422 putative and literature supported ligand-receptor pairs. With up to hundreds of potential interactions between any two of these 144 primary cell types, there are millions of possible cell-cell communication paths across the entire network. Static visualization of such complex networks not only can be obscure and impractical but also difficult. With that, and to benefit the research community, we provide an online resource that visualizes, on demand, our cell-cell communication network for any given subset of the ligand-receptor pairs and profiled primary cells. An online version of the resource is located at: [Ramilowski_et_al_2015](http://fantom.gsc.riken.jp/5/suppl/Ramilowski_et_al_2015/vis/) and mirrored at [forrest-lab.github.io/connectome](http://forrest-lab.github.io/connectome).
8 |
9 | We developed the online connectome visualization application using various open source and custom tools. The vector graphic visualization is generated using the [D3.js visualization library][d3]. The application interface was developed using the [AngularJS web application framework][angular] and the [twitter bootstrap front-end framework][twbs].
10 |
11 | The visualization interface takes the the expression files generated in this study along with other metadata in tabular format [Ramilowski_et_al_2015](http://fantom.gsc.riken.jp/5/suppl/Ramilowski_et_al_2015/) to generate the network/hive visualization as shown in figure 5 in the paper.
12 |
13 | # For Developers
14 |
15 | ## Background
16 |
17 | To install a copy of this application you will need [node and npm](http://nodejs.org/), Grunt, and Bower. If you are not familiar it would be worthwhile to read up on [node and npm](http://www.joyent.com/blog/installing-node-and-npm/), [Grunt](https://github.com/gruntjs/grunt/wiki/Getting-started) and [bower](http://bower.io/).
18 |
19 | ## Download and Install
20 |
21 | ```
22 | git clone https://github.com/Hypercubed/connectome.git
23 | cd connectome
24 | npm install
25 | bower install
26 | ```
27 |
28 | ## Summary of Directory Layout
29 |
30 | app/ --> all of the files to be used in development
31 | bower_components/ --> AngularJS and 3rd party JavaScript libraries installed using bower
32 | components/ --> Application components
33 | data/ --> data files
34 | test/ --> test source files and libraries
35 | package.json --> npm's config file
36 | bower.json --> bower's config file
37 | Gruntfile.js --> Grunt config file
38 | README.md --> This file
39 |
40 | ## Adding data
41 |
42 | This git repository include the data files that acompany the above referenced paper. You add your own into the app/data/ folder. All data files should be Tab-Seperated-Value (TSV) files.
43 |
44 | ## Grunt
45 |
46 | Grunt is a JavaScript based task runner. In this project Grunt is used for many tasks including testing, minification, and even deployment. If you are not familiar with Grunt please read the [Getting started guide](https://github.com/gruntjs/grunt/wiki/Getting-started).
47 |
48 | Summary of Grunt tasks:
49 |
50 | clean Clean files and folders.
51 | test Run all tests
52 | build Prepare project for deployment.
53 | serve Run a test server
54 | deploy Build and deploy to github
55 |
56 | ## Running the app during development
57 |
58 | Running `grunt serve` will run a test server on the local host and open your default web browser to http://localhost:9000/.
59 |
60 | # Contact
61 |
62 | For more information please contact J. Harshbarger
63 |
64 | ## Acknowledgments
65 |
66 | This work was supported by a research grant from the Japanese Ministry of Education, Culture, Sports, Science and Technology (MEXT) to the RIKEN Center for Life Science Technologies.
67 |
68 | ## Reference
69 |
70 | > **A draft network of ligand–receptor-mediated multicellular signalling in human**
71 |
72 | > Jordan A. Ramilowski, Tatyana Goldberg, Jayson Harshbarger, Edda Kloppman, Marina Lizio, Venkata P. Satagopam, Masayoshi Itoh, Hideya Kawaji, Piero Carninci, Burkhard Rost & Alistair R. R. Forrest
73 |
74 | > Nature Communications 6, Article number: 7866 [doi:10.1038/ncomms8866](http://doi.org/10.1038/ncomms8866)
75 |
76 | ## License
77 |
78 | [MIT License](http://en.wikipedia.org/wiki/MIT_License)
79 |
80 | Copyright (c) 2015 RIKEN, Japan.
81 |
82 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
83 |
84 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
85 |
86 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
87 |
88 |
89 | [d3]: http://d3js.org/ "Data-Driven Documents"
90 | [angular]: http://angularjs.org/ "AngularJS Framework"
91 | [twbs]: http://getbootstrap.com/2.3.2/ "Twitter bootstrap"
92 |
--------------------------------------------------------------------------------
/app/components/ui/panel-controller.js:
--------------------------------------------------------------------------------
1 |
2 | (function() {
3 | 'use strict';
4 |
5 | angular.module('lrSpaApp')
6 |
7 | .controller('PanelCtrl', function ($scope, localStorageService) {
8 |
9 | this.state = {
10 | info: false,
11 | data: false
12 | };
13 |
14 | // Panel state
15 | localStorageService.bind($scope, 'panelState', this.state);
16 |
17 | $scope.gridOptions = {};
18 |
19 | $scope.itemClicked = function(row) {
20 | //console.log(row.selectionProvider.selectedItems);
21 |
22 | if (row.entity.locked) {
23 | row.entity.ticked = false;
24 | }
25 |
26 | //if(row.entity.receptor && row.entity.ligand) {
27 | // row.entity.receptor.ticked = row.entity.ticked;
28 | // row.entity.ligand.ticked = row.entity.ticked;
29 | //}
30 |
31 | if (row.selected === true) {
32 | row.selectionProvider.selectedItems.forEach(function(d) {
33 | d.ticked = row.entity.ticked;
34 | d.locked = row.entity.locked;
35 |
36 | //if(d.receptor && d.ligand) {
37 | // d.receptor.ticked = row.entity.ticked;
38 | // d.ligand.ticked = row.entity.ticked;
39 | //}
40 |
41 | });
42 | }
43 | };
44 |
45 | var defaults = {
46 | showFooter: true,
47 | enableSorting: true,
48 | multiSelect: true,
49 | showFilter: true,
50 | showColumnMenu: false,
51 | showGroupPanel: false,
52 | enableCellSelection: false,
53 | selectWithCheckboxOnly: false,
54 | showSelectionCheckbox: true,
55 | enableColumnResize: true,
56 | sortInfo: { fields: ['2*locked + !ticked'], directions: ['asc'] },
57 | //groups: ['locked'],
58 | //groupsCollapsedByDefault: true,
59 | //rowTemplate: 'rowTemplate',
60 | //menuTemplate: 'menuTemplate',
61 | //checkboxCellTemplate: '
',
62 | checkboxHeaderTemplate: 'checkboxHeaderTemplate.html',
63 | beforeSelectionChange: function(row, e) { // Without shift or ctrl deselect previous
64 | if (!angular.isArray(row) && !e.ctrlKey && !e.shiftKey) {
65 | row.selectionProvider.toggleSelectAll(false,true);
66 | }
67 | return true;
68 | },
69 | columnDefs: [
70 | {
71 | //field:'ticked',
72 | field: '2*locked + !ticked',
73 | displayName:'Visible',
74 | width: 60,
75 | cellTemplate: 'cellTemplate',
76 | headerCellTemplate: 'visibleHeaderCellTemplate'
77 | }
78 | ]
79 | };
80 |
81 | $scope.gridOptions = {};
82 |
83 | $scope.gridOptions.cells = angular.extend({}, defaults, {
84 | data: 'data.cells',
85 | columnDefs: [
86 | defaults.columnDefs[0],
87 | {field:'name', displayName:'Cell Type'},
88 | {field:'meta.Ontology', displayName:'Ontology'}
89 | ]
90 | });
91 |
92 | $scope.gridOptions.genes = angular.extend({}, defaults, {
93 | data: 'data.genes',
94 | columnDefs: [
95 | defaults.columnDefs[0],
96 | {field:'name', displayName:'Gene Symbol'},
97 | {field:'description', width: '25%', displayName:'Gene Name'},
98 | {field:'class', displayName:'Class'},
99 | //{field:'age', displayName:'Age',cellFilter:'number'},
100 | //{field:'consensus', displayName:'Subcellular Localization'},
101 | {field:'hgncid', displayName:'HGNC ID',cellTemplate:'cellHGNCTemplate'},
102 | {field:'uniprotid', displayName:'UniProt ID', cellTemplate:'cellUniProtTemplate'},
103 | {field:'taxon', displayName:'Taxon'}
104 | ]
105 | });
106 |
107 | $scope.gridOptions.pairs = angular.extend({}, defaults, {
108 | data: 'data.pairs',
109 | columnDefs: [
110 | defaults.columnDefs[0],
111 | //{field:'ticked', displayName:'Visible', width: 60, cellTemplate: 'cellPairTemplate'},
112 | {field:'name', displayName:'Pair Name'},
113 | {field:'Ligand', displayName:'Ligand',cellTemplate: 'cellLigandTemplate'},
114 | {field:'Receptor', displayName:'Receptor',cellTemplate: 'cellReceptorTemplate'},
115 | {field:'Source', cellTemplate: 'cellSourceTemplate' /*, displayName:'Source' , */ },
116 | {field:'Evidence', cellTemplate: 'cellPubMedTemplate' /*, displayName:'Evidence' */}
117 | ]
118 | });
119 |
120 | //$scope.gridOptions.cells = angular.extend({}, defaultGridOptions,{
121 | // data: 'data.cells',
122 | // columnDefs: defaultGridOptions.columnDefs.concat([
123 | // {field:'meta.Ontology', displayName:'Ontology'}
124 | // ])
125 | //});
126 |
127 | /* $scope.gridOptions.genes = angular.extend({}, defaultGridOptions, {
128 | data: 'data.genes',
129 | columnDefs: defaultGridOptions.columnDefs.concat([
130 | {field:'class', displayName:'Type'},
131 | {field:'age', displayName:'Age'},
132 | {field:'taxon', displayName:'Taxon',cellFilter:'number'},
133 | {field:'consensus', displayName:'Consensus'},
134 | {field:'description', displayName:'Description'},
135 | {field:'hgncid', displayName:'HGNC ID'},
136 | {field:'uniprotid', displayName:'UniProt ID'}
137 | ])
138 | }); */
139 |
140 | /* $scope.gridOptions.pairs = angular.extend({}, defaultGridOptions, {
141 | data: 'data.pairs',
142 | columnDefs: defaultGridOptions.columnDefs.concat([
143 | {field:'Ligand', displayName:'Ligand',cellTemplate: 'cellLigandTemplate'},
144 | {field:'Receptor', displayName:'Receptor',cellTemplate: 'cellReceptorTemplate'},
145 | ])
146 | }); */
147 |
148 | });
149 |
150 | })();
151 |
--------------------------------------------------------------------------------
/app/components/ui/item.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{item.name}} {{item.class}}
4 |
5 |
6 |
12 |
18 |
19 | Taxon:
20 | UniProtID:
21 |
22 |
23 |
42 |
43 |
80 |
81 |
82 |
83 |
84 |
85 |
113 |
114 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
136 |
137 |
149 |
150 |
151 |
152 |
153 |
154 |
155 | Expression value: {{item.value | number:2}} TPM
156 |
157 | Specificity:
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/test/spec/services/ligandReceptorData.js:
--------------------------------------------------------------------------------
1 | /* global _F */
2 |
3 | 'use strict';
4 |
5 | describe('Service: ligandReceptorData', function () {
6 |
7 | var _name = _F('name');
8 | var _median = _F('median');
9 |
10 | // load the service's module
11 | beforeEach(module('lrSpaApp'));
12 |
13 | // instantiate service
14 | var $httpBackend, ligandReceptorData;
15 |
16 | beforeEach(function() {
17 |
18 | inject(function (_$httpBackend_, _ligandReceptorData_) {
19 |
20 | $httpBackend = _$httpBackend_;
21 | ligandReceptorData = _ligandReceptorData_;
22 |
23 | var pairs = ['Ligand','Receptor','Source','Databases'].join('\t')+'\n';
24 | pairs += ['l1','r1','source1','none'].join('\t')+'\n';
25 | pairs += ['l2','r2','source2','none'].join('\t');
26 |
27 | var expr = ['gene','c1','c2','c3'].join('\t')+'\n';
28 | expr += ['l1','0','100','11'].join('\t')+'\n';
29 | expr += ['r1','11','0','0'].join('\t')+'\n';
30 | expr += ['l2','9','0','0'].join('\t')+'\n';
31 | expr += ['r2','5','11','0'].join('\t');
32 |
33 | var ont = ['Cell','Ontology'].join('\t')+'\n';
34 | ont += ['c1','mesenchymal'].join('\t')+'\n';
35 | ont += ['c2','mesenchymal'].join('\t')+'\n';
36 | ont += ['c3','mesenchymal'].join('\t');
37 |
38 | var genes = ['ApprovedSymbol','ApprovedName','Class','UniProtID','HGNCID','Taxon'].join('\t')+'\n';
39 | genes += ['l1','l1-desc','ligand','id','id','tax'].join('\t')+'\n';
40 | genes += ['r1','r1-desc','receptor','id','id','tax'].join('\t')+'\n';
41 | genes += ['l2','l2-desc','ligand','id','id','tax'].join('\t')+'\n';
42 | genes += ['r2','r2-desc','receptor','id','id','tax'].join('\t')+'\n';
43 |
44 | $httpBackend.expectGET('data/LR.pairs.txt').respond(pairs);
45 | $httpBackend.expectGET('data/LR.expr.txt').respond(expr);
46 | $httpBackend.expectGET('data/ontology.txt').respond(ont);
47 | $httpBackend.expectGET('data/LR.genes.txt').respond(genes);
48 |
49 | });
50 | });
51 |
52 | it('should load files', function () {
53 |
54 | ligandReceptorData.load().then(function(data) {
55 | expect(data.expr.length).toEqual(5);
56 | //expect(data.cells.length).toEqual(3);
57 | //expect(data.pairs.length).toEqual(2);
58 | //expect(data.genes.length).toEqual(4);
59 |
60 |
61 | expect(data.cells.map(_name)).toEqual(['c1','c2','c3']);
62 | expect(data.genes.map(_name)).toEqual(['l1','r1','l2','r2']);
63 | expect(data.pairs.map(_name)).toEqual(['l1-r1','l2-r2']);
64 | expect(data.genes.map(_median)).toEqual([11, 0, 0, 5]);
65 | });
66 |
67 | $httpBackend.flush();
68 | });
69 |
70 | describe('getCells', function() {
71 | it('should return correct results', function () {
72 |
73 | ligandReceptorData.load().then(function() {
74 | expect(ligandReceptorData.getCells({}).length).toEqual(3);
75 | expect(ligandReceptorData.getCells({ name: 'c1' }).length).toEqual(1);
76 | expect(ligandReceptorData.getCells({ name: 'c4' }).length).toEqual(0);
77 | });
78 |
79 | $httpBackend.flush();
80 | });
81 | });
82 |
83 | describe('getGenes', function() {
84 | it('should return correct results', function () {
85 |
86 | ligandReceptorData.load().then(function() {
87 | expect(ligandReceptorData.getGenes({}).length).toEqual(4);
88 | expect(ligandReceptorData.getGenes({ name: 'l1' }).length).toEqual(1);
89 | expect(ligandReceptorData.getGenes({ name: 'l3' }).length).toEqual(0);
90 | expect(ligandReceptorData.getGenes({ class: 'ligand' }).length).toEqual(2);
91 | });
92 |
93 | $httpBackend.flush();
94 | });
95 | });
96 |
97 | describe('getPairs', function() {
98 | it('should return correct results', function () {
99 |
100 | ligandReceptorData.load().then(function() {
101 | expect(ligandReceptorData.getPairs({}).length).toEqual(2);
102 | expect(ligandReceptorData.getPairs({ name: 'l1-r1' }).length).toEqual(1);
103 | expect(ligandReceptorData.getPairs({ name: 'l2-r1' }).length).toEqual(0);
104 | expect(ligandReceptorData.getPairs({ ligand: {}, receptor: {} }).length).toEqual(2);
105 | expect(ligandReceptorData.getPairs({ ligand: { name: 'l1' } }).length).toEqual(1);
106 | expect(ligandReceptorData.getPairs({ ligand: { name: 'l1' }, receptor: { name: 'r1' } }).length).toEqual(1);
107 | expect(ligandReceptorData.getPairs({ ligand: { name: 'l1' }, receptor: { name: 'r2' } }).length).toEqual(0);
108 | });
109 |
110 | $httpBackend.flush();
111 | });
112 | });
113 |
114 | describe('getExpressionValues', function() {
115 | it('should return correct results', function () {
116 |
117 | ligandReceptorData.load().then(function() {
118 | expect(ligandReceptorData.getExpressionValues({}).length).toEqual(6);
119 | expect(ligandReceptorData.getExpressionValues({},2).length).toEqual(2);
120 | expect(ligandReceptorData.getExpressionValues({ligandMin: 10, receptorMin: 10}).length).toEqual(4);
121 | expect(ligandReceptorData.getExpressionValues({cell: {name:'c1'} }).length).toEqual(3);
122 | expect(ligandReceptorData.getExpressionValues({cell: {name:'c2'} }).length).toEqual(2);
123 | expect(ligandReceptorData.getExpressionValues({cell: {name:'c3'} }).length).toEqual(1);
124 | expect(ligandReceptorData.getExpressionValues({cell: {name:['c1','c2']} }).length).toEqual(5);
125 | });
126 |
127 | $httpBackend.flush();
128 | });
129 | });
130 |
131 | describe('getPathways', function() {
132 | it('should return correct results', function () {
133 |
134 | ligandReceptorData.load().then(function() {
135 | expect(ligandReceptorData.getPathways({}).length).toEqual(2);
136 | expect(ligandReceptorData.getPathways({ligandMin: 1, receptorMin: 1}).length).toEqual(4);
137 | expect(ligandReceptorData.getPathways({ligandMin: 1, receptorMin: 1},2).length).toEqual(2);
138 | });
139 |
140 | $httpBackend.flush();
141 | });
142 | });
143 |
144 | });
145 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Ligand Receptor Connectome
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
48 |
49 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/app/components/ui/searchModalContent.html:
--------------------------------------------------------------------------------
1 |
×
2 |
3 |
4 |
62 |
63 |
70 |
71 |
72 |
73 |
74 |
144 |
145 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/app/components/graph/graph-factory.js:
--------------------------------------------------------------------------------
1 | /* global _ */
2 |
3 | (function() {
4 | 'use strict';
5 |
6 | angular.module('lrSpaApp')
7 |
8 | .service('Graph', function($log, site) {
9 |
10 | var defaultSettings = {};
11 |
12 | //TODO:
13 | // use better class contructor pattern?
14 | // Expose data through methods? (getNode, getFilteredNodes, getNeighbors?)
15 | // replace with sigma.js?
16 |
17 | return function Graph(settings) {
18 |
19 | settings = settings || defaultSettings;
20 |
21 | var graph = {};
22 |
23 | var defaultData = {
24 | nodes: [], // -> nodesArray
25 | edges: [], // -> edgesArray
26 |
27 | nodesIndex: {},
28 | edgesIndex: {}, // edgesIndex[nodeA.id][nodeB.id]
29 |
30 | inEdgesIndex: {},
31 | outEdgesIndex: {},
32 |
33 | // temp
34 | _nodes: [], // -> nodesArray
35 | _edges: [], // -> edgesArray
36 | _inEdgesIndex: {},
37 | _outEdgesIndex: {},
38 |
39 | edgeCount: 0, // get rid?
40 | ligandExtent: [0,100000], // get rid?
41 | receptorExtent: [0,100000], // get rid?
42 |
43 | selectedItems: []
44 | };
45 |
46 | graph.clear = function clear() {
47 | graph.data = defaultData;
48 | };
49 |
50 | graph.clear();
51 |
52 | graph.Node = function Node(id, name, type) {
53 | if (id) {this.id = id;}
54 | if (name) {this.name = name;}
55 | if (type) {this.type = type;}
56 |
57 | this.values = [0,0];
58 | this.value = 0;
59 | this.lout = []; // todo: remove
60 | this.lin = [];
61 |
62 | this._expr = []; // rename these
63 | this._ligands = [];
64 | this._receptors = [];
65 | };
66 |
67 | graph.addNode = function addNode(node) {
68 | if (typeof node !== 'object' || arguments.length !== 1) {
69 | $log.error('addNode: Wrong arguments.');
70 | }
71 |
72 | //if (!node.ticked) { return; }
73 |
74 | var id = node.id;
75 |
76 | graph.data.nodes.push(node);
77 | graph.data.nodesIndex[id] = node;
78 |
79 | graph.data.edgesIndex[id] = {};
80 |
81 | graph.data.inEdgesIndex[id] = [];
82 | graph.data.outEdgesIndex[id] = [];
83 | graph.data._inEdgesIndex[id] = [];
84 | graph.data._outEdgesIndex[id] = [];
85 | };
86 |
87 | graph.Edge = function Edge(src,tgt,name) {
88 | name = name || src.name+'->'+tgt.name;
89 | return {
90 | source: src,
91 | target: tgt,
92 | value: 0,
93 | name: name,
94 | values: [0, 0] // remove these
95 | };
96 | };
97 |
98 | graph.addEdge = function addEdge(edge) {
99 | if (arguments.length !== 1 || typeof edge !== 'object') {
100 | $log.error('addNode: Wrong arguments.');
101 | }
102 |
103 | edge.ticked = edge.source.ticked && edge.target.ticked;
104 |
105 | if (edge.ticked) {
106 | graph.data.edges.push(edge);
107 | }
108 |
109 | var src = edge.source.id;
110 | var tgt = edge.target.id;
111 |
112 | if (edge.source.ticked) { // todo: push sorted
113 | graph.data.edgesIndex[src] = graph.data.edgesIndex[src] || {};
114 | graph.data.edgesIndex[src][tgt] = edge;
115 |
116 | graph.data.outEdgesIndex[src] = graph.data.outEdgesIndex[src] || [];
117 | graph.data.outEdgesIndex[src].push(edge);
118 | if (edge.ticked) {
119 | graph.data._outEdgesIndex[src].push(edge);
120 | }
121 | }
122 |
123 | if (edge.target.ticked) {
124 | graph.data.inEdgesIndex[tgt] = graph.data.inEdgesIndex[tgt] || [];
125 | graph.data.inEdgesIndex[tgt].push(edge);
126 | if (edge.ticked) {
127 | graph.data._inEdgesIndex[tgt].push(edge);
128 | }
129 | }
130 |
131 | };
132 |
133 | function __getDATA() {
134 | var _json = {
135 |
136 | };
137 |
138 | _json.nodes = graph.data._nodes.map(function(node, i) {
139 |
140 | var _n = {
141 | data: {
142 | id: i,
143 | name: node.name,
144 | type: node.type,
145 | class: node.class,
146 | value: String(node.value)
147 | },
148 | position: {
149 | x: node.x,
150 | y: node.y
151 | }
152 | };
153 |
154 | angular.extend(_n.data, node.meta);
155 |
156 | return _n;
157 | });
158 |
159 | _json.edges = graph.data._edges.map(function(edge, i) {
160 | return {
161 | data: {
162 | id: i,
163 | name: edge.name,
164 | interaction: edge.type,
165 | source: graph.data._nodes.indexOf(edge.source),
166 | target: graph.data._nodes.indexOf(edge.target),
167 | value: String(edge.value)
168 | }
169 | };
170 | });
171 |
172 | return _json;
173 | }
174 |
175 | graph.getJSON = function _getJSON() {
176 | var _json = {
177 | 'format_version' : '1.0',
178 | 'generated_by' : [site.name,site.version].join('-'),
179 | 'target_cytoscapejs_version' : '~2.1',
180 | data: {},
181 | elements: __getDATA()
182 | };
183 |
184 | _json.elements.nodes.forEach(function(d) {
185 | d.data.id = String(d.data.id);
186 | });
187 |
188 | _json.elements.edges.forEach(function(d) {
189 | d.data.id = String(d.data.id);
190 | d.data.source = String(d.data.source);
191 | d.data.target = String(d.data.target);
192 | });
193 |
194 | return JSON.stringify(_json);
195 | };
196 |
197 | graph.getGML = function __getGML() {
198 |
199 | function quote(str) {
200 | return '"'+str+'"';
201 | }
202 |
203 | function indent(n, p) {
204 | n = n || 2;
205 | p = p || ' ';
206 | var pp = strRepeat(p, n);
207 | return function(s) {
208 | return pp+s;
209 | };
210 | }
211 |
212 | function strRepeat(str, qty){
213 | var result = '';
214 | while (qty > 0) {
215 | result += str;
216 | qty--;
217 | }
218 | return result;
219 | }
220 |
221 | function convert(obj, k) {
222 | if (_.isString(obj)) {return [k,quote(obj)].join(' ');}
223 | if (_.isArray(obj)) {return [k,quote(String(obj))].join(' ');}
224 | if (_.isObject(obj)) {
225 | var e = _.map(obj, convert);
226 | e.unshift(k,'[');
227 | e.push(']');
228 | return e.join(' ');
229 | }
230 | return [k,String(obj)].join(' ');
231 | }
232 |
233 | var _data = __getDATA();
234 |
235 | var _gml = [];
236 | _gml.push('graph [');
237 |
238 | _data.nodes.forEach(function(d) {
239 | _gml.push(' node [');
240 | var e = _.map(d.data, convert).map(indent(4));
241 | _gml = _gml.concat(e);
242 |
243 | _gml.push(' graphics [');
244 |
245 | _gml.push(indent(6)(convert(d.position, 'center')));
246 |
247 | _gml.push(' ]');
248 |
249 | _gml.push(' ]');
250 | });
251 |
252 | _data.edges.forEach(function(d) {
253 | _gml.push(' edge [');
254 | var e = _.map(d.data, convert).map(indent(4)); //function(v,k) { return ' '+convert(v,k); } );
255 | _gml = _gml.concat(e);
256 | _gml.push(' ]');
257 | });
258 |
259 | _gml.push(']');
260 |
261 | return _gml.join('\n');
262 |
263 | };
264 |
265 | return graph;
266 |
267 | };
268 |
269 | });
270 |
271 | })();
272 |
--------------------------------------------------------------------------------
/app/components/ui/main.css:
--------------------------------------------------------------------------------
1 | /* Space out content a bit */
2 | body {
3 | padding: 0px;
4 | margin: 0;
5 | }
6 |
7 | .marketing,
8 | .footer,
9 | .container-fluid {
10 | padding: 0px;
11 | margin: 0;
12 | }
13 |
14 | #header {
15 | height: 40px;
16 | position: absolute;
17 | padding: 5px 20px;
18 | top: 0;
19 | left: 0;
20 | right: 0;
21 | color: #777;
22 | border-top: 1px solid #e5e5e5;
23 | }
24 |
25 | .navbar-toggle {
26 | display: inline-block;
27 | float: left;
28 | padding: 5px;
29 | margin: 0;
30 | background-color: buttonface;
31 | }
32 |
33 | .icon-bar {
34 | background-color: red;
35 | }
36 |
37 | /* Custom page footer */
38 |
39 | #ui {
40 | background-color: #eee;
41 | position: absolute;
42 | top: 0;
43 | left: 0;
44 | width: 100%;
45 | opacity: 0.9;
46 | }
47 |
48 | #ui .title, #ui p {
49 | padding: 0 5px;
50 | }
51 |
52 | #ui .title, #ui .title + p {
53 | text-align: center;
54 | }
55 |
56 | #ui .collapsed > a > .caret, #ui a.collapsed > .caret, .caret.collapsed {
57 | border: 1px solid red;
58 | border-left: 4px solid;
59 | border-right: 4px solid transparent;
60 | border-bottom: 4px solid transparent;
61 | border-top: 4px solid transparent;
62 | }
63 |
64 | #ui:hover {
65 | opacity: 1;
66 | }
67 |
68 | .panel-group .panel {
69 | overflow: visible;
70 | }
71 |
72 | #content {
73 | overflow: hidden;
74 | }
75 |
76 | #footer {
77 | height: 30px;
78 | position: absolute;
79 | bottom: 0;
80 | left: 0;
81 | right: 0;
82 | background-color: #f5f5f5;
83 | color: #777;
84 | border-top: 1px solid #e5e5e5;
85 | }
86 |
87 |
88 | /* Customize container */
89 |
90 | @media (min-width: 768px) {
91 | .container {
92 | max-width: 730px;
93 | }
94 | }
95 | @media (min-width: 1200px) {
96 | .container {
97 | max-width: 1170px;
98 | }
99 | }
100 | .container-narrow > hr {
101 | margin: 30px 0;
102 | }
103 |
104 | /* Main marketing message and sign up button */
105 | .jumbotron {
106 | text-align: center;
107 | border-bottom: 1px solid #e5e5e5;
108 | }
109 | .jumbotron .btn {
110 | font-size: 21px;
111 | padding: 14px 24px;
112 | }
113 |
114 | /* Supporting marketing content */
115 | .marketing {
116 | margin: 40px 0;
117 | }
118 | .marketing p + h4 {
119 | margin-top: 28px;
120 | }
121 |
122 | .rotate {
123 | display: inline-block;
124 | }
125 |
126 |
127 |
128 | .snap-content {
129 | background-color: white;
130 | }
131 |
132 | #ui .panel-group .panel {
133 | border-radius: 0;
134 | background-color: transparent;
135 | border: 0;
136 | -webkit-box-shadow: none;
137 | box-shadow: none;
138 | }
139 |
140 | #ui .panel-heading {
141 | padding: 10px 5px;
142 | border: 0;
143 | border-radius: 0;
144 | background-color: buttonface;
145 | cursor: pointer;
146 | }
147 |
148 | #ui .panel-body {
149 | padding: 15px 5px;
150 | }
151 |
152 | #ui .panel .panel-title a:after {
153 | font-family: 'Glyphicons Halflings';
154 | content: "\e114";
155 | float: right;
156 | color: grey;
157 | font-size: 12px;
158 | margin: 5px 5px;
159 | }
160 |
161 | #ui .panel.collapsed .panel-title a:after {
162 | content: "\e080"; /* adjust as needed, taken from bootstrap.css */
163 | }
164 |
165 | .btn-vert-block > button{
166 | margin-bottom:5px;
167 | }
168 |
169 | .multiSelect.inlineBlock {
170 | display: inline;
171 | }
172 |
173 | .multiSelect .button {
174 | width: 100%;
175 | }
176 |
177 | .multiSelect .show {
178 | width: 320px;
179 | overflow-y: auto;
180 | height: 300px;
181 | }
182 |
183 | .panel-right {
184 | /* border: 2px solid green; */
185 | padding: 0;
186 | position: absolute;
187 | top: 0px;
188 | bottom: auto;
189 | right: 0px;
190 | left: auto;
191 | /* width: 400px; */
192 | max-height: 100%;
193 | z-index: 100;
194 | overflow-y: auto;
195 | overflow-x: clip;
196 | }
197 |
198 | .growl {
199 | right: auto;
200 | left: 10px;
201 | width: 400px;
202 | }
203 |
204 | .growl .alert {
205 | margin-bottom: 4px;
206 | }
207 |
208 | .new-tip {
209 | line-height: 1;
210 | padding: 12px;
211 | opacity: 0.7;
212 | border-radius: 4px;
213 | margin: 5px;
214 | }
215 |
216 | .new-tip h5 {
217 | font-weight: bold;
218 | }
219 |
220 | .position-fixed {
221 | position: fixed;
222 | }
223 |
224 | .pointer {
225 | cursor: pointer;
226 | }
227 |
228 | .new-tip:hover, .new-tip.hover {
229 | opacity: 1;
230 | }
231 |
232 | .animate-fade {
233 |
234 | }
235 |
236 | .chosen-container-multi {
237 | width: 100% !important;
238 | }
239 |
240 | .chosen-container-multi .chosen-choices li.search-field input[type="text"] {
241 | height: auto;
242 | }
243 |
244 | .chosen-choices .search-field:first-child, .chosen-choices .search-field:first-child input[type="text"] {
245 | width: 100% !important;
246 | }
247 |
248 | .animate-fade.ng-hide-add, .animate-fade.ng-hide-remove,
249 | .animate-fade.ng-enter, .animate-fade.ng-leave {
250 | -webkit-transition:all linear 0.5s;
251 | -moz-transition:all linear 0.5s;
252 | -o-transition:all linear 0.5s;
253 | transition:all linear 0.5s;
254 | /* display:block!important; */
255 | }
256 |
257 | .animate-fade.ng-hide-add.ng-hide-add-active,
258 | .animate-fade.ng-hide-remove,
259 | .animate-fade.ng-enter,
260 | .animate-fade.ng-leave.ng-leave-active {
261 | opacity: 0;
262 | }
263 |
264 | .animate-fade.ng-hide-add,
265 | .animate-fade.ng-hide-remove.ng-hide-remove-active,
266 | .animate-fade.ng-leave,
267 | .animate-fade.ng-enter.ng-enter-active {
268 | opacity: 1;
269 | }
270 |
271 | .animate-thin {
272 |
273 | }
274 |
275 | .animate-thin.ng-hide-add, .animate-thin.ng-hide-remove,
276 | .animate-thin.ng-enter, .animate-thin.ng-leave {
277 | -webkit-transition:all linear 0.5s;
278 | -moz-transition:all linear 0.5s;
279 | -o-transition:all linear 0.5s;
280 | transition:all linear 0.5s;
281 | display:block!important;
282 | position: relative;
283 | }
284 |
285 | .animate-thin.ng-hide-add.ng-hide-add-active,
286 | .animate-thin.ng-hide-remove,
287 | .animate-thin.ng-enter,
288 | .animate-thin.ng-leave.ng-leave-active {
289 | left: 100%;
290 | }
291 |
292 | .animate-thin.ng-hide-add,
293 | .animate-thin.ng-hide-remove.ng-hide-remove-active,
294 | .animate-thin.ng-leave,
295 | .animate-thin.ng-enter.ng-enter-active {
296 | left: 0;
297 | }
298 |
299 | /* Multilevel menu */
300 |
301 | #vis .dropdown-menu {
302 | min-width: 260px;
303 | z-index: 2000;
304 | }
305 |
306 | .dropdown-submenu {
307 | position: relative;
308 | }
309 |
310 | .dropdown-submenu>.dropdown-menu {
311 | top: 0;
312 | left: 100%;
313 | margin-top: -6px;
314 | margin-left: -1px;
315 | -webkit-border-radius: 0 6px 6px 6px;
316 | -moz-border-radius: 0 6px 6px;
317 | border-radius: 0 6px 6px 6px;
318 | }
319 |
320 | .dropdown-submenu:hover>.dropdown-menu {
321 | display: block;
322 | }
323 |
324 | .dropdown-submenu>a:after {
325 | display: block;
326 | content: " ";
327 | float: right;
328 | width: 0;
329 | height: 0;
330 | border-color: transparent;
331 | border-style: solid;
332 | border-width: 5px 0 5px 5px;
333 | border-left-color: #ccc;
334 | margin-top: 5px;
335 | margin-right: -10px;
336 | }
337 |
338 | .dropdown-submenu:hover>a:after {
339 | border-left-color: #fff;
340 | }
341 |
342 | .dropdown-submenu.pull-left {
343 | float: none;
344 | }
345 |
346 | .dropdown-submenu.pull-left>.dropdown-menu {
347 | left: -100%;
348 | margin-left: 10px;
349 | -webkit-border-radius: 6px 0 6px 6px;
350 | -moz-border-radius: 6px 0 6px 6px;
351 | border-radius: 6px 0 6px 6px;
352 | }
353 |
354 | .nav-tabs > li {
355 | cursor: pointer;
356 | }
357 |
358 | .btn-icon {
359 | padding: 0;
360 | background-color: none;
361 | color: inherit;
362 | }
363 |
364 | .icon-check, input[type=checkbox].glyphicon:before, input[type=checkbox].fa:before {
365 | visibility: visible;
366 | opacity:0.5;
367 | }
368 |
369 | .icon-check.icon-checked, input[type=checkbox].glyphicon:checked:before, input[type=checkbox].fa:checked:before {
370 | opacity:1;
371 | }
372 |
373 | input[type=checkbox].glyphicon,input[type=checkbox].fa{
374 | visibility: hidden;
375 | }
376 |
377 | .icon-checked.glyphicon-eye-close:before, input[type=checkbox].glyphicon-eye-close:checked:before {
378 | content: "\e105";
379 | }
380 |
381 | .icon-checked.fa-unlock:before, input[type=checkbox].fa-unlock:checked:before {
382 | content: "\f023";
383 | }
384 |
385 | .dropdown, .modal-dialog {
386 | z-index: 2000;
387 | }
388 |
389 | .modal-content button.close {
390 | padding: 10px;
391 | }
392 |
393 | .modal-content .nav-tabs > li {
394 | margin: 10px 0px -1px 10px;
395 | }
396 |
397 | .ngHeaderButton {
398 | -moz-border-radius: 10%;
399 | -webkit-border-radius: 10%;
400 | border-radius: 10%;
401 | width: auto;
402 | height: auto;
403 | top: 2px;
404 | text-size: 10px;
405 | padding: 3px 5px;
406 | }
407 |
408 | .ngRow .locked {
409 | color: #aaa;
410 | }
411 |
412 | .select2-container {
413 | width: 100%;
414 | }
415 |
416 | .miniGrid {
417 | border: 0;
418 | background-color: none;
419 | font-size: x-small;
420 | height: 50px;
421 | margin-top: 5px;
422 | }
423 |
424 | .miniGrid .ngRow.even, .miniGrid .ngRow {
425 | background-color: inherit;
426 | }
427 |
428 | .miniGrid .hgCellText {
429 | height: auto;
430 | }
431 |
432 | .ngHeaderContainer {
433 | position: static;
434 | }
435 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // Generated on 2014-04-10 using generator-angular 0.8.0
2 | 'use strict';
3 |
4 | // # Globbing
5 | // for performance reasons we're only matching one level down:
6 | // 'test/spec/{,*/}*.js'
7 | // use this if you want to recursively match all subfolders:
8 | // 'test/spec/**/*.js'
9 |
10 | module.exports = function (grunt) {
11 |
12 | // Load grunt tasks automatically
13 | require('load-grunt-tasks')(grunt);
14 |
15 | // Time how long tasks take. Can help when optimizing build times
16 | require('time-grunt')(grunt);
17 |
18 | // Define the configuration for all the tasks
19 | grunt.initConfig({
20 |
21 | // Project settings
22 | build: {
23 | // configurable paths
24 | app: 'app',
25 | dist: 'dist',
26 | prod: '/cygdrive/w/home/harshbarger/ligand-receptor'
27 | },
28 |
29 | // Watches files for changes and runs tasks based on the changed files
30 | watch: {
31 | bower: {
32 | files: ['bower.json'],
33 | tasks: ['bowerInstall']
34 | },
35 | js: {
36 | files: ['<%= build.app %>/components/**/*.js'],
37 | tasks: ['newer:jshint:all'],
38 | options: {
39 | livereload: '<%= connect.options.livereload %>'
40 | }
41 | },
42 | styles: {
43 | files: ['<%= build.app %>/components/**/*.css'],
44 | tasks: ['newer:copy:styles','autoprefixer']
45 | },
46 | //styles: {
47 | // files: ['<%= build.app %>/styles/**/*.css'],
48 | // tasks: ['newer:copy:styles', 'newer:copy:dist','autoprefixer']
49 | //},
50 | //jsTest: {
51 | // files: ['test/spec/{,*/}*.js'],
52 | // tasks: ['newer:jshint:test', 'karma']
53 | // },
54 | gruntfile: {
55 | files: ['Gruntfile.js']
56 | },
57 | livereload: {
58 | options: {
59 | livereload: '<%= connect.options.livereload %>'
60 | },
61 | files: [
62 | '<%= build.app %>/{,*/}*.html',
63 | '<%= build.app %>/components/**/*.html',
64 | '.tmp/components/**/*.css',
65 | '<%= build.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
66 | ]
67 | }
68 | },
69 |
70 | // The actual grunt server settings
71 | connect: {
72 | options: {
73 | port: 9000,
74 | // Change this to '0.0.0.0' to access the server from outside.
75 | hostname: 'localhost',
76 | livereload: 35730
77 | },
78 | livereload: {
79 | options: {
80 | open: true,
81 | base: [
82 | '.tmp',
83 | '<%= build.app %>'
84 | ]
85 | }
86 | },
87 | test: {
88 | options: {
89 | port: 9001,
90 | base: [
91 | '.tmp',
92 | 'test',
93 | '<%= build.app %>'
94 | ]
95 | }
96 | },
97 | dist: {
98 | options: {
99 | base: '<%= build.dist %>'
100 | }
101 | }
102 | },
103 |
104 | // Make sure code styles are up to par and there are no obvious mistakes
105 | jshint: {
106 | options: {
107 | jshintrc: '.jshintrc',
108 | reporter: require('jshint-stylish')
109 | },
110 | all: [
111 | 'Gruntfile.js',
112 | '<%= build.app %>/components/**/*.js',
113 | '!<%= build.app %>/components/tree/d3.hive.v0.js'
114 | ],
115 | test: {
116 | options: {
117 | jshintrc: 'test/.jshintrc'
118 | },
119 | src: ['test/spec/{,*/}*.js']
120 | }
121 | },
122 |
123 | // Empties folders to start fresh
124 | clean: {
125 | dist: {
126 | files: [{
127 | dot: true,
128 | src: [
129 | '.tmp',
130 | '<%= build.dist %>/*',
131 | '!<%= build.dist %>/.git*'
132 | ]
133 | }]
134 | },
135 | server: '.tmp'
136 | },
137 |
138 | // Add vendor prefixed styles
139 | autoprefixer: {
140 | options: {
141 | browsers: ['last 1 version']
142 | },
143 | dist: {
144 | files: [{
145 | expand: true,
146 | cwd: '.tmp/components/',
147 | src: '**/*.css',
148 | dest: '.tmp/components/'
149 | }]
150 | }
151 | },
152 |
153 | // Automatically inject Bower components into the app
154 | bowerInstall: {
155 | app: {
156 | src: ['<%= build.app %>/index.html'],
157 | ignorePath: '<%= build.app %>/',
158 | //exclude: [ /chosen\.jquery\.js/ ]
159 | overrides: {
160 | chosen: {
161 | main: 'bower_components/chosen/chosen.jquery.js'
162 | }
163 | }
164 | }
165 | },
166 |
167 | // Renames files for browser caching purposes
168 | rev: {
169 | dist: {
170 | files: {
171 | src: [
172 | '<%= build.dist %>/scripts/{,*/}*.js',
173 | '<%= build.dist %>/styles/{,*/}*.css',
174 | '<%= build.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
175 | '<%= build.dist %>/styles/fonts/*'
176 | ]
177 | }
178 | }
179 | },
180 |
181 | // Reads HTML for usemin blocks to enable smart builds that automatically
182 | // concat, minify and revision files. Creates configurations in memory so
183 | // additional tasks can operate on them
184 | useminPrepare: {
185 | html: '<%= build.app %>/index.html',
186 | options: {
187 | dest: '<%= build.dist %>',
188 | flow: {
189 | html: {
190 | steps: {
191 | js: ['concat', 'uglifyjs'],
192 | css: ['cssmin']
193 | },
194 | post: {}
195 | }
196 | }
197 | }
198 | },
199 |
200 | // Performs rewrites based on rev and the useminPrepare configuration
201 | usemin: {
202 | html: ['<%= build.dist %>/{,*/}*.html'],
203 | css: ['<%= build.dist %>/styles/**/*.css'],
204 | options: {
205 | assetsDirs: ['<%= build.dist %>']
206 | }
207 | },
208 |
209 | // The following *-min tasks produce minified files in the dist folder
210 | cssmin: {
211 | options: {
212 | //root: '<%= build.app %>'
213 | }
214 | },
215 |
216 | imagemin: {
217 | dist: {
218 | files: [{
219 | expand: true,
220 | cwd: '<%= build.app %>/images',
221 | src: '{,*/}*.{png,jpg,jpeg,gif}',
222 | dest: '<%= build.dist %>/images'
223 | }]
224 | }
225 | },
226 |
227 | svgmin: {
228 | dist: {
229 | files: [{
230 | expand: true,
231 | cwd: '<%= build.app %>/images',
232 | src: '{,*/}*.svg',
233 | dest: '<%= build.dist %>/images'
234 | }]
235 | }
236 | },
237 |
238 | htmlmin: {
239 | dist: {
240 | options: {
241 | collapseWhitespace: false,
242 | collapseBooleanAttributes: true,
243 | removeCommentsFromCDATA: true,
244 | removeOptionalTags: false
245 | },
246 | files: [{
247 | expand: true,
248 | cwd: '<%= build.dist %>',
249 | src: ['*.html', 'components/**/*.html'],
250 | dest: '<%= build.dist %>'
251 | }]
252 | }
253 | },
254 |
255 | // ngmin tries to make the code safe for minification automatically by
256 | // using the Angular long form for dependency injection. It doesn't work on
257 | // things like resolve or inject so those have to be done manually.
258 | ngmin: {
259 | dist: {
260 | files: [{
261 | expand: true,
262 | cwd: '.tmp/concat/scripts',
263 | src: '*.js',
264 | dest: '.tmp/concat/scripts'
265 | }]
266 | }
267 | },
268 |
269 | // Replace Google CDN references
270 | cdnify: {
271 | dist: {
272 | html: ['<%= build.dist %>/*.html']
273 | }
274 | },
275 |
276 | // Copies remaining files to places other tasks can use
277 | copy: {
278 | dist: {
279 | files: [{
280 | expand: true,
281 | dot: true,
282 | cwd: '<%= build.app %>',
283 | dest: '<%= build.dist %>',
284 | src: [
285 | '*.{ico,png,txt}',
286 | '.htaccess',
287 | '*.html',
288 | 'views/{,*/}*.html',
289 | 'images/{,*/}*.{webp}',
290 | 'components/{,*/}*.html',
291 | 'data/*.txt',
292 | 'fonts/*'
293 | ]
294 | }, {
295 | expand: true,
296 | cwd: '.tmp/images',
297 | dest: '<%= build.dist %>/images',
298 | src: ['generated/*']
299 | }, {
300 | expand: true,
301 | cwd: '<%= build.app %>/bower_components/chosen',
302 | dest: '<%= build.dist %>/styles',
303 | src: ['*.png']
304 | }, {
305 | expand: true,
306 | cwd: '<%= build.app %>/bower_components/select2',
307 | dest: '<%= build.dist %>/styles',
308 | src: ['*.png','*.gif']
309 | }]
310 | },
311 | styles: {
312 | expand: true,
313 | cwd: '<%= build.app %>/components',
314 | dest: '.tmp/components',
315 | src: '**/*.css'
316 | },
317 | fonts: {
318 | expand: true,
319 | cwd: '<%= build.app %>/bower_components/bootstrap/dist/',
320 | dest: '<%= build.dist %>',
321 | src: 'fonts/*.*'
322 | },
323 | 'fonts-fa': {
324 | expand: true,
325 | cwd: '<%= build.app %>/bower_components/font-awesome/',
326 | dest: '<%= build.dist %>',
327 | src: 'fonts/*.*'
328 | }
329 | },
330 |
331 | // Run some tasks in parallel to speed up the build process
332 | concurrent: {
333 | server: [
334 | 'copy:styles'
335 | ],
336 | test: [
337 | 'copy:styles'
338 | ],
339 | dist: [
340 | 'copy:styles',
341 | 'copy:fonts',
342 | 'copy:fonts-fa',
343 | 'imagemin',
344 | 'svgmin'
345 | ]
346 | },
347 |
348 | // By default, your `index.html`'s will take care of
349 | // minification. These next options are pre-configured if you do not wish
350 | // to use the Usemin blocks.
351 | // cssmin: {
352 | // dist: {
353 | // files: {
354 | // '<%= build.dist %>/styles/main.css': [
355 | // '.tmp/styles/{,*/}*.css',
356 | // '<%= build.app %>/styles/{,*/}*.css'
357 | // ]
358 | // }
359 | // }
360 | // },
361 | // uglify: {
362 | // dist: {
363 | // files: {
364 | // '<%= build.dist %>/scripts/scripts.js': [
365 | // '<%= build.dist %>/scripts/scripts.js'
366 | // ]
367 | // }
368 | // }
369 | // },
370 | // concat: {
371 | // dist: {}
372 | // },
373 |
374 | // Test settings
375 | karma: {
376 | unit: {
377 | configFile: 'karma.conf.js',
378 | singleRun: true
379 | }
380 | },
381 |
382 | // Rsync to 'production" server
383 | rsync: {
384 | options: {
385 | args: ['--verbose','--delete'],
386 | recursive: true,
387 | exclude: ['.git*']
388 | },
389 | prod: {
390 | options: {
391 | src: '<%= build.dist %>/',
392 | dest: '<%= build.prod %>'
393 | }
394 | }
395 | },
396 |
397 | 'gh-pages': {
398 | options: {
399 | base: 'dist'
400 | },
401 | src: ['**']
402 | }
403 |
404 | });
405 |
406 |
407 | grunt.registerTask('serve', function (target) {
408 | if (target === 'dist') {
409 | return grunt.task.run(['build', 'connect:dist:keepalive']);
410 | }
411 |
412 | grunt.task.run([
413 | 'clean:server',
414 | 'bowerInstall',
415 | 'concurrent:server',
416 | 'autoprefixer',
417 | 'connect:livereload',
418 | 'watch'
419 | ]);
420 | });
421 |
422 | grunt.registerTask('test', [
423 | 'clean:server',
424 | 'concurrent:test',
425 | 'autoprefixer',
426 | 'connect:test',
427 | 'karma'
428 | ]);
429 |
430 | grunt.registerTask('build', [
431 | 'clean:dist',
432 | 'bowerInstall',
433 | 'useminPrepare',
434 | 'concurrent:dist',
435 | 'autoprefixer',
436 | 'concat',
437 | 'ngmin',
438 | 'copy:dist',
439 | //'cdnify',
440 | 'cssmin',
441 | 'uglify',
442 | 'rev',
443 | 'usemin',
444 | 'htmlmin'
445 | ]);
446 |
447 | grunt.registerTask('deploy', [
448 | 'build',
449 | 'gh-pages'
450 | ]);
451 |
452 | grunt.registerTask('default', [
453 | 'jshint',
454 | 'test',
455 | 'build'
456 | ]);
457 |
458 | };
459 |
--------------------------------------------------------------------------------
/app/components/ui/bottom-panel.html:
--------------------------------------------------------------------------------
1 |
17 |
18 |
21 |
22 |
35 |
36 |
47 |
48 |
59 |
60 |
70 |
71 |
81 |
82 |
94 |
95 |
102 |
103 |
110 |
111 |
117 |
118 |
123 |
124 |
146 |
147 |
148 |
149 |
150 | Cell types ({{selectedIds.cells.length}}/{{ (data.cells | filter : { locked: false }).length}})
151 |
152 |
153 |
154 |
155 | Genes ({{selectedIds.genes.length}}/{{(data.genes | filter : { locked: false }).length}})
156 |
157 |
158 |
159 | Pairs ({{selectedIds.pairs.length}}/{{(data.pairs | filter : { locked: false }).length}})
160 |
161 |
162 |
163 | Options
164 |
165 |
166 |
Graph
167 |
174 |
175 |
176 |
221 |
226 |
227 |
228 |
229 | Help
230 |
231 |
Ligand Receptor Connectome {{site.version}}
232 |
233 | A visual guide to FANTOM5 Ligand-Receptor interactions as described in Ramilowski et al. ‘A draft network of ligand-receptor mediated multicellular signaling in human’ 2015.
234 | Site source code at Hypercubed/connectome .
235 |
236 |
237 |
238 | Entity and options panel - (bottom right)
239 | Displays entities and options panel
240 |
241 |
242 | Tabs : switch between lists of cells, genes, and ligand receptor pairs
243 | Visibility Toggle buttons : ( left in each row) turn on/off cells and genes (ligand/receptors)
244 | ctrl-click and shift-click : select multiple nodes and change visibility as a group
245 | Search... button: (upper right of the data list) filters each lists by substring search
246 |
247 |
248 |
249 | Information panel - (top right)
250 | Displays information on current network and on selected nodes/edges
251 |
252 |
253 |
254 | Search feature -
255 | Search for top expression values and/or top cell-to-cell communication paths
256 |
257 |
258 |
259 | Visualization style -
260 | Switch between hive/force-direct plots
261 |
262 |
263 |
264 | Save feature -
265 | Save current view as SVG, download data in JSON or GML format, bookmark this view for later
266 |
267 |
268 |
269 | Visualization
270 |
271 |
272 | Click : select/deselect a node (information appears in the information panel)
273 | Ctrl-click : select multiple nodes
274 | Shift-Click : select a node and neighbors
275 | Right click : additional options
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 | Toggle navigation
285 |
286 |
287 |
288 |
289 |
290 |
--------------------------------------------------------------------------------
/app/components/ui/main.js:
--------------------------------------------------------------------------------
1 | /* global saveAs */
2 | /* global _F */
3 | /* global Base64 */
4 | /* global ngGridFlexibleHeightPlugin */
5 |
6 | (function() {
7 | 'use strict';
8 |
9 | var _value = _F('value');
10 | var _specificity = _F('specificity');
11 | var _ticked = _F('ticked');
12 | var _i = _F('i');
13 |
14 | angular.module('lrSpaApp')
15 |
16 | .controller('ResetCtrl', function ($state,localStorageService) {
17 | localStorageService.clearAll();
18 | $state.go('home.hive-graph');
19 | })
20 |
21 | .controller('MainCtrl', function ($scope, $rootScope, $log, $state, $filter, $templateCache, $timeout, $window, $location, growl, filterFilter, cfpLoadingBar, debounce, site, localStorageService, loadedData, ligandReceptorData, forceGraph, hiveGraph) {
22 |
23 | $rootScope.$on('$routeChangeError', function(event) {
24 | $log.warn(event);
25 | });
26 |
27 | $scope.data = loadedData;
28 |
29 | $scope.max = Math.max; // TODO: check if still needed
30 |
31 | var defaultOptions = {
32 | showLabels: true,
33 | maxEdges: 100,
34 | ligandFilter: 10,
35 | receptorFilter: 10,
36 | ligandRankFilter: 1,
37 | receptorRankFilter: 1,
38 | edgeRankFilter: 1,
39 | };
40 |
41 | var defaultIds = {pairs:[569],cells:[13,15,16,17,33,62,69,72,73],genes:[145,768]};
42 |
43 | $scope.options = angular.copy(defaultOptions);
44 | $scope.selectedIds = angular.copy(defaultIds);
45 |
46 | // Options
47 | //localStorageService.bind($scope, 'options', angular.extend({}, defaultOptions));
48 | //localStorageService.bind($scope, 'selectedIds', angular.extend({}, defaultIds));
49 |
50 | //$scope.selected = {};
51 | $scope.resetOptions = function() {
52 | $scope.options = angular.extend({}, defaultOptions);
53 | clearlocks();
54 | };
55 |
56 | function clearlocks() {
57 | loadedData.pairs.forEach(function(d) {
58 | d.locked = false;
59 | });
60 | loadedData.genes.forEach(function(d) {
61 | d.locked = false;
62 | });
63 | loadedData.cells.forEach(function(d) {
64 | d.locked = false;
65 | });
66 | }
67 |
68 | $scope.resetVis = function() {
69 | $scope.options = angular.copy(defaultOptions);
70 | $scope.selectedIds = angular.copy(defaultIds);
71 | loadSelection();
72 | };
73 |
74 | $scope.clearVis = function() {
75 | $scope.selectedIds = {pairs:[],cells:[],genes:[]};
76 | loadSelection();
77 | };
78 |
79 | var _fixed = _F('fixed');
80 |
81 | $scope.updateSelection = function() {
82 | graphService.data.selectedItems = graphService.data.nodes.filter(_fixed);
83 | };
84 |
85 | $scope.rightClick = function(e) { // TODO: check if still needed
86 | if (e.target.__data__ && e.target.__data__.type) {
87 | $scope.clickedItem = e.target.__data__;
88 | } else {
89 | $scope.clickedItem = null;
90 | }
91 | };
92 |
93 | // Graph service
94 | var graphService; // This should controlled by the directive
95 |
96 | $scope.state = $state.current;
97 |
98 | $scope.$watch('state.name', function(name) { // git rid of watchers
99 | $state.go(name);
100 | if (graphService) {graphService.clear();}
101 | graphService = (name === 'home.hive-graph') ? hiveGraph : forceGraph;
102 | $scope.graph = graphService;
103 | $scope.graphData = graphService.data;
104 | updateNetwork();
105 | });
106 |
107 | // TODO: move to saveModel controller
108 | $scope.saveJson = function() { // TODO: a directive/service?
109 | var txt = graphService.graph.getJSON();
110 | var blob = new Blob([txt], { type: 'text/json' });
111 | saveAs(blob, 'lr-graph.json');
112 | };
113 |
114 | $scope.saveGml = function() { // TODO: a directive/service?
115 | var txt = graphService.graph.getGML();
116 | var blob = new Blob([txt], { type: 'text/gml' });
117 | saveAs(blob, 'lr-graph.gml');
118 | };
119 |
120 | function _ticked2(arr) { // must be
121 | return function(d) {
122 | if (d.locked) { return false; }
123 | d.fixed = false;
124 | d.ticked = arr.indexOf(d.i) > -1;
125 | return d.ticked;
126 | };
127 | }
128 |
129 | function loadSelection() {
130 |
131 | $log.debug('load from local storage');
132 |
133 | $scope.data.pairs.filter(_ticked2($scope.selectedIds.pairs));
134 | $scope.data.cells.filter(_ticked2($scope.selectedIds.cells));
135 | $scope.data.genes.filter(_ticked2($scope.selectedIds.genes));
136 |
137 | }
138 |
139 | $scope.revoveSelectedItem = function(index) { // TODO: check if still needed
140 | graphService.data.selectedItems[index].fixed = false;
141 | graphService.data.selectedItems.splice(index, 1);
142 | graphService.update();
143 | };
144 |
145 | var updateNetwork = debounce(function updateNetwork() { // This should be handeled by the directive
146 | $log.debug('update network');
147 |
148 | if (graphService) {
149 | graphService.makeNetwork($scope.data, $scope.options);
150 | graphService.draw($scope.options);
151 | saveUndo();
152 | }
153 |
154 | });
155 |
156 |
157 | // TODO: move these to findModelCtrl
158 | $scope.showExpressionEdges = function _showExpressionEdges(_filter, max) {
159 | var filter = angular.copy(_filter);
160 | var acc = (filter.rank === 'specificity') ? _specificity : _value;
161 |
162 | if (filter.gene.class !== '') {
163 | delete filter.gene.id;
164 | }
165 |
166 | filter.gene = angular.extend({}, filter.gene, {locked: false});
167 | filter.cell = angular.extend({}, filter.cell, {locked: false});
168 |
169 | //console.log(filter);
170 |
171 | filter.ligandMin = $scope.options.ligandFilter;
172 | filter.receptorMin = $scope.options.receptorFilter;
173 |
174 | var start = new Date().getTime();
175 |
176 | var edges;
177 |
178 | if (filter.gene.class === 'each') {
179 | var f = angular.copy(filter);
180 | f.gene.class = 'ligand';
181 | var edges1 = ligandReceptorData.getExpressionValues(f, max, acc);
182 |
183 | f.gene.class = 'receptor';
184 | var edges2 = ligandReceptorData.getExpressionValues(f, max, acc);
185 |
186 | edges = edges1.concat(edges2);
187 | } else {
188 | edges = ligandReceptorData.getExpressionValues(filter, max, acc);
189 | }
190 |
191 | $log.debug('found',edges.length,'expression edges');
192 |
193 | if (edges.length < 1) {
194 | growl.addWarnMessage('No expression edges match search criteria and expression thresholds.');
195 | } else {
196 | //growl.addSuccessMessage('Found '+edges.length+' expression edges');
197 |
198 | edges.forEach(function(d) {
199 | d.gene.ticked = true;
200 | d.cell.ticked = true;
201 | });
202 |
203 | loadedData.pairs.forEach(function(pair) {
204 | pair.ticked = !pair.locked && pair.ligand.ticked && pair.receptor.ticked;
205 | });
206 | }
207 |
208 | var time = (new Date().getTime()) - start;
209 | $log.debug('Execution time:', time/1000, 's');
210 |
211 | };
212 |
213 | $scope.showPaths = function _showPaths(_filter, max) {
214 | var filter = angular.copy(_filter);
215 |
216 | var acc = (filter.rank === 'specificity') ? _specificity : _value;
217 |
218 | filter.pair = angular.extend({}, filter.pair, {locked: false});
219 | filter.pair.ligand = angular.extend({}, filter.pair.ligand, {locked: false});
220 | filter.pair.receptor = angular.extend({}, filter.pair.receptor, {locked: false});
221 | filter.source = angular.extend({}, filter.source, {locked: false});
222 | filter.target = angular.extend({}, filter.target, {locked: false});
223 |
224 | cfpLoadingBar.start();
225 | var start = Date.now();
226 |
227 | $timeout(function() {
228 |
229 | filter.ligandMin = $scope.options.ligandFilter;
230 | filter.receptorMin = $scope.options.receptorFilter;
231 |
232 | var paths;
233 |
234 | if (filter.direction === 'each' && !angular.equals(filter.target, filter.source)) {
235 | $log.debug('Bi-directional search');
236 |
237 | var f = angular.copy(filter); // do I need this, already a copy?
238 | f.direction = 'AB';
239 |
240 | var paths1 = ligandReceptorData.getPathways(f, max, acc);
241 |
242 | f = angular.copy(filter);
243 | f.direction = 'BA';
244 | f.source = filter.target;
245 | f.target = filter.source;
246 |
247 | var paths2 = ligandReceptorData.getPathways(f, max, acc);
248 |
249 | paths = paths1.concat(paths2);
250 | } else {
251 | paths = ligandReceptorData.getPathways(filter, max, acc);
252 | }
253 |
254 | $log.debug('found',paths.length,'expression edges');
255 |
256 | if (paths.length < 1) {
257 | growl.addWarnMessage('No pathways match search criteria and expression thresholds.');
258 | } else {
259 | paths.forEach(function(d) {
260 | d.pair.ticked = true;
261 | d.source.ticked = true;
262 | d.ligand.ticked = true;
263 | d.receptor.ticked = true;
264 | d.target.ticked = true;
265 | });
266 | }
267 |
268 | var time = Date.now() - start;
269 | $log.debug('Execution time:', time/1000, 's');
270 |
271 | cfpLoadingBar.complete();
272 | });
273 |
274 | };
275 |
276 | $scope.hide = function(arr) { // TODO: check if still needed
277 | if (!angular.isArray(arr)) { arr = [arr]; }
278 | arr.forEach(function(d) {
279 | if (d.type === 'gene' || d.type === 'sample') {
280 | d.ticked = false;
281 | }
282 | });
283 | };
284 |
285 | $scope.pathFilter = { // TODO: check if still needed
286 | source: {},
287 | target: {}
288 | };
289 |
290 | // TODO: create a state undo service
291 | $scope.undoStack = [];
292 | $scope.undoIndex = -1;
293 |
294 | function saveUndo() {
295 | var b = save();
296 | if ($scope.undoStack[$scope.undoIndex] !== b) {
297 | $scope.undoStack.push(b);
298 | $scope.undoIndex = $scope.undoStack.length-1;
299 | }
300 | }
301 |
302 | $scope.undo = function() {
303 | $scope.undoIndex--;
304 | var b = $scope.undoStack[$scope.undoIndex];
305 | load(b);
306 | };
307 |
308 | $scope.redo = function() {
309 | $scope.undoIndex++;
310 | var b = $scope.undoStack[$scope.undoIndex];
311 | load(b);
312 | };
313 |
314 | function save() {
315 | var obj = {
316 | i: $scope.selectedIds,
317 | o: $scope.options,
318 | v: site.apiVersion
319 | };
320 | var json = JSON.stringify(obj);
321 | return Base64.encode(json);
322 | }
323 |
324 | function load(bin) {
325 | var json = Base64.decode(bin);
326 | var obj = JSON.parse(json);
327 |
328 | if (obj.v !== site.apiVersion) {
329 | $log.warn('Possible version issue');
330 | }
331 |
332 | $log.debug('load',obj);
333 |
334 | $scope.selectedIds = obj.i;
335 | $scope.options = obj.o;
336 |
337 | loadSelection();
338 | updateNetwork();
339 | }
340 |
341 | //$scope.loadSave = function() {// TODO: check if still needed
342 | // var ret = $window.prompt('Copy this url to your clipboard to save the current state.', $scope.getSaveUrl());
343 | //};
344 |
345 | $scope.getSaveUrl = function() {
346 | return $location.absUrl()+'?save='+save();
347 | };
348 |
349 | // Start here
350 | clearlocks();
351 |
352 | if ($location.search().save) {
353 | var b = $location.search().save;
354 | load(b);
355 | $location.search('save',null);
356 | } else {
357 | loadSelection();
358 | updateNetwork();
359 | }
360 |
361 | ['cells','pairs','genes'].forEach(function setSelectionWatch(key) {
362 | var arr = $scope.data[key];
363 | var watchFn = function () {
364 | return arr.map(_ticked);
365 | };
366 | var callBack = function() {
367 |
368 | //console.log('dataChanged',key);
369 |
370 | $scope.selectedIds[key] = arr.filter(_ticked).map(_i);
371 | updateNetwork();
372 | };
373 | $scope.$watch(watchFn, callBack,true); // git rid of watchers
374 | });
375 |
376 | $scope.$watchCollection('options', updateNetwork); // git rid of watchers
377 |
378 | /*jshint -W055 */
379 | $scope.ngGridPlugins = [new ngGridFlexibleHeightPlugin()];
380 | /*jshint +W055 */
381 |
382 | });
383 |
384 | })();
385 |
--------------------------------------------------------------------------------
/app/components/data/ligandReceptorData-service.js:
--------------------------------------------------------------------------------
1 | /* global d3 */
2 | /* global _F */
3 |
4 | (function() {
5 | 'use strict';
6 |
7 | var _value = _F('value');
8 | var _i0 = _F(0);
9 |
10 | angular.module('lrSpaApp')
11 |
12 | .constant('files', {
13 | expression: 'data/LR.expr.txt',
14 | pairs: 'data/LR.pairs.txt',
15 | genes: 'data/LR.genes.txt',
16 | ontology: 'data/ontology.txt'
17 | })
18 |
19 | .service('ligandReceptorData', function($q, $log,$http,$timeout,dsv,files) {
20 |
21 | var service = {};
22 |
23 | var cache = false;
24 |
25 | service.data = {
26 | expr: [],
27 | pairs: [],
28 | cells: [],
29 | genes: [],
30 | ontology: []
31 | };
32 |
33 | service.load = function load() {
34 |
35 | function _loadPairs(filename) {
36 | return dsv.tsv.get(filename, {cache: cache}, function(d,i) {
37 | return {
38 | i: i,
39 | id: d.Ligand+'_'+d.Receptor,
40 | name: d.Ligand+'-'+d.Receptor,
41 | Ligand: d.Ligand,
42 | Receptor: d.Receptor,
43 | ligandId: d.Ligand+'.ligand',
44 | receptorId: d.Receptor+'.receptor',
45 | Source: d.Source,
46 | Evidence: d.Evidence,
47 | PMIDs: d.PMIDs,
48 | Databases: d.Databases.split(',').join(', ')
49 | };
50 | })
51 | .error(function(data, status, headers, config) {
52 | $log.warn('Error',data, status, headers, config);
53 | })
54 | .success(function(data) {
55 | $log.debug('Pairs loaded:',data.length);
56 | })
57 | .then(function(res) {
58 | return res.data;
59 | });
60 | }
61 |
62 | function _loadExpression(filename) {
63 | return dsv.tsv.getRows(filename, {cache: cache}, function(row, i) {
64 |
65 | if (i === 0) { return row; }
66 | return row.map(function(e,i) {
67 | return i === 0 ? e : +e;
68 | });
69 | })
70 | .error(function(data, status, headers, config) {
71 | $log.warn('Error',data, status, headers, config);
72 | })
73 | .success(function(data) {
74 | $log.debug('Expression rows:', data.length);
75 | })
76 | .then(function(res) {
77 | return res.data;
78 | });
79 | }
80 |
81 | function _loadGenes(filename) {
82 | return dsv.tsv.get(filename, {cache: cache}, function(d) {
83 | /*jshint camelcase: false */
84 | return {
85 | name: d.ApprovedSymbol,
86 | description: d.ApprovedName,
87 | class: d.Class.toLowerCase(),
88 | id: d.ApprovedSymbol+'.'+d.Class.toLowerCase(),
89 | age: d.Age,
90 | taxon: d.Taxon,
91 | consensus: d.Consensus_Call,
92 | type: 'gene',
93 | hgncid: d.HGNCID,
94 | uniprotid: d.UniProtID
95 | };
96 | })
97 | /*jshint camelcase: true */
98 | .error(function(data, status, headers, config) {
99 | $log.warn('Error',data, status, headers, config);
100 | })
101 | .success(function(data) {
102 | $log.debug('Genes loaded:', data.length);
103 | })
104 | .then(function(res) {
105 | return res.data;
106 | });
107 | }
108 |
109 | function _loadOntology(filename) {
110 | return dsv.tsv.get(filename, {cache: cache})
111 | .error(function(data, status, headers, config) {
112 | $log.warn('Error',data, status, headers, config);
113 | })
114 | .then(function(res) {
115 |
116 | var _ontology = {};
117 |
118 | res.data.forEach(function(_item) {
119 | _ontology[_item.Cell] = _item.Ontology;
120 | });
121 |
122 | return _ontology;
123 | });
124 | }
125 |
126 | return $q.all([_loadPairs(files.pairs), _loadExpression(files.expression), _loadOntology(files.ontology), _loadGenes(files.genes)])
127 | .then(function(data) {
128 |
129 | service.data.pairs = data[0];
130 | var _expr = service.data.expr = data[1];
131 | var _ontology = data[2];
132 | service.data.genes = data[3];
133 |
134 | //console.log(data);
135 |
136 | // get samples from expression table
137 | service.data.cells = _expr[0].slice(1).map(function(d,i) {
138 |
139 | var _cell = {
140 | name: d,
141 | id: String(i), // better name?
142 | i: i,
143 | type: 'sample'
144 | };
145 |
146 | var _o = _ontology[d];
147 | if (_o) {
148 | _cell.meta = _cell.meta || {};
149 | _cell.meta.Ontology = _o;
150 | }
151 |
152 | return _cell;
153 | });
154 |
155 | $log.debug('Samples loaded:', service.data.cells.length);
156 | $log.debug('Done loading');
157 |
158 | /* function matchKeys(meta, match) { // Do this on load
159 | var keys = d3.keys(meta);
160 | var values = {};
161 |
162 | keys.forEach(function(k) {
163 | if (k.match(match)) {
164 | values[k.replace(match,'').toLowerCase()] = meta[k];
165 | }
166 | });
167 |
168 | return values;
169 | } */
170 |
171 | // Get index for each gene in expression table
172 | var _genesIndecies = _expr.slice(1).map(_i0);
173 | service.data.genes = service.data.genes.map(function(gene) {
174 | var i = _genesIndecies.indexOf(gene.name);
175 | gene.i = i;
176 |
177 | if (i > -1) {
178 | gene.median = d3.median(_expr[gene.i + 1].slice(1));
179 | } else {
180 | gene.median = 0;
181 | }
182 |
183 | return gene;
184 | });
185 |
186 | /* service.data.genes = _expr.slice(1).map(function(row, i) { // TODO: generate one gene file
187 | return {
188 | name: row[0],
189 | id: row[0],
190 | //id: i, // todo: get rid of this
191 | i: i,
192 | pairs: [], // todo: get rid of this
193 | type: 'gene',
194 | class: 'unknown',
195 | description: '',
196 | _genes: [], // todo: get rid of this
197 | ligands: [], // todo: get rid of this
198 | receptors: []
199 | };
200 | }); */
201 |
202 | // cross reference pairs
203 | service.data.pairs = service.data.pairs.filter(function(pair) {
204 |
205 | var _ligand, _receptor;
206 |
207 | service.data.genes.forEach(function(gene) {
208 | //if (i === 0) {
209 | // console.log(gene.id, pair.ligandId);
210 | //};
211 | if (gene.id === pair.ligandId) {
212 | _ligand = gene;
213 | } else if (gene.id === pair.receptorId) {
214 | _receptor = gene;
215 | }
216 | });
217 |
218 | if (!_ligand || !_receptor) {
219 | $log.warn('Ligand or receptor missing from expression table', pair.ligandId, pair.receptorId);
220 | pair.index = [-1,-1];
221 | return false;
222 | }
223 |
224 | pair.index = [_ligand.i,_receptor.i];
225 | pair.ligand = _ligand;
226 | pair.receptor = _receptor;
227 |
228 | if (_ligand.class !== 'ligand') {
229 | $log.warn('Class inconsistancy',_ligand.name);
230 | return false;
231 | }
232 |
233 | if(_receptor.class !== 'receptor') {
234 | $log.warn('Class inconsistancy',_receptor.name);
235 | return false;
236 | }
237 |
238 | //console.log(_receptor.class == 'receptor');
239 |
240 | // cross reference
241 | //_ligand.class = 'ligand';
242 | //_ligand._genes.push(_receptor.i);
243 | //_ligand.receptors.push({ i: _receptor.i });
244 | //_ligand.meta = matchKeys(pair, 'Ligand.');
245 | //_ligand.description = _ligand.meta.name;
246 | //delete _ligand.meta.name;
247 |
248 | //_receptor.class = 'receptor';
249 | //_receptor._genes.push(_ligand.i);
250 | //_receptor.ligands.push({ i: _ligand.i });
251 | ////_receptor.meta = matchKeys(pair, 'Receptor.');
252 | //_receptor.description = _receptor.meta.name;
253 | //delete _receptor.meta.name;
254 |
255 | //console.log(pair);
256 |
257 | return true;
258 | });
259 |
260 | return service.data;
261 |
262 | });
263 | };
264 |
265 | function _match(obj, text) {
266 | if (text === '') { return true; }
267 |
268 | var key;
269 |
270 | // if both are objects check each key
271 | if (obj && text && typeof obj === 'object' && typeof text === 'object' ) {
272 | if (angular.equals(obj, text)) { return true; }
273 | for (key in text) {
274 | if (!hasOwnProperty.call(obj, key) || !_match(obj[key], text[key])) {
275 | return false;
276 | }
277 | }
278 | return true;
279 | }
280 |
281 | // if array, check at least one match
282 | if (angular.isArray(text)) {
283 | if (text.length === 0) { return true; }
284 | for (key in text) {
285 | if (_match(obj, text[key])) {
286 | return true;
287 | }
288 | }
289 | return false;
290 | }
291 |
292 | if (typeof text === 'boolean') {
293 | return obj === text;
294 | }
295 |
296 | return ''+obj === ''+text;
297 | }
298 |
299 | service.getGenes = function _getGenes(geneFilter) {
300 | if (!geneFilter) { return service.data.genes; }
301 |
302 | return service.data.genes.filter(function(gene) {
303 | //console.log(geneFilter , gene);
304 | return _match(gene,geneFilter);
305 | });
306 | };
307 |
308 | service.getCells = function _getCells(cellFilter) {
309 | if (!cellFilter) { return service.data.cells; }
310 | return service.data.cells.filter(function(cell) {
311 | return _match(cell,cellFilter);
312 | });
313 | };
314 |
315 | service.getExpressionValues = function (filter, max, acc) {
316 | filter = filter || {};
317 | acc = acc || _value;
318 |
319 | var ligandMin = filter.ligandMin || 0;
320 | var receptorMin = filter.receptorMin || 0;
321 |
322 | var edges = [];
323 |
324 | var matchedCells = service.getCells(filter.cell);
325 | var matchedGenes = service.getGenes(filter.gene);
326 | //console.log(matchedCells);
327 |
328 | var count = 0;
329 |
330 | matchedGenes.forEach(function(gene) {
331 | if (gene.i < 0) { return; }
332 | //if (gene.locked) { return false; } // TODO: don't do this here
333 | //if (filter.gene && !_match(gene,filter.gene)) { return false; }
334 |
335 | var min = Math.max(gene.class === 'ligand' ? ligandMin : receptorMin,0);
336 |
337 | matchedCells.forEach(function(cell) {
338 |
339 | if (cell.i < 0) { return; }
340 | //if (cell.locked) { return false; }
341 | //if (filter.cell && !_match(cell,filter.cell)) { return false; }
342 |
343 | var v = +service.data.expr[gene.i+1][cell.i+1];
344 |
345 | if (v > min) { // todo: insertion sort
346 |
347 | count++;
348 |
349 | edges.push( // TODO: sorted push
350 | {
351 | gene: gene,
352 | cell: cell,
353 | value: v,
354 | specificity: (v+1)/(gene.median+1)
355 | });
356 |
357 | if (edges.length > max) {
358 | edges = edges.sort(function(a,b){ return acc(b) - acc(a); }).slice(0,max);
359 | }
360 |
361 | }
362 |
363 | });
364 | });
365 |
366 | //$log.debug('found',edges.length, 'expression values', count);
367 | return edges;
368 |
369 | };
370 |
371 | service.getPairs = function _getPairs(pairFilter) {
372 | if (!pairFilter) { return service.data.pairs; }
373 | return service.data.pairs.filter(function(pair) {
374 | return _match(pair,pairFilter);
375 | });
376 | };
377 |
378 | service.getPathways = function getPathways(filter, max, acc) {
379 | max = max || 10;
380 | acc = acc || _value;
381 |
382 | var paths = [];
383 |
384 | $log.debug('Calculating pathways');
385 |
386 | var ligandMin = (filter.ligandMin !== undefined) ? filter.ligandMin : 10;
387 | var receptorMin = (filter.receptorMin !== undefined) ? filter.receptorMin : 10;
388 |
389 | var count = 0;
390 |
391 | var matchedPairs = service.getPairs(filter.pair);
392 | if (matchedPairs.length < 1) { return []; }
393 |
394 | var matchedSourceCells = service.getCells(filter.source);
395 | if (matchedSourceCells.length < 1) { return []; }
396 |
397 | var matchedTargetCells = service.getCells(filter.target);
398 | if (matchedTargetCells.length < 1) { return []; }
399 |
400 | matchedPairs.forEach(function (pair) {
401 |
402 | //if (pair.locked) { return false; }
403 |
404 | if (pair.ligand.i < 0 || pair.receptor.i < 0) { return; }
405 |
406 | //if (filter.pair && !_match(pair,filter.pair)) { return; }
407 |
408 | //if (pair.ligand.locked) { return false; }
409 | //if (pair.receptor.locked) { return false; }
410 |
411 | if (filter.ligand && !_match(pair.ligand,filter.ligand)) { return; } // TOD: are these needed?
412 | if (filter.receptor && !_match(pair.receptor,filter.receptor)) { return; }
413 |
414 | //console.log(pair.name);
415 |
416 | //var ligandEdges = service.getExpressionValues({ cell: filter.source, gene: pair.ligand });
417 | //var receptorEdges = service.getExpressionValues({ cell: filter.target, gene: pair.receptor });
418 |
419 | //console.log(ligandEdges, receptorEdges);
420 |
421 | matchedSourceCells.forEach(function(source) {
422 | //ligandEdges.forEach(function(ligandEdge) {
423 | //var source = ligandEdge.cell;
424 |
425 | //if (source.locked) { return false; } // Move these out
426 |
427 | var l = +service.data.expr[pair.ligand.i+1][source.i+1];
428 | var ls = (l+1)/(pair.ligand.median+1);
429 |
430 | if (l <= ligandMin) { return; }
431 |
432 | //if (filter.source && !_match(source,filter.source)) { return; }
433 | //if (filter.cell && !_match(source,filter.cell)) { return; }
434 |
435 | //console.log(source.name);
436 |
437 | matchedTargetCells.forEach(function(target) {
438 | //receptorEdges.forEach(function(receptorEdge) {
439 | //var target = receptorEdge.cell;
440 |
441 | //if (target.locked) { return false; }
442 |
443 | var r = +service.data.expr[pair.receptor.i+1][target.i+1];
444 | var rs = (r+1)/(pair.receptor.median+1);
445 |
446 | if (r <= receptorMin) { return; }
447 |
448 | //if (filter.target && !_match(target,filter.target)) { return; }
449 | //if (filter.cell && !_match(target,filter.cell)) { return; }
450 |
451 | //console.log(target.name);
452 |
453 | // todo: use insertion sort (fin position, if pos > max, don't push)
454 | paths.push({
455 | pair: pair,
456 | source: source,
457 | ligand: pair.ligand,
458 | receptor: pair.receptor,
459 | target: target,
460 | ligandExpression: l,
461 | receptorExpression: r,
462 | value: l*r,
463 | specificity: ls*rs
464 | });
465 |
466 | if (paths.length > max) {
467 | paths = paths.sort(function(a,b) { return acc(b) - acc(a); }).slice(0,max);
468 | }
469 |
470 | count++;
471 |
472 | });
473 | });
474 |
475 | });
476 |
477 | $log.debug('found',paths.length,'paths out of',count);
478 | //console.log('found',paths.length,'paths out of',count);
479 |
480 | return paths;
481 | };
482 |
483 | return service;
484 |
485 | });
486 |
487 | })();
488 |
--------------------------------------------------------------------------------
/app/components/charts/force-controller.js:
--------------------------------------------------------------------------------
1 | /* global d3 */
2 | /* global lrd3 */
3 | /* global _F */
4 |
5 | (function() {
6 | 'use strict';
7 |
8 | angular.module('lrSpaApp')
9 |
10 | .service('forceGraph', function($log, $window, $rootScope, $timeout, Graph, debounce, growl, cfpLoadingBar) { // TODO: should be a directive
11 |
12 | var chart = new lrd3.charts.forceGraph();
13 | var graph = new Graph();
14 |
15 | //function setupChart() {
16 | //chart = force2Graph(); // TODO: look at options
17 |
18 | // Events
19 | var _fixed = _F('fixed');
20 |
21 | chart.on('hover', debounce(function(d) {
22 | graph.data.hoverEvent = true;
23 |
24 | graph.data.selectedItems = graph.data.selectedItems.filter(_fixed);
25 |
26 | if (d && graph.data.selectedItems.indexOf(d) < 0) {
27 | graph.data.selectedItems.unshift(d);
28 | }
29 |
30 | }));
31 |
32 | chart.on('selectionChanged', debounce(function() {
33 | //console.log(graph.data.selectedItems);
34 | var nodes = graph.data.nodes.filter(_fixed);
35 | var edges = graph.data.edges.filter(_fixed);
36 | graph.data.selectedItems = nodes.concat(edges);
37 | }));
38 |
39 | // Accesors
40 | var _value = _F('value');
41 | var _ticked = _F('ticked');
42 | //var _F = function(key) { return function(d) {return d[key];}; };
43 |
44 | //var type = _F('type');
45 | var _valueComp = function(a,b) { return _value(b) - _value(a); };
46 | var _valueFilter = function(d) { return d.type !== 'node' || d.value >= 0; };
47 | //var typeFilter = function(type) { return function(d) {return d.type === type;}; };
48 |
49 | var _value0 = function(d) { return d.values[0]; };
50 | var _value1 = function(d) { return d.values[1]; };
51 | //var gtZero = function(d) {return d>0;};
52 |
53 | function _makeNodes(genes, cells) {
54 |
55 | graph.data.nodes = [];
56 | graph.data.nodesIndex = {};
57 |
58 | cells.forEach(function(cell) {
59 | var _node = angular.extend(cell, new graph.Node());
60 |
61 | _node.type = 'sample';
62 | graph.addNode(_node);
63 | });
64 |
65 | genes.forEach(function(gene) {
66 | var _node = angular.extend(gene, new graph.Node());
67 | _node.type = 'gene';
68 |
69 | graph.addNode(_node);
70 | });
71 |
72 | }
73 |
74 | function _sortAndFilterNodes() { //TODO: DRY this!!!
75 |
76 | graph.data.nodeCount = graph.data.nodes.length; // do I need this?
77 |
78 | var nodes = graph.data._nodes.sort(_valueComp).filter(_valueFilter); // Sort and filter out zeros
79 |
80 | var rankedLigands = nodes // Ligand
81 | .map(_value0)
82 | //.filter(gtZero)
83 | .sort(d3.ascending);
84 |
85 | var rankedReceptors = nodes // Receptor
86 | .map(_value1)
87 | //.filter(gtZero)
88 | .sort(d3.ascending);
89 |
90 | graph.data.ligandExtent = d3.extent(rankedLigands); // TODO: Already ranked, don't need extent
91 | graph.data.receptorExtent = d3.extent(rankedReceptors);
92 |
93 | /* var filter0 = d3.quantile(rankedLigands, 1-options.ligandRankFilter);
94 | var filter1 = d3.quantile(rankedReceptors, 1-options.receptorRankFilter);
95 |
96 | filter0 = Math.max(filter0, 0) || 0;
97 | filter1 = Math.max(filter1, 0) || 0;
98 |
99 | //console.log(filter0,filter1);
100 |
101 | var filtered = nodes.filter(function(d) {
102 | //console.log(d);
103 | return ( d.type !== 'sample' || d.values[0] >= filter0 || d.values[1] >= filter1 );
104 | }); */
105 |
106 | //console.log(filtered.length);
107 |
108 | graph.data._nodes = nodes; //filtered;
109 |
110 | }
111 |
112 | //var MAXEDGES = 1000;
113 |
114 | var StopIteration = new Error('Maximum number of edges exceeded');
115 |
116 | function _makeEdges(cells, genes, pairs, expr, options) { // TODO: better
117 |
118 | try {
119 | return __makeEdges(cells, genes, pairs, expr, options);
120 | } catch(e) {
121 | if(e !== StopIteration) {
122 | throw e;
123 | } else {
124 | growl.addErrorMessage(StopIteration.message);
125 | return [];
126 | }
127 | }
128 | }
129 |
130 | function __makeEdges(cells, genes, pairs, expr, options) { //selected nodes
131 |
132 | graph.data.edges = [];
133 |
134 | //expression edges
135 | cells.forEach(function(cell) {
136 | if (!cell.ticked) { return; }
137 | //var nodeExpr = [];
138 |
139 | genes.forEach(function(gene) {
140 | if (!gene.ticked) { return; }
141 |
142 | //console.log(gene.i, cell.i);
143 | var v = (gene.i > -1 && cell.i > -1) ? +expr[gene.i + 1][cell.i + 1] : 0;
144 | var min = (gene.class === 'receptor') ? options.receptorFilter : options.ligandFilter;
145 | min = Math.max(min,0);
146 |
147 | if (v > min) {
148 | var src, tgt;
149 |
150 | if (gene.class === 'receptor') {
151 | src = gene;
152 | tgt = cell;
153 | } else {
154 | src = cell;
155 | tgt = gene;
156 | }
157 |
158 | //console.log(data.edgesIndex[src.id][tgt.id]);
159 | var s = (v+1)/(gene.median+1);
160 |
161 | var _edge = new graph.Edge(graph.data.nodesIndex[src.id],graph.data.nodesIndex[tgt.id]);
162 | _edge.gene = gene;
163 | _edge.cell = cell;
164 | _edge.value = v;
165 | //_edge.i = gene.i; // remove
166 | _edge.id = ''+src.id+tgt.id; // remove {target, source}.id
167 | _edge.type = 'expression'; // remove
168 | _edge.class = gene.class;
169 | _edge._specificity = s;
170 | _edge.specificity = Math.log(s)/Math.log(10);
171 |
172 | graph.addEdge(_edge);
173 | //nodeExpr.push(_edge);
174 | }
175 |
176 | });
177 |
178 | });
179 |
180 | // sample-sample edges
181 | pairs.forEach(function addLinks(_pair) {
182 | if (!_pair.ticked) { return; }
183 |
184 | var ligandEdges = graph.data.inEdgesIndex[_pair.ligandId];
185 | var receptorEdges = graph.data.outEdgesIndex[_pair.receptorId];
186 |
187 | ligandEdges.forEach(function(ligand) {
188 | receptorEdges.forEach(function(receptor) {
189 |
190 | var value = ligand.value*receptor.value;
191 | if (value > 0 && ligand.value > options.receptorFilter && receptor.value > options.ligandFilter) {
192 | //console.log(ligand.value,receptor.value,value);
193 |
194 | //var _edge;
195 |
196 | //if (data.edgesIndex[ligand.source.id]) {
197 | //console.log(data.edgesIndex[ligand.source.id][receptor.target.id]);
198 | //}
199 | var src = ligand.source;
200 | var tgt = receptor.target;
201 |
202 | var _edge =
203 | graph.data.edgesIndex[src.id][tgt.id] ||
204 | new graph.Edge(src,tgt);
205 |
206 | var _s = ligand._specificity*receptor._specificity;
207 | var s = Math.log(_s)/Math.log(10);
208 |
209 | if (!_edge._specificity) {
210 | _edge._specificity = _edge.specificity = 0;
211 | }
212 |
213 | _edge.type = 'sample-sample';
214 | _edge.id =
215 | _edge.name = src.name + ' -> ' + tgt.name;
216 | _edge.id = ''+src.id+tgt.id;
217 | _edge.value += value;
218 | _edge._specificity += _s;
219 | _edge.specificity += s;
220 |
221 | _edge.pairs = _edge.pairs || [];
222 | _edge.pairs.push({
223 | pair: _pair,
224 | value: value,
225 | specificity: s
226 | });
227 |
228 | graph.addEdge(_edge);
229 | }
230 | //delete ligandEdges[i];
231 | //delete receptorEdges[j];
232 | });
233 | //delete ligandEdges[i];
234 | });
235 |
236 | });
237 |
238 | var _type = _F('type');
239 | var _typeIsExpression = _type.eq('expression');
240 | var _valueDesc = function(a,b) { return b.value - a.value; };
241 |
242 |
243 | graph.data.nodes.forEach(function(node) { // todo: move
244 | if (!node.ticked) { return; }
245 |
246 | //var a = function(a,b) { return b.value - a.value; };
247 |
248 | //console.log(data.edgesIndex[node.id]);
249 |
250 | //data.edgesIndex[node.id] = data.edgesIndex[node.id].sort(a);
251 | graph.data.outEdgesIndex[node.id] = graph.data.outEdgesIndex[node.id].sort(_valueDesc);
252 | graph.data.inEdgesIndex[node.id] = graph.data.inEdgesIndex[node.id].sort(_valueDesc);
253 |
254 | graph.data._outEdgesIndex[node.id] = graph.data._outEdgesIndex[node.id].sort(_valueDesc);
255 | graph.data._inEdgesIndex[node.id] = graph.data._inEdgesIndex[node.id].sort(_valueDesc);
256 |
257 | node.values[0] = d3.sum(graph.data._outEdgesIndex[node.id].filter(_typeIsExpression),_value);
258 | node.values[1] = d3.sum(graph.data._inEdgesIndex[node.id].filter(_typeIsExpression),_value);
259 | node.value = d3.sum(node.values);
260 | //console.log(node.id);
261 | });
262 |
263 | }
264 |
265 | function _draw(options) {
266 | $log.debug('Drawing graph');
267 |
268 | //if (!chart) {
269 | // setupChart(options);
270 | //}
271 |
272 | if (graph.data.nodes.length < 1) {
273 | _clear();
274 | return;
275 | }
276 |
277 | //$timeout(function() {
278 | d3.select('#vis svg')
279 | .classed('labels',options.showLabels)
280 | .datum(graph.data)
281 | .call(chart);
282 | //});
283 |
284 | }
285 |
286 | function _update() {
287 | if (chart.update) {
288 | $log.debug('Updating graph');
289 | chart.update();
290 | }
291 | }
292 |
293 | function _clear() {
294 | $log.debug('Clearing');
295 |
296 | graph.data.nodes = []; // Todo: graph.clear()
297 | graph.data.edges = [];
298 | graph.data.nodesIndex = {};
299 | graph.data.edgesIndex = {};
300 |
301 | d3.selectAll('#vis svg g').remove();
302 | }
303 |
304 | function _makeNetwork(_data, options) { // pairs, cells, expr, options
305 |
306 | if (!_data) {return;}
307 |
308 | var pairs = _data.pairs.filter(function(d) { return d.ticked; }); // remove?
309 | var cells = _data.cells.filter(function(d) { return d.ticked; });
310 |
311 | var expr = _data.expr;
312 | var genes = _data.genes.filter(function(d) { return d.ticked; });
313 |
314 | $log.debug('Constructing');
315 |
316 | if (cells.length < 1 && genes.length < 1) {
317 | _clear();
318 |
319 | growl.addWarnMessage('No cells or genes selected');
320 | return;
321 | }
322 |
323 | cfpLoadingBar.start();
324 |
325 | _makeNodes(_data.genes, _data.cells);
326 | _makeEdges(_data.cells, _data.genes, pairs, expr, options);
327 |
328 | graph.data.edges = graph.data.edges.filter(_F('type').eq('sample-sample'));
329 |
330 | //console.log(data.nodes);
331 |
332 | $log.debug('Total nodes: ', graph.data.nodes.length);
333 | $log.debug('Total Edges: ', graph.data.edges.length);
334 |
335 | graph.data.edgeCount = graph.data.edges.length; // needed?
336 |
337 | //_sortAndFilterEdges(options);
338 |
339 | cfpLoadingBar.inc();
340 |
341 | graph.data._nodes = graph.data.nodes
342 | .filter(_ticked)
343 | .filter(_F('type').eq('sample')); // combine these -> _sortAndFilterNodes
344 | _sortAndFilterNodes(options);
345 |
346 | graph.data.edges.forEach(function(d) { // -> sort and filter edges
347 | d.ticked = false;
348 | });
349 |
350 | graph.data._nodes.forEach(function(node) {
351 | graph.data.outEdgesIndex[node.id].forEach(function(d) {
352 | d.ticked = true;
353 | });
354 | });
355 |
356 |
357 | graph.data._edges = graph.data.edges
358 | .filter(_ticked);
359 | //.filter(_F('type').eq('sample-sample'));
360 |
361 | if (graph.data._edges.length > options.edgeRankFilter*graph.data._edges.length) {
362 |
363 | graph.data._edges = graph.data._edges
364 | .sort(_valueComp)
365 | .slice(0,options.edgeRankFilter*graph.data.edges.length);
366 |
367 | graph.data._nodes.forEach(function(node) {
368 | node.inDegree = node.outDegree = 0;
369 | });
370 |
371 | graph.data._edges.forEach(function(edge) {
372 | edge.target.inDegree++;
373 | edge.source.outDegree++;
374 | });
375 |
376 | graph.data._nodes = graph.data._nodes.filter(function(node) {
377 | return node.inDegree > 0 || node.outDegree > 0;
378 | });
379 |
380 | }
381 |
382 | $log.debug('Filtered nodes: ', graph.data._nodes.length);
383 | $log.debug('Filtered edges: ', graph.data._edges.length);
384 |
385 | cfpLoadingBar.inc();
386 |
387 | graph.data._nodes.forEach(function(d) {
388 |
389 | if (d.type === 'gene') {
390 | d.group = 'gene.'+d.class;
391 | } else {
392 | d.group = 'sample';
393 | if (d.values[0] > 0) { d.class='ligand';} // Ligand only, lime green
394 | if (d.values[1] > 0) { d.class='receptor';} // Receptor only, Very light blue
395 | if (d.values[0] > 0 && d.values[1] > 0) {d.class='both';} // Both, Dark moderate magenta
396 | }
397 | //console.log(d.ntype);
398 | });
399 |
400 | cfpLoadingBar.complete();
401 |
402 | }
403 |
404 | /* function __getDATA() { // This is hive version, move to service
405 | var _json = {
406 |
407 | };
408 |
409 | _json.nodes = graph.data.nodes.map(function(node, i) {
410 |
411 | var _n = {
412 | data: {
413 | id: i,
414 | name: node.name,
415 | type: node.type.split('.')[1] || 'sample',
416 | value: String(node.value),
417 | genes: node.genes
418 | },
419 | position: {
420 | x: node.x,
421 | y: node.y
422 | }
423 | };
424 |
425 | _.extend(_n.data, node.meta);
426 |
427 | return _n;
428 | });
429 |
430 | _json.edges = graph.data.edges.map(function(edge) {
431 | return {
432 | data: {
433 | id: edge.index,
434 | name: edge.name,
435 | source: graph.data.nodes.indexOf(edge.source),
436 | target: graph.data.nodes.indexOf(edge.target),
437 | value: String(edge.value)
438 | }
439 | };
440 | });
441 |
442 | return _json;
443 | }
444 |
445 | function _getJSON() { // This is hive version, move to service
446 | var _json = {
447 | 'format_version' : '1.0',
448 | 'generated_by' : [name,version].join('-'),
449 | 'target_cytoscapejs_version' : '~2.1',
450 | data: {},
451 | elements: __getDATA()
452 | };
453 |
454 | _json.elements.nodes.forEach(function(d) {
455 | d.data.id = String(d.data.id);
456 | });
457 |
458 | _json.elements.edges.forEach(function(d) {
459 | d.data.id = String(d.data.id);
460 | d.data.source = String(d.data.source);
461 | d.data.target = String(d.data.target);
462 | });
463 |
464 | return JSON.stringify(_json);
465 | }
466 |
467 | function _getGML() { // This is hive version, move to service
468 |
469 | function quote(str) {
470 | return '"'+str+'"';
471 | }
472 |
473 | function indent(n, p) {
474 | n = n || 2;
475 | p = p || ' ';
476 | var pp = strRepeat(p, n);
477 | return function(s) {
478 | return pp+s;
479 | };
480 | }
481 |
482 | function strRepeat(str, qty){
483 | var result = '';
484 | while (qty > 0) {
485 | result += str;
486 | qty--;
487 | }
488 | return result;
489 | }
490 |
491 | function convert(obj, k) {
492 | if (_.isString(obj)) {return [k,quote(obj)].join(' ');}
493 | if (_.isArray(obj)) {return [k,quote(String(obj))].join(' ');}
494 | if (_.isObject(obj)) {
495 | var e = _.map(obj, convert);
496 | e.unshift(k,'[');
497 | e.push(']');
498 | return e.join(' ');
499 | }
500 | return [k,String(obj)].join(' ');
501 | }
502 |
503 | var _data = __getDATA();
504 |
505 | var _gml = [];
506 | _gml.push('graph [');
507 |
508 | _data.nodes.forEach(function(d) {
509 | _gml.push(' node [');
510 | var e = _.map(d.data, convert).map(indent(4));
511 | _gml = _gml.concat(e);
512 |
513 | _gml.push(' graphics [');
514 |
515 | _gml.push(indent(6)(convert(d.position, 'center')));
516 |
517 | _gml.push(' ]');
518 |
519 | _gml.push(' ]');
520 | });
521 |
522 | _data.edges.forEach(function(d) {
523 | _gml.push(' edge [');
524 | var e = _.map(d.data, convert).map(indent(4)); //function(v,k) { return ' '+convert(v,k); } );
525 | _gml = _gml.concat(e);
526 | _gml.push(' ]');
527 | });
528 |
529 | _gml.push(']');
530 |
531 | return _gml.join('\n');
532 |
533 | }*/
534 |
535 | return {
536 | graph: graph,
537 | data: graph.data,
538 | chart: chart,
539 | update: debounce(_update, 30),
540 | makeNetwork: _makeNetwork,
541 | draw: _draw,
542 | clear: _clear//,
543 | //getJSON: _getJSON,
544 | //getGML: _getGML
545 | };
546 |
547 | });
548 |
549 | })();
550 |
--------------------------------------------------------------------------------
/app/components/charts/hive-chart.js:
--------------------------------------------------------------------------------
1 | /* global d3 */
2 | /* global _F */
3 |
4 | (function(root) {
5 | 'use strict';
6 |
7 | function degrees(radians) {
8 | return radians / Math.PI * 180-90;
9 | }
10 |
11 | var hiveSymbol = function() {
12 | var type = d3.functor('circle'), size = 64;
13 |
14 | /* jshint -W014 */
15 | function symbolCircle(size) {
16 | var r = Math.sqrt(size / 3.14159);
17 | return 'M0,' + r
18 | + 'A' + r + ',' + r + ' 0 1,1 0,' + (-r)
19 | + 'A' + r + ',' + r + ' 0 1,1 0,' + r
20 | + 'Z';
21 | }
22 |
23 | function symbolSquare(size) {
24 | var r = Math.sqrt(size) / 2;
25 | return 'M' + -r + ',' + -r
26 | + 'L' + r + ',' + -r
27 | + ' ' + r + ',' + r
28 | + ' ' + -r + ',' + r
29 | + 'Z';
30 | }
31 |
32 | function symbolV(size) {
33 | var r = Math.sqrt(size) / 2;
34 | return 'M' + -r + ',' + -r
35 | + 'L' + 0 + ',' + 0
36 | + ' ' + r + ',' + -r
37 | + ' ' + r + ',' + r
38 | + ' ' + -r + ',' + r
39 | + 'Z';
40 | }
41 |
42 | function symbolA(size) {
43 | var r = Math.sqrt(size) / 2;
44 | return 'M' + -r + ',' + -r
45 | + 'L' + r + ',' + -r
46 | + ' ' + r + ',' + 0
47 | + ' ' + 0 + ',' + r
48 | + ' ' + -r + ',' + 0
49 | + 'Z';
50 | }
51 | /* jshint +W014 */
52 |
53 | var symbols = d3.map({
54 | 'circle': symbolCircle,
55 | 'square': symbolSquare,
56 | 'A': symbolA,
57 | 'V': symbolV
58 | });
59 |
60 | /* jshint -W040 */
61 | function symbol(d, i) {
62 | return (symbols.get(type.call(this, d, i))
63 | || symbolCircle)
64 | (size.call(this, d, i));
65 | }
66 | /* jshint +W040 */
67 |
68 | symbol.type = function(x) {
69 | if (!arguments.length) {return type;}
70 | type = d3.functor(x);
71 | return symbol;
72 | };
73 |
74 | // size of symbol in square pixels
75 | symbol.size = function(x) {
76 | if (!arguments.length) {return size;}
77 | size = d3.functor(x);
78 | return symbol;
79 | };
80 |
81 | return symbol;
82 | };
83 |
84 | var hiveGraph = function() {
85 | var width = 500, height = 500;
86 | var margin = { top: 10, right: 60, bottom: 240, left: 60};
87 | //var padding = { top: 60, right: 100, bottom: 60, left: 60};
88 |
89 | function chart(selection) {
90 | selection.each(chart.draw);
91 | }
92 |
93 | // elements
94 | var nodes, links;
95 |
96 | // Private objects
97 | var zoom = d3.behavior.zoom();
98 |
99 | // Scales
100 | var groups = ['gene.ligand','gene.receptor','sample'];
101 | var ncolor = d3.scale.ordinal()
102 | .domain(['ligand','both','receptor'])
103 | //.range(['#8c564b','yellow','#1f77b4']);
104 | .range(['#ed1940','yellow','#3349ff']);
105 | //.range(['#ed1940','#a650e2','#9999ff']);
106 | //.range(['#ed1940','#a650e2','#3349ff']);
107 | var slog = d3.scale.log().range([2,9]).clamp(true); // Maps value to normalized edge width
108 | var eopac = d3.scale.linear().range([0.2,0.8]).clamp(true);
109 | var rsize = d3.scale.linear().range([3, 12]).clamp(true); // Maps value to size
110 | var angle = d3.scale.ordinal().domain(groups).range([-Math.PI/4+Math.PI, Math.PI/4+Math.PI, 0]); // maps type to angle
111 | var radius = d3.scale.linear(); // maps position to radius
112 |
113 | var _y = {}; // maps index to position
114 | groups.forEach(function(d) {
115 | _y[d] = d3.scale.ordinal().rangePoints([0, 1]);
116 | });
117 |
118 | // Value accessors
119 | //var _type = _F('type');
120 | var _group = _F('group');
121 | var _value = _F('value');
122 | //var _name = _F('name');
123 | var _id = _F('id');
124 | var _class = _F('class');
125 | var _fixed = _F('fixed');
126 | var _edgeFixed = _F('source',_fixed).and(_F('target',_fixed));
127 | var _hover = _F('hover');
128 |
129 | var linkName = function(d) { return [d.source.name, d.name, d.target.name].join(':'); };
130 | var nodeName = function(d) { // TODO: not this
131 | return d.name.split('.')[0];
132 | };
133 |
134 | // Range accesors
135 | var _angle = _F('group', angle); //function(d) { return angle(d.type); };
136 | var _radius = function(d) {
137 | if (!_y[_group(d)]) { return 0; }
138 |
139 | return radius(_y[_group(d)](d._i));
140 | };
141 | var _ncolor = _F('class', ncolor); //function(d) { return ncolor(d.class); };
142 | var _slog = _F(_value, slog);
143 |
144 | var dispatch = d3.dispatch('hover','selectionChanged','contextmenu');
145 |
146 | chart.draw = function draw(graph) {
147 |
148 | var container = d3.select(this);
149 |
150 | width = parseInt(container.style('width'));
151 | height = parseInt(container.style('height'));
152 |
153 | var size = Math.min(height, width)/(1+Math.cos(Math.PI/3))/1.5;
154 | radius.range([size/10, size]);
155 |
156 | chart.update = function() {
157 | updateClasses();
158 | };
159 |
160 | chart.container = container;
161 |
162 | container
163 | .attr('width', width)
164 | .attr('height', height)
165 | ;
166 |
167 | // Ranges
168 | var _e = d3.extent(graph._edges, _value); // Edge values
169 | slog.domain(_e);
170 | eopac.domain(_e);
171 |
172 | var _n = d3.extent(graph._nodes, _value); // Node values
173 | rsize.domain(_n);
174 |
175 | var nodesByType = d3.nest() // Nest nodes by type
176 | .key(_group)
177 | .sortKeys(d3.ascending)
178 | .entries(graph._nodes);
179 |
180 | nodesByType.forEach(function(type) { // Setup domain for position range
181 | if (_y[type.key]) {
182 | _y[type.key].domain(d3.range(type.values.length));
183 |
184 | type.values.forEach(function(node,i) {
185 | node._i = i;
186 | });
187 | }
188 | });
189 |
190 | function _labelAngle(d) {
191 | var a = -_angle(d)+Math.PI;
192 | a = (a+2*Math.PI) % (2*Math.PI);
193 | if (a > Math.PI/2 && a < 3*Math.PI/2) {return a;}
194 | return a+Math.PI/2;
195 | }
196 |
197 | var hiveLink = d3.hive.link()
198 | .angle(_angle)
199 | .radius(_radius);
200 |
201 | container.selectAll('defs').remove();
202 |
203 | /* var markers = container
204 | .append('defs')
205 | .selectAll('marker').data(graph._edges).enter()
206 | .append('svg:marker')
207 | .attr({
208 | class: 'Triangle',
209 | viewBox: '0 -5 10 10',
210 | refY: 0,
211 | refX: 20,
212 | markerWidth: 5,
213 | markerHeight: 5,
214 | 'stroke-width': 1,
215 | markerUnits: 'strokeWidth',
216 | orient: 'auto',
217 | id: function(d,i) { return 'arrow-'+i; }
218 | });
219 |
220 | markers.append('svg:path')
221 | .attr('d', 'M0,-5L10,0L0,5'); */
222 |
223 | var g = container.selectAll('.hiveGraph').data([1]);
224 |
225 | g.enter()
226 | .append('g')
227 | .attr('class', 'hiveGraph')
228 | .each(function() { // Only called once
229 | container.call(zoom.on('zoom', rescale));
230 | zoom.translate([width/2,height/2+(height/2-size)]);
231 | zoom.event(container);
232 | });
233 |
234 | container.on('click', function() {
235 | if (d3.event.defaultPrevented) {return;}
236 | d3.event.stopPropagation();
237 |
238 | graph.nodes.forEach(function(d) {
239 | d.fixed = false;
240 | });
241 |
242 | updateClasses();
243 | dispatch.selectionChanged();
244 | });
245 |
246 | // rescale g
247 | function rescale() {
248 | var trans=d3.event.translate;
249 | var scale=d3.event.scale;
250 |
251 | g.attr('transform',
252 | 'translate(' + trans + ') scale(' + scale + ')');
253 | }
254 |
255 | //g.selectAll('.axis').remove();
256 |
257 | var axes = g.selectAll('.axis')
258 | .data(groups);
259 |
260 | axes.enter().append('line')
261 | .style({
262 | stroke: '#000',
263 | 'stroke-width': '1.5px'
264 | });
265 |
266 | axes
267 | .attr({
268 | class: 'axis',
269 | transform: function(d) { return 'rotate(' + degrees(angle(d)) + ')'; },
270 | x1: radius.range()[0],
271 | x2: radius.range()[1]
272 | });
273 |
274 | //function sign(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }
275 |
276 | function classNeighbors(node, direction, key, value) {
277 | if (arguments.length < 4) { value = true; }
278 |
279 | var _tgt = (direction <= 0) ? 'source' : 'target';
280 | //var _dir = (direction <= 0) ? 'lin' : 'lout';
281 |
282 | var edges = (direction <= 0) ? graph._inEdgesIndex[node.id] : graph._outEdgesIndex[node.id];
283 |
284 | edges.forEach(function(d) {
285 | if(d) {
286 | d[key] = value; // class edge
287 |
288 | var t = d[_tgt];
289 | t[key] = value; // class node
290 |
291 | if (t.type === 'gene') {
292 | classNeighbors(t, direction, key, value);
293 | }
294 |
295 | }
296 | });
297 | }
298 |
299 | function mouseoverEdgeHighlight(d) {
300 |
301 | //var edge = d3.select(this);
302 |
303 | var tgt = d.target;
304 | var src = d.source;
305 |
306 | d.hover = tgt.hover = src.hover = true;
307 |
308 | if (tgt.type === 'gene') {
309 | classNeighbors(tgt, 3, 'hover');
310 | }
311 |
312 | if (src.type === 'gene') {
313 | classNeighbors(src, -3, 'hover');
314 | }
315 |
316 | chart.container.classed('hover',true);
317 | updateClasses();
318 |
319 | dispatch.hover(d);
320 | }
321 |
322 | function mouseoverNodeHighlight(d) {
323 |
324 | //var node = d3.select(this);
325 |
326 | d.hover = true;
327 |
328 | classNeighbors(d, 3, 'hover');
329 | classNeighbors(d, -3, 'hover');
330 |
331 | chart.container.classed('hover',true);
332 | updateClasses();
333 |
334 | dispatch.hover(d);
335 | }
336 |
337 | function updateClasses() {
338 | nodes
339 | .classed('hover', _hover)
340 | .classed('fixed', _fixed);
341 |
342 | links
343 | .classed('hover', _hover)
344 | .classed('fixed', _edgeFixed);
345 | }
346 |
347 | var _hoff = function(d) {d.hover = false; };
348 | function mouseoutHighlight() {
349 | chart.container.classed('hover',false);
350 |
351 | nodes.each(_hoff);
352 | links.each(_hoff);
353 |
354 | updateClasses();
355 | dispatch.hover(null);
356 | }
357 |
358 | // LINKS
359 | var gLinks = g.selectAll('g.links').data([graph._edges]);
360 |
361 | gLinks.enter()
362 | .append('g')
363 | .classed('links', true);
364 |
365 | links = gLinks.selectAll('.link')
366 | .data(_F(), linkName);
367 |
368 | links.enter().append('svg:path')
369 | .classed('link', true)
370 | .style({
371 | fill: 'none',
372 | stroke: '#666',
373 | 'stroke-width': '1.5px',
374 | })
375 | .on('mouseover.highlight',mouseoverEdgeHighlight)
376 | .on('mouseout.highlight',mouseoutHighlight)
377 | //.on('mouseover', tooltipShow)
378 | //.on('mouseout', tooltipHide)
379 | ;
380 |
381 | links
382 | .attr({
383 | id: function(d) { return 'link-'+d._index; },
384 | d: hiveLink,
385 | //'marker-end': function(d,i) { return d.type === 'pair' ? 'url(#arrow-'+i+')' : ''; }
386 | })
387 | .style({
388 | 'stroke-width': _slog,
389 | opacity: function(d) { return eopac(d.value); }
390 | })
391 | ;
392 |
393 | links.exit().remove();
394 |
395 | /* links.each(function(d, i) {
396 | if (d.type !== 'pair') {return;}
397 |
398 | var def = d3.select(markers[0][i]);
399 | var dy = def.attr('refX');
400 |
401 | var l = this.getTotalLength();
402 | var p0 = this.getPointAtLength(l);
403 | var pm1 = this.getPointAtLength(l-dy);
404 |
405 | var a = Math.atan((pm1.y-p0.y)/(pm1.x-p0.x));
406 |
407 | def
408 | .attr('orient', degrees(a-Math.PI/2));
409 | }); */
410 |
411 | //console.log(graph.edges);
412 |
413 | // NODES
414 | var nodesLayer = g.selectAll('g.nodes').data([graph._nodes]);
415 |
416 | nodesLayer.enter()
417 | .append('g')
418 | .classed('nodes', true);
419 |
420 | // Select
421 |
422 | nodesLayer.selectAll('.node').remove(); // TODO: not this, hack to ensure nodes are stacked
423 |
424 | nodes = nodesLayer.selectAll('.node').data(_F(), _id);
425 |
426 | function nodeClick(d) {
427 | d3.event.stopPropagation();
428 |
429 | var p = d.fixed;
430 |
431 | if (d3.event.altKey) { // remove
432 | d.ticked = (d.ticked) ? false : true;
433 | } else if (d3.event.ctrlKey && !d3.event.shiftKey) { // add to selection
434 | d.fixed = (d.fixed) ? false : true;
435 | } else if (d3.event.shiftKey) { // add all to selection
436 | graph.nodes.forEach(function(d) {
437 | d.fixed = (d.hover) ? !p : (!d3.event.ctrlKey) ? false : d.fixed;
438 | });
439 | } else { // change selection
440 | graph.nodes.forEach(function(d) {
441 | d.fixed = false;
442 | });
443 | d.fixed = (p) ? false : true;
444 | }
445 |
446 | updateClasses(); //function(d) { return d.source.fixed && d.target.fixed; });
447 | dispatch.selectionChanged(d);
448 | }
449 |
450 | // Create
451 | var nodesEnter = nodes.enter().append('g')
452 | .classed('node', true)
453 | .style({
454 | fill: '#ccc',
455 | 'fill-opacity': 1,
456 | stroke: '#333',
457 | 'stroke-width': '1px'
458 | })
459 | .on('click', nodeClick)
460 | .on('dblclick', function(d) {
461 | //if (d3.event.defaultPrevented) {return;}
462 |
463 | //console.log(d3.event);
464 | d3.event.stopPropagation();
465 |
466 | var outEdges = graph.outEdgesIndex[d.id];
467 |
468 | for (var i = 0; i < outEdges.length; i++) {
469 | if (!outEdges[i].target.ticked) {
470 | //console.log(outEdges[i].target);
471 | outEdges[i].target.ticked = true;
472 | break;
473 | }
474 | }
475 |
476 | updateClasses();
477 | dispatch.selectionChanged(d);
478 |
479 | //graph.outEdgesIndex[d.id].forEach(function(edge) {
480 |
481 | //});
482 |
483 | /* if (d3.event.altKey || d3.event.ctrlKey) {
484 | d3.event.stopPropagation();
485 | if (d3.event.altKey) {
486 | d.ticked = (d.ticked) ? false : true;
487 | } else if (d3.event.ctrlKey) {
488 | d.fixed = (d.fixed) ? false : true;
489 | }
490 |
491 | updateClasses(); //function(d) { return d.source.fixed && d.target.fixed; });
492 | dispatch.selectionChanged(d);
493 | } */
494 |
495 | })
496 | .on('contextmenu', function(d) {
497 | d3.event.preventDefault();
498 | dispatch.contextmenu(d);
499 | })
500 | .on('mouseover.highlight', mouseoverNodeHighlight) //function() { nodeClassed.call(this, 'hover', true); })
501 | .on('mouseout.highlight', mouseoutHighlight) //function() { nodeClassed.call(this, 'hover', false); })
502 | //.on('mouseover', tooltipShow)
503 | //.on('mouseout', tooltipHide)
504 | ;
505 |
506 | //nodesEnter.append('rect')
507 | //.on('dblclick', tooltip.toggle)
508 | //.on('mouseout', tooltip.hide)
509 | //;
510 |
511 | function _t(d) {
512 | if (d.group === 'gene.ligand') { return 'A'; }
513 | if (d.group === 'gene.receptor') { return 'V'; }
514 | return 'circle';
515 | }
516 |
517 | function _r(d) { return (d.type === 'gene') ? 90 : rsize(d.value)*15; }
518 | //function __r(d) { return -_r(d); }
519 | //function _2r(d) { return 2*_r(d); }
520 | //function rx(d) { return (d.type === 'gene') ? 0 : _r(d); }
521 |
522 | //var sym = d3.svg.symbol().type('circle').size(_r);
523 |
524 | var sym = hiveSymbol().type(_t).size(_r);
525 |
526 | nodesEnter.append('path')
527 | .attr('d', sym);
528 |
529 | nodesEnter.append('text')
530 | .style({
531 | stroke: 'none',
532 | fill: '#333',
533 | 'stroke-width': '1px',
534 | 'font-size': '10px'
535 | })
536 | .attr({
537 | 'text-anchor': 'start',
538 | 'dy': 3,
539 | 'dx': 15
540 | })
541 | .attr('transform', function(d) {
542 | return 'rotate( '+degrees(_labelAngle(d))+' )';
543 | });
544 |
545 | nodes
546 | .attr('id', function(d) { return 'node-'+d._index; })
547 | .classed('fixed', _fixed)
548 | .attr('transform', function(d) {
549 | return 'rotate( '+degrees(_angle(d))+' ) translate(' + _radius(d) + ')';
550 | //return 'rotate( '+degrees(_angle(d))+' ) translate(' + _radius(d) + ') rotate( '+degrees(_labelAngle(d))+' )';
551 | });
552 |
553 | nodes.each(function(d) { // Store the x-y position for output
554 | var b = this.getBoundingClientRect();
555 | d.x = b.left;
556 | d.y = b.top;
557 | });
558 |
559 | nodes.select('path')
560 | /* .attr({
561 | x: __r,
562 | y: __r,
563 | width: _2r,
564 | height: _2r,
565 | rx: rx,
566 | ry: rx,
567 | }) */
568 | .style('fill',_ncolor);
569 |
570 | nodes.select('text')
571 | .text(nodeName)
572 | .attr({
573 | dy: function(d) { return (d.type === 'gene') ? 0 : 3; },
574 | dx: function(d) { return (d.type === 'gene') ? 10 : 15; }
575 | });
576 |
577 | nodes.exit().remove();
578 |
579 | function legendHighlight(d) {
580 |
581 | if (d === null) {
582 | container.classed('hover', false);
583 | nodes.style('opacity', 1).classed('hover', false);
584 | return;
585 | }
586 |
587 | var h = _group.eq(d.group).and(_class.eq(d.class));
588 | //function o(n) { return h(n) ? 1 : 0.2; };
589 |
590 | nodes
591 | //.style('opacity', o)
592 | .classed('hover', h);
593 |
594 | container.classed('hover', true);
595 |
596 | }
597 |
598 | var legend = root.models.legend()
599 | .colors(_ncolor)
600 | .on('mouseover', _F(null, legendHighlight))
601 | .on('mouseout', function() { legendHighlight(null); });
602 |
603 | var _l = [
604 | { name: 'Ligand expressing cell', class: 'ligand', group: 'sample' },
605 | { name: 'Receptor expressing cell', class: 'receptor', group: 'sample' },
606 | { name: 'Ligand and receptor expressing cell', class: 'both', group: 'sample' },
607 | { name: 'Ligand gene', class: 'ligand', group: 'gene.ligand' },
608 | { name: 'Receptor gene', class: 'receptor', group: 'gene.receptor' }
609 | ];
610 |
611 | container.selectAll('.caxis').remove(); // TODO: not this
612 |
613 | container.append('g')
614 | .attr({
615 | class: 'axis caxis',
616 | transform: 'translate('+(margin.left)+','+margin.top+')'
617 | })
618 | .datum(_l)
619 | .call(legend);
620 |
621 | };
622 |
623 | d3.rebind(chart, dispatch, 'on');
624 | return chart;
625 | };
626 |
627 | root.charts.hiveGraph = hiveGraph;
628 | root.models.hiveSymbol = hiveSymbol;
629 |
630 | })(window.lrd3);
631 |
--------------------------------------------------------------------------------
/app/components/charts/force-chart.js:
--------------------------------------------------------------------------------
1 | /* global _F */
2 | /* global d3 */
3 |
4 | (function(root) {
5 | 'use strict';
6 |
7 | var forceGraph = function() {
8 | var width = 500, height = 500;
9 | var margin = { top: 10, right: 60, bottom: 240, left: 60};
10 | //var padding = { top: 60, right: 100, bottom: 60, left: 60};
11 |
12 | function chart(selection) {
13 | selection.each(chart.draw);
14 | }
15 |
16 | // elements
17 | var nodes, links;
18 |
19 | // Private objects
20 | var zoom = d3.behavior.zoom();
21 |
22 | var groups = ['gene.ligand','gene.receptor','sample'];
23 |
24 | // Scales
25 | var ncolor = d3.scale.ordinal()
26 | .domain(['ligand','both','receptor'])
27 | .range(['#ed1940','yellow','#3349ff']);
28 | //.range(['#ed1940','#a650e2','#9999ff']);
29 | //.range(['#ed1940','#a650e2','#3349ff']);
30 | var slog = d3.scale.log().range([2,9]).clamp(true); // Maps value to normalized edge width
31 | var eopac = d3.scale.linear().range([0.2,0.8]).clamp(true);
32 | var rsize = d3.scale.linear().range([3, 12]).clamp(true); // Maps value to size
33 | //var angle = d3.scale.ordinal().domain(groups).range([-Math.PI/4+Math.PI, Math.PI/4+Math.PI, 0]); // maps type to angle
34 | var radius = d3.scale.linear(); // maps position to radius
35 |
36 | var _y = {}; // maps index to position
37 | groups.forEach(function(d) {
38 | _y[d] = d3.scale.ordinal().rangePoints([0, 1]);
39 | });
40 |
41 | // Value accessors
42 | //var _type = _F('type');
43 | var _group = _F('group');
44 | var _value = _F('value');
45 | //var _name = _F('name');
46 | var _id = _F('id');
47 | var _class = _F('class');
48 | var _fixed = _F('fixed');
49 | //var _edgeFixed = _F('source',_fixed).and(_F('target',_fixed));
50 | var _hover = _F('hover');
51 |
52 | var linkName = function(d) { return [d.source.name, d.name, d.target.name].join(':'); };
53 | var nodeName = function(d) { // TODO: not this
54 | return d.name.split('.')[0];
55 | };
56 |
57 | // Range accesors
58 | //var _angle = _F('group', angle); //function(d) { return angle(d.type); };
59 | //var _radius = function(d) { return radius(_y[_group(d)](d._i)); };
60 | var _ncolor = _F('class', ncolor); //function(d) { return ncolor(d.class); };
61 | var _slog = _F(_value, slog);
62 |
63 | var dispatch = d3.dispatch('hover','selectionChanged');
64 |
65 | chart.draw = function draw(graph) {
66 |
67 | var container = d3.select(this);
68 |
69 | width = parseInt(container.style('width'));
70 | height = parseInt(container.style('height'));
71 |
72 | var size = Math.min(height, width)/(1+Math.cos(Math.PI/3))/1.5;
73 | radius.range([size/10, size]);
74 |
75 | chart.update = function() {
76 | updateClasses();
77 | };
78 |
79 | chart.container = container;
80 |
81 | container
82 | .attr('width', width)
83 | .attr('height', height)
84 | ;
85 |
86 | container.on('click', function() {
87 | if (d3.event.defaultPrevented) {return;}
88 | d3.event.stopPropagation();
89 |
90 | graph.nodes.forEach(function(d) {
91 | d.fixed = false;
92 | });
93 |
94 | updateClasses();
95 | dispatch.selectionChanged();
96 | });
97 |
98 | // Ranges
99 | var _e = d3.extent(graph._edges, _value); // Edge values
100 | slog.domain(_e);
101 | eopac.domain(_e);
102 |
103 | var _n = d3.extent(graph._nodes, _value); // Node values
104 | rsize.domain(_n);
105 |
106 | /* var nodesByType = d3.nest() // Nest nodes by type
107 | .key(_group)
108 | .sortKeys(d3.ascending)
109 | .entries(graph._nodes);
110 |
111 | nodesByType.forEach(function(type) { // Setup domain for position range
112 | _y[type.key].domain(d3.range(type.values.length));
113 |
114 | type.values.forEach(function(node,i) {
115 | node._i = i;
116 | //node.group = group;
117 | });
118 | }); */
119 |
120 | function tick() {
121 |
122 |
123 | links.attr('d', function(d) {
124 |
125 | var
126 | x1 = d.source.x,
127 | y1 = d.source.y,
128 | x2 = d.target.x,
129 | y2 = d.target.y,
130 | dx = x2 - x1,
131 | dy = y2 - y1;
132 |
133 | var dr = Math.sqrt(dx * dx + dy * dy),
134 | drx = dr,
135 | dry = dr,
136 | xRotation = 0, // degrees
137 | largeArc = 0, // 1 or 0
138 | sweep = 1; // 1 or 0
139 |
140 | // Self edge.
141 | if ( x1 === x1 && y1 === y2 ) {
142 |
143 | // Needs to be 1.
144 | largeArc = 1;
145 |
146 | // Change sweep to change orientation of loop.
147 | sweep = 0;
148 |
149 | var dcx = x1 - width/2;
150 | var dcy = y1 - height/2;
151 | var dcr = Math.sqrt(dcx * dcx + dcy * dcy);
152 |
153 | xRotation = Math.atan(dcy/dcx || 0)*180/Math.PI;
154 |
155 | drx = 30;
156 | dry = 20;
157 |
158 | x2 += dcy/dcr;
159 | y2 -= dcx/dcr;
160 |
161 | } else {
162 | var _radius = rsize(d.target.value)+_slog(d)+5;
163 | var theta = Math.PI/6; //Math.atan(dy / dx || 0);
164 |
165 | var c = Math.cos(theta);
166 | var s = Math.sin(theta);
167 |
168 | x2 -= _radius * (dx/dr*c-dy/dr*s);
169 | y2 -= _radius * (dx/dr*s+dy/dr*c);
170 | //dx = x2-x1;
171 | //dy = y2-y1;
172 |
173 | //dr = drx = dry = Math.sqrt(dx * dx + dy * dy);
174 | }
175 |
176 | return 'M' + x1 + ',' + y1 + 'A' + drx + ',' + dry + ' ' + xRotation + ',' + largeArc + ',' + sweep + ' ' + x2 + ',' + y2;
177 | });
178 |
179 | nodes
180 | .attr('transform', function(d) {
181 | return 'translate(' + d.x + ',' + d.y + ')';
182 | });
183 |
184 | }
185 |
186 | var force = d3.layout.force() // todo: move
187 | .charge(-2000)
188 | .linkDistance(function linkDistance(d) {
189 | return d.type === 'pair' ? 1 : 400;
190 | })
191 | .size([width, height])
192 | .on('tick', tick);
193 |
194 | graph._nodes.forEach(function(d) {
195 | delete d.x;
196 | delete d.y;
197 | });
198 |
199 | force
200 | .nodes(graph._nodes)
201 | .links(graph._edges)
202 | .start();
203 |
204 | var drag = force.drag()
205 | .on('dragstart', function() {
206 | d3.event.sourceEvent.stopPropagation();
207 | });
208 |
209 | // MARKERS
210 | container.selectAll('defs').remove();
211 |
212 | container
213 | .append('defs')
214 | .append('svg:marker')
215 | .attr('class', 'Triangle')
216 | .attr('viewBox', '0 -5 10 10')
217 | .attr('refY', 0)
218 | .attr('refX', 2.5)
219 | .attr('markerWidth', 2.5)
220 | .attr('markerHeight', 2.5)
221 | .attr('stroke-width', 1)
222 | .attr('markerUnits','strokeWidth')
223 | //.style('stroke', function(d) { return color(d.value); })
224 | //.style('fill', function(d) { return color(d.value); })
225 | .attr('orient', 'auto')
226 | .attr('id', 'arrow')
227 | .append('svg:path')
228 | .attr('d', 'M0,-5L10,0L0,5');
229 |
230 | var g = container.selectAll('.networkGraph').data([1]);
231 |
232 | g.enter()
233 | .append('g')
234 | .attr('class', 'networkGraph')
235 | .each(function() { // Only called once
236 | container.call(zoom.on('zoom', rescale));
237 | zoom.translate([0,margin.top]);
238 | zoom.event(container);
239 | });
240 |
241 | // rescale g
242 | function rescale() {
243 | var trans=d3.event.translate;
244 | var scale=d3.event.scale;
245 |
246 | g.attr('transform',
247 | 'translate(' + trans + ') scale(' + scale + ')');
248 | }
249 |
250 | function classNeighbors(node, direction, key, value) {
251 | if (arguments.length < 4) { value = true; }
252 |
253 | var _tgt = (direction <= 0) ? 'source' : 'target';
254 | //var _dir = (direction <= 0) ? 'lin' : 'lout';
255 |
256 | var edges = (direction <= 0) ? graph._inEdgesIndex[node.id] : graph._outEdgesIndex[node.id];
257 |
258 | edges.forEach(function(d) {
259 | if(d) {
260 | d[key] = value; // class edge
261 |
262 | var t = d[_tgt];
263 | t[key] = value; // class node
264 |
265 | if (t.type === 'gene') {
266 | classNeighbors(t, direction, key, value);
267 | }
268 |
269 | }
270 | });
271 | }
272 |
273 | function mouseoverEdgeHighlight(d) {
274 |
275 | var tgt = d.target;
276 | var src = d.source;
277 |
278 | d.hover = tgt.hover = src.hover = true;
279 |
280 | if (tgt.type !== 'node') {
281 | classNeighbors(tgt, 3, 'hover');
282 | }
283 |
284 | if (src.type !== 'node') {
285 | classNeighbors(src, -3, 'hover');
286 | }
287 |
288 | chart.container.classed('hover',true);
289 | updateClasses();
290 |
291 | dispatch.hover(d);
292 | }
293 |
294 | function mouseoverNodeHighlight(d) {
295 |
296 | //var node = d3.select(this);
297 |
298 | d.hover = true;
299 |
300 | classNeighbors(d, 3, 'hover');
301 | classNeighbors(d, -3, 'hover');
302 |
303 | chart.container.classed('hover',true);
304 | updateClasses();
305 |
306 | dispatch.hover(d);
307 | }
308 |
309 | function updateClasses() {
310 | //console.log('updateClasses');
311 |
312 | nodes
313 | .classed('hover', _hover)
314 | .classed('fixed', _fixed);
315 |
316 | links
317 | .classed('hover', _hover)
318 | .classed('fixed', _fixed)
319 | //.attr('marker-end', function(d,i) { return (d.source !== d.target) ? 'url("#arrow-'+i+'")' : ''; })
320 | ;
321 | }
322 |
323 | var _hoff = function(d) {d.hover = false; };
324 | function mouseoutHighlight() {
325 | chart.container.classed('hover',false);
326 |
327 | nodes.each(_hoff);
328 | links.each(_hoff);
329 |
330 | updateClasses();
331 | dispatch.hover(null);
332 | }
333 |
334 | function edgeClick(d) {
335 | if (d3.event.defaultPrevented) {return;}
336 | d3.event.stopPropagation();
337 |
338 | var p = d.fixed;
339 |
340 | if (d3.event.altKey) { // remove
341 | d.ticked = p ? false : true;
342 | } else if (d3.event.ctrlKey && !d3.event.shiftKey) { // add to selection
343 | d.fixed = p ? false : true;
344 | } else if (d3.event.shiftKey) { // add all to selection
345 | graph.nodes.forEach(function(d) {
346 | d.fixed = (d.hover) ? !p : (!d3.event.ctrlKey) ? false : d.fixed;
347 | });
348 | } else { // change selection
349 | graph.nodes.forEach(function(d) {
350 | d.fixed = false;
351 | });
352 | d.fixed = (p) ? false : true;
353 | }
354 |
355 | updateClasses(); //function(d) { return d.source.fixed && d.target.fixed; });
356 | dispatch.selectionChanged(d);
357 | }
358 |
359 | // LINKS
360 | var gLinks = g.selectAll('g.links').data([graph._edges]);
361 |
362 | gLinks.enter()
363 | .append('g')
364 | .classed('links', true);
365 |
366 | links = gLinks.selectAll('.link')
367 | .data(_F(), linkName);
368 |
369 | links.enter().append('path')
370 | .classed('link', true)
371 | .style({
372 | fill: 'none',
373 | stroke: '#666',
374 | 'stroke-width': '1.5px',
375 | })
376 | .style('opacity', function(d) { return eopac(d.value); })
377 | .on('mouseover.highlight',mouseoverEdgeHighlight)
378 | .on('mouseout.highlight',mouseoutHighlight)
379 | .on('click',edgeClick)
380 | //.on('mouseover', tooltipShow)
381 | //.on('mouseout', tooltipHide)
382 | .attr('marker-end', function(d) { return (d.source !== d.target) ? 'url("#arrow")' : ''; })
383 | ;
384 |
385 | links
386 | //.attr('id', function(d,i) { return 'link-'+i; })
387 | .style('stroke-width', _slog)
388 | //.attr('d', hiveLink)
389 | //.attr('marker-end', function(d,i) { return (d.source !== d.target) ? 'url(#arrow-'+i+')' : 'none'; })
390 | ;
391 |
392 | links.exit().remove();
393 |
394 | // NODES
395 | var nodesLayer = g.selectAll('g.nodes').data([graph._nodes]);
396 |
397 | nodesLayer.enter()
398 | .append('g')
399 | .classed('nodes', true);
400 |
401 | // Select
402 | //nodesLayer.selectAll('.node').remove(); // TODO: not this, hack to ensure nodes are stacked
403 |
404 | nodes = nodesLayer.selectAll('.node').data(_F(), _id);
405 |
406 | function nodeClick(d) {
407 | if (d3.event.defaultPrevented) {return;}
408 | d3.event.stopPropagation();
409 |
410 | var p = d.fixed;
411 |
412 | if (d3.event.altKey) { // remove
413 | d.ticked = (d.ticked) ? false : true;
414 | } else if (d3.event.ctrlKey && !d3.event.shiftKey) { // add to selection
415 | d.fixed = (d.fixed) ? false : true;
416 | } else if (d3.event.shiftKey) { // add all to selection
417 | graph.nodes.forEach(function(d) {
418 | d.fixed = (d.hover) ? !p : (!d3.event.ctrlKey) ? false : d.fixed;
419 | });
420 | } else { // change selection
421 | graph.nodes.forEach(function(d) {
422 | d.fixed = false;
423 | });
424 | d.fixed = (p) ? false : true;
425 | }
426 |
427 | updateClasses(); //function(d) { return d.source.fixed && d.target.fixed; });
428 | dispatch.selectionChanged(d);
429 | }
430 |
431 | // Create
432 | var nodesEnter = nodes.enter().append('g')
433 | .classed('node', true)
434 | .style({fill: '#ccc','fill-opacity': 1,stroke: '#333','stroke-width': '1px'})
435 | .on('click', nodeClick)
436 | .on('dblclick', function(d) {
437 | //if (d3.event.defaultPrevented) {return;}
438 |
439 | //console.log(d3.event);
440 | d3.event.stopPropagation();
441 |
442 | var edges = d3.event.shiftKey ? graph.inEdgesIndex[d.id] : graph.outEdgesIndex[d.id];
443 | //var alt = d3.event.altKey;
444 |
445 | if (d3.event.altKey) {
446 | for (var i = edges.length; i > -1; i--) {
447 | if (edges[i].type === 'sample-sample') {
448 | var node = d3.event.shiftKey ? edges[i].source : edges[i].target;
449 | if (node.ticked) {
450 | node.ticked = false;
451 | break;
452 | }
453 | }
454 | }
455 | } else {
456 | for (var j = 0; j < edges.length; j++) {
457 | if (edges[j].type === 'sample-sample') {
458 | var _node = d3.event.shiftKey ? edges[j].source : edges[j].target;
459 | if (!_node.ticked) {
460 | _node.ticked = true;
461 | break;
462 | }
463 | }
464 | }
465 | }
466 |
467 | updateClasses();
468 | dispatch.selectionChanged(d);
469 |
470 | })
471 | .on('mouseover.highlight', mouseoverNodeHighlight) //function() { nodeClassed.call(this, 'hover', true); })
472 | .on('mouseout.highlight', mouseoutHighlight) //function() { nodeClassed.call(this, 'hover', false); })
473 | //.on('mouseover', tooltipShow)
474 | //.on('mouseout', tooltipHide)
475 | .call(drag)
476 | ;
477 |
478 | nodesEnter
479 | .append('rect')
480 | //.on('dblclick', tooltip.toggle)
481 | //.on('mouseout', tooltip.hide)
482 | ;
483 |
484 | nodesEnter
485 | .append('text')
486 | .style({'stroke': 'none','fill': '#333','stroke-width': '1px','font-size': '10px'})
487 | .attr('text-anchor', 'start')
488 | .attr('dy', 3)
489 | .attr('dx', 15)
490 | //.on('mouseover', nodeLabelTooltip.show)
491 | //.on('mouseout', nodeLabelTooltip.hide)
492 | ;
493 |
494 | nodes
495 | .attr('id', function(d) { return 'node-'+d._index; })
496 | .classed('fixed', _fixed);
497 |
498 | function _r(d) { return (d.type === 'gene') ? 5 : rsize(d.value); }
499 | function __r(d) { return -_r(d); }
500 | function _2r(d) { return 2*_r(d); }
501 | function rx(d) { return (d.type === 'gene') ? 0 : _r(d); }
502 |
503 | nodes
504 | .select('rect')
505 | .attr('x',__r)
506 | .attr('y',__r)
507 | .attr('width',_2r)
508 | .attr('height',_2r)
509 | .attr('rx',rx)
510 | .attr('ry',rx)
511 | .style('fill',_ncolor);
512 |
513 | nodes
514 | .select('text')
515 | .text(nodeName)
516 | .attr('dy',function(d) { return (d.type === 'gene') ? 0 : 3; })
517 | .attr('dx',function(d) { return (d.type === 'gene') ? 10 : 15; })
518 | ;
519 |
520 | nodes.exit().remove();
521 |
522 | function legendHighlight(d) {
523 |
524 | if (d === null) {
525 | container.classed('hover', false);
526 | nodes.classed('hover', false);
527 | return;
528 | }
529 |
530 | var h = _group.eq(d.group).and(_class.eq(d.class));
531 |
532 | nodes
533 | .classed('hover', h);
534 |
535 | container.classed('hover', true);
536 |
537 | }
538 |
539 | var _l = [
540 | { name: 'Ligand expressing cell', class: 'ligand', group: 'sample' },
541 | { name: 'Receptor expressing cell', class: 'receptor', group: 'sample' },
542 | { name: 'Ligand and receptor expressing cell', class: 'both', group: 'sample' }//,
543 | //{ name: 'Ligand gene', class: 'ligand', group: 'gene.ligand' },
544 | //{ name: 'Receptor gene', class: 'receptor', group: 'gene.receptor' }
545 | ];
546 |
547 | var legend = root.models.legend()
548 | .colors(_ncolor)
549 | .on('mouseover', _F(null, legendHighlight))
550 | .on('mouseout', function() { legendHighlight(null); });
551 |
552 | container.selectAll('.caxis').remove();
553 |
554 | container.append('g')
555 | .attr('class', 'axis caxis')
556 | .attr('transform', 'translate('+(margin.left)+','+margin.top+')')
557 | .datum(_l)
558 | .call(legend);
559 |
560 | /* container.selectAll('.caxis').remove(); // TODO: not this
561 |
562 | var labels = container.selectAll('.caxis').data([_l]).enter().append('g')
563 | .attr('class', 'axis caxis')
564 | .attr('transform', 'translate('+(margin.left)+','+margin.top+')')
565 | .selectAll('.label').data(_F()).enter().append('g')
566 | .attr('class', 'label')
567 | .on('mouseover', _F(null, highlight)) //function(d) { highlight(d); }) //function(d) { highlight(d); }
568 | .on('mouseout', function() { highlight(null); })
569 | .attr('transform', function(d,i) { return 'translate(0,'+((i+1)*20)+')'; });
570 |
571 | var _rx = function rx(d) { return (d.name.match(/gene/)) ? 0 : 15; };
572 |
573 | labels.append('rect')
574 | .style({'stroke-width': '1px', 'stroke': 'rgb(51, 51, 51)'})
575 | .attr('width', 15)
576 | .attr('height', 15)
577 | .attr('rx',_rx)
578 | .attr('ry',_rx)
579 | .style('fill', _ncolor);
580 |
581 | labels.append('text')
582 | .style({'stroke': 'none','fill': '#333','stroke-width': '1px','font-size': '10px'})
583 | .attr('x', 20)
584 | .attr('dy', '1.2em')
585 | .text(_F('name')); */
586 |
587 | };
588 |
589 | d3.rebind(chart, dispatch, 'on');
590 | return chart;
591 | };
592 |
593 | root.charts.forceGraph = forceGraph;
594 |
595 | })(window.lrd3);
596 |
--------------------------------------------------------------------------------