Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions examples/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@
VIcon,
VSelect,
)
from trame_flow.widgets.flow import CustomNode, Node, NodeEditor, create_node
from trame_flow.module.core import Node, create_node
from trame_flow.widgets.flow import (
Background,
Controls,
CustomNode,
Handle,
NodeEditor,
NodeResizer,
NodeToolbar,
)


class Example(TrameApp):
Expand Down Expand Up @@ -44,7 +53,9 @@ def build_ui(self):
with layout.toolbar:
VSelect(
label="Node type",
items=("['default', 'input', 'output', 'text', 'title']",),
items=(
"['default', 'input', 'output', 'text', 'title', 'toolbar']",
),
v_model=("node_type", "default"),
density="compact",
hide_details="true",
Expand All @@ -62,11 +73,22 @@ def build_ui(self):
VIcon("mdi-minus")

with NodeEditor() as self.vueflow:
Background(gap=10, size=1, pattern_color="#81818a")
Controls()
with CustomNode("title"):
Handle(type="target", position="top")
H4("{{props.data.label}}")
P("{{props.data.subtitle}}")
with CustomNode("text", var_name="nodeProps"):
NodeResizer()
P("{{nodeProps.data.label}}")
with CustomNode("toolbar"):
with NodeToolbar(
is_visible=True,
style="background: darkgray; padding: 4px; font-size: 1.2em; border-radius: 4px",
):
P("This is a toolbar")
P("{{props.data.label}}")

def on_graph_change(nodes, edges):
with self.state:
Expand Down
8 changes: 5 additions & 3 deletions examples/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
VRow,
VTextarea,
)
from trame_flow.widgets.flow import Node, NodeEditor
from trame_flow.module.core import Node
from trame_flow.widgets.flow import Background, Controls, NodeEditor

DEFAULT_GRAPH = """{
"nodes": [
Expand Down Expand Up @@ -122,8 +123,9 @@ def build_ui(self):
VContainer(fluid=True, classes="h-100"),
VRow(classes="h-100"),
):
with VCol():
self.vueflow = NodeEditor()
with VCol(), NodeEditor() as self.vueflow:
Background(gap=10, size=1, pattern_color="#81818a")
Controls()
with VCol():
VTextarea(
density="compact",
Expand Down
9 changes: 7 additions & 2 deletions examples/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
VRow,
VSlider,
)
from trame_flow.widgets.flow import NodeEditor, create_node
from trame_flow.module.core import create_node
from trame_flow.widgets.flow import Background, Controls, NodeEditor


class Example(TrameApp):
Expand Down Expand Up @@ -47,7 +48,11 @@ def build_ui(self):
VRow(classes="h-100"),
):
with VCol():
self.vueflow = NodeEditor(show_mini_map=False, show_controls=False)
with NodeEditor(
show_mini_map=False, show_controls=False
) as self.vueflow:
Background(gap=10, size=1, pattern_color="#81818a")
Controls()
self.vueflow.add_node(
create_node(id="0", type="default", x=0, y=0, label="My node")
)
Expand Down
187 changes: 187 additions & 0 deletions src/trame_flow/module/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
from typing import Literal, Optional, Union

from typing_extensions import NotRequired, TypedDict

__all__ = [
"DEFAULT_EXTENT",
"Dimensions",
"Edge",
"EdgeMarkerType",
"EdgeType",
"Extent",
"Graph",
"HandlePosition",
"Node",
"NodeType",
"Position",
"create_edge",
"create_node",
]


class Position(TypedDict):
x: float
y: float


HandlePosition = Literal["top", "bottom", "left", "right"]


class Dimensions(TypedDict):
height: float
width: float


# "parent" or [[x-from, y-from], [x-to, y-to]]
Extent = Union[Literal["parent"], list[list[float]]]
DEFAULT_EXTENT = [[float("-inf"), float("-inf")], [float("+inf"), float("+inf")]]

NodeType = Literal["default", "input", "output"] | str


Node = TypedDict(
"Node",
{
"ariaLabel": NotRequired[str],
"class": NotRequired[str],
"connectable": NotRequired[bool],
"data": NotRequired[dict],
"deletable": NotRequired[bool],
"draggable": NotRequired[bool],
"expandParent": NotRequired[bool],
"extent": NotRequired[Extent],
"focusable": NotRequired[bool],
"height": Union[int, str],
"hidden": NotRequired[bool],
"id": str,
"parentNode": NotRequired[str],
"position": Position,
"selectable": NotRequired[bool],
"sourcePosition": NotRequired[HandlePosition],
"style": NotRequired[dict],
"targetPosition": NotRequired[HandlePosition],
"type": NodeType,
"width": Union[int, str],
"zIndex": NotRequired[int],
},
)


def create_node(
id: str,
type: NodeType,
x: float,
y: float,
label: str,
parent_id: Optional[str] = None,
expand_parent: bool = False,
extent: Optional[Extent] = None,
width: Union[int, str] = "auto",
height: Union[int, str] = "auto",
style: Optional[dict] = None,
data: Optional[dict] = None,
) -> Node:
"""Helper function to build a Node."""
node = Node(
id=id,
type=type,
data={"label": label},
position=Position(x=x, y=y),
expandParent=expand_parent,
width=width,
height=height,
)
if extent:
node["extent"] = extent
if extent == "parent" and parent_id is None:
parent_id = ""
if parent_id:
node["parentNode"] = parent_id
if style:
node["style"] = style
if data:
node["data"] = node["data"] | data
# set default node style for custom node
if type not in ["default", "input", "output"]:
node["class"] = "vue-flow__node-default"
return node


EdgeType = Literal["default", "step", "smoothstep", "straight"]

EdgeMarkerType = Literal["arrow", "arrowclosed"]


class EdgeMarker(TypedDict):
color: NotRequired[str]
height: NotRequired[float]
id: NotRequired[str]
markerUnits: NotRequired[str]
orient: NotRequired[str]
strokeWidth: NotRequired[float]
type: EdgeMarkerType
width: NotRequired[float]


Edge = TypedDict(
"Edge",
{
"animated": NotRequired[bool],
"ariaLabel": NotRequired[str],
"class": NotRequired[str],
"data": NotRequired[dict],
"deletable": NotRequired[bool],
"focusable": NotRequired[bool],
"hidden": NotRequired[bool],
"id": str,
"interactionWidth": NotRequired[float],
"label": NotRequired[str],
"labelBgBorderRadius": NotRequired[float],
"labelBgPadding": NotRequired[tuple[float, float]],
"labelBgStyle": NotRequired[dict],
"labelShowBg": NotRequired[bool],
"labelStyle": NotRequired[dict],
"markerEnd": NotRequired[Union[EdgeMarkerType, EdgeMarker]],
"markerStart": NotRequired[Union[EdgeMarkerType, EdgeMarker]],
"selectable": NotRequired[bool],
"source": str,
"style": NotRequired[dict],
"target": str,
"type": EdgeType,
"zIndex": NotRequired[int],
},
)


def create_edge(
source_id: str,
target_id: str,
type: EdgeType = "default",
label: Optional[str] = None,
animated: bool = False,
marker_start: Optional[Union[EdgeMarkerType, EdgeMarker]] = None,
marker_end: Optional[Union[EdgeMarkerType, EdgeMarker]] = None,
style: Optional[dict] = None,
):
"""Helper function to build an edge."""
edge = Edge(
id=f"{source_id}->{target_id}",
source=source_id,
target=target_id,
type=type,
animated=animated,
)
if label:
edge["label"] = label
if marker_start:
edge["markerStart"] = marker_start
if marker_end:
edge["markerEnd"] = marker_end
if style:
edge["style"] = style
return edge


class Graph(TypedDict):
nodes: list[Node]
edges: list[Edge]
19 changes: 19 additions & 0 deletions src/trame_flow/widgets/flow/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from trame_flow.widgets.flow.background import Background
from trame_flow.widgets.flow.controls import Controls, ControlsButton
from trame_flow.widgets.flow.minimap import MiniMap, MiniMapNode
from trame_flow.widgets.flow.node_editor import CustomNode, Handle, NodeEditor
from trame_flow.widgets.flow.node_resizer import NodeResizer
from trame_flow.widgets.flow.node_toolbar import NodeToolbar

__all__ = [
"Background",
"Controls",
"ControlsButton",
"CustomNode",
"Handle",
"MiniMap",
"MiniMapNode",
"NodeEditor",
"NodeResizer",
"NodeToolbar",
]
27 changes: 27 additions & 0 deletions src/trame_flow/widgets/flow/background.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from trame_flow.widgets.flow.common import HtmlElement

__all__ = [
"Background",
]


class Background(HtmlElement):
"""Background for `NodeEditor`."""

def __init__(
self,
**kwargs,
):
super().__init__("Background", **kwargs)

self._attr_names += [
"variant",
"gap",
"size",
("pattern_color", "patternColor"),
("bg_color", "bgColor"),
"height",
"width",
"x",
"y",
]
10 changes: 10 additions & 0 deletions src/trame_flow/widgets/flow/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from trame_client.widgets.core import AbstractElement

from trame_flow import module


class HtmlElement(AbstractElement):
def __init__(self, _elem_name, children=None, **kwargs):
super().__init__(_elem_name, children, **kwargs)
if self.server:
self.server.enable_module(module)
40 changes: 40 additions & 0 deletions src/trame_flow/widgets/flow/controls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from trame_flow.widgets.flow.common import HtmlElement

__all__ = [
"Controls",
"ControlsButton",
]


class Controls(HtmlElement):
"""Controls for `NodeEditor`."""

def __init__(
self,
**kwargs,
):
super().__init__("Controls", **kwargs)

self._attr_names += [
("show_fit_view", "showFitView"),
("show_interactive", "showInteractive"),
("show_zoom", "showZoom"),
("fit_view_params", "fitViewParams"),
]

self._event_names += [
("zoom_in", "zoom-in"),
("zoom_out", "zoom-out"),
("fit_view", "fit-view"),
("interaction_change", "interaction-change"),
]


class ControlsButton(HtmlElement):
"""ControlsButton for `Controls`."""

def __init__(
self,
**kwargs,
):
super().__init__("ControlsButton", **kwargs)
Loading