Published
Edited
Dec 16, 2021
1 fork
11 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// To start, we'll reimport several of the global variables that were used in the last example.
import {
d3, rocketLaunchesTable,
xScale, yScale,
xAxis, yAxis,
height, width,
bar_color,
margin,
chart_lollipop } from "@hydrosquall/d3js-bar-charts-lollipops-and-nesting"

// If you prefer to work outside the Observable environment, I included a basic example of how to convert
// one of the old barcharts from last time into a regular HTML page, and hosted the example on blockbuilder:
// http://blockbuilder.org/hydrosquall/1352e7dc49aa697dc4b162df21dc297c
// Note that the chart will probably look a little different every day, since the list of rocket launches coming up is not a constant. The GIF contains what the launch set looked like as of 4/15/2018.
Insert cell
Insert cell
// This is a copy of the code from our last lesson:
// https://beta.observablehq.com/@hydrosquall/d3js-bar-charts-lollipops-and-nesting#chart_lollipop
chart_lollipop_local = {
const svg = d3.select(DOM.svg(width, height)); // This is just an Observable pattern. Think of it as "home".
const groups = svg.append("g")
.selectAll("g.lollipop")
.data(rocketLaunchesTable).enter()
.append("g")
.attr("class", "lollipop")
.attr('transform', `translate(10, 0)`); // Shift everything over to the left a bit
// Here our our original rectangles
let scaleFactor = 20;
groups.append("rect")
.attr("x", d => xScale(d.name))
.attr("y", d => yScale(d.num_missions))
.attr("height", d => yScale(0) - yScale(d.num_missions))
.attr("width", xScale.bandwidth() / scaleFactor) // shrink the rectangles to save pixels
.attr("fill", bar_color)
let circleOffset = xScale.bandwidth() / (2 * scaleFactor); // Center the circles
// We can add some circles on top!
groups.append("circle")
.attr("cx", d => xScale(d.name) + circleOffset)
.attr("cy", d => yScale(d.num_missions))
.attr("r", d => 5)
.attr("fill", bar_color)
.append("title")
.text(d => d.name)
svg.append("g")
.call(yAxis)
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart_lollipop_click_all_circles = {
const svg = d3.select(DOM.svg(width, height)); // This is just an Observable pattern. Think of it as "home".
const groups = svg.append("g")
.selectAll("g.lollipop")
.data(rocketLaunchesTable).enter()
.append("g")
.attr("class", "lollipop")
.attr('transform', `translate(10, 0)`); // Shift everything over to the left a bit
// Here our our original rectangles
let scaleFactor = 20;
groups.append("rect")
.attr("x", d => xScale(d.name))
.attr("y", d => yScale(d.num_missions))
.attr("height", d => yScale(0) - yScale(d.num_missions))
.attr("width", xScale.bandwidth() / scaleFactor) // shrink the rectangles to save pixels
.attr("fill", bar_color)
let circleOffset = xScale.bandwidth() / (2 * scaleFactor); // Center the circles
// We can add some circles on top!
groups.append("circle")
.attr("cx", d => xScale(d.name) + circleOffset)
.attr("cy", d => yScale(d.num_missions))
.attr("r", d => 5)
.attr("fill", bar_color)
.append("title")
.text(d => d.name)
// Let's add event listeners to each of the circles
groups.selectAll('circle')
// .attr('fill', 'grey') // Uncomment me to confirm we selected the circles we wanted to.
.on('click', function(d) { // note we have to use REGULAR function (not arrow syntax) for us
// to be able to use d3.select(this)
console.log(d); // we have access to the data object! check your console.
console.log(this); // we also have access to the DOM object...
d3.select(this)
.attr('fill', MATERIAL_BLUE) // we can change color!
.attr('r', d.num_missions * 3) // or size! or really, any visual property we want.
})
svg.append("g")
.call(yAxis)
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof text_color = html`<input type="text" value="#3333ff">`
Insert cell
Insert cell
md`Text size is currently ${text_size} px.`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart_lollipop_click_button = {
const svg = d3.select(DOM.svg(width, height)); // This is just an Observable pattern. Think of it as "home".
const groups = svg.append("g")
.selectAll("g.lollipop")
.data(rocketLaunchesTable).enter()
.append("g")
.attr("class", "lollipop")
.attr('transform', `translate(10, 0)`); // Shift everything over to the left a bit
// Here our our original rectangles
let scaleFactor = 20;
groups.append("rect")
.attr("x", d => xScale(d.name))
.attr("y", d => yScale(d.num_missions))
.attr("height", d => yScale(0) - yScale(d.num_missions))
.attr("width", xScale.bandwidth() / scaleFactor) // shrink the rectangles to save pixels
.attr("fill", bar_color)
let circleOffset = xScale.bandwidth() / (2 * scaleFactor); // Center the circles
// We can add some circles on top!
groups.append("circle")
.attr("cx", d => xScale(d.name) + circleOffset)
.attr("cy", d => yScale(d.num_missions))
.attr("r", d => lollipop_size)
.attr("fill", bar_color)
.append("title")
.text(d => d.name)
// Let's change all the circles with the same country of origin as the original one.
groups.selectAll('circle')
.on('mouseover', function(d) { // note we have to use REGULAR function (not arrow syntax) for us
// to be able to use d3.select(this)
const countryCode = d.location.country;
console.log(countryCode); // we also have access to the DOM object...
groups.selectAll('circle')
.filter(d=> d.location.country != countryCode)
.attr('opacity', 0.1);
}).on('mouseleave', function(d) {
groups.selectAll('circle')
.attr('opacity', 1);
});
svg.append("g")
.call(yAxis);
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart_lollipop_click_all_circles_transition = {
const svg = d3.select(DOM.svg(width, height)); // This is just an Observable pattern. Think of it as "home".
const groups = svg.append("g")
.selectAll("g.lollipop")
.data(rocketLaunchesTable).enter()
.append("g")
.attr("class", "lollipop")
.attr('transform', `translate(10, 0)`); // Shift everything over to the left a bit
// Here our our original rectangles
let scaleFactor = 20;
groups.append("rect")
.attr("x", d => xScale(d.name))
.attr("y", d => yScale(d.num_missions
))
.attr("height", d => yScale(0) - yScale(d.num_missions))
.attr("width", xScale.bandwidth() / scaleFactor) // shrink the rectangles to save pixels
.attr("fill", bar_color)
let circleOffset = xScale.bandwidth() / (2 * scaleFactor); // Center the circles
groups.append("circle")
.attr("cx", d => xScale(d.name) + circleOffset)
.attr("cy", d => yScale(d.num_missions))
.attr("r", d => 5)
.attr("fill", bar_color)
.append("title")
.text(d => d.name)
// Let's add event listeners to each of the circles
groups.selectAll('circle')
.on(`${listen_mode}`, function(d) {
console.log(d); // we have access to the data object! check your console.
console.log(this); // we also have access to the DOM object...
d3.select(this)
.transition()
// .ease(d3.easeLinear) // See also d3.easeCubicInOut, d3.easeQuadInOut
// .duration(750) // 750 is the default duration in milliseconds.
.attr('fill', MATERIAL_BLUE) // we can change color!
.attr('r', d.num_missions * 3) // or size! or really, any visual property we want.
})
svg.append("g")
.call(yAxis)
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// We'll see shortly why we need a new scale. This is based on the old code for xScale that we
// had previously used.
xScaleSorted = d3.scaleBand()
// .domain(rocketLaunchesTable.map(d => d.name)) // old domain (looks for strings)
.domain(d3.range(rocketLaunchesTable.map(d => d.name).length)) // new domain!
.range(xRange)
.padding(0.15) // how much space to put around each bar
Insert cell
chart_lollipop_sort = {
const svg = d3.select(DOM.svg(width, height)); // This is just an Observable pattern. Think of it as "home".
const groups = svg.append("g")
.selectAll("g.lollipop")
.data(rocketLaunchesTable).enter()
.append("g")
.attr("class", "lollipop")
.attr('transform', `translate(10, 0)`); // Shift everything over to the left a bit
let scaleFactor = 20;
groups.append("rect")
.attr("x", d => xScale(d.name))
.attr("y", d => yScale(d.num_missions))
.attr("height", d => yScale(0) - yScale(d.num_missions))
.attr("width", xScale.bandwidth() / scaleFactor) // shrink the rectangles to save pixels
.attr("fill", bar_color)
let circleOffset = xScale.bandwidth() / (2 * scaleFactor); // Center the circles
groups.append("circle")
.attr("cx", d => xScale(d.name) + circleOffset)
.attr("cy", d => yScale(d.num_missions))
.attr("r", d => 5)
.attr("fill", bar_color)
.append("title")
.text(d => d.name);
svg.append("g")
.call(yAxis);
// Let's make a function for sorting things
svg.on('click', ()=> {
svg.selectAll('circle')
.sort((a,b) => d3.ascending(a.num_missions, b.num_missions)) // Challenge: flip orders...
.transition()
.duration(1500)
.attr('cx', (d, i) => xScaleSorted(i));// adjust cx position based on the sort'
});
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart_lollipop_sort_individually = {
const svg = d3.select(DOM.svg(width, height)); // This is just an Observable pattern. Think of it as "home".
const groups = svg.append("g")
.selectAll("g.lollipop")
.data(rocketLaunchesTable).enter()
.append("g")
.attr("class", "lollipop")
.attr('transform', `translate(10, 0)`); // Shift everything over to the left a bit
// Here our our original rectangles
let scaleFactor = 20;
groups.append("rect")
.attr("x", d => xScale(d.name))
.attr("y", d => yScale(d.num_missions))
.attr("height", d => yScale(0) - yScale(d.num_missions))
.attr("width", xScale.bandwidth() / scaleFactor) // shrink the rectangles to save pixels
.attr("fill", bar_color)
let circleOffset = xScale.bandwidth() / (2 * scaleFactor); // Center the circles
// We can add some circles on top!
groups.append("circle")
.attr("cx", d => xScale(d.name) + circleOffset)
.attr("cy", d => yScale(d.num_missions))
.attr("r", d => 5)
.attr("fill", bar_color)
.append("title")
.text(d => d.name);
svg.append("g")
.call(yAxis);
let sortAscending = false;
const sortingFunction = (a,b) => sortAscending ? d3.ascending(a.num_missions, b.num_missions)
: d3.descending(a.num_missions, b.num_missions);
svg.on('click', ()=> {
const t = d3.transition()
.duration(1500);
const perElementDelay = 50;
sortAscending = !sortAscending; // toggle the sort order on every click
svg.selectAll('circle')
.sort(sortingFunction)
.transition(t)
.delay((d, i) => i * perElementDelay)
.attr('cx', (d, i) => xScaleSorted(i) + circleOffset );// adjust cx position based on the sort

svg.selectAll('rect')
.sort(sortingFunction)
.transition(t)
.delay((d, i) => i * perElementDelay)
.attr('x', (d, i) => xScaleSorted(i));// adjust x position based on the sort
});
return svg.node();
}
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