|
// Dimensions of sunburst. |
|
var width = 800; |
|
var height = 800; |
|
var radius = Math.min(width, height) / 2; |
|
|
|
// Breadcrumb dimensions: width, height, spacing, width of tip/tail. |
|
var b = { |
|
w: 65, h: 40, s: 6, t: 12 |
|
}; |
|
|
|
// Mapping of step names to colors. |
|
var colors = { |
|
"C60": "#e56400", // nano |
|
"C60_20hUV": "#CE6413",// nano |
|
"C60_7DUV": "#B76526",// nano |
|
"ZnO": "#D6ABF2",// nano |
|
"PS": "#AAF1B2",// nano |
|
"MS2RNA": "#DE4F87",// nano |
|
"BL21RNA": "#D8707E",// nano |
|
"TiO2": "#A6a33d",// nano |
|
"MWNTLO": "#CCE5FF",// nano |
|
"MWNTHO": "#66B2FF",// nano |
|
"MWNT": "#99CCFF",// nano |
|
|
|
"sphere": "#F0ABF2",// shape |
|
"ellipsoid": "#DEC9AB",// shape |
|
"tube": "74116D", // shape |
|
|
|
"none": "#D3D3D3", // NOM Layer and everything |
|
|
|
"HHA": "#A0522D", // NOM Layer Variations on red |
|
"EHA": "#A52A2A", // NOM Layer |
|
"SRHA": "#800000", // NOM Layer |
|
"HFA": "#B8860B", // NOM Layer |
|
"Alg": "#808000", // NOM Layer |
|
|
|
"NaCl": "#842DCE", // electrolyte |
|
"CaCl2": "#009999", // electrolyte |
|
|
|
"directSonic": "#008000", // Suspension protocol |
|
"stock": "#ffa500", // Suspension protocol |
|
"L.L.Ex.": "#5CACEE", // Suspension protocol |
|
|
|
"<1nm": "#B0C4DE", // Debye |
|
"1_10nm": "#B0E0E6", // Debye |
|
"10_20nm": "#ADD8E6", // Debye |
|
"20_30nm": "#87CEEB", // Debye |
|
"30_40nm": "#87CEEB", // Debye |
|
"40_50nm": "#87CEEB", // Debye |
|
">50": "#87CEEB", // Debye |
|
|
|
"0mgNOM/L": "#D3D3D3", // NOM concentration None color |
|
"1mgNOM/L": "#F5DEB3", // NOM concentration |
|
"5mgNOM/L": "#DEB887", // NOM concentration |
|
"10mgNOM/L": "#D2B48C",// NOM concentration |
|
|
|
"2.96mgNM/L": "#4A766E",//Particle Concentration |
|
"5mgNM/L": "#2FAA96",//Particle Concentration |
|
"5.8mgNM/L": "#8CD9D9",//Particle Concentration |
|
"6mgNM/L": "#36DBCA",//Particle Concentration |
|
"6.05mgNM/L": "#36DBCA",//Particle Concentration same color as below |
|
"6.07mgNM/L": "#36DBCA",//Particle Concentration same color |
|
"10mgNM/L": "#90FEFB",//Particle Concentration |
|
"50mgNM/L": "#DAF4F0", //Particle Concentration |
|
"100mgNM/L": "#DBFEF8", //Particle Concentration |
|
|
|
"5.2": "#FFCCE6",// pH |
|
"5.5": "#FF99CC",// pH |
|
"5.6": "#FF66B3",// pH |
|
"6.0": "#FF3399", // pH |
|
"6.7": "#FF0080",// pH |
|
"6.9": "#CC0066",// pH |
|
"7.0": "#CC0066",// pH |
|
"7.1": "#99004D",// pH |
|
"7.8": "#660033",// pH |
|
"8.0": "#471F33",// pH |
|
|
|
"15C": "#FFA500",// temp |
|
"20C": "#FF8C00",// temp |
|
"25C": "#FF7F50",// temp |
|
"35C": "#FF6347",// temp |
|
"45C": "#FF4500",// temp |
|
|
|
">=1": "#ff0000",// alpha |
|
"0.1_1": "#0000ff",// alpha |
|
"0.01_0.1": "#80ff00",// alpha |
|
"0.001_0.01": "#ffff00",// alpha |
|
"<0.001": "#FF6347"// alpha |
|
}; |
|
|
|
|
|
// Total size of all segments; we set this later, after loading the data. |
|
var totalSize = 0; |
|
|
|
var vis = d3.select("#chart").append("svg:svg") |
|
.attr("width", width + 50) |
|
.attr("height", height) |
|
.append("svg:g") |
|
.attr("id", "container") |
|
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); |
|
|
|
var partition = d3.layout.partition() |
|
.size([2 * Math.PI, radius * radius]) |
|
.value(function (d) { |
|
return d.size; |
|
}); |
|
|
|
var arc = d3.svg.arc() |
|
.startAngle(function (d) { |
|
return d.x; |
|
}) |
|
.endAngle(function (d) { |
|
return d.x + d.dx; |
|
}) |
|
.innerRadius(function (d) { |
|
return Math.sqrt(d.y); |
|
}) |
|
.outerRadius(function (d) { |
|
return Math.sqrt(d.y + d.dy); |
|
}); |
|
|
|
// Use d3.text and d3.csv.parseRows so that we do not need to have a header |
|
// row, and can receive the csv as an array of arrays. |
|
d3.text("enmExperimentalData.csv", function (text) { |
|
var csv = d3.csv.parseRows(text); |
|
var json = buildHierarchy(csv); |
|
createVisualization(json); |
|
}); |
|
|
|
// Main function to draw and set up the visualization, once we have the data. |
|
function createVisualization(json) { |
|
|
|
// Basic setup of page elements. |
|
initializeBreadcrumbTrail(); |
|
drawLegend(); |
|
d3.select("#togglelegend").on("click", toggleLegend); |
|
|
|
// Bounding circle underneath the sunburst, to make it easier to detect |
|
// when the mouse leaves the parent g. |
|
vis.append("svg:circle") |
|
.attr("r", radius) |
|
.style("opacity", 0); |
|
|
|
// For efficiency, filter nodes to keep only those large enough to see. |
|
var nodes = partition.nodes(json) |
|
.filter(function (d) { |
|
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees |
|
}); |
|
|
|
var path = vis.data([json]).selectAll("path") |
|
.data(nodes) |
|
.enter().append("svg:path") |
|
.attr("display", function (d) { |
|
return d.depth ? null : "none"; |
|
}) |
|
.attr("d", arc) |
|
.attr("fill-rule", "evenodd") |
|
.style("fill", function (d) { |
|
return colors[d.name]; |
|
}) |
|
.style("opacity", 1) |
|
.on("mouseover", mouseover); |
|
|
|
// Add the mouseleave handler to the bounding circle. |
|
d3.select("#container").on("mouseleave", mouseleave); |
|
|
|
// Get total size of the tree = value of root node from partition. |
|
totalSize = path.node().__data__.value; |
|
}; |
|
|
|
// Fade all but the current sequence, and show it in the breadcrumb trail. |
|
function mouseover(d) { |
|
|
|
var percentage = (100 * d.value / totalSize).toPrecision(3); |
|
var percentageString = percentage + "%"; |
|
if (percentage < 0.1) { |
|
percentageString = "< 0.1%"; |
|
} |
|
|
|
d3.select("#percentage") |
|
.text(percentageString); |
|
|
|
d3.select("#explanation") |
|
.style("visibility", ""); |
|
|
|
var sequenceArray = getAncestors(d); |
|
updateBreadcrumbs(sequenceArray, percentageString); |
|
|
|
// Fade all the segments. |
|
d3.selectAll("path") |
|
.style("opacity", 0.1); |
|
|
|
// Then highlight only those that are an ancestor of the current segment. |
|
vis.selectAll("path") |
|
.filter(function (node) { |
|
return (sequenceArray.indexOf(node) >= 0); |
|
}) |
|
.style("opacity", 1); |
|
} |
|
|
|
// Restore everything to full opacity when moving off the visualization. |
|
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 and then reactivate it. |
|
d3.selectAll("path") |
|
.transition() |
|
.duration(1000) |
|
.style("opacity", 1) |
|
.each("end", function () { |
|
d3.select(this).on("mouseover", mouseover); |
|
}); |
|
|
|
d3.select("#explanation") |
|
.style("visibility", "hidden"); |
|
} |
|
|
|
// 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; |
|
} |
|
|
|
function initializeBreadcrumbTrail() { |
|
// Add the svg area. |
|
var trail = d3.select("#sequence").append("svg:svg") |
|
.attr("width", width + 200) // ADD TO WIDTH TO FIT BETTER LOOKING BREADCRUMBTRAIL |
|
.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"); |
|
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 breadcrumb trail to show the current 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 colors[d.name]; |
|
}); |
|
|
|
entering.append("svg:text") |
|
.attr("x", (b.w + b.t) / 2) |
|
.attr("y", b.h / 2) |
|
.attr("dy", "0.35em") |
|
.attr("dx", ".15em") |
|
.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", ""); |
|
|
|
} |
|
|
|
function drawLegend() { |
|
|
|
// Dimensions of legend item: width, height, spacing, radius of rounded rect. |
|
var li = { |
|
w: 100, h: 25, s: 3, r: 3 |
|
}; |
|
|
|
var legend = d3.select("#legend").append("svg:svg") |
|
.attr("width", li.w) |
|
.attr("height", d3.keys(colors).length * (li.h + li.s)); |
|
|
|
var g = legend.selectAll("g") |
|
.data(d3.entries(colors)) |
|
.enter().append("svg:g") |
|
.attr("transform", function (d, i) { |
|
return "translate(0," + i * (li.h + li.s) + ")"; |
|
}); |
|
|
|
g.append("svg:rect") |
|
.attr("rx", li.r) |
|
.attr("ry", li.r) |
|
.attr("width", li.w) |
|
.attr("height", li.h) |
|
.style("fill", function (d) { |
|
return d.value; |
|
}); |
|
|
|
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.key; |
|
}); |
|
} |
|
|
|
function toggleLegend() { |
|
var legend = d3.select("#legend"); |
|
if (legend.style("visibility") == "hidden") { |
|
legend.style("visibility", ""); |
|
} else { |
|
legend.style("visibility", "hidden"); |
|
} |
|
} |
|
|
|
// Take a 2-column CSV and transform it into a hierarchical structure suitable |
|
// for a partition layout. The first column is a sequence of step names, from |
|
// root to leaf, separated by hyphens. The second column is a count of how |
|
// often that sequence occurred. |
|
function buildHierarchy(csv) { |
|
var root = {"name": "root", "children": []}; |
|
for (var i = 0; i < csv.length; i++) { |
|
var sequence = csv[i][0]; |
|
var size = +csv[i][1]; |
|
if (isNaN(size)) { // e.g. if this is a header row |
|
continue; |
|
} |
|
var parts = sequence.split("-"); |
|
var currentNode = root; |
|
for (var j = 0; j < parts.length; j++) { |
|
var children = currentNode["children"]; |
|
var nodeName = parts[j]; |
|
var childNode; |
|
if (j + 1 < parts.length) { |
|
// Not yet at the end of the sequence; move down the tree. |
|
var foundChild = false; |
|
for (var k = 0; k < children.length; k++) { |
|
if (children[k]["name"] == nodeName) { |
|
childNode = children[k]; |
|
foundChild = true; |
|
break; |
|
} |
|
} |
|
// If we don't already have a child node for this branch, create it. |
|
if (!foundChild) { |
|
childNode = {"name": nodeName, "children": []}; |
|
children.push(childNode); |
|
} |
|
currentNode = childNode; |
|
} else { |
|
// Reached the end of the sequence; create a leaf node. |
|
childNode = {"name": nodeName, "size": size}; |
|
children.push(childNode); |
|
} |
|
} |
|
} |
|
return root; |
|
}; |