SOURCE

console 命令行工具 X clear

                    
>
console
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>Document</title>
	<style>
		body {
			width: 100%;
			height: 100%;
			display: flex;
            background: linear-gradient(180deg,rgba(3, 9, 37, 1),rgba(3,9, 37, 1),rgba(3, 9, 37, 1),rgba(12, 27, 66, 1));
		}

		#radial-bar-chart {
			margin: auto;
			position: relative;
			box-shadow: 0 0 10px 0 #eee;
			border: 1px solid;
		}

		.tooltip {
			position: absolute;
			left: 0;
			top: 0;
			box-shadow: 0 0 10px 0 #ccc;
			width: 200px;
			height: 100px;
			border-radius: 4px;
			box-sizing: border-box;
			padding: 15px 10px;
			color: #606060;
			opacity: 0;
			background-color: #fff;
            pointer-events: none;
		}

		.tooltip>.x {
			margin-bottom: 15px;
		}
	</style>
</head>

<body>
	<div id="radial-bar-chart"></div>

	<!-- <script src="https://d3js.org/d3.v5.min.js"></script> -->
	<script>
		const provinces = [
            '北京市',
            '天津市',
            '上海市',
            '重庆市',
            '河北省',
            '河南省',
            '云南省',
            '辽宁省',
            '辽省',
            '',
            '',
            '',
            '',
            '',
            '',
            '',
            '',
            '',
            
        ]
        const objectId = 'chart-pie-bar'
        const series = [{
          "color": "#156dff"
        },
        {
          "color": "#00eaeb"
        }]
        const PadAngle = Math.PI * 2 / 180;
        const width = 800
        const height = 600
        const innerRadius = 90
        const outerRadius = 400
        const data = randomData()
        const chartColor = 'rgba(7, 92, 214, .7)'
        const fontColor = '#606060'
        const gridLineColor = 'rgb(210, 214, 218)'
        const radiusScale = d3.scaleLinear()
            .range([innerRadius, outerRadius - 15])
            .domain([0, d3.max(data, d => d.y)])
        // 通过arc函数生成原型或圆环
        const arc = d3.arc()
            .innerRadius(innerRadius)
            .outerRadius(outerRadius)
            .padAngle(PadAngle)
       		//   .startAngle(0)
            // .endAngle(Math.PI);
        // id="chart-inset-shadow1"内阴影
        // id="chart-inset-shadow"内发光 + 透明
        const svg = d3.select('#radial-bar-chart')
            .append('svg')
            .attr('width', width)
            .attr('height', height)
            .html(`
    <style>
    .arc-bar {
        cursor: pointer;
    }
    </style>
    <filter id="chart-inset-shadow1" x="-50%" y="-50%" width="200%" height="200%">
      <feComponentTransfer in="SourceAlpha">
        <feFuncA type="table" tableValues="1 0" />
      </feComponentTransfer>
      <feGaussianBlur stdDeviation="${5}" />
      <feOffset dx="0" dy="0" result="offsetblur" />
      <feFlood flood-color="${series[0].color}" result="color" />
      <feComposite in2="offsetblur" operator="in" />
      <feComposite in2="SourceAlpha" operator="in" />
      <feMerge>
        <feMergeNode in="SourceGraphic" />
        <feMergeNode />
      </feMerge>
    </filter>
    <filter id="chart-inset-shadow" width="200%" height="200%" x="-50%" y="-50%">
        <feGaussianBlur in="SourceGraphic" result="gass" stdDeviation="${5}" />
        <feMerge>
            <feMergeNode in="gass" />
        </feMerge>
    </filter>
    <defs>
      <linearGradient id="linear-${objectId}" x1="0%" y1="0%" x2="100%" y2="0%">
        <stop offset="0%" stop-color="${series[1].color}" />
        <stop offset="100%" stop-color="${series[0].color}" />
      </linearGradient>
    </defs>
    `)
            .append('g')
            .attr('transform', `translate(${width / 2}, ${height - 42})`)
        drawGrid()
        drawChart()
        svg.append('g')
            .attr('class', 'x-axis')
            .call(xAxis)
        renderToolTip()

        let scale = d3.scaleBand().domain([1,2,3,4]).range([0,100])
        // console.log(scale(1))
        // console.log(scale(2))
        // console.log(scale(3))
        // console.log(scale(4))
        // console.log(scale(5))
        
        function drawChart() {
            // console.log('chart')
            // 饼图数据
            // const pieData = d3.pie().value(d => 10).padAngle(PadAngle)(data)
            const pieData = d3.pie().value(d => 10)(data)
            // console.log(JSON.stringify(pieData))
            const arcs = svg
                .append('g')
                .attr('class', 'arc-bar')
                .attr('transform', 'rotate(-90)')
                .selectAll('g')
                .data(pieData);

                arcs.enter()
                .append('clipPath')
                .attr('id', d => {
                    if (d.data.y) {
                        return `${d.data.y}-${d.index}`
                    }
                })
                .append('path');

                // 内发光
                arcs.enter()
                .append('path')
                .attr('class', 'arc')
                // .attr('fill', '')
                .attr('fill', 'transparent')
                // .attr("stroke", `url(#linear-${objectId})`) // 描边渐变色
                .attr("stroke", series[0].color)
                // .attr('fill', chartColor)
                // .attr('stroke', 'red')
                .attr('stroke-width', 15)
                // .attr('stroke-width', 1)
                .attr("filter", `url(#chart-inset-shadow)`)
                // .attr("filter", `url(#chart-inset-shadow1)`)
                .attr('clip-path', (d) => {
                    return `url(#${d.data.y}-${d.index})`
                });        

                // border
                arcs.enter()
                .append('path')
                .attr('class', 'arc')
                .attr('fill', 'transparent')
                .attr("stroke", `url(#linear-${objectId})`) // 描边渐变色
                // .attr("stroke", this.settings.series[0].color)
                // .attr('stroke', 'red')
                .attr('stroke-width', 2)
                .attr('clip-path', (d) => {
                    return `url(#${d.data.y}-${d.index})`
                });

             arcs.enter()
                .selectAll('path')
                .attr('d', function (d) {
                    if (d.data.y > 0) {
                        arc.outerRadius(radiusScale(0.0001))
                        return arc(d)
                    }
                })
                .transition()
                .delay((d, i) => {
                    return (i % data.length) *500
                })
                .duration(800)
                .ease(d3.easeLinear)
                .attr('d', function (d, i) {
                    // console.log(i)
                    // console.log(JSON.stringify(d));
                    // 动态设定外半径
                    if (d.data.y > 0) {
                        arc.outerRadius(radiusScale(d.data.y))
                        // 注册path data
                        return arc(d)
                    }
                });
        }
        function xAxis(g) {
            const xData = data.filter(d => d && d.x !== '' && d.x).map(d => d.x)
            // console.log(xData)
            const interval = Math.PI / (provinces.length)
            // 计算x轴标签位置
            // const xLabel = d3.scaleBand()
            const xLabel = d3.scaleBand()
                .domain(xData)
                .range([-0.5 * Math.PI + 1 * interval , 0.5 * Math.PI + 1 * interval])
                // .padding(interval)
                // .range([0 * Math.PI, 1 * Math.PI])
            g.attr('class', 'x-axis')
            .attr('text-anchor', 'start')
            .attr('dominant-baseline', 'middle')
            .attr('stroke', fontColor)
            .attr('stroke-width', 1)
            .style('font-size', 14)
            .style('font-weight', 100)
            .call(g => {
                g.selectAll('g')
                    .data(xData)
                    .enter()
                    .append('g')
                    .attr('transform', d => {
                        if (d) {
                            // console.log(xLabel(d), interval, d, -0.5 * Math.PI + 1 * interval)
                            const rotateAngle = tsRadian2angle(xLabel(d) - xLabel.bandwidth() * 0 +
                            PadAngle * 0 + interval * 0) - 90 * 1
                            // console.log(rotateAngle)
                            return `
                                rotate(${ (rotateAngle) })
                                translate(${ outerRadius + 30 }, 0)
                            `
                        }
                    })
                    .call(g => {
                        g.append('text')
                            .text(d => d)
                            .attr('transform', function (d, i) {
                                // console.log(d, i, this, 'sssss')
                                // 通过getBBox访问到text元素的长度
                                // const val = tsRadian2angle(xLabel(d.x) + xLabel.bandwidth() / 2) > 180
                                //     ? `rotate(180) translate(${ -this.getBBox().width - 10 }, 0)`
                                //     : 'translate(10, 0)'
                                // console.log(this.getBBox().height, this.getBBox().width)
                                const val = 
                                    `rotate(90) translate(${ -this.getBBox().width * 0.5 }, 0)`
                                return val
                            })
                    })
            })
        }
        
        // 绘制背景网格
        function drawGrid() {
            const gridWrapper = svg.append('g')
                .attr('class', 'grid-wrapper')
                .attr('fill', 'transparent')
                .attr('text-anchor', 'end')
                .attr('dominant-baseline', 'middle')
                .style('font-size', 12)
                .style('font-weight', 500)
            // 最外层圆环和数据最大值的径向长度的间距
            const xLineOverflow = 5;
            // 最外层圆环对应的最大数据刻度
            const maxPopulation = d3.max(data, d => d.y) * (outerRadius - innerRadius) / ( outerRadius - innerRadius - xLineOverflow  )
            const arcCount = Math.ceil(outerRadius / innerRadius);
            const unitValue = maxPopulation / (arcCount - 1);
            for (let i = 0; i < arcCount; i++) {
                const n = Math.round(unitValue* i)
                // 均分环宽
                const r = i === 0 ? innerRadius :
                (outerRadius - innerRadius - xLineOverflow) / (arcCount - 1) * i + innerRadius
                const semicircle = d3.path();
                semicircle.arc(
                     0,
                     0,
                     r,
                     0,
                     Math.PI,
                     true
                 );
                //  console.log(semicircle);
                // gridWrapper.append('circle')
                //     .attr('r', r)
                gridWrapper.append('path')
                    .attr('d', semicircle)
                    .attr('stroke-width', 1)
                    .attr('stroke', gridLineColor)
                gridWrapper.append('text')
                    .text(n)
                    .attr('transform', `translate(${-r}, 10)`)
                    .attr('fill', fontColor)
                    .style('font-size', 12)
                gridWrapper.append('text')
                    .text(n)
                    .attr('text-anchor', 'start')
                    .attr('transform', `translate(${r}, 10)`)
                    .attr('fill', fontColor)
                    .style('font-size', 12)
            }
            const bandWidth = 2 * Math.PI / data.length
            for (let i = 0; i < Math.floor(data.length / 2); i++) {
                const x1 = innerRadius * Math.sin( bandWidth * i + bandWidth / 2)
                const y1 = innerRadius * Math.cos( bandWidth * i + bandWidth / 2)
                const x2 = outerRadius * Math.sin( bandWidth * i + bandWidth / 2)
                const y2 = outerRadius * Math.cos( bandWidth * i + bandWidth / 2)
                gridWrapper.append('line')
                    .attr('x1',  x1)
                    .attr('y1',  y1)
                    .attr('x2',  x2)
                    .attr('y2',  y2)
                    .attr('transform', 'rotate(-90)')
                    .attr('stroke', gridLineColor)
                    .attr('stroke-width', 1)
                    .attr('stroke-dasharray', '5,5')
            }

            gridWrapper.append('line')
                .attr('x1',  0)
                .attr('y1',  innerRadius)
                .attr('x2',  0)
                .attr('y2',  outerRadius)
                .attr('transform', 'rotate(-90)')
                .attr('stroke', gridLineColor)
                .attr('stroke-width', 1)
                .attr('stroke-dasharray', '5,5')
            gridWrapper.append('line')
                .attr('x1',  0)
                .attr('y1',  innerRadius)
                .attr('x2',  0)
                .attr('y2',  outerRadius)
                .attr('transform', 'rotate(90)')
                .attr('stroke', gridLineColor)
                .attr('stroke-width', 1)
                .attr('stroke-dasharray', '5,5')
        }
        function renderToolTip() {
            // 设置tooltip
            const toolTip = d3.select('#radial-bar-chart')
                .append('div')
                .attr('class', 'tooltip')
            toolTip.html(
                `
                    <div class="x">
                    </div>
                    <div class="y">
                        <span>y</span>
                        <span class="value"></span>
                    </div>
                `
            )
            // 设置mousemove事件
            d3.selectAll('.arc')
                .on('mouseenter', e => {
                    console.log(e)
                const svg = document.querySelector('svg')
                const [x, y] = d3.mouse(svg)
                toolTip.style('opacity', 1)
                toolTip.style('left', `${ x + 50 }px`)
                toolTip.style('top', `${ y + 50 }px`)
                toolTip.select('.x').text(e.data.x)
                toolTip.select('.y .value').text(e.data.y)
            })
            svg.on('mouseleave', () => {
                toolTip.style('opacity', 0)
            })
        }
        
        // 转换弧度值为角度
        function tsRadian2angle(radian) {
            return radian * 180 / Math.PI
        }
        
        // gen random data
        function randomData() {
            var data = []
            for (const p of provinces) {
                data.push({
                    x: p,
                    y:p ? Math.random() * 400 + 100 >>> 0 : 0
                })
            }
            data.sort((a, b) => b.y - a.y)
            // 缓存一次data
            // console.log(JSON.stringify(data))
            data = [{"x":"辽宁省","y":493},{"x":"云南省","y":457},{"x":"河北省","y":403},{"x":"天津市","y":325},{"x":"上海市","y":313},{"x":"辽省","y":293},{"x":"北京市","y":253},{"x":"河南省","y":219},{"x":"重庆市","y":177},{"x":"","y":0},{"x":"","y":0},{"x":"","y":0},{"x":"","y":0},{"x":"","y":0},{"x":"","y":0},{"x":"","y":0},{"x":"","y":0},{"x":"","y":0}]
            return data
        }
        // TODO: 内半径颜色
	</script>
</body>

</html>

本项目引用的自定义外部资源