Skip to content

Commit

Permalink
Merge pull request #49 from kirichoi/develop
Browse files Browse the repository at this point in the history
Pull to HEAD
  • Loading branch information
Kiri Choi authored Apr 26, 2022
2 parents dee876d + 112916f commit 965021c
Show file tree
Hide file tree
Showing 9 changed files with 1,650 additions and 856 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![PyPI version](https://badge.fury.io/py/netplotlib.svg)](https://badge.fury.io/py/netplotlib)

A simple package for visualizing and analyzing reaction network models
A package for visual analysis of biochemical reaction network models

Copyright 2018-2020 Kiri Choi
Copyright 2018-2021 Kiri Choi

## Introduction

Netplotlib is an extension to [NetworkX](https://networkx.github.io/) and [matplotlib](https://matplotlib.org/) to draw and analyze reaction network diagrams in SBML or Antimony strings with ease. Netplotlib supports visualization of quantities such as flux and species rate of change. Netplotlib provides functions to visualize network model ensemble.
Netplotlib is an extension to [NetworkX](https://networkx.github.io/) and [matplotlib](https://matplotlib.org/) for visual analysis of biochemical reaction network diagrams written in SBML or Antimony strings. Netplotlib supports visualization of quantities such as flux and species rate of change. Netplotlib provides functions to visualize an ensemble of biochemical reaction networks.

```{python}
import netplotlib as npl
Expand Down
2 changes: 1 addition & 1 deletion netplotlib/VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.0
1.2.0
268 changes: 268 additions & 0 deletions netplotlib/layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
# -*- coding: utf-8 -*-
"""
Layout extension support for netplorlib library
Kiri Choi (c) 2021
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import (FancyArrowPatch, FancyBboxPatch, ArrowStyle,
Arc, RegularPolygon, Rectangle, PathPatch,
Circle)
from matplotlib import gridspec, cm, colors
from matplotlib.path import Path
from matplotlib.transforms import Bbox
from matplotlib.text import Text
from matplotlib.patheffects import AbstractPathEffect
import libsbml

def getPos(layoutPlugin):
"""
Return the layout of the model
:param layoutPlugin: SBML layout plugin
:returns pos: Dictionary of all nodes and corresponding coordinates
"""

pos = dict()

layout = layoutPlugin.getLayout(0) # TODO: currently support only the first layout

for i in range(layout.getNumCompartmentGlyphs()):
cg = layout.getCompartmentGlyph(i)
bbox = cg.getBoundingBox()
cgpos = bbox.getPosition()
pos[cg.getCompartmentId()] = np.array([cgpos.x_offset, cgpos.y_offset])

for i in range(layout.getNumSpeciesGlyphs()):
sg = layout.getSpeciesGlyph(i)
bbox = sg.getBoundingBox()
sgpos = bbox.getPosition()
pos[sg.getSpeciesId()] = np.array([sgpos.x_offset, sgpos.y_offset])

return pos

def stretch_font(text, width, height):

class TextScaler(AbstractPathEffect):

def __init__(self, text, width, height):
self._text = text
self._width = width
self._height = height

def draw_path(self, renderer, gc, tpath, affine, rgbFace=None):
ax = self._text.axes
renderer = ax.get_figure().canvas.get_renderer()
bbox = text.get_window_extent(renderer=renderer)
bbox = Bbox(ax.transData.inverted().transform(bbox))

scale_x = self._width/bbox.width
scale_y = self._height/bbox.height

affine = affine.identity().scale(scale_x, scale_y) + affine
renderer.draw_path(gc, tpath, affine, rgbFace)

text.set_path_effects([TextScaler(text, width, height)])

def draw(NetworkClass, show=True, savePath=None, dpi=150):
"""
Draw network diagram using SBML Layout package
:param show: flag to show the diagram
:param savePath: path to save the diagram
:param dpi: dpi settings for the diagram
"""

size = dict()

fig = plt.figure()

if NetworkClass.customAxis != None:
ax = NetworkClass.customAxis
else:
ax = plt.gca()

layout = NetworkClass._Var.layoutPlugin.getLayout(0) # TODO: currently support only the first layout

layoutCompartmentGlyphIds = []
layoutSpeciesGlyphIds = []
layoutReactionGlyphIds = []
layoutTextGlyphIds = []

for n in range(layout.getNumCompartmentGlyphs()):
cg = layout.getCompartmentGlyph(n)
bbox = cg.getBoundingBox()
cgpos = bbox.getPosition()
layoutCompartmentGlyphIds.append(cg.getCompartmentId())

dim = bbox.getDimensions()
size[cg.getCompartmentId()] = np.array([dim.getWidth(), dim.getHeight()])

node_color = NetworkClass.compartmentColor

c = FancyBboxPatch((cgpos.x_offset,
cgpos.y_offset),
dim.getWidth(),
dim.getHeight(),
boxstyle="round,pad=0.01, rounding_size=0.02",
linewidth=NetworkClass.compartmentEdgelw,
edgecolor=NetworkClass.compartmentEdgeColor,
facecolor=node_color,
alpha=0.5)

ax.add_patch(c)

for n in range(layout.getNumSpeciesGlyphs()):
sg = layout.getSpeciesGlyph(n)
bbox = sg.getBoundingBox()
sgpos = bbox.getPosition()
layoutSpeciesGlyphIds.append(sg.getSpeciesId())

dim = bbox.getDimensions()
size[sg.getSpeciesId()] = np.array([dim.getWidth(), dim.getHeight()])

if (sg.getSpeciesId() in NetworkClass._Var.boundaryId):
node_color = NetworkClass.boundaryColor
else:
node_color = NetworkClass.nodeColor

c = FancyBboxPatch((sgpos.x_offset,
sgpos.y_offset),
dim.getWidth(),
dim.getHeight(),
boxstyle="round,pad=0.01, rounding_size=0.02",
linewidth=NetworkClass.nodeEdgelw,
edgecolor=NetworkClass.nodeEdgeColor,
facecolor=node_color)

NetworkClass._Var.G.nodes[sg.getSpeciesId()]['patch'] = c

ax.add_patch(c)

for n in range(layout.getNumReactionGlyphs()):
rg = layout.getReactionGlyph(n)

cg = rg.getCurve()

for c in range(cg.getNumCurveSegments()):
csg = cg.getCurveSegment(c)
if type(csg) == libsbml.LineSegment:
csgs = csg.getStart()
csge = csg.getEnd()

e = PathPatch(Path([(csgs.getXOffset(), csgs.getYOffset()),
(csge.getXOffset(), csge.getYOffset())],
[Path.MOVETO, Path.LINETO]),
color=NetworkClass.reactionColor,
lw=(1+NetworkClass.edgelw))
else:
csgs = csg.getStart()
csge = csg.getEnd()
csgcp1 = csg.getBasePoint1()
csgcp2 = csg.getBasePoint2()

e = FancyArrowPatch(path=Path([(csgs.getXOffset(), csgs.getYOffset()),
(csgcp1.getXOffset(), csgcp1.getYOffset()),
(csgcp2.getXOffset(), csgcp2.getYOffset()),
(csge.getXOffset(), csge.getYOffset())],
[Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]),
color=NetworkClass.reactionColor,
lw=(1+NetworkClass.edgelw))
ax.add_patch(e)

for b in range(rg.getNumSpeciesReferenceGlyphs()):
srg = rg.getSpeciesReferenceGlyph(b)
srgc = srg.getCurve()

if srg.getRoleString() == 'substrate':
arrowstyle = ArrowStyle.Curve()
elif srg.getRoleString() == 'sidesubstrate':
arrowstyle = ArrowStyle.Curve()
elif srg.getRoleString() == 'product':
arrowstyle = ArrowStyle.CurveFilledB(head_length=2.5*NetworkClass.edgelw, head_width=2*NetworkClass.edgelw)
elif srg.getRoleString() == 'sideproduct':
arrowstyle = ArrowStyle.CurveFilledB(head_length=2.5*NetworkClass.edgelw, head_width=2*NetworkClass.edgelw)
elif srg.getRoleString() == 'activator':
arrowstyle = ArrowStyle.Curve()
elif srg.getRoleString() == 'inhibitor':
arrowstyle = ArrowStyle.BarAB(widthA=0.0, angleA=None, widthB=3*NetworkClass.edgelw, angleB=None)
else:
arrowstyle = ArrowStyle.Curve()

for bc in range(srgc.getNumCurveSegments()):
srgcs = srgc.getCurveSegment(bc)
if type(srgcs) == libsbml.LineSegment:
srgcss = srgcs.getStart()
srgcse = srgcs.getEnd()

e = FancyArrowPatch(path=Path([(srgcss.getXOffset(), srgcss.getYOffset()),
(srgcse.getXOffset(), srgcse.getYOffset())],
[Path.MOVETO, Path.LINETO]),
arrowstyle=arrowstyle,
color=NetworkClass.reactionColor,
lw=(1+NetworkClass.edgelw))

else:
srgcss = srgcs.getStart()
srgcse = srgcs.getEnd()
srgcscp1 = srgcs.getBasePoint1()
srgcscp2 = srgcs.getBasePoint2()

e = FancyArrowPatch(path=Path([(srgcss.getXOffset(), srgcss.getYOffset()),
(srgcscp1.getXOffset(), srgcscp1.getYOffset()),
(srgcscp2.getXOffset(), srgcscp2.getYOffset()),
(srgcse.getXOffset(), srgcse.getYOffset())],
[Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]),
arrowstyle=arrowstyle,
color=NetworkClass.reactionColor,
lw=(1+NetworkClass.edgelw))

ax.add_patch(e)

if srg.getRoleString() == 'activator':
e = Circle((srgcse.getXOffset(), srgcse.getYOffset()),
radius=3*NetworkClass.edgelw,
color=NetworkClass.reactionColor,
lw=(1+NetworkClass.edgelw))
ax.add_patch(e)

for n in range(layout.getNumTextGlyphs()):
tg = layout.getTextGlyph(n)
bbox = tg.getBoundingBox()
tgpos = bbox.getPosition()
speciesName = NetworkClass._Var.sbmlmodel.getSpecies(tg.getOriginOfTextId())
if speciesName != None:
if speciesName.getName() != '':
speciesName = speciesName.getName()
else:
speciesName = speciesName.getId()
else:
sg = layout.getSpeciesGlyph(tg.getOriginOfTextId())
speciesName = sg.getSpeciesId()
layoutTextGlyphIds.append(speciesName)

dim = bbox.getDimensions()

mattext = ax.text(tgpos.x_offset, tgpos.y_offset, speciesName,
horizontalalignment='left', verticalalignment='bottom',
color=NetworkClass.labelColor)

stretch_font(mattext, dim.getWidth(), dim.getHeight())

layoutDim = layout.getDimensions()
plt.xlim(0, layoutDim.getWidth())
plt.ylim(0, layoutDim.getHeight())
plt.axis('off')
plt.show()

if savePath != None:
try:
fig.savefig(savePath, bbox_inches='tight', dpi=dpi)
except IOError as e:
raise Exception("Error saving diagram: " + str(e))

if show and NetworkClass.customAxis == None:
plt.show()
plt.close()
Loading

0 comments on commit 965021c

Please sign in to comment.