D3數據可視化-Area

案例

Area 圖形可以填充整塊的圖形,下面是同樣的數據(2017年2季度到2018年1季度手機品牌市場佔有情況)分別在 area 和在 stack bar 圖呈現的結果。

解析

d3.area 可以生成一個圖形生成器,輸入一個數組數據,將返回值傳入 path 元素的 d 屬性就可以渲染出對應的 area 圖形。

首先嚐試一個比較簡單的 case,只生成單一 area 圖形的情況。

可以構造一個 area 生成器:

const areaGenerator = d3.area().x([x]).y0([y]).y1([y])

y0 函數可以確定 x 點對應的 y 軸投影下限(base),y1 確定上限(top) 。單一的 area 與折線圖非常類似,相當於現充了 y 點到 x 座標軸的的折線圖。

要生成 stack 形式的 area,可以拆分爲兩個步驟:

  1. 先將數據轉化爲 series 結構,每個數據點用 [baseline, topline] 描述上下限。
  2. 將 series 數據傳遞 area 生成器,生成對應的 path 圖形。

利用 d3.stack 生成 series 數據:

const stack = d3.stack().keys([keys]);
const series = stack(data);

生成 path 圖形的步驟如下:

在 x 軸使用 scalePoint:

// scalePoint in x axis
const xAccessor = (d) => `${d.year}-${d.quarter}`;
const xScale = d3.scalePoint()
  .domain(data.map(xAccessor))
  .range([0, maxWidth]);

在 y 軸使用 scaleLinear:

// scaleLinear in y axis
const stackMin = serie => d3.min(serie, (d) => d[0]);
const stackMax = serie => d3.max(serie, (d) => d[1]);
const yScale = d3.scaleLinear()
  .domain([d3.min(series, stackMin), d3.max(series, stackMax)])
  .range([maxHeight, 0])

構造 area 生成器:

// area generator
const area = d3.area()
  .x((d) => xScale(xAccessor(d)))
  .y0((d) => yScale(d[0]))
  .y1((d) => yScale(d[1]))

爲每個 serie 添加 area 圖形:

// add path 
svg.selectAll('path.area')
  .data(series)
  .enter()
  .append('path')
  .attr('class', 'area')
  .attr('d', area)
  .attr('fill', (d, i) => colorArray[i % colorArray.length])

實現

Git地址

完整源碼如下:

<!DOCTYPE html>
<html>
  <body>
    <style>
      svg {
        border: 1px solid lightgrey;
      }

    </style>
    <script src="http://d3js.org/d3.v5.min.js"></script>
    <script type="text/javascript">
      const maxHeight = 400;
      const maxWidth = 600;
      const barWidth = 20;

      const colorArray = ['#38CCCB', '#0074D9', '#2FCC40', '#FEDC00', '#FF4036', 'lightgrey'];

      function singleArea() {
          const data = [
            {
              date: '2019-01-01',
              value: 1,
            },
            {
              date: '2019-01-02',
              value: 10,
            },
            {
              date: '2019-01-03',
              value: 35,
            },
          ];

        const xScale = d3.scaleTime()
          .domain(d3.extent(data, (d) => new Date(d.date)))
          .range([0, maxWidth]);

        const yScale = d3.scaleLinear()
          .domain(d3.extent(data, (d) => d.value))
          .range([maxHeight, 0])

        const area = d3.area()
          .x((d) => xScale(new Date(d.date)))
          .y1((d) => yScale(d.value))
          .y0((d) => yScale(0))

       const svg = d3.select('body')
         .append('svg')
         .attr('width', maxWidth)
         .attr('height', maxHeight)

        svg.append('g')
          .selectAll('path.area')
          .data([data])
          .enter()
          .append('path')
          .attr('class', 'area')
          .attr('d', area)
          .attr('fill', colorArray[0])
      }

      function stackArea() {
        const stackData = [
          {
            year: 2017,
            quarter: 2,
            samsung: 0.229,
            apple: 0.118,
            huawei: 0.110,
            oppo: 0.08,
            xiaomi: 0.062,
            others: 0.401,
          },
          {
            year: 2017,
            quarter: 3,
            samsung: 0.221,
            apple: 0.124,
            huawei: 0.104,
            oppo: 0.081,
            xiaomi: 0.075,
            others: 0.396,
          },

          {
            year: 2017,
            quarter: 4,
            samsung: 0.189,
            apple: 0.196,
            huawei: 0.107,
            oppo: 0.069,
            xiaomi: 0.071,
            others: 0.368,
          },
          {
            year: 2018,
            quarter: 1,
            samsung: 0.235,
            apple: 0.157,
            huawei: 0.118,
            oppo: 0.074,
            xiaomi: 0.084,
            others: 0.332,
          },
        ]

        const stack = d3.stack()
          .keys(['apple', 'samsung', 'huawei', 'oppo', 'xiaomi', 'others']);

        const series = stack(stackData);

        const stackMin = (serie) => d3.min(serie, (d) => d[0]);
        const stackMax = (serie) => d3.max(serie, (d) => d[1]);
        const yScale = d3.scaleLinear()
          .domain([d3.min(series, stackMin), d3.max(series, stackMax)])
          .range([maxHeight, 0])

      const xAccessor = (d) => `${d.year}-${d.quarter}`;

      const xScale = d3.scalePoint()
        .domain(stackData.map(xAccessor))
        .range([0, maxWidth])

      const area = d3.area()
        .x((d) => xScale(xAccessor(d.data)))
        .y0((d) => yScale(d[0]))
        .y1((d) => yScale(d[1]))

      const svg = d3.select('body')
        .append('svg')
        .attr('width', maxWidth)
        .attr('height', maxHeight)

      svg.append('g')
        .selectAll('path')
        .data(series)
        .enter()
        .append('path')
        .attr('d', area)
        .attr('fill', (d, i) => colorArray[i % colorArray.length])
      }

      singleArea()
      stackArea()

    </script>
  </body>
</html>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章