nwb-linkml/nwb_linkml/plot.py
sneakers-the-rat 4faaa8efe8 I believe that's a full translation
or at least all the semantics are present. it's not pretty by any stretch of the imagination
2023-08-25 00:22:47 -07:00

170 lines
4.4 KiB
Python

"""
Various visualization routines, mostly to help development for now
"""
from typing import TYPE_CHECKING, Optional, List, TypedDict, Union
from rich import print
import random
from dash import Dash, html
import dash_cytoscape as cyto
cyto.load_extra_layouts()
from nwb_schema_language import Namespace, Group, Dataset
from nwb_linkml.io import load_nwb_core
if TYPE_CHECKING:
from nwb_linkml.adapters import NamespacesAdapter
# from nwb_schema_language.datamodel import Namespaces
class _CytoNode(TypedDict):
id: str
label: str
class _CytoEdge(TypedDict):
source: str
target: str
class CytoElement(TypedDict):
data: _CytoEdge | _CytoNode
classes: Optional[str]
class Node:
def __init__(self,
id: str,
label: str,
klass: str,
parent: Optional[str] = None):
self.id = id
self.label = label
self.parent = parent
self.klass = klass
def make(self) -> List[CytoElement]:
node = [
CytoElement(data= _CytoNode(id=self.id, label=self.label), classes=self.klass)
]
if self.parent:
edge = [
CytoElement(data=_CytoEdge(source=self.parent, target=self.id))
]
node += edge
return node
def make_node(element: Group | Dataset, parent=None, recurse:bool=True) -> List[Node]:
if element.neurodata_type_def is None:
if element.name is None:
if element.neurodata_type_inc is None:
name = 'anonymous'
else:
name = element.neurodata_type_inc
else:
name = element.name
id = name + '-' + str(random.randint(0,1000))
label = id
classname = str(type(element).__name__).lower() + '-child'
else:
id = element.neurodata_type_def
label = element.neurodata_type_def
classname = str(type(element).__name__).lower()
if parent is None:
parent = element.neurodata_type_inc
node = Node(
id=id,
label=label,
parent=parent,
klass=classname
)
nodes = [node]
if isinstance(element, Group) and recurse:
for group in element.groups:
nodes += make_node(group, parent=id)
for dataset in element.datasets:
nodes += make_node(dataset, parent=id)
return nodes
def make_graph(namespaces: 'NamespacesAdapter', recurse:bool=True) -> List[CytoElement]:
namespaces.populate_imports()
nodes = []
element: Namespace | Group | Dataset
print('walking graph')
i = 0
for element in namespaces.walk_types(namespaces, (Group, Dataset)):
if element.neurodata_type_def is None:
# skip child nodes at top level, we'll get them in recursion
continue
if any([element.neurodata_type_def == node.id for node in nodes]):
continue
nodes.extend(make_node(element, recurse=recurse))
print('making elements')
cytoelements = []
for node in nodes:
cytoelements += node.make()
print(cytoelements)
return cytoelements
def plot_dependency_graph(namespaces: 'NamespacesAdapter', recurse:bool=True) -> Dash:
graph = make_graph(namespaces, recurse=recurse)
app = Dash(__name__)
styles = [
{
'selector': 'node',
'style': {
'content': 'data(label)'
}
},
{
'selector': '.dataset',
'style': {
'background-color': 'red',
'shape': 'rectangle'
}
},
{
'selector': '.group',
'style': {
'background-color': 'blue',
'shape': 'rectangle'
}
},
{
'selector': '.dataset-child',
'style': {
'background-color': 'red'
}
},
{
'selector': '.group-child',
'style': {
'background-color': 'blue'
}
}
]
app.layout = html.Div([
cyto.Cytoscape(
id='nwb_graph',
elements = graph,
style={'width': '100%', 'height': '100vh'},
layout= {'name': 'klay', 'rankDir': 'LR'},
stylesheet=styles
)
])
return app
if __name__ == "__main__":
core = load_nwb_core()
app = plot_dependency_graph(core, recurse=True)
print('opening dash')
app.run(debug=True)