Yog.Layout.Geometry (YogEx v0.99.1)

Copy Markdown View Source

Geometry helpers for converting center-based node positions to bounding rectangles, computing anchor points on rectangle edges, and deriving connector endpoints for edges.

These utilities are designed to support rendering pipelines that need to convert abstract layout coordinates into concrete geometric primitives for drawing.

Summary

Functions

Returns a point on the edge of a bounding rectangle.

Returns connector endpoints for a list of {from_id, to_id} edge pairs.

Converts a center-based position map to a map of bounding rectangles.

Functions

anchor(arg, direction)

@spec anchor({float(), float(), float(), float()}, atom()) :: {float(), float()}

Returns a point on the edge of a bounding rectangle.

The rectangle is given as {x, y, w, h} where x and y are the top-left corner.

Directions

  • :top — top-center {x + w/2, y}
  • :bottom — bottom-center {x + w/2, y + h}
  • :left — left-center {x, y + h/2}
  • :right — right-center {x + w, y + h/2}
  • :top_left{x, y}
  • :top_right{x + w, y}
  • :bottom_left{x, y + h}
  • :bottom_right{x + w, y + h}
  • :center{x + w/2, y + h/2}

Examples

iex> Yog.Layout.Geometry.anchor({10.0, 20.0, 4.0, 2.0}, :right)
{14.0, 21.0}

iex> Yog.Layout.Geometry.anchor({10.0, 20.0, 4.0, 2.0}, :center)
{12.0, 21.0}

iex> Yog.Layout.Geometry.anchor({10.0, 20.0, 4.0, 2.0}, :top)
{12.0, 20.0}

iex> Yog.Layout.Geometry.anchor({10.0, 20.0, 4.0, 2.0}, :bottom)
{12.0, 22.0}

iex> Yog.Layout.Geometry.anchor({10.0, 20.0, 4.0, 2.0}, :left)
{10.0, 21.0}

edge_endpoints(positions, edges, opts \\ [])

@spec edge_endpoints(
  %{required(any()) => {float(), float()}},
  [{any(), any()}],
  keyword()
) :: [{{float(), float()}, {float(), float()}}]

Returns connector endpoints for a list of {from_id, to_id} edge pairs.

Without :node_size, endpoints are the node centers. With :node_size, endpoints are clipped to the closest cardinal side midpoint of each node's bounding rectangle.

Cardinal side selection

Given dx = to_cx - from_cx and dy = to_cy - from_cy:

  • If dx == 0 and dy == 0: returns centers (overlapping nodes degrade gracefully).
  • If abs(dx) >= abs(dy): horizontal dominance. From-node uses :right if dx > 0, else :left. To-node uses :left if dx > 0, else :right.
  • Else: vertical dominance. From-node uses :bottom if dy > 0, else :top. To-node uses :top if dy > 0, else :bottom.

Options

  • :node_size — Either a {w, h} tuple or an arity-2 function fn node_id, {cx, cy} -> {w, h} end. When provided, endpoints are clipped to rect edges; otherwise raw centers are returned.

Examples

iex> Yog.Layout.Geometry.edge_endpoints(
...>   %{a: {0.0, 0.0}, b: {10.0, 0.0}},
...>   [{:a, :b}],
...>   node_size: {2.0, 2.0}
...> )
[{{1.0, 0.0}, {9.0, 0.0}}]

rects(positions, opts \\ [])

@spec rects(
  %{required(any()) => {float(), float()}},
  keyword()
) :: %{required(any()) => {float(), float(), float(), float()}}

Converts a center-based position map to a map of bounding rectangles.

Each entry node_id => {cx, cy} becomes node_id => {left_x, top_y, width, height}, where left_x = cx - w/2 and top_y = cy - h/2.

Options

  • :size - Either a {w, h} tuple applied uniformly to all nodes, or an arity-2 function fn node_id, {cx, cy} -> {w, h} end for per-node sizes. Defaults to {1.0, 1.0}.

Examples

iex> Yog.Layout.Geometry.rects(%{a: {10.0, 20.0}}, size: {4.0, 2.0})
%{a: {8.0, 19.0, 4.0, 2.0}}