Histogramy D3 a vyřešení problému se zásobníkem

d3.js je extrémně výkonná grafická knihovna a výjimečně užitečná, pokud jde o reprezentaci dat. Ale spolu s velkou mocí přichází velká zodpovědnost...ve skutečnosti ne zodpovědnost, ale spíše velké problémy. Můžete najít ukázky všech druhů grafů d3, ale mám zkušenost, že příklady, které byly zveřejněny ve většině galerií, používají velmi specifické sady dat, díky nimž graf vypadá skvěle, ale v reálném světě nejsou data vždy pěkná. formátovaný. Jedním takovým problémem, který jsem viděl znovu a znovu, je používání histogramových grafů.

Histogramy jsou skvělým způsobem, jak shrnout distribuční data do opravdu jednoduchého grafu. d3 má vestavěnou funkcionalitu, která funguje docela dobře pro histogramy, ale spousta času to docela dobře nezkrátí. Když se podíváte na ukázkový histogram d3, je obecně soubor dat pěkně nakonfigurován, takže vše úhledně zapadá do přesných přihrádek a histogram je vykreslen jako kouzlo. Ale co se stane, když máte data, která chcete zmapovat v 10 přihrádkách, ale vaše data se pohybují od nuly po nějaké náhodné číslo, jako je 10,47? d3 se snaží přinutit graf, aby odpovídal datům, a dělá to dobře, ale někdy to prostě vypadá úplně špatně.

Vezměte si tento příklad. Existují 4 studenti, kteří jsou házeni do různých košů podle počtu minut, které studovali. První pole představuje 3 studenty, kteří se učili nula minut, a poslední pole představuje 1 studenta, který se učil 24,6 minut.

Ten poslední kousek řádku je technicky správný. Přihrádka, do které byl student umístěn, spadá do přihrádky 24 - 25, ale graf neukazuje celou šířku pruhu, jak se očekávalo. Představuje pouze šířku 0,4 přihrádky, ale každý druhý pruh v grafu představuje plnou hodnotu 1 přihrádky. Rozhodně to není ideální výsledek. Když používáte funkci automatického bin() d3, často je to výsledek. Zde je kód d3, který lze použít k automatickému seskupování dat pro vytváření grafů:

// The Number of Bins that should be registered
const numberOfBins = 25;

// Use d3 to generate the bin array of all values automatically
const histogram = d3
      .bin()
      .domain(x.domain())
      .value(d => d.value)
      .thresholds(numberOfBins);

// Save the Array of Bins to a constant
const bins = histogram(values);

Vše technicky funguje a je to zmapované, ale ta poslední přihrádka je problém. Tento problém se objevuje v otázkách znovu a znovu na StackOverflow. Ten poslední zásobník je třeba nějak vyladit, aby měl správnou šířku. Moje myšlenka byla pokračovat a získat šířku první přihrádky v poli hodnot přihrádek (souřadnice výkresu x0 a x1) a jednoduše rozšířit hodnotu souřadnice x1 poslední přihrádky na správnou šířku. Zdá se to logické, protože osa se generuje automaticky, takže by měla odpovídajícím způsobem vykreslit osu správné délky. Jednoduchá oprava pole a šířka je správná:

// Save the Array of Bins to a constant
const bins = histogram(values);

//Last Bin value fixed
bins[bins.length - 1].x1 = bins[bins.length - 1].x0 + bins[0].x1;

Problém s šířkou koše je vyřešen, ale nyní je tu nový problém! Rozsah a doména xAxis již musí být deklarovány, takže d3.bin() ví, kolik místa košík zabere, a podle toho vypočítat hodnoty přihrádky. Přidání další šířky do poslední přihrádky posune pruhy mimo graf. Abychom to napravili, bylo by nutné aktualizovat xAxis, ale to by ovlivnilo velikosti přihrádek a jste zpět na začátku. Je frustrující, že funkce d3 bin() funguje pouze tehdy, když jsou datové sady pěkně naformátované a podle mých zkušeností je to obvykle nerealistické.

Když jsem se hlouběji ponořil do toho, co funkce d3 bin() dělá, uvědomil jsem si, že místo toho, abyste nechali d3 vytvořit velikosti přihrádek, můžete jej přinutit, aby používal vlastní šířky přihrádek tím, že mu předáte vlastní pole hodnot jako položku prahů namísto hodnotu jednoho čísla.

Vlastní pole hodnot je vytvořeno mapováním celé délky osy x (xAxis.scale().domain()[1]) dělené počtem přihrádek (numberOfBin), aby se získala šířka jednotlivé přihrádky, a poté vynásobením aktuální index (* i). Toto pole se předá funkci prahů().

// Set the number of bins   
const numberOfBins = 25;


const thresholdArr = [...Array(numberOfBins)].map(
   (item, i) => (xAxis.scale().domain()[1] / numberOfBins) * i
);

// Generate the final bins array
const histogram = d3
      .bin()
      .domain(x.domain())
      .value(d => d.value)
      .thresholds(thresholdArr);

// Save the bins to a constant
const bins = histogram(values);

To je očekávaný vzhled! Vygenerování prahové hodnoty mimo d3 a následné ruční doplnění hodnot pole dělá trik. Dokud d3 neaktualizuje funkci koše, je to jednoduchý způsob, jak obejít poslední problém s košem. Doufám, že to pomůže dalším lidem, kteří se nevyhnutelně setkají se stejným problémem, který jsem měl já.