2012-10-25 78 views
17

我有一个这样的数据结构(假设数据结构是不可转让的):结合家长和嵌套数据与d3.js

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

使用d3.js JavaScript库,我想绘制四个矩形,其中一个用于colors阵列中的每种颜色。来自segments阵列中每个条目的信息用于绘制与其color阵列中的每种颜色对应的矩形。例如,红色和绿色的矩形将有一个宽度和高度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> 

我已经想出了一些代码,实现这一点,但我发现了部分关于使用来自data两个不同层次嵌套的数据会令人困惑,我觉得可能会有更习惯的方法来完成d3.js的相同操作。下面的代码(在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); 

是否有更好的和/或更地道的方式来访问使用d3.js的数据结构,从两个不同层次的嵌套的数据?

编辑

这是我想出了一个解决方案,这要归功于meetamit's answer的封闭思想,并采用更地道d3.js压痕感谢nautat's answer

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

}); 

以下是完整的工作示例:http://jsbin.com/welcome/42885/edit

+0

为什么需要在您的html代码中嵌套svg标签? – btel

+0

我想将所有与“segment”对应的矩形组合在一起。如果我理解正确,我可以通过更改父'svg'元素的'x'和'y'属性来移动第一个'segment'中的所有矩形。如果有更好的方法,我很想知道它。 –

+0

如何使用变换属性: ...':http://www.w3.org/TR/SVG/coords.html#TransformAttribute。如果我理解正确,那么每张图纸应该只有一个svg。 – btel

回答

27

您可以使用封

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

使用闭包感觉对我来说是最好的。我不得不修改你的例子来让事情有效,但总的想法正是我所期待的。请参阅编辑我的问题的完整工作示例。 –

1

在你真正开始创建元素之前,我会试图将colors弄平。如果发生数据更改,我会更新此扁平数据结构并重绘。扁平数据需要存储在某个地方,以便实现真正的d3转换。

这是一个更长的例子,为我工作。你可以看到它在行动here

下面是代码:

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) 

重要这里是flatten函数它执行数据变换,并存储/重用结果作为父数据元素_colors属性。另一个重要的路线是;

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

指出在那里能得到的数据(第一个参数),什么为每个数据元素的唯一的ID(第二个参数)。这有助于识别转场等的现有颜色。

3

你可以这样做以下调整你的数据:

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

这将改变你的输入数据到:

// 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"} 
     ] 
    ] 

这然后可以在标准嵌套数据选择模式中使用:

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

顺便说一句,如果你想更加d3-idiomatic,我会改变链接方法的缩进样式。每次选择更改时,Mike都建议使用半缩进。这有助于明确你正在做什么选择。例如在最后的代码中;变量nested指的是enter()选择。请参阅“选择”一章:http://bost.ocks.org/mike/d3/workshop/

+0

感谢关于缩进风格的提示。我注意到编写d3.js代码的人员正在缩进,但并不理解动机。该链接非常有帮助! –

+0

我想说,这实际上是D3做事的方式(根据Mike Bostock)。请参阅http://bost.ocks.org/mike/nest/#data –