Source code for ipyforcegraph.behaviors.recording

"""Behaviors for recording the state of the ``ipyforcegraph`` graphs."""

# Copyright (c) 2023 ipyforcegraph contributors.
# Distributed under the terms of the Modified BSD License.

from typing import Any, Optional, Tuple

import ipywidgets as W
import traitlets as T

from ..sources.dataframe import DataFrameSource
from ..trait_utils import JSON_TYPES, coerce
from ._base import Behavior, TFeature, _make_trait


[docs]@W.register class GraphImage(Behavior): """Captures multiple subsequent frames of a canvas, each as an :class:`~ipywidgets.widgets.widget_media.Image`.""" _model_name: str = T.Unicode("GraphImageModel").tag(sync=True) capturing = T.Bool(False, help="whether the frame capture is currently active").tag( sync=True ) frame_count = T.Int(1, help="the number of frames to capture").tag(sync=True) frames: Tuple[W.Image, ...] = W.TypedTuple( T.Instance(W.Image), help="a tuple of :class:`~ipywidgets.widgets.widget_media.Image` to populate with frames of the graph", ).tag(sync=True, **W.widget_serialization) def _get_frames(self) -> Tuple[W.Image, ...]: return tuple( [W.Image(description=f"frame {i}") for i in range(self.frame_count)] ) @T.default("frames") def _default_frames(self) -> Tuple[W.Image, ...]: return self._get_frames() @T.observe("frame_count") def _on_frame_count(self, change: T.Bunch) -> None: frames = self.frames self.frames = tuple() for frame in frames: frame.close() self.frames = self._get_frames()
[docs]@W.register class GraphData(Behavior): """Captures multiple subsequent ticks of a graph simulation, each as a :class:`~pandas.DataFrame`.""" _model_name: str = T.Unicode("GraphDataModel").tag(sync=True) capturing: bool = T.Bool( False, help="whether the dataframe capture is currently active" ).tag(sync=True) source_count = T.Int(1, help="the number of sources to capture").tag(sync=True) sources: Tuple[DataFrameSource, ...] = W.TypedTuple( T.Instance(DataFrameSource), help="a tuple of :class:`~ipyforcegraph.sources.dataframe.DataFrameSource` to be populated with data of the graph", ).tag(sync=True, **W.widget_serialization) def _get_sources(self) -> Tuple[DataFrameSource, ...]: return tuple([DataFrameSource() for i in range(self.source_count)]) @T.default("sources") def _default_sources(self) -> Tuple[DataFrameSource, ...]: return self._get_sources() @T.observe("source_count") def _on_source_count(self, change: T.Bunch) -> None: sources = self.sources self.sources = tuple() for source in sources: source.close() self.sources = self._get_sources()
[docs]@W.register class GraphCamera(Behavior): """Captures the current center and zoom of the graph viewport.""" _model_name: str = T.Unicode("GraphCameraModel").tag(sync=True) zoom: float = T.Float( None, allow_none=True, help="the current 2D zoom level of the viewport" ).tag(sync=True) center: Tuple[float, ...] = W.TypedTuple( T.Float(), allow_none=True, help="the center of the viewport as `[x, y, z?]`" ).tag(sync=True) look_at: Tuple[float, ...] = W.TypedTuple( T.Float(), allow_none=True, help="the direction of a 3D camera as `[x, y, z?]`" ).tag(sync=True) visible: Tuple[int, ...] = W.TypedTuple( T.Int(), help="the indices of all visible nodes" ).tag(sync=True) capturing: bool = T.Bool( False, help="whether visible nodes should be captured as ``visible``" ).tag(sync=True)
[docs]@W.register class GraphDirector(Behavior): """Set a desired center and zoom of the graph viewport.""" _model_name: str = T.Unicode("GraphDirectorModel").tag(sync=True) zoom: Optional[float] = T.Float( None, allow_none=True, help="the desired 2D zoom level of the viewport" ).tag(sync=True) center: Optional[Tuple[float, ...]] = W.TypedTuple( T.Float(), allow_none=True, help="the desired center of the viewport as `[x, y, z?]`", ).tag(sync=True) look_at: Tuple[float, ...] = W.TypedTuple( T.Float(), allow_none=True, help="the direction of a 3d camera as `[x, y, z]`" ).tag(sync=True) zoom_first: bool = T.Bool( False, help="whether to zoom the viewport before panning" ).tag(sync=True) visible: TFeature = _make_trait( "fit nodes in viewport for which this column/template is truthy", by_template=True, by_column=True, ) zoom_duration: float = T.Float(0.2, help="seconds to animate a zoom").tag(sync=True) pan_duration: float = T.Float(0.2, help="seconds to animate a pan").tag(sync=True) fit_duration: float = T.Float(0.2, help="seconds to animate a fit").tag(sync=True) fit_padding: float = T.Float( 10, help="pixels of padding between visible nodes and viewport" ).tag(sync=True) @T.validate("visible") def _validate_scale_bools(self, proposal: T.Bunch) -> Any: return coerce(proposal, JSON_TYPES.boolean)