Skip to content

Instantly share code, notes, and snippets.

@johnbaums
Created June 20, 2023 23:42
Show Gist options
  • Save johnbaums/b0cf365c978d144afb6a77a9ba80b288 to your computer and use it in GitHub Desktop.
Save johnbaums/b0cf365c978d144afb6a77a9ba80b288 to your computer and use it in GitHub Desktop.
.bubbles {
stroke-width: 1px;
stroke: black;
opacity: .8
}
.bubbles:hover {
stroke: black;
}
* {
font-family: sans-serif;
}
.tooltip {
position: absolute;
}
.tooltip:hover, text {
cursor: default;
}
// https://d3-graph-gallery.com/graph/bubble_template.html
// !preview r2d3 data=readr::read_csv("testd3.csv"), d3_version="6", container="div"
//
// r2d3: https://rstudio.github.io/r2d3
//
// set the dimensions and margins of the graph
var margin = {top: 40, right: window.innerWidth * 0.3, bottom: 60, left: 30},
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = div
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
r2d3.onRender(function(data, s, w, h, options) {
// ---------------------------//
// AXIS AND SCALE //
// ---------------------------//
// Add X axis
var x = d3.scaleLinear()
.domain([0, 50000])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(4));
// Add X axis label:
svg.append("text")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height+50 )
.text("GDP per Capita");
// Add Y axis
var y = d3.scaleLinear()
.domain([35, 90])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add Y axis label:
svg.append("text")
.attr("text-anchor", "end")
.attr("x", 0)
.attr("y", -20 )
.text("Life expectancy")
.attr("text-anchor", "start")
// Add a scale for bubble size
var z = d3.scaleSqrt()
.domain([200000, 1310000000])
.range([ 2, 30]);
// Add a scale for bubble color
var myColor = d3.scaleOrdinal()
.domain(["Asia", "Europe", "Americas", "Africa", "Oceania"])
.range(d3.schemeSet1);
// ---------------------------//
// TOOLTIP //
// ---------------------------//
// -1- Create a tooltip div that is hidden by default:
var tooltip = div
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "black")
.style("border-radius", "5px")
.style("border-style", "solid")
.style("border-width", "2px")
.style("padding", "10px")
.style("color", "white")
// -2- Create 3 functions to show / update (when mouse move but stay on same circle) / hide the tooltip
var showTooltip = function(event, d) {
//console.log(+new Date)
tooltip
.style("opacity", 1)
.style("border-color", myColor(d.continent))
.html(d.country)
.style("left", (event.pageX - margin.left) + "px")
.style("top", (event.pageY - margin.bottom) + "px")
}
var moveTooltip = function(event, d) {
tooltip
.style("left", (event.pageX - margin.left) + "px")
.style("top", (event.pageY - margin.bottom) + "px")
}
var hideTooltip = function(event, d) {
tooltip
.style("opacity", 0)
}
// ---------------------------//
// HIGHLIGHT GROUP //
// ---------------------------//
// What to do when one group is hovered
var highlight = function(event, d){
// reduce opacity of all groups
d3.selectAll(".bubbles").style("opacity", .05)
// expect the one that is hovered
d3.selectAll("."+d).style("opacity", 1)
}
// And when it is not hovered anymore
var noHighlight = function(event, d){
d3.selectAll(".bubbles").style("opacity", 1)
}
// ---------------------------//
// CIRCLES //
// ---------------------------//
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("class", function(d) { return "bubbles " + d.continent })
.attr("cx", function (d) { return x(d.gdpPercap); } )
.attr("cy", function (d) { return y(d.lifeExp); } )
.attr("r", function (d) { return z(d.pop); } )
.attr("fill", function(d) { return myColor(d.continent); } )
//.style("fill", function (d) { return myColor(d.continent); } )
// -3- Trigger the functions for hover
.on("mouseover", showTooltip)
.on("mousemove", moveTooltip)
.on("mouseout", hideTooltip)
// ---------------------------//
// LEGEND //
// ---------------------------//
// Add legend: circles
var valuesToShow = [10000000, 100000000, 1000000000]
var xCircle = window.innerWidth * 0.75
var xLabel = window.innerWidth * 0.75 + 50
svg
.selectAll("legend")
.data(valuesToShow)
.enter()
.append("circle")
.attr("cx", xCircle)
.attr("cy", function(d){ return height - 100 - z(d) } )
.attr("r", function(d){ return z(d) })
.attr("fill", "none")
//.style("fill", "none")
.attr("stroke", "black")
// Add legend: segments
svg
.selectAll("legend")
.data(valuesToShow)
.enter()
.append("line")
.attr('x1', function(d){ return xCircle + z(d) } )
.attr('x2', xLabel)
.attr('y1', function(d){ return height - 100 - z(d) } )
.attr('y2', function(d){ return height - 100 - z(d) } )
.attr('stroke', 'black')
.style('stroke-dasharray', ('2,2'))
// Add legend: labels
svg
.selectAll("legend")
.data(valuesToShow)
.enter()
.append("text")
.attr('x', xLabel)
.attr('y', function(d){ return height - 100 - z(d) } )
.text( function(d){ return d/1000000 } )
.style("font-size", 10)
.attr('alignment-baseline', 'middle')
// Legend title
svg.append("text")
.attr('x', xCircle)
.attr("y", height - 100 +30)
.text("Population (M)")
.attr("text-anchor", "middle")
// Add one dot in the legend for each name.
var size = 20
var allgroups = ["Asia", "Europe", "Americas", "Africa", "Oceania"]
svg.selectAll("myrect")
.data(allgroups)
.enter()
.append("circle")
.attr("cx", window.innerWidth*0.75)
.attr("cy", function(d,i){ return 10 + i*(size+5)}) // 100 is where the first dot appears. 25 is the distance between dots
.attr("r", 8)
.attr("fill", function(d){ return myColor(d)})
//.style("fill", function(d){ return myColor(d)})
.on("mouseover", highlight)
.on("mouseleave", noHighlight)
// Add labels beside legend dots
svg.selectAll("mylabels")
.data(allgroups)
.enter()
.append("text")
.attr("x", window.innerWidth*0.75 + size*.6)
.attr("y", function(d,i){ return i * (size + 5) + (size*0.6)}) // 100 is where the first dot appears. 25 is the distance between dots
.attr("fill", function(d){ return myColor(d)})
//.style("fill", function(d){ return myColor(d)})
.text(function(d){ return d})
.attr("text-anchor", "left")
.style("alignment-baseline", "middle")
.on("mouseover", highlight)
.on("mouseleave", noHighlight)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment