【问题标题】:D3 brushing on grouped bar chartD3在分组条形图上刷
【发布时间】:2014-01-31 16:41:26
【问题描述】:

我正在尝试让刷牙工作类似于此示例,但使用分组条形图:http://bl.ocks.org/mbostock/1667367

我对刷牙的工作原理不是很了解(我找不到任何好的教程),所以我对出了什么问题有点茫然。我将尝试在下面包含相关的代码。该图表按天跟踪修复损坏的构建的时间,然后按投资组合分组。到目前为止,画笔已创建,用户可以移动和拖动它,但主图表中的条形被奇怪地重新绘制,x 轴根本没有更新。您可以提供的任何帮助将不胜感激。谢谢。

// x0 is the time scale on the X axis
var main_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
var mini_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);

// x1 is the portfolio scale on the X axis
var main_x1 = d3.scale.ordinal();
var mini_x1 = d3.scale.ordinal();

// Define the X axis
var main_xAxis = d3.svg.axis()
    .scale(main_x0)
    .tickFormat(dateFormat)
    .orient("bottom");

var mini_xAxis = d3.svg.axis()
    .scale(mini_x0)
    .tickFormat(dateFormat)
    .orient("bottom");

绑定数据后...

// define the axis domains
main_x0.domain(data.result.map( function(d) { return d.date; } )
    .sort(d3.ascending));
mini_x0.domain(data.result.map( function(d) { return d.date; } )
    .sort(d3.ascending));

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

// Create brush for mini graph
var brush = d3.svg.brush()
  .x(mini_x0)
  .on("brush", brushed);

添加轴等后

// Create the bars
var bar = main.selectAll(".bars")
  .data(nested)
.enter().append("g")
  .attr("class", function(d) { return d.key + "-group bar"; })
  .attr("fill", function(d) { return color(d.key); } );

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

这里是画笔功能(尝试几个不同的选项)...

function brushed() {
    main_x1.domain(brush.empty() ? mini_x1.domain() : brush.extent());

    //main.select("rect")
      //.attr("x", function(d) { return d.values; })
      //.attr("width", function(d) { return d.values; });
    bar.select("rect")
      .attr("width", function(d) { return main_x1.rangeBand(); })
      .attr("x", function(d) { return main_x1(d.portfolio); });
      //.attr("y", function(d) { console.log(d); return main_y(d.buildFixTime); })
      //.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });

    main.select(".x.axis").call(main_xAxis);
}

【问题讨论】:

  • 看起来您在 rect 元素中附加了 rect 元素——您不需要这样做。此外,您可能希望在您的 brushed 函数中使用 bar.selectAll("rect")。看起来您正在更新 main_x1 的域,然后重绘 main_xAxis,它使用了不同的比例。
  • 好的,有道理,但是当我将域更改为 main_x0 时,我收到以下错误:Uncaught TypeError: Object 113 has no method 'getDay'
  • 你能提供一个完整的例子来说明问题吗?
  • 你能把它缩小到一个最小的例子吗?回购中有很多代码,其中大部分与此无关。
  • 嗨 Lars,这是一个简化版:github.com/goodspeedj/builds/blob/master/js/test.js 再次感谢。

标签: javascript d3.js brush


【解决方案1】:

当您的 x-scale 是 ordinal 比例时,问题来自尝试使用画笔设置 x-scale 域。换句话说,您的 x 轴的预期域是类别列表,而不是最大最小数值范围。所以问题就出在刷机功能上面:

function brushed() {
    main_x0.domain(brush.empty() ? mini_x0.domain() : brush.extent());

brush.extent() 设置的域是一个由两个数字组成的数组,然后完全摆脱了您的序数比例。

According to the wiki,如果附加到画笔函数的刻度之一是序数刻度,则brush.extent() 返回的值是输出范围内的值,而不是输入域内的值。 Ordinal scales don't have an invert() method 将范围值转换为域值。

所以,你有几个选择如何继续:

您可以使用主要 x 轴的线性时间刻度而不是序数刻度来重新绘制整个图表。但是你必须编写自己的函数来计算每天在该轴上的宽度,而不是能够使用.rangeBand()

您可以创建自己的“反转”函数来确定哪些分类值(mini_x0.domain 上的日期)包含在brush.extent() 返回的范围内。然后你必须both重置main_x0.domain以仅包含轴上的那些日期,过滤掉你的矩形以仅绘制这些矩形。

或者您可以将main_x0.保留为,并改为更改范围。通过使图表的范围更大,您可以将条形隔开得更大。结合剪切路径来切断绘图区域外的条形,这具有仅显示特定条形子集的效果,这就是您想要的。

但是新的范围应该是什么? brush.extent() 返回的范围是刷亮矩形的开始和结束位置。如果您使用这些值作为主图上的范围,您的整个图将被压缩到该宽度。这与你想要的相反。您想要的是最初填充该宽度的图形区域被拉伸以填充整个绘图区域。

所以,如果你的原始 x 范围是从 [0,100] 开始,并且画笔覆盖了 [20,60] 区域,那么你需要一个满足这些条件的新范围:

  • 新范围宽度的 20% 标记为 0;
  • 新范围宽度的 60% 标记为 100。

因此,

  • 新范围的总宽度为 ( (100-0) / (60-20) )*(100-0) = 250;
  • 新范围的开始是 (0 - (20/100)*250) = -50;
  • 新范围的结尾是 (-50) + 250 = 200。

现在您可以自己计算所有代数来计算这种转换。但这实际上只是另一种缩放方程,所以为什么不创建一个 new 缩放函数来在旧范围和放大范围之间进行转换。

具体来说,我们需要一个线性刻度,其输出范围设置为绘图区域的实际范围。然后根据我们要拉伸以覆盖绘图区域的拉丝区域的范围设置域。最后,我们通过使用线性刻度来计算出原始范围的最大值和最小值在屏幕之外的距离,从而计算出 序数刻度 的范围。从那里,我们可以调整其他序数比例并重新定位所有矩形。

在代码中:

//Initialization:
var main_xZoom = d3.scale.linear()
    .range([0, main_width - 275])
    .domain([0, main_width - 275]);

//Brushing function:
function brushed() {
    var originalRange = main_xZoom.range();
    main_xZoom.domain(brush.empty() ? 
                     originalRange: 
                     brush.extent() );

    main_x0.rangeRoundBands( [
        main_xZoom(originalRange[0]),
        main_xZoom(originalRange[1])
        ], 0.2);

    main_x1.rangeRoundBands([0, main_x0.rangeBand()], 0);

    bar.selectAll("rect")
        .attr("transform", function (d) {
            return "translate(" + main_x0(d.date) + ",0)";
        })
        .attr("width", function (d) {
            return main_x1.rangeBand();
        })
        .attr("x", function (d) {
            return main_x1(d.portfolio);
        });

    main.select("g.x.axis").call(main_xAxis);
}

基于您的简化代码的工作小提琴(注意:您仍然需要在主图上设置一个剪切矩形):
http://fiddle.jshell.net/CjaD3/1/

【讨论】:

  • 这是一个有趣的谜题。事后看来,我很遗憾建议 (on your previous question) 您使用序数刻度而不是时间刻度。实际上,让它在时间尺度上工作并不难,您只需要通过找到连续两天的缩放值之间的差异来获取每个天簇的宽度。
  • @AmeliaBR 对于这个例子,我感激不尽。它非常好,正是我开始所需要的。非常感谢!
  • @AmeliaBR 这是一个很棒的解释;谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多