D3.js v5.0 堆栈图

在这里插入图片描述
数据:

 data: [
      { month: '2019-1-1', apples: 3840, bananas: 1920, cherries: 960, oranges: 300 },
      { month: '2019-1-2', apples: 1600, bananas: 1440, cherries: 960, oranges: 400 },
      { month: '2019-1-3', apples: 640, bananas: 960, cherries: 640, oranges: 200 },
      { month: '2019-1-4', apples: 320, bananas: 480, cherries: 640, oranges: 500 },
    ],
import * as d3 from 'd3';

export default function stackRect(id, data) {
  (() => {
    d3.select(id)
      .selectAll('svg')
      .remove();
  })();
  const height = 800;
  const width = 800;
  const margin = 20;
  const padding = {
    left: 100,
    right: 100,
    top: 100,
    bottom: 100,
  };

  const axisHeight = height - padding.top - padding.bottom;
  const axisWidth = width - padding.left - padding.right;
  const colorZ = d3.scaleOrdinal(d3.schemePaired);

  // 数据转换器
  const stack = d3
    .stack()
    .keys(['apples', 'bananas', 'cherries', 'oranges'])
    .order(d3.stackOrderNone) // 使用原始数据的顺序不进行顺序调整
    .offset(d3.stackOffsetNone);// 堆栈偏移-零基线
  const stackData = stack(data);

  // x与y比例尺
  const xScale = d3
    .scaleBand()
    .range([0, axisWidth])
    .domain(data.map(d => d.month));
  const yScale = d3
    .scaleLinear()
    .range([axisHeight, 0])
    .domain([0, d3.max(stackData[stackData.length - 1], item => item[1])]);

  // x轴和y轴
  const xAxis = d3.axisBottom().scale(xScale);
  const yAxis = d3.axisLeft(yScale);
  // 画布
  const svg = d3
    .select(id)
    .append('svg')
    .attr('width', width)
    .attr('height', height);
  // 添加x轴
  svg
    .append('g')
    .attr('class', 'axis')
    .attr('transform', `translate(${padding.left},${height - padding.bottom})`)
    .call(xAxis);
  // 添加y轴
  svg
    .append('g')
    .attr('class', 'axis')
    .attr('transform', `translate(${padding.left},${padding.top})`)
    .call(yAxis);
  // 将二维数组的第一维剥离,打散成n列
  const rectContainer = svg
    .selectAll('rectContainer')
    .data(stackData)
    .enter()
    .append('g')
    .attr('class', 'rectContainer')
    .attr('fill', d => colorZ(d.key));
  // 渲染每一列
  rectContainer
    .selectAll('rect')
    .data(d => d)
    .enter()
    .append('rect')
    .attr('x', d => {
      return xScale(d.data.month) + padding.left + margin / 2;
    })
    .attr('y', d => {
      return yScale(d[1]) + padding.top;
    })
    .attr('width', () => {
      return xScale.bandwidth() - margin;
    })
    .attr('height', d => {
      return height - padding.top - padding.bottom - yScale(d[1] - d[0]);
    })
    .attr('stroke', '#ccc');

  // 添加图例
  svg
    .selectAll('circle')
    .data(['apples', 'bananas', 'cherries', 'oranges'])
    .enter()
    .append('circle')
    .attr('cx', () => {
      return width - padding.right - 80;
    })
    .attr('cy', (d, i) => {
      return padding.top + 25 * i;
    })
    .attr('r', '6')
    .attr('fill', d => {
      return colorZ(d);
    });
  const texts = svg
    .selectAll('textContainer')
    .data(['apples', 'bananas', 'cherries', 'oranges'])
    .enter()
    .append('g')
    .attr('class', 'textContainer');
  texts
    .append('text')
    .attr('x', () => {
      return width - padding.right - 60;
    })
    .attr('y', (d, i) => {
      return padding.top + 25 * i;
    })
    .attr('dy', '0.32em')
    .text(d => d)
    .attr('fill', d => colorZ(d));
}