# yFiles Graphs for Streamlit (Python-first coding guide) The widget is a Streamlit component. ## 1) Installation ```bash pip install yfiles_graphs_for_streamlit ``` ## 2) Minimal working example ```python import streamlit as st from yfiles_graphs_for_streamlit import StreamlitGraphWidget, Node, Edge, Layout st.set_page_config(page_title="yFiles Graphs for Streamlit", layout="wide") nodes = [ Node(id=0, properties={"firstName": "Alpha", "label": "Person A"}), Node(id=1, properties={"firstName": "Bravo", "label": "Person B"}), Node(id=2, properties={"firstName": "Charlie", "label": "Person C", "has_hat": False}), Node(id=3, properties={"firstName": "Delta", "label": "Person D", "likes_pizza": True}) ] edges = [ Edge(start=0, end=1, properties={"since": "1992", "label": "knows"}), Edge(start=1, end=3, properties={"label": "knows", "since": "1992"}), Edge(start=2, end=3, properties={"label": "knows", "since": "1992"}), Edge(start=0, end=2, properties={"label": "knows", "since": 234}) ] graph = StreamlitGraphWidget(nodes, edges) # Shows the interactive graph component graph.show() ``` ## 3) Data model you pass in - **Nodes:** list of dicts. Each node **must** have `id`. Optional `properties` dict for arbitrary data. - **Edges:** list of dicts. Each edge **must** have `id`, `start`, `end` referencing node `id`s. Optional `properties` dict. ## 4) Constructors ```python from yfiles_graphs_for_streamlit import StreamlitGraphWidget # Provide nodes/edges directly widget = StreamlitGraphWidget(nodes, edges) # Import from other graph formats widget = StreamlitGraphWidget.from_graph(g) # supports neo4j, graph_tool, networkx, pygraphviz, pandas, igraph ``` **NetworkX example** ```python from yfiles_graphs_for_streamlit import StreamlitGraphWidget from networkx import erdos_renyi_graph g = erdos_renyi_graph(10, 0.3, seed=2) widget = StreamlitGraphWidget.from_graph(g) widget.show() ``` ## 5) Rendering the component ```python from yfiles_graphs_for_streamlit import Layout nodes_sel, edges_sel = widget.show( directed=True, # default True graph_layout=Layout.ORGANIC, # default Layout.ORGANIC sync_selection=False, # default False sidebar={"enabled": False}, # or {"enabled": True, "start_with": "Neighborhood"|"Data"|"Search"|"About"} neighborhood={"max_distance": 1, "selected_nodes": []}, overview=True, # default True height=500, # default 500 (pixels) key="graph-component" # default None ) ``` ### Return value of `show()` - `sync_selection=False` → returns `None`. - `sync_selection=True` → returns a **tuple** `(selected_nodes, selected_edges)`; each item is a `List[Dict]` from your original data. ### Preserving state across reruns It is recommended to define a specific `key` to preserve the widget's state across reruns. For example, when the passed data is changed interactively, then the component only updates its graph without rebuilding the entire component. ## 6) Data‑driven visualization mappings Each setter takes a **callable** that receives your original item and returns the specified type. Each of the following mapping function can also be set as property on the widget. However, due to better type hints, keyword arguments are preferred for defining data mappings. ### Property mappings (what downstream mappings “see”) ```python widget = StreamlitGraphWidget( nodes, edges, node_property_mapping=lambda node: node.get("properties", {}), edge_property_mapping=lambda edge: edge.get("properties", {}), ) # By default, the original dict is returned. ``` ### Label mappings ```python from yfiles_graphs_for_streamlit import LabelStyle, FontWeight, LabelPosition, TextWrapping, TextAlignment widget = StreamlitGraphWidget( nodes, edges, # Option A: specify a string (resolved first against properties, otherwise used verbatim) node_label_mapping="label", # Option B: set a lambda, return a LabelStyle edge_label_mapping=lambda edge: LabelStyle( text=edge["properties"]["label"], font="serif", font_size=12, font_weight=FontWeight.BOLD, color="#222", background_color="#eef", position=LabelPosition.NORTH, maximum_width=160, wrapping=TextWrapping.WORD, text_alignment=TextAlignment.CENTER, ), ) ``` ### Color mappings (CSS color strings) ```python widget = StreamlitGraphWidget( nodes, edges, node_color_mapping=lambda node: "#4CAF50", edge_color_mapping=lambda edge: "rgb(120,120,120)", ) ``` ### Item visualization mappings ```python from yfiles_graphs_for_streamlit import NodeStyle, EdgeStyle, NodeShape, DashStyle widget = StreamlitGraphWidget( nodes, edges, node_styles_mapping=lambda node: NodeStyle( color="#1976d2", image=None, # URL or data URL if desired shape=NodeShape.ROUND_RECTANGLE, ), edge_styles_mapping=lambda edge: EdgeStyle( color="#999", directed=True, thickness=2.0, dash_style=DashStyle.DASH_DOT, # or a custom pattern string like "5, 10" ), ) ``` Additional edge-specific helpers: ```python widget = StreamlitGraphWidget( nodes, edges, # Factor multiplied with the edge's base thickness edge_thickness_factor_mapping=lambda edge: 1.0 + float(edge["properties"].get("weight", 0)), # Per-edge directed override directed_mapping=lambda edge: edge["properties"].get("label") == "knows", ) ``` ### Geometry mappings > Automatic layout overwrites positions unless you select `Layout.NO_LAYOUT`. ```python widget = StreamlitGraphWidget( nodes, edges, node_scale_factor_mapping=lambda node: 1.0, node_size_mapping=lambda node: (80.0, 30.0), # (width, height) node_position_mapping=lambda node: (100.0, 200.0), # (x, y) node_layout_mapping=lambda node: (100.0, 200.0, 80.0, 30.0), # (x, y, width, height) ) ``` ### Geospatial mapping ```python widget = StreamlitGraphWidget( nodes, edges, node_coordinate_mapping=lambda node: (52.5200, 13.4050), # (latitude, longitude) ) # Use graph_layout=Layout.MAP to position nodes by geo-coordinates. ``` ### Hierarchy mappings (grouping) There are two ways to group nodes * `node_parent_mapping: Optional[Union[str, Callable[[dict], Union[str, int, float]]]]` * This mapping does not create new group nodes and just resolves the mapped id against the given dataset. It should be used when the group nodes are already **part of** the given dataset. * It should return an id for each given node object which is then used as parent group node for this child node. If the parent node does not existing in the dataset, no grouping is created. * `node_parent_group_mapping: Optional[Union[str, Callable[[dict], Union[str, dict]]]]` * This mapping always creates new group nodes based on the given mapping. It should be used when the group nodes are **not part of** the given dataset. * The returned value must either be a `str` which is used as label and id for the new group node (i.e. nodes with the same mapped `str` are grouped together), or it must be a dict with a mandatory `label` property (return same labels for different nodes defines the group for these nodes) and optional more key-value pairs that are added as properties to the group. These additional properties are also considered when resolving other node mappings (e.g. for the styling of group nodes). * Example Snippets ```python StreamlitGraphWidget( nodes=airports, edges=flight_paths, # Assuming each node has a "country" property, group all nodes with the same "country" into groups, # labeled with the value of the "country" property. node_parent_group_mapping="country" ).show() ``` ```python StreamlitGraphWidget( nodes=airports, edges=flight_paths, # Assuming each node has a "country" property, group all nodes with the same "country" into groups, # and assign additional properties to group nodes that can be mapped e.g. by node_styles_mapping. node_parent_group_mapping=lambda node: {'label': node['properties']['country'], 'color': '#9F4499', 'char_count': len(node['properties']['country'])} ).show() ``` ### Layout‑affecting mappings ```python widget = StreamlitGraphWidget( nodes, edges, node_type_mapping=lambda node: node["properties"].get("type", "default"), node_cell_mapping=lambda node: (node["properties"].get("row", 0), node["properties"].get("col", 0)), ) ``` ### Heat mapping (normalized 0..1) ```python widget = StreamlitGraphWidget( nodes, edges, heat_mapping=lambda item: float(item["properties"].get("score", 0.0)), ) ``` ## 7) Recipes **A. Labels from a property, colors by boolean** ```python widget = StreamlitGraphWidget( nodes, edges, node_label_mapping=lambda node: "label", node_color_mapping=lambda node: "#2e7d32" if node["properties"].get("likes_pizza") else "#9e9e9e", ) ``` **B. Thicker, directed edges for “since 1992”** ```python widget = StreamlitGraphWidget( nodes, edges, edge_thickness_factor_mapping=lambda edge: 2.0 if edge["properties"].get("since") == "1992" else 1.0, directed_mapping=lambda edge: True, ) ``` **C. Geospatial view** ```python from yfiles_graphs_for_streamlit import Layout widget = StreamlitGraphWidget( nodes, edges, node_coordinate_mapping=lambda node: (node["properties"]["lat"], node["properties"]["lon"]), ) widget.show(graph_layout=Layout.MAP) ``` **D. Manual positions (no layout)** ```python from yfiles_graphs_for_streamlit import Layout widget = StreamlitGraphWidget( nodes, edges, node_layout_mapping=lambda node: (node["properties"]["x"], node["properties"]["y"], 80, 30), ) widget.show(graph_layout=Layout.NO_LAYOUT) ``` **E. Group nodes by department** ```python from yfiles_graphs_for_streamlit import Layout widget = StreamlitGraphWidget( nodes, edges, node_parent_group_mapping=lambda node: node["properties"].get("department", "Unknown"), ) widget.show(graph_layout=Layout.HIERARCHICAL) ``` **F. Read interactive selection (tuple return)** ```python selected_nodes, selected_edges = widget.show(sync_selection=True) ``` **G. Custom height / inside a fixed-height container** ```python # Fixed height (pixels). Minimum recommended height is 420px. widget.show(height=700) # If the Streamlit container has a fixed height, subtract ~40px for borders and padding with st.container(height=800): widget.show(height=760) ``` ## 8) Option reference | Option | Type | Description | Default | |------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------| | `directed` | bool | Whether edges show direction indicators. | `True` | | `graph_layout` | `Layout` | Automatic layout. See **Enums → Layout**. | `Layout.ORGANIC` | | `sync_selection` | bool | If `True`, `show()` returns `(selected_nodes, selected_edges)`. | `False` | | `sidebar` | dict | Sidebar options: `{"enabled": bool, "start_with": "Neighborhood" or "Data" or "Search" or "About"}`. | `{"enabled": False}` | | `neighborhood` | dict | `{"max_distance": int, "selected_nodes": list}` to filter neighbors. | `{"max_distance": 1, "selected_nodes": []}` | | `overview` | bool | Whether the overview is expanded. | `True` | | `height` | int | Height of the component in pixels. The widget needs at least **420px** to render correctly; lower values clip UI elements. When placed inside a fixed-height `st.container(height=...)`, account for the container's top/bottom padding and borders (e.g. use `container_height - 40`). | `500` | | `key` | `str` | Streamlit's optional unique identifier that defines the component's stable identity and state across reruns. Use a fixed key to preserve the state between reruns. If omitted, Streamlit assigns an implicit key based on the call location and code execution path. Changing the key recreates the component with a fresh state. | `None` | --- ## 9) Enums (import from `yfiles_graphs_for_streamlit`) ### `Layout` - `Layout.CIRCULAR` — Arrange in a single cycle; bundle edge paths. - `Layout.CIRCULAR_STRAIGHT_LINE` — Cycle with straight-line edges. - `Layout.HIERARCHICAL` — Layered, directional flow. - `Layout.ORGANIC` — Force-directed natural layout. - `Layout.INTERACTIVE_ORGANIC` — Organic that adapts while interacting. - `Layout.ORTHOGONAL` — Grid-like nodes, right-angled edges. - `Layout.RADIAL` — Central node with concentric rings. - `Layout.TREE` — Rooted tree layout. - `Layout.MAP` — Uses `(lat, lon)` coordinates. - `Layout.ORTHOGONAL_EDGE_ROUTER` — Right-angle routing emphasis. - `Layout.ORGANIC_EDGE_ROUTER` — Smooth, curved routing. - `Layout.NO_LAYOUT` — Do not apply automatic layout (use provided positions). ### `NodeShape` - `ELLIPSE`, `HEXAGON`, `HEXAGON_STANDING`, `OCTAGON`, `PILL`, `RECTANGLE`, `ROUND_RECTANGLE`, `TRIANGLE`, `SQUIRCLE` ### `DashStyle` - `SOLID`, `DASH`, `DOT`, `DASH_DOT`, `DASH_DOT_DOT` *(Also accepts custom dash patterns as strings like `"5 10"` or `"5, 10"`.)* ### `FontWeight` - `BOLD`, `BOLDER`, `NORMAL`, `LIGHTER` ### `TextAlignment` *(multiline only)* - `CENTER`, `LEFT`, `RIGHT` ### `TextWrapping` *(effective if `maximum_width` is set)* - `NONE`, `CLIP`, `TRIM_CHARACTER`, `TRIM_CHARACTER_ELLIPSIS`, `TRIM_WORD`, `TRIM_WORD_ELLIPSIS`, `WRAP_CHARACTER`, `WRAP_CHARACTER_ELLIPSIS`, `WRAP_WORD`, `WRAP_WORD_ELLIPSIS` ### `LabelPosition` - `CENTER`, `NORTH`, `EAST`, `SOUTH`, `WEST` --- ## 10) Notes & best practices - Each mapping property can also be passed as a keyword argument to the constructor or `from_graph()`. Due to type hints, keyword arguments are preferred for defining data mappings. - Use **`Layout.NO_LAYOUT`** to respect manual positions from geometry mappings. - Prefer **Enums** over raw strings to reduce typos and get IDE completion. - With **`sync_selection=True`**, debounce downstream expensive operations if selections change frequently. - The widget needs at least **420px** of height to render correctly. Setting a lower `height` results in clipped UI elements.