jmgraph 3.2.25 → 3.2.26
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/dist/jmgraph.core.min.js +1 -1
- package/dist/jmgraph.core.min.js.map +1 -1
- package/dist/jmgraph.js +725 -61
- package/dist/jmgraph.min.js +1 -1
- package/package.json +1 -1
- package/src/core/jmGradient.js +588 -62
package/dist/jmgraph.js
CHANGED
|
@@ -2714,31 +2714,63 @@ var jmGradient = /*#__PURE__*/function () {
|
|
|
2714
2714
|
}
|
|
2715
2715
|
|
|
2716
2716
|
if (!d) {
|
|
2717
|
-
d = Math.min(location.width, location.height);
|
|
2718
|
-
}
|
|
2719
|
-
|
|
2717
|
+
d = Math.min(location.width || 0, location.height || 0);
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
if (d <= 0) {
|
|
2721
|
+
d = Math.max(bounds.width || 0, bounds.height || 0, 100);
|
|
2722
|
+
}
|
|
2720
2723
|
|
|
2724
|
+
var width = bounds.width || d;
|
|
2725
|
+
var height = bounds.height || d;
|
|
2721
2726
|
|
|
2722
2727
|
if (_jmUtils.jmUtils.checkPercent(x1)) {
|
|
2723
|
-
x1 = _jmUtils.jmUtils.percentToNumber(x1) *
|
|
2728
|
+
x1 = _jmUtils.jmUtils.percentToNumber(x1) * width;
|
|
2729
|
+
} else if (typeof x1 === 'number' && x1 >= 0 && x1 <= 1) {
|
|
2730
|
+
x1 = x1 * width;
|
|
2731
|
+
} else if (typeof x1 === 'string') {
|
|
2732
|
+
var num = parseFloat(x1);
|
|
2733
|
+
if (!isNaN(num)) x1 = num;
|
|
2724
2734
|
}
|
|
2725
2735
|
|
|
2726
2736
|
if (_jmUtils.jmUtils.checkPercent(x2)) {
|
|
2727
|
-
x2 = _jmUtils.jmUtils.percentToNumber(x2) *
|
|
2737
|
+
x2 = _jmUtils.jmUtils.percentToNumber(x2) * width;
|
|
2738
|
+
} else if (typeof x2 === 'number' && x2 >= 0 && x2 <= 1) {
|
|
2739
|
+
x2 = x2 * width;
|
|
2740
|
+
} else if (typeof x2 === 'string') {
|
|
2741
|
+
var _num = parseFloat(x2);
|
|
2742
|
+
|
|
2743
|
+
if (!isNaN(_num)) x2 = _num;
|
|
2728
2744
|
}
|
|
2729
2745
|
|
|
2730
2746
|
if (_jmUtils.jmUtils.checkPercent(y1)) {
|
|
2731
|
-
y1 = _jmUtils.jmUtils.percentToNumber(y1) *
|
|
2747
|
+
y1 = _jmUtils.jmUtils.percentToNumber(y1) * height;
|
|
2748
|
+
} else if (typeof y1 === 'number' && y1 >= 0 && y1 <= 1) {
|
|
2749
|
+
y1 = y1 * height;
|
|
2750
|
+
} else if (typeof y1 === 'string') {
|
|
2751
|
+
var _num2 = parseFloat(y1);
|
|
2752
|
+
|
|
2753
|
+
if (!isNaN(_num2)) y1 = _num2;
|
|
2732
2754
|
}
|
|
2733
2755
|
|
|
2734
2756
|
if (_jmUtils.jmUtils.checkPercent(y2)) {
|
|
2735
|
-
y2 = _jmUtils.jmUtils.percentToNumber(y2) *
|
|
2757
|
+
y2 = _jmUtils.jmUtils.percentToNumber(y2) * height;
|
|
2758
|
+
} else if (typeof y2 === 'number' && y2 >= 0 && y2 <= 1) {
|
|
2759
|
+
y2 = y2 * height;
|
|
2760
|
+
} else if (typeof y2 === 'string') {
|
|
2761
|
+
var _num3 = parseFloat(y2);
|
|
2762
|
+
|
|
2763
|
+
if (!isNaN(_num3)) y2 = _num3;
|
|
2736
2764
|
}
|
|
2737
2765
|
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2766
|
+
x1 = Number(x1) || 0;
|
|
2767
|
+
y1 = Number(y1) || 0;
|
|
2768
|
+
x2 = Number(x2) || 0;
|
|
2769
|
+
y2 = Number(y2) || 0;
|
|
2770
|
+
var sx1 = x1 + (bounds.left || 0);
|
|
2771
|
+
var sy1 = y1 + (bounds.top || 0);
|
|
2772
|
+
var sx2 = x2 + (bounds.left || 0);
|
|
2773
|
+
var sy2 = y2 + (bounds.top || 0);
|
|
2742
2774
|
|
|
2743
2775
|
if (this.type === 'linear') {
|
|
2744
2776
|
if (control.mode === 'webgl' && control.webglControl) {
|
|
@@ -2752,25 +2784,41 @@ var jmGradient = /*#__PURE__*/function () {
|
|
|
2752
2784
|
var r1 = this.r1 || 0;
|
|
2753
2785
|
var r2 = this.r2;
|
|
2754
2786
|
|
|
2787
|
+
if (d <= 0) {
|
|
2788
|
+
d = Math.max(bounds.width || 0, bounds.height || 0, 1);
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2755
2791
|
if (_jmUtils.jmUtils.checkPercent(r1)) {
|
|
2756
2792
|
r1 = _jmUtils.jmUtils.percentToNumber(r1);
|
|
2757
2793
|
r1 = d * r1;
|
|
2794
|
+
} else if (typeof r1 === 'number' && r1 >= 0 && r1 <= 1) {
|
|
2795
|
+
r1 = r1 * d;
|
|
2796
|
+
} else if (typeof r1 === 'string') {
|
|
2797
|
+
r1 = parseFloat(r1) || 0;
|
|
2758
2798
|
}
|
|
2759
2799
|
|
|
2760
2800
|
if (_jmUtils.jmUtils.checkPercent(r2)) {
|
|
2761
2801
|
r2 = _jmUtils.jmUtils.percentToNumber(r2);
|
|
2762
2802
|
r2 = d * r2;
|
|
2803
|
+
} else if (typeof r2 === 'number' && r2 >= 0 && r2 <= 1) {
|
|
2804
|
+
r2 = r2 * d;
|
|
2805
|
+
} else if (typeof r2 === 'string') {
|
|
2806
|
+
r2 = parseFloat(r2);
|
|
2763
2807
|
}
|
|
2764
2808
|
|
|
2809
|
+
if (r2 === undefined || r2 === null || isNaN(Number(r2)) || Number(r2) <= 0) {
|
|
2810
|
+
r2 = d / 2;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
r1 = Number(r1) || 0;
|
|
2814
|
+
r2 = Number(r2) || d / 2;
|
|
2815
|
+
|
|
2765
2816
|
if (control.mode === 'webgl' && control.webglControl) {
|
|
2766
|
-
// WebGL 着色器中 v_text_coord 是绝对坐标,需要传递绝对坐标
|
|
2767
2817
|
gradient = control.webglControl.createRadialGradient(sx1, sy1, r1, sx2, sy2, r2, bounds);
|
|
2768
2818
|
gradient.key = this.toString();
|
|
2769
|
-
}
|
|
2770
|
-
else if (context.createRadialGradient) {
|
|
2819
|
+
} else if (context.createRadialGradient) {
|
|
2771
2820
|
gradient = context.createRadialGradient(sx1, sy1, r1, sx2, sy2, r2);
|
|
2772
|
-
}
|
|
2773
|
-
else if (context.createCircularGradient) {
|
|
2821
|
+
} else if (context.createCircularGradient) {
|
|
2774
2822
|
gradient = context.createCircularGradient(sx1, sy1, r2);
|
|
2775
2823
|
}
|
|
2776
2824
|
} //颜色渐变
|
|
@@ -2791,74 +2839,690 @@ var jmGradient = /*#__PURE__*/function () {
|
|
|
2791
2839
|
return gradient;
|
|
2792
2840
|
}
|
|
2793
2841
|
/**
|
|
2794
|
-
*
|
|
2795
|
-
*
|
|
2796
|
-
* linear-gradient
|
|
2797
|
-
*
|
|
2842
|
+
* 解析渐变字符串
|
|
2843
|
+
* 支持的格式:
|
|
2844
|
+
* - linear-gradient(180deg, #8b5cf6 0%, #6366f1 50%, #4f46e5 100%)
|
|
2845
|
+
* - linear-gradient(to top, red, blue)
|
|
2846
|
+
* - linear-gradient(to right bottom, red, blue)
|
|
2847
|
+
* - linear-gradient(45deg, red, blue)
|
|
2848
|
+
* - linear-gradient(0.5turn, red, blue)
|
|
2849
|
+
* - radial-gradient(circle, red, blue)
|
|
2850
|
+
* - radial-gradient(ellipse at top, red, blue)
|
|
2798
2851
|
*
|
|
2799
2852
|
* @method fromString
|
|
2800
2853
|
* @for jmGradient
|
|
2801
|
-
* @
|
|
2854
|
+
* @param {string} s 渐变字符串
|
|
2802
2855
|
*/
|
|
2803
2856
|
|
|
2804
2857
|
}, {
|
|
2805
2858
|
key: "fromString",
|
|
2806
2859
|
value: function fromString(s) {
|
|
2807
|
-
if (!s)
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2860
|
+
if (!s) {
|
|
2861
|
+
console.warn('jmGradient: 渐变字符串为空');
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2811
2864
|
|
|
2812
|
-
var
|
|
2865
|
+
var gradientMatch = s.match(/(linear|radial)-gradient\s*\(\s*(.+)\)/i);
|
|
2813
2866
|
|
|
2867
|
+
if (!gradientMatch || gradientMatch.length < 3) {
|
|
2868
|
+
console.warn('jmGradient: 无效的渐变字符串格式: "' + s + '"');
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2814
2871
|
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2872
|
+
var type = gradientMatch[1].toLowerCase();
|
|
2873
|
+
|
|
2874
|
+
if (type !== 'linear' && type !== 'radial') {
|
|
2875
|
+
console.warn('jmGradient: 不支持的渐变类型 "' + type + '",仅支持 linear 和 radial');
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
this.type = type;
|
|
2880
|
+
|
|
2881
|
+
var content = _jmUtils.jmUtils.trim(gradientMatch[2]);
|
|
2882
|
+
|
|
2883
|
+
var splitIndex = this._findSplitIndex(content);
|
|
2884
|
+
|
|
2885
|
+
if (splitIndex < 0) {
|
|
2886
|
+
console.warn('jmGradient: 无法解析渐变内容: "' + content + '"');
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
var params = content.substring(0, splitIndex).trim();
|
|
2891
|
+
var colorPart = content.substring(splitIndex + 1).trim();
|
|
2892
|
+
|
|
2893
|
+
if (!colorPart) {
|
|
2894
|
+
console.warn('jmGradient: 未找到颜色停止点');
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
if (this.type === 'linear') {
|
|
2899
|
+
this._parseLinearParams(params);
|
|
2900
|
+
} else {
|
|
2901
|
+
this._parseRadialParams(params);
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
var colorCount = this._parseColorStops(colorPart);
|
|
2905
|
+
|
|
2906
|
+
if (colorCount === 0) {
|
|
2907
|
+
console.warn('jmGradient: 未找到有效的颜色停止点: "' + colorPart + '"');
|
|
2908
|
+
} else if (colorCount < 2) {
|
|
2909
|
+
console.warn('jmGradient: 颜色停止点至少需要2个,当前只有 ' + colorCount + ' 个');
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
/**
|
|
2913
|
+
* 找到参数和颜色的分割位置(第一个不在括号内的逗号)
|
|
2914
|
+
* @param {string} content 内容字符串
|
|
2915
|
+
* @returns {number} 分割位置索引
|
|
2916
|
+
*/
|
|
2917
|
+
|
|
2918
|
+
}, {
|
|
2919
|
+
key: "_findSplitIndex",
|
|
2920
|
+
value: function _findSplitIndex(content) {
|
|
2921
|
+
var depth = 0;
|
|
2922
|
+
|
|
2923
|
+
for (var i = 0; i < content.length; i++) {
|
|
2924
|
+
var _char = content[i];
|
|
2925
|
+
|
|
2926
|
+
if (_char === '(') {
|
|
2927
|
+
depth++;
|
|
2928
|
+
} else if (_char === ')') {
|
|
2929
|
+
depth--;
|
|
2930
|
+
} else if (_char === ',' && depth === 0) {
|
|
2931
|
+
return i;
|
|
2838
2932
|
}
|
|
2839
|
-
}
|
|
2840
|
-
//color step
|
|
2933
|
+
}
|
|
2841
2934
|
|
|
2935
|
+
return -1;
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* 验证渐变配置是否有效
|
|
2939
|
+
* @returns {boolean} 是否有效
|
|
2940
|
+
*/
|
|
2842
2941
|
|
|
2843
|
-
|
|
2942
|
+
}, {
|
|
2943
|
+
key: "isValid",
|
|
2944
|
+
value: function isValid() {
|
|
2945
|
+
if (!this.type) return false;
|
|
2844
2946
|
|
|
2845
|
-
if (
|
|
2846
|
-
|
|
2847
|
-
|
|
2947
|
+
if (this.type === 'linear') {
|
|
2948
|
+
return typeof this.x1 !== 'undefined' || typeof this.x2 !== 'undefined' || typeof this._angle !== 'undefined';
|
|
2949
|
+
}
|
|
2848
2950
|
|
|
2849
|
-
|
|
2951
|
+
if (this.type === 'radial') {
|
|
2952
|
+
return this.stops && this.stops.length >= 2;
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
return false;
|
|
2956
|
+
}
|
|
2957
|
+
/**
|
|
2958
|
+
* 解析线性渐变参数
|
|
2959
|
+
* @param {string} params 参数字符串
|
|
2960
|
+
*/
|
|
2961
|
+
|
|
2962
|
+
}, {
|
|
2963
|
+
key: "_parseLinearParams",
|
|
2964
|
+
value: function _parseLinearParams(params) {
|
|
2965
|
+
var trimmed = _jmUtils.jmUtils.trim(params);
|
|
2850
2966
|
|
|
2851
|
-
|
|
2852
|
-
|
|
2967
|
+
if (trimmed.startsWith('to ')) {
|
|
2968
|
+
var direction = trimmed.substring(3).toLowerCase().trim();
|
|
2853
2969
|
|
|
2854
|
-
|
|
2970
|
+
var dir = this._directionToAngle(direction);
|
|
2971
|
+
|
|
2972
|
+
this._angle = dir.angle;
|
|
2973
|
+
this.x1 = dir.x1;
|
|
2974
|
+
this.y1 = dir.y1;
|
|
2975
|
+
this.x2 = dir.x2;
|
|
2976
|
+
this.y2 = dir.y2;
|
|
2977
|
+
} else if (this._hasAngleUnit(trimmed)) {
|
|
2978
|
+
var angle = this._parseAngle(trimmed);
|
|
2979
|
+
|
|
2980
|
+
this._angle = angle;
|
|
2981
|
+
|
|
2982
|
+
var coords = this._angleToCoords(angle);
|
|
2983
|
+
|
|
2984
|
+
this.x1 = coords.x1;
|
|
2985
|
+
this.y1 = coords.y1;
|
|
2986
|
+
this.x2 = coords.x2;
|
|
2987
|
+
this.y2 = coords.y2;
|
|
2988
|
+
} else if (trimmed.startsWith('at ')) {
|
|
2989
|
+
var radialMatch = trimmed.match(/at\s+(.+)/i);
|
|
2990
|
+
|
|
2991
|
+
if (radialMatch) {
|
|
2992
|
+
this._parseRadialParams(trimmed);
|
|
2993
|
+
}
|
|
2994
|
+
} else {
|
|
2995
|
+
var parts = trimmed.split(/\s+/);
|
|
2996
|
+
|
|
2997
|
+
if (parts.length >= 4) {
|
|
2998
|
+
this.x1 = parts[0];
|
|
2999
|
+
this.y1 = parts[1];
|
|
3000
|
+
this.x2 = parts[2];
|
|
3001
|
+
this.y2 = parts[3];
|
|
3002
|
+
} else if (parts.length === 2) {
|
|
3003
|
+
this.x1 = 0;
|
|
3004
|
+
this.y1 = 0;
|
|
3005
|
+
this.x2 = parts[0];
|
|
3006
|
+
this.y2 = parts[1];
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
/**
|
|
3011
|
+
* 解析径向渐变参数
|
|
3012
|
+
* @param {string} params 参数字符串
|
|
3013
|
+
*/
|
|
3014
|
+
|
|
3015
|
+
}, {
|
|
3016
|
+
key: "_parseRadialParams",
|
|
3017
|
+
value: function _parseRadialParams(params) {
|
|
3018
|
+
var trimmed = _jmUtils.jmUtils.trim(params);
|
|
3019
|
+
|
|
3020
|
+
this.shape = 'ellipse';
|
|
3021
|
+
this.position = {
|
|
3022
|
+
x: '50%',
|
|
3023
|
+
y: '50%'
|
|
3024
|
+
};
|
|
3025
|
+
var atMatch = trimmed.match(/^(.+?)\s+at\s+(.+)$/i);
|
|
3026
|
+
|
|
3027
|
+
if (atMatch) {
|
|
3028
|
+
var shapePart = _jmUtils.jmUtils.trim(atMatch[1]);
|
|
3029
|
+
|
|
3030
|
+
var posPart = _jmUtils.jmUtils.trim(atMatch[2]);
|
|
3031
|
+
|
|
3032
|
+
this._parseRadialShape(shapePart);
|
|
3033
|
+
|
|
3034
|
+
this._parseRadialPosition(posPart);
|
|
3035
|
+
} else if (trimmed.startsWith('circle') || trimmed.startsWith('ellipse')) {
|
|
3036
|
+
this._parseRadialShape(trimmed);
|
|
3037
|
+
|
|
3038
|
+
this.x1 = '50%';
|
|
3039
|
+
this.y1 = '50%';
|
|
3040
|
+
this.x2 = '50%';
|
|
3041
|
+
this.y2 = '50%';
|
|
3042
|
+
} else {
|
|
3043
|
+
var parts = trimmed.split(/\s+/);
|
|
3044
|
+
|
|
3045
|
+
if (parts.length >= 3) {
|
|
3046
|
+
this.x1 = parts[0];
|
|
3047
|
+
this.y1 = parts[1];
|
|
3048
|
+
this.r1 = parts[2];
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
if (parts.length >= 6) {
|
|
3052
|
+
this.x2 = parts[3];
|
|
3053
|
+
this.y2 = parts[4];
|
|
3054
|
+
this.r2 = parts[5];
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
if (this.x1 === undefined && this.y1 === undefined) {
|
|
3059
|
+
this.x1 = '50%';
|
|
3060
|
+
this.y1 = '50%';
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
if (this.x2 === undefined && this.y2 === undefined) {
|
|
3064
|
+
this.x2 = '50%';
|
|
3065
|
+
this.y2 = '50%';
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
if (this.r2 === undefined) {
|
|
3069
|
+
this.r2 = '50%';
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
/**
|
|
3073
|
+
* 解析径向渐变形状
|
|
3074
|
+
* @param {string} shapePart 形状描述
|
|
3075
|
+
*/
|
|
3076
|
+
|
|
3077
|
+
}, {
|
|
3078
|
+
key: "_parseRadialShape",
|
|
3079
|
+
value: function _parseRadialShape(shapePart) {
|
|
3080
|
+
if (shapePart.startsWith('circle')) {
|
|
3081
|
+
this.shape = 'circle';
|
|
3082
|
+
var sizeMatch = shapePart.match(/circle\s*\(\s*([^)]+)\s*\)/i);
|
|
3083
|
+
|
|
3084
|
+
if (sizeMatch) {
|
|
3085
|
+
this.r2 = _jmUtils.jmUtils.trim(sizeMatch[1]);
|
|
3086
|
+
}
|
|
3087
|
+
} else if (shapePart.startsWith('ellipse')) {
|
|
3088
|
+
this.shape = 'ellipse';
|
|
3089
|
+
|
|
3090
|
+
var _sizeMatch = shapePart.match(/ellipse\s*\(\s*([^)]+)\s*\)/i);
|
|
3091
|
+
|
|
3092
|
+
if (_sizeMatch) {
|
|
3093
|
+
var sizes = _jmUtils.jmUtils.trim(_sizeMatch[1]).split(/\s+/);
|
|
3094
|
+
|
|
3095
|
+
if (sizes.length >= 2) {
|
|
3096
|
+
this.rx = sizes[0];
|
|
3097
|
+
this.ry = sizes[1];
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
/**
|
|
3103
|
+
* 解析径向渐变位置
|
|
3104
|
+
* @param {string} posPart 位置描述
|
|
3105
|
+
*/
|
|
3106
|
+
|
|
3107
|
+
}, {
|
|
3108
|
+
key: "_parseRadialPosition",
|
|
3109
|
+
value: function _parseRadialPosition(posPart) {
|
|
3110
|
+
var parts = posPart.split(/\s+/);
|
|
3111
|
+
|
|
3112
|
+
if (parts.length >= 2) {
|
|
3113
|
+
this.x1 = parts[0];
|
|
3114
|
+
this.y1 = parts[1];
|
|
3115
|
+
this.x2 = parts[0];
|
|
3116
|
+
this.y2 = parts[1];
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* 解析颜色停止点
|
|
3121
|
+
* @param {string} colorPart 颜色部分字符串
|
|
3122
|
+
* @returns {number} 成功解析的颜色数量
|
|
3123
|
+
*/
|
|
3124
|
+
|
|
3125
|
+
}, {
|
|
3126
|
+
key: "_parseColorStops",
|
|
3127
|
+
value: function _parseColorStops(colorPart) {
|
|
3128
|
+
if (!colorPart) {
|
|
3129
|
+
return 0;
|
|
3130
|
+
}
|
|
2855
3131
|
|
|
2856
|
-
|
|
2857
|
-
|
|
3132
|
+
var stops = this._splitColorStops(colorPart);
|
|
3133
|
+
|
|
3134
|
+
var lastOffset = -1;
|
|
3135
|
+
var colorCount = 0;
|
|
3136
|
+
|
|
3137
|
+
for (var i = 0; i < stops.length; i++) {
|
|
3138
|
+
var stop = _jmUtils.jmUtils.trim(stops[i]);
|
|
3139
|
+
|
|
3140
|
+
if (!stop) continue;
|
|
3141
|
+
|
|
3142
|
+
var parsed = this._parseSingleColorStop(stop);
|
|
3143
|
+
|
|
3144
|
+
if (!parsed) {
|
|
3145
|
+
continue;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
var color = parsed.color,
|
|
3149
|
+
offset = parsed.offset;
|
|
3150
|
+
|
|
3151
|
+
if (color === 'transparent') {
|
|
3152
|
+
color = 'rgba(0,0,0,0)';
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
if (!this._isValidColor(color)) {
|
|
3156
|
+
console.warn('jmGradient: 无效的颜色格式 "' + color + '"');
|
|
3157
|
+
continue;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
var normalizedOffset = this._normalizeOffset(offset);
|
|
3161
|
+
|
|
3162
|
+
var finalOffset = normalizedOffset;
|
|
3163
|
+
|
|
3164
|
+
if (finalOffset === null) {
|
|
3165
|
+
if (i === 0) {
|
|
3166
|
+
finalOffset = 0;
|
|
3167
|
+
} else if (i === stops.length - 1) {
|
|
3168
|
+
finalOffset = 1;
|
|
3169
|
+
} else {
|
|
3170
|
+
var nextOffset = this._findNextOffset(stops, i);
|
|
3171
|
+
|
|
3172
|
+
if (nextOffset !== null) {
|
|
3173
|
+
finalOffset = (lastOffset + nextOffset) / 2;
|
|
3174
|
+
} else {
|
|
3175
|
+
finalOffset = Math.min(1, lastOffset + (1 - lastOffset) / (stops.length - i));
|
|
2858
3176
|
}
|
|
2859
3177
|
}
|
|
2860
3178
|
}
|
|
3179
|
+
|
|
3180
|
+
if (finalOffset !== null) {
|
|
3181
|
+
if (finalOffset < 0 || finalOffset > 1) {
|
|
3182
|
+
console.warn('jmGradient: 颜色偏移量 ' + finalOffset + ' 超出有效范围 [0, 1],将调整为有效范围');
|
|
3183
|
+
finalOffset = Math.max(0, Math.min(1, finalOffset));
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
lastOffset = finalOffset;
|
|
3187
|
+
this.addStop(finalOffset, color);
|
|
3188
|
+
colorCount++;
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
return colorCount;
|
|
3193
|
+
}
|
|
3194
|
+
/**
|
|
3195
|
+
* 分割颜色停止点字符串
|
|
3196
|
+
* @param {string} colorPart 颜色部分字符串
|
|
3197
|
+
* @returns {string[]} 颜色停止点数组
|
|
3198
|
+
*/
|
|
3199
|
+
|
|
3200
|
+
}, {
|
|
3201
|
+
key: "_splitColorStops",
|
|
3202
|
+
value: function _splitColorStops(colorPart) {
|
|
3203
|
+
var stops = [];
|
|
3204
|
+
var depth = 0;
|
|
3205
|
+
var current = '';
|
|
3206
|
+
|
|
3207
|
+
for (var i = 0; i < colorPart.length; i++) {
|
|
3208
|
+
var _char2 = colorPart[i];
|
|
3209
|
+
|
|
3210
|
+
if (_char2 === '(') {
|
|
3211
|
+
depth++;
|
|
3212
|
+
current += _char2;
|
|
3213
|
+
} else if (_char2 === ')') {
|
|
3214
|
+
depth--;
|
|
3215
|
+
current += _char2;
|
|
3216
|
+
} else if (_char2 === ',' && depth === 0) {
|
|
3217
|
+
stops.push(current.trim());
|
|
3218
|
+
current = '';
|
|
3219
|
+
} else {
|
|
3220
|
+
current += _char2;
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
if (current.trim()) {
|
|
3225
|
+
stops.push(current.trim());
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
return stops;
|
|
3229
|
+
}
|
|
3230
|
+
/**
|
|
3231
|
+
* 解析单个颜色停止点
|
|
3232
|
+
* @param {string} stop 单个颜色停止点字符串
|
|
3233
|
+
* @returns {object|null} {color, offset} 或 null
|
|
3234
|
+
*/
|
|
3235
|
+
|
|
3236
|
+
}, {
|
|
3237
|
+
key: "_parseSingleColorStop",
|
|
3238
|
+
value: function _parseSingleColorStop(stop) {
|
|
3239
|
+
var hexMatch = stop.match(/^(#[a-fA-F0-9]{3,8})\s*(\d+(?:\.\d+)?%?)?$/i);
|
|
3240
|
+
|
|
3241
|
+
if (hexMatch) {
|
|
3242
|
+
return {
|
|
3243
|
+
color: hexMatch[1],
|
|
3244
|
+
offset: hexMatch[2] || null
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
var rgbaMatch = stop.match(/^(rgba?\s*\([^)]+\))\s*(\d+(?:\.\d+)?%?)?$/i);
|
|
3249
|
+
|
|
3250
|
+
if (rgbaMatch) {
|
|
3251
|
+
return {
|
|
3252
|
+
color: rgbaMatch[1],
|
|
3253
|
+
offset: rgbaMatch[2] || null
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
var hslaMatch = stop.match(/^(hsla?\s*\([^)]+\))\s*(\d+(?:\.\d+)?%?)?$/i);
|
|
3258
|
+
|
|
3259
|
+
if (hslaMatch) {
|
|
3260
|
+
return {
|
|
3261
|
+
color: hslaMatch[1],
|
|
3262
|
+
offset: hslaMatch[2] || null
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
var namedMatch = stop.match(/^([a-zA-Z]+)\s*(\d+(?:\.\d+)?%?)?$/i);
|
|
3267
|
+
|
|
3268
|
+
if (namedMatch && this._isValidColor(namedMatch[1])) {
|
|
3269
|
+
return {
|
|
3270
|
+
color: namedMatch[1],
|
|
3271
|
+
offset: namedMatch[2] || null
|
|
3272
|
+
};
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
return null;
|
|
3276
|
+
}
|
|
3277
|
+
/**
|
|
3278
|
+
* 查找下一个有偏移量的颜色停止点
|
|
3279
|
+
* @param {string[]} stops 颜色停止点数组
|
|
3280
|
+
* @param {number} currentIndex 当前索引
|
|
3281
|
+
* @returns {number|null} 下一个偏移量或null
|
|
3282
|
+
*/
|
|
3283
|
+
|
|
3284
|
+
}, {
|
|
3285
|
+
key: "_findNextOffset",
|
|
3286
|
+
value: function _findNextOffset(stops, currentIndex) {
|
|
3287
|
+
for (var i = currentIndex + 1; i < stops.length; i++) {
|
|
3288
|
+
var parsed = this._parseSingleColorStop(_jmUtils.jmUtils.trim(stops[i]));
|
|
3289
|
+
|
|
3290
|
+
if (parsed && parsed.offset) {
|
|
3291
|
+
return this._normalizeOffset(parsed.offset);
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
return null;
|
|
3296
|
+
}
|
|
3297
|
+
/**
|
|
3298
|
+
* 验证颜色格式是否有效
|
|
3299
|
+
* @param {string} color 颜色字符串
|
|
3300
|
+
* @returns {boolean} 是否有效
|
|
3301
|
+
*/
|
|
3302
|
+
|
|
3303
|
+
}, {
|
|
3304
|
+
key: "_isValidColor",
|
|
3305
|
+
value: function _isValidColor(color) {
|
|
3306
|
+
if (!color) return false;
|
|
3307
|
+
var hexPattern = /^#([a-fA-F0-9]{3,8})$/;
|
|
3308
|
+
if (hexPattern.test(color)) return true;
|
|
3309
|
+
var rgbPattern = /^rgba?\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*[\d.]+\s*)?\)$/i;
|
|
3310
|
+
if (rgbPattern.test(color)) return true;
|
|
3311
|
+
var hslPattern = /^hsla?\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})%?\s*,\s*(\d{1,3})%?\s*(,\s*[\d.]+\s*)?\)$/i;
|
|
3312
|
+
if (hslPattern.test(color)) return true;
|
|
3313
|
+
var namedColors = ['red', 'blue', 'green', 'yellow', 'orange', 'purple', 'pink', 'white', 'black', 'cyan', 'magenta', 'gray', 'grey', 'brown', 'navy', 'teal', 'olive', 'maroon', 'silver', 'lime', 'aqua', 'fuchsia', 'violet', 'indigo', 'gold', 'silver', 'transparent'];
|
|
3314
|
+
if (namedColors.includes(color.toLowerCase())) return true;
|
|
3315
|
+
return false;
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* 标准化偏移值
|
|
3319
|
+
* @param {string} offset 偏移字符串
|
|
3320
|
+
* @returns {number|null} 0-1之间的数值或null
|
|
3321
|
+
*/
|
|
3322
|
+
|
|
3323
|
+
}, {
|
|
3324
|
+
key: "_normalizeOffset",
|
|
3325
|
+
value: function _normalizeOffset(offset) {
|
|
3326
|
+
if (!offset) return null;
|
|
3327
|
+
offset = _jmUtils.jmUtils.trim(offset);
|
|
3328
|
+
|
|
3329
|
+
if (offset.endsWith('%')) {
|
|
3330
|
+
return parseFloat(offset) / 100;
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
var num = parseFloat(offset);
|
|
3334
|
+
if (isNaN(num)) return null;
|
|
3335
|
+
|
|
3336
|
+
if (num > 1) {
|
|
3337
|
+
return num / 100;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
return num;
|
|
3341
|
+
}
|
|
3342
|
+
/**
|
|
3343
|
+
* 检查字符串是否包含角度单位
|
|
3344
|
+
* @param {string} str 待检查字符串
|
|
3345
|
+
* @returns {boolean}
|
|
3346
|
+
*/
|
|
3347
|
+
|
|
3348
|
+
}, {
|
|
3349
|
+
key: "_hasAngleUnit",
|
|
3350
|
+
value: function _hasAngleUnit(str) {
|
|
3351
|
+
return /^-?\d+(\.\d+)?\s*(deg|rad|grad|turn)$/i.test(str);
|
|
3352
|
+
}
|
|
3353
|
+
/**
|
|
3354
|
+
* 解析角度值
|
|
3355
|
+
* @param {string} angleStr 角度字符串
|
|
3356
|
+
* @returns {number} 弧度值
|
|
3357
|
+
*/
|
|
3358
|
+
|
|
3359
|
+
}, {
|
|
3360
|
+
key: "_parseAngle",
|
|
3361
|
+
value: function _parseAngle(angleStr) {
|
|
3362
|
+
angleStr = _jmUtils.jmUtils.trim(angleStr);
|
|
3363
|
+
var match = angleStr.match(/^(-?\d+(\.\d+)?)\s*(deg|rad|grad|turn)?$/i);
|
|
3364
|
+
if (!match) return 0;
|
|
3365
|
+
var value = parseFloat(match[1]);
|
|
3366
|
+
var unit = (match[3] || 'deg').toLowerCase();
|
|
3367
|
+
|
|
3368
|
+
switch (unit) {
|
|
3369
|
+
case 'deg':
|
|
3370
|
+
return value * Math.PI / 180;
|
|
3371
|
+
|
|
3372
|
+
case 'rad':
|
|
3373
|
+
return value;
|
|
3374
|
+
|
|
3375
|
+
case 'grad':
|
|
3376
|
+
return value * Math.PI / 200;
|
|
3377
|
+
|
|
3378
|
+
case 'turn':
|
|
3379
|
+
return value * 2 * Math.PI;
|
|
3380
|
+
|
|
3381
|
+
default:
|
|
3382
|
+
return value * Math.PI / 180;
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
/**
|
|
3386
|
+
* 将角度转换为起点和终点坐标
|
|
3387
|
+
* @param {number} angle 弧度值
|
|
3388
|
+
* @returns {object} 坐标对象
|
|
3389
|
+
*/
|
|
3390
|
+
|
|
3391
|
+
}, {
|
|
3392
|
+
key: "_angleToCoords",
|
|
3393
|
+
value: function _angleToCoords(angle) {
|
|
3394
|
+
var x = Math.cos(angle);
|
|
3395
|
+
var y = -Math.sin(angle);
|
|
3396
|
+
return {
|
|
3397
|
+
x1: Math.round((0.5 - x * 0.5) * 1000) / 1000,
|
|
3398
|
+
y1: Math.round((0.5 + y * 0.5) * 1000) / 1000,
|
|
3399
|
+
x2: Math.round((0.5 + x * 0.5) * 1000) / 1000,
|
|
3400
|
+
y2: Math.round((0.5 - y * 0.5) * 1000) / 1000
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* 将方向关键词转换为角度和坐标
|
|
3405
|
+
* @param {string} direction 方向描述
|
|
3406
|
+
* @returns {object} 包含angle和坐标的对象
|
|
3407
|
+
*/
|
|
3408
|
+
|
|
3409
|
+
}, {
|
|
3410
|
+
key: "_directionToAngle",
|
|
3411
|
+
value: function _directionToAngle(direction) {
|
|
3412
|
+
var directions = {
|
|
3413
|
+
'to top': {
|
|
3414
|
+
angle: 0,
|
|
3415
|
+
x1: '50%',
|
|
3416
|
+
y1: '100%',
|
|
3417
|
+
x2: '50%',
|
|
3418
|
+
y2: '0%'
|
|
3419
|
+
},
|
|
3420
|
+
'to bottom': {
|
|
3421
|
+
angle: Math.PI,
|
|
3422
|
+
x1: '50%',
|
|
3423
|
+
y1: '0%',
|
|
3424
|
+
x2: '50%',
|
|
3425
|
+
y2: '100%'
|
|
3426
|
+
},
|
|
3427
|
+
'to left': {
|
|
3428
|
+
angle: -Math.PI / 2,
|
|
3429
|
+
x1: '100%',
|
|
3430
|
+
y1: '50%',
|
|
3431
|
+
x2: '0%',
|
|
3432
|
+
y2: '50%'
|
|
3433
|
+
},
|
|
3434
|
+
'to right': {
|
|
3435
|
+
angle: Math.PI / 2,
|
|
3436
|
+
x1: '0%',
|
|
3437
|
+
y1: '50%',
|
|
3438
|
+
x2: '100%',
|
|
3439
|
+
y2: '50%'
|
|
3440
|
+
},
|
|
3441
|
+
'to top left': {
|
|
3442
|
+
angle: -Math.PI * 3 / 4,
|
|
3443
|
+
x1: '100%',
|
|
3444
|
+
y1: '100%',
|
|
3445
|
+
x2: '0%',
|
|
3446
|
+
y2: '0%'
|
|
3447
|
+
},
|
|
3448
|
+
'to top right': {
|
|
3449
|
+
angle: -Math.PI / 4,
|
|
3450
|
+
x1: '0%',
|
|
3451
|
+
y1: '100%',
|
|
3452
|
+
x2: '100%',
|
|
3453
|
+
y2: '0%'
|
|
3454
|
+
},
|
|
3455
|
+
'to bottom left': {
|
|
3456
|
+
angle: Math.PI * 3 / 4,
|
|
3457
|
+
x1: '100%',
|
|
3458
|
+
y1: '0%',
|
|
3459
|
+
x2: '0%',
|
|
3460
|
+
y2: '100%'
|
|
3461
|
+
},
|
|
3462
|
+
'to bottom right': {
|
|
3463
|
+
angle: Math.PI / 4,
|
|
3464
|
+
x1: '0%',
|
|
3465
|
+
y1: '0%',
|
|
3466
|
+
x2: '100%',
|
|
3467
|
+
y2: '100%'
|
|
3468
|
+
},
|
|
3469
|
+
'top': {
|
|
3470
|
+
angle: 0,
|
|
3471
|
+
x1: '50%',
|
|
3472
|
+
y1: '100%',
|
|
3473
|
+
x2: '50%',
|
|
3474
|
+
y2: '0%'
|
|
3475
|
+
},
|
|
3476
|
+
'bottom': {
|
|
3477
|
+
angle: Math.PI,
|
|
3478
|
+
x1: '50%',
|
|
3479
|
+
y1: '0%',
|
|
3480
|
+
x2: '50%',
|
|
3481
|
+
y2: '100%'
|
|
3482
|
+
},
|
|
3483
|
+
'left': {
|
|
3484
|
+
angle: -Math.PI / 2,
|
|
3485
|
+
x1: '100%',
|
|
3486
|
+
y1: '50%',
|
|
3487
|
+
x2: '0%',
|
|
3488
|
+
y2: '50%'
|
|
3489
|
+
},
|
|
3490
|
+
'right': {
|
|
3491
|
+
angle: Math.PI / 2,
|
|
3492
|
+
x1: '0%',
|
|
3493
|
+
y1: '50%',
|
|
3494
|
+
x2: '100%',
|
|
3495
|
+
y2: '50%'
|
|
3496
|
+
}
|
|
3497
|
+
};
|
|
3498
|
+
var dir = directions[direction];
|
|
3499
|
+
|
|
3500
|
+
if (dir) {
|
|
3501
|
+
return dir;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
var keywordMatch = direction.match(/to\s+(top|bottom|left|right)/i);
|
|
3505
|
+
|
|
3506
|
+
if (keywordMatch) {
|
|
3507
|
+
var mainDir = keywordMatch[1].toLowerCase();
|
|
3508
|
+
var secDir = direction.replace(keywordMatch[0], '').trim();
|
|
3509
|
+
|
|
3510
|
+
if (secDir) {
|
|
3511
|
+
var combined = "to ".concat(mainDir, " ").concat(secDir);
|
|
3512
|
+
|
|
3513
|
+
if (directions[combined]) {
|
|
3514
|
+
return directions[combined];
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
2861
3517
|
}
|
|
3518
|
+
|
|
3519
|
+
return {
|
|
3520
|
+
angle: 0,
|
|
3521
|
+
x1: '50%',
|
|
3522
|
+
y1: '100%',
|
|
3523
|
+
x2: '50%',
|
|
3524
|
+
y2: '0%'
|
|
3525
|
+
};
|
|
2862
3526
|
}
|
|
2863
3527
|
/**
|
|
2864
3528
|
* 转换为渐变的字符串表达
|