@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 +27 -0
- package/README.md +199 -0
- package/dist/d3-sankey-circular-ng.js +784 -0
- package/dist/d3-sankey-circular-ng.min.js +2 -0
- package/package.json +59 -0
- package/src/align.js +23 -0
- package/src/constant.js +5 -0
- package/src/index.js +3 -0
- package/src/sankey.js +644 -0
- package/src/sankeyLinkCircular.js +97 -0
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
|
+
|