OLD | NEW |
(Empty) | |
| 1 function sequences(root) { |
| 2 |
| 3 // Dimensions of sunburst. |
| 4 var width = 1600; |
| 5 var height = 800; |
| 6 var radius = Math.min(width, height) / 2; |
| 7 |
| 8 var x = d3.scale.linear() |
| 9 .range([0, 2 * Math.PI]); |
| 10 |
| 11 var y = d3.scale.sqrt() |
| 12 .range([0, radius]); |
| 13 |
| 14 // Breadcrumb dimensions: width, height, spacing, width of tip/tail. |
| 15 var b = { |
| 16 w: 75, h: 30, s: 3, t: 10 |
| 17 }; |
| 18 |
| 19 var path ={}; |
| 20 var nodes = {}; |
| 21 |
| 22 // Mapping of step names to colors. |
| 23 var colors = { |
| 24 "home": "#5687d1", |
| 25 "product": "#7b615c", |
| 26 "search": "#de783b", |
| 27 "account": "#6ab975", |
| 28 "other": "#a173d1", |
| 29 "end": "#bbbbbb" |
| 30 }; |
| 31 |
| 32 // Total size of all segments; we set this later, after loading the data. |
| 33 var totalSize = 0; |
| 34 |
| 35 var svg = d3.select("#chart").append("svg:svg") |
| 36 .attr("width", width) |
| 37 .attr("height", height) |
| 38 .append("svg:g") |
| 39 .attr("id", "container") |
| 40 .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); |
| 41 |
| 42 var partition = d3.layout.partition() |
| 43 .size([2 * Math.PI, radius * radius]) |
| 44 .value(function(d) { return d.size; }); |
| 45 |
| 46 var arc = d3.svg.arc() |
| 47 .startAngle(function(d) { return d.x; }) |
| 48 .endAngle(function(d) { return d.x + d.dx; }) |
| 49 .innerRadius(function(d) { return Math.sqrt(d.y); }) |
| 50 .outerRadius(function(d) { return Math.sqrt(d.y + d.dy); }); |
| 51 |
| 52 initializePage(); |
| 53 createVisualization(root); |
| 54 |
| 55 function initializePage() { |
| 56 |
| 57 // Basic setup of page elements. |
| 58 initializeBreadcrumbTrail(); |
| 59 drawLegend(); |
| 60 d3.select("#togglelegend").on("click", toggleLegend); |
| 61 |
| 62 // Bounding circle underneath the sunburst, to make it easier to detect |
| 63 // when the mouse leaves the parent g. |
| 64 svg.append("svg:circle") |
| 65 .attr("r", radius) |
| 66 .style("opacity", 0); |
| 67 } |
| 68 |
| 69 // Main function to draw and set up the visualization, once we have the data. |
| 70 function createVisualization(json) { |
| 71 |
| 72 // For efficiency, filter nodes to keep only those large enough to see. |
| 73 nodes = partition.nodes(root) |
| 74 .filter(function(d) { |
| 75 return (d.dx > 0.005); // 0.005 radians = 0.29 degrees |
| 76 }); |
| 77 |
| 78 //var path = svg.data([json]).selectAll("path") |
| 79 path = svg.selectAll("path") |
| 80 .data(nodes) |
| 81 .enter().append("svg:path") |
| 82 .attr("display", function(d) { return d.depth ? null : "none"; }) |
| 83 .attr("d", arc) |
| 84 .attr("fill-rule", "evenodd") |
| 85 .style("fill", function(d) { return colors[d.name]; }) |
| 86 .style("opacity", 1) |
| 87 .on("mouseover", mouseover) |
| 88 .on("click", click); |
| 89 |
| 90 // Add the mouseleave handler to the bounding circle. |
| 91 d3.select("#container").on("mouseleave", mouseleave); |
| 92 |
| 93 // Get total size of the tree = value of root node from partition. |
| 94 totalSize = path.node().__data__.value; |
| 95 }; |
| 96 |
| 97 function click(d) { |
| 98 |
| 99 console.log(d); |
| 100 } |
| 101 |
| 102 // Fade all but the current sequence, and show it in the breadcrumb trail. |
| 103 function mouseover(d) { |
| 104 |
| 105 var percentage = (100 * d.value / totalSize).toPrecision(3); |
| 106 var percentageString = percentage + "%"; |
| 107 if (percentage < 0.1) { |
| 108 percentageString = "< 0.1%"; |
| 109 } |
| 110 |
| 111 d3.select("#percentage") |
| 112 .text(percentageString); |
| 113 |
| 114 d3.select("#funcname") |
| 115 .text(d.name); |
| 116 |
| 117 d3.select("#explanation") |
| 118 .style("visibility", ""); |
| 119 |
| 120 var sequenceArray = getAncestors(d); |
| 121 updateBreadcrumbs(sequenceArray, percentageString); |
| 122 |
| 123 // Fade all the segments. |
| 124 d3.selectAll("path") |
| 125 .style("opacity", 0.3); |
| 126 |
| 127 // Then highlight only those that are an ancestor of the current segment. |
| 128 svg.selectAll("path") |
| 129 .filter(function(node) { |
| 130 return (sequenceArray.indexOf(node) >= 0); |
| 131 }) |
| 132 .style("opacity", 1); |
| 133 } |
| 134 |
| 135 // Restore everything to full opacity when moving off the visualization. |
| 136 function mouseleave(d) { |
| 137 |
| 138 // Hide the breadcrumb trail |
| 139 d3.select("#trail") |
| 140 .style("visibility", "hidden"); |
| 141 |
| 142 // Deactivate all segments during transition. |
| 143 d3.selectAll("path").on("mouseover", null); |
| 144 |
| 145 // Transition each segment to full opacity and then reactivate it. |
| 146 d3.selectAll("path") |
| 147 .transition() |
| 148 .duration(1000) |
| 149 .style("opacity", 1) |
| 150 .each("end", function() { |
| 151 d3.select(this).on("mouseover", mouseover); |
| 152 }); |
| 153 |
| 154 d3.select("#explanation") |
| 155 .transition() |
| 156 .duration(1000) |
| 157 .style("visibility", "hidden"); |
| 158 } |
| 159 |
| 160 // Given a node in a partition layout, return an array of all of its ancestor |
| 161 // nodes, highest first, but excluding the root. |
| 162 function getAncestors(node) { |
| 163 var path = []; |
| 164 var current = node; |
| 165 while (current.parent) { |
| 166 path.unshift(current); |
| 167 current = current.parent; |
| 168 } |
| 169 return path; |
| 170 } |
| 171 |
| 172 function initializeBreadcrumbTrail() { |
| 173 // Add the svg area. |
| 174 var trail = d3.select("#sequence").append("svg:svg") |
| 175 .attr("width", width) |
| 176 .attr("height", 50) |
| 177 .attr("id", "trail"); |
| 178 // Add the label at the end, for the percentage. |
| 179 trail.append("svg:text") |
| 180 .attr("id", "endlabel") |
| 181 .style("fill", "#000"); |
| 182 } |
| 183 |
| 184 // Generate a string that describes the points of a breadcrumb polygon. |
| 185 function breadcrumbPoints(d, i) { |
| 186 var points = []; |
| 187 points.push("0,0"); |
| 188 points.push(b.w + ",0"); |
| 189 points.push(b.w + b.t + "," + (b.h / 2)); |
| 190 points.push(b.w + "," + b.h); |
| 191 points.push("0," + b.h); |
| 192 if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex. |
| 193 points.push(b.t + "," + (b.h / 2)); |
| 194 } |
| 195 return points.join(" "); |
| 196 } |
| 197 |
| 198 // Update the breadcrumb trail to show the current sequence and percentage. |
| 199 function updateBreadcrumbs(nodeArray, percentageString) { |
| 200 |
| 201 // Data join; key function combines name and depth (= position in sequence). |
| 202 var g = d3.select("#trail") |
| 203 .selectAll("g") |
| 204 .data(nodeArray, function(d) { return d.name + d.depth; }); |
| 205 |
| 206 // Add breadcrumb and label for entering nodes. |
| 207 var entering = g.enter().append("svg:g"); |
| 208 |
| 209 entering.append("svg:polygon") |
| 210 .attr("points", breadcrumbPoints) |
| 211 .style("fill", function(d) { return colors[d.name]; }); |
| 212 |
| 213 entering.append("svg:text") |
| 214 .attr("x", (b.w + b.t) / 2) |
| 215 .attr("y", b.h / 2) |
| 216 .attr("dy", "0.35em") |
| 217 .attr("text-anchor", "middle") |
| 218 .text(function(d) { |
| 219 var str = d.name; |
| 220 if (str.length > 10) { |
| 221 str = str.substr(0, 8) + "..."; |
| 222 } |
| 223 return str; |
| 224 }); |
| 225 |
| 226 // Set position for entering and updating nodes. |
| 227 g.attr("transform", function(d, i) { |
| 228 return "translate(" + i * (b.w + b.s) + ", 0)"; |
| 229 }); |
| 230 |
| 231 // Remove exiting nodes. |
| 232 g.exit().remove(); |
| 233 |
| 234 // Now move and update the percentage at the end. |
| 235 d3.select("#trail").select("#endlabel") |
| 236 .attr("x", (nodeArray.length + 0.5) * (b.w + b.s)) |
| 237 .attr("y", b.h / 2) |
| 238 .attr("dy", "0.35em") |
| 239 .attr("text-anchor", "middle") |
| 240 .text(percentageString); |
| 241 |
| 242 // Make the breadcrumb trail visible, if it's hidden. |
| 243 d3.select("#trail") |
| 244 .style("visibility", ""); |
| 245 |
| 246 } |
| 247 |
| 248 function drawLegend() { |
| 249 |
| 250 // Dimensions of legend item: width, height, spacing, radius of rounded rect. |
| 251 var li = { |
| 252 w: 75, h: 30, s: 3, r: 3 |
| 253 }; |
| 254 |
| 255 var legend = d3.select("#legend").append("svg:svg") |
| 256 .attr("width", li.w) |
| 257 .attr("height", d3.keys(colors).length * (li.h + li.s)); |
| 258 |
| 259 var g = legend.selectAll("g") |
| 260 .data(d3.entries(colors)) |
| 261 .enter().append("svg:g") |
| 262 .attr("transform", function(d, i) { |
| 263 return "translate(0," + i * (li.h + li.s) + ")"; |
| 264 }); |
| 265 |
| 266 g.append("svg:rect") |
| 267 .attr("rx", li.r) |
| 268 .attr("ry", li.r) |
| 269 .attr("width", li.w) |
| 270 .attr("height", li.h) |
| 271 .style("fill", function(d) { return d.value; }); |
| 272 |
| 273 g.append("svg:text") |
| 274 .attr("x", li.w / 2) |
| 275 .attr("y", li.h / 2) |
| 276 .attr("dy", "0.35em") |
| 277 .attr("text-anchor", "middle") |
| 278 .text(function(d) { return d.key; }); |
| 279 } |
| 280 |
| 281 function toggleLegend() { |
| 282 var legend = d3.select("#legend"); |
| 283 if (legend.style("visibility") == "hidden") { |
| 284 legend.style("visibility", ""); |
| 285 } else { |
| 286 legend.style("visibility", "hidden"); |
| 287 } |
| 288 } |
| 289 |
| 290 } |
| 291 |
| 292 window.addEventListener("message", function(event) { |
| 293 var data = { |
| 294 name: "Foo", |
| 295 size: 20, |
| 296 children: [ |
| 297 { |
| 298 name: "Bar1", |
| 299 children: [ |
| 300 { |
| 301 name: "Bar11", |
| 302 size: 5, |
| 303 children: [] |
| 304 }, |
| 305 { |
| 306 name: "Bar12", |
| 307 size: 5, |
| 308 children: [] |
| 309 } |
| 310 ] |
| 311 }, |
| 312 { |
| 313 name: "Bar2", |
| 314 children: [ |
| 315 { |
| 316 name: "Bar21", |
| 317 size: 1, |
| 318 children: [] |
| 319 }, |
| 320 { |
| 321 name: "Bar22", |
| 322 size: 2, |
| 323 children: [] |
| 324 } |
| 325 ] |
| 326 } |
| 327 ] |
| 328 } |
| 329 // sequences(data); |
| 330 sequences(event.data); |
| 331 }) |
OLD | NEW |