apextree 1.1.0 → 1.3.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/README.md +60 -62
- package/apextree.es.min.js +7127 -0
- package/apextree.min.js +1 -0
- package/demo/bottom_to_top_view.html +9 -7
- package/demo/custom_font_options.html +3 -6
- package/demo/dynamic_view_change.html +3 -6
- package/demo/expand_collapse_nodes.html +2 -2
- package/demo/left_to_right_view.html +2 -2
- package/demo/per_node_options.html +3 -6
- package/demo/right_to_left_view.html +2 -2
- package/demo/top_to_bottom_view.html +2 -2
- package/index.d.ts +1 -0
- package/lib/ApexTree.d.ts +9 -0
- package/lib/models/Export.d.ts +9 -0
- package/lib/models/Graph.d.ts +35 -0
- package/lib/models/Paper.d.ts +32 -0
- package/lib/models/Toolbar.d.ts +15 -0
- package/lib/models/index.d.ts +2 -0
- package/lib/settings/DirectionConfig.d.ts +47 -0
- package/lib/settings/Options.d.ts +49 -0
- package/lib/utils/EdgeUtils.d.ts +2 -0
- package/lib/utils/GraphUtils.d.ts +10 -0
- package/package.json +19 -27
- package/.editorconfig +0 -7
- package/.eslintignore +0 -2
- package/.eslintrc +0 -9
- package/.prettierrc +0 -5
- package/.vscode/settings.json +0 -5
- package/LICENSE +0 -674
- package/dist/ApexTree.js +0 -608
- package/dist/ApexTree.min.js +0 -2
- package/dist/ApexTree.min.js.LICENSE.txt +0 -10
- package/globals.d.ts +0 -4
- package/src/ApexTree.ts +0 -34
- package/src/icons/add-circle.svg +0 -3
- package/src/icons/export-icon.svg +0 -2
- package/src/icons/fit-screen-icon.svg +0 -9
- package/src/icons/minus-circle.svg +0 -3
- package/src/icons/zoom-in-icon.svg +0 -8
- package/src/icons/zoom-out-icon.svg +0 -8
- package/src/models/Export.ts +0 -39
- package/src/models/Graph.ts +0 -284
- package/src/models/Paper.ts +0 -117
- package/src/models/Toolbar.ts +0 -71
- package/src/models/index.ts +0 -2
- package/src/settings/DirectionConfig.ts +0 -227
- package/src/settings/Options.ts +0 -98
- package/src/utils/EdgeUtils.ts +0 -30
- package/src/utils/GraphUtils.ts +0 -135
- package/tsconfig.json +0 -28
- package/webpack.common.ts +0 -68
- package/webpack.config.ts +0 -7
- package/webpack.dev.ts +0 -12
- package/webpack.prod.ts +0 -9
- /package/{src/utils/index.ts → lib/utils/index.d.ts} +0 -0
package/src/models/Graph.ts
DELETED
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import { G, Path } from '@svgdotjs/svg.js';
|
|
2
|
-
import { flextree, FlextreeNode } from 'd3-flextree';
|
|
3
|
-
import { ExpandCollapseButtonSize, getEdge } from 'src/utils';
|
|
4
|
-
import {
|
|
5
|
-
generateStyles,
|
|
6
|
-
getTooltip,
|
|
7
|
-
getTooltipStyles,
|
|
8
|
-
highlightToPath,
|
|
9
|
-
updateTooltip,
|
|
10
|
-
} from 'src/utils';
|
|
11
|
-
import { Paper } from 'src/models/Paper';
|
|
12
|
-
import { DirectionConfig } from 'src/settings/DirectionConfig';
|
|
13
|
-
import {
|
|
14
|
-
FontOptions,
|
|
15
|
-
NodeOptions,
|
|
16
|
-
TooltipOptions,
|
|
17
|
-
TreeDirection,
|
|
18
|
-
TreeOptions,
|
|
19
|
-
} from 'src/settings/Options';
|
|
20
|
-
import addSvg from 'src/icons/add-circle.svg';
|
|
21
|
-
import minusSvg from 'src/icons/minus-circle.svg';
|
|
22
|
-
|
|
23
|
-
export interface GraphPoint {
|
|
24
|
-
readonly x: number;
|
|
25
|
-
readonly y: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface Node {
|
|
29
|
-
readonly id: string;
|
|
30
|
-
readonly name: string;
|
|
31
|
-
readonly children: Array<Node>;
|
|
32
|
-
readonly expanded: boolean;
|
|
33
|
-
readonly options?: NodeOptions & TooltipOptions & FontOptions;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface TreeNode<N> extends FlextreeNode<N> {
|
|
37
|
-
hiddenChildren: Array<TreeNode<N>> | undefined;
|
|
38
|
-
edge?: Path;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export class Graph extends Paper {
|
|
42
|
-
public options: TreeOptions;
|
|
43
|
-
public rootNode: TreeNode<Node>;
|
|
44
|
-
public element: HTMLElement;
|
|
45
|
-
constructor(element: HTMLElement, options: TreeOptions) {
|
|
46
|
-
super(element, options.width, options.height, options.canvasStyle);
|
|
47
|
-
this.element = element;
|
|
48
|
-
this.options = options;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
public construct(data: Node): void {
|
|
52
|
-
const { nodeWidth, nodeHeight, siblingSpacing, childrenSpacing } =
|
|
53
|
-
this.options;
|
|
54
|
-
const flexLayout = flextree({
|
|
55
|
-
nodeSize: () => {
|
|
56
|
-
return DirectionConfig[this.options.direction].nodeFlexSize({
|
|
57
|
-
nodeWidth,
|
|
58
|
-
nodeHeight,
|
|
59
|
-
siblingSpacing,
|
|
60
|
-
childrenSpacing,
|
|
61
|
-
});
|
|
62
|
-
},
|
|
63
|
-
spacing: 0,
|
|
64
|
-
});
|
|
65
|
-
const tree = flexLayout.hierarchy(data);
|
|
66
|
-
this.rootNode = flexLayout(tree) as any;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public renderNode(node: TreeNode<Node>, mainGroup: G) {
|
|
70
|
-
const options = this.options;
|
|
71
|
-
const {
|
|
72
|
-
nodeWidth,
|
|
73
|
-
nodeHeight,
|
|
74
|
-
nodeTemplate,
|
|
75
|
-
highlightOnHover,
|
|
76
|
-
borderRadius,
|
|
77
|
-
enableTooltip,
|
|
78
|
-
tooltipTemplate,
|
|
79
|
-
enableExpandCollapse,
|
|
80
|
-
} = options;
|
|
81
|
-
const {
|
|
82
|
-
tooltipId,
|
|
83
|
-
tooltipMaxWidth,
|
|
84
|
-
tooltipBGColor,
|
|
85
|
-
tooltipBorderColor,
|
|
86
|
-
fontSize,
|
|
87
|
-
fontWeight,
|
|
88
|
-
fontFamily,
|
|
89
|
-
fontColor,
|
|
90
|
-
borderWidth,
|
|
91
|
-
borderStyle,
|
|
92
|
-
borderColor,
|
|
93
|
-
nodeBGColor,
|
|
94
|
-
nodeStyle,
|
|
95
|
-
nodeClassName,
|
|
96
|
-
} = { ...options, ...node.data.options };
|
|
97
|
-
const { x, y } = DirectionConfig[options.direction].swap(node);
|
|
98
|
-
|
|
99
|
-
const graphInstance = this;
|
|
100
|
-
|
|
101
|
-
const group = Paper.drawGroup(x, y, node.data.id, node.parent?.data.id);
|
|
102
|
-
const nodeContent = nodeTemplate(
|
|
103
|
-
node.data[options.contentKey as keyof Node],
|
|
104
|
-
);
|
|
105
|
-
const object = Paper.drawTemplate(nodeContent, { nodeWidth, nodeHeight });
|
|
106
|
-
const groupStyle = generateStyles({
|
|
107
|
-
fontSize,
|
|
108
|
-
fontWeight,
|
|
109
|
-
fontFamily,
|
|
110
|
-
fontColor,
|
|
111
|
-
});
|
|
112
|
-
const borderStyles = generateStyles({
|
|
113
|
-
borderColor,
|
|
114
|
-
borderStyle,
|
|
115
|
-
borderWidth: `${borderWidth}px`,
|
|
116
|
-
borderRadius,
|
|
117
|
-
backgroundColor: nodeBGColor,
|
|
118
|
-
});
|
|
119
|
-
object.attr('style', borderStyles.concat(nodeStyle));
|
|
120
|
-
object.attr('class', nodeClassName);
|
|
121
|
-
group.attr('style', groupStyle);
|
|
122
|
-
group.add(object);
|
|
123
|
-
const nodes = this.rootNode.nodes;
|
|
124
|
-
|
|
125
|
-
if (highlightOnHover) {
|
|
126
|
-
group.on('mouseover', function () {
|
|
127
|
-
const self = this.node.dataset.self;
|
|
128
|
-
const selfNode = nodes.find((n) => n.data.id === self);
|
|
129
|
-
selfNode && highlightToPath(nodes, selfNode, true, options);
|
|
130
|
-
});
|
|
131
|
-
group.on('mouseout', function () {
|
|
132
|
-
const self = this.node.dataset.self;
|
|
133
|
-
const selfNode = nodes.find((n) => n.data.id === self);
|
|
134
|
-
selfNode && highlightToPath(nodes, selfNode, false, options);
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (enableTooltip) {
|
|
139
|
-
const tooltipContent = tooltipTemplate
|
|
140
|
-
? tooltipTemplate(node.data[this.options.contentKey as keyof Node])
|
|
141
|
-
: nodeContent;
|
|
142
|
-
group.on('mousemove', function (e: MouseEvent) {
|
|
143
|
-
const styles = getTooltipStyles(
|
|
144
|
-
e.pageX,
|
|
145
|
-
e.pageY,
|
|
146
|
-
tooltipMaxWidth,
|
|
147
|
-
tooltipBorderColor,
|
|
148
|
-
tooltipBGColor,
|
|
149
|
-
!tooltipTemplate,
|
|
150
|
-
);
|
|
151
|
-
updateTooltip(tooltipId, styles.join(' '), tooltipContent);
|
|
152
|
-
});
|
|
153
|
-
group.on('mouseout', function (e: MouseEvent) {
|
|
154
|
-
if ((e.relatedTarget as HTMLElement).tagName === 'svg') {
|
|
155
|
-
updateTooltip(tooltipId);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
mainGroup.add(group);
|
|
160
|
-
|
|
161
|
-
if (!node.children && !node.hiddenChildren) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (enableExpandCollapse) {
|
|
166
|
-
//add expand/collapse buttons
|
|
167
|
-
const expandButtonRadius = ExpandCollapseButtonSize / 2;
|
|
168
|
-
const buttonGroup = Paper.drawGroup(x + nodeWidth / 2 - expandButtonRadius, y + nodeHeight - expandButtonRadius, node.data.id);
|
|
169
|
-
const buttonClickArea = Paper.drawCircle({cx: expandButtonRadius, cy: expandButtonRadius, r: expandButtonRadius, style: 'fill: #FFF; cursor: pointer;'});
|
|
170
|
-
buttonGroup.data('expanded', false);
|
|
171
|
-
buttonGroup.add(buttonClickArea);
|
|
172
|
-
if (node.hiddenChildren) {
|
|
173
|
-
buttonGroup.add(addSvg as any);
|
|
174
|
-
} else {
|
|
175
|
-
buttonGroup.add(minusSvg as any);
|
|
176
|
-
}
|
|
177
|
-
buttonGroup.on('click', function() {
|
|
178
|
-
if (node.hiddenChildren) {
|
|
179
|
-
graphInstance.expand(this.node.dataset.self);
|
|
180
|
-
} else {
|
|
181
|
-
graphInstance.collapse(this.node.dataset.self);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
mainGroup.add(buttonGroup);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
public renderEdge(node: TreeNode<Node>, group: G) {
|
|
189
|
-
const { nodeWidth, nodeHeight } = this.options;
|
|
190
|
-
const edge = getEdge(node, nodeWidth, nodeHeight, this.options.direction);
|
|
191
|
-
if (!edge) return;
|
|
192
|
-
const path = Paper.drawPath(edge, {
|
|
193
|
-
id: `${node.data.id}-${node.parent?.data.id}`,
|
|
194
|
-
});
|
|
195
|
-
node.edge = path;
|
|
196
|
-
group.add(path);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
public collapse(nodeId: string) {
|
|
200
|
-
const nodes = this.rootNode.descendants();
|
|
201
|
-
const node = nodes.find((n: TreeNode<Node>) => n.data.id === nodeId);
|
|
202
|
-
if (node?.children) {
|
|
203
|
-
node.hiddenChildren = node.children;
|
|
204
|
-
node.hiddenChildren.forEach((child: any) => this.collapse(child));
|
|
205
|
-
node.children = undefined;
|
|
206
|
-
this.render({keepOldPosition: true});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
public expand(nodeId: string) {
|
|
211
|
-
const nodes = this.rootNode.descendants();
|
|
212
|
-
const node = nodes.find((n: any) => n.data.id === nodeId);
|
|
213
|
-
if (node?.hiddenChildren) {
|
|
214
|
-
node.children = node.hiddenChildren;
|
|
215
|
-
node.children.forEach((child: any) => this.expand(child));
|
|
216
|
-
node.hiddenChildren = undefined;
|
|
217
|
-
this.render({keepOldPosition: true});
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
public changeLayout(direction: TreeDirection = 'top') {
|
|
222
|
-
this.options = { ...this.options, direction };
|
|
223
|
-
this.render({keepOldPosition: false});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
public fitScreen() {
|
|
227
|
-
const { childrenSpacing, siblingSpacing } = this.options;
|
|
228
|
-
const { viewBoxDimensions } = DirectionConfig[this.options.direction];
|
|
229
|
-
const {
|
|
230
|
-
x,
|
|
231
|
-
y,
|
|
232
|
-
width: vWidth,
|
|
233
|
-
height: vHeight,
|
|
234
|
-
} = viewBoxDimensions({
|
|
235
|
-
rootNode: this.rootNode,
|
|
236
|
-
childrenSpacing,
|
|
237
|
-
siblingSpacing,
|
|
238
|
-
});
|
|
239
|
-
this.updateViewBox(x, y, vWidth, vHeight);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
public render({keepOldPosition = false} = {}): void {
|
|
243
|
-
const oldViewbox = this.canvas.viewbox();
|
|
244
|
-
this.clear();
|
|
245
|
-
const {
|
|
246
|
-
containerClassName,
|
|
247
|
-
enableTooltip,
|
|
248
|
-
tooltipId,
|
|
249
|
-
fontSize,
|
|
250
|
-
fontWeight,
|
|
251
|
-
fontFamily,
|
|
252
|
-
fontColor,
|
|
253
|
-
} = this.options;
|
|
254
|
-
const globalStyle = generateStyles({
|
|
255
|
-
fontSize,
|
|
256
|
-
fontWeight,
|
|
257
|
-
fontFamily,
|
|
258
|
-
fontColor,
|
|
259
|
-
});
|
|
260
|
-
const mainGroup = Paper.drawGroup(0, 0, containerClassName);
|
|
261
|
-
mainGroup.attr('style', globalStyle);
|
|
262
|
-
mainGroup.id(containerClassName);
|
|
263
|
-
|
|
264
|
-
const nodes = this.rootNode.nodes;
|
|
265
|
-
nodes.forEach((node: any) => {
|
|
266
|
-
this.renderEdge(node, mainGroup);
|
|
267
|
-
});
|
|
268
|
-
nodes.forEach((node: any) => {
|
|
269
|
-
this.renderNode(node, mainGroup);
|
|
270
|
-
});
|
|
271
|
-
this.add(mainGroup);
|
|
272
|
-
this.fitScreen();
|
|
273
|
-
|
|
274
|
-
if (keepOldPosition) {
|
|
275
|
-
this.updateViewBox(oldViewbox.x, oldViewbox.y, oldViewbox.width, oldViewbox.height);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (enableTooltip) {
|
|
279
|
-
const tooltipElement = getTooltip(tooltipId);
|
|
280
|
-
const body = document.body || document.getElementsByTagName('body')[0];
|
|
281
|
-
body.append(tooltipElement);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
package/src/models/Paper.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { Circle } from '@svgdotjs/svg.js';
|
|
2
|
-
import { CircleAttr } from '@svgdotjs/svg.js';
|
|
3
|
-
import {Element, ForeignObject, G, Path, Rect, Svg, SVG, Text, TextAttr} from '@svgdotjs/svg.js';
|
|
4
|
-
import '@svgdotjs/svg.panzoom.js';
|
|
5
|
-
import {DefaultOptions, NodeOptions} from 'src/settings/Options';
|
|
6
|
-
|
|
7
|
-
export class Paper {
|
|
8
|
-
private readonly width: number;
|
|
9
|
-
private readonly height: number;
|
|
10
|
-
public canvas: Svg;
|
|
11
|
-
constructor(element: HTMLElement, width: number, height: number, canvasStyle: string) {
|
|
12
|
-
this.width = width;
|
|
13
|
-
this.height = height;
|
|
14
|
-
this.canvas = SVG()
|
|
15
|
-
.addTo(element)
|
|
16
|
-
.size(width, height)
|
|
17
|
-
.viewbox(`0 0 ${width} ${height}`)
|
|
18
|
-
.panZoom({zoomFactor: 0.2, zoomMin: 0.1})
|
|
19
|
-
.attr({style: canvasStyle});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public add(element: Element) {
|
|
23
|
-
this.canvas.add(element);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public resetViewBox(): void {
|
|
27
|
-
this.canvas.viewbox(`0 0 ${this.width} ${this.height}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public updateViewBox(x: number, y: number, width: number, height: number): void {
|
|
31
|
-
this.canvas.viewbox(`${x} ${y} ${width} ${height}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public zoom(zoomFactor: number): void {
|
|
35
|
-
const newZoomVal = this.canvas.zoom() + zoomFactor;
|
|
36
|
-
if (newZoomVal >= 0.1) {
|
|
37
|
-
this.canvas.zoom(newZoomVal);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
public get(selector: string): Element {
|
|
42
|
-
return this.canvas.findOne(selector) as any;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public clear() {
|
|
46
|
-
this.canvas.clear().viewbox(`0 0 ${this.width} ${this.height}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
static drawRect({
|
|
50
|
-
x1 = undefined,
|
|
51
|
-
y1 = undefined,
|
|
52
|
-
width = 0,
|
|
53
|
-
height = 0,
|
|
54
|
-
radius = 0,
|
|
55
|
-
color = '#fefefe',
|
|
56
|
-
opacity = 1,
|
|
57
|
-
} = {}): Rect {
|
|
58
|
-
const rect = new Rect();
|
|
59
|
-
rect.attr({
|
|
60
|
-
x: x1 ?? undefined,
|
|
61
|
-
y: y1 ?? undefined,
|
|
62
|
-
width,
|
|
63
|
-
height,
|
|
64
|
-
rx: radius,
|
|
65
|
-
ry: radius,
|
|
66
|
-
opacity,
|
|
67
|
-
});
|
|
68
|
-
rect.fill(color);
|
|
69
|
-
return rect;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
static drawCircle(attributes: CircleAttr = {}): Circle {
|
|
73
|
-
const circle = new Circle();
|
|
74
|
-
circle.attr(attributes);
|
|
75
|
-
return circle;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
static drawText(text: string = '', {x, y, dx, dy}: Partial<TextAttr>): Text {
|
|
79
|
-
const textSvg = new Text();
|
|
80
|
-
textSvg.font({fill: '#f06'});
|
|
81
|
-
textSvg.tspan(text);
|
|
82
|
-
|
|
83
|
-
if (x !== undefined && y !== undefined) {
|
|
84
|
-
textSvg.move(x, y);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (dx !== undefined && dy !== undefined) {
|
|
88
|
-
textSvg.attr({dx, dy});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return textSvg;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
static drawTemplate(template: any, {nodeWidth, nodeHeight}: Partial<NodeOptions> = {}): ForeignObject {
|
|
95
|
-
const object = new ForeignObject({
|
|
96
|
-
width: nodeWidth,
|
|
97
|
-
height: nodeHeight,
|
|
98
|
-
});
|
|
99
|
-
const element = SVG(template);
|
|
100
|
-
element.node.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
|
101
|
-
object.add(element);
|
|
102
|
-
return object;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
static drawGroup(x: number = 0, y: number = 0, id?: string, parent?: string): G {
|
|
106
|
-
const group = new G();
|
|
107
|
-
group.attr({transform: `translate(${x}, ${y})`, 'data-self': id, 'data-parent': parent});
|
|
108
|
-
return group;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
static drawPath(pathString: string, {id = '', borderColor = DefaultOptions.borderColor} = {}): Path {
|
|
112
|
-
const path = new Path({d: pathString});
|
|
113
|
-
path.id(id);
|
|
114
|
-
path.fill('none').stroke({color: borderColor, width: 1});
|
|
115
|
-
return path;
|
|
116
|
-
}
|
|
117
|
-
}
|
package/src/models/Toolbar.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import ZoomInIcon from 'src/icons/zoom-in-icon.svg';
|
|
2
|
-
import ZoomOutIcon from 'src/icons/zoom-out-icon.svg';
|
|
3
|
-
import FitScreenIcon from 'src/icons/fit-screen-icon.svg';
|
|
4
|
-
import ExportIcon from 'src/icons/export-icon.svg';
|
|
5
|
-
import {Export} from 'src/models/Export';
|
|
6
|
-
import {Graph} from 'src/models/index';
|
|
7
|
-
|
|
8
|
-
export enum ToolbarItem {
|
|
9
|
-
ZoomIn = 'zoom-in',
|
|
10
|
-
ZoomOut = 'zoom-out',
|
|
11
|
-
FitScreen = 'fit-screen',
|
|
12
|
-
Export = 'export',
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const ToolBarIcons = {
|
|
16
|
-
[ToolbarItem.ZoomIn]: ZoomInIcon,
|
|
17
|
-
[ToolbarItem.ZoomOut]: ZoomOutIcon,
|
|
18
|
-
[ToolbarItem.FitScreen]: FitScreenIcon,
|
|
19
|
-
[ToolbarItem.Export]: ExportIcon,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const ZoomChangeFactor = 0.1;
|
|
23
|
-
|
|
24
|
-
export class Toolbar {
|
|
25
|
-
private readonly export: Export;
|
|
26
|
-
constructor(
|
|
27
|
-
public element: HTMLElement | null,
|
|
28
|
-
public graph: Graph,
|
|
29
|
-
) {
|
|
30
|
-
this.export = new Export(graph);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public render(): void {
|
|
34
|
-
const container = document.createElement('div');
|
|
35
|
-
container.id = 'toolbar';
|
|
36
|
-
container.setAttribute('style', 'display: flex;gap: 5px;position: absolute;right: 20px;top: 20px;');
|
|
37
|
-
|
|
38
|
-
const btnZoomIn = this.createToolbarItem(ToolbarItem.ZoomIn, ToolBarIcons[ToolbarItem.ZoomIn]);
|
|
39
|
-
const btnZoomOut = this.createToolbarItem(ToolbarItem.ZoomOut, ToolBarIcons[ToolbarItem.ZoomOut]);
|
|
40
|
-
const btnFitScreen = this.createToolbarItem(ToolbarItem.FitScreen, ToolBarIcons[ToolbarItem.FitScreen]);
|
|
41
|
-
const btnExport = this.createToolbarItem(ToolbarItem.Export, ToolBarIcons[ToolbarItem.Export]);
|
|
42
|
-
|
|
43
|
-
btnZoomIn.addEventListener('click', () => {
|
|
44
|
-
this.graph.zoom(ZoomChangeFactor);
|
|
45
|
-
});
|
|
46
|
-
btnZoomOut.addEventListener('click', () => {
|
|
47
|
-
this.graph.zoom(-ZoomChangeFactor);
|
|
48
|
-
});
|
|
49
|
-
btnFitScreen.addEventListener('click', () => {
|
|
50
|
-
this.graph.fitScreen();
|
|
51
|
-
});
|
|
52
|
-
btnExport.addEventListener('click', () => {
|
|
53
|
-
this.export.exportToSVG();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
container.append(btnZoomIn, btnZoomOut, btnFitScreen, btnExport);
|
|
57
|
-
|
|
58
|
-
this.element?.append(container);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public createToolbarItem(itemName: ToolbarItem, icon: string): HTMLElement {
|
|
62
|
-
const itemContainer = document.createElement('div');
|
|
63
|
-
itemContainer.id = itemName;
|
|
64
|
-
itemContainer.innerHTML = icon;
|
|
65
|
-
itemContainer.setAttribute(
|
|
66
|
-
'style',
|
|
67
|
-
'width: 30px;height: 30px;display: flex;align-items: center;justify-content: center;border: 1px solid #BCBCBC;background-color: #FFFFFF;cursor: pointer;',
|
|
68
|
-
);
|
|
69
|
-
return itemContainer;
|
|
70
|
-
}
|
|
71
|
-
}
|
package/src/models/index.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import {GraphPoint} from 'src/models';
|
|
2
|
-
import {Node, TreeNode} from 'src/models/Graph';
|
|
3
|
-
|
|
4
|
-
/* Horizontal diagonal generation algorithm - https://observablehq.com/@bumbeishvili/curved-edges-compact-horizontal */
|
|
5
|
-
export const curvedEdgesHorizontal = (s: GraphPoint, t: GraphPoint, m: GraphPoint): string => {
|
|
6
|
-
// Define source and target x,y coordinates
|
|
7
|
-
const x = s.x;
|
|
8
|
-
const y = s.y;
|
|
9
|
-
const ex = t.x;
|
|
10
|
-
const ey = t.y;
|
|
11
|
-
|
|
12
|
-
const mx = m?.x ?? x;
|
|
13
|
-
const my = m?.y ?? y;
|
|
14
|
-
|
|
15
|
-
// Values in case of top reversed and left reversed diagonals
|
|
16
|
-
const xrvs = ex - x < 0 ? -1 : 1;
|
|
17
|
-
const yrvs = ey - y < 0 ? -1 : 1;
|
|
18
|
-
|
|
19
|
-
// Define preferred curve radius
|
|
20
|
-
const rdef = 35;
|
|
21
|
-
|
|
22
|
-
// Reduce curve radius, if source-target x space is smaller
|
|
23
|
-
let r = Math.abs(ex - x) / 2 < rdef ? Math.abs(ex - x) / 2 : rdef;
|
|
24
|
-
|
|
25
|
-
// Further reduce curve radius, is y space is more small
|
|
26
|
-
r = Math.abs(ey - y) / 2 < r ? Math.abs(ey - y) / 2 : r;
|
|
27
|
-
|
|
28
|
-
// Defin width and height of link, excluding radius
|
|
29
|
-
// const h = Math.abs(ey - y) / 2 - r;
|
|
30
|
-
const w = Math.abs(ex - x) / 2 - r;
|
|
31
|
-
|
|
32
|
-
// Build and return custom arc command
|
|
33
|
-
const pathArray = [
|
|
34
|
-
`M ${mx} ${my}`,
|
|
35
|
-
`L ${mx} ${y}`,
|
|
36
|
-
`L ${x} ${y}`,
|
|
37
|
-
`L ${x + w * xrvs} ${y}`,
|
|
38
|
-
`C ${x + w * xrvs + r * xrvs} ${y} ${x + w * xrvs + r * xrvs} ${y} ${x + w * xrvs + r * xrvs} ${y + r * yrvs}`,
|
|
39
|
-
`L ${x + w * xrvs + r * xrvs} ${ey - r * yrvs}`,
|
|
40
|
-
`C ${x + w * xrvs + r * xrvs} ${ey} ${x + w * xrvs + r * xrvs} ${ey} ${ex - w * xrvs} ${ey}`,
|
|
41
|
-
`L ${ex} ${ey}`,
|
|
42
|
-
];
|
|
43
|
-
return pathArray.join(' ');
|
|
44
|
-
};
|
|
45
|
-
/* Vertical diagonal generation algorithm - https://observablehq.com/@bumbeishvili/curved-edges-compacty-vertical */
|
|
46
|
-
export const curvedEdgesVertical = (s: GraphPoint, t: GraphPoint, m: GraphPoint, offsets = {sy: 0}): string => {
|
|
47
|
-
const x = s.x;
|
|
48
|
-
let y = s.y;
|
|
49
|
-
|
|
50
|
-
const ex = t.x;
|
|
51
|
-
const ey = t.y;
|
|
52
|
-
|
|
53
|
-
const mx = m?.x ?? x;
|
|
54
|
-
const my = m?.y ?? y;
|
|
55
|
-
|
|
56
|
-
const xrvs = ex - x < 0 ? -1 : 1;
|
|
57
|
-
const yrvs = ey - y < 0 ? -1 : 1;
|
|
58
|
-
|
|
59
|
-
y += offsets.sy;
|
|
60
|
-
|
|
61
|
-
const rdef = 35;
|
|
62
|
-
let r = Math.abs(ex - x) / 2 < rdef ? Math.abs(ex - x) / 2 : rdef;
|
|
63
|
-
|
|
64
|
-
r = Math.abs(ey - y) / 2 < r ? Math.abs(ey - y) / 2 : r;
|
|
65
|
-
|
|
66
|
-
const h = Math.abs(ey - y) / 2 - r;
|
|
67
|
-
const w = Math.abs(ex - x) - r * 2;
|
|
68
|
-
//w=0;
|
|
69
|
-
const pathArray = [
|
|
70
|
-
`M ${mx} ${my}`,
|
|
71
|
-
`L ${x} ${my}`,
|
|
72
|
-
`L ${x} ${y}`,
|
|
73
|
-
`L ${x} ${y + h * yrvs}`,
|
|
74
|
-
`C ${x} ${y + h * yrvs + r * yrvs} ${x} ${y + h * yrvs + r * yrvs} ${x + r * xrvs} ${y + h * yrvs + r * yrvs}`,
|
|
75
|
-
`L ${x + w * xrvs + r * xrvs} ${y + h * yrvs + r * yrvs}`,
|
|
76
|
-
`C ${ex} ${y + h * yrvs + r * yrvs} ${ex} ${y + h * yrvs + r * yrvs} ${ex} ${ey - h * yrvs}`,
|
|
77
|
-
`L ${ex} ${ey}`,
|
|
78
|
-
];
|
|
79
|
-
return pathArray.join(' ');
|
|
80
|
-
};
|
|
81
|
-
export interface DirectionConfigProperties {
|
|
82
|
-
readonly containerX: (params: Partial<ConfigParams>) => number;
|
|
83
|
-
readonly containerY: (params: Partial<ConfigParams>) => number;
|
|
84
|
-
readonly edgeX: (params: Partial<ConfigParams>) => number;
|
|
85
|
-
readonly edgeY: (params: Partial<ConfigParams>) => number;
|
|
86
|
-
readonly edgeMidX: (params: Partial<ConfigParams>) => number;
|
|
87
|
-
readonly edgeMidY: (params: Partial<ConfigParams>) => number;
|
|
88
|
-
readonly edgeParentX: (params: Partial<ConfigParams>) => number;
|
|
89
|
-
readonly edgeParentY: (params: Partial<ConfigParams>) => number;
|
|
90
|
-
readonly nodeFlexSize: (params: Partial<ConfigParams>) => [number, number];
|
|
91
|
-
readonly calculateEdge: (s: GraphPoint, t: GraphPoint, m: GraphPoint, offsets: any) => string;
|
|
92
|
-
readonly swap: (node: TreeNode<Node>) => {x: number; y: number};
|
|
93
|
-
readonly viewBoxDimensions: ({
|
|
94
|
-
rootNode,
|
|
95
|
-
childrenSpacing,
|
|
96
|
-
siblingSpacing,
|
|
97
|
-
}: {
|
|
98
|
-
rootNode: TreeNode<Node>;
|
|
99
|
-
childrenSpacing: number;
|
|
100
|
-
siblingSpacing: number;
|
|
101
|
-
}) => {x: number; y: number; width: number; height: number};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
interface ConfigParams {
|
|
105
|
-
readonly node: any;
|
|
106
|
-
readonly parent: any;
|
|
107
|
-
readonly width: number;
|
|
108
|
-
readonly height: number;
|
|
109
|
-
readonly nodeWidth: number;
|
|
110
|
-
readonly nodeHeight: number;
|
|
111
|
-
readonly siblingSpacing: number;
|
|
112
|
-
readonly childrenSpacing: number;
|
|
113
|
-
readonly x: number;
|
|
114
|
-
readonly y: number;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export const DirectionConfig: Record<string, DirectionConfigProperties> = {
|
|
118
|
-
top: {
|
|
119
|
-
containerX: ({width}: ConfigParams) => width / 2,
|
|
120
|
-
containerY: () => 0,
|
|
121
|
-
edgeX: ({node, nodeWidth}: ConfigParams) => node.x + nodeWidth / 2,
|
|
122
|
-
edgeY: ({node}: ConfigParams) => node.y,
|
|
123
|
-
edgeMidX: ({node, nodeWidth}: ConfigParams) => node.x + nodeWidth / 2,
|
|
124
|
-
edgeMidY: ({node}: ConfigParams) => node.y,
|
|
125
|
-
edgeParentX: ({parent, nodeWidth}: ConfigParams) => parent.x + nodeWidth / 2,
|
|
126
|
-
edgeParentY: ({parent, nodeHeight}: ConfigParams) => parent.y + nodeHeight,
|
|
127
|
-
nodeFlexSize: ({nodeWidth, nodeHeight, siblingSpacing, childrenSpacing}: ConfigParams): [number, number] => {
|
|
128
|
-
return [nodeWidth + siblingSpacing, nodeHeight + childrenSpacing];
|
|
129
|
-
},
|
|
130
|
-
calculateEdge: curvedEdgesVertical,
|
|
131
|
-
swap: (node: TreeNode<Node>) => ({
|
|
132
|
-
x: node.left,
|
|
133
|
-
y: node.top,
|
|
134
|
-
}),
|
|
135
|
-
viewBoxDimensions: ({rootNode, childrenSpacing, siblingSpacing}) => {
|
|
136
|
-
const {left, top, right, bottom} = rootNode.extents;
|
|
137
|
-
const width = Math.abs(left) + Math.abs(right);
|
|
138
|
-
const height = Math.abs(top) + Math.abs(bottom);
|
|
139
|
-
const x = Math.abs(left) + siblingSpacing / 2;
|
|
140
|
-
const y = (rootNode.ySize - childrenSpacing) / 2;
|
|
141
|
-
return {x: -x, y: -y, width, height};
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
bottom: {
|
|
145
|
-
containerX: ({width}: ConfigParams) => width / 2,
|
|
146
|
-
containerY: ({height, nodeHeight}: ConfigParams) => height - nodeHeight - 10,
|
|
147
|
-
edgeX: ({node, nodeWidth}: ConfigParams) => node.x + nodeWidth / 2,
|
|
148
|
-
edgeY: ({node, nodeHeight}: ConfigParams) => node.y + nodeHeight,
|
|
149
|
-
edgeMidX: ({node, nodeWidth}: ConfigParams) => node.x + nodeWidth / 2,
|
|
150
|
-
edgeMidY: ({node, nodeHeight}: ConfigParams) => node.y + nodeHeight,
|
|
151
|
-
edgeParentX: ({parent, nodeWidth}: ConfigParams) => parent.x + nodeWidth / 2,
|
|
152
|
-
edgeParentY: ({parent}: ConfigParams) => parent.y,
|
|
153
|
-
nodeFlexSize: ({nodeWidth, nodeHeight, siblingSpacing, childrenSpacing}: ConfigParams): [number, number] => {
|
|
154
|
-
return [nodeWidth + siblingSpacing, nodeHeight + childrenSpacing];
|
|
155
|
-
},
|
|
156
|
-
calculateEdge: curvedEdgesVertical,
|
|
157
|
-
swap: (node: TreeNode<Node>) =>
|
|
158
|
-
({
|
|
159
|
-
...node,
|
|
160
|
-
y: -node.y,
|
|
161
|
-
}) as TreeNode<Node>,
|
|
162
|
-
viewBoxDimensions: ({rootNode, childrenSpacing, siblingSpacing}) => {
|
|
163
|
-
const {left, top, right, bottom} = rootNode.extents;
|
|
164
|
-
const width = Math.abs(left) + Math.abs(right);
|
|
165
|
-
const height = Math.abs(top) + Math.abs(bottom);
|
|
166
|
-
const x = Math.abs(left) - (rootNode.xSize - siblingSpacing) / 2;
|
|
167
|
-
const y = height - rootNode.ySize + childrenSpacing / 2;
|
|
168
|
-
return {x: -x, y: -y, width, height};
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
left: {
|
|
172
|
-
containerX: () => 10,
|
|
173
|
-
containerY: ({height}: ConfigParams) => height / 2,
|
|
174
|
-
edgeX: ({node}: ConfigParams) => node.x,
|
|
175
|
-
edgeY: ({node, nodeHeight}: ConfigParams) => node.y + nodeHeight / 2,
|
|
176
|
-
edgeMidX: ({node}: ConfigParams) => node.x,
|
|
177
|
-
edgeMidY: ({node, nodeHeight}: ConfigParams) => node.y + nodeHeight / 2,
|
|
178
|
-
edgeParentX: ({parent, nodeWidth}: ConfigParams) => parent.x + nodeWidth,
|
|
179
|
-
edgeParentY: ({parent, nodeHeight}: ConfigParams) => parent.y + nodeHeight / 2,
|
|
180
|
-
nodeFlexSize: ({nodeWidth, nodeHeight, siblingSpacing, childrenSpacing}: ConfigParams) => {
|
|
181
|
-
return [nodeHeight + siblingSpacing, nodeWidth + childrenSpacing];
|
|
182
|
-
},
|
|
183
|
-
calculateEdge: curvedEdgesHorizontal,
|
|
184
|
-
swap: (node: TreeNode<Node>) =>
|
|
185
|
-
({
|
|
186
|
-
...node,
|
|
187
|
-
x: node.y,
|
|
188
|
-
y: node.x,
|
|
189
|
-
}) as TreeNode<Node>,
|
|
190
|
-
viewBoxDimensions: ({rootNode, childrenSpacing, siblingSpacing}) => {
|
|
191
|
-
const {left, top, right, bottom} = rootNode.extents;
|
|
192
|
-
const width = Math.abs(top) + Math.abs(bottom);
|
|
193
|
-
const height = Math.abs(left) + Math.abs(right);
|
|
194
|
-
const x = Math.abs(top) + childrenSpacing / 2;
|
|
195
|
-
const y = Math.abs(left) - siblingSpacing;
|
|
196
|
-
return {x: -x, y: -y, width, height};
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
right: {
|
|
200
|
-
containerX: ({width, nodeWidth}: ConfigParams) => width - nodeWidth - 10,
|
|
201
|
-
containerY: ({height}: ConfigParams) => height / 2,
|
|
202
|
-
edgeX: ({node, nodeWidth}: ConfigParams) => node.x + nodeWidth,
|
|
203
|
-
edgeY: ({node, nodeHeight}: ConfigParams) => node.y + nodeHeight / 2,
|
|
204
|
-
edgeMidX: ({node, nodeWidth}: ConfigParams) => node.x + nodeWidth,
|
|
205
|
-
edgeMidY: ({node, nodeHeight}: ConfigParams) => node.y + nodeHeight / 2,
|
|
206
|
-
edgeParentX: ({parent}: ConfigParams) => parent.x,
|
|
207
|
-
edgeParentY: ({parent, nodeHeight}: ConfigParams) => parent.y + nodeHeight / 2,
|
|
208
|
-
nodeFlexSize: ({nodeWidth, nodeHeight, siblingSpacing, childrenSpacing}: ConfigParams) => {
|
|
209
|
-
return [nodeHeight + siblingSpacing, nodeWidth + childrenSpacing];
|
|
210
|
-
},
|
|
211
|
-
calculateEdge: curvedEdgesHorizontal,
|
|
212
|
-
swap: (node: TreeNode<Node>) =>
|
|
213
|
-
({
|
|
214
|
-
...node,
|
|
215
|
-
x: -node.y,
|
|
216
|
-
y: node.x,
|
|
217
|
-
}) as TreeNode<Node>,
|
|
218
|
-
viewBoxDimensions: ({rootNode, siblingSpacing, childrenSpacing}) => {
|
|
219
|
-
const {left, top, right, bottom} = rootNode.extents;
|
|
220
|
-
const width = Math.abs(top) + Math.abs(bottom);
|
|
221
|
-
const height = Math.abs(left) + Math.abs(right);
|
|
222
|
-
const x = width - rootNode.ySize + childrenSpacing / 2;
|
|
223
|
-
const y = Math.abs(left) - siblingSpacing;
|
|
224
|
-
return {x: -x, y: -y, width, height};
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
};
|