├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── index.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── DataModel.ts
├── DefaultConfig.ts
├── EventObject.ts
├── GraphEditor.ts
├── GraphManager.ts
├── GraphViewer.ts
├── SelectionManager.ts
├── TemcEventSource.ts
├── Utils.ts
├── command
│ ├── AttributeChange.ts
│ ├── Change.ts
│ ├── Command.ts
│ ├── GeometryChange.ts
│ ├── GroupChange.ts
│ ├── NodeChange.ts
│ ├── OrderChange.ts
│ └── UndoRedoManager.ts
├── constants
│ ├── EventType.ts
│ ├── StyleMap.ts
│ └── TemcConstants.ts
├── index.all.node.ts
├── index.all.ts
├── lib
│ ├── GifuctLib.ts
│ └── gifuct.ts
├── model
│ ├── ArcNode.ts
│ ├── BaseConnectedLineNode.ts
│ ├── CircleNode.ts
│ ├── ContainerNode.ts
│ ├── EditableShapeNode.ts
│ ├── EllipseNode.ts
│ ├── GroupNode.ts
│ ├── ImageNode.ts
│ ├── LabelNode.ts
│ ├── LineArrowNode.ts
│ ├── LineNode.ts
│ ├── Node.ts
│ ├── PathNode.ts
│ ├── PenNode.ts
│ ├── PolylineArrowNode.ts
│ ├── PolylineNode.ts
│ ├── RectNode.ts
│ ├── RegularPolygonNode.ts
│ ├── RingNode.ts
│ ├── ShapeNode.ts
│ ├── StarNode.ts
│ ├── StraightConnectedLineNode.ts
│ ├── SymbolNode.ts
│ ├── TextNode.ts
│ └── WedgeNode.ts
├── shape
│ ├── AbstractShape.ts
│ ├── ArrowShape.ts
│ ├── BaseConnectedLineShape.ts
│ ├── BaseLineShape.ts
│ ├── BasePolylineShape.ts
│ ├── LineShape.ts
│ ├── PenShape.ts
│ ├── PolylineArrowShape.ts
│ ├── PolylineShape.ts
│ └── StraightConnectedLineShape.ts
├── snapGrid
│ └── SnapGrid.ts
└── types
│ ├── common.ts
│ ├── config.ts
│ ├── index.ts
│ └── snap-grid.ts
├── test
├── css
│ └── layui.css
├── js
│ ├── jquery.js
│ ├── jsoneditor.min.js
│ └── layui.js
├── performance-test.html
├── sandbox.html
├── shape
│ └── basic.ts
├── unit-tests.html
└── unit
│ ├── Animation-test.ts
│ ├── Arc-test.ts
│ ├── Circle-test.ts
│ ├── DataModel-test.ts
│ ├── Ellipse-test.ts
│ ├── GraphEditor-test.ts
│ ├── Group-test.ts
│ ├── Image-test.ts
│ ├── Label-test.ts
│ ├── Line-test.ts
│ ├── Path-test.ts
│ ├── Pen-test.ts
│ ├── Polyline-test.ts
│ ├── Rect-test.ts
│ ├── RegularPolygon-test.ts
│ ├── Ring-test.ts
│ ├── Star-test.ts
│ ├── Symbol-test.ts
│ ├── Text-test.ts
│ ├── Utils-test.ts
│ ├── Wedge-test.ts
│ └── test-utils.ts
├── tsconfig-cmj.json
├── tsconfig.json
└── typedoc.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | dist
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 |
9 | # Runtime data
10 | pids
11 | *.pid
12 | *.seed
13 | *.pid.lock
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # nyc test coverage
22 | .nyc_output
23 |
24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # Bower dependency directory (https://bower.io/)
28 | bower_components
29 |
30 | # node-waf configuration
31 | .lock-wscript
32 |
33 | # Compiled binary addons (https://nodejs.org/api/addons.html)
34 | build/Release
35 |
36 | # Dependency directories
37 | node_modules/
38 | jspm_packages/
39 |
40 | # TypeScript v1 declaration files
41 | typings/
42 |
43 | # Optional npm cache directory
44 | .npm
45 |
46 | # Optional eslint cache
47 | .eslintcache
48 |
49 | # Optional REPL history
50 | .node_repl_history
51 |
52 | # Output of 'npm pack'
53 | *.tgz
54 |
55 | # Yarn Integrity file
56 | .yarn-integrity
57 |
58 | # dotenv environment variables file
59 | .env
60 |
61 | # next.js build output
62 | .next
63 | .parcel-cache
64 | docs
65 | output
66 | esm
67 | cmj
68 | umd
69 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.1.6 2024-06-25
2 | ### Fixed
3 | 1 解决绘制模式下,有时画笔消失的问题
4 |
5 | ## 1.1.2 2024-06-14
6 | ### Fixed
7 | 1 解决锁定不生效的问题
8 | 2 解决对齐不生效的问题
9 |
10 | ## 1.1.1 2024-06-12
11 | ### Added
12 | 1 添加对Context对象的支持
13 | 2 添加镜像翻转功能
14 |
15 | ## 1.1.0 2024-06-11
16 | ### Fixed
17 | 1 解决背景色在拖动过程中,不同步的问题。
18 | 2 解决图元中有动画时,移动位置,动画位置错位的问题。
19 | ### Added
20 | 1 添加画布锁定功能
21 | 2 添加画布适配方式功能
22 | 3 添加画布对齐功能
23 | 4 添加画布适应内容功能
24 |
25 | ## 1.0.99 2023-10-23
26 | ### Fixed
27 | 1 解决条件脚本中,不支持viewer导致的错误。
28 |
29 |
30 | ## 1.0.98 2023-10-18
31 | ### Fixed
32 | 1 预览时,cpu使用率能降低一些
33 | 2 解决如果图形中含有动画则使用fitView方法动画会变形的问题
34 | 3 添加快捷键V,可切换到选择模式
35 |
36 | ## 1.0.97 2023-10-16
37 | ### Added
38 | 1 添加fitView方法,可以让图在指定宽高的容器中占满
39 |
40 | ## 1.0.96 2023-10-13
41 | ### Added
42 | 1 折线路径添加shift,绘制直路径的功能
43 | ### Fixed
44 | 1 解决在绘制过程中,鼠标进行其他操作,会出错的问题
45 |
46 | ## 1.0.95 2023-10-13
47 | ### Fixed
48 | 1 解决clear方法中错误销毁stage的问题
49 |
50 | ## 1.0.93 2023-10-12
51 | ### Fixed
52 | 1 解决select+A全选有时候不生效的问题
53 | 2 解决快捷键撤销,一次撤销两步的问题
54 |
55 | ### Added
56 | 1 添加ctrl+shift+方向键的微调功能
57 | 2 添加判断,当组内有图元时,不允许再次成组
58 |
59 | ## 1.0.92 2023-10-11
60 | ### Added
61 | 1 解决addNode方法中动画必填的问题
62 |
63 | ## 1.0.91 2023-10-11
64 | ### Added
65 | 1 解决addNode方法中属性类型不匹配的问题
66 |
67 | ## 1.0.90 2023-09-26
68 | ### Added
69 | 1 解决删除gif图片删除后,未销毁,导致内存未能下降的问题
70 |
71 |
72 |
73 | ## 1.0.89 2023-09-26
74 | ### Added
75 | 1 解决多个gif相互影响的问题
76 |
77 | ## 1.0.88 2023-09-25
78 | ### Added
79 | 1 解决ImageNode的image未定义的情况,会出错的问题
80 | 2 解决事件条件中含有运算target中为0情况时,事件不响应的问题
81 | 3 insertImage添加对gif图片的支持
82 |
83 | ## 1.0.87 2023-09-21
84 | ### Added
85 | 1 添加对服务器图片的支持
86 | 2 解决是否可以构建图元的状态返回有误的问题
87 |
88 | ## 1.0.86 2023-09-20
89 | ### Fixed
90 | 1 增大hitStrokeWidth,方便直线的选中
91 | 2 解决旋转角度为0时,属性并未存上的问题
92 | 3 减少默认微调的步数,更好的精细控制
93 | 4 解决多选的时候,属性并未取交集的问题
94 | 5 解决直线坐标中含有0时,未能正确渲染的问题
95 |
96 | ## 1.0.85 2023-09-19
97 | ### Fixed
98 | 1 解决onNodeAttributes监听的返回值canvasAction错误的问题
99 |
100 | ## 1.0.84 2023-09-18
101 | ### Fixed
102 | 1 解决updateVariable传入null的属性,可能报错的问题
103 |
104 |
105 | ## 1.0.83 2023-09-15
106 | ### Fixed
107 | 1 解决快捷键阻止冒泡后,撤销不能用的问题
108 | 2 解决事件中条件为运算类型时,不生效的问题
109 |
110 | ## 1.0.82 2023-09-15
111 | ### Fixed
112 | 1 宽度和高度归类为矩形,图片的独有属性
113 |
114 | ## 1.0.81 2023-09-15
115 | ### Fixed
116 | 1 修改事件中执行脚本的传参
117 |
118 | ## 1.0.80 2023-09-14
119 | ### Fixed
120 | 1 解决绑定事件动作执行,如果有触发条件的情况下,未能正确动作的问题
121 | 2 GraphViewer去掉init方法
122 |
123 | ## 1.0.78 2023-09-14
124 | ### Fixed
125 | 1 解决页面中存在两个画布情况下,背景色错乱的问题
126 |
127 | ## 1.0.77 2023-09-14
128 | ### Fixed
129 | 1 解决1.0.75的修改导致的GraphViewer构建的时候传入graph出错的问题
130 | 2 单个图形也可以组成图元
131 |
132 |
133 | ## 1.0.76 2023-09-14
134 | ### Fixed
135 | 1 解决预览属性变化事件不生效的问题
136 |
137 | ## 1.0.75 2023-09-13
138 | ### Fixed
139 | 1 解决背景色不生效的问题
140 |
141 | ## 1.0.74 2023-09-11
142 | ### Fixed
143 | 1 解决图片在有旋转动画的情况下,如果移动位置,动画消失的问题
144 | 2 事件中的attributes属性值由数组改为json对象
145 |
146 | ## 1.0.73 2023-09-08
147 | ### Added
148 | 1 添加对事件中的trigger的增删改的方法
149 |
150 | ## 1.0.72 2023-09-08
151 | ### Fixed
152 | 1 修改事件的结构,同时添加一个事件对多个条件的支持
153 |
154 | ## 1.0.71 2023-09-07
155 | ### Added
156 | 1 解决setAttributeValues在修改rotation及其他的情况下,其他属性未能生效的问题
157 |
158 | ## 1.0.70 2023-09-07
159 | ### Added
160 | 1 解决addEvent参数类型不正确的问题
161 |
162 | ## 1.0.69 2023-09-07
163 | ### Added
164 | 1 添加getNodesAttributes方法,用于选中多个的节点,获取交集属性
165 |
166 | ## 1.0.67 2023-09-05
167 | ### Fixed
168 | 1 deleteVariable,getAttributes方法可以不传id
169 |
170 |
171 | ## 1.0.66 2023-08-31
172 | ### Added
173 | 1 添加getGridConfig方法和getStyleConfig方法
174 |
175 |
176 | ## 1.0.65 2023-08-29
177 | ### Fixed
178 | 1 constructSymbol方法返回对象
179 | 2 解决组成图元和解构图元可用状态不对的问题
180 |
181 | ## 1.0.64 2023-08-28
182 | ### Fixed
183 | 1 解决删除一个有动画的形状,撤销操作错误的问题
184 | 2 添加onFnStateChanged方法
185 |
186 |
187 | ## 1.0.63 2023-08-28
188 | ### Fixed
189 | 1 组成图元的方法修改为constructSymbol,拆解图元的方法改为deConstructSymbol
190 | 2 添加获取背景色的方法
191 | 3 读取状态中修改order的状态
192 |
193 |
194 | ## 1.0.62 2023-08-23
195 | ### Fixed
196 | 1 解决背景色设置位置不正确的问题
197 |
198 | ## 1.0.58 2023-08-23
199 | ### Fixed
200 | 1 添加OrderDirection枚举
201 |
202 | ## 1.0.56 2023-08-23
203 | ### Fixed
204 | 1 添加clear方法
205 |
206 | ## 1.0.55 2023-08-16
207 | ### Fixed
208 | 1 解决编辑态转成普通状态时,编辑态的锚点仍然存在的问题
209 | 2 解决图元或组中存在动画时,动画不响应的问题
210 |
211 | ## 1.0.54 2023-08-16
212 | ### Fixed
213 | 1 解决获取动画的默认周期出错问题
214 |
215 | ## 1.0.53 2023-08-09
216 | ### Fixed
217 | 1 GraphViewer添加init接口,用于解析绑定的事件
218 |
219 | ## 1.0.52 2023-08-08
220 | ### Fixed
221 | 1 添加getVariable接口
222 |
223 | ## 1.0.51 2023-08-08
224 | ### Fixed
225 | 1 解决更新变量没有外部通知的问题
226 |
227 | ## 1.0.50 2023-08-08
228 | ### Fixed
229 | 1 解决预览时不传入数据,出错的问题
230 |
231 | ## 1.0.49 2023-08-08
232 | ### Fixed
233 | 1 解决打包umd下解析不了的问题
234 |
235 | ## 1.0.48 2023-08-07
236 | ### Fixed
237 | 1 解决快捷键影响外部定义的快捷键的问题
238 |
239 | ## 1.0.47 2023-08-07
240 | ### Fixed
241 | 1 绘制折线以双击结束
242 |
243 | ## 1.0.47 2023-08-07
244 | ### Fixed
245 | 1 添加命名空间Giraffe
246 |
247 |
248 | ## 1.0.45 2023-08-07
249 | ### Fixed
250 | 1 解决对预览不识别组内元素的问题
251 |
252 | ## 1.0.44 2023-08-07
253 | ### Fixed
254 | 1 解决流动动画不生效的问题
255 | 2 添加对Utils工具类的导出
256 |
257 |
258 | ## 1.0.43 2023-08-01
259 | ### Fixed
260 | 1 解决对齐辅助线在画布比例发生变化时,不能在正确位置出现的问题,优化设计,提高对齐辅助线的效率
261 | 2 解决旋转的组元素,移动过程中坐标混乱的问题
262 | 3 起点和终点完全一样的直线,不予绘制,防止误操作而产生的无意义元素
263 | 4 解决绘制模式下,可能会触发移动事件的问题
264 | 5 添加右键菜单解决方案
265 | 6 添加剪切功能
266 | 7 解决组元素在复制过程中子元素未重新生成id的问题
267 | 8 解决图元拖拽到画布上的对象都是同一个对象的问题
268 |
269 |
270 | ## 1.0.42 2023-06-13
271 | ### Fixed
272 | 1 将保存变量saveVariable拆分为addVariable和updateVariable方法
273 |
274 | ## 1.0.41 2023-06-12
275 | ### Fixed
276 | 1 事件中改变属性和执行脚本用不同的属性来表示,执行脚本用fnJs来存储
277 |
278 | ## 1.0.40 2023-06-12
279 | ### Added
280 | 1 解决某些情况下,updateEvent导致的value为空的问题
281 |
282 | ## 1.0.39 2023-06-07
283 | ### Added
284 | 1 GraphViewer中的refreshGraph添加传参设置
285 |
286 | ## 1.0.38 2023-06-06
287 | ### Fixed
288 | 1 解决带有旋转的图形的组,再解组后,停止动画,图形不能恢复原来状态的问题
289 |
290 | ## 1.0.38 2023-06-06
291 | ### Fixed
292 | 1 解决五角星在旋转状态下,移动位置,形状消失的问题
293 | 2 解决旋转的图形成组后,整个组移动过程中,旋转中心偏移的问题
294 |
295 | ## 1.0.35 2023-06-05
296 | ### Fixed
297 | 1. 解决元素在有动画的情况下,成组后,旋转中心偏移的问题
298 |
299 | ## 1.0.34 2023-06-02
300 | ### Fixed
301 | 1. 解决设置属性为rotation时,角度叠加的问题
302 |
303 | ## 1.0.33 2023-06-01
304 | ### Fixed
305 | 1. 解决正在旋转的元素,drag起始位置有问题的问题
306 |
307 | ## 1.0.32 2023-05-30
308 | ### Fixed
309 | 1. 解决非100%显示比例下,旋转动画有错的问题
310 | 2. 快捷键配置增加对shift的支持
311 |
312 | ## 1.0.28 2023-05-29
313 | ### Fixed
314 | 1. 解决图形中有动画时,预览错误的问题
315 | 2. 解决预览模式下,拖动画布会导致旋转中心偏离的问题
316 | 3. 修改getAttributesValues方法
317 | 4. 解决resize操作,撤销有时出错的问题
318 |
319 | ## 1.0.25 2023-05-26
320 | ### Added
321 | 1. 解决预览模式下在某些情况下元素可拖拽的问题
322 |
323 | ## 1.0.23 2023-05-25
324 | ### Added
325 | 1. GraphViewer添加setGraph接口
326 | 2. 解决水平等距的形状,在坐标相同的情况下,出现错误的问题
327 |
328 |
329 |
330 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
graph-editor-core
3 |
HTML5 Canvas Library Based on Konva
4 |
5 |
6 | ## Install
7 |
8 | #### NPM
9 |
10 | ```js
11 | npm install graph-editor-core --save
12 | ```
13 |
14 | #### Yarn
15 | ```js
16 | yarn add graph-editor-core
17 | ```
18 |
19 |
20 |
21 |
22 | #### Umd
23 | graph-editor-core also supports UMD loading
24 |
25 | ```js
26 |
27 | ```
28 |
29 |
30 | ## Getting Started
31 |
32 | ```js
33 | const editor = new GraphEditor({
34 | container: document.getElementById(''),
35 | })
36 | ```
37 |
38 | ## Documentation
39 | [Full Documentation](https://unpkg.com/graph-editor-core/docs/index.html)
40 | [CHANGELOG](https://unpkg.com/graph-editor-core/CHANGELOG.md)
41 |
42 | ## License
43 | According to the terms of the [MIT license](LICENSE), graph-editor-core is freely distributable.
44 |
45 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lovekobe24/graph-editor-core/b4170d16bf5c530da800548f395b50286cef98d8/index.js
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graph-editor-core",
3 | "version": "1.1.6",
4 | "description": "",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/lovekobe24/graph-editor-core"
8 | },
9 | "files": [
10 | "README.md",
11 | "CHANGELOG.md",
12 | "esm",
13 | "cmj",
14 | "umd",
15 | "docs"
16 | ],
17 | "typings": "./esm/index.all.d.ts",
18 | "main": "./esm/index.all.js",
19 | "type": "module",
20 | "scripts": {
21 | "compile": "npm run clean && npm run tsc && npm run rollup",
22 | "start": "npm run test:watch",
23 | "test:watch": "parcel serve ./test/unit-tests.html ./test/sandbox.html ./test/performance-test.html --dist-dir=output --port 51161 --no-cache",
24 | "clean": "rm -rf ./umd && rm -rf ./types && rm -rf ./cmj && rm -rf ./test-build && rm -rf ./esm",
25 | "tsc": "tsc --removeComments && tsc --build ./tsconfig-cmj.json",
26 | "rollup": "rollup -c --bundleConfigAsCjs",
27 | "doc": "typedoc --options ./typedoc.json"
28 | },
29 | "author": "",
30 | "license": "ISC",
31 | "devDependencies": {
32 | "@rollup/plugin-node-resolve": "^15.0.2",
33 | "@rollup/plugin-terser": "^0.4.1",
34 | "@types/jest": "^29.5.1",
35 | "assert": "^2.0.0",
36 | "parcel": "^2.8.3",
37 | "process": "^0.11.10",
38 | "rollup": "^3.21.6",
39 | "rollup-plugin-commonjs": "^10.1.0",
40 | "rollup-plugin-dts": "^5.3.0",
41 | "rollup-plugin-ignore": "^1.0.10",
42 | "rollup-plugin-replace": "^2.2.0",
43 | "rollup-plugin-typescript2": "^0.34.1",
44 | "ts-loader": "^9.4.2",
45 | "typedoc": "^0.24.7",
46 | "typescript": "^4.9.5",
47 | "webpack": "^5.76.0",
48 | "webpack-cli": "^5.0.1"
49 | },
50 | "dependencies": {
51 | "@rollup/plugin-inject": "^5.0.3",
52 | "gifuct-js": "^2.1.2",
53 | "konva": "^9.3.11"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs';
2 | import typescript from 'rollup-plugin-typescript2';
3 | import { nodeResolve } from '@rollup/plugin-node-resolve'
4 | import ignore from 'rollup-plugin-ignore'
5 | import inject from '@rollup/plugin-inject'
6 | import path from 'path'
7 | import replace from 'rollup-plugin-replace';
8 | import pkg from './package.json';
9 |
10 | export default [
11 | {
12 | input: `src/GraphEditor.ts`,
13 | output: [
14 | {
15 | file: 'umd/graph-editor.js',
16 | name: 'GraphEditor',
17 | format: 'iife',
18 | sourcemap: false,
19 | freeze: false,
20 | banner: `/*!
21 | * GraphEditor v${pkg.version}
22 | * (c) ${new Date().getFullYear()} Zhang Na
23 | * License: MIT
24 | */`
25 | }
26 | ],
27 | plugins: [
28 | ignore(['canvas']),
29 | nodeResolve(),
30 | commonjs(),
31 | typescript(),
32 | replace({
33 | delimiters: ['', ''],
34 | '__VERSION__': pkg.version
35 | })
36 | ],
37 | },
38 | {
39 | input: `src/GraphViewer.ts`,
40 | output: [
41 | {
42 | file: 'umd/graph-viewer.js',
43 | name: 'GraphViewer',
44 | format: 'iife',
45 | sourcemap: false,
46 | freeze: false,
47 | banner: `/*!
48 | * GraphViewer v${pkg.version}
49 | * (c) ${new Date().getFullYear()} Zhang Na
50 | * License: MIT
51 | */`
52 | }
53 | ],
54 | plugins: [
55 | ignore(['canvas']),
56 | nodeResolve(),
57 | commonjs(),
58 | typescript(),
59 | replace({
60 | delimiters: ['', ''],
61 | '__VERSION__': pkg.version
62 | })
63 | ],
64 | },
65 | {
66 | input: `src/index.all.ts`,
67 | output: [
68 | {
69 | file: 'umd/graph-editor-core.js',
70 | name: 'Giraffe',
71 | format: 'iife',
72 | sourcemap: false,
73 | freeze: false,
74 | banner: `/*!
75 | * Giraffe v${pkg.version}
76 | * (c) ${new Date().getFullYear()} Zhang Na
77 | * License: MIT
78 | */`
79 | }
80 | ],
81 | plugins: [
82 | ignore(['canvas']),
83 | nodeResolve(),
84 | commonjs(),
85 | typescript(),
86 | replace({
87 | delimiters: ['', ''],
88 | '__VERSION__': pkg.version
89 | })
90 | ],
91 | },
92 | {
93 | plugins: [
94 | ignore(['canvas']),
95 | nodeResolve(),
96 | commonjs({
97 | exclude: '/node_modules/'
98 | }),
99 | typescript({
100 | tsconfigOverride: {
101 | compilerOptions: { declaration: true }
102 | },
103 | outDir: 'esm',
104 | declarationDir: 'esm'
105 | }),
106 | inject({
107 | global: path.resolve('./build/global')
108 | }),
109 | //terser()
110 | ],
111 | input: 'src/index.all.ts',
112 | preserveModules: false,
113 | output: {
114 | dir: 'esm',
115 | format: 'esm'
116 | }
117 | }
118 | ]
--------------------------------------------------------------------------------
/src/DefaultConfig.ts:
--------------------------------------------------------------------------------
1 | import type { EditorConfig } from "./types";
2 |
3 | export const defaultConfig:EditorConfig = {
4 | view: {
5 | grid: {
6 | show: true,
7 | distance: 50,
8 | color: '#cccccc'
9 | },
10 | size: {
11 | width: 1000,
12 | height: 800
13 | }
14 | },
15 | drawing: {
16 | snapToGrid: {
17 | enable: false,
18 | strokeWidth: 2,
19 | stroke: '#067BEF',
20 | dash: [3, 6]
21 | },
22 | editAnchor: {
23 | anchorFill: 'white',
24 | anchorStroke: 'rgb(0, 161, 255)'
25 | },
26 | connectable:false
27 | },
28 | selection: {
29 | transformer: {
30 | anchorSize: 8,
31 | rotateAnchorOffset: 30,
32 | anchorStroke: 'rgb(0, 161, 255)',
33 | anchorStrokeWidth:1,
34 | anchorFill: 'white',
35 | borderStroke: 'rgb(0, 161, 255)',
36 | borderWidth: 5
37 | },
38 | zone: {
39 | fill: 'rgba(105, 105, 105, 0.7)',
40 | stroke: '#dbdbdb'
41 | },
42 | keyboard: {
43 | enabled: true,
44 | movingSpaces: 1,
45 | map: {
46 | delete: ['Delete'],
47 | moveLeft: ['Control+ArrowLeft'],
48 | moveRight: ['Control+ArrowRight'],
49 | moveUp: ['Control+ArrowUp'],
50 | moveDown: ['Control+ArrowDown'],
51 | moveLeftSlow: ['Control+Shift+ArrowLeft'],
52 | moveRightSlow: ['Control+Shift+ArrowRight'],
53 | moveUpSlow: ['Control+Shift+ArrowUp'],
54 | moveDownSlow: ['Control+Shift+ArrowDown'],
55 | selectAll: ['Control+a'],
56 | deselect: ['Control+d'],
57 | inverseSelect: ['Control+i'],
58 | copy:['Control+c'],
59 | paste:['Control+v'],
60 | group:['Control+g'],
61 | undo:['Control+z'],
62 | redo:['Control+y'],
63 | toSelectMode:['v']
64 | }
65 | }
66 | },
67 | style: {
68 | strokeWidth: 2,
69 | stroke: '#ff0000',
70 | draggable: true,
71 | strokeScaleEnabled: false,
72 | hitStrokeWidth:6
73 | },
74 | history:{
75 | keyboard:{
76 | enabled:true
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/src/EventObject.ts:
--------------------------------------------------------------------------------
1 | export default class EventObject {
2 |
3 | name: string;
4 | properties: any = {};
5 |
6 | constructor(name: string, ...params: any) {
7 | this.name = name;
8 | for (let i = 0, len = params.length; i < len; i += 2) {
9 | let key = params[i], val = params[i + 1];
10 | if (typeof key === 'string' && val !== undefined) {
11 | this.properties[key] = val;
12 | }
13 | }
14 | }
15 |
16 | getName() {
17 | return this.name;
18 | }
19 |
20 | getProperty(key: string) {
21 | return this.properties[key];
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/src/GraphManager.ts:
--------------------------------------------------------------------------------
1 | import type { Stage } from "konva/lib/Stage";
2 | import { GRAPH_EDITOR_WARNING, MAX_SCALE, MIN_SCALE,LEFT_ALIGN,TOP_ALIGN,NORMAL_FIT } from "./constants/TemcConstants";
3 | import type { DataModel } from "./DataModel";
4 | import { Utils } from "./Utils";
5 | import Konva from "konva";
6 | import type { Node } from './model/Node';
7 | import type { Layer } from "konva/lib/Layer";
8 | import { ContainerNode, ContainerNodeAttrs } from "./model/ContainerNode";
9 | import AttributeChange from "./command/AttributeChange";
10 | import Command from "./command/Command";
11 | import GeometryChange from "./command/GeometryChange";
12 | import type { Config } from './types'
13 |
14 |
15 | import { SymbolNode } from './model/SymbolNode';
16 | import { RectNode } from './model/RectNode';
17 | import { EllipseNode } from './model/EllipseNode';
18 | import { CircleNode } from './model/CircleNode';
19 | import { ImageNode } from './model/ImageNode';
20 | import { StarNode } from "./model/StarNode";
21 | import { RegularPolygonNode } from './model/RegularPolygonNode';
22 | import { ArcNode } from "./model/ArcNode";
23 | import { PathNode } from "./model/PathNode";
24 | import { TextNode } from "./model/TextNode";
25 | import { RingNode } from './model/RingNode';
26 | import { WedgeNode } from './model/WedgeNode';
27 | import { LabelNode } from "./model/LabelNode";
28 | import { GroupNode } from "./model/GroupNode";
29 | import { LineArrowNode } from "./model/LineArrowNode";
30 | import { LineNode } from "./model/LineNode";
31 | import { PenNode } from "./model/PenNode";
32 | import { PolylineNode } from "./model/PolylineNode";
33 | import { PolylineArrowNode } from "./model/PolylineArrowNode";
34 |
35 | /*
36 | * JavaScript Framework v@@version
37 | * Licensed under the MIT
38 | * Date: @@date
39 | */
40 | export abstract class GraphManager {
41 | container: HTMLDivElement | null;
42 | config: Config = {};
43 | nodeLayer: Layer = new Konva.Layer();
44 | //背景绘制层
45 | backgroundColorLayer: Layer = new Konva.Layer({ listening: false, name: '背景层' });
46 | backgroundRect:any;
47 | dataModel: DataModel | undefined;
48 | stage: Stage | undefined;
49 | width: number | undefined;
50 | height: number | undefined;
51 | /**
52 | * The settings
53 | */
54 | name: string = '';
55 | //画布是否锁定
56 | locked:boolean=true;
57 | //水平对齐
58 | hAlign:String=LEFT_ALIGN;
59 | //垂直对齐
60 | vAlign:String=TOP_ALIGN;
61 | //显示方式设置
62 | fit:string=NORMAL_FIT;
63 |
64 | constructor(config: Config) {
65 | if (Utils.isBrowser() && !config.container) {
66 | throw new Error(GRAPH_EDITOR_WARNING + 'It needs to have a container element')
67 | }
68 | }
69 |
70 | /**
71 | * 修改视图的显示比例
72 | * @param scale 显示比例
73 | */
74 | setScale(scale: number) {
75 | if (scale > MAX_SCALE) {
76 | scale = MAX_SCALE
77 | }
78 | if (scale < MIN_SCALE) {
79 | scale = MIN_SCALE
80 | }
81 | if (this.stage) {
82 | this.stage.scaleX(scale);
83 | this.stage.scaleY(scale);
84 | if (this.width) {
85 | this.stage.setAttr('width', this.width * scale);
86 | }
87 | if (this.height) {
88 | this.stage.setAttr('height', this.height * scale);
89 | }
90 | }
91 | this.dataModel?.getNodes().forEach((node: any) => {
92 | node.updateRefAnimation("scaleChange");
93 | });
94 | }
95 |
96 | /**
97 | * 获取当前画布的显示比例
98 | */
99 | getScale() {
100 | return this.stage?.getAttr('scaleX');
101 | }
102 |
103 | /**
104 | * 根据节点id获取节点
105 | * @param nodeId 节点id
106 | */
107 | getNode(nodeId: string) {
108 | return this.dataModel ? this.dataModel.getNodeById(nodeId) : null;
109 | }
110 |
111 |
112 | setSize(width: number, height: number) {
113 | this.width = width;
114 | this.height = height;
115 | let scale = this.stage?.getAttr('scaleX');
116 | this.stage?.setAttr('width', width * scale);
117 | this.stage?.setAttr('height', height * scale);
118 | //修改背景矩形
119 | this.backgroundRect.setAttr('width', width * scale);
120 | this.backgroundRect.setAttr('height', height * scale);
121 | }
122 |
123 | /**
124 | * 获取画布的宽度和高度
125 | */
126 | getSize(){
127 | return {width:this.width,height:this.height}
128 | }
129 |
130 | /**
131 | * 加载指定的图形
132 | * @param graphContent 图形信息的字符串
133 | */
134 | setGraph(graphContent: any) {
135 | if (this.stage?.getChildren().length == 0)
136 | return
137 | //清空原来的数据
138 | this.clear();
139 | let graphJson = JSON.parse(graphContent);
140 | this.setSize(graphJson.width, graphJson.height);
141 | let bgColor = graphJson.backgroundColor;
142 | if (bgColor) {
143 | this.setBackgroundColor(bgColor);
144 | }
145 | if (graphJson.name) {
146 | this.name = graphJson.name;
147 | }
148 | if(graphJson.hAlign){
149 | this.hAlign=graphJson.hAlign;
150 | }
151 | if(graphJson.vAlign){
152 | this.vAlign=graphJson.vAlign;
153 | }
154 | if(graphJson.fit){
155 | this.fit=graphJson.fit;
156 | }
157 | if(graphJson.locked!=undefined){
158 | this.locked=graphJson.locked;
159 | }
160 |
161 | this.dataModel?.fromObject(graphJson.model);
162 | }
163 | fitView(width: number, height: number) {
164 | if (this.width && this.height) {
165 | let scaleX = width / this.width;
166 | let scaleY = height / this.height;
167 | this.stage?.scaleX(scaleX);
168 | this.stage?.scaleY(scaleY);
169 | this.stage?.setAttr('width', width);
170 | this.stage?.setAttr('height', height);
171 | this.dataModel?.getNodes().forEach((node: any) => {
172 | node.updateRefAnimation("scaleChange");
173 | });
174 | }
175 |
176 | }
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | /**
186 | * 清空画布内容
187 | */
188 | clear() {
189 | this.dataModel?.clear();
190 | }
191 | /**
192 | * 销毁画布
193 | */
194 | destroy() {
195 | this.dataModel?.destroy();
196 | delete this.dataModel;
197 | this.stage?.destroy();
198 |
199 | }
200 |
201 | /**
202 | * 获取画布上所有的节点
203 | */
204 | getNodes() {
205 | let nodes: any = [];
206 | this.dataModel?.getNodes().forEach((element: any) => {
207 | nodes.push(element.toObject());
208 | });
209 | return nodes;
210 | }
211 | getSetNodesAttributesChange(nodes: Array, attrValues: any, toHistory: boolean = true) {
212 | let undoRedoManager = this.dataModel?.getUndoRedoManager();
213 | let rotationAngle: any = null;
214 | if (attrValues.hasOwnProperty('rotation')) {
215 | rotationAngle = attrValues['rotation'];
216 | delete attrValues['rotation'];
217 | }
218 | // 将预设属性值分成 [全部预设属性值]、[容器节点预设属性值]、[成员节点预设属性值] 三类
219 | let containerAttrKeys = Object.keys(new ContainerNodeAttrs());
220 | let nextAllAttrValues: any = attrValues, nextContainerAttrValues: any = {}, nextMemberAttrValues: any = {};
221 | for (let name in nextAllAttrValues) {
222 | let attrValue = nextAllAttrValues[name];
223 | if (containerAttrKeys.includes(name)) {
224 | nextContainerAttrValues[name] = attrValue;
225 | } else {
226 | nextMemberAttrValues[name] = attrValue;
227 | }
228 | }
229 | let attrValuesMap = new Map();
230 | for (let node of nodes) {
231 | // 1. 针对顶级节点中的容器节点,缓存[容器节点预设属性值]、[容器节点复归属性值]
232 | if (node instanceof ContainerNode) {
233 | let prevContainerAttrValues: any = {};
234 | if (toHistory) {
235 | for (let name in nextContainerAttrValues) {
236 | prevContainerAttrValues[name] = node.getAttributeValue(name);
237 | }
238 | }
239 | attrValuesMap.set(node, [prevContainerAttrValues, nextContainerAttrValues]);
240 | // 2. 递归全部节点中的非容器成员节点,缓存[成员节点预设属性值]、[成员节点复归属性值]
241 | (function loop(nodes) {
242 | for (let node of nodes) {
243 | if (node instanceof ContainerNode) {
244 | loop(node.getMembers());
245 | } else {
246 | let prevMemberAttrValues: any = {};
247 | if (toHistory) {
248 | for (let name in nextMemberAttrValues) {
249 | prevMemberAttrValues[name] = node.getAttributeValue(name);
250 | }
251 | }
252 | attrValuesMap.set(node, [prevMemberAttrValues, nextMemberAttrValues]);
253 | }
254 | }
255 | })(node.getMembers());
256 | }
257 | // 3. 针对顶级节点中的非容器节点,缓存[全部预设属性值]、[全部复归属性值]
258 | else {
259 | let prevAllAttrValues: any = {};
260 | if (toHistory) {
261 | for (let name in nextAllAttrValues) {
262 | prevAllAttrValues[name] = node.getAttributeValue(name);
263 | }
264 | }
265 |
266 | attrValuesMap.set(node, [prevAllAttrValues, nextAllAttrValues]);
267 | }
268 | }
269 | if (this.dataModel) {
270 | if (rotationAngle != null) {
271 | let attrChange = new AttributeChange(attrValuesMap, this.dataModel);
272 | if (attrValues.hasOwnProperty('x') || attrValues.hasOwnProperty('y')) {
273 | //最为复杂的情况x,y,rotation同时修改
274 | undoRedoManager.execute(new Command([attrChange]), false);
275 | }
276 | //如果是手动修改rotation,则需要改变x,y
277 | nodes.forEach((node) => {
278 | let attrs = node.getRef().getClientRect();
279 | attrs.rotation = 0;
280 | let diff = rotationAngle - node.getRef().getAttr('rotation');
281 | let rad = Utils.getRad(diff);
282 | const shape = Utils.rotateAroundCenter(attrs, rad);
283 | Utils.fitNodesInto(node.getRef(), attrs, shape);
284 | })
285 |
286 | let rotateChange = new GeometryChange(nodes, 'rotate', this.dataModel, false);
287 | return [attrChange, rotateChange]
288 |
289 |
290 | } else {
291 | //没有旋转,则直接修改属性
292 | let attrChange = new AttributeChange(attrValuesMap, this.dataModel);
293 | return [attrChange];
294 | }
295 |
296 | }
297 |
298 | }
299 | setNodesAttributes(nodes: Array, attrValues: any, toHistory: boolean = true) {
300 | let undoRedoManager = this.dataModel?.getUndoRedoManager();
301 | let changeArray = this.getSetNodesAttributesChange(nodes, attrValues, toHistory);
302 | let cmd = new Command(changeArray);
303 | undoRedoManager.execute(cmd, toHistory);
304 | }
305 | /**
306 | * 设置背景色
307 | * @param color 背景色
308 | * @example
309 | * editor.setBackgroundColor('red');
310 | */
311 | setBackgroundColor(color: string) {
312 | if (this.backgroundRect) {
313 | this.backgroundRect.setAttr('fill',color);
314 | } else {
315 | console.warn(GRAPH_EDITOR_WARNING + "未能设置背景色");
316 | }
317 | }
318 | /**
319 | *
320 | * @returns 获取背景色
321 | */
322 | getBackgroundColor() {
323 | return this.backgroundRect.getAttr('fill');
324 | }
325 | }
--------------------------------------------------------------------------------
/src/SelectionManager.ts:
--------------------------------------------------------------------------------
1 | import type { DataModel } from "./DataModel";
2 | import EVENT_TYPE from './constants/EventType';
3 | import EventObject from './EventObject';
4 | import { Util } from "konva/lib/Util";
5 | import {Utils} from "./Utils";
6 |
7 | export class SelectionManager {
8 |
9 | selectedNodes: any;
10 | dataModel: any;
11 |
12 | constructor(dataModel: DataModel) {
13 | this.selectedNodes = [];
14 | this.dataModel = dataModel;
15 | }
16 |
17 | /**
18 | * 根据节点的id,设置选中的数据模型中选中的节点
19 | * @param nodeIds
20 | * @param fireEvent
21 | */
22 | setSelection(nodeIds: any, notifyStage: boolean) {
23 | this.selectedNodes = this.dataModel.getNodes().filter((item: any) => {
24 | return nodeIds.includes(item.id)
25 | })
26 |
27 | this.dataModel.fireEvent(new EventObject(EVENT_TYPE.SELECT_CHANGE, 'nodes',Utils.getObjectNodes(this.selectedNodes)), null);
28 | this.dataModel.fireEvent(new EventObject(EVENT_TYPE.FN_STATE_CHANGE), null);
29 |
30 | if (notifyStage) {
31 | //改变stage中选中的节点元素
32 | this.dataModel.fireEvent(new EventObject(EVENT_TYPE.SELECTION_KONVA_NODE_CHANGE, 'nodes', this.selectedNodes), null);
33 | }
34 | }
35 |
36 | clearSelection() {
37 | this.selectedNodes = [];
38 | }
39 |
40 | getSelection() {
41 | return this.selectedNodes;
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/src/TemcEventSource.ts:
--------------------------------------------------------------------------------
1 | import EventObject from "./EventObject";
2 |
3 | export default class TemcEventSource {
4 | eventListeners: any;
5 | eventSource: any;
6 |
7 | constructor() {
8 |
9 | }
10 |
11 | addListener(name: string, func: any) {
12 | if (this.eventListeners == null) {
13 | this.eventListeners = [];
14 | }
15 | this.eventListeners.push(name);
16 | this.eventListeners.push(func);
17 | }
18 |
19 | getEventSource() {
20 | return this.eventSource;
21 | }
22 |
23 | fireEvent(evt: any, sender: any) {
24 | if (this.eventListeners != null) {
25 | if (evt == null) {
26 | evt = new EventObject('unknown');
27 | }
28 | if (sender == null) {
29 | sender = this.getEventSource();
30 | }
31 | if (sender == null) {
32 | sender = this;
33 | }
34 | let args = [sender, evt];
35 | for (let i = 0; i < this.eventListeners.length; i += 2) {
36 | let listen = this.eventListeners[i];
37 |
38 | if (listen == null || listen == evt.getName()) {
39 | this.eventListeners[i + 1].apply(this, args);
40 | }
41 | }
42 | }
43 | };
44 |
45 | }
--------------------------------------------------------------------------------
/src/command/AttributeChange.ts:
--------------------------------------------------------------------------------
1 | import Change from './Change'
2 | import type { Node } from "../model/Node";
3 | import type { DataModel } from '../DataModel';
4 |
5 | class AttributeChange extends Change {
6 |
7 | attrValuesMap: Map>;
8 | dataModel: DataModel;
9 |
10 | constructor(attrValuesMap: Map> = new Map(), dataModel: DataModel) {
11 | super();
12 | this.dataModel = dataModel;
13 | this.attrValuesMap = attrValuesMap;
14 | }
15 |
16 | private apply(flag: boolean) {
17 | let map = new Map();
18 | for (let [node, attrValuesPairs] of this.attrValuesMap) {
19 | map.set(node, attrValuesPairs[flag ? 1 : 0]);
20 | }
21 | this.dataModel.setAttributeValues(map);
22 | }
23 |
24 | execute() {
25 | this.apply(true);
26 | }
27 |
28 | undo() {
29 | this.apply(false);
30 | }
31 |
32 | }
33 |
34 | export default AttributeChange;
--------------------------------------------------------------------------------
/src/command/Change.ts:
--------------------------------------------------------------------------------
1 | abstract class Change {
2 | abstract execute(): void;
3 | abstract undo(): void;
4 | }
5 |
6 | export default Change;
--------------------------------------------------------------------------------
/src/command/Command.ts:
--------------------------------------------------------------------------------
1 | import type Change from './Change'
2 |
3 | class Command {
4 |
5 | changes: Array;
6 |
7 | constructor(changes: Array = []) {
8 | this.changes = changes;
9 | }
10 |
11 | undo() {
12 | let len = this.changes.length, i = len - 1;
13 | while (i > -1) this.changes[i--].undo();
14 | }
15 |
16 | execute() {
17 | let len = this.changes.length, i = 0;
18 | while (i < len) this.changes[i++].execute();
19 | }
20 |
21 | }
22 |
23 | export default Command;
--------------------------------------------------------------------------------
/src/command/GeometryChange.ts:
--------------------------------------------------------------------------------
1 | import { ACTION_TO_STYLE_MAP } from "../constants/StyleMap";
2 | import type { DataModel } from "../DataModel";
3 | import { BaseConnectedLineNode } from "../model/BaseConnectedLineNode";
4 |
5 | import type { Node } from '../model/Node';
6 | import Change from "./Change";
7 |
8 | class GeometryChange extends Change {
9 |
10 | attrValuesMap: Map>;
11 | dataModel: DataModel;
12 | canvasAction:boolean;
13 |
14 | constructor(nodes: Array, action: string, dataModel: any,canvasAction:boolean=true) {
15 | super();
16 | let map = new Map(), actionAttrKeys = ACTION_TO_STYLE_MAP[action];
17 | for (let node of nodes) {
18 | if(node instanceof BaseConnectedLineNode){
19 | let konvaNode = node.getRef();
20 | let prevAllAttrs = node.getAttributeValues(), nextAllAttrs = konvaNode.attrs;
21 | let prevAttrs: any = {}, nextAttrs: any = {};
22 | let relatedAttKeys=['points'];
23 | relatedAttKeys.forEach((key)=>{
24 | if(prevAllAttrs.hasOwnProperty(key)) prevAttrs[key] = prevAllAttrs[key];
25 | let nextAttrValue=nextAllAttrs.hasOwnProperty(key)?nextAllAttrs[key]: konvaNode.getAttr(key);
26 | if(nextAttrValue!=undefined) nextAttrs[key] = nextAttrValue;
27 | })
28 | map.set(node, [prevAttrs, nextAttrs]);
29 | }else{
30 | let konvaNode = node.getRef();
31 | let prevAllAttrs = node.getAttributeValues(), nextAllAttrs = konvaNode.attrs;
32 | let prevAttrs: any = {}, nextAttrs: any = {};
33 | for (let key of actionAttrKeys) {
34 | if(prevAllAttrs.hasOwnProperty(key)) prevAttrs[key] = prevAllAttrs[key];
35 | let nextAttrValue=nextAllAttrs.hasOwnProperty(key)?nextAllAttrs[key]: konvaNode.getAttr(key);
36 | //不能简单用if(nextAttrValue)因为nextAttrValue有可能为0
37 | if(nextAttrValue!=undefined) nextAttrs[key] = nextAttrValue;
38 | }
39 | map.set(node, [prevAttrs, nextAttrs]);
40 | }
41 |
42 | }
43 | this.attrValuesMap = map;
44 | this.dataModel = dataModel;
45 | this.canvasAction=canvasAction;
46 | }
47 |
48 | private apply(flag: boolean) {
49 | let map = new Map();
50 | for (let [node, attrValuesPairs] of this.attrValuesMap) {
51 | map.set(node, attrValuesPairs[flag ? 1 : 0]);
52 | }
53 | this.dataModel.setAttributeValues(map,this.canvasAction);
54 | }
55 |
56 | execute() {
57 | this.apply(true);
58 | }
59 |
60 | undo() {
61 | this.apply(false);
62 | }
63 |
64 | }
65 |
66 | export default GeometryChange;
--------------------------------------------------------------------------------
/src/command/GroupChange.ts:
--------------------------------------------------------------------------------
1 | import type { Node } from '../model/Node';
2 | import { GroupNode } from '../model/GroupNode';
3 | import Change from './Change';
4 | import Konva from 'konva';
5 | import { ContainerNode } from '../model/ContainerNode';
6 |
7 | class GroupChange extends Change {
8 |
9 | isGroup: boolean = true;
10 | executed: boolean = false;
11 | groups: Array = [];
12 | groupZIndexs: Array = [];
13 | members: Array = [];
14 | memberZIndexs: Array = [];
15 | dataModel: any;
16 |
17 | constructor(nodes: Array, isGroup: boolean, dataModel: any) {
18 | super();
19 | this.dataModel = dataModel;
20 | this.isGroup = isGroup;
21 | if (isGroup) {
22 | const group = new GroupNode();
23 | group.setAttributeValue('draggable', true);
24 | group.setMembers(nodes.map((item: any) => {
25 | let node = item.clone(true);
26 | node.setAttributeValue('draggable', false);
27 | return node;
28 | }));
29 |
30 | group.setDataModel(this.dataModel);
31 | this.groups = [group];
32 | this.groupZIndexs = [-1];
33 | this.members = nodes;
34 | this.memberZIndexs = nodes.map((item: any) => item.getZIndex());
35 | } else {
36 | nodes = nodes.filter((item: Node) => item instanceof ContainerNode);
37 | this.groups = nodes;
38 | this.groupZIndexs = nodes.map((item: any) => item.getZIndex());
39 | for (let node of nodes) {
40 | const _nodes = (node as GroupNode).getMembers();
41 | for (let _node of _nodes) {
42 | _node.destroyAnimation();
43 | const _konvaNode = _node.getRef();
44 | const parentTransform = _konvaNode.getParent().getTransform();
45 | const newLocalTransform = new Konva.Transform();
46 | newLocalTransform.multiply(parentTransform.copy())
47 | .multiply(_konvaNode.getTransform());
48 | const attrs =newLocalTransform.decompose();
49 | const member = _node.clone(true);
50 | member.setAttributeValue('scaleX', attrs['scaleX']);
51 | member.setAttributeValue('scaleY', attrs['scaleY']);
52 | member.setAttributeValue('rotation', attrs['rotation']);
53 | member.setAttributeValue('skewX', attrs['skewX']);
54 | member.setAttributeValue('skewY', attrs['skewY']);
55 | member.setAttributeValue('x', attrs['x']);
56 | member.setAttributeValue('y', attrs['y']);
57 | member.setAttributeValue('draggable', true);
58 | this.members.push(member);
59 | this.memberZIndexs.push(-1);
60 | }
61 | }
62 | }
63 | }
64 |
65 | group() {
66 | this.members.forEach(item => { item.remove(); });
67 | this.groups.forEach((item, index) => {
68 | this.dataModel.addNode(item, this.groupZIndexs[index]);
69 | //旋转的元素成组后,要添加上子元素的动画,因为在ref是新生成的
70 | if(item instanceof GroupNode){
71 | item.getMembers().forEach((item:Node)=>{
72 | let autoPlay = item.getAnimationValue('autoPlay');
73 | if(autoPlay){
74 | item.updateRefAnimation("group");
75 | }
76 | })
77 | }
78 | });
79 | const groupIds = this.groups.map(item => item.getId());
80 | this.dataModel.getSelectionManager().setSelection(groupIds, true);
81 | }
82 |
83 | unGroup() {
84 |
85 | this.groups.forEach(item => { item.remove(); });
86 | this.members.forEach((item, index) => {
87 | this.dataModel.addNode(item, this.memberZIndexs[index]);
88 | });
89 | const memberIds = this.members.map(item => item.getId());
90 | this.dataModel.getSelectionManager().setSelection(memberIds, true);
91 | }
92 |
93 | execute() {
94 | if (this.executed === false) {
95 | if (this.isGroup) {
96 | this.group();
97 | } else {
98 | this.unGroup();
99 | }
100 | this.executed = true;
101 | }
102 | }
103 |
104 | undo() {
105 | if (this.executed === true) {
106 | if (this.isGroup) {
107 | this.unGroup();
108 | } else {
109 | this.group();
110 | }
111 | this.executed = false;
112 | }
113 | }
114 |
115 | }
116 |
117 | export default GroupChange;
--------------------------------------------------------------------------------
/src/command/NodeChange.ts:
--------------------------------------------------------------------------------
1 | import type { DataModel } from "../DataModel";
2 |
3 | class NodeChange {
4 | operateNodes: any;
5 | action: string;
6 | dataModel: DataModel;
7 | nodeToIndex: any;
8 |
9 | constructor(nodes: any, action: string, dataModel: DataModel) {
10 | this.operateNodes = nodes;
11 | this.action = action;
12 | this.dataModel = dataModel;
13 | this.nodeToIndex = new Map();
14 | this.operateNodes.forEach((item: any) => {
15 | this.nodeToIndex.set(item, item.getZIndex());
16 | });
17 | }
18 |
19 | execute() {
20 | switch (this.action) {
21 | case 'add': this.dataModel.addNodes(this.operateNodes,this.nodeToIndex); break;
22 | case 'delete': this.dataModel.removeNodes(this.operateNodes); break;
23 | }
24 | }
25 |
26 | undo() {
27 | switch (this.action) {
28 | case 'add': this.dataModel.removeNodes(this.operateNodes); break;
29 | case 'delete': this.dataModel.addNodes(this.operateNodes,this.nodeToIndex); break;
30 | }
31 | }
32 | }
33 |
34 | export default NodeChange;
--------------------------------------------------------------------------------
/src/command/OrderChange.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | import EVENT_TYPE from "../constants/EventType";
4 | import { MOVE_STYLE } from "../constants/StyleMap";
5 |
6 | import {Utils} from "../Utils";
7 | import EventObject from "../EventObject";
8 | class OrderChange {
9 | previousValue: any;
10 | selectNode: any;
11 | dataModel: any;
12 | konvaNode: any;
13 | newIndex: number;
14 | oldIndex: number;
15 | direction: string;
16 | constructor(selectNode: any, direction: string, dataModel: any) {
17 |
18 | this.selectNode = selectNode;
19 | this.dataModel = dataModel;
20 | this.konvaNode = this.selectNode.getRef();
21 | this.direction = direction;
22 | switch (this.direction) {
23 | case 'up':
24 | this.konvaNode.moveUp();
25 | break;
26 | case 'down':
27 | this.konvaNode.moveDown();
28 | break;
29 | case 'top':
30 | this.konvaNode.moveToTop();
31 | break;
32 | case 'bottom':
33 | this.konvaNode.moveToBottom();
34 | break;
35 | }
36 | this.newIndex = this.konvaNode.zIndex();
37 | this.oldIndex = this.dataModel.nodes.indexOf(selectNode);
38 | }
39 |
40 | execute() {
41 | Utils.moveItemInArray(this.dataModel.nodes,this.oldIndex,this.newIndex);
42 | this.selectNode.getRef().zIndex(this.newIndex);
43 |
44 | }
45 |
46 | undo() {
47 | Utils.moveItemInArray(this.dataModel.nodes,this.newIndex,this.oldIndex);
48 | this.selectNode.getRef().zIndex(this.oldIndex);
49 | }
50 |
51 | }
52 |
53 | export default OrderChange;
--------------------------------------------------------------------------------
/src/command/UndoRedoManager.ts:
--------------------------------------------------------------------------------
1 | import type { GraphManager } from "../GraphManager";
2 | import {Utils} from "../Utils";
3 |
4 | import type Command from "./Command";
5 |
6 | class UndoRedoManager {
7 |
8 | limit: number = 30;
9 | undos: any = [];
10 | redos: any = [];
11 | settings:any;
12 |
13 | constructor(gm:GraphManager,limit: number) {
14 |
15 | this.settings=gm.config
16 | if (limit > 0) this.limit = limit;
17 | // if (Utils.isBrowser()) {
18 | // window.addEventListener('keydown', this.onKeyDown.bind(this))
19 | // }
20 | }
21 |
22 | canUndo() {
23 | return this.undos.length > 0;
24 | }
25 |
26 | undo() {
27 | if (this.canUndo()) {
28 | let cmd = this.undos.pop();
29 | this.redos.push(cmd);
30 | cmd.undo();
31 | }
32 | }
33 |
34 | canRedo() {
35 | return this.redos.length > 0;
36 | }
37 |
38 | redo() {
39 | if (this.canRedo()) {
40 | let cmd = this.redos.pop();
41 | this.undos.push(cmd);
42 | cmd.execute();
43 | }
44 | }
45 |
46 | record(cmd: Command) {
47 | if (this.undos.length === this.limit) this.undos.shift();
48 | this.undos.push(cmd);
49 | this.redos = [];
50 | }
51 |
52 | execute(cmd: Command,toHistory:boolean=true) {
53 | if(toHistory){
54 | this.record(cmd);
55 | }
56 | cmd.execute();
57 | }
58 |
59 | // private onKeyDown(
60 | // e: Event & {
61 | // key: string
62 | // metaKey: boolean
63 | // ctrlKey: boolean
64 | // shiftKey: boolean
65 | // }
66 | // ) {
67 |
68 | // if (this.settings?.history?.keyboard?.enabled === false) {
69 | // return
70 | // }
71 |
72 | // const isSpecialKey = e.metaKey || e.ctrlKey
73 | // const isShiftKey = e.shiftKey === true
74 | // const key = e.key.toLowerCase()
75 |
76 | // isSpecialKey && !isShiftKey && key === 'z' && this.undo()
77 | // isSpecialKey && isShiftKey && key === 'z' && this.redo()
78 | // }
79 |
80 | }
81 |
82 | export default UndoRedoManager;
--------------------------------------------------------------------------------
/src/constants/EventType.ts:
--------------------------------------------------------------------------------
1 | let EVENT_TYPE = {
2 | ADD_KONVA_NODE:'addKonvaNode',
3 | ADD_NODES:'addNodes',
4 | ADD_KONVA_NODES:'addKonvaNodes',
5 |
6 | REMOVE_NODE: 'removeNode',
7 | REMOVE_NODES: 'removeNodes',
8 | WRAP_NODES_INTO_GROUP: 'wrapNodesIntoGroup',
9 | UNWRAP_NODES_FROM_GROUP: 'unwrapNodesFromGroup',
10 | SELECT_CHANGE: 'selectionChange',
11 | FN_STATE_CHANGE: 'fnStateChange',
12 | PROPERTY_CHANGE:'propertyChange',
13 | SELECTION_KONVA_NODE_CHANGE:'selectionKonvaNodeChange',
14 | NODE_ATTRIBUTE_CHANGE: 'nodeAttributeChange',
15 | NODE_EVENTS_CHANGE:'nodeEventsChange',
16 | NODE_PROPERTY_CHANGE:'nodePropertyChange',
17 | //节点的变量发生改变
18 | NODE_VARIABLE_CHANGE:'nodeVariableChange',
19 | MODEL_VARIABLE_CHANGE:'modelVariableChange',
20 | NODE_ANIMATION_CHANGE:'nodeAnimationChange',
21 | BACKGROUND_COLOR_CHANGE:'bgColorChange',
22 | ZINDEX_CHANGE:'zIndexChange'
23 | };
24 |
25 | export default EVENT_TYPE;
--------------------------------------------------------------------------------
/src/constants/StyleMap.ts:
--------------------------------------------------------------------------------
1 | const RESIZE_STYLE = ['x', 'y', 'scaleX', 'scaleY', 'skewX', 'skewY', 'rotation', 'points'];
2 | const MOVE_STYLE: any = ['x', 'y'];
3 | const STYLE_DEFAULT_VAL: any = { 'scaleX': 1, 'scaleY': 1, 'skewX': 0, 'skewY': 0, 'rotation': 0 };
4 | const ACTION_TO_STYLE_MAP: any = {
5 | 'move': MOVE_STYLE,
6 | 'resize': RESIZE_STYLE,
7 | 'rotate': ['x', 'y', 'rotation'],
8 | 'lineResize': ['x1', 'x2', 'y1', 'y2']
9 | };
10 |
11 | export { RESIZE_STYLE, MOVE_STYLE, STYLE_DEFAULT_VAL, ACTION_TO_STYLE_MAP };
--------------------------------------------------------------------------------
/src/constants/TemcConstants.ts:
--------------------------------------------------------------------------------
1 |
2 | export const SCRIPT = 'script';
3 | export const COMPARISON = 'operation';
4 |
5 |
6 | export const ROTATE_BY_CENTER = 'rotateByCenter';
7 | export const BLINK = 'blink';
8 | export const FLOW = 'flow';
9 | export const NONE = 'none';
10 |
11 | export const TRANSFORM = 'transform';
12 |
13 |
14 | //事件行为
15 | export const UPDATE_ANIMATION_ACTION = "updateAnimation";
16 | export const CHANGE_ATTRS_ACTION = 'changeAttributes';
17 | export const EXECUTE_SCRIPT_ACTION = 'executeScript';
18 | //事件类型
19 | export const MOUSE_LEAVE_EVT_TYPE = 'mouseleave';
20 | export const MOUSE_ENTER_EVT_TYPE = 'mouseenter';
21 | export const MOUSE_CLICK_EVT_TYPE = 'click';
22 | export const MOUSE_DBL_CLICK_EVT_TYPE = 'dblclick';
23 | export const VALUE_UPDATE_EVT_TYPE = 'valuechange';
24 |
25 | export const EVENT_TRIGGERS = 'triggers';
26 |
27 |
28 | export const DIRECTION_LEFT = 'left';
29 | export const DIRECTION_RIGHT = 'right';
30 | export const DIRECTION_TOP = 'top';
31 | export const DIRECTION_BOTTOM = 'bottom';
32 | export const DIRECTION_HORIZONTAL = 'horizontal';
33 | export const DIRECTION_VERTICAL = 'vertical';
34 |
35 | export const REGULAR_MODE = 'regularMode';
36 | export const DRAWING_MODE = 'drawingMode';
37 | export const EDITING_MODE = 'editingMode';
38 | export const DRAWING_MODE_SUB_CONNECTED_LINE ='drawingModeSubConnectedLine'
39 |
40 | export const COVER_FIT = 'cover';
41 | export const CONTAIN_FIT = 'contain';
42 | export const NORMAL_FIT = 'normal';
43 | export const LEFT_ALIGN ='left';
44 | export const RIGHT_ALIGN ='right';
45 | export const CENTER_ALIGN ='center';
46 | export const TOP_ALIGN ='top';
47 | export const BOTTOM_ALIGN ='bottom';
48 |
49 |
50 | export const DRAWING_MOUSE_DOWN = 0;
51 | export const DRAWING_MOUSE_MOVE = 1;
52 | export const DRAWING_MOUSE_UP = 2;
53 | export const DRAWING_MOUSE_DBL_CLICK = 3;
54 | export const DRAWING_MOUSE_CLICK=4;
55 | export const DRAWING_MOUSE_OUT=5;
56 |
57 | export const MAX_SCALE = 10;
58 | export const MIN_SCALE = 0.1;
59 |
60 | export const GRAPH_EDITOR_WARNING = "GraphEditor warning ";
61 | export const GRAPH_EDITOR_INFO = "GraphEditor info ";
62 |
63 |
64 | const supportAnimation = [BLINK, ROTATE_BY_CENTER, FLOW,NONE];
65 | const supportEventType = [MOUSE_CLICK_EVT_TYPE, MOUSE_LEAVE_EVT_TYPE, MOUSE_ENTER_EVT_TYPE,MOUSE_DBL_CLICK_EVT_TYPE, VALUE_UPDATE_EVT_TYPE];
66 | const supportEventAction = [UPDATE_ANIMATION_ACTION, CHANGE_ATTRS_ACTION, EXECUTE_SCRIPT_ACTION];
67 | const animationToDefaultPeriod: any = {
68 | 'blink': 1,
69 | 'rotateByCenter': 6,
70 | 'flow': 4
71 | }
72 |
73 | export {
74 | supportAnimation,
75 | supportEventType,
76 | supportEventAction,
77 | animationToDefaultPeriod
78 | }
--------------------------------------------------------------------------------
/src/index.all.node.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | export * from './index.all'
--------------------------------------------------------------------------------
/src/index.all.ts:
--------------------------------------------------------------------------------
1 | // main entry for umd build for rollup
2 | /*
3 | * JavaScript Framework v@@version
4 | * Licensed under the MIT
5 | * Date: @@date
6 | */
7 | import GraphEditor from './GraphEditor';
8 | import GraphViewer from './GraphViewer';
9 |
10 | import { SymbolNode } from './model/SymbolNode';
11 | import { RectNode } from './model/RectNode';
12 | import { EllipseNode } from './model/EllipseNode';
13 | import { CircleNode } from './model/CircleNode';
14 | import { ImageNode } from './model/ImageNode';
15 | import { StarNode } from "./model/StarNode";
16 | import { RegularPolygonNode } from './model/RegularPolygonNode';
17 | import { ArcNode } from "./model/ArcNode";
18 | import { PathNode } from "./model/PathNode";
19 | import { TextNode } from "./model/TextNode";
20 | import { RingNode } from './model/RingNode';
21 | import { WedgeNode } from './model/WedgeNode';
22 | import { LabelNode } from "./model/LabelNode";
23 | import { GroupNode } from "./model/GroupNode";
24 | import { LineArrowNode } from "./model/LineArrowNode";
25 | import { LineNode } from "./model/LineNode";
26 | import { PenNode } from "./model/PenNode";
27 | import { PolylineNode } from "./model/PolylineNode";
28 | import { PolylineArrowNode } from "./model/PolylineArrowNode";
29 | import { Utils } from './Utils';
30 |
31 | import { StraightConnectedLineNode } from './model/StraightConnectedLineNode';
32 |
33 |
34 |
35 | RectNode.register();
36 | EllipseNode.register();
37 | ArcNode.register();
38 | CircleNode.register();
39 | GroupNode.register();
40 | ImageNode.register();
41 | LabelNode.register();
42 | LineArrowNode.register();
43 | LineNode.register();
44 | PathNode.register();
45 | PenNode.register();
46 | PolylineArrowNode.register();
47 | PolylineNode.register()
48 | RegularPolygonNode.register();
49 | RingNode.register();
50 | StarNode.register();
51 | SymbolNode.register();
52 | TextNode.register();
53 | WedgeNode.register();
54 | StraightConnectedLineNode.register();
55 |
56 | export { GraphEditor }
57 | export { GraphViewer }
58 |
59 |
60 | export { DataModel } from './DataModel';
61 | export { Node } from './model/Node';
62 | export { RectNode } from './model/RectNode';
63 | export { EllipseNode } from './model/EllipseNode';
64 | export { GroupNode } from './model/GroupNode';
65 | export { ImageNode } from './model/ImageNode';
66 | export { LabelNode } from './model/LabelNode';
67 | export { LineArrowNode } from './model/LineArrowNode';
68 | export { LineNode } from './model/LineNode';
69 | export { PathNode } from './model/PathNode';
70 | export { PenNode } from './model/PenNode';
71 | export { PolylineNode } from './model/PolylineNode';
72 | export { RegularPolygonNode } from './model/RegularPolygonNode';
73 | export { RingNode } from './model/RingNode';
74 | export { ShapeNode } from './model/ShapeNode';
75 | export { StarNode } from './model/StarNode';
76 | export { SymbolNode } from './model/SymbolNode';
77 | export { TextNode } from './model/TextNode';
78 | export { WedgeNode } from './model/WedgeNode';
79 | export { EditableShapeNode } from './model/EditableShapeNode';
80 | export {EditorConfig} from './types/config';
81 | export {GridConfig} from './types/config';
82 | export {StyleConfig} from './types/config';
83 | export {AlignDirection} from './types/common'
84 | export {GEvent,EventType,EventAction,EventWhenType} from './types/common'
85 |
86 | export {Utils};
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/lib/GifuctLib.ts:
--------------------------------------------------------------------------------
1 | import Konva from "konva";
2 | import { parseGIF, decompressFrames, ParsedFrame } from "gifuct-js";
3 | export class GifuctLib {
4 | tempCanvas: any;
5 | tempCtx: any;
6 | c: any;
7 | ctx: any;
8 | gifCanvas: any;
9 | gifCtx: any;
10 | loadedFrames:any;
11 | frameIndex:number=1;
12 | pixelPercent = 100;
13 | frameImageData: ImageData;
14 | playing:any=true;
15 | async loadGIF(gifURL: string) {
16 | const resp = await fetch(gifURL);
17 | const arrayBuffer = await resp.arrayBuffer();
18 | const gif = parseGIF(arrayBuffer);
19 | const frames = decompressFrames(gif, true);
20 | return { gif, frames };
21 | };
22 |
23 | renderGIF (frames: ParsedFrame[]) {
24 | this.loadedFrames = frames;
25 | this.frameIndex = 0;
26 | this.c = document.createElement("canvas");
27 | this.ctx = this.c.getContext("2d");
28 | this.c.width = frames[0].dims.width;
29 | this.c.height = frames[0].dims.height;
30 | // full gif canvas
31 | this.gifCanvas = document.createElement("canvas");
32 | this.gifCtx = this.gifCanvas.getContext("2d");
33 | this.gifCanvas.width = this.c.width;
34 | this.gifCanvas.height = this.c.height;
35 | this.tempCanvas = document.createElement("canvas")
36 | this.tempCtx = this.tempCanvas.getContext("2d")
37 | };
38 |
39 | drawPatch (frame: ParsedFrame) {
40 |
41 | if (this.gifCtx && this.tempCtx) {
42 | const dims = frame.dims;
43 |
44 | if (
45 | !this.frameImageData ||
46 | dims.width != this.frameImageData.width ||
47 | dims.height != this.frameImageData.height
48 | ) {
49 | this.tempCanvas.width = dims.width;
50 | this.tempCanvas.height = dims.height;
51 | this.frameImageData = this.tempCtx.createImageData(dims.width, dims.height);
52 | }
53 |
54 | // set the patch data as an override
55 | this.frameImageData.data.set(frame.patch);
56 |
57 | // draw the patch back over the canvas
58 | this.tempCtx.putImageData(this.frameImageData, 0, 0);
59 |
60 | this.gifCtx.drawImage(this.tempCanvas, dims.left, dims.top);
61 | } else {
62 | console.error("drawPatch error ctx not defined", this.ctx, this.gifCtx);
63 | }
64 | };
65 |
66 | manipulate() {
67 | if (this.ctx && this.gifCtx) {
68 | const imageData =this.gifCtx.getImageData(
69 | 0,
70 | 0,
71 | this.gifCanvas.width,
72 | this.gifCanvas.height
73 | );
74 | const other = this.gifCtx.createImageData(this.gifCanvas.width, this.gifCanvas.height);
75 |
76 |
77 | // do pixelation
78 | const pixelsX = 5 + Math.floor((this.pixelPercent / 100) * (this.c.width - 5));
79 | const pixelsY = (pixelsX * this.c.height) / this.c.width;
80 |
81 | if (this.pixelPercent != 100) {
82 | // ctx.mozImageSmoothingEnabled = false
83 | // ctx.webkitImageSmoothingEnabled = false
84 | this.ctx.imageSmoothingEnabled = false;
85 | }
86 |
87 | this.ctx.putImageData(imageData, 0, 0);
88 | this.ctx.drawImage(this.c, 0, 0, this.c.width, this.c.height, 0, 0, pixelsX, pixelsY);
89 | this.ctx.drawImage(this.c, 0, 0, pixelsX, pixelsY, 0, 0, this.c.width, this.c.height);
90 | } else {
91 | console.error("manipulate error ctx not defined", this.ctx, this.gifCtx);
92 | }
93 | };
94 |
95 | renderFrame (layer: Konva.Layer){
96 | let _this=this;
97 | // get the frame
98 | const frame = this.loadedFrames[this.frameIndex];
99 |
100 | const start = new Date().getTime();
101 |
102 | if (frame.disposalType === 2 && this.gifCtx) {
103 | // gifCtx.clearRect(0, 0, c.width, c.height)
104 | // see: https://github.com/matt-way/gifuct-js/issues/35
105 | const prevFrame = this.loadedFrames[this.frameIndex - 1];
106 | const { width, height, left, top } = prevFrame.dims;
107 | this.gifCtx.clearRect(left, top, width, height);
108 | }
109 |
110 | // draw the patch
111 | this.drawPatch(frame);
112 |
113 | // perform manipulation
114 | this.manipulate();
115 |
116 | // update the frame index
117 | this.frameIndex++;
118 | if (this.frameIndex >=this.loadedFrames.length) {
119 | this.frameIndex = 0;
120 | }
121 |
122 | const end = new Date().getTime();
123 | const diff = end - start;
124 |
125 | layer.draw();
126 |
127 | if (this.playing) {
128 | // delay the next gif frame
129 | setTimeout(function () {
130 | requestAnimationFrame(() => _this.renderFrame(layer));
131 | }, Math.max(0, Math.floor(frame.delay - diff)));
132 | }
133 | };
134 |
135 | destoryAnimation(){
136 | this.playing=false;
137 | }
138 |
139 | async load(gifURL: string, layer: Konva.Layer, callbackFn: any) {
140 | let _this = this;
141 | try {
142 | const { gif, frames } = await _this.loadGIF(gifURL);
143 |
144 | _this.renderGIF(frames);
145 | _this.renderFrame(layer);
146 | callbackFn(this.c);
147 | return this.c;
148 | } catch (error) {
149 | console.log("gifuct try/catch", error);
150 | }
151 | }
152 | }
--------------------------------------------------------------------------------
/src/lib/gifuct.ts:
--------------------------------------------------------------------------------
1 | import { parseGIF, decompressFrames, ParsedFrame } from "gifuct-js";
2 | import Konva from "konva";
3 |
4 | // user canvas
5 |
6 |
7 | // gif patch canvas
8 | let tempCanvas:any;
9 | let tempCtx:any ;
10 | let c:any,ctx:any,gifCanvas:any,gifCtx:any;
11 |
12 |
13 | /**
14 | * load a gif with the current input url value
15 | *
16 | */
17 | const loadGIF = async (gifURL: string) => {
18 | const resp = await fetch(gifURL);
19 | const arrayBuffer = await resp.arrayBuffer();
20 | const gif = parseGIF(arrayBuffer);
21 | const frames = decompressFrames(gif, true);
22 | return { gif, frames };
23 | };
24 |
25 | let loadedFrames: ParsedFrame[] = [];
26 | let frameIndex = 1;
27 | let playing = true;
28 |
29 | const pixelPercent = 100;
30 |
31 | let frameImageData: ImageData;
32 |
33 | const renderGIF = (frames: ParsedFrame[]) => {
34 | loadedFrames = frames;
35 | frameIndex = 0;
36 | c = document.createElement("canvas");
37 | ctx = c.getContext("2d");
38 | c.width = frames[0].dims.width;
39 | c.height = frames[0].dims.height;
40 | // full gif canvas
41 | gifCanvas = document.createElement("canvas");
42 | gifCtx = gifCanvas.getContext("2d");
43 | gifCanvas.width = c.width;
44 | gifCanvas.height = c.height;
45 | tempCanvas = document.createElement("canvas")
46 | tempCtx = tempCanvas.getContext("2d")
47 |
48 |
49 | };
50 |
51 | const drawPatch = (frame: ParsedFrame) => {
52 |
53 | if (gifCtx && tempCtx) {
54 | const dims = frame.dims;
55 |
56 | if (
57 | !frameImageData ||
58 | dims.width != frameImageData.width ||
59 | dims.height != frameImageData.height
60 | ) {
61 | tempCanvas.width = dims.width;
62 | tempCanvas.height = dims.height;
63 | frameImageData = tempCtx.createImageData(dims.width, dims.height);
64 | }
65 |
66 | // set the patch data as an override
67 | frameImageData.data.set(frame.patch);
68 |
69 | // draw the patch back over the canvas
70 | tempCtx.putImageData(frameImageData, 0, 0);
71 |
72 | gifCtx.drawImage(tempCanvas, dims.left, dims.top);
73 | } else {
74 | console.error("drawPatch error ctx not defined", ctx, gifCtx);
75 | }
76 | };
77 |
78 | const manipulate = () => {
79 | if (ctx && gifCtx) {
80 | const imageData = gifCtx.getImageData(
81 | 0,
82 | 0,
83 | gifCanvas.width,
84 | gifCanvas.height
85 | );
86 | const other = gifCtx.createImageData(gifCanvas.width, gifCanvas.height);
87 |
88 |
89 | // do pixelation
90 | const pixelsX = 5 + Math.floor((pixelPercent / 100) * (c.width - 5));
91 | const pixelsY = (pixelsX * c.height) / c.width;
92 |
93 | if (pixelPercent != 100) {
94 | // ctx.mozImageSmoothingEnabled = false
95 | // ctx.webkitImageSmoothingEnabled = false
96 | ctx.imageSmoothingEnabled = false;
97 | }
98 |
99 | ctx.putImageData(imageData, 0, 0);
100 | ctx.drawImage(c, 0, 0, c.width, c.height, 0, 0, pixelsX, pixelsY);
101 | ctx.drawImage(c, 0, 0, pixelsX, pixelsY, 0, 0, c.width, c.height);
102 | } else {
103 | console.error("manipulate error ctx not defined", ctx, gifCtx);
104 | }
105 | };
106 |
107 | const renderFrame = (layer: Konva.Layer) => {
108 | // get the frame
109 | const frame = loadedFrames[frameIndex];
110 |
111 | const start = new Date().getTime();
112 |
113 | if (frame.disposalType === 2 && gifCtx) {
114 | // gifCtx.clearRect(0, 0, c.width, c.height)
115 | // see: https://github.com/matt-way/gifuct-js/issues/35
116 | const prevFrame = loadedFrames[frameIndex - 1];
117 | const { width, height, left, top } = prevFrame.dims;
118 | gifCtx.clearRect(left, top, width, height);
119 | }
120 |
121 | // draw the patch
122 | drawPatch(frame);
123 |
124 | // perform manipulation
125 | manipulate();
126 |
127 | // update the frame index
128 | frameIndex++;
129 | if (frameIndex >= loadedFrames.length) {
130 | frameIndex = 0;
131 | }
132 |
133 | const end = new Date().getTime();
134 | const diff = end - start;
135 |
136 | layer.draw();
137 |
138 | if (playing) {
139 | // delay the next gif frame
140 | setTimeout(function () {
141 | requestAnimationFrame(() => renderFrame(layer));
142 | }, Math.max(0, Math.floor(frame.delay - diff)));
143 | }
144 | };
145 |
146 | const gifuct = async (gifURL: string, layer: Konva.Layer, callbackFn: any) => {
147 | try {
148 | const { gif, frames } = await loadGIF(gifURL);
149 |
150 | renderGIF(frames);
151 | renderFrame(layer);
152 | callbackFn(c);
153 | return c;
154 | } catch (error) {
155 | console.log("gifuct try/catch", error);
156 | }
157 | };
158 |
159 | export default gifuct;
160 |
161 |
162 |
--------------------------------------------------------------------------------
/src/model/ArcNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class ArcNodeAttrs extends ShapeNodeAttrs {
6 | angle = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
7 | innerRadius = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
8 | outerRadius = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
9 | clockwise = { "value": false, "default": false, "group": "geometry", "type": "Boolean" };
10 | }
11 |
12 | export class ArcNode extends ShapeNode {
13 |
14 | static className = 'ArcNode';
15 | className:string='ArcNode';
16 | attributes = new ArcNodeAttrs();
17 |
18 | constructor(opt?: any) {
19 | super();
20 |
21 | if (opt) this.fromObject(opt);
22 | }
23 |
24 | createRef() {
25 | let konvaNode = new Konva.Arc();
26 | konvaNode.id(this.id);
27 | this.ref = konvaNode;
28 | }
29 |
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/src/model/BaseConnectedLineNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 | import { EditableShapeNode, EditableShapeNodeAttrs } from './EditableShapeNode';
5 |
6 | export class BaseConnectedLineNodeAttrs extends EditableShapeNodeAttrs {
7 | points = { "value": [], "default": [], "group": "geometry", "type": "Array" };
8 | }
9 |
10 | export class BaseConnectedLineNode extends EditableShapeNode {
11 | from: any;
12 | to: any;
13 |
14 | setFrom(from: string) {
15 | this.from = from;
16 | }
17 | setTo(to: string) {
18 | this.to = to;
19 | }
20 | updateRefAnchors(attrValues: any): void {
21 |
22 | }
23 | createRef(): void {
24 | let konvaNode = new Konva.Line();
25 | konvaNode.id(this.id);
26 | this.ref = konvaNode;
27 | }
28 |
29 |
30 | attributes = new BaseConnectedLineNodeAttrs();
31 |
32 | constructor(opt?: any) {
33 | super();
34 | if (opt) this.fromObject(opt);
35 | }
36 |
37 | /**
38 | * 将节点转成JSON对象
39 | * @param isArray 是否去掉key,转成数组,节省空间
40 | * @param distinct 是否过滤掉属性值为默认值的属性
41 | * @param unique 是否id唯一,如果为true,则会重新生成id
42 | * @returns
43 | */
44 | toObject(isArray: boolean = false, distinct: boolean = true, unique: boolean = false) {
45 | let obj: any = super.toObject(isArray, distinct, unique);
46 |
47 | obj.from = this.from;
48 | obj.to = this.to;
49 | return obj;
50 | }
51 |
52 | fromObject(obj: any) {
53 | super.fromObject(obj);
54 | this.setFrom(obj.from);
55 | this.setTo(obj.to);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/model/CircleNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class CircleNodeAttrs extends ShapeNodeAttrs {
6 | radius = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
7 | }
8 |
9 | export class CircleNode extends ShapeNode {
10 |
11 | static className = 'CircleNode';
12 | className = 'CircleNode';
13 | attributes = new CircleNodeAttrs();
14 |
15 | constructor(opt?: any) {
16 | super();
17 | if (opt) this.fromObject(opt);
18 | }
19 |
20 | createRef() {
21 | let konvaNode = new Konva.Circle();
22 | konvaNode.id(this.id);
23 | this.ref = konvaNode;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/model/ContainerNode.ts:
--------------------------------------------------------------------------------
1 | import Konva from 'konva';
2 | import { Node, NodeAttrs } from './Node';
3 | import {Utils} from '../Utils';
4 |
5 | export class ContainerNodeAttrs extends NodeAttrs { }
6 |
7 | export abstract class ContainerNode extends Node {
8 |
9 | className = 'ContainerNode';
10 | members: Array = [];
11 |
12 | getMembers() {
13 | return this.members;
14 | }
15 |
16 | setMembers(nodes: Array) {
17 | this.members = nodes;
18 | this.ref.destroyChildren();
19 | this.ref.add(...nodes.map(node => node.getRef()));
20 | }
21 |
22 | getMemberAttributes(unionMode: boolean = false) {
23 | let memberAttrs: any = {}, members = this.members;
24 | for (let i = 0, len = members.length; i < len; i++) {
25 | let member = members[i], attrs = member instanceof ContainerNode ? member.getMemberAttributes(unionMode) : member.getAttributes(true);
26 | if (i === 0) {
27 | memberAttrs = attrs;
28 | } else if (unionMode) {
29 | Object.assign(memberAttrs, attrs);
30 | } else {
31 | let loop = false;
32 | for (let name in memberAttrs) {
33 | loop = true;
34 | if (!attrs.hasOwnProperty(name)) {
35 | delete memberAttrs[name];
36 | }
37 | }
38 | if (!loop) break;
39 | }
40 | }
41 | return memberAttrs;
42 | }
43 |
44 | setMemberAttributeValues(attrValues: any) {
45 | this.members.forEach(member => {
46 | if (member instanceof ContainerNode) {
47 | member.setMemberAttributeValues(attrValues);
48 | } else {
49 | member.setAttributeValues(attrValues);
50 | }
51 | });
52 | }
53 |
54 | fromObject(obj: any) {
55 | super.fromObject(obj);
56 | let members;
57 | if (obj instanceof Array) {
58 | members = obj[7];
59 | } else {
60 | ({ members } = obj);
61 | }
62 | if (members) this.setMembers(members.map((item: any) => Node.create(item)));
63 | }
64 |
65 | toObject(isArray: boolean = false, distinct: boolean = true,unique:boolean=false) {
66 | let obj: any = super.toObject(isArray,distinct,unique);
67 | let members = this.getMembers().map((item: any) => item.toObject(isArray,distinct,unique));
68 | if (isArray) {
69 | obj.push(members);
70 | } else {
71 | obj.members = members;
72 | }
73 | return obj;
74 | }
75 |
76 | createRef() {
77 | let konvaNode = new Konva.Group();
78 | konvaNode.id(this.id);
79 | this.ref = konvaNode;
80 | }
81 | updateRefAttrs(attrValues: any) {
82 | super.updateRefAttrs(attrValues);
83 | (function loop(nodes) {
84 | for (let node of nodes) {
85 | if (node instanceof ContainerNode) {
86 | loop(node.getMembers());
87 | } else {
88 | //如果有动画是正在执行的,则要重新生成动画
89 | let shouldUpdateAnimation = Utils.getShouldUpdateAnimation(attrValues);
90 | let autoPlay = node.getAnimationValue('autoPlay');
91 | if (autoPlay && shouldUpdateAnimation) {
92 | node.updateRefAnimation("updateRefAttrs");
93 | }
94 | }
95 | }
96 | })(this.getMembers());
97 | }
98 |
99 | /**
100 | * 更新konva节点的动画
101 | */
102 | updateRefAnimation(reason: string) {
103 | super.updateRefAnimation(reason);
104 | (function loop(nodes) {
105 | for (let node of nodes) {
106 | if (node instanceof ContainerNode) {
107 | loop(node.getMembers());
108 | } else {
109 | node.updateRefAnimation(reason);
110 | }
111 | }
112 | })(this.getMembers());
113 | }
114 |
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/src/model/EditableShapeNode.ts:
--------------------------------------------------------------------------------
1 | import {Utils} from '../Utils';
2 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
3 |
4 | export abstract class EditableShapeNodeAttrs extends ShapeNodeAttrs { }
5 |
6 | export abstract class EditableShapeNode extends ShapeNode {
7 |
8 | className = 'EditableShapeNode';
9 | refAnchors: any = null;
10 |
11 | getRefAnchors() {
12 | return this.refAnchors;
13 | }
14 |
15 | createRefAnchors() {
16 |
17 | }
18 |
19 | abstract updateRefAnchors(attrValues: any): void;
20 |
21 | destroyRefAnchors() {
22 | if (this.refAnchors !== null) {
23 | let anchors = this.refAnchors;
24 | for (let anchor of anchors) anchor.destroy();
25 | this.refAnchors = null;
26 | }
27 | }
28 |
29 | setAttributeValues(attrValues: any) {
30 | super.setAttributeValues(attrValues);
31 | this.updateRefAnchors(attrValues);
32 | }
33 |
34 | remove() {
35 | let index = this.getZIndex();
36 | if (index !== -1) {
37 | this.dataModel.nodes.splice(index, 1);
38 | if (this.ref !== null) this.ref.remove();
39 | this.destroyRefAnchors();
40 | }
41 | }
42 | destroy(){
43 | let index = this.getZIndex();
44 | if (index !== -1) {
45 | this.dataModel.nodes.splice(index, 1);
46 | if (this.ref !== null) this.ref.destroy();
47 | this.destroyRefAnchors();
48 | }
49 | }
50 | getAnchorRadius(strokeWidth:number){
51 |
52 | let scale=this.ref.getParent().getStage().getAttr('scaleX');
53 | return 3/scale;
54 | // if(strokeWidth<3){
55 |
56 | // }else{
57 | // return strokeWidth/scale;
58 | // }
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/model/EllipseNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class EllipseNodeAttrs extends ShapeNodeAttrs {
6 | radiusX = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
7 | radiusY = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
8 | }
9 |
10 | export class EllipseNode extends ShapeNode {
11 |
12 | static className = 'EllipseNode';
13 | className = 'EllipseNode';
14 | attributes = new EllipseNodeAttrs();
15 |
16 | constructor(opt?: any) {
17 | super();
18 | if (opt) this.fromObject(opt);
19 | }
20 |
21 | createRef() {
22 | let konvaNode = new Konva.Ellipse();
23 | konvaNode.id(this.id);
24 | this.ref = konvaNode;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/model/GroupNode.ts:
--------------------------------------------------------------------------------
1 | import { ContainerNode, ContainerNodeAttrs } from './ContainerNode';
2 |
3 | export class GroupNodeAttrs extends ContainerNodeAttrs { }
4 |
5 | export class GroupNode extends ContainerNode {
6 |
7 | static className = 'GroupNode';
8 | className = 'GroupNode';
9 | attributes = new GroupNodeAttrs();
10 |
11 | constructor(opt?: any) {
12 | super();
13 | if (opt) this.fromObject(opt);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/model/ImageNode.ts:
--------------------------------------------------------------------------------
1 | import Konva from 'konva';
2 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
3 | import { Utils } from '../Utils';
4 | import gifuct from '../lib/gifuct';
5 | import { GRAPH_EDITOR_WARNING } from '../constants/TemcConstants';
6 | import { GifuctLib } from '../lib/GifuctLib';
7 |
8 |
9 | export class ImageNodeAttrs extends ShapeNodeAttrs {
10 | image = { "value": undefined, "default": undefined, "group": "geometry", "type": "Image" };
11 | crop = { "value": null, "default": null, "group": "geometry", "type": "Object" };
12 | width = { "value": "auto", "default": "auto", "group": "geometry", "type": "Number" };
13 | height = { "value": "auto", "default": "auto", "group": "geometry", "type": "Number" };
14 | name = { "value": "", "default": "", "group": "", "type": "String" };
15 |
16 | }
17 |
18 | export class ImageNode extends ShapeNode {
19 |
20 |
21 | static className = 'ImageNode';
22 | className = 'ImageNode';
23 | attributes = new ImageNodeAttrs();
24 | gifuctLib:any;
25 | constructor(opt?: any) {
26 | super();
27 | if (opt) this.fromObject(opt);
28 | }
29 | setAttributeValues(attrValues: any) {
30 |
31 | let _this = this;
32 | super.setAttributeValues(attrValues);
33 | let { image } = attrValues;
34 | if (image && image != "undefined") {
35 | if (!(image.startsWith('data'))) {
36 | fetch(image)
37 | .then(response => response.blob())
38 | .then(blob => {
39 | const reader = new FileReader()
40 | reader.readAsDataURL(blob)
41 | reader.onloadend = () => {
42 | const base64data = reader.result?.toString();
43 | if (base64data) {
44 | _this.setAttributeValue('image', base64data);
45 | }
46 | }
47 | })
48 | }
49 | }
50 | }
51 |
52 |
53 |
54 | updateRefAttrs(attrValues: any) {
55 | if (this.ref !== null) {
56 | this.ref.setAttrs(attrValues);
57 | let { image } = attrValues;
58 | if (image && image != "undefined") {
59 | if (image.startsWith('data:image/gif')) {
60 | if (this.ref.getParent()) {
61 | this.gifuctLib = new GifuctLib();
62 | let name = this.getAttributeValue('name');
63 | this.gifuctLib.load(image, this.ref.getParent(), (c: any) => {
64 | c.setAttribute('id', name);
65 | this.ref.setAttr('image', c);
66 | })
67 | let imageObj = new Image();
68 | imageObj.src = image;
69 | this.ref.setAttr('image', imageObj);
70 | }
71 |
72 | } else {
73 | let imageObj = new Image();
74 | imageObj.src = image;
75 | this.ref.setAttr('image', imageObj);
76 | }
77 |
78 |
79 | }
80 | //如果有动画是正在执行的,则要根据属性的改变重新生成动画,比如修改了位置,则旋转动画要重新生成
81 | let shouldUpdateAnimation = Utils.getShouldUpdateAnimation(attrValues);
82 | let autoPlay = this.getAnimationValue('autoPlay');
83 | let isRotate = this.getAnimationValue('type') == 'rotateByCenter';
84 | if (autoPlay && isRotate && shouldUpdateAnimation) {
85 | this.updateRefAnimation("updateRefAttrs");
86 | }
87 | }
88 | }
89 | updateGifAnimation(reason: string) {
90 | let image = this.getAttributeValue('image');
91 | let name = this.getAttributeValue('name');
92 | if (image.startsWith('data:image/gif')) {
93 | if (this.ref.getParent()) {
94 | this.gifuctLib = new GifuctLib();
95 | this.gifuctLib.load(image, this.ref.getParent(), (c: any) => {
96 | c.setAttribute('id', name);
97 | this.ref.setAttr('image', c);
98 | })
99 | let imageObj = new Image();
100 | imageObj.src = image;
101 | this.ref.setAttr('image', imageObj);
102 | } else {
103 | console.warn(GRAPH_EDITOR_WARNING + "节点未加入到Layer,不能渲染gif")
104 | }
105 |
106 | }
107 | }
108 |
109 | createRef() {
110 | let konvaNode = new Konva.Image({ image: undefined });
111 | konvaNode.id(this.id);
112 | this.ref = konvaNode;
113 | }
114 |
115 | remove() {
116 | super.remove()
117 | this.destroyGifAnimation();
118 | }
119 | destroyGifAnimation(){
120 | if(this.gifuctLib){
121 | this.gifuctLib.destoryAnimation()
122 | this.gifuctLib=null;
123 | }
124 | }
125 | /**
126 | * 销毁节点
127 | */
128 | destroy() {
129 | super.remove()
130 | this.destroyGifAnimation();
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/src/model/LabelNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class LabelNodeAttrs extends ShapeNodeAttrs {
6 | textFill = { "value": "", "default": "", "group": "fill", "type": "String" };
7 | fontFamily = { "value": "Arial", "default": "Arial", "group": "font", "type": "String", enums: ["宋体", "Arial", "Calibri"] };
8 | fontSize = { "value": 12, "default": 12, "group": "font", "type": "Number" };
9 | fontStyle = { "value": "normal", "default": "normal", "group": "font", "type": "String", enums: ["normal", "bold", "italic", "bold italic"] };
10 | fontVariant = { "value": "normal", "default": "normal", "group": "font", "type": "String", enums: ["normal", "small-caps"] };
11 | textDecoration = { "value": "", "default": "", "group": "font", "type": "String", enums: ["", "underline", "line-through", "underline line-through"] };
12 | text = { "value": "", "default": "", "group": "text", "type": "String" };
13 | pointerDirection = { "value": "", "default": "", "group": "other", "type": "String", enums: ["up", "down", "left", "right"] };
14 | pointerWidth = { "value": 0, "default": 0, "group": "other", "type": "Number" };
15 | pointerHeight = { "value": 0, "default": 0, "group": "other", "type": "Number" };
16 | cornerRadius = { "value": 0, "default": 0, "group": "other", "type": "Number" };
17 | padding = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
18 | }
19 |
20 |
21 | export class LabelNode extends ShapeNode {
22 |
23 | static className = 'LabelNode';
24 | className = 'LabelNode';
25 | attributes = new LabelNodeAttrs();
26 | textAttributes = ['text', 'fontFamily', 'fontSize', 'padding', 'textFill', 'textDecoration', 'fontStyle'];
27 | labelAttributes = ['x', 'y', 'rotation', 'scaleX', 'scaleY', 'skewX', 'skewY', 'draggable'];
28 |
29 | constructor(opt?: any) {
30 | super();
31 | if (opt) this.fromObject(opt);
32 | }
33 |
34 | updateRefAttrs(attrValues: any) {
35 | if (this.ref !== null) {
36 | for (let name in attrValues) {
37 | let attrValue = attrValues[name];
38 | if (this.labelAttributes.includes(name)) {
39 | this.ref.setAttr(name, attrValue);
40 | } else if (this.textAttributes.includes(name)) {
41 | this.ref.getText().setAttr(name === 'textFill' ? 'fill' : name, attrValue);
42 | } else {
43 | this.ref.getTag().setAttr(name, attrValue);
44 | }
45 | }
46 | }
47 | }
48 |
49 | createRef() {
50 | let konvaNode = new Konva.Label();
51 | konvaNode.add(new Konva.Tag({}));
52 | konvaNode.add(new Konva.Text({}));
53 | konvaNode.id(this.id);
54 | this.ref = konvaNode;
55 | }
56 |
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/model/LineArrowNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { LineNode, LineNodeAttrs } from './LineNode';
4 |
5 | export class LineArrowNodeAttrs extends LineNodeAttrs {
6 | pointerLength = { "value": 20, "default": 20, "group": "geometry", "type": "Number" };
7 | pointerWidth = { "value": 20, "default": 20, "group": "geometry", "type": "Number" };
8 | pointerAtBeginning = { "value": false, "default": false, "group": "geometry", "type": "Boolean" };
9 | pointerAtEnding = { "value": true, "default": true, "group": "geometry", "type": "Boolean" };
10 | }
11 |
12 | export class LineArrowNode extends LineNode {
13 |
14 | static className = 'LineArrowNode';
15 | className = 'LineArrowNode';
16 | attributes = new LineArrowNodeAttrs();
17 |
18 | constructor(opt?: any) {
19 | super();
20 | if (opt) this.fromObject(opt);
21 | }
22 |
23 | createRef() {
24 | let konvaNode = new Konva.Arrow();
25 | konvaNode.id(this.id);
26 | this.ref = konvaNode;
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/src/model/LineNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { EditableShapeNode, EditableShapeNodeAttrs } from './EditableShapeNode';
4 | import Command from '../command/Command';
5 | import AttributeChange from '../command/AttributeChange';
6 | import { Utils } from '../Utils';
7 |
8 | export class LineNodeAttrs extends EditableShapeNodeAttrs {
9 | x1 = { "value": null, "default": null, "group": "geometry", "type": "Number" };
10 | y1 = { "value": null, "default": null, "group": "geometry", "type": "Number" };
11 | x2 = { "value": null, "default": null, "group": "geometry", "type": "Number" };
12 | y2 = { "value": null, "default": null, "group": "geometry", "type": "Number" };
13 | }
14 |
15 | export class LineNode extends EditableShapeNode {
16 |
17 | static className = 'LineNode';
18 | className = 'LineNode';
19 | attributes: any = new LineNodeAttrs();
20 |
21 | constructor(opt?: any) {
22 | super();
23 | if (opt) this.fromObject(opt);
24 | }
25 | updateRefAttrs(attrValues: any) {
26 | if (this.ref !== null) {
27 |
28 | let { x1, y1, x2, y2, ...otherAttrValues } = attrValues;
29 | if (x1 !== undefined || y1 !== undefined || x2 !== undefined || y2 !== undefined) {
30 |
31 | let points = this.ref.points().slice();
32 | if (x1 !== undefined) points[0] = x1;
33 | if (y1 !== undefined) points[1] = y1;
34 | if (x2 !== undefined) points[2] = x2;
35 | if (y2 !== undefined) points[3] = y2;
36 | otherAttrValues['points'] = points;
37 | }
38 | super.updateRefAttrs(otherAttrValues);
39 | }
40 | }
41 |
42 | createRef() {
43 | let konvaNode = new Konva.Line();
44 | konvaNode.id(this.id);
45 | this.ref = konvaNode;
46 | }
47 |
48 | createRefAnchors(opt?: any) {
49 | super.createRefAnchors();
50 | let transformIn = this.ref.getTransform();
51 | let transformOut = transformIn.copy().invert();
52 | let strokeWidth = this.attributes['strokeWidth'].value;
53 | let radius = this.getAnchorRadius(strokeWidth);
54 | let fillColor = opt ? opt.anchorFill : '#0000ff';
55 | let anchorStrokeColor = opt ? opt.anchorStroke : '#0000ff';
56 | let x1 = this.attributes['x1'];
57 | let y1 = this.attributes['y1'];
58 | let x2 = this.attributes['x2'];
59 | let y2 = this.attributes['y2'];
60 |
61 | let pointA = transformIn.point({ x: x1.value, y: y1.value });
62 | let anchorA = new Konva.Circle({
63 | x: pointA.x,
64 | y: pointA.y,
65 | radius: radius,
66 | hitStrokeWidth: radius + 8,
67 | stroke: anchorStrokeColor,
68 | fill: fillColor,
69 | draggable: true
70 | });
71 |
72 | let oldX1: any, oldY1: any;
73 | anchorA.on('dragstart', (e: any) => {
74 | e.cancelBubble = true;
75 | oldX1 = x1.value;
76 | oldY1 = y1.value;
77 | });
78 | anchorA.on('dragmove', () => {
79 | let point = transformOut.point({ x: anchorA.x(), y: anchorA.y() });
80 | x1.value = point.x;
81 | y1.value = point.y;
82 | this.ref.points([x1.value, y1.value, x2.value, y2.value]);
83 | });
84 | anchorA.on('dragend', (e: any) => {
85 | e.cancelBubble = true;
86 | let undoRedoManager = this.dataModel.getUndoRedoManager();
87 | let map = new Map([[this, [{ x1: oldX1, y1: oldY1 }, { x1: x1.value, y1: y1.value }]]]);
88 | let attributeChange = new AttributeChange(map, this.dataModel);
89 | let cmd = new Command([attributeChange]);
90 | undoRedoManager.record(cmd);
91 | });
92 |
93 | let pointB = transformIn.point({ x: x2.value, y: y2.value });
94 | let anchorB = new Konva.Circle({
95 | x: pointB.x,
96 | y: pointB.y,
97 | radius: radius,
98 | stroke: anchorStrokeColor,
99 | hitStrokeWidth: radius + 8,
100 | fill: fillColor,
101 | draggable: true
102 | });
103 |
104 | let oldX2: any, oldY2: any;
105 | anchorB.on('dragstart', (e: any) => {
106 | e.cancelBubble = true;
107 | oldX2 = x2.value;
108 | oldY2 = y2.value;
109 | });
110 | anchorB.on('dragmove', () => {
111 | let point = transformOut.point({ x: anchorB.x(), y: anchorB.y() });
112 | x2.value = point.x;
113 | y2.value = point.y;
114 | this.ref.points([x1.value, y1.value, x2.value, y2.value]);
115 | });
116 | anchorB.on('dragend', (e: any) => {
117 | e.cancelBubble = true;
118 | let undoRedoManager = this.dataModel.getUndoRedoManager();
119 | let map = new Map([[this, [{ x2: oldX2, y2: oldY2 }, { x2: x2.value, y2: y2.value }]]]);
120 | let attributeChange = new AttributeChange(map, this.dataModel);
121 | let cmd = new Command([attributeChange]);
122 | undoRedoManager.record(cmd);
123 | });
124 |
125 | this.refAnchors = [anchorA, anchorB];
126 |
127 | }
128 |
129 | updateRefAnchors(attrValues: any) {
130 | if (this.refAnchors !== null) {
131 | let { x1, y1, x2, y2 } = attrValues;
132 | if (x1 !== undefined || y1 !== undefined || x2 !== undefined || y2 !== undefined) {
133 | let transformIn = this.ref.getTransform();
134 | if (x1 !== undefined || y1 !== undefined) {
135 | if (x1 === undefined) x1 = this.attributes['x1'].value;
136 | if (y1 === undefined) y1 = this.attributes['y1'].value;
137 | let anchorA = this.refAnchors[0];
138 | let pointA = transformIn.point({ x: x1, y: y1 });
139 | anchorA.setAttrs({ x: pointA.x, y: pointA.y });
140 | }
141 | if (x2 !== undefined || y2 !== undefined) {
142 | if (x2 === undefined) x2 = this.attributes['x2'].value;
143 | if (y2 === undefined) y2 = this.attributes['y2'].value;
144 | let anchorB = this.refAnchors[1];
145 | let pointB = transformIn.point({ x: x2, y: y2 });
146 | anchorB.setAttrs({ x: pointB.x, y: pointB.y });
147 | }
148 | }
149 | }
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/src/model/PathNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class PathNodeAttrs extends ShapeNodeAttrs {
6 | data = { "value": "", "default": "", "group": "hidden", "type": "String" };
7 | }
8 |
9 | export class PathNode extends ShapeNode {
10 |
11 | static className = 'PathNode';
12 | className = 'PathNode';
13 | attributes = new PathNodeAttrs();
14 |
15 | constructor(opt?: any) {
16 | super();
17 | if (opt) this.fromObject(opt);
18 | }
19 |
20 | createRef() {
21 | let konvaNode = new Konva.Path();
22 | konvaNode.id(this.id);
23 | this.ref = konvaNode;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/model/PenNode.ts:
--------------------------------------------------------------------------------
1 | import Konva from "konva";
2 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
3 |
4 | export class PenNodeAttrs extends ShapeNodeAttrs {
5 | points = { "value": [], "default": [], "group": "geometry", "type": "Array" };
6 | tension = { "value": 0, "default": 0, "group": "geometry", "type": "Number", "min": 0, "max": 1 };
7 | closed = { "value": false, "default": false, "group": "geometry", "type": "Boolean" };
8 | bezier = { "value": false, "default": false, "group": "geometry", "type": "Boolean" };
9 | }
10 |
11 | export class PenNode extends ShapeNode {
12 |
13 | static className = 'PenNode';
14 | className = 'PenNode';
15 | attributes = new PenNodeAttrs();
16 |
17 | constructor(opt?: any) {
18 | super();
19 | if (opt) this.fromObject(opt);
20 | }
21 |
22 | createRef() {
23 | let konvaNode = new Konva.Line();
24 | konvaNode.id(this.id);
25 | this.ref = konvaNode;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/model/PolylineArrowNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { PolylineNode, PolylineNodeAttrs } from './PolylineNode';
4 |
5 | export class PolylineArrowNodeAttrs extends PolylineNodeAttrs {
6 | pointerLength = { "value": 20, "default": 20, "group": "geometry", "type": "Number" };
7 | pointerWidth = { "value": 20, "default": 20, "group": "geometry", "type": "Number" };
8 | pointerAtBeginning = { "value": false, "default": false, "group": "geometry", "type": "Boolean" };
9 | pointerAtEnding = { "value": true, "default": true, "group": "geometry", "type": "Boolean" };
10 | }
11 |
12 | export class PolylineArrowNode extends PolylineNode {
13 |
14 | static className = 'PolylineArrowNode'
15 | className = 'PolylineArrowNode'
16 | attributes = new PolylineArrowNodeAttrs();
17 |
18 | constructor(opt?: any) {
19 | super();
20 | if (opt) this.fromObject(opt);
21 | }
22 |
23 | createRef() {
24 | let konvaNode = new Konva.Arrow();
25 | konvaNode.id(this.id);
26 | this.ref = konvaNode;
27 | }
28 |
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/model/PolylineNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { EditableShapeNode, EditableShapeNodeAttrs } from './EditableShapeNode';
4 | import Command from '../command/Command';
5 | import AttributeChange from '../command/AttributeChange';
6 |
7 | export class PolylineNodeAttrs extends EditableShapeNodeAttrs {
8 | points = { "value": [], "default": [], "group": "geometry", "type": "Array" };
9 | tension = { "value": 0, "default": 0, "group": "geometry", "type": "Number", "min": 0, "max": 1 };
10 | closed = { "value": false, "default": false, "group": "geometry", "type": "Boolean" };
11 | bezier = { "value": false, "default": false, "group": "geometry", "type": "Boolean" };
12 | }
13 |
14 | export class PolylineNode extends EditableShapeNode {
15 |
16 | static className = 'PolylineNode';
17 | className = 'PolylineNode';
18 | attributes = new PolylineNodeAttrs();
19 |
20 | constructor(opt?: any) {
21 | super();
22 | if (opt) this.fromObject(opt);
23 | }
24 |
25 | createRef() {
26 | let konvaNode = new Konva.Line();
27 | konvaNode.id(this.id);
28 | this.ref = konvaNode;
29 | }
30 |
31 | createRefAnchors(opt?:any) {
32 | super.createRefAnchors();
33 | if (this.refAnchors === null) {
34 | let anchors: Array = [];
35 | let transformIn = this.ref.getTransform();
36 | let transformOut = transformIn.copy().invert();
37 | let strokeWidth = this.attributes['strokeWidth'].value;
38 | let radius=this.getAnchorRadius(strokeWidth);
39 | let fillColor=opt?opt.anchorFill:'#0000ff';
40 | let anchorStrokeColor=opt?opt.anchorStroke:'#0000ff';
41 | let coords = this.attributes['points'].value as Array;
42 | for (let i = 1, len = coords.length; i < len; i += 2) {
43 | let point = transformIn.point({ x: coords[i - 1], y: coords[i] });
44 | let anchor = new Konva.Circle({
45 | x: point.x,
46 | y: point.y,
47 | radius: radius,
48 | hitStrokeWidth: strokeWidth * 2 + 8,
49 | stroke:anchorStrokeColor,
50 | fill: fillColor,
51 | draggable: true,
52 | shapeType: 'anchor'
53 | });
54 | let oldCoords: any;
55 | anchor.on('dragstart', (e: any) => {
56 | e.cancelBubble = true;
57 | oldCoords = coords.slice();
58 | });
59 | anchor.on('dragmove', () => {
60 | let point = transformOut.point({ x: anchor.x(), y: anchor.y() });
61 | coords[i - 1] = point.x;
62 | coords[i] = point.y;
63 | this.ref.points(coords);
64 | });
65 | anchor.on('dragend', (e: any) => {
66 | e.cancelBubble = true;
67 | let newCoords: any = coords.slice();
68 | let undoRedoManager = this.dataModel.getUndoRedoManager();
69 | let map = new Map([[this, [{ points: oldCoords }, { points: newCoords }]]]);
70 | let attributeChange = new AttributeChange(map, this.dataModel);
71 | let cmd = new Command([attributeChange]);
72 | undoRedoManager.record(cmd);
73 | });
74 | anchors.push(anchor);
75 | }
76 | this.refAnchors = anchors;
77 | }
78 | }
79 |
80 | updateRefAnchors(attrValues: any) {
81 | if (this.refAnchors !== null) {
82 | if (attrValues.hasOwnProperty('points')) {
83 | let transformIn = this.ref.getTransform();
84 | let coords = attrValues['points'] as Array;
85 | let anchors = this.refAnchors;
86 | for (let i = 0, len = anchors.length; i < len; i++) {
87 | let point = transformIn.point({ x: coords[i * 2], y: coords[i * 2 + 1] });
88 | anchors[i].setAttrs({ x: point.x, y: point.y });
89 | }
90 | }
91 | }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/model/RectNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class RectNodeAttrs extends ShapeNodeAttrs {
6 | width = { "value": "auto", "default": "auto", "group": "geometry", "type": "Number" };
7 | height = { "value": "auto", "default": "auto", "group": "geometry", "type": "Number" };
8 | }
9 |
10 | export class RectNode extends ShapeNode {
11 | static className: string='RectNode';
12 | attributes = new RectNodeAttrs();
13 | className:string='RectNode';
14 | constructor(opt?: any) {
15 | super();
16 | if (opt) this.fromObject(opt);
17 | }
18 |
19 | createRef() {
20 | let konvaNode = new Konva.Rect();
21 | konvaNode.id(this.id);
22 | this.ref = konvaNode;
23 | }
24 | }
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/model/RegularPolygonNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class RegularPolygonNodeAttrs extends ShapeNodeAttrs {
6 | sides = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
7 | radius = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
8 | }
9 |
10 | export class RegularPolygonNode extends ShapeNode {
11 |
12 | static className = 'RegularPolygonNode';
13 | className = 'RegularPolygonNode';
14 | attributes = new RegularPolygonNodeAttrs();
15 |
16 | constructor(opt?: any) {
17 | super();
18 | if (opt) this.fromObject(opt);
19 | }
20 |
21 | createRef() {
22 | let konvaNode = new Konva.RegularPolygon();
23 | konvaNode.id(this.id);
24 | this.ref = konvaNode;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/model/RingNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class RingNodeAttrs extends ShapeNodeAttrs {
6 | innerRadius = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
7 | outerRadius = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
8 | }
9 |
10 | export class RingNode extends ShapeNode {
11 |
12 | static className = 'RingNode';
13 | className = 'RingNode';
14 | attributes = new RingNodeAttrs();
15 |
16 | constructor(opt?: any) {
17 | super();
18 | if (opt) this.fromObject(opt);
19 | }
20 |
21 | createRef() {
22 | let konvaNode = new Konva.Ring();
23 | konvaNode.id(this.id);
24 | this.ref = konvaNode;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/model/ShapeNode.ts:
--------------------------------------------------------------------------------
1 | import { Node, NodeAttrs } from './Node';
2 |
3 | export abstract class ShapeNodeAttrs extends NodeAttrs {
4 | stroke = { "value": "#000", "default": "#000", "group": "stroke", "type": "String" };
5 | strokeWidth = { "value": 1, "default": 1, "group": "stroke", "type": "Number" };
6 | lineJoin = { "value": "miter", "default": "miter", "group": "stroke", "type": "String", enums: ["miter", "round", "bevel"] };
7 | lineCap = { "value": "butt", "default": "butt", "group": "stroke", "type": "String", enums: ["butt", "round", "square"] };
8 | dash = { "value": [], "default": [], "group": "stroke", "type": "Array", enums: [[2, 2], [5, 5], [10, 10]] };
9 | shadowColor = { "value": "#ccc", "default": "#ccc", "group": "shadow", "type": "String" };
10 | shadowBlur = { "value": 0, "default": 0, "group": "shadow", "type": "Number" };
11 | shadowOffsetX = { "value": 0, "default": 0, "group": "shadow", "type": "Number" };
12 | shadowOffsetY = { "value": 0, "default": 0, "group": "shadow", "type": "Number" };
13 | shadowOpacity = { "value": 1, "default": 1, "group": "shadow", "type": "Number" };
14 | hitStrokeWidth = { "value": "auto", "default": "auto", "group": "hidden", "type": "Number" };
15 | strokeScaleEnabled = { "value": true, "default": true, "group": "hidden", "type": "Boolean" };
16 | fill = { "value": "", "default": "", "group": "fill", "type": "String" };
17 | fillGradient = { "value": "", "default": "", "group": "fill", "type": "String", enums: ['', 'linear', 'radial'] };
18 | fillLinearGradientStartPointX = { "value": 0, "default": 0, "group": "fill", "type": "Number" };
19 | fillLinearGradientStartPointY = { "value": 0, "default": 0, "group": "fill", "type": "Number" };
20 | fillLinearGradientEndPointX = { "value": 0, "default": 0, "group": "fill", "type": "Number" };
21 | fillLinearGradientEndPointY = { "value": 0, "default": 0, "group": "fill", "type": "Number" };
22 | fillLinearGradientColorStops = { "value": null, "default": null, "group": "fill", "type": "Array", enums: [[0, '#bdc3c7', 1, '#2c3e50'], [0, '#ee9ca7', 1, '#ffdde1'], [0, '#2193b0', 1, '#6dd5ed']] };
23 | fillRadialGradientStartRadius = { "value": 0, "default": 0, "group": "fill", "type": "Number" };
24 | fillRadialGradientEndRadius = { "value": 0, "default": 0, "group": "fill", "type": "Number" };
25 | fillRadialGradientColorStops = { "value": null, "default": null, "group": "fill", "type": "Array", enums: [[0, '#bdc3c7', 1, '#2c3e50'], [0, '#ee9ca7', 1, '#ffdde1'], [0, '#2193b0', 1, '#6dd5ed']] };
26 | }
27 |
28 | export abstract class ShapeNode extends Node {
29 |
30 | className = 'ShapeNode';
31 |
32 | updateRefAttrs(attrValues: any) {
33 | if (this.ref !== null) {
34 | if (attrValues.hasOwnProperty('fillGradient')) {
35 | let isRadial = attrValues['fillGradient'] === 'radial';
36 | let isLinear = attrValues['fillGradient'] === 'linear';
37 | let radialAttrNames = ['fillRadialGradientStartRadius', 'fillRadialGradientEndRadius', 'fillRadialGradientColorStops'];
38 | let linearAttrNames = ['fillLinearGradientStartPointX', 'fillLinearGradientStartPointY', 'fillLinearGradientEndPointX', 'fillLinearGradientEndPointY', 'fillLinearGradientColorStops'];
39 | let obj: any = {}, attributes = this.attributes;
40 | for (let key of radialAttrNames) {
41 | obj[key] = isRadial ? attributes[key].value : attributes[key].default;
42 | }
43 | for (let key of linearAttrNames) {
44 | obj[key] = isLinear ? attributes[key].value : attributes[key].default;
45 | }
46 | Object.assign(attrValues, obj);
47 | }
48 | super.updateRefAttrs(attrValues);
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/model/StarNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class StarNodeAttrs extends ShapeNodeAttrs {
6 | numPoints = { "value": 0, "default": 0, "group": "geometry", "type": "Int" };
7 | innerRadius = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
8 | outerRadius = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
9 | }
10 |
11 | export class StarNode extends ShapeNode {
12 |
13 | static className = 'StarNode';
14 | className = 'StarNode';
15 | attributes = new StarNodeAttrs();
16 |
17 | constructor(opt?: any) {
18 | super();
19 | if (opt) this.fromObject(opt);
20 | }
21 |
22 | createRef() {
23 | let konvaNode = new Konva.Star();
24 | konvaNode.id(this.id);
25 | this.ref = konvaNode;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/model/StraightConnectedLineNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { EditableShapeNode, EditableShapeNodeAttrs } from './EditableShapeNode';
4 | import Command from '../command/Command';
5 | import AttributeChange from '../command/AttributeChange';
6 | import { Utils } from '../Utils';
7 | import { BaseConnectedLineNode, BaseConnectedLineNodeAttrs } from './BaseConnectedLineNode';
8 |
9 | export class StraightConnectedLineNodeAttrs extends BaseConnectedLineNodeAttrs {
10 |
11 | }
12 |
13 | export class StraightConnectedLineNode extends BaseConnectedLineNode {
14 |
15 | static className = 'StraightConnectedLineNode';
16 | className = 'StraightConnectedLineNode';
17 | attributes: any = new StraightConnectedLineNodeAttrs();
18 |
19 | constructor(opt?: any) {
20 | super();
21 | if (opt) this.fromObject(opt);
22 | }
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/model/SymbolNode.ts:
--------------------------------------------------------------------------------
1 | import { ContainerNode, ContainerNodeAttrs } from './ContainerNode';
2 |
3 | export class SymbolNodeAttrs extends ContainerNodeAttrs { }
4 |
5 | export class SymbolNode extends ContainerNode {
6 |
7 | static className = 'SymbolNode';
8 | className = 'SymbolNode';
9 | attributes = new SymbolNodeAttrs();
10 | symbolName: string | undefined;
11 |
12 | constructor(opt?: any) {
13 | super();
14 | if (opt) this.fromObject(opt);
15 | }
16 |
17 | getSymbolName() {
18 | return this.symbolName;
19 | }
20 |
21 | setSymbolName(name: string) {
22 | this.symbolName = name;
23 | }
24 |
25 | fromObject(obj: any) {
26 | super.fromObject(obj);
27 | let symbolName: string;
28 | if (obj instanceof Array) {
29 | symbolName = obj[8];
30 | } else {
31 | ({ symbolName } = obj);
32 | }
33 | if (symbolName) this.setSymbolName(symbolName);
34 | }
35 |
36 | toObject(isArray: boolean = false, distinct: boolean = true,unique:boolean=false) {
37 | let obj: any = super.toObject(isArray,distinct,unique);
38 | let symbolName = this.getSymbolName();
39 | if (isArray) {
40 | obj.push(symbolName);
41 | } else {
42 | obj.symbolName = symbolName;
43 | }
44 | return obj;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/model/TextNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 | import Command from "../command/Command";
5 | import AttributeChange from '../command/AttributeChange';
6 |
7 | export class TextNodeAttrs extends ShapeNodeAttrs {
8 | align = { "value": "left", "default": "left", "group": "geometry", "type": "String", enums: ["left", "center", "right"] };
9 | verticalAlign = { "value": "top", "default": "top", "group": "geometry", "type": "String", enums: ["top", "middle", "bottom"] };
10 | padding = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
11 | lineHeight = { "value": 1, "default": 1, "group": "geometry", "type": "Number" };
12 | wrap = { "value": "word", "default": "word", "group": "geometry", "type": "String", enums: ["word", "char", "none"] };
13 | ellipsis = { "value": false, "default": false, "group": "geometry", "type": "Boolean" };
14 | fontFamily = { "value": "Arial", "default": "Arial", "group": "font", "type": "String", enums: ["宋体", "Arial", "Calibri"] };
15 | fontSize = { "value": 12, "default": 12, "group": "font", "type": "Number" };
16 | fontStyle = { "value": "normal", "default": "normal", "group": "font", "type": "String", enums: ["normal", "bold", "italic", "bold italic"] };
17 | fontVariant = { "value": "normal", "default": "normal", "group": "font", "type": "String", enums: ["normal", "small-caps"] };
18 | textDecoration = { "value": "", "default": "", "group": "font", "type": "String", enums: ["", "underline", "line-through", "underline line-through"] };
19 | text = { "value": "", "default": "", "group": "text", "type": "String" };
20 | }
21 |
22 | export class TextNode extends ShapeNode {
23 |
24 | static className = 'TextNode';
25 | className = 'TextNode';
26 | attributes = new TextNodeAttrs();
27 |
28 | constructor(opt?: any) {
29 | super();
30 | if (opt) this.fromObject(opt);
31 | }
32 |
33 | createRef() {
34 | let konvaNode = new Konva.Text();
35 | konvaNode.id(this.id);
36 | this.ref = konvaNode;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/model/WedgeNode.ts:
--------------------------------------------------------------------------------
1 |
2 | import Konva from 'konva';
3 | import { ShapeNode, ShapeNodeAttrs } from './ShapeNode';
4 |
5 | export class WedgeNodeAttrs extends ShapeNodeAttrs {
6 | angle = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
7 | radius = { "value": 0, "default": 0, "group": "geometry", "type": "Number" };
8 | clockwise = { "value": false, "default": false, "group": "geometry", "type": "Boolean" };
9 | }
10 |
11 | export class WedgeNode extends ShapeNode {
12 |
13 | static className = 'WedgeNode';
14 | className:string='WedgeNode';
15 | attributes = new WedgeNodeAttrs();
16 |
17 | constructor(opt?: any) {
18 | super();
19 | if (opt) this.fromObject(opt);
20 | }
21 |
22 | createRef() {
23 | let konvaNode = new Konva.Wedge();
24 | konvaNode.id(this.id);
25 | this.ref = konvaNode;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/shape/AbstractShape.ts:
--------------------------------------------------------------------------------
1 | class AbstractShape {
2 |
3 | handleTagName: string | undefined;
4 |
5 | constructor() { }
6 |
7 | insertShapeElement(dataModel: any, node: any) {
8 | dataModel.addNodes([node]);
9 | }
10 |
11 | }
12 |
13 | export default AbstractShape;
--------------------------------------------------------------------------------
/src/shape/ArrowShape.ts:
--------------------------------------------------------------------------------
1 |
2 | import BaseLineShape from './BaseLineShape';
3 | import { LineArrowNode } from './../model/LineArrowNode';
4 |
5 | class ArrowShape extends BaseLineShape {
6 |
7 | constructor() {
8 | super();
9 | this.handleTagName = 'lineArrow';
10 | }
11 |
12 | createElement(firstPoint: any, point: any, graphEditor: any) {
13 | let lineArrowNode: any = new LineArrowNode();
14 | let defaultStyleConfig = graphEditor.getConfig().style;
15 | let lineStyle = {
16 | 'x': 0,
17 | 'y': 0,
18 | 'x1': firstPoint.x,
19 | 'y1': firstPoint.y,
20 | 'x2': point.x,
21 | 'y2': point.y,
22 | 'fill': defaultStyleConfig.stroke
23 | };
24 | let style = { ...lineStyle, ...defaultStyleConfig };
25 | lineArrowNode.setAttributeValues(style);
26 | return lineArrowNode;
27 | }
28 |
29 | }
30 |
31 | export default ArrowShape;
--------------------------------------------------------------------------------
/src/shape/BaseConnectedLineShape.ts:
--------------------------------------------------------------------------------
1 | import Konva from 'konva';
2 | import { DRAWING_MOUSE_DOWN, DRAWING_MOUSE_MOVE, DRAWING_MOUSE_UP } from '../constants/TemcConstants';
3 | import { LineNode } from '../model/LineNode';
4 | import BaseLineShape from './BaseLineShape';
5 | import AbstractShape from './AbstractShape';
6 |
7 | class BaseConnectedLineShape extends AbstractShape {
8 | createElement(firstPoint: any, point: any, graphEditor: any) {
9 | throw new Error('Method not implemented.');
10 | }
11 |
12 | constructor() {
13 | super();
14 | }
15 |
16 | getOriginPoint(konvaNode:any,point:any,stageTf:Konva.Transform){
17 | console.log(konvaNode.getAbsoluteTransform());
18 | let tfWithoutStage=konvaNode.getAbsoluteTransform().copy().multiply(stageTf.invert());
19 |
20 | console.log(tfWithoutStage);
21 | let nodeTf=tfWithoutStage.invert();
22 | return nodeTf.point(point);
23 |
24 | }
25 |
26 |
27 |
28 | }
29 |
30 | export default BaseConnectedLineShape;
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/shape/BaseLineShape.ts:
--------------------------------------------------------------------------------
1 | import Konva from 'konva';
2 | import AbstractShape from "./AbstractShape";
3 | import { DRAWING_MOUSE_DOWN, DRAWING_MOUSE_MOVE, DRAWING_MOUSE_UP, REGULAR_MODE,DRAWING_MOUSE_OUT } from '../constants/TemcConstants';
4 |
5 | abstract class BaseLineShape extends AbstractShape {
6 |
7 | firstPoint: any;
8 | tempLine: any;
9 |
10 | constructor() {
11 | super();
12 | }
13 |
14 | notifyDrawingAction(graphEditor: any, point: any, type: number, btn: number) {
15 | let isSquare = graphEditor.getIsSquare();
16 | switch (type) {
17 | case DRAWING_MOUSE_DOWN:
18 | if (btn === 0) {
19 | this.firstPoint = point;
20 | this.tempLine = new Konva.Line({
21 | x: 0,
22 | y: 0,
23 | points: [this.firstPoint.x, this.firstPoint.y],
24 | strokeWidth: 2,
25 | stroke: '#ff0000'
26 | })
27 | graphEditor.getDrawingLayer().add(this.tempLine);
28 | let line = new Konva.Line({
29 | x: 100,
30 | y: 50,
31 | points: [73, 70],
32 | stroke: 'red',
33 | tension: 1
34 | });
35 | graphEditor.getDrawingLayer().add(line);
36 | } else {
37 | if (this.firstPoint && this.tempLine) {
38 | if (isSquare) {
39 | point = this.relocationPoint(this.firstPoint, point);
40 | }
41 | let node = this.createElement(this.firstPoint, point, graphEditor);
42 | this.tempLine.destroy();
43 | if (node) {
44 | this.insertShapeElement(graphEditor.getDataModel(), node);
45 | }
46 |
47 | }
48 | if (this.tempLine) {
49 | this.tempLine.destroy();
50 | }
51 | }
52 |
53 | break;
54 | case DRAWING_MOUSE_MOVE:
55 | if (this.firstPoint && this.tempLine) {
56 | if (isSquare) {
57 | point = this.relocationPoint(this.firstPoint, point);
58 | }
59 | this.tempLine.setAttrs({
60 | points: [this.firstPoint.x, this.firstPoint.y, point.x, point.y],
61 | })
62 | }
63 | break;
64 | case DRAWING_MOUSE_UP:
65 | if (this.firstPoint && this.tempLine) {
66 | if (isSquare) {
67 | point = this.relocationPoint(this.firstPoint, point);
68 | }
69 | let node = this.createElement(this.firstPoint, point, graphEditor);
70 | this.tempLine.destroy();
71 | if (node) {
72 | this.insertShapeElement(graphEditor.getDataModel(), node);
73 | }
74 |
75 | }
76 | if (this.tempLine) {
77 | this.tempLine.destroy();
78 | }
79 | break;
80 | case DRAWING_MOUSE_OUT:
81 | //销毁对象
82 | if (this.tempLine) {
83 | this.tempLine.destroy();
84 | this.tempLine = null;
85 | }
86 |
87 | break;
88 | }
89 |
90 | }
91 |
92 | abstract createElement(firstPoint: any, point: any, graphEditor: any): any;
93 |
94 | relocationPoint(firstPoint: any, currentPoint: any) {
95 | let returnPoint = currentPoint;
96 | //获取斜率
97 | let k = (currentPoint.y - firstPoint.y) / (currentPoint.x - firstPoint.x);
98 | // 根据斜率来判断是什么样的直线
99 | if (k > -0.5 && k < 0.5) {
100 | //水平的直线
101 | returnPoint.x = currentPoint.x;
102 | returnPoint.y = firstPoint.y;
103 | } else if (k > 10 || k < -10) {
104 | //垂直的直线
105 | returnPoint.x = firstPoint.x;
106 | returnPoint.y = currentPoint.y;
107 | } else {
108 | //生成和坐标轴为45度角的直线
109 | if (k > 0) {
110 | returnPoint.x = currentPoint.x;
111 | returnPoint.y = currentPoint.x - firstPoint.x + firstPoint.y;
112 | } else {
113 | returnPoint.x = currentPoint.x;
114 | returnPoint.y = firstPoint.x + firstPoint.y - currentPoint.x;
115 | }
116 | }
117 | return returnPoint;
118 | }
119 |
120 | }
121 |
122 | export default BaseLineShape;
--------------------------------------------------------------------------------
/src/shape/BasePolylineShape.ts:
--------------------------------------------------------------------------------
1 | import Konva from 'konva';
2 | import AbstractShape from "./AbstractShape";
3 | import AttributeChange from '../command/AttributeChange';
4 | import Command from "../command/Command";
5 | import { DRAWING_MOUSE_DBL_CLICK, DRAWING_MOUSE_CLICK, DRAWING_MOUSE_MOVE, DRAWING_MOUSE_UP, REGULAR_MODE,DRAWING_MOUSE_OUT } from '../constants/TemcConstants';
6 | import { PolylineNode } from '../model/PolylineNode';
7 |
8 | class BasePolylineShape extends AbstractShape {
9 |
10 | tempLine: any;
11 |
12 | constructor() {
13 | super();
14 | }
15 |
16 | createElement(points: any, graphEditor: any) {
17 | let polyline = new PolylineNode();
18 | let style = Object.assign({ points }, graphEditor.getConfig().style);
19 | polyline.setAttributeValues({ ...style, hitStrokeWidth: style.strokeWidth * 2 + 8 });
20 | return polyline;
21 | }
22 |
23 | notifyDrawingAction(graphEditor: any, point: any, type: number, btn: number) {
24 | let isSquare = graphEditor.getIsSquare();
25 | switch (type) {
26 | case DRAWING_MOUSE_CLICK:
27 |
28 | if (btn === 0) {
29 | if (this.tempLine) {
30 | let points = this.tempLine.points();
31 | points.push(point.x, point.y);
32 | this.tempLine.points(points);
33 | } else {
34 | this.tempLine = new Konva.Line({
35 | points: [point.x, point.y, point.x, point.y],
36 | strokeWidth: 2,
37 | stroke: '#ff0000'
38 | });
39 | graphEditor.getDrawingLayer().add(this.tempLine);
40 | }
41 | }else{
42 |
43 | if (this.tempLine) {
44 |
45 | let points = this.tempLine.points();
46 | if(points.length==2) return
47 | let newPoints = JSON.parse(JSON.stringify(points));
48 | let polyline = this.createElement(newPoints, graphEditor);
49 | this.insertShapeElement(graphEditor.getDataModel(), polyline);
50 | this.tempLine.destroy();
51 | this.tempLine = null;
52 | }
53 | }
54 | break;
55 | case DRAWING_MOUSE_DBL_CLICK:
56 | if (this.tempLine) {
57 |
58 | let points = this.tempLine.points();
59 | points.pop();
60 | points.pop();
61 | points.pop();
62 | points.pop();
63 | if(points.length==2) return
64 | let newPoints = JSON.parse(JSON.stringify(points));
65 | let polyline = this.createElement(newPoints, graphEditor);
66 | this.insertShapeElement(graphEditor.getDataModel(), polyline);
67 | this.tempLine.destroy();
68 | this.tempLine = null;
69 | }
70 |
71 | break;
72 | case DRAWING_MOUSE_MOVE:
73 | if (this.tempLine) {
74 | let points = this.tempLine.points();
75 | points.pop();
76 | points.pop();
77 | let len=points.length;
78 | let previousPoint={x:points[len-2],y:points[len-1]};
79 | if(isSquare){
80 | point = this.reLocateLinePointWhenDrawing(previousPoint, point);
81 | }
82 | points.push(point.x, point.y);
83 | this.tempLine.points(points);
84 | }
85 | break;
86 | case DRAWING_MOUSE_OUT:
87 | //销毁对象
88 | if(this.tempLine){
89 | this.tempLine.destroy();
90 | this.tempLine = null;
91 | }
92 |
93 | break;
94 | case DRAWING_MOUSE_UP:
95 |
96 | break;
97 | }
98 | }
99 | reLocateLinePointWhenDrawing(oldPoint:any,point:any){
100 |
101 | //获取斜率
102 | let k = (point.y - oldPoint.y)
103 | / (point.x - oldPoint.x);
104 |
105 | // 根据斜率来判断是什么样的直线
106 | if (k > -0.5 && k < 0.5) {
107 | //水平的直线
108 | return {x:point.x,y:oldPoint.y};
109 | } else if (k > 10 || k < -10) {
110 | //垂直的直线
111 | return {x:oldPoint.x, y:point.y};
112 | } else {
113 | //生成和坐标轴为45度角的直线
114 | if (k > 0) {
115 | return {x:point.x, y:point.x
116 | - oldPoint.x
117 | + oldPoint.y};
118 | } else {
119 | return {x:point.x, y:oldPoint.x
120 | + oldPoint.y - point.x};
121 | }
122 | }
123 |
124 | }
125 | }
126 |
127 | export default BasePolylineShape;
--------------------------------------------------------------------------------
/src/shape/LineShape.ts:
--------------------------------------------------------------------------------
1 | import { LineNode } from './../model/LineNode';
2 | import BaseLineShape from './BaseLineShape';
3 |
4 | class LineShape extends BaseLineShape {
5 |
6 | constructor() {
7 | super();
8 | this.handleTagName = 'line';
9 | }
10 |
11 | createElement(firstPoint: any, point: any, graphEditor: any) {
12 | if (firstPoint.x == point.x && firstPoint.y == point.y) {
13 | return
14 | }
15 | let lineNode: any = new LineNode();
16 | let defaultStyleConfig = graphEditor.getConfig().style;
17 |
18 | let lineStyle = {
19 | 'x': 0,
20 | 'y': 0,
21 | 'x1': firstPoint.x,
22 | 'y1': firstPoint.y,
23 | 'x2': point.x,
24 | 'y2': point.y
25 | };
26 | let style = { ...lineStyle, ...defaultStyleConfig };
27 | lineNode.setAttributeValues(style);
28 | return lineNode;
29 |
30 |
31 | }
32 |
33 | }
34 |
35 | export default LineShape;
--------------------------------------------------------------------------------
/src/shape/PenShape.ts:
--------------------------------------------------------------------------------
1 | import Konva from "konva";
2 | import { DRAWING_MOUSE_DOWN, DRAWING_MOUSE_MOVE, DRAWING_MOUSE_OUT, DRAWING_MOUSE_UP, REGULAR_MODE } from "../constants/TemcConstants";
3 | import { PenNode } from "../model/PenNode";
4 | import AbstractShape from "./AbstractShape";
5 |
6 | class PenShape extends AbstractShape {
7 |
8 | firstPoint: any;
9 | tempLine: any;
10 |
11 | constructor() {
12 | super();
13 | this.handleTagName = 'pen';
14 | }
15 |
16 | notifyDrawingAction(graphEditor: any, point: any, type: number, btn: number) {
17 | switch (type) {
18 | case DRAWING_MOUSE_DOWN:
19 | if (btn === 0) {
20 | this.firstPoint = point;
21 | this.tempLine = new Konva.Line({
22 | x: 0,
23 | y: 0,
24 | points: [this.firstPoint.x, this.firstPoint.y],
25 | strokeWidth: 2,
26 | stroke: '#ff0000'
27 | })
28 | graphEditor.getHelpLayer().add(this.tempLine);
29 | } else {
30 | if (this.firstPoint) {
31 | let node = this.createElement(this.tempLine.points(), graphEditor);
32 | this.tempLine.destroy();
33 | if (node) {
34 | this.insertShapeElement(graphEditor.getDataModel(), node);
35 | }
36 |
37 | }
38 | if (this.tempLine) {
39 | this.tempLine.destroy();
40 | this.tempLine = null;
41 | }
42 | this.firstPoint = null;
43 | }
44 |
45 | break;
46 | case DRAWING_MOUSE_MOVE:
47 | if (this.firstPoint && this.tempLine) {
48 | let points = this.tempLine.points();
49 | points.push(point.x);
50 | points.push(point.y);
51 | this.tempLine.setAttrs({
52 | points: points,
53 | })
54 | }
55 | break;
56 | case DRAWING_MOUSE_UP:
57 |
58 | if (this.firstPoint && this.tempLine) {
59 | let node = this.createElement(this.tempLine.points(), graphEditor);
60 | this.tempLine.destroy();
61 | if (node) {
62 | this.insertShapeElement(graphEditor.getDataModel(), node);
63 | }
64 | }
65 | if (this.tempLine) {
66 | this.tempLine.destroy();
67 | this.tempLine = null;
68 | }
69 | this.firstPoint = null;
70 | break;
71 | case DRAWING_MOUSE_OUT:
72 | //销毁对象
73 | if(this.tempLine){
74 | this.tempLine.destroy();
75 | this.tempLine = null;
76 | }
77 |
78 | break;
79 | }
80 | }
81 |
82 | createElement(points: any, graphEditor: any) {
83 | if (points.length == 2)
84 | return
85 | let polyline = new PenNode();
86 | let style = Object.assign({ points }, graphEditor.getConfig().style);
87 | polyline.setAttributeValues({ ...style });
88 | return polyline;
89 | }
90 |
91 | }
92 |
93 | export default PenShape;
--------------------------------------------------------------------------------
/src/shape/PolylineArrowShape.ts:
--------------------------------------------------------------------------------
1 |
2 | import BasePolylineShape from './BasePolylineShape';
3 | import { PolylineArrowNode } from './../model/PolylineArrowNode';
4 |
5 | class PolylineArrowShape extends BasePolylineShape {
6 |
7 | constructor() {
8 | super();
9 | this.handleTagName = 'polylineArrow';
10 | }
11 |
12 | createElement(points: any, graphEditor: any) {
13 | let polyline = new PolylineArrowNode();
14 | let style = Object.assign({ points, 'fill': graphEditor.getConfig().style.stroke }, graphEditor.getConfig().style);
15 | polyline.setAttributeValues({ ...style, hitStrokeWidth: style.strokeWidth * 2 + 8 });
16 | return polyline;
17 | }
18 |
19 | }
20 |
21 | export default PolylineArrowShape;
--------------------------------------------------------------------------------
/src/shape/PolylineShape.ts:
--------------------------------------------------------------------------------
1 | import Konva from 'konva';
2 | import AbstractShape from "./AbstractShape";
3 | import { DRAWING_MOUSE_DOWN, DRAWING_MOUSE_MOVE, DRAWING_MOUSE_UP, REGULAR_MODE } from './../constants/TemcConstants';
4 | import { PolylineNode } from './../model/PolylineNode';
5 | import BasePolylineShape from './BasePolylineShape';
6 |
7 | class PolylineShape extends BasePolylineShape {
8 |
9 | constructor() {
10 | super();
11 | this.handleTagName = 'polyline';
12 | }
13 |
14 | createElement(points: any, graphEditor: any) {
15 | let polyline = new PolylineNode();
16 | let style = Object.assign({ points }, graphEditor.getConfig().style);
17 | polyline.setAttributeValues({ ...style, hitStrokeWidth: style.strokeWidth * 2 + 8 });
18 | return polyline;
19 | }
20 | }
21 |
22 | export default PolylineShape;
--------------------------------------------------------------------------------
/src/shape/StraightConnectedLineShape.ts:
--------------------------------------------------------------------------------
1 | import Konva from 'konva';
2 | import { DRAWING_MOUSE_DOWN, DRAWING_MOUSE_MOVE, DRAWING_MOUSE_UP } from '../constants/TemcConstants';
3 | import { LineNode } from '../model/LineNode';
4 | import BaseLineShape from './BaseLineShape';
5 |
6 | import AbstractShape from './AbstractShape';
7 | import BaseConnectedLineShape from './BaseConnectedLineShape';
8 | import { StraightConnectedLineNode } from '../model/StraightConnectedLineNode';
9 |
10 | class StraightConnectedLineShape extends BaseConnectedLineShape {
11 | firstPoint: any;
12 | tempLine: any;
13 | createElement(firstPoint: any, point: any, graphEditor: any) {
14 | throw new Error('Method not implemented.');
15 | }
16 |
17 | constructor() {
18 | super();
19 | this.handleTagName = 'straightConnectedLine';
20 | }
21 | notifyDrawingAction(graphEditor: any, point: any, type: number) {
22 |
23 | let isSquare = graphEditor.getIsSquare();
24 | switch (type) {
25 | case DRAWING_MOUSE_DOWN:
26 | console.log('StraightConnectedLineShape notifyDrawingAction mousedonw')
27 | if(graphEditor.currentTarget!=null){
28 | console.log(graphEditor.currentTarget)
29 | this.firstPoint = point;
30 | let fromPoint=this.getOriginPoint(graphEditor.currentTarget,point,graphEditor.stage.getAbsoluteTransform().copy());
31 | let sourceId=graphEditor.currentTarget.attrs.id;
32 | console.log(sourceId);
33 | this.tempLine = new Konva.Line({
34 | x: 0,
35 | y: 0,
36 | points: [this.firstPoint.x, this.firstPoint.y],
37 | strokeWidth: 2,
38 | stroke: '#ff0000',
39 |
40 | })
41 | this.tempLine.from={
42 | id:sourceId,
43 | point:fromPoint
44 | }
45 | graphEditor.getDrawingLayer().add(this.tempLine);
46 | let line = new Konva.Line({
47 | x: 100,
48 | y: 50,
49 | points: [73, 70],
50 | stroke: 'red',
51 | tension: 1
52 | });
53 | graphEditor.getDrawingLayer().add(line);
54 | }
55 |
56 | break;
57 | case DRAWING_MOUSE_MOVE:
58 |
59 |
60 | if (this.firstPoint && this.tempLine) {
61 | this.tempLine.setAttrs({
62 | points: [this.firstPoint.x, this.firstPoint.y, point.x, point.y],
63 | })
64 | }
65 | break;
66 | case DRAWING_MOUSE_UP:
67 | if (this.firstPoint && graphEditor.currentTarget) {
68 | let toPoint=this.getOriginPoint(graphEditor.currentTarget,point,graphEditor.stage.getAbsoluteTransform().copy());
69 | let disTargetId=graphEditor.currentTarget.attrs.id;
70 | let node = this.createElementWithTarget(this.firstPoint, point, graphEditor,this.tempLine.from,{
71 | id:disTargetId,
72 | point:toPoint
73 | });
74 | this.tempLine.destroy();
75 | if(node){
76 | this.insertShapeElement(graphEditor.getDataModel(), node);
77 | }
78 |
79 | }
80 | if (this.tempLine) {
81 | this.tempLine.destroy();
82 | }
83 | break;
84 | }
85 |
86 | }
87 |
88 |
89 | createElementWithTarget(firstPoint: any, point: any, graphEditor: any,fromInfo:any,toInfo:any) {
90 | if (firstPoint.x == point.x && firstPoint.y == point.y) {
91 | return
92 | }
93 | let lineNode: any = new StraightConnectedLineNode();
94 | let defaultStyleConfig = graphEditor.getConfig().style;
95 |
96 | let lineStyle = {
97 | 'x': 0,
98 | 'y': 0,
99 | 'points': [firstPoint.x,firstPoint.y,point.x,point.y],
100 | 'draggable':false
101 |
102 | };
103 | let style = {...defaultStyleConfig,...lineStyle};
104 | console.log(style);
105 | lineNode.setFrom(fromInfo);
106 | lineNode.setTo(toInfo);
107 | lineNode.setAttributeValues(style);
108 |
109 | return lineNode;
110 |
111 |
112 | }
113 |
114 |
115 | }
116 |
117 | export default StraightConnectedLineShape;
118 |
119 |
120 |
--------------------------------------------------------------------------------
/src/snapGrid/SnapGrid.ts:
--------------------------------------------------------------------------------
1 | import Konva from 'konva'
2 |
3 |
4 | import type {
5 | GuideLine,
6 | LineStops,
7 | NodeEdgeBound,
8 | Orientation,
9 | Nullable,
10 | EditorConfig
11 | } from '../types'
12 | import GraphEditor from '../GraphEditor'
13 |
14 |
15 | export class SnapGrid {
16 |
17 | private editor: GraphEditor
18 |
19 |
20 | private lines: Konva.Line[] = []
21 |
22 |
23 | private GUIDELINE_OFFSET = 5
24 |
25 |
26 | private options?: Nullable = {}
27 | private active = false
28 | stage: any
29 | private lineGuideStops: LineStops;
30 |
31 | /**
32 | * 构造函数
33 | * @param editor GraphEditor对象
34 | */
35 | constructor(editor: GraphEditor) {
36 | let config = editor.config as EditorConfig;
37 | const options = config.drawing?.snapToGrid
38 |
39 | this.editor = editor
40 | this.options = options
41 | if (options?.enable) {
42 | this.active = options.enable;
43 | }
44 | if (this.active && this.editor) {
45 | this.stage = this.editor.stage;
46 | this.registerEvents()
47 | }
48 | }
49 |
50 |
51 | /**
52 | * Creates list of line stops to find guide lines
53 | *
54 | * @param konvaNode the dragging node
55 | * @returns the list of line stops
56 | */
57 | private getLineGuideStops(konvaNode: Konva.Shape): LineStops {
58 | var vertical = [0, this.stage.width() / 2, this.stage.width()];
59 | var horizontal = [0, this.stage.height() / 2, this.stage.height()];
60 |
61 | this.editor.getDataModel()?.getNodes().forEach((shape: any) => {
62 |
63 | if (shape.getRef() === konvaNode) {
64 | return
65 | }
66 |
67 | const box = shape.getRef().getClientRect()
68 |
69 | vertical.push([box.x, box.x + box.width, box.x + box.width / 2]);
70 | horizontal.push([box.y, box.y + box.height, box.y + box.height / 2]);
71 | })
72 |
73 | return {
74 | vertical: vertical.flat(),
75 | horizontal: horizontal.flat(),
76 | };
77 | }
78 |
79 | // what points of the object will trigger to snapping?
80 | // it can be just center of the object
81 | // but we will enable all edges and center
82 | private getObjectSnappingEdges(node: any) {
83 | var box = node.getClientRect();
84 | var absPos = node.absolutePosition();
85 | return {
86 | vertical: [
87 | {
88 | guide: Math.round(box.x),
89 | offset: Math.round(absPos.x - box.x),
90 | snap: 'start',
91 | },
92 | {
93 | guide: Math.round(box.x + box.width / 2),
94 | offset: Math.round(absPos.x - box.x - box.width / 2),
95 | snap: 'center',
96 | },
97 | {
98 | guide: Math.round(box.x + box.width),
99 | offset: Math.round(absPos.x - box.x - box.width),
100 | snap: 'end',
101 | },
102 | ],
103 | horizontal: [
104 | {
105 | guide: Math.round(box.y),
106 | offset: Math.round(absPos.y - box.y),
107 | snap: 'start',
108 | },
109 | {
110 | guide: Math.round(box.y + box.height / 2),
111 | offset: Math.round(absPos.y - box.y - box.height / 2),
112 | snap: 'center',
113 | },
114 | {
115 | guide: Math.round(box.y + box.height),
116 | offset: Math.round(absPos.y - box.y - box.height),
117 | snap: 'end',
118 | },
119 | ],
120 | };
121 | }
122 |
123 | /**
124 | * Creates the guide lines based on the given line stops and edge bounds
125 | *
126 | * @param lineStops the line stops
127 | * @param nodeEdgeBounds the node edge bounds
128 | * @returns the list of guide lines
129 | */
130 | private getGuides(lineGuideStops: any, itemBounds: any): GuideLine[] {
131 | var resultV: any = [];
132 | var resultH: any = [];
133 |
134 | lineGuideStops.vertical.forEach((lineGuide: number) => {
135 | itemBounds.vertical.forEach((itemBound: any) => {
136 | var diff = Math.abs(lineGuide - itemBound.guide);
137 | // if the distance between guild line and object snap point is close we can consider this for snapping
138 | if (diff < this.GUIDELINE_OFFSET) {
139 | resultV.push({
140 | lineGuide: lineGuide,
141 | diff: diff,
142 | snap: itemBound.snap,
143 | offset: itemBound.offset,
144 | });
145 | }
146 | });
147 | });
148 |
149 | lineGuideStops.horizontal.forEach((lineGuide: number) => {
150 | itemBounds.horizontal.forEach((itemBound: any) => {
151 | var diff = Math.abs(lineGuide - itemBound.guide);
152 | if (diff < this.GUIDELINE_OFFSET) {
153 | resultH.push({
154 | lineGuide: lineGuide,
155 | diff: diff,
156 | snap: itemBound.snap,
157 | offset: itemBound.offset,
158 | });
159 | }
160 | });
161 | });
162 |
163 | var guides: GuideLine[] = [];
164 |
165 | // find closest snap
166 | var minV = resultV.sort((a: any, b: any) => a.diff - b.diff)[0];
167 | var minH = resultH.sort((a: any, b: any) => a.diff - b.diff)[0];
168 | if (minV) {
169 | guides.push({
170 | stop: minV.lineGuide,
171 | offset: minV.offset,
172 | orientation: 'vertical',
173 | snap: minV.snap,
174 | });
175 | }
176 | if (minH) {
177 | guides.push({
178 | stop: minH.lineGuide,
179 | offset: minH.offset,
180 | orientation: 'horizontal',
181 | snap: minH.snap,
182 | });
183 | }
184 | return guides;
185 | }
186 |
187 | /**
188 | * Draws the guide lines
189 | *
190 | * @param guideLines the list of guide lines
191 | */
192 | private drawGuides(guideLines: GuideLine[]) {
193 | if (!guideLines.length) {
194 | return
195 | }
196 |
197 | guideLines.forEach(guideLine => {
198 | const options: Konva.LineConfig = {
199 | stroke: '#000',
200 | strokeWidth: 1,
201 | dash: [2, 6],
202 | ...this.options
203 | }
204 |
205 | if (guideLine.orientation === 'vertical') {
206 | options.points = [0, 0, 0, this.stage.height()]
207 | } else if (guideLine.orientation === 'horizontal') {
208 | options.points = [0, 0, this.stage.width(), 0]
209 | }
210 |
211 | const line = new Konva.Line(options)
212 | //考虑line受stage的transform的影响
213 | const stageTransform = this.stage?.getAbsoluteTransform().copy();
214 | stageTransform?.invert();
215 | if (guideLine.orientation === 'vertical') {
216 |
217 | let translateFactor = stageTransform?.point({
218 | x: guideLine.stop,
219 | y: 0
220 | });
221 | line.absolutePosition(translateFactor);
222 |
223 | } else if (guideLine.orientation === 'horizontal') {
224 | let translateFactor = stageTransform?.point({
225 | x: 0,
226 | y: guideLine.stop
227 | });
228 | line.absolutePosition(translateFactor)
229 | }
230 |
231 | this.editor.getHelpLayer().add(line)
232 | this.lines.push(line)
233 | })
234 | }
235 |
236 | /**
237 | * Sets the node's absolute position to the nearest snap line
238 | *
239 | * @param node The node of dragging shape
240 | * @param guideLines The list of guide lines
241 | */
242 | private setNodePosition(node: Konva.Shape, guideLines: GuideLine[]) {
243 | guideLines.forEach(({ orientation, stop: stop, offset }) => {
244 | const position = node.absolutePosition()
245 |
246 | if (orientation === 'vertical') {
247 | position.x = stop + offset
248 | } else if (orientation === 'horizontal') {
249 | position.y = stop + offset
250 | }
251 |
252 | node.absolutePosition(position)
253 | })
254 | }
255 |
256 |
257 | /**
258 | * 选中框的移动事件
259 | * @param event 事件
260 | */
261 | private onDragMove(event: Konva.KonvaEventObject) {
262 | const transformer = event.target as unknown as Konva.Transformer;
263 | const node = transformer.nodes().length == 1 ? (transformer.nodes()[0] as Konva.Shape) : event.target as Konva.Shape
264 | this.destroy()
265 | // find possible snapping lines
266 | if (!this.lineGuideStops) {
267 | this.lineGuideStops = this.getLineGuideStops(node);
268 | }
269 |
270 | // find snapping points of current object
271 | var itemBounds = this.getObjectSnappingEdges(node);
272 | // now find where can we snap current object
273 | var guides = this.getGuides(this.lineGuideStops, itemBounds);
274 | // do nothing of no snapping
275 | if (!guides.length) {
276 | return;
277 | }
278 | this.drawGuides(guides);
279 | this.setNodePosition(node, guides)
280 | }
281 |
282 | /**
283 | * 移动结束后
284 | */
285 | private onDragEnd() {
286 | this.lineGuideStops =null;
287 | this.destroy()
288 | }
289 |
290 | /**
291 | * 销毁
292 | */
293 | private destroy() {
294 | if (this.lines.length > 0) {
295 | this.lines.forEach(line => line.destroy())
296 | this.lines = []
297 | }
298 | }
299 |
300 | /**
301 | * 注册事件
302 | */
303 | private registerEvents() {
304 | this.editor.transformer.on('dragmove', this.onDragMove.bind(this))
305 | this.editor.transformer.on('dragend', this.onDragEnd.bind(this))
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/src/types/common.ts:
--------------------------------------------------------------------------------
1 | import { NodeAttrs } from "../model/Node"
2 |
3 |
4 | export type Nullable = T | null
5 |
6 | export interface UnknownObject {
7 | [key: string]: boolean | number | string | object | null | undefined
8 | }
9 | export type AlignDirection = 'left'| 'right'| 'top' | 'bottom' | 'horizontal' | 'vertical'
10 | export type MoveDirection = 'left'| 'right'| 'up' | 'down'
11 | export type OrderDirection = 'top'| 'bottom'| 'up' | 'down'
12 | export const orderDirection = ["top", "bottom", "up", "down"];
13 | export type EventType = 'click' | 'valuechange'| 'mousemove' | 'mouseout' | 'dblclick'
14 | export type EventAction = 'changeAttributes' | 'updateAnimation' | 'executeScript'
15 | export type EventWhenType = 'script'|'operation'
16 | export declare interface Dimensions {
17 | width: number
18 | height: number
19 | }
20 |
21 | export declare interface Point {
22 | x: number
23 | y: number
24 | }
25 |
26 | export declare interface NodeConfig {
27 | className: string
28 | attributes: any
29 | animation?:any
30 | variables?:any
31 | }
32 |
33 | export declare interface GEvent {
34 | type?:EventType,
35 | action?:EventAction,
36 | attributes?:any,
37 | animation?:boolean,
38 | script?:string,
39 | triggers?:[{
40 | type?:EventWhenType,
41 | operation?:{
42 | source?: any,
43 | operator?: string,
44 | target?: any,
45 | }
46 | script?:string
47 | }]
48 | }
49 |
--------------------------------------------------------------------------------
/src/types/config.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | import type { Nullable } from '.'
5 | import type Konva from 'konva'
6 |
7 | export declare interface BaseConfig {
8 | container?: HTMLDivElement
9 | graph?:string
10 | style?:StyleConfig
11 | }
12 | export declare interface EditorConfig extends BaseConfig {
13 |
14 | view?: Partial<{
15 | grid:GridConfig,
16 | size: Partial<{
17 | width: number,
18 | height: number
19 | }>
20 | }>
21 | drawing?: Partial<{
22 | snapToGrid: Partial<{
23 | enable: boolean,
24 | strokeWidth: number,
25 | stroke: string,
26 | dash: Array
27 | }>
28 | editAnchor:Partial<{
29 | anchorFill: string,
30 | anchorStroke: string
31 | }>
32 | connectable:boolean
33 | }>
34 | selection?: Partial<{
35 | interactive: boolean
36 | keyboard: Partial<
37 | Nullable<{
38 | enabled: boolean
39 | movingSpaces: number
40 | map: Partial<
41 | Nullable<{
42 | delete?: string[]
43 | moveLeft?: string[]
44 | moveRight?: string[]
45 | moveUp?: string[]
46 | moveDown?: string[]
47 | moveLeftSlow?: string[]
48 | moveRightSlow?: string[]
49 | moveUpSlow?: string[]
50 | moveDownSlow?: string[]
51 | selectAll?: string[]
52 | deselect?: string[]
53 | inverseSelect?: string[]
54 | copy?: string[]
55 | paste?: string[]
56 | group?: string[]
57 | undo?:string[]
58 | redo?:string[]
59 | toSelectMode?:string[]
60 | }>
61 | >
62 | }>
63 | >
64 | transformer: Konva.TransformerConfig
65 | zone: Partial<{
66 | fill: string,
67 | stroke: string
68 | }>
69 | }>
70 | history?: Partial<{
71 | keyboard: {
72 | enabled: boolean
73 | }
74 | }>
75 | }
76 |
77 | export declare interface ViewerConfig extends BaseConfig{
78 |
79 | }
80 |
81 | export declare interface StyleConfig{
82 | strokeWidth?: number,
83 | stroke?: string,
84 | draggable?: boolean,
85 | strokeScaleEnabled?: boolean,
86 | hitStrokeWidth?: number,
87 | fill?:string
88 | }
89 |
90 | export declare interface GridConfig{
91 | show?: boolean,
92 | distance?: number,
93 | color?: string
94 | }
95 |
96 | export declare type Config = Partial
97 |
98 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './common'
2 | export * from './snap-grid'
3 | export * from './config'
--------------------------------------------------------------------------------
/src/types/snap-grid.ts:
--------------------------------------------------------------------------------
1 | export type Orientation = 'vertical' | 'horizontal'
2 | export type Snap = 'start' | 'end' | 'center'
3 |
4 | export type LineStops = Record | null
5 |
6 | export interface NodeEdgeBound {
7 | guide: number
8 | offset: number
9 | snap: Snap
10 | orientation: Orientation
11 | }
12 |
13 | export interface GuideLine {
14 | stop: number
15 | snap: Snap
16 | offset: number
17 | orientation: Orientation
18 | }
19 |
--------------------------------------------------------------------------------
/test/performance-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KonvaJS Sandbox
7 |
9 |
16 |
18 |
19 |
20 |
21 |
22 | 预览
23 | 清空画布
24 | 多个gif
25 | 跳转
26 | 保存
27 | 开启动画
28 |
29 |
33 |
34 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/test/shape/basic.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | const list = [
4 | {
5 | label: '矩形',
6 | className: 'RectNode',
7 | attributes: {
8 | width: 100,
9 | height: 50,
10 | fill:''
11 | },
12 | path: ' '
13 | },
14 | {
15 | label: '椭圆',
16 | className: 'EllipseNode',
17 | attributes: {
18 | radiusX: 50,
19 | radiusY: 25
20 | },
21 | path: ' '
22 | },
23 | {
24 | label: '正多边形',
25 | className: 'RegularPolygonNode',
26 | attributes: {
27 | sides: 6,
28 | radius: 40,
29 | },
30 | path: ' '
31 | },
32 | {
33 | label: '星形',
34 | className: 'StarNode',
35 | attributes: {
36 | numPoints: 6,
37 | innerRadius: 20,
38 | outerRadius: 40,
39 | },
40 | path: ' '
41 | },
42 | {
43 | label: '圆形',
44 | className: 'CircleNode',
45 | attributes: {
46 | radius: 50
47 | },
48 | path: ' '
49 | },
50 | {
51 | label: '环形',
52 | className: 'RingNode',
53 | attributes: {
54 | innerRadius: 20,
55 | outerRadius: 40
56 | },
57 | path: ' '
58 | },
59 | {
60 | label: '扇形',
61 | className: 'WedgeNode',
62 | attributes: {
63 | angle: 90,
64 | radius: 50,
65 | clockwise: true
66 | },
67 | path: ' '
68 | },
69 | {
70 | label: '弧形',
71 | className: 'ArcNode',
72 | attributes: {
73 | angle: 30,
74 | innerRadius: 50,
75 | outerRadius: 100,
76 | clockwise: false
77 | },
78 | path: ' '
79 | },
80 | {
81 | label: '心形',
82 | className: 'PathNode',
83 | attributes: {
84 | data: 'M213.1,6.7c-32.4-14.4-73.7,0-88.1,30.6C110.6,4.9,67.5-9.5,36.9,6.7C2.8,22.9-13.4,62.4,13.5,110.9C33.3,145.1,67.5,170.3,125,217c59.3-46.7,93.5-71.9,111.5-106.1C263.4,64.2,247.2,22.9,213.1,6.7z'
85 | },
86 | path: ' '
87 | },
88 | {
89 | label:'风扇',
90 | className:'PathNode',
91 | attributes:{
92 | scaleX:0.1,
93 | scaleY:0.1,
94 | data:'M696.8 504.5c0-11.7-1.4-23-3.4-34.1 2 11 3.4 22.4 3.4 34.1h260.8c0-135.6-60.6-256.8-155.8-338.8L632.4 364.6c-32.7-28.6-75-46.4-121.9-46.4-11.5 0-22.7 1.4-33.6 3.4 10.9-2 22.1-3.4 33.6-3.4V57.3c-137.1 0-259.6 61.8-341.6 158.9L368 385.8c-26.9 32.3-43.8 73.3-43.8 118.7 0 11.5 1.4 22.7 3.4 33.6-2-10.9-3.4-22.1-3.4-33.6H63.3c0 137.1 61.8 259.6 158.9 341.6L391.8 647c32.3 26.9 73.3 43.8 118.7 43.8 11.7 0 23-1.4 34.1-3.4-11.1 2.1-22.4 3.4-34.1 3.4v260.8c135.6 0 256.8-60.6 338.8-155.8l-199-169.4c28.6-32.8 46.5-75.1 46.5-121.9zM684 437.9c3.5 8.8 6.4 17.9 8.5 27.3-2.1-9.4-5.1-18.5-8.5-27.3z m-26-46.1c0.4 0.5 0.7 0.9 1.1 1.3-0.4-0.4-0.8-0.8-1.1-1.3z m10.9 15.7c4 6.4 7.7 13.1 10.9 20.1-3.2-7-7-13.6-10.9-20.1z m-198.1-84.9c-9.3 2-18.3 5-27 8.4 8.7-3.4 17.7-6.3 27-8.4z m-38.2 13.1c-6.9 3.2-13.5 6.8-19.9 10.8 6.4-4 13.1-7.6 19.9-10.8z m-35.2 21.7c-0.9 0.7-1.7 1.5-2.6 2.2 0.8-0.8 1.7-1.5 2.6-2.2zM337 571.2c-3.4-8.7-6.3-17.7-8.4-27 2 9.3 5 18.2 8.4 27z m28.5 49c-0.7-0.9-1.5-1.7-2.2-2.6 0.8 0.9 1.5 1.7 2.2 2.6z m-13-18z m46.2-97.7c0-61.6 50.1-111.8 111.8-111.8s111.8 50.1 111.8 111.8c0 61.6-50.1 111.8-111.8 111.8s-111.8-50.2-111.8-111.8zM623.1 652c-0.5 0.4-0.9 0.7-1.3 1.1 0.4-0.4 0.8-0.8 1.3-1.1z m-15.7 10.9c-6.4 4-13.1 7.7-20.1 10.9 7-3.2 13.7-7 20.1-10.9zM577 678c-8.8 3.5-17.9 6.4-27.3 8.5 9.5-2.1 18.5-5.1 27.3-8.5z'
95 | },
96 | path:' '
97 |
98 |
99 | },
100 | {
101 | label: '文字',
102 | className: 'TextNode',
103 | attributes: {
104 | text: '文本信息',
105 | fontSize: 14,
106 | fontFamily: 'Calibri',
107 | padding:5,
108 | fill:'#ff0000',
109 | strokeWidth:0
110 | },
111 | path: ' '
112 | },
113 | {
114 | label: '标签',
115 | className: 'LabelNode',
116 | attributes: {
117 | text: 'Tooltip',
118 | fontSize: 30,
119 | fontFamily: 'Calibri',
120 | textFill:'#ffffff',
121 | fill: 'black',
122 | pointerWidth: 10,
123 | pointerHeight: 10,
124 | lineJoin: 'round',
125 | shadowColor: 'black',
126 | shadowBlur: 10,
127 | shadowOffsetX: 10,
128 | shadowOffsetY: 10,
129 | shadowOpacity: 0.5,
130 | padding:10,
131 | pointerDirection:'down'
132 | },
133 |
134 | path: ' '
135 | },
136 | {
137 | label: '图片',
138 | className: 'ImageNode',
139 | attributes: {
140 | width:200,
141 | height:200,
142 | fill:"",
143 | stroke:"",
144 | image:'https://i.postimg.cc/BQDBBTp3/image.jpg'
145 | },
146 | url: 'https://i.postimg.cc/BQDBBTp3/image.jpg'
147 | },
148 | {
149 | label: 'Yoda',
150 | className: 'ImageNode',
151 | attributes: {
152 | width:200,
153 | height:200,
154 | fill:"",
155 | stroke:"",
156 | image:'https://konvajs.org/assets/yoda.gif'
157 | },
158 | url: 'https://konvajs.org/assets/yoda.gif'
159 | }
160 | ];
161 |
162 |
163 |
164 | export default list;
--------------------------------------------------------------------------------
/test/unit-tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
15 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/test/unit/Animation-test.ts:
--------------------------------------------------------------------------------
1 | import assert from "assert";
2 | import { createEditor } from "./test-utils";
3 |
4 | describe('Animation', function () {
5 |
6 | let editor=createEditor('Animation');
7 | it('add blink rect node to editor', function () {
8 |
9 | editor.addNode({
10 | className: 'RectNode',
11 | attributes: {
12 | x: 50,
13 | y: 50,
14 | width: 100,
15 | height: 60,
16 | strokeWidth: 2,
17 | fill:'blue',
18 | stroke: 'red'
19 | }
20 | })
21 | editor.setAnimation({
22 | 'type':'blink',
23 | 'autoPlay':true
24 | })
25 | assert.equal(editor.getSelection().length, 1);
26 | assert.equal(editor.getSelection()[0].animation.type, 'blink');
27 | })
28 | it('add rotate rect node to editor', function () {
29 |
30 | editor.addNode({
31 | className: 'RectNode',
32 | attributes: {
33 | x: 50,
34 | y: 100,
35 | width: 100,
36 | height: 60,
37 | strokeWidth: 2,
38 | fill:'yellow',
39 | stroke: 'red'
40 | }
41 | })
42 | editor.setAnimation({
43 | 'type':'rotateByCenter',
44 | 'autoPlay':true
45 | })
46 | assert.equal(editor.getSelection().length, 1);
47 | assert.equal(editor.getSelection()[0].animation.type, 'rotateByCenter');
48 | })
49 | it('add flow line node to editor', function () {
50 |
51 | editor.addNode({
52 | className: 'LineNode',
53 | attributes: {
54 | x1: 10,
55 | y1: 10,
56 | x2: 200,
57 | y2: 10,
58 | strokeWidth: 2,
59 | stroke: 'red',
60 | dash:[5,5]
61 | }
62 | })
63 | editor.setAnimation({
64 | 'type':'flow',
65 | 'autoPlay':true
66 | })
67 | assert.equal(editor.getSelection().length, 1);
68 | assert.equal(editor.getSelection()[0].animation.type, 'flow');
69 | })
70 | it('add path rotate node to editor', function () {
71 |
72 | editor.addNode({
73 | className: 'LineNode',
74 | attributes: {
75 | x1: 10,
76 | y1: 10,
77 | x2: 200,
78 | y2: 10,
79 | strokeWidth: 2,
80 | stroke: 'red',
81 | dash:[5,5]
82 | }
83 | })
84 | editor.setAnimation({
85 | 'type':'rotateByCenter',
86 | 'autoPlay':true
87 | })
88 | assert.equal(editor.getSelection().length, 1);
89 | assert.equal(editor.getSelection()[0].animation.type, 'flow');
90 | })
91 | })
--------------------------------------------------------------------------------
/test/unit/Arc-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 |
4 | import { createEditor } from './test-utils';
5 | import assert from 'assert';
6 | import { ArcNode } from '../../src/model/ArcNode';
7 | describe('ArcNode', function () {
8 |
9 | it('createNode', function () {
10 | let editor = createEditor('ArcNode');
11 | let arcNode = new ArcNode(
12 | {
13 | attributes: {
14 | x: 50,
15 | y: 50,
16 | angle: 50,
17 | innerRadius: 20,
18 | outerRadius: 50,
19 | strokeWidth: 2,
20 | stroke: 'red',
21 | draggable:true
22 | }
23 | }
24 | );
25 | editor.getDataModel().addNode(arcNode);
26 | assert.equal(arcNode.getAttributeValue('angle'), 50);
27 |
28 | })
29 | it('add rect node to editor', function () {
30 |
31 | // editor.addNode({
32 | // className: 'ArcNode',
33 | // attributes: {
34 | // x: 50,
35 | // y: 50,
36 | // angle: 50,
37 | // innerRadius: 20,
38 | // outerRadius: 50,
39 | // strokeWidth: 2,
40 | // stroke: 'red'
41 | // }
42 | // })
43 | // assert.equal(editor.getSelection().length, 1);
44 | // assert.equal(editor.getSelection()[0].attributes.innerRadius, 20);
45 | })
46 |
47 | })
48 |
49 |
50 |
--------------------------------------------------------------------------------
/test/unit/Circle-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 |
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | import { CircleNode } from '../../src/model/CircleNode';
7 | describe('CircleNode', function () {
8 |
9 | it('createNode', function () {
10 | let circleNode=new CircleNode(
11 | {
12 | attributes: {
13 | x: 50,
14 | y: 50,
15 | radius: 50,
16 | strokeWidth: 2,
17 | stroke: 'red'
18 | }
19 | }
20 | );
21 | assert.equal(circleNode.getAttributeValue('radius'), 50);
22 |
23 | })
24 | it('add circle node to editor', function () {
25 | let editor=createEditor('CircleNode');
26 | editor.addNode({
27 | className: 'CircleNode',
28 | attributes: {
29 | x: 50,
30 | y: 50,
31 | radius: 50,
32 | stroke: 'red'
33 | }
34 | })
35 | assert.equal(editor.getSelection().length, 1);
36 | })
37 | })
38 |
39 |
40 |
--------------------------------------------------------------------------------
/test/unit/DataModel-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import { createEditor } from './test-utils';
5 | import assert from 'assert';
6 | import { PathNode } from '../../src/model/PathNode';
7 | import { PenNode } from '../../src/model/PenNode';
8 | import { PolylineNode } from '../../src/model/PolylineNode';
9 | import { RegularPolygonNode } from '../../src/model/RegularPolygonNode';
10 | import { StarNode } from '../../src/model/StarNode';
11 | import { TextNode } from '../../src/model/TextNode';
12 | import { WedgeNode } from '../../src/model/WedgeNode';
13 |
14 | describe('DataModel', function () {
15 | let editor = createEditor('DataModel');
16 | let wedgeNode;
17 | let rectNode;
18 | it('create wedge node', function () {
19 | wedgeNode = new WedgeNode(
20 | {
21 | attributes: {
22 | x: 50,
23 | y: 50,
24 | angle: 100,
25 | radius: 15,
26 | clockwise: false,
27 | strokeWidth: 2,
28 | stroke: 'green',
29 | draggable: true
30 | }
31 | }
32 | );
33 | rectNode = new RectNode(
34 | {
35 | attributes: {
36 | x: 50,
37 | y: 50,
38 | width: 100,
39 | height: 60,
40 | strokeWidth: 2,
41 | stroke: 'red'
42 | }
43 | }
44 | );
45 | editor?.getDataModel()?.addNode(wedgeNode);
46 | editor?.getDataModel()?.addNode(rectNode);
47 | assert.equal(wedgeNode.getAttributeValue('radius'), 15);
48 |
49 | })
50 | it('getNodes', function () {
51 | let nodes = editor?.getDataModel()?.getNodes();
52 | assert.equal(nodes?.length, 2);
53 |
54 | })
55 | it('getNodeById', function () {
56 | let nodes = editor?.getDataModel()?.getNodes();
57 | assert.equal(nodes?.length, 2);
58 |
59 | })
60 | it('toObject', function () {
61 | let obj = editor?.getDataModel()?.toObject();
62 | console.log(JSON.stringify(obj));
63 | assert.equal(obj?.nodes.length, 2);
64 | })
65 | it('onSelectionChanged', function () {
66 | editor?.getDataModel()?.onSelectionChanged(function (sender, event) {
67 | console.log("onSelectionChange", event);
68 | });
69 | })
70 | it('onModelChanged', function () {
71 | editor?.getDataModel()?.onModelChanged(function (sender, event) {
72 | console.log("onModelChanged", event);
73 | });
74 | })
75 | it('group', function () {
76 | editor?.getDataModel()?.group([wedgeNode, rectNode]);
77 | let nodes = editor?.getDataModel()?.getNodes();
78 | assert.equal(nodes?.length, 1);
79 | })
80 | it('unGroup', function () {
81 | let nodes = editor?.getDataModel()?.getNodes();
82 | if (nodes?.length) {
83 | editor?.getDataModel()?.unGroup([nodes[0]]);
84 | }
85 |
86 | assert.equal(nodes?.length, 2);
87 | })
88 | // it('saveVariable', function () {
89 | // editor?.getDataModel()?.addVariable('state', {
90 | // defaultVal: 1
91 | // });
92 | // assert.equal(editor?.getDataModel()?.getVariable('state').defaultVal, 1);
93 | // })
94 | // it('getVariables', function () {
95 | // assert.equal(editor?.getDataModel()?.getVariables().hasOwnProperty('state'), true);
96 | // })
97 | // it('setVariables', function () {
98 | // editor?.getDataModel()?.setVariables({
99 | // 'temperature': {
100 | // defaultVal: 1
101 | // }
102 | // });
103 | // assert.equal(editor?.getDataModel()?.getVariables().hasOwnProperty('temperature'), true);
104 | // })
105 | // it('deleteVariable', function () {
106 | // editor?.getDataModel()?.deleteVariable('temperature');
107 | // assert.equal(editor?.getDataModel()?.getVariables().hasOwnProperty('temperature'), false);
108 | // })
109 | it('setAttributeValues', function () {
110 | let attrMap = new Map();
111 | attrMap.set(rectNode, { 'fill': 'yellow' });
112 | editor?.getDataModel()?.setAttributeValues(attrMap);
113 | assert.equal(rectNode.getAttributeValue('fill'), 'yellow');
114 | })
115 | it('addEvent', function () {
116 | editor?.getDataModel()?.addEvent(rectNode, {
117 | type: 'valueUpdate',
118 | action: 'changeProperty',
119 | value: [],
120 | where: {
121 | type: 'none'
122 | }
123 | });
124 | assert.equal(rectNode.getEvents()[0].type, 'valueUpdate');
125 | })
126 | it('updateEvent', function () {
127 | editor?.getDataModel()?.updateEvent(rectNode, {
128 | value: [
129 | {
130 | name: 'fill',
131 | val: 'red'
132 | }
133 | ],
134 | }, 0);
135 | assert.equal(rectNode.getEvents()[0].value.length, 1);
136 | })
137 | it('deleteEvent', function () {
138 | editor?.getDataModel()?.deleteEvent(rectNode, 0);
139 | assert.equal(rectNode.getEvents().length, 0);
140 | })
141 | it('setNodeTag', function () {
142 | editor?.getDataModel()?.setNodeTag(rectNode, "矩形");
143 | assert.equal(rectNode.getTag(), "矩形");
144 | })
145 | })
146 |
147 |
148 |
--------------------------------------------------------------------------------
/test/unit/Ellipse-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 |
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | describe('EllipseNode', function () {
7 |
8 | it('createNode', function () {
9 | let ellipseNode=new EllipseNode(
10 | {
11 | attributes: {
12 | x: 50,
13 | y: 50,
14 | radiusX: 50,
15 | radiusY: 60,
16 | strokeWidth: 2,
17 | stroke: 'red'
18 | }
19 | }
20 | );
21 | assert.equal(ellipseNode.getAttributeValue('radiusX'), 50);
22 |
23 | })
24 | it('add ellipse node to editor', function () {
25 | let editor=createEditor('EllipseNode');
26 | editor.addNode({
27 | className: 'EllipseNode',
28 | attributes: {
29 | x: 50,
30 | y: 50,
31 | width: 50,
32 | height: 60,
33 | strokeWidth: 2,
34 | stroke: 'red'
35 | }
36 | })
37 | assert.equal(editor.getSelection().length, 1);
38 | assert.equal(editor.getSelection()[0].className, 'EllipseNode');
39 | })
40 | })
41 |
42 |
43 |
--------------------------------------------------------------------------------
/test/unit/GraphEditor-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 |
4 | import { createEditor } from './test-utils';
5 | import assert from 'assert';
6 | import { PathNode } from '../../src/model/PathNode';
7 | import { PenNode } from '../../src/model/PenNode';
8 | import { PolylineNode } from '../../src/model/PolylineNode';
9 | import { RegularPolygonNode } from '../../src/model/RegularPolygonNode';
10 | import { StarNode } from '../../src/model/StarNode';
11 | import { TextNode } from '../../src/model/TextNode';
12 | import { WedgeNode } from '../../src/model/WedgeNode';
13 | import { Node } from '../../src/model/Node';
14 |
15 | describe('GraphEditor', function () {
16 | let editor = createEditor('GraphEditor');
17 | let wedgeNode;
18 | let rectNode;
19 | it('add node', function () {
20 | editor.addNode({
21 | className: 'RectNode',
22 | attributes: {
23 | x: 100,
24 | y: 20,
25 | width: 200,
26 | height: 60,
27 | strokeWidth: 2,
28 | fill: 'gray',
29 | stroke: 'red'
30 | }
31 | })
32 | editor.addNode({
33 | className: 'EllipseNode',
34 | attributes: {
35 | x: 100,
36 | y: 30,
37 | radiusX: 50,
38 | radiusY: 25,
39 | strokeWidth: 4,
40 | fill: 'gray',
41 | stroke: 'green'
42 | }
43 | })
44 | assert.equal(editor.getNodes().length, 2);
45 |
46 | })
47 | it('copy add paste', function () {
48 | editor.copy();
49 | editor.paste();
50 | assert.equal(editor.getNodes().length, 3);
51 |
52 | })
53 | it('move', function () {
54 | let ids= editor.getNodes().map(item =>item.id);
55 | editor.move('down',30,ids[0]);
56 | assert.equal(editor.getAttributeValue('y',ids[0]), 50);
57 | })
58 | it('setAttributeValues', function () {
59 | editor.setAttributeValues({
60 | 'fill':'red'
61 | });
62 | assert.equal(editor.getSelection()[0].attributes.fill, 'red');
63 | })
64 |
65 | it('undo', function () {
66 | editor.undo();
67 | assert.equal(editor.getSelection()[0].attributes.fill, 'gray');
68 | })
69 | it('redo', function () {
70 | editor.redo();
71 | assert.equal(editor.getSelection()[0].attributes.fill, 'red');
72 | })
73 | it('align', function () {
74 | let allNodes:Array= editor.getNodes();
75 | let ids= allNodes.map(item =>item.id);
76 | editor.align('left',ids);
77 | })
78 |
79 | it('saveVariable', function () {
80 | let allNodes:Array= editor.getNodes();
81 | let ids= allNodes.map(item =>item.id);
82 | editor.addVariable('变量1',{
83 | type:'integer',
84 | defaultVal:0
85 | },false,ids[0]);
86 | editor.addVariable('变量3',{
87 | type:'integer',
88 | defaultVal:0
89 | },false,ids[0]);
90 | //修改变量
91 | editor.updateVariable('变量2',{
92 | type:'integer',
93 | defaultVal:0
94 | },false,'变量1',ids[0]);
95 | assert.equal(editor?.getDataModel()?.getNodeById(ids[0]).getVariables().hasOwnProperty('变量3'), true);
96 | })
97 | it('deleteVariable', function () {
98 | let ids= editor.getNodes().map(item =>item.id);
99 | editor.deleteVariable('变量3',false,ids[0]);
100 | assert.equal(editor?.getDataModel()?.getNodeById(ids[0]).getVariables().hasOwnProperty('变量3'), false);
101 | })
102 | it('onModelChanged', function () {
103 | editor.onModelChanged(function (sender, event) {
104 | //console.log("onModelChanged", event);
105 | });
106 | })
107 | it('group', function () {
108 | let ids= editor.getNodes().map(item =>item.id);
109 | editor.group(ids);
110 | let nodes = editor.getNodes();
111 | assert.equal(nodes.length, 1);
112 | })
113 | it('unGroup', function () {
114 | editor.unGroup();
115 | assert.equal(editor.getNodes().length, 3);
116 | })
117 | it('setAttributeValue', function () {
118 | let ids= editor.getNodes().map(item =>item.id);
119 | editor.setAttributeValue('fill','yellow',ids[0]);
120 | assert.equal(editor.getAttributeValue('fill',ids[0]), 'yellow');
121 | })
122 | it('getAttributes', function () {
123 | let ids= editor.getNodes().map(item =>item.id);
124 | let attrs=editor.getAttributes(ids[0]);
125 | assert.equal(attrs['fill'].value, 'yellow');
126 | })
127 | it('addEvent', function () {
128 | let ids= editor.getNodes().map(item =>item.id);
129 | editor.addEvent({
130 | type: 'valueUpdate',
131 | action: 'changeProperty',
132 | value: [],
133 | where: {
134 | type: 'none'
135 | }
136 | },ids[0]);
137 | assert.equal(editor.getNodes()[0].events[0].action, 'changeProperty');
138 | })
139 | // it('addEvent', function () {
140 | // let ids= editor.getNodes().map(item =>item.id);
141 | // editor.addEvent({
142 | // type:'valueUpdate',
143 | // action:'executeScript',
144 | // fnjs: 'console.log("world")',
145 | // where: {
146 | // type: 'none'
147 | // }
148 | // },ids[0]);
149 | // assert.equal(editor.getNodes()[0].events[0].action, 'changeProperty');
150 | // })
151 | it('updateEvent', function () {
152 | let ids= editor.getNodes().map(item =>item.id);
153 | editor.updateEvent({
154 | type: 'valueUpdate',
155 | action: 'changeProperty',
156 | value: [
157 | {name:'x',val:'23'}
158 | ],
159 | where: {
160 | type:"comparison",
161 | key:"变量1",
162 | comparison:"=",
163 | value:1
164 | }
165 | },0,ids[0]);
166 | console.log("event is",editor.getNodes()[0].events[0]);
167 | assert.equal(editor.getNodes()[0].events[0].value.length, 1);
168 | })
169 | it('deleteEvent', function () {
170 | let ids= editor.getNodes().map(item =>item.id);
171 | editor.deleteEvent(0,ids[0]);
172 | assert.equal(editor.getNodes()[0].events.length, 0);
173 | })
174 | it('toJSON', function () {
175 | let jsonStr=editor.toJSON();
176 | //console.log(jsonStr);
177 | })
178 | it('setAnimation', function () {
179 | let ids= editor.getNodes().map(item =>item.id);
180 | //console.log(ids);
181 | editor.setAnimation({
182 | type:'blink',
183 | autoPlay:true
184 | },ids[0]);
185 | assert.equal(editor.getNodes()[0].animation.type, "blink");
186 | })
187 | it('getAllNodeAttributes', function () {
188 | let rectNode=new RectNode();
189 | let attrs = {};
190 | let _classes = Node._classes;
191 | for (const _name in _classes) {
192 | let _class = _classes[_name];
193 | let obj = new _classes[_name]();
194 | let _attrs = obj.attributes;
195 | Object.assign(attrs, _attrs);
196 | }
197 |
198 | })
199 | })
200 |
201 |
202 |
--------------------------------------------------------------------------------
/test/unit/Group-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | import { CircleNode } from '../../src/model/CircleNode';
7 | import { GroupNode } from '../../src/model/GroupNode';
8 | describe('GroupNode', function () {
9 | let editor=createEditor('GroupNode');
10 | it('createNode', function () {
11 | let rectNode=new RectNode(
12 | {
13 | attributes: {
14 | x: 50,
15 | y: 50,
16 | width: 100,
17 | height: 60,
18 | strokeWidth: 2,
19 | stroke: 'red'
20 | }
21 | }
22 | );
23 | let ellipseNode=new EllipseNode(
24 | {
25 | attributes: {
26 | x: 50,
27 | y: 50,
28 | radiusX: 50,
29 | radiusY: 60,
30 | strokeWidth: 2,
31 | stroke: 'red'
32 | }
33 | }
34 | );
35 | let groupNode=new GroupNode(
36 | {
37 | attributes: {
38 | x: 50,
39 | y: 50,
40 | draggable:true
41 | }
42 | }
43 | );
44 | groupNode.setMembers([rectNode,ellipseNode]);
45 | editor.getDataModel().addNode(groupNode);
46 | assert.equal(groupNode.getAttributeValue('x'), 50);
47 |
48 | })
49 |
50 | })
51 |
52 |
53 |
--------------------------------------------------------------------------------
/test/unit/Image-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import { createEditor } from './test-utils';
5 | import assert from 'assert';
6 | import { ImageNode } from '../../src/model/ImageNode';
7 | describe('Image', function () {
8 |
9 |
10 | it('add image node to editor', function () {
11 | let editor = createEditor('Image');
12 |
13 | let imageNode=new ImageNode({
14 | attributes: {
15 | image: "https://i.postimg.cc/HnFSw-Nf0/image.png",
16 | x: 50,
17 | y: 50,
18 | width: 100,
19 | height: 60,
20 | strokeWidth: 2
21 | }
22 | })
23 | editor.getDataModel().addNode(imageNode);
24 | assert.equal(imageNode.getAttributeValue('width'),100);
25 | })
26 | })
27 |
28 |
29 |
--------------------------------------------------------------------------------
/test/unit/Label-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import { createEditor } from './test-utils';
5 | import assert from 'assert';
6 | import { CircleNode } from '../../src/model/CircleNode';
7 | import { GroupNode } from '../../src/model/GroupNode';
8 | import { LabelNode } from '../../src/model/LabelNode';
9 | describe('LabelNode', function () {
10 | let editor = createEditor('LabelNode');
11 | it('createNode', function () {
12 | let labelNode = new LabelNode(
13 | {
14 | attributes: {
15 | x: 50,
16 | y: 50,
17 | width: 100,
18 | height: 60,
19 | strokeWidth: 2,
20 | stroke: 'red',
21 | textFill: 'green',
22 | fontSize: 14,
23 | text: '标签测试',
24 | padding: 10
25 | }
26 | }
27 | );
28 | editor.getDataModel().addNode(labelNode);
29 | assert.equal(labelNode.getAttributeValue('stroke'), 'red');
30 | })
31 | })
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/unit/Line-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import { createEditor } from './test-utils';
5 | import assert from 'assert';
6 | import { CircleNode } from '../../src/model/CircleNode';
7 | import { GroupNode } from '../../src/model/GroupNode';
8 | import { LabelNode } from '../../src/model/LabelNode';
9 | import { LineNode } from '../../src/model/LineNode';
10 | import { LineArrowNode } from '../../src/model/LineArrowNode';
11 | describe('LineNode', function () {
12 | let editor = createEditor('LineNode');
13 | it('create line Node', function () {
14 | let lineNode = new LineNode(
15 | {
16 | attributes: {
17 | x1: 50,
18 | y1: 50,
19 | x2: 100,
20 | y2: 60,
21 | strokeWidth: 2,
22 | stroke: 'red',
23 | draggable: true
24 | }
25 | }
26 | );
27 | editor.getDataModel().addNode(lineNode);
28 | assert.equal(lineNode.getAttributeValue('x1'), 50);
29 | })
30 | it('create line arrow Node', function () {
31 | let lineArrowNode = new LineArrowNode(
32 | {
33 | attributes: {
34 | x1: 80,
35 | y1: 80,
36 | x2: 100,
37 | y2: 100,
38 | strokeWidth: 2,
39 | stroke: 'red',
40 | fill:'red',
41 | draggable: true
42 | }
43 | }
44 | );
45 | editor.getDataModel().addNode(lineArrowNode);
46 | assert.equal(lineArrowNode.getAttributeValue('x1'), 80);
47 | })
48 | it('create line arrow Node', function () {
49 | let lineArrowNode = new LineArrowNode(
50 | {
51 | attributes: {
52 | x1: 100,
53 | y1: 150,
54 | x2: 150,
55 | y2: 150,
56 | strokeWidth: 2,
57 | stroke: 'red',
58 | fill:'red',
59 | pointerAtBeginning:true,
60 | draggable: true,
61 | pointerLength:20
62 | }
63 | }
64 | );
65 | editor.getDataModel().addNode(lineArrowNode);
66 | assert.equal(lineArrowNode.getAttributeValue('x1'), 100);
67 | })
68 | })
69 |
70 |
71 |
--------------------------------------------------------------------------------
/test/unit/Path-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | import { CircleNode } from '../../src/model/CircleNode';
7 | import { PathNode } from '../../src/model/PathNode';
8 | describe('PathNode', function () {
9 | let editor=createEditor('PathNode');
10 | it('createNode', function () {
11 | let pathNode=new PathNode(
12 | {
13 | attributes: {
14 | x: 0,
15 | y: 0,
16 | scaleX:0.2,
17 | scaleY:0.2,
18 | data: 'M170.666667 85.333333h682.666666a85.333333 85.333333 0 0 1 85.333334 85.333334v512a85.333333 85.333333 0 0 1-85.333334 85.333333h-170.666666l-170.666667 170.666667-170.666667-170.666667H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333333V170.666667a85.333333 85.333333 0 0 1 85.333334-85.333334m0 85.333334v512h206.08L512 817.92 647.253333 682.666667H853.333333V170.666667H170.666667z',
19 | strokeWidth: 4,
20 | stroke: 'green',
21 | draggable:true
22 | }
23 | }
24 | );
25 | editor.getDataModel()?.addNode(pathNode);
26 | assert.equal(pathNode.getAttributeValue('x'), 0);
27 |
28 | })
29 | it('add circle node to editor', function () {
30 |
31 |
32 | })
33 | })
34 |
35 |
36 |
--------------------------------------------------------------------------------
/test/unit/Pen-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | import { PathNode } from '../../src/model/PathNode';
7 | import { PenNode } from '../../src/model/PenNode';
8 | describe('PenNode', function () {
9 | let editor=createEditor('PenNode');
10 | it('createNode', function () {
11 | let penNode=new PenNode(
12 | {
13 | attributes: {
14 | x: 0,
15 | y: 0,
16 | points: [23, 20, 23, 160, 70, 93, 150, 109, 290, 139, 270, 93],
17 | strokeWidth: 4,
18 | stroke: 'green',
19 | draggable:true
20 | }
21 | }
22 | );
23 | editor.getDataModel().addNode(penNode);
24 | assert.equal(penNode.getAttributeValue('x'), 0);
25 |
26 | })
27 | it('create pen node by drawing', function () {
28 | editor.drawShape('pen');
29 |
30 | })
31 | })
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/unit/Polyline-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | import { PathNode } from '../../src/model/PathNode';
7 | import { PenNode } from '../../src/model/PenNode';
8 | import { PolylineNode } from '../../src/model/PolylineNode';
9 | describe('PolylineNode', function () {
10 | let editor=createEditor('PolylineNode');
11 | it('create polylineNode', function () {
12 | let polylineNode=new PolylineNode(
13 | {
14 | attributes: {
15 | x: 0,
16 | y: 0,
17 | points: [23, 20, 23, 160, 70, 93, 150, 109, 290, 139, 270, 93],
18 | strokeWidth: 4,
19 | stroke: 'green',
20 | draggable:true
21 | }
22 | }
23 | );
24 | editor.getDataModel().addNode(polylineNode);
25 | assert.equal(polylineNode.getAttributeValue('x'), 0);
26 |
27 | })
28 | it('create pen node by drawing', function () {
29 | editor.drawShape('polyline');
30 |
31 | })
32 | })
33 |
34 |
35 |
--------------------------------------------------------------------------------
/test/unit/Rect-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | describe('RectNode', function () {
7 | let rectNode;
8 | let editor=createEditor('RectNode');
9 | it('add rect node to editor', function () {
10 |
11 | editor.addNode({
12 | className: 'RectNode',
13 | attributes: {
14 | x: 50,
15 | y: 50,
16 | width: 100,
17 | height: 60,
18 | strokeWidth: 2,
19 | stroke: 'red'
20 | }
21 | })
22 | assert.equal(editor.getSelection().length, 1);
23 | assert.equal(editor.getSelection()[0].attributes.width, 100);
24 | })
25 | it('createNode', function () {
26 | rectNode=new RectNode(
27 | {
28 | attributes: {
29 | x: 50,
30 | y: 50,
31 | width: 100,
32 | height: 60,
33 | strokeWidth: 2,
34 | stroke: 'green',
35 | draggable:true
36 | }
37 | }
38 | );
39 | editor.getDataModel()?.addNode(rectNode);
40 | assert.equal(rectNode?.getAttributeValue('width'), 100);
41 |
42 | })
43 | it('rect rotate', function () {
44 |
45 | editor.setAttributeValue('rotation',30);
46 | editor.setAttributeValue('rotation',90);
47 | })
48 | })
49 |
50 |
51 |
--------------------------------------------------------------------------------
/test/unit/RegularPolygon-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | import { PathNode } from '../../src/model/PathNode';
7 | import { PenNode } from '../../src/model/PenNode';
8 | import { PolylineNode } from '../../src/model/PolylineNode';
9 | import { RegularPolygonNode } from '../../src/model/RegularPolygonNode';
10 | describe('RegularPolygon', function () {
11 | let editor=createEditor('RegularPolygon');
12 | it('create regularPolygon', function () {
13 | let regularPolygonNode=new RegularPolygonNode(
14 | {
15 | attributes: {
16 | x: 50,
17 | y: 50,
18 | sides:5,
19 | radius:20,
20 | strokeWidth: 4,
21 | stroke: 'green',
22 | draggable:true
23 | }
24 | }
25 | );
26 | editor.getDataModel()?.addNode(regularPolygonNode);
27 | assert.equal(regularPolygonNode.getAttributeValue('radius'), 20);
28 |
29 | })
30 | it('modify attributes', function () {
31 | let regularPolygonNode=new RegularPolygonNode(
32 | {
33 | attributes: {
34 | x: 100,
35 | y: 100,
36 | sides:5,
37 | radius:20,
38 | strokeWidth: 4,
39 | stroke: 'red',
40 | draggable:true
41 | }
42 | }
43 | );
44 | editor.getDataModel()?.addNode(regularPolygonNode);
45 | regularPolygonNode.setAttributeValue('sides',6);
46 | assert.equal(regularPolygonNode.getAttributeValue('radius'), 20);
47 |
48 | })
49 | })
50 |
51 |
52 |
--------------------------------------------------------------------------------
/test/unit/Ring-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | import { PathNode } from '../../src/model/PathNode';
7 | import { PenNode } from '../../src/model/PenNode';
8 | import { PolylineNode } from '../../src/model/PolylineNode';
9 | import { RegularPolygonNode } from '../../src/model/RegularPolygonNode';
10 | import { RingNode } from '../../src/model/RingNode';
11 | describe('RingNode', function () {
12 | let editor=createEditor('RingNode');
13 | let ringNode;
14 | it('create ring node', function () {
15 | ringNode=new RingNode(
16 | {
17 | attributes: {
18 | x: 50,
19 | y: 50,
20 | innerRadius:20,
21 | outerRadius:50,
22 | strokeWidth: 4,
23 | stroke: 'green',
24 | draggable:true
25 | }
26 | }
27 | );
28 | editor.getDataModel().addNode(ringNode);
29 | assert.equal(ringNode.getAttributeValue('innerRadius'), 20);
30 |
31 | })
32 | it('modify attributes', function () {
33 | ringNode.setAttributeValue('innerRadius',10);
34 | assert.equal(ringNode.getAttributeValue('innerRadius'), 10);
35 |
36 | })
37 | })
38 |
39 |
40 |
--------------------------------------------------------------------------------
/test/unit/Star-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | import { PathNode } from '../../src/model/PathNode';
7 | import { PenNode } from '../../src/model/PenNode';
8 | import { PolylineNode } from '../../src/model/PolylineNode';
9 | import { RegularPolygonNode } from '../../src/model/RegularPolygonNode';
10 | import { StarNode } from '../../src/model/StarNode';
11 |
12 | describe('StarNode', function () {
13 | let editor=createEditor('StarNode');
14 | let starNode;
15 | it('create star node', function () {
16 | starNode=new StarNode(
17 | {
18 | attributes: {
19 | x: 50,
20 | y: 50,
21 | numPoints:6,
22 | innerRadius:20,
23 | outerRadius:50,
24 | strokeWidth: 4,
25 | stroke: 'green',
26 | draggable:true
27 | }
28 | }
29 | );
30 | editor.getDataModel().addNode(starNode);
31 | assert.equal(starNode.getAttributeValue('numPoints'), 6);
32 |
33 | })
34 | it('modify attributes', function () {
35 | starNode.setAttributeValue('numPoints',10);
36 | assert.equal(starNode.getAttributeValue('numPoints'), 10);
37 |
38 | })
39 | })
40 |
41 |
42 |
--------------------------------------------------------------------------------
/test/unit/Symbol-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import {createEditor} from './test-utils';
5 | import assert from 'assert';
6 | import { PathNode } from '../../src/model/PathNode';
7 | import { PenNode } from '../../src/model/PenNode';
8 | import { PolylineNode } from '../../src/model/PolylineNode';
9 | import { RegularPolygonNode } from '../../src/model/RegularPolygonNode';
10 | import { SymbolNode } from '../../src/model/SymbolNode';
11 |
12 |
13 | describe('SymbolNode', function () {
14 | let editor=createEditor('SymbolNode');
15 | let symbolNode;
16 | it('create symbol node', function () {
17 | let rectNode=new RectNode(
18 | {
19 | attributes: {
20 | x: 50,
21 | y: 50,
22 | width: 100,
23 | height: 60,
24 | strokeWidth: 2,
25 | stroke: 'red'
26 | }
27 | }
28 | );
29 | let ellipseNode=new EllipseNode(
30 | {
31 | attributes: {
32 | x: 50,
33 | y: 50,
34 | radiusX: 50,
35 | radiusY: 60,
36 | strokeWidth: 2,
37 | stroke: 'red'
38 | }
39 | }
40 | );
41 | symbolNode=new SymbolNode(
42 | {
43 | attributes: {
44 | x: 50,
45 | y: 50,
46 | draggable:true
47 | }
48 | }
49 | );
50 | symbolNode.setMembers([rectNode,ellipseNode]);
51 | symbolNode.setSymbolName('breaker');
52 | editor.getDataModel().addNode(symbolNode);
53 | assert.equal(symbolNode.getSymbolName(), 'breaker');
54 |
55 | })
56 | it('modify attributes', function () {
57 |
58 |
59 | })
60 | })
61 |
62 |
63 |
--------------------------------------------------------------------------------
/test/unit/Text-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import {createEditor} from './test-utils';
3 | import assert from 'assert';
4 | import { PathNode } from '../../src/model/PathNode';
5 | import { PenNode } from '../../src/model/PenNode';
6 | import { PolylineNode } from '../../src/model/PolylineNode';
7 | import { RegularPolygonNode } from '../../src/model/RegularPolygonNode';
8 | import { StarNode } from '../../src/model/StarNode';
9 | import { TextNode } from '../../src/model/TextNode';
10 |
11 | describe('TextNode', function () {
12 | let editor=createEditor('TextNode');
13 | let textNode;
14 | it('create text node', function () {
15 | textNode=new TextNode(
16 | {
17 | attributes: {
18 | x: 50,
19 | y: 50,
20 | align:'left',
21 | padding :10,
22 | fontFamily : "Arial",
23 | fontSize : 12,
24 | fontStyle : "normal",
25 | fontVariant : "normal",
26 | text: "中文",
27 | strokeWidth: 2,
28 | stroke: 'green',
29 | draggable:true
30 | }
31 | }
32 | );
33 | editor.getDataModel()?.addNode(textNode);
34 | assert.equal(textNode.getAttributeValue('padding'), 10);
35 |
36 | })
37 | it('modify attributes', function () {
38 | textNode.setAttributeValue('padding',5);
39 | assert.equal(textNode.getAttributeValue('padding'), 5);
40 |
41 | })
42 | })
43 |
44 |
45 |
--------------------------------------------------------------------------------
/test/unit/Utils-test.ts:
--------------------------------------------------------------------------------
1 | import assert from "assert";
2 | import {Utils} from "../../src/Utils";
3 |
4 | describe('Util', function () {
5 | it('getRad', function () {
6 | assert.equal(Utils.getRad(90), 1.5707963267948966)
7 | })
8 | })
--------------------------------------------------------------------------------
/test/unit/Wedge-test.ts:
--------------------------------------------------------------------------------
1 | import { RectNode } from '../../src/model/RectNode';
2 | import { EllipseNode } from '../../src/model/EllipseNode';
3 | import { GraphEditor } from '../../src/GraphEditor';
4 | import { createEditor } from './test-utils';
5 | import assert from 'assert';
6 | import { PathNode } from '../../src/model/PathNode';
7 | import { PenNode } from '../../src/model/PenNode';
8 | import { PolylineNode } from '../../src/model/PolylineNode';
9 | import { RegularPolygonNode } from '../../src/model/RegularPolygonNode';
10 | import { StarNode } from '../../src/model/StarNode';
11 | import { TextNode } from '../../src/model/TextNode';
12 | import { WedgeNode } from '../../src/model/WedgeNode';
13 |
14 | describe('WedgeNode', function () {
15 | let editor = createEditor('WedgeNode');
16 | let wedgeNode;
17 | it('create text node', function () {
18 | wedgeNode = new WedgeNode(
19 | {
20 | attributes: {
21 | x: 50,
22 | y: 50,
23 | angle: 30,
24 | radius: 15,
25 | clockwise: false,
26 | strokeWidth: 2,
27 | stroke: 'green',
28 | draggable: true
29 | }
30 | }
31 | );
32 | editor.getDataModel().addNode(wedgeNode);
33 | assert.equal(wedgeNode.getAttributeValue('radius'), 15);
34 |
35 | })
36 | it('modify attributes', function () {
37 | wedgeNode.setAttributeValue('radius', 20);
38 | assert.equal(wedgeNode.getAttributeValue('radius'), 20);
39 |
40 | })
41 | })
42 |
43 |
44 |
--------------------------------------------------------------------------------
/test/unit/test-utils.ts:
--------------------------------------------------------------------------------
1 | import GraphEditor from '../../src/GraphEditor'
2 | export function createEditor(title) {
3 |
4 | var container =
5 | (global.document.createElement('div')) || undefined;
6 |
7 | container.style.width="200px";
8 | container.style.height="260px";
9 | container.style.margin="10px";
10 | container.style.border="1px solid gray";
11 | container.style.display="flex";
12 | container.style.flexDirection="column";
13 | var titleContainer =
14 | (global.document.createElement('div')) || undefined;
15 | titleContainer.innerHTML=title;
16 | var graphContainer =
17 | (global.document.createElement('div')) || undefined;
18 |
19 | container.appendChild(titleContainer);
20 | container.appendChild(graphContainer);
21 |
22 | var editor = new GraphEditor(
23 | {
24 | container:graphContainer,
25 | view: {
26 | size: {
27 | width: 200,
28 | height: 200
29 | }
30 | }
31 | }
32 | );
33 | document.getElementById("preview")?.appendChild(container);
34 | return editor;
35 | }
--------------------------------------------------------------------------------
/tsconfig-cmj.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "cmj",
4 | "module": "CommonJS",
5 | "target": "ES2015",
6 | "noEmitOnError": true,
7 | "lib": ["ES2015", "dom"],
8 | "moduleResolution": "node",
9 | "declaration": true,
10 | "removeComments": true
11 | },
12 | "include": ["./src/**/*.ts"],
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ES6",
4 | "target": "ES5",
5 | "noImplicitAny": true,
6 | "noEmit": true,
7 | "strictNullChecks": true,
8 | "removeComments": true,
9 | "preserveConstEnums": true,
10 | "allowSyntheticDefaultImports": true,
11 | "esModuleInterop": true,
12 | "moduleResolution": "Node",
13 | "resolveJsonModule": true,
14 | "skipLibCheck": true,
15 | "rootDir": "src",
16 | "baseUrl": "/",
17 | "lib": [
18 | "ES2017",
19 | "ES2019.array",
20 | "dom"
21 | ],
22 | "downlevelIteration": true
23 | },
24 | "compileOnSave": true,
25 | "include": [
26 | "src/**/*"
27 | ],
28 | "exclude": [
29 | "src/**/*.test.ts"
30 | ]
31 | }
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoints": ["src/index.all.ts"],
3 | "includes": "src/*.ts",
4 | "out": "docs",
5 | "readme": "none",
6 | "includeVersion": true,
7 | "disableSources": true,
8 | "exclude": [
9 | "**/node_modules/**"
10 | ],
11 | }
12 |
--------------------------------------------------------------------------------