D3JsDay26圆圈图的实战力,直辖市人口比例-带入真实资料做圆圈图

昨天已经介绍了产生圆圈图的范例,今天将要带入真实资料作范例,接下来将会使用先前的县市人口数的资料制作环圈图,预计将会制作直辖市人口比例和非直辖市人口比例,滑鼠滑到该区域的时候可以看的到该县市的人数以及占整个圆的比例。

处理资料

我们一样先观看资料状况,这边取data.result.records的资料查看情况,主要观看观察site_id栏位,如下图

https://ithelp.ithome.com.tw/upload/images/20211011/2012509511Mad5WyZg.png

使用d3.group结合array.slice群组化资料

由於里面的值都是县市+乡镇划分又台湾的县市的名称都是以三个字为名,例如新北市、台南市、XX市等等,因此我们透过d3.group的分组方式取前三个字来当作划分依据,程序码如下

 d3.json("populationDensity.json")
    .then(function (data) {
      console.log(data);
      let districtData = d3.group(data.result.records,d=>{
        return d.site_id.slice(0, 3);
      })
    console.log(districtData);
     }

我们console.log(districtData)可以看到以下内容

https://ithelp.ithome.com.tw/upload/images/20211011/20125095kPYpnA7bYt.png

用Array.from将Map物件转换成Array

接下来使用Array.from转成阵列以便後续方便操作,如下列程序码

const districtAry = Array.from(districtData);

操作完应当如下图

https://ithelp.ithome.com.tw/upload/images/20211011/20125095G3sKUIoU95.png

清理资料

由於政府的资料有些属於可能人员输入时造成的错误或是像是东沙群岛等等并非这次要呈现的范畴,等等将使用array的操作方式稍作整理,原先内容如下图,

https://ithelp.ithome.com.tw/upload/images/20211011/20125095I9jAsVOlXi.png

使用array清理资料的程序码如下

districtAry.splice(-6);
districtAry.shift();

增加一个元素为该县市的人口数

我们要对该县市的乡镇市行政区加总,这边使用先前介绍过d3.sum()来进行。

首先我们对districtAry使用forEach遍历,将元素里面的阵列索引值1也就是乡镇市进行加总後,对该元素再增加一个索引值当作总人口数如下列第3行districtAry.forEach......所示

districtAry.forEach(function (el) {
    el.push(d3.sum(el[1], (d) => Number(d.people_total)));
});

使用console.log()应当可以看到增加了一个元素如下图

https://ithelp.ithome.com.tw/upload/images/20211011/20125095SJsnktYarC.png

增加一个元素写入是否为直辖市

我们先宣告一个municipality的阵列,把直辖市的县市给带进去阵列当中,然後判断如果districtAry里面的县市栏位内容与municipality相等的话就在districtAry存入一个value是"直辖市",否则就存入"非直辖市"
程序码如下

let municipality = ["台北市","新北市","台南市","高雄市","桃园市","台中市"];
  districtAry.forEach((districtAryEl) => {
      for (let i = 0; i < municipality.length; i++) {
        if (districtAryEl[0] == municipality[i]) {
          districtAryEl[3] = "直辖市";
          return;
        } else {
          districtAryEl[3] = "非直辖市";
        }
      }
    });

处理完毕

到目前为止应当可以看到如下图每笔阵列的value有内容也有一个阵列,这个阵列里面有四种资料,该元素里面是一个阵列,索引值0~3的内容如下图

https://ithelp.ithome.com.tw/upload/images/20211011/20125095oPxAOv5D6y.png

直辖市、非直辖市、全台人口数加总

为了得到直辖市和非直辖市的人口,我们需要有所有人数和直辖市人数以及非直辖市人数,为了重复使用我们宣告一个宣告一个加总的函式,加总的内容有直辖市、非直辖市和总台湾人口,详细程序码如下

const municipalitySumFun = (municipalityStr) =>
d3.sum(districtAry, (d) => {
    if (d[3] === municipalityStr) return d[2];
    if (municipalityStr==undefined) return d[2];
});
let districtArySum = municipalitySumFun();
let municipalitySum = municipalitySumFun("直辖市");
let NotMunicipalitySum = municipalitySumFun("非直辖市");

带入console.log()检查一下

console.log(districtArySum);
console.log(municipalitySum);
console.log(NotMunicipalitySum);

结果呈现如下图

https://ithelp.ithome.com.tw/upload/images/20211011/201250958Vm6GDNUWh.png

arc函式和pie函式

我们希望这次的图形是圆圈图,也就是空心的圆,中间的圆形部分可以当滑鼠移入某区域的时候得出该县市和人口比例与人口数的资料,因此宣告的arc()如下

const arc = d3.arc().innerRadius(50).outerRadius(100).cornerRadius(2);

这里要注意的地方是由於我们的资料是多维阵列,预计要带入的pie资料并非第一层阵列的索引值,而是第一层阵列元素里的阵列(第二层)的第2个索引值

如下图

https://ithelp.ithome.com.tw/upload/images/20211011/20125095yJkZqcIWqT.png

因此我们需要使用d3.pie().value()这个函式,官方说明如下

https://ithelp.ithome.com.tw/upload/images/20211011/201250957JT9933uu2.png

d3.pie().value()官方说明

换句话说也就是我们必须指定什麽资料要做成pie的开始角度、结束角度、和value物件等等

具体程序码如下

let pie = d3.pie().value(function (d) {
              return d[2];
        });
console.log(pie(districtAry));

显示出来的console.log如下图,应当可以看到转换後的值是来自於县市人口总数所构成的开始角度、结束角度等等的物件。

https://ithelp.ithome.com.tw/upload/images/20211011/20125095aVHanjyduj.png

开始绘制图形

我们这次选用d3.schemeTableau10的颜色组,将渲染的圆形放大2.5倍
顺带一提这次使用是Tableau的色系,Tableau也是先前D3JsDay02 学学D3JS 技能提高SSS—为什麽D3那一篇所介绍的资料视觉化软件。

具体程序码如下

let color = d3.scaleOrdinal(d3.schemeTableau10);
g.attr(
  "transform",
  `translate(${width / 2}, ${height / 2}) scale(2.5)`
)
g.attr(
      "transform",
      `translate(${width / 2}, ${height / 2}) scale(2.5)`
    )
g.selectAll("g")
    .data(d3.sort(pie(districtAry), (d) => d.index))
    .join("g")
    .append("path")
    .attr("stroke", "white")
    .attr("stroke-width", ".25")
    .attr("fill", (d, i) => color(d.data[3]))
    .attr("d",function(d){
    return arc(d);
    })

到目前为止应当可以看到一个具体的圆圈图呈现如下图
https://ithelp.ithome.com.tw/upload/images/20211011/20125095BUd1aU6y2M.png

添加动画

我们希望慢慢的绘制出整个圆形,可能你会想要使用下列的程序码进行动画绘制

g.selectAll("g")
.data(d3.sort(pie(districtAry), (d) => d.index))
//中间省略
//中间省略
//中间省略
.transition()
.ease(d3.easeLinear)
.duration(500)
.delay(function (d, i) {
return i*500;
})
.attr("d",function(d){
return arc(d);
})

但画面看起来不够柔顺

添加捕间动画

这里我们使用补间动画

transition().attrTween()来增加每个扇形区域进行绘制的时候要进行的动画

官方说明如下

https://ithelp.ithome.com.tw/upload/images/20211011/20125095Of5OBzbclQ.png

换句话说就是第二个参数要接收一个callback函式,里面要return一个插值器函数(interpolate),然後在根据时间**t(t将会介於0~1逐渐增加)**来对当前的元素内容修改值

我们直接观看程序码

//上面省略
.transition()
.ease(d3.easeLinear)
.duration(500)
.delay(function (d, i) {
return i*500;
})
.attrTween("d", function (d) {  //也就是设置每一个d的补间动画
    let i = d3.interpolate(d.startAngle, d.endAngle);
    return function (t) {
      d.endAngle = i(t);
      return arc(d);
    };
  });

d3.interpolate输入的参数是0~1
然後时间参数t是介於0~1的数字,我们宣告一个interpolate转换函式将时间参数转换成结束角度,在每t秒的时候的结束角度皆不同并且使用 arc(d)绘制出来。

最後结果应当会呈现如下图

计算百分比函式

最後我们要显示人口百分比,这里先宣告一个计算百分比的函式

function Percentage(num, total) {
    if (num == 0 || total == 0) {
      return 0;
    }
    return Math.round((num / total) * 10000) / 100.0;
  }

添加滑鼠滑入事件

接下来将插入滑鼠事件,selection.html()函式与众多的函式一样可以接收资料d,因此我们撰写程序码如下

g.selectAll("g")
.on("mouseenter", function (e) {
    let appendText = d3
      .select(this)
      .append("text")
      .style("font-size", "10px")
      .style("text-anchor", "middle")
      .html(d=>{
        return `<tspan x="0" y="-.5em">${d.data[0]}</tspan>
              <tspan x="0" y=".5em">${d.data[2]}人</tspan>
              <tspan x="0" y="1.5em">${Percentage(d.data[2],districtArySum)}%</tspan>`
  })
})
.on("mouseleave", function (e) {
    d3.select(this).select("text").remove();
});

上述程序码的第三个tspan就会将执行刚刚所宣告的Percentage()来计算百分比,另外tspany我们改用em来当作单位以便得知换行所需要的y值

最後补上直辖市和县辖市的人口比例在画面上就大功告成了,程序码如下

svg.append("g").html(`
        <text  y="${height-100}" style="font-size:50; transform:translate(-20%,0)">
            <tspan x="${width/2}">直辖市${Percentage(municipalitySum,districtArySum)}%</tspan>
            <tspan x="${width/2}"  dx="-0.5em"dy="1em">非直辖市${Percentage(NotMunicipalitySum,districtArySum)}%</tspan>
        </text>
        `)

最後呈现结果如下图

本日gitPage如下
gitPage


<<:  Day 27 | Unity游戏开发 - 对话介面管理器

>>:  Day 26 隐藏小知识

Day01 - 要做些什麽

一时兴起的参加了铁人赛,基本上对自己的期许只有完赛而已XD 这个系列会是一个非常松散、随便的系列。主...

2. 工程师不只是工程师

前言 我自己觉得这篇跟leadership比较没有关系,感觉蛮适合给正在寻找下阶段目标(但不一定要...

JavaScript Day16 - 箭头函式

函式陈述式与函式表达式 函式陈述式:之前直接定义 function 的方式 会被提升到最上面,所以可...

[day1]行动支付小小小优惠

IT铁人赛2021 [Day1] 金融支付API 大大大优惠,XX行动支付,现在推出认同卡,7891...

用 event 来准备传给後端的 data

今天来将画面做好,我们面对的资料长这样。 需求: 通常一个使用者的 form 表单是用在新增/编辑资...