草庐IT

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

coder 2024-05-16 原文

我正在通过嵌套 .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 值和类。

我的问题在于访问条形的高度和 y 值的“速率”数据。在上面的表格中,它给出了一个 NaN。我还尝试使用类别数据附加 g 元素,然后附加 rects:

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

这允许我访问速率数据,但将所有 36 个柱映射到每个 rangeBand。

它在更扁平的数据结构中也能正常工作,但当它向下嵌套两层时我似乎无法使用它,尽管查看了大量示例和 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);

    });

2016 年 2 月 18 日更新。

看来我还没有充分解释我想做的事情。图表的折线图和条形图版本将分开显示,即用户可以根据对选择元素的输入来查看任一版本。另请注意,我无法控制数据最初的输入方式。

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

这个问题是在我还在研究它时提出的,但我从未解决过这个问题——我使用了一种解决方法,即对数据进行两个单独的嵌套。

最佳答案

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

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

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

新专栏:

  • 月:时间变量;映射到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 变量明确地对数据进行分组。现在您的数据存在于两层中。第一层有三个组(每个类别一个)。第二层包含每条线/每组柱的汇率数据。您还必须嵌套数据选择 - 第一层用于绘制线条,第二层用于绘制条形。

嵌套数据:

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 )

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

创建一个函数来选择条/线和过渡/切换它们的可见性/不透明度。当您的下拉输入更改时运行,并将下拉输入作为函数的输入。

关于javascript - d3访问分组条形图中的嵌套数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34302958/

有关javascript - d3访问分组条形图中的嵌套数据的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby-on-rails - Rails 编辑表单不显示嵌套项 - 2

    我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby - 将散列转换为嵌套散列 - 2

    这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

  6. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  7. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  8. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  9. Ruby——嵌套类和子类是一回事吗? - 2

    下面例子中的Nested和Child有什么区别?是否只是同一事物的不同语法?classParentclassNested...endendclassChild 最佳答案 不,它们是不同的。嵌套:Computer之外的“Processor”类只能作为Computer::Processor访问。嵌套为内部类(namespace)提供上下文。对于ruby​​解释器Computer和Computer::Processor只是两个独立的类。classComputerclassProcessor#Tocreateanobjectforthisc

  10. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

随机推荐