iT邦幫忙

2022 iThome 鐵人賽

DAY 2
0

回顧

  • 昨天有兩個地方藏著魔鬼

人數貼錯了

  • 首先是我在 剪下、貼上、存成 csv 的步驟中,沒有檢查好複製的資料,以致於光平里多了許多幽靈人口 (從1428 變成 54085)

數值或字串

  • 再來第二點,這個溪湖的 人口數/女性 的資料型態,每三個位數被自動加上逗號了!!

  • 如果不把它轉換成無逗號型態的話,那存成 csv 之後便會造成他被當成一般的文字,後續我們在透過程式處理的時候,會有額外資料整理的工作。

  • 更正上述內容之後,重新把資料存成 csv 之後,再放到 observablehq 上吧。

準備

  • 閱讀少量英文文件
  • 今天會需要看一些 JavaScript

操作

依然是新增一個空白記事本

  • 在畫面右上角,會有一個「New」的按鈕
  • 選「Blank」(空白)

這個記事本裡面是可以寫 Markdown 語法的

  • 如果你沒寫過 Markdown 的話,可以參考 markdown.tw
  • 一個 # (井字號 再 空一格) ,後面加的文字就會變成大標題
  • 匯入 csv 檔,請選擇「File attachment」

一點很重要,就是,檔案的名稱,不要用中文字! 昨天有提到,檔名會變成變數名稱!!
我的檔名因為叫做 xihu_population.csv (意思是 溪湖_人口.csv),所以引入後,變數名稱自然叫做 xihu_population

  • 打開箭頭,看看中文資料有無正確顯示
  • 順便檢查有無幽靈人口

看看 Observablehq 的 D3 官方文件,評估做法

引用別人做好的記事本

  • 只有一行喔,理解是引用來的就好

觀察別人寫的 function

  • 這邊寫得密密麻麻的參數們,有些有給預設值,有些沒有。
  • 太多字了,一時半刻看不完。
  • 先不用全部弄懂,你應該先知道自己想要做到什麼,再來猜要去覆寫哪些參數!

觀察別人如何使用 function

  • 只有改了些許的內容,寬度、高度、顏色好理解
  • x軸y軸 分別給上對應的數值
    • 我希望等等我畫出來的圖,x軸 是村里的名稱
    • y軸 則是個村里的人口
    • 然後要把資料集,替換成我們引入的那個變數 xihu_population
  • yLabel 顧名思義,應該是指標籤
  • yFormat 是 "%" 的話,人口是純量不是比率,等等把他砍了
  • xDomain 光看名字可能無法理解。不過我們往回看那個 function 後,發現你需要給 xDomain 一個 Array ,裏面放入最大最小
    • 阿!我的 x軸 都是村里名,哪來最大和最小咧? --> 用不到,所以刪掉!!

打造我們的保安宮信徒直條圖

  1. 由下往上插入程式碼片段
    • 按「+」
    • 貼上後,執行
    • 再來按「+」
    • 再貼上並執行
    // Copyright 2021 Observable, Inc.
    // Released under the ISC license.
    // https://observablehq.com/@d3/bar-chart
    function BarChart(data, {
      x = (d, i) => i, // given d in data, returns the (ordinal) x-value
      y = d => d, // given d in data, returns the (quantitative) y-value
      title, // given d in data, returns the title text
      marginTop = 20, // the top margin, in pixels
      marginRight = 0, // the right margin, in pixels
      marginBottom = 30, // the bottom margin, in pixels
      marginLeft = 40, // the left margin, in pixels
      width = 640, // the outer width of the chart, in pixels
      height = 400, // the outer height of the chart, in pixels
      xDomain, // an array of (ordinal) x-values
      xRange = [marginLeft, width - marginRight], // [left, right]
      yType = d3.scaleLinear, // y-scale type
      yDomain, // [ymin, ymax]
      yRange = [height - marginBottom, marginTop], // [bottom, top]
      xPadding = 0.1, // amount of x-range to reserve to separate bars
      yFormat, // a format specifier string for the y-axis
      yLabel, // a label for the y-axis
      color = "currentColor" // bar fill color
    } = {}) {
      // Compute values.
      const X = d3.map(data, x);
      const Y = d3.map(data, y);
    
      // Compute default domains, and unique the x-domain.
      if (xDomain === undefined) xDomain = X;
      if (yDomain === undefined) yDomain = [0, d3.max(Y)];
      xDomain = new d3.InternSet(xDomain);
    
      // Omit any data not present in the x-domain.
      const I = d3.range(X.length).filter(i => xDomain.has(X[i]));
    
      // Construct scales, axes, and formats.
      const xScale = d3.scaleBand(xDomain, xRange).padding(xPadding);
      const yScale = yType(yDomain, yRange);
      const xAxis = d3.axisBottom(xScale).tickSizeOuter(0);
      const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);
    
      // Compute titles.
      if (title === undefined) {
        const formatValue = yScale.tickFormat(100, yFormat);
        title = i => `${X[i]}\n${formatValue(Y[i])}`;
      } else {
        const O = d3.map(data, d => d);
        const T = title;
        title = i => T(O[i], i, data);
      }
    
      const svg = d3.create("svg")
          .attr("width", width)
          .attr("height", height)
          .attr("viewBox", [0, 0, width, height])
          .attr("style", "max-width: 100%; height: auto; height: intrinsic;");
    
      svg.append("g")
          .attr("transform", `translate(${marginLeft},0)`)
          .call(yAxis)
          .call(g => g.select(".domain").remove())
          .call(g => g.selectAll(".tick line").clone()
              .attr("x2", width - marginLeft - marginRight)
              .attr("stroke-opacity", 0.1))
          .call(g => g.append("text")
              .attr("x", -marginLeft)
              .attr("y", 10)
              .attr("fill", "currentColor")
              .attr("text-anchor", "start")
              .text(yLabel));
    
      const bar = svg.append("g")
          .attr("fill", color)
        .selectAll("rect")
        .data(I)
        .join("rect")
          .attr("x", i => xScale(X[i]))
          .attr("y", i => yScale(Y[i]))
          .attr("height", i => yScale(0) - yScale(Y[i]))
          .attr("width", xScale.bandwidth());
    
      if (title) bar.append("title")
          .text(title);
    
      svg.append("g")
          .attr("transform", `translate(0,${height - marginBottom})`)
          .call(xAxis);
    
      return svg.node();
    }
    
  2. 再來,貼畫圖那段並且修改成下列樣子
chart = BarChart(xihu_population, {
  x: d => d.村名,
  y: d => d.合計,
  yDomain: [0, 6000],
  yLabel: "↑ 各里保安宮信徒人數上限",
  width,
  height: 500,  // 圖表的高度像素
  color: "orange"
})
  1. 看結果

  2. 另存圖片

結論

今天透過 d3 提供的方式產生直條圖,如果你感受不到什麼好處或特殊之處,那是因為我們還沒深入去操作。 不過對於傳產人員來說,應該是可以學習的範圍,我的觀察啦。 有問題的話可以在留言區提出,謝謝。

參考資料


上一篇
【Day 1】 簡單的直條圖
下一篇
【Day 3】 分群的 “D3” 直條圖
系列文
適用於傳產從業人員的實用報表製圖術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言