diff --git a/src/ipyforcegraph/_base.py b/src/ipyforcegraph/_base.py index 0acb213..08e9597 100644 --- a/src/ipyforcegraph/_base.py +++ b/src/ipyforcegraph/_base.py @@ -3,18 +3,22 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. +from typing import TYPE_CHECKING + import ipywidgets as W import traitlets as T -from . import _types as _t from .constants import EXTENSION_NAME, EXTENSION_SPEC_VERSION +if TYPE_CHECKING: + from . import _types as _t + class ForceBase(W.Widget): """The base class for all ``IPyForceGraph`` widgets.""" - _model_name: _t.Tstr = T.Unicode("ForceBaseModel").tag(sync=True) - _model_module: _t.Tstr = T.Unicode(EXTENSION_NAME).tag(sync=True) - _model_module_version: _t.Tstr = T.Unicode(EXTENSION_SPEC_VERSION).tag(sync=True) - _view_module: _t.Tstr = T.Unicode(EXTENSION_NAME).tag(sync=True) - _view_module_version: _t.Tstr = T.Unicode(EXTENSION_SPEC_VERSION).tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ForceBaseModel").tag(sync=True) + _model_module: "_t.Tstr" = T.Unicode(EXTENSION_NAME).tag(sync=True) + _model_module_version: "_t.Tstr" = T.Unicode(EXTENSION_SPEC_VERSION).tag(sync=True) + _view_module: "_t.Tstr" = T.Unicode(EXTENSION_NAME).tag(sync=True) + _view_module_version: "_t.Tstr" = T.Unicode(EXTENSION_SPEC_VERSION).tag(sync=True) diff --git a/src/ipyforcegraph/_types.py b/src/ipyforcegraph/_types.py index a47d673..a7d8b2c 100644 --- a/src/ipyforcegraph/_types.py +++ b/src/ipyforcegraph/_types.py @@ -19,3 +19,5 @@ Tbool = T.Bool[bool, Union[bool, int]] Tdict_any = T.Instance[Dict[Any, Any]] + +Tenum_str_str = T.Enum[str, str] diff --git a/src/ipyforcegraph/behaviors/_base.py b/src/ipyforcegraph/behaviors/_base.py index 1931512..377a232 100644 --- a/src/ipyforcegraph/behaviors/_base.py +++ b/src/ipyforcegraph/behaviors/_base.py @@ -3,15 +3,17 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. -from typing import Any, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union import ipywidgets as W import traitlets as T -from .. import _types as _t from .._base import ForceBase from ..trait_utils import JSON_TYPES, coerce +if TYPE_CHECKING: + from .. import _types as _t + TFeature = Optional[Union["Column", "Nunjucks", str]] TNumFeature = Optional[Union["Column", "Nunjucks", str, int, float]] TBoolFeature = Optional[Union["Column", "Nunjucks", str, bool]] @@ -36,9 +38,9 @@ class DEFAULT_RANK: class Behavior(ForceBase): """The base class for all IPyForceGraph graph behaviors.""" - _model_name: _t.Tstr = T.Unicode("BehaviorModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("BehaviorModel").tag(sync=True) - rank: _t.Tint = T.Int( + rank: "_t.Tint" = T.Int( DEFAULT_RANK.behavior, help=("order in which behaviors are applied: lower numbers are applied first."), ).tag(sync=True) @@ -47,8 +49,8 @@ class Behavior(ForceBase): class BaseD3Force(Behavior): """A base for all ``d3-force-3d`` force wrappers.""" - _model_name: _t.Tstr = T.Unicode("BaseD3ForceModel").tag(sync=True) - active: _t.Tbool = T.Bool(True, help="whether the force is currently active").tag( + _model_name: "_t.Tstr" = T.Unicode("BaseD3ForceModel").tag(sync=True) + active: "_t.Tbool" = T.Bool(True, help="whether the force is currently active").tag( sync=True ) @@ -56,7 +58,7 @@ class BaseD3Force(Behavior): class ShapeBase(ForceBase): """A base class from which all :mod:`~ipyforcegraph.behaviors.shapes` inherit.""" - _model_name: _t.Tstr = T.Unicode("ShapeBaseModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ShapeBaseModel").tag(sync=True) class DynamicValue(ForceBase): @@ -66,11 +68,11 @@ class DynamicValue(ForceBase): JSON_DATA_TYPES = JSON_TYPES.get_supported_types() - value: _t.Tstr = T.Unicode( + value: "_t.Tstr" = T.Unicode( "", help="the source used to compute the value for the trait" ).tag(sync=True) - coerce: _t.Tstr_maybe = T.Unicode( + coerce: "_t.Tstr_maybe" = T.Unicode( help="name of a JSON Schema ``type`` into which to coerce the final value", allow_none=True, ).tag(sync=True) @@ -98,7 +100,7 @@ def _validate_coercer(self, proposal: T.Bunch) -> Optional[str]: class Column(DynamicValue): """A column from a :class:`~ipyforcegraph.sources.dataframe.DataFrameSource`.""" - _model_name: _t.Tstr = T.Unicode("ColumnModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ColumnModel").tag(sync=True) class Nunjucks(DynamicValue): @@ -141,7 +143,7 @@ class Nunjucks(DynamicValue): rgb({{ c }},0,0) """ - _model_name: _t.Tstr = T.Unicode("NunjucksModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("NunjucksModel").tag(sync=True) def _make_trait( @@ -178,7 +180,7 @@ def _make_trait( class HasScale(ShapeBase): """A shape that has ``scale_on_zoom``.""" - _model_name: _t.Tstr = T.Unicode("HasScaleModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("HasScaleModel").tag(sync=True) scale_on_zoom: TBoolFeature = _make_trait( "whether font size/stroke respects the global scale. Has no impact on `link` shapes.", @@ -193,7 +195,7 @@ def _validate_scale_bools(self, proposal: T.Bunch) -> Any: class HasFillAndStroke(HasScale): """A shape that has ``fill`` and ``stroke``.""" - _model_name: _t.Tstr = T.Unicode("HasFillModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("HasFillModel").tag(sync=True) fill: TFeature = _make_trait("the fill color of a shape") stroke: TFeature = _make_trait("the stroke color of a shape") stroke_width: TNumFeature = _make_trait("the stroke width of a shape", numeric=True) @@ -215,7 +217,7 @@ def _validate_has_fill_and_stroke_arrays(self, proposal: T.Bunch) -> Any: class HasOffsets(ShapeBase): """A shape that can be offset in the horizontal, vertical, or elevation dimensions.""" - _model_name: _t.Tstr = T.Unicode("HasOffsetsModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("HasOffsetsModel").tag(sync=True) offset_x: float = _make_trait( "the relative horizontal offset from the middle of the shape in ``px``", @@ -238,7 +240,7 @@ def _validate_offset_numerics(self, proposal: T.Bunch) -> Any: class HasDimensions(HasFillAndStroke, HasOffsets): """A shape that has ``width``, ``height`` and ``depth``.""" - _model_name: _t.Tstr = T.Unicode("HasDimensionsModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("HasDimensionsModel").tag(sync=True) width: TNumFeature = _make_trait("the width of a shape in ``px``", numeric=True) height: TNumFeature = _make_trait("the height of a shape in ``px``", numeric=True) diff --git a/src/ipyforcegraph/behaviors/forces.py b/src/ipyforcegraph/behaviors/forces.py index 3404cea..4a76f78 100644 --- a/src/ipyforcegraph/behaviors/forces.py +++ b/src/ipyforcegraph/behaviors/forces.py @@ -10,12 +10,11 @@ # Distributed under the terms of the Modified BSD License. import enum -from typing import Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, Optional import ipywidgets as W import traitlets as T -from .. import _types as _t from ..trait_utils import JSON_TYPES, coerce, validate_enum from ._base import ( BaseD3Force, @@ -26,8 +25,10 @@ _make_trait, ) -TForceDict = Dict[str, BaseD3Force] -TForceDictDesc = T.TraitType[Dict[str, BaseD3Force], Dict[str, BaseD3Force]] +if TYPE_CHECKING: + from .. import _types as _t + + TForceDict = Dict[str, BaseD3Force] @W.register @@ -39,43 +40,45 @@ class GraphForces(Behavior): For more, see the frontend documentation on https://github.com/vasturiano/force-graph#force-engine-d3-force-configuration """ - _model_name: _t.Tstr = T.Unicode("GraphForcesModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("GraphForcesModel").tag(sync=True) - forces: TForceDictDesc = T.Dict( + forces: "T.TraitType[Dict[str, BaseD3Force], Dict[str, BaseD3Force]]" = T.Dict( value_trait=T.Instance(BaseD3Force, allow_none=True), help="named forces. Set a name `None` to remove a force: By default, ForceGraph has `link`, `charge`, and `center`", ).tag(sync=True, **W.widget_serialization) - warmup_ticks: _t.Tint = T.Int( + warmup_ticks: "_t.Tint" = T.Int( 0, min=0, help="layout engine cycles to dry-run at ignition before starting to render", ).tag(sync=True) - cooldown_ticks: _t.Tint = T.Int( + cooldown_ticks: "_t.Tint" = T.Int( -1, help="frames to render before stopping and freezing the layout engine. Values less than zero will be translated to `Infinity`", ).tag(sync=True) - alpha_min: _t.Tfloat = T.Float( + alpha_min: "_t.Tfloat" = T.Float( 0.0, min=0.0, max=1.0, help="simulation alpha min parameter" ).tag(sync=True) - alpha_decay: _t.Tfloat = T.Float( + alpha_decay: "_t.Tfloat" = T.Float( 0.0228, min=0.0, max=1.0, help="simulation intensity decay parameter", ).tag(sync=True) - velocity_decay: _t.Tfloat = T.Float( + velocity_decay: "_t.Tfloat" = T.Float( 0.4, min=0.0, max=1.0, help="nodes' velocity decay that simulates the medium resistance", ).tag(sync=True) - def __init__(self, forces: Optional[TForceDict] = None, *args: Any, **kwargs: Any): + def __init__( + self, forces: "Optional[TForceDict]" = None, *args: Any, **kwargs: Any + ): kwargs["forces"] = forces or {} super().__init__(*args, **kwargs) @@ -87,7 +90,7 @@ class Link(BaseD3Force): https://github.com/d3/d3-force#links """ - _model_name: _t.Tstr = T.Unicode("LinkForceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("LinkForceModel").tag(sync=True) distance: TNumFeature = _make_trait( "the 'desired' distance of a link. Context takes ``link``", numeric=True @@ -112,21 +115,21 @@ class Center(BaseD3Force): https://github.com/d3/d3-force#centering """ - _model_name: _t.Tstr = T.Unicode("CenterForceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("CenterForceModel").tag(sync=True) - x: _t.Tfloat_maybe = T.Float( + x: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the x-coordinate of the position to center the nodes on", ).tag(sync=True) - y: _t.Tfloat_maybe = T.Float( + y: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the y-coordinate of the position to center the nodes on", ).tag(sync=True) - z: _t.Tfloat_maybe = T.Float( + z: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the z-coordinate of the position to center the nodes on (only applies to ``ForceGraph3D``)", @@ -141,7 +144,7 @@ class X(BaseD3Force): https://github.com/d3/d3-force#positioning """ - _model_name: _t.Tstr = T.Unicode("XForceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("XForceModel").tag(sync=True) x: TNumFeature = _make_trait( "the x-coordinate of the centering position to the specified number. " @@ -169,7 +172,7 @@ class Y(BaseD3Force): https://github.com/d3/d3-force#positioning """ - _model_name: _t.Tstr = T.Unicode("YForceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("YForceModel").tag(sync=True) y: TNumFeature = _make_trait( "the y-coordinate of the centering position. " "Context takes ``node``.", @@ -199,7 +202,7 @@ class Z(BaseD3Force): https://github.com/d3/d3-force#positioning """ - _model_name: _t.Tstr = T.Unicode("ZForceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ZForceModel").tag(sync=True) z: TNumFeature = _make_trait( "the z-coordinate of the centering position. Context takes ``node``.", @@ -230,7 +233,7 @@ class ManyBody(BaseD3Force): https://github.com/d3/d3-force#many-body """ - _model_name: _t.Tstr = T.Unicode("ManyBodyForceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ManyBodyForceModel").tag(sync=True) strength: TNumFeature = _make_trait( "a nunjucks template to use to calculate strength. Context takes ``node``", @@ -239,19 +242,19 @@ class ManyBody(BaseD3Force): allow_none=False, ) - theta: _t.Tfloat_maybe = T.Float( + theta: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the Barnes-Hut approximation criterion", ).tag(sync=True) - distance_min: _t.Tfloat_maybe = T.Float( + distance_min: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the minimum distance between nodes over which this force is considered", ).tag(sync=True) - distance_max: _t.Tfloat_maybe = T.Float( + distance_max: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the maximum distance between nodes over which this force is considered", @@ -270,7 +273,7 @@ class Radial(BaseD3Force): https://github.com/d3/d3-force#forceRadial """ - _model_name: _t.Tstr = T.Unicode("RadialForceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("RadialForceModel").tag(sync=True) radius: TNumFeature = _make_trait( "radius of the force. Context takes ``node``", @@ -284,19 +287,19 @@ class Radial(BaseD3Force): allow_none=False, ) - x: _t.Tfloat_maybe = T.Float( + x: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the x-coordinate of the centering position", ).tag(sync=True) - y: _t.Tfloat_maybe = T.Float( + y: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the y-coordinate of the centering position", ).tag(sync=True) - z: _t.Tfloat_maybe = T.Float( + z: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the z-coordinate of the centering position", @@ -315,14 +318,14 @@ class Collision(BaseD3Force): https://github.com/d3/d3-force#collision """ - _model_name: _t.Tstr = T.Unicode("CollisionForceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("CollisionForceModel").tag(sync=True) radius: TNumFeature = _make_trait( "The radius of collision by node. Context takes ``node``", numeric=True, ) - strength: _t.Tfloat_maybe = T.Float( + strength: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, min=0.0, @@ -342,9 +345,9 @@ class Cluster(BaseD3Force): https://github.com/vasturiano/d3-force-cluster-3d """ - _model_name: _t.Tstr = T.Unicode("ClusterForceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ClusterForceModel").tag(sync=True) - strength: _t.Tfloat_maybe = T.Float( + strength: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, min=0.0, @@ -352,7 +355,7 @@ class Cluster(BaseD3Force): help="the strength of the force", ).tag(sync=True) - inertia: _t.Tfloat_maybe = T.Float( + inertia: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, min=0.0, @@ -425,16 +428,16 @@ class Mode(enum.Enum): radial_out = "radialout" radial_in = "radialin" - _model_name: _t.Tstr = T.Unicode("DAGBehaviorModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("DAGBehaviorModel").tag(sync=True) - mode: T.Enum[str, str] = T.Enum( + mode: "_t.Tenum_str_str" = T.Enum( values=[*[m.value for m in Mode], *Mode], help="DAG constraint layout mode/direction", default_value=None, allow_none=True, ).tag(sync=True) - level_distance: _t.Tfloat_maybe = T.Float( + level_distance: "_t.Tfloat_maybe" = T.Float( default_value=None, help="distance between DAG levels", allow_none=True, diff --git a/src/ipyforcegraph/behaviors/particles.py b/src/ipyforcegraph/behaviors/particles.py index d88da60..a495ec5 100644 --- a/src/ipyforcegraph/behaviors/particles.py +++ b/src/ipyforcegraph/behaviors/particles.py @@ -3,15 +3,17 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. -from typing import Any +from typing import TYPE_CHECKING, Any import ipywidgets as W import traitlets as T -from .. import _types as _t from ..trait_utils import JSON_TYPES, coerce from ._base import Behavior, TFeature, TNumFeature, _make_trait +if TYPE_CHECKING: + from .. import _types as _t + @W.register class LinkParticles(Behavior): @@ -22,7 +24,7 @@ class LinkParticles(Behavior): or they will exceed the frame rate of the animation. """ - _model_name: _t.Tstr = T.Unicode("LinkParticleModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("LinkParticleModel").tag(sync=True) color: TFeature = _make_trait("the color of the particles") density: TNumFeature = _make_trait( diff --git a/src/ipyforcegraph/behaviors/recording.py b/src/ipyforcegraph/behaviors/recording.py index c03fdb9..6f90a0c 100644 --- a/src/ipyforcegraph/behaviors/recording.py +++ b/src/ipyforcegraph/behaviors/recording.py @@ -3,22 +3,24 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. -from typing import Any, Optional, Tuple +from typing import TYPE_CHECKING, Any, Optional, Tuple import ipywidgets as W import traitlets as T -from .. import _types as _t from ..sources.dataframe import DataFrameSource from ..trait_utils import JSON_TYPES, coerce from ._base import Behavior, TFeature, _make_trait +if TYPE_CHECKING: + from .. import _types as _t + @W.register class GraphImage(Behavior): """Captures multiple subsequent frames of a canvas, each as an :class:`~ipywidgets.widgets.widget_media.Image`.""" - _model_name: _t.Tstr = T.Unicode("GraphImageModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("GraphImageModel").tag(sync=True) capturing = T.Bool(False, help="whether the frame capture is currently active").tag( sync=True @@ -56,9 +58,9 @@ def _on_frame_count(self, change: T.Bunch) -> None: class GraphData(Behavior): """Captures multiple subsequent ticks of a graph simulation, each as a :class:`~pandas.DataFrame`.""" - _model_name: _t.Tstr = T.Unicode("GraphDataModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("GraphDataModel").tag(sync=True) - capturing: _t.Tbool = T.Bool( + capturing: "_t.Tbool" = T.Bool( False, help="whether the dataframe capture is currently active" ).tag(sync=True) @@ -92,9 +94,9 @@ def _on_source_count(self, change: T.Bunch) -> None: class GraphCamera(Behavior): """Captures the current center and zoom of the graph viewport.""" - _model_name: _t.Tstr = T.Unicode("GraphCameraModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("GraphCameraModel").tag(sync=True) - zoom: _t.Tfloat_maybe = T.Float( + zoom: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the current 2D zoom level of the viewport" ).tag(sync=True) @@ -110,7 +112,7 @@ class GraphCamera(Behavior): T.Int(), help="the indices of all visible nodes" ).tag(sync=True) - capturing: _t.Tbool = T.Bool( + capturing: "_t.Tbool" = T.Bool( False, help="whether visible nodes should be captured as ``visible``" ).tag(sync=True) @@ -119,9 +121,9 @@ class GraphCamera(Behavior): class GraphDirector(Behavior): """Set a desired center and zoom of the graph viewport.""" - _model_name: _t.Tstr = T.Unicode("GraphDirectorModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("GraphDirectorModel").tag(sync=True) - zoom: _t.Tfloat_maybe = T.Float( + zoom: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the desired 2D zoom level of the viewport" ).tag(sync=True) @@ -131,11 +133,11 @@ class GraphDirector(Behavior): help="the desired center of the viewport as `[x, y, z?]`", ).tag(sync=True) - look_at: _t.Tfloat_maybe = W.TypedTuple( + look_at: "_t.Tfloat_maybe" = W.TypedTuple( T.Float(), allow_none=True, help="the direction of a 3d camera as `[x, y, z]`" ).tag(sync=True) - zoom_first: _t.Tbool = T.Bool( + zoom_first: "_t.Tbool" = T.Bool( False, help="whether to zoom the viewport before panning" ).tag(sync=True) @@ -145,19 +147,19 @@ class GraphDirector(Behavior): by_column=True, ) - zoom_duration: _t.Tfloat = T.Float(0.2, help="seconds to animate a zoom").tag( + zoom_duration: "_t.Tfloat" = T.Float(0.2, help="seconds to animate a zoom").tag( sync=True ) - pan_duration: _t.Tfloat = T.Float(0.2, help="seconds to animate a pan").tag( + pan_duration: "_t.Tfloat" = T.Float(0.2, help="seconds to animate a pan").tag( sync=True ) - fit_duration: _t.Tfloat = T.Float(0.2, help="seconds to animate a fit").tag( + fit_duration: "_t.Tfloat" = T.Float(0.2, help="seconds to animate a fit").tag( sync=True ) - fit_padding: _t.Tfloat = T.Float( + fit_padding: "_t.Tfloat" = T.Float( 10, help="pixels of padding between visible nodes and viewport" ).tag(sync=True) diff --git a/src/ipyforcegraph/behaviors/scales.py b/src/ipyforcegraph/behaviors/scales.py index b4590f9..6de6bf3 100644 --- a/src/ipyforcegraph/behaviors/scales.py +++ b/src/ipyforcegraph/behaviors/scales.py @@ -8,15 +8,17 @@ # Distributed under the terms of the Modified BSD License. import enum -from typing import Any, Tuple +from typing import TYPE_CHECKING, Any, Tuple import ipywidgets as W import traitlets as T -from .. import _types as _t from ..trait_utils import validate_enum from ._base import Column +if TYPE_CHECKING: + from .. import _types as _t + class ContinuousColor(Column): """A column which will interpolate a numeric column on a color scale.""" @@ -63,15 +65,15 @@ class Scheme(enum.Enum): ylorbr = "YlOrBr" ylorrd = "YlOrRd" - _model_name: _t.Tstr = T.Unicode("ContinuousColorModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ContinuousColorModel").tag(sync=True) - scheme: T.Enum[str, str] = T.Enum( + scheme: "_t.Tenum_str_str" = T.Enum( values=[*[m.value for m in Scheme], *Scheme], help="name of a continuous ``d3-scale-chromatic`` scheme", allow_none=True, ).tag(sync=True) - domain: Tuple[float, float] = W.TypedTuple( + domain: "Tuple[float, float]" = W.TypedTuple( (0.0, 1.0), help=("the ``[min, max]`` to map to the scale's colors"), ).tag(sync=True) @@ -98,15 +100,15 @@ class Scheme(enum.Enum): set3 = "Set3" tableau10 = "Tableau10" - _model_name: _t.Tstr = T.Unicode("OrdinalColorModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("OrdinalColorModel").tag(sync=True) - scheme: T.Enum[str, str] = T.Enum( + scheme: "_t.Tenum_str_str" = T.Enum( values=[*[m.value for m in Scheme], *Scheme], help="name of an ordinal ``d3-scale-chromatic`` scheme", allow_none=True, ).tag(sync=True) - domain: T.Container[Tuple[Any, ...]] = T.Tuple( + domain: "T.Container[Tuple[Any, ...]]" = T.Tuple( (0.0, 1.0), help=("values mapped to ordinal colors in the range"), ).tag(sync=True) diff --git a/src/ipyforcegraph/behaviors/selection.py b/src/ipyforcegraph/behaviors/selection.py index fb33263..ae9f539 100644 --- a/src/ipyforcegraph/behaviors/selection.py +++ b/src/ipyforcegraph/behaviors/selection.py @@ -3,21 +3,23 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. -from typing import Optional, Tuple, Union +from typing import TYPE_CHECKING, Optional, Tuple, Union import ipywidgets as W import traitlets as T -from .. import _types as _t from ._base import DEFAULT_RANK, Behavior +if TYPE_CHECKING: + from .. import _types as _t + @W.register class NodeSelection(Behavior): """Enable node selection with synced row `indices` (not ``id``) of selected ``nodes``.""" - _model_name: _t.Tstr = T.Unicode("NodeSelectionModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("NodeSelectionModel").tag(sync=True) selected: Tuple[Union[int, str], ...] = W.TypedTuple( T.Union((T.Int(), T.Unicode())), @@ -25,17 +27,17 @@ class NodeSelection(Behavior): help="the row indices of any selected nodes", ).tag(sync=True) - column_name: _t.Tstr_maybe = T.Unicode( + column_name: "_t.Tstr_maybe" = T.Unicode( None, help="an optional name of a ``node``'s column to update when selected", allow_none=True, ).tag(sync=True) - multiple: _t.Tbool = T.Bool( + multiple: "_t.Tbool" = T.Bool( True, help="if ``False``, only one ``node`` can be selected at a time" ).tag(sync=True) - selected_color: _t.Tstr = T.Unicode( + selected_color: "_t.Tstr" = T.Unicode( "rgba(179, 163, 105, 1.0)", help="the color of selected nodes", ).tag(sync=True) @@ -49,7 +51,7 @@ def _default_rank(self) -> Optional[int]: class LinkSelection(Behavior): """Enable link selection with synced ids of selected links.""" - _model_name: _t.Tstr = T.Unicode("LinkSelectionModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("LinkSelectionModel").tag(sync=True) selected: Tuple[Union[int, str], ...] = W.TypedTuple( T.Union((T.Int(), T.Unicode())), @@ -57,21 +59,21 @@ class LinkSelection(Behavior): help="the 0-based indices of any selected links", ).tag(sync=True) - column_name: _t.Tstr_maybe = T.Unicode( + column_name: "_t.Tstr_maybe" = T.Unicode( None, help="an optional name of ``node``'s column to update when selected", allow_none=True, ).tag(sync=True) - multiple: _t.Tbool = T.Bool( + multiple: "_t.Tbool" = T.Bool( True, help="if ``False``, only one ``link`` can be selected at a time" ).tag(sync=True) - selected_color: _t.Tstr = T.Unicode( + selected_color: "_t.Tstr" = T.Unicode( "rgba(31, 120, 179, 1.0)", help="the color of selected links" ).tag(sync=True) - selected_curvature: _t.Tfloat_maybe = T.Float( + selected_curvature: "_t.Tfloat_maybe" = T.Float( None, allow_none=True, help="the curvature of selected links, default: ``None`` preserves unselected ``curvature``.", @@ -84,7 +86,7 @@ class LinkSelection(Behavior): help="the line-dash of selected links, default: ``None`` preserves unselected ``line_dash``", ).tag(sync=True) - selected_width: _t.Tfloat = T.Float(2, help="the width of selected links").tag( + selected_width: "_t.Tfloat" = T.Float(2, help="the width of selected links").tag( sync=True ) diff --git a/src/ipyforcegraph/behaviors/shapes.py b/src/ipyforcegraph/behaviors/shapes.py index f41e0f5..b768ad6 100644 --- a/src/ipyforcegraph/behaviors/shapes.py +++ b/src/ipyforcegraph/behaviors/shapes.py @@ -3,12 +3,11 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. -from typing import Any, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Optional, Sequence, Tuple, Union import ipywidgets as W import traitlets as T -from .. import _types as _t from ..trait_utils import JSON_TYPES, coerce from ._base import ( DEFAULT_RANK, @@ -22,6 +21,9 @@ _make_trait, ) +if TYPE_CHECKING: + from .. import _types as _t + @W.register class Text(HasFillAndStroke, HasOffsets): @@ -30,7 +32,7 @@ class Text(HasFillAndStroke, HasOffsets): If the ``text`` trait is (or evaluates to) ``0`` or ``None``, no shape will be drawn. """ - _model_name: _t.Tstr = T.Unicode("TextShapeModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("TextShapeModel").tag(sync=True) text: TFeature = _make_trait("the text of a shape") font: TFeature = _make_trait("the font face of a shape") @@ -62,7 +64,7 @@ class Ellipse(HasDimensions): If the ``width`` trait is (or evaluates to) ``0`` or ``None``, no shape will be drawn. """ - _model_name: _t.Tstr = T.Unicode("EllipseShapeModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("EllipseShapeModel").tag(sync=True) @W.register @@ -72,7 +74,7 @@ class Rectangle(HasDimensions): If the ``width`` trait is (or evaluates to) ``0`` or ``None``, no shape will be drawn. """ - _model_name: _t.Tstr = T.Unicode("RectangleShapeModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("RectangleShapeModel").tag(sync=True) @W.register @@ -87,7 +89,7 @@ class NodeShapes(Behavior): user selection. """ - _model_name: _t.Tstr = T.Unicode("NodeShapeModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("NodeShapeModel").tag(sync=True) size: TFeature = _make_trait("the size of the default circle shape", numeric=True) color: TFeature = _make_trait("the color of the default circle shape") @@ -123,7 +125,7 @@ class LinkShapes(Behavior): ``line_dash`` is not displayed in :class:`~ipyforcegraph.graphs.ForceGraph3D`. """ - _model_name: _t.Tstr = T.Unicode("LinkShapeModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("LinkShapeModel").tag(sync=True) color: TFeature = _make_trait("the color of the link") curvature: TNumFeature = _make_trait( @@ -164,7 +166,7 @@ def _validate_link_shape_arrays(self, proposal: T.Bunch) -> Any: class LinkArrows(Behavior): """Customize the size, position, and color of arrows on ``links``.""" - _model_name: _t.Tstr = T.Unicode("LinkArrowModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("LinkArrowModel").tag(sync=True) color: TFeature = _make_trait("the color of the arrow") length: TNumFeature = _make_trait("the length of the arrow", numeric=True) diff --git a/src/ipyforcegraph/behaviors/tooltip.py b/src/ipyforcegraph/behaviors/tooltip.py index 0e0fac2..91b6803 100644 --- a/src/ipyforcegraph/behaviors/tooltip.py +++ b/src/ipyforcegraph/behaviors/tooltip.py @@ -3,14 +3,16 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. -from typing import Any, Optional +from typing import TYPE_CHECKING, Any, Optional import ipywidgets as W import traitlets as T -from .. import _types as _t from ._base import Behavior, TFeature, _make_trait +if TYPE_CHECKING: + from .. import _types as _t + @W.register class NodeTooltip(Behavior): @@ -19,7 +21,7 @@ class NodeTooltip(Behavior): These may be strings or full HTML. """ - _model_name: _t.Tstr = T.Unicode("NodeTooltipModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("NodeTooltipModel").tag(sync=True) label: TFeature = _make_trait( "the label to display when hovering over the ``node``, can be ``Column`` or ``Nunjucks`` template", @@ -38,7 +40,7 @@ class LinkTooltip(Behavior): These may be strings or full HTML. """ - _model_name: _t.Tstr = T.Unicode("LinkTooltipModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("LinkTooltipModel").tag(sync=True) label: TFeature = _make_trait( "the label to display when hovering over the ``link``, can be ``Column`` or ``Nunjucks`` template", diff --git a/src/ipyforcegraph/behaviors/wrappers.py b/src/ipyforcegraph/behaviors/wrappers.py index a7a4987..da028ca 100644 --- a/src/ipyforcegraph/behaviors/wrappers.py +++ b/src/ipyforcegraph/behaviors/wrappers.py @@ -4,12 +4,11 @@ # Distributed under the terms of the Modified BSD License. import enum -from typing import Any, Union +from typing import TYPE_CHECKING, Any, Union import ipywidgets as W import traitlets as T -from .. import _types as _t from .._base import ForceBase from ..constants import RESERVED_COLUMNS from ..trait_utils import validate_enum @@ -18,6 +17,10 @@ TAnyWrapped = Union[DynamicValue, "WrapperBase", str, bool, int, float] +if TYPE_CHECKING: + from .. import _types as _t + + def _make_color_channel(name: str) -> Any: """Create a color channel""" return T.Float( @@ -28,9 +31,9 @@ def _make_color_channel(name: str) -> Any: class WrapperBase(ForceBase): """A wrapper for other dynamic values""" - _model_name: _t.Tstr = T.Unicode("WrapperBaseModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("WrapperBaseModel").tag(sync=True) - wrapped: T.TraitType[TAnyWrapped, TAnyWrapped] = T.Union( + wrapped: "T.TraitType[TAnyWrapped, TAnyWrapped]" = T.Union( [ T.Instance(DynamicValue), T.Instance("ipyforcegraph.behaviors.wrappers.WrapperBase"), @@ -77,9 +80,9 @@ def __repr__(self) -> str: class CaptureAs(WrapperBase): """A wrapper that stores dynamically-computed values in the underlying ``node`` or ``link`.""" - _model_name: _t.Tstr = T.Unicode("CaptureAsModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("CaptureAsModel").tag(sync=True) - column_name: _t.Tstr = T.Unicode( + column_name: "_t.Tstr" = T.Unicode( allow_none=False, help="name of a column to update with a derived value" ).tag(sync=True) @@ -98,7 +101,7 @@ def _validate_column_name(self, proposal: T.Bunch) -> str: class ReplaceCssVariables(WrapperBase): """A wrapper that replaces all CSS ``var(--)`` values in the wrapped value.""" - _model_name: _t.Tstr = T.Unicode("ReplaceCssVariablesModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ReplaceCssVariablesModel").tag(sync=True) class Colorize(WrapperBase): @@ -115,22 +118,22 @@ class Space(enum.Enum): hcl = "hcl" cubehelix = "cubehelix" - _model_name: _t.Tstr = T.Unicode("ColorizeModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ColorizeModel").tag(sync=True) - space: T.Enum[str, str] = T.Enum( + space: "_t.Tenum_str_str" = T.Enum( default_value=Space.hsl.value, values=[*[m.value for m in Space], *Space], help="name of a ``d3-color`` color space", ).tag(sync=True) - a: _t.Tfloat_maybe = _make_color_channel("a*") - b: _t.Tfloat_maybe = _make_color_channel("blue (or ``b*``)") - c: _t.Tfloat_maybe = _make_color_channel("chroma") - g: _t.Tfloat_maybe = _make_color_channel("green") - h: _t.Tfloat_maybe = _make_color_channel("hue") - l: _t.Tfloat_maybe = _make_color_channel("luminance (or lightness)") # noqa: E741 - r: _t.Tfloat_maybe = _make_color_channel("red") - s: _t.Tfloat_maybe = _make_color_channel("saturation") + a: "_t.Tfloat_maybe" = _make_color_channel("a*") + b: "_t.Tfloat_maybe" = _make_color_channel("blue (or ``b*``)") + c: "_t.Tfloat_maybe" = _make_color_channel("chroma") + g: "_t.Tfloat_maybe" = _make_color_channel("green") + h: "_t.Tfloat_maybe" = _make_color_channel("hue") + l: "_t.Tfloat_maybe" = _make_color_channel("luminance (or lightness)") # noqa: E741 + r: "_t.Tfloat_maybe" = _make_color_channel("red") + s: "_t.Tfloat_maybe" = _make_color_channel("saturation") opacity: float = _make_color_channel("opacity") @T.validate("space") @@ -141,6 +144,6 @@ def _validate_enum(self, proposal: T.Bunch) -> Any: class Tint(WrapperBase): """Apply a uniform lighten/darken amount to a color.""" - _model_name: _t.Tstr = T.Unicode("TintModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("TintModel").tag(sync=True) value: float = _make_color_channel("tint") diff --git a/src/ipyforcegraph/graphs.py b/src/ipyforcegraph/graphs.py index 1f4b22a..8780a9b 100644 --- a/src/ipyforcegraph/graphs.py +++ b/src/ipyforcegraph/graphs.py @@ -3,30 +3,32 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. -from typing import Any, Tuple +from typing import TYPE_CHECKING, Any, Tuple import ipywidgets as W import traitlets as T -from . import _types as _t from ._base import ForceBase from .behaviors import Behavior from .behaviors._base import _make_trait from .sources.dataframe import DataFrameSource +if TYPE_CHECKING: + from . import _types as _t + @W.register class ForceGraph(W.DOMWidget, ForceBase): """Base force-directed graph widget.""" - _model_name: _t.Tstr = T.Unicode("ForceGraphModel").tag(sync=True) - _view_name: _t.Tstr = T.Unicode("ForceGraphView").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ForceGraphModel").tag(sync=True) + _view_name: "_t.Tstr" = T.Unicode("ForceGraphView").tag(sync=True) - source: T.Instance[DataFrameSource] = T.Instance( + source: "T.Instance[DataFrameSource]" = T.Instance( DataFrameSource, kw={}, help="the source of ``node`` and ``link`` data" ).tag(sync=True, **W.widget_serialization) - behaviors: Tuple[Behavior, ...] = W.TypedTuple( + behaviors: "Tuple[Behavior, ...]" = W.TypedTuple( T.Instance(Behavior), kw={}, help=( @@ -40,22 +42,22 @@ class ForceGraph(W.DOMWidget, ForceBase): ), ).tag(sync=True, **W.widget_serialization) - default_node_color: _t.Tstr = T.Unicode( + default_node_color: "_t.Tstr" = T.Unicode( "rgba(31, 120, 179, 1.0)", help="a default ``node`` color, which can be overridden by :class:`~ipyforcegraph.behaviors.shapes.NodeShapes`", ).tag(sync=True) - default_node_size: _t.Tfloat = T.Float( + default_node_size: "_t.Tfloat" = T.Float( 1, help="a default ``node`` size, which can be overridden by :class:`~ipyforcegraph.behaviors.shapes.NodeShapes`", ).tag(sync=True) - default_link_color: _t.Tstr = T.Unicode( + default_link_color: "_t.Tstr" = T.Unicode( "rgba(66, 66, 66, 0.5)", help="a default ``link`` color, which can be overridden by :class:`~ipyforcegraph.behaviors.shapes.LinkShapes`", ).tag(sync=True) - default_link_width: _t.Tfloat = T.Float( + default_link_width: "_t.Tfloat" = T.Float( 1.0, help="a default ``link`` width, which can be overridden by :class:`~ipyforcegraph.behaviors.shapes.LinkShapes`", ).tag(sync=True) @@ -77,5 +79,5 @@ def reheat(self) -> None: class ForceGraph3D(ForceGraph): """3D force-directed graph widget.""" - _model_name: _t.Tstr = T.Unicode("ForceGraph3DModel").tag(sync=True) - _view_name: _t.Tstr = T.Unicode("ForceGraph3DView").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("ForceGraph3DModel").tag(sync=True) + _view_name: "_t.Tstr" = T.Unicode("ForceGraph3DView").tag(sync=True) diff --git a/src/ipyforcegraph/sources/dataframe.py b/src/ipyforcegraph/sources/dataframe.py index 2a6c5af..2720097 100644 --- a/src/ipyforcegraph/sources/dataframe.py +++ b/src/ipyforcegraph/sources/dataframe.py @@ -1,7 +1,7 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. -from typing import Tuple +from typing import TYPE_CHECKING, Tuple import ipywidgets as W import numpy as N @@ -9,22 +9,24 @@ import traitlets as T import traittypes as TT -from .. import _types as _t from .._base import ForceBase from ..serializers import dataframe_serialization +if TYPE_CHECKING: + from .. import _types as _t + @W.register class DataFrameSource(ForceBase): """A Graph Source that stores the ``nodes`` and ``links`` as :class:`~pandas.DataFrame` instances.""" - _model_name: _t.Tstr = T.Unicode("DataFrameSourceModel").tag(sync=True) + _model_name: "_t.Tstr" = T.Unicode("DataFrameSourceModel").tag(sync=True) nodes: P.DataFrame = TT.PandasType( klass=P.DataFrame, help="the :class:`~pandas.DataFrame` of node data" ).tag(sync=True, **dataframe_serialization) - node_id_column: _t.Tstr = T.Unicode( + node_id_column: "_t.Tstr" = T.Unicode( "id", help="the name of the column for a node's identifier, or 0-based position in the column if `None`", ).tag(sync=True) @@ -33,7 +35,7 @@ class DataFrameSource(ForceBase): T.Unicode(), help="columns to preserve when updating ``nodes``" ).tag(sync=True) - link_id_column: _t.Tstr = T.Unicode( + link_id_column: "_t.Tstr" = T.Unicode( "id", help="the name of the column for a links's identifier, or 0-based position in the column if `None`", ).tag(sync=True) @@ -46,12 +48,12 @@ class DataFrameSource(ForceBase): klass=P.DataFrame, help="the :class:`~pandas.DataFrame` of link data" ).tag(sync=True, **dataframe_serialization) - link_source_column: _t.Tstr = T.Unicode( + link_source_column: "_t.Tstr" = T.Unicode( "source", help="the name of the column for a link's source, defaulting to ``source``", ).tag(sync=True) - link_target_column: _t.Tstr = T.Unicode( + link_target_column: "_t.Tstr" = T.Unicode( "target", help="the name of the column for a link's target, defaulting to ``target``", ).tag(sync=True) diff --git a/src/ipyforcegraph/sources/dodo.py b/src/ipyforcegraph/sources/dodo.py index 6080e46..a37762e 100644 --- a/src/ipyforcegraph/sources/dodo.py +++ b/src/ipyforcegraph/sources/dodo.py @@ -12,7 +12,7 @@ from copy import deepcopy from importlib.util import module_from_spec, spec_from_file_location from pathlib import Path -from typing import Any, Dict, List, Union +from typing import TYPE_CHECKING, Any, Dict, List, Union from uuid import uuid4 import pandas as P @@ -22,44 +22,47 @@ from doit.dependency import Dependency, JsonDB, SqliteDB from doit.task import Task -from .. import _types as _t from .dataframe import DataFrameSource TAnyDict = Dict[str, Any] Tasks = List[Task] +if TYPE_CHECKING: + from .. import _types as _t + + class DodoSource(DataFrameSource): """A source that displays the files, tasks, and dependencies of a ``dodo.py``.""" - graph_data: _t.Tdict_any = T.Dict( + graph_data: "_t.Tdict_any" = T.Dict( help="an internal collection of observed Data" ).tag(sync=False) - project_root: T.TraitType[Path, Union[str, Path]] = T.Union( + project_root: "T.TraitType[Path, Union[str, Path]]" = T.Union( [T.Unicode(), T.Instance(Path)], help="a path to a folder that contains a ``dodo.py``", ).tag(sync=False) - backend: _t.Tstr = T.Unicode( + backend: "_t.Tstr" = T.Unicode( "sqlite3", help="the backend for ``doit``'s dependency state" ).tag(sync=False) - dep_file: _t.Tstr = T.Unicode( + dep_file: "_t.Tstr" = T.Unicode( ".doit.db", help="the path to ``doit``'s ``dep_file``, relative to the ``project_root``", ).tag(sync=False) - dodo_file: _t.Tstr = T.Unicode( + dodo_file: "_t.Tstr" = T.Unicode( "dodo.py", help="the path to a ``dodo.py``, relative to the ``project_root``", ).tag(sync=True) - show_files: _t.Tbool = T.Bool( + show_files: "_t.Tbool" = T.Bool( True, help="create a node for each file, or collapse to dep groups" ).tag(sync=False) - show_directories: _t.Tbool = T.Bool( + show_directories: "_t.Tbool" = T.Bool( False, help="create nodes for directories, and links for containment" ).tag(sync=False) diff --git a/src/ipyforcegraph/sources/widget.py b/src/ipyforcegraph/sources/widget.py index 7f0e2ef..e2f6dfa 100644 --- a/src/ipyforcegraph/sources/widget.py +++ b/src/ipyforcegraph/sources/widget.py @@ -1,16 +1,18 @@ # Copyright (c) 2023 ipyforcegraph contributors. # Distributed under the terms of the Modified BSD License. -from typing import Any, Dict, Optional, Tuple, Type +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type import IPython import ipywidgets as W import pandas as P import traitlets as T -from .. import _types as _t from .dataframe import DataFrameSource +if TYPE_CHECKING: + from .. import _types as _t + TAnyDict = Dict[str, Any] @@ -26,7 +28,7 @@ class WidgetSource(DataFrameSource): T.Instance(T.HasTraits), help="the traitleted from which to discover data" ) - graph_data: _t.Tdict_any = T.Dict( + graph_data: "_t.Tdict_any" = T.Dict( help="an internal collection of observed Data" ).tag(sync=False)