知识图谱可视化前奏之d3.js

  • 2019 年 10 月 5 日
  • 筆記

知识图谱可视化前奏之d3.js

0.说在前面1.d3.js初识2.绘制完整的柱形图3.让图表动起来4.浅析Update、Enter、Exit5.交互式操作6.作者的话

0.说在前面

这两天一直在更机器学习及leetcode,今天来一篇知识图谱的核心知识,那就是数据可视化,可视化方面霸主地位的d3,从认识到绘图,你将学会d3基本操作以及前端可视化的套路。 让我们一起来感受d3的魅力吧!

1.d3.js初识

D3.js是一个用于根据数据操作文档的JavaScript库。D3可帮助您使用HTML,SVG和CSS将数据变为现实。D3强调Web标准,为您提供现代浏览器的全部功能,而无需将自己与专有框架联系起来,将强大的可视化组件和数据驱动方法结合到DOM操作中。

d3.js引用

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

选择器

<P>Hello World1</P>  <P>Hello World2</P>  <script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>  <script>      /*      选择器:d3.select()或d3.selectAll()      像下面d3.select().selectAll().text()这种称为链式语法      */      //d3.select("body").selectAll("p").text("Hello D3js")      var c = d3.select("body")            .selectAll("p")            .text("您好,d3.js!")      c.style("color","red")        .style("font-size","72px")    </script>  

选择器进阶

<p>Apple</p>  <p id="sec" class="twlas">Pear</p>  <p class="twlas">hah</p>  <h1>Banana</h1>  <script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>  <script type="text/javascript">      /*    1.选择所有的p      var p = d3.selectAll("p")    */      /*    2.选择第一个p      var p = d3.select("p")    */      /*    3.选择第二个p元素 给第二个设置个id,通过id查找      var p = d3.select("#sec")    */    /*    4.选择最后两个p元素 给最后两个设置相同的class,在查找时通过记得加.      var p = d3.selectAll(".twlas")      p.style("color","red")       .style("font-size","32px")       .style("font-weight","bold")    */    /*    5.通过function(d,i)设置第一和第三个style      p.style("color", function(d, i) {          if(i!=1) {              return "red"          }      })    */      //插入元素      var body = d3.select("body")      body.append("p")          .text("append p element")        //在id为sec前面插入一个p标签      body.insert("p","#sec")          .text("insert p element")          // 删除元素      var p = body.select("#sec")      p.remove()  </script>  

选择元素和绑定数据

<p>Apple</p>  <p>Pear</p>  <p>Banana</p>  <script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>  <script type="text/javascript">      /*          选择元素和绑定数据          选择元素              ---d3.select();d3.selectAll() 函数返回结果为选择集          绑定数据              ---datum() 绑定一个数据到选择集上              ---data()  绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定      */        var p = d3.select("body")                .selectAll("p")        //------datum()练习------        var str = "China"      p.datum(str)       .text(function(d, i){          return "第 " + (i+1) + " 个元素绑定的数据是 " + d      })      /*          第 1 个元素绑定的数据是 China          第 2 个元素绑定的数据是 China          第 3 个元素绑定的数据是 China      */      //------data()练习------      var dataset = ["I like dogs", "I like cats", "I like snakes"]      p.data(dataset)       .text(function(d, i) {          return "第 " + (i+1) + " 个元素绑定的数据是 " + d       })       /*          第 1 个元素绑定的数据是 I like dogs          第 2 个元素绑定的数据是 I like cats          第 3 个元素绑定的数据是 I like snakes       */  </script>  

绘制柱形图

<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>  <script type="text/javascript">      var width = 300      var height = 300      var svg = d3.select("body")                  .append("svg")                  .attr("width",width)                  .attr("height",height)      var dataset = [250, 210, 170, 130, 90];  //数据(表示矩形的宽度)      var rectHeight = 25;   //每个矩形所占的像素高度(包括空白)        //在 SVG 中,x 轴的正方向是水平向右,y 轴的正方向是垂直向下的      svg.selectAll("rect")       .data(dataset) //绑定数组       .enter() //指定选择集的enter部分       .append("rect") // 添加足够数量的举行元素       .attr("x",20)       .attr("y",function(d,i){           return i * rectHeight;       })       .attr("width",function(d){           return d;       })       .attr("height",rectHeight-2) //减去2表示每个柱子之间留空白       .attr("fill","#09F");       /*          svg.selectAll("rect")   //选择svg内所有的矩形             .data(dataset)  //绑定数组             .enter()        //指定选择集的enter部分             .append("rect") //添加足够数量的矩形元素          当有数据,而没有足够图形元素的时候,使用此方法可以添加足够的元素。       */  </script>  

比例尺

<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>  <script type="text/javascript">    /*    var dataset = [ 250 , 210 , 170 , 130 , 90 ];    绘图时,直接使用 250 给矩形的宽度赋值,即矩形的宽度就是 250 个像素。此方式非常具有局限性,如果数值过大或过小。于是,我们需要一种计算关系,能够:将某一区域的值映射到另一区域,其大小关系不变。这就是比例尺(Scale)。    */    /*    1.线性比例尺    线性比例尺,能将一个连续的区间,映射到另一区间。要解决柱形图宽度的问题,就需要线性比例尺。    */    var  dataset = [1.2, 2.3, 0.9, 1.5, 3.3]    var min = d3.min(dataset)    var max = d3.max(dataset)    /*      比例尺的定义域 domain 为:[0.9, 3.3]      比例尺的值域 range 为:[0, 300]    */    var linear = d3.scaleLinear()             .domain([min,max])             .range(0,300)    linear(0.9);    //返回 0    console.log(linear(2.3));    //返回 175    console.log(linear(3.3));    //返回 300      /*    2.序数比例尺    有时候,定义域和值域不一定是连续的    */    var index = [0, 1, 2, 3, 4];    var color = ["red", "blue", "green", "yellow", "black"];      var ordinal = d3.scaleOrdinal()            .domain(index)            .range(color);      ordinal(0); //返回 red    ordinal(2); //返回 green    ordinal(4); //返回 black  </script>  

给柱状图添加比例尺

<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>  <script>  var width = 300;    //画布的宽度  var height = 300;    //画布的高度  var svg = d3.select("body")                //选择文档中的body元素        .append("svg")                //添加一个svg元素        .attr("width", width)        //设定宽度        .attr("height", height);    //设定高度  var dataset = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];  var linear = d3.scaleLinear()          .domain([0, d3.max(dataset)])          .range([0, 250]);  var rectHeight = 25;    //每个矩形所占的像素高度(包括空白)  svg.selectAll("rect")      .data(dataset)      .enter()      .append("rect")      .attr("x",20)      .attr("y",function(d,i){        return i * rectHeight;      })      .attr("width",function(d){          return linear(d);      })      .attr("height",rectHeight-2)      .attr("fill","steelblue");  </script>  

svg中添加坐标轴

/*第一种方式*/  var xaxis = d3.axisBottom(linearx)        .ticks(7);            //指定刻度的数量  svg.append("g")     .call(xaxis);  /*第二种方式*/  function xaxis(selection) {    selection        .attr("name1", "value1")        .attr("name2", "value2");  }  foo(d3.selectAll("div"))  //因此  xaxis(svg.append("g"))  

添加x与y坐标轴

...  <head>      ...      <style type="text/css">          .xaxis,.yaxis text {              font-family: sans-serif;              font-size: 10px;          }      </style>  </head>  <body>      <script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>      <script type="text/javascript">          var width = 300;    //画布的宽度          var height = 300;   //画布的高度          var svg = d3.select("body")             //选择文档中的body元素                      .append("svg")              //添加一个svg元素                      .attr("width", width)       //设定宽度                      .attr("height", height);    //设定高度          var datasetx = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];            var linearx = d3.scaleLinear()                  .domain([0, d3.max(datasetx)])                  .range([0, 250]);          var lineary = d3.scaleLinear()                  .domain([0, 125])                  .range([0, 125]);            var rectHeight = 25;    //每个矩形所占的像素高度(包括空白)          datasety = [25,50,75,100,125]            svg.selectAll("rect")              .data(datasetx)              .enter()              .append("rect")              .attr("x",30)              .attr("y",function(d,i){                  return i * rectHeight;              })              .attr("width",function(d){                  return linearx(d);              })              .attr("height",rectHeight-2)              .attr("fill","steelblue");        //若axisBottom改为axisTop()则横坐标的ticks朝上          var xaxis = d3.axisBottom(linearx)                      .ticks(7);          //指定刻度的数量          var yaxis = d3.axisLeft(lineary)                      .tickValues(datasety)                      .ticks(7)      //在 SVG 中添加一个分组元素 <g>,再将坐标轴的其他元素添加到这个 <g> 里即可          svg.append("g")              .attr("class","xaxis")              .attr("transform","translate(30,0)")              .call(xaxis);          svg.append("g")              .attr("class","yaxis")              .attr("transform","translate(30,-2)")              .call(yaxis);      </script>  </body>  </html>  

原理分析

<!--通过以上代码,在谷歌浏览器上可以看出svg里面  就添加好坐标轴的分组g元素,里面又含有line与text元素,  分组元素<g>,是 SVG 画布中的元素,意思是 group。  此元素是将其他元素进行组合的容器,在这里是用于将坐标轴的其他元素分组存放。如果需要手动添加这些元素就太麻烦了,为此,D3 提供了一个组件:d3.axisBottom()。它为我们完成了以上工作。-->  <g>  <!-- 第一个刻度 -->  <g>  <line></line>   <!-- 第一个刻度的直线 -->  <text></text>   <!-- 第一个刻度的文字 -->  </g>  <!-- 第二个刻度 -->  <g>  <line></line>   <!-- 第二个刻度的直线 -->  <text></text>   <!-- 第二个刻度的文字 -->  </g>  ...  <!-- 坐标轴的轴线 -->  <path></path>  </g>  

scaleOrdinal使用

//在上述代码中添加下面即可  var xTexts = [ "我", "你", "他" ];  var oridnal = d3.scaleOrdinal()                  .domain(xTexts)                  .range([0,100,250])  

2.绘制完整的柱形图

<body>      <script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>      <script type="text/javascript">          //画布大小      var width = 400;      var height = 400;        //在 body 里添加一个 SVG 画布      var svg = d3.select("body")          .append("svg")          .attr("width", width)          .attr("height", height);        //画布周边的空白      var padding = {left:20, right:30, top:50, bottom:20};        //定义一个数组      var dataset = [10, 20, 30, 40, 33, 24, 12, 5];        //x轴的比例尺      var xScale = d3.scaleBand()          .domain(d3.range(dataset.length))          .rangeRound([0, width - padding.left - padding.right]);        //y轴的比例尺      var yScale = d3.scaleLinear()          .domain([0,d3.max(dataset)])          //[height - padding.top - padding.bottom, 0]这样可以使得y轴正方向向上          .range([ 0,height - padding.top - padding.bottom]);        //定义x轴      var xAxis = d3.axisTop(xScale)      //定义y轴      var yAxis = d3.axisLeft(yScale)        //矩形之间的空白      var rectPadding = 4;        //添加矩形元素      var rects = svg.selectAll(".MyRect")          .data(dataset)          .enter()          .append("rect")          .attr("class","MyRect")      //          .attr("transform","translate(" + padding.left + "," + padding.top + ")")          /*          柱子绘制位置          */          .attr("x", function(d,i){              return xScale(i) + rectPadding/2;          } )          .attr("y",function(d){              return 0;          })          .attr("width", xScale.bandwidth() - rectPadding)          .attr("height", function(d){              return yScale(d);          });        //添加文字元素      var texts = svg.selectAll(".MyText")          .data(dataset)          .enter()          .append("text")          .attr("class","MyText")          .attr("transform","translate(" + padding.left + "," + padding.top + ")")          .attr("x", function(d,i){              return xScale(i) + rectPadding/2;          } )          .attr("y",function(d){              return yScale(d);          })          .attr("dx",function(){              return (xScale.bandwidth() - rectPadding)/2;          })          .attr("dy",function(d){              return 20;          })          .text(function(d){              return d;          });        //添加x轴      svg.append("g")          .attr("class","axis")          .attr("transform","translate(" + padding.left + "," + padding.top + ")")          .call(xAxis);        //添加y轴      svg.append("g")          .attr("class","axis")          .attr("transform","translate(" + padding.left + "," + padding.top + ")")          .call(yAxis);      </script>  </body>  

图形修改及润色

<!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Document</title>      <style type="text/css">            .MyText {              fill: #37C743FF;              text-anchor: middle;          }          /*css修改柱子颜色*/          .MyRect {              fill: steelblue;          }      </style>  </head>  <body>      <script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>      <script type="text/javascript">          //画布大小      var width = 400;      var height = 400;        //在 body 里添加一个 SVG 画布      var svg = d3.select("body")          .append("svg")          .attr("width", width)          .attr("height", height);        //画布周边的空白      var padding = {left:20, right:30, top:20, bottom:20};        //定义一个数组      var dataset = [10, 20, 30, 40, 33, 24, 12, 5];        //x轴的比例尺      var xScale = d3.scaleBand()          .domain(d3.range(dataset.length))          .rangeRound([0, width - padding.left - padding.right]);        //y轴的比例尺      var yScale = d3.scaleLinear()          .domain([0,d3.max(dataset)])          //[height - padding.top - padding.bottom, 0]这样可以使得y轴正方向向上          .range([height - padding.top - padding.bottom, 0]);        //定义x轴      var xAxis = d3.axisBottom(xScale)      //定义y轴      var yAxis = d3.axisLeft(yScale)        //矩形之间的空白      var rectPadding = 4;        //添加矩形元素      var rects = svg.selectAll(".MyRect")          .data(dataset)          .enter()          .append("rect")          .attr("class","MyRect")          .attr("transform","translate(" + padding.left + "," + padding.top + ")")            .attr("x", function(d,i){              return xScale(i) + rectPadding/2;          } )          .attr("y",function(d){              return yScale(d);          })          .attr("width", xScale.bandwidth() - rectPadding)          .attr("height", function(d){              //y轴朝上写法与上述y轴比例尺的.range([height - padding.top - padding.bottom, 0])配合使用              return height - padding.top - padding.bottom - yScale(d);          });        //添加文字元素      var texts = svg.selectAll(".MyText")          .data(dataset)          .enter()          .append("text")          .attr("class","MyText")          .attr("transform","translate(" + padding.left + "," + padding.top + ")")          //text的x          .attr("x", function(d,i){              return xScale(i) + rectPadding/2;          } )          //text的y          .attr("y",function(d){              return yScale(d);          })          //dx为text的位移,向左为负,向右为正          .attr("dx",function(){              return (xScale.bandwidth() - rectPadding)/2 ;          })          //根据图形自己调整,dy为text相对于柱子的位移,向下为正,向上为负号          //当y坐标向下时,为默认情况,此时这里dy为正值时,则正常显示,但当y坐标为上,由于height - padding.top - padding.bottom - yScale(d),此时会出现覆盖情况,text不显示,需手动调整          .attr("dy",function(d){              return -5;          })          .text(function(d){              return d;          });        //添加x轴      svg.append("g")          .attr("class","axis")          .attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")          .call(xAxis);        //添加y轴      svg.append("g")          .attr("class","axis")          .attr("transform","translate(" + padding.left + "," + padding.top + ")")          .call(yAxis);      </script>  </body>  </html>  

3.让图表动起来

图表动起来

动态的图表,是指图表在某一时间段会发生某种变化,可能是形状、颜色、位置等,而且用户是可以看到变化的过程的。  transition() 启动过渡效果  duration() 指定过渡的持续事件 单位为毫秒  ease() 指定过渡的方式 d3.easeBounce d3.easeLinear等  调用:ease(d3.easeBounce)  delay() 指定延迟的时间,表示一定时间后才开始转变,单位同样为毫秒。此函数可以对整体指定延迟,也可以对个别指定延迟。  对整体指定时:    .transition()    .duration(1000)    .delay(500)  如此,图形整体在延迟 500 毫秒后发生变化,变化的时长为 1000 毫秒。因此,过渡的总时长为1500毫秒。  又如,对一个一个的图形(图形上绑定了数据)进行指定时:  .transition()  .duration(1000)  .delay(funtion(d,i){      return 200*i;  })  如此,假设有 10 个元素,那么第 1 个元素延迟 0 毫秒(因为 i = 0),第 2 个元素延迟 200 毫秒,第 3 个延迟 400 毫秒,依次类推….整个过渡的长度为 200 * 9 + 1000 = 2800 毫秒。  

为上述图形添加动态效果

//添加矩形元素      var rects = svg.selectAll(".MyRect")          .data(dataset)          .enter()          .append("rect")          .attr("class","MyRect")          .attr("transform","translate(" + padding.left + "," + padding.top + ")")            .attr("x", function(d,i){              return xScale(i) + rectPadding/2;          } )            .attr("width", xScale.bandwidth() - rectPadding)          .attr("y",function(d){              var min = yScale.domain()[0];              return yScale(min);          })          .attr("height", function(d){              return 0;          })          .transition()          .delay(function(d,i){              return i * 200;          })          .duration(2000)          .ease(d3.easeBounce)          .attr("y",function(d){              return yScale(d);          })          .attr("height", function(d){              return height - padding.top - padding.bottom - yScale(d);          });          //添加文字元素      var texts = svg.selectAll(".MyText")          .data(dataset)          .enter()          .append("text")          .attr("class","MyText")          .attr("transform","translate(" + padding.left + "," + padding.top + ")")          //text的x          .attr("x", function(d,i){              return xScale(i) + rectPadding/2;          } )          //dx为text的位移,向左为负,向右为正          .attr("dx",function(){              return (xScale.bandwidth() - rectPadding)/2 ;          })          //根据图形自己调整,dy为text相对于柱子的位移,向下为正,向上为负号          //当y坐标向下时,为默认情况,此时这里dy为正值时,则正常显示,但当y坐标为上,由于height - padding.top - padding.bottom - yScale(d),此时会出现覆盖情况,text不显示,需手动调整          .attr("dy",function(d){              return -5;          })            //text的y          .attr("y",function(d){              //获取y的最小值              var min = yScale.domain()[0];              return yScale(min);          })          .transition()          .delay(function(d,i) {              return i*200          })          .duration(2000)          .ease(d3.easeCubic)          .attr("y",function(d){              return yScale(d)          })          //必须放在最后,否则报错!          .text(function(d){              return d;          });  

4.浅析Update、Enter、Exit

what is Update and Enter?

如果数组为 [3, 6, 9, 12, 15],将此数组绑定到p的选择集上。  以下分为两种:    -第一种:数组元素(数据)大于p标签元素个数    -第二种:数组元素(数据)小于p标签元素个数    第一种情况中会有几个数组元素没有对应的p标签元素,此时这部分称为enter,而有数据与p元素相对应的称为update。    第二种情况中会有几个空余的p元素未能与数据相对应,此时没有数据绑定的部分被称为 Exit。  

Update和Enter使用

给定一个元素个数为6的数组,3个p标签,分别处理Update与Enter

<p></p>  <p></p>  <p></p>  <script type="text/javascript" src="d3.min.js" charset="utf-8"></script>  <script type="text/javascript">      var dataset = [3,6,9,15,18,20]      // 选择body中的p元素      var p =d3.select("body")               .selectAll("p")      //获取enter部分      var update = p.data(dataset)      //update部分的处理:更新属性值      update.text(function(d,i) {          return "index " + i +" update " + d      })      var enter = update.enter()      //enter部分的处理:添加元素后赋予属性值      enter.append("p")           .text(function(d, i){              return "index " + i +" enter " + d      });  </script>  

output

index 0 update 3  index 1 update 6  index 2 update 9  index 3 enter 15  index 4 enter 18  index 5 enter 20  

Update 和 Exit 的使用

当对应的元素过多时 ( 绑定数据数量 < 对应元素 ),需要删掉多余的元素。

<p></p>  <p></p>  <p></p>  <script type="text/javascript" src="d3.min.js" charset="utf-8"></script>  <script type="text/javascript">      var dataset = [3];      //选择body中的p元素      var p = d3.select("body").selectAll("p");      //获取update部分      var update = p.data(dataset);      //获取exit部分      var exit = update.exit();      //update部分的处理:更新属性值      update.text(function(d){          return "update " + d;      });      //exit部分的处理:修改p元素的属性      exit.text(function(d){              return "exit";          });      //exit部分的处理通常是删除元素      // exit.remove();  </script>  

output

update 3  exit  exit  

5.交互式操作

用户用于交互的工具一般有三种:鼠标、键盘、触屏

//添加矩形元素  var rects = svg.selectAll(".MyRect")      .data(dataset)      .enter()      .append("rect")      .attr("class","MyRect")   //把类里的 fill 属性清空      .attr("transform","translate(" + padding.left + "," + padding.top + ")")      .attr("x", function(d,i){          return xScale(i) + rectPadding/2;      } )      .attr("y",function(d){          return yScale(d);      })      .attr("width", xScale.bandwidth() - rectPadding )      .attr("height", function(d){          return height - padding.top - padding.bottom - yScale(d);      })      .attr("fill","steelblue")       //填充颜色不要写在CSS里      .on("mouseover",function(d,i){          console.log("MouseOver");          d3.select(this)              .attr("fill","yellow");      })      .on("click",function (d,i) {          console.log("Click!");      })      .on("mouseout",function(d,i){          console.log("MouseOut")          d3.select(this)              .transition()              .duration(500)              .attr("fill","steelblue");      });