D3.js 绘制柱状图

使用D3 V4版本绘制

使用D3绘制柱状图,绘制效果如下:

柱状图效果

使用D3绘制柱状图,先对需要绘制的图形拆解,主要分为以下几个部分:
1. 比例尺的定义
2. 坐标轴的绘制
3. 添加矩形
4. 修改坐标轴样式
5. 柱状图装饰效果添加


首先,先定义data

this.graphData = [
  { id:1, name: '数据一', value: 2345 },
  { id:2, name: '数据二', value: 4467 },
  { id:3, name: '数据三', value: 2356 },
  { id:4, name: '数据四', value: 3895 },
  { id:5, name: '数据五', value: 3495 },
  { id:6, name: '数据六', value: 3895 },
  { id:7, name: '数据七', value: 2597 },
  { id:8, name: '数据八', value: 2997 },
];

柱状图X轴展示的数据:

const xData = dataset.map((item) => item.name);

绘制过程如下:

添加画布
首先,图形要添加到画布上:

const width = 650;
const height = 420;
const svg = d3.select(node).append('svg')
    .attr('width', width)
    .attr('height', height);

定义比例尺

const xScale = d3.scaleBand()
    .domain(xData)
    .rangeRound([0, width - paddingLeft - paddingRight])
const yScale = d3.scaleLinear()
    .domain([0, max * 1.5])
    .range([height - paddingTop - paddingBottom, 0]);

scale定义方式不同,则使用方式也是不同。此处xData为离散型数据,对应连续区间,所以使用d3.scaleBand();

关于几种常用比例尺的区别:

1. d3.scaleLinear() 线性比例尺
domain:连续型
range:连续型
使用: scale_l = d3.scaleLinear().domain([1,10]).range([0,100])
2. d3.scaleOrdinal() 序数比例尺
domain: 离散型
range:离散型
可以简单理解为map映射
使用: scale_o = d3.scaleOrdinal().domain(['a', 'b', 'c']).range([10, 20, 30])
3. d3.scaleBand() 序数比例尺
domain: 离散型
range:连续型
可以理解为用domain将range平均分割
使用: scale_b= d3.scaleBand().domain([1,2,3,4]).range([0,100])
4. d3.scalePoint() 序数比例尺
domain: 离散型
range:连续型
与scaleBand()不同的地方在于带宽为零
使用: scale_p= d3.scalePoint().domain([1,2,3,4]).range([0,100])

关于 Band Scales :
Band Scales
关于 Point Scales :
Point Scales

添加坐标轴

// 定义X坐标轴
const xAxis = d3.axisBottom(xScale)
   .ticks(0)
   .tickPadding(12);
// 定义Y坐标轴
const yAxis = d3.axisLeft(yScaleAxis)
   .ticks(5)
   .tickPadding(8)
   .tickFormat(d3.format('d'));
// 添加
svg.append('g')
   .attr('class', 'r-xAxis')
   .attr('transform', `translate(${paddingLeft},${height - paddingBottom})`)
   .call(xAxis);
svg.append('g')
   .attr('class', 'r-yAxis')
   .attr('transform', `translate(${paddingLeft},${paddingTop})`)
   .call(yAxis);

svg 坐标轴原点 (0, 0) 在左上角, y 坐标轴最大值在上方,最小值展示在下方,故yScale domain([0, max * 1.5]) 对应 range([height - paddingTop - paddingBottom, 0]);

y 轴使用了d3.tickFormat,使用请参考 d3.format

添加矩形

yScale.range([0, height - paddingTop - paddingBottom]);

const rectGroup = svg.selectAll('.rectItem')
  .data(dataset)
  .enter()
  .append('g')
  .attr('class', 'rectItem');
rectGroup.append('rect')
  .attr('width', rectWidth)
  .attr('height', (d) => yScale(d.value))
  .attr('y', (d) => height - paddingTop - paddingBottom - yScale(d.value))
  .attr('fill', 'url(#rbGraphsColor)')
  .attr('transform', `translate(${paddingLeft},${paddingTop})`)
  .attr('x', (d) => xScale(d.name) + ((xScale.bandwidth() - rectWidth) / 2));

添加矩形后效果展示

添加矩形后效果展示
设置坐标轴样式

关于坐标轴的构成

<g>
	<!-- 第一个刻度 -->
	<g>
		<line></line>   <!-- 第一个刻度的直线 -->
		<text></text>   <!-- 第一个刻度的文字 -->
	</g>
	<!-- 第二个刻度 -->
	<g>
		<line></line>   <!-- 第二个刻度的直线 -->
		<text></text>   <!-- 第二个刻度的文字 -->
	</g> 
	...
	<!-- 坐标轴的轴线 -->
	<path></path>
</g>

本例中css样式设置:

.r-xAxis, .r-yAxis {
	line {
     	display: none;
    }
    path {
      stroke: #393c45;
    }
    text {
      font-size: 14px;
    }
}

其他

  • 添加柱状图后面网格线
  • 添加数据文字
  • 添加柱状图装饰效果
  • 添加动效

此处只介绍如何添加网格线

svg.selectAll('.r-yAxis .tick')
   .append('line')
   .attr('class', 'row-grid')
   .attr('x1', 0)
   .attr('x2', width - paddingLeft - paddingRight)
   .attr('y1', 0)
   .attr('y2', 0);
 .row-grid {
   display: block;
   stroke: #b4b5b5;
   /* shape-rendering: crispedges;*/
   stroke-width: 1;
   opacity: 0.7;
 }

柱状图添加网格线

最后,附上完整代码:

JS

import * as d3 from 'd3';
export default function graphs(node, dataset) {
    (() => {
        d3.select(node).selectAll('svg').remove();
    })();

    // ***************数据初始化*******************
    const width = node.offsetWidth;
    const height = node.offsetHeight;
    const paddingTop = 60;
    const paddingBottom = 98;
    const paddingLeft = 90;
    const paddingRight = 24;
    const rectWidth = 40;
    const decoRectWidth = 2;
    const delay = 0;
    const duration = 2000;
    const max = Math.max(...dataset.map((item) => item.value));

    const xData = dataset.map((item) => item.name); // 对接数据时根据name名创建

    /****************************** 比例尺 ***************************************/
    const xScale = d3.scaleBand()
        .domain(xData)
        .rangeRound([0, width - paddingLeft - paddingRight])
    const yScale = d3.scaleLinear()
        .domain([0, max * 1.5])
        .range([height - paddingTop - paddingBottom, 0]);

    // 绘制
    const svg = d3.select(node).append('svg')
        .attr('width', width)
        .attr('height', height);
    // 坐标轴
    const xAxis = d3.axisBottom(xScale)
        .ticks(0)
        .tickPadding(12);
    const yAxis = d3.axisLeft(yScale)
        .ticks(5)
        .tickPadding(8)
        .tickFormat(d3.format('d'));
    // ***************坐标轴***************
    svg.append('g')
        .attr('class', 'r-xAxis')
        .attr('transform', `translate(${paddingLeft},${height - paddingBottom})`)
        .call(xAxis);

    svg.append('g')
        .attr('class', 'r-yAxis')
        .attr('transform', `translate(${paddingLeft},${paddingTop})`)
        .call(yAxis);
    // 添加横向网格线
    svg.selectAll('.r-yAxis .tick')
        .append('line')
        .attr('class', 'row-grid')
        .attr('x1', 0)
        .attr('x2', width - paddingLeft - paddingRight)
        .attr('y1', 0)
        .attr('y2', 0);

    // ***************矩形图******************
    yScale.range([0, height - paddingTop - paddingBottom]);

    const rectGroup = svg.selectAll('.rectItem')
        .data(dataset)
        .enter()
        .append('g')
        .attr('class', 'rectItem');

    rectGroup.append('rect')
        .attr('width', rectWidth)
        .attr('height', 0)
        .attr('y', height - paddingTop - paddingBottom)
        .attr('fill', 'url(#rbGraphsColor)')
        .attr('transform', `translate(${paddingLeft},${paddingTop})`)
        .attr('x', (d) => xScale(d.name) + ((xScale.bandwidth() - rectWidth) / 2))
        .transition()
        .delay(delay)
        .duration(duration)
        .attr('height', (d) => yScale(d.value))
        .attr('y', (d) => height - paddingTop - paddingBottom - yScale(d.value));
    // 中间装饰矩形
    rectGroup.append('rect')
        .attr('width', decoRectWidth)
        .attr('height', 0)
        .attr('y', height - paddingTop - paddingBottom)
        .attr('fill', '#30ca6e')
        .attr('opacity', 0.6)
        .attr('transform', `translate(${paddingLeft},${paddingTop})`)
        .attr('x', (d) => xScale(d.name) + ((xScale.bandwidth() - decoRectWidth) / 2))
        .transition()
        .delay(delay)
        .duration(duration)
        .attr('height', (d) => yScale(d.value))
        .attr('y', (d) => height - paddingTop - paddingBottom - yScale(d.value));
    // 上方装饰矩形
    rectGroup.append('rect')
        .attr('width', rectWidth)
        .attr('height', 2)
        .attr('y', height - paddingTop - paddingBottom - 4)
        .attr('fill', '#32dd77')
        .attr('transform', `translate(${paddingLeft},${paddingTop})`)
        .attr('x', (d) => xScale(d.name) + ((xScale.bandwidth() - rectWidth) / 2))
        .transition()
        .delay(delay)
        .duration(duration)
        .attr('y', (d) => height - paddingTop - paddingBottom - yScale(d.value));

    rectGroup.append('text')
        .text((d) => d.value)
        .attr('y', height - paddingTop - paddingBottom)
        .attr('class', 'valueDes')
        .attr('transform', `translate(${20},${paddingTop - 5})`)
        .attr('x', (d) => xScale(d.name) + ((xScale.bandwidth() - rectWidth) / 2) + paddingLeft)
        .transition()
        .delay(delay)
        .duration(duration)
        .attr('y', (d) => height - paddingTop - paddingBottom - yScale(d.value));

    const xyLine = svg.append('g')
        .attr('class', 'lineGroup');
    // x
    xyLine.append('line')
        .attr('class', 'xyLine')
        .attr('x1', paddingLeft - 1)
        .attr('x2', width - paddingRight)
        .attr('y1', height - paddingBottom)
        .attr('y2', height - paddingBottom);
    // y
    xyLine.append('line')
        .attr('class', 'xyLine')
        .attr('x1', paddingLeft)
        .attr('x2', paddingLeft)
        .attr('y1', paddingTop)
        .attr('y2', (height - paddingBottom) + 1);
}

CSS

svg {
  .valueDes {
    text-anchor: middle;
    font-size: 14px;
    fill: #c4c4c4;
  }
  .r-xAxis {
    path, line {
      display: none;
    }
    text {
      font-size: 14px;
      color: #fdfdfd;
    }
  }
  .r-yAxis {
    path, line {
      display: none;
    }
    text {
      font-size: 14px;
      color: #c4c4c4;
    }
    .row-grid {
      display: block;
      stroke: #393c45;
      stroke-width: 1;
      opacity: 0.7;
    }
  }
  .xyLine {
    stroke: #393c45;
    stroke-width: 1;
  }
}
参考文章 :
  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值