-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Metabolic pathways visualisation (#731): internal pathways skeleton
- Loading branch information
1 parent
d9e10ac
commit 2a628f9
Showing
40 changed files
with
1,541 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import angular from 'angular'; | ||
import cytoscapePathwayComponent from './ngbCytoscapePathway'; | ||
|
||
|
||
// Import components | ||
import ngbInternalPathwaysResult from './ngbInternalPathwaysResult'; | ||
import ngbInternalPathwaysTable from './ngbInternalPathwaysTable'; | ||
import service from './ngbPathways.service'; | ||
|
||
// Import internal modules | ||
import ngbPathwaysPanel from './ngbPathwaysPanel.component'; | ||
import controller from './ngbPathwaysPanel.controller'; | ||
|
||
// Import Style | ||
import './ngbPathwaysPanel.scss'; | ||
import ngbPathwaysPanelPaginate from './ngbPathwaysPanelPaginate'; | ||
|
||
// Import external modules | ||
export default angular | ||
.module('ngbPathwaysPanel', [ | ||
cytoscapePathwayComponent, | ||
ngbInternalPathwaysTable, | ||
ngbInternalPathwaysResult, | ||
ngbPathwaysPanelPaginate | ||
]) | ||
.service('ngbPathwaysService', service.instance) | ||
.component('ngbPathwaysPanel', ngbPathwaysPanel) | ||
.controller(controller.UID, controller) | ||
.name; |
15 changes: 15 additions & 0 deletions
15
client/client/app/components/ngbPathways/ngbCytoscapePathway/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import angular from 'angular'; | ||
|
||
// Import internal modules | ||
import cytoscapeComponent from './ngbCytoscapePathway.component'; | ||
import cytoscapeController from './ngbCytoscapePathway.controller'; | ||
|
||
// Import Style | ||
import './ngbCytoscapePathway.scss'; | ||
import cytoscapeSettings from './ngbCytoscapePathway.settings'; | ||
|
||
export default angular.module('ngbCytoscapePathway', []) | ||
.constant('cytoscapeSettings', cytoscapeSettings) | ||
.controller(cytoscapeController.UID, cytoscapeController) | ||
.component('ngbCytoscapePathway', cytoscapeComponent) | ||
.name; |
13 changes: 13 additions & 0 deletions
13
...nt/client/app/components/ngbPathways/ngbCytoscapePathway/ngbCytoscapePathway.component.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import ngbCytoscapePathwayController from './ngbCytoscapePathway.controller'; | ||
|
||
export default { | ||
template: require('./ngbCytoscapePathway.tpl.html'), | ||
bindings: { | ||
elements: '<', | ||
tag: '@', | ||
onElementClick: '&', | ||
storageName: '@', | ||
elementsOptions: '<' | ||
}, | ||
controller: ngbCytoscapePathwayController | ||
}; |
229 changes: 229 additions & 0 deletions
229
...t/client/app/components/ngbPathways/ngbCytoscapePathway/ngbCytoscapePathway.controller.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
import angular from 'angular'; | ||
|
||
const Cytoscape = require('cytoscape'); | ||
const graphml = require('cytoscape-graphml'); | ||
const sbgnStylesheet = require('cytoscape-sbgn-stylesheet'); | ||
const $ = require('jquery'); | ||
|
||
const SCALE = 0.3; | ||
const elementOptionsType = { | ||
NODE: 'nodes', | ||
EDGE: 'edges' | ||
}; | ||
|
||
export default class ngbCytoscapePathwayController { | ||
constructor($element, $scope, $compile, $window, $timeout, dispatcher, cytoscapeSettings) { | ||
this.$scope = $scope; | ||
this.$compile = $compile; | ||
this.cytoscapeContainer = $element.find('.cytoscape-container')[0]; | ||
this.settings = cytoscapeSettings; | ||
this.dispatcher = dispatcher; | ||
this.$timeout = $timeout; | ||
this.actionsManager = { | ||
ready: false | ||
}; | ||
|
||
const resizeHandler = () => { | ||
if (this.resizeCytoscape()) { | ||
this.centerCytoscape(); | ||
} | ||
}; | ||
graphml(Cytoscape, $); | ||
angular.element($window).on('resize', resizeHandler); | ||
const cytoscapeActiveEventHandler = this.reloadCytoscape.bind(this); | ||
this.dispatcher.on('cytoscape:panel:active', cytoscapeActiveEventHandler); | ||
const handleSelectionChange = (e) => { | ||
if (this.viewer && e && e.id) { | ||
this.viewer.edges().forEach((edge) => { | ||
const data = edge.data(); | ||
if (data.id === e.id) { | ||
// deselect logic | ||
} | ||
}); | ||
} | ||
}; | ||
this.dispatcher.on('cytoscape:selection:change', handleSelectionChange); | ||
$scope.$on('$destroy', () => { | ||
this.dispatcher.removeListener('cytoscape:panel:active', cytoscapeActiveEventHandler); | ||
this.dispatcher.removeListener('cytoscape:selection:change', handleSelectionChange); | ||
angular.element($window).off('resize', resizeHandler); | ||
}); | ||
} | ||
|
||
static get UID() { | ||
return 'ngbCytoscapePathwayController'; | ||
} | ||
|
||
$onChanges(changes) { | ||
if (!!changes.elements && | ||
!!changes.elements.previousValue && | ||
!!changes.elements.currentValue && | ||
(changes.elements.previousValue.id !== changes.elements.currentValue.id)) { | ||
this.reloadCytoscape(true); | ||
} | ||
} | ||
|
||
reloadCytoscape(active) { | ||
if (active) { | ||
if (this.viewer) { | ||
this.viewer.destroy(); | ||
this.viewer = null; | ||
} | ||
this.$timeout(() => { | ||
const sbgnStyle = sbgnStylesheet(Cytoscape); | ||
// Object.keys(sbgnStyle).forEach(key => { | ||
// if(sbgnStyle[key].selector === 'edge') { | ||
// Object.keys(sbgnStyle[key].properties).forEach(propKey => { | ||
// if(sbgnStyle[key].properties[propKey].name === 'curve-style') { | ||
// sbgnStyle[key].properties[propKey].value = 'taxi'; | ||
// } | ||
// }); | ||
// } | ||
// }); | ||
// sbgnStyle.edge['curve-style'] = 'taxi'; | ||
// const savedState = JSON.parse(localStorage.getItem(this.storageName) || '{}'); | ||
// const savedLayout = savedState.layout ? savedState.layout[this.elements.id] : undefined; | ||
// let elements, layoutSettings; | ||
this.viewer = Cytoscape({ | ||
container: this.cytoscapeContainer, | ||
style: sbgnStyle, | ||
layout: {name: 'preset'}, | ||
elements: { | ||
nodes: this.positionedNodes(this.elements.nodes), | ||
edges: this.elements.edges, | ||
}, | ||
}); | ||
const layout = this.viewer.layout(this.settings.loadedLayout); | ||
layout.on('layoutready', () => { | ||
this.$compile(this.cytoscapeContainer)(this.$scope); | ||
this.viewer.on('dragfree', this.saveLayout.bind(this)); | ||
this.resizeCytoscape(); | ||
}); | ||
this.viewer.edges().on('click', e => { | ||
const edgeData = e.target.data(); | ||
const { | ||
label, | ||
tooltip | ||
} = edgeData || {}; | ||
if (label || tooltip) { | ||
this.onElementClick({ | ||
data: { | ||
id: edgeData.id, | ||
tooltip: edgeData.tooltip, | ||
title: edgeData.fullLabel | ||
} | ||
}); | ||
} else { | ||
this.onElementClick(null); | ||
} | ||
}); | ||
layout.run(); | ||
const viewerContext = this; | ||
this.actionsManager = { | ||
ZOOM_STEP: 0.25, | ||
duration: 250, | ||
zoom: () => viewerContext.viewer.zoom(), | ||
zoomIn() { | ||
const zoom = this.zoom() + this.ZOOM_STEP; | ||
viewerContext.viewer.zoom(zoom); | ||
viewerContext.centerCytoscape(); | ||
this.canZoomIn = zoom < viewerContext.viewer.maxZoom(); | ||
this.canZoomOut = zoom > viewerContext.viewer.minZoom(); | ||
}, | ||
zoomOut() { | ||
const zoom = this.zoom() - this.ZOOM_STEP; | ||
viewerContext.viewer.zoom(zoom); | ||
viewerContext.centerCytoscape(); | ||
this.canZoomIn = zoom < viewerContext.viewer.maxZoom(); | ||
this.canZoomOut = zoom > viewerContext.viewer.minZoom(); | ||
}, | ||
canZoomIn: true, | ||
canZoomOut: true, | ||
ready: true | ||
}; | ||
}); | ||
} | ||
} | ||
|
||
resizeCytoscape() { | ||
if (this.viewer) { | ||
this.viewer.resize(); | ||
const newSize = { | ||
width: this.viewer.width(), | ||
height: this.viewer.height() | ||
}; | ||
const changed = !this.viewerSize || | ||
this.viewerSize.width !== newSize.width || | ||
this.viewerSize.height !== newSize.height; | ||
this.viewerSize = newSize; | ||
return changed; | ||
} | ||
return false; | ||
} | ||
|
||
centerCytoscape() { | ||
if (this.viewer) { | ||
this.viewer.center(); | ||
} | ||
} | ||
|
||
saveLayout() { | ||
const savedState = JSON.parse(localStorage.getItem(this.storageName) || '{}'); | ||
if (!Object.prototype.hasOwnProperty.call(savedState, 'layout')) { | ||
savedState.layout = {}; | ||
} | ||
savedState.layout = { | ||
...savedState.layout, | ||
[this.elements.id]: { | ||
nodes: this.getPlainNodes(this.viewer.nodes().jsons()), | ||
edges: this.viewer.edges().jsons() | ||
} | ||
}; | ||
|
||
localStorage.setItem(this.storageName, JSON.stringify(savedState)); | ||
} | ||
|
||
getPlainNodes(nodes) { | ||
return nodes.reduce((r, cv) => { | ||
delete cv.data.dom; | ||
r.push({ | ||
data: cv.data, | ||
position: cv.position, | ||
selected: cv.selected | ||
}); | ||
return r; | ||
}, []); | ||
} | ||
|
||
applyOptions(elements, type) { | ||
if (!this.elementsOptions) { | ||
return; | ||
} | ||
switch (type) { | ||
case elementOptionsType.NODE: { | ||
return elements.reduce((r, cv) => { | ||
if (this.elementsOptions.nodes[cv.data.id]) { | ||
cv.data = { | ||
...cv.data, | ||
...this.elementsOptions.nodes[cv.data.id] | ||
}; | ||
} | ||
r.push(cv); | ||
return r; | ||
}, []); | ||
} | ||
} | ||
} | ||
|
||
positionedNodes(nodes) { | ||
nodes.forEach(node => { | ||
if (node.data.bbox) { | ||
node.position = { | ||
x: node.data.bbox.x/SCALE, | ||
y: node.data.bbox.y/SCALE | ||
}; | ||
} | ||
}); | ||
return nodes; | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
client/client/app/components/ngbPathways/ngbCytoscapePathway/ngbCytoscapePathway.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
ngb-cytoscape-pathway { | ||
display: block; | ||
height: 100%; | ||
width: 100%; | ||
position: relative; | ||
.cytoscape-container { | ||
width: 100%; | ||
height: 100%; | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
client/client/app/components/ngbPathways/ngbCytoscapePathway/ngbCytoscapePathway.settings.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
export default { | ||
viewer: {}, | ||
style: { | ||
node: { | ||
'content': 'data(id)', | ||
'width': 175, | ||
'height': 60, | ||
'text-opacity': 1, | ||
'text-valign': 'center', | ||
'text-halign': 'center', | ||
'shape': 'rectangle', | ||
'label': 'data(id)', | ||
'background-opacity': 0, | ||
'opacity': 0, | ||
'color': '#000', | ||
'border-width': 1 | ||
}, | ||
edge: { | ||
'curve-style': 'bezier', | ||
'width': 1, | ||
'line-color': '#37474F', | ||
'target-arrow-color': '#37474F', | ||
'target-arrow-shape': 'triangle', | ||
'overlay-color': '#4285F4', | ||
'overlay-padding': '4px', | ||
'underlay-color': '#4285F4', | ||
'underlay-padding': '3px', | ||
'underlay-opacity': '0' | ||
}, | ||
edgeLabel: { | ||
'text-rotation': 'none', | ||
'content': 'data(label)', | ||
'font-size': '12px', | ||
'font-weight': 'bold', | ||
'text-background-color': '#fff', | ||
'text-background-opacity': 1, | ||
'color': '#2c4f9e' | ||
} | ||
}, | ||
defaultLayout: { | ||
name: 'dagre', | ||
rankDir: 'TB' | ||
}, | ||
loadedLayout: { | ||
name: 'preset', | ||
nodeDimensionsIncludeLabels: true, | ||
}, | ||
options: { | ||
wheelSensitivity: 0.25, | ||
minZoom: 0.25, | ||
maxZoom: 4 | ||
} | ||
}; |
4 changes: 4 additions & 0 deletions
4
client/client/app/components/ngbPathways/ngbCytoscapePathway/ngbCytoscapePathway.tpl.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<ngb-cytoscape-toolbar-panel actions-manager="$ctrl.actionsManager" | ||
ng-if="$ctrl.actionsManager.ready" | ||
></ngb-cytoscape-toolbar-panel> | ||
<div class="cytoscape-container"></div> |
Oops, something went wrong.