'How to fill "holes" in dataset to plot on a table with D3 for zebra striping
I'm running into an issue with zebra striping my table when there is "missing" data. I'll explain below with an abstracted example:
Say I have painting objects. Each painting has an artist and belongs to a set.
[{
'name':'The Jewish Bride',
'set':3,
'artist':'Rembrandt'
},...]
Approach 0
It's pretty straightforward to get a table going where each artist is displayed with the names of their paintings.
| Artist | Painting | ||
|---|---|---|---|
| Rembrandt | The Jewish Bride | Another painting name | … |
| Another artist | … | ||
| … | … |
Here's a sample codepen: https://codepen.io/KevinGutowski/pen/gOobaeR
Approach 1
The difficulty comes in where I'm trying to group these paintings by set. Here's an example of what I'm trying to accomplish
| Artist | Set 1 | Set 2 | Set 3 |
|---|---|---|---|
| Leonardo da Vinci | Mona Lisa (1) | Last Supper (3) | |
| Michelangelo | The Creation of Adam (2) | ||
| Rembrandt | The Night Watch (1), Herman Doomer (1) | The Jewish Bride (3) |
My 'missing" data stems from the fact that some artists don't have a painting in a set.
I tried using a <table> to plot this data out but couldn't figure out how to properly position the cells in the correct columns:
| Artist | Set 1 | Set 2 | Set 3 |
|---|---|---|---|
| Leonardo da Vinci | Mona Lisa (1) | Last Supper (3) | |
| Michelangelo | The Creation of Adam (2) | ||
| Rembrandt | The Night Watch (1), Herman Doomer (1) | The Jewish Bride (3) |
https://codepen.io/KevinGutowski/pen/RwxNWyK
The empty data causes all the cells to shift over to the left.
Approach 2
Finally, in attempt to fix the position issue I converted over to using css grid.
https://codepen.io/KevinGutowski/pen/VwywoxX
I get really close however now that I want to zebra stripe my rows, it's impossible due to the empty "cells" in my grid.
Any ideas for how to approach this? Regardless if I'm using a <table> or css grid it seems like I need to setup my data so that each row has some object (even if its empty) within each column.
Solution 1:[1]
Rendering tabular data in d3 is probably one of the few cases, where it is better to have wide data instead of long data.
In order to get your data from long into wide the d3.group is a first step (although I liked the deprecated d3.nest more, because it kept the original keys) and then flatten the nested structure. Something like this works for your use case:
const paintingsByArtistAndSet = Array.from(
d3.group(paintings,d => d.artist, d => d.set),
([artist, sets]) => {
let ret = {};
ret["Artist"] = artist;
Array.from(sets).forEach(d => {
ret["Set " + d[0]] = d[1].map(d => d.name).join(", ");
});
return ret;
}
);
Whenever you have wide data, you can easily generate an array of columns using a set, even if not every row has an entry for every column:
const columns = Array.from(data.reduce((cols, d) => {
Object.keys(d).forEach(col => {
cols.add(col);
});
return cols;
}, new Set()));
Then, use this columns array as data for appending the table cells in each row.
rows.selectAll("td")
.data(row => columns.map(col => col in row ? row[col] : "-"))
.join("td")
const paintings = [{
'name': 'Mona Lisa',
'set': 1,
'artist': 'Leonardo da Vinci'
},
{
'name': 'Last Supper',
'set': 2,
'artist': 'Leonardo da Vinci'
},
{
'name': 'The Creation of Adam',
'set': 2,
'artist': 'Michelangelo'
},
{
'name': 'The Night Watch',
'set': 1,
'artist': 'Rembrandt'
},
{
'name': 'Herman Doomer',
'set': 1,
'artist': 'Rembrandt'
},
{
'name': 'The Jewish Bride',
'set': 3,
'artist': 'Rembrandt'
}
];
const paintingsByArtistAndSet = Array.from(
d3.group(paintings, d => d.artist, d => d.set),
([artist, sets]) => {
let ret = {};
ret["Artist"] = artist;
Array.from(sets).forEach(d => {
ret["Set " + d[0]] = d[1].map(d => d.name).join(", ");
});
return ret;
}
);
const columns = Array.from(paintingsByArtistAndSet.reduce((cols, d) => {
Object.keys(d).forEach(col => {
cols.add(col);
});
return cols;
}, new Set()));
const table = d3.select("body").append("table");
table.append("thead")
.append("tr")
.selectAll("th")
.data(columns)
.join("th")
.text(d => d);
table.append("tbody")
.selectAll("tr")
.data(paintingsByArtistAndSet)
.join("tr")
.selectAll("td")
.data(row => columns.map(col => col in row ? row[col] : "-"))
.join("td")
.text(d => d);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | deristnochda |
