SOURCE

console 命令行工具 X clear

                    
>
console
const props = {
    width: 500,
    height: 500,
    nodeWidth: 300,
    nodeHeight: 150,
    radius: 300 * .25 - 10,  // nodeWidth * .25 - 10
    innerRadius: 300 * .25 - 35   // nodeWidth * .25 - 35
}
const state = {
    color: ['#bd94ff', '#48eaa7'],
    ratio: 1,
    mousePosition: {
        x: 0,
        y: 0
    }
}
const data = {
    desc: "自定义图形",
    count: 'count Text',
    percent: 60,
    pieData: [
        { desc: '圆环', name: 'A', value: .6 },
        { desc: '圆环', name: 'B', value: .4 }
    ],
    x: 250,
    y: 250
}

const canvas = document.getElementById("canvas")
// canvas.wdith = props.width 
// canvas.height = props.height 
const ctx = canvas.getContext('2d')
let ratio = getPixelRatio(ctx)
let scale = ratio

drawNode(ctx, data, data.x, data.y, 
    props.nodeWidth, props.nodeHeight, props.radius,
    props.innerRadius, state.color, scale, null, false)


canvas.addEventListener('mousemove', e => {
    let eventX = e.clientX * ratio - canvas.getBoundingClientRect().left
    let eventY = e.clientX * ratio - canvas.getBoundingClientRect().top

    let mousePoint = {
        x: eventX,
        y: eventY,
        clientX: e.clientX,
        clientY: e.clientY
    }

    let isRingRange = isRingPostion(mousePoint, data, props.nodeWidth, props.innerRadius, props.radius, scale)
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    drawNode(ctx, data, data.x, data.y, props.nodeWidth, 
        props.nodeHeight, props.radius, props.innerRadius, 
        state.color, scale, mousePoint, this)
    if(!isRingRange){
        console.log(58,'tip is null')
    }
},false)

canvas.addEventListener('wheel',e =>{
    console.log(63,'tip is null')
})










/**
    * 绘制一个节点
    * ctx:上下文
    * node:节点数据{desc: string,count:number,percent:number,pieData:[]}
    * x:节点中心横坐标
    * y:节点中心纵坐标
    * width:节点容器宽度
    * height:节点容器高度
    * radius:外环半径
    * innerRadius:内环半径
    * scale:缩放比例
    * mousePoint:鼠标对象
    * isRingRange:鼠标是否在圆环上
    * treePage:当前页面对象
    */
function drawNode(ctx, node, x, y, width = 220, height = 110, radius, innerRadius, pieColor, scale = 1, mousePoint = null, isRingRange = false, treePage) {
    ctx.strokeStyle = "#e9e9e9"
    ctx.lineWidth = 1.5 * scale
    width = width * scale
    height = height * scale
    ctx.strokeRect(x - width / 2, y - height / 2, width, height)

    let fontSize = 12 * scale
    ctx.font = fontSize + 'px Arial'
    ctx.textBaseline = 'middle'
    ctx.textAlign = 'center'
    ctx.fillStyle = '#9c9c9c'
    let textX1 = y - width * .25
    let textY1 = y - 14 * scale
    ctx.fillText(node.desc, textX1, textY1)

    ctx.font = fontSize + 'px Arial'
    ctx.textBaseline = 'middle'
    ctx.textAlign = 'center'
    ctx.fillStyle = '#7e317e'
    let textX2 = y - width * .25
    let textY2 = y + 14 * scale
    ctx.fillText(node.count, textX2, textY2)

    drawRingPie(ctx, node, x , y, radius * scale, innerRadius * scale,
        pieColor, scale, mousePoint, isRingRange, treePage)
}

/**
     * 画环形饼图
     * ctx:上下文
     * node:节点信息
     * x:饼图圆心横坐标
     * y:饼图圆心纵坐标
     * radius:外层圆半径
     * innerRadius:内层圆半径
     * color:饼图颜色数组
     * scale:缩放比例
     * mousePoint:鼠标对象
     * isRingRange:鼠标是否在圆环上
     * treePage:当前页面对象
     */
function drawRingPie(ctx, node, x, y, radius, innerRadius, color, scale = 1, mousePoint = null, isRingRange, treePage) {
    ctx.save()
    // console.log(96, x, y)
    ctx.translate(x, y)
    ctx.scale(scale, scale)

    let startRadian = 0, endRadian = 0

    for (let i = 0; i < node.pieData.length; i++) {
        ctx.beginPath()
        ctx.moveTo(0, 0)
        endRadian += node.pieData[i].value * Math.PI * 2
        ctx.arc(0, 0, radius, startRadian, endRadian, false)
        ctx.closePath()
        ctx.fillStyle = color[i]
        ctx.fill()
        startRadian = endRadian
        if(mousePoint && ctx.isPointInPath(mousePoint.x ,mousePoint.y) && isRingRange) {
            ctx.clearRect(-radius,-radius,2*radius,2*radius)
            console.log(151)
            drawDynamicPie(ctx,node,radius,color,i)
        }
    }

    // ctx.beginPath()
    // ctx.moveTo(0, 0)
    // ctx.arc(0, 0, innerRadius, 0, Math.PI * 2, false)
    // ctx.closePath()

    // ctx.fillStyle = '#fff'
    // ctx.strokeStyle = '#fff'
    // ctx.stroke()
    // ctx.fill()
    ctx.restore()

    let fontSize = 12 * scale
    ctx.font = fontSize + 'px Arial'
    ctx.textBaseline = 'middle'
    ctx.textAlign = 'center'
    ctx.fillStyle = '#000'
    ctx.fillText(node.percent + '%', x, y)
}


function drawDynamicPie (ctx,node,radius,color,index){
    let startRadian = 0 , endRadian = 0 
    for(let i = 0 ; i < node.pieData.length ; i++){
        ctx.beginPath()
        ctx.moveTo(0,0)

        endRadian += node.pieData[i].value * Math.PI * 2 
        ctx.arc(0,0,index ===i ? radius + 5 : radius , startRadian,endRadian,false)

        ctx.closePath()
        ctx.fillStyle = color[i]
        ctx.fill()
        startRadian = endRadian
    } 
}

const  isRingPostion = (mousePoint , node , nodeWidth ,innerRadius,radius,scale) => {
    if(!mousePoint) {
        return false 
    }

    nodeWidth = nodeWidth * scale 
    innerRadius = innerRadius * scale 
    radius =  radius *scale 
    let eventX = mousePoint.x ,
        eventY = mousePoint.y 

    let cricleX = node.x + nodeWidth / 4 ,
        cricleY = node.y 
    let distanceFromCenter = Math.sqrt(Math.pow(cricleX - eventX,2) + Math.pow(cricleY - eventY,2))

    if(distanceFromCenter > innerRadius && distanceFromCenter < radius) {
        return true 
    }  
    return false  

}

function getPixelRatio  (ctx) {
    var backingStore = ctx.backingStorePixelRatio || 
        ctx.webkitBackingStorePixelRatio ||
        ctx.mozBackingStorePixelRatio ||
        ctx.msBackingStorePixelRatio ||
        ctx.oBackingStorePixelRatio || 1 
    return (window.devicePixelRatio || 1) / backingStore
}

function getTipPosition (){

}





<body>
    <!-- 
        https://blog.csdn.net/mafan121/article/details/81625363 
        https://blog.csdn.net/mafan121/article/details/81625592
        
        https://github1s.com/windSandEye/custom-tree/blob/master/src/registerServiceWorker.js
     -->
    <!-- canvas详解 -->
    <canvas id="canvas" width="500" height="500" ></canvas>
    <div id="tip"> tip </div>
</body>
#canvas{
    border: 1px solid red;
}