Vraag Hoe om te gaan met lagen met ontbrekende gegevenspunten in d3.layout.stack ()


Ik gebruik d3.stack om een ​​gestapeld vlakdiagram te maken, maar ik krijg een foutmelding als ik niet een gelijk aantal items in elke laag heb. Ik begin met een reeks gegevens zoals deze:

[  
   {key:'Group1',value,date},  
   {key:'Group1',value,date},  
   {key:'Group1',value,date},  
   {key:'Group2',value,date},  
   {key:'Group2',value,date}  
]

en nadat ik het door nest () en stack () heb uitgevoerd, heb ik zoals verwacht de volgende indeling:

[  
   {key: 'Group1',  
    values: [ {key,value,date}, {key,value,date}, {key,value,date} ] },  
   {key: 'Group2',  
    values: [ {key,value,date}, {key,value,date} ]  }  
]

Ik heb een voorbeeld van een gestapeld gebied enigszins aangepast om het probleem in deze jsFiddle aan te tonen: http://jsfiddle.net/brentkeller/rTC3c/2/

Als u een van de gegevenspunten in de array sourceData verwijdert, wordt het foutbericht 'Kan geen eigenschap' 1 'van niet gedefinieerd' in de console weergegeven.

Is er een manier om d3.stack alleen maar te laten uitgaan van nulwaarden voor de ontbrekende gegevenspunten? Zo niet, is er dan een elegante oplossing om de ontbrekende waarden in te vullen?


22
2018-02-05 17:36


oorsprong


antwoorden:


Dit is niet d3-specifiek, maar eerder een algemene oplossing voor het opvullen van de hiaten in een reeks van gecodeerde gegevens. Ik heb je jsfiddle aangepast hier met de volgende functie:

function assignDefaultValues( dataset )
{
    var defaultValue = 0;
    var keys = [ 'Group1' , 'Group2', 'Group3' ];
    var hadData = [ true, true, true];
    var newData = [];
    var previousdate = new Date();
    var sortByDate = function(a,b){ return a.date > b.date ? 1 : -1; };

    dataset.sort(sortByDate);
    dataset.forEach(function(row){
        if(row.date.valueOf() !== previousdate.valueOf()){
            for(var i = 0 ; i < keys.length ; ++i){
                if(hadData[i] === false){
                    newData.push( { key: keys[i], 
                                   value: defaultValue, 
                                   date: previousdate });
                }
                hadData[i] = false;
            }
            previousdate = row.date;
        }
        hadData[keys.indexOf(row.key)] = true; 
    });
    for( i = 0 ; i < keys.length ; ++i){
        if(hadData[i] === false){
            newData.push( { key: keys[i], value: defaultValue, 
                            date: previousdate });
        }
    }
    return dataset.concat(newData).sort(sortByDate);
}

Het loopt door de gegeven dataset en wanneer het een nieuwe tegenkomt date waarde, wijst een standaardwaarde toe aan keys die nog niet zijn gezien.


17
2018-02-24 04:01



Stack doet precies wat het zegt, grafieken stapelen, zodat jij als gebruiker verantwoordelijk bent voor het verstrekken van de gegevens in de juiste indeling. Dit is logisch als je erover nadenkt, omdat stack in principe data-indeling agnostisch is. Het biedt veel flexibiliteit, met als enige beperking dat het voor elke laag toegang heeft tot hetzelfde aantal punten. Hoe zou het bepalen welke punten ontbreken? Gegeven dat de eerste laag vijf punten had en de tweede laag tien punten, mist de eerste laag vijf punten? Of zijn beide lagen ontbrekende punten omdat een derde laag zelfs meer punten bevat. En als er punten ontbreken, welke? Aan het begin, aan het einde, ergens in het midden? Nogmaals, er is geen verstandige manier voor een stackimplementatie om dit uit te zoeken (tenzij het zeer rigide datastructuren zou forceren).

Maar, is er niets dat u kunt doen? Ik denk dat je kan. Ik kan je niet een volledige implementatie geven, maar kan je een aantal tips in de goede richting geven. We beginnen hier:

var stack = d3.layout.stack()
  .offset("zero")
  .values(function(d) { return d.values; })

Hier geeft u alleen de waarden terug, die in uw voorbeeld het resultaat zijn van de nest-operator. Dus op dit punt heb je de mogelijkheid om de waarden te "repareren".

Het eerste dat u hoeft te doen, is het bepalen van het maximale aantal waarnemingen.

var nested = nest.entries(data);
var max = nested.reduce(function(prev, cur) {
  return Math.max(prev, cur.values.length);
}, 0);

Nu het lastige gedeelte. Zodra u het maximale aantal elementen kent, moet u dit aanpassen de functie die wordt doorgegeven aan waarden. Hier moet je aannames doen over de gegevens. Uit uw vraag begrijp ik dat voor sommige groepen waarden ontbreken. Dus er zijn er twee mogelijkheden. Of u gaat ervan uit dat de groep met het maximale aantal elementen alle items in het bereik bevat of dat u een bepaald bereik aanneemt en controleert alle groepen als ze bevatten waarden voor elke "vinkje" in uw bereik. Dus als uw bereik een datumbereik is (zoals in uw voorbeeld) en je verwacht voor elke dag (of wat ooit interval voor die kwestie) een meting, je moet de items in de groep lopen en de gaten zelf vullen. Ik zal proberen een (ongetest) voorbeeld voor een numeriek bereik te geven:

// define some calculated values that can be reused in correctedValues
var range = [0, 1];
var step = 0.1;

function correctedValues(d) {
  var values = d.values;
  var result = [];
  var expected = 0;
  for (var i = 0; i < values.length; ++i) {
     var value = values[i];
     // Add null-entries
     while (value.x > expected) {
       result.push({x: expected, otherproperties_you_need... });
       expected += step;
     }
     result.push(value); // Now add the real data point.
     expected = value.x;
  }

  // Fill up the end of of the array if needed
  while(expected < range[1]) {
    result.push({x: expected, otherproperties_you_need... });
    expected += step;
  }
  return result;
}

// Now use our costom function for the stack
var stack = d3.layout.stack()
 .offset("zero")
 .values(correctedValues)
...

Zoals gezegd, dit deel is niet getest en lost niet direct uw probleem op (omdat ik een numeriek bereik gebruik), maar ik denk dat het u een idee moet geven over hoe u uw probleem kunt oplossen (en wat de werkelijke oorzaak van uw probleem is).


5
2018-02-21 17:23



Zoals anderen hebben uitgelegd, zou het onredelijk zijn als de gestapelde grafiek de ontbrekende waarden voor elk gegevenspunt zou raden, omdat er zoveel manieren zijn om de waarden te interpoleren en er geen voor de hand liggende keuze is.

Echter, d3.svg.line() lijkt een redelijke manier te bieden om je eigen methode van interpolatie te kiezen en ontbrekende waarden in te vullen. Hoewel het is ontworpen voor het genereren van SVG-paden, kunt u het waarschijnlijk aanpassen voor het definiëren van regels in het algemeen. Interpolatiemethoden worden hier voorgesteld:

https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-line_interpolate

Het is jammer dat de klasse, voor nu, al deze prachtige interpolatiemethoden heeft (die nergens anders in d3 voorkomen) maar beperkt is tot het genereren van SVG-padgegevens in plaats van willekeurige tussentijdse waarden. Misschien als @mbostock dit ziet, zal hij overwegen de functionaliteit te generaliseren.

Echter, voor nu wil je misschien gewoon een vork van d3 maken en het tussenresultaat van nemen line(data) voordat het wordt geschreven naar een SVG path string, in het deel van de bron dat doet de interpolatie, hieronder:

  function line(data) {
    var segments = [],
        points = [],
        i = -1,
        n = data.length,
        d,
        fx = d3_functor(x),
        fy = d3_functor(y);

    function segment() {
      segments.push("M", interpolate(projection(points), tension));
    }

    while (++i < n) {
      if (defined.call(this, d = data[i], i)) {
        points.push([+fx.call(this, d, i), +fy.call(this, d, i)]);
      } else if (points.length) {
        segment();
        points = [];
      }
    }

    if (points.length) segment();

    return segments.length ? segments.join("") : null;
  }

1
2018-02-21 20:51