// used to color node by depth //var color = d3.scaleOrdinal(); var color; var format = d3.format(",d"); // accessor functions for x and y var x = function(d) { return d.x; }; var y = function(d) { return d.y; }; // normal line generator var line = d3.line() .curve(d3.curveLinear) .x(x) .y(y); // configure size, margin, and circle radius var config = { w: 900, h: 450, r: 4, pad: 10 }; // maximum diameter of circle is minimum dimension config.d = Math.min(config.w, config.h); var file = "resources/java8.csv"; d3.csv(file, convert, callback); function convert(row) { var parts = row.name.split(".") row.id = row.name; row.name = parts[parts.length - 1]; row.value = +row.size; return row; } function callback(error, data) { if (error) { console.warn(file, error); return; } // console.log("data:", data.length, data); // var maxsize = Math.max.apply(Math, data.map(function(o) {return o.value})); // used to create hierarchies // https://github.com/d3/d3-hierarchy#stratify var stratify = d3.stratify() .id(function(d) {return d.id; }) .parentId(function(d) { // should match existing id (except for root) return d.id.substring(0,d.id.lastIndexOf(".")); }); // convert csv into hierarchy var root = stratify(data); // root = root.children[7]; // console.log(root.children[4].id); // sort by height then value // https://github.com/d3/d3-hierarchy#node_sort root.sort(function(a, b) { if (a.height != b.height) { return d3.ascending(a.height, b.height); } else { return d3.ascending(a.value, b.value); } }); // console.log("root:", root); drawTraditionalStraight("traditional", root.children[13].copy()); drawCircularDendrogram("circular_dendrogram", root.children[10].copy()); drawTreeMap("treemap", root.children[9].copy()); drawCirclePacking("circle_packing", root.children[8].copy()); drawSunburst("sunburst", root.children[5].copy()); } function drawNodes(g, nodes, raise, bydepth) { g.selectAll("circle") .data(nodes) .enter() .append("circle") .attr("r", function(d) { return d.r ? d.r : config.r; }) .attr("cx", x) .attr("cy", y) .attr("id", function(d) { return d.data.name; }) .attr("class", "node") .style("fill", function(d) { if(bydepth) {return color(d.depth);} else {return color(d.value);} }) .on("mouseover.tooltip", function(d) { show_tooltip(g, d3.select(this)); d3.select(this).classed("selected", true); if (raise) { d3.select(this).raise(); } }) .on("mouseout.tooltip", function(d) { g.select("#tooltip").remove(); d3.select(this).classed("selected", false); }); } function drawLinks(g, links, generator) { var paths = g.selectAll("path") .data(links) .enter() .append("path") .attr("d", generator) .attr("class", "link"); } function drawTraditionalStraight(id, root) { color = d3.scaleOrdinal(); color.domain(d3.range(root.height + 1)); color.range(d3.schemeYlOrRd[root.height + 1]); var svg = d3.select("body").select("#" + id); svg.attr("width", config.w); svg.attr("height", config.h); var g = svg.append("g"); g.attr("id", "plot"); g.attr("transform", translate(config.pad, config.pad)); // setup node layout generator var tree = d3.tree() .size([ config.w - 2 * config.pad, config.h - 2 * config.pad]); // run layout to calculate x, y attributes tree(root); // create line generator // var straightLine = function(d) { // return line([d.source, d.target]); // } var curvedLine = function(d) { var mid = (d.source.y + d.target.y) / 2; return "M" + d.source.x + "," + d.source.y + "C" + d.source.x + "," + mid + " " + d.target.x + "," + mid + " " + d.target.x + "," + d.target.y; } drawLinks(g, root.links(), curvedLine); drawNodes(g, root.descendants(), true, true); } function drawCircularDendrogram(id, root) { color = d3.scaleOrdinal(); color.domain(d3.range(root.height + 1)); color.range(d3.schemeYlOrRd[root.height + 1]); var svg = d3.select("body").select("#" + id); svg.attr("width", config.w); svg.attr("height", config.h); var g = svg.append("g"); g.attr("id", "plot"); // for polar coordinates, easier if treat (0, 0) as center g.attr("transform", translate(config.w / 2, config.h / 2)); // setup node layout generator // let x be theta (in degrees) and y be radial var cluster = d3.cluster().size([360, (config.d / 2) - config.pad]); // run layout to calculate x, y attributes cluster(root); // x, y are in polar coordinates // must convert back into cartesian coordinates root.each(function(d) { d.theta = d.x; d.radial = d.y; var point = toCartesian(d.radial, d.theta); d.x = point.x; d.y = point.y; }); // create hacky v4 curved link generator for radial // var curvedLine = function(d) { // var mid = (d.source.radial + d.target.radial) / 2; // var v0 = toCartesian(mid, d.source.theta); // var v1 = toCartesian(mid, d.target.theta); // return "M" + d.source.x + "," + d.source.y // + "C" + v0.x + "," + v0.y // + " " + v1.x + "," + v1.y // + " " + d.target.x + "," + d.target.y; // }; var straightLine = function(d) { return line([d.source, d.target]); } drawLinks(g, root.links(), straightLine); drawNodes(g, root.descendants(), true, true); } function drawCirclePacking(id, root) { color = d3.scaleSequential(d3.interpolateYlOrRd); // console.log(root); var svg = d3.select("body").select("#" + id); svg.attr("width", config.w + 50); svg.attr("height", config.h + 50); var g = svg.append("g"); g.attr("id", "plot"); // translate so circle is in middle of plot area var xshift = config.pad + (config.w + 50 - config.d) / 2; var yshift = config.pad + (config.h + 50 - config.d) / 2; g.attr("transform", translate(xshift, yshift)); // calculate sum for nested circles root.sum(function(d) {return d.value; }); // setup circle packing layout var diameter = config.d - 2 * config.pad; var pack = d3.pack().size([diameter, diameter]).padding(5); // run layout to calculate x, y, and r attributes pack(root); color.domain([0, root.value]); // draw nested circles drawNodes(g, root.descendants(), false, false); } function drawTreeMap(id, root) { color = d3.scaleSequential(d3.interpolateYlOrRd); var svg = d3.select("body").select("#" + id); svg.attr("width", 1400); svg.attr("height", 1060); // calculate sum for nested circles root.sum(function(d) { return d.value; }); var treemap = d3.treemap() .size([1400, 1060]) .paddingOuter(3) .paddingTop(19) .paddingInner(1) .round(true); // run layout to calculate x, y, and r attributes treemap(root); color.domain([0, root.value]); // draw nested rectangles var cell = svg.selectAll(".node") .data(root.descendants()) .enter() .append("g") .attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; }) .attr("class", "node"); cell.append("rect") .attr("id", function(d) { return "rect-" + d.data.id; }) .attr("width", function(d) { return d.x1 - d.x0; }) .attr("height", function(d) { return d.y1 - d.y0; }) .style("fill", function(d) {return color(d.value);}); cell.append("clipPath") .attr("id", function(d) { return "clip-" + d.data.id; }) .append("use") .attr("xlink:href", function(d) {return "#rect-" + d.data.id + ""; }); var label = cell.append("text") .attr("clip-path", function(d) { return "url(#clip-" + d.data.id + ")";}); label.filter(function(d) {return d.children;}) .selectAll("tspan") .data(function(d) {return d.data.id.substring(d.data.id.lastIndexOf(".") + 1) .split(" ") .concat("\xa0" + format(d.value));}) .enter().append("tspan") .attr("x", function(d,i) { return i ? null : 4;}) .attr("y", 8) .text(function(d) {return d;}); label.filter(function(d) {return !d.children;}) .selectAll("tspan") .data(function(d) { return d.data.id.substring(d.data.id.lastIndexOf(".") + 1) .split(" ") .concat(format(d.value));}) .enter().append("tspan") .attr("x", 4) .attr("y", function(d,i) { return 8 + i * 10;}) .text(function(d) {return d}); cell.append("title") .text(function(d) {return d.data.name;}); } function drawSunburst(id, root) { var x = d3.scaleLinear() .range([0,2 * Math.PI]), y = d3.scaleSqrt() .range([0,340]), text; // color = d3.scaleOrdinal(d3.schemeCategory20); color = d3.scaleSequential(d3.interpolateYlOrRd); var partition = d3.partition(), arc = d3.arc() .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); }) .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); }) .innerRadius(function(d) { return Math.max(0, y(d.y0)); }) .outerRadius(function(d) { return Math.max(0, y(d.y1)); }); var svg = d3.select("body").select("#" + id) .attr("width", 960) .attr("height", 700) .append("g") .attr("transform", "translate(" + 960 / 2 + "," + (700 / 2) + ")"); root.sum(function(d) {return d.value; }); color.domain([0, root.value]); svg.selectAll("path") .data(partition(root).descendants()) .enter().append("g") .attr("class", "node"); var path = svg.selectAll(".node") .append("path") .attr("d", arc) .style("fill", function(d) { return color(d.value)}) .on("click", function(d) { text.transition().attr("opacity", 0); svg.transition() .duration(750) .tween("scale", function() { var xd = d3.interpolate(x.domain(), [d.x0, d.x1]), yd = d3.interpolate(y.domain(), [d.y0, 1]), yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, 340]); return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); }; }) .selectAll("path") .attrTween("d", function(d) { return function() { return arc(d); }; }) .on("end", function(e) { // check if the animated element's data e lies within the visible angle span given in d if (e.x0 > d.x0 && e.x0 < d.x1) { // get a selection of the associated text element var arcText = d3.select(this.parentNode).select("text"); // fade in the text element and recalculate positions arcText.transition().duration(750) .attr("opacity", 1) .attr("class", "visible") .attr("transform", function() { return "rotate(" + ((x((e.x0 + e.x1)/2) - Math.PI / 2) / Math.PI * 180) + ")" }) .attr("x", function(d) { return y(d.y0); }) .text(function(d) {return d.data.name + "\n" + format(d.value);}) } }) }); text = svg.selectAll(".node") .append("text") .attr("transform", function(d) { return "rotate(" + ((x((d.x0 + d.x1)/2) - Math.PI / 2) / Math.PI * 180) + ")"; }) .attr("x", function(d) { return y(d.y0); }) .attr("dx", "6") .attr("dy", ".35em") .text(function(d) {return d.data.name + "\n" + format(d.value);}) }