C’è un modo per dire a crossfilter di trattare gli elementi dell’array come record separati invece di trattare l’intero array come chiave singola?

Ho set di dati in cui alcuni dei valori di campo sono array e mi piacerebbe utilizzare crossfilter e d3.js o dc.js per visualizzare l’istogramma di quante volte ciascuno di quei valori era presente nel set di dati.

Ecco un esempio:

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

E JSFiddle http://jsfiddle.net/uhXf5/2/

Quando eseguo quel codice, produce un grafico come questo:

Graph1

Ma quello che voglio è qualcosa del genere:

inserisci la descrizione dell'immagine qui

Per rendere le cose ancora più complicate sarebbe fantastico poter fare clic su una qualsiasi delle righe e filtrare il set di dati tramite il tag su cui è stato fatto clic.

Qualcuno ha qualche idea su come ottenerlo?

Grazie, Kostya

Risolto da solo, ecco il trucco con codice funzionante http://jsfiddle.net/uhXf5/6/

Ecco il codice nel caso in cui qualcuno si imbattesse in un problema simile:

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

L’esempio sopra è un ottimo approccio. Puoi fare un passo in più però. Nella soluzione sopra, verrà filtrato solo in base alla prima selezione effettuata. Qualsiasi selezione successiva viene ignorata.

Se si desidera che risponda a tutte le selezioni, si creerà un filterHandler come segue:

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

Esempio di lavoro qui: http://jsfiddle.net/jeffsteinmetz/cwShL/

Mi piacerebbe provare a fornire un contesto per l’approccio elencato da Jeff e Kostya.

Noterai che tagGroup utilizza groupAll a differenza del metodo di gruppo tipico. Crossfilter ci dice che “L’object restituito è simile a un raggruppamento standard, tranne che non ha metodi top o di ordine, ma usa il valore per recuperare il valore di riduzione per tutti i record corrispondenti.” Kostya ha chiamato il metodo “.value ()” per recuperare il singolo object che rappresenta l’intero gruppo.

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

Questo object non funzionerà bene con dc.js perché dc.js si aspetta che l’object gruppo abbia un metodo all. Kostya ha patchato quell’object per avere un metodo “tutto” in questo modo:

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

Ciò funzionerà con un semplice grafico dc.js, ma non sarà ansible utilizzare tutte le funzionalità dc.js poiché non tutte le funzioni di gruppo sono presenti. Ad esempio, non sarà ansible utilizzare il metodo “cap” sul grafico perché il metodo cap prevede che l’object gruppo abbia un metodo “top”. Puoi anche applicare il patch al metodo top in questo modo:

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

Ciò consentirà al tuo grafico di utilizzare il metodo cap:

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

Un esempio aggiornato è disponibile su http://jsfiddle.net/djmartin_umich/m7V89/#base

La risposta di Jeff funziona, ma non è necessario tenere traccia della variabile “trovata” o continuare il ciclo se è stato trovato un object. Se X è in [X, Y, Z], questo ha già tagliato la quantità di iterazioni in 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; }); 

In alternativa, è ansible applicare il metodo dc.js filterFunction e questo dovrebbe gestire tutti i casi.

Questo è molto più semplice ora, dal momento che crossfilter e dc supportano le dimensioni con array. Vedi questa domanda per il contesto e l’esempio: Uso di dimensioni con matrici in dc.js / crossfilter