@hpcc-js/graph 2.87.2 → 2.87.3
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 +43 -43
- package/README.md +256 -256
- package/dist/index.es6.js +7 -7
- package/dist/index.es6.js.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +8 -8
- package/src/AdjacencyGraph.ts +224 -224
- package/src/Edge.css +23 -23
- package/src/Edge.ts +257 -257
- package/src/Graph.css +18 -18
- package/src/Graph.ts +1075 -1075
- package/src/GraphData.ts +187 -187
- package/src/GraphLayouts.ts +173 -173
- package/src/Sankey.css +46 -46
- package/src/Sankey.ts +291 -291
- package/src/Subgraph.css +10 -10
- package/src/Subgraph.ts +165 -165
- package/src/Vertex.css +3 -3
- package/src/Vertex.ts +282 -282
- package/src/__package__.ts +3 -3
- package/src/__tests__/data.ts +444 -444
- package/src/__tests__/index.ts +1 -1
- package/src/__tests__/test1.ts +18 -18
- package/src/__tests__/test2.ts +80 -80
- package/src/__tests__/test3.ts +46 -46
- package/src/__tests__/test4.ts +66 -66
- package/src/__tests__/test5.ts +85 -85
- package/src/graph2/dataGraph.ts +305 -305
- package/src/graph2/graph.css +34 -34
- package/src/graph2/graph.ts +135 -135
- package/src/graph2/graphReactT.ts +44 -44
- package/src/graph2/graphT.ts +1330 -1330
- package/src/graph2/index.ts +7 -7
- package/src/graph2/layouts/circle.ts +37 -37
- package/src/graph2/layouts/dagre.ts +132 -132
- package/src/graph2/layouts/dagreWorker.ts +35 -35
- package/src/graph2/layouts/forceDirected.ts +117 -117
- package/src/graph2/layouts/forceDirectedWorker.ts +30 -30
- package/src/graph2/layouts/geoForceDirected.ts +112 -112
- package/src/graph2/layouts/graphviz.ts +124 -124
- package/src/graph2/layouts/graphvizWorker.ts +71 -71
- package/src/graph2/layouts/index.ts +7 -7
- package/src/graph2/layouts/layout.ts +105 -105
- package/src/graph2/layouts/null.ts +35 -35
- package/src/graph2/layouts/placeholders.ts +103 -103
- package/src/graph2/layouts/tree.ts +328 -328
- package/src/graph2/liteMap.ts +72 -72
- package/src/graph2/liteSVGZooom.ts +61 -61
- package/src/graph2/sankeyGraph.css +45 -45
- package/src/graph2/sankeyGraph.ts +316 -316
- package/src/graph2/subgraph.tsx +30 -30
- package/src/graph2/vertex.tsx +31 -31
- package/src/index.ts +8 -8
- package/src/test.ts +649 -649
|
@@ -1,316 +1,316 @@
|
|
|
1
|
-
import { Palette, publish, SVGWidget, Utility } from "@hpcc-js/common";
|
|
2
|
-
import { compare2 } from "@hpcc-js/util";
|
|
3
|
-
import { sankey as d3Sankey, sankeyLinkHorizontal as d3SankeyLinkHorizontal } from "d3-sankey";
|
|
4
|
-
import { select as d3Select } from "d3-selection";
|
|
5
|
-
import { AnnotationColumn, toJsonObj } from "./dataGraph";
|
|
6
|
-
|
|
7
|
-
import "../../src/graph2/sankeyGraph.css";
|
|
8
|
-
import { EdgeBaseProps, VertexBaseProps } from "./graphT";
|
|
9
|
-
|
|
10
|
-
export class SankeyGraph extends SVGWidget {
|
|
11
|
-
@publish([], "any", "Vertex Columns", null, { internal: true })
|
|
12
|
-
vertexColumns: publish<this, string[]>;
|
|
13
|
-
@publish([], "any", "Vertices (Nodes)", null, { internal: true })
|
|
14
|
-
vertices: publish<this, Array<Array<string | number | boolean>>>;
|
|
15
|
-
@publish("", "set", "Vertex Category ID column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
16
|
-
vertexCategoryColumn: publish<this, string>;
|
|
17
|
-
@publish("", "set", "Vertex ID column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
18
|
-
vertexIDColumn: publish<this, string>;
|
|
19
|
-
@publish("", "set", "Vertex label column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
20
|
-
vertexLabelColumn: publish<this, string>;
|
|
21
|
-
@publish("", "set", "Vertex centroid column (boolean)", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
22
|
-
vertexCentroidColumn: publish<this, string>;
|
|
23
|
-
@publish("?", "string", "Vertex default FAChar")
|
|
24
|
-
vertexFAChar: publish<this, string>;
|
|
25
|
-
@publish("", "set", "Vertex FAChar column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
26
|
-
vertexFACharColumn: publish<this, string>;
|
|
27
|
-
@publish("", "set", "Vertex tooltip column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
28
|
-
vertexTooltipColumn: publish<this, string>;
|
|
29
|
-
@publish([], "propertyArray", "Annotations", null, { autoExpand: AnnotationColumn })
|
|
30
|
-
vertexAnnotationColumns: publish<this, AnnotationColumn[]>;
|
|
31
|
-
|
|
32
|
-
@publish([], "any", "Edge columns", null, { internal: true })
|
|
33
|
-
edgeColumns: publish<this, string[]>;
|
|
34
|
-
@publish([], "any", "Edges (Edges)", null, { internal: true })
|
|
35
|
-
edges: publish<this, Array<Array<string | number | boolean>>>;
|
|
36
|
-
@publish("", "set", "Edge ID column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
37
|
-
edgeIDColumn: publish<this, string>;
|
|
38
|
-
@publish("", "set", "Edge label column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
39
|
-
edgeLabelColumn: publish<this, string>;
|
|
40
|
-
@publish("", "set", "Edge source ID column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
41
|
-
edgeSourceColumn: publish<this, string>;
|
|
42
|
-
@publish("", "set", "Edge target ID column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
43
|
-
edgeTargetColumn: publish<this, string>;
|
|
44
|
-
@publish("", "set", "Edge target ID column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
45
|
-
edgeWeightColumn: publish<this, string>;
|
|
46
|
-
|
|
47
|
-
protected _d3Sankey: any;
|
|
48
|
-
protected _selection: any;
|
|
49
|
-
_palette: any;
|
|
50
|
-
|
|
51
|
-
constructor() {
|
|
52
|
-
super();
|
|
53
|
-
Utility.SimpleSelectionMixin.call(this);
|
|
54
|
-
|
|
55
|
-
this._drawStartPos = "origin";
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private _prevVertices: readonly VertexBaseProps[] = [];
|
|
59
|
-
private _masterVertices: VertexBaseProps[] = [];
|
|
60
|
-
private _masterVerticesMap: { [key: string]: VertexBaseProps } = {};
|
|
61
|
-
mergeVertices() {
|
|
62
|
-
const columns = this.vertexColumns();
|
|
63
|
-
const annotationColumns = this.vertexAnnotationColumns();
|
|
64
|
-
const catIdx = this.indexOf(columns, this.vertexCategoryColumn(), "category");
|
|
65
|
-
const idIdx = this.indexOf(columns, this.vertexIDColumn(), "id");
|
|
66
|
-
const labelIdx = this.indexOf(columns, this.vertexLabelColumn(), "label");
|
|
67
|
-
const centroidIdx = this.indexOf(columns, this.vertexCentroidColumn(), "centroid");
|
|
68
|
-
const vertexTooltipIdx = this.indexOf(columns, this.vertexTooltipColumn(), "tooltip");
|
|
69
|
-
const annotationIdxs = annotationColumns.map(ac => this.indexOf(columns, ac.columnID(), ""));
|
|
70
|
-
const vertices: VertexBaseProps[] = this.vertices().map((v): VertexBaseProps => {
|
|
71
|
-
return {
|
|
72
|
-
categoryID: "" + v[catIdx],
|
|
73
|
-
id: "" + v[idIdx],
|
|
74
|
-
text: "" + v[labelIdx],
|
|
75
|
-
tooltip: "" + v[vertexTooltipIdx],
|
|
76
|
-
origData: toJsonObj(v, columns),
|
|
77
|
-
centroid: !!v[centroidIdx],
|
|
78
|
-
annotationIDs: annotationIdxs.map((ai, i) => !!v[ai] ? annotationColumns[i].annotationID() : undefined).filter(a => !!a)
|
|
79
|
-
};
|
|
80
|
-
});
|
|
81
|
-
const diff = compare2(this._prevVertices, vertices, d => d.id);
|
|
82
|
-
diff.exit.forEach(item => {
|
|
83
|
-
this._masterVertices = this._masterVertices.filter(i => i.id !== item.id);
|
|
84
|
-
});
|
|
85
|
-
diff.enter.forEach(item => {
|
|
86
|
-
this._masterVertices.push(item);
|
|
87
|
-
this._masterVerticesMap[item.id] = item;
|
|
88
|
-
});
|
|
89
|
-
this._prevVertices = vertices;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
indexOf(columns: readonly string[], column: string, defColumn: string = ""): number {
|
|
93
|
-
const retVal = columns.indexOf(column);
|
|
94
|
-
return retVal >= 0 ? retVal : columns.indexOf(defColumn);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
protected _prevEdges: readonly EdgeBaseProps[] = [];
|
|
98
|
-
protected _masterEdges: EdgeBaseProps[] = [];
|
|
99
|
-
mergeEdges() {
|
|
100
|
-
const columns = this.edgeColumns();
|
|
101
|
-
const idIdx = this.indexOf(columns, this.edgeIDColumn(), "id");
|
|
102
|
-
const sourceIdx = this.indexOf(columns, this.edgeSourceColumn(), "source");
|
|
103
|
-
const targetIdx = this.indexOf(columns, this.edgeTargetColumn(), "target");
|
|
104
|
-
const labelIdx = this.indexOf(columns, this.edgeLabelColumn(), "label");
|
|
105
|
-
const weightIdx = this.indexOf(columns, this.edgeWeightColumn(), "weight");
|
|
106
|
-
const edges: EdgeBaseProps[] = this.edges().map(e => {
|
|
107
|
-
const source = this._masterVerticesMap["" + e[sourceIdx]];
|
|
108
|
-
if (!source) console.error(`Invalid edge source entity "${e[sourceIdx]}" does not exist.`);
|
|
109
|
-
const target = this._masterVerticesMap["" + e[targetIdx]];
|
|
110
|
-
if (!target) console.error(`Invalid edge target entity "${e[targetIdx]}" does not exist.`);
|
|
111
|
-
return {
|
|
112
|
-
type: "edge",
|
|
113
|
-
id: idIdx >= 0 ? "" + e[idIdx] : "" + e[sourceIdx] + "->" + e[targetIdx],
|
|
114
|
-
source,
|
|
115
|
-
target,
|
|
116
|
-
value: +e[weightIdx] || 0,
|
|
117
|
-
label: labelIdx >= 0 ? ("" + e[labelIdx]) : "",
|
|
118
|
-
origData: toJsonObj(e, columns)
|
|
119
|
-
};
|
|
120
|
-
}).filter(e => e.source && e.target);
|
|
121
|
-
const diff = compare2(this._masterEdges, edges, d => d.id);
|
|
122
|
-
diff.exit.forEach(item => {
|
|
123
|
-
this._masterEdges = this._masterEdges.filter(i => i.id !== item.id);
|
|
124
|
-
});
|
|
125
|
-
diff.enter.forEach(item => {
|
|
126
|
-
this._masterEdges.push(item);
|
|
127
|
-
});
|
|
128
|
-
this._prevEdges = edges;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
sankeyData() {
|
|
132
|
-
this.mergeVertices();
|
|
133
|
-
this.mergeEdges();
|
|
134
|
-
return {
|
|
135
|
-
vertices: this._masterVertices,
|
|
136
|
-
edges: this._masterEdges
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
enter(domNode, element) {
|
|
141
|
-
super.enter(domNode, element);
|
|
142
|
-
|
|
143
|
-
this._d3Sankey = d3Sankey();
|
|
144
|
-
this._selection.widgetElement(element);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
update(domNode, element) {
|
|
148
|
-
super.update(domNode, element);
|
|
149
|
-
|
|
150
|
-
this._palette = this._palette.switch(this.paletteID());
|
|
151
|
-
|
|
152
|
-
const strokeWidth = this.vertexStrokeWidth();
|
|
153
|
-
|
|
154
|
-
const sankeyData = this.sankeyData();
|
|
155
|
-
this._d3Sankey
|
|
156
|
-
.nodeId(d => d.id)
|
|
157
|
-
.extent([
|
|
158
|
-
[0, 0],
|
|
159
|
-
[this.width(), this.height()]
|
|
160
|
-
])
|
|
161
|
-
// .nodeAlign(sankeyCenter)
|
|
162
|
-
// .nodeWidth(this.vertexWidth())
|
|
163
|
-
// .nodePadding(this.vertexPadding())
|
|
164
|
-
;
|
|
165
|
-
if (sankeyData.vertices.length > 0) {
|
|
166
|
-
this._d3Sankey({
|
|
167
|
-
nodes: sankeyData.vertices,
|
|
168
|
-
links: sankeyData.edges
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
const context = this;
|
|
172
|
-
|
|
173
|
-
// Links ---
|
|
174
|
-
const link = element.selectAll(".link").data(sankeyData.edges);
|
|
175
|
-
link.enter().append("path")
|
|
176
|
-
.attr("class", "link")
|
|
177
|
-
.each(function () {
|
|
178
|
-
d3Select(this)
|
|
179
|
-
.append("title")
|
|
180
|
-
;
|
|
181
|
-
})
|
|
182
|
-
.merge(link)
|
|
183
|
-
.attr("d", d3SankeyLinkHorizontal())
|
|
184
|
-
.style("stroke-width", (d: any) => Math.max(1, d.width))
|
|
185
|
-
// .sort(function (a, b) { return b.width - a.width; })
|
|
186
|
-
.select("title")
|
|
187
|
-
.text(function (d) {
|
|
188
|
-
return d.source.text + " → " + d.target.text + "\n" + d.value;
|
|
189
|
-
})
|
|
190
|
-
;
|
|
191
|
-
link.exit().remove();
|
|
192
|
-
|
|
193
|
-
// Nodes ---
|
|
194
|
-
const node = element.selectAll(".node").data(sankeyData.vertices);
|
|
195
|
-
node.enter().append("g")
|
|
196
|
-
.attr("class", "node")
|
|
197
|
-
.call(this._selection.enter.bind(this._selection))
|
|
198
|
-
.on("click", function (d) {
|
|
199
|
-
context.click(d.origData, "", context._selection.selected(this));
|
|
200
|
-
})
|
|
201
|
-
.on("dblclick", function (d) {
|
|
202
|
-
context.dblclick(d.origData, "", context._selection.selected(this));
|
|
203
|
-
})
|
|
204
|
-
.each(function () {
|
|
205
|
-
const gElement = d3Select(this);
|
|
206
|
-
gElement.append("rect");
|
|
207
|
-
gElement.append("text");
|
|
208
|
-
})
|
|
209
|
-
/*
|
|
210
|
-
.call(d3.behavior.drag()
|
|
211
|
-
.origin(function (d) { return d; })
|
|
212
|
-
.on("dragstart", function () {
|
|
213
|
-
this.parentNode.appendChild(this);
|
|
214
|
-
})
|
|
215
|
-
.on("drag", dragmove)
|
|
216
|
-
)
|
|
217
|
-
*/
|
|
218
|
-
.merge(node)
|
|
219
|
-
.attr("transform", function (d) {
|
|
220
|
-
let _x = 0;
|
|
221
|
-
let _y = 0;
|
|
222
|
-
if (d.x0) _x = d.x0;
|
|
223
|
-
if (d.y0) _y = d.y0;
|
|
224
|
-
return "translate(" + (_x + strokeWidth) + "," + (_y + strokeWidth) + ")";
|
|
225
|
-
})
|
|
226
|
-
.each(function () {
|
|
227
|
-
const n = d3Select(this);
|
|
228
|
-
n.select("rect")
|
|
229
|
-
.attr("height", (d: any) => {
|
|
230
|
-
return d.y1 - d.y0;
|
|
231
|
-
})
|
|
232
|
-
.attr("width", (d: any) => d.x1 - d.x0)
|
|
233
|
-
.style("fill", function (d: any) { return context._palette(d.categoryID); })
|
|
234
|
-
.style("stroke", function (d: any) { return context.vertexStrokeColor(); })
|
|
235
|
-
.style("stroke-width", function (d: any) { return strokeWidth; })
|
|
236
|
-
.style("cursor", (context.xAxisMovement() || context.yAxisMovement()) ? null : "default")
|
|
237
|
-
;
|
|
238
|
-
n.select("text")
|
|
239
|
-
.attr("x", -6)
|
|
240
|
-
.attr("y", function (d: any) {
|
|
241
|
-
return (d.y1 - d.y0) / 2;
|
|
242
|
-
})
|
|
243
|
-
.attr("dy", ".35em")
|
|
244
|
-
.attr("text-anchor", "end")
|
|
245
|
-
.attr("transform", null)
|
|
246
|
-
.text(function (d: any) {
|
|
247
|
-
return d.text;
|
|
248
|
-
})
|
|
249
|
-
.filter(function (d: any) {
|
|
250
|
-
return d.x0 < context.width() / 2;
|
|
251
|
-
})
|
|
252
|
-
.attr("x", 6 + context._d3Sankey.nodeWidth())
|
|
253
|
-
.attr("text-anchor", "start")
|
|
254
|
-
;
|
|
255
|
-
});
|
|
256
|
-
node.exit().remove();
|
|
257
|
-
|
|
258
|
-
/*
|
|
259
|
-
function dragmove(d) {
|
|
260
|
-
var gElement = d3.select(this);
|
|
261
|
-
if (context.xAxisMovement()) {
|
|
262
|
-
d.x = Math.max(0, Math.min(context.width() - d.dx, d3.event.x));
|
|
263
|
-
}
|
|
264
|
-
if (context.yAxisMovement()) {
|
|
265
|
-
d.y = Math.max(0, Math.min(context.height() - d.dy, d3.event.y));
|
|
266
|
-
}
|
|
267
|
-
gElement.attr("transform", "translate(" + d.x + "," + d.y + ")");
|
|
268
|
-
context._d3Sankey.relayout();
|
|
269
|
-
link.attr("d", context._d3SankeyPath);
|
|
270
|
-
|
|
271
|
-
gElement.select("text")
|
|
272
|
-
.attr("x", -6)
|
|
273
|
-
.attr("y", function (d) { return d.dy / 2; })
|
|
274
|
-
.attr("dy", ".35em")
|
|
275
|
-
.attr("text-anchor", "end")
|
|
276
|
-
.attr("transform", null)
|
|
277
|
-
.text(function (d) { return d.name; })
|
|
278
|
-
.filter(function (d) { return d.x < context.width() / 2; })
|
|
279
|
-
.attr("x", 6 + context._d3Sankey.nodeWidth())
|
|
280
|
-
.attr("text-anchor", "start")
|
|
281
|
-
;
|
|
282
|
-
}
|
|
283
|
-
*/
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
paletteID: { (): string; (_: string): SankeyGraph; };
|
|
287
|
-
vertexStrokeWidth: { (): number; (_: number): SankeyGraph; };
|
|
288
|
-
vertexStrokeColor: { (): string; (_: string): SankeyGraph; };
|
|
289
|
-
vertexWidth: { (): number; (_: number): SankeyGraph; };
|
|
290
|
-
vertexPadding: { (): number; (_: number): SankeyGraph; };
|
|
291
|
-
xAxisMovement: { (): boolean; (_: boolean): SankeyGraph; };
|
|
292
|
-
yAxisMovement: { (): boolean; (_: boolean): SankeyGraph; };
|
|
293
|
-
|
|
294
|
-
exit(domNode, element) {
|
|
295
|
-
super.exit(domNode, element);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Events ---
|
|
299
|
-
click(row, column, selected) {
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
dblclick(row, column, selected) {
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
SankeyGraph.prototype._class += " graph_SankeyGraph";
|
|
306
|
-
SankeyGraph.prototype.mixin(Utility.SimpleSelectionMixin);
|
|
307
|
-
|
|
308
|
-
SankeyGraph.prototype._palette = Palette.ordinal("category10");
|
|
309
|
-
|
|
310
|
-
SankeyGraph.prototype.publish("paletteID", "category10", "set", "Color palette for this widget", SankeyGraph.prototype._palette.switch());
|
|
311
|
-
SankeyGraph.prototype.publish("vertexStrokeWidth", 1, "number", "Vertex Stroke Width");
|
|
312
|
-
SankeyGraph.prototype.publish("vertexStrokeColor", "darkgray", "string", "Vertex Stroke Color");
|
|
313
|
-
SankeyGraph.prototype.publish("vertexWidth", 36, "number", "Vertex Width");
|
|
314
|
-
SankeyGraph.prototype.publish("vertexPadding", 20, "number", "Vertex Padding");
|
|
315
|
-
SankeyGraph.prototype.publish("xAxisMovement", false, "boolean", "Enable x-axis movement");
|
|
316
|
-
SankeyGraph.prototype.publish("yAxisMovement", false, "boolean", "Enable y-axis movement");
|
|
1
|
+
import { Palette, publish, SVGWidget, Utility } from "@hpcc-js/common";
|
|
2
|
+
import { compare2 } from "@hpcc-js/util";
|
|
3
|
+
import { sankey as d3Sankey, sankeyLinkHorizontal as d3SankeyLinkHorizontal } from "d3-sankey";
|
|
4
|
+
import { select as d3Select } from "d3-selection";
|
|
5
|
+
import { AnnotationColumn, toJsonObj } from "./dataGraph";
|
|
6
|
+
|
|
7
|
+
import "../../src/graph2/sankeyGraph.css";
|
|
8
|
+
import { EdgeBaseProps, VertexBaseProps } from "./graphT";
|
|
9
|
+
|
|
10
|
+
export class SankeyGraph extends SVGWidget {
|
|
11
|
+
@publish([], "any", "Vertex Columns", null, { internal: true })
|
|
12
|
+
vertexColumns: publish<this, string[]>;
|
|
13
|
+
@publish([], "any", "Vertices (Nodes)", null, { internal: true })
|
|
14
|
+
vertices: publish<this, Array<Array<string | number | boolean>>>;
|
|
15
|
+
@publish("", "set", "Vertex Category ID column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
16
|
+
vertexCategoryColumn: publish<this, string>;
|
|
17
|
+
@publish("", "set", "Vertex ID column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
18
|
+
vertexIDColumn: publish<this, string>;
|
|
19
|
+
@publish("", "set", "Vertex label column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
20
|
+
vertexLabelColumn: publish<this, string>;
|
|
21
|
+
@publish("", "set", "Vertex centroid column (boolean)", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
22
|
+
vertexCentroidColumn: publish<this, string>;
|
|
23
|
+
@publish("?", "string", "Vertex default FAChar")
|
|
24
|
+
vertexFAChar: publish<this, string>;
|
|
25
|
+
@publish("", "set", "Vertex FAChar column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
26
|
+
vertexFACharColumn: publish<this, string>;
|
|
27
|
+
@publish("", "set", "Vertex tooltip column", function (this: SankeyGraph) { return this.vertexColumns(); }, { optional: true })
|
|
28
|
+
vertexTooltipColumn: publish<this, string>;
|
|
29
|
+
@publish([], "propertyArray", "Annotations", null, { autoExpand: AnnotationColumn })
|
|
30
|
+
vertexAnnotationColumns: publish<this, AnnotationColumn[]>;
|
|
31
|
+
|
|
32
|
+
@publish([], "any", "Edge columns", null, { internal: true })
|
|
33
|
+
edgeColumns: publish<this, string[]>;
|
|
34
|
+
@publish([], "any", "Edges (Edges)", null, { internal: true })
|
|
35
|
+
edges: publish<this, Array<Array<string | number | boolean>>>;
|
|
36
|
+
@publish("", "set", "Edge ID column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
37
|
+
edgeIDColumn: publish<this, string>;
|
|
38
|
+
@publish("", "set", "Edge label column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
39
|
+
edgeLabelColumn: publish<this, string>;
|
|
40
|
+
@publish("", "set", "Edge source ID column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
41
|
+
edgeSourceColumn: publish<this, string>;
|
|
42
|
+
@publish("", "set", "Edge target ID column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
43
|
+
edgeTargetColumn: publish<this, string>;
|
|
44
|
+
@publish("", "set", "Edge target ID column", function (this: SankeyGraph) { return this.edgeColumns(); }, { optional: true })
|
|
45
|
+
edgeWeightColumn: publish<this, string>;
|
|
46
|
+
|
|
47
|
+
protected _d3Sankey: any;
|
|
48
|
+
protected _selection: any;
|
|
49
|
+
_palette: any;
|
|
50
|
+
|
|
51
|
+
constructor() {
|
|
52
|
+
super();
|
|
53
|
+
Utility.SimpleSelectionMixin.call(this);
|
|
54
|
+
|
|
55
|
+
this._drawStartPos = "origin";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private _prevVertices: readonly VertexBaseProps[] = [];
|
|
59
|
+
private _masterVertices: VertexBaseProps[] = [];
|
|
60
|
+
private _masterVerticesMap: { [key: string]: VertexBaseProps } = {};
|
|
61
|
+
mergeVertices() {
|
|
62
|
+
const columns = this.vertexColumns();
|
|
63
|
+
const annotationColumns = this.vertexAnnotationColumns();
|
|
64
|
+
const catIdx = this.indexOf(columns, this.vertexCategoryColumn(), "category");
|
|
65
|
+
const idIdx = this.indexOf(columns, this.vertexIDColumn(), "id");
|
|
66
|
+
const labelIdx = this.indexOf(columns, this.vertexLabelColumn(), "label");
|
|
67
|
+
const centroidIdx = this.indexOf(columns, this.vertexCentroidColumn(), "centroid");
|
|
68
|
+
const vertexTooltipIdx = this.indexOf(columns, this.vertexTooltipColumn(), "tooltip");
|
|
69
|
+
const annotationIdxs = annotationColumns.map(ac => this.indexOf(columns, ac.columnID(), ""));
|
|
70
|
+
const vertices: VertexBaseProps[] = this.vertices().map((v): VertexBaseProps => {
|
|
71
|
+
return {
|
|
72
|
+
categoryID: "" + v[catIdx],
|
|
73
|
+
id: "" + v[idIdx],
|
|
74
|
+
text: "" + v[labelIdx],
|
|
75
|
+
tooltip: "" + v[vertexTooltipIdx],
|
|
76
|
+
origData: toJsonObj(v, columns),
|
|
77
|
+
centroid: !!v[centroidIdx],
|
|
78
|
+
annotationIDs: annotationIdxs.map((ai, i) => !!v[ai] ? annotationColumns[i].annotationID() : undefined).filter(a => !!a)
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
const diff = compare2(this._prevVertices, vertices, d => d.id);
|
|
82
|
+
diff.exit.forEach(item => {
|
|
83
|
+
this._masterVertices = this._masterVertices.filter(i => i.id !== item.id);
|
|
84
|
+
});
|
|
85
|
+
diff.enter.forEach(item => {
|
|
86
|
+
this._masterVertices.push(item);
|
|
87
|
+
this._masterVerticesMap[item.id] = item;
|
|
88
|
+
});
|
|
89
|
+
this._prevVertices = vertices;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
indexOf(columns: readonly string[], column: string, defColumn: string = ""): number {
|
|
93
|
+
const retVal = columns.indexOf(column);
|
|
94
|
+
return retVal >= 0 ? retVal : columns.indexOf(defColumn);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
protected _prevEdges: readonly EdgeBaseProps[] = [];
|
|
98
|
+
protected _masterEdges: EdgeBaseProps[] = [];
|
|
99
|
+
mergeEdges() {
|
|
100
|
+
const columns = this.edgeColumns();
|
|
101
|
+
const idIdx = this.indexOf(columns, this.edgeIDColumn(), "id");
|
|
102
|
+
const sourceIdx = this.indexOf(columns, this.edgeSourceColumn(), "source");
|
|
103
|
+
const targetIdx = this.indexOf(columns, this.edgeTargetColumn(), "target");
|
|
104
|
+
const labelIdx = this.indexOf(columns, this.edgeLabelColumn(), "label");
|
|
105
|
+
const weightIdx = this.indexOf(columns, this.edgeWeightColumn(), "weight");
|
|
106
|
+
const edges: EdgeBaseProps[] = this.edges().map(e => {
|
|
107
|
+
const source = this._masterVerticesMap["" + e[sourceIdx]];
|
|
108
|
+
if (!source) console.error(`Invalid edge source entity "${e[sourceIdx]}" does not exist.`);
|
|
109
|
+
const target = this._masterVerticesMap["" + e[targetIdx]];
|
|
110
|
+
if (!target) console.error(`Invalid edge target entity "${e[targetIdx]}" does not exist.`);
|
|
111
|
+
return {
|
|
112
|
+
type: "edge",
|
|
113
|
+
id: idIdx >= 0 ? "" + e[idIdx] : "" + e[sourceIdx] + "->" + e[targetIdx],
|
|
114
|
+
source,
|
|
115
|
+
target,
|
|
116
|
+
value: +e[weightIdx] || 0,
|
|
117
|
+
label: labelIdx >= 0 ? ("" + e[labelIdx]) : "",
|
|
118
|
+
origData: toJsonObj(e, columns)
|
|
119
|
+
};
|
|
120
|
+
}).filter(e => e.source && e.target);
|
|
121
|
+
const diff = compare2(this._masterEdges, edges, d => d.id);
|
|
122
|
+
diff.exit.forEach(item => {
|
|
123
|
+
this._masterEdges = this._masterEdges.filter(i => i.id !== item.id);
|
|
124
|
+
});
|
|
125
|
+
diff.enter.forEach(item => {
|
|
126
|
+
this._masterEdges.push(item);
|
|
127
|
+
});
|
|
128
|
+
this._prevEdges = edges;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
sankeyData() {
|
|
132
|
+
this.mergeVertices();
|
|
133
|
+
this.mergeEdges();
|
|
134
|
+
return {
|
|
135
|
+
vertices: this._masterVertices,
|
|
136
|
+
edges: this._masterEdges
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
enter(domNode, element) {
|
|
141
|
+
super.enter(domNode, element);
|
|
142
|
+
|
|
143
|
+
this._d3Sankey = d3Sankey();
|
|
144
|
+
this._selection.widgetElement(element);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
update(domNode, element) {
|
|
148
|
+
super.update(domNode, element);
|
|
149
|
+
|
|
150
|
+
this._palette = this._palette.switch(this.paletteID());
|
|
151
|
+
|
|
152
|
+
const strokeWidth = this.vertexStrokeWidth();
|
|
153
|
+
|
|
154
|
+
const sankeyData = this.sankeyData();
|
|
155
|
+
this._d3Sankey
|
|
156
|
+
.nodeId(d => d.id)
|
|
157
|
+
.extent([
|
|
158
|
+
[0, 0],
|
|
159
|
+
[this.width(), this.height()]
|
|
160
|
+
])
|
|
161
|
+
// .nodeAlign(sankeyCenter)
|
|
162
|
+
// .nodeWidth(this.vertexWidth())
|
|
163
|
+
// .nodePadding(this.vertexPadding())
|
|
164
|
+
;
|
|
165
|
+
if (sankeyData.vertices.length > 0) {
|
|
166
|
+
this._d3Sankey({
|
|
167
|
+
nodes: sankeyData.vertices,
|
|
168
|
+
links: sankeyData.edges
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const context = this;
|
|
172
|
+
|
|
173
|
+
// Links ---
|
|
174
|
+
const link = element.selectAll(".link").data(sankeyData.edges);
|
|
175
|
+
link.enter().append("path")
|
|
176
|
+
.attr("class", "link")
|
|
177
|
+
.each(function () {
|
|
178
|
+
d3Select(this)
|
|
179
|
+
.append("title")
|
|
180
|
+
;
|
|
181
|
+
})
|
|
182
|
+
.merge(link)
|
|
183
|
+
.attr("d", d3SankeyLinkHorizontal())
|
|
184
|
+
.style("stroke-width", (d: any) => Math.max(1, d.width))
|
|
185
|
+
// .sort(function (a, b) { return b.width - a.width; })
|
|
186
|
+
.select("title")
|
|
187
|
+
.text(function (d) {
|
|
188
|
+
return d.source.text + " → " + d.target.text + "\n" + d.value;
|
|
189
|
+
})
|
|
190
|
+
;
|
|
191
|
+
link.exit().remove();
|
|
192
|
+
|
|
193
|
+
// Nodes ---
|
|
194
|
+
const node = element.selectAll(".node").data(sankeyData.vertices);
|
|
195
|
+
node.enter().append("g")
|
|
196
|
+
.attr("class", "node")
|
|
197
|
+
.call(this._selection.enter.bind(this._selection))
|
|
198
|
+
.on("click", function (d) {
|
|
199
|
+
context.click(d.origData, "", context._selection.selected(this));
|
|
200
|
+
})
|
|
201
|
+
.on("dblclick", function (d) {
|
|
202
|
+
context.dblclick(d.origData, "", context._selection.selected(this));
|
|
203
|
+
})
|
|
204
|
+
.each(function () {
|
|
205
|
+
const gElement = d3Select(this);
|
|
206
|
+
gElement.append("rect");
|
|
207
|
+
gElement.append("text");
|
|
208
|
+
})
|
|
209
|
+
/*
|
|
210
|
+
.call(d3.behavior.drag()
|
|
211
|
+
.origin(function (d) { return d; })
|
|
212
|
+
.on("dragstart", function () {
|
|
213
|
+
this.parentNode.appendChild(this);
|
|
214
|
+
})
|
|
215
|
+
.on("drag", dragmove)
|
|
216
|
+
)
|
|
217
|
+
*/
|
|
218
|
+
.merge(node)
|
|
219
|
+
.attr("transform", function (d) {
|
|
220
|
+
let _x = 0;
|
|
221
|
+
let _y = 0;
|
|
222
|
+
if (d.x0) _x = d.x0;
|
|
223
|
+
if (d.y0) _y = d.y0;
|
|
224
|
+
return "translate(" + (_x + strokeWidth) + "," + (_y + strokeWidth) + ")";
|
|
225
|
+
})
|
|
226
|
+
.each(function () {
|
|
227
|
+
const n = d3Select(this);
|
|
228
|
+
n.select("rect")
|
|
229
|
+
.attr("height", (d: any) => {
|
|
230
|
+
return d.y1 - d.y0;
|
|
231
|
+
})
|
|
232
|
+
.attr("width", (d: any) => d.x1 - d.x0)
|
|
233
|
+
.style("fill", function (d: any) { return context._palette(d.categoryID); })
|
|
234
|
+
.style("stroke", function (d: any) { return context.vertexStrokeColor(); })
|
|
235
|
+
.style("stroke-width", function (d: any) { return strokeWidth; })
|
|
236
|
+
.style("cursor", (context.xAxisMovement() || context.yAxisMovement()) ? null : "default")
|
|
237
|
+
;
|
|
238
|
+
n.select("text")
|
|
239
|
+
.attr("x", -6)
|
|
240
|
+
.attr("y", function (d: any) {
|
|
241
|
+
return (d.y1 - d.y0) / 2;
|
|
242
|
+
})
|
|
243
|
+
.attr("dy", ".35em")
|
|
244
|
+
.attr("text-anchor", "end")
|
|
245
|
+
.attr("transform", null)
|
|
246
|
+
.text(function (d: any) {
|
|
247
|
+
return d.text;
|
|
248
|
+
})
|
|
249
|
+
.filter(function (d: any) {
|
|
250
|
+
return d.x0 < context.width() / 2;
|
|
251
|
+
})
|
|
252
|
+
.attr("x", 6 + context._d3Sankey.nodeWidth())
|
|
253
|
+
.attr("text-anchor", "start")
|
|
254
|
+
;
|
|
255
|
+
});
|
|
256
|
+
node.exit().remove();
|
|
257
|
+
|
|
258
|
+
/*
|
|
259
|
+
function dragmove(d) {
|
|
260
|
+
var gElement = d3.select(this);
|
|
261
|
+
if (context.xAxisMovement()) {
|
|
262
|
+
d.x = Math.max(0, Math.min(context.width() - d.dx, d3.event.x));
|
|
263
|
+
}
|
|
264
|
+
if (context.yAxisMovement()) {
|
|
265
|
+
d.y = Math.max(0, Math.min(context.height() - d.dy, d3.event.y));
|
|
266
|
+
}
|
|
267
|
+
gElement.attr("transform", "translate(" + d.x + "," + d.y + ")");
|
|
268
|
+
context._d3Sankey.relayout();
|
|
269
|
+
link.attr("d", context._d3SankeyPath);
|
|
270
|
+
|
|
271
|
+
gElement.select("text")
|
|
272
|
+
.attr("x", -6)
|
|
273
|
+
.attr("y", function (d) { return d.dy / 2; })
|
|
274
|
+
.attr("dy", ".35em")
|
|
275
|
+
.attr("text-anchor", "end")
|
|
276
|
+
.attr("transform", null)
|
|
277
|
+
.text(function (d) { return d.name; })
|
|
278
|
+
.filter(function (d) { return d.x < context.width() / 2; })
|
|
279
|
+
.attr("x", 6 + context._d3Sankey.nodeWidth())
|
|
280
|
+
.attr("text-anchor", "start")
|
|
281
|
+
;
|
|
282
|
+
}
|
|
283
|
+
*/
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
paletteID: { (): string; (_: string): SankeyGraph; };
|
|
287
|
+
vertexStrokeWidth: { (): number; (_: number): SankeyGraph; };
|
|
288
|
+
vertexStrokeColor: { (): string; (_: string): SankeyGraph; };
|
|
289
|
+
vertexWidth: { (): number; (_: number): SankeyGraph; };
|
|
290
|
+
vertexPadding: { (): number; (_: number): SankeyGraph; };
|
|
291
|
+
xAxisMovement: { (): boolean; (_: boolean): SankeyGraph; };
|
|
292
|
+
yAxisMovement: { (): boolean; (_: boolean): SankeyGraph; };
|
|
293
|
+
|
|
294
|
+
exit(domNode, element) {
|
|
295
|
+
super.exit(domNode, element);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Events ---
|
|
299
|
+
click(row, column, selected) {
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
dblclick(row, column, selected) {
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
SankeyGraph.prototype._class += " graph_SankeyGraph";
|
|
306
|
+
SankeyGraph.prototype.mixin(Utility.SimpleSelectionMixin);
|
|
307
|
+
|
|
308
|
+
SankeyGraph.prototype._palette = Palette.ordinal("category10");
|
|
309
|
+
|
|
310
|
+
SankeyGraph.prototype.publish("paletteID", "category10", "set", "Color palette for this widget", SankeyGraph.prototype._palette.switch());
|
|
311
|
+
SankeyGraph.prototype.publish("vertexStrokeWidth", 1, "number", "Vertex Stroke Width");
|
|
312
|
+
SankeyGraph.prototype.publish("vertexStrokeColor", "darkgray", "string", "Vertex Stroke Color");
|
|
313
|
+
SankeyGraph.prototype.publish("vertexWidth", 36, "number", "Vertex Width");
|
|
314
|
+
SankeyGraph.prototype.publish("vertexPadding", 20, "number", "Vertex Padding");
|
|
315
|
+
SankeyGraph.prototype.publish("xAxisMovement", false, "boolean", "Enable x-axis movement");
|
|
316
|
+
SankeyGraph.prototype.publish("yAxisMovement", false, "boolean", "Enable y-axis movement");
|