17

私はこのようなデータ構造を持っています(データ構造が交渉不可能であると仮定します):

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

d3.js javascriptライブラリを使用して、両方のcolors配列の各色に1つずつ、合計4つの長方形を描画したいと思います。配列の各エントリからの情報は、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>

私はこれを達成するいくつかのコードを思いついたが、2つの異なるレベルのネストからのデータを使用することについての部分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を使用して、データ構造内の2つの異なるレベルのネストからデータにアクセスするためのより良いおよび/またはより慣用的な方法はありますか?

編集

クロージャのアイデアに対するmeetamitの回答のおかげで、私が思いついた解決策は次のとおりです。また、nautatの回答のおかげでより慣用的なd3.jsインデントを使用しています。

$(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

4

3 に答える 3

29

クロージャを使用できます

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);
  });
于 2012-10-29T06:09:27.583 に答える
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-イディオムになりたい場合は、連鎖メソッドのインデントスタイルを少し変更します。マイクは、選択が変更されるたびに半分のインデントを使用することを提案しました。これは、作業している選択を非常に明確にするのに役立ちます。たとえば、最後のコードでは、変数nestedenter()選択を参照します。http://bost.ocks.org/mike/d3/workshop/の「selections」の章を参照してください。

于 2012-10-30T18:48:44.890 に答える
1

colors実際に要素の作成を開始する前に、フラット化を試みます。データに変更が発生した場合は、このフラット化されたデータ構造を更新して再描画します。平坦化されたデータは、実際のd3遷移を可能にするためにどこかに保存する必要があります。

これが私のために働いたより長い例です。ヨンはここで実際に動作しているのを見ることができます。

コードは次のとおりです。

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親データ要素のプロパティとして保存/再利用する関数です。もう1つの重要な行は次のとおりです。

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

これは、データを取得する場所(最初のパラメーター)と各データ要素の一意のID(2番目のパラメーター)を指定します。これは、トランジションなどの既存の色を識別するのに役立ちます。

于 2012-10-28T15:29:50.213 に答える