web-dev-qa-db-ja.com

配列全体を単一のキーとして扱うのではなく、配列の要素を個別のレコードとして扱うようにクロスフィルターに指示する方法はありますか?

一部のフィールド値が配列であるデータセットがあり、クロスフィルターとd3.jsまたはdc.jsを使用して、これらの値のそれぞれがデータセットに存在した回数のヒストグラムを表示します。

次に例を示します。

var data = [
    {"key":"KEY-1","tags":["tag1", "tag2"]},
    {"key":"KEY-2","tags":["tag2"]},
    {"key":"KEY-3","tags":["tag3", "tag1"]}];

var cf = crossfilter(data);

var tags = cf.dimension(function(d){ return d.tags;});
var tagsGroup = tags.group();


dc.rowChart("#chart")
    .renderLabel(true)
    .dimension(tags)
    .group(tagsGroup)
    .xAxis().ticks(3);

dc.renderAll();

そしてJSFiddle http://jsfiddle.net/uhXf5/2/

そのコードを実行すると、次のようなグラフが生成されます。

graph1

しかし、私が欲しいのは次のようなものです:

enter image description here

物事をさらに複雑にするには、行をクリックして、クリックされたタグでデータセットをフィルタリングできると便利です。

誰かがそれを達成する方法を考えていますか?

ありがとう、コスチャ

39

自分で解決しました、ここで作業コードのフィドルです http://jsfiddle.net/uhXf5/6/

誰かが同様の問題に遭遇した場合のコードは次のとおりです。

function reduceAdd(p, v) {
  v.tags.forEach (function(val, idx) {
     p[val] = (p[val] || 0) + 1; //increment counts
  });
  return p;
}

function reduceRemove(p, v) {
  v.tags.forEach (function(val, idx) {
     p[val] = (p[val] || 0) - 1; //decrement counts
  });
  return p;

}

function reduceInitial() {
  return {};  
}


var data = [
    {"key":"KEY-1","tags":["tag1", "tag2"], "date":new Date("10/02/2012")},
    {"key":"KEY-2","tags":["tag2"], "date": new Date("10/05/2012")},
    {"key":"KEY-3","tags":["tag3", "tag1"], "date":new Date("10/08/2012")}];

var cf = crossfilter(data);

var tags = cf.dimension(function(d){ return d.tags;});
var tagsGroup = tags.groupAll().reduce(reduceAdd, reduceRemove, reduceInitial).value();
// hack to make dc.js charts work
tagsGroup.all = function() {
  var newObject = [];
  for (var key in this) {
    if (this.hasOwnProperty(key) && key != "all") {
      newObject.Push({
        key: key,
        value: this[key]
      });
    }
  }
  return newObject;
}


var dates = cf.dimension(function(d){ return d.date;});
var datesGroup = dates.group();


var chart = dc.rowChart("#chart");
    chart                                                                                       
    .renderLabel(true)
    .dimension(tags)
    .group(tagsGroup)
    .filterHandler(function(dimension, filter){     
        dimension.filter(function(d) {return chart.filter() != null ? d.indexOf(chart.filter()) >= 0 : true;}); // perform filtering
        return filter; // return the actual filter value
       })
    .xAxis().ticks(3);

var chart2 = dc.barChart("#chart2");
    chart2  
    .width(500)
    .transitionDuration(800)
    .margins({top: 10, right: 50, bottom: 30, left: 40})
    .dimension(dates)
    .group(datesGroup)
    .elasticY(true)
    .elasticX(true)
    .round(d3.time.day.round)    
    .x(d3.time.scale())    
    .xUnits(d3.time.days)
    .centerBar(true)
    .renderHorizontalGridLines(true)       
    .brushOn(true);    


dc.renderAll();
34

上記の例は素晴らしいアプローチです。あなたはそれをさらに一歩進めることができます。上記のソリューションでは、最初に行った選択に基づいてのみフィルタリングします。以降の選択は無視されます。

すべての選択に応答する場合は、次のようにfilterHandlerを作成します。

 barChart.filterHandler (function (dimension, filters) {
   dimension.filter(null);   
    if (filters.length === 0)
        dimension.filter(null);
    else
        dimension.filterFunction(function (d) {
            for (var i=0; i < d.length; i++) {
                if (filters.indexOf(d[i]) >= 0) return true;
            }
            return false; 
        });
  return filters; 
  }
);

ここで作業サンプル: http://jsfiddle.net/jeffsteinmetz/cwShL/

20
Jeff Steinmetz

JeffとKostyaが挙げたアプローチのコンテキストを提供したいと思います。

一般的なgroupメソッドとは異なり、tagsGroupはgroupAllを使用していることに気づくでしょう。 Crossfilterは、「返されるオブジェクトは、topまたはorderメソッドがないことを除いて、標準のグループ化に似ています。代わりに、valueを使用して、一致するすべてのレコードのreduce値を取得します。」 Kostyaは「.value()」メソッドを呼び出して、グループ全体を表す単一のオブジェクトを取得しました。

var tagsGroup = tags.groupAll().reduce(reduceAdd, reduceRemove, reduceInitial).value();

Dc.jsはグループオブジェクトにallメソッドがあることを想定しているため、このオブジェクトはdc.jsではうまく機能しません。 Kostyaはそのオブジェクトに次のような「all」メソッドを持つようにパッチを当てました。

// hack to make dc.js charts work
tagsGroup.all = function() {
  var newObject = [];
  for (var key in this) {
    if (this.hasOwnProperty(key) && key != "all") {
      newObject.Push({
        key: key,
        value: this[key]
      });
    }
  }
  return newObject;
}

これはシンプルなdc.jsチャートで機能しますが、すべてのグループ関数が存在するわけではないため、すべてのdc.js機能を使用することはできません。たとえば、capメソッドはグループオブジェクトに「top」メソッドがあることを想定しているため、チャートで「cap」メソッドを使用することはできません。次のように、topメソッドにパッチを適用することもできます。

topicsGroup.top = function(count) {
    var newObject = this.all();
     newObject.sort(function(a, b){return b.value - a.value});
    return newObject.slice(0, count);
};

これにより、グラフでcapメソッドを使用できるようになります。

barChart
    .renderLabel(true)
    .height(200)
    .dimension(topicsDim)
    .group(topicsGroup)
    .cap(2)
    .ordering(function(d){return -d.value;})
    .xAxis().ticks(3);

更新された例は http://jsfiddle.net/djmartin_umich/m7V89/#base で入手できます。

15
DJ Martin

ジェフの答えは機能しますが、「見つかった」変数を追跡したり、アイテムが見つかった場合にループを継続したりする必要はありません。 Xが[X、Y、Z]にある場合、これはすでに反復量を1/3に削減しています。

else
    dimension.filterFunction(function (d) {
        for (var i=0; i < d.length; i++) {
            if (filters.indexOf(d[i]) >= 0) return true;
        }
        return false; 
    });

または、dc.jsのfilterFunctionメソッドにパッチを適用すると、すべてのケースを処理できます。

7
Matt Traynham

crossfilterdcは配列を使用して次元をサポートするため、これははるかに簡単です。コンテキストと例については、この質問を参照してください: dc.js/crossfilterの配列で次元を使用

3
emiguevara