Skip to content

Commit

Permalink
Metabolic pathways visualisation (#731): internal pathways skeleton
Browse files Browse the repository at this point in the history
  • Loading branch information
DmitriiKrasnov committed Jan 26, 2022
1 parent d9e10ac commit 2a628f9
Show file tree
Hide file tree
Showing 40 changed files with 1,541 additions and 9 deletions.
8 changes: 8 additions & 0 deletions client/client/app/app.layout.constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ export default {
title: 'Lineage',
name: 'layout>strainLineage'
},
pathways: {
displayed: false,
icon: 'device_hub',
panel: 'ngbPathwaysPanel',
position: 'right',
title: 'Pathways',
name: 'layout>pathways'
},
homologs: {
isHidden: true,
displayed: false,
Expand Down
4 changes: 3 additions & 1 deletion client/client/app/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ngbHomologsPanel from './ngbHomologsPanel';
import ngbLogModule from './ngbLog';
import ngbMolecularViewer from './ngbMolecularViewer';
import ngbOrganizeTracks from './ngbOrganizeTracks';
import ngbPathways from './ngbPathways';
import ngbProjectInfoPanel from './ngbProjectInfoPanel';
import ngbSashimiPlot from './ngbSashimiPlot';
import ngbStrainLineage from './ngbStrainLineage';
Expand Down Expand Up @@ -40,5 +41,6 @@ export default angular.module('NGB_Panels', [
ngbGenesTablePanel,
ngbHomologsPanel,
ngbHeatmapPanel,
ngbStrainLineage
ngbStrainLineage,
ngbPathways
]).name;
29 changes: 29 additions & 0 deletions client/client/app/components/ngbPathways/index.js
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;
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;
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
};
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;
}
}
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%;
}
}
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
}
};
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>
Loading

0 comments on commit 2a628f9

Please sign in to comment.