简体   繁体   English

D3刷组合条形图

[英]D3 brushing on grouped bar chart

I am trying to get brushing to work similar to this example, but with a grouped bar chart: http://bl.ocks.org/mbostock/1667367 我试图刷牙工作类似于这个例子,但有一个分组的条形图: http//bl.ocks.org/mbostock/1667367

I don't really have a good understanding of how brushing works (I haven't been able to find any good tutorials), so I'm a bit at a loss as to what is going wrong. 我真的不太了解刷牙是如何工作的(我还没有找到任何好的教程),所以我对于出了什么问题感到有些不知所措。 I will try to include the relevant bits of code below. 我将尝试在下面包含相关的代码。 The chart is tracking the time to fix broken builds by day and then grouped by portfolio. 该图表跟踪按天修复损坏的构建的时间,然后按投资组合分组。 So far the brush is created and the user can move and drag it, but the bars in the main chart are re-drawn oddly and the x axis is not updated at all. 到目前为止,刷子已创建,用户可以移动并拖动它,但主图表中的条形图被奇怪地重新绘制,x轴根本不会更新。 Any help you can give would be greatly appreciated. 您将给予的任何帮助将不胜感激。 Thank you. 谢谢。

// 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");

After binding the data... 绑定数据后......

// 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);

After adding the axis's, etc. 添加轴后等

// 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); });

Here is the brush function (trying several different options)... 这是刷子功能(尝试几种不同的选项)......

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);
}

The problem comes from trying to use the brush to set the x-scale domain, when your x-scale is an ordinal scale. 问题来自于尝试使用画笔来设置x比例域,当x尺度是序数尺度时。 In other words, the expected domain of your x-axis is a list of categories, not a max-min numerical extent. 换句话说,x轴的预期域是一个类别列表,而不是max-min数值范围。 So the problem is right at the top of the brushing function: 所以问题就在刷牙功能的顶部:

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

The domain set by brush.extent() is an array of two numbers, which then completely throws off your ordinal scale. brush.extent()设置的域是一个包含两个数字的数组,然后完全brush.extent()您的序数。

According to the wiki , if one of the scales attached to a brush function is an ordinal scale, the values returned by brush.extent() are values in the output range, not in the input domain. 根据wiki ,如果附加到画笔函数的其中一个音阶是序数音阶,则brush.extent()返回的值是输出范围中的值,而不是输入域中的值。 Ordinal scales don't have an invert() method to convert range values into domain values. 序数标度没有用于将范围值转换为域值invert()方法

So, you have a few options on how to proceed: 那么,您有几个选项可以继续:

You could re-do the whole graph using a linear time scale for your main x-axes instead of an ordinal scale. 您可以使用主x轴的线性时间刻度而不是序数刻度来重新绘制整个图形。 But then you have to write your own function to figure out the width of each day on that axis instead of being able to use .rangeBand() . 但是你必须编写自己的函数来计算该轴上每天的宽度,而不是能够使用.rangeBand()

You can create your own "invert" function to figure out which categorical values (dates on the mini_x0.domain ) are included in the range returned by brush.extent() . 您可以创建自己的“反转”函数,以确定哪些分类值( mini_x0.domain上的mini_x0.domain )包含在brush.extent()返回的范围内。 Then you would have to both reset the main_x0.domain to only include those dates on the axis, and filter out your rectangles to only draw those rectangles. 然后,你就必须重置main_x0.domain只包含在轴线上的日期你的矩形过滤掉,只画那些矩形。

Or you can leave the domain of main_x0. 或者你可以离开main_x0.域名 main_x0. be, and change the range instead. 是,并改为改变范围 By making the range of the graph larger, you space out the bars greater. 通过使图形的范围更大,可以使条形更大。 In combination with a clipping path to cut off bars outside the plotting area, this has the effect of only showing a certain subset of bars, which is what you want anyway. 结合剪切路径来切断绘图区域外的条形图,这具有仅显示条形的某个子集的效果,这无论如何都是您想要的。

But what should the new range be? 但新范围应该是什么? The range returned by brush.extent() is the beginning and end positions of the brushing rectangle. brush.extent()返回的范围是刷子矩形的起始位置和结束位置。 If you used these values as the range on the main graph, your entire graph would be squished down to just that width. 如果您将这些值用作主图上的范围,则整个图形将被压缩到该宽度。 That's the opposite of what you want. 这与你想要的相反。 What you want is for the area of the graph that originally filled that width to be stretched to fill the entire plotting area. 你想要的是最初填充该宽度的图形区域被拉伸以填充整个绘图区域。

So, if your original x range is from [0,100], and the brush covers the area [20,60], then you need a new range that satisfies these conditions: 因此,如果您的原始x范围是[0,100],并且画笔覆盖了区域[20,60],那么您需要一个满足以下条件的新范围:

  • the 20% mark of the new range width is at 0; 新范围宽度的20%标记为0;
  • the 60% mark of the new range width is at 100. 新范围宽度的60%标记为100。

Therefore, 因此,

  • the total width of the new range is ( (100-0) / (60-20) )*(100-0) = 250; 新范围的总宽度为((100-0)/(60-20))*(100-0)= 250;
  • the start of the new range is at (0 - (20/100)*250) = -50; 新范围的起点是(0 - (20/100)* 250)= - 50;
  • the end of the new range is at (-50) + 250 = 200. 新范围的结尾是(-50)+ 250 = 200。

Now you could do all the algebra for figuring out this conversion yourself. 现在你可以做所有的代数来自己搞清楚这个转换。 But this is really just another type of scaling equation, so why not create a new scale function to convert between the old range and the zoomed-in range. 但这实际上只是另一种缩放方程式,所以为什么不创建一个新的缩放函数来在旧范围和放大范围之间进行转换。

Specifically, we need a linear scale , with its output range set to be the actual range of the plotting area. 具体来说,我们需要一个线性比例 ,其输出范围设置为绘图区域的实际范围。 Then set the domain according to the range of the brushed area that we want to stretch to cover the plotting area. 然后根据我们想要拉伸的拉丝区域的范围设置域以覆盖绘图区域。 Finally, we figure out the range of the ordinal scale by using the linear scale to figure out how far off the screen the original max and min values of the range would be. 最后,我们通过使用线性比例来计算出序数比例的范围,以计算出距离屏幕的原始最大值和最小值的距离。 And from there, we-can resize the other ordinal scale and reposition all the rectangles. 从那里,我们可以调整其他序数尺度并重新定位所有矩形。

In code: 在代码中:

//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);
}

Working fiddle based on your simplified code (Note: you still need to set a clipping rectangle on the main plot): 基于简化代码的工作小提琴(注意:您仍然需要在主图上设置剪裁矩形):
http://fiddle.jshell.net/CjaD3/1/ http://fiddle.jshell.net/CjaD3/1/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM