vue加d3js实现3d饼图

前言

之前项目中需要用到3d饼图,最开始使用的是highcharts ,但是后来发现highcharts商用收费,而且收费有点贵,最终只能放弃了。
查了一下,发现可以使用d3js 来实现,对d3不太熟悉的可以看一下下面这两篇文章:

D3JS的简单学习

D3JS常用API

如果你学过jquery 的话,你会发现上手还是非常容易的。d3基本的使用还是很容易的,但是用d3写一个3d饼图还是有亿点点难的,但是如果你能站在巨人的肩膀上,那就相对简单了。

巨人:就是 3D Dount

3D Donut 有时候会打不开,多刷新几次试试。

在这里插入图片描述

把源码拷贝下拉,在此基础上进行封装就简单多了

实现

效果图
在这里插入图片描述
使用案例

<template>
    <div>
        <div id="abc"></div>
        <el-button type="primary" @click="changeData">修改</el-button>
    </div>

</template>

<script>
import pie from './js/pie';
export default {
    
    
    data() {
    
    
        return {
    
    
            salesData: [
                {
    
     label: 'Basic',value: 10, color: '#3366CC' },
                {
    
     label: 'Plus',value: 20, color: '#DC3912' },
                {
    
     label: 'Lite', value: 30,color: '#FF9900' },
                {
    
     label: 'Elite',value: 40, color: '#109618' },
                {
    
     label: 'Delux',value: 50, color: '#990099' }
            ]
        };
    },
    mounted() {
    
    
        pie('#abc',700,400,this.salesData,250, 200, 150, 100, 30);
    },
    methods: {
    
    
        changeData() {
    
    
            pie('#abc',700,400,this.randomData(),250, 200, 150, 100, 30);
        },
        randomData() {
    
    
            return this.salesData.map(function(d) {
    
    
                return {
    
     label: d.label, value: 1000 * Math.random(), color: d.color };
            });
        }
    }
};
</script>

<style scoped lang="scss">
</style>

pie.js

import * as d3 from 'd3';
import {
    
     draw } from './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) {
    
    
    d3.select(id)
        .selectAll('svg')
        .remove();

    const svg = d3
        .select(id)
        .append('svg')
        .attr('width', width)
        .attr('height', height);

    let gId = id.replaceAll('#','');

    svg.append('g').attr('id', gId + 'pie');
    draw(gId + 'pie', data,x,y,rx,ry,h);
}

renderUtils.js

import * as d3 from 'd3';

export const draw = (
    id, // svg的id】
    data, // 要渲染的数据
    x , // 横向偏移量
    y , // 纵向偏移量
    rx , // 椭圆的x半径
    ry , // 椭圆的y半径
    h , // 饼图的高
    ir = 0 // 饼图的内半径
) => {
    
    
    // 生成饼图数据
    const dataset = d3
        .pie()
        .sort(null)
        .value(function(d) {
    
    
            return d.value;
        })(data);

    // 获取svg
    const slices = d3
        .select(`#${
      
      id}`)
        .append('g')
        .attr('transform', `translate(${
      
      x},${
      
      y})`)
        .attr('class', 'slices');

    // 环形内曲面
    slices
        .selectAll('.innerSlice')
        .data(dataset)
        .enter()
        .append('path')
        .attr('class', 'innerSlice')
        .style('fill', function(d) {
    
    
            return d3.hsl(d.data.color).darker(0.7);
        })
        .attr('d', function(d) {
    
    
            return pieInner(d, rx + 0.5, ry + 0.5, h, ir);
        })
        .each(function(d) {
    
    
            this._current = d;
        });

    // 上层2d平面
    slices
        .selectAll('.topSlice')
        .data(dataset)
        .enter()
        .append('path')
        .attr('class', 'topSlice')
        .style('fill', function(d) {
    
    
            return d.data.color;
        })
        .style('stroke', function(d) {
    
    
            return d.data.color;
        })
        .attr('d', function(d) {
    
    
            return pieTop(d, rx, ry, ir);
        })
        .each(function(d) {
    
    
            this._current = d;
        });


    // 侧面曲面
    slices
        .selectAll('.outerSlice')
        .data(dataset)
        .enter()
        .append('path')
        .attr('class', 'outerSlice')
        .style('fill', function(d) {
    
    
            return d3.hsl(d.data.color).darker(0.7);
        })
        .attr('d', function(d) {
    
    
            return pieOuter(d, rx - 0.5, ry - 0.5, h);
        })
        .each(function(d) {
    
    
            this._current = d;
        });

    // 线条
    slices
        .selectAll('lines')
        .data(dataset)
        .enter()
        .append('line')
        .attr('stroke',d => d.data.color)
        .attr('x1',0)
        .attr('y1', 0)
        .attr('x2', function(d) {
    
    
            return 1.5 * rx * Math.cos(0.5 * (d.startAngle + d.endAngle));
        })
        .attr('y2', function(d) {
    
    
            return 1.5 * ry * Math.sin(0.5 * (d.startAngle + d.endAngle));
        });

    // 文本
    slices
        .selectAll('desc')
        .data(dataset)
        .enter()
        .append('text')
        .attr('font-weight',900)
        .attr('fill','#ffffff')
        .attr('stroke','#000000')
        .attr('stroke-width','0.5px')
        .attr('transform',d => {
    
    
            let x = 1.6 * rx * Math.cos(0.5 * (d.startAngle + d.endAngle));
            let y = 1.6 * ry * Math.sin(0.5 * (d.startAngle + d.endAngle));
            return 'translate(' + x + ',' + y + ')';
        })
        .attr('font-size','12px')
        .attr('text-anchor','middle')
        .text(d => d.data.label);
};


// 生成内曲面
function pieInner(d, rx, ry, h, ir) {
    
    
    const startAngle = d.startAngle < Math.PI ? Math.PI : d.startAngle;
    const endAngle = d.endAngle < Math.PI ? Math.PI : d.endAngle;

    const sx = ir * rx * Math.cos(startAngle);
    const sy = ir * ry * Math.sin(startAngle);
    const ex = ir * rx * Math.cos(endAngle);
    const ey = ir * ry * Math.sin(endAngle);

    const ret = [];
    ret.push(
        'M',
        sx,
        sy,
        'A',
        ir * rx,
        ir * ry,
        '0 0 1',
        ex,
        ey,
        'L',
        ex,
        h + ey,
        'A',
        ir * rx,
        ir * ry,
        '0 0 0',
        sx,
        h + sy,
        'z'
    );
    return ret.join(' ');
}

// 生成饼图的顶部
function pieTop(d, rx, ry, ir) {
    
    
    if (d.endAngle - d.startAngle === 0) {
    
     return 'M 0 0'; }
    const sx = rx * Math.cos(d.startAngle);
    const sy = ry * Math.sin(d.startAngle);
    const ex = rx * Math.cos(d.endAngle);
    const ey = ry * Math.sin(d.endAngle);

    const ret = [];
    ret.push(
        'M',
        sx,
        sy,
        'A',
        rx,
        ry,
        '0',
        d.endAngle - d.startAngle > Math.PI ? 1 : 0,
        '1',
        ex,
        ey,
        'L',
        ir * ex,
        ir * ey
    );
    ret.push(
        'A',
        ir * rx,
        ir * ry,
        '0',
        d.endAngle - d.startAngle > Math.PI ? 1 : 0,
        '0',
        ir * sx,
        ir * sy,
        'z'
    );
    return ret.join(' ');
}

// 外曲面算法
function pieOuter(d, rx, ry, h) {
    
    
    const startAngle = d.startAngle > Math.PI ? Math.PI : d.startAngle;
    const endAngle = d.endAngle > Math.PI ? Math.PI : d.endAngle;

    const sx = rx * Math.cos(startAngle);
    const sy = ry * Math.sin(startAngle);
    const ex = rx * Math.cos(endAngle);
    const ey = ry * Math.sin(endAngle);

    const ret = [];
    ret.push(
        'M',
        sx,
        h + sy,
        'A',
        rx,
        ry,
        '0 0 1',
        ex,
        h + ey,
        'L',
        ex,
        ey,
        'A',
        rx,
        ry,
        '0 0 0',
        sx,
        sy,
        'z'
    );
    return ret.join(' ');
}

// 计算扇形所占百分比
function getPercent(d) {
    
    
    return d.endAngle - d.startAngle > 0.2
        ? `${
      
      Math.round((1000 * (d.endAngle - d.startAngle)) / (Math.PI * 2)) / 10}%`
        : '';
}

猜你喜欢

转载自blog.csdn.net/weixin_41897680/article/details/123916740