Day21-D3 基础图表:散点图/散布图

本篇大纲:基本散布图范例、进阶散布图范例

今天的一天一图表,我们要来画 散点图 / 散布图!散布图适合用在以下几种情况:

  • 了解资料的分布状况
  • 比较不同资料的分布状况

https://ithelp.ithome.com.tw/upload/images/20211003/20134930WoXFkv33Fb.jpg

除了呈现资料之外,也可以加上一些互动效果

  • 滑鼠点击时加上点点
  • hover 时圆点变色+呈现座标

https://i.imgur.com/aVB7BQG.gif

今天我们就用这两个范例来实际绘制散点图吧!


基本散点图范例

我们先来建立一个最基本的散布图。一般来说,这种散布图的资料都是 raw data,就是几百几单一、没有经过统计的资料。这样一来才能将所有的单笔资料变成一个点,并於散布图呈现所有点点的分布情况。

我们今天使用的资料是 这个 ,确定资料後就可以开始画图啦!首先,我们先用async await 的方法串接API,拿到我们的资料;接着起手式一样是建立RWD的svg图表

// css
.scatter1 {
      margin: auto;
      width: 80%;
      min-width: 300px;
      margin: auto;
    }
// html
<div class="scatter1 m-auto"></div>
// 先取资料
async function getData(){
  let dataGet;

  const cors = "https://secret-ocean-49799.herokuapp.com/";
  const url = 'https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/2_TwoNum.csv'

  dataGet = await d3.csv(`${cors}${url}`);
  scatter1(dataGet)
}
getData()

// 建立图表
function scatter1(data){
  // RWD 清除原本的图型
  d3.select(".scatter1").select('svg').remove()

  // 建立 svg
  const svgWidth = parseInt(d3.select('.scatter1').style('width')),
        svgHeight = svgWidth
        margin = 50
  const svg = d3.select('.scatter1')
    .append('svg')
    .attr('width', svgWidth)
    .attr('height', svgHeight)

// 接下来的程序码...
// 接下来的程序码...
// 接下来的程序码...
}

// RWD
d3.select(window).on("resize", scatter1());

建立好 svg 後,我们开始绘制比例尺跟XY座标轴

// 建立X轴线
const xScale = d3.scaleLinear()
       .domain([0,4000])
       .range([0, (svgWidth - margin*2)])

const xAxis = d3.axisBottom(xScale)
                

svg.append('g')
   .attr('transform', `translate(${margin}, ${svgHeight - margin/2})`)
   .call(xAxis)

// 建立Y轴线
console.log(data)
const yScale = d3.scaleLinear()
       .domain([0,500000])
       .range([(svgHeight - margin), 0])

const yAxis = d3.axisLeft(yScale)    
                .tickFormat(d=>'$'+d) 

svg.append('g')
   .attr('transform', `translate(${margin}, ${margin/2})`)
   .call(yAxis)

最後我们把资料绑定到 < circle >上

// 加上点点
svg.append('g')
   .selectAll('dot')
   .data(data)
   .enter()
     .append('circle')
     .attr('cx', d => xScale(d.GrLivArea))
     .attr('cy', d => yScale(d.SalePrice))
     .attr('r', 1.5)
     .style('fill', '#69b3a2')

这样就完成啦!
https://ithelp.ithome.com.tw/upload/images/20211003/20134930D3eX5tqNd6.jpg

如果我们想把特定范围的资料设定成不同颜色,也可以这样做

// 加上点点
svg.append('g')
   .selectAll('dot')
   .data(data)
   .enter()
     .append('circle')
     .attr('cx', d => xScale(d.GrLivArea))
     .attr('cy', d => yScale(d.SalePrice))
     .attr('r', 1.5)
   // 将大於129000的点点都变成粉红色
     .style('fill', d => {
       return d.SalePrice > 129000? 'pink':'#69b3a2' 
     })

图表就变成这样!
https://ithelp.ithome.com.tw/upload/images/20211003/20134930xZlXOUqEsG.jpg

这样就完成啦!散布图是不是很简单呢~接着我们再看看范例二的图表吧!


范例二的画面与互动效果

范例二画面与互动效果有:

  • XY 座标轴以及 < circle > 画出的散布圆点,比例尺可以用 scaleLinearscalePoint 制作
  • 滑鼠滑过跟移开时,圆点变色(mouseover & mouseleave )
  • 滑鼠 hover 时呈现圆点座标 (d3.pointer)
  • 滑鼠点击时,加上新的圆点

我们就按照这些列出效果来绘制散点图吧!首先,我们先建立 svg 跟座标轴

// css
.scatter1 {
      margin: auto;
      width: 80%;
      min-width: 300px;
      margin: auto;
    }
// html
<div class="scatter2 m-auto"></div>
function scatter2(){
// 建立svg
let w = parseInt(d3.select(".scatter2").style("width")),
    h = w*0.8,
    margin = {
        top: 40,
        right: 20,
        bottom: 20,
        left: 40
        },
    radius = 5;

let svg = d3.select(".scatter2").append("svg").attr('width', w).attr('height', h);
console.log(w, h)

let dataset = [
    { x: 100,
      y: 110 },
    { x: 83,
      y: 43 },
    { x: 92,
      y: 28 },
    { x: 49,
      y: 74 },
    { x: 51,
      y: 10 },
    { x: 25,
      y: 98 },
    { x: 77,
      y: 30},
    { x: 20,
      y: 83 },
    { x: 11,
      y: 63 },
    { x: 4,
      y: 55 },
    { x: 0,
      y: 0 },
    { x: 85,
      y: 100 },
    { x: 60,
      y: 40 },
    { x: 70,
      y: 80 },
    { x: 10,
      y: 20 },
    { x: 40,
      y: 50 },
    { x: 25,
      y: 31 }
];

// 接下来的程序码写在这边...
// 接下来的程序码写在这边...
// 接下来的程序码写在这边...

}

// RWD
d3.select(window).on("resize", scatter2());

接着建立比例尺与座标轴

// 建立X比例尺与轴线
const xScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function (d) {
        return d.x + 10;
    })])
    .range([margin.left, w - margin.right]);

const xAxis = d3.axisTop()
    .scale(xScale)

const xAxisLine = svg.append('g')
    .attr('class', 'xAxis')
    .attr('transform', `translate(0, ${margin.top})`)
    .call(xAxis)

// 建立Y比例尺与轴线
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) {
    return d.y + 10;
})])
.range([margin.top, h - margin.bottom])

const yAxis = d3.axisLeft()
    .scale(yScale)

const yAxisLine = svg.append('g')
    .attr('class', 'yAxis')
    .attr('transform', `translate(${margin.left}, 0)`)
    .call(yAxis)

建立好轴线跟比例尺後,开始把资料绑定到 < circle >上

// 资料绑定上circle
svg.selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr('cx', function (d) {
        return xScale(d.x);
    })
    .attr('cy', function (d) {
        return yScale(d.y);
    })
    .attr('r', 5)
    .attr('fill', '#000')
    .on("mouseover", handleMouseOver)
    .on("mouseout", handleMouseOut);

这时我们就会得到初步的散点图表了
https://ithelp.ithome.com.tw/upload/images/20211003/20134930zBEknBEnsz.jpg

再来,我们来进行滑鼠的互动设定吧~先来设定 mouseover 跟 mouseleave 的方法

//  mouseover 时点点变色+tooltip
function handleMouseOver(d, i) {
    // 选定this的元素,改变hover过去的颜色跟形状
    d3.select(this)
        .attr('fill', 'orange')
        .attr('r', radius * 2);

    // 加上tooltips
    let pt = d3.pointer(event, this)
    svg.append("text")
        .attr('class', 'hoverTextInfo')
        .attr('x', pt[0] + 10)
        .attr('y', pt[1] - 10)
        .style('fill', 'red')
        .text([`x:${d.x}, y:${d.y}`])
}

// mouseleave 时变回原样
function handleMouseOut(d, i) {
    d3.selectAll('.hoverTextInfo').remove()
    d3.select(this)
        .attr('fill', 'black')
        .attr('r', radius)
}

接着设定滑鼠点击时,要纪录点击当下的座标,并将这个座标用 .invert 的方法换算成资料,再把这个新增的资料用push的方法推入原本的资料阵列

// 滑鼠click的时候增加一个点
svg.on("click", function () {
let coords = d3.pointer(event, this);

let newData = {
  // 把XY座标轴转换成资料
    x: Math.round(xScale.invert(coords[0])),
    y: Math.round(yScale.invert(coords[1]))
};

// 将增加的资料座标推入原本的data
dataset.push(newData);

最後,我们要重新把更新过後的资料绑定到DOM上,同时把滑鼠over跟leave的方法也绑定上去

// 将新的资料绑定上circle、绑定 mouseover/mouseleave 方法
svg.selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr('cx', function (d) {
        return xScale(d.x);
    })
    .attr('cy', function (d) {
        return yScale(d.y);
    })
    .attr('r', 5)
    .attr('fill', '#000')
    .on("mouseover", handleMouseOver)
    .on("mouseout", handleMouseOut);
})

完成!!
https://i.imgur.com/aVB7BQG.gif


Github Page 图表与 Github 程序码

这边附上本章的程序码与图表 GithubGithub Page,需要的人请自行取用~


<<:  Alpine Linux Porting (1.99) ES LEBT !!!

>>:  2021-Day28. Serverless(十 六):Deploy Ktor To Azure 的准备工作

Day 4 - 条件运算式

大家好,我是长风青云。今天是铁人赛第四天。 今天我真的有点赶,我觉得我以後还是不要当天再开始好了。 ...

Day 17 - Android Studio Toggle按钮

Day 17 - Android Studio Toggle按钮 昨天我们介绍了如何写一个简单的加减...

Day18:Flow 的中间运算子,资料输出前还可以做很多事喔

我们在上一篇的文章中,介绍了 Flow 的基本概念,包括如何建立一个 Flow,以及 Flow 是一...

[Refactoring] Chapter 1 Refactoring: A First Example - RPG Game Hunting Mission

本篇同步发布於个人Blog: [Refactoring] Chapter 1 Refactoring...

网路是怎样连接的(五)Socket API

思考重点 如何将应用程序消息委托给协议栈发送? socket是调用那些函式进行收发操作? 核心知识 ...