首页 > 解决方案 > 在 D3 分组条形图中自定义网格线

问题描述

我是 D3 新手,我正在尝试使用 D3 构建分组条形图。当一个图结束时,我需要在 X 轴上放置我的网格线。目前它位于中间。此外,目前网格线出现在绘制图形的顶部,如何将网格线移动到图形后面。在 Y 轴上,是否可以让我的刻度从 1000、2000、3000、4000 开始,而不是当前显示 0.5k、1k 等

<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <div id="grouped-chart"></div>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script>
      const data = [
        {
          category: {
            model: 'A',
          },
          type1: '1000',
          type2: '2000',
        },
        {
          category: {
            model: 'B',
          },
          type1: '2000',
          type2: '3000',
        },
        {
          category: {
            model: 'C',
          },
          type1: '1500',
          type2: '4000',
        },
      ];
      const margin = { top: 20, right: 30, bottom: 30, left: 60 };
      const width = 400 - margin.left - margin.right;
      const height = 400 - margin.top - margin.bottom;
      const svg = d3
        .select('#grouped-chart')
        .append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .style('background', '#fff');
      const g = svg
        .append('g')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
      const x0 = d3.scaleBand().rangeRound([0, width]).paddingInner(0.1);

      const x1 = d3.scaleBand().padding(0.05);

      const y = d3.scaleLinear().rangeRound([height, 0]);

      const z = d3.scaleOrdinal().range(['#004c6d', '#255e7e', '#3d708f']);
      const keys = Object.keys(data[0]).slice(1);
      x0.domain(data.map((d) => d.category.model));
      x1.domain(keys).rangeRound([0, x0.bandwidth()]);
      y.domain([0, 4000]);

      

      const gX = g
        .append('g')
        .attr('class', 'axis')
        .attr('transform', 'translate(0,' + height + ')')
        .call(d3.axisBottom(x0));

      const gY = g
        .append('g')
        .attr('class', 'axis')
        .call(d3.axisLeft(y).ticks(null, 's'));

      g.append('g')
        .attr('class', 'grid')
        .attr('transform', `translate(0, ${height})`)
        .call(d3.axisBottom().scale(x0).tickSize(-height, 0, 0).tickFormat(''));

      g.append('g')
        .attr('class', 'grid')
        .call(d3.axisLeft().scale(y).tickSize(-width, 0, 0).tickFormat(''));

g.append('g')
 .selectAll('g')
 .data(data)
 .enter()
 .append('g')
 .attr('transform', (d) => 'translate(' + x0(d.category.model) + ',0)')
 .selectAll('rect')
 .data((d) =>
 keys.map((key) => {
   return { key: key, value: d[key] };
 })
 )
 .enter()
 .append('rect')
 .attr('x', (d) => x1(d.key))
 .attr('y', (d) => y(d.value))
 .attr('width', x1.bandwidth())
 .attr('height', (d) => height - y(d.value))
 .attr('fill', (d) => z(d.key));
    </script>
  </body>
</html>

标签: javascriptd3.js

解决方案


对于 y 轴刻度线,您可以将刻度数设置为 4。然后它们会以千位绘制:d3.axisLeft(y).ticks(4, 's')。您还可以显式设置tickValues

对于网格线,您可以复制由轴创建的刻度线并调整它们的坐标以穿过整个图表。对于垂直网格线,您可以将它们的 x 坐标偏移x0.step() / 2,以便将它们放在组之间(step() 文档)。

另外,我会推荐灰色刻度线而不是黑色,这样它们就不会引起太多关注。最后,您为条形选择的两种颜色有点难以区分。ColorgoricalColorBrewer等工具可用于挑选颜色。许多 ColorBrewer 方案在d3-scale-chromatic中提供。

<html>

<head>
  <meta charset="UTF-8" />
</head>

<body>
  <div id="grouped-chart"></div>
  <script src="https://d3js.org/d3.v7.min.js"></script>
  <script>
    // data

    const data = [
      {
        category: {
          model: 'A',
        },
        type1: '1000',
        type2: '2000',
      },
      {
        category: {
          model: 'B',
        },
        type1: '2000',
        type2: '3000',
      },
      {
        category: {
          model: 'C',
        },
        type1: '1500',
        type2: '4000',
      },
    ];

    // set up

    const margin = { top: 20, right: 30, bottom: 30, left: 60 };
    const width = 400 - margin.left - margin.right;
    const height = 400 - margin.top - margin.bottom;

    const svg = d3.select('#grouped-chart')
      .append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .style('background', '#fff');

    const g = svg
      .append('g')
        .attr('transform', `translate(${margin.left},${margin.top})`);

    // scales

    const keys = Object.keys(data[0]).slice(1);

    const x0 = d3.scaleBand()
        .domain(data.map((d) => d.category.model))
        .rangeRound([0, width])
        .paddingInner(0.1);

    const x1 = d3.scaleBand()
        .domain(keys)
        .rangeRound([0, x0.bandwidth()])
        .padding(0.05);

    const y = d3.scaleLinear()
        .domain([0, d3.max(data, d => Math.max(+d.type1, +d.type2))])
        .rangeRound([height, 0]);

    const z = d3.scaleOrdinal()
        .domain(keys)
        // colors picked with colorgorical
        .range(["rgb(88,181,225)", "rgb(26,101,135)"]);

    // axes

    g.append('g')
        .attr('class', 'axis')
        // move group to bottom of chart
        .attr('transform', `translate(0,${height})`)
        // add axis
        .call(d3.axisBottom(x0).tickSizeOuter(0))
        // copy tick marks to create grid lines
        .call(g => g.selectAll('.tick > line')
          // skip last tick since there's no group after it
          .filter((d, i, nodes) => i < nodes.length - 1)
          .clone()
            .attr('stroke', '#cccccc')
            // move to in between the groups
            .attr('x1', x0.step() / 2)
            .attr('x2', x0.step() / 2)
            // make them go from top to bottom of chart
            .attr('y1', -height)
            .attr('y2', 0));

    g.append('g')
        .attr('class', 'axis')
        // add axis
        .call(d3.axisLeft(y).ticks(4, 's'))
        // copy tick marks to create grid lines
        .call(g => g.selectAll('.tick > line')
          // skip first tick since there's already a baseline from the x axis
          .filter((d, i) => i > 0)
          .clone()
            .attr('stroke', '#cccccc')
            .attr('x1', 0)
            .attr('x2', width));

    // bars

    g.append('g')
      .selectAll('g')
      .data(data)
      .join('g')
        .attr('transform', d => `translate(${x0(d.category.model)},0)`)
      .selectAll('rect')
      .data(d => keys.map((key) => ({ key: key, value: d[key] })))
      .join('rect')
        .attr('x', d => x1(d.key))
        .attr('y', d => y(d.value))
        .attr('width', x1.bandwidth())
        .attr('height', d => height - y(d.value))
        .attr('fill', d => z(d.key));
  </script>
</body>

</html>


推荐阅读