Utilities
The pydreamplet.utils module contains small helpers used by colors, generators,
scales, charts, and label layout.
Utilities are imported from pydreamplet.utils.
from pydreamplet.utils import calculate_ticks, place_labels_1d
Visual Example
This example uses place_labels_1d() to resolve overlapping labels while keeping
each label close to its original anchor.
import pydreamplet as dp
from pydreamplet.utils import place_labels_1d
anchors = [42, 55, 68, 128, 136, 206]
sizes = [36, 36, 36, 44, 44, 38]
placements = place_labels_1d(anchors, sizes, gap=4, bounds=(24, 276))
svg = dp.SVG(300, 150)
svg.append(dp.Line(24, 114, 276, 114, stroke="currentColor", opacity=0.3))
for anchor, placement in zip(anchors, placements):
svg.append(
dp.Line(anchor, 114, placement.position, 78, stroke="currentColor", opacity=0.35),
dp.Circle(cx=anchor, cy=114, r=3, fill="#f83898"),
dp.Rect(
x=placement.start,
y=60,
width=placement.size,
height=24,
rx=4,
fill="#14b8a6",
opacity=0.24,
stroke="#14b8a6",
stroke_width=1.5,
),
dp.Text(
str(int(anchor)),
x=placement.position,
y=76,
font_size=11,
text_anchor="middle",
fill="currentColor",
),
)
svg.append(dp.Text("anchor positions", x=24, y=134, font_size=12, fill="currentColor"))
svg.append(dp.Text("resolved label positions", x=58, y=45, font_size=12, fill="currentColor"))
Numeric Helpers
math_round(x: Real) -> int
Rounds with half-up behavior by returning int(x + 0.5).
from pydreamplet.utils import math_round
assert math_round(3.4) == 3
assert math_round(3.6) == 4
constrain(value: Real, min_val: Real, max_val: Real) -> Real
Clamps value into the inclusive range [min_val, max_val].
from pydreamplet.utils import constrain
assert constrain(10, 0, 5) == 5
assert constrain(-3, 0, 5) == 0
radians(degrees: Real) -> Real
degrees(radians: Real) -> Real
Converts between degrees and radians.
from pydreamplet.utils import degrees, radians
assert radians(180) == 3.141592653589793
assert degrees(3.141592653589793) == 180.0
Ticks
calculate_ticks(
min_val: Real,
max_val: Real,
num_ticks: int = 5,
below_max: bool = True,
) -> list[Real]
Returns rounded tick values using 1, 2, 5, or 10 times a power-of-ten step.
min_val must be less than max_val. When below_max is True, ticks above
the maximum are removed.
from pydreamplet.utils import calculate_ticks
assert calculate_ticks(0, 42986, 5) == [0, 10000, 20000, 30000, 40000]
assert calculate_ticks(0, 1, 5) == [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
assert calculate_ticks(0, 42986, 3, below_max=False) == [0, 20000, 40000, 60000]
Pie Angles
pie_angles(
values: Sequence[Real],
start_angle: Real = 0,
end_angle: Real | None = None,
) -> list[tuple[float, float]]
Splits an angular span proportionally by values. If end_angle is omitted,
the span is a full turn from start_angle to start_angle + 360.
from pydreamplet.utils import pie_angles
assert pie_angles([1, 2, 3]) == [(0, 60), (60, 180), (180, 360)]
assert pie_angles([1, 2, 3], start_angle=90) == [(90, 150), (150, 270), (270, 450)]
assert pie_angles([]) == []
A list whose sum is zero raises ZeroDivisionError.
Sampling
sample_uniform(
input_list: list[Any],
n: int,
precedence: Literal["first", "last"] | None = None,
) -> tuple[int, ...]
Returns evenly spaced indices. By default, the first and last indices are
included, every adjacent index gap is equal, and n is the maximum number of
indices returned. The result can contain fewer than n indices when an equal
integer gap is not possible with n indices. If n is greater than the input
length, every index is returned. With n=1, the default mode returns the first
index.
precedence="first" and "last" retain the anchored sampling behavior.
from pydreamplet.utils import sample_uniform
items = list(range(10))
assert sample_uniform(items, n=4) == (0, 3, 6, 9)
assert sample_uniform(items, n=20) == tuple(range(10))
assert sample_uniform(items, n=4, precedence="first") == (0, 3, 6, 9)
assert sample_uniform(items, n=3, precedence="last") == (1, 5, 9)
Invalid precedence values raise ValueError.
Label Layout
force_distance(values: Sequence[Real], distance: Real) -> list[Real]
Adjusts unsorted numeric positions so adjacent sorted positions are at least
distance apart, then returns results in the original input order.
from pydreamplet.utils import force_distance
positions = force_distance([2, 6, 7, 8, 10, 16, 18], distance=2)
assert positions == [2, 5, 7, 9, 11, 16, 18]
resolve_collisions_1d(
anchors: Sequence[Real],
sizes: Sequence[Real],
*,
gap: Real = 0,
bounds: tuple[Real, Real] | None = None,
) -> list[float]
Resolves centered 1D items so their extents do not overlap. anchors and
sizes must have the same length. gap and every size must be non-negative.
When bounds are provided, each item's full extent is kept inside the range.
If the last item exceeds the upper bound, the backward pass moves only its
adjacent collision chain. Earlier, non-colliding items are not shifted as a
group.
from pydreamplet.utils import resolve_collisions_1d
assert resolve_collisions_1d([0, 1, 10], [4, 4, 4], gap=1) == [0, 5, 10]
assert resolve_collisions_1d([5, 0, 1], [4, 4, 4], gap=1) == [10, 0, 5]
For bounded layouts, labels outside the affected collision chain retain their positions:
positions = resolve_collisions_1d(
[139.82, 92.97, 291.10, 331.26, 340.63, 221.48],
[19.11] * 6,
gap=4,
bounds=(18, 350),
)
assert [round(position, 3) for position in positions] == [
139.82,
92.97,
291.1,
317.335,
340.445,
221.48,
]
place_labels_1d(
anchors: Sequence[Real],
sizes: Sequence[Real],
*,
gap: Real = 0,
bounds: tuple[Real, Real] | None = None,
) -> list[LabelPlacement]
Wraps resolve_collisions_1d() and returns LabelPlacement records.
from pydreamplet.utils import place_labels_1d
placements = place_labels_1d([0, 1], [4, 4], gap=1)
assert [placement.position for placement in placements] == [0, 5]
assert placements[0].start == -2
assert placements[0].end == 2
LabelPlacement is a frozen dataclass with anchor, position, and size
fields plus computed start and end properties.
Bounding Boxes
bboxes_overlap(a: BoundingBox, b: BoundingBox, padding: Real = 0) -> bool
Returns True when two BoundingBox objects overlap. Touching edges are not
considered overlapping. Positive padding expands both boxes before testing.
from pydreamplet import BoundingBox
from pydreamplet.utils import bboxes_overlap
left = BoundingBox(0, 0, 10, 10)
right = BoundingBox(10, 0, 5, 5)
assert bboxes_overlap(left, right) is False
assert bboxes_overlap(left, right, padding=0.1) is True
Negative padding raises ValueError.