jmgraph 3.2.19 → 3.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +311 -6
- package/dist/jmgraph.core.min.js +1 -1
- package/dist/jmgraph.core.min.js.map +1 -1
- package/dist/jmgraph.js +2022 -368
- package/dist/jmgraph.min.js +1 -1
- package/index.js +23 -25
- package/package.json +1 -1
- package/src/core/jmControl.js +199 -30
- package/src/core/jmFilter.js +150 -0
- package/src/core/jmGraph.js +207 -7
- package/src/core/jmLayer.js +142 -0
- package/src/core/jmPath.js +55 -0
- package/src/core/jmUtils.js +46 -37
- package/src/lib/webgl/base.js +10 -36
- package/src/lib/webgl/gradient.js +16 -3
- package/src/lib/webgl/index.js +5 -4
- package/src/lib/webgl/path.js +156 -33
- package/src/shapes/jmEllipse.js +91 -0
- package/src/shapes/jmLabel.js +126 -75
- package/src/shapes/jmPolygon.js +129 -0
- package/src/shapes/jmRect.js +107 -29
- package/src/shapes/jmStar.js +160 -0
- package/example/ball.html +0 -217
- package/example/base.html +0 -112
- package/example/canvas.html +0 -54
- package/example/cell.html +0 -284
- package/example/controls/arc.html +0 -129
- package/example/controls/arrowline.html +0 -78
- package/example/controls/bezier.html +0 -299
- package/example/controls/img.html +0 -97
- package/example/controls/label.html +0 -87
- package/example/controls/line.html +0 -173
- package/example/controls/prismatic.html +0 -63
- package/example/controls/rect.html +0 -64
- package/example/controls/resize.html +0 -112
- package/example/controls/test.html +0 -360
- package/example/es.html +0 -70
- package/example/es5module.html +0 -63
- package/example/heartarc.html +0 -116
- package/example/index.html +0 -47
- package/example/js/require.js +0 -5
- package/example/love/img/bling/bling.tps +0 -265
- package/example/love/img/bling.json +0 -87
- package/example/love/img/bling.tps +0 -295
- package/example/love/img/doc/bling.gif +0 -0
- package/example/love/img/love.json +0 -95
- package/example/love/img/love.tps +0 -315
- package/example/love/img/qq/qq.tps +0 -399
- package/example/love/img/qq.json +0 -242
- package/example/love/index.html +0 -40
- package/example/love/js/game.js +0 -558
- package/example/music.html +0 -211
- package/example/node/test.js +0 -138
- package/example/pdf.html +0 -187
- package/example/progress.html +0 -173
- package/example/pso.html +0 -148
- package/example/sort.html +0 -805
- package/example/tweenjs.html +0 -84
- package/example/webgl.html +0 -278
- package/example/xfj/img/dr_die.gif +0 -0
- package/example/xfj/index.html +0 -332
- package/example/xfj/shake.js +0 -49
- package/example/xfj/testori.html +0 -76
|
@@ -53,8 +53,14 @@ class WebglGradient {
|
|
|
53
53
|
|
|
54
54
|
// 转为渐变为纹理
|
|
55
55
|
toImageData(control, bounds, points=null) {
|
|
56
|
-
//
|
|
57
|
-
|
|
56
|
+
// 缓存基于渐变参数(不含 bounds,因为同一个渐变只是位置不同时纹理相同)
|
|
57
|
+
const gradientKey = this.toString();
|
|
58
|
+
if(this.__cachedData && this.__cacheKey === gradientKey &&
|
|
59
|
+
this.__cachedData.data && this.__cachedData.data.width === Math.ceil(bounds.width) &&
|
|
60
|
+
this.__cachedData.data.data && this.__cachedData.data.data.height === Math.ceil(bounds.height)) {
|
|
61
|
+
return this.__cachedData;
|
|
62
|
+
}
|
|
63
|
+
|
|
58
64
|
if(!control.textureContext) {
|
|
59
65
|
return null;
|
|
60
66
|
}
|
|
@@ -72,11 +78,18 @@ class WebglGradient {
|
|
|
72
78
|
|
|
73
79
|
const data = control.toFillTexture(gradient, bounds, points);
|
|
74
80
|
|
|
75
|
-
|
|
81
|
+
this.__cachedData = data;
|
|
82
|
+
this.__cacheKey = gradientKey;
|
|
76
83
|
|
|
77
84
|
return data;
|
|
78
85
|
}
|
|
79
86
|
|
|
87
|
+
// 当渐变参数变化时使缓存失效
|
|
88
|
+
invalidateCache() {
|
|
89
|
+
this.__cachedData = null;
|
|
90
|
+
this.__cacheKey = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
// 根据绘制图形的坐标计算出对应点的颜色
|
|
81
94
|
/*
|
|
82
95
|
toPointColors(points) {
|
package/src/lib/webgl/index.js
CHANGED
|
@@ -105,12 +105,13 @@ class webgl {
|
|
|
105
105
|
// 由具体的绘制方法处理
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
//
|
|
108
|
+
// 测量文本宽度(复用纹理 canvas 的 context)
|
|
109
109
|
measureText(text) {
|
|
110
|
-
|
|
110
|
+
const ctx = this.base.textureContext;
|
|
111
|
+
if(ctx && ctx.measureText) return ctx.measureText(text);
|
|
111
112
|
const canvas = document.createElement('canvas');
|
|
112
|
-
const
|
|
113
|
-
return
|
|
113
|
+
const ctx2 = canvas.getContext('2d');
|
|
114
|
+
return ctx2.measureText(text);
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
// 创建线性渐变
|
package/src/lib/webgl/path.js
CHANGED
|
@@ -9,6 +9,40 @@ class WebglPath extends WebglBase {
|
|
|
9
9
|
this.needCut = option.needCut || false;
|
|
10
10
|
this.control = option.control;
|
|
11
11
|
this.points = [];
|
|
12
|
+
// 缓存 buffer 和纹理,避免每帧创建/销毁
|
|
13
|
+
this.__cachedBuffers = [];
|
|
14
|
+
this.__cachedTexture = null;
|
|
15
|
+
this.__cachedTextureKey = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 释放缓存的 WebGL 资源
|
|
19
|
+
dispose() {
|
|
20
|
+
for(const buf of this.__cachedBuffers) {
|
|
21
|
+
this.deleteBuffer(buf);
|
|
22
|
+
}
|
|
23
|
+
this.__cachedBuffers = [];
|
|
24
|
+
if(this.__cachedTexture) {
|
|
25
|
+
this.deleteTexture(this.__cachedTexture);
|
|
26
|
+
this.__cachedTexture = null;
|
|
27
|
+
this.__cachedTextureKey = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 获取或创建 buffer,优先复用缓存
|
|
32
|
+
getOrCreateBuffer(data, attr) {
|
|
33
|
+
let buffer = this.__cachedBuffers.find(b => b.attr === attr);
|
|
34
|
+
if(buffer) {
|
|
35
|
+
const gl = this.context;
|
|
36
|
+
const float32 = new Float32Array(data);
|
|
37
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
|
|
38
|
+
gl.bufferData(gl.ARRAY_BUFFER, float32, gl.DYNAMIC_DRAW);
|
|
39
|
+
buffer.data = data;
|
|
40
|
+
return buffer;
|
|
41
|
+
}
|
|
42
|
+
buffer = this.createFloat32Buffer(data);
|
|
43
|
+
buffer.attr = attr;
|
|
44
|
+
this.__cachedBuffers.push(buffer);
|
|
45
|
+
return buffer;
|
|
12
46
|
}
|
|
13
47
|
|
|
14
48
|
// 应用变换到点
|
|
@@ -63,6 +97,7 @@ class WebglPath extends WebglBase {
|
|
|
63
97
|
endDraw() {
|
|
64
98
|
if(this.points) delete this.points;
|
|
65
99
|
if(this.pathPoints) delete this.pathPoints;
|
|
100
|
+
// 缓存的纹理保留到下次绘制(渐变可能不变)
|
|
66
101
|
}
|
|
67
102
|
|
|
68
103
|
// 图形封闭
|
|
@@ -74,21 +109,47 @@ class WebglPath extends WebglBase {
|
|
|
74
109
|
}
|
|
75
110
|
}
|
|
76
111
|
|
|
77
|
-
//
|
|
112
|
+
// 绘制点数组(使用 DYNAMIC_DRAW 复用 buffer,避免每帧 create/delete)
|
|
78
113
|
writePoints(points, attr = this.program.attrs.a_position) {
|
|
79
|
-
|
|
80
114
|
const fixedPoints = [];
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
const [a, b, c, d, tx, ty] = this.transformMatrix;
|
|
116
|
+
const isIdentity = (a === 1 && b === 0 && c === 0 && d === 1 && tx === 0 && ty === 0);
|
|
117
|
+
const offsetLeft = this.parentAbsoluteBounds.left;
|
|
118
|
+
const offsetTop = this.parentAbsoluteBounds.top;
|
|
119
|
+
|
|
120
|
+
if(isIdentity) {
|
|
121
|
+
// 单位矩阵时直接加偏移,避免逐点调用 applyTransform
|
|
122
|
+
for(let i = 0; i < points.length; i++) {
|
|
123
|
+
fixedPoints.push(points[i].x + offsetLeft, points[i].y + offsetTop);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
for(const p of points) {
|
|
127
|
+
const transformedPoint = this.applyTransform(p);
|
|
128
|
+
fixedPoints.push(
|
|
129
|
+
transformedPoint.x + offsetLeft,
|
|
130
|
+
transformedPoint.y + offsetTop
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const float32 = new Float32Array(fixedPoints);
|
|
135
|
+
const gl = this.context;
|
|
136
|
+
|
|
137
|
+
// 复用已有 buffer 或创建新的
|
|
138
|
+
if(this.__cachedBuffers.length > 0) {
|
|
139
|
+
// 找一个同 attr 的 buffer 复用
|
|
140
|
+
let buffer = this.__cachedBuffers.find(b => b.attr === attr);
|
|
141
|
+
if(buffer) {
|
|
142
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
|
|
143
|
+
gl.bufferData(gl.ARRAY_BUFFER, float32, gl.DYNAMIC_DRAW);
|
|
144
|
+
buffer.data = fixedPoints;
|
|
145
|
+
this.writeVertexAttrib(buffer, attr, 2, 0, 0);
|
|
146
|
+
return buffer;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const vertexBuffer = this.createFloat32Buffer(float32, gl.ARRAY_BUFFER, gl.DYNAMIC_DRAW);
|
|
90
150
|
this.writeVertexAttrib(vertexBuffer, attr, 2, 0, 0);
|
|
91
151
|
vertexBuffer.attr = attr;
|
|
152
|
+
this.__cachedBuffers.push(vertexBuffer);
|
|
92
153
|
return vertexBuffer;
|
|
93
154
|
}
|
|
94
155
|
|
|
@@ -370,9 +431,10 @@ class WebglPath extends WebglBase {
|
|
|
370
431
|
|
|
371
432
|
// 分割成一个个规则的三角形,不规则的多边形不全割的话纹理就会没法正确覆盖
|
|
372
433
|
getTriangles(points) {
|
|
373
|
-
|
|
374
434
|
this.trianglesCache = this.trianglesCache||(this.trianglesCache={});
|
|
375
|
-
|
|
435
|
+
// 快速缓存 key:用长度和首尾点坐标(比 JSON.stringify 快几个数量级)
|
|
436
|
+
const len = points.length;
|
|
437
|
+
const key = len + '_' + points[0].x + '_' + points[0].y + '_' + points[len-1].x + '_' + points[len-1].y;
|
|
376
438
|
if(this.trianglesCache[key]) return this.trianglesCache[key];
|
|
377
439
|
|
|
378
440
|
const res = [];
|
|
@@ -411,10 +473,9 @@ class WebglPath extends WebglBase {
|
|
|
411
473
|
points = regular? points : this.pathToPoints(points);
|
|
412
474
|
const buffer = this.writePoints(points);
|
|
413
475
|
this.context.drawArrays(regular? this.context.LINE_LOOP: this.context.POINTS, 0, points.length);
|
|
414
|
-
|
|
476
|
+
// buffer 由 endDraw 统一清理
|
|
415
477
|
}
|
|
416
|
-
colorBuffer && this.
|
|
417
|
-
colorBuffer && this.disableVertexAttribArray(colorBuffer.attr);
|
|
478
|
+
colorBuffer && this.disableVertexAttribArray(colorBuffer && colorBuffer.attr);
|
|
418
479
|
}
|
|
419
480
|
|
|
420
481
|
// 填充图形
|
|
@@ -445,8 +506,7 @@ class WebglPath extends WebglBase {
|
|
|
445
506
|
|
|
446
507
|
this.fillPolygons(points);
|
|
447
508
|
|
|
448
|
-
colorBuffer && this.
|
|
449
|
-
colorBuffer && this.disableVertexAttribArray(colorBuffer.attr);
|
|
509
|
+
colorBuffer && this.disableVertexAttribArray(colorBuffer && colorBuffer.attr);
|
|
450
510
|
|
|
451
511
|
}
|
|
452
512
|
|
|
@@ -456,8 +516,24 @@ class WebglPath extends WebglBase {
|
|
|
456
516
|
fillImage(img, points, bounds) {
|
|
457
517
|
if(!img) return;
|
|
458
518
|
|
|
459
|
-
//
|
|
460
|
-
|
|
519
|
+
// 对于 ImageData,生成缓存 key(基于渐变参数或 bounds),复用纹理
|
|
520
|
+
let texture = null;
|
|
521
|
+
if(img instanceof ImageData) {
|
|
522
|
+
const key = `${img.width}_${img.height}_${bounds.width}_${bounds.height}_${bounds.left}_${bounds.top}`;
|
|
523
|
+
if(this.__cachedTexture && this.__cachedTextureKey === key) {
|
|
524
|
+
texture = this.__cachedTexture;
|
|
525
|
+
} else {
|
|
526
|
+
texture = this.createDataTexture(img);
|
|
527
|
+
// 释放旧纹理
|
|
528
|
+
if(this.__cachedTexture) {
|
|
529
|
+
this.deleteTexture(this.__cachedTexture);
|
|
530
|
+
}
|
|
531
|
+
this.__cachedTexture = texture;
|
|
532
|
+
this.__cachedTextureKey = key;
|
|
533
|
+
}
|
|
534
|
+
} else {
|
|
535
|
+
texture = this.createImgTexture(img);
|
|
536
|
+
}
|
|
461
537
|
this.context.uniform1i(this.program.uniforms.u_sample.location, 0); // 纹理单元传递给着色器
|
|
462
538
|
|
|
463
539
|
// 指定纹理区域尺寸
|
|
@@ -470,7 +546,10 @@ class WebglPath extends WebglBase {
|
|
|
470
546
|
|
|
471
547
|
this.fillTexture(points);
|
|
472
548
|
|
|
473
|
-
|
|
549
|
+
// 仅对非缓存纹理(非 ImageData)立即删除
|
|
550
|
+
if(!(img instanceof ImageData)) {
|
|
551
|
+
this.deleteTexture(texture);
|
|
552
|
+
}
|
|
474
553
|
}
|
|
475
554
|
|
|
476
555
|
fillTexture(points) {
|
|
@@ -486,23 +565,67 @@ class WebglPath extends WebglBase {
|
|
|
486
565
|
|
|
487
566
|
// 进行多边形填充
|
|
488
567
|
fillPolygons(points, isTexture = false) {
|
|
489
|
-
if(points.length
|
|
490
|
-
|
|
491
|
-
if(triangles.length) {
|
|
492
|
-
for(const triangle of triangles) {
|
|
493
|
-
this.fillPolygons(triangle, isTexture);// 这里就变成了规则的图形了
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
568
|
+
if(points.length <= 3) {
|
|
569
|
+
// 3个点以下的三角形直接画
|
|
498
570
|
const buffer = this.writePoints(points);
|
|
499
|
-
// 纹理坐标
|
|
500
571
|
const coordBuffer = isTexture? this.writePoints(points, this.program.attrs.a_text_coord): null;
|
|
572
|
+
this.context.drawArrays(this.context.TRIANGLE_FAN, 0, points.length);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
501
575
|
|
|
576
|
+
// 规则图形(凸多边形,如圆):直接用 TRIANGLE_FAN 一次性绘制,无需 earcut
|
|
577
|
+
if(this.isRegular) {
|
|
578
|
+
const buffer = this.writePoints(points);
|
|
579
|
+
const coordBuffer = isTexture? this.writePoints(points, this.program.attrs.a_text_coord): null;
|
|
502
580
|
this.context.drawArrays(this.context.TRIANGLE_FAN, 0, points.length);
|
|
503
|
-
|
|
504
|
-
coordBuffer && this.deleteBuffer(coordBuffer);
|
|
581
|
+
return;
|
|
505
582
|
}
|
|
583
|
+
|
|
584
|
+
// 不规则图形:需要 earcut 三角化后,合并为一个大的顶点缓冲区,单次 drawArrays
|
|
585
|
+
const triangles = this.needCut? this.earCutPointsToTriangles(points): this.getTriangles(points);
|
|
586
|
+
if(!triangles.length) return;
|
|
587
|
+
|
|
588
|
+
// 合并所有三角形的顶点到一个数组
|
|
589
|
+
const allVertices = [];
|
|
590
|
+
const allTexCoords = [];
|
|
591
|
+
for(const triangle of triangles) {
|
|
592
|
+
for(const p of triangle) {
|
|
593
|
+
allVertices.push(p.x, p.y);
|
|
594
|
+
if(isTexture) allTexCoords.push(p.x, p.y);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// 一次性上传所有数据并绘制
|
|
599
|
+
const vertexData = new Float32Array(allVertices);
|
|
600
|
+
const gl = this.context;
|
|
601
|
+
|
|
602
|
+
// 复用或创建 position buffer
|
|
603
|
+
let posBuffer = this.__cachedBuffers.find(b => b.attr === this.program.attrs.a_position);
|
|
604
|
+
if(!posBuffer) {
|
|
605
|
+
posBuffer = this.createFloat32Buffer(vertexData, gl.ARRAY_BUFFER, gl.DYNAMIC_DRAW);
|
|
606
|
+
posBuffer.attr = this.program.attrs.a_position;
|
|
607
|
+
this.__cachedBuffers.push(posBuffer);
|
|
608
|
+
} else {
|
|
609
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer.buffer);
|
|
610
|
+
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.DYNAMIC_DRAW);
|
|
611
|
+
}
|
|
612
|
+
this.writeVertexAttrib(posBuffer, this.program.attrs.a_position, 2, 0, 0);
|
|
613
|
+
|
|
614
|
+
if(isTexture && allTexCoords.length) {
|
|
615
|
+
const texData = new Float32Array(allTexCoords);
|
|
616
|
+
let texBuffer = this.__cachedBuffers.find(b => b.attr === this.program.attrs.a_text_coord);
|
|
617
|
+
if(!texBuffer) {
|
|
618
|
+
texBuffer = this.createFloat32Buffer(texData, gl.ARRAY_BUFFER, gl.DYNAMIC_DRAW);
|
|
619
|
+
texBuffer.attr = this.program.attrs.a_text_coord;
|
|
620
|
+
this.__cachedBuffers.push(texBuffer);
|
|
621
|
+
} else {
|
|
622
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, texBuffer.buffer);
|
|
623
|
+
gl.bufferData(gl.ARRAY_BUFFER, texData, gl.DYNAMIC_DRAW);
|
|
624
|
+
}
|
|
625
|
+
this.writeVertexAttrib(texBuffer, this.program.attrs.a_text_coord, 2, 0, 0);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
gl.drawArrays(gl.TRIANGLES, 0, allVertices.length / 2);
|
|
506
629
|
}
|
|
507
630
|
|
|
508
631
|
// 填充图形
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {jmArc} from "./jmArc.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 画椭圆
|
|
5
|
+
* 椭圆是通过缩放圆形来实现的,支持完整的椭圆和椭圆弧
|
|
6
|
+
* 可以指定起始角度和结束角度来绘制椭圆弧
|
|
7
|
+
*
|
|
8
|
+
* @class jmEllipse
|
|
9
|
+
* @extends jmArc
|
|
10
|
+
* @param {object} params 椭圆的参数
|
|
11
|
+
* @param {object} [params.center={x:0,y:0}] 椭圆中心点坐标
|
|
12
|
+
* @param {number} [params.width=100] 椭圆宽度(长轴直径)
|
|
13
|
+
* @param {number} [params.height=60] 椭圆高度(短轴直径)
|
|
14
|
+
* @param {number} [params.startAngle=0] 起始角度(弧度)
|
|
15
|
+
* @param {number} [params.endAngle=Math.PI*2] 结束角度(弧度)
|
|
16
|
+
* @param {boolean} [params.anticlockwise=false] 是否逆时针绘制
|
|
17
|
+
*/
|
|
18
|
+
export default class jmEllipse extends jmArc {
|
|
19
|
+
|
|
20
|
+
constructor(params, t='jmEllipse') {
|
|
21
|
+
params = params || {};
|
|
22
|
+
params.isRegular = true; // 标记为规则图形
|
|
23
|
+
super(params, t);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 初始化图形点
|
|
28
|
+
* 为WebGL模式生成控制点,2D模式使用draw方法直接绘制
|
|
29
|
+
*
|
|
30
|
+
* @method initPoints
|
|
31
|
+
* @private
|
|
32
|
+
* @for jmEllipse
|
|
33
|
+
*/
|
|
34
|
+
initPoints() {
|
|
35
|
+
// WebGL模式使用父类的点生成方法
|
|
36
|
+
if(this.graph.mode === 'webgl') {
|
|
37
|
+
return super.initPoints();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 2D模式:生成4个控制点用于边界计算
|
|
41
|
+
// 这些点不是实际的绘制点,而是用于碰撞检测和边界计算
|
|
42
|
+
let location = this.getLocation();
|
|
43
|
+
|
|
44
|
+
this.points = [];
|
|
45
|
+
this.points.push({x:location.center.x - location.width/2, y:location.center.y}); // 左
|
|
46
|
+
this.points.push({x:location.center.x, y:location.center.y - location.height/2}); // 上
|
|
47
|
+
this.points.push({x:location.center.x + location.width/2, y:location.center.y}); // 右
|
|
48
|
+
this.points.push({x:location.center.x, y:location.center.y + location.height/2}); // 下
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 重写基类画图,此处为画一个椭圆
|
|
53
|
+
* 使用Canvas的变换功能(平移和缩放)来绘制椭圆
|
|
54
|
+
*
|
|
55
|
+
* @method draw
|
|
56
|
+
*/
|
|
57
|
+
draw() {
|
|
58
|
+
// WebGL模式使用父类的绘制方法
|
|
59
|
+
if(this.graph.mode === 'webgl') {
|
|
60
|
+
return super.draw();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 获取边界和位置信息
|
|
64
|
+
let bounds = this.parent && this.parent.absoluteBounds ? this.parent.absoluteBounds : this.absoluteBounds;
|
|
65
|
+
let location = this.getLocation();
|
|
66
|
+
|
|
67
|
+
// 获取椭圆弧参数
|
|
68
|
+
let start = this.startAngle || 0;
|
|
69
|
+
let end = this.endAngle || Math.PI * 2;
|
|
70
|
+
let anticlockwise = this.anticlockwise || false;
|
|
71
|
+
|
|
72
|
+
// 椭圆绘制:通过变换圆形来实现
|
|
73
|
+
// 1. 保存当前绘图状态
|
|
74
|
+
this.context.save();
|
|
75
|
+
|
|
76
|
+
// 2. 平移到椭圆中心
|
|
77
|
+
this.context.translate(location.center.x + bounds.left, location.center.y + bounds.top);
|
|
78
|
+
|
|
79
|
+
// 3. 缩放坐标系,使圆形变为椭圆
|
|
80
|
+
// 将X轴缩放width/2,Y轴缩放height/2,这样单位圆就变成了椭圆
|
|
81
|
+
this.context.scale(location.width/2, location.height/2);
|
|
82
|
+
|
|
83
|
+
// 4. 绘制单位圆(会被缩放成椭圆)
|
|
84
|
+
this.context.arc(0, 0, 1, start, end, anticlockwise);
|
|
85
|
+
|
|
86
|
+
// 5. 恢复绘图状态
|
|
87
|
+
this.context.restore();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { jmEllipse };
|
package/src/shapes/jmLabel.js
CHANGED
|
@@ -22,7 +22,7 @@ export default class jmLabel extends jmControl {
|
|
|
22
22
|
this.style.textAlign = this.style.textAlign || 'left';
|
|
23
23
|
//文字垂直对齐
|
|
24
24
|
this.style.textBaseline = this.style.textBaseline || 'middle';
|
|
25
|
-
this.text = params.text || '';
|
|
25
|
+
this.text = params.text || params.value || '';
|
|
26
26
|
|
|
27
27
|
this.center = params.center || null;
|
|
28
28
|
}
|
|
@@ -108,43 +108,137 @@ export default class jmLabel extends jmControl {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* 测试获取文本所占大小
|
|
111
|
-
*
|
|
111
|
+
* 计算文本渲染所需的宽度和高度,支持自动换行
|
|
112
|
+
*
|
|
112
113
|
* @method testSize
|
|
113
|
-
* @return {object} 含文本大小的对象
|
|
114
|
+
* @return {object} 含文本大小的对象 {width, height}
|
|
114
115
|
*/
|
|
115
116
|
testSize() {
|
|
117
|
+
// 使用缓存提高性能,避免重复计算
|
|
116
118
|
if(this.__size) return this.__size;
|
|
117
119
|
|
|
118
|
-
if(this.webglControl)
|
|
120
|
+
if(this.webglControl) {
|
|
121
|
+
this.__size = this.webglControl.testSize(this.text, this.style);
|
|
122
|
+
}
|
|
119
123
|
else {
|
|
120
124
|
this.context.save && this.context.save();
|
|
121
|
-
|
|
125
|
+
|
|
126
|
+
// 设置字体样式用于测量
|
|
122
127
|
this.setStyle({
|
|
123
128
|
font: this.style.font || (this.style.fontSize + 'px ' + this.style.fontFamily)
|
|
124
129
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
|
|
131
|
+
// 计算文本尺寸
|
|
132
|
+
if(this.style.maxWidth && this.text) {
|
|
133
|
+
// 文本换行处理
|
|
134
|
+
const lines = this.wrapText(this.text, this.style.maxWidth);
|
|
135
|
+
let maxWidth = 0;
|
|
136
|
+
|
|
137
|
+
// 找出最宽的一行
|
|
138
|
+
for(let line of lines) {
|
|
139
|
+
const width = this.context.measureText(line).width;
|
|
140
|
+
if(width > maxWidth) maxWidth = width;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 计算总高度(行数 × 行高)
|
|
144
|
+
const lineHeight = this.style.lineHeight || this.style.fontSize * 1.2;
|
|
145
|
+
this.__size = {
|
|
146
|
+
width: maxWidth,
|
|
147
|
+
height: lineHeight * lines.length
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// 单行文本
|
|
152
|
+
this.__size = this.context.measureText ?
|
|
153
|
+
this.context.measureText(this.text) :
|
|
154
|
+
{width: 15};
|
|
155
|
+
this.__size.height = this.style.fontSize ? this.style.fontSize : 15;
|
|
156
|
+
}
|
|
157
|
+
|
|
129
158
|
this.context.restore && this.context.restore();
|
|
130
|
-
this.__size.height = this.style.fontSize?this.style.fontSize:15;
|
|
131
159
|
}
|
|
132
160
|
|
|
161
|
+
// 设置默认宽高
|
|
133
162
|
if(!this.width) this.width = this.__size.width;
|
|
134
163
|
if(!this.height) this.height = this.__size.height;
|
|
135
164
|
|
|
136
165
|
return this.__size;
|
|
137
166
|
}
|
|
138
167
|
|
|
168
|
+
/**
|
|
169
|
+
* 文本换行处理
|
|
170
|
+
* 根据最大宽度将文本分割成多行
|
|
171
|
+
* 支持中英文混合文本,优先在空格处换行
|
|
172
|
+
*
|
|
173
|
+
* @method wrapText
|
|
174
|
+
* @param {string} text 文本内容
|
|
175
|
+
* @param {number} maxWidth 最大宽度(像素)
|
|
176
|
+
* @return {array} 换行后的文本数组
|
|
177
|
+
*/
|
|
178
|
+
wrapText(text, maxWidth) {
|
|
179
|
+
// 参数验证
|
|
180
|
+
if(!text || !maxWidth) return [text || ''];
|
|
181
|
+
|
|
182
|
+
// 检查缓存,避免重复计算
|
|
183
|
+
const cacheKey = `${text}_${maxWidth}`;
|
|
184
|
+
if(this.__wrapTextCache && this.__wrapTextCache.key === cacheKey) {
|
|
185
|
+
return this.__wrapTextCache.lines;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const lines = [];
|
|
189
|
+
|
|
190
|
+
// 先按换行符分割
|
|
191
|
+
const paragraphs = text.split('\n');
|
|
192
|
+
|
|
193
|
+
for(let paragraph of paragraphs) {
|
|
194
|
+
// 如果段落为空,添加空行
|
|
195
|
+
if(!paragraph) {
|
|
196
|
+
lines.push('');
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 按空格分割单词
|
|
201
|
+
const words = paragraph.split(' ');
|
|
202
|
+
let currentLine = words[0];
|
|
203
|
+
|
|
204
|
+
for(let i = 1; i < words.length; i++) {
|
|
205
|
+
const word = words[i];
|
|
206
|
+
const testLine = currentLine + ' ' + word;
|
|
207
|
+
const metrics = this.context.measureText(testLine);
|
|
208
|
+
const testWidth = metrics.width;
|
|
209
|
+
|
|
210
|
+
if(testWidth <= maxWidth) {
|
|
211
|
+
// 当前行还能容纳这个单词
|
|
212
|
+
currentLine = testLine;
|
|
213
|
+
} else {
|
|
214
|
+
// 当前行已满,保存当前行并开始新行
|
|
215
|
+
if(currentLine) lines.push(currentLine);
|
|
216
|
+
currentLine = word;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 添加最后一行
|
|
221
|
+
if(currentLine) lines.push(currentLine);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 缓存结果
|
|
225
|
+
this.__wrapTextCache = {
|
|
226
|
+
key: cacheKey,
|
|
227
|
+
lines: lines
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return lines;
|
|
231
|
+
}
|
|
232
|
+
|
|
139
233
|
/**
|
|
140
234
|
* 根据位置偏移画字符串
|
|
141
235
|
*
|
|
142
236
|
* @method draw
|
|
143
237
|
*/
|
|
144
|
-
draw() {
|
|
238
|
+
draw() {
|
|
145
239
|
|
|
146
240
|
//获取当前控件的绝对位置
|
|
147
|
-
let bounds = this.parent && this.parent.absoluteBounds?this.parent.absoluteBounds:this.absoluteBounds;
|
|
241
|
+
let bounds = this.parent && this.parent.absoluteBounds?this.parent.absoluteBounds:this.absoluteBounds;
|
|
148
242
|
const size = this.testSize();
|
|
149
243
|
let location = this.location;
|
|
150
244
|
let x = location.left + bounds.left;
|
|
@@ -184,7 +278,16 @@ export default class jmLabel extends jmControl {
|
|
|
184
278
|
}
|
|
185
279
|
else if(this.style.fill && this.context.fillText) {
|
|
186
280
|
if(this.style.maxWidth) {
|
|
187
|
-
|
|
281
|
+
// 绘制换行文本
|
|
282
|
+
const lines = this.wrapText(txt, this.style.maxWidth);
|
|
283
|
+
const lineHeight = this.style.fontSize;
|
|
284
|
+
// 调整起始Y位置以支持垂直对齐
|
|
285
|
+
const startY = y - (lines.length - 1) * lineHeight / 2;
|
|
286
|
+
|
|
287
|
+
for(let i = 0; i < lines.length; i++) {
|
|
288
|
+
const lineY = startY + i * lineHeight;
|
|
289
|
+
this.context.fillText(lines[i], x, lineY);
|
|
290
|
+
}
|
|
188
291
|
}
|
|
189
292
|
else {
|
|
190
293
|
this.context.fillText(txt,x,y);
|
|
@@ -192,74 +295,22 @@ export default class jmLabel extends jmControl {
|
|
|
192
295
|
}
|
|
193
296
|
else if(this.context.strokeText) {
|
|
194
297
|
if(this.style.maxWidth) {
|
|
195
|
-
|
|
298
|
+
// 绘制换行文本
|
|
299
|
+
const lines = this.wrapText(txt, this.style.maxWidth);
|
|
300
|
+
const lineHeight = this.style.fontSize;
|
|
301
|
+
// 调整起始Y位置以支持垂直对齐
|
|
302
|
+
const startY = y - (lines.length - 1) * lineHeight / 2;
|
|
303
|
+
|
|
304
|
+
for(let i = 0; i < lines.length; i++) {
|
|
305
|
+
const lineY = startY + i * lineHeight;
|
|
306
|
+
this.context.strokeText(lines[i], x, lineY);
|
|
307
|
+
}
|
|
196
308
|
}
|
|
197
309
|
else {
|
|
198
310
|
this.context.strokeText(txt,x,y);
|
|
199
311
|
}
|
|
200
312
|
}
|
|
201
313
|
}
|
|
202
|
-
//如果有指定边框,则画出边框
|
|
203
|
-
if(this.style.border) {
|
|
204
|
-
//如果指定了边框样式
|
|
205
|
-
if(this.style.border.style) {
|
|
206
|
-
this.context.save && this.context.save();
|
|
207
|
-
this.setStyle(this.style.border.style);
|
|
208
|
-
}
|
|
209
|
-
if(this.mode === '2d') {
|
|
210
|
-
this.context.moveTo(this.points[0].x + bounds.left,this.points[0].y + bounds.top);
|
|
211
|
-
if(this.style.border.top) {
|
|
212
|
-
this.context.lineTo(this.points[1].x + bounds.left,this.points[1].y + bounds.top);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if(this.style.border.right) {
|
|
216
|
-
this.context.moveTo(this.points[1].x + bounds.left,this.points[1].y + bounds.top);
|
|
217
|
-
this.context.lineTo(this.points[2].x + bounds.left,this.points[2].y + bounds.top);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if(this.style.border.bottom) {
|
|
221
|
-
this.context.moveTo(this.points[2].x + bounds.left,this.points[2].y + bounds.top);
|
|
222
|
-
this.context.lineTo(this.points[3].x + bounds.left,this.points[3].y + bounds.top);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if(this.style.border.left) {
|
|
226
|
-
this.context.moveTo(this.points[3].x + bounds.left,this.points[3].y + bounds.top);
|
|
227
|
-
this.context.lineTo(this.points[0].x + bounds.left,this.points[0].y + bounds.top);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
const points = [];
|
|
232
|
-
if(this.style.border.top) {
|
|
233
|
-
points.push(this.points[0]);
|
|
234
|
-
points.push(this.points[1]);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if(this.style.border.right) {
|
|
238
|
-
points.push({
|
|
239
|
-
...this.points[1],
|
|
240
|
-
m: true
|
|
241
|
-
});
|
|
242
|
-
points.push(this.points[2]);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if(this.style.border.bottom) {
|
|
246
|
-
points.push({
|
|
247
|
-
...this.points[2],
|
|
248
|
-
m: true
|
|
249
|
-
});
|
|
250
|
-
points.push(this.points[3]);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if(this.style.border.left) {
|
|
254
|
-
points.push({
|
|
255
|
-
...this.points[3],
|
|
256
|
-
m: true
|
|
257
|
-
});
|
|
258
|
-
points.push(this.points[0]);
|
|
259
|
-
}
|
|
260
|
-
points.length && this.webglControl && this.webglControl.stroke(points);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
314
|
}
|
|
264
315
|
|
|
265
316
|
endDraw() {
|