Published
Edited
Feb 26, 2021
Insert cell
md`# Playing numerical data`
Insert cell
Insert cell
Insert cell
// scores is an array so we can use things like map, to execute a function on each element, making a new list with the result of each functions execution
// useful for unpacking data, like just getting the SAT math scores as an array
scores.map(e => e.SATM)
Insert cell
//or the ACT scores
scores.map(e => e.ACT)
Insert cell
Insert cell
{
// map a function over the data we have and produce svg circle
let svg = d3.create('svg');
svg.attr('height',height);
svg.attr('width',width);
let main_g = svg.append('g')
main_g.selectAll('circle').data(scores).join('circle').attr('cx',d => d.SATM)
.attr('cy', d => d.ACT).attr('r',5).attr('fill','black');
return svg.node();
}

Insert cell
md`now use the linear scales to spread the data through the entire svg`
Insert cell
{
// map over the data and take only the SATM field from each object
let SATM_data = scores.map(e => e.SATM);
let ACT_data = scores.map(e => e.ACT);
let margin = 5;
let scalex = d3
.scaleLinear()
.domain(d3.extent(SATM_data))
.range([0, width]);
// flip the range so that we have 0 at the bottom of the svg, instead of the top
let scaley = d3
.scaleLinear()
.domain(d3.extent(ACT_data))
.range([height, 0]);
let svg = d3.create('svg');
svg.attr('height',height);
svg.attr('width',width);
let main_g = svg.append('g')
main_g.selectAll('circle').data(scores).join('circle').attr('cx',d => scalex(d.SATM))
.attr('cy', d => scaley(d.ACT)).attr('r',5).attr('fill','black');
return svg.node();
}
Insert cell
md`demo of playing with color. look at this tutorial for more help with scales and color again https://observablehq.com/@d3/learn-d3-scales?collection=@d3/learn-d3`
Insert cell
{
// d3 is super accomodating with what you give as a range, for instance this will interpolate color for you, play with the value passed when we execute color_scale on the return

let color_scale = d3
.scaleLinear()
.domain([0, 1])
.range(["rgb(0,0,0)", "rgb(255,255,255)"]);
return color_scale(.5);
}
Insert cell
html`<svg viewBox="0 0 ${width} ${height}">
<g>
${
// map a function over the data we have and produce svg circles
scores.map(e => {
// map over the data and take only the SATM field from each object
let SATM_data = scores.map(e => e.SATM);
let ACT_data = scores.map(e => e.ACT);
let SATV_data = scores.map(e => e.SATV);
let color_scale = d3
.scaleLinear()
.domain(d3.extent(SATV_data))
.range(["rgb(0,0,0)", "rgb(255,255,255)"]);
let scalex = d3
.scaleLinear()
.domain(d3.extent(SATM_data))
.range([0, width]);
// flip the range so that we have 0 at the bottom of the svg, instead of the top
let scaley = d3
.scaleLinear()
.domain(d3.extent(ACT_data))
.range([height, 0]);
// especially don't forget the "" around the rhs of fill, because it won't be parsed as rgb correctly otherwise
let res = svg`<circle cx=${scalex(e.SATM)} cy=${scaley(
e.ACT
)} r=5 fill="${color_scale(e.SATV)}"></circle>`;
return res;
})
}
</g>
</svg>`
Insert cell
counts = {
let ACT_data = scores.map(e => e.ACT);
let counts = {};
for (var i = 0; i < ACT_data.length; i++) {
let num = ACT_data[i];
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
return Object.values(counts)
}
Insert cell
Math.min(...counts)

Insert cell
Math.max(...counts)
Insert cell
dic_counts = {
let dic_counts = {15: 1, 16: 1, 17: 3, 18: 4, 19: 4, 20: 5, 21: 6, 22: 7, 23: 15, 24: 13, 25: 12, 26: 23, 27: 28, 28: 22, 29: 27, 30: 21, 31: 25, 32: 23, 33: 13, 34: 14, 35: 4}
return dic_counts}

Insert cell
ACT_data = scores.map(e=> e.ACT)
Insert cell
{
let hist_data = {}
for (let act_score of ACT_data) {
if (hist_data[act_score] == undefined) {
hist_data[act_score] = 1
} else {
hist_data[act_score] +=1
}
}
return hist_data
}
Insert cell
function myreduce(accumulator,current) {
if (accumulator[current] == undefined) {
accumulator[current] = 1
} else {
accumulator[current]+=1
}
return accumulator
}
Insert cell
ACT_data.reduce(myreduce,{})
Insert cell
ACT_data.reduce((accumulator,current) => {
if (accumulator[current] == undefined) {
accumulator[current] = 1
} else {
accumulator[current]+=1
}
return accumulator
},{})
Insert cell
{
// Up above this I made the count object and also got the max and min counts for the domain values
//let SATV_data = scores.map(e => e.SATV);
let ACT_data = scores.map(e => e.ACT);
let hist_object = ACT_data.reduce((accumulator, current) => {
if (accumulator[current] == undefined) {
accumulator[current] = 1;
} else {
accumulator[current] += 1;
}
return accumulator;
}, {});
let hist_list = [];
for (let key in hist_object) {
hist_list.push({ score: parseInt(key), count: hist_object[key] });
}

console.log(" hist list", hist_list);

// discuss this approach later =D
// let mapped = [dic_counts].map(d => {
// return {
// score: Object.keys(d),
// count: d[Object.keys(d)]
// }
// });
let margin = 50;
//y = d3.scaleLinear()
//.domain([0, d3.max(data, d => d.value)]).nice()
//.range([height - margin.bottom, margin.top])
let scalex = d3
.scaleBand()
.domain(hist_list.map(e => e.score)) //Math.min(...ACT_data), Math.max(...ACT_data)
.range([0, width - 2 * margin])
.padding(0.1);
let scaley = d3
.scaleLinear()
.domain([0, d3.extent(hist_list.map(e => e.count))[1]])
.range([height - 2 * margin, 10]);

console.log("scale y domain", scaley.domain());
let svg = d3.create('svg');
svg.attr('height', height);
svg.attr('width', width);
let g = svg.append('g');
// bar chart: https://observablehq.com/@d3/bar-chart
g.attr('transform', `translate(${margin},${margin})`);
g.selectAll('rect')
.data(hist_list)
.join('rect')
.attr('x', d => scalex(d.score)) //Object.keys(dic_counts)
.attr('y', d => scaley(d.count)) //Object.values(dic_counts)
.attr("height", d => height - 2 * margin - scaley(d.count))
.attr("width", scalex.bandwidth())
.attr('fill', 'black');

let left_axis_g = svg.append("g");
left_axis_g.attr("transform", `translate(${margin - 10},${margin})`);
let left_axis = d3.axisLeft().scale(scaley);
left_axis_g.call(left_axis);
let bottom_axis_g = svg.append("g");
bottom_axis_g.attr(
"transform",
`translate(${margin},${height - margin + 10})`
);
let bottom_axis = d3.axisBottom().scale(scalex);
bottom_axis_g.call(bottom_axis);

return svg.node();
}
Insert cell
{
// Up above this I made the count object and also got the max and min counts for the domain values
//let SATV_data = scores.map(e => e.SATV);
let ACT_data = scores.map(e => e.ACT);
let mapped = [dic_counts].map(d => {
return {
score: Object.keys(d),
count: d[Object.keys(d)]
}
});
let margin = 50;
//y = d3.scaleLinear()
//.domain([0, d3.max(data, d => d.value)]).nice()
//.range([height - margin.bottom, margin.top])
let scalex = d3
.scaleBand()
.domain(d3.range(15,35)) //Math.min(...ACT_data), Math.max(...ACT_data)
.range([0, width-2*margin])
.padding(0.1);
let scaley = d3
.scaleLinear()
.domain(d3.extent(counts))
.range([height-2*margin, 0])
var svgContainer = d3.select("body").append("svg").attr("width", 200).attr("height", 200);
// bar chart: https://observablehq.com/@d3/bar-chart
for (var i=1; i <= 36; i++){
var rectangle = svgContainer
.append('rect')
.attr('x',i*10)
.attr('y',i)
.attr('width',5)
.attr('height',counts[i]);
}
// g.attr('transform',`translate(${margin},${margin})`);
// g.selectAll('rect')
// .data(mapped)
// .join('rect')
// .attr('x',d => scalex(d.score)) //Object.keys(dic_counts)
// .attr('y',d => scaley(d.count)) //Object.values(dic_counts)
// .attr("height", d => height-2*margin - scaley(d.count))
// .attr("width", scalex.bandwidth())
// .attr('fill','black');
// let left_axis_g = svg.append("g");
// left_axis_g.attr("transform", `translate(${margin - 10},${margin})`);
// let left_axis = d3.axisLeft().scale(scaley);
// left_axis_g.call(left_axis);
// let bottom_axis_g = svg.append("g");
// bottom_axis_g.attr(
// "transform",
// `translate(${margin},${height - margin + 10})`
// );
// let bottom_axis = d3.axisBottom().scale(scalex)
// bottom_axis_g.call(bottom_axis);

return rectangle.node();
}
Insert cell
{
// Trying again, with a histogram
// https://www.d3-graph-gallery.com/graph/histogram_basic.html
let ACT_data = scores.map(e => e.ACT);
let margin = 50;
let scalex = d3
.scaleLinear()
.domain(d3.range(15,35)) //Math.min(...ACT_data), Math.max(...ACT_data)
.range([0, width-2*margin])
let histogram = d3.histogram()
.value(d => d.ACT).domain(scalex.domain()).thresholds(scalex.ticks(20));
let bins = histogram(ACT_data);
let scaley = d3
.scaleLinear()
.domain([d3.max(bins, d => d.length)])
.range([height-2*margin, 0])
let svg = d3.create('svg')
svg.attr('height',height);
svg.attr('width',width);
let g = svg.append('g');
// bar chart: https://observablehq.com/@d3/bar-chart
g.attr('transform',`translate(${margin},${margin})`);
g.selectAll('rect')
.data(bins)
.enter()
.append('rect') // following the code in the d3 gallery
.attr('x',1)
.attr("height", d => height-2*margin - scaley(d.length))
.attr("width", d => scalex(d.x1)-scalex(d.x0) -1)
.attr('fill','black');
let left_axis_g = svg.append("g");
left_axis_g.attr("transform", `translate(${margin - 10},${margin})`);
let left_axis = d3.axisLeft().scale(scaley);
left_axis_g.call(left_axis);
let bottom_axis_g = svg.append("g");
bottom_axis_g.attr("transform",`translate(${margin},${height - margin + 10})`
);
let bottom_axis = d3.axisBottom().scale(scalex)
bottom_axis_g.call(bottom_axis);

return svg.node();
}
Insert cell
{
// map over the data and take only the SATM field from each object
let ADD_data = scores.map(e => e.SATM + e.SATV);
////let SATV_data = scores.map(e => e.SATV);
let GPA_data = scores.map(e => e.GPA);
let margin = 10;
let scalex = d3
.scaleLinear()
.domain(d3.extent(ADD_data))
.range([0, width-2*margin]);
// flip the range so that we have 0 at the bottom of the svg, instead of the top
let scaley = d3
.scaleLinear()
.domain(d3.extent(GPA_data))
.range([height-2*margin, 0]);
let svg = d3.create('svg')
svg.attr('height',height);
svg.attr('width',width);
let g = svg.append('g')
g.selectAll('circle').data(scores).join('circle').attr('cx',d => scalex(d.SATM+d.SATV))
.attr('cy', d => scaley(d.GPA)).attr('r',5).attr('fill','black');
g.attr('transform',`translate(${margin}, ${margin})`)
return svg.node();
}

Insert cell
{
// map over the data and take only the SATM field from each object
let SATM_data = scores.map(e => e.SATM);
let ACT_data = scores.map(e => e.ACT);
let GPA_data = scores.map(e => e.GPA);
let margin = 50
let color_scale = d3
.scaleLinear()
.domain(d3.extent(GPA_data))
.range(["rgb(0,255,0)", "rgb(0,0,255)"]);
let scalex = d3
.scaleLinear()
.domain(d3.extent(SATM_data))
.range([0, width-2*margin]);
// flip the range so that we have 0 at the bottom of the svg, instead of the top
let scaley = d3
.scaleLinear()
.domain(d3.extent(ACT_data))
.range([height-2*margin, 0]);
let svg = d3.create('svg');
svg.attr('height',height);
svg.attr('width',width);
let g = svg.append('g')
g.attr('transform',`translate(${margin},${margin})`);
g.selectAll('circle').data(scores).join('circle').attr('cx',d => scalex(d.SATM))
.attr('cy', d => scaley(d.ACT)).attr('r',5).attr('fill',d=> color_scale(d.GPA));
// left axis in one line
svg.append("g")
.attr("transform", `translate(${margin - 10},${margin})`)
.call(d3.axisLeft().scale(scaley));
let bottom_axis_g = svg.append("g");
bottom_axis_g.attr("transform",`translate(${margin},${height - margin + 10})`
);
let bottom_axis = d3.axisBottom().scale(scalex)
bottom_axis_g.call(bottom_axis);
return svg.node();
}

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