D3刷在分组的条形图 [英] D3 brushing on grouped bar chart

查看:177
本文介绍了D3刷在分组的条形图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试刷新以类似于此示例的方式工作,但使用分组的条形图: http: //bl.ocks.org/mbostock/1667367



我真的不太清楚刷牙的工作原理(我一直没能找到任何好的教程),所以我有点失去了什么是错误。我将尝试包括下面的代码的相关位。图表正在跟踪修复损坏的构建按天的时间,然后按投资组合分组。到目前为止,画刷被创建,用户可以移动和拖动它,但主图表中的条形图被重绘得奇怪,x轴根本没有更新。任何帮助,你可以给予非常感谢。谢谢。

  // x0是X轴上的时间刻度
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是X轴上的投资组合范围
var main_x1 = d3.scale.ordinal();
var mini_x1 = d3.scale.ordinal();

//定义X轴
var main_xAxis = d3.svg.axis()
.scale(main_x0)
.tickFormat(dateFormat)
(底部);

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

绑定数据后...

  //定义轴域
main_x0.domain(data.result.map(function(d){return d.date;})
.sort 。上升));
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);

//为迷你图创建画笔
var brush = d3.svg.brush()
.x(mini_x0)
.on(brush );

添加轴的等。

  //创建条形图
var bar = main.selectAll(。bars)
.data(嵌套)
.enter (g)
.attr(class,function(d){return d.key +-group bar;})
.attr 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){returntranslate + main_x0(d.date)+,0);})
.attr(width,function(d){return main_x1.rangeBand();})
.attr x函数(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);
}


解决方案

使用画笔设置x-scale域,当您的x-scale是 ordinal 缩放时。换句话说,x轴的预期域是类别列表,而不是max-min数值范围。所以问题是在刷牙功能的顶部:

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

brush.extent()设置的域



根据wiki ,如果附加到刷子函数的标度之一是序数标度,则 brush.extent()是输出范围中的值,而不是输入域中的值。 有序标度没有 invert()方法将范围值转换为域值。



因此,您有几个选项如何处理:



您可以使用主要x轴的线性时间标度而不是序数标度重新执行整个图形。但是,你必须编写自己的函数来计算每一个轴的宽度,而不是使用 .rangeBand()



您可以创建自己的反转函数,以确定哪些分类值( mini_x0.domain 上的日期)包含在范围内由 brush.extent()返回。然后您必须两者重置 main_x0.domain 才能在轴上包含这些日期,

main_x0。是,并更改范围。通过使图表的范围更大,您可以使条形更宽敞。与裁剪路径结合使用以截断绘图区域外的小节,这只会显示某个子集的小节,这是您想要的。



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



所以,如果你想要的是图形区域 填充该宽度以填充整个绘图区域。您的原始x范围是从[0,100],刷子覆盖区域[20,60],那么您需要一个满足这些条件的新范围:




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

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



因此,




  • 新范围的总宽度为-0)/(60-20))*(100-0)= 250;

  • 新范围的开始处于(0-(20/100)* 250) -50;

  • 新范围的结尾是(-50)+ 250 = 200。


$ b b

现在你可以做所有的代数来自己找出这个转换。但这只是另一种类型的缩放公式,因此为什么不创建一个新的缩放函数来在旧范围和放大范围之间进行转换。



具体来说,我们需要一个线性标度,其输出范围设置为绘图区域的实际范围。然后根据我们要拉伸的刷新区域的范围设置域以覆盖绘图区域。最后,我们通过使用线性标尺来计算出范围的原始最大值和最小值离屏幕多远的距离,从而找出顺序标度的范围。



在代码中:

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

//刷新函数:
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])$ ​​b $ b],0.2);

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

bar.selectAll(rect)
.attr(transform,function(d){
returntranslate(+ 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/


I am trying to get brushing to work similar to this example, but with a grouped bar chart: 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. 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. In other words, the expected domain of your x-axis is a list of categories, not a max-min numerical extent. 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.

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. Ordinal scales don't have an invert() method to convert range values into domain values.

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. 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().

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(). 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.

Or you can leave the domain of 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. 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:

  • the 20% mark of the new range width is at 0;
  • the 60% mark of the new range width is at 100.

Therefore,

  • the total width of the new range is ( (100-0) / (60-20) )*(100-0) = 250;
  • the start of the new range is at (0 - (20/100)*250) = -50;
  • the end of the new range is at (-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/

这篇关于D3刷在分组的条形图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆