首页 > 解决方案 > D3 分组条形图不沿 x 比例分布

问题描述

我正在尝试按城市创建一个具有 3 个类别的分组条形图,但是它只绘制一个组,并且所有其他组都处于相同的位置,因此它们彼此重叠。它不会占用整个宽度,也不会沿 x 比例分布。我的 x0 和 x1 秤有问题。如果我理解正确,x0 是图表中矩形的开始位置,x1 刻度是矩形的结束位置?你能解释一下我在这里做错了什么吗?

这是问题的一个片段:

const groupedBarChartData = {
  dataset: [
    {
      City: 'York',
      'Sales': 20000,
      'Returns': 25000,
      'Total': 22000,
    },
    {
      City: 'London',
      'Sales': 40000,
      'Returns': 43000,
      'Total': 45000,
    },
    {
      City: 'Manchester',
      'Sales': 40000,
      'Returns': 7000,
      'Total': 19999,
    }]}
  
  
// svg dimensions
const width = 1192;
const height = 610;
const colorScale = d3
  .scaleOrdinal()
  .range(['red', 'blue', 'green', 'orange']);
  
  
  const svg = d3.select('body').append('svg');

    // shape data
    const data = groupedBarChartData.dataset;

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

    // x axis
    const x0Scale = d3.scaleBand().rangeRound([0, width]);
    const x1Scale = d3.scaleBand();

    // set x0 - cities
    x0Scale.domain(data.map((d) => d['City']));

    // set x1  - categories
    x1Scale.domain(keys).rangeRound([0, x0Scale.bandwidth()]);

    // y axis
    const yScale = d3.scaleLinear().range([height, 0]);

    // equal to Math.round(data.map((x) => Math.max(...keys.map((el) => x[el]))).sort((a, b) => b - a)[0])
    yScale.domain([
      0,
      Math.round(d3.max(data, (d) => d3.max(keys, (key) => d[key]))),
    ]);

    // draw chart
    svg
      .selectAll('rect')
      .data(data)
      .enter()
      .selectAll('rect')
      .data((d) =>
        keys.map((key) => {
          return { key: key, value: d[key] };
        })
      )
      .enter()
      .append('rect')
      .attr('x', (d, i) => x1Scale(d.key))
      .attr('y', (d) => yScale(d.value))
      .attr('width', x1Scale.bandwidth() - 15)
      .attr('height', (d) => height - yScale(d.value))
      .attr('fill', (d) => colorScale(d.key));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

它给我的结果是:

正位

标签: javascriptjsond3.jsdata-visualizationbar-chart

解决方案


发生这种情况是因为您仅基于 x1Scale 定位矩形,这些位置仅基于类别:

  .attr('x', (d, i) => x1Scale(d.key))

你没有根据城市定位。

最常见的解决方案是使用一个父级g表示和定位父级城市,然后再去定位代表类别的条形:

    svg
      .selectAll('rect')
      .data(data)
      .enter()
      .append("g")
      .attr("transform", d=>`translate(${x0Scale(d.City)},0)`)
      .selectAll('rect')
      ... 

这给了我们:

const groupedBarChartData = {
  dataset: [
    {
      City: 'York',
      'Sales': 20000,
      'Returns': 25000,
      'Total': 22000,
    },
    {
      City: 'London',
      'Sales': 40000,
      'Returns': 43000,
      'Total': 45000,
    },
    {
      City: 'Manchester',
      'Sales': 40000,
      'Returns': 7000,
      'Total': 19999,
    }]}
  
  
// svg dimensions
const width = 1192;
const height = 610;
const colorScale = d3
  .scaleOrdinal()
  .range(['red', 'blue', 'green', 'orange']);
  
  
  const svg = d3.select('body').append('svg').attr("width",width).attr("height",height);

    // shape data
    const data = groupedBarChartData.dataset;

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

    // x axis
    const x0Scale = d3.scaleBand().rangeRound([0, width]);
    const x1Scale = d3.scaleBand();

    // set x0 - cities
    x0Scale.domain(data.map((d) => d['City']));

    // set x1  - categories
    x1Scale.domain(keys).rangeRound([0, x0Scale.bandwidth()]);

    // y axis
    const yScale = d3.scaleLinear().range([height, 0]);

    // equal to Math.round(data.map((x) => Math.max(...keys.map((el) => x[el]))).sort((a, b) => b - a)[0])
    yScale.domain([
      0,
      Math.round(d3.max(data, (d) => d3.max(keys, (key) => d[key]))),
    ]);

    // draw chart
    svg
      .selectAll('rect')
      .data(data)
      .enter()
      .append("g")
      .attr("transform", d=>`translate(${x0Scale(d.City)},0)`)
      .selectAll('rect')
      .data((d) =>
        keys.map((key) => {
          return { key: key, value: d[key]};
        })
      )
      .enter()
      .append('rect')
      .attr('x', (d, i) => x1Scale(d.key))
      .attr('y', (d) => yScale(d.value))
      .attr('width', x1Scale.bandwidth() - 15)
      .attr('height', (d) => height - yScale(d.value))
      .attr('fill', (d) => colorScale(d.key));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

或者,如果您不想要嵌套元素,您可以将城市信息传递给每个条形图的基准,并使用它来定位条形图和类别:

  .selectAll('rect')
  .data((d) =>
    keys.map((key) => {
      return { key: key, value: d[key], city: d.City };
    })
  )
  .enter()
  .append('rect')
  .attr('x', (d, i) => x1Scale(d.key)+x0Scale(d.city))
  ... 

const groupedBarChartData = {
  dataset: [
    {
      City: 'York',
      'Sales': 20000,
      'Returns': 25000,
      'Total': 22000,
    },
    {
      City: 'London',
      'Sales': 40000,
      'Returns': 43000,
      'Total': 45000,
    },
    {
      City: 'Manchester',
      'Sales': 40000,
      'Returns': 7000,
      'Total': 19999,
    }]}
  
  
// svg dimensions
const width = 1192;
const height = 610;
const colorScale = d3
  .scaleOrdinal()
  .range(['red', 'blue', 'green', 'orange']);
  
  
  const svg = d3.select('body').append('svg').attr("width",width).attr("height",height);

    // shape data
    const data = groupedBarChartData.dataset;

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

    // x axis
    const x0Scale = d3.scaleBand().rangeRound([0, width]);
    const x1Scale = d3.scaleBand();

    // set x0 - cities
    x0Scale.domain(data.map((d) => d['City']));

    // set x1  - categories
    x1Scale.domain(keys).rangeRound([0, x0Scale.bandwidth()]);

    // y axis
    const yScale = d3.scaleLinear().range([height, 0]);

    // equal to Math.round(data.map((x) => Math.max(...keys.map((el) => x[el]))).sort((a, b) => b - a)[0])
    yScale.domain([
      0,
      Math.round(d3.max(data, (d) => d3.max(keys, (key) => d[key]))),
    ]);

    // draw chart
    svg
      .selectAll('rect')
      .data(data)
      .enter()
      .selectAll('rect')
      .data((d) =>
        keys.map((key) => {
          return { key: key, value: d[key], city: d.City };
        })
      )
      .enter()
      .append('rect')
      .attr('x', (d, i) => x1Scale(d.key)+x0Scale(d.city))
      .attr('y', (d) => yScale(d.value))
      .attr('width', x1Scale.bandwidth() - 15)
      .attr('height', (d) => height - yScale(d.value))
      .attr('fill', (d) => colorScale(d.key));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>


推荐阅读