Skip to main content

KDEdge

KDEEB-style edge bundling for Python.

This package bundles graph edges into smooth polylines for visualization. It currently supports one bundling method:

  • kdeeb: kernel-density edge bundling
Edge bundling of US migration

Install

Python 3.10 or higher is required.

Install from PyPI:

pip install kdedge

The core package depends only on NumPy. SciPy and Numba are optional and are automatically used when installed. To install both optional dependencies, run:

pip install kdedge[perf]

Documentation

Sphinx documentation sources live in docs/.

Documentation is hosted on Read the Docs.

How it works

The input is a set of 2D node positions and an edge list.

Each edge starts as a straight segment between its source and target node. The edges are then sampled into polylines with control points along the edge.

Each sample point contributes to a density field, a scalar field that counts the number of edges passing through it. The density field is smoothed with a kernel, for example a Gaussian blur.

The sample points are then moved toward the gradient of the density field. This means edges are pulled toward high-density regions where many edges overlap. After moving the sample points, the polylines are smoothed to reduce sharp corners.

This process is repeated for a number of iterations, the edges are resampled, the density field is recomputed, and the sample points are moved again. Over time, edges that share similar routes are pulled together into bundles.

In kdeeb mode, all edges share one density field, so similar routes collapse into common bundles.

Example

import numpy as np

from kdedge import bundle

nodes = np.array(
    [
        [-1.0, 0.0],
        [0.0, 1.0],
        [1.0, 0.0],
        [0.0, -1.0],
    ],
    dtype=np.float64,
)

edges = np.array(
    [
        [0, 2],
        [1, 3],
        [0, 1],
        [2, 3],
    ],
    dtype=np.int64,
)

polylines = bundle(
    nodes=nodes,
    edges=edges,
    iterations=10,
    mode="kdeeb",
)

print(len(polylines))
print(polylines[0].shape)

The result is a list of polylines as NumPy arrays. Each polyline keeps the original edge endpoints and adds interior control points as needed.

Kernel Density Estimation Implementation

This package follows the KDEEB idea from Graph Bundling by Kernel Density Estimation (2012) by Hurter, C., Ersoy, O. and Telea, A. The implementation is inspired by the authors' C# demo.

However, the numerical approximations and the default parameters in this package are different, so values are not directly interchangeable and results are not comparable. This package defaults to a more Python-friendly implementation and default parameters that are efficient in NumPy/SciPy, especially for very dense graphs. A wrapper function kdeeb() is provided for a preset that is closer to the original KDEEB algorithm.

Key differences:

  • Density field: by default this package builds a point-count grid and smooths the entire grid with gaussian_filter (density_mode="gaussian_filter"). The KDEEB paper describes kernel-density estimation in general, and the paper authors' C# demo uses explicit point splatting with an 11x11 radial kernel on a 300x300 accumulation grid.
  • Python-friendly: the default gaussian_filter path is chosen because it is efficient to compute with SciPy for dense for dense graphs. Use density_mode="point_splat" for a kernel closer to the KDEEB authors' splatting idea. For sparse graphs, explicit point splatting may be more efficient. Custom kernels can be defined by passing a splatting function or by operating on the density grid (numpy.ndarray).
  • Gradient estimation: this package computes np.gradient(...) on the full density image and bilinearly interpolates the gradient at each sample point. The authors' demo estimates a local gradient in a 15x15 window around every sample point.
  • Coordinate system and grid: this package internally operates on a square pixel grid with grid_size=512 by default. The authors' demo instead operates on normalized coordinates.
  • Resampling: this package resamples each polyline by uniform arc length with spacing sampling * grid_size (default 0.01 * 512 = 5.12 pixels). The authors' demo uses split/remove thresholds in normalized coordinates (splitDistance=0.005, removeDistance=0.0025).
  • Smoothing: after moving the sample points, the polylines are smoothed to achieve smooth curves. This package defaults to Laplacian smoothing with factor smooth=0.35 and smooth_iterations=1. The authors' CPU demo instead applies much stronger smoothing, with 50 iterations after every bundling step, with a smoothing factor that is roughly comparable to smooth=0.333.
  • Scheduling: the KDEEB paper uses a shared schedule h_i = l^i h_max for both bandwidth and advection. This effectively limits movement of the sample points to the bandwidth of the kernel function. The authors'C# demo instead uses fixed constants such as KernelSize = 11 and attractionFactor = 1.0. The bundle() function in this package uses the parameters sigma (for Gaussian kernel) and attract-strength as separate controls unless they are explicitly coupled by using the same schedule parameter.

KDEEB-style wrapper kdeeb()

This function provides a preset that is closer to the original KDEEB algorithm, with the following defaults:

  • a shared schedule h_i = l^i h_max for both density bandwidth and advection
  • default bandwidth_decay = 0.7
  • automatic h_max estimation from the initial straight edges
  • density_mode="point_splat" with an Epanechnikov splat kernel
  • moderate smoothing with smooth=1/3 and smooth_iterations=8 (Note: the KDEEB C# demo instead applies 50 smoothing passes)
  • fixed sampling near 1% of the graph bounding box via sampling=0.01

Example:

from kdedge import kdeeb

polylines = kdeeb(
    nodes=nodes,
    edges=edges,
    iterations=10,
)

Results

Example output images are in results.

Development

The core API is in kdedge/algo.py, the kernel functions are in kdedge/kernels.py.

Tests can be run with pytest for a specific Python version or for multiple Python versions with tox:

pip install pytest
pytest

pip install tox
tox
# Optionally install more Python versions, e.g. with: pyenv install 3.11.8

Static type checking can be tested with mypy or pyright:

pip install pyright
pyright

pip install mypy
mypy kdedge

Runtime type checking can be tested with pytest and the beartype or typeguard plugin:

pip install pytest-beartype
pytest --beartype-packages="kdedge"

pip install typeguard
pytest --typeguard-packages="kdedge"

Code formatting can be checked with ruff:

pip install ruff
ruff check kdedge tests

# Autofix simple issues:
ruff check --fix kdedge tests

# Formatting a file:
ruff format kdedge/algo.py

Numba and SciPy implementations

Some functions are implemented multiple times with different backends (pure NumPy, SciPy or Numba) for performance reasons. See kdedge/impl/impl_numpy.py, kdedge/impl/impl_scipy.py and kdedge/impl/impl_numba.py for the implementations.

The fastest implementation for each function is currently hardcoded (based on benchmarking results). A pure NumPy implementation is used if SciPy or Numba are not available.

Benchmark tests to compare some of the Numba and NumPy functions are in benchmarks/.

Run the benchmarks with:

python -m benchmarks

References

Metadata

Release files for kdedge 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kdedge 0.0.1
File Size Uploaded
kdedge-0.0.1.tar.gz 32.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for kdedge 0.0.1
File Interpreter ABI Platform
kdedge-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 55.8 kB

Release files / kdedge-0.0.1.tar.gz

Download URL kdedge-0.0.1.tar.gz
Size 32.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e7890aaef18a972a293ff5512b1c162c81f33e24722e765da34a263c9b12e280
BLAKE2b-256 checksum
How to use checksums
c6a27647e8c141ceb04082efba118f8da5081bb95a7949f5e5d0e55caebc72f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 1, 2026.

Transparency log

Release files / kdedge-0.0.1-py3-none-any.whl

Download URL kdedge-0.0.1-py3-none-any.whl
Size 23.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
678be2ca1f8dd51a8f8210146b47b235d3a831f7156db416fb67508d38d2bfe8
BLAKE2b-256 checksum
How to use checksums
babfadbfdffe6b02301eb4e7d6405c57df4cb3410300deaa1ab46043d21d94ca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page