@lucastho/d3-sankey-circular-ng 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,27 @@
1
+ Copyright 2015, Mike Bostock
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the author nor the names of contributors may be used to
15
+ endorse or promote products derived from this software without specific prior
16
+ written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+
2
+ # d3-sankey-circular
3
+
4
+ A fork of [d3-sankey](https://github.com/d3/d3-sankey) that adds support for
5
+ **circular links** (back-edges and self-loops).
6
+
7
+ When a graph is acyclic, this fork behaves **exactly** like the original
8
+ d3-sankey — same layout, same output, no extra fields written. Circular support
9
+ only activates when the graph actually contains a cycle.
10
+
11
+ This fork also ships a new link generator, `sankeyLinkCircular`, which renders
12
+ links as **stroked centerlines** rather than filled ribbons. The visible
13
+ thickness of a link comes entirely from `stroke-width`, which makes circular
14
+ routing dramatically simpler to compute and draw.
15
+
16
+ > Fork of [d3/d3-sankey](https://github.com/d3/d3-sankey) (ISC).
17
+ > Original work © Mike Bostock. Circular-link additions © (your name).
18
+ >Circularity concepts from Tom Shanley's d3-sankey-circular (https://observablehq.com/@tomshanley/sankey-circular-deconstructed) onto the modern codebase.
19
+ > Mostly produced by Claude
20
+
21
+
22
+ ## Installing
23
+
24
+ ```bash
25
+ npm install @yourname/d3-sankey-circular
26
+ ```
27
+
28
+ ```js
29
+ import { sankey, sankeyLinkCircular } from "@yourname/d3-sankey-circular";
30
+ ```
31
+
32
+ In a browser via importmap (see [`examples/`](./examples)):
33
+
34
+ ```html
35
+ <script type="module">
36
+ import { sankey, sankeyLinkCircular } from "@yourname/d3-sankey-circular";
37
+ </script>
38
+ ```
39
+
40
+ ## What's different from d3-sankey
41
+
42
+ | Concern | d3-sankey | this fork |
43
+ | --------------------- | ---------------------------------- | --------------------------------------------------------------- |
44
+ | Cyclic graphs | Throws `"circular link"` | Detects back-edges and routes them as loops |
45
+ | Self-loops (`a → a`) | Not supported | Supported |
46
+ | Link rendering | Filled ribbon (`sankeyLinkHorizontal`) | Stroked centerline (`sankeyLinkCircular`) — replaces the ribbon generator |
47
+ | Acyclic output | — | **Byte-for-byte identical** (the original code path is reused) |
48
+
49
+ Everything from the original API (`nodeId`, `nodeAlign`, `nodeSort`,
50
+ `nodeWidth`, `nodePadding`, `nodes`, `links`, `linkSort`, `size`, `extent`,
51
+ `iterations`) is preserved unchanged.
52
+
53
+ ## Migrating from d3-sankey
54
+
55
+ This fork **does not export `sankeyLinkHorizontal`**. Use `sankeyLinkCircular`
56
+ for all links — it renders normal links as stroked centerlines with the same
57
+ shape. Remember the [stroked-centerline rendering model](#rendering-model-stroked-centerlines):
58
+ set `fill: none` and `stroke-width` on your links.
59
+
60
+
61
+
62
+
63
+ ## Rendering model: stroked centerlines
64
+
65
+ This is the most important thing to understand before using the library.
66
+
67
+ `sankeyLinkCircular` returns an SVG path string describing the **centerline** of
68
+ each link — a single line down the middle of the ribbon, not the ribbon's
69
+ outline. You give that line its thickness with CSS/SVG:
70
+
71
+ ```css
72
+ .link {
73
+ fill: none; /* REQUIRED — there is no ribbon to fill */
74
+ stroke-linejoin: round;
75
+ stroke-linecap: butt;
76
+ }
77
+ ```
78
+
79
+ ```js
80
+ svg.append("g")
81
+ .selectAll("path")
82
+ .data(graph.links)
83
+ .join("path")
84
+ .attr("class", d => d.circular ? "link circular" : "link normal")
85
+ .attr("d", linkPath)
86
+ .attr("stroke-width", d => Math.max(1, d.width)); // REQUIRED
87
+ ```
88
+
89
+ If you forget `fill: none` or `stroke-width`, your links will render as solid
90
+ black blobs or hairlines. This differs from upstream d3-sankey, where you fill a
91
+ closed ribbon path and never set `stroke-width`.
92
+
93
+ For normal (acyclic) links the centerline is the same cubic Bézier *shape*
94
+ as upstream d3-sankey's `sankeyLinkHorizontal`, just stroked instead of
95
+ filled — so the visual result matches. (This fork does not export
96
+ `sankeyLinkHorizontal` itself; `sankeyLinkCircular` handles both normal and
97
+ circular links.)
98
+
99
+
100
+ ## API reference (additions)
101
+
102
+ ### Layout output
103
+
104
+ When the input graph contains at least one cycle, `sankey(graph)` writes these
105
+ extra fields:
106
+
107
+ **On each circular link:**
108
+
109
+ - `link.circular` — `true` for back-edges and self-loops, `false` otherwise.
110
+ (Set to `false` on every link for acyclic graphs.)
111
+ - `link.circularLinkType` — `"top"` or `"bottom"`, the side the loop is routed
112
+ on.
113
+ - `link.circularPathData` — the geometry consumed by `sankeyLinkCircular`:
114
+
115
+ ```
116
+ {
117
+ points: [{x, y}, ...], // centerline vertices (source → ... → target)
118
+ radius, // corner-fillet radius
119
+ type, // "top" | "bottom"
120
+ selfLoop, // boolean
121
+ laneY, // the horizontal lane the loop runs along
122
+ sourceX, targetX, sourceY, targetY
123
+ }
124
+ ```
125
+
126
+ **On the graph:**
127
+
128
+ - `graph.circularReservation` — the vertical/horizontal space reserved inside
129
+ the extent for loop routing:
130
+
131
+ ```
132
+ {
133
+ top, // px reserved above the node band for "top" loops
134
+ bottom, // px reserved below the node band for "bottom" loops
135
+ gutter, // px reserved left/right so out-and-around curves don't clip
136
+ gap, // spacing between stacked loops
137
+ topStack, // links routed on top, in stacking order
138
+ bottomStack // links routed on bottom, in stacking order
139
+ }
140
+ ```
141
+
142
+ For acyclic graphs neither `graph.circularReservation` nor the circular link
143
+ fields are written.
144
+
145
+ ### `sankeyLinkCircular()`
146
+
147
+ Constructs a link path generator for use with the layout above.
148
+
149
+ - **`linkPath(link)`** — returns the SVG path `d` string for a link. Dispatches
150
+ to a circular or normal centerline automatically based on `link.circular`.
151
+
152
+ - **`linkPath.points(link)`** — returns the array of `{x, y}` vertices for a
153
+ link. Useful for debug overlays (drawing corner/endpoint markers). For normal
154
+ links this is the two endpoints; for circular links it's
155
+ `link.circularPathData.points`.
156
+
157
+ - **`linkPath.debug([boolean])`** — get/set a debug flag. With no arguments,
158
+ returns the current flag; with an argument, sets it and returns the generator.
159
+
160
+ ## How the circular layout works
161
+
162
+ A short tour, in case you need to extend it:
163
+
164
+ 1. **Detect cycles.** A DFS tags every back-edge (and self-loop) as
165
+ `link.circular`. Ignoring those links, the remaining graph is a DAG, so the
166
+ original depth/height/breadth passes run unmodified (they just skip circular
167
+ links).
168
+
169
+ 2. **Phase 1 — measure.** Run the standard acyclic layout in the *full* extent.
170
+ This is a throwaway pass whose only purpose is to learn each link's `width`
171
+ and each node's `y` position. Those tell us how thick each loop lane must be
172
+ and whether a loop should route over the top or under the bottom.
173
+
174
+ 3. **Reserve space.** Sum the loop widths (plus gaps) on each side to compute how
175
+ much vertical room the loops need, and reserve left/right gutters so the
176
+ out-and-around bends don't clip the extent — all *inside* the user's extent,
177
+ so there's no new sizing API.
178
+
179
+ 4. **Phase 2 — final layout.** Re-run the same layout confined to the shrunken
180
+ band. The routing side decided in phase 1 stays frozen so the reservation
181
+ can't oscillate against a re-decided route.
182
+
183
+ 5. **Path data.** Build each loop's centerline: out the source's right face, up
184
+ (or down) into its lane, across, and back down (or up) into the target's left
185
+ face, with rounded corners.
186
+
187
+ If the graph is acyclic, only step 1 (which finds nothing) and the standard
188
+ layout run.
189
+
190
+ ## Examples
191
+
192
+ See [`examples/circular.html`](./examples/circular.html) for a runnable demo
193
+ with a debug overlay (centerline, corner markers, and lane guides).
194
+
195
+ ## License
196
+
197
+ ISC. See [`LICENSE`](./LICENSE). Original d3-sankey © Mike Bostock; circular
198
+ additions © (your name).
199
+