d3-link-area 0.0.0-stage → 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2026 Michele Mauri
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose
4
+ with or without fee is hereby granted, provided that the above copyright notice
5
+ and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
9
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
11
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
12
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
13
+ THIS SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,134 @@
1
- # Temporary Holding Version
1
+ # d3-link-area
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Links drawn as areas, for D3: the filled counterpart of [`d3.link`](https://d3js.org/d3-shape/link), as `d3.area` is of `d3.line`. Use it for the bands of Sankey diagrams, alluvial diagrams and parallel sets, drawn as **closed paths** that keep a constant perpendicular thickness and can **change width from source to target**.
4
+
5
+ ## The problem
6
+
7
+ The usual way to draw a filled flow is to take one cubic Bézier for the top edge and the same curve shifted down by the flow's width for the bottom edge. The band is then constant in *vertical* thickness, but the eye reads thickness *across* the flow: where the curve is steep, the band looks pinched (by a factor of cos θ).
8
+
9
+ The other common approach, a centerline drawn with a thick `stroke-width` (as `d3.sankeyLinkHorizontal` does), has the right thickness, but it isn't a fillable shape and it can't change width along the way.
10
+
11
+ `d3-link-area` offsets the two edges perpendicularly to the same centerline used by [`d3.linkHorizontal`](https://d3js.org/d3-shape/link), interpolating the width from source to target. You get a single closed path you can fill, stroke, hover or clip, at a thickness that matches the data.
12
+
13
+ ## Installing
14
+
15
+ ```sh
16
+ npm install d3-link-area
17
+ ```
18
+
19
+ ```js
20
+ import {linkAreaHorizontal, linkAreaVertical} from "d3-link-area";
21
+ ```
22
+
23
+ Or in the browser, after D3:
24
+
25
+ ```html
26
+ <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
27
+ <script src="https://cdn.jsdelivr.net/npm/d3-link-area@0.1"></script>
28
+ <script>
29
+ const link = d3.linkAreaHorizontal();
30
+ </script>
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ The API mirrors [d3-shape links](https://d3js.org/d3-shape/link). A point is `[x, y, width]` by default, where `y` is the *center* of the band at that end:
36
+
37
+ ```js
38
+ const link = linkAreaHorizontal();
39
+ link({source: [100, 50, 20], target: [400, 200, 40]}); // "M100,40C…Z"
40
+ ```
41
+
42
+ With [d3-sankey](https://github.com/d3/d3-sankey):
43
+
44
+ ```js
45
+ const link = linkAreaHorizontal()
46
+ .source(d => [d.source.x1, d.y0, d.width])
47
+ .target(d => [d.target.x0, d.y1, d.width]);
48
+
49
+ svg.append("g")
50
+ .selectAll("path")
51
+ .data(links)
52
+ .join("path")
53
+ .attr("d", link)
54
+ .attr("fill", d => color(d.source.id));
55
+ ```
56
+
57
+ Note the `fill` instead of `stroke` / `stroke-width`.
58
+
59
+ ## API
60
+
61
+ <a name="linkAreaHorizontal" href="#linkAreaHorizontal">#</a> <b>linkAreaHorizontal</b>()
62
+
63
+ Returns a new link area generator for flows going left to right (or right to left). The ends of the band are vertical cuts that meet the node edges.
64
+
65
+ <a name="linkAreaVertical" href="#linkAreaVertical">#</a> <b>linkAreaVertical</b>()
66
+
67
+ Same, for flows going top to bottom (or bottom to top). The ends are horizontal cuts.
68
+
69
+ <a name="_link" href="#_link">#</a> <i>link</i>(<i>arguments…</i>)
70
+
71
+ Generates the band for the given *arguments*, which are passed to the accessors along with `this`. Returns a path data string, or draws to the [context](#link_context) and returns undefined.
72
+
73
+ <a name="link_source" href="#link_source">#</a> <i>link</i>.<b>source</b>([<i>source</i>])
74
+ <br><a name="link_target" href="#link_target">#</a> <i>link</i>.<b>target</b>([<i>target</i>])
75
+
76
+ Accessors for the two ends. Default to `d => d.source` and `d => d.target`.
77
+
78
+ <a name="link_x" href="#link_x">#</a> <i>link</i>.<b>x</b>([<i>x</i>])
79
+ <br><a name="link_y" href="#link_y">#</a> <i>link</i>.<b>y</b>([<i>y</i>])
80
+
81
+ Accessors for the position of each end, called with the value returned by the source or target accessor. Default to `p => p[0]` and `p => p[1]`. The position is the center of the band.
82
+
83
+ <a name="link_width" href="#link_width">#</a> <i>link</i>.<b>width</b>([<i>width</i>])
84
+
85
+ Accessor for the thickness of the band at each end, measured along the node edge. Defaults to `p => p[2]`. Can be a constant.
86
+
87
+ <a name="link_curvature" href="#link_curvature">#</a> <i>link</i>.<b>curvature</b>([<i>curvature</i>])
88
+
89
+ How far the Bézier control points reach toward the other end, from 0 to 1. Defaults to 0.5, the same curve as `d3.linkHorizontal`. Lower values make the flow leave the node more steeply.
90
+
91
+ <a name="link_ease" href="#link_ease">#</a> <i>link</i>.<b>ease</b>([<i>ease</i>])
92
+
93
+ The easing of the width transition between source and target. Defaults to smoothstep, so the band keeps the node's width as it leaves and enters. Any [d3-ease](https://d3js.org/d3-ease) function works, for example `d3.easeLinear` for a straight taper.
94
+
95
+ <a name="link_samples" href="#link_samples">#</a> <i>link</i>.<b>samples</b>([<i>samples</i>])
96
+
97
+ The number of segments each edge is sampled into before trimming and fitting. Defaults to 64. More samples follow very tight bends more closely; they cost computation, not nodes in the output.
98
+
99
+ <a name="link_tolerance" href="#link_tolerance">#</a> <i>link</i>.<b>tolerance</b>([<i>tolerance</i>])
100
+
101
+ The maximum distance, in the same units as the coordinates, between the output and the sampled outline. Each edge is drawn with as few cubic Béziers as this allows, typically two to four for an ordinary flow, so the paths stay light and easy to edit in a vector editor. Defaults to 0.25. Raise it for fewer nodes, lower it for more accuracy.
102
+
103
+ <a name="link_fillet" href="#link_fillet">#</a> <i>link</i>.<b>fillet</b>([<i>fillet</i>])
104
+
105
+ How much to round the corners left where folds are trimmed away (see [below](#wide-tight-flows)), relative to the size of the fold. Defaults to 1; 0 leaves sharp corners.
106
+
107
+ <a name="link_curve" href="#link_curve">#</a> <i>link</i>.<b>curve</b>([<i>curve</i>])
108
+
109
+ If set, the sampled edges are drawn through this [curve](https://d3js.org/d3-shape/curve) instead of being fitted with cubics, for example `d3.curveLinear` for a polygon through every sample. Defaults to null.
110
+
111
+ <a name="link_context" href="#link_context">#</a> <i>link</i>.<b>context</b>([<i>context</i>])
112
+
113
+ If *context* is set, the generator draws to it (for example a `CanvasRenderingContext2D`) instead of returning a string. Defaults to null.
114
+
115
+ ## Wide, tight flows
116
+
117
+ When half a band's width is larger than the curve's radius of curvature, its inner edge would fold onto itself (the "swallowtail" of offset curves) and poke behind the node. The generator trims those folds: the outline never goes past the source or target node, and it never crosses itself.
118
+
119
+ Every trimmed corner is then rounded with a fillet tangent to the edge on both sides. At the nodes, the edge leaves and enters along the direction of the flow; when the fold is so deep that this would make the edge bulge out and back, it runs along the side of the node instead. The fillet is sized on the fold, so slight folds get slight rounding, and it is widened where needed so that it never bulges outward. Set [`fillet`](#link_fillet) to tune it.
120
+
121
+ If a band is so wide that its steep middle part doesn't fit between the nodes at all, its edges run along the node edges there. No band can be both constant in thickness and that tight, so leave more room between the columns, or lower the `curvature`.
122
+
123
+ ## Demo
124
+
125
+ ```sh
126
+ npm install
127
+ python3 -m http.server
128
+ ```
129
+
130
+ Then open <http://localhost:8000/demo/>.
131
+
132
+ ## License
133
+
134
+ ISC