效果预览
源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body {
background: black;
width: 400px;
height: 400px;
}
.arc path {
cursor: pointer;
}
.tips {
display: none;
position: absolute;
background: #ffffff;
border: 1px #6a5acd solid;
border-radius: 5px;
padding: 10px;
}
.tips div {
margin: 5px 0 5px 0;
}
.tips div i {
display: inline-block;
width: 12px;
height: 12px;
background-color: slateblue;
border-radius: 50%;
margin-right: 5px;
}
.tips div span:nth-child(3) {
margin-left: 30px;
}
.middle-title, .middle-number {
text-anchor: middle;
fill: #ffffff;
}
.middle-title {
dominant-baseline: middle;
}
.rect {
cursor: pointer;
}
</style>
</head>
<body>
<!--提示框-->
<div class="tips">
<div>来源</div>
<div>
<i></i>
<span class="title"></span>
<span class="number"></span>
</div>
</div>
<svg viewBox="0 0 400 400" height="100%" width="100%" preserveAspectRatio="xMinYMin meet"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<!-- 阴影-->
<defs>
<filter id="shadow" x="-2" y="-2" width="500%" height="500%">
<feOffset result="offOut" in="SourceGraphic" dx="0" dy="0"/>
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="10"/>
<feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
</filter>
</defs>
</svg>
<script src="d3.v6.js"></script>
<script>
let hsl = null
let w = 400
let h = 400
let legendPadding = 30; // 图例间隔距离
let outerRadius = w / 2
let innerRadius = w / 3
let x = outerRadius - 45; // 图形移动位置
let dataset = [{
value: 5,
title: 'bhjb'
}, {
value: 10,
title: '123'
}, {
value: 20,
title: '000'
}, {
value: 45,
title: '是的发'
}, {
value: 20,
title: '898'
}, {
value: 25,
title: '5656'
}]
let pie = d3.pie().value(function (d) {
return d.value
}).sortValues(function (a, b) {
return a
})
let radiusSize = [15, 65]
let arc = arcFunc(innerRadius - radiusSize[0], outerRadius - radiusSize[1]);
function arcFunc(innerRadius, outerRadius) {
return d3.arc().innerRadius(innerRadius).outerRadius(outerRadius).cornerRadius(10).padAngle(0.05);
}
let svg = d3.select('svg')
svg._groups[0][0].onload = function () {
let percentage = x / this.clientWidth * 100
// 渐变色
svg._groups[0][0].innerHTML +=
`<defs>
<radialGradient id="showdown" cx="${percentage}%" cy="50%" r="25%" fx="${percentage}%" fy="50%" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="rgba(255,255,255,1)"/>
<stop offset="50%" stop-color="rgba(255,255,255,.5)"/>
<stop offset="100%" stop-color="rgba(255,255,255,0)"/>
</radialGradient>
</defs>`
// 中间数字
svg.append('text')
.classed('middle-number', true)
.text(d3.sum(dataset, function (d) {
return d.value
}))
.attr('font-size', '40')
.attr('x', x)
.attr('y', outerRadius - 30)
// 中间文字
svg.append('text')
.classed('middle-title', true)
.text('总数')
.attr('font-size', '40')
.attr('x', x)
.attr('y', outerRadius + 60)
let arcs = svg.selectAll('g.arc')
.data(pie(dataset))
.enter()
.append('g')
.classed('arc', true)
.attr('transform', `translate(${x},${outerRadius})`)
arcs.append('path')
.attr('fill', (d, i) => {
return d3.schemeCategory10[i]
})
.attr('d', arc)
.attr('filter', 'url(#shadow)')
// 鼠标移入
.on('mousemove', function (d, value) {
// 显示提示框
tipsShow(d, value)
// 样式改变
pathStyle.call(this, value, true)
})
// 鼠标移出
.on('mouseout', function (d, value) {
// 隐藏提示框
tipsHide()
// 还原样式
pathStyle.call(this, value, false)
});
// 图例
let g = svg.append('g')
let g1 = g.selectAll('g.rect')
.data(dataset, function (d, i) {
d.index = i
return i
})
.enter()
.append('g')
.attr('class', 'rect')
.on('mousemove', function (d, val) {
tipsShow(d, val, false)
pathStyle.call(arcs._groups[0][val.index].children[0], val, true)
})
.on('mouseout', function (d, val) {
tipsHide()
pathStyle.call(arcs._groups[0][val.index].children[0], val, false)
})
let text = g1.append('text')
.text(function (d) {
return d.title
})
.attr('y', '16')
.attr('fill', '#ffffff')
.attr('x', '25')
// 图例文字长度
let numArr = [];
for (let item of text._groups[0]) {
numArr.push(item.getBBox().width)
}
// 获取最长的文字长度
let maxNum = d3.max(numArr)
g1.append('rect')
.attr('width', 20)
.attr('height', 20)
.attr('fill', function (d, i) {
return d3.schemeCategory10[i]
})
.attr('rx', '5')
.attr('ry', '5')
// 中间横线
svg.append('line')
.attr('x1', x - 100)
.attr('y1', outerRadius)
.attr('x2', x + 100)
.attr('y2', outerRadius)
.attr('stroke', 'url(#showdown)')
.attr('stroke-width', '2')
legendTranslate('right')
/**
* 图例排序及居中计算
* @param {string} key - 图例显示的位置
**/
function legendTranslate(key) {
let num = 0
if (key === 'top') {
// 图例顶部
g1.attr('transform', function (d, i) {
if (i) {
num += (text._groups[0][i === 1 ? 0 : i - 1].getBBox().width) + legendPadding
}
return `translate(${num},0)`
})
}
if (key === 'right') {
// 图例右边排列
g1.attr('transform', function (d, i) {
if (i) {
num += legendPadding // 各图例间隔
}
return `translate(0,${num})`
})
}
// 居中计算
let n = outerRadius - (g.node().getBBox()[key === 'top' ? 'width' : 'height'] / 2)
g.attr('transform', `translate(${key === 'top' ? n : w - (maxNum + legendPadding)},${key === 'right' ? n : 0})`)
}
/**
* 显示提示框/中间内容改变
* @param {HTMLElement} d - dom元素
* @param {Object} val - 饼图数据
* @param {Boolean} tipsHide - 是否展示提示框
**/
function tipsShow(d, val, tipsHide = true) {
let title = val.data ? val.data.title : val.title
let value = val.data ? val.data.value : val.value
// 提示框
d3.selectAll('.tips div i')
.style('background', d3.schemeCategory10[val.index])
// 标题
d3.select('.tips .title')
.text(title)
// 百分比
d3.select('.tips .number')
.text(function () {
let percent = Number(value) / d3.sum(dataset, function (d) {
return d.value;
}) * 100
return `${percent.toFixed(1)}%`
})
d3.select('.tips')
.style('display', tipsHide ? 'block' : 'none')
.style('left', `${d.clientX + 20}px`)
.style('top', `${d.clientY}px`)
.style('border-color', d3.schemeCategory10[val.index])
// 环形图中间内容
d3.select('.middle-title')
.text(title)
d3.select('.middle-number')
.text(value)
}
/**
* 隐藏提示框/中间内容还原
**/
function tipsHide() {
// 提示框
d3.select('.tips')
.style('display', 'none')
// 环形图中间内容
d3.select('.middle-title')
.text('总数')
d3.select('.middle-number')
.text(d3.sum(dataset, function (d) {
return d.value
}))
}
/**
* 当前path样式改变
* @param {Object} value - 饼图数据
* @param {Boolean} into - true改变样式,false还原
**/
function pathStyle(value, into = false) {
if (into) {
hsl = d3.hsl(d3.schemeCategory10[value.index])
hsl.s += 0.1
d3.select(this)
.transition()
.duration(400)
.attr('fill', hsl)
.attr('d', arcFunc(innerRadius - radiusSize[0], outerRadius - (radiusSize[1] - 5)))
} else {
hsl = d3.hsl(d3.schemeCategory10[value.index])
hsl.s -= 0.1
d3.select(this)
.transition()
.duration(400)
.attr('fill', hsl)
.attr('d', arc)
}
}
}
</script>
</body>
</html>