Public
Edited
Apr 16
Insert cell
Insert cell
Insert cell
firstRoundsPicks = nbaPlayers.filter(player => player.draft_round === 1)
Insert cell
draftYearExtent = d3.extent(firstRoundsPicks, player => player.draft_year)
Insert cell
draftYears = d3.range(draftYearExtent[0], draftYearExtent[1] + 1)
Insert cell
draftNumberExtent = d3.extent(firstRoundsPicks, player => player.draft_number)
Insert cell
draftNumbers = d3.range(draftNumberExtent[0], draftNumberExtent[1] + 1)
Insert cell
draftYearToNumberToPlayer = d3.index(
firstRoundsPicks,
d => d.draft_year,
d => d.draft_number
)
Insert cell
Insert cell
draftPicks2 = d3.cross(
draftYears,
draftNumbers
)
Insert cell
Insert cell
netRatingDomain = {
const extent = d3.extent(draftPicks, player => player.net_rating);
return extent;
}
Insert cell
activePlayerNetRatingColor = d3.scaleSequential()
.domain(netRatingDomain)
.interpolator(d3.interpolateGreens)
Insert cell
nonActivePlayerColor = d3.scaleOrdinal()
.domain(['< 25% games played', 'inactive'])
.range(['#af8dc3', '#DEDAE0'])
Insert cell
html`<div style="display: flex; align-content: center;">
<div style="margin-right: 20px">
${legend({color: activePlayerNetRatingColor, title: "Net rating (min. 25% games played in '20-'21 season)"})}
</div>
${swatches({color: nonActivePlayerColor})}
</div>`
Insert cell
{
// dimensions and set up

const margin = {top: 0, right: 0, bottom: 40, left: 80};

const maxWidth = width;
const maxHeight = 500;

const numCols = draftNumbers.length;
const numRows = draftYears.length;

// calculate the maximum size that a cell can be
const cellSize = Math.min(
(maxWidth - margin.left - margin.right) / numCols,
(maxHeight - margin.top - margin.bottom) / numRows
);

const visWidth = cellSize * numCols * 1.1;
const visHeight = cellSize * numRows * 1.1;
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);

const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);

// scales

const x = d3.scaleBand()
.domain(draftNumbers)
.range([0, visWidth])
// space betwen columns
.padding(0.2);

const y = d3.scaleBand()
.domain(draftYears)
.range([visHeight, 0])
// space between rows
.padding(0.2);

// axis generators

const xAxis = d3.axisBottom(x).tickSize(0);
const yAxis = d3.axisLeft(y).tickSize(0);

// draw axes

// x-axis

g.append('g')
.attr('transform', `translate(0,${visHeight})`)
.call(xAxis)
.call(g => g.select('.domain').remove())
// axis label
.append('text')
.attr('x', visWidth / 2)
.attr('y', 30)
.attr('fill', 'black')
.attr('text-anchor', 'center')
.text('draft position');

// y-axis
g.append('g')
.call(yAxis)
.call(g => g.select('.domain').remove())
// axis label
.append('text')
.attr('x', -35)
.attr('y', visHeight / 2)
.attr('fill', 'black')
.attr('dominant-baseline', 'center')
.attr('text-anchor', 'end')
.text('draft year');

// draw squares

// create one group for each cell
const cells = g.append('g')
.selectAll('g')
.data(draftPicks)
.join('g')
// move the cell into its position
.attr('transform', d => `translate(${x(d.number)},${y(d.year)})`);

// add rectangle to each cell
cells.append('rect')
.attr('width', x.bandwidth())
.attr('height', y.bandwidth())
.attr('fill', d => {
// the color scale that we use depends on whether or not the
// player is active
if (d.type === 'active') {
return activePlayerNetRatingColor(d.net_rating);
} else {
return nonActivePlayerColor(d.type);
}
})
// out of curiosity, add tooltip
.append('title')
.text(d => d.name);

// add text label to each cell
cells.filter(d => d.type === 'active')
.append('text')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('x', x.bandwidth() / 2)
.attr('y', y.bandwidth() / 2)
.attr('font-size', 9)
.attr('font-weight', 'bold')
.attr('font-family', 'sans-serif')
// use white labels on dark cells and black labels on light cells
.attr('fill', d => d3.hcl(activePlayerNetRatingColor(d.net_rating)).l < 60 ? 'white' : 'black')
.text(d => d.net_rating)

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Inputs.table(netflix)
Insert cell
Insert cell
monthExtent = d3.extent(titlesAddedByMonth[0].counts, d => d.month)
Insert cell
maxCount = d3.max(
titlesAddedByMonth,
d => d3.max(d.counts, c => c.count)
)
Insert cell
types = titlesAddedByMonth.map(d => d.type)
Insert cell
lineColor = d3.scaleOrdinal()
.domain(types)
.range(['#7fcdbb', '#2c7fb8'])
Insert cell
swatches({color: lineColor})
Insert cell
{
// set up
const margin = {top: 10, right: 10, bottom: 20, left: 20};

const visWidth = width - margin.left - margin.right;
const visHeight = 450 - margin.top - margin.bottom;

const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.righ)
.attr('height', visHeight + margin.top + margin.bottom);

const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);

// scales

const x = d3.scaleTime()
.domain(monthExtent)
.range([0, visWidth]);

const y = d3.scaleLinear()
.domain([0, maxCount])
.range([visHeight, 0]);

// line generator

const line = d3.line()
.x(d => x(d.month))
.y(d => y(d.count));

// axes

const xAxis = d3.axisBottom(x);
const yAxis = d3.axisLeft(y);

// draw lines

g.append('g')
.selectAll('path')
.data(titlesAddedByMonth)
.join('path')
.attr('d', d => line(d.counts))
.attr('fill', 'none')
.attr('stroke', d => lineColor(d.type))
.attr('stroke-width', 2.2);

// draw axes and grids

// x-axis and verical grid lines
g.append('g')
.attr('transform', `translate(0,${visHeight})`)
.call(xAxis)
.call(
// clone the axis tick marks to create the grid lines
g => g.selectAll('.tick > line').clone()
.attr('y1', 0)
.attr('y2', -visHeight)
.attr('stroke', '#ccc')
.attr('stroke-width', 0.5)
)

// y-axis and horizontal grid lines
g.append('g')
.call(yAxis)
.call(
// clone the axis tick marks to create the grid lines
g => g.selectAll('.tick > line')
// skip the first tick mark, since that's the baseline
// of the x-axis
.filter((d, i) => i !== 0).clone()
.attr('x1', 0)
.attr('x2', visWidth)
.attr('stroke', '#ccc')
.attr('stroke-width', 0.5)
)

return svg.node();
}
Insert cell
Insert cell
roles = netflix.map(d => d.cast.map(name => ({name, type: d.type}))).flat()
Insert cell
topActors = Array.from(
d3.rollup(
roles,
g => g.length,
d => d.name,
d => d.type
),
([name, typeToCount]) => {
const obj = Object.fromEntries(typeToCount);
obj.name = name;
obj.total = d3.sum(typeToCount.values());
return obj;
}
)
.sort((a, b) => d3.descending(a.total, b.total))
.filter(d => d.total >= 20)
Insert cell
stacked = d3.stack()
.keys(types)
.value((d, key) => d[key] ?? 0)(topActors)
Insert cell
typeColor = d3.scaleOrdinal()
.domain(types)
.range(['#542788', '#e08214'])
Insert cell
{
// set up
const margin = {top: 20, right: 10, bottom: -20, left: 110};

const visWidth = width - margin.left - margin.right;
const visHeight = 500 - margin.top - margin.bottom;

const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom + 50);

const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);

// scales

const x = d3.scaleLinear()
.domain([0, d3.max(topActors, d => d.total)]).nice()
.range([0, visWidth]);

const names = topActors.slice().sort((a, b) => d3.descending(a.total, b.total)).map(d => d.name);
const y = d3.scaleBand()
.domain(names)
.range([0, visHeight])
.padding(0.2);

// axes

const xAxis = d3.axisBottom(x);
const yAxis = d3.axisLeft(y);

// draw bars

const layers = g.append('g')
.selectAll('g')
.data(stacked)
.join('g')
.attr('fill', d => typeColor(d.key));

layers.selectAll('rect')
.data(d => d)
.join('rect')
.attr('x', d => x(d[0]))
.attr('width', d => x(d[1]) - x(d[0]))
.attr('y', d => y(d.data.name))
.attr('height', y.bandwidth());

// draw axes

// x-axis
g.append('g')
.attr('transform', `translate(0,${visHeight})`)
.call(xAxis);

// y-axis
g.append('g')
.call(yAxis);

const legend = svg.append('g')
.attr('transform', `translate(${visWidth/2},${margin.top - 20})`);

const legendSpacing = 100;
typeColor.domain().forEach((key, i) => {
const legendRow = legend.append('g')
.attr('transform', `translate(${i * legendSpacing}, 0)`);
// ícono de color
legendRow.append('rect')
.attr('width', 15)
.attr('height', 15)
.attr('fill', typeColor(key));
// texto descriptivo
legendRow.append('text')
.attr('x', 20)
.attr('y', 12)
.text(key)
.style('font-size', '12px')
.attr('alignment-baseline', 'middle');
});

return svg.node();
}
Insert cell
import {legend, swatches} from '@d3/color-legend'
Insert cell
nbaPlayers = FileAttachment("nba_players_2020_2021.csv").csv({typed: true})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more