Skip to content

Instantly share code, notes, and snippets.

@maartenzam
Last active April 23, 2016 21:18
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 maartenzam/af5c4590b47a22a15a06cc81b026a753 to your computer and use it in GitHub Desktop.
Save maartenzam/af5c4590b47a22a15a06cc81b026a753 to your computer and use it in GitHub Desktop.
Cancer scatterplot

A responsive scatterplot, showing prevalence and survival rates of different types of cancer for both sexes. Use the buttons to zoom in on the rare types of cancer.

More explication here, live here (Dutch).

Soort Geslacht Prevalentie Overleving
Head and neck m 21.4 50.7
Esophagus m 10.5 23.5
Stomach m 5.4 35.2
Colon m 29.7 65.5
Rectum m 14.2 66.8
Liver m 6.2 21
Gall bladder m 1.7 19.6
Pancreas m 8.1 11.3
Lung m 53.5 15.8
Pleura and peritoneum m 2 5.3
Skin m 12.1 86.6
Breast m 0.8 83.1
Prostate m 75.5 95.2
Testicle m 6.3 96.5
Kidney m 11.1 74.6
Bladder m 16 55.8
Brain m 7.2 20.7
Thyroid m 3.2 91.3
Head and neck f 6.3 57.6
Esophagus f 3 26
Stomach f 3.3 43.7
Colon f 21.2 67.2
Rectum f 7.7 66.7
Liver f 2.2 19.9
Gall bladder f 1.3 17.3
Pancreas f 6.3 11
Lung f 23.4 22.6
Pleura and peritoneum f 0.4 15.1
Skin f 18.5 92.8
Breast f 109.8 89.6
Cervix f 8 69
Uterus f 11.5 79.2
Ovary f 7.1 42.7
Kidney f 5.5 75.2
Bladder f 3 48.6
Brain f 4 22.3
Thyroid f 9.4 95.6
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Kankerscatter</title>
<style>
body {
font: 12px sans-serif;
margin: 4px;
}
#chart {
width: 100%;
height: 100%;
min-width: 300px;
min-height: 300px;
position: absolute;
}
.d3-tip {
line-height: 1;
font: 14px sans-serif;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: rgb(185, 185, 185);
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
button {
background-color: #fa4951;
color: white;
border: none;
border-radius: 1px;
font-size: 14px;
margin: 0px 4px;
padding: 4px;
width: 160px;
cursor: pointer;
}
button.pressed {
background-color: #fdafb3;
}
/*Axes stuff*/
.axis {
fill: #aaa;
}
.axis path {
stroke: #aaa;
stroke-width: 2;
opacity: 0.5;
}
.axis.y .tick line {
stroke: #aaa;
opacity: 0.3;
}
.axis.y path {
visibility: hidden;
}
.axis.x .tick line {
stroke: #aaa;
opacity: 0;
}
.axis .tick text {
font-size: 14px;
fill: #aaa;
opacity: 0.8;
}
text.label {
fill: #555;
opacity: 1;
font-size: 18px;
}
</style>
</head>
<body>
<button id="zoomin">Rare cancers</button><button id="zoomout" class="pressed">All cancers</button><br />
<svg id="chart"></svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script>
var margin = {top: 40, right: 40, bottom: 40, left: 60},
dim = parseInt(d3.select("#chart").style("width")),
width = dim - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
//scales and axes
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickValues([0,20,40,60,80,100])
.outerTickSize(0)
.innerTickSize(8)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.tickValues([20,40,60,80,100])
.innerTickSize(-width)
.outerTickSize(0)
.orient("left");
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Tooltip
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<div><span></span> <span style='color:white'>" + d.Soort + " (" + d.Geslacht+ ")</span></div>" + "<div><span></span> <span style='color:white'>" + d.Prevalentie + " cases 100.000</span></div>" +
"<div><span></span> <span style='color:white'>"+ Math.round(d.Overleving) + " % survival</span></div>";
})
svg.call(tip);
d3.csv("cancerdata.csv", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.Prevalentie = +d.Prevalentie;
d.Overleving = +d.Overleving;
});
x.domain([0, 100]);
y.domain([0, 110]);
//generate data for the connecting lines
var linedata = d3.nest()
.key(function(d) {return d.Soort})
.entries(data);
linedata = linedata.filter(function(el) {return el.values.length == 2; });
svg.selectAll(".connect")
.data(linedata)
.enter().append("line")
.attr("class", function(d) { return "connect " + d.key; })
.attr("x1", function(d) { return x(d.values[0].Overleving)})
.attr("x2", function(d) { return x(d.values[1].Overleving)})
.attr("y1", function(d) { return y(d.values[0].Prevalentie)})
.attr("y2", function(d) { return y(d.values[1].Prevalentie)})
.attr("stroke", "grey")
.attr("stroke-width", 1);
linedata.forEach(function(d) {
d.key = d.key.replace('kanker', '')
});
//Chartlabels
svg.selectAll("text.chartlabel")
.data(linedata)
.enter().append("text")
.text(function(d) {return d.key})
.attr("class", "chartlabel")
.attr("x", function(d) { return x((d.values[0].Overleving + d.values[1].Overleving)/2); })
.attr("y", function(d) { return y((d.values[0].Prevalentie + d.values[1].Prevalentie)/2); })
.style("opacity", function(d) {
if((d.values[0].Prevalentie + d.values[1].Prevalentie)/2 < 25) {
return 0;
}
else {
return 0.4;
}
})
.style("font-size", function() {
if(width < 1024) {
return 16;
}
else {
return 22;
}
})
.style("fill", "#c2b6a4")
.attr("text-anchor", "middle");
//Axes
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", 40)
.style("text-anchor", "end")
.text("Survival rate after 5 years (%)");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Prevalence (cases per year per 100.000 people)")
//Mars and Venus symbols
var symbolcolors = {m: "#e86756", f: "#9FA8DA", mout: "#9a0b16", fout: "#3f51b5"};
var symbolgroup = svg.selectAll(".symbol")
.data(data)
.enter().append("g")
.attr("class", function(d) { return d.Geslacht; })
.attr("transform", function(d) { return "translate(" + x(d.Overleving) + "," + y(d.Prevalentie) + ")"; });
var vrouwen = svg.selectAll("g.f");
vrouwen
.append("line")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", 0)
.attr("y2", 20)
.attr("stroke", symbolcolors.fout)
.attr("stroke-width", 2);
vrouwen
.append("line")
.attr("x1", -7)
.attr("x2", 7)
.attr("y1", 14)
.attr("y2", 14)
.attr("stroke", symbolcolors.fout)
.attr("stroke-width", 2);
var mannen = svg.selectAll("g.m");
mannen
.append("line")
.attr("x1", 0)
.attr("x2", 14)
.attr("y1", 0)
.attr("y2", -14)
.attr("stroke", symbolcolors.mout)
.attr("stroke-width", 2);
mannen
.append("line")
.attr("x1", 7)
.attr("x2", 15)
.attr("y1", -14)
.attr("y2", -14)
.attr("stroke", symbolcolors.mout)
.attr("stroke-width", 2);
mannen
.append("line")
.attr("x1", 14)
.attr("x2", 14)
.attr("y1", -6)
.attr("y2", -14)
.attr("stroke", symbolcolors.mout)
.attr("stroke-width", 2);
symbolgroup.append("circle")
.attr("class", "dot")
.attr("r", 8)
.style("fill", function(d) { return symbolcolors[d.Geslacht]; })
.style("stroke", function(d) { return symbolcolors[d.Geslacht + "out"]; })
.style("stroke-width", 2)
.on('mouseover', function(d) {
tip.show(d);
d3.select(this).style("stroke-width", 3);
})
.on('mouseout', function(d) {
d3.select(this).style("stroke-width", 2);
tip.hide(d);
});
});
//button functionality
d3.select("#zoomin").on("click", function() {
d3.select("#zoomout").classed("pressed", false);
d3.select("#zoomin").classed("pressed", true);
y.domain([0, 21]);
svg.select('.y.axis')
.transition().duration(1500)
.call(yAxis);
svg.selectAll("g.m, g.f")
.transition().duration(1500)
.attr("transform", function(d) { return "translate(" + x(d.Overleving) + "," + y(d.Prevalentie) + ")"; });
svg.selectAll("line.connect")
.transition().duration(1500)
.attr("y1", function(d) { return y(d.values[0].Prevalentie)})
.attr("y2", function(d) { return y(d.values[1].Prevalentie)});
svg.selectAll("text.chartlabel")
.transition().duration(1500)
.attr("x", function(d) { return x((d.values[0].Overleving + d.values[1].Overleving)/2); })
.attr("y", function(d) { return y((d.values[0].Prevalentie + d.values[1].Prevalentie)/2); })
.style("opacity", 0.4);
});
d3.select("#zoomout").on("click", function() {
d3.select("#zoomin").classed("pressed", false);
d3.select("#zoomout").classed("pressed", true);
y.domain([0, 110]);
svg.select('.y.axis')
.transition().duration(1500)
.call(yAxis);
svg.selectAll("g.m, g.f")
.transition().duration(1500)
.attr("transform", function(d) { return "translate(" + x(d.Overleving) + "," + y(d.Prevalentie) + ")"; });
svg.selectAll("line.connect")
.transition().duration(1500)
.attr("y1", function(d) { return y(d.values[0].Prevalentie)})
.attr("y2", function(d) { return y(d.values[1].Prevalentie)});
svg.selectAll("text.chartlabel")
.transition().duration(1500)
.attr("x", function(d) { return x((d.values[0].Overleving + d.values[1].Overleving)/2); })
.attr("y", function(d) { return y((d.values[0].Prevalentie + d.values[1].Prevalentie)/2); })
.style("opacity", function(d) { if((d.values[0].Prevalentie + d.values[1].Prevalentie)/2 < 25) {
return 0;
}
else {
return 0.4;
}
});
});
//Responsiveness
function resize() {
var dim = parseInt(d3.select("#chart").style("width")),
width = dim - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
// Update the range of the scale with new width/height
x.range([0, width]);
y.range([height, 0]);
// Update the axis and text with the new scale
svg.select('.x.axis')
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.select('.x.axis').select('.label')
.attr("x",width);
svg.select('.y.axis')
.call(yAxis);
// Update the ticks
yAxis.innerTickSize(-width);
svg.selectAll("g.m, g.f")
.attr("transform", function(d) { return "translate(" + x(d.Overleving) + "," + y(d.Prevalentie) + ")"; });
svg.selectAll("line.connect")
.attr("x1", function(d) { return x(d.values[0].Overleving)})
.attr("x2", function(d) { return x(d.values[1].Overleving)})
.attr("y1", function(d) { return y(d.values[0].Prevalentie)})
.attr("y2", function(d) { return y(d.values[1].Prevalentie)});
svg.selectAll("text.chartlabel")
.attr("x", function(d) { return x((d.values[0].Overleving + d.values[1].Overleving)/2); })
.attr("y", function(d) { return y((d.values[0].Prevalentie + d.values[1].Prevalentie)/2); })
.style("font-size", function() {
if(width < 1024) {
return 16;
}
else {
return 22;
}
});
}
d3.select(window).on('resize', resize);
resize();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment