101 lines
2.7 KiB
Python
101 lines
2.7 KiB
Python
import json
|
|
import logging
|
|
|
|
from fasthtml.components import Script, Div
|
|
|
|
from myfasthtml.core.dbmanager import DbObject
|
|
from myfasthtml.core.instances import MultipleInstance
|
|
|
|
logger = logging.getLogger("VisNetwork")
|
|
|
|
|
|
class VisNetworkState(DbObject):
|
|
def __init__(self, owner):
|
|
super().__init__(owner)
|
|
with self.initializing():
|
|
# persisted in DB
|
|
self.nodes: list = []
|
|
self.edges: list = []
|
|
self.options: dict = {
|
|
"autoResize": True,
|
|
"interaction": {
|
|
"dragNodes": True,
|
|
"zoomView": True,
|
|
"dragView": True,
|
|
},
|
|
"physics": {"enabled": True}
|
|
}
|
|
|
|
|
|
class VisNetwork(MultipleInstance):
|
|
def __init__(self, parent, _id=None, nodes=None, edges=None, options=None):
|
|
super().__init__(parent, _id=_id)
|
|
logger.debug(f"VisNetwork created with id: {self._id}")
|
|
|
|
self._state = VisNetworkState(self)
|
|
self._update_state(nodes, edges, options)
|
|
|
|
def _update_state(self, nodes, edges, options):
|
|
logger.debug(f"Updating VisNetwork state with {nodes=}, {edges=}, {options=}")
|
|
if not nodes and not edges and not options:
|
|
return
|
|
|
|
state = self._state.copy()
|
|
if nodes is not None:
|
|
state.nodes = nodes
|
|
if edges is not None:
|
|
state.edges = edges
|
|
if options is not None:
|
|
state.options = options
|
|
|
|
self._state.update(state)
|
|
|
|
def add_to_options(self, **kwargs):
|
|
logger.debug(f"add_to_options: {kwargs=}")
|
|
new_options = self._state.options.copy() | kwargs
|
|
self._update_state(None, None, new_options)
|
|
return self
|
|
|
|
def render(self):
|
|
|
|
# Serialize nodes and edges to JSON
|
|
# This preserves all properties (color, shape, size, etc.) that are present
|
|
js_nodes = ",\n ".join(
|
|
json.dumps(node) for node in self._state.nodes
|
|
)
|
|
js_edges = ",\n ".join(
|
|
json.dumps(edge) for edge in self._state.edges
|
|
)
|
|
|
|
# Convert Python options to JS
|
|
js_options = json.dumps(self._state.options, indent=2)
|
|
|
|
return (
|
|
Div(
|
|
id=self._id,
|
|
cls="mf-vis",
|
|
),
|
|
|
|
# The script initializing Vis.js
|
|
Script(f"""
|
|
(function() {{
|
|
const container = document.getElementById("{self._id}");
|
|
const nodes = new vis.DataSet([
|
|
{js_nodes}
|
|
]);
|
|
const edges = new vis.DataSet([
|
|
{js_edges}
|
|
]);
|
|
const data = {{
|
|
nodes: nodes,
|
|
edges: edges
|
|
}};
|
|
const options = {js_options};
|
|
const network = new vis.Network(container, data, options);
|
|
}})();
|
|
""")
|
|
)
|
|
|
|
def __ft__(self):
|
|
return self.render()
|