Rotating circles¶
Source: examples/panels/visnetwork/rotating_circles.py
Test: tests/panels/visnetwork/examples/test_rotating_circles.py
Five nodes are pinned at fixed coordinates equally spaced on a circle, and each is linked to one free node whose position the physics engine works out. The ring closes on itself, so the five fixed nodes form a pentagon with five satellites hanging off it.
A periodic callback continuously recomputes the ring angle from a velocity slider and pushes new positions to visnetwork_panel.nodes, so the whole ring spins. Five nodes are pinned to the ring; the remaining five are laid out by the physics engine and get dragged along. Drag the sliders to change the angular velocity and the ring radius.
The code¶
import numpy as np
from panelini.panels.visnetwork import VisNetwork
r = 100 # radius of the ring
phi_0 = 0 # base angle
# Angular offsets for the 5 ring nodes, equally spaced
phi_1 = 2 * np.pi / 5
phi_2 = 2 * 2 * np.pi / 5
phi_3 = 3 * 2 * np.pi / 5
phi_4 = 4 * 2 * np.pi / 5
# Nodes 1-5 are fixed on the ring; nodes 6-10 are free
nodes = [
{"id": 1, "label": "Node 1", "color": "#e04141",
"x": r * np.cos(phi_0), "y": r * np.sin(phi_0), "fixed": True},
# ... nodes 2-5 at phi_0 + phi_1 .. phi_4 ...
{"id": 6, "label": "Node 6", "color": "#e04141"},
# ... nodes 7-10 ...
]
# A ring of 5 edges, plus one spoke from each ring node to a free node
edges = [
{"from": 1, "to": 2}, {"from": 2, "to": 3}, {"from": 3, "to": 4},
{"from": 4, "to": 5}, {"from": 5, "to": 1},
{"from": 1, "to": 6}, {"from": 2, "to": 7}, {"from": 3, "to": 8},
{"from": 4, "to": 9}, {"from": 5, "to": 10},
]
visnetwork_panel = VisNetwork(nodes=nodes, edges=edges, sizing_mode="stretch_both")
vel_slider = pn.widgets.FloatSlider(name="Velocity", start=-20, end=20, value=1)
radius_slider = pn.widgets.FloatSlider(name="Radius", start=0, end=500, value=r)
app = pn.Column(visnetwork_panel, vel_slider, radius_slider)
def rotate() -> None:
"""Advance the ring by one frame and push the new positions to the graph."""
now = time.time()
_anim["phi"] += vel_slider.value * (now - _anim["t"])
_anim["t"] = now
radius = radius_slider.value
ring = [
{
**node,
"x": radius * np.cos(_anim["phi"] + offset),
"y": radius * np.sin(_anim["phi"] + offset),
}
for node, offset in zip(nodes[:5], _offsets, strict=True)
]
visnetwork_panel.nodes = ring + nodes[5:]
pn.state.onload(lambda: pn.state.add_periodic_callback(rotate, period=50))
The animation is a periodic callback rather than a blocking while loop, so it works under panel serve and in the browser alike - a blocking loop would freeze the single-threaded WASM runtime.
Run it live¶
This example runs entirely in your browser via Pyodide. The first load downloads packages, so give it a few seconds.