Published unlisted
Edited
Jan 28, 2020
1 star
Insert cell
Insert cell
Insert cell
nba_data = {
let the_data = await FileAttachment('nba_players.json').json()
the_data.forEach((datum,i) => {
datum.id = i
})
return the_data
}
Insert cell
Insert cell
create_groups = (main_plot) => {
let scatterplot_g = main_plot.append('g').attr('id', 'scatterplot')
let pcp_data_g = main_plot.append('g').attr('id', 'pcpdata')
.attr('transform', 'translate(0,'+(scatter_size+pc_pad)+')')
let pcp_pcs_g = main_plot.append('g').attr('id', 'pcppcs')
.attr('transform', 'translate('+(scatter_size+pc_pad)+',0)')
}
Insert cell
{
let svg_elem = DOM.svg(width,width)
let svg_selection = d3.select(svg_elem)
let main_plot = svg_selection.append('g').attr('transform', 'translate('+pad+','+pad+')')
create_groups(main_plot)
return svg_elem
}
Insert cell
Insert cell
Insert cell
Insert cell
scatter_x_scale = d3.scaleLinear().domain(d3.extent(nba_data, d => d.pc0)).range([0,scatter_size])
Insert cell
scatter_y_scale = d3.scaleLinear().domain(d3.extent(nba_data, d => d.pc1)).range([0,scatter_size])
Insert cell
Insert cell
attribute_scale = d3.scalePoint().domain(att_order).range([0,scatter_size])
Insert cell
pc_scale = d3.scalePoint().domain(pc_order).range([0,scatter_size])
Insert cell
pcp_data_scale = {
let all_scales = {}
att_order.forEach((att,i) => {
all_scales[att] = d3.scaleLinear().domain(d3.extent(nba_data, d => d[att])).range([pcp_size,0])
})
return all_scales
}
Insert cell
pcp_pcs_scale = {
let all_scales = {}
pc_order.forEach((pc,i) => {
let all_extents = pc_order.map(pc => d3.extent(nba_data, d => d[pc]))
all_scales[pc] = d3.scaleLinear().domain([d3.min(all_extents, d => d[0]),d3.max(all_extents, d => d[1])]).range([0,pcp_size])
//all_scales[pc] = d3.scaleLinear().domain(d3.extent(nba_data, d => d[pc])).range([0,pcp_size])
})
return all_scales
}
Insert cell
Insert cell
{
let svg_elem = DOM.svg(width,width)
let svg_selection = d3.select(svg_elem)
let main_plot = svg_selection.append('g').attr('transform', 'translate('+pad+','+pad+')')
create_groups(main_plot)
create_scatterplot(main_plot.select('#scatterplot'))
return svg_elem
}
Insert cell
create_scatterplot = (scatter_group) => {
scatter_group.selectAll('circle').data(nba_data, d => d.id).enter().append('circle')
.attr('cx', d => scatter_x_scale(d.pc0)).attr('cy', d => scatter_y_scale(d.pc1)).attr('r', 4)
.attr('fill', default_color).attr('stroke', d3.hcl(0,0,70))
tweak_scatterplot(scatter_group)
}
Insert cell
Insert cell
Insert cell
{
let svg_elem = DOM.svg(width,width)
let svg_selection = d3.select(svg_elem)
let main_plot = svg_selection.append('g').attr('transform', 'translate('+pad+','+pad+')')
create_groups(main_plot)
create_scatterplot(main_plot.select('#scatterplot'))
create_data_pcp(main_plot.select('#pcpdata'))
create_pcs_pcp(main_plot.select('#pcppcs'))
return svg_elem
}
Insert cell
data_line = d3.line()
.x(d => attribute_scale(d.att))
.y(d => pcp_data_scale[d.att](d.value))
Insert cell
pc_line = d3.line()
.y(d => pc_scale(d.pc))
.x(d => pcp_pcs_scale[d.pc](d.value))
Insert cell
create_data_pcp = (pcp_data_group) => {
pcp_data_group.selectAll('path').data(nba_data, d => d.id).enter().append('path')
.attr('d', d => {
let poly_line = att_order.map((a,i) => {
return {att:a,value:d[a]}
})
return data_line(poly_line)
})
.attr('class', 'line')
.attr('fill', 'None').attr('stroke', default_color).attr('stroke-width', 0.2).attr('stroke-opacity', 0.7)
pcp_data_axes(pcp_data_group)
}
Insert cell
Insert cell
create_pcs_pcp = (pcp_pcs_group) => {
pcp_pcs_group.selectAll('path').data(nba_data, d => d.id).enter().append('path')
.attr('d', d => {
let poly_line = pc_order.map((pc,i) => {
return {pc:pc,value:d[pc]}
})
return pc_line(poly_line)
})
.attr('class', 'line')
.attr('fill', 'None').attr('stroke', default_color).attr('stroke-width', 0.2).attr('stroke-opacity', 0.7)
pcp_pcs_axes(pcp_pcs_group)
}
Insert cell
Insert cell
Insert cell
{
let svg_elem = DOM.svg(width,width)
let svg_selection = d3.select(svg_elem)
let main_plot = svg_selection.append('g').attr('transform', 'translate('+pad+','+pad+')')
create_groups(main_plot)
create_scatterplot(main_plot.select('#scatterplot'))
create_data_pcp(main_plot.select('#pcpdata'))
create_pcs_pcp(main_plot.select('#pcppcs'))
scatterplot_brush(main_plot.select('#scatterplot'), main_plot.select('#pcpdata'), main_plot.select('#pcppcs'))
pc_brush(main_plot.select('#scatterplot'), main_plot.select('#pcpdata'), main_plot.select('#pcppcs'))
return svg_elem
}
Insert cell
Insert cell
linked_updates = (selected_data, scatterplot_group, pcp_data_group, pcp_pc_group) => {
let scatterplot_update = scatterplot_group.selectAll('circle').data(selected_data, d => d.id)
scatterplot_update.attr('fill', selected_color).raise()
scatterplot_update.exit().attr('fill', default_color)
let pcp_data_update = pcp_data_group.selectAll('.line').data(selected_data, d => d.id)
pcp_data_update.attr('stroke', selected_color).attr('stroke-width', 1.2).raise()
pcp_data_update.exit().attr('stroke', default_color).attr('stroke-width', .2).lower()
let pcp_pcs_update = pcp_pc_group.selectAll('.line').data(selected_data, d => d.id)
pcp_pcs_update.attr('stroke', selected_color).attr('stroke-width', 1.2).raise()
pcp_pcs_update.exit().attr('stroke', default_color).attr('stroke-width', .2).lower()
}
Insert cell
Insert cell
scatterplot_brush = (scatterplot_group, pcp_data_group, pcp_pc_group) => {
let brush = d3.brush().extent([[0,0],[scatter_size,scatter_size]])
brush.on('brush', () => {
let rect_select = d3.event.selection;
let min_pc0 = scatter_x_scale.invert(rect_select[0][0]);
let max_pc0 = scatter_x_scale.invert(rect_select[1][0]);
let min_pc1 = scatter_y_scale.invert(rect_select[0][1]);
let max_pc1 = scatter_y_scale.invert(rect_select[1][1]);

let brushed_data = nba_data.filter(d => {
return d.pc0 >= min_pc0 && d.pc0 <= max_pc0 && d.pc1 >= min_pc1 && d.pc1 <= max_pc1;
});
linked_updates(brushed_data, scatterplot_group, pcp_data_group, pcp_pc_group)
})
.on('end', () => {
scatterplot_group.selectAll('.selection,.handle').attr('style', 'display: none;')
})
scatterplot_group.call(brush)
}
Insert cell
Insert cell
pc_brush = (scatterplot_group, pcp_data_group, pcp_pc_group) => {
let brush_pad = 8
let data_brush = d3.brushY().extent([[-brush_pad,0],[brush_pad,pcp_size]])
pcp_data_group.selectAll('guides').data(att_order).enter().append('g').attr('class', 'dataguide')
.attr('transform', d => 'translate('+(attribute_scale(d))+','+(0)+')').raise()
data_brush.on('brush', att => {
let rect_select = d3.event.selection;
let min_att = pcp_data_scale[att].invert(rect_select[1]);
let max_att = pcp_data_scale[att].invert(rect_select[0]);

let brushed_data = nba_data.filter(d => {
return d[att] >= min_att && d[att] <= max_att
});
linked_updates(brushed_data, scatterplot_group, pcp_data_group, pcp_pc_group)
})
.on('end', () => {
pcp_data_group.selectAll('.selection,.handle').attr('style', 'display: none;')
})
let pc_brush = d3.brushX().extent([[0,-brush_pad],[pcp_size,brush_pad]])
pcp_pc_group.selectAll('guides').data(pc_order).enter().append('g').attr('class', 'pcguide')
.attr('transform', d => 'translate('+(0)+','+(pc_scale(d))+')').raise()
pc_brush.on('brush', pc => {
let rect_select = d3.event.selection;
let min_att = pcp_pcs_scale[pc].invert(rect_select[0]);
let max_att = pcp_pcs_scale[pc].invert(rect_select[1]);

let brushed_data = nba_data.filter(d => {
return d[pc] >= min_att && d[pc] <= max_att
});
linked_updates(brushed_data, scatterplot_group, pcp_data_group, pcp_pc_group)
})
.on('end', () => {
pcp_pc_group.selectAll('.selection,.handle').attr('style', 'display: none;')
})
pcp_data_group.selectAll('.dataguide').call(data_brush)
pcp_pc_group.selectAll('.pcguide').call(pc_brush)
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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