【问题标题】:D3 Grouped Bar Chart with JSON nested data带有 JSON 嵌套数据的 D3 分组条形图
【发布时间】:2014-01-25 01:39:32
【问题描述】:

我正在尝试创建一个与此示例类似的分组条形图:http://bl.ocks.org/mbostock/3887051 我的数据是来自 MongoDB 的 JSON,我无法访问一些嵌套数据。我希望图表在 Y 轴上按 buildFixTime 排列,X 轴按时间排列,条形按“组”分组。这是一个示例记录:

{
        "result" : [
                {
                        "_id" : {
                                "month" : 1,
                                "day" : 22,
                                "year" : 2014,
                                "group" : "Sales"
                        },
                        "buildFixTime" : 3710497
                },
                {
                        "_id" : {
                                "month" : 1,
                                "day" : 23,
                                "year" : 2014,
                                "group" : "Sales"
                        },
                        "buildFixTime" : 79209205
                },
                {
                        "_id" : {
                                "month" : 1,
                                "day" : 24,
                                "year" : 2014,
                                "group" : "Sales"
                        },
                        "buildFixTime" : 35611663.4
                },
                {
                        "_id" : {
                                "month" : 1,
                                "day" : 24,
                                "year" : 2014,
                                "group" : "Service"
                        },
                        "buildFixTime" : 18273221.333333332
                }
        ],
        "ok" : 1
}

加载 JSON 文件后,我有以下代码(不包括不重要的部分):

// This adds new elements to the data object
data.result.forEach(function(d) {
  d.group = d._id.group;
  d.date = new Date(d._id.year, d._id.month-1, d._id.day);
});

// define the axis domains
main_x0.domain(d3.extent(data.result, function(d) { return d.date; }));
main_x1.domain(d3.ascending(data.result, function(d) { return d.group; } ));
main_y.domain(d3.extent(data.result, function(d) { return (d.buildFixTime / 1000) / 60 / 60 + 2 ; }));

// flatten out the data
var nested = d3.nest()
    .key(function(d) { return d._id.group; })
    .entries(data.result);

添加轴线等后,我有这个块,这就是问题所在。我认为我需要能够访问转换/翻译行中的日期,但我不确定如何获取它 - 我应该创建另一个 D3 嵌套对象以进一步展平数据吗?感谢您的帮助!

var bar = main.selectAll(".bars")
    .data(nested)
  .enter().append("g")
    .attr("class", "g")

    // This seems to be where part of my problem is
    .attr("transform", function(d) { console.log(d); return "translate(" + main_x0(d.values.date) + ",0)"; });

bar.selectAll("rect").append("rect")
    .data(function(d) { return d.values; })
  .enter().append("rect")
    .attr("transform", function(d) { console.log(d.date); return "translate(" + main_x0(d.date) + ",0)"; })
    .attr("width", main_x1.rangeBand())
    .attr("x", function(d) { return main_x1(d.date); })
    .attr("y", function(d) { return main_y(d.buildFixTime); })
    .attr("height", function(d) { return main_height - main_y(d.buildFixTime); })
    .style("fill", function(d) { return color(d.key); });

【问题讨论】:

  • 我不确定您为什么要在 g 元素上设置 transform。对于各个条形,您拥有放置它们所需的所有信息,而这正是您真正需要做的。
  • 你能澄清一下吗?您希望将给定月份的所有条形图组合在一起,还是将给定“组”的所有条形图组合在一起?
  • 你能提供正确的来源吗?我很想建立小组。

标签: json d3.js


【解决方案1】:

您似乎对您的两个 x-scales 感到有些困惑,每个 x-scales 做什么以及何时使用它们。

使用链接示例中的变量名称,x0 刻度在页面上分隔不同的条形集群

我并不完全清楚您所说的“我希望 [to] 将 X 轴按时间和条形按“组”分组。”我假设您想要所有“组”每个月都聚集在一起,不同的月份分布在页面上。如果您希望将每个“组”的所有日期聚集在一起,那么 x0 的域应该是您的,而不是您的日期,您必须调整本讨论的其余部分.

由于日期可以被视为连续数字,因此您可以对它们使用线性刻度;通过传入数据的extent(即最大值和最小值)而不是所有可能值的排序列表,这似乎就是您正在做的事情。但是,线性比例没有简单的方法来告诉您每个数据点之间有多少空间,我们需要这些空间来定位和调整条形。因此,为了让一切变得更简单,将其设为序数比例并将其域设置为已排序的日期。

x1 比例将集群内的各个条形间隔,相对于该集群的起始位置。因此,域是您希望在每个月列出的“组”。

您似乎从未设置过此比例的范围。 x1 刻度的范围是每个簇的可用宽度,它由 x0 刻度的带宽决定:如果你有很多簇,每个簇会更窄,所以各个条也必须更窄.

因此您的轴设置将是(假设集群之间有 20% 的填充,而各个条之间没有填充):

// initialization
main_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width], 0.2); 
main_x1 = d3.scale.ordinal();
main_y  = d3.scale.linear().range([main_height, 0] );

// once you have the data
main_x0.domain(data.result.map( function(d) { return d.date; } )
                          .sort(d3.ascending) 
               );

main_x1.domain(data.result.map( function(d) { return d.group; } )
                          .sort(d3.ascending) 
               )
       .rangeRoundBands([0, main_x0.rangeBand() ], 0);

main_y.domain(d3.extent(data.result, function(d) { return d.buildFixTime ; }));

(PS 我不知道你为什么在你的 y 尺度域上有各种各样的转换方程。你在绘制数据时只使用原始 buildFixTime,所以这就是你想要的域. 从毫秒到小时的转换应该在你的tickFormat 函数中完成。)

然后,在您的绘图方法中,您需要记住main_x0 是您的日期集群 比例,而main_x1 是您每个日期内的组规模。每个条的水平位置是这样确定的:首先将其移动到集群的开头,然后再将其再次移动到该组在集群中的位置。应用于日期的 x0 比例为您提供第一个班次,而应用于组的 x1 比例为您提供第二个班次。

换句话说,如果你想要这样的布局:

abcde  abcde  abcde  abcde
 Jan.   Feb.   Mar.   Apr.  
       *             
       >>>>|

然后要找到二月份类别“e”的位置,您需要 x0(Feb) + x1(e)。第一个为您提供 2 月集群的开始(标记为 *),第二个为您提供进入类别 e 的额外转变(标记为 >)。

您的数据按“组”嵌套。因此,您的每个<g> 元素都包含特定类别中的所有条:所有as 在一个中,所有bs 在另一个中。虽然您可以在您的<g> 元素上使用变换来根据该组在每个集群中需要多少移动来移动每个组的起点,但我认为这会使事情变得复杂。正如 Lars 所说,您可以在定位单个条形时做到这一切。

所以你的代码看起来像:

var bar = main.selectAll(".bars")
    .data(nested)
  .enter().append("g")
    .attr("class", function(d){return d.key;})
    //add a useful class based on the data
    //d.key is the value used to create the nested array
    //i.e., the group value for all the bars in the <g>

    .style("fill", function(d) { return color(d.key); });
    //Set the style here and it will be inherited by the bars
    //in the group. If you set it on the individual rectangles, 
    //you will need to use d.group (the data property), 
    //not d.key (the property created by the nest method) 

bar.selectAll("rect").append("rect")
    .data(function(d) { return d.values; })
     //the nested sub-array for each group

  .enter().append("rect")
    .attr("transform", function(d) {
          return "translate(" + main_x0(d.date) + ",0)"; 
     })
    .attr("x", function(d) { return main_x1(d.group); })
    //by using both a transform and an x-position, the two scales
    //are kept separate.  But make sure that you pass in the correct
    //data variable for each scale!  

    .attr("width", main_x1.rangeBand())
    //the width is calculated from the x1 scale, which positions
    //the individual bars within a cluster

    .attr("y", function(d) { return main_y(d.buildFixTime); })
    .attr("height", function(d) { 
             return main_height - main_y(d.buildFixTime); 
     });
    //note that these functions are based on a y-scale with an inverted range, 
    //one that goes from [main_height, 0], as I set up above

同样,如果您希望您的条形按相反的方式分组,您必须将所有 x0 比例函数切换为使用 d.group,并将所有 x1 比例函数切换为使用 d.date

【讨论】:

  • 首先,谢谢 - 这是一个很好的答案 - 非常清晰易懂。尽管实现了您的代码,但我遇到了错误。这一行: .attr("width", main_x1.rangeBand()) 给了我一个“ 属性 width="Infinity" 的无效值我错过了 main_x1 对象的步骤吗?
  • 听起来域没有被设置,所以你得到一个固定的间隔除以 0 组 = 每组无限宽度。 -- 那是因为错误地使用了d3.ascending 排序运算符。我没有发现的原始代码中的错误。 ascending and descending functions 不直接对数组进行排序,它们应该传递给Array.sort function。我会更正答案。
  • 做到了 - 非常感谢您的帮助。我将研究 map 函数以了解域发生了什么。再次感谢。
  • @AmeliaBR - 我们如何在 d3.js 中对多天的数据进行分组假设我每天都有一个月的数据。键是日期,值是平均谷歌排名。我希望它像这样 1-3、4-6、7-9 分组,这将代表 3 天内的平均排名。能指导一下吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-12
相关资源
最近更新 更多