无法创建堆积的条形图? [英] Not able to create stacked bar chart?

查看:96
本文介绍了无法创建堆积的条形图?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用d3图表创建堆积的条形图.我在前端有按钮.单击按钮时应显示图表.单击按钮时出现以下错误.

错误:属性y:预期长度为"NaN".

错误:属性高度:预期长度"NaN".

错误:属性宽度:预期长度为"10,900".

import * as d3 from 'd3';

export class stackedbarchart  {

    sbar(box_id){

      d3.select(box_id)
      .select("svg")
      .remove();

      var margin = { top: 20, right: 160, bottom: 35, left: 30 };

      var width = 1100 - margin.left - margin.right,
          height = 500 - margin.top - margin.bottom;

      var svg = d3.select("body")
        .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 + ")");

      var data = [
        { country:"India", sales:100, profit:100, loss:10 },
        { country:"US", sales:100, profit:80, loss:40 },
        { country:"aus", sales:100, profit:70, loss:30 }
      ];

      var stack = d3.stack()
      .keys(["sales","profit","loss"])
      .order(d3.stackOrderNone)
      .offset(d3.stackOffsetNone);

      var series = stack(data);

      var x = d3.scaleOrdinal()
        .domain(series[0].map(function(d) {
          console.log(d)
           return d.x; }))
        .range([10, width-10], 0.02);

      var y = d3.scaleLinear()
        .domain([0, d3.max(series, function(d) {   
          return d3.max(d, function(d) { 
            return d.y0 + d.y; });  })])
        .range([height, 0]);

      var colors = ["b33040", "#d25c4d", "#f2b447", "#d9d574"];

      var yAxis = d3.axisLeft(y)
        .ticks(5)
        .tickSize(-width, 0, 0)
        .tickFormat( function(d) { return d } );

      var xAxis = d3.axisBottom(x)
      console.log(xAxis)

      svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

      svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

      var groups = svg.selectAll("g.cost")
        .data(series)
        .enter().append("g")
        .attr("class", "cost")
        .style("fill", function(d, i) { return colors[i]; });

      var rect = groups.selectAll("rect")    
        .data(function(d) { return d; })
        .enter()
        .append("rect")
        .attr("x", function(d) { return x(d.x); })
        .attr("y", function(d) { return y(d.y0 + d.y); })
        .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
        .attr("width", x.range())
        .on("mouseover", function() { tooltip.style("display", null); })
        .on("mouseout", function() { tooltip.style("display", "none"); })
        .on("mousemove", function(d) {
          var xPosition = d3.mouse(this)[0] - 15;
          var yPosition = d3.mouse(this)[1] - 25;
          tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
          tooltip.select("text").text(d.y);
        });

      var legend = svg.selectAll(".legend")
        .data(colors)
        .enter().append("g")
        .attr("class", "legend")
        .attr("transform", function(d, i) { return "translate(30," + i * 19 + ")"; });

      legend.append("rect")
        .attr("x", width - 18)
        .attr("width", 18)
        .attr("height", 18)
        .style("fill", function(d, i) {return colors.slice().reverse()[i];});

      legend.append("text")
        .attr("x", width + 5)
        .attr("y", 9)
        .attr("dy", ".35em")
        .style("text-anchor", "start")
        .text(function(d, i) { 
          switch (i) {
            case 0: return "sales";
            case 1: return "profit";
            case 2: return "loss"; 
          }
        });

      var tooltip = svg.append("g")
        .attr("class", "tooltip")
        .style("display", "none");

      tooltip.append("rect")
        .attr("width", 30)
        .attr("height", 20)
        .attr("fill", "white")
        .style("opacity", 0.5);

      tooltip.append("text")
        .attr("x", 15)
        .attr("dy", "1.2em")
        .style("text-anchor", "middle")
        .attr("font-size", "12px")
        .attr("font-weight", "bold") ;

    }
}

任何帮助或向正确方向的提示将不胜感激.

解决方案

D3v4中的更改

在d3v4中进行的更改会破坏d3v3图表. d3v3中的堆栈具有x个访问器,并使用y0y表示堆栈图的每个段的开始和结束值.结果看起来像这样:

 .append("rect")
  .attr("x", function(d) { return x(d.x); })
  .attr("y", function(d) { return y(d.y0 + d.y); })
  .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })

此方法不适用于d3v4 +,但似乎是您正在使用的方法.乍一看,您的代码可能基于 v3代码.

d3v4 +中的堆栈

让我们看看d3.stack在d3v4 +中产生了什么.

我们有输入数据:

var data = [
  { country:"India", sales:100, profit:100, loss:10 },
  { country:"US", sales:100, profit:80, loss:40 },
  { country:"aus", sales:100, profit:70, loss:30 }
];

还有一个堆栈生成器:

var stack = d3.stack()
  .keys(["sales","profit","loss"])
  .order(d3.stackOrderNone)     // default value, does not need to be specified.
  .offset(d3.stackOffsetNone)   // default value, does not need to be specified

当我们将数据传递给堆栈生成器时,我们得到:

[/*  India     US        AUS   */
 [[0,  100],[0,  100],[0,  100]],  // Sales
 [[100,200],[100,180],[100,170]],  // Profit
 [[200,210],[180,220],[170,200]]   // Loss 
]

如果将数组安排为表格,则列代表原始数据数组中的每个项目(代表国家的系列),行代表每个键.两个元素阵列中的每一个都代表一个堆叠的条形段.

包含给定键的所有值(例如,用于销售的[[0,100],[0,100],[0,100]])的数组,还包含具有该键名的属性"key".

表示单个段的每个数组(例如:[0,100])具有属性data,该属性是原始数据数组中的一项(例如:{"country": "India","sales": 100,"profit": 100,"loss": 10}).

堆栈数组中的项目数(行数)不等于序列数(在这种情况下为国家),但等于键的数量.

创建天平

您遇到的第一个问题是如何指定刻度:

var x = d3.scaleOrdinal()
  .domain(series[0].map(function(d) { return d.x; }))
  .range([10, width-10], 0.02);

var y = d3.scaleLinear()
  .domain([0, d3.max(series, function(d) { return d3.max(d, function(d) { return d.y0 + d.y; });  })])
  .range([height, 0]);

因为堆栈没有创建任何y0yx属性,所以这些将不起作用.

x刻度应该是带刻度(条形图通常使用带d3的带刻度),我们可以使用原始数据数组来创建域:

var x = d3.scaleBand()  // band scale
   .domain(data.map(function(d) {
         return d.country; }))  // return the country.
   .range([10, width-10]); 

y标度需要访问d [0]和d [1],而不是d.y0和d.y:

var y = d3.scaleLinear()
   .domain([0, d3.max(series, function(d) {   
       return d3.max(d, function(d) { 
            return d[0] + d[1]; });  })]) // access stack values (not d.y, d.y0)
   .range([height, 0]);  

附加矩形

现在让我们转到附加矩形的位置:

    .append("rect")
    .attr("x", function(d) { return x(d.x); })
    .attr("y", function(d) { return y(d.y0 + d.y); })
    .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
    .attr("width", x.range())

由于未定义d.x,因此可以使用x(d.data.country). d.y0d.y出现的地方,我们需要替换d[0]d[1].最后,x.range()几乎是图形的宽度,我们可以改用x.bandwidth(),它是单个条形的宽度.

下面是您的代码(从定义边距开始),其中包含以下更新:

 var margin = { top: 20, right: 160, bottom: 35, left: 30 };

var width = 500 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

var svg = d3.select("body")
   .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 + ")");

var data = [
  { country:"India", sales:100, profit:100, loss:10 },
  { country:"US", sales:100, profit:80, loss:40 },
  { country:"Aus", sales:100, profit:70, loss:30 },
  { country:"NZ", sales:100, profit:70, loss:30 }
];

var stack = d3.stack()
   .keys(["sales","profit","loss"])
   .order(d3.stackOrderNone)
   .offset(d3.stackOffsetNone);

var series = stack(data);


var x = d3.scaleBand()  // band scale
   .domain(data.map(function(d) {
         return d.country; }))  // return the country.
   .range([10, width-10])
   .padding(0.1); // space between (value between 0 and 1).

var y = d3.scaleLinear()
   .domain([0, d3.max(series, function(d) {   
       return d3.max(d, function(d) { 
            return d[0] + d[1]; });  })]) // access stack values (not d.y, d.y0)
   .range([height, 0]);  

// color scale:
var colors = d3.scaleOrdinal()
  .range(["b33040", "#d25c4d", "#f2b447", "#d9d574"])
  .domain(series.map(function(d) { return d.key; }));

var yAxis = d3.axisLeft(y)
   .ticks(5)
   .tickSize(-width, 0, 0)
   .tickFormat( function(d) { return d } );

var xAxis = d3.axisBottom(x)
     
svg.append("g")
   .attr("class", "y axis")
   .call(yAxis);

svg.append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + height + ")")
  .call(xAxis);

var groups = svg.selectAll("g.cost")
  .data(series)
  .enter().append("g")
  .attr("class", "cost")
  .style("fill", function(d) { return colors(d); });


// More changes:
var rect = groups.selectAll("rect")    
 .data(function(d) { return d; })
 .enter()
 .append("rect")
 .attr("x", function(d) { return x(d.data.country); })  // Access country.
 .attr("y", function(d) { return y(d[0] + d[1]); })
 .attr("height", function(d) { return y(d[0]) - y(d[0] + d[1]); })
 .attr("width", x.bandwidth())

// Minor changes:
var legend = svg.selectAll(".legend")
 .data(series)
 .enter().append("g")
 .attr("class", "legend")
 .attr("transform", function(d, i) { return "translate(30," + i * 19 + ")"; });

legend.append("rect")
  .attr("x", width - 18)
  .attr("width", 18)
  .attr("height", 18)
  .style("fill", function(d) {
      return colors(d.key);
  });

legend.append("text")
  .attr("x", width + 5)
  .attr("y", 9)
  .attr("dy", ".35em")
  .style("text-anchor", "start")
  .text(function(d) { return d.key; }) 

 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> 

为简化代码段,我删除了鼠标交互

创建图例

这可以简化一点,但这不是问题的一部分,不过,我将其与色标一起包括在这里.

I am creating a stacked bar chart using d3 charts. I have button in the front-end. On clicking the button chart should display.On clicking a button I am getting following error.

Error: attribute y: Expected length, "NaN".

Error: attribute height: Expected length, "NaN".

Error: attribute width: Expected length, "10,900".

import * as d3 from 'd3';

export class stackedbarchart  {

    sbar(box_id){

      d3.select(box_id)
      .select("svg")
      .remove();

      var margin = { top: 20, right: 160, bottom: 35, left: 30 };

      var width = 1100 - margin.left - margin.right,
          height = 500 - margin.top - margin.bottom;

      var svg = d3.select("body")
        .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 + ")");

      var data = [
        { country:"India", sales:100, profit:100, loss:10 },
        { country:"US", sales:100, profit:80, loss:40 },
        { country:"aus", sales:100, profit:70, loss:30 }
      ];

      var stack = d3.stack()
      .keys(["sales","profit","loss"])
      .order(d3.stackOrderNone)
      .offset(d3.stackOffsetNone);

      var series = stack(data);

      var x = d3.scaleOrdinal()
        .domain(series[0].map(function(d) {
          console.log(d)
           return d.x; }))
        .range([10, width-10], 0.02);

      var y = d3.scaleLinear()
        .domain([0, d3.max(series, function(d) {   
          return d3.max(d, function(d) { 
            return d.y0 + d.y; });  })])
        .range([height, 0]);

      var colors = ["b33040", "#d25c4d", "#f2b447", "#d9d574"];

      var yAxis = d3.axisLeft(y)
        .ticks(5)
        .tickSize(-width, 0, 0)
        .tickFormat( function(d) { return d } );

      var xAxis = d3.axisBottom(x)
      console.log(xAxis)

      svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

      svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

      var groups = svg.selectAll("g.cost")
        .data(series)
        .enter().append("g")
        .attr("class", "cost")
        .style("fill", function(d, i) { return colors[i]; });

      var rect = groups.selectAll("rect")    
        .data(function(d) { return d; })
        .enter()
        .append("rect")
        .attr("x", function(d) { return x(d.x); })
        .attr("y", function(d) { return y(d.y0 + d.y); })
        .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
        .attr("width", x.range())
        .on("mouseover", function() { tooltip.style("display", null); })
        .on("mouseout", function() { tooltip.style("display", "none"); })
        .on("mousemove", function(d) {
          var xPosition = d3.mouse(this)[0] - 15;
          var yPosition = d3.mouse(this)[1] - 25;
          tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
          tooltip.select("text").text(d.y);
        });

      var legend = svg.selectAll(".legend")
        .data(colors)
        .enter().append("g")
        .attr("class", "legend")
        .attr("transform", function(d, i) { return "translate(30," + i * 19 + ")"; });

      legend.append("rect")
        .attr("x", width - 18)
        .attr("width", 18)
        .attr("height", 18)
        .style("fill", function(d, i) {return colors.slice().reverse()[i];});

      legend.append("text")
        .attr("x", width + 5)
        .attr("y", 9)
        .attr("dy", ".35em")
        .style("text-anchor", "start")
        .text(function(d, i) { 
          switch (i) {
            case 0: return "sales";
            case 1: return "profit";
            case 2: return "loss"; 
          }
        });

      var tooltip = svg.append("g")
        .attr("class", "tooltip")
        .style("display", "none");

      tooltip.append("rect")
        .attr("width", 30)
        .attr("height", 20)
        .attr("fill", "white")
        .style("opacity", 0.5);

      tooltip.append("text")
        .attr("x", 15)
        .attr("dy", "1.2em")
        .style("text-anchor", "middle")
        .attr("font-size", "12px")
        .attr("font-weight", "bold") ;

    }
}

Any help or hint in to the right direction would be much appreciated.

解决方案

Changes in D3v4

Changes were made in d3v4 that would break a d3v3 chart. Stacks in d3v3 had x accessors and used y0 and y to represent start and end values for each segment of a stacked chart. The result looks something like this:

 .append("rect")
  .attr("x", function(d) { return x(d.x); })
  .attr("y", function(d) { return y(d.y0 + d.y); })
  .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })

This approach does not work with d3v4+, but it appears to be the approach you are using. At a glance, it appears your code may be based on this v3 code.

Stacks in d3v4+

Let's take a look at what d3.stack produces in d3v4+.

We have our input data:

var data = [
  { country:"India", sales:100, profit:100, loss:10 },
  { country:"US", sales:100, profit:80, loss:40 },
  { country:"aus", sales:100, profit:70, loss:30 }
];

And a stack generator:

var stack = d3.stack()
  .keys(["sales","profit","loss"])
  .order(d3.stackOrderNone)     // default value, does not need to be specified.
  .offset(d3.stackOffsetNone)   // default value, does not need to be specified

When we pass the data to the stack generator we get:

[/*  India     US        AUS   */
 [[0,  100],[0,  100],[0,  100]],  // Sales
 [[100,200],[100,180],[100,170]],  // Profit
 [[200,210],[180,220],[170,200]]   // Loss 
]

If arranging the array as a table, the columns represent each item in the original data array (a series representing a country), the rows represent each key. Each of the two element arrays represent a single stacked bar segment.

The arrays that contain all the values for a given key (for example [[0,100],[0,100],[0,100]] for sales), also contain a property "key" that hold's that key name.

Each of the arrays that represent a single segment (eg: [0,100]), have a property data that is an item in the original data array (eg: {"country": "India","sales": 100,"profit": 100,"loss": 10}).

The number of items in the stack array (the number of rows) is not equal to the number of series (countries in this case), but is equal to the number of keys.

Creating The Scales

The first issue you have is how you specify scales:

var x = d3.scaleOrdinal()
  .domain(series[0].map(function(d) { return d.x; }))
  .range([10, width-10], 0.02);

var y = d3.scaleLinear()
  .domain([0, d3.max(series, function(d) { return d3.max(d, function(d) { return d.y0 + d.y; });  })])
  .range([height, 0]);

Because the stack doesn't create any y0, y or x properties, these won't work.

The x scale should be a band scale (bar charts generally use band scales with d3), and we can just use the original data array to create a domain:

var x = d3.scaleBand()  // band scale
   .domain(data.map(function(d) {
         return d.country; }))  // return the country.
   .range([10, width-10]); 

The y scale needs to access d[0] and d[1] rather than d.y0 and d.y:

var y = d3.scaleLinear()
   .domain([0, d3.max(series, function(d) {   
       return d3.max(d, function(d) { 
            return d[0] + d[1]; });  })]) // access stack values (not d.y, d.y0)
   .range([height, 0]);  

Appending The Rectangles

Now let's move to where we append the rectangles:

    .append("rect")
    .attr("x", function(d) { return x(d.x); })
    .attr("y", function(d) { return y(d.y0 + d.y); })
    .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
    .attr("width", x.range())

As d.x is undefined, we can use x(d.data.country). Where d.y0 and d.y appears we need to subsitute d[0] and d[1]. Lastly x.range() is the width of the graph almost, we can use x.bandwidth() instead, which is the width of a single bar.

Below is your code (starting where you define the margin) with these updates:

var margin = { top: 20, right: 160, bottom: 35, left: 30 };

var width = 500 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

var svg = d3.select("body")
   .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 + ")");

var data = [
  { country:"India", sales:100, profit:100, loss:10 },
  { country:"US", sales:100, profit:80, loss:40 },
  { country:"Aus", sales:100, profit:70, loss:30 },
  { country:"NZ", sales:100, profit:70, loss:30 }
];

var stack = d3.stack()
   .keys(["sales","profit","loss"])
   .order(d3.stackOrderNone)
   .offset(d3.stackOffsetNone);

var series = stack(data);


var x = d3.scaleBand()  // band scale
   .domain(data.map(function(d) {
         return d.country; }))  // return the country.
   .range([10, width-10])
   .padding(0.1); // space between (value between 0 and 1).

var y = d3.scaleLinear()
   .domain([0, d3.max(series, function(d) {   
       return d3.max(d, function(d) { 
            return d[0] + d[1]; });  })]) // access stack values (not d.y, d.y0)
   .range([height, 0]);  

// color scale:
var colors = d3.scaleOrdinal()
  .range(["b33040", "#d25c4d", "#f2b447", "#d9d574"])
  .domain(series.map(function(d) { return d.key; }));

var yAxis = d3.axisLeft(y)
   .ticks(5)
   .tickSize(-width, 0, 0)
   .tickFormat( function(d) { return d } );

var xAxis = d3.axisBottom(x)
     
svg.append("g")
   .attr("class", "y axis")
   .call(yAxis);

svg.append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + height + ")")
  .call(xAxis);

var groups = svg.selectAll("g.cost")
  .data(series)
  .enter().append("g")
  .attr("class", "cost")
  .style("fill", function(d) { return colors(d); });


// More changes:
var rect = groups.selectAll("rect")    
 .data(function(d) { return d; })
 .enter()
 .append("rect")
 .attr("x", function(d) { return x(d.data.country); })  // Access country.
 .attr("y", function(d) { return y(d[0] + d[1]); })
 .attr("height", function(d) { return y(d[0]) - y(d[0] + d[1]); })
 .attr("width", x.bandwidth())

// Minor changes:
var legend = svg.selectAll(".legend")
 .data(series)
 .enter().append("g")
 .attr("class", "legend")
 .attr("transform", function(d, i) { return "translate(30," + i * 19 + ")"; });

legend.append("rect")
  .attr("x", width - 18)
  .attr("width", 18)
  .attr("height", 18)
  .style("fill", function(d) {
      return colors(d.key);
  });

legend.append("text")
  .attr("x", width + 5)
  .attr("y", 9)
  .attr("dy", ".35em")
  .style("text-anchor", "start")
  .text(function(d) { return d.key; })

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

To simplify the snippet I removed mouse interaction

Creating the Legend

This can be simplified a bit, it isn't part of the question, nonetheless, I've included it here, along with a color scale.

这篇关于无法创建堆积的条形图?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆