jmgraph 3.2.16 → 3.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +251 -428
  3. package/build/gulpfile.js +142 -142
  4. package/build/package-lock.json +10666 -0
  5. package/build/package.json +71 -71
  6. package/dev.js +9 -9
  7. package/dist/jmgraph.core.min.js +1 -1
  8. package/dist/jmgraph.core.min.js.map +1 -1
  9. package/dist/jmgraph.js +3500 -2668
  10. package/dist/jmgraph.min.js +1 -1
  11. package/example/ball.html +216 -216
  12. package/example/base.html +111 -111
  13. package/example/canvas.html +53 -53
  14. package/example/cell.html +283 -283
  15. package/example/controls/arc.html +128 -128
  16. package/example/controls/arrowline.html +77 -77
  17. package/example/controls/bezier.html +298 -298
  18. package/example/controls/img.html +96 -96
  19. package/example/controls/label.html +86 -86
  20. package/example/controls/line.html +172 -172
  21. package/example/controls/prismatic.html +62 -62
  22. package/example/controls/rect.html +63 -63
  23. package/example/controls/resize.html +111 -111
  24. package/example/controls/test.html +359 -359
  25. package/example/es.html +69 -69
  26. package/example/es5module.html +62 -63
  27. package/example/heartarc.html +115 -115
  28. package/example/index.html +46 -46
  29. package/example/js/require.js +4 -4
  30. package/example/love/img/bling/bling.tps +265 -265
  31. package/example/love/img/bling.json +87 -87
  32. package/example/love/img/bling.tps +295 -295
  33. package/example/love/img/love.json +95 -95
  34. package/example/love/img/love.tps +315 -315
  35. package/example/love/img/qq/qq.tps +399 -399
  36. package/example/love/img/qq.json +242 -242
  37. package/example/love/index.html +40 -40
  38. package/example/love/js/game.js +558 -558
  39. package/example/music.html +210 -210
  40. package/example/node/test.js +137 -137
  41. package/example/pdf.html +186 -186
  42. package/example/progress.html +172 -172
  43. package/example/pso.html +147 -147
  44. package/example/sort.html +804 -815
  45. package/example/tweenjs.html +83 -83
  46. package/example/webgl.html +278 -278
  47. package/example/xfj/index.html +331 -331
  48. package/example/xfj/shake.js +48 -48
  49. package/example/xfj/testori.html +75 -75
  50. package/index.js +99 -99
  51. package/package.json +58 -56
  52. package/src/core/jmControl.js +1376 -1531
  53. package/src/core/jmEvents.js +240 -281
  54. package/src/core/jmGradient.js +231 -231
  55. package/src/core/jmGraph.js +569 -569
  56. package/src/core/jmList.js +92 -157
  57. package/src/core/jmObject.js +83 -103
  58. package/src/core/jmPath.js +35 -35
  59. package/src/core/jmProperty.js +71 -110
  60. package/src/core/jmShadow.js +65 -65
  61. package/src/core/jmUtils.js +906 -919
  62. package/src/lib/earcut.js +680 -680
  63. package/src/lib/earcut.md +73 -73
  64. package/src/lib/webgl/base.js +522 -452
  65. package/src/lib/webgl/core/buffer.js +48 -48
  66. package/src/lib/webgl/core/mapSize.js +40 -40
  67. package/src/lib/webgl/core/mapType.js +43 -43
  68. package/src/lib/webgl/core/program.js +138 -138
  69. package/src/lib/webgl/core/shader.js +13 -13
  70. package/src/lib/webgl/core/texture.js +60 -60
  71. package/src/lib/webgl/gradient.js +168 -168
  72. package/src/lib/webgl/index.js +137 -11
  73. package/src/lib/webgl/path.js +568 -561
  74. package/src/shapes/jmArrowLine.js +36 -36
  75. package/src/shapes/jmImage.js +244 -244
  76. package/src/shapes/jmLabel.js +271 -271
  77. package/src/shapes/jmResize.js +332 -330
@@ -1,562 +1,569 @@
1
- import WebglBase from './base.js';
2
-
3
- // path 绘制类
4
- class WebglPath extends WebglBase {
5
- constructor(graph, option) {
6
- super(graph, option);
7
- // 是否是规则的,不规则的处理方式更为复杂和耗性能
8
- this.isRegular = option.isRegular || false;
9
- this.needCut = option.needCut || false;
10
- this.control = option.control;
11
- this.points = [];
12
- }
13
-
14
- setParentBounds(parentBounds = this.parentAbsoluteBounds) {
15
-
16
- //this.useProgram();
17
-
18
- if(parentBounds) this.parentAbsoluteBounds = parentBounds;
19
- // 写入当前canvas大小
20
- this.context.uniform2f(this.program.uniforms.a_center_point.location, this.graph.width / 2, this.graph.height / 2);
21
- }
22
-
23
- setFragColor(color) {
24
-
25
- if(!Array.isArray(color)) {
26
- color = this.convertColor(color);
27
- if(typeof color.a === 'undefined') color.a = 1;
28
- this.context.uniform4f(this.program.uniforms.v_single_color.location, color.r, color.g, color.b, color.a * this.style.globalAlpha);
29
- return null;
30
- }
31
-
32
- const colorData = [];
33
- for(let c of color) {
34
- c = this.convertColor(c);
35
- if(typeof c.a === 'undefined') c.a = 1;
36
- colorData.push(c.r, c.g, c.b, c.a * this.style.globalAlpha);
37
- }
38
-
39
- const colorBuffer = this.createFloat32Buffer(colorData);
40
- this.writeVertexAttrib(colorBuffer, this.program.attrs.a_color, 4, 0, 0);
41
- colorBuffer.attr = this.program.attrs.a_color;
42
- return colorBuffer;
43
- }
44
-
45
- beginDraw() {
46
- this.useProgram();
47
- }
48
-
49
- // 开始绘制
50
- draw(points, parentBounds = this.parentAbsoluteBounds) {
51
- //this.useProgram();
52
-
53
- this.setParentBounds(parentBounds);
54
-
55
- this.points = points;
56
- }
57
-
58
- endDraw() {
59
- if(this.points) delete this.points;
60
- if(this.pathPoints) delete this.pathPoints;
61
- }
62
-
63
- // 图形封闭
64
- closePath() {
65
- if(this.points && this.points.length > 2 && this.points[0] !== this.points[this.points.length-1]) {
66
- const start = this.points[0];
67
- const end = this.points[this.points.length-1];
68
- if(start != end && !(start.x === end.x && start.y === end.y)) this.points.push(start);
69
- }
70
- }
71
-
72
- // 绘制点数组
73
- writePoints(points, attr = this.program.attrs.a_position) {
74
-
75
- const fixedPoints = [];
76
- for(const p of points) {
77
- fixedPoints.push(
78
- p.x + this.parentAbsoluteBounds.left,
79
- p.y + this.parentAbsoluteBounds.top
80
- );
81
- }
82
- const vertexBuffer = this.createFloat32Buffer(fixedPoints);
83
- this.writeVertexAttrib(vertexBuffer, attr, 2, 0, 0);
84
- vertexBuffer.attr = attr;
85
- return vertexBuffer;
86
- }
87
-
88
- // 连接二个点
89
- genLinePoints(start, end) {
90
- const points = [start];
91
- const dx = end.x - start.x;
92
- const dy = end.y - start.y;
93
- if(dx !== 0 || dy !== 0) {
94
- const len = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
95
- const cos = dx / len;
96
- const sin = dy / len;
97
- const step = 0.5;
98
- for(let l=step; l<len; l+=step) {
99
- const x = start.x + cos * l;
100
- const y = start.y + sin * l;
101
- points.push({
102
- x,
103
- y
104
- });
105
- }
106
- }
107
- points.push(end);
108
- return points;
109
- }
110
-
111
- // 把path坐标集合分解成一个个点,并且处理moveTo线段能力
112
- pathToPoints(points=this.points) {
113
- let start = null;
114
- const res = [];
115
- for(let i=0; i<points.length; i++) {
116
- const p = points[i];
117
- if(start && !p.m) {
118
- const linePoints = this.genLinePoints(start, p);
119
- res.push(...linePoints);
120
- }
121
- else if(start && !res.includes(start)) {
122
- res.push(start);
123
- }
124
- start = p;
125
- }
126
- if(!res.includes(start)) res.push(start);
127
- return res;
128
- }
129
- // 二点是否重合
130
- equalPoint(p1, p2) {
131
- return p1.x === p2.x && p1.y === p2.y;
132
- }
133
- // 把path坐标集合转为线段集
134
- pathToLines(points) {
135
- let start = null;
136
- const res = [];
137
- for(let i=0; i<points.length; i++) {
138
- const p = points[i];
139
- // 不重合的二个点,组成线段
140
- if(start && !p.m && !(start.x == p.x && start.y == p.y)) {
141
- const line = {
142
- start,
143
- end: p,
144
- };
145
- res.push(line);
146
- }
147
- start = p;
148
- }
149
- return res;
150
- }
151
-
152
- // 裁剪线段,如果二段线段有交点,则分割成四段, 端头相交的线段不用分割
153
- cutLines(lines, index1=0, index2=0) {
154
- if(lines && lines.length < 3) return lines;
155
-
156
- index2 = Math.max(index1 + 1, index2); //如果指定了比下一个更大的索引,则用更大的,说明前面的已经处理过了,不需要重复
157
-
158
- // 找出线段相交的点,并切割线段
159
- while(index1 < lines.length) {
160
- const line1 = lines[index1];
161
-
162
- while(index2 < lines.length) {
163
- const line2 = lines[index2];
164
- // 如果二条线顶点有重合,则不用处理
165
- if(this.equalPoint(line1.start, line2.start) || this.equalPoint(line1.end, line2.end) ||
166
- this.equalPoint(line1.start, line2.end) || this.equalPoint(line1.end, line2.start)) {
167
- index2++;
168
- continue;
169
- }
170
- let cuted = false;
171
- const intersection = this.getIntersection(line1, line2);// 计算交点
172
- if(intersection) {
173
- // 如果交点不是线段的端点,则分割成二条线段
174
- if(!this.equalPoint(line1.start, intersection) && !this.equalPoint(line1.end, intersection)) {
175
- const sub1 = {
176
- start: line1.start,
177
- end: intersection
178
- };
179
- const sub2 = {
180
- start: intersection,
181
- end: line1.end
182
- };
183
- // 从原数组中删除当前线段,替换成新的线段
184
- lines.splice(index1, 1, sub1, sub2);
185
- // 当前线段被重新替换,需要重新从它开始处理
186
- cuted = true;
187
- index2 ++;// 因为多加入了一个线段,则对比线索引需要加1
188
- }
189
- // 如果交点不是线段的端点,则分割成二条线段
190
- if(!this.equalPoint(line2.start, intersection) && !this.equalPoint(line2.end, intersection)) {
191
- const sub1 = {
192
- start: line2.start,
193
- end: intersection
194
- };
195
- const sub2 = {
196
- start: intersection,
197
- end: line2.end
198
- };
199
- // 从原数组中删除当前线段,替换成新的线段
200
- lines.splice(index2, 1, sub1, sub2);
201
- index2 ++; // 线段2也切成了二段,对比索引要继续加1
202
- }
203
- }
204
- index2++;
205
- // 如果已经分割了起始线段,则第一个子线段开始,重新对比后面还未对比完的。直接所有对比完成返回
206
- if(cuted) return this.cutLines(lines, index1, index2);
207
- }
208
- index1++;
209
- index2 = index1 + 1;
210
- }
211
- return lines;
212
- }
213
-
214
- // 计算二个线段的交点
215
- getIntersection(line1, line2) {
216
- // 如果首尾相接,也认为是有交点
217
- if(this.equalPoint(line1.start, line2.start) || this.equalPoint(line1.start, line2.end)) return line1.start;
218
- if(this.equalPoint(line1.end, line2.start) || this.equalPoint(line1.end, line2.end)) return line1.end;
219
-
220
- // 三角形abc 面积的2倍
221
- const area_abc = (line1.start.x - line2.start.x) * (line1.end.y - line2.start.y) - (line1.start.y - line2.start.y) * (line1.end.x - line2.start.x);
222
-
223
- // 三角形abd 面积的2倍
224
- const area_abd = (line1.start.x - line2.end.x) * (line1.end.y - line2.end.y) - (line1.start.y - line2.end.y) * (line1.end.x - line2.end.x);
225
-
226
- // 面积符号相同则两点在线段同侧,不相交 (=0表示在线段顶点上);
227
- if (area_abc * area_abd > 0) {
228
- return null;
229
- }
230
-
231
- // 三角形cda 面积的2倍
232
- const area_cda = (line2.start.x - line1.start.x) * (line2.end.y - line1.start.y) - (line2.start.y - line1.start.y) * (line2.end.x - line1.start.x);
233
- // 三角形cdb 面积的2倍
234
- // 注意: 这里有一个小优化.不需要再用公式计算面积,而是通过已知的三个面积加减得出.
235
- const area_cdb = area_cda + area_abc - area_abd ;
236
- if(area_cda * area_cdb > 0) {
237
- return null ;
238
- }
239
- if(area_abd === area_abc) return null;
240
-
241
- //计算交点坐标
242
- const t = area_cda / (area_abd - area_abc);
243
- const dx= t * (line1.end.x - line1.start.x);
244
- const dy= t * (line1.end.y - line1.start.y);
245
-
246
- return {
247
- x: line1.start.x + dx,
248
- y: line1.start.y + dy
249
- };
250
- }
251
-
252
- // 找出跟当前线段尾部相交的所有线段
253
- getIntersectionLines(line, lines, index, point=line.end, points=[], root=null) {
254
- const res = {
255
- line,
256
- polygons: []
257
- };
258
-
259
- points.push(point);
260
-
261
- if(root && this.equalPoint(root.line.start, point)) {
262
- points.unshift(root.line.start); // 把起始地址加入进去
263
- root.polygons.push(points);
264
- return res;
265
- }
266
-
267
- for(;index<lines.length; index++) {
268
- const l = lines[index];
269
- if(this.equalPoint(point, l.start)) {
270
- if(points.includes(l.end)) continue;
271
- this.getIntersectionLines(l, lines, index+1, l.end, [...points], root||res);
272
- }
273
- else if(this.equalPoint(point, l.end)) {
274
- if(points.includes(l.start)) continue;
275
- this.getIntersectionLines(l, lines, index+1, l.start, [...points], root||res);
276
- }
277
- }
278
- return res;
279
- }
280
-
281
- // 根据路径点坐标,切割出封闭的多边形
282
- getPolygon(points) {
283
- let polygons = [];
284
- let lines = this.pathToLines(points); // 分解得到线段
285
- if(lines && lines.length > 2) {
286
- lines = this.cutLines(lines); // 把所有相交点切割线段找出来
287
- for(let i=0; i<lines.length-1; i++) {
288
- const line1 = lines[i];
289
- let polygon = [];// 当前图形
290
-
291
- const treeLine = this.getIntersectionLines(line1, lines, i+1);
292
-
293
- if(treeLine.polygons.length) polygons.push(...treeLine.polygons);
294
- continue;
295
- let lastLine = line1; // 下一个还在连接状态的线
296
- for(let j=i+1; j<lines.length; j++) {
297
- const line2 = lines[j];
298
- // 如果跟下一条线相接,则表示还在形成图形中
299
- if(this.equalPoint(lastLine.end, line2.start)) {
300
- polygon.push(lastLine.end);
301
- lastLine = line2;
302
- if(i === j+1) continue; //下一条相连 则不需要处理相交情况
303
- }
304
- else {
305
- polygon = [];
306
- }
307
- // 因为前面进行了分割线段,则里只有处理端点相连的情况
308
- const intersection = this.equalPoint(line1.start, line2.end)? line1.start: null;//this.getIntersection(line1, line2);// 计算交点
309
- if(intersection) {
310
- polygon.push(intersection);// 交叉点为图形顶点
311
- // 如果上一个连接线不是当前交叉线,则表示重新开始闭合
312
- // 如果上一个连接线是当前交叉线,形成了封闭的图形
313
- if(lastLine === line2 && polygon.length > 1) {
314
- polygons.push(polygon);
315
-
316
- // 封闭后,下一个起始线条就是从交点开始计算起
317
- /*lastLine = {
318
- start: intersection,
319
- end: line2.end
320
- };*/
321
- polygon = [];// 重新开始新一轮找图形
322
-
323
- /*
324
- // 如果交点是上一条线的终点,则新图形为空
325
- if(this.equalPoint(line2.end, intersection)) {
326
- polygon = [];// 重新开始新一轮找图形
327
- }
328
- else {
329
- // 同时交点也要加到上一个图形中第一个点,形成封闭
330
- polygon.unshift(intersection);
331
-
332
- polygon = [ intersection ];// 重新开始新一轮找图形
333
- }*/
334
- }
335
- else {
336
- lastLine = line2;
337
- }
338
- }
339
- }
340
- }
341
- }
342
-
343
- // 当有多个封闭图形时,再弟归一下,里面是不是有封闭图形内还有子封闭图形
344
- /*if(polygons.length > 1) {
345
- const newPolygons = [];
346
- for(const polygon of polygons) {
347
- // 只有大于4才有可能有子封闭图形
348
- if(polygon.length > 4) {
349
- const childPolygons = this.getPolygon(polygon);
350
- // 当有多个子图形时,表示它不是最终封闭图形,跳过,
351
- // 因为它的子图形之前有加入的,不需要重复加入
352
- if(childPolygons.length > 1) {
353
- //newPolygons.push(...childPolygons);
354
- continue;
355
- }
356
- }
357
- newPolygons.push(polygon);
358
- }
359
- polygons = newPolygons;
360
- }*/
361
- return polygons;
362
- }
363
-
364
- // 分割成一个个规则的三角形,不规则的多边形不全割的话纹理就会没法正确覆盖
365
- getTriangles(points) {
366
-
367
- //this.trianglesCache = this.trianglesCache||(this.trianglesCache={});
368
- //const key = JSON.stringify(points);
369
- //if(this.trianglesCache[key]) return this.trianglesCache[key];
370
-
371
- const res = [];
372
- const polygons = this.getPolygon(points);
373
- if(polygons.length) {
374
- for(const polygon of polygons) {
375
- // 需要分割三角形,不然填充会有问题
376
- const triangles = this.earCutPointsToTriangles(polygon);
377
- res.push(...triangles);
378
- }
379
- }
380
- //this.trianglesCache[key] = res;
381
- return res;
382
- }
383
-
384
- // 画线条
385
- stroke(points = this.points, color = this.style.strokeStyle, lineWidth = this.style.lineWidth) {
386
- if(!points || !points.length) return;
387
- // this.useProgram();
388
-
389
- let colorBuffer = null;
390
- if(color) {
391
- colorBuffer = this.setFragColor(color);
392
- }
393
- // 线宽
394
- if(lineWidth) {
395
- this.context.uniform1f(this.program.uniforms.a_point_size.location, lineWidth);// * this.graph.devicePixelRatio
396
- }
397
- // 标注为stroke
398
- if(this.program.uniforms.a_type) {
399
- // 4表示单画一个圆点,1表示方块形成的线条
400
- this.context.uniform1i(this.program.uniforms.a_type.location, points.length === 1? 4 :1);
401
- }
402
- if(points && points.length) {
403
- const regular = lineWidth <= 1.2;
404
- points = regular? points : this.pathToPoints(points);
405
- const buffer = this.writePoints(points);
406
- this.context.drawArrays(regular? this.context.LINE_LOOP: this.context.POINTS, 0, points.length);
407
- this.deleteBuffer(buffer);
408
- }
409
- colorBuffer && this.deleteBuffer(colorBuffer);
410
- colorBuffer && this.disableVertexAttribArray(colorBuffer.attr);
411
- }
412
-
413
- // 填充图形
414
- fill(bounds = {left: 0, top: 0, width: 0, height: 0}, type = 1) {
415
-
416
- if(this.points && this.points.length) {
417
- // 如果是颜色rgba
418
- if(this.style.fillStyle) {
419
- this.fillColor(this.style.fillStyle, this.points, bounds, type);
420
- }
421
- if(this.style.fillImage) {
422
- this.fillImage(this.style.fillImage, this.points, bounds, type);
423
- }
424
- }
425
- }
426
-
427
- fillColor(color, points, bounds, type=1) {
428
-
429
- // 如果是渐变色,则需要计算偏移量的颜色
430
- if(this.isGradient(color)) {
431
- const imgData = color.toImageData(this, bounds, points);
432
- return this.fillImage(imgData.data, imgData.points, bounds);
433
- }
434
-
435
- // 标注为fill
436
- this.context.uniform1i(this.program.uniforms.a_type.location, type);
437
- const colorBuffer = this.setFragColor(color);
438
-
439
- this.fillPolygons(points);
440
-
441
- colorBuffer && this.deleteBuffer(colorBuffer);
442
- colorBuffer && this.disableVertexAttribArray(colorBuffer.attr);
443
-
444
- }
445
-
446
- // 区域填充图片
447
- // points绘制的图形顶点
448
- // 图片整体绘制区域
449
- fillImage(img, points, bounds) {
450
- if(!img) return;
451
-
452
- // 设置纹理
453
- const texture = img instanceof ImageData? this.createDataTexture(img) : this.createImgTexture(img);
454
- this.context.uniform1i(this.program.uniforms.u_sample.location, 0); // 纹理单元传递给着色器
455
-
456
- // 指定纹理区域尺寸
457
- this.context.uniform4f(this.program.uniforms.v_texture_bounds.location,
458
- bounds.left + this.parentAbsoluteBounds.left,
459
- bounds.top + this.parentAbsoluteBounds.top,
460
- bounds.width,
461
- bounds.height,
462
- ); // 纹理单元传递给着色器
463
-
464
- this.fillTexture(points);
465
-
466
- this.deleteTexture(texture);
467
- }
468
-
469
- fillTexture(points) {
470
- if(points && points.length) { // 标注为纹理对象
471
- this.context.uniform1i(this.program.uniforms.a_type.location, 2);
472
- // 纹理坐标
473
- //const coordBuffer = this.writePoints(points, this.program.attrs.a_text_coord);
474
- this.fillPolygons(points, true);
475
- //this.deleteBuffer(coordBuffer);
476
- this.disableVertexAttribArray(this.program.attrs.a_text_coord);
477
- }
478
- }
479
-
480
- // 进行多边形填充
481
- fillPolygons(points, isTexture = false) {
482
- //const indexBuffer = this.createUint16Buffer(triangles, this.context.ELEMENT_ARRAY_BUFFER);
483
- //this.context.drawElements(this.context.TRIANGLES, triangles.length, this.context.UNSIGMED_SHORT, 0);
484
- //this.deleteBuffer(indexBuffer);
485
- /*if(points.length > 3 && (!regular || this.needCut)) {
486
- const triangles = regular && this.needCut? this.earCutPointsToTriangles(points): this.getTriangles(points);
487
- if(triangles.length) {
488
- for(const triangle of triangles) {
489
- this.fillPolygons(triangle, isTexture);// 这里就变成了规则的图形了
490
- }
491
- }
492
- }
493
- else {*/
494
- const buffer = this.writePoints(points);
495
- // 纹理坐标
496
- const coordBuffer = isTexture? this.writePoints(points, this.program.attrs.a_text_coord): null;
497
-
498
- this.context.drawArrays(this.context.TRIANGLE_FAN, 0, points.length);
499
- this.deleteBuffer(buffer);
500
- coordBuffer && this.deleteBuffer(coordBuffer);
501
- //}
502
- }
503
-
504
- // 填充图形
505
- drawImage(img, left=0, top=0, width=img.width, height=img.height) {
506
- width = width || img.width;
507
- height = height || img.height;
508
-
509
- this.fillImage(img, this.points, {
510
- left,
511
- top,
512
- width,
513
- height
514
- });
515
- }
516
-
517
- drawText(text, x, y, bounds) {
518
- let canvas = this.textureCanvas;
519
- if(!canvas) {
520
- return null;
521
- }
522
- canvas.width = bounds.width;
523
- canvas.height = bounds.height;
524
-
525
- if(!canvas.width || !canvas.height) {
526
- return null;
527
- }
528
-
529
- this.textureContext.clearRect(0, 0, canvas.width, canvas.height);
530
- // 修改字体
531
- this.textureContext.font = this.style.font || (this.style.fontSize + 'px ' + this.style.fontFamily);
532
-
533
- x -= bounds.left;
534
- y -= bounds.top;
535
-
536
- this.setTextureStyle(this.style);
537
-
538
- if(this.style.fillStyle && this.textureContext.fillText) {
539
-
540
- if(this.style.maxWidth) {
541
- this.textureContext.fillText(text, x, y, this.style.maxWidth);
542
- }
543
- else {
544
- this.textureContext.fillText(text, x, y);
545
- }
546
- }
547
- if(this.textureContext.strokeText) {
548
-
549
- if(this.style.maxWidth) {
550
- this.textureContext.strokeText(text, x, y, this.style.maxWidth);
551
- }
552
- else {
553
- this.textureContext.strokeText(text, x, y);
554
- }
555
- }
556
- // 用纹理图片代替文字
557
- const data = this.textureContext.getImageData(0, 0, canvas.width, canvas.height);
558
- this.fillImage(data, this.points, bounds);
559
- }
560
- }
561
-
1
+ import WebglBase from './base.js';
2
+
3
+ // path 绘制类
4
+ class WebglPath extends WebglBase {
5
+ constructor(graph, option) {
6
+ super(graph, option);
7
+ // 是否是规则的,不规则的处理方式更为复杂和耗性能
8
+ this.isRegular = option.isRegular || false;
9
+ this.needCut = option.needCut || false;
10
+ this.control = option.control;
11
+ this.points = [];
12
+ }
13
+
14
+ // 应用变换到点
15
+ applyTransform(point) {
16
+ return super.applyTransform(point);
17
+ }
18
+
19
+ setParentBounds(parentBounds = this.parentAbsoluteBounds) {
20
+
21
+ //this.useProgram();
22
+
23
+ if(parentBounds) this.parentAbsoluteBounds = parentBounds;
24
+ // 写入当前canvas大小
25
+ this.context.uniform2f(this.program.uniforms.a_center_point.location, this.graph.width / 2, this.graph.height / 2);
26
+ }
27
+
28
+ setFragColor(color) {
29
+
30
+ if(!Array.isArray(color)) {
31
+ color = this.convertColor(color);
32
+ if(typeof color.a === 'undefined') color.a = 1;
33
+ this.context.uniform4f(this.program.uniforms.v_single_color.location, color.r, color.g, color.b, color.a * this.style.globalAlpha);
34
+ return null;
35
+ }
36
+
37
+ const colorData = [];
38
+ for(let c of color) {
39
+ c = this.convertColor(c);
40
+ if(typeof c.a === 'undefined') c.a = 1;
41
+ colorData.push(c.r, c.g, c.b, c.a * this.style.globalAlpha);
42
+ }
43
+
44
+ const colorBuffer = this.createFloat32Buffer(colorData);
45
+ this.writeVertexAttrib(colorBuffer, this.program.attrs.a_color, 4, 0, 0);
46
+ colorBuffer.attr = this.program.attrs.a_color;
47
+ return colorBuffer;
48
+ }
49
+
50
+ beginDraw() {
51
+ this.useProgram();
52
+ }
53
+
54
+ // 开始绘制
55
+ draw(points, parentBounds = this.parentAbsoluteBounds) {
56
+ //this.useProgram();
57
+
58
+ this.setParentBounds(parentBounds);
59
+
60
+ this.points = points;
61
+ }
62
+
63
+ endDraw() {
64
+ if(this.points) delete this.points;
65
+ if(this.pathPoints) delete this.pathPoints;
66
+ }
67
+
68
+ // 图形封闭
69
+ closePath() {
70
+ if(this.points && this.points.length > 2 && this.points[0] !== this.points[this.points.length-1]) {
71
+ const start = this.points[0];
72
+ const end = this.points[this.points.length-1];
73
+ if(start != end && !(start.x === end.x && start.y === end.y)) this.points.push(start);
74
+ }
75
+ }
76
+
77
+ // 绘制点数组
78
+ writePoints(points, attr = this.program.attrs.a_position) {
79
+
80
+ const fixedPoints = [];
81
+ for(const p of points) {
82
+ // 应用变换矩阵
83
+ const transformedPoint = this.applyTransform(p);
84
+ fixedPoints.push(
85
+ transformedPoint.x + this.parentAbsoluteBounds.left,
86
+ transformedPoint.y + this.parentAbsoluteBounds.top
87
+ );
88
+ }
89
+ const vertexBuffer = this.createFloat32Buffer(fixedPoints);
90
+ this.writeVertexAttrib(vertexBuffer, attr, 2, 0, 0);
91
+ vertexBuffer.attr = attr;
92
+ return vertexBuffer;
93
+ }
94
+
95
+ // 连接二个点
96
+ genLinePoints(start, end) {
97
+ const points = [start];
98
+ const dx = end.x - start.x;
99
+ const dy = end.y - start.y;
100
+ if(dx !== 0 || dy !== 0) {
101
+ const len = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
102
+ const cos = dx / len;
103
+ const sin = dy / len;
104
+ const step = 0.5;
105
+ for(let l=step; l<len; l+=step) {
106
+ const x = start.x + cos * l;
107
+ const y = start.y + sin * l;
108
+ points.push({
109
+ x,
110
+ y
111
+ });
112
+ }
113
+ }
114
+ points.push(end);
115
+ return points;
116
+ }
117
+
118
+ // 把path坐标集合分解成一个个点,并且处理moveTo线段能力
119
+ pathToPoints(points=this.points) {
120
+ let start = null;
121
+ const res = [];
122
+ for(let i=0; i<points.length; i++) {
123
+ const p = points[i];
124
+ if(start && !p.m) {
125
+ const linePoints = this.genLinePoints(start, p);
126
+ res.push(...linePoints);
127
+ }
128
+ else if(start && !res.includes(start)) {
129
+ res.push(start);
130
+ }
131
+ start = p;
132
+ }
133
+ if(!res.includes(start)) res.push(start);
134
+ return res;
135
+ }
136
+ // 二点是否重合
137
+ equalPoint(p1, p2) {
138
+ return p1.x === p2.x && p1.y === p2.y;
139
+ }
140
+ // 把path坐标集合转为线段集
141
+ pathToLines(points) {
142
+ let start = null;
143
+ const res = [];
144
+ for(let i=0; i<points.length; i++) {
145
+ const p = points[i];
146
+ // 不重合的二个点,组成线段
147
+ if(start && !p.m && !(start.x == p.x && start.y == p.y)) {
148
+ const line = {
149
+ start,
150
+ end: p,
151
+ };
152
+ res.push(line);
153
+ }
154
+ start = p;
155
+ }
156
+ return res;
157
+ }
158
+
159
+ // 裁剪线段,如果二段线段有交点,则分割成四段, 端头相交的线段不用分割
160
+ cutLines(lines, index1=0, index2=0) {
161
+ if(lines && lines.length < 3) return lines;
162
+
163
+ index2 = Math.max(index1 + 1, index2); //如果指定了比下一个更大的索引,则用更大的,说明前面的已经处理过了,不需要重复
164
+
165
+ // 找出线段相交的点,并切割线段
166
+ while(index1 < lines.length) {
167
+ const line1 = lines[index1];
168
+
169
+ while(index2 < lines.length) {
170
+ const line2 = lines[index2];
171
+ // 如果二条线顶点有重合,则不用处理
172
+ if(this.equalPoint(line1.start, line2.start) || this.equalPoint(line1.end, line2.end) ||
173
+ this.equalPoint(line1.start, line2.end) || this.equalPoint(line1.end, line2.start)) {
174
+ index2++;
175
+ continue;
176
+ }
177
+ let cuted = false;
178
+ const intersection = this.getIntersection(line1, line2);// 计算交点
179
+ if(intersection) {
180
+ // 如果交点不是线段的端点,则分割成二条线段
181
+ if(!this.equalPoint(line1.start, intersection) && !this.equalPoint(line1.end, intersection)) {
182
+ const sub1 = {
183
+ start: line1.start,
184
+ end: intersection
185
+ };
186
+ const sub2 = {
187
+ start: intersection,
188
+ end: line1.end
189
+ };
190
+ // 从原数组中删除当前线段,替换成新的线段
191
+ lines.splice(index1, 1, sub1, sub2);
192
+ // 当前线段被重新替换,需要重新从它开始处理
193
+ cuted = true;
194
+ index2 ++;// 因为多加入了一个线段,则对比线索引需要加1
195
+ }
196
+ // 如果交点不是线段的端点,则分割成二条线段
197
+ if(!this.equalPoint(line2.start, intersection) && !this.equalPoint(line2.end, intersection)) {
198
+ const sub1 = {
199
+ start: line2.start,
200
+ end: intersection
201
+ };
202
+ const sub2 = {
203
+ start: intersection,
204
+ end: line2.end
205
+ };
206
+ // 从原数组中删除当前线段,替换成新的线段
207
+ lines.splice(index2, 1, sub1, sub2);
208
+ index2 ++; // 线段2也切成了二段,对比索引要继续加1
209
+ }
210
+ }
211
+ index2++;
212
+ // 如果已经分割了起始线段,则第一个子线段开始,重新对比后面还未对比完的。直接所有对比完成返回
213
+ if(cuted) return this.cutLines(lines, index1, index2);
214
+ }
215
+ index1++;
216
+ index2 = index1 + 1;
217
+ }
218
+ return lines;
219
+ }
220
+
221
+ // 计算二个线段的交点
222
+ getIntersection(line1, line2) {
223
+ // 如果首尾相接,也认为是有交点
224
+ if(this.equalPoint(line1.start, line2.start) || this.equalPoint(line1.start, line2.end)) return line1.start;
225
+ if(this.equalPoint(line1.end, line2.start) || this.equalPoint(line1.end, line2.end)) return line1.end;
226
+
227
+ // 三角形abc 面积的2倍
228
+ const area_abc = (line1.start.x - line2.start.x) * (line1.end.y - line2.start.y) - (line1.start.y - line2.start.y) * (line1.end.x - line2.start.x);
229
+
230
+ // 三角形abd 面积的2倍
231
+ const area_abd = (line1.start.x - line2.end.x) * (line1.end.y - line2.end.y) - (line1.start.y - line2.end.y) * (line1.end.x - line2.end.x);
232
+
233
+ // 面积符号相同则两点在线段同侧,不相交 (=0表示在线段顶点上);
234
+ if (area_abc * area_abd > 0) {
235
+ return null;
236
+ }
237
+
238
+ // 三角形cda 面积的2倍
239
+ const area_cda = (line2.start.x - line1.start.x) * (line2.end.y - line1.start.y) - (line2.start.y - line1.start.y) * (line2.end.x - line1.start.x);
240
+ // 三角形cdb 面积的2倍
241
+ // 注意: 这里有一个小优化.不需要再用公式计算面积,而是通过已知的三个面积加减得出.
242
+ const area_cdb = area_cda + area_abc - area_abd ;
243
+ if(area_cda * area_cdb > 0) {
244
+ return null ;
245
+ }
246
+ if(area_abd === area_abc) return null;
247
+
248
+ //计算交点坐标
249
+ const t = area_cda / (area_abd - area_abc);
250
+ const dx= t * (line1.end.x - line1.start.x);
251
+ const dy= t * (line1.end.y - line1.start.y);
252
+
253
+ return {
254
+ x: line1.start.x + dx,
255
+ y: line1.start.y + dy
256
+ };
257
+ }
258
+
259
+ // 找出跟当前线段尾部相交的所有线段
260
+ getIntersectionLines(line, lines, index, point=line.end, points=[], root=null) {
261
+ const res = {
262
+ line,
263
+ polygons: []
264
+ };
265
+
266
+ points.push(point);
267
+
268
+ if(root && this.equalPoint(root.line.start, point)) {
269
+ points.unshift(root.line.start); // 把起始地址加入进去
270
+ root.polygons.push(points);
271
+ return res;
272
+ }
273
+
274
+ for(;index<lines.length; index++) {
275
+ const l = lines[index];
276
+ if(this.equalPoint(point, l.start)) {
277
+ if(points.includes(l.end)) continue;
278
+ this.getIntersectionLines(l, lines, index+1, l.end, [...points], root||res);
279
+ }
280
+ else if(this.equalPoint(point, l.end)) {
281
+ if(points.includes(l.start)) continue;
282
+ this.getIntersectionLines(l, lines, index+1, l.start, [...points], root||res);
283
+ }
284
+ }
285
+ return res;
286
+ }
287
+
288
+ // 根据路径点坐标,切割出封闭的多边形
289
+ getPolygon(points) {
290
+ let polygons = [];
291
+ let lines = this.pathToLines(points); // 分解得到线段
292
+ if(lines && lines.length > 2) {
293
+ lines = this.cutLines(lines); // 把所有相交点切割线段找出来
294
+ for(let i=0; i<lines.length-1; i++) {
295
+ const line1 = lines[i];
296
+ let polygon = [];// 当前图形
297
+
298
+ const treeLine = this.getIntersectionLines(line1, lines, i+1);
299
+
300
+ if(treeLine.polygons.length) polygons.push(...treeLine.polygons);
301
+ continue;
302
+ let lastLine = line1; // 下一个还在连接状态的线
303
+ for(let j=i+1; j<lines.length; j++) {
304
+ const line2 = lines[j];
305
+ // 如果跟下一条线相接,则表示还在形成图形中
306
+ if(this.equalPoint(lastLine.end, line2.start)) {
307
+ polygon.push(lastLine.end);
308
+ lastLine = line2;
309
+ if(i === j+1) continue; //下一条相连 则不需要处理相交情况
310
+ }
311
+ else {
312
+ polygon = [];
313
+ }
314
+ // 因为前面进行了分割线段,则里只有处理端点相连的情况
315
+ const intersection = this.equalPoint(line1.start, line2.end)? line1.start: null;//this.getIntersection(line1, line2);// 计算交点
316
+ if(intersection) {
317
+ polygon.push(intersection);// 交叉点为图形顶点
318
+ // 如果上一个连接线不是当前交叉线,则表示重新开始闭合
319
+ // 如果上一个连接线是当前交叉线,形成了封闭的图形
320
+ if(lastLine === line2 && polygon.length > 1) {
321
+ polygons.push(polygon);
322
+
323
+ // 封闭后,下一个起始线条就是从交点开始计算起
324
+ /*lastLine = {
325
+ start: intersection,
326
+ end: line2.end
327
+ };*/
328
+ polygon = [];// 重新开始新一轮找图形
329
+
330
+ /*
331
+ // 如果交点是上一条线的终点,则新图形为空
332
+ if(this.equalPoint(line2.end, intersection)) {
333
+ polygon = [];// 重新开始新一轮找图形
334
+ }
335
+ else {
336
+ // 同时交点也要加到上一个图形中第一个点,形成封闭
337
+ polygon.unshift(intersection);
338
+
339
+ polygon = [ intersection ];// 重新开始新一轮找图形
340
+ }*/
341
+ }
342
+ else {
343
+ lastLine = line2;
344
+ }
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ // 当有多个封闭图形时,再弟归一下,里面是不是有封闭图形内还有子封闭图形
351
+ /*if(polygons.length > 1) {
352
+ const newPolygons = [];
353
+ for(const polygon of polygons) {
354
+ // 只有大于4才有可能有子封闭图形
355
+ if(polygon.length > 4) {
356
+ const childPolygons = this.getPolygon(polygon);
357
+ // 当有多个子图形时,表示它不是最终封闭图形,跳过,
358
+ // 因为它的子图形之前有加入的,不需要重复加入
359
+ if(childPolygons.length > 1) {
360
+ //newPolygons.push(...childPolygons);
361
+ continue;
362
+ }
363
+ }
364
+ newPolygons.push(polygon);
365
+ }
366
+ polygons = newPolygons;
367
+ }*/
368
+ return polygons;
369
+ }
370
+
371
+ // 分割成一个个规则的三角形,不规则的多边形不全割的话纹理就会没法正确覆盖
372
+ getTriangles(points) {
373
+
374
+ //this.trianglesCache = this.trianglesCache||(this.trianglesCache={});
375
+ //const key = JSON.stringify(points);
376
+ //if(this.trianglesCache[key]) return this.trianglesCache[key];
377
+
378
+ const res = [];
379
+ const polygons = this.getPolygon(points);
380
+ if(polygons.length) {
381
+ for(const polygon of polygons) {
382
+ // 需要分割三角形,不然填充会有问题
383
+ const triangles = this.earCutPointsToTriangles(polygon);
384
+ res.push(...triangles);
385
+ }
386
+ }
387
+ //this.trianglesCache[key] = res;
388
+ return res;
389
+ }
390
+
391
+ // 画线条
392
+ stroke(points = this.points, color = this.style.strokeStyle, lineWidth = this.style.lineWidth) {
393
+ if(!points || !points.length) return;
394
+ // this.useProgram();
395
+
396
+ let colorBuffer = null;
397
+ if(color) {
398
+ colorBuffer = this.setFragColor(color);
399
+ }
400
+ // 线宽
401
+ if(lineWidth) {
402
+ this.context.uniform1f(this.program.uniforms.a_point_size.location, lineWidth);// * this.graph.devicePixelRatio
403
+ }
404
+ // 标注为stroke
405
+ if(this.program.uniforms.a_type) {
406
+ // 4表示单画一个圆点,1表示方块形成的线条
407
+ this.context.uniform1i(this.program.uniforms.a_type.location, points.length === 1? 4 :1);
408
+ }
409
+ if(points && points.length) {
410
+ const regular = lineWidth <= 1.2;
411
+ points = regular? points : this.pathToPoints(points);
412
+ const buffer = this.writePoints(points);
413
+ this.context.drawArrays(regular? this.context.LINE_LOOP: this.context.POINTS, 0, points.length);
414
+ this.deleteBuffer(buffer);
415
+ }
416
+ colorBuffer && this.deleteBuffer(colorBuffer);
417
+ colorBuffer && this.disableVertexAttribArray(colorBuffer.attr);
418
+ }
419
+
420
+ // 填充图形
421
+ fill(bounds = {left: 0, top: 0, width: 0, height: 0}, type = 1) {
422
+
423
+ if(this.points && this.points.length) {
424
+ // 如果是颜色rgba
425
+ if(this.style.fillStyle) {
426
+ this.fillColor(this.style.fillStyle, this.points, bounds, type);
427
+ }
428
+ if(this.style.fillImage) {
429
+ this.fillImage(this.style.fillImage, this.points, bounds, type);
430
+ }
431
+ }
432
+ }
433
+
434
+ fillColor(color, points, bounds, type=1) {
435
+
436
+ // 如果是渐变色,则需要计算偏移量的颜色
437
+ if(this.isGradient(color)) {
438
+ const imgData = color.toImageData(this, bounds, points);
439
+ return this.fillImage(imgData.data, imgData.points, bounds);
440
+ }
441
+
442
+ // 标注为fill
443
+ this.context.uniform1i(this.program.uniforms.a_type.location, type);
444
+ const colorBuffer = this.setFragColor(color);
445
+
446
+ this.fillPolygons(points);
447
+
448
+ colorBuffer && this.deleteBuffer(colorBuffer);
449
+ colorBuffer && this.disableVertexAttribArray(colorBuffer.attr);
450
+
451
+ }
452
+
453
+ // 区域填充图片
454
+ // points绘制的图形顶点
455
+ // 图片整体绘制区域
456
+ fillImage(img, points, bounds) {
457
+ if(!img) return;
458
+
459
+ // 设置纹理
460
+ const texture = img instanceof ImageData? this.createDataTexture(img) : this.createImgTexture(img);
461
+ this.context.uniform1i(this.program.uniforms.u_sample.location, 0); // 纹理单元传递给着色器
462
+
463
+ // 指定纹理区域尺寸
464
+ this.context.uniform4f(this.program.uniforms.v_texture_bounds.location,
465
+ bounds.left + this.parentAbsoluteBounds.left,
466
+ bounds.top + this.parentAbsoluteBounds.top,
467
+ bounds.width,
468
+ bounds.height,
469
+ ); // 纹理单元传递给着色器
470
+
471
+ this.fillTexture(points);
472
+
473
+ this.deleteTexture(texture);
474
+ }
475
+
476
+ fillTexture(points) {
477
+ if(points && points.length) { // 标注为纹理对象
478
+ this.context.uniform1i(this.program.uniforms.a_type.location, 2);
479
+ // 纹理坐标
480
+ //const coordBuffer = this.writePoints(points, this.program.attrs.a_text_coord);
481
+ this.fillPolygons(points, true);
482
+ //this.deleteBuffer(coordBuffer);
483
+ this.disableVertexAttribArray(this.program.attrs.a_text_coord);
484
+ }
485
+ }
486
+
487
+ // 进行多边形填充
488
+ fillPolygons(points, isTexture = false) {
489
+ //const indexBuffer = this.createUint16Buffer(triangles, this.context.ELEMENT_ARRAY_BUFFER);
490
+ //this.context.drawElements(this.context.TRIANGLES, triangles.length, this.context.UNSIGMED_SHORT, 0);
491
+ //this.deleteBuffer(indexBuffer);
492
+ /*if(points.length > 3 && (!regular || this.needCut)) {
493
+ const triangles = regular && this.needCut? this.earCutPointsToTriangles(points): this.getTriangles(points);
494
+ if(triangles.length) {
495
+ for(const triangle of triangles) {
496
+ this.fillPolygons(triangle, isTexture);// 这里就变成了规则的图形了
497
+ }
498
+ }
499
+ }
500
+ else {*/
501
+ const buffer = this.writePoints(points);
502
+ // 纹理坐标
503
+ const coordBuffer = isTexture? this.writePoints(points, this.program.attrs.a_text_coord): null;
504
+
505
+ this.context.drawArrays(this.context.TRIANGLE_FAN, 0, points.length);
506
+ this.deleteBuffer(buffer);
507
+ coordBuffer && this.deleteBuffer(coordBuffer);
508
+ //}
509
+ }
510
+
511
+ // 填充图形
512
+ drawImage(img, left=0, top=0, width=img.width, height=img.height) {
513
+ width = width || img.width;
514
+ height = height || img.height;
515
+
516
+ this.fillImage(img, this.points, {
517
+ left,
518
+ top,
519
+ width,
520
+ height
521
+ });
522
+ }
523
+
524
+ drawText(text, x, y, bounds) {
525
+ let canvas = this.textureCanvas;
526
+ if(!canvas) {
527
+ return null;
528
+ }
529
+ canvas.width = bounds.width;
530
+ canvas.height = bounds.height;
531
+
532
+ if(!canvas.width || !canvas.height) {
533
+ return null;
534
+ }
535
+
536
+ this.textureContext.clearRect(0, 0, canvas.width, canvas.height);
537
+ // 修改字体
538
+ this.textureContext.font = this.style.font || (this.style.fontSize + 'px ' + this.style.fontFamily);
539
+
540
+ x -= bounds.left;
541
+ y -= bounds.top;
542
+
543
+ this.setTextureStyle(this.style);
544
+
545
+ if(this.style.fillStyle && this.textureContext.fillText) {
546
+
547
+ if(this.style.maxWidth) {
548
+ this.textureContext.fillText(text, x, y, this.style.maxWidth);
549
+ }
550
+ else {
551
+ this.textureContext.fillText(text, x, y);
552
+ }
553
+ }
554
+ if(this.textureContext.strokeText) {
555
+
556
+ if(this.style.maxWidth) {
557
+ this.textureContext.strokeText(text, x, y, this.style.maxWidth);
558
+ }
559
+ else {
560
+ this.textureContext.strokeText(text, x, y);
561
+ }
562
+ }
563
+ // 用纹理图片代替文字
564
+ const data = this.textureContext.getImageData(0, 0, canvas.width, canvas.height);
565
+ this.fillImage(data, this.points, bounds);
566
+ }
567
+ }
568
+
562
569
  export default WebglPath;