两个饼图划切线和数据分布线的部分逻辑charts canvas

73 阅读2分钟
    const [startAngle, setStartAngle] = useState<number>(90)
    const [endAngle, setEndAngle] = useState<number>(90)
    const [delayOverdue, setDelayOverdue] = useState<any>([]) // 大饼图
    const [dueOverdue, setDueOverdue] = useState<any>([]) // 小饼图
    const circle1Radius = 100;
    const circle2Radius = 80;
    const [isCanvas, setIsCanvas] = useState<any>(true)
    let size: any = useSize(document.querySelector('#divpie'));
   useUpdateEffect(() => {
        if (isCanvas) {
            pieFn(startAngle)
        }
    }, [size]);


// 模拟接口

   //逾期大盘-图接口
    const { loading: loadMARKET, run: runMARKET } = useRequest(GET_SERVICE_STAGING_OVERDUE_MARKET, {
        manual: true,
        onSuccess: (list: { error_code: number; data: any; msg: any }) => {
            if (list.error_code != 0) {
                return
            }
            const aa = list.data.delayOverdue
            aa[0].itemStyle = {
                normal: {
                    borderColor: '#fff',
                    borderWidth: '2',
                    shadowColor: '#b3e4f1'
                }
            }
            aa[1].itemStyle = {
                normal: {
                    borderColor: '#fff',
                    borderWidth: '2',
                    shadowColor: '#b1a9fd'
                }
            }

            const bb = list.data.dueOverdue
            // 设置圆的样式
            bb[0].itemStyle = {
                normal: {
                    borderColor: '#fff',
                    borderWidth: '2',
                    shadowColor: '#f2dac4'
                }
            }
            bb[1].itemStyle = {
                normal: {
                    borderColor: '#fff',
                    borderWidth: '2',
                    shadowColor: '#ebd9ad'
                }
            }
            setDueOverdue(list.data.dueOverdue)
            setDelayOverdue(list.data.delayOverdue)
            const pieChart = calculatePieChartAngles(list.data.dueOverdue);
            const pieChartsmall = calculatePieChartAngles(list.data.delayOverdue);

            let chpre = parseInt((parseInt(pieChart[0].angle) / 2).toString())
            let chpre2 = parseInt((parseInt(pieChartsmall[0].angle) / 2).toString())

            setStartAngle(chpre)
            setEndAngle(chpre2)
            if (list.data.dueOverdue && list.data.dueOverdue[0].value == 0) {
                setIsCanvas(false)
            } else {
                setIsCanvas(true)
                // console.log('比较', circle1, circle2)
                pieFn(chpre)
            }
        }
    })
    
    // 获取偏转角
    const calculatePieChartAngles = (data) => {
        const total = data.reduce((acc, item) => acc + item.value, 0);
        return data.map((item, index) => {
            const itemPercentage = item.value / total;
            const itemAngle = itemPercentage * 360;
            return { ...item, angle: itemAngle, key: index };
        });
    }
    
   // 获取两圆圆心坐标
    const getTwoCenter = (ele: { width: number, height: number }) => {
        let CenterX = parseInt((ele['width'] / 4).toString())
        let CenterY = parseInt((ele['height'] / 2).toString())
        let secondX = parseInt((CenterX * 3).toString())
        return {
            first: { x: CenterX, y: CenterY },
            second: { x: secondX, y: CenterY }
        }
    }
    // 算出小圆切点坐标
    const calculateTangentPoints = (px:number, py:number, cx:number, cy:number, r:number) => {
        // 求点到圆心的距离
        let distance = Math.sqrt((px - cx) * (px - cx) + (py - cy) * (py - cy));

        // 点p 到切点的距离
        let length = Math.sqrt(distance * distance - r * r);

        if (distance <= r) {
            console.log("输入的数值不在范围内");
            return;
        }

        // 点到圆心的单位向量
        let ux = (cx - px) / distance;
        let uy = (cy - py) / distance;

        // 计算切线与圆心连线的夹角
        let angle = Math.asin(r / distance);

        // 向正反两个方向旋转单位向量
        let q1x = ux * Math.cos(angle) - uy * Math.sin(angle);
        let q1y = ux * Math.sin(angle) + uy * Math.cos(angle);
        let q2x = ux * Math.cos(-angle) - uy * Math.sin(-angle);
        let q2y = ux * Math.sin(-angle) + uy * Math.cos(-angle);

        // 得到新座标y
        q1x = q1x * length + px;
        q1y = q1y * length + py;
        q2x = q2x * length + px;
        q2y = q2y * length + py;
        return [{ x: q1x, y: q1y }, { x: q2x, y: q2y }]
    }
    
    // 画线函数
    const pieFn = (chpre: any) => {
        if (!size['width']) return
        let result_center = getTwoCenter(size)

        let arr1half: any = chpre > 90 ? 90 : chpre
        let arr2half = 360 - arr1half
        const arr1Rad = (arr1half * Math.PI) / 180; // arr1所占的角度转换为弧度表示
        const arr2Rad = (arr2half * Math.PI) / 180;// arr2所占的角度转换为弧度表示

        const bottompiex = result_center['first']['x'] + Math.cos(arr1Rad) * circle1Radius;  //大圆 下点位的x点的坐标
        const bottompiey = result_center['first']['y'] + Math.sin(arr1Rad) * circle1Radius;  //大圆 下点位的y点的坐标
        const toppiex = result_center['first']['x'] + Math.cos(arr2Rad) * circle1Radius;   //大圆 上点位的x点的坐标
        const toppiey = result_center['first']['y'] + Math.sin(arr2Rad) * circle1Radius;   //大圆 上点位y点的坐标

        //计算小圆切点
        let bottompoint:any = calculateTangentPoints(bottompiex, bottompiey, result_center['second']['x'], result_center['second']['y'], circle2Radius)
        let toppoint:any = calculateTangentPoints(toppiex, toppiey, result_center['second']['x'], result_center['second']['y'], circle2Radius)
        // console.log('切点坐标:', bottompoint, result_center['second']['x'], result_center['second']['y'] + circle2Radius)
        
        let canvas = document.getElementById('targetcanvas')
        canvas.width = size.width
        canvas.height = size.height
        var ctx = canvas.getContext("2d")
        // 在绘制之前设置属性
        ctx.imageSmoothingEnabled = true   // 可选,启用或禁用抗锯齿功能
        ctx.imageSmoothingQuality = 'high'   // 可选,设置抗锯齿质量为高
        ctx.beginPath()
        ctx.moveTo(bottompiex, bottompiey)
        ctx.lineTo(bottompoint[0]['x'], bottompoint[0]['y'])
        // ctx.lineTo(result_center['second']['x'], result_center['second']['y'] + circle2Radius)
        ctx.strokeStyle = '#cccccc' // 设置线条颜色
        ctx.lineWidth = 1   // 设置线条宽度
        ctx.fill()
        ctx.stroke()
        ctx.beginPath()
        ctx.moveTo(toppiex, toppiey)
        ctx.lineTo(toppoint[1]['x'], toppoint[1]['y'])
        ctx.strokeStyle = '#cccccc'; // 设置线条颜色
        ctx.lineWidth = 1 // 设置线条宽度
        ctx.fill();
        ctx.stroke();
    }
    

 const option1: ECOption = {
        tooltip: {
            trigger: 'item',
            formatter: (params) => {
                const { name, value, percent } = params;
                const divcontent = `<div style="padding: 10px 2px;color:black;display:flex;justifyContent:start">
                <div>
              <div style="padding: 4px 0px;color:#333333">${name}</div>
              <div style="padding: 4px 0px;color:#333333"><span style="display:inline-block;width:10px;height:10px;line-height:10px;border-radius:50%;background-color:#7262fd;margin-right:10px"></span>金额:${value}</div>
              <div style="padding:  4px 0px;color:#333333"><span style="display:inline-block;width:10px;height:10px;line-height:10px;border-radius:50%;background-color:#45cbf1;margin-right:10px"></span>百分比:${percent}%</div></div>
           `
                return divcontent;
            },
        },
        legend: false,
        color: [
            "#45cbf1",
            "#7262fd",
        ],
        series: [
            {
                name: '占比图1',
                type: 'pie',
                radius: circle1Radius,
                startAngle: startAngle,
                data: dueOverdue,
                minAngle: 3,
                emphasis: {
                    label: {
                        show: false,
                    },
                    scale: 0, // 禁止鼠标移入放大
                },
                label: {
                    show: false,
                    position: 'center'
                },
            }
        ]
    };
    const option2: ECOption = {
        tooltip: {
            trigger: 'item',
            formatter: (params) => {
                const { name, value, percent } = params;
                const divcontent = `<div style="padding: 10px 2px;color:black;display:flex;justifyContent:start">
                <div>
              <div style="padding: 4px 0px;color:#333333">${name}</div>
              <div style="padding: 4px 0px;color:#333333"><span style="display:inline-block;width:10px;height:10px;line-height:10px;border-radius:50%;background-color:#ffb607;margin-right:10px"></span>金额:${value}</div>
              <div style="padding:  4px 0px;color:#333333"><span style="display:inline-block;width:10px;height:10px;line-height:10px;border-radius:50%;background-color:#ff860f;margin-right:10px"></span>百分比:${percent}%</div></div>
           `
                return divcontent;
            },
        },
        color: [
            "#ff860f",
            "#ffb607",
        ],

        legend: false,
        series: [
            {
                name: '占比图2',
                type: 'pie',
                radius: circle2Radius,
                data: delayOverdue,
                startAngle: endAngle,
                minAngle: 3,
                emphasis: {
                    label: {
                        show: false,
                    },
                    scale: 0, // 禁止鼠标移入放大
                },
                label: {
                    show: false,
                    position: 'center'
                },
            }
        ]
    };



    <div className="desktop_overduePie_con">
                                    <canvas id="targetcanvas" className="isShowCanvas" style={{ opacity: isCanvas ? 1 : 0 }} />
                                    <div id="divpie">
                                        {
                                            isCanvas ? (
                                                <div className="pieCharts_flex">
                                                    <div className="pieCharts">
                                                        <ReactEChartsCore
                                                            echarts={echarts}
                                                            option={option1}
                                                            theme={"theme_name"}
                                                            style={{ width: "100%", height: "240px" }}
                                                        />
                                                    </div>
                                                    <div className="pieCharts">
                                                        <ReactEChartsCore
                                                            echarts={echarts}
                                                            option={option2}
                                                            theme={"theme_name"}
                                                            style={{ width: "100%", height: "240px" }}
                                                        />
                                                    </div>
                                                </div>
                                            ) : (<div style={{ display: 'flex', width: '100%' }} >
                                                <div className="pieCharts" style={{ width: '100%', position: 'relative', }}>
                                                    <ReactEChartsCore
                                                        echarts={echarts}
                                                        option={option1}
                                                        theme={"theme_name"}
                                                        style={{ width: "100%", height: "240px" }}
                                                    />
                                                </div>
                                            </div>)
                                        }
                                    </div>
                                </div>