bezierkit
A small mathematical toolkit for constructing and analyzing Bézier curves.
bezierkit is renderer-independent: it provides curve construction,
evaluation, subdivision, and sampling while leaving plotting to consumers such
as EconViz, Matplotlib, Plotly, SVG, or Typst adapters.
Installation
Add the library to your project with uv:
uv add bezierkit
Add the optional command-line interface:
uv add "bezierkit[cli]"
For development, clone the repo and sync all extras:
uv sync --all-extras --dev
Run anything inside the environment with uv run, e.g. uv run pytest or
uv run bezierkit --help. A plain pip install bezierkit /
pip install "bezierkit[cli]" also works if you are not using uv.
Python API
from bezierkit import BezierCurve, CubicBezierSegment, PiecewiseBezier, Point
from bezierkit.construction import PlanarSlopes
from bezierkit.sampling import UniformSampler
curve = BezierCurve.cubic(
Point(0, 0),
Point(1, 2),
Point(3, 2),
Point(4, 0),
)
segment = CubicBezierSegment(
Point(0, 0),
Point(1, 2),
Point(3, 2),
Point(4, 0),
)
segment.control_points # (P0, P1, P2, P3)
path = PiecewiseBezier([
CubicBezierSegment.from_line(Point(0, 0), Point(2, 0)),
CubicBezierSegment.from_line(Point(2, 0), Point(2, 4)),
])
path.at(0.75) # Point(2.0, 2.0)
curve.at(0.5) # Point(2.0, 1.5)
curve.derivative().at(0.5) # first derivative
left, right = curve.split(0.3)
sample = UniformSampler(200).sample(curve)
demand = PlanarSlopes(
start=Point(0, 5),
end=Point(5, 0),
start_slope=-2,
end_slope=-0.3,
).build()
The engine supports arbitrary degree and dimension. Point, Vector,
PointSet, control polygons, and sampling results are immutable value objects.
CubicBezierSegment is the primary renderer-facing value object. It exposes
p0, p1, p2, p3, tight axis-aligned bounds, and cubic-preserving split
and reversal operations. Existing BezierCurve.cubic(...) code remains valid;
use CubicBezierSegment.from_curve(curve) to migrate without changing any
control point, or segment.as_curve() when a degree-generic API is required.
PiecewiseBezier divides [0, 1] uniformly across the segments of a single
subpath. Closure is metadata and never inserts a hidden closing segment.
PiecewiseBezier.compound(...) preserves independent subpaths for fills and
holes; evaluate and split each subpath independently because a compound path
has no single continuous parameterization. Reversal preserves each subpath;
splitting is defined only for a single open subpath.
Linear and quadratic inputs can be elevated without changing their geometry:
from bezierkit.bezier import to_cubic
cubic = to_cubic(BezierCurve.quadratic(
Point(0, 0), Point(3, 6), Point(9, 0)
))
Batch evaluation uses a vectorized de Casteljau implementation by default.
BernsteinEvaluator is also available from bezierkit.bezier.evaluation for
explicit low-degree throughput tradeoffs; see benchmarks/ for the
reproducible 400, 10,000, and 100,000-value benchmark.
Interpolation, fitting, and level sets
from bezierkit.fitting import fit_graph
from bezierkit.implicit import trace_implicit
from bezierkit.interpolation import graph_hermite
segment = graph_hermite(
x0=1, x1=1.5,
y0=4, y1=8 / 3,
m0=-8, m1=-32 / 9,
)
path = fit_graph(
lambda x: 4 / x**2,
lambda x: -8 / x**3,
x0=0.8, x1=3,
tolerance=1e-3,
)
contours = trace_implicit(
lambda x, y: x**2 * y,
levels=[1, 2, 4],
viewport=(0.5, 4, 0, 6),
resolution=(121, 121),
tolerance=0.01,
gradient=lambda x, y: (2 * x * y, x**2),
)
Adaptive fitting reports a measured Euclidean error on every segment and
raises ToleranceNotMet if configured limits prevent the requested tolerance.
Implicit tracing preserves disconnected and closed components as independent
paths and uses parametric gradient tangents, including at vertical tangencies.
The derivations, error bound, continuity conditions, and sampling limitations
are documented in docs/math/interpolation.md.
Exporters and adapters
from bezierkit.export.json import dumps, loads
from bezierkit.export.svg import to_svg_path_data
from bezierkit.export.tikz import to_tikz
json_document = dumps(path, metadata={"name": "indifference-curve"})
same_path = loads(json_document).path
svg_path_data = to_svg_path_data(path, precision=5)
tikz = to_tikz(path, precision=5, options="thick")
TikZ uses native .. controls ... and ... .. commands and SVG uses native
C commands; neither exporter flattens cubic geometry or chooses a theme.
The versioned JSON schema preserves all controls, subpaths, closure, dimension,
and caller metadata.
Matplotlib interoperability is optional:
uv add "bezierkit[matplotlib]"
from bezierkit.adapters.matplotlib import from_path, to_path
geometry = from_path(matplotlib_path, transform=affine_transform)
round_trip = to_path(geometry)
Affine transforms preserve controls exactly. Non-affine transforms require
the explicit approximate_path(..., tolerance=...) API. Format guarantees and
round-trip tolerances are documented in docs/exporters.md.
Command-line interface
Run bezierkit directly if it's installed in your active environment, or
prefix every command with uv run (e.g. uv run bezierkit --help) when
working inside a uv project without activating the venv.
Evaluate a curve or derivative:
bezierkit evaluate \
--points "0,0" --points "1,2" --points "3,2" --points "4,0" \
--t 0:1:0.25 --order 1 --json
Sample as CSV:
bezierkit sample \
--points "0,0" --points "1,2" --points "3,2" --points "4,0" \
--count 50 --format csv > curve.csv
Construct a curve from planar slopes and pipe its control points directly into another command:
bezierkit construct slopes \
--start "0,5" --end "5,0" \
--start-slope -2 --end-slope -0.3 \
| bezierkit sample --count 20 --format json
Run bezierkit --help for the complete command tree. Expected domain and input
errors are printed without a traceback; add the global --debug option before
the subcommand when diagnosing unexpected failures.
Scope
Version 1.0 is the first stable release: the public API listed in
bezierkit.__all__ and the subpackages documented above follow semantic
versioning from here on, and the JSON path schema stays at version 1. See
CHANGELOG.md for the release history. Rendering style and
diagram semantics remain intentionally outside this package. Intersections, B-splines,
and NURBS are future work.
Metadata
Release files for bezierkit 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bezierkit-1.0.0.tar.gz | 169.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bezierkit-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 221.5 kB
Release files / bezierkit-1.0.0.tar.gz
| Download URL | bezierkit-1.0.0.tar.gz |
|---|---|
| Size | 169.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
309fa0984a6cee703342c8e133a2983725e3bd6c12aca725d48decfe877a4060
|
|
BLAKE2b-256 checksum How to use checksums |
958495fb05e260c1c214c0d69cd5f80c235d87075bd30b8306c94cf928c4d52b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / bezierkit-1.0.0-py3-none-any.whl
| Download URL | bezierkit-1.0.0-py3-none-any.whl |
|---|---|
| Size | 52.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
42542e262c3829ce422fb3968b5d91049a02d12257611d9ef51ca5dc107eb553
|
|
BLAKE2b-256 checksum How to use checksums |
b0bf899871c291ad2ead783008f085fa995096bac09c768a85a4c2b371ec11e6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|