D3JsDay13 让资料拥有过渡动画,让各位观众看见神话—过渡动画

过渡动画

transition这个翻译成过渡的意思,一个吸引人的图表当中,加入了一点动画成分和过渡的转变能吸引使用者的目光,也让观看者有预期心理画面将要转换,另外也使得图表更有质感。

首先我们先创建一个<rect>以宽50高300的长方形。

const svg = d3.select("body")
            .append("svg")
            .attr("width",800)
            .attr("height",800);
svg.append("rect")
    .attr("x",10)
    .attr("y",0)
    .attr("width",50)
    .attr("height",300)
    .attr("fill","orange");

transition()函式

官方文件如下图

https://ithelp.ithome.com.tw/upload/images/20210928/20125095V3ivJBpxnx.png

d3-transition官方API文件

这时候我们要考虑的地方是transition()要放在到哪个位置,依据官方的文件说明,前面要有一个selection,也就是你所选择的元素,後方需要带入要改变的样式,因此依据指示我们可以放在append()的後面

程序码如下

svg.append("rect")
    .attr("x",10)
    .attr("y",0)
    .attr("width",50)
    .attr("height",300)
    .attr("fill","orange")
    ;

此时当你按下浏览器的重新整理按钮的时候或是刚载入网页的时候应当会看到如下面这个范例
codepen范例如下
D3Day13-1范例


但出现的速度如此之快因此我们可以加入另一个函式来处理播放时间。

duration()和delay()函式

duration()添加至transtition()後面,以下范例设定3000毫秒,也就是3秒

程序码如下

 const svg = d3.select("body")
            .append("svg")
            .attr("width",600)
            .attr("height",600);
svg.append("rect")
.transition()
.duration(3000)
.attr("x",10)
.attr("y",0)
.attr("width",50)
.attr("height",300)
.attr("fill","orange");

codepen范例如下

D3day13-2


delay()函式放在transition()函式後面,主要的用途是要延迟几秒再执行的意思

codepen范例如下

D3day13-3

ease()函式系列

easeLinear()

这边主要是带入要呈现渐变时期的动画时的效果,里面带入参数第一个是指定你要的函式第二个是时间,具体用法程序码如下

svg.append("rect")
    .transition()
    .ease(d3.easeLinear,1)
    .duration(1500)
    .attr("x",10)
    .attr("y",0)
    .attr("width",50)
    .attr("height",300)
    .attr("fill","orange");

函数图形如下
https://ithelp.ithome.com.tw/upload/images/20210928/20125095avXX5rDvsi.png
这边带入的就是线性的动画,虽然官方文件写参数值带入t,但笔者观看原始码後线性所转回的值与原先是一样的情况,基本上你带入99所呈现的画面应当不会有所差别。

codepen范例如下

D3day13-4


Linear原始码如下

https://ithelp.ithome.com.tw/upload/images/20210928/20125095JQjBBWPrDz.png

官方easeLinear说明

官方Linear原始码

easePolyIn()

这边官方说明就有提到t数值会改变动画的效果
官方原文如下

https://ithelp.ithome.com.tw/upload/images/20210928/20125095BTy0OZOact.png
程序码如下

    svg.append("rect")
        .transition()
        .ease(d3.easePoly,4)
        .duration(5000)
        .attr("x",10)
        .attr("y",0)
        .attr("width",50)
        .attr("height",300)
        .attr("fill","orange")
        ;

codepen范例如下

D3day13-5


函数图型如下

https://ithelp.ithome.com.tw/upload/images/20210928/20125095UhvDHxwVjv.png

官方easePolyIn说明

这边仅介绍两个函数当范例
其他更多的函数可以参考官方API文件

easeAPI官方文件

值得注意的一点是有些函数并不适用於每个属性像是这个长条图有宽和高
因此如果你想尝试使用d3.easeElasticInOut()这个函数的话
由於函数图型有超出原本的区间,因此会造成宽高变成负值就会输出错误。

https://ithelp.ithome.com.tw/upload/images/20210928/20125095Q9K50gTOY7.png

另外D3的作者有发表关於其他范例,可以参考d3作者所用的范例,它所用的范例是改变位移的属性,因此也就能更加了解每个函式直接的差别。

范例如下

官方范例网址如下

D3作者的范例

长条图动画

原本的<rect>的程序码如下,延续我们之前的范例,假设我们希望画面载入的时候长条图由底部开始渐渐变长的话,该如何撰写呢?

svg.selectAll("rect")
    .data(newTaipei)
    .join("rect")
    .attr("x", (d, i) => {
        return padding + i * 60;
    })
    .attr("y", (d) => {
        return scaleY(d.people_total);
    })
    .attr("width", 50)
    .attr("height", (d) => {
        return 400 - scaleY(d.people_total);
    })
    .attr("fill", "orange");

由於transition方法链後面所带入的东西是你要改变的属性,因此势必我们得思考後面带入的东西是什麽?因为我们预计会改变长方形的高度,所以我们必须关注height属性,另外我们的svg的(0,0)座标是在左上角,如果只改变height属性的话,长方形的动画会从上方伸长到下方,因此我们也得改变y的起始点位置,这边svg的底部y的位置是400,所以在transition前面写下.attr("y", 400),高度从0开始伸长所以写下.attr("height", 0)

而我们原本的程序码是放在transition之後当作最後的结果,最後的程序码就如下面所示

svg.selectAll("rect")
            .data(newTaipei)
            .join("rect")
            .attr("x", (d, i) => {
              return padding + i * 60;
            })
            .attr("y", 400)
            .attr("height", 0)
            .attr("width", 50)
            .attr("fill", "orange")
            .transition()
            .duration(3000)
            .attr("y", (d) => {
              return scaleY(d.people_total);
            })
            .attr("height", (d) => {
              return 400 - scaleY(d.people_total);
            });

整个做出长条图从无到有的过程大概分成三个阶段

  1. 先制作出初始样貌放在transition前面
  2. 加入transition函式和delayduration等等函式
  3. 放在transition後面是最後要改变的属性或样式

本日完整程序码如下

  let newTaipei = taipei.map((el) => {
            el.people_total = Number(el.people_total);
            el.area = Number(el.area);
            el.population_density = Number(el.population_density);
            el.site_id = el.site_id.substr(3);
            return el;
          });

          let padding = 50;
          let svg = d3
            .select("body")
            .append("svg")
            .attr("width", 800)
            .attr("height", 450);
          let min = d3.min(newTaipei, (d) => d.people_total);
          let max = d3.max(newTaipei, (d) => d.people_total);
          console.log(newTaipei);
          let scaleY = d3.scaleLinear().domain([0, 320000]).range([400, 0]);

          svg.selectAll("rect")
            .data(newTaipei)
            .join("rect")
            .attr("x", (d, i) => {
              return padding + i * 60;
            })
            .attr("y", 400)
            .attr("height", 0)
            .attr("width", 50)
            .attr("fill", "orange")
            .transition()
            .duration(3000)
            .attr("y", (d) => {
              return scaleY(d.people_total);
            })
            .attr("height", (d) => {
              return 400 - scaleY(d.people_total);
            });

          svg.selectAll("text")
            .data(newTaipei)
            .join("text")
            .text((d) => {
              return d.site_id;
            })
            .attr("x", (d, i) => {
              return padding + i * 60;
            })
            .attr("y", (y) => {
              return 450 - 20;
            });

          let axisY = d3.axisRight(scaleY)
                        .ticks(5)
                        .tickFormat(function (d) {
                        return d / 10000 + "万";
                        });
          let g = svg.append("g");
          axisY(g);
        });

实际效果如下

页面如下
githubPage


<<:  好用的Decorator 如何在class中使用?

>>:  Day 13 Docker 安装

[Day 14] Leetcode 115. Distinct Subsequences (C++)

前言 今日挑战的题目是115. Distinct Subsequences,虽然是hard,但因为有...

Day19 简易资料库RealmSwift小实作6

提示框就不多说了! 最後就是按钮,判断是否编辑,是的话执行底下func执行完後按钮改回"输...

Day15 - 动态 新增/删除 Collection 项目(三) - Tag Helper

这篇调整的方向只是把上一篇的 Html Helper 改为 Tag Helper 而已 ! Case...

Day.23 Binary Search Tree

终於讲到树,快接近尾声了(烟 二元搜寻图(Binary Search Tree)是一种很高效的资料结...

JavaScript Day 5. 型别辨识 typeof

偶尔程序也会碰到需要辨别变数的状态,有时候是用於防止使用者输入奇怪的文字,有时候是帮助自己确认变数的...