-
Notifications
You must be signed in to change notification settings - Fork 3
expose layout options #176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -35,8 +35,8 @@ You can provide these either as a percentage of the available space (eg. ``"80%" | |||||
|
||||||
Further you can change the layout of the graph using the ``layout`` parameter, which can be set to one of the following values: | ||||||
|
||||||
* ``Layout.FORCE_DIRECTED`` - Nodes are arranged using the Force-Directed algorithm, which simulates physical forces | ||||||
* ``Layout.HIERARCHICAL`` - Arranges nodes by the directionality of their relationships, creating a tree-like structure | ||||||
* ``Layout.FORCE_DIRECTED`` - Nodes are arranged using the Force-Directed algorithm, which simulates physical forces. To customize the layout use `ForceDirectedOptions` via `layout_options`.` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* ``Layout.HIERARCHICAL`` - Arranges nodes by the directionality of their relationships, creating a tree-like structure. To customize the layout use `HierarchicalLayoutOptions` via `layout_options`.` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* ``Layout.COORDINATE`` - Arranges nodes based on coordinates defined in `x` and `y` properties on each node. | ||||||
|
||||||
Another thing of note is the ``max_allowed_nodes`` parameter, which controls the maximum number of nodes that is allowed | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,25 @@ | ||
from .node import Node | ||
from .options import CaptionAlignment, Layout, Renderer | ||
from .options import ( | ||
CaptionAlignment, | ||
Direction, | ||
ForceDirectedLayoutOptions, | ||
HierarchicalLayoutOptions, | ||
Layout, | ||
Packing, | ||
Renderer, | ||
) | ||
from .relationship import Relationship | ||
from .visualization_graph import VisualizationGraph | ||
|
||
__all__ = ["VisualizationGraph", "Node", "Relationship", "CaptionAlignment", "Layout", "Renderer"] | ||
__all__ = [ | ||
"VisualizationGraph", | ||
"Node", | ||
"Relationship", | ||
"CaptionAlignment", | ||
"Layout", | ||
"Renderer", | ||
"HierarchicalLayoutOptions", | ||
"ForceDirectedLayoutOptions", | ||
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general I'm against introducing smaller objects like this an API. I find it to be a bit more overwhelming for the user to have to learn about and import these classes, instead of just letting them provide a dict. Internally it's great to have though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good feedback. how about we also allow them to use dict if they dont like the typed version? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, we could allow both types. But that also makes the docs look complicated 😅 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or we just allow dict only for user-input and have the internal field still use the type? Kind of pre-validation step |
||
"Direction", | ||
"Packing", | ||
] |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,10 +2,10 @@ | |||||
|
||||||
import warnings | ||||||
from enum import Enum | ||||||
from typing import Any, Optional | ||||||
from typing import Any, Optional, Union | ||||||
|
||||||
import enum_tools.documentation | ||||||
from pydantic import BaseModel, Field | ||||||
from pydantic import BaseModel, Field, model_validator | ||||||
|
||||||
|
||||||
@enum_tools.documentation.document_enum | ||||||
|
@@ -33,6 +33,49 @@ class Layout(str, Enum): | |||||
GRID = "grid" | ||||||
|
||||||
|
||||||
@enum_tools.documentation.document_enum | ||||||
class Direction(str, Enum): | ||||||
""" | ||||||
The direction in which the layout should be oriented | ||||||
""" | ||||||
|
||||||
LEFT = "left" | ||||||
RIGHT = "right" | ||||||
UP = "up" | ||||||
DOWN = "down" | ||||||
|
||||||
|
||||||
@enum_tools.documentation.document_enum | ||||||
class Packing(str, Enum): | ||||||
""" | ||||||
The packing method to be used | ||||||
""" | ||||||
|
||||||
BIN = "bin" | ||||||
STACK = "stack" | ||||||
|
||||||
|
||||||
class HierarchicalLayoutOptions(BaseModel): | ||||||
""" | ||||||
The options for the hierarchical layout. | ||||||
""" | ||||||
|
||||||
direction: Optional[Direction] = None | ||||||
packaging: Optional[Packing] = None | ||||||
|
||||||
|
||||||
class ForceDirectedLayoutOptions(BaseModel): | ||||||
""" | ||||||
The options for the force-directed layout. | ||||||
""" | ||||||
|
||||||
gravity: Optional[float] = None | ||||||
simulationStopVelocity: Optional[float] = None | ||||||
|
||||||
|
||||||
LayoutOptions = Union[HierarchicalLayoutOptions, ForceDirectedLayoutOptions] | ||||||
|
||||||
|
||||||
@enum_tools.documentation.document_enum | ||||||
class Renderer(str, Enum): | ||||||
""" | ||||||
|
@@ -72,6 +115,9 @@ class RenderOptions(BaseModel, extra="allow"): | |||||
""" | ||||||
|
||||||
layout: Optional[Layout] = None | ||||||
layout_options: Optional[Union[HierarchicalLayoutOptions, ForceDirectedLayoutOptions]] = Field( | ||||||
None, serialization_alias="layoutOptions" | ||||||
) | ||||||
renderer: Optional[Renderer] = None | ||||||
|
||||||
pan_X: Optional[float] = Field(None, serialization_alias="panX") | ||||||
|
@@ -84,5 +130,16 @@ class RenderOptions(BaseModel, extra="allow"): | |||||
min_zoom: Optional[float] = Field(None, serialization_alias="minZoom", description="The minimum zoom level allowed") | ||||||
allow_dynamic_min_zoom: Optional[bool] = Field(None, serialization_alias="allowDynamicMinZoom") | ||||||
|
||||||
@model_validator(mode="after") | ||||||
def check_layout_options_match(self) -> RenderOptions: | ||||||
if self.layout_options is None: | ||||||
return self | ||||||
|
||||||
if self.layout == Layout.HIERARCHICAL and not isinstance(self.layout_options, HierarchicalLayoutOptions): | ||||||
raise ValueError("layout_options must be of type HierarchicalLayoutOptions for hierarchical layout") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
if self.layout == Layout.FORCE_DIRECTED and not isinstance(self.layout_options, ForceDirectedLayoutOptions): | ||||||
raise ValueError("layout_options must be of type ForceDirectedLayoutOptions for force-directed layout") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
return self | ||||||
|
||||||
def to_dict(self) -> dict[str, Any]: | ||||||
return self.model_dump(exclude_none=True, by_alias=True) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,24 @@ | ||
from neo4j_viz.options import Layout, RenderOptions | ||
import pytest | ||
|
||
from neo4j_viz.options import Direction, HierarchicalLayoutOptions, Layout, RenderOptions | ||
|
||
|
||
def test_render_options() -> None: | ||
options = RenderOptions(layout=Layout.FORCE_DIRECTED) | ||
options = RenderOptions(layout=Layout.HIERARCHICAL) | ||
|
||
assert options.to_dict() == {"layout": "hierarchical"} | ||
|
||
|
||
def test_render_options_with_layout_options() -> None: | ||
options = RenderOptions( | ||
layout=Layout.HIERARCHICAL, layout_options=HierarchicalLayoutOptions(direction=Direction.LEFT) | ||
) | ||
|
||
assert options.to_dict() == {"layout": "hierarchical", "layoutOptions": {"direction": "left"}} | ||
|
||
|
||
assert options.to_dict() == {"layout": "forcedirected"} | ||
def test_layout_options_match() -> None: | ||
with pytest.raises( | ||
ValueError, match="layout_options must be of type ForceDirectedLayoutOptions for force-directed layout" | ||
): | ||
RenderOptions(layout=Layout.FORCE_DIRECTED, layout_options=HierarchicalLayoutOptions(direction=Direction.LEFT)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit unrelated to this PR)