Sunburst visualization of modal importance aerosol activation experiment results.
Last active
August 29, 2015 14:23
-
-
Save darothen/269ed42d1a1f7cec66be to your computer and use it in GitHub Desktop.
mode_sunburst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/). | |
var colorbrewer = {Dark2: { | |
3: ["#1b9e77","#d95f02","#7570b3"], | |
4: ["#1b9e77","#d95f02","#7570b3","#e7298a"], | |
5: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e"], | |
6: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02"], | |
7: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d"], | |
8: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"] | |
}, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<script src="colorbrewer.js"></script> | |
<style> | |
body { | |
font-family: 'Open Sans', sans-serif; | |
font-size: 12px; | |
font-weight: 400; | |
background-color: #fff; | |
margin-top: 10px; | |
} | |
#main { | |
width: 700px; | |
} | |
#sidebar { | |
float: right; | |
width: 100px; | |
} | |
#sequence { | |
margin-left: 50px; | |
width: 650px; | |
height: 70px; | |
} | |
#legend { | |
margin-top: 50px; | |
padding: 10px 0 0 3px; | |
visibility: visible; | |
} | |
#sequence text, #legend text { | |
font-weight: 600; | |
fill: #fff; | |
} | |
#chart { | |
width: 600px; | |
float: left; | |
} | |
#chart path { | |
stroke: #fff; | |
} | |
#percentage { | |
font-size: 2.5em; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="main"> | |
<div id="sequence"></div> | |
<div id="chart"></div> | |
<div id="sidebar"> | |
<div id="legend"></div> | |
</div> | |
</div> | |
</body> | |
<script src="mode_sunburst.js"></script> | |
</html> | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"name": "mode_data", "children": [{"name": "ACC", "children": [{"name": "AIT", "children": [{"name": "MBS", "size": 1}]}, {"name": "DST01", "children": [{"name": "MBS", "size": 19}, {"name": "MOS", "size": 6}, {"name": "SSLT01", "size": 5}]}, {"name": "MBS", "children": [{"name": "AIT", "size": 1}, {"name": "DST01", "size": 16}, {"name": "MOS", "size": 2318}, {"name": "SSLT01", "size": 561}]}, {"name": "MOS", "children": [{"name": "DST01", "size": 6}, {"name": "MBS", "size": 5364}, {"name": "SSLT01", "size": 3353}]}, {"name": "SSLT01", "children": [{"name": "DST01", "size": 1}, {"name": "MBS", "size": 1574}, {"name": "MOS", "size": 7344}]}]}, {"name": "DST01", "children": [{"name": "ACC", "children": [{"name": "DST02", "size": 5}, {"name": "MBS", "size": 2}, {"name": "MOS", "size": 2}]}, {"name": "DST02", "children": [{"name": "ACC", "size": 2}]}, {"name": "MBS", "children": [{"name": "ACC", "size": 4}, {"name": "MOS", "size": 1}]}, {"name": "MOS", "children": [{"name": "ACC", "size": 2}, {"name": "MBS", "size": 1}]}]}, {"name": "MBS", "children": [{"name": "ACC", "children": [{"name": "DST01", "size": 11}, {"name": "MOS", "size": 456}]}, {"name": "DST01", "children": [{"name": "ACC", "size": 5}]}, {"name": "MOS", "children": [{"name": "ACC", "size": 96}, {"name": "DST01", "size": 1}]}]}, {"name": "MOS", "children": [{"name": "ACC", "children": [{"name": "DST01", "size": 2}, {"name": "MBS", "size": 370}, {"name": "SSLT01", "size": 50}]}, {"name": "DST01", "children": [{"name": "ACC", "size": 1}]}, {"name": "MBS", "children": [{"name": "ACC", "size": 95}]}]}]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Based on: http://jsfiddle.net/QDP9H/ | |
// | |
var json_file = "mode.json"; | |
var modes_array = [ | |
"ACC", "AIT", "MOS", "MBS", "DST01", "DST02", "SSLT01" | |
]; | |
// Breadcrumb dimensions: width, height, spacing, width of tip/tail. | |
var b = { | |
w: 75, h: 30, s: 3, t: 10 | |
}; | |
var width = 600, | |
height = 600, | |
offset = 50, | |
radius = (Math.min(width, height) / 2) - offset, | |
color = d3.scale.ordinal() | |
.domain(modes_array) | |
.range(colorbrewer.Dark2[modes_array.length]), | |
totalCount = 0.0, | |
notchRads = Math.PI/8; | |
iterLevels = 0; | |
var svg = d3.select("#chart").append("svg:svg") | |
.attr("width", width) | |
.attr("height", height) | |
.append("svg:g") | |
.attr("id", "container") | |
.attr("transform", "translate(" + width / 2 + "," + height * .52 + ")"); | |
var partition = d3.layout.partition() | |
.sort(null) | |
.size([2 * Math.PI - notchRads, 100]) | |
.value(function(d) { | |
totalCount += d.size; // update total number of samples | |
if ( iterLevels < d.depth ) { | |
iterLevels += 1; | |
} | |
return d.size; | |
}); | |
var arc = d3.svg.arc() | |
.startAngle(function(d) { return d.x + notchRads/2; }) | |
.endAngle(function(d) { return d.x + d.dx + notchRads/2; }) | |
.innerRadius(function(d) { return radius * (d.y) / 100; }) | |
.outerRadius(function(d) { return radius * (d.y + d.dy) / 100; }); | |
var legend = null; | |
// This main method creates the visualization as a callback | |
// to the JSON-reading method | |
d3.json(json_file, function(error, root) { | |
if (error) throw error; | |
console.log(root); | |
// Setup breadcumb trail elements | |
initializeBreadcrumbTrail(); | |
var path = svg.datum(root).selectAll("path") | |
.data(partition.nodes) | |
.enter().append("path") | |
.attr("class", "arc") | |
.attr("name", function(d) { return d.name }) | |
.attr("display", function(d) { return d.depth ? null : "none"; }) // hide inner ring | |
.attr("d", arc) | |
.style("fill", function(d) { | |
return color(d.name); | |
}) | |
.on("mouseover", function(d) { | |
console.log(d.name, d.depth, d.x, d.dx); | |
mouseover(d); | |
}); | |
// Add the mouseleave handler to the bounding circle | |
d3.select("#container").on("mouseleave", mouseleave); | |
// Add text inside the 'notch' denoting the iterations); | |
var g = svg.selectAll("g") | |
.data(range(1, iterLevels)) | |
.enter().append("svg:g"); | |
g.append("svg:text") | |
.attr("y", function(d) { | |
var vert = height/2; | |
var levelWidth = radius/(iterLevels + 1.0); | |
return -vert + levelWidth * (d - 0.5) + offset; | |
}) | |
.attr("dy", "1em") | |
.attr("text-anchor", "middle") | |
.text(function(d) { return d }); | |
svg.insert("svg:g", ":first-child") | |
.attr("transform", function(d) { | |
var levelWidth = radius/(iterLevels + 1.0); | |
var titleLoc = -height/2 - levelWidth/10. + offset | |
return "translate(0," + titleLoc + ")"; | |
}) | |
.append("svg:text") | |
.attr("class", "title") | |
.attr("dy", "0.35em") | |
.attr("text-anchor", "middle") | |
.style({ | |
"fill": "#000", | |
"font-weight": "bold" | |
}) | |
.text("Iteration"); | |
drawLegend(); | |
}); | |
// Fade all but the current sequence, and show it in the | |
// breadcrumb trail | |
function mouseover(d) { | |
var percentage = (100 * d.value / totalCount).toPrecision(3); | |
var percentageString = percentage + "%"; | |
if ( percentage < 0.1 ) { | |
percentageString = "< 0.1%"; | |
} | |
d3.select("#percentage") | |
.text(percentageString); | |
var sequenceArray = getAncestors(d); | |
updateBreadcrumbs(sequenceArray, percentageString); | |
// Fade all the segments | |
d3.selectAll("path") // arcs in sunburst | |
.style("opacity", 0.3); | |
d3.selectAll(".legend_rec") // labels in legend | |
.style("opacity", 0.3); | |
// Highlight only those that are an ancestor of | |
// the current segment | |
svg.selectAll("path") | |
.filter(function(node) { | |
return (sequenceArray.indexOf(node) >= 0); | |
}) | |
.style("opacity", 1); | |
} | |
// Restore everything to full opacity when moving off the vis | |
function mouseleave(d) { | |
// Hide the breadcrumb trail | |
d3.select("#trail") | |
.style("visibility", "hidden"); | |
// Deactivate all segments during transition | |
d3.selectAll("path").on("mouseover", null); | |
// Transition each segment to full opacity then reactivate it | |
d3.selectAll("path") | |
.transition() | |
.duration(1000) | |
.style("opacity", 1) | |
.each("end", function() { | |
d3.select(this).on("mouseover", mouseover); | |
}); | |
d3.selectAll(".legend_rec") // labels in legend | |
.transition() | |
.duration(1000) | |
.style("opacity", 1); | |
} | |
// Function for drawing the legend | |
function drawLegend() { | |
// Dimensions of legend item: width, height, spacing, radius of rounded rect. | |
var li = { | |
w: 75, h: 30, s: 3, r: 3 | |
}; | |
legend = d3.select("#legend").append("svg:svg") | |
.attr("width", li.w) | |
.attr("height", color.domain().length * (li.h + li.s)); | |
var g = legend.selectAll("g") | |
.data(modes_array) | |
.enter().append("svg:g") | |
.attr("transform", function(d, i) { | |
return "translate(0," + (i+1) * (li.h + li.s) + ")"; | |
}); | |
g.append("svg:rect") | |
.attr("class", "legend_rec") | |
.attr("mode", function(d) { return d; }) | |
.attr("rx", li.r) | |
.attr("ry", li.r) | |
.attr("width", li.w) | |
.attr("height", li.h) | |
.style("fill", function(d) { return color(d); }); | |
g.append("svg:text") | |
.attr("x", li.w / 2) | |
.attr("y", li.h / 2) | |
.attr("dy", "0.35em") | |
.attr("text-anchor", "middle") | |
.text(function(d) { return d }); | |
legend.insert("svg:g", ":first-child") | |
.attr("transform", function(d) { | |
return "translate(0,0)"; | |
}) | |
.append("svg:text") | |
.attr("class", "title") | |
.attr("x", li.w / 2) | |
.attr("y", li.h / 2) | |
.attr("dy", "0.35em") | |
.attr("text-anchor", "middle") | |
.style({ | |
"fill": "#000", | |
"font-weight": "bold" | |
}) | |
.text("Mode Name"); | |
} | |
// Breadcrumb / sequence analysis | |
function initializeBreadcrumbTrail() { | |
// Add the svg area for the graphic | |
var trail = d3.select("#sequence").append("svg:svg") | |
.attr("width", width) | |
.attr("height", 50) | |
.attr("id", "trail"); | |
// Add the label at the end, for the percentage | |
trail.append("svg:text") | |
.attr("id", "endlabel") | |
.style("fill", "#000"); | |
} | |
// Generate a string that describes the points of a breadcrumb | |
// polygon | |
function breadcrumbPoints(d, i) { | |
var points = []; | |
points.push("0,0"); // these vertices are for notched rectangles | |
points.push(b.w + ",0"); | |
points.push(b.w + b.t + "," + (b.h / 2)); | |
points.push(b.w + "," + b.h); | |
points.push("0," + b.h); | |
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex. | |
points.push(b.t + "," + (b.h / 2)); | |
} | |
return points.join(" "); | |
} | |
// Update the breadcumb trail sequence and percentage | |
function updateBreadcrumbs(nodeArray, percentageString) { | |
// Data join; key function combines name and depth | |
// (= position in sequence) | |
var g = d3.select("#trail").selectAll("g") | |
.data(nodeArray, function(d) { return d.name + d.depth; }); | |
// Add breadcrumb and label for entering nodes | |
var entering = g.enter().append("svg:g"); | |
entering.append("svg:polygon") | |
.attr("points", breadcrumbPoints) | |
.style("fill", function(d) { return color(d.name); }); | |
entering.append("svg:text") | |
.attr("x", (b.w + b.t) / 2) | |
.attr("y", b.h/2) | |
.attr("dy", "0.35em") | |
.attr("text-anchor", "middle") | |
.text(function(d) { return d.name; }); | |
// Set position for entering and updating nodes | |
g.attr("transform", function(d, i) { | |
return "translate(" + i*(b.w + b.s) + ",0)"; | |
}); | |
// Remove exiting nodes. | |
g.exit().remove() | |
// Now move and update the percentage at the end. | |
d3.select("#trail").select("#endlabel") | |
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s)) | |
.attr("y", b.h / 2) | |
.attr("dy", "0.35em") | |
.attr("text-anchor", "middle") | |
.text(percentageString); | |
// Make the breadcrumb trail visible if it's hidden | |
d3.select("#trail") | |
.style("visibility", ""); | |
} | |
// Given a node in a partition layout, return an array of all of | |
// its ancestor nodes, highest first, but excluding the root. | |
function getAncestors(node) { | |
var path = []; | |
var current = node; | |
while ( current.parent ) { | |
path.unshift(current); | |
current = current.parent; | |
} | |
return path; | |
} | |
// Range function | |
function range(start, end) { | |
var rangeArray = []; | |
for (var i=start; i <= end; i++) { | |
rangeArray.push(i); | |
} | |
return rangeArray; | |
} | |
d3.select(self.frameElement).style("height", (height+100) + "px"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Looking good! I've made some changes on my machine, but I can't make a pull request. Recommendation(s) on how to merge?