Skip to content

Instantly share code, notes, and snippets.

@timproDev
Last active January 26, 2018 17:25
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 timproDev/d73bf52bc26f20b920a478e928bbc1c8 to your computer and use it in GitHub Desktop.
Save timproDev/d73bf52bc26f20b920a478e928bbc1c8 to your computer and use it in GitHub Desktop.
Bubble Scale with centered labels

The challenge here was to place the label ticks centered between the border of each circle. I came across some very helpful code on stackoverflow but cannot recall the genius who present this pattern.

It is something like this:

eventText.attr("y",function(d,i){
  // grab data of next key and math the distance between for placement            
  if (i >= 0 && i < (data.length - 1)) {
  var nextData = i>=0?eventText.data()[i+1]:eventText.data()[eventText.data().length+1];        
  var thisHeight = scale(d.value) * 2;
  var nextHeight = scale(nextData.value) * 2;
  var newcoord = nextHeight + ((thisHeight - nextHeight) / 2);
  return h - (newcoord);
  } else {
  return h - scale(d.value);
  }
});
var d3Config = d3Config || {};
d3Config.init = function(settings) {
console.log("Configing!!");
d3Config.linkStyle(settings);
if (typeof settings.win.d3 === "undefined") {
console.log("lib undefined");
d3Config.loadLib(settings);
}
if (typeof settings.win.d3 !== "undefined") {
console.log("lib defined");
d3Config.loadComp(settings);
}
}
d3Config.linkStyle = function(params){
var linkElem = document.createElement('link');
document.getElementsByTagName('head')[0].appendChild(linkElem);
linkElem.rel = 'stylesheet';
linkElem.type = 'text/css';
linkElem.href = (params.path + params.css);
}
d3Config.loadLib = function(params){
$.when(
$.getScript((params.path + params.lib)),
$.Deferred(function( deferred ){
$( deferred.resolve );
})
).done(function(){
console.log("lib defined");
d3Config.loadComp(params);
});
}
d3Config.loadComp = function(params){
$.when(
$.getScript((params.path + params.comp)),
$.Deferred(function( deferred ){
$( deferred.resolve );
})
).done(function(){
params.win[params.func](params.path, params.data, params.elem, params.options);
d3Config.chartRefire(params);
});
}
d3Config.chartRefire = function(params){
var resizeTimer;
$(params.win).on('resize', function(e) {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
params.win[params.func](params.path, params.data, params.elem, params.options);
}, 300);
});
}
Event name Year Total payout # of claims Avg. claim
Hurricane Katrina 2005 16.32 168019 97147
Superstorm Sandy 2012 8.63 131616 65547
Hurricane Harvey 2017 11.0 84000 N/A
Hurricane Irma 2017 9.0 N/A N/A
Hurricane Ike 2008 2.70 46687 57826
Louisiana Severe Storms 2016 2.43 27044 89734
Hurricane Ivan 2004 1.61 28300 56974
Hurricane Irene 2011 1.34 44292 30347
Tropical Storm Allison 2001 1.10 30671 36028
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bubble Scale</title>
<link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="">
<script src="https://code.jquery.com/jquery-3.2.1.js" integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE=" crossorigin="anonymous"></script>
</head>
<body>
<section style="width:770px; margin: 4rem auto;">
<div class="mx-dv-container" id="cBubbleScale">
<h3 class="chart-title">NFIP's Largest Flood Events</h3>
<div class="chart-premise"></div>
<div class="plot-wrap w-svg"></div>
<div class="plot-wrap no-svg"></div>
</div>
</section>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(){
var d3settings = {
win: window,
path:"",
css: "m-flood-bubble-scale.css",
lib: "d3v4-473-jetpack.js",
comp: "m-flood-bubble-scale.js",
data: "events.csv",
elem: "cBubbleScale",
func: "bubbleScale",
options:{
axisCategory:"genre",
axisValue:"Total payout",
chartHeight:550
}
};
d3Config.init(d3settings);
});
</script>
<script src="d3-config.js"></script>
<script src="https://rawgit.com/timproDev/d3-first-timer/master/js/d3v4-473-jetpack.js"></script>
</body>
</html>
/* Global Styles and resets */
@media only screen and (min-width: 641px) {
body, html {
overflow-x: hidden; } }
@media only screen and (max-width: 40em) {
html {
-webkit-text-size-adjust: 100%; } }
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: "Quicksand", Arial, sans-serif;
font-weight:300;
height: auto;
display: block;
background-color: #fcfcfc; }
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block; }
img {
border: none; }
button,
input,
optgroup,
select,
textarea {
color: inherit;
/* 1 */
font: inherit;
/* 2 */
margin: 0;
/* 3 */
-webkit-font-smoothing: antialiased; }
input, button, select {
outline: none; }
*,
*:before,
*:after {
box-sizing: border-box; }
.prm-box, .tooltip {
box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.5);
border-radius: 2px;
background-color: #fff; }
.chart-container-linedot-table, .chart-container-table {
margin-top: 1rem; }
.chart-container-linedot-table table, .chart-container-table table {
width: 100%;
background-color: #fff; }
.chart-table-container {
margin-top: 1rem; }
.chart-table-container table {
width: 100%;
background-color: #fff; }
.mx-dv-container {
text-align: center;
margin: 0 0 2rem 0;
background-color: transparent;
border-top: 6px solid #f5f5f5;
border-bottom: 6px solid #f5f5f5;
font-weight: normal;
font-size: 16px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:500;
/* IE HACK CANVAS */ }
.mx-dv-container .plot-wrap.no-svg {
display: none;
padding-bottom: 1rem; }
.mx-dv-container .plot-wrap.no-svg img {
width: 100%;
max-width: 706px; }
.mx-dv-container .svg-container {
position: relative; }
.mx-dv-container .svg-container canvas {
display: block;
height: 100%;
visibility: hidden; }
.mx-dv-container .svg-container svg {
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%; }
.mx-dv-container .chart-title {
font-weight: normal;
font-size: 21px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:500;
padding: 1.15rem 0 0 0;
margin: 0;
text-align: left; }
.mx-dv-container .chart-premise, .mx-dv-container .chart-citation {
font-weight: normal;
font-size: 16px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:300;
color: #808080;
padding: .25rem 0 1rem 0;
margin: 0;
text-align: left; }
.mx-dv-container .chart-premise p, .mx-dv-container .chart-premise input, .mx-dv-container .chart-citation p, .mx-dv-container .chart-citation input {
margin: 0;
text-align: left;
display: inline-block;
vertical-align: middle;
padding: 0 .75rem .25rem 0; }
.mx-dv-container .chart-citation {
font-weight: normal;
font-size: 13px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:300;
color: #bdbdbd; }
.mx-dv-container .raw-wrap {
text-align: right;
padding: 0 1.5rem 0 0;
margin: 0; }
.mx-dv-container .raw-wrap .raw-btn {
font-weight: normal;
font-size: 14px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:300;
cursor: pointer;
margin: 0 0 .75rem 0;
padding: 0; }
.tooltip {
top: -1000px;
position: fixed;
padding: .65rem;
pointer-events: none;
max-width: 25%;
z-index: 500; }
.tooltip-info {
color: #45555f;
font-weight: normal;
font-size: 16px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:500;
padding: 0;
margin: 0;
display: block;
width: 100%; }
.tooltip-info span {
font-weight: normal;
font-size: 16px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:700;
}
.tooltip-hidden {
opacity: 0;
transition: all .3s;
transition-delay: .1s; }
.dnld-btn {
display: block;
width: 100%;
margin-top: -1.25rem;
padding: .75rem 1.25rem;
text-align: right;
font-weight: normal;
font-size: 14px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:300;
}
.dnld-btn:hover {
text-decoration: underline; }
@media only screen and (max-width: 40em) {
.mx-dv-container .plot-wrap.no-svg {
display: block; }
.mx-dv-container .plot-wrap.w-svg {
display: none; } }
#cBubbleScale .domain {
display: none; }
#cBubbleScale .value-text {
font-weight: normal;
font-size: 21px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:700;
fill: #233444; }
#cBubbleScale .event-text {
font-weight: normal;
font-size: 14px;
font-family: "Quicksand", Arial, sans-serif;
font-weight:500;
fill: #1281b2; }
#cBubbleScale .scale-fixed {
fill-opacity: .2; }
#cBubbleScale .anno-line {
fill: #325C6F; }
#cBubbleScale .anno-dot {
fill: #fff;
stroke-width: .65;
stroke: #325C6F; }
function bubbleScale(setPath, setData, elem, opts){
var settings = {
axisCategory: opts.axisCategory,
axisValue: opts.axisValue,
orientationVal: opts.orientationVal,
rangeMax: opts.rangeMax,
sortDescending: opts.sortDescending,
chartHeight: opts.chartHeight,
transSpeed: 700,
dataPath: setPath + setData,
chartContainerID: "#" + elem
};
var margin = {
top:0,
left:20,
right:20,
bottom:20
};
var chartContainer = d3.select(settings.chartContainerID);
var plotContainer = chartContainer.select(".plot-wrap.w-svg").html('');
var w = plotContainer.node().clientWidth - margin.left - margin.right,
h = settings.chartHeight - margin.top - margin.bottom;
// h = .7 * width;
// cancel on mobile
if(w < 576) {
return;
};
var premiseDiv = chartContainer.select("div.chart-premise");
// create inner div
var div = plotContainer
.append("div.svg-container")
.style("width", (w + margin.left + margin.right) + "px")
.style("height", (h + margin.top + margin.bottom) + "px");
// insert canvas inner div
div.insert("canvas");
// append svg in inner div
var svg = div
.append("svg")
.style("width", (w + margin.left + margin.right) + "px")
.style("height", (h + margin.top + margin.bottom) + "px")
.append("g.wrap")
.translate([margin.left,margin.top]);
d3.queue()
.defer(d3.csv, settings.dataPath)
.await(ready);
var formatMoney = d3.format(".3n");
function ready(error, data){
if (error) throw "error: not loading data, bro";
// remove Ivan and Irene
data = data.filter(function(d){
if (d["Event name"] !== "Louisiana Severe Storms" && d["Event name"] !== "Tropical Storm Allison") {
return d;
}
});
// ui interaction
var optionsArray = d3.keys(data[0]);
// Remove Catastrophe, Events and Fatalities keys from UI options
optionsArray = optionsArray.filter(function(d){
if (d !== "Year" && d !== "Event name" && d !== "Avg. claim") {
return d;
}
});
data.forEach(function(d){
d.value = +d[settings.axisValue];
});
// sort data, highest to lowest
data.sort(function(a,b){
return d3.descending(+a.value, +b.value)
});
var maxx = d3.max(data, function(d){ return d.value; });
var scale = d3.scaleLinear()
.domain([0, maxx])
.range([0,h/2]);
// bubbles
var bubble = svg.selectAll("circle.scale-fixed")
.data(data)
.enter()
.append("g")
.append("circle.scale-fixed")
.attr("fill-opacity",".2")
.attr("fill","#3fa0c6")
.attr("stroke","#ffffff")
.attr("stroke-width",".65px")
.attr("r", function(d) {
return scale(d.value);
})
.attr("cx",w/2)
.attr("cy",function(d){
return h - scale(d.value);
});
// line indicator
var rectLine = svg.selectAll("rect.anno-line")
.data(data)
.enter()
.append("g")
.append("rect.anno-line")
.attr("width",function(d,i){
return w/2;
})
.attr("height",".5")
.attr("x",function(d,i){
if (i % 2) {
return 0;
} else {
return w/2;
}
});
// line indicator
rectLine
.attr("y",function(d,i){
if (i >= 0 && i < (data.length - 1)) {
var nextData = i>=0?rectLine.data()[i+1]:rectLine.data()[rectLine.data().length+1];
var thisHeight = scale(d.value) * 2;
var nextHeight = scale(nextData.value) * 2;
var newcoord = nextHeight + ((thisHeight - nextHeight) / 2);
return h - (newcoord);
} else {
return h - scale(d.value);
}
});
// placement indicator
var dot = svg.selectAll("circle.anno-dot")
.data(data)
.enter()
.append("g")
.append("circle.anno-dot")
.attr("r", 4)
.attr("cx",w/2);
dot.attr("cy",function(d,i){
if (i >= 0 && i < (data.length - 1)) {
var nextData = i>=0?dot.data()[i+1]:dot.data()[dot.data().length+1];
var thisHeight = scale(d.value) * 2;
var nextHeight = scale(nextData.value) * 2;
var newcoord = nextHeight + ((thisHeight - nextHeight) / 2);
return h - (newcoord);
} else {
return h - scale(d.value);
}
});
// event LABEL EVENT
var eventText = svg.selectAll("text.event-text")
.data(data)
.enter()
.append("text.event-text")
// stagger the placement of the rect, every other
.attr("x",function(d,i){
if (i % 2) {
return 0;
} else {
return w;
}
})
.attr("text-anchor",function(d,i){
if (i % 2) {
return "start";
} else {
return "end";
}
})
.attr("dy","-6px")
.text(function(d){
return d["Event name"];
});
eventText.attr("y",function(d,i){
// grab data of next key and math the distance between for placement
if (i >= 0 && i < (data.length - 1)) {
var nextData = i>=0?eventText.data()[i+1]:eventText.data()[eventText.data().length+1];
var thisHeight = scale(d.value) * 2;
var nextHeight = scale(nextData.value) * 2;
var newcoord = nextHeight + ((thisHeight - nextHeight) / 2);
return h - (newcoord);
} else {
return h - scale(d.value);
}
});
// event VALUE
var eventText = svg.selectAll("text.value-text")
.data(data)
.enter()
.append("text.value-text")
// stagger the placement of the rect, every other
.attr("x",function(d,i){
if (i % 2) {
return 0;
} else {
return w;
}
})
.attr("text-anchor",function(d,i){
if (i % 2) {
return "start";
} else {
return "end";
}
})
.attr("dy","20px")
.text(function(d){
var money = formatMoney(d["Total payout"]);
return "$" + money + " Billion";
});
eventText.attr("y",function(d,i){
// grab data of next key and math the distance between for placement
if (i >= 0 && i < (data.length - 1)) {
var nextData = i>=0?dot.data()[i+1]:dot.data()[dot.data().length+1];
var thisHeight = scale(d.value) * 2;
var nextHeight = scale(nextData.value) * 2;
var newcoord = nextHeight + ((thisHeight - nextHeight) / 2);
return h - (newcoord);
} else {
return h - scale(d.value);
}
});
}; // end of ready
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment