d3访问分组条形图中的嵌套数据

时间:2015-12-16 02:26:47

标签: javascript csv d3.js nested grouping

我通过嵌套.csv文件构建分组条形图。该图表也可以作为折线图查看,因此我想要一个适合线对象的嵌套结构。我原来的.csv看起来像这样:

Month,Actual,Forecast,Budget
Jul-14,200000,-,74073.86651
Aug-14,198426.57,-,155530.2499
Sep-14,290681.62,-,220881.4631
Oct-14,362974.9,-,314506.6437
Nov-14,397662.09,-,382407.67
Dec-14,512434.27,-,442192.1932
Jan-15,511470.25,511470.25,495847.6137
Feb-15,-,536472.5467,520849.9105
Mar-15,-,612579.9047,596957.2684
Apr-15,-,680936.5086,465313.8723
May-15,-,755526.7173,739904.081
Jun-15,-,811512.772,895890.1357

我的嵌套是这样的:

  d3.csv("data/net.csv", function(error, data) {
    if (error) throw error;

            var headers = d3.keys(data[0]).filter(function(head) {
            return head != "Month";
          });

                  data.forEach(function(d) {
                    d.month = parseDate(d.Month);
          });
            var categories = headers.map(function(name) { 

              return {
                name: name, // "name": the csv headers except month
                values: data.map(function(d) { 
                  return {
                    date: d.month, 
                    rate: +(d[name]),
                    };
                }),
              };

            });

构建我的图表的代码是:

  var bars = svg.selectAll(".barGroup")
        .data(data) // Select nested data and append to new svg group elements
        .enter()
        .append("g")
        .attr("class", "barGroup")   
        .attr("transform", function (d) { return "translate(" + xScale(d.month) + ",0)"; });

  bars.selectAll("rect")
        .data(categories)
        .enter()
        .append("rect")
        .attr("width", barWidth)
        .attr("x", function (d, i) { if (i < 2) {return 0;} else {return xScale.rangeBand() / 2;}})
        .attr("y", function (d) { return yScale(d.rate); })
        .attr("height", function (d) { return h - yScale(d.rate); })
        .attr("class", function (d) { return lineClass(d.name); });

g元素很好,单个条形图正在映射到它们,正确应用了x值和类。

我的问题在于访问&#39; rate&#39;对于条的高度和y值。在上面的表格中,它给出了NaN。我还尝试使用类别数据附加g元素,然后附加rects:

  .data(function(d) { return d.values })

这允许我访问费率数据,但将所有36个条形图映射到每个rangeBands。

它在一个更扁平的数据结构中也可以正常工作,但是当它嵌套两个级别时,我似乎无法使用它,尽管通过大量的例子和SO问题进行了查看。

如何访问费率数据?

根据Cyril的要求,这里是完整的代码:

    var margin = {top: 20, right: 18, bottom: 80, left: 50},
        w = parseInt(d3.select("#bill").style("width")) - margin.left - margin.right,
        h = parseInt(d3.select("#bill").style("height")) - margin.top - margin.bottom;

    var customTimeFormat = d3.time.format.multi([
      [".%L", function(d) { return d.getMilliseconds(); }],
      [":%S", function(d) { return d.getSeconds(); }],
      ["%I:%M", function(d) { return d.getMinutes(); }],
      ["%I %p", function(d) { return d.getHours(); }],
      ["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }],
      ["%b %d", function(d) { return d.getDate() != 1; }],
      ["%b", function(d) { return d.getMonth(); }],
      ["%Y", function() { return true; }]
    ]);


    var parseDate = d3.time.format("%b-%y").parse;

    var displayDate = d3.time.format("%b %Y");

    var xScale = d3.scale.ordinal()
        .rangeRoundBands([0, w], .1);

    var xScale1 = d3.scale.linear()
          .domain([0, 2]);

    var yScale = d3.scale.linear()
         .range([h, 0])
         .nice();

    var xAxis = d3.svg.axis()
        .scale(xScale)
        .tickFormat(customTimeFormat)
        .orient("bottom");

    var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient("left")
        .innerTickSize(-w)
        .outerTickSize(0);

    var svg = d3.select("#svgCont")
        .attr("width", w + margin.left + margin.right)
        .attr("height", h + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var thous = d3.format(",.0f")

    var lineClass = d3.scale.ordinal().range(["actual", "forecast", "budget"]);  

    var tip = d3.tip()
      .attr('class', 'd3-tip')
      .offset([-10, 0])
      .html(function(d) {
        return "<p id='date'>" + displayDate(d.date) + "</p><p id='value'>$" + thous(d.rate);
      })

    d3.csv("data/net.csv", function(error, data) {
      if (error) throw error;

              var headers = d3.keys(data[0]).filter(function(head) {
              return head != "Month";
            });

                    data.forEach(function(d) {
                      d.month = parseDate(d.Month);
            });
              var categories = headers.map(function(name) { 

                return {
                  name: name, 
                  values: data.map(function(d) {
                    return {
                      date: d.month, 
                      rate: +(d[name]),
                      };
                  }),
                };

              });

    var min = d3.min(categories, function(d) {
                        return d3.min(d.values, function(d) {
                            return d.rate;
                        });
                    });



    var max = d3.max(categories, function(d) {
                        return d3.max(d.values, function(d) {
                            return d.rate;
                        });
                    });

    var minY = min < 0 ? min * 1.2 : min * 0.8;

                  xScale.domain(data.map(function(d) { return d.month; }));
                  yScale.domain([minY, (max * 1.1)]);

    var barWidth = headers.length > 2 ? xScale.rangeBand() / 2 : xScale.rangeBand() ;

    svg.call(tip);

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

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

    var bars = svg.selectAll(".barGroup")
          .data(data) 
          .enter()
          .append("g")
          .attr("class", "barGroup")   
          .attr("transform", function (d) { return "translate(" + xScale(d.month) + ",0)"; });

    bars.selectAll("rect")
          .data(categories)
          .enter()
          .append("rect")
          .attr("width", barWidth)
          .attr("x", function (d, i) { if (i < 2) {return 0;} else {return xScale.rangeBand() / 2;}})
          .attr("y", function (d) { return yScale(d.rate); })
          .attr("height", function (d) { return h - yScale(d.rate); })
          .attr("class", function (d) { return lineClass(d.name) + " bar"; });


    var legend = svg.selectAll(".legend")
          .data(headers) 
          .enter()
          .append("g")
          .attr("class", "legend");

    legend.append("line")
          .attr("class", function(d) { return lineClass(d); })
          .attr("x1", 0)
          .attr("x2", 40)
          .attr("y1", function(d, i) { return (h + 30) + (i *14); })
          .attr("y2", function(d, i) { return (h + 30) + (i *14); });

    legend.append("text")
        .attr("x", 50)
        .attr("y", function(d, i) { return (h + 32) + (i *14); })
        .text(function(d) { return d; });

    svg.selectAll(".bar")
       .on('mouseover', tip.show)
       .on('mouseout', tip.hide);

    });

2月18日更新&#39; 16。

似乎我还没有解释我想要做的事情。将分别看到图表的线条和条形图,即用户可以根据对选择元素的输入看到任一个。另请注意,我无法控制数据最初的来源。

我有a version of exactly how it should work here.

当我还在努力解决这个问题时,我提出了这个问题,但我从来没有解决过这个问题 - 我使用了一种解决方法来做两个独立的数据嵌套。

2 个答案:

答案 0 :(得分:5)

链接到jsfiddle: https://jsfiddle.net/sladav/rLh4qwyf/1/

我认为问题的根源在于您要使用原始数据集中未明确存在的两个变量:(1)类别(2)费率

您的数据格式为宽格式,每个类别都有自己的变量,而rate的值存在于月份和给定类别之一的十字路口。我认为你最终的嵌套方式至少应该解决这个问题,但我不清楚翻译中是否存在某些东西或哪些内容会丢失。从概念上讲,我认为从一个与你想要完成的事情相匹配的组织开始更有意义。我重新格式化了原始数据并再次进行了处理 - 从概念上讲,嵌套看似简单明了......

NEW COLUMNS:

  • 月:时间变量;映射到X轴
  • 类别:分类值[实际,预测,预算];用于分组/着色
  • 率:数值;映射到Y轴

重组CSV(删除NULL):

Month,Category,Rate
Jul-14,Actual,200000
Aug-14,Actual,198426.57
Sep-14,Actual,290681.62
Oct-14,Actual,362974.9
Nov-14,Actual,397662.09
Dec-14,Actual,512434.27
Jan-15,Actual,511470.25
Jan-15,Forecast,511470.25
Feb-15,Forecast,536472.5467
Mar-15,Forecast,612579.9047
Apr-15,Forecast,680936.5086
May-15,Forecast,755526.7173
Jun-15,Forecast,811512.772
Jul-14,Budget,74073.86651
Aug-14,Budget,155530.2499
Sep-14,Budget,220881.4631
Oct-14,Budget,314506.6437
Nov-14,Budget,382407.67
Dec-14,Budget,442192.1932
Jan-15,Budget,495847.6137
Feb-15,Budget,520849.9105
Mar-15,Budget,596957.2684
Apr-15,Budget,465313.8723
May-15,Budget,739904.081
Jun-15,Budget,895890.1357

使用新格式化的数据,首先使用d3.nest使用CATEGORY变量显式地对数据进行分组。现在您的数据分为两层。第一层有三个组(每个类别一个)。第二层包含每行/每组条的RATE数据。您还必须嵌套数据选择 - 第一层用于绘制线条,第二层用于条形图。

嵌套数据:

var nestedData = d3.nest()
      .key(function(d) { return d.Category;})
      .entries(data)

为您的分组第一层数据创建svg组:

d3.select(".plot-space").selectAll(".g-category")
    .data(nestedData)
    .enter().append("g")
    .attr("class", "g-category")

使用此数据添加您的行/路径:

d3.selectAll(".g-category").append("path")
    .attr("class", "line")
    .attr("d", function(d){ return lineFunction(d.values);})
    .style("stroke", function(d) {return color(d.key);})

最后,“进入”第二层添加条形/矩形:

d3.selectAll(".g-category").selectAll(".bars")
     .data(function(d) {return d.values;})
     .enter().append("rect")
        .attr("class", "bar")
        .attr("x", function(d) {return x(d.Month);})
        .attr("y", function(d) {return y(d.Rate);})
        .attr("width", 20)
        .attr("height", function(d) {return height - y(d.Rate)})
        .attr("fill", function(d) {return color(d.Category)})

这是一种直截了当的方法(至少对我来说),因为你一次只使用一个类别,使用分组数据绘制一条线,然后使用单个数据点绘制条形。

懒惰编辑:

并排获得类别栏

将序数比例映射类别创建为[1,nCategories]。使用它来动态偏移条形

translate( newScale(category)*barWidth )

显示条形或线条(不是两者)

创建一个选择条形图/线条和过渡/切换其可见性/不透明度的函数。当您的下拉输入更改并使用下拉输入作为函数的输入时运行。

答案 1 :(得分:1)

我相信,问题在于您将类别数组绑定到条形选择,如下所示:

bars.selectAll("rect").data(categories)

据我所知(没有正在运行的演示) categories 是一个只有四个值的数组(每个类别一个)。

你必须更深入一步&#39;在您的嵌套数据结构中。

要为每个类别绘制一组条形图,您需要迭代类别并将包含实际值的数组绑定到选区。

类似的东西:

  categories.each(function (category) {
    var klass = category.name;
    bars.selectAll("rect ." + klass)
        .data(category.values)
        .enter()
        .append("rect")
        .attr("class", klass)
        .attr("width", barWidth)
        .attr("x", function (d, i) { /* omitted */})
        .attr("y", function (d) { return yScale(d.rate); })
        .attr("height", function (d) { return h - yScale(d.rate); });
  });

----编辑

而不是上面的代码,考虑像绘制线条一样绘制条形图。像这样:

var bars = svg.selectAll(".barGroup")
    .data(categories)
    .enter()
    .append("g")
    .attr("class", function (d) { return lineClass(d.name) + "Bar barGroup"; })
    .attr("transform", function (d, i) {
        var x = i > 1 ? xScale.rangeBand() / 2 : 0;
        return "translate(" + x + ",0)";
    })
    .selectAll('rect')
    .data(function (d) { return d.values; })
    .enter()
    .append("rect")
    .attr("class", "bar")
    .attr("width", barWidth)
    .attr("x", function (d, i) { return xScale(d.date); })
    .attr("y", function (d, i) { return yScale(d.rate); })
    .attr("height", function (d) { return h - yScale(d.rate); });