如何使用D3.js简单的制作可交互柱状图(完整代码)

前言

  • 了解D3是什么,D3的基本用法
  • 完成一个简易的可交互的柱状图
    在这里插入图片描述

最近假期写了点东西,趁略有闲余之时,玩了一下D3,之前项目中主要是用各类charts,比如Echarts,诸如此类还有highCharts等,不过那种是用别人写好的库,添加好数据就完成了,就是配置的问题,这种类库最大的问题是定制很难,而D3是自己制作图形,可定制方面比较强,自由度很大,做完之后还是感觉很爽的

现在是数据时代,越来越多的人认识到数据的重要性,但是海量的数据却不能直观的展示并不能展现出它的价值,只有将数据分析、总结,才能够展现它的价值。所以有了数据分析和数据可视化。

PS:代码在最底下

D3是什么

D3js 是一个可以基于数据来操作文档的 JavaScript 库。D3 可以借助 SVG, Canvas 以及 HTML 将你的数据生动的展现出来. D3 结合了强大的可视化交互技术以及数据驱动 DOM 的技术, 让你可以借助于现代浏览器的强大功能自由的对数据进行可视化。
点开官网,大大的 Data-Driven Documents ,这就是为什么叫做D3了。

D3基础

安装

D3的引入有两种,一种是:

<script src="https://d3js.org/d3.v5.min.js"></script>

或者是npm或yarn

npm install d3

D3之选择器

D3的选择器有两种,一种是select,一种是selectAll
d3.select():选择所有指定元素的第一个
d3.selectAll():选择指定全部元素

<div class="box"></div>
<div class="box"></div>
<div id="app"></div>

let a1 = d3.select('div')// 标签
let a2 = d3.select('.box')// class的第一个
let a3 = d3.selectAll('.box')// class的全部
let a4 = d3.select('#app')// id

D3之绑定数据

D3绑定数据有两种,一种是datum,通常绑定单个元素,一种是data,通常绑定多种元素
datum():绑定一个数据到选择集上
data():绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定

D3还支持向jquery一样的链式操作,比如:

var dataList = [1, 2, 3];

var container = d3.select("#app");
container.selectAll('p')
  .data(dataList)// 将数据每一项与元素绑定
  .text(function (d, i) {// d代表数据,i是数据的index编号,这里的d就是data()中绑定数据的各项
    return "第 " + i + " 个元素绑定的数据是: " + d;
  })
  .style("color",function (d,i) {// 通过d和i的判断,可以轻松的让每一项执行不同的操作
     if (d>2) {
          return "red"
     }
     if (i==0) {
          return "yellow"
     }
   }

D3之添加、删除元素

添加元素有两种,append(),insert(),删除元素是remove()
append():在选择集末尾插入元素

d3.select("body").append("p")

insert():在选择集前面插入元素

d3.select("body").insert("p","#second")

remove():删除元素

d3.select("#second").remove();

理解updata()、enter()、exit()

绑定数据的时候经常会出现数据与元素不匹配的情况,像数据一般动态请求过来,很难知道数据的个数而事前创建好元素,这时候就需要 enter 和 exit 来处理这个问题。enter 操作用来添加新的 DOM 元素,exit 操作用来移除多余的 DOM 元素
(1)数据数量 = 元素数量 此时就是update
(2)数据数量 > 元素数量 此时就是enter
(3)数据数量 < 元素数量 此时就是exit

updata指的是数据数量和元素相等,实际上并不存在这个函数,只是为了要与之后的 enter 和 exit 一起说明才想象有这样一个函数,元素正好满足时,后面直接操作即可。
enter是指数据数量大于元素数量,这个方法是最常用的一个方法,他可以让我们方便的构建出对应的元素而不用事先创建好,后面通常先跟 append() 操作

svg.selectAll("rect")   //选择svg内所有的矩形
    .data(dataList)     //绑定数据
    .enter()            //指定选择集的enter部分
    .append("rect")     //添加足够数量的矩形元素

exit 是指数据数量小于元素数量,通常是要删除元素,使之与数据相匹配,后面通常跟 remove() 操作

svg.selectAll("rect")   //选择svg内所有的矩形
    .data(dataList)     //绑定数据
    .exit()             //指定超出数据数量的元素部分
    .remove()           //将多余的删除

过渡

D3 支持动画效果,这种动画效果可以通过对样式属性的过渡实现。其补间插值支持多种方式,比如线性、弹性等。此外 D3 内置了多种插值方式,比如对数值类型、字符类型路径数据以及颜色等。

首先需要创建一个过渡对象。每个选择集中都有transition()方法,可用d3.select(“rect”).transition()的方式来创建过渡。
设定延迟的时间。过渡会经过一定时间后才开始发生。单位是毫秒。 transition.delay([delay])
设定过渡的持续时间(不包括延迟时间),单位是毫秒。 transition.duration([duration])
设定过渡样式,例如线性过渡、在目标处弹跳几次等方式。 transition.ease(vlaue[,arguments])

var svg = d3.select("#body")

svg.append("rect")
    .attr("fill","yellow")
    .attr("x",100)
    .attr("y",100)
    .attr("width",100)
    .attr("height",100)// 初始长宽是100
    .transition()// 设置过渡
    .duration(750)// 过渡持续时间
    .delay(function(d, i) { return i * 10; })// 过渡延迟,自定义,让每个元素过渡延迟不同
    .attr("width",300)
    .attr("height",300)// 过渡后长宽为300

svg基础

什么是SVG?
SVG 指可伸缩矢量图形 (Scalable Vector Graphics)
SVG 用来定义用于网络的基于矢量的图形
SVG 使用 XML 格式定义图形
SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
SVG 是万维网联盟的标准
SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体

SVG常用的标签有:
矩形 rect
圆形 circle
椭圆 ellipse
线 line
折线 polyline
多边形 polygon
路径 path

具体的大家可以网上查一查,今天主要用的是svg的矩形标签rect

D3之加载外部数据

D3有自己加载数据的方法
比如csv数据可以通过d3.csv(data,function) 进行操作
json同理,只需要将csv改为json

 d3.csv('./data.csv').then((result) => {
    var container = d3.select("#app");
    container.selectAll('h1')
      .data(result.columns)
      .enter()
      .append('h1')
      .text(function (d) {
        return d;
      })
 })

data.csv

10,20,30,40,50,60

D3之比例尺的使用

D3中有个重要的概念就是比例尺。比例尺就是把一组输入域映射到输出域的函数。映射就是两个数据集之间元素相互对应的关系。比如输入是1,输出是100,输入是5,输出是10000,那么这其中的映射关系就是你所定义的比例尺。

比例尺可以很方便的定义我们的数据的使用,而不用定死,我们可以定义比例尺,通过他的映射关系就可以很方便的转为正确对应的值了。

D3中有各种比例尺函数,有连续性的,有非连续性的,这里主要介绍两种常用的比例尺

d3.scaleLinear() 线性比例尺

使用d3.scaleLinear()创造一个线性比例尺,而domain()是输入域,range()是输出域,相当于将domain中的数据集映射到range的数据集中。

let scale = d3.scaleLinear().domain([1,5]).range([0,100])

映射关系:

定义好scale后,由此可见,可知
scale(1) // 输出:0
scale(4) // 输出:75
scale(5) // 输出:100

d3.scaleBand() 序数比例尺

d3.scaleBand()并不是一个连续性的比例尺,domain()中使用一个数组,不过range()需要是一个连续域。

let scale = d3.scaleBand().domain([1,2,3,4]).range([0,100])

映射关系:

由此可见:
scale(1) // 输出:0
scale(2) // 输出:25
scale(4) // 输出:75

d3.scaleBand()只针对domain()中的数据集映射相应的值。
这个比例尺可以很方便的让我们定义柱形图的x轴位置

颜色比例尺

D3提供了一些颜色比例尺,10就是10种颜色,20就是20种:

d3.schemeCategory10
d3.schemeCategory20
d3.schemeCategory20b
d3.schemeCategory20c

D3之坐标轴

坐标轴,是可视化图表中经常出现的一种图形,由一些列线段和刻度组成。坐标轴在 SVG 中是没有现成的图形元素的,需要用其他的元素组合构成。D3 提供了坐标轴的组件,如此在 SVG 画布中绘制坐标轴变得像添加一个普通元素一样简单。

D3提供了现成的坐标轴,d3.axisBottom(xScale)。d3.axisLeft(xScale)。其他方向同理
只需要将定义好的比例尺传进去,定义好坐标轴。

call() 函数,其参数是前面定义的坐标轴 axis。
创建一个元素,通过.call()放入定义好的坐标轴即可生成坐标轴

// 为坐标轴定义一个线性比例尺
var xScale = d3.scaleLinear()
    .domain([0, d3.max(dataset)])// d3.max()是内置的方法,可在传入的数组中取出最大值
    .range([0, 250]);
// 定义一个坐标轴
var xAxis = d3.axisBottom(xScale) //定义一个axis,由bottom可知,是朝下的
    .ticks(7); //设置刻度数目
// 生成坐标轴    
svg.append("g")
   .call(axis);

完整代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>D3柱状图</title>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <style>
    #wrap{
      width: 700px;
      height: 500px;
      margin: 0 auto;
    }
    .container {
      margin: 40px auto 0;
      display: block;
    }
    #prompt {
      width: 60px;
      height: 40px;
      border-radius: 4px;
      text-align: center;
      line-height: 40px;
      background: #666;
      color: #fff;
      display: none;
      position: absolute;
      left: 0;
      top: 0;
      z-index: 1;
      transition: all 0.1s;
    }
    .tool {
      margin-top: 20px;
      text-align: center;
    }
  </style>
</head>
<body>
  <div id="wrap">
    <div id="prompt"></div>
  </div>
  <hr/>
  <div class="tool">
    <button id="btn-sort">排序</button>
    <button id="btn-add">添加</button>
  </div>

<script>

  const WIDTH = 600;// 画布宽度
  const HEIGHT = 400;// 画布高度
  const PADDING = 30;// 画布四周空间

  // 模拟数据
  let dataList = [24, 36, 15, 8, 28, 18];

  // 排序标记
  let sort_flag = false;

  // 生成svg画布
  d3.select('#wrap')
    .append('svg')
    .classed('container', true)
    .attr('width', WIDTH + PADDING * 2)
    .attr('height', HEIGHT + PADDING *2)
    .style('background', '#f0f0f0')

  // 容器画布
  let container = d3.select('.container')

  // y线性比例尺
  let yScale = d3.scaleLinear()
    .domain([0, d3.max(dataList)])
    .range([HEIGHT, 0])// 坐标轴从下往上,所以反过来
  let yAxis = d3.axisLeft(yScale);

  // x序数比例尺
  let xScale = d3.scaleBand()
    .domain(d3.range(dataList.length))
    .range([0, WIDTH])
    .paddingInner(0.2)// 定义柱形之间的间隙
  let xAxis = d3.axisBottom(xScale);

  // y轴坐标轴展示
  container.append('g')
    .attr('id', 'yAxis')
    .attr('transform', 'translate(' + PADDING + ',' + PADDING + ')')
    .call(yAxis)

  // x轴坐标轴展示
  container.append('g')
    .attr('id', 'xAxis')
    .attr('transform', 'translate(' + PADDING + ',' + (HEIGHT + PADDING) + ')')
    .call(xAxis)

  // 柱状图容器
  let rectGroup = container.append('g').attr('id', 'rectGroup');
  // 文本容器
  let textGroup = container.append('g').attr('id', 'textGroup');

  // 加载rect
  function renderRect() {
    rectGroup.selectAll('rect')
      .data(dataList)
      .enter()
      .append('rect')
      .classed('rect', true)
      .on('click', (d)=>{// 添加点击提示的事件
        let x = d3.event.pageX;
        let y = d3.event.pageY;
        d3.select('#prompt')
          .style('display', 'block')
          .style('left', x + 'px')
          .style('top', y + 'px')
          .text(d)
      })

    container.selectAll('.rect')
      .attr('width', xScale.bandwidth())
      .attr('height', 0)
      .attr('x', (d, i)=>{
        return xScale(i) + PADDING
      })
      .attr('y', (d, i)=>{
        return HEIGHT + PADDING
      })
      .style('fill', 'skyblue')
      .transition()// 设置过渡
      .duration(300)
      .delay((d, i)=>{
        return i * 20
      })
      .attr('height', (d, i)=>{
        return HEIGHT - yScale(d)
      })
      .attr('y', (d, i)=>{
        return yScale(d) + PADDING
      })

  }

  // 加载文本
  function renderText() {
    textGroup.selectAll('text')
      .data(dataList)
      .enter()
      .append('text')
      .classed('text', true)

    textGroup.selectAll('text')
      .attr('text-anchor', 'middle')// 将文本中点设置为中心
      .text((d, i)=>{
        return d
      })
      .attr('x', (d, i)=>{
        return xScale(i) + xScale.bandwidth() / 2 + PADDING
      })
      .attr('y', HEIGHT + PADDING - 10)
      .transition()
      .duration(300)
      .delay((d, i)=>{
        return i * 20
      })
      .attr('y', (d, i)=>{
        return yScale(d) + PADDING - 10
      })
      .style('fill', (d, i)=>{
        if (d > 25) {// 大于25的文本显示为红色
          return 'red'
        }
      })
  }

  // 更新视图(修改数据时调用)
  function refresh() {
    // 更新比例尺
    yScale.domain([0, d3.max(dataList)])
    d3.select('#yAxis').call(yAxis);
    xScale.domain(d3.range(dataList.length))
    d3.select('#xAxis').call(xAxis);

    // 重新加载内容
    renderRect();
    renderText();
  }

  // 加载事件
  function initEvent() {
    // 离开时关闭提示
    d3.select('#wrap').on('mouseleave', ()=>{
      d3.select('#prompt').style('display', 'none')
    })

    // 点击排序
    d3.select('#btn-sort').on('click', ()=>{
      rectGroup.selectAll('rect')
        .sort((a, b)=>{// d3自带方法,升序降序
          return sort_flag ? d3.descending(a, b) : d3.ascending(a, b)
        })
        .transition()
        .duration(500)
        .attr('x', (d, i)=>{
          return xScale(i) + PADDING
        })

        textGroup.selectAll('text')
        .sort((a, b)=>{// d3自带方法,升序降序
          return sort_flag ? d3.descending(a, b) : d3.ascending(a, b)
        })
        .transition()
        .duration(500)
        .attr('x', (d, i)=>{
          return xScale(i) + xScale.bandwidth() / 2 + PADDING
        })

        sort_flag = !sort_flag;// 切换顺序
    })

    // 点击添加
    d3.select('#btn-add').on('click', ()=>{
      let randomNum = Math.ceil(Math.random() * 50)// 生成一个随机数字
      dataList.push(randomNum)// 放入数据

      refresh();// 更新视图
    })
  }

  // 视图初始化
  renderRect();
  renderText();
  initEvent();

</script>
</body>
</html>
发布了1 篇原创文章 · 获赞 5 · 访问量 61

猜你喜欢

转载自blog.csdn.net/mclryan/article/details/104444321