简体   繁体   English

将父数据和嵌套数据与d3.js结合使用

[英]Combining Parent and Nested Data with d3.js

I have a data structure like this ( assume that the data structure is non-negotiable ): 我有这样的数据结构( 假设数据结构是不可协商的 ):

data = {
    segments : [
        {x : 20, size : 10, colors : ['#ff0000','#00ff00']},
        {x : 40, size : 20, colors : ['#0000ff','#000000']}
    ]};

Using the d3.js javascript library, I'd like to draw four rectangles, one for each color in both colors arrays. 使用d3.js javascript库,我想绘制四个矩形,两个colors数组中的每种颜色一个。 Information from each entry in the segments array is used to draw the rectangles corresponding to each color in its color array. 来自segments数组中每个条目的信息用于绘制与其color数组中的每种颜色相对应的矩形。 Eg, The red and green rectangles will have a width and height of 10. The resulting html should look like this: 例如,红色和绿色矩形的宽度和高度为10.生成的html应如下所示:

<div id="container">
    <svg width="200" height="200">
        <g>
            <rect x="20" y="20" width="10" height="10" fill="#ff0000"></rect>
            <rect x="30" y="30" width="10" height="10" fill="#00ff00"></rect>
        </g>
        <g>
            <rect x="40" y="40" width="20" height="20" fill="#0000ff"></rect>
            <rect x="60" y="60" width="20" height="20" fill="#000000"></rect>
        </g>
    </svg>
</div>

I've come up with some code that accomplishes this, but I found the part about using data from two different levels of nesting in data to be confusing, and I feel that there might be a more idiomatic way to accomplish the same with d3.js. 我想出了一些完成此任务的代码,但我发现使用来自data嵌套的两个不同级别的数据的部分令人困惑,我觉得可能有更惯用的方法来实现与d3相同的方法。 JS。 Here's the code (full example at http://jsbin.com/welcome/39650/edit ): 这是代码( http://jsbin.com/welcome/39650/edit上的完整示例):

function pos(d,i) { return d.x + (i * d.size); } // rect position
function size(d,i) { return d.size; }            // rect size
function f(d,i) { return d.color; }              // rect color

// add the top-level svg element and size it
vis = d3
    .select('#container')
    .append('svg')
    .attr('width',200)
    .attr('height',200);

// add the nested svg elements
var nested = vis
    .selectAll('g')
    .data(data.segments)
    .enter()
    .append('g');

// Add a rectangle for each color
nested
    .selectAll('rect')
    .data(function(d) {
        // **** ATTENTION ****
        // Is there a more idiomatic, d3-ish way to approach this?
        var expanded = [];
        for(var i = 0; i < d.colors.length; i++) {
            expanded.push({
                color : d.colors[i],
                x     : d.x
                size  : d.size });
        }
        return expanded;
    })
    .enter()
    .append('rect')
    .attr('x',pos)
    .attr('y',pos)
    .attr('width',size)
    .attr('height',size)
    .attr('fill',f);

Is there a better and/or more idiomatic way to access data from two different levels of nesting in a data structure using d3.js? 是否有更好的和/或更惯用的方法来使用d3.js从数据结构中的两个不同级别的嵌套访问数据?

Edit 编辑

Here's the solution I came up with, thanks to meetamit's answer for the closure idea, and using more idiomatic d3.js indentation thanks to nautat's answer : 这是我想出了解决方案,这要归功于meetamit的回答为封闭的想法,并用更地道d3.js压痕感谢nautat的回答

$(function() {
  var
    vis = null,
    width = 200,
    height = 200,
    data = {
        segments : [
           {x : 20, y : 0, size : 10, colors : ['#ff0000','#00ff00']},
           {x : 40, y : 0, size : 20, colors : ['#0000ff','#000000']}
        ]
    };

    // set the color
    function f(d,i) {return d;}

    // set the position
    function pos(segment) {
      return function(d,i) {
        return segment.x + (i * segment.size);
      };
    }

    // set the size
    function size(segment) {
      return function() {
        return segment.size;
      };
    }

    // add the top-level svg element and size it
    vis = d3.select('#container').append('svg')
        .attr('width',width)
        .attr('height',height);

    // add the nested svg elements
    var nested = vis
        .selectAll('g')
          .data(data.segments)
        .enter().append('g');

    // Add a rectangle for each color.  Size of rectangles is determined
    // by the "parent" data object.
    nested
    .each(function(segment, i) {
      var 
          ps = pos(segment),
          sz = size(segment);

      var colors = d3.select(this)
        .selectAll('rect')
          .data(segment.colors)
        .enter().append('rect')
          .attr('x', ps)
          .attr('y',ps)
          .attr('width', sz)
          .attr('height',sz)
          .attr('fill', f);
  });

});

Here's the full working example: http://jsbin.com/welcome/42885/edit 以下是完整的工作示例: http//jsbin.com/welcome/42885/edit

You can use closures 你可以使用闭包

var nested = vis
  .selectAll('g')
  .data(data.segments);


nested.enter()
  .append('g')
  .each(function(segment, i) {
    var colors = d3.select(this)
      .selectAll('rect')
      .data(segment.colors);

    colors.enter()
      .append('rect')
      .attr('x', function(color, j) { return pos(segment, j); })
      // OR: .attr('x', function(color, j) { return segment.x + (j * segment.size); })
      .attr('width', function(color, j) { return size(segment); })
      .attr('fill', String);
  });

You could do something like the following to restructure your data: 您可以执行以下操作来重构数据:

newdata = data.segments.map(function(s) {
  return s.colors.map(function(d) {
    var o = this; // clone 'this' in some manner, for example:
    o = ["x", "size"].reduce(function(obj, k) { return(obj[k] = o[k], obj); }, {});
    return (o.color = d, o); 
  }, s);
});

This will transform your input data into: 这会将您的输入数据转换为:

// newdata:
    [
      [
        {"size":10,"x":20,"color":"#ff0000"},
        {"size":10,"x":20,"color":"#00ff00"}],
      [
        {"size":20,"x":40,"color":"#0000ff"},
        {"size":20,"x":40,"color":"#000000"}
      ]
    ]

which then can be used in the standard nested data selection pattern: 然后可以在标准嵌套数据选择模式中使用:

var nested = vis.selectAll('g')
    .data(newdata)
  .enter().append('g');

nested.selectAll('rect')
    .data(function(d) { return d; })
  .enter().append('rect')
    .attr('x',pos)
    .attr('y',pos)
    .attr('width',size)
    .attr('height',size)
    .attr('fill',f);

BTW, if you'd like to be more d3-idiomatic, I would change the indentation style a bit for the chained methods. 顺便说一下,如果你想更多地使用d3-idiomatic,我会为链式方法改变缩进样式。 Mike proposed to use half indentation every time the selection changes. Mike建议每次选择更改时使用半缩进。 This helps to make it very clear what selection you are working on. 这有助于清楚地说明您正在进行的选择。 For example in the last code; 例如在最后一个代码中; the variable nested refers to the enter() selection. 变量nested是指enter()选择。 See the 'selections' chapter in: http://bost.ocks.org/mike/d3/workshop/ 请参阅“选择”一章: http//bost.ocks.org/mike/d3/workshop/

I would try to flatten the colors before you actually start creating the elements. 在你真正开始创建元素之前,我会尝试展平colors If changes to the data occur I would then update this flattened data structure and redraw. 如果发生数据更改,我会更新此扁平数据结构并重绘。 The flattened data needs to be stored somewhere to make real d3 transitions possible. 需要将展平的数据存储在某处以使真正的d3转换成为可能。

Here is a longer example that worked for me. 这是一个对我有用的较长的例子。 Yon can see it in action here . Yon可以在这里看到它。

Here is the code: 这是代码:

var data = {
    segments : [
        {x : 20, size : 10, colors : ['#ff0000','#00ff00']},
        {x : 40, size : 20, colors : ['#0000ff','#000000']}
    ]
};

function pos(d,i) { return d.x + (i * d.size); } // rect position
function size(d,i) { return d.size; }            // rect size
function f(d,i) { return d.color; }              // rect color

function flatten(data) {
    // converts the .colors to a ._colors list
    data.segments.forEach( function(s,i) {
        var list = s._colors = s._colors || [];
        s.colors.forEach( function(c,j) {
            var obj = list[j] = list[j] || {}
            obj.color = c
            obj.x = s.x
            obj.size = s.size
        });
    });
}

function changeRect(chain) {
    return chain
    .transition()
    .attr('x',pos)
    .attr('y',pos)
    .attr('width',size)
    .attr('height',size)
    .attr('fill',f)
    .style('fill-opacity', 0.5)
}

vis = d3
.select('#container')
.append('svg')
.attr('width',200)
.attr('height',200);

// add the top-level svg element and size it
function update(){

    flatten(data);

    // add the nested svg elements
    var all = vis.selectAll('g')
    .data(data.segments)

    all.enter().append('g');
    all.exit().remove();

    // Add a rectangle for each color
    var rect = all.selectAll('rect')
    .data(function (d) { return d._colors; }, function(d){return d.color;})

    changeRect( rect.enter().append('rect') )
    changeRect( rect )

    rect.exit().remove()
}

function changeLater(time) {
    setTimeout(function(){
        var ds = data.segments
        ds[0].x    = 10 + Math.random() * 100;
        ds[0].size = 10 + Math.random() * 100;
        ds[1].x    = 10 + Math.random() * 100;
        ds[1].size = 10 + Math.random() * 100;
        if(time == 500)  ds[0].colors.push("orange")
        if(time == 1000) ds[1].colors.push("purple")
        if(time == 1500) ds[1].colors.push("yellow")
        update()
    }, time)
}

update()
changeLater(500)
changeLater(1000)
changeLater(1500)

Important here is the flatten function which does the data conversion and stores/reuses the result as _colors property in the parent data element. 这里重要的是flatten函数,它执行数据转换并将结果作为_colors属性存储/ _colors用于父数据元素。 Another important line is; 另一个重要的路线是;

.data(function (d) { return d._colors; }, function(d){return d.color;})

which specifies where to get the data (first parameter) AND what the unique id for each data element is (second parameter). 它指定获取数据的位置(第一个参数)以及每个数据元素的唯一ID(第二个参数)。 This helps identifying existing colors for transitions, etc. 这有助于识别转换的现有颜色等。

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

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