How to change alignment of nodes in a Sankey diagram using D3? - node.js
I have generated a Sankey diagram as shown above using d3 code (.js file) mentioned below [the .html and .css files are not quoted here].
Now I want the Sankey diagram to look like below with node "Technology" and "Strategy" appearing apart as a fourth level:
What are the necessary changes to be done in the D3 code?
var svg = d3.select("svg").attr("style", "outline: thin solid grey;"),
width = +svg.attr("width"),
height = +svg.attr("height");
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
var school = {"nodes": [
{"name":"High School"}, // 0
{"name":"Community College"}, // 1
{"name":"Finance"}, // 2
{"name":"Accounting"}, // 3
{"name":"ITS"}, // 4
{"name":"Marketing"}, // 5
{"name":"Analytics"}, // 6
{"name":"Security"}, // 7
{"name":"Consulting"}, // 8
{"name":"Banking"}, // 9
{"name":"Internal"}, // 10
{"name":"Securities"}, // 11
{"name":"Public"}, // 12
{"name":"Audting"}, // 13
{"name":"Internal"}, // 14
{"name":"Retail"}, // 15
{"name":"Technology"}, // 16
{"name":"Strategy"} // 17
],
"links":[
// FirstYear
{"source":0,"target":2,"value":33},
{"source":0,"target":3,"value":42},
{"source":0,"target":4,"value":74},
{"source":0,"target":5,"value":60},
// Community College
{"source":1,"target":2,"value":7},
{"source":1,"target":3,"value":13},
{"source":1,"target":4,"value":11},
{"source":1,"target":5,"value":9},
// Finance
{"source":2,"target":9,"value":16},
{"source":2,"target":10,"value":14},
{"source":2,"target":11,"value":10},
// Accounting
{"source":3,"target":12,"value":20},
{"source":3,"target":13,"value":12},
{"source":3,"target":7,"value":8},
{"source":3,"target":14,"value":15},
// Marketing
{"source":5,"target":6,"value":30},
{"source":5,"target":15,"value":39},
// ITS
{"source":4,"target":6,"value":40},
{"source":4,"target":7,"value":20},
{"source":4,"target":12,"value":6},
{"source":4,"target":8,"value":19},
// ITS Consulting
{"source":8,"target":16,"value":10},
{"source":8,"target":17,"value":9},
]};
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]]);
var link = svg.append("g")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.selectAll("path");
var node = svg.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g");
sankey(school);
link = link
.data(school.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.width); });
// link hover values
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
node = node
.data(school.nodes)
.enter().append("g");
node.append("rect")
.attr("x", function(d) { return d.x0; })
.attr("y", function(d) { return d.y0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("fill", function(d) { return color(d.name.replace(/ .*/, "")); })
.attr("stroke", "#000");
node.append("text")
.attr("x", function(d) { return d.x0 - 6; })
.attr("y", function(d) { return (d.y1 + d.y0) / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(function(d) { return d.name; })
.filter(function(d) { return d.x0 < width / 2; })
.attr("x", function(d) { return d.x1 + 6; })
.attr("text-anchor", "start");
svg.append("text")
.attr("x", 10)
.attr("y", 30)
.attr("class", "graphTitle")
.text("STUDENT CHOICES");
svg.append("text")
.attr("x", width - 80)
.attr("y", height - 10)
.attr("class", "footnote")
.text("data is fictitious");
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey#0"></script>
<svg width="600" height="500"></svg>
The alignment of d3.sankey can be configured using .nodeAlign(), and for your requirement, you will need .nodeAlign(d3.sankeyLeft)
If it is not specified, the alignment defaults to d3.sankeyJustify, which is what you are currently seeing.
https://github.com/d3/d3-sankey#alignments
For those who are looking for a quick ans. Have a nice day!
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.nodeAlign(function (node) {
// you may specify the horizatonal location here
// i.e. if your data structure contain node.horizontalPosition (an integer)
// you can return node.horizontalPosition
return node.depth; //align left
})
.extent([[1, 1], [width - 1, height - 6]]);
Related
Problems loading CSV data in D3. svg.selectAll(...).data(...).enter is not a function
I am trying to load a CSV data set into d3 by assigning it to a variable, but it seems that I keep receiving an error saying that enter() is not a function. I think the issue lies in the way I'm loading the CSV data. For reference, I'm following this tutorial: http://duspviz.mit.edu/d3-workshop/scatterplots-and-more/ Here is my code for reference. var ratData = []; d3.csv("rat-data.csv", function(d) { return { city : d.city, // city name rats : +d.rats // force value of rats to be number (+) }; }, function(error, rows) { // catch error if error, read rows ratData = rows; // set ratData equal to rows console.log(ratData); createVisualization(); // call function to create chart }); function createVisualization(){ // Width and height of SVG var w = 150; var h = 175; // Get length of dataset var arrayLength = ratData.length; // length of dataset var maxValue = d3.max(ratData, function(d) { return +d.rats;} ); // get maximum var x_axisLength = 100; // length of x-axis in our layout var y_axisLength = 100; // length of y-axis in our layout // Use a scale for the height of the visualization var yScale = d3.scaleLinear() .domain([0, maxValue]) .range([0, y_axisLength]); //Create SVG element var svg = d3.select("body") .append("svg") .attr("width", w) .attr("height", h); // Select and generate rectangle elements svg.selectAll( "rect" ) .data( ratData ) .enter() .append("rect") .attr( "x", function(d,i){ return i * (x_axisLength/arrayLength) + 30; // Set x coordinate of rectangle to index of data value (i) *25 }) .attr( "y", function(d){ return h - yScale(d.rats); // Set y coordinate of rect using the y scale }) .attr( "width", (x_axisLength/arrayLength) - 1) .attr( "height", function(d){ return yScale(d.rats); // Set height of using the scale }) .attr( "fill", "steelblue"); // Create y-axis svg.append("line") .attr("x1", 30) .attr("y1", 75) .attr("x2", 30) .attr("y2", 175) .attr("stroke-width", 2) .attr("stroke", "black"); // Create x-axis svg.append("line") .attr("x1", 30) .attr("y1", 175) .attr("x2", 130) .attr("y2", 175) .attr("stroke-width", 2) .attr("stroke", "black"); // y-axis label svg.append("text") .attr("class", "y label") .attr("text-anchor", "end") .text("No. of Rats") .attr("transform", "translate(20, 20) rotate(-90)") .attr("font-size", "14") .attr("font-family", "'Open Sans', sans-serif"); }; // end of function
d3 - foreign object - getting the right scope
I am using foreignObject to use HTML inside a d3 circle. What I want: use HTML inside d3 objects reach the normal scopes outside of HTML code Now I have problems to get the right scope for using functions. If I use "this" inside the .html(".....") I get the "g" element of d3. So is there a way to get to my normal scopes? Or is there even a more elegant way to solve this problem? var foreign = that._foreignObject = d3.select(this).append("foreignObject") .attr("width", 30) .attr("height", 30) .append("xhtml:div") .style("font", "15px 'Arial'") .html("<button name=Klickmich type=button value=play onclick=>"); EDIT: 16.12.2013: var nodeEnter = node.enter().append("svg:g") .attr("class", "node") .attr("transform", function (d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) //click once .on("click", function (d) { if (that._foreignObject) { that._foreignObject.remove(); } that.toggle(d); that.update(d); that.onNodeClick(d); var circle = d3.select(this).select("circle") .transition() .duration(750) .attr("r", 17) .attr("width", 40) .attr("heigth", 40) .style("stroke-dasharray", ("5,2")) .style("stroke-width", 2) .style("stroke", "black"); var button = "<button name=Klickmich type=button value=play onclick=>"; var foreign = that._foreignObject = d3.select(this).append("foreignObject") .attr("width", 30) .attr("height", 30) .append("xhtml:div") .style("font", "15px 'Arial'") .html(button) .on("click", function (d) { console.log("heyho"); }); d3.select(this).select("text").transition() .duration(750) .style("font-size", 15) .style("font-weight", "bold") .attr("x", function (d) { return d.children || d._children ? -20 : -20; }); })
If i Click on my html button i set this._clicked = 1; And the onClick function of the node is build as follows: .on("click", function (d) { if (this._clicked == null) { // some code } } this._clicked = null;
Setting up a click event in d3 that hides all other elements
I am fairly new to using d3, but what I am trying to do is make a chord diagram of some site traffic, and I am trying to make it interactive by changing the color of the paths when a user clicks on the group for a certain site.here is the style and script section of my code: <style type="text/css"> .group text { font: 11px sans-serif; pointer-events: none; } #circle circle { fill: none; pointer-events: all; } .group path { stroke: #000; fill-opacity: .5; } path.chord { stroke-width: .75; fill-opacity: .75; } #circle:hover path.fade { display: none; } </style> </head> <body> <script type="text/javascript"> // Chart dimensions. var w = 600, h = 700, r1 = Math.min(w, h) / 2 - 4, r0 = r1 - 20, format = d3.format(",.3r"); // Square matrices, asynchronously loaded; credits is the transpose of sitename. var sitename = []; // The chord layout, for computing the angles of chords and groups. var layout = d3.layout.chord() .sortGroups(d3.descending) .sortSubgroups(d3.descending) .sortChords(d3.descending) .padding(.04); // The color scale, for different categories of "worrisome" risk. var fill = d3.scale.ordinal(); // The arc generator, for the groups. var arc = d3.svg.arc() .innerRadius(r0) .outerRadius(r1); // The chord generator (quadratic Bézier), for the chords. var chord = d3.svg.chord() .radius(r0); // Add an SVG element for each diagram, and translate the origin to the center. var svg = d3.select("body").selectAll("div") .data([sitename]) .enter().append("div") .style("display", "inline-block") .style("width", w + "px") .style("height", h + "px") .append("svg:svg") .attr("width", w) .attr("height", h) .append("svg:g") .attr("transform", "translate(" + w / 2 + "," + h / 2 + ")"); // Load our data file… d3.csv("data2.csv", function(data) { var uniqueids = {}, array = [], n = 0; // Compute a unique id for each site. data.forEach(function(d) { d.siteid1 = uniqueIDMaker(d.siteid1); d.siteid2 = uniqueIDMaker(d.siteid2); d.valueOf = value; // convert object to number implicitly }); // Initialize a square matrix of sitename and users for (var i = 0; i < n; i++) { sitename[i] = []; for (var j = 0; j < n; j++) { sitename[i][j] = 0; } } // Populate the matrices, and stash a map from id to site. data.forEach(function(d) { sitename[d.siteid1.id][d.siteid2.id] = d; array[d.siteid1.id] = d.siteid1; array[d.siteid2.id] = d.siteid2; }); // For each diagram… svg.each(function(matrix, j) { var svg = d3.select(this); // Compute the chord layout. layout.matrix(matrix); // Add chords. svg.selectAll(".chord") .data(layout.chords) .enter().append("svg:path") .attr("class", "chord") .style("fill", function(d) { return fill(d.source.value); }) .style("stroke", function(d) { return d3.rgb(fill(d.source.value)).darker(); }) .attr("d", chord) .on("dblclick",function(){ d3.select(this) .style("fill","red") .style("stroke","yellow") }) .append("svg:title") .text(function(d) { return "site " + d.source.value.siteid2.name + " and site " + d.source.value.siteid1.name + " have " + format(d.source.value) + " common users"; }) ; // Add groups. var g = svg.selectAll("g.group") .data(layout.groups) .enter().append("svg:g") .attr("class", "group"); // Add the group arc. g.append("svg:path") .style("fill", function(d) { return fill(array[d.index]); }) .attr("id", function(d, i) { return "group" + d.index + "-" + j; }) .attr("d", arc) .append("svg:title") .text(function(d) { return "site " + array[d.index].name + " has " + format(d.value) + "users"; }); g.append("svg:text") .attr("x", 6) .attr("dy", 15) .filter(function(d) { return d.value > 110; }) .append("svg:textPath") .attr("xlink:href", function(d) { return "#group" + d.index + "-" + j; }) .text(function(d) { return array[d.index].name; }); }); function uniqueIDMaker(d) { return uniqueids[d] || (uniqueids[d] = { name: d, id: n++ }); } function value() { return +this.count; }}); </script> any help would be greatly appreciated http://jsfiddle.net/Rw3aK/2/ is a jsFiddle of the script, not sure how to make it read from a file, so here is the contents of data2.csv: siteid1,siteid2,count,pubid1,pubid2 8,94,11132,57141,57141 201,94,10035,57141,57141 201,8,9873,57141,57141 0,94,8488,45020,57141 0,8,8258,45020,57141 0,201,7644,45020,57141 0,1,6973,45020,45020 94,1,5719,57141,45020 8,1,5670,57141,45020 1,201,5410,57141,45020
I forked your jsfiddle and converted your CSV data to JSON, now in a variable data: http://jsfiddle.net/mdml/K6FHW/. I also modified your code slightly so that when you click on a group, all outgoing chords are highlighted red. When you click on a group again, the chords change back to their original color. Here're the relevant snippets: When adding the chords, label each chord with a class according to the chord's source svg.selectAll(".chord") .data(layout.chords) .enter().append("svg:path") .attr("class", function(d){ return "chord chord-" + d.source.index; }) ... When clicking a group, check if that group's chords are highlighted. If so, fill the chords with their default color If not, fill the chords red Then record whether or not the group's chords are highlighted in a variable d.chordHighlighted g.append("svg:path") ... .attr("id", function (d, i) { return "group" + d.index + "-" + j; }) ... .on("click", function(d){ if (d.chordHighlighted) d3.selectAll(".chord-" + d.index) .style("fill", fill(d.value)); else{ d3.selectAll(".chord-" + d.index) .style("fill", "red"); } d.chordHighlighted = d.chordHighlighted ? false : true; })
add images to bubble layout in D3.js
Unable to add images to bubble layout in D3.js . I am trying to append images to the circles in bubble layout but it doesnt works out . the image is not getting transformed. I want to have look and feel of this:- http://www.cloudshapes.co.uk/labs/attention-hungry-cabinet-ministers/ here is the fiddle link for what I have been trying to do: http://jsfiddle.net/Ankitb/eYGCY/4/ var force = d3.layout.force() .charge(-300) .size([w, h]) .nodes(nodes) .on("tick", tick) .start(); function tick() { svg.selectAll("circle") .attr("cx", function (d) { return d.x; }) .attr("cy", function (d) { return d.y; }); } var interval = setInterval(function () { var d = { x: w / 4 + 2 *( Math.random() - 1), y: h / 4 + 2 *( Math.random() - 1) }; var personDot = svg.append("g") .attr("class", "g-person-dots") .selectAll("g") .data([d]) .enter().append("g") .attr("transform", function (d) { return "translate(" + d.x+ "," + d.y + ")"; }); personDot.append("circle") .data([d]).attr("r", 40) //.attr("r", 1e-6) .attr("cx",0).attr("cy",0) .transition().style("stroke", "gray").style("fill","white") .ease(Math.sqrt); personDot.append("image").data([d]) .attr("xlink:href", "PeopleProfilePicture.jpg") // .attr("x", function (d, i) { return -mugDiameter / 2 - mugDiameter * (i % 9); }) //.attr("y", function (d, i) { return -mugDiameter / 2 - mugDiameter * (i / 9 | 0); }) .attr("width", 80) .attr("height", 80) .attr("transform", function (d) { return "translate(" + -d.x / 10 + "," + -d.y / 10 + ")"; }); if (nodes.push(d) > 10) clearInterval(interval); else { force.start(); } }, 30);
The translation of an element is relative to its parent element. That is, by default the element will be in the same position as its parent. Therefore, the translation you need to do does not depend on the dynamic data that you pass in, but only on the dimensions of the image. You need to set transform as follows. .attr("transform", "translate(-40,-40)"); You may also want to make the background of your images transparent such that you can still see the circle.
d3 Gives a 406 Error when trying to run d3js example on IIS
I have setup a basic IIS server and am trying to demonstrate a d3js example. First I create an html page with the example code: <!DOCTYPE html> <meta charset="utf-8"> <style> body { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .bar { fill: steelblue; } .x.axis path { display: none; } </style> <body> <script src="http://d3js.org/d3.v3.min.js"></script> <script> var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scale.ordinal() .rangeRoundBands([0, width], .1); var y = d3.scale.linear() .rangeRound([height, 0]); var color = d3.scale.ordinal() .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left") .tickFormat(d3.format(".2s")); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); d3.csv("data.csv", function(error, data) { color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; })); data.forEach(function(d) { var y0 = 0; d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; }); d.total = d.ages[d.ages.length - 1].y1; }); data.sort(function(a, b) { return b.total - a.total; }); x.domain(data.map(function(d) { return d.State; })); y.domain([0, d3.max(data, function(d) { return d.total; })]); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Population"); var state = svg.selectAll(".state") .data(data) .enter().append("g") .attr("class", "g") .attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; }); state.selectAll("rect") .data(function(d) { return d.ages; }) .enter().append("rect") .attr("width", x.rangeBand()) .attr("y", function(d) { return y(d.y1); }) .attr("height", function(d) { return y(d.y0) - y(d.y1); }) .style("fill", function(d) { return color(d.name); }); var legend = svg.selectAll(".legend") .data(color.domain().slice().reverse()) .enter().append("g") .attr("class", "legend") .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); legend.append("rect") .attr("x", width - 18) .attr("width", 18) .attr("height", 18) .style("fill", color); legend.append("text") .attr("x", width - 24) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function(d) { return d; }); }); </script> and then i create the data.csv file: State,Under 5 Years,5 to 13 Years,14 to 17 Years,18 to 24 Years,25 to 44 Years,45 to 64 Years,65 Years and Over AL,310504,552339,259034,450818,1231572,1215966,641667 AK,52083,85640,42153,74257,198724,183159,50277 AZ,515910,828669,362642,601943,1804762,1523681,862573 AR,202070,343207,157204,264160,754420,727124,407205 CA,2704659,4499890,2159981,3853788,10604510,8819342,4114496 CO,358280,587154,261701,466194,1464939,1290094,511094 CT,211637,403658,196918,325110,916955,968967,478007 DE,59319,99496,47414,84464,230183,230528,121688 DC,36352,50439,25225,75569,193557,140043,70648 FL,1140516,1938695,925060,1607297,4782119,4746856,3187797 GA,740521,1250460,557860,919876,2846985,2389018,981024 HI,87207,134025,64011,124834,356237,331817,190067 ID,121746,201192,89702,147606,406247,375173,182150 IL,894368,1558919,725973,1311479,3596343,3239173,1575308 IN,443089,780199,361393,605863,1724528,1647881,813839 IA,201321,345409,165883,306398,750505,788485,444554 KS,202529,342134,155822,293114,728166,713663,366706 KY,284601,493536,229927,381394,1179637,1134283,565867 LA,310716,542341,254916,471275,1162463,1128771,540314 ME,71459,133656,69752,112682,331809,397911,199187 MD,371787,651923,316873,543470,1556225,1513754,679565 MA,383568,701752,341713,665879,1782449,1751508,871098 MI,625526,1179503,585169,974480,2628322,2706100,1304322 MN,358471,606802,289371,507289,1416063,1391878,650519 MS,220813,371502,174405,305964,764203,730133,371598 MO,399450,690476,331543,560463,1569626,1554812,805235 MT,61114,106088,53156,95232,236297,278241,137312 NE,132092,215265,99638,186657,457177,451756,240847 NV,199175,325650,142976,212379,769913,653357,296717 NH,75297,144235,73826,119114,345109,388250,169978 NJ,557421,1011656,478505,769321,2379649,2335168,1150941 NM,148323,241326,112801,203097,517154,501604,260051 NY,1208495,2141490,1058031,1999120,5355235,5120254,2607672 NC,652823,1097890,492964,883397,2575603,2380685,1139052 ND,41896,67358,33794,82629,154913,166615,94276 OH,743750,1340492,646135,1081734,3019147,3083815,1570837 OK,266547,438926,200562,369916,957085,918688,490637 OR,243483,424167,199925,338162,1044056,1036269,503998 PA,737462,1345341,679201,1203944,3157759,3414001,1910571 RI,60934,111408,56198,114502,277779,282321,147646 SC,303024,517803,245400,438147,1193112,1186019,596295 SD,58566,94438,45305,82869,196738,210178,116100 TN,416334,725948,336312,550612,1719433,1646623,819626 TX,2027307,3277946,1420518,2454721,7017731,5656528,2472223 UT,268916,413034,167685,329585,772024,538978,246202 VT,32635,62538,33757,61679,155419,188593,86649 VA,522672,887525,413004,768475,2203286,2033550,940577 WA,433119,750274,357782,610378,1850983,1762811,783877 WV,105435,189649,91074,157989,470749,514505,285067 WI,362277,640286,311849,553914,1487457,1522038,750146 WY,38253,60890,29314,53980,137338,147279,65614 When I access the page, nothing displays...A quick look through fiddler shows the html content downloads fine (you can also see it in show source). You can also look at the data.csv file directly by accessing it from the url http://localhost/data.csv The problem shown in fiddler is a 406 error when d3js attempts to load the CSV file. Any ideas? Thanks
What does your HTTP Request's Accept header contain? Apache is probably configured in such a way so as to return a 406 because the value in the Accept header does not include whatever MIME type your CSV is returning. See http://blogs.msdn.com/b/ieinternals/archive/2011/03/27/http-406-not-acceptable-php-ie9-standards-mode-accepts-only-text_2f00_css-for-stylesheets.aspx for a similar problem sometimes seen in browsers.
Change the build action for the csv file to "Resource" in the file's properties using your Visual Studio.