D3库实践笔记之图表交互 |可视化系列36

2020-11-25 15:06:22 浏览数 (1)

对于前端可视化库来说,交互效果是其基本功能,需要有优雅的效果和简洁的API才能出彩,而如果一个前端可视化工具只能生成静态图表,绝对会显得格格不入,因为在前端拥有交互功能并不复杂。与图表的交互,是指图表元素能根据用户的键盘鼠标操作做出相应的反应,例如悬停高亮、缩放、漫游、拖动节点、点击涟漪效果等等。

对于HTML元素来说,要响应用户的行为,可以在图形元素上添加一个或多个事件监听器,当监测到对应行为时,执行某些响应代码。

事件监听器

JavaScript 有一个事件模型,在这个模型中,“事件”由发生的事情来触发,比如用户通过键鼠或触摸屏输入信息。大多数情况下,没人监听事件,事件就自生自灭,我们就无感知。而如果我们添加事件监听器后,触发对应的事件就能调用这个监听器的设置,具体来说就是执行某些代码。

D3的选择集有一个方法on(),用来设定事件的监听器。在可视化绘制时我们普遍用了var svg=d3.select("body").append("svg")或类似的代码,就可以使用以下代码给元素绑定事件监听器:

代码语言:javascript复制
var rect=svg.selectAll("rect").data(ds).enter().append("rect")
            .on("mouseover", function() {
                //触发事件后执行一些操作
                 d3.select(this).style("fill","#BA5C25");
                })
            .on("mouseout", function(d) {
                 d3.select(this).style("fill","#1EAFAE");
                });

以上代码可以给柱状图添加悬停高亮的交互效果,mouseover是事件名称,function()是监听器函数。当鼠标移动到某个柱子上时,触发一个mouseover事件,调用function()将d3所选中的柱的填充色修改为设置的颜色。演示如下:

d3交互之悬停高亮

为图表赋予交互能力只要两步:

•给选择集绑定事件监听器;•定义响应行为。

键鼠事件

在交互中最常见的行为当然要属鼠标触发的,经典的鼠标行为有单机、双击、选中拖动等。常用的事件如下:

•click:单击事件,鼠标单击某个元素触发,相当于mousedown和mouseup组合在一起;•dblclick:鼠标双击事件;•mouseover:鼠标的光标放在某元素上(悬停在元素上);•mouseout:光标从某元素上移出来时;•mousedown:鼠标按钮被按下;•mouseup:鼠标按钮被松开;

以下代码为图表标题添加了一个单击事件的监听器,当点击标题元素,会将标题加粗并在控制台输出当前标题文本;而如果当前是加粗的效果,点击后是变成非加粗文本,也就是点击会切换加粗和正常文本两种效果;

代码语言:javascript复制
// var svg=d3.select("body").append("svg")  等等
svg.append("text").attr("x",200).attr("y",20)
.text("D3绘制柱状图").on("click", function() {
    if (d3.select(this).style("font-weight")=="bold"){
        d3.select(this).text("D3绘制柱状图").style("font-weight","normal");
    }else{
        d3.select(this).text("D3绘制柱状图-click").style("font-weight","bold");
    }
    console.log(d3.select(this).text());//输出标题文本
    });

d3-click-title

键盘事件也很实用,特别是可以结合一些控制鼠标按键的自动化程序。键盘事件有三种:

•keydown:当用户按下任意键时触发,按住不放会重复触发此事件,这一事件不会区分字母的大小写,例如“A”和“a”被视为一致;•keypress:当用户按下字符键(大小写字母、数字、加号、等号、回车等)时触发,按住不放会重复触发此事件,该事件就会区分字母的大小写;•keyup:当用户松开按键时触发,该事件不区分字母的大小写;

keydown和keypress事件的区别在于keydown用于任意键的事件,而keypress用于字符键,如果只需要处理字母数字类的响应,或是要对大小写字母分别处理的时候,使用keypress;如果要处理上下左右(↑→)、Shift、Ctrl等特殊键的输入,使用keydown。

随着各种移动设备的普及,触屏有着广泛的使用场景,无论是我们的手机还是触屏的显示器,触屏离我们很近。常用的触屏事件有以下三种:

•touchstart:当触摸点被放在触摸屏上时,也就是触摸到某个元素;•touchmove:当触摸点在触摸屏上移动时;•touchend:当触摸点从触摸屏上拿开时;

我们可以为触摸事件配置点击事件以及拖动事件,也就是触摸有选中并拖动的效果。

缩放

通过d3.zoom().on("zoom", zoomed)配置缩放的交互,具体用法如下。需要说明的是在v3.x版本中是使用d3.behavior.zoom()创建缩放行为,而v5.x及之后的版本是d3.zoom(),不再有behavior这一层抽象;

给矩形和坐标轴添加缩放交互响应:

代码语言:javascript复制
var zoom = d3.zoom()
    .scaleExtent([0.1, 90])
    //.translateExtent([[-100, -100], [60  90, 60  100]])
    .on("zoom", zoomed);
    svg.call(zoom);
    function zoomed() {
            var rects=svg.selectAll("rect");
            rects.attr("transform", d3.event.transform);
            tt.attr("transform", d3.event.transform);
            gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
        }

绑定d3.zoom()的行为后,就具备了漫游的交互,zoom不仅仅可以放大缩小,还可以拖动元素进行漫游。

漫游是一种拖拽效果,但在力导向图等的交互中,我们希望有更纯粹的拖拽元素效果,因此d3也有d3.drag()用于创建拖拽行为。和zoom一样的,在v5.x版本中是使用d3.drag()而v3.x版本是使用d3.behavior.drag()。drag没有缩放功能。

drag和zoom一般通过call调用,写在svg.append("rect")语句中变成svg.append("rect").call(zoom),或者写svg.call(zoom)

代码语言:javascript复制
drag = simulation => {
  function dragstarted(event) {
    if (!event.active) simulation.alphaTarget(0.3).restart();
    event.subject.fx = event.subject.x;
    event.subject.fy = event.subject.y;
  }

  function dragged(event) {
    event.subject.fx = event.x;
    event.subject.fy = event.y;
  }

  function dragended(event) {
    if (!event.active) simulation.alphaTarget(0);
    event.subject.fx = null;
    event.subject.fy = null;
  }

  return d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);
};
var node = svg.append("g").attr("stroke", "#fff").attr("stroke-width", 1.5)
      .selectAll("circle").data(nodes).join("circle")
      .attr("r", 5).attr("fill", "#CAE9E0")
      .call(drag(simulation));

d3力导向图拖动效果

悬停文本标签

要实现鼠标悬停在图形元素上时显示其标签的tooltip效果,仍然使用选择集的on监听mouseover和mouseout事件,只是把响应的代码从修改选定的rect元素变成了增加文本标签元素,具体实现是可以选择加svg的<text>标签或者加HTML的<div>标签,按需使用。

代码语言:javascript复制
svg.selectAll("rect").data(dataset).enter().append("rect")
   .attr("class","bar").style("fill","#1EAFAE")
   .attr("x",function(d,i){return i*75 75  "px";})
   .attr("y",function(d){return (375-d*3.5)  "px";})
   .attr("width",50).attr("height",d=>d*3.5  "px")
   .on("mouseover", function() {
      d3.select(this).append("title").attr("id", "tooltip")
        .text(function(d) {
              return "Value of this bar: "   d;
         });
    })
   .on("mouseout", function(d) {
           d3.select("#tooltip").remove();
});

过渡动画

过渡动画同样通过事件监听和缓动实现过渡效果和数据更新,实现友好的交互;还有便是用好.transition(),在方法链上,要把transition的调用插到选择元素之后,改变任何属性之前。transition()默认情况延迟(delay)为0ms,持续时长(duration)为250ms,可以自行设置这两个参数。

例如对一个矩形的变换应用过渡效果:

代码语言:javascript复制
svg.append("rect")
.attr("fill","steelblue")
.attr("x",30)
.attr("y",30)
.attr("width",100)
.attr("height",30)
.transition() //在更新width之前调用
.attr("width",300);

和HTML元素交互

D3作为一个JavaScript库,自然可以和原生的HTML元素进行交互,例如响应按钮的点击事件,在html中配置了按钮和点击监测,<button type="button" onclick="update()"> 更新 </button>,点击按钮触发事件,在函数update里面调用d3的绘制代码,实现交互。

状态条是很实用的元素,通过状态条调节d3图表的参数,例如下面通过状态条调节绘制矩形的填充颜色,给状态条添加了onchange的事件监听器,有变化时更新矩形的颜色。

代码语言:javascript复制
//html中需要额外引入 <script src="https://unpkg.com/d3-simple-slider"></script>
var num2hex = rgb => {
    return rgb.map(color => {
        var s = color.toString(16);
        if (s.length === 1) {
          s= '0'   s;
        }
        return s;
      }).join('');
  };

var rgb = [0,175,174];
var colors = ['red', 'green', 'blue'];

var svg = d3.select('div#d3-slider').append('svg')
    .attr('width', 600).attr('height', 400).append('g')
    .attr('transform', 'translate(30,30)');
var box = svg
    .append('rect')
    .attr('width', 160)
    .attr('height', 100)
    .attr('transform', 'translate(400,30)')
    .attr('fill', `#${num2hex(rgb)}`);

rgb.forEach((color, i) => {
    var slider = d3
      .sliderBottom()
      .min(0)
      .max(255)
      .step(1)
      .width(300)
      .default(rgb[i])
      .displayValue(false)
      .fill(colors[i])
      .on('onchange', num => {
        rgb[i] = num;
        box.attr('fill', `#${num2hex(rgb)}`);
        d3.select('p#value-color').text(`#${num2hex(rgb)}`);
      });
    svg.append('g').attr('transform', `translate(30,${60 * i})`).call(slider);
});
d3.select('p#value-color').text(`#${num2hex(rgb)}`);

d3状态条改颜色

可视化结果输出

d3绘制的图像是svg或canvas对象,要将生成的可视化结果导出可以选择直接复制svg节点数据,从DOM里直接复制 SVG 代码,然后粘贴到文本文件里,命名为chart.svg,如果觉得麻烦可以用其他工具,导出的需求挺普遍,当然有大佬造了轮子,d3-downloadable[1]是一个JavaScript库,用于下载绘制的svg图形,在html里引入后,在JavaScript代码里加入svg.call(d3.downloadable({width:w,height:h,filename: "filename"}));就可以下载svg文件了。而如果只需要图片,就可以直接用截图工具截图保存,例如在写这些笔记时,自己大部分图片都是直接截图的,部分svg图形在DOM里直接复制出来粘到文本文件里。

总结

交互是JavaScript可视化库的基本功能,一些封装的基于前端的Python库也都实现了缩放漫游、悬停文本标签等交互功能。d3实现交互效果并不复杂,只需要对选择集使用on(),设定事件的监听器,在监听器里写交互的代码,定义响应的行为。基础可视化实现挺简单,而深度交互的内容很多,如更优雅的过渡和渐变效果、更深入的适应触摸设备交互、迷你图加入悬停框等等,在之后的具体实践中深入学习。

References

[1] d3-downloadable: https://github.com/likr/d3-downloadable

0 人点赞