Skip to content

Instantly share code, notes, and snippets.

@maisnamraju
Forked from tlfrd/.block
Last active January 5, 2022 01:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maisnamraju/7b0773bb619d0b7ab49b81a89f758da1 to your computer and use it in GitHub Desktop.
Save maisnamraju/7b0773bb619d0b7ab49b81a89f758da1 to your computer and use it in GitHub Desktop.
Bubble Chart
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
.chart-background {
opacity: 0;
}
div.tooltip{
position: absolute;
text-align: center;
padding: 5px;
font: 12px sans-serif;
background-color: white;
border: 1px #b7b7b7 solid;
pointer-events: none;
width: 100px;
}
.dot {
fill: #7ff4a8;
stroke: black;
stroke-width: 0.75;
}
.active {
fill: blue;
}
.dot1 {
fill: #ffb4d9;
stroke: black;
stroke-width: 0.75;
}
.dot2 {
fill: #90bcf9;
stroke: black;
stroke-width: 0.75;
}
.axis-label {
font-size: 12px;
font-weight: 700;
}
.title {
font-size: 16px;
font-weight: 700;
}
.sub-title {
font-weight: 700;
}
.grid-line {
stroke: black;
opacity: 0.2;
stroke-dasharray: 1,2;
}
text {
font-family: sans-serif;
font-size: 12px;
}
.dot-label {
font-weight: 700;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
</style>
</head>
<body>
<script>
var margin = {top: 120, right: 150, bottom: 125, left: 270};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body").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 + ")");
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var formatPercent = d3.format(".0%");
var formatThousands = function(d) {
return d == 0 ? "0" : d3.format(".2s")(d);
};
var formatRatio = function(d) {
return d == 0 ? "" : d + ":1";
}
var uniLookup = {};
var xAccessor = function(d, x) {
return x(uniLookup[d.name].nonAcademicStaff);
};
var y1Accessor = function(d, y) {
return y(d.max / d.min);
};
var r1Accessor = function(d, r) {
return r(uniLookup[d.name].over);
}
var xScale = d3.scaleLinear(),
y1Scale = d3.scaleLinear(),
r1Scale = d3.scaleSqrt();
var config1 = {
top: 0,
left: 0,
width: 900,
height: height,
labelsToShow: ["Imperial", "UCL", "LSE", "LBS", "Brunel", "UEL"],
dotClassName: "dot1",
title: "Pay Ratios",
subtitle: "(Highest to Lowest Paid)",
xLabel: "No. of Non-academic Staff",
tickSize: 0,
domainOpacity: 0,
xAxisOffset: 10,
yAxisOffset: 10,
xTicks: 5,
yTicks: null
};
var dataUrl1 = "https://raw.githubusercontent.com/tlfrd/pay-ratios/master/data/over140k.json";
d3.queue()
.defer(d3.json, dataUrl1)
.await(load);
// When all data has loaded draw the scatter plot(s)
function load(error, data1, data2) {
if (error) throw error;
var over140 = data1.number_over_140k;
over140.forEach(d => uniLookup[d.name] = d);
// Filter out universities where number of women is not provided
over140 = over140.filter(a => a.women != "-");
xScale.domain([0, d3.max(over140, d => d.nonAcademicStaff)]);
y1Scale.domain([0, d3.max(ratios1516, d => d.max / d.min)]);
r1Scale.domain(d3.extent(over140, d => d.over))
.range([2, 20]);
scatterPlot(ratios1516, config1, xScale, y1Scale, r1Scale, xAccessor, y1Accessor, r1Accessor, formatThousands, formatRatio);
}
var legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(" + [-120, 0] + ")")
legend.append("circle")
.attr("class", "dot")
.attr("r", 10);
var flip = true;
function pulse(time) {
legend.select("circle")
.transition()
.duration(time)
.attr("r", d => flip ? 20 : 10)
.on("end", function() {
flip = !flip;
pulse(time);
})
}
pulse(3000);
var legendLabel = legend.append("g")
.attr("transform", "translate(" + [0, -50] + ")")
.attr("text-anchor", "middle")
legendLabel.append("text")
.text("No. of Staff")
legendLabel.append("text")
.attr("y", 14)
.text("Earning Over 140k");
// Draws a scatter plot
function scatterPlot(data, cfg, x, y, r, xAcc, yAcc, rAcc, xFormat, yFormat) {
var plot = svg.append("g")
.attr("class", "scatter-plot")
.attr("transform", "translate(" + [cfg.left, cfg.top] + ")");
plot.append("rect")
.attr("class", "chart-background")
.attr("width", cfg.width)
.attr("height", cfg.height);
x.range([0, cfg.width]);
y.range([cfg.height, 0]);
var xAxis = d3.axisBottom(x.nice())
.ticks(cfg.xTicks)
.tickSize(cfg.tickSize)
.tickFormat(xFormat);
var xAxisGroup = plot.append("g")
.attr("transform", "translate(" + [0, cfg.height + cfg.xAxisOffset] + ")")
.call(xAxis);
xAxisGroup.select(".domain").style("opacity", cfg.domainOpacity);
var xLabel = plot.append("g")
.append("text")
.attr("class", "axis-label")
.attr("text-anchor", "middle")
.attr("transform", "translate(" + [cfg.width / 2, cfg.height + 60] +")")
.text(cfg.xLabel);
var yAxis = d3.axisLeft(y.nice())
.ticks(cfg.yTicks)
.tickSize(cfg.tickSize)
.tickFormat(yFormat);
var yAxisGroup = plot.append("g")
.attr("transform", "translate(" + [-cfg.yAxisOffset, 0] + ")")
.call(yAxis);
yAxisGroup.select(".domain").style("opacity", cfg.domainOpacity);
var title = plot.append("g")
.attr("transform", "translate(" + [cfg.width / 2, -50] + ") ")
title.append("text")
.attr("class", "title")
.attr("text-anchor", "middle")
.text(cfg.title)
title.append("text")
.attr("class", "sub-title")
.attr("y", 15)
.attr("text-anchor", "middle")
.text(cfg.subtitle)
var gridLinesX = plot.append("g")
.selectAll("line")
.data(x.ticks(cfg.xTicks).slice(1, -1))
.enter().append("line")
.attr("class", "grid-line")
.attr("x1", d => x(d))
.attr("y1", d => 0)
.attr("x2", d => x(d))
.attr("y2", d => cfg.height);
var gridLinesY = plot.append("g")
.selectAll("line")
.data(y.ticks(cfg.yTicks).slice(1, -1))
.enter().append("line")
.attr("class", "grid-line")
.attr("x1", d => 0)
.attr("y1", d => y(d))
.attr("x2", d => cfg.width)
.attr("y2", d => y(d));
var dots = plot.append("g")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", cfg.dotClassName)
.attr("id", (d, i) => d.id = "c" + i)
.attr("cx", d => xAcc(d, x))
.attr("cy", d => yAcc(d, y))
.attr("r", d => rAcc(d, r))
.on("mouseover", function(d) {
d3.selectAll("circle#" + d.id)
.raise()
.transition()
.style("stroke-width", 1.5);
showLabel(d);
})
.on("mousemove", moveLabel)
.on("mouseout", function(d) {
d3.selectAll("circle#" + d.id)
.lower()
.transition()
.style("stroke-width", 0.75);
hideLabel();
})
var labels = plot.append("g")
.selectAll("text")
.data(data.filter(function(a) {
return cfg.labelsToShow.includes(a.name);
}))
.enter().append("text")
.attr("class", "dot-label")
.attr("x", d => xAcc(d, x))
.attr("y", d => yAcc(d, y))
.attr("text-anchor", "middle")
.attr("dy", d => -(rAcc(d, r) + 10))
.text(d => d.name)
}
function showLabel(d) {
var coords = [d3.event.clientX, d3.event.clientY];
var top = coords[1] + 30,
left = coords[0] - 50;
div.transition()
.duration(200)
.style("opacity", 1);
div.html("<b>" + d.name + "</b></br>" +
"Students: " + uniLookup[d.name].students + "</br>" +
"Staff: " + uniLookup[d.name].nonAcademicStaff)
.style("top", top + "px")
.style("left", left + "px");
}
function moveLabel() {
var coords = [d3.event.clientX, d3.event.clientY];
var top = coords[1] + 30,
left = coords[0] - 50;
div.style("top", top + "px")
.style("left", left + "px");
}
function hideLabel(d) {
div.transition()
.duration(200)
.style("opacity", 0);
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment