【问题标题】:How to place the bars of a bar chart in the right positions of the xAxis using d3.js?如何使用 d3.js 将条形图的条形图放置在 xAxis 的正确位置?
【发布时间】:2021-12-30 18:35:44
【问题描述】:

我正在制作条形图,但在将条形位置与 xAxis 匹配时遇到问题。它们不在正确的位置,例如,将条形悬停在 2010 年标记上方,您可以看到它显示 2007 年的值。我该如何解决?

 let url = "https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json";

const padding = 50;
const height = 460;
const width = 940;
const barthickness = 2.909090909090909;

 var svg = d3.select('body')
    .append('svg')
    .attr('width', width)
    .attr('height', height);

var arr = [];
var years = [];

d3.json(url, function(data) {
        for (let i = 0; i < data.data.length; i++) {
            arr[i] = data.data[i];
            years[i] = parseInt(data.data[i][0].slice(0,4));
        }  

     const yScale = d3.scaleLinear()
                      .domain([0, d3.max(arr, (d) => d[1])])
                      .range([height - padding, padding]);

    const xScale = d3.scaleLinear()
                       .domain([d3.min(years, d => d), d3.max(years, (d) => d)])
                       .range([padding, width - padding]);

const xAxis = d3.axisBottom(xScale);

const yAxis = d3.axisLeft(yScale);

    svg.append("g")
       .attr("transform", "translate(0," + (height - padding) + ")")
       .call(xAxis);

   svg.append('g')
      .attr('transform', 'translate(' + padding + ', 0)')
      .call(yAxis)

    svg.selectAll('rect')
        .data(arr)
        .enter()
        .append('rect')
        .attr('fill', 'blue')
        .attr('height', d => height - padding - yScale(d[1]))
        .attr('width', barthickness)
        .attr('x', (d, i) => padding + (3.2* i))
        .attr('y', d => yScale(d[1]))
        .append('title')
        .text((d, i) => years[i] + ': ' + d[1])
    });
        &lt;script src="https://d3js.org/d3.v4.min.js"&gt;&lt;/script&gt;

【问题讨论】:

    标签: javascript json d3.js dataset bar-chart


    【解决方案1】:

    问题是您没有使用 x-scale 来定位条形。您正在使用 padding + (3.2* i) 设置条形的 x 坐标,该坐标与您的比例不一致。您的图表宽 840 像素,有 275 个条形图,每条形图约为 3.055 像素。您的代码每 3.2 个像素放置一个条形,这太远了。

    通常使用条形图,而不是硬编码条形厚度,您使用band scale。您需要在轴上使用刻度并定位条形。

    另外,由于您使用的是时态数据,您也可以考虑使用面积图而不是条形图。

    下面我为您的数据提供了两个外观相似的图表。一个是条形图,另一个是面积图。

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="UTF-8">
      <script src="https://d3js.org/d3.v7.js"></script>
    </head>
    
    <body>
      <div id="bar-chart"></div>
      <div id="area-chart"></div>
    
      <script>
        const url = 'https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json';
    
        d3.json(url).then(json => {
          // convert the string into Date objects
          const parse = d3.timeParse('%Y-%m-%d');
          const data = json.data.map(d => ({ date: parse(d[0]), value: d[1] }));
    
          barchart(data);
          areachart(data);
        });
    
        function barchart(data) {
          // set up
    
          const margin = { top: 20, right: 20, bottom: 20, left: 30 };
    
          const width = 600 - margin.left - margin.right;
          const height = 300 - margin.top - margin.bottom;
    
          const svg = d3.select('#bar-chart')
            .append('svg')
              .attr('width', width + margin.left + margin.right)
              .attr('height', height + margin.top + margin.bottom)
            .append('g')
              .attr('transform', `translate(${margin.left},${margin.top})`);
    
          // scales
    
          const x = d3.scaleBand()
              .domain(data.map(d => d.date))
              .range([0, width]);
    
          const y = d3.scaleLinear()
              .domain([0, d3.max(data, d => d.value)])
              .range([height, 0]);
    
          // axes
    
          // by default, axes for band scales show tick marks for every bar
          // that would be too cluttered for this data, so we override this
          // by explicitly setting tickValues()
          const [minDate, maxDate] = d3.extent(data, d => d.date);
          const xAxis = d3.axisBottom(x)
              .tickSizeOuter(0)
              // only show the year in the tick labels
              .tickFormat(d3.timeFormat('%Y'))
              .tickValues(d3.timeTicks(minDate, maxDate, 10));
    
          const yAxis = d3.axisLeft(y)
              .tickSizeOuter(0)
              .ticks(10, '~s');
    
          svg.append('g')
              .attr('transform', `translate(0,${height})`)
              .call(xAxis);
    
          svg.append('g')
              .call(yAxis);
    
          // bars
    
          // function to convert Date into string showing the month and year
          const format = d3.timeFormat('%b %Y');
    
          svg.selectAll('rect')
            .data(data)
            .join('rect')
              .attr('x', d => x(d.date))
              .attr('width', d => x.bandwidth())
              .attr('y', d => y(d.value))
              .attr('height', d => height - y(d.value))
              .attr('fill', 'steelblue')
            .append('title')
              .text(d => `${format(d.date)}: ${d.value}`)
        }
    
        function areachart(data) {
          // set up
    
          const margin = { top: 20, right: 20, bottom: 20, left: 30 };
    
          const width = 600 - margin.left - margin.right;
          const height = 300 - margin.top - margin.bottom;
    
          const svg = d3.select('#area-chart')
            .append('svg')
              .attr('width', width + margin.left + margin.right)
              .attr('height', height + margin.top + margin.bottom)
            .append('g')
              .attr('transform', `translate(${margin.left},${margin.top})`);
    
          // scales
    
          const x = d3.scaleTime()
              .domain(d3.extent(data, d => d.date))
              .range([0, width]);
    
          const y = d3.scaleLinear()
              .domain([0, d3.max(data, d => d.value)])
              .range([height, 0]);
    
          // area generator
    
          const area = d3.area()
              .x(d => x(d.date))
              .y0(y(0))
              .y1(d => y(d.value))
              .curve(d3.curveStepAfter);
    
          // axes
          const xAxis = d3.axisBottom(x)
              .tickSizeOuter(0)
              // only show the year in the tick labels
              .tickFormat(d3.timeFormat('%Y'));
    
          const yAxis = d3.axisLeft(y)
              .tickSizeOuter(0)
              .ticks(10, '~s');
    
          svg.append('g')
              .attr('transform', `translate(0,${height})`)
              .call(xAxis);
    
          svg.append('g')
              .call(yAxis);
    
          // area
    
          svg.append('path')
              .attr('d', area(data))
              .attr('fill', 'steelblue')
        }
    
      </script>
    </body>
    
    </html>

    【讨论】:

    • 非常感谢您在我的问题上考虑了这么多,我必须研究您的代码,因为您使用了很多我仍然不知道的方法和功能。你说我应该使用比例来计算条的宽度,我该怎么做?我之前考虑过,但没有成功地为此目的使用秤。我之所以这么问是因为,正如我所说,您使用了一些我仍然不知道的东西,我觉得在短期内,使用比例来改变条形的宽度会解决问题。
    • 如果xd3.scaleBand,那么x.bandwidth()对应于条形图中条形的宽度。
    猜你喜欢
    • 1970-01-01
    • 2012-09-23
    • 2018-02-07
    • 2012-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多