Public
Edited
Jul 11, 2021
Insert cell
Insert cell
d3 = require("d3@5")
Insert cell
cereals = d3.csvParse(await FileAttachment("cereal.csv").text())
Insert cell
{
const width = 800;
const height = 800;
const margin = 100;

// create SVG artboard, background
const svg = d3
.create('svg')
.attr('viewBox', [0, 0, width, height])
.attr('width', width)
.attr('heigth', height);

const bg = svg
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height);

// draw axis
svg
.append('polyline')
.attr("points", [
[margin, margin],
[margin, height - margin],
[width - margin, height - margin]
])
.attr('stroke-width', 3)
.attr('stroke', 'white');

//scales for converting data to pixels
let xScale = d3
.scaleLinear()
.domain(d3.extent(cereals, d => parseFloat(d.calories)))
.range([margin, width - margin]);

let yScale = d3
.scaleLinear()
.domain(d3.extent(cereals, d => parseFloat(d.potass)))
.range([margin, height - margin]);

let zScale = d3
.scaleLinear()
.domain(d3.extent(cereals, d => parseFloat(d.fiber)))
.range([5, 20]);

// scales for converting data to colormaps
let colorScale = d3
.scaleLinear()
.domain(d3.extent(cereals, d => parseFloat(d.rating)))
.range([0, 1]);

// use a data join to create the circles and place an color them with data
svg
.selectAll('.cerealCircle')
.data(cereals)
.enter()
.append('circle')
.attr('cx', d => xScale(d.calories))
.attr('cy', d => yScale(d.potass))
.attr('fill', d => d3.interpolatePlasma(colorScale(d.rating)))
.attr('r', 10)
.attr('class', 'cerealCircle')
// update the text area, add a stroke to themselves when a circle is hovered
.on('mouseover', function(d) {
d3.select(this)
.raise()
.transition()
.duration(1)
.attr('stroke', 'white')
.attr('stroke-width', 3);

d3.select('#infoBlock')
.transition()
.duration(1)
.text(d.name + ' : Rating ' + parseFloat(d.rating).toFixed(2));
})
// go back to the original text, remove a stroke when a circle is hovered off
.on('mouseout', function(d) {
d3.select(this)
.transition()
.duration(300)
.attr('stroke-width', 0);

d3.select('#infoBlock')
.transition()
.duration(300)
.text('Hover over a circle!');
});

// Create a text area to show which cereal circle is hovered
const infoText = svg
.append('text')
.attr('x', width / 2)
.attr('y', height - margin / 2)
.attr('fill', 'white')
.attr('text-anchor', 'middle')
.attr('font-size', 20)
.attr('font-family', 'helvetica')
.attr('id', 'infoBlock')
.text('Hover over a circle!');

// Place axis
svg
.append('text')
.attr('x', margin)
.attr('y', margin / 2)
.attr('fill', 'white')
.attr('font-size', 15)
.attr('font-family', 'helvetica')
.text('X-Axis:');

svg
.append('text')
.attr('x', width - margin)
.attr('y', margin / 2)
.attr('fill', 'white')
.attr('font-size', 15)
.attr('font-family', 'helvetica')
.attr('text-anchor', 'end')
.text('Y-Axis:');

svg
.append('text')
.attr('x', width / 2 - margin)
.attr('y', margin / 2)
.attr('fill', 'white')
.attr('font-size', 15)
.attr('font-family', 'helvetica')
.attr('text-anchor', 'end')
.text('Size:');

svg
.append('text')
.attr('x', (width / 4) * 3 - margin)
.attr('y', margin / 2)
.attr('fill', 'white')
.attr('font-size', 15)
.attr('font-family', 'helvetica')
.attr('text-anchor', 'end')
.text('Color:');

// Add the text buttons to the SVG artboard to control what is visualized
let xChoices = [
{ name: "Sugar", value: "sugars" },
{ name: "Calories", value: "calories" },
{ name: "Fat", value: "fat" }
];
let yChoices = [
{ name: "Vitamins", value: "vitamins" },
{ name: "Pottasium", value: "potass" },
{ name: "Protein", value: "protein" }
];
let zChoices = [
{ name: "Fiber", value: "fiber" },
{ name: "None", value: "10" },
{ name: "Carbo", value: "carbo" }
];
let colorChoices = [
{ name: "Rating", value: "rating" },
{ name: "Sodium", value: "sodium" }
];

// create all x buttons
svg
.selectAll('.xChoice')
.data(xChoices)
.enter()
.append('text')
.attr('x', margin + 60)
.attr('y', (d, i) => ((i + 1) / 4) * margin)
.attr('fill', 'white')
.attr('font-size', 15)
.attr('font-family', 'helvetica')
.text(d => d.name)
.on('click', function(xC) {
xScale = d3
.scaleLinear()
.domain(d3.extent(cereals, d => parseFloat(d[xC.value])))
.range([margin, width - margin]);

d3.selectAll('.cerealCircle')
.data(cereals)
.transition()
.duration(1000)
.ease(d3.easeCubicInOut)
.attr('cx', d => xScale(parseFloat(d[xC.value])));
});

// create all y button
svg
.selectAll('.yChoice')
.data(yChoices)
.enter()
.append('text')
.attr('x', width - margin + 10)
.attr('y', (d, i) => ((i + 1) / 4) * margin)
.attr('fill', 'white')
.attr('font-size', 15)
.attr('font-family', 'helvetica')
.text(d => d.name)
.on('click', function(yC) {
yScale = d3
.scaleLinear()
.domain(d3.extent(cereals, d => parseFloat(d[yC.value])))
.range([margin, height - margin]);

d3.selectAll('.cerealCircle')
.data(cereals)
.transition()
.duration(1000)
.ease(d3.easeCubicInOut)
.attr('cy', d => yScale(parseFloat(d[yC.value])));
});

// create all size button
svg
.selectAll('.zChoice')
.data(zChoices)
.enter()
.append('text')
.attr('x', width / 2 - margin + 10)
.attr('y', (d, i) => ((i + 1) / 4) * margin)
.attr('fill', 'white')
.attr('font-size', 15)
.attr('font-family', 'helvetica')
.text(d => d.name)
.on('click', function(zC) {
if (zC.name == "None") {
d3.selectAll('.cerealCircle')
.data(cereals)
.transition()
.duration(1000)
.ease(d3.easeElasticOut)
.attr('r', 10);
} else {
zScale = d3
.scaleLinear()
.domain(d3.extent(cereals, d => parseFloat(d[zC.value])))
.range([5, 20]);

d3.selectAll('.cerealCircle')
.data(cereals)
.transition()
.duration(1000)
.ease(d3.easeElasticOut)
.attr('r', d => zScale(parseFloat(d[zC.value])));
}
});

// create all color button
svg
.selectAll('.colorChoice')
.data(colorChoices)
.enter()
.append('text')
.attr('x', (width / 4) * 3 - margin + 10)
.attr('y', (d, i) => ((i + 1) / 3) * margin)
.attr('fill', 'white')
.attr('font-size', 15)
.attr('font-family', 'helvetica')
.text(d => d.name)
.on('click', function(cC) {
colorScale = d3
.scaleLinear()
.domain(d3.extent(cereals, d => parseFloat(d[cC.value])))
.range([0, 1]);

d3.selectAll('.cerealCircle')
.data(cereals)
.transition()
.duration(1000)
.ease(d3.easeBounceOut)
.attr('fill', d => d3.interpolatePlasma(colorScale(d[cC.value])));
});

return svg.node();
}
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