docs/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>steelbreeze/pivot</title>
<style>
@import url("https://cdn.jsdelivr.net/npm/holiday.css@0.9.8");
th {
text-align: left;
}
.Label {
text-transform: capitalize;
}
</style>
</head>
<body>
<nav>
<ul>
<li>
<a href="/">steelbreeze</a>
</li>
<li>
<a href="../landscape">landscape</a>
</li>
<li>
<a href="../pivot">pivot</a>
</li>
<li>
<a href="../state">state</a>
</li>
</ul>
</nav>
<header>
<h1>steelbreeze/pivot</h1>
<p>Minimalist JavaScript library written in TypeScript to pivot data by one or more dimensions.</p>
</header>
<main>
<h2>Example</h2>
<h3>Fulham Football Club men's squad at the end of the 2020/21 season</h3>
<table id="squad"></table>
</main>
<html-include href="https://steelbreeze.net/components/footer.html"></html-include>
<script src="https://steelbreeze.net/components/html-include.js"></script>
<script src="./dist/pivot.min.js"></script>
<script type="module">
const squad = [
{ shirt: 1, position: 'Goalkeeper', givenName: 'Alphonse', familyName: 'Areola', country: 'France', dateOfBirth: new Date('1993-02-27') },
{ shirt: 12, position: 'Goalkeeper', givenName: 'Marek', familyName: 'Rodak', country: 'Slovakia', dateOfBirth: new Date('1996-12-13') },
{ shirt: 31, position: 'Goalkeeper', givenName: 'Fabrico', familyName: 'Agosto Ramirez', country: 'Spain', dateOfBirth: new Date('1987-12-31') },
{ shirt: 2, position: 'Defender', givenName: 'Kenny', familyName: 'Tete', country: 'Netherlands', dateOfBirth: new Date('1995-10-09') },
{ shirt: 3, position: 'Defender', givenName: 'Michael', familyName: 'Hector', country: 'Jamaica', dateOfBirth: new Date('1992-07-19') },
{ shirt: 4, position: 'Defender', givenName: 'Denis', familyName: 'Odoi', country: 'Belgium', dateOfBirth: new Date('1988-05-27') },
{ shirt: 5, position: 'Defender', givenName: 'Joachim', familyName: 'Anderson', country: 'Denmark', dateOfBirth: new Date('1996-05-31') },
{ shirt: 13, position: 'Defender', givenName: 'Tim', familyName: 'Ream', country: 'USA', dateOfBirth: new Date('1987-10-05') },
{ shirt: 16, position: 'Defender', givenName: 'Tosin', familyName: 'Abarabioyo', country: 'England', dateOfBirth: new Date('1997-09-24') },
{ shirt: 23, position: 'Defender', givenName: 'Joe', familyName: 'Bryan', country: 'England', dateOfBirth: new Date('1993-09-17') },
{ shirt: 30, position: 'Defender', givenName: 'Terence', familyName: 'Kongolo', country: 'France', dateOfBirth: new Date('1994-02-14') },
{ shirt: 33, position: 'Defender', givenName: 'Antonee', familyName: 'Robinson', country: 'USA', dateOfBirth: new Date('1997-08-08') },
{ shirt: 34, position: 'Defender', givenName: 'Ola', familyName: 'Aina', country: 'Nigeria', dateOfBirth: new Date('1996-10-08') },
{ shirt: 6, position: 'Midfielder', givenName: 'Kevin', familyName: 'McDonald', country: 'Scotland', dateOfBirth: new Date('1988-11-04') },
{ shirt: 10, position: 'Midfielder', givenName: 'Tom', familyName: 'Cairney', country: 'Scotland', dateOfBirth: new Date('1991-01-20') },
{ shirt: 15, position: 'Midfielder', givenName: 'Ruben', familyName: 'Loftus-Cheek', country: 'England', dateOfBirth: new Date('1996-01-23') },
{ shirt: 18, position: 'Midfielder', givenName: 'Mario', familyName: 'Lemina', country: 'Gabon', dateOfBirth: new Date('1993-09-01') },
{ shirt: 21, position: 'Midfielder', givenName: 'Harrison', familyName: 'Reed', country: 'England', dateOfBirth: new Date('1995-01-27') },
{ shirt: 25, position: 'Midfielder', givenName: 'Josh', familyName: 'Onomah', country: 'England', dateOfBirth: new Date('1997-04-27') },
{ shirt: 29, position: 'Midfielder', givenName: 'Andre-Frank', familyName: 'Zambo Anguissa', country: 'Cameroon', dateOfBirth: new Date('1995-11-16') },
{ shirt: 48, position: 'Midfielder', givenName: 'Fabio', familyName: 'Carvalho', country: 'England', dateOfBirth: new Date('2002-08-30') },
{ shirt: 9, position: 'Forward', givenName: 'Aleksander', familyName: 'Mitrovic', country: 'Serbia', dateOfBirth: new Date('1994-09-16') },
{ shirt: 14, position: 'Forward', givenName: 'Bobby', familyName: 'De Cordova-Reid', country: 'Jamaica', dateOfBirth: new Date('1993-02-02') },
{ shirt: 17, position: 'Forward', givenName: 'Ivan', familyName: 'Cavaleiro', country: 'Portugal', dateOfBirth: new Date('1993-10-18') },
{ shirt: 19, position: 'Forward', givenName: 'Ademola', familyName: 'Lookman', country: 'England', dateOfBirth: new Date('1997-10-20') },
{ shirt: 27, position: 'Forward', givenName: 'Josh', familyName: 'Maja', country: 'Nigeria', dateOfBirth: new Date('1998-12-27') }
];
// create axes out of the dimensions derived from the squad data
const position = ['Goalkeeper', 'Defender', 'Midfielder', 'Forward'].map(criteria('position'));
const country = squad.map(player => player.country).filter(distinct).sort().map(criteria('country'));
// create the pivot cube
const cube = pivot.pivot(squad, country, position);
// find the average age of players by position by country, select just the full name
const result = pivot.aggregate(cube, players => players.map(player => `${player.givenName} ${player.familyName}`));
document.getElementById("squad").innerHTML =
`<thead>` +
position[0].metadata.map((_, pi) => `<tr>${position[0].metadata.map(() => '<th></th>').join('')}${position.map(c => th(c.metadata[pi])).join('')}</tr>`).join('') +
`</thead>` +
result.map((row, i) => `<tr>${country[i].metadata.map(th).join('')}${row.map(cell => td(cell || '', '')).join('')}</tr>`).join('');
// generate a header cell in a table
function th(data) {
return `<th${data.key ? ` class="${data.key}"` : ``}>${data.value}</th>`;
}
// generate a cell in a table
function td(t, c) {
return `<td${c ? ` class="${c}"` : ``}>${t.join(', ')}</td>`;
}
// create criteria with little metadata
function criteria(key) {
return value => Object.assign(
row => row[key] === value,
{
metadata: [
// { key: 'key', value: key },
{ key, value }
]
});
}
// function to pass into Array.prototype.filter to return unique values.
function distinct(value, index, source) {
return source.indexOf(value) === index;
}
</script>
</body>
</html>