简体   繁体   English

D3 堆积条形图:条形重叠

[英]D3 Stacked Bar Chart: Bars Overlap

I'm building an interactive bar chart with D3 (version 4).我正在使用 D3(版本 4)构建交互式条形图。 Users may choose a single category (this works fine as coded below) or they can view all categories, in which case the color-coded bars should be stacked.用户可以选择一个类别(如下所示,这很好用),或者他们可以查看所有类别,在这种情况下,颜色编码的条应该堆叠起来。 The issue is that I'm not sure how to manipulate the y-coordinates so that each bar is placed above another when the user has chosen All Categories.问题是我不确定如何操作 y 坐标,以便当用户选择所有类别时,每个栏都放在另一个栏上方。 Right now they overlap because they all start from y:0.现在它们重叠,因为它们都从 y:0 开始。

Here's what it looks like (each stack would add up to +/- 200 to ~280 if not overlapped):这是它的样子(如果不重叠,每个堆栈将加起来 +/- 200 到 ~280):

堆积条形图,减去堆积

And here's my function for displaying the chart:这是我的 function 用于显示图表:

function doChart(chart_json, category, years){
    console.log("loading data for " + (category ? category : 'ALL CATEGORIES')  + "...")
    
    /*
    ****************
    Prepare the data
    ****************    
    */
        
    if(category){
        var filter = chart_json.filter(obj =>{ if(obj.category === category) return obj });
        var max_count = d3.max(filter[0].counts)
        var data = [filter[0]]; // array of one category
    }else{
        var max_count = d3.max(chart_json.totals)
        var data = chart_json // array of all categories 
    }
    
    /*
    **********************
    Set up the chart scale
    **********************  
    */
        
    // D3 margin convention  
    var margin = {top: 20, right: 30, bottom: 20, left: 50},
        height = 400 - margin.top - margin.bottom,
        width = parseInt(d3.select(chart_container).style('width'), 10),
        width = width - margin.left - margin.right
    
    // X scale uses years
    var xScale = d3.scaleBand()
        .domain(years) 
        .range([10, width-10])
        .padding(.2)
    
    // Y scale uses category counts
    var yScale = d3.scaleLinear()
        .domain([0, max_count]) 
        .range([height, 0]);  
                        
    // Add the SVG to the container
    var svg = d3.select(chart_container).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 + ")");
    
    // Append the x axis
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(d3.axisBottom(xScale)
            .tickValues(years)
            .tickFormat(d3.format("Y"))); // no commas; format as years
    
    // Append the y axis
    svg.append("g")
        .attr("class", "y axis")
        .call(d3.axisLeft(yScale).ticks( Math.min(10, yScale.domain()[1] ))) // no more ticks than there are total (max 10)
        
    /*
    *****************
    Add the data bars
    *****************
    */

    var bars = svg.append("g").attr("id","bars")
    years.forEach((year,i)=>{
        var bar = bars.append("g").attr("id","bars_"+year)
        var year_index = i;
        bar.selectAll("bar")
          .data(data) 
          .enter()
          .append("rect")
            .attr("x", xScale(year) )
            .attr("y", (d) => {
                return yScale(d.counts[year_index]) 
               })
            .attr("category", (d) =>{
                return d.category
            })
            .attr("year", year)
            .attr("total", (d) => {
                return yScale(d.counts[year_index]) 
               })
            .attr("class", "rect_"+year)
            .attr("width", xScale.bandwidth())
            .attr("height", (d) =>{
                return height - yScale(d.counts[year_index])
            })
            .attr("fill", (d) =>{
                return getMarkerColor(d.category)
            })        

    })
    
}

Here's some sample data ( chart_json for All Categories):这是一些示例数据(所有类别的chart_json ):

    [{
    "category": "CLUBS AND ORGANIZATIONS",
    "counts": [
      1,
      4,
      4,
      1,
      7,
      3,
      2,
      7
    ]
  },
  {
    "category": "ENTERTAINMENT",
    "counts": [
      4,
      4,
      5,
      3,
      1,
      2,
      1,
      7
    ]
  },
  {
    "category": "FINANCE, INSURANCE AND REAL ESTATE",
    "counts": [
      18,
      14,
      12,
      10,
      17,
      15,
      7,
      9
    ]
  },
  {
    "category": "FOOD SERVICES AND DRINKING PLACES",
    "counts": [
      19,
      14,
      20,
      17,
      11,
      13,
      14,
      64
    ]
  },
  {
    "category": "HOTELS AND MOTELS",
    "counts": [
      1,
      null,
      1,
      1,
      null,
      null,
      1,
      4
    ]
  },
  {
    "category": "INSTITUTION",
    "counts": [
      null,
      null,
      null,
      null,
      3,
      2,
      null,
      19
    ]
  },
  {
    "category": "MANUFACTURER",
    "counts": [
      1,
      1,
      null,
      1,
      4,
      null,
      null,
      null
    ]
  },
  {
    "category": "MUSEUMS AND GALLERIES",
    "counts": [
      null,
      null,
      null,
      null,
      null,
      null,
      null,
      3
    ]
  },
  {
    "category": "PROFESSIONAL",
    "counts": [
      20,
      13,
      17,
      18,
      26,
      10,
      10,
      2
    ]
  },
  {
    "category": "RETAIL",
    "counts": [
      139,
      122,
      128,
      135,
      122,
      105,
      55,
      38
    ]
  },
  {
    "category": "SERVICE",
    "counts": [
      23,
      19,
      28,
      19,
      38,
      22,
      25,
      17
    ]
  },
  {
    "category": "VACANT",
    "counts": [
      18,
      23,
      7,
      8,
      32,
      17,
      null,
      35
    ]
  },
  {
    "category": "OTHER",
    "counts": [
      9,
      10,
      11,
      17,
      27,
      13,
      8,
      28
    ]
  }]

Answering my own question here.在这里回答我自己的问题。 I set a variable y0 to zero and, for each data point incremented by it the count for that data point before passing it to my yScale() function.我将变量y0设置为零,并且对于每个由它递增的数据点,在将其传递给我的yScale() function 之前,该数据点的计数。 Previously I was just passing the count itself.以前我只是通过计数本身。

var bars = svg.append("g").attr("id","bars")
years.forEach((year,i)=>{
    var bar = bars.append("g").attr("id","bars_"+year)
    var year_index = i;
    var y0 = 0; // stacking
    bar.selectAll("bar")
      .data(data) 
      .enter()
      .append("rect")
        .attr("x", xScale(year) )
        .attr("y", (d) => {
            var count = d.counts[year_index] ? d.counts[year_index] : 0
            y0 += count // stacking
            return yScale(y0)
           })
        .attr("category", (d) =>{
            return d.category
        })
        .attr("year", year)
        .attr("total", (d) => {
            return d.counts[year_index] ? d.counts[year_index] : 0
           })
        .attr("class", "rect_"+year)
        .attr("width", xScale.bandwidth())
        .attr("height", (d) =>{
            var count = d.counts[year_index] ? d.counts[year_index] : 0
            var bar_height = count ? (height - yScale(count)) : 0
            return bar_height
        })
        .attr("fill", (d) =>{
            return getMarkerColor(d.category)
        })        

})

This basically solved the problem, though I still need to properly sort the data, but that is a separate issue.这基本上解决了问题,虽然我仍然需要正确排序数据,但这是一个单独的问题。

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM