kdedge 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
kdedge-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cvzi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
kdedge-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,256 @@
1
+ Metadata-Version: 2.4
2
+ Name: kdedge
3
+ Version: 0.0.1
4
+ Summary: Edge bundling algorithms for graph visualization using kernel density estimation (KDE).
5
+ Author-email: cuzi <cuzi@openmail.cc>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/cvzi/kdedge
8
+ Project-URL: documentation, https://kdedge.readthedocs.io/
9
+ Project-URL: repository, https://github.com/cvzi/kdedge
10
+ Project-URL: downloads, https://pypi.org/project/kdedge/
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: Implementation :: CPython
21
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
22
+ Classifier: Typing :: Typed
23
+ Classifier: Topic :: Scientific/Engineering :: Visualization
24
+ Classifier: Intended Audience :: Science/Research
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: numpy~=2.0
29
+ Provides-Extra: perf
30
+ Requires-Dist: scipy~=1.15; extra == "perf"
31
+ Requires-Dist: numba~=0.65; extra == "perf"
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest~=9.0; extra == "test"
34
+ Requires-Dist: tox~=4.0; extra == "test"
35
+ Requires-Dist: hypothesis~=6.0; extra == "test"
36
+ Provides-Extra: typetests
37
+ Requires-Dist: pyright>=1.1; extra == "typetests"
38
+ Requires-Dist: pytest-beartype>=0.2.0; extra == "typetests"
39
+ Requires-Dist: mypy>=1.16; extra == "typetests"
40
+ Requires-Dist: scipy_stubs>=1.15.0; extra == "typetests"
41
+ Requires-Dist: typeguard>=4.5; extra == "typetests"
42
+ Provides-Extra: coverage
43
+ Requires-Dist: coverage>=7.5.1; extra == "coverage"
44
+ Requires-Dist: codacy_coverage>=1.3.11; extra == "coverage"
45
+ Provides-Extra: ruff
46
+ Requires-Dist: ruff>=0.15; extra == "ruff"
47
+ Provides-Extra: docs
48
+ Requires-Dist: sphinx>=9; extra == "docs"
49
+ Dynamic: license-file
50
+
51
+ # KDEdge
52
+
53
+ KDEEB-style edge bundling for Python.
54
+
55
+ This package bundles graph edges into smooth polylines for visualization.
56
+ It currently supports one bundling method:
57
+
58
+ - `kdeeb`: kernel-density edge bundling
59
+
60
+ <kbd>
61
+ <img width="1200" height="634" alt="Edge bundling of US migration" src="https://github.com/user-attachments/assets/a346db98-3367-4b52-aa69-17ed3f7ac53f" />
62
+ </kbd>
63
+
64
+
65
+ ## Install
66
+
67
+ Python 3.10 or higher is required.
68
+
69
+ Install from [PyPI](https://pypi.org/project/kdedge/):
70
+
71
+ ```bash
72
+ pip install kdedge
73
+ ```
74
+
75
+ The core package depends only on [NumPy](https://pypi.org/project/numpy/). [SciPy](https://pypi.org/project/scipy/) and [Numba](https://numba.pydata.org/) are optional and are automatically used when installed.
76
+ To install both optional dependencies, run:
77
+
78
+ ```bash
79
+ pip install kdedge[perf]
80
+ ```
81
+
82
+
83
+ ## Documentation
84
+
85
+ Sphinx documentation sources live in [`docs/`](docs/).
86
+
87
+ Documentation is hosted on [Read the Docs](https://kdedge.readthedocs.io/).
88
+
89
+
90
+ ## How it works
91
+
92
+ The input is a set of 2D node positions and an edge list.
93
+
94
+ 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.
95
+
96
+ 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.
97
+
98
+ 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.
99
+
100
+ 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.
101
+
102
+ In `kdeeb` mode, all edges share one density field, so similar routes collapse into common bundles.
103
+
104
+
105
+ ## Example
106
+
107
+ ```python
108
+ import numpy as np
109
+
110
+ from kdedge import bundle
111
+
112
+ nodes = np.array(
113
+ [
114
+ [-1.0, 0.0],
115
+ [0.0, 1.0],
116
+ [1.0, 0.0],
117
+ [0.0, -1.0],
118
+ ],
119
+ dtype=np.float64,
120
+ )
121
+
122
+ edges = np.array(
123
+ [
124
+ [0, 2],
125
+ [1, 3],
126
+ [0, 1],
127
+ [2, 3],
128
+ ],
129
+ dtype=np.int64,
130
+ )
131
+
132
+ polylines = bundle(
133
+ nodes=nodes,
134
+ edges=edges,
135
+ iterations=10,
136
+ mode="kdeeb",
137
+ )
138
+
139
+ print(len(polylines))
140
+ print(polylines[0].shape)
141
+ ```
142
+
143
+ The result is a list of polylines as NumPy arrays.
144
+ Each polyline keeps the original edge endpoints and adds interior control points as needed.
145
+
146
+
147
+ ## Kernel Density Estimation Implementation
148
+
149
+ 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](https://webspace.science.uu.nl/~telea001/uploads/Software/KDEEB).
150
+
151
+ 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.
152
+
153
+ Key differences:
154
+
155
+ - **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.
156
+ - **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`).
157
+ - **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.
158
+ - **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.
159
+ - **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`).
160
+ - **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`.
161
+ - **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.
162
+
163
+
164
+ ## KDEEB-style wrapper ``kdeeb()``
165
+
166
+ This function provides a preset that is closer to the original KDEEB algorithm, with the following defaults:
167
+ - a shared schedule `h_i = l^i h_max` for both density bandwidth and advection
168
+ - default `bandwidth_decay = 0.7`
169
+ - automatic `h_max` estimation from the initial straight edges
170
+ - `density_mode="point_splat"` with an [Epanechnikov](https://www.researchgate.net/figure/Examples-of-kernel-functions-a-Gaussian-b-Epanechnikov-c-Triangular-and-d_fig1_321982186) splat kernel
171
+ - moderate smoothing with `smooth=1/3` and `smooth_iterations=8` (Note: the KDEEB C# demo instead applies `50` smoothing passes)
172
+ - fixed sampling near `1%` of the graph bounding box via `sampling=0.01`
173
+
174
+ Example:
175
+
176
+ ```python
177
+ from kdedge import kdeeb
178
+
179
+ polylines = kdeeb(
180
+ nodes=nodes,
181
+ edges=edges,
182
+ iterations=10,
183
+ )
184
+ ```
185
+
186
+ ## Results
187
+
188
+ Example output images are in [`results`](https://github.com/cvzi/kdedge/tree/results).
189
+
190
+
191
+ ## Development
192
+
193
+ The core API is in kdedge/algo.py, the kernel functions are in kdedge/kernels.py.
194
+
195
+ Tests can be run with [pytest](https://pytest.org/) for a specific Python version or for multiple Python versions with [tox](https://tox.wiki/):
196
+
197
+ ```bash
198
+ pip install pytest
199
+ pytest
200
+
201
+ pip install tox
202
+ tox
203
+ # Optionally install more Python versions, e.g. with: pyenv install 3.11.8
204
+ ```
205
+
206
+ Static type checking can be tested with [mypy](https://www.mypy-lang.org/) or [pyright](https://github.com/microsoft/pyright):
207
+ ```bash
208
+ pip install pyright
209
+ pyright
210
+
211
+ pip install mypy
212
+ mypy kdedge
213
+ ```
214
+
215
+ Runtime type checking can be tested with `pytest` and the [beartype](https://beartype.readthedocs.io/) or [typeguard](https://typeguard.readthedocs.io/) plugin:
216
+ ```bash
217
+ pip install pytest-beartype
218
+ pytest --beartype-packages="kdedge"
219
+
220
+ pip install typeguard
221
+ pytest --typeguard-packages="kdedge"
222
+ ```
223
+
224
+ Code formatting can be checked with [ruff](https://docs.astral.sh/ruff/):
225
+
226
+ ```bash
227
+ pip install ruff
228
+ ruff check kdedge tests
229
+
230
+ # Autofix simple issues:
231
+ ruff check --fix kdedge tests
232
+
233
+ # Formatting a file:
234
+ ruff format kdedge/algo.py
235
+ ```
236
+
237
+ ### Numba and SciPy implementations
238
+
239
+ Some functions are implemented multiple times with different backends (pure NumPy, SciPy or Numba) for performance reasons.
240
+ See kdedge/impl/impl_numpy.py, kdedge/impl/impl_scipy.py and kdedge/impl/impl_numba.py for the implementations.
241
+
242
+ 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.
243
+
244
+ Benchmark tests to compare some of the Numba and NumPy functions are in [benchmarks/](./benchmarks).
245
+
246
+ Run the benchmarks with:
247
+
248
+ ```bash
249
+ python -m benchmarks
250
+ ```
251
+
252
+ ## References
253
+
254
+ - KDEEB website: [https://webspace.science.uu.nl/~telea001/InfoVis/KDEEB](https://webspace.science.uu.nl/~telea001/InfoVis/KDEEB)
255
+ - KDEEB paper: [Hurter, C., Ersoy, O. and Telea, A. (2012), *Graph Bundling by Kernel Density Estimation*, Computer Graphics Forum 31(3).](https://webspace.science.uu.nl/~telea001/uploads/PAPERS/EuroVis12/kdeeb.pdf)
256
+ - KDEEB authors' demo software: [https://webspace.science.uu.nl/~telea001/uploads/Software/KDEEB](https://webspace.science.uu.nl/~telea001/uploads/Software/KDEEB)
kdedge-0.0.1/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # KDEdge
2
+
3
+ KDEEB-style edge bundling for Python.
4
+
5
+ This package bundles graph edges into smooth polylines for visualization.
6
+ It currently supports one bundling method:
7
+
8
+ - `kdeeb`: kernel-density edge bundling
9
+
10
+ <kbd>
11
+ <img width="1200" height="634" alt="Edge bundling of US migration" src="https://github.com/user-attachments/assets/a346db98-3367-4b52-aa69-17ed3f7ac53f" />
12
+ </kbd>
13
+
14
+
15
+ ## Install
16
+
17
+ Python 3.10 or higher is required.
18
+
19
+ Install from [PyPI](https://pypi.org/project/kdedge/):
20
+
21
+ ```bash
22
+ pip install kdedge
23
+ ```
24
+
25
+ The core package depends only on [NumPy](https://pypi.org/project/numpy/). [SciPy](https://pypi.org/project/scipy/) and [Numba](https://numba.pydata.org/) are optional and are automatically used when installed.
26
+ To install both optional dependencies, run:
27
+
28
+ ```bash
29
+ pip install kdedge[perf]
30
+ ```
31
+
32
+
33
+ ## Documentation
34
+
35
+ Sphinx documentation sources live in [`docs/`](docs/).
36
+
37
+ Documentation is hosted on [Read the Docs](https://kdedge.readthedocs.io/).
38
+
39
+
40
+ ## How it works
41
+
42
+ The input is a set of 2D node positions and an edge list.
43
+
44
+ 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.
45
+
46
+ 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.
47
+
48
+ 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.
49
+
50
+ 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.
51
+
52
+ In `kdeeb` mode, all edges share one density field, so similar routes collapse into common bundles.
53
+
54
+
55
+ ## Example
56
+
57
+ ```python
58
+ import numpy as np
59
+
60
+ from kdedge import bundle
61
+
62
+ nodes = np.array(
63
+ [
64
+ [-1.0, 0.0],
65
+ [0.0, 1.0],
66
+ [1.0, 0.0],
67
+ [0.0, -1.0],
68
+ ],
69
+ dtype=np.float64,
70
+ )
71
+
72
+ edges = np.array(
73
+ [
74
+ [0, 2],
75
+ [1, 3],
76
+ [0, 1],
77
+ [2, 3],
78
+ ],
79
+ dtype=np.int64,
80
+ )
81
+
82
+ polylines = bundle(
83
+ nodes=nodes,
84
+ edges=edges,
85
+ iterations=10,
86
+ mode="kdeeb",
87
+ )
88
+
89
+ print(len(polylines))
90
+ print(polylines[0].shape)
91
+ ```
92
+
93
+ The result is a list of polylines as NumPy arrays.
94
+ Each polyline keeps the original edge endpoints and adds interior control points as needed.
95
+
96
+
97
+ ## Kernel Density Estimation Implementation
98
+
99
+ 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](https://webspace.science.uu.nl/~telea001/uploads/Software/KDEEB).
100
+
101
+ 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.
102
+
103
+ Key differences:
104
+
105
+ - **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.
106
+ - **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`).
107
+ - **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.
108
+ - **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.
109
+ - **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`).
110
+ - **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`.
111
+ - **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.
112
+
113
+
114
+ ## KDEEB-style wrapper ``kdeeb()``
115
+
116
+ This function provides a preset that is closer to the original KDEEB algorithm, with the following defaults:
117
+ - a shared schedule `h_i = l^i h_max` for both density bandwidth and advection
118
+ - default `bandwidth_decay = 0.7`
119
+ - automatic `h_max` estimation from the initial straight edges
120
+ - `density_mode="point_splat"` with an [Epanechnikov](https://www.researchgate.net/figure/Examples-of-kernel-functions-a-Gaussian-b-Epanechnikov-c-Triangular-and-d_fig1_321982186) splat kernel
121
+ - moderate smoothing with `smooth=1/3` and `smooth_iterations=8` (Note: the KDEEB C# demo instead applies `50` smoothing passes)
122
+ - fixed sampling near `1%` of the graph bounding box via `sampling=0.01`
123
+
124
+ Example:
125
+
126
+ ```python
127
+ from kdedge import kdeeb
128
+
129
+ polylines = kdeeb(
130
+ nodes=nodes,
131
+ edges=edges,
132
+ iterations=10,
133
+ )
134
+ ```
135
+
136
+ ## Results
137
+
138
+ Example output images are in [`results`](https://github.com/cvzi/kdedge/tree/results).
139
+
140
+
141
+ ## Development
142
+
143
+ The core API is in kdedge/algo.py, the kernel functions are in kdedge/kernels.py.
144
+
145
+ Tests can be run with [pytest](https://pytest.org/) for a specific Python version or for multiple Python versions with [tox](https://tox.wiki/):
146
+
147
+ ```bash
148
+ pip install pytest
149
+ pytest
150
+
151
+ pip install tox
152
+ tox
153
+ # Optionally install more Python versions, e.g. with: pyenv install 3.11.8
154
+ ```
155
+
156
+ Static type checking can be tested with [mypy](https://www.mypy-lang.org/) or [pyright](https://github.com/microsoft/pyright):
157
+ ```bash
158
+ pip install pyright
159
+ pyright
160
+
161
+ pip install mypy
162
+ mypy kdedge
163
+ ```
164
+
165
+ Runtime type checking can be tested with `pytest` and the [beartype](https://beartype.readthedocs.io/) or [typeguard](https://typeguard.readthedocs.io/) plugin:
166
+ ```bash
167
+ pip install pytest-beartype
168
+ pytest --beartype-packages="kdedge"
169
+
170
+ pip install typeguard
171
+ pytest --typeguard-packages="kdedge"
172
+ ```
173
+
174
+ Code formatting can be checked with [ruff](https://docs.astral.sh/ruff/):
175
+
176
+ ```bash
177
+ pip install ruff
178
+ ruff check kdedge tests
179
+
180
+ # Autofix simple issues:
181
+ ruff check --fix kdedge tests
182
+
183
+ # Formatting a file:
184
+ ruff format kdedge/algo.py
185
+ ```
186
+
187
+ ### Numba and SciPy implementations
188
+
189
+ Some functions are implemented multiple times with different backends (pure NumPy, SciPy or Numba) for performance reasons.
190
+ See kdedge/impl/impl_numpy.py, kdedge/impl/impl_scipy.py and kdedge/impl/impl_numba.py for the implementations.
191
+
192
+ 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.
193
+
194
+ Benchmark tests to compare some of the Numba and NumPy functions are in [benchmarks/](./benchmarks).
195
+
196
+ Run the benchmarks with:
197
+
198
+ ```bash
199
+ python -m benchmarks
200
+ ```
201
+
202
+ ## References
203
+
204
+ - KDEEB website: [https://webspace.science.uu.nl/~telea001/InfoVis/KDEEB](https://webspace.science.uu.nl/~telea001/InfoVis/KDEEB)
205
+ - KDEEB paper: [Hurter, C., Ersoy, O. and Telea, A. (2012), *Graph Bundling by Kernel Density Estimation*, Computer Graphics Forum 31(3).](https://webspace.science.uu.nl/~telea001/uploads/PAPERS/EuroVis12/kdeeb.pdf)
206
+ - KDEEB authors' demo software: [https://webspace.science.uu.nl/~telea001/uploads/Software/KDEEB](https://webspace.science.uu.nl/~telea001/uploads/Software/KDEEB)
@@ -0,0 +1,43 @@
1
+ """
2
+ Edge bundling algorithms for visualizing graphs.
3
+
4
+ """
5
+
6
+ __version__: str = "0.0.1"
7
+ __author__: str = "cuzi"
8
+ __email__: str = "cuzi@openmail.cc"
9
+ __source__: str = "https://github.com/cvzi/kdedge"
10
+ __license__: str = """
11
+ MIT License
12
+
13
+ Copyright (c) cuzi 2025
14
+
15
+ Permission is hereby granted, free of charge, to any person obtaining a copy
16
+ of this software and associated documentation files (the "Software"), to deal
17
+ in the Software without restriction, including without limitation the rights
18
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
+ copies of the Software, and to permit persons to whom the Software is
20
+ furnished to do so, subject to the following conditions:
21
+
22
+ The above copyright notice and this permission notice shall be included in all
23
+ copies or substantial portions of the Software.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
+ SOFTWARE.
32
+ """
33
+
34
+ __all__ = [
35
+ "bundle",
36
+ "exponential_schedule",
37
+ "kdeeb",
38
+ "linear_schedule",
39
+ "list_backends"
40
+ ]
41
+
42
+ from .algo import bundle, exponential_schedule, kdeeb, linear_schedule
43
+ from .impl.registry import list_backends
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from typing import Literal, TypeAlias
6
+
7
+ import numpy as np
8
+ from numpy.typing import ArrayLike, NDArray
9
+
10
+ __all__ = [
11
+ "ArraySplatKernel",
12
+ "FloatArray",
13
+ "GridShape",
14
+ "SplatKernel",
15
+ "SplatKernelArrayFn",
16
+ "SplatKernelFn",
17
+ "as_float_array",
18
+ ]
19
+
20
+ EPS = 1e-12
21
+
22
+ FloatArray: TypeAlias = NDArray[np.floating]
23
+ GridShape: TypeAlias = tuple[int, int]
24
+ SplatKernelFn: TypeAlias = Callable[[float, float], float]
25
+ SplatKernelArrayFn: TypeAlias = Callable[[FloatArray, float], FloatArray]
26
+ AdvectEdgesFn: TypeAlias = Callable[
27
+ [
28
+ list[FloatArray], # polylines
29
+ FloatArray, # own_field
30
+ FloatArray | None, # node_grad
31
+ float, # attract
32
+ float, # node_repel
33
+ float, # smooth_lambda
34
+ int, # smooth_iterations
35
+ int, # h
36
+ int, # w
37
+ ],
38
+ list[FloatArray],
39
+ ]
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class ArraySplatKernel:
44
+ fn: SplatKernelArrayFn
45
+
46
+ SplatKernel: TypeAlias = (
47
+ Literal["linear", "cone", "gaussian"] | SplatKernelFn | ArraySplatKernel | None
48
+ )
49
+
50
+ def as_float_array(values: ArrayLike | float | int, *,
51
+ copy: bool = False) -> FloatArray:
52
+ if copy:
53
+ return np.array(values, dtype=np.float64, copy=True)
54
+ return np.asarray(values, dtype=np.float64)