|  | function sunburstDraw(scope, element) { | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Angular variables | 
        
          |  | * | 
        
          |  | */ | 
        
          |  | // watch for changes on scope.data | 
        
          |  | scope.$watch("data", function() { | 
        
          |  | var data = scope.data; | 
        
          |  | render(data); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Dimensions of svg, sunburst, legend, breadcrumbs | 
        
          |  | * | 
        
          |  | */ | 
        
          |  | // svg dimensions | 
        
          |  | var width = 500; | 
        
          |  | var height = 300; | 
        
          |  | var radius = Math.min(width, height) / 2; | 
        
          |  |  | 
        
          |  | // Breadcrumb dimensions: width, height, spacing, width of tip/tail. | 
        
          |  | var b = { | 
        
          |  | w: 60, | 
        
          |  | h: 30, | 
        
          |  | s: 3, | 
        
          |  | t: 10 | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | // Legend dimensions: width, height, spacing, radius of rounded rect. | 
        
          |  | var li = { | 
        
          |  | w: 75, | 
        
          |  | h: 30, | 
        
          |  | s: 3, | 
        
          |  | r: 3 | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | // margins | 
        
          |  | var margin = { | 
        
          |  | top: radius, | 
        
          |  | bottom: 50, | 
        
          |  | left: radius, | 
        
          |  | right: 0 | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | // sunburst margins | 
        
          |  | var sunburstMargin = { | 
        
          |  | top: 2 * radius + b.h, | 
        
          |  | bottom: 0, | 
        
          |  | left: 0, | 
        
          |  | right: radius / 2 | 
        
          |  | }; | 
        
          |  |  | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Drawing variables: | 
        
          |  | * | 
        
          |  | * e.g. colors, totalSize, partitions, arcs | 
        
          |  | */ | 
        
          |  | // Mapping of nodes to colorscale. | 
        
          |  | var colors = d3.scale.category10(); | 
        
          |  |  | 
        
          |  | // Total size of all nodes, to be used later when data is loaded | 
        
          |  | var totalSize = 0; | 
        
          |  |  | 
        
          |  | // create d3.layout.partition | 
        
          |  | var partition = d3.layout.partition() | 
        
          |  | .size([2 * Math.PI, radius * radius]) | 
        
          |  | .value(function(d) { | 
        
          |  | return d.size; | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | // create arcs for drawing D3 paths | 
        
          |  | 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); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Define and initialize D3 select references and div-containers | 
        
          |  | * | 
        
          |  | * e.g. vis, breadcrumbs, lastCrumb, summary, sunburst, legend | 
        
          |  | */ | 
        
          |  | // create main vis selection | 
        
          |  | var vis = d3.select(element[0]) | 
        
          |  | .append("div").classed("vis-continer", true) | 
        
          |  | .style("position", "relative") | 
        
          |  | .style("margin-top", "20px") | 
        
          |  | .style("margin-bottom", "20px") | 
        
          |  | .style("left", "50px") | 
        
          |  | .style("height", height + 2 * b.h + "px"); | 
        
          |  |  | 
        
          |  | // create and position SVG | 
        
          |  | var sunburst = vis | 
        
          |  | .append("div").classed("sunburst-container", true) | 
        
          |  | .style("position", "absolute") | 
        
          |  | .style("left", sunburstMargin.left + "px") | 
        
          |  | .append("svg") | 
        
          |  | .attr("width", width) | 
        
          |  | .attr("height", height) | 
        
          |  | .append("g") | 
        
          |  | .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | 
        
          |  |  | 
        
          |  | // create and position legend | 
        
          |  | var legend = vis | 
        
          |  | .append("div").classed("legend-container", true) | 
        
          |  | .style("position", "absolute") | 
        
          |  | .style("top", b.h + "px") | 
        
          |  | .style("left", 2 * radius + sunburstMargin.right + "px") | 
        
          |  | .style("width", 50 + "px") | 
        
          |  | .style("height", 50 + "px") | 
        
          |  | .append("svg") | 
        
          |  | .attr("width", li.w) | 
        
          |  | .attr("height", height); | 
        
          |  |  | 
        
          |  | // create and position breadcrumbs container and svg | 
        
          |  | var breadcrumbs = vis | 
        
          |  | .append("div").classed("breadcrumbs-container", true) | 
        
          |  | .style("position", "absolute") | 
        
          |  | .style("top", sunburstMargin.top + "px") | 
        
          |  | .append("svg") | 
        
          |  | .attr("width", width) | 
        
          |  | .attr("height", b.h) | 
        
          |  | .attr("fill", "white") | 
        
          |  | .attr("font-weight", 600); | 
        
          |  |  | 
        
          |  | // create last breadcrumb element | 
        
          |  | var lastCrumb = breadcrumbs | 
        
          |  | .append("text").classed("lastCrumb", true); | 
        
          |  |  | 
        
          |  | // create and position summary container | 
        
          |  | var summary = vis | 
        
          |  | .append("div").classed("summary-container", true) | 
        
          |  | .style("position", "absolute") | 
        
          |  | .style("top", radius * 0.80 + "px") | 
        
          |  | .style("left", sunburstMargin.left + radius / 2 + "px") | 
        
          |  | .style("width", radius + "px") | 
        
          |  | .style("height", radius + "px") | 
        
          |  | .style("text-align", "center") | 
        
          |  | .style("font-size", "11px") | 
        
          |  | .style("color", "#666") | 
        
          |  | .style("z-index", "-1"); | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Render process: | 
        
          |  | * | 
        
          |  | * 1) Load data | 
        
          |  | * 2) Build Tree | 
        
          |  | * 3) Draw visualization | 
        
          |  | */ | 
        
          |  | // render visualization | 
        
          |  | function render(data) { | 
        
          |  | var parsedData = d3.csv.parseRows(data); // load data | 
        
          |  | var json = buildHierarchy(parsedData); // build json tree | 
        
          |  | removeVisualization(); // remove existing visualization if any | 
        
          |  | createVisualization(json); // visualize json tree | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Helper functions: | 
        
          |  | * | 
        
          |  | * @function removeVisualization(): removes existing SVG components | 
        
          |  | * @function createVisualization(json): create visualization from json tree structure | 
        
          |  | * @function colorMap(d): color nodes with colors mapping | 
        
          |  | * @function mouseover(d): mouseover function | 
        
          |  | * @function mouseleave(d): mouseleave function | 
        
          |  | * @function getAncestors(node): get ancestors of a specified node | 
        
          |  | * @function buildHierarchy(data): generate json nested structure from csv data input | 
        
          |  | */ | 
        
          |  | // removes existing SVG components | 
        
          |  | function removeVisualization() { | 
        
          |  | sunburst.selectAll(".nodePath").remove(); | 
        
          |  | legend.selectAll("g").remove(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // visualize json tree structure | 
        
          |  | function createVisualization(json) { | 
        
          |  | drawSunburst(json); // draw sunburst | 
        
          |  | drawLegend(); // draw legend | 
        
          |  | }; | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // helper function colorMap - color gray if "end" is detected | 
        
          |  | function colorMap(d) { | 
        
          |  | return colors(d.name); | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // helper function to draw the sunburst and breadcrumbs | 
        
          |  | function drawSunburst(json) { | 
        
          |  | // Build only nodes of a threshold "visible" sizes to improve efficiency | 
        
          |  | var nodes = partition.nodes(json) | 
        
          |  | .filter(function(d) { | 
        
          |  | return (d.dx > 0.005); // 0.005 radians = 0.29 degrees | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | // this section is required to update the colors.domain() every time the data updates | 
        
          |  | var uniqueNames = (function(a) { | 
        
          |  | var output = []; | 
        
          |  | a.forEach(function(d) { | 
        
          |  | if (output.indexOf(d.name) === -1) output.push(d.name); | 
        
          |  | }); | 
        
          |  | return output; | 
        
          |  | })(nodes); | 
        
          |  | colors.domain(uniqueNames); // update domain colors | 
        
          |  |  | 
        
          |  | // create path based on nodes | 
        
          |  | var path = sunburst.data([json]).selectAll("path") | 
        
          |  | .data(nodes).enter() | 
        
          |  | .append("path").classed("nodePath", true) | 
        
          |  | .attr("display", function(d) { | 
        
          |  | return d.depth ? null : "none"; | 
        
          |  | }) | 
        
          |  | .attr("d", arc) | 
        
          |  | .attr("fill", colorMap) | 
        
          |  | .attr("opacity", 1) | 
        
          |  | .attr("stroke", "white") | 
        
          |  | .on("mouseover", mouseover); | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // // trigger mouse click over sunburst to reset visualization summary | 
        
          |  | vis.on("click", click); | 
        
          |  |  | 
        
          |  | // Update totalSize of the tree = value of root node from partition. | 
        
          |  | totalSize = path.node().__data__.value; | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // helper function to draw legend | 
        
          |  | function drawLegend() { | 
        
          |  | // remove "root" label from legend | 
        
          |  | var labels = colors.domain().splice(1, colors.domain().length); | 
        
          |  |  | 
        
          |  | // create legend "pills" | 
        
          |  | var g = legend.selectAll("g") | 
        
          |  | .data(labels).enter() | 
        
          |  | .append("g") | 
        
          |  | .attr("transform", function(d, i) { | 
        
          |  | return "translate(0," + i * (li.h + li.s) + ")"; | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | g.append("rect").classed("legend-pills", true) | 
        
          |  | .attr("rx", li.r) | 
        
          |  | .attr("ry", li.r) | 
        
          |  | .attr("width", li.w) | 
        
          |  | .attr("height", li.h) | 
        
          |  | .style("fill", function(d) { | 
        
          |  | return colors(d); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | g.append("text").classed("legend-text", true) | 
        
          |  | .attr("x", li.w / 2) | 
        
          |  | .attr("y", li.h / 2) | 
        
          |  | .attr("dy", "0.35em") | 
        
          |  | .attr("text-anchor", "middle") | 
        
          |  | .attr("fill", "white") | 
        
          |  | .attr("font-size", "10px") | 
        
          |  | .attr("font-weight", 600) | 
        
          |  | .text(function(d) { | 
        
          |  | return d; | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // helper function mouseover to handle mouseover events/animations and calculation of ancestor nodes etc | 
        
          |  | function mouseover(d) { | 
        
          |  | // build percentage string | 
        
          |  | var percentage = (100 * d.value / totalSize).toPrecision(3); | 
        
          |  | var percentageString = percentage + "%"; | 
        
          |  | if (percentage < 1) { | 
        
          |  | percentageString = "< 1.0%"; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | // update breadcrumbs (get all ancestors) | 
        
          |  | var ancestors = getAncestors(d); | 
        
          |  | updateBreadcrumbs(ancestors, percentageString); | 
        
          |  |  | 
        
          |  | // update sunburst (Fade all the segments and highlight only ancestors of current segment) | 
        
          |  | sunburst.selectAll("path") | 
        
          |  | .attr("opacity", 0.3); | 
        
          |  | sunburst.selectAll("path") | 
        
          |  | .filter(function(node) { | 
        
          |  | return (ancestors.indexOf(node) >= 0); | 
        
          |  | }) | 
        
          |  | .attr("opacity", 1); | 
        
          |  |  | 
        
          |  | // update summary | 
        
          |  | summary.html( | 
        
          |  | "Stage: " + d.depth + "<br />" + | 
        
          |  | "<span class='percentage'>" + percentageString + "</span><br />" + | 
        
          |  | d.value + " of " + totalSize + "<br />" | 
        
          |  | ); | 
        
          |  |  | 
        
          |  | // display summary and breadcrumbs if hidden | 
        
          |  | summary.style("visibility", ""); | 
        
          |  | breadcrumbs.style("visibility", ""); | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // helper function click to handle mouseleave events/animations | 
        
          |  | function click(d) { | 
        
          |  | // Deactivate all segments then retransition each segment to full opacity. | 
        
          |  | sunburst.selectAll("path").on("mouseover", null); | 
        
          |  | sunburst.selectAll("path") | 
        
          |  | .transition() | 
        
          |  | .duration(1000) | 
        
          |  | .attr("opacity", 1) | 
        
          |  | .each("end", function() { | 
        
          |  | d3.select(this).on("mouseover", mouseover); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | // hide summary and breadcrumbs if visible | 
        
          |  | breadcrumbs.style("visibility", "hidden"); | 
        
          |  | summary.style("visibility", "hidden"); | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // Return array of ancestors of 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; | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // Generate a string representation for drawing 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 breadcrumbs to show the current sequence and percentage. | 
        
          |  | function updateBreadcrumbs(ancestors, percentageString) { | 
        
          |  | // Data join, where primary key = name + depth. | 
        
          |  | var g = breadcrumbs.selectAll("g") | 
        
          |  | .data(ancestors, function(d) { | 
        
          |  | return d.name + d.depth; | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | // Add breadcrumb and label for entering nodes. | 
        
          |  | var breadcrumb = g.enter().append("g"); | 
        
          |  |  | 
        
          |  | breadcrumb | 
        
          |  | .append("polygon").classed("breadcrumbs-shape", true) | 
        
          |  | .attr("points", breadcrumbPoints) | 
        
          |  | .attr("fill", colorMap); | 
        
          |  |  | 
        
          |  | breadcrumb | 
        
          |  | .append("text").classed("breadcrumbs-text", true) | 
        
          |  | .attr("x", (b.w + b.t) / 2) | 
        
          |  | .attr("y", b.h / 2) | 
        
          |  | .attr("dy", "0.35em") | 
        
          |  | .attr("font-size", "10px") | 
        
          |  | .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(); | 
        
          |  |  | 
        
          |  | // Update percentage at the lastCrumb. | 
        
          |  | lastCrumb | 
        
          |  | .attr("x", (ancestors.length + 0.5) * (b.w + b.s)) | 
        
          |  | .attr("y", b.h / 2) | 
        
          |  | .attr("dy", "0.35em") | 
        
          |  | .attr("text-anchor", "middle") | 
        
          |  | .attr("fill", "black") | 
        
          |  | .attr("font-weight", 600) | 
        
          |  | .text(percentageString); | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // Take a 4-column CSV of ["sequence", "stage", "node", "value"] and | 
        
          |  | // transform it into a hierarchical structure suitable for a partition layout. | 
        
          |  | function buildHierarchy(csv) { | 
        
          |  | var data = csv2json(csv); // build JSON dataframe from csv using helper function | 
        
          |  |  | 
        
          |  | // build tree | 
        
          |  | var root = { | 
        
          |  | name: "root", | 
        
          |  | children: [] | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | data.forEach(function(d) { | 
        
          |  | var nodes = d.nodes; | 
        
          |  | var size = parseInt(d.size); | 
        
          |  |  | 
        
          |  | // build graph, nodes, and child nodes | 
        
          |  | var currentNode = root; | 
        
          |  | for (var j = 0; j < nodes.length; j++) { | 
        
          |  | var children = currentNode.children; | 
        
          |  | var nodeName = nodes[j]; | 
        
          |  | var childNode; | 
        
          |  |  | 
        
          |  | if (j + 1 < nodes.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 (!foundChild) { // If we don't already have a child node for this branch, create it. | 
        
          |  | 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; | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  | // helper function to buildHierarchy to transform 4-column CSV into a JSON dataframe. | 
        
          |  | function csv2json(csv) { | 
        
          |  | var data = []; | 
        
          |  | var sequences = []; | 
        
          |  |  | 
        
          |  | // sort the dataframe ascending by sequence (d[0]) then by stage (d[1]) | 
        
          |  | csv.sort(function(a, b) { | 
        
          |  | if (a[2] === b[2]) { | 
        
          |  | return d3.ascending(a[0], b[0]); | 
        
          |  | } | 
        
          |  | return d3.ascending(a[1], b[1]); | 
        
          |  | }); | 
        
          |  | csv.forEach(function(record) { | 
        
          |  | var sequence = record[0]; | 
        
          |  | if (sequences.indexOf(sequence) < 0) sequences.push(sequence); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | sequences.forEach(function(sequence) { | 
        
          |  | var d = { | 
        
          |  | nodes: [], | 
        
          |  | size: 0 | 
        
          |  | }; | 
        
          |  | csv.forEach(function(record) { | 
        
          |  | var node = record[2]; | 
        
          |  | var size = record[3]; | 
        
          |  | if (sequence === record[0]) { | 
        
          |  | d.nodes.push(node); | 
        
          |  | d.size = size; | 
        
          |  | } | 
        
          |  | }); | 
        
          |  | data.push(d); | 
        
          |  | }); | 
        
          |  | return data; | 
        
          |  | } | 
        
          |  | } | 
  
Hi Chris, is there anyway to use JSON directly instead of CSV? I read that d3 parses the CSV into a JSON graph anyway. If so, do you have a sample of how the JSON structure looks like? Thanks! :)