纵向普通图例的实现,效果图如下
完整代码如下
import * as d3 from 'd3';
import { pieInner, pieTop, pieOuter } from '../utils/renderUtils'
/**
* 生成3d饼图
* @param {*} id :id唯一标识
* @param {*} width :svg的宽
* @param {*} height :svg的高
* @param {*} data :要渲染的数据
* @param {*} x :横向偏移量
* @param {*} y :纵向偏移量
* @param {*} rx :饼图的横向半径
* @param {*} ry :饼图的纵向半径
* @param {*} h :饼图的高度
*/
export default function pie(id, width, height, data, x, y, rx, ry, h, config,ir=0){
//先移除所有的svg
d3.select(id).selectAll('svg').remove();
//创建一个svg容器,用来存放饼图
const pieSvg = d3
.select(id)
.append('svg')
.attr('width','100%')
.attr('height','100%');
pieSvg.append('g').attr('id','pie_chart');
//生成饼图数据
const dataset = d3
.pie()
.sort(null)
.value((d) => {
return d.value
})(data);
// 获取上面插入的g
const slices = d3
.select('#pie_chart')
.append('g')
.attr('transform','translate(350,200)')
.attr('class','slices')
.style('cursor','pointer');
//生成环形内曲面
slices
.selectAll('.innerSlice')
.data(dataset)
.enter()
.append('path')
.attr('class','innerSlice')
.style('fill',(d,index) => {
let colorIndex = index % config.color.length
return d3.hsl(config.color[colorIndex]).darker(0.7)
})
.attr('d',d => {
return pieInner(d, rx+0.5, ry+0.5, h, ir)
});
//上层2D平面
slices.selectAll('.topSlice')
.data(dataset)
.enter()
.append('path')
.transition()
.delay(0)
.duration(500)
.attrTween('d',(d) => {
//动画效果
let interpolate = d3.interpolate(d.startAngle, d.endAngle);
return function(t){
d.endAngle = interpolate(t);
return pieTop(d, rx, ry, ir)
}
})
.attr('class','topSlice')
.style('fill',(d,index) => {
let colorIndex = index % config.color.length
return config.color[colorIndex]
})
.style('stroke',(d,index) => {
let colorIndex = index % config.color.length
console.log(config.color[colorIndex],'==config.color[colorIndex]')
return config.color[colorIndex]
})
//侧面曲面
slices.selectAll('.outerSlice')
.data(dataset)
.enter()
.append('path')
.transition()
.delay(0)
.duration(500)
.attrTween('d',(d) => {
//动画效果
let interpolate = d3.interpolate(d.startAngle, d.endAngle);
return function(t){
d.endAngle = interpolate(t);
return pieOuter(d, rx- 0.5, ry - 0.5, h)
}
})
.attr('class','outerSlice')
.style('fill',(d,index) => {
let colorIndex = index % config.color.length
return d3.hsl(config.color[colorIndex]).darker(0.7)
});
//是否显示标签
if(config.sfShowLabel){
//创建文本标签引导线
const line = d3.select('#pie_chart')
.append('g')
.attr('transform',`translate(350,200)`)
.attr('class','line');
//引导线
line.selectAll('.line')
.data(dataset)
.enter()
.append('line')
.attr('stroke',(d,index) => {
let colorIndex = index % config.color.length
return config.color[colorIndex]
})
.attr('x1',0)
.attr('y1',0)
.attr('x2',d => {
return 1.5 * rx * Math.cos(0.5 * (d.startAngle + d.endAngle))
})
.attr('y2',d => {
return 1.5 * ry * Math.sin(0.5 * (d.startAngle + d.endAngle))
})
.style('visibility', d => {
return 'visible'
});
//文本
const textbox = d3.select('#pie_chart')
.append('g')
.attr('transform','translate(350,200)')
.attr('class','textbox');
const text = textbox.selectAll('desc')
.data(dataset)
.enter()
.append('text')
.attr('font-size','14px')
.attr('text-anchor','middle')
.style('visibility', d => {
return 'visible'
})
.attr('fill',(d,index) => {
let colorIndex = index % config.color.length
return config.color[colorIndex]
})
.text(d => {
return `${d.data.label}:${d.data.value}${d.data.DWMC}`
})
.attr('transform', d => {
let x = 1.5 * (rx + 10) * Math.cos(0.5 * (d.startAngle + d.endAngle))
let y = 1.5 * (ry + 10) * Math.sin(0.5 * (d.startAngle + d.endAngle))
return `translate(${x},${y})`
})
}
//是否显示图例
if(config.sfLegend){
//此处为了实现横向和纵向图例的效果,必须在外层盒子中创建薪的svg来包裹图例盒子
const legendSvg = d3.select(id)
.append('svg')
.style('position','absolute');
let legend = null //每一个图例
let legendBox = null //图例层最外层大盒子
//判断是否横向图例
if(config.legendOrient === 'horizontal'){
//横向
legendBox = legendSvg.attr('width',700)
.attr('height',20)
.style('top',() => {
return 400 - 30
})
.style('left',7)
.append('g')
.attr('id','legend_svg')
legend = legendBox.selectAll('g')
.data(data)
.enter()
.append('g');
}else{
//纵向 vertical
legendBox = legendSvg.attr('height',400)
.attr('width',200)
.style('top',0)
.style('left',7)
.append('g')
.attr('id','legend_svg');
legend = legendBox.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('transform',(d,i) => {
return `translate(0,${i * 20})`
});
}
//创建图例前的小圆点样式
let legendItembox = legend.append('circle')
.attr('cx',10)
.attr('cy',10)
.attr('r',7);
legendItembox.attr('fill',(d,index) => {
let colorIndex = index % config.color.length
return config.color[colorIndex]
});
//图例旁边的文字
legend.append('text')
.text(d => {
return d.label
})
.style('font-size','14px')
.attr('fill','#000000')
.attr('y',11)
.attr('x',30)
.attr('dy',3);
//处理横向图例的翻页还是普通
if(config.legendOrient === 'horizontal'){
//判断是普通图例还是翻页图例
if(config.legendType === 'scroll'){
legendSvg.attr('width',700 - 80);
//计算每一个图例的距离
let list = []
let left = 0
legend.each(function(){
left += d3.select(this).node().getBBox().width + 10
list.push(left)
})
legend.attr('transform',function(d, i){
if(i === 0){
return 'translate(0,0)'
}else {
return `translate(${list[i - 1]},0)`
}
})
//图例宽度大于盒子宽度
if(legendSvg.node().getBBox().width > (700 - 80)){
//获取总页数
let legendNumLength = 0
let totalPage = 1 //总页数
let curPage = 1 //翻页定义当前页数
let transitionX = 0 //需要移动图例整体所占的距离
let lineList = [] //如果是普通图例判断一行显示多少个
legend.each(function(d,i){
legendNumLength += d3.select(this).node().getBBox().width + 10
if(legendNumLength > (700 - 80 - (d3.select(this).node().getBBox().width + 10))){
legendNumLength = 0
//避免移动到最后一页多加一夜
if(data.length - 1 !== i){
lineList.push(i)
totalPage++
}
}
})
//定义横向翻页的svg
const legendPageSvg = d3.select(id)
.append('svg')
.attr('width',100)
.attr('height',20)
.style('position','absolute')
.style('top',400 - 30)
.style('left',700 - 80);
let sym = d3.symbol().type(d3.symbolTriangle).size(100);
const pageBox = legendPageSvg.append('g')
.attr('id','legend_page')
.attr('transform','translate(15,8)');
//定义左侧翻页符号
pageBox.append('path')
.attr('d',sym)
.attr('fill','green')
.attr('transform','rotate(30)')
.style('cursor','pointer')
.on('click',function(){
if(curPage > 1){
let legendNumLength = 0
let curLength = 0
legend.each(function(d,i){
legendNumLength += d3.select(this).node().getBBox().width + 10
if(legendNumLength <= 700 - 80){
curLength = legendNumLength
}
})
transitionX -= curLength
legendBox.transition()
.duration(500)
.attr('transform',`translate(-${transitionX}, 0)`);
curPage--
legendText.text(`${curPage}/${totalPage}`)
}
});
//页码
const legendText = pageBox.append('text')
.text(`${curPage}/${totalPage}`)
.attr('fill','red')
.attr('font-size',12)
.attr('transform','translate(10,5)');
//右侧翻页按钮
pageBox.append('path')
.attr('d',sym)
.attr('fill','green')
.attr('transform','translate(40,0) rotate(-30)')
.style('cursor','pointer')
.on('click',function(){
if(curPage < totalPage){
let legendNumLength = 0
let curLength = 0
legend.each(function(d,i){
legendNumLength += d3.select(this).node().getBBox().width + 10
if(legendNumLength <= 700 - 80){
curLength = legendNumLength
}
});
transitionX += curLength
legendBox.transition()
.duration(500)
.attr('transform',`translate(-${transitionX}, 0)`);
curPage++
legendText.text(`${curPage}/${totalPage}`)
}
});
}
}else{
//类型是普通图例
legendSvg.attr('width',700 - 20);
//获取总页数
let legendNumLength = 0
let totalPage = 1 //总页数
let lineList = [] //如果是普通图例判断一行显示多少个
legend.each(function(d,i) {
legendNumLength += d3.select(this).node().getBBox().width + 10
if(legendNumLength > (700 - 20 - (d3.select(this).node().getBBox().width + 10))){
legendNumLength = 0
//避免移动到最后一页多加一夜
if(data.length - 1 !== i){
lineList.push(i)
totalPage++
}
}
})
let maxWidth = 0 //获取最大的图例宽度
legend.each(function() {
if(maxWidth < d3.select(this).node().getBBox().width){
maxWidth = d3.select(this).node().getBBox().width
}
});
legendSvg.attr('height',20*totalPage)
.style('top',450 - 30 - 20*(totalPage - 1));
let listX = [] //各个图例横向的X
let listY = [] //图例纵向的Y
let left = 0
let top = 0
legend.each(function(d,i) {
left += d3.select(this).node().getBBox().width + 10
lineList.forEach(item => {
if(item === i){
left = 0
top += 20
}
})
listX.push(left)
listY.push(top)
});
legend.attr('transform',(d,i) => {
if(i === 0){
return 'translate(0,0)'
}else{
return `translate(${listX[i - 1]},${listY[i - 1]})`
}
})
}
}else{
let maxWidth = 0 //获取最大宽度
legend.each(function(){
if(maxWidth < d3.select(this).node().getBBox().width){
maxWidth = d3.select(this).node().getBBox().width
}
})
if(config.legendType === 'scroll'){
console.log('翻页')
}else{
//普通图例
//计算分组
let totalPage = 1 //总页码
let legendNumHeight = 0 //初始高度
let rowList = [] //普通图例一列显示多少个图例
legend.each(function(d,i){
legendNumHeight += 20
if(legendNumHeight > 400){
legendNumHeight = 0
if(data.length - 1 !== i){
totalPage ++
rowList.push(i)
}
}
})
legendSvg.attr('width',(maxWidth + 10) * totalPage);
let listX = [] //各个图例的X
let listY = [] //各个图例的Y
let left = 0
let top = 0
legend.each(() => {
top += 20
rowList.forEach(item => {
if(item - 1 === i){
left += maxWidth + 10
top = 0
}
})
listX.push(left)
listY.push(top)
})
legend.attr('transform',(d,i) => {
if(i === 0){
return 'translate(0,0)'
}else{
return `translate(${listX[i - 1]},${listY[i - 1]})`
}
})
}
}
}
}