How to create a Flow diagram with a circular Twist

Hacking a Chord Diagram to create a radial Sankey

Posted: August 28, 2015-Likes: 2-Comments: 16-Categories: D3.js, Interactive, Tutorial-Tags: Chord Diagram, D3.js, Data Visualization
You are here: ...
Home / Blog / D3.js / Hacking a Chord diagram to visualize a Flow

In this blog post I’ll explain how you can hack a Chord diagram to shape it into a more Sankey like flow diagram. Why not just take a regular Sankey to visualize the flow? Well, the visualization was meant for use in the media to engage the viewers, so I wanted to create something visually different. In the end it appears that I just wanted to challenge myself and dive into the D3 source code (for the first time).

I’ve you’re just interested in the final code to recreate it yourself, scroll down to the last section where you can find a link to a working example. Otherwise, read on if you want to understand how it was made.

The Idea

At Deloitte, I worked on the State of the State project in which several small teams of data analyst and subject experts try to answer questions with actual (open) data that arise due to new rules or regulations of the government. For example, if a politician says that there are more than enough empty office buildings in the Netherlands so we should shut down any plans for building new homes in cities, we try to see if that is really viable. How many square meters is empty? How many of these buildings are even good enough for transformation to apartments (not too old or new, not in an industrialized zone, not too small, etc.)? How much is each city expected to expand in population in the next 10 years. Besides Housing, there are three more subjects: Health, Education and Labor.

The hacked Chord Diagram became part of the Education track. Do students really end up in the occupations that they studied for? And how is the new population of graduates divided among the occupational sectors? What % become teachers for example?
To answer these questions we had access to a survey that is held amongst all students from MBO & HBO about 1.5 years after they graduated. (I have no idea how to translate these. Its the two levels below University that you can take after completing High School. By completing HBO you have a degree that is the same as a University Bachelor, although the teachings are more practical than at a University. MBO is the level below HBO). We had the 2014 results which were filled in by ~40% of the graduated population in 2013; about 18000 respondents for HBO and 21000 for MBO. Through the survey, we knew on a case by case basis what each respondent had studied and in what sector there were currently working.

To visualize this flow between education and occupation I started drawing some ideas, one of which was a flow chart like a Sankey, but more circular. Where the two halves of the circle are slightly pulled apart to distinguish them as different groups and there are lines connecting different sciences to different occupations. The manager was immediately drawn to this idea so then I had to figure out how to make it (I was not at all sure that I could create it when I showed the design to the manager)

The design sketch and inspirational images to convey the idea to the manager

The Result

Because the tutorial that explains how to create the Flow chart at the top of the page is quite long, I’ll just go right ahead and show you the end result that eventually can be found on the State of the State website. We had data for two different educational levels, therefore I also had to incorporate a data change into the script. I’m extremely grateful that AmeliaBR wrote a very extensive explanation on how to do it with Chord constancy. I still only truly understand about half of her script after adjusting it to fit with the rest of my code, but at least it works :)

A user can now switch between MBO and HBO by clicking a button right below the visual. Since HBO is a higher level than MBO the management functions get a larger share of the students while the salesmen and service providers shrink quite a lot compared to MBO. Below you can see a short animated gif of the change from MBO to HBO. I’m sorry that the language is in Dutch, that’s why I recreated the HBO version in English at the top of this page. Besides the option to change between MBO and HBO, I also included a subtle flow into the grey fill of the Chords themselves. It doesn’t work in IE (… -_- ) but in the other browsers it is meant to slightly hint at a flow from left to right.

Final Flow chart in the Education track of the State of the State project

The full education track will be filled with several more charts, but I’ll discuss those in a later project page. I’ve already made a blog post about improving the user experience by using invisible voronoi’s on mouseover events on a scatterplot, which I also used a lot in the State of the State visualizations

Samples of the other visuals in the State of the State Education track

The Math

On to the tutorial. Since I wanted to have an interactive chart, I knew that D3 was probably the way to go. Looking at the base charts and layout available I saw two options. Try to use a Chord Diagram as the base and pull the circle apart or use a Sankey diagram as the base and make the two straight ends circular. I’m still not sure if it was a good guess, but I felt that it would be better to start from a Chord diagram.

In the image below you can already see the steps that we are going to have to take to create the circular Flow chart. There are about 6 steps and the next sections will try to thoroughly explain what is going on, how to shape your data and what you have to adjust to your code

The results of each step in the process of creating a circular Flow chart

Step 1 – How to set up a Chord Diagram matrix to show Flows between the left and right half

A normal Chord diagram input in terms of data is a matrix that shows how many (say, people) flowed from one region to another. What makes a Chord diagram so special is that a flow from Region A to B can be different than the flow from region B to A. (If you are new to Chord Diagrams then perhaps the next part will seem to make little sense, but please check out this Chord Diagram explanation that I build a few months ago to get the gist)

Say, we have educations A, B and C and the possible occupations are X, Y and Z. The first step was to figure out how to build the Chord Diagram matrix to make sure that the inner chords only connected a section from the left half (the educations) to a section from the right half (the occupations), like in the image below. I’ve colored the inner chords in the image so it becomes more clear that chords starting at A only go to X, Y or Z, and this is also true for chords starting at B and C. Apart from the restriction that chords can only connect two arcs from opposite halves of the circle, the image below is an ordinary Chord Diagram.

Chords from the left half flow only to arcs on the right half

I first made a list of the flows. How many people should go from education A to occupation X, how many from A to Y and from A to Z. Next from B to X, Y and Z and finally the same for education C. These were just random numbers, the exact values didn’t matter

  • A → X: 15
  • A → Y: 20
  • A → Z: 5
  • B → X: 5
  • B → Y: 15
  • B → Z: 5
  • C → X: 10
  • C → Y: 5
  • C → Z: 15

Below you can see the matrix that results from the defined flows and gives you the image above. Right below it are four more images that are my attempt at an explanation of how the matrix is build up.

The resulting matrix needed to show flows in a Chord Diagram

Order the rows and columns in a clockwise fashion around the Chord Diagram

Blocks of zeros

The bottom left section is filled with the flows as defined

The top right section is a copy of the bottom left section flipped along the diagonal

And just for completeness, the table above would be written like this in JavaScript:

The matrix in JavaScript

Step 2 – How to create an empty section in between the two halves

Alright, now we already have a flow diagram with circular halves, but it’s still difficult to see what the two groups are. Right now there is no difference in the distance between A and X (different group) and A and B (same group). Therefore, let’s create a bit of distance so the Gestalt law of Proximity makes the two groups intuitively visible.

We can make this distance by creating two new arcs right in between the two circle halves. These arcs will have exactly one chord (or flow) and that one will run between them. The thickness of this one chord will define how much of the rest of the circle remains for the other arcs. In the image below you see the desired end result where the extra dummy arc is added.

Two dummy arcs in between the educations and occupations. Notice how the dummy arc only have one flow in between them

To do this, the matrix that we created in the previous step needs to be adjusted. Because we have two new arcs, the matrix will need two new rows and columns. One in between the occupations and educations (thus between Z and C) and one at the end (because the chord diagram is circular, this in essence means another one in between the other half, between A and X). The two arcs only have a flow in between themselves, so all entries in these new cells are zero except for the two locations that define the flow between the two dummy arcs.

Below you can see the matrix with the new dummy arcs and again an attempt at an explanation of what has been added. The number of 40 defines the width of the two dummy arcs relative to the other arcs. Making it bigger will result in less space left for the other arcs and making it smaller does the reverse.

The Chord Diagram matrix needed to create the two dummy arcs

Two dummy arcs are added, thus resulting in two new columns and rows

There is only one flow for the dummy arcs and that is between them

These two steps, first without and then with the dummy arcs, is exactly how I figured out how to do it. Just plain pen and paper still works best for me. The image below is the result of building the matrix for the first time (and the added extra dummy rows squished in between)

Figuring out the shape of the matrix with plain pen and paper

Step 3 – How to make the two sections symmetrical

Now we have a dummy arc in between the two halves. We could make the dummy arcs and chord white so it wouldn’t be visible anymore. Then there would be two intuitively distinct groups. But the result wouldn’t be symmetrical, the X and A arc do not start/end at the same height and people do like symmetry. So we need to rotate the entire chord diagram so that the two dummy arcs are centered horizontally. Again, the image below gives the end result that we want to achieve in this step.

The entire Chord diagram has been rotated to make it symmetrical

You might not expect it, but the rotation actually needs adjustments to quite a few different places in the code. There are three elements to the diagram: the arcs, the labels next to the arcs and the inner chords. All of these need to be rotated. Maybe you are wondering “Why not create a g group, put all of these elements in the g element and then only rotate the g?”. Well, the problem is that when I tried this, things go horribly wrong later on (image below) when we try to pull the pieces apart due to things like conflicting transforms. But that is not all, right now pulling the pieces apart means a translation across the horizontal axis. If we first do a rotation, in the new system of x and y, this has then become a combination of a translation in the x & y direction, making it very complex)

Tried to rotate first and then pull the pieces apart...

So, let’s do it a bit more complex right now to save ourselves a lot of headache later on. We can make the arcs and chords rotate by using the startAngle and endAngle functions of the d3.svg.arc()d3.svg.chord() functions, which define the locations of the arcs and inner chords. Normally you do not define these functions when calling d3.svg.arc()d3.svg.chord() because the default is fine. But we want to add an offset to this default. So, instead of



var arc = d3.svg.arc()
	.innerRadius(innerRadius)
	.outerRadius(outerRadius);

var path = d3.svg.chord()
	.radius(innerRadius);

We create two new functions that will define the new start and end angle of the chords and arcs and add these to the d3.svg.arc()d3.svg.chord() functions



//Include the offset in de start and end angle to rotate the Chord diagram clockwise
function startAngle(d) { return d.startAngle + offset; }
function endAngle(d) { return d.endAngle + offset; }

//startAngle and endAngle now include the offset in degrees
var arc = d3.svg.arc()
	.innerRadius(innerRadius)
	.outerRadius(outerRadius)
	.startAngle(startAngle)
	.endAngle(endAngle);

var path = d3.svg.chord()
	.radius(innerRadius)
	.startAngle(startAngle)
	.endAngle(endAngle);

To make things easy for us, we can calculate the offset to make sure that the dummy arcs are exactly in the center. Furthermore, it would probably also be easier that we can define the widths of the dummy arcs in a percentage compared to the visible arcs. Below you can see the code that does both of these two things



var respondents = 95, //Total number of respondents (i.e. the number that makes up the group)
	emptyPerc = 0.4, //What % of the circle should become empty in comparison to the visible arcs
	emptyStroke = Math.round(respondents*emptyPerc); //How many "units" would define this empty percentage
var matrix = [
	[0,0,0,0,10,5,15,0], //Z
	[0,0,0,0,5,15,20,0], //Y
	[0,0,0,0,15,5,5,0], //X
	[0,0,0,0,0,0,0,emptyStroke], //Dummy stroke
	[10,5,15,0,0,0,0,0], //C
	[5,15,5,0,0,0,0,0], //B
	[15,20,5,0,0,0,0,0], //A
	[0,0,0,emptyStroke,0,0,0,0] //Dummy stroke
];
//Calculate how far the Chord Diagram needs to be rotated clockwise
//to make the dummy invisible chord center vertically
var offset = Math.PI * (emptyStroke/(respondents + emptyStroke)) / 2;

The new variable respondents gives the result that we get when summing all of the values in the lower left block (or upper right block). It is the size of A, B and C together (and thus also X, Y and Z together). Next we define what percentage we want to be empty relative to respondents with the emptyPerc variable. In emptyStroke this percentage is translated to the number of units (nonexisting people) that is needed to create an arc of that width. We can then replace emptyStroke in the two locations where we previously hardcoded the “40” value. That was part one, we can now define the width of the dummy arc in a percentage. Next up is calculating the offset which happens in just one line.

We need to rotate the full chord diagram by half of the arc length of a dummy arc. By rotating it by half an arc, the two dummy arcs will end up right in the middle. Mathematically this results in the following formula:

offset = (0.5 * emptyStroke) / (2 * (emptyStroke + respondents)) * 2π

Let me explain in steps. A full circle consists of the two dummy arcs, sized by emptyStroke, and the two halves of education and occupation (which are the same size by design). So a full circle represents 2 * (emptyStroke + respondents) units. We want to rotate this by half of a dummy arc, which is 0.5 * emptyStroke. We can turn this into a percentage; what percentage of a full circle should the diagram be rotated:

percentage of rotation = (0.5 * emptyStroke) / (2 * (emptyStroke + respondents))

The start and end angles use radians and a full circle is 2π radians, thus we need to multiply the percentage by 2π to convert it to radians. Cleaning up the formula a bit we end up with

offset = π * emptyStroke / (emptyStroke + respondents)  / 2

This is the variable that is called in the new startAngle and endAngle functions. However, we’re still not done. We also need to rotate the labels along with the arcs. Luckily, this is easily done. We only need to add the offset variable to the .each function that calculates the text angle in the call that creates the labels:



//The text needs to be rotated with the offset in the clockwise direction
g.append("text")
	//Slightly altered function where the offset is added to the angle
        .each(function(d) { d.angle = ((d.startAngle + d.endAngle) / 2) + offset;})
	.attr("dy", ".35em")
	.attr("class", "titles")
	.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
	.attr("transform", function(d,i) { 
		var c = arc.centroid(d);
		return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
		+ "translate(" + (innerRadius + 55) + ")"
		+ (d.angle > Math.PI ? "rotate(180)" : "")
	})
	.text(function(d,i) { return Names[i]; });

And that was it for the rotation. We can now clean things up and make the dummy arcs truly disappear (make the opacities equal to 0 and/or fills to white). I altered the colors to make it closer to the very first example at the top of the page. Without the dummy arcs visible it is much more apparent that we have two separate groups and that the chords run between these two groups.

Adjusted colors and invisible dummy arcs and chord

The Code

Now we’ve gotten to the section where we have to do something that I really wasn’t sure that I actually could. The image above already shows what could be an end result. The Chord diagram shows a flow between two groups. But I felt it just didn’t look right. The entire image still fits inside one circle and the chords are too short. Not very good arguments, I know, but I felt it could look better if the two halves could be pulled apart. That involved quite a lot of math as well, but it translated itself into a lot more complex code adjustments than the previous section (but no worries, I wrapped the most complex part in a nice new function that you can download and use).

Step 4 – How to pull the two halves apart

I’ll split this up into two steps. First we’ll move the arcs and the text apart. These pieces only need to be displaced horizontally, they do not need to be adjusted in width or height. The image below again shows what we will end up with after this first step.

The arcs and text pulled apart

We need to a new variable and apply a transform to both the arcs and the text elements. The new variable is pullOutSize. This defines how many pixels the left side will be moved even further to the left and how many pixels the right side will be moved further to the right. Thus the total distance that is created between the two pieces is twice the pullOutSize.



//How many pixels should the slices be pulled from the center
var pullOutSize = 50;

Next, we have to add a transform to the arcs that will translate all the arcs in the desired direction. As you can see in the code below, it’s just one addition to the standard code to create an arc. For the arcs on the right side, the variable pullOutSize needs to a positive number, but for the left side pullOutSize needs to be a negative number. Therefore there is a small if/else statement that looks if the starting angle of an arc is already past half of the circle (π) and then applies the right sign to pullOutSize. For convenience I save the value of pullOutSize (with correct sign, positive or negative) in the data itself.



//Add the transform step to pull the arcs away from the center
svg.append("g").selectAll("path")
	.data(chord.groups)
  .enter().append("path")
	.style("fill", function(d) { return fill(d.index); })
        .style("stroke", function(d) { return fill(d.index); })
	.attr("d", arc)
	.attr("transform", function(d, i) {
		//The pullOutSize should be added to the arcs on the right and subtracted from the arcs on the left
		//Therefore check of the starting angle is larger than half of a circle to figure out when to flip between these two options
		//Save the pullOutSize in the data so it can be use again for the text in a following step
		d.pullOutSize = pullOutSize * ( d.startAngle + 0.01 > Math.PI ? -1 : 1); //The 0.01 is for rounding errors
		return "translate(" + d.pullOutSize + ',' + 0 + ")";
	});

And now the text. This section already includes a transform step so we need to be careful on how to add the new translation without messing with the other transform steps. The original code first rotates the text and then moves the text outward (which then happens along a radial outward from the circle because of the rotation that was performed first) and finally flips the text if it is located on the left side of the circle.

I found that this needed to change to get the result that I was after. I first had to place the text at the location where the original arc is located, then translate it further to the right/left, then rotate and finally flip it if it is on the left side. Thankfully, the d3.svg.arc() has a very useful option. You can request where the location is of the center of an arc with the .centroid() function. What this function returns is the x and y location. Very useful since I only need to add (or subtract) pullOutSize from the x location. Next, I rotate the text as in the original first transform step. Because the text is already located at the arc, we do not need to push it very far out, so the third step is no longer “innerRadius + 55“, but only “55“. Finally we do still need to flip the text on the left side of the circle. Right below the code you can see what each transform step does to the text. Step 1 is the result by applying only the first line of the transform step “translate(” + (c[0] + d.pullOutSize) + “,” + c[1] + “)“. Step 2 is the result of applying the first and second line, and so on. I hope this makes the code easier to understand.



/*
//This is the original Chord Diagram code to create the labels along the arcs
//Already including the "offset" in the .each function
svg.append("text")
      .each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2 + offset; })
      .attr("dy", ".35em")
      .style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
      .attr("transform", function(d) {
	    return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
            + "translate(" + (innerRadius + 55) + ")"
            + (d.angle > Math.PI ? "rotate(180)" : "");
      })
      .text(function(d,i) { return Names[i]; });
*/
	  
//Next to the offset already present, change the transform step to incorporate the pulling apart of the two halves of the circle
svg.append("text")
	.each(function(d) { d.angle = ((d.startAngle + d.endAngle) / 2) + offset;})
	.attr("dy", ".35em")
	.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
	.attr("transform", function(d,i) { 
	     var c = arc.centroid(d); //Where does the center of the arc fall
	     return "translate(" + (c[0] + d.pullOutSize) + "," + c[1] + ")" //First move the arc pullOutSize away from the original location along a horizontal line
	     + "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" //Stil the same
	     + "translate(" + 55 + ",0)" //Changed because of the first translate already applied. How far should the label be placed away from the arc itself
	     + (d.angle > Math.PI ? "rotate(180)" : "") //Stil the same
	})
	.text(function(d,i) { return Names[i]; });

The four steps in the new translation part of the text elements

Step 5 – How to make the chords touch the pulled apart arcs again

Now we’re almost there. Just need to fix those chords… The problem is that the chords need to be drawn differently, not transformed. I knew that I had to dive into the d3.svg.chord() function itself for this. There was no useful function, such as the .centroid function before, that could help me out (at least, not that I could find). This was the first time that I really went into the D3.js code. First I pulled all of the essential d3.svg.chord() lines from the complete library and then tried to understand exactly what each piece was doing, what its result was.

Below you can see a screenshot where I was figuring the code out. I had placed different colored dots in the corners of one chord so I understood how each of these dots would have to change to pull them apart. Let me spare you the details of how I finally managed to actually make it work (I truly surprised myself when I had made the adjustment, refreshed and saw that it all worked O_O. I was giddy the remainder of the day). All I can say is that I learned a lot about the workings of SVG paths (M’s, Q’s, A’s and Z’s) thanks to this excellent tutorial that I have forgotten now that we’re a few weeks further.

Drawing one chord and placing points on the corners while investigating the d3.svg.chord() function

The upside is that all you have to do to get the chords to be drawn in a stretched form is download the new d3.stretched.chord.js function and then replace d3.svg.chord() in your path variable with stretchedChord(). And you also supply the variable that contains the pull out (I’ve used pullOutSize throughout) to the .pullOutSize() option. Yup, that’s all. Strange, how all the previous steps were changes throughout the script and then the most difficult of all could be caught off in a new function for which you need to adjust one line (and load the script of course) :)



var path = stretchedChord() //replaced d3.svg.chord() with the custom chord function
	.radius(innerRadius)
	.startAngle(startAngle)
	.endAngle(endAngle)
	.pullOutSize(pullOutSize); //supply the variable that contains the pull out amount

By changing the pullOutSize variable you can stretch or squeeze the left and right halves as far as you like.

Pulling the pieces apart took quite some time, but when I compare the non stretched version with the stretched one I really do like it a lot better when the two halves are further apart (instead of forming a perfect circle).

Using a custom function to draw the Chords to create the final result

For the few who might be wondering what happened with the two dummy arcs and the chord that connected them after all those transformations I created the image below. As you can see, they are still there, but it’s a good thing that they are invisible :)

Whatever happened to those dummy arcs and that dummy chord...

Step 6 – Optional: How to apply a more visually appealing sorting to the chords

One final tiny adjustment. Right now the chords get sorted from thickest to thinnest chord along an arc. But it would be better to just leave them be, so that the first chord of A flows to X, the middle one of A goes to Y and the bottom one of A goes to Z to remove the number of overlaps. Just removing the .sortSubgroups(d3.descending) line from the d3.layout.chord() function sadly does the reverse. The top chord of A goes to Z and the bottom goes to X, so a lot of overlap. I tried to use the option to apply a custom sort function to the chords (sortSubgroups), but you can only use the values of the chords themselves, not the indices of the chords. Therefore, to get what I wanted, I again had to dive into the D3.js source code, take out the complete d3.layout.chord() function and literally add 10 characters, namely a “.reverse()“, and remove the .sortSubgroups line in the front-end to make it work. In case you want to use it as well, you can download the adjusted d3.layout.chord.sort.js and make the following adjustment to your code:



//Call the custom layout function instead of d3.layout.chord()
var chord = customChordLayout() 
	.padding(.02)
	.sortChords(d3.descending)
	.matrix(matrix);
//Note that you no longer call .sortSubgroups(d3.descending), this would mess the sorting up again

Adjusted ordering to create a more aesthetically pleasing overlap of chords

The code for all intermediate steps

That was quite a long tutorial and I am far from sure that anybody else ever wants to create a circular Flow chart. But in case there is somebody, I hope the steps above will help you in creating it. I’d love to hear any thoughts or comments that you might have!

Prev / Next Post
Comments (16)
  • Patrick Gunderson - September 1, 2015 - Reply

    The virtual scroll on this site makes it nauseating to use on a mac. Smooth scroll is built in to the OS and needs to get shut off or else the motion disconnects from the tactile sense given by the OS and gives the same feeling as a VR headset with lag.

    • Nadieh
      (Author) Nadieh - September 1, 2015 - Reply

      Thank you for telling me. I’ve turned the smooth scroll function off and hope this has improved the experience!

      • Patrick Gunderson - September 1, 2015 - Reply

        Thank you!! so much better!

  • Brett Uglow - September 16, 2015 - Reply

    This is an outstanding use of D3! Is the code open source?

    • Nadieh
      (Author) Nadieh - September 16, 2015 - Reply

      Thank you! You can find links to all of the code that was used in the last section of the blog

  • Sinh - November 5, 2015 - Reply

    Great tutorial — this kind of graph make the chord which already contain a lot of information more clear and easier to read!

    Thank you so much!

  • Mike - January 19, 2016 - Reply

    Love this chord! So much!

    I finally got mine sorted, but am having one small issue.. ive sent you an email but thought id post here too.. I have one arc thats wandered off and run away on its own, but the chord lines are going to the right place.. any ideas?

    http://b2resource.com/rangers/tests/arc/index.html

    • Nadieh
      (Author) Nadieh - January 23, 2016 - Reply

      Hi Mike,

      I just replied to your e-mail before I made the connection that this comment was also from you. The link isn’t working so sadly I cannot see it. But my advice would be the same, try to put the code so far in a block of jsFiddle, that way I can have a look :) (but good that you got yours almost sorted).

      My first guess is that it might have something to do with an if/else statement in the section where the outer arcs are created. Sometimes there are some rounding errors and the start angle of the first slice on the left hand side (at the bottom) might just be smaller than Math.PI. So perhaps you could try to make the number that I add to the d.startAngle a bit larger and see what happens (the 0.001 below)

      g.append(“path”)
      .style(“stroke”, function(d,i) { return (Names[i] === “” ? “none” : “#00A1DE”); })
      .style(“fill”, function(d,i) { return (Names[i] === “” ? “none” : “#00A1DE”); })
      .style(“pointer-events”, function(d,i) { return (Names[i] === “” ? “none” : “auto”); })
      .attr(“d”, arc)
      .attr(“transform”, function(d, i) { //Pull the two slices apart
      d.pullOutSize = pullOutSize * ( d.startAngle + 0.001 > Math.PI ? -1 : 1);
      return “translate(” + d.pullOutSize + ‘,’ + 0 + “)”;
      });

      Good luck!

  • Pratik Jain - April 17, 2016 - Reply

    Hey there!

    First of all, this is a great visualization! It makes the chord diagram very dynamic and interesting. Would it however be possible to use this diagram similarly if both sides do not add up to the same amount? (the total on each side is equal to the total number of respondents, which is the same number)

    • Nadieh
      (Author) Nadieh - April 22, 2016 - Reply

      Thank you Pratik! In theory that would be possible, but you’d have to make changes to the data set up and how the “dummy” arc is calculated and the whole set-up is rotated. So it wouldn’t be very straightforward

  • Rebecca - May 11, 2016 - Reply

    Thank you :)

    I’m trying to rewrite the chord parts of d3.js as well, but running into an unexpected snag. When I separate out d3.layout.chord() and d3.svg.chord() and save them into separate files d3.layout.chord.js and d3.svg.chord.js, the chords don’t plot because they don’t recognize functions declared in d3.js unless a “d3.” is prepended (like d3.d3_source, d3.d3_target, etc). You don’t appear to have this problem; can you explain why?

  • Matt - May 18, 2016 - Reply

    Fantastic work! Wow!

  • Ray - June 27, 2016 - Reply

    Hi Nadieh, this is absolutely awesome!!! Thank you for the tutorial because this really helped me to understand Chord charts. There are very few resources that, if any, that explain how to make a half-chord chart!

    Question, I noticed that when I hover over the chords themselves, they don’t make the other chords opaque. I tried to change it but the results were terrible, every chord would blink randomly, except the one I wanted to keep visible. This is so I can get the hover info from a specific chord connection while the others are invisible, which currently is not the behavior.

    Also, if I wanted to use a color palette, like d3_cat10 or a custom palette for the chords, could you explain how that could be added?

    Thank you for contributing such a fantastic tutorial to the community! I enjoy the visual art that you create and have spent hours looking at your work! Incredibly creative and unique, you are a visualization artist!

  • Jon - July 13, 2016 - Reply

    Hi! Awesome stuff. I have a question similar to Pratik Jain’s. I also have a data set where the two sides are not mirrors of each other. That caused a few issues with the rotations, but I think I got past them. I just needed to calculate the two dummy object arc values in the matrix separately. The rotation was computed for the top arc segment, which got it into the right place, and the second dummy arc was long enough to compensate for the shorter side.

    It’s not perfectly symmetrical, but looks good. I could also distribute the difference in sizes between the two sides between both dummy arcs, which would balance it out a bit more.

    …Anyways, my issue is one that actually happens even if both sides add up to the same amount. All that needs to be done to throw things off is if the start and end values for a chord are different (like how I changed your example matrix below X->C and Y->C).

    var myMatrix = [
    [0, 0, 0, 0, 5, 5, 15, 0], //X
    [0, 0, 0, 0, 10, 15, 20, 0], //Y
    [0, 0, 0, 0, 15, 5, 5, 0], //Z
    [0, 0, 0, 0, 0, 0, 0, emptyStroke], //Dummy stroke
    [10, 5, 15, 0, 0, 0, 0, 0], //C
    [5, 15, 5, 0, 0, 0, 0, 0], //B
    [15, 20, 5, 0, 0, 0, 0, 0], //A
    [0, 0, 0, emptyStroke, 0, 0, 0, 0] //Dummy stroke
    ];

    When that happens, the chords start glitching out. I’m guessing that there is an assumption being made with the chord stretching code that both sides of the chord will be the same length. But I’m stalling on the math there.

    Any pointers you could offer would be great.

    Thanks again!

  • aron - May 27, 2017 - Reply

    Hi Nadieh,
    I think I’ve found a clue to the “glitching” chords that several commenters have asked about. If I remove padding, like so:

    let chord = customChordLayout()
    // .padding(.02) // <<<< problem value…
    .sortChords(d3.descending)
    .matrix(matrix);

    the layout is split correctly no matter how many values are on each side. I think d3-layout-chord-sort-js is not accounting for the padding somehow?

  • aron - June 10, 2017 - Reply

    I figured out my source of “glitching” chords – it was not the padding.
    I had a non-uniform matrix – I was supposed to have a 12 x 12 matrix, but in fact my last four rows were length 13.

    I was trying to filter out some of my dataset, and doing it wrong.

    When this happened, the ‘spacer’ arcs became visible, or some of the interior chords were drawn to the spacer arcs.

    Hope this helps some other users of this excellent viz – check your matrix!

Leave a Reply