当前位置:   article > 正文

【D3使用教程】(5) 动态更新与过渡动画_d3.js 折线图clippath加了append("clippath)就卡顿

d3.js 折线图clippath加了append("clippath)就卡顿

在这里插入图片描述

数据总是在变化的,那么我们要如何将变化的数据反映到图表上呢?
在D3中,这些变化通过更新进行处理。而过渡通过使用动画用于处理视觉上的展示。

#(1)更新
首先,我们定义一个序数比例尺:
let xScale = d3.scale.ordinal()//定义一个序数比例尺,用于处理序数

注:序数是一些有固定顺序的一些类别,如:

  • 新生、大二、大三、毕业班
  • B等、A等、AA等
  • 非常不喜欢、不喜欢、没感觉、喜欢、非常喜欢

然后,为比例尺设定输入值的值域。在线性比例尺中,用包含两个值的数组来设置值域,如[0,100];而在序数比例尺中,值域是序数,不是线性或定量的数据。
如:

let xScale = d3.scale.ordinal()
               .domain(["新生","大二","大三","毕业班"])
  • 1
  • 2

但是,如果没有明确的类别,我们可以给每个数据点或条形分配一个其在数据集中对应位置的ID值,如0、1、2、3等等。

而在本例子中,我们使用

.domain(d3.range(dataset.length))
//相当于.domain([0,dataset.length]),若dataset.length为3,那么就是[0,1,2]
  • 1
  • 2

(2)自动分档

与线性比例尺使用的连续范围值不同,序数比例尺使用的是离散范围值,即输出值是事先确定好的,可以是数值,也可以不是。
在映射范围时,可以使用range(),也可以使用rangeBands()。后者接收一个最小值和一个最大值,然后根据输入值域的长度自动将其切分成相等的块域或“档”,如:
.rangeBands([0.w])//计算从0到w可以均分为几档,然后把比例尺的范围设定为这些“档”,例如有3档,那么w/3为每一档的“宽度”。还可以给rangeBands()传入第二个参数,指定档之间的间距。

rangeRoundBands()会对输出的值舍入为最接近的整数。如3.1415变成3。整数值有助于将视觉元素与像素网格对齐。

#(3)更新

到目前为止,我们的代码还是随着页面的加载执行。对于更新数据来说,可以在开始的绘制代码一执行完毕就更新,但这样更新太快。为了能看到更新的变化,需要把更新的代码与其他代码分开。因此,需要在页面加载之后添加一个“触发器”,用以触发数据和图表的更新。例如,使用鼠标点击事件。

  • 通过事件监听实现交互

首先在body中添加一个p标签,用于点击事件更新图表:

<p>Click on thie text to update the chart</p>
  • 1

接着在D3代码最后,添加D3的事件监听。

d3.select("p")
  .on("click",function() {//selection.on()方法是添加事件监听器的简便方法,接受两个参数:事件类型和监听器(匿名函数)
    //p标签被单击时执行的任务
    alert("Hey!");
  });
  • 1
  • 2
  • 3
  • 4
  • 5

接下来,我们需要改变数据,或者说更新数据。为此,需要:

  1. 重新绑定新数据与已有元素;
  2. 选择相应的图形,如散点、矩形,再调用一次data()方法; 例如这里,我们选择散点(圆形)为例:
  3. 最后更新视觉元素的属性,以反映更新后的数据值
dataset = [
      [5,20],[480,90],[250,50],[100,33],[330,95],[410,12],[475,44],[25,67],[85,21],[220,88]
      ];
   svg.selectAll("circle")
      .data(dataset); //重新绑定新数据
  • 1
  • 2
  • 3
  • 4
  • 5

我们将这三步的代码放到事件监听函数里面:

       d3.select("p")
          .on("click",function() {//selection.on()方法是添加事件监听器的简便方法,接受两个参数:事件类型和监听器(匿名函数)
          	//p标签被单击时执行的任务
          	//新数据集
	        dataset = [
	            [5,20],[480,90],[250,50],[100,33],[330,95],[410,12],[475,44],[25,67],[85,21],[220,88]
	            ];
	        //更新所有散点,注意到这里没有enter()和append()
	        svg.selectAll("circle")
	           .data(dataset) //重新绑定新数据
	           .attr("cx",function(d,i){
	                return xScale(d[0]); 
	           })
	           .attr("cy",function(d){
	               return yScale(d[1]);
	           })
	           .attr("r",function(d){
	               return rScale(d[1]);
	           });
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

最后点击p标签执行点击事件,更新数据。当然,如果图表上有标签或者颜色编码,你需要记得一并更新。
这里写图片描述
- 过渡动画

你是不是觉得更新数据的效果不够炫酷?
那么我们来认识下D3中提供的过渡动画—transition()

要创建一个过渡效果,只需要在更新时简单添加一行代码:

.transition()
  • 1

但是多少的持续时间是合适的呢?根据经验,细微的界面反馈(如鼠标悬停在元素上触发过渡),过渡时间大约150毫秒较合适,而更显著的视觉过渡(比如整个数据视图的变化)持续1000毫秒较合适。

除此之外,我们还可以设置过渡类型,D3中使用ease()指定不同的过渡类型,默认的效果的"cublic-in-out",另外还有"linear"线性类型。

对于ease()的使用,需要再transition()之后、attr()之前指定。当然,除了ease()还有circle()、elastic()、bounce()等函数用于处理过渡动画。

你可能还想设置动画的开始时间,delay(1000)或delay(function(){})可以设置。

#(4)完成代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        div.bar {
            display: inline-block;
            width: 20px;
            height: 75px;
            margin-right: 2px;
            background-color: teal;

        }
        .axis path,
        .axis line {
              fill: none;
              stroke:black;
              shape-rendering:crispEdges;
        }
        .axis text {
             font-size:11px;
        }
        p {width:300px;border:1px solid #ccc;border-radius: 3px;padding:5px;}
    </style>
</head>
<body>
	<p>Click on thie text to update the chart</p>
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
    <script src="https://d3js.org/d3.v3.js"></script>
    <script>
        //D3.js code
       let w = 800;
       let h = 200;
       let padding = 30;
       let svg = d3.select("body").append("svg").attr("width",w).attr("height",h);//把append()返回的新元素保存在了变量svg中


        // let dataset = [
        //     [5,20],[480,90],[250,50],[100,33],[330,95],[410,12],[475,44],[25,67],[85,21],[220,88]
        //     ];
       let dataset = [];
       let numDataPoints = 50;
       let xRange = Math.random() * 1000;
       let yRange = Math.random() * 1000;
       for(let i = 0;i<numDataPoints;i++) {
            let newNumber1 = Math.floor(Math.random()* xRange);
            let newNumber2 = Math.floor(Math.random()* yRange);
            dataset.push([newNumber1,newNumber2]);//初始化随机数据集
       }
       let xScale = d3.scale.linear()
                            .domain([0,d3.max(dataset,function(d){return d[0];})])
                            .range([padding,w-padding*2])
                            .nice();//nice()告诉比例尺取得为range()设置的任何值域,把两端的值扩展到最接近的整数。如[0.2000011166,0.99999943]优化为[0.2,1]
        let yScale = d3.scale.linear()
                             .domain([0,d3.max(dataset,function(d){return d[1];})])
                             .range([h-padding,padding])
                             .nice();
        let rScale = d3.scale.linear()
                             .domain([0,d3.max(dataset,function(d){return d[1];})])
                             .range([2,5])
                             .nice();          
        // 数轴
       let xAxis = d3.svg.axis()
                         .scale(xScale)
                         .orient("bottom")
                         .ticks(5); 
       let yAxis = d3.svg.axis()
                         .scale(yScale)
                         .orient("left")
                         .ticks(5);                        
        svg.selectAll("circle")
           .data(dataset)
           .enter()
           .append("circle")
           .attr("cx",function(d,i){
                return xScale(d[0]); //返回缩放后的值
           })
           .attr("cy",function(d){
               return yScale(d[1]);
           })
           .attr("r",function(d){
               return rScale(d[1]);
           });
           //添加标签     
            // svg.selectAll("text")
            // .data(dataset)
            // .enter()
            // .append('text')
            // .text(function(d){
            //     return d[0]+ "," + d[1];//设置标签内容
            // })
            // .attr({
            //     fill : "black",
            //     x : function(d) {return xScale(d[0])+10;},//将标签与散点位置一一对应
            //     y : function(d) {return yScale(d[1]);}
            // })
            // .style("font-size", "11px");
       //添加数轴
        svg.append("g")
           .attr("class","x axis")
           .attr("transform","translate(0,"+(h-padding)+")")
           .call(xAxis);
        svg.append("g")
           .attr("class","y axis")
           .attr("transform","translate("+padding+",0)")
           .call(yAxis);

        d3.select("p")
          .on("click",function() {//selection.on()方法是添加事件监听器的简便方法,接受两个参数:事件类型和监听器(匿名函数)
          	//p标签被单击时执行的任务
          	//新数据集
	        dataset = [
	            [5,20],[480,90],[250,50],[100,33],[330,95],[410,12],[475,44],[25,67],[85,21],[220,88]
	            ];
			
	        //更新所有散点,注意到这里没有enter()和append()
	        svg.selectAll("circle")
	           .data(dataset) //重新绑定新数据
	           .transition() //过渡动画
	           .duration(1000) //过渡动画持续时间 1s
	           .ease("linear")
	           .each("start",function(){//过渡开始
	           		d3.select(this)
	           		  .attr("fill","magenta")//改变颜色
	           		  .attr("r",3)//改变半径
	           })
	           .attr("cx",function(d,i){
	                return xScale(d[0]); 
	           })
	           .attr("cy",function(d){
	               return yScale(d[1]);
	           })
	           .each("end",function() {
	           	   d3.select(this)
	           	     .attr("fill","black")
	           	     .attr("r",2);
	           });
	           // .attr("r",function(d){
	           //     return rScale(d[1]);
	           // });
	        //更新比例尺值域
	        // yScale.domain([0,d3.max(dataset)]);
	        //更新x轴
	        svg.select('.x.axis')//选择数轴
	           .transition()//初始化一个过渡
	           .duration(1000)//设定过渡的持续时间
	           .call(xAxis);//调用适当的数轴生成器
	        //更新y轴
	        svg.select('.y.axis')
	           .transition()
	           .duration(1000)
	           .call(yAxis);

        });


    </script>
    <script type="text/javascript"></script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162

(5)剪切路径

你可能注意到,在散点图更新中,x和y值较低的圆形会超出图表区域的边界,与轴线重叠在一起。

在SVG中,支持剪切路径(clipping:path),就是PS中的蒙版。剪切路径是一个SVG元素,可以包含可见的元素,并与这个可见元素一起构成可以应用到其他元素的剪切路径或蒙版。在把蒙版应用到某个元素时,只有落在该蒙版内的像素才会显示。

与g元素类似,clipPath也不可见,但它可以包含可见的元素。

使用剪切路劲的步骤如下:

  1. 定义clipPath并给它一个ID
  2. 在这个clipPath中放一个可见元素,如一个矩形
  3. 在需要使用蒙版的元素上添加一个对clipPath的引用;
//定义剪切路径
        svg.append("clipPath") //创建clipPath元素
           .attr("id", "chart-area") //指定Id
           .append("rect") //在clipPath中,创建并添加新的rect元素
           .attr("x",padding) //设置rect的大小和位置
           .attr("y",padding)
           .attr("width",w-padding*3)
           .attr("height",h-padding*2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

现在需要把这个蒙版应用到所有散点上,可以分别给每个散点添加一个对该clipPath的引用。
我们先把所有圆形放到一个组g中,然后给这个组添加引用。

 svg.append("g")//对圆形编组
    .attr("id","circles")//指定它的id为circles
    .attr("clip-path","url(#chart-area)") //添加对clipPath的引用                   
    .selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr("cx",function(d,i){
        return xScale(d[0]); //返回缩放后的值
    })
    .attr("cy",function(d){
        return yScale(d[1]);
    })
    .attr("r",function(d){
        return rScale(d[1]);
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

如下图所示,我们建立了一个剪贴路径:
这里写图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/132664
推荐阅读
相关标签
  

闽ICP备14008679号