仪表盘模拟角度渐变

描述:当前是关于Echarts图表中的 仪表盘 示例。
 
            /*
* 角度(圆锥)渐变
* 原理: 使用axisLine的color来模拟实现, 平滑的渐变过渡借助axisTick实现.
* 其他方案: axisTick的color设为image, 用角度渐变图片填充. 
          1.当使用ps生成的png图片时,此方案的问题是需要保持echarts大小不变,
          尺寸一旦变化就需要修改图片的分辨率, 和裁剪图片,适应场景有限制
*         2. 为了解除ps的限制,可以使用canvas的create-conical-gradient polyfill
          根据echarts尺寸以及仪表盘radius动态生成角度渐变的图片填充在axisTick中
* 
* 不均匀刻度
* 原理:通过计算每个splitNumber的单位刻度值实现,公式在81行parseValue方法
*/

// 角度渐变的关键色值,在ps中获取
// 一定要注意ps中角度渐变的起始角度要保持和图形的起始角度(startAngle)一致
const colors = [
    [0, 235, 15, 15],
    [0.1, 235, 15, 15],
    [0.16, 255, 246, 0],
    [0.4, 15, 235, 103],
    [0.54, 15, 235, 103],
    [0.67, 255, 246, 0],
    [0.78, 235, 15, 15],
    [0.9, 0, 99, 177],
    [1, 235, 15, 15],
];

// 将如: 0.04-0.16之间的渐变值补全
const gradientColors = [];
colors.forEach((color, i) => {
    if (i === colors.length - 1) return;
    let maxDiffValue = 0;
    const diffs = color.map((s, j) => {
        const diff = s - colors[i + 1][j];
        const positiveDiff = Math.abs(diff);
        if (Math.abs(maxDiffValue) < positiveDiff) {
            maxDiffValue = positiveDiff;
        }
        return diff;
    });
    gradientColors.push([color[0], `rgb(${color.slice(1).join(',')})`]);
    let start = color[0];
    const end = colors[i + 1][0];
    const step = (end - start) / maxDiffValue;
    for (let k = 0; k < maxDiffValue - 1; k++) {
        let rgb = 'rgb($0,$1,$2)';
        for (let o = 1; o < color.length; o++) {
            let c = color[o];
            c = c - diffs[o] / maxDiffValue;
            color[o] = c;
            rgb = rgb.replace(`$${o - 1}`, c);
        }
        start = start + step;
        gradientColors.push([start, rgb]);
    }
});
const lastColor = colors[colors.length - 1];
gradientColors.push([lastColor[0], `rgb(${lastColor.slice(1).join(',')})`]);

// 测试数据
const testData = {
    startAngle: 225,
    endAngle: -45,
    splitNumber: 6,
    max: 65,
    data: [30],
};

// 仪表盘axisLabel展示的值,个数为splitNumber + 1
const axisLabelData = [0, 40, 45, 50, 55, 60, 65];
// 仪表盘axisLabel根据splitNumber生成的值
const axisLabelOriginData = (() => {
    const data = [];
    for (let index = 0; index < testData.splitNumber + 1; index++) {
        //  echarts自动计算axisLabel时, 精确的是10位小数
        data.push(+((testData.max / testData.splitNumber) * index).toFixed(10));
    }
    return data;
})();

// 将正常值转为指针偏转的值
function parseValue(x, [kp, kn], [zp, zn]) {
    if (x === 0) return 0;
    const y = ((zn - zp) / (kn - kp)) * (x - kp) + zp;
    return y;
}
let value = testData.data[0];
let valueIndex = -1;
axisLabelData.some((item, index) => {
    valueIndex = index;
    return value <= item;
});
const k = [axisLabelData[valueIndex - 1], axisLabelData[valueIndex]];
const z = [axisLabelOriginData[valueIndex - 1], axisLabelOriginData[valueIndex]];
value = parseValue(value, k, z);

// 外层渐变圆环
const baseOut = {
    type: 'gauge',
    axisLabel: {
        show: false,
    },
    axisLine: {
        show: false,
        lineStyle: {
            color: gradientColors, // 因为使用axisLine填充的颜色之间会存在透明间隔, 所以使用axisTick来实现渐变平滑过渡.
        },
    },
    axisTick: {
        length: 4,
        lineStyle: {
            color: 'auto',
            width: 2, // 调整width和splitNumber来使渐变平滑过渡
        },
        splitNumber: 150, // 调整width和splitNumber来使渐变平滑过渡
    },
    splitLine: {
        show: false,
        lineStyle: {
            color: 'auto',
        },
    },
    detail: {
        show: false,
    },
};
// 内层刻度和指针
const baseInner = {
    radius: '70%',
    type: 'gauge',
    axisLine: {
        show: false,
        lineStyle: {
            color: gradientColors,
        },
    },
    splitLine: {
        length: 12,
        lineStyle: {
            color: 'auto',
        },
    },
    axisTick: {
        lineStyle: {
            width: 2,
            color: 'auto',
        },
        splitNumber: 10,
    },
    pointer: {
        width: 5,
        length: '70%',
    },
    axisLabel: {
        formatter(value) {
            const index = axisLabelOriginData.findIndex((item) => item === value);
            return axisLabelData[index];
        },
    },
};
option = {
    series: [
        baseOut,
        {
            ...baseInner,
            ...testData,
            data: [value],
            detail: {
                // 只支持字符串和函数
                formatter: String(testData.data[0]),
            },
        },
    ],
};