Published
Edited
May 8, 2019
2 forks
15 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const svg = this ? d3.select(this) : create_svg();

function create_svg() {
const svg = d3.select(DOM.svg(width, height)).style("font", "14px Montserrat");
//background
svg
.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "#F5F5F2");
svg.append("g");
return svg;
}

const t = svg.transition()
.duration(1000);
const g = svg.select("g");
g.selectAll('circle')
.data(freedom_year, d => d.countries) //is this right? maybe this is why update doesn't work?
.join(
enter => enter.append("circle")
.attr("class", "bubble")
.attr("id", (d, i) => "point-" + i)
.attr("fill-opacity", 0)
.attr("fill", d => colorScale(d.region_simple))
.attr("cx", width/2 - margin.left)
.attr("cy", height/2 - margin.bottom)
.attr("r", 0)
.call(enter => enter.transition(t)
.attr("fill-opacity", 0.75)
.attr("cx", d => xScale(d.population))
.attr("cy", d => yScale(d.gdp_ppp_cap))
.attr("r", d => aScale(d.pf_score))),
update => update
.call(update => update.transition(t)
.attr("cx", d => xScale(d.population))
.attr("cy", d => yScale(d.gdp_ppp_cap))
.attr("r", d => aScale(d.pf_score))),
exit => exit
.call(exit => exit.transition(t)
.attr("r", 0)
.style("fill-opacity", 0)
.remove())
);
let limVoronoi = d3.distanceLimitedVoronoi()
.x(d => xScale(d.population))
.y(d => yScale(d.gdp_ppp_cap))
.limit(25)
svg.append("g")
.selectAll("path")
.data(limVoronoi(freedom_year))
.join("path")
.attr("d", (d, i) => d.path)
//.datum((d, i) => d.point) //what is this for?
.attr("id", (d, i) => "voronoi-" + i)
//.style("stroke", "steelblue")
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", tooltipMouseOver)
.on("mouseout", tooltipMouseOut)
//x axis
svg.append("g")
.call(xAxis);
//y axis
svg.append("g")
.call(yAxis);
/*
//x grid lines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.attr("stroke-opacity", 0.1)
.call(make_x_gridlines()
.tickSize(-height+margin.top+margin.bottom)
.tickFormat("")
)
//y grid lines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(" + margin.left + ",0)")
.attr("stroke-opacity", 0.1)
.call(make_y_gridlines()
.tickSize(-width + margin.left + margin.right)
.tickFormat("")
)
*/
yield svg.node();

//color legend
svg.append("g")
.attr("transform", "translate(" + (width - margin.right + 20) + ",100)")
.call(legend)
//x axis label
svg.append("text")
.attr("class", "text")
.attr("transform", "translate(" + (width/2) + "," + (height - 20) + ")")
.style("text-anchor", "middle")
.style("font-size", "20px")
.text("Population");
//y axis label
svg.append("text")
.attr("class", "text")
.attr("transform", "translate(" + (margin.left/4) + "," + (height/2) + ")rotate(-90)")
.style("text-anchor", "middle")
.style("font-size", "20px")
.text("GDP (PPP) per capita");
//title
svg.append("text")
.attr("class", "text")
.attr("transform", "translate(0," + margin.top/3 + ")")
.style("text-anchor", "left")
.style("font-size", "25px")
.text("Personal freedom score for world countries")
//subtitle
svg.append("text")
.attr("class", "text")
.attr("transform", "translate(0," + margin.top/1.6 + ")")
.style("text-anchor", "left")
.style("font-size", "20px")
.style("font-style", "italic")
.text("Bubbles are colored by region and sized by personal freedom score")
}
Insert cell
Insert cell
Insert cell
xScale = d3.scaleLog()
.domain(d3.extent(freedom_year, d => d.population)).nice()
.range([margin.left, width-margin.right]);
Insert cell
yScale = d3.scaleLog()
.domain(d3.extent(freedom_year, d => d.gdp_ppp_cap)).nice()
.range([height - margin.bottom, margin.top]);
Insert cell
aScale = d3.scaleSqrt()
.domain([3, d3.max(freedom_year, d => d.pf_score)])
.range([0, 12]);
Insert cell
pf_min = d3.min(freedom_year, d=>d.pf_score)
Insert cell
//this is too many colors, change somehow later
colorScale = d3.scaleOrdinal()
.domain(d3.map(freedom_year, d => d.region_simple).keys())
.range(colors);
Insert cell
Insert cell
xAxis = g => g
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(xScale))
.call(g => g.select(".domain").remove());
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(yScale))
.call(g => g.select(".domain").remove());
Insert cell
function make_x_gridlines() {
return d3.axisBottom(xScale)
.ticks(5)
};
Insert cell
function make_y_gridlines() {
return d3.axisLeft(yScale)
.ticks(3)
};
Insert cell
Insert cell
width = 960
Insert cell
height = 800
Insert cell
margin = ({top:100, right:250, bottom: 70, left: 60})
Insert cell
colors = ["#596F7E", "#168B98", "#ED5B67", "#fd8f24","#919c4c"]
Insert cell
minYear = d3.min(freedom, d => d.year)
Insert cell
maxYear = d3.max(freedom, d => d.year)
Insert cell
html`
<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">

<style>
.text {
font-family: "Montserrat";
}
.grid {
font-family: "Montserrat";
}
div.tooltip {
font-family: "Montserrat";
position: absolute;
text-align: left;
padding: 0.5em;
font: 0.8em;
background: rgb(255, 255, 255);
border: 1px solid rgba(0, 0, 0, 0.4);
border-radius: 0.5em;
pointer-events: none;
}
.bubble {
mix-blend-mode: multiply;
}
</style>`
Insert cell
decFormat = d3.format(".2f")
Insert cell
bigFormat = d3.format(",.0f")
Insert cell
Insert cell
legend = d3_legend.legendColor()
.scale(colorScale)
.title("Region")
//.labelWrap(30)
Insert cell
//bubble legend, from Nadieh Bremer
//http://bl.ocks.org/nbremer/801c4bb101e86d19a1d0
function bubbleLegend(wrapperVar, scale, sizes, titleName) {

let legendSize1 = sizes[0],
legendSize2 = sizes[1],
legendSize3 = sizes[2],
legendCenter = 0,
legendBottom = 50,
legendLineLength = 25,
textPadding = 5,
numFormat = d3.format(",");
wrapperVar.append("text")
.attr("class","legendTitle")
.attr("transform", "translate(" + legendCenter + "," + 0 + ")")
.attr("x", 0 + "px")
.attr("y", 0 + "px")
.attr("dy", "1em")
.text(titleName);
wrapperVar.append("circle")
.attr('r', scale(legendSize1))
.attr('class',"legendCircle")
.attr('cx', legendCenter)
.attr('cy', (legendBottom-scale(legendSize1)));
wrapperVar.append("circle")
.attr('r', scale(legendSize2))
.attr('class',"legendCircle")
.attr('cx', legendCenter)
.attr('cy', (legendBottom-scale(legendSize2)));
wrapperVar.append("circle")
.attr('r', scale(legendSize3))
.attr('class',"legendCircle")
.attr('cx', legendCenter)
.attr('cy', (legendBottom-scale(legendSize3)));
wrapperVar.append("line")
.attr('class',"legendLine")
.attr('x1', legendCenter)
.attr('y1', (legendBottom-2*scale(legendSize1)))
.attr('x2', (legendCenter + legendLineLength))
.attr('y2', (legendBottom-2*scale(legendSize1)));
wrapperVar.append("line")
.attr('class',"legendLine")
.attr('x1', legendCenter)
.attr('y1', (legendBottom-2*scale(legendSize2)))
.attr('x2', (legendCenter + legendLineLength))
.attr('y2', (legendBottom-2*scale(legendSize2)));
wrapperVar.append("line")
.attr('class',"legendLine")
.attr('x1', legendCenter)
.attr('y1', (legendBottom-2*scale(legendSize3)))
.attr('x2', (legendCenter + legendLineLength))
.attr('y2', (legendBottom-2*scale(legendSize3)));
wrapperVar.append("text")
.attr('class',"legendText")
.attr('x', (legendCenter + legendLineLength + textPadding))
.attr('y', (legendBottom-2*scale(legendSize1)))
.attr('dy', '0.25em')
.text("$ " + numFormat(Math.round(legendSize1/1e9)) + " B");
wrapperVar.append("text")
.attr('class',"legendText")
.attr('x', (legendCenter + legendLineLength + textPadding))
.attr('y', (legendBottom-2*scale(legendSize2)))
.attr('dy', '0.25em')
.text("$ " + numFormat(Math.round(legendSize2/1e9)) + " B");
wrapperVar.append("text")
.attr('class',"legendText")
.attr('x', (legendCenter + legendLineLength + textPadding))
.attr('y', (legendBottom-2*scale(legendSize3)))
.attr('dy', '0.25em')
.text("$ " + numFormat(Math.round(legendSize3/1e9)) + " B");
}
Insert cell
Insert cell
function tooltipMouseOver(d, i) {
let el = d3.select("body");
let div = el
.append("g")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);

div
.html(`
Country: ${d.datum['countries']} </br>
Personal Freedom Score: ${decFormat(d.datum['pf_score'])} </br>
Population: ${bigFormat(d.datum['population'])} </br>
GDP per capita: ${bigFormat(d.datum['gdp_ppp_cap'])}
`)
.style("opacity", 0.9)
.style("left", (d3.event.pageX + 28) + "px")
.style("top", (d3.event.pageY - 28) + "px");
let el2 = d3.selectAll("circle#point-"+i);
el2
.transition()
.duration(100)
.style("fill-opacity", 0.9)
.style("stroke", "black")
.style("stroke-width", "1px")
.style("stroke-opacity", 0.7);
let guides = d3.select("svg")
.append("g")
.attr("class", "guides")
.style("pointer-events", "none");
guides
.append("line")
.attr("x1", el2.attr("cx"))
.attr("x2", el2.attr("cx"))
.attr("y1", el2.attr("cy"))
.attr("y2", height - margin.bottom)
.style("stroke", el2.attr("fill"))
.style("opacity", 0)
.transition()
.duration(200)
.style("opacity", 0.7);
guides
.append("line")
.attr("x1", el2.attr("cx"))
.attr("x2", margin.left)
.attr("y1", el2.attr("cy"))
.attr("y2", el2.attr("cy"))
.style("stroke", el2.attr("fill"))
.style("opacity", 0)
.transition()
.duration(200)
.style("opacity", 0.7);
}


Insert cell
function tooltipMouseOut(d, i) {
let el = d3.selectAll(".tooltip");
el.each(function() {
el.remove();
});
let el2 = d3.selectAll("circle#point-"+i);
el2
.transition()
.duration(100)
.style("fill-opacity", 0.75)
.style("stroke-width", "0px");
d3.select(".guides").remove();

}
Insert cell
Insert cell
d3 = require("d3")
Insert cell
d3_legend = require('https://bundle.run/d3-svg-legend@2.25.6')
Insert cell
import { slider, radio, text } from '@jashkenas/inputs'
Insert cell
Insert cell
Insert cell
Insert cell
freedom = d3.csv("https://gist.githubusercontent.com/will-r-chase/16827fa79e02af9e3a0651fb0d79b426/raw/92b321a8bc4d98e463156ef03a5da5cf05065704/freedom_clean.csv", d3.autoType)
Insert cell
freedom_year = freedom.filter(obj => {return obj.year === selectedYear})
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