top of page

How to Create a Gradient Chord Chart in Tableau

The following blog by Brian Moore was originally published on Do Mo(o)re With Data November 28, 2022 and is cross-posted here with permission. Brian is a Tableau Visionary, Tableau Public Ambassador, and a Senior Data Analytics and Viz Consultant for Cleartelligence.


Chord charts are a great way to display and quantify relationships between entities, but there are some limitations. Recently, I built a series of chord charts to show which actors have appeared together in films, and how often. Once I had built out the charts and attempted to add some color, I hit a wall. In some cases, when building these relationship diagrams, there is a logical directional flow to the relationship. Something moves from Entity 1 (Source) to Entity 2 (Target). In those cases, its easy to assign meaningful color to the chart. You can assign it by Source if you want to highlight where things are flowing from, or you can assign it by Target if you want to highlight where things are flowing to. But what if there is no flow to the relationship? How do you assign color then?


This roadblock got me thinking. If I want to add a unique color to each Entity in my chart, and there is no directional flow, then the color of the chord should be a blend of the two colors. Or better yet, it should transition from one color to the other. I have seen others do some really cool stuff with applying gradients to “solid” chart types. Ken Flerlage has an excellent post here about applying gradients to Bar Charts and Area Charts. There’s another great post from Ivett Kovacs about gradients here. Some different techniques, but the foundation is the same. If you want to apply a gradient to an otherwise “solid” chart, use a series of thin lines to “color in” the chart. So that’s exactly what I did.


I’ll warn you ahead of time, this is going to be a long post. But if you’re just interested in building one of these charts, and not so much in how to build it, I’ve got some templates for you to use. Just follow the instructions in the “Setting Up Your Data Source” section below, and swap out the data source in the template workbook. You can download the template here, and download the sample data here.


Setting Up Your Data Source

In the sample data (link above), there are two tabs; one for our data, and one for densification. You do not need to make any changes to the Densification table, but I’ll talk through the setup as we build each section of this chart. In the Base Data, you should only update the first four columns and not the calculated fields (red text) in the next five columns. Here is a quick explanation of each field and what it represents


From_ID: Unique Identifier for each Source entity. This should be a sequential number starting at 1 and ending at however many entities you have in your data. For each entity, you may have multiple rows, one for each relationship, but they should all have the same From_ID.


From_Name: A display name for each Source entity.


To_Name: A display name for the other entity in the relationship, or the “Target”.


Value: The measure being displayed. In the sample data, this value field represents the number of movies that the two actors appeared in together.

The following fields should not be changed. They will update automatically based on the first 4 columns, but here is a quick description of what they are calculating.


To_ID: This is a lookup to get the correct ID for the Target entity


From_Total: This is a sum of the Value field for each Source entity


From_Run_Total: This is a running total of the Value field


Unique_Relationship: This is a unique identifier for the relationship


Unique_Relationship_Order: This is used to identify the first and second occurrence of each Unique_Relationship (there will be two rows for each relationship, one where Actor 1 is the Source and Actor 2 is the Target, and one where Actor 2 is the Source and Actor 1 is the Target).


Building the Chart

This chart is actually comprised of 4 different sections, identified by the ShapeType field in the densification table. There are the outer polygons (Outer_Poly), the gradient chords (Inner_Lines), the borders for the gradient chords (Inner_Lines_Border), and a small rounded polygon on the end of each chord (Lines_End_Poly) to fill the gap between the chords and the outer polygons.

Before we start working on any of these individual sections, there are a number of calculations that we are going to need for all of them.


First, let’s create a parameter that will let us control the spacing between each of the outer polygons. Call this parameter [Section Spacing], set the data type to Float, and set the value very low, around .01. Once you have the chart built you can set this higher for more spacing, or lower for less spacing.


Now let’s use that parameter, along with the Max of our running total field from the data source (which represents the grand total of the [Value] field), to calculate the width of our spacing between polygons. We’ll call this [Section Spacing Width]

Section Spacing Width = [Section Spacing]*{MAX([From Run Total])}

Now we need an adjusted grand total that accounts for all of the spaces as well. We’ll call this [Max Run Total – Adj] and it will be our grand total plus the number of spaces * the width of the spaces.


Max Run Total – Adj = {MAX([From Run Total])+MAX([From ID]*[Section Spacing Width])}

Next, we want to calculate the position around the circle where each of our sections, or entities, will start. We’ll do this by subtracting the [Value] field from the running total, adding the spacing for all previous sections, and then dividing that by our adjusted grand total. Call this field [Section_Start].

Section_Start = { FIXED [From ID] : MIN(([From Run Total]-[Value]) + (([From ID]-1)*[Section Spacing Width]))} / [Max Run Total – Adj]

Now we need to do the same thing to calculate the position around the circle where each section ends. The only difference between this and the previous calc, is that we are going to use the running total without subtracting the value. Call this field [Section_End]

Section_End = { FIXED [From ID] : MAX([From Run Total] + (([From ID]-1)*[Section Spacing Width]))} / [Max Run Total – Adj]

Now for an easy one. Let’s calculate the width of each section by subtracting the [Section_Start] from the [Section_End]. Call this [Section_Width].

Section_Width = [Section_End]-[Section_Start]

Next, we need to do the same thing to get the start and end positions for each of the “sub-sections”, or each of the entity’s relationships. The calculations are almost identical, the only difference is that we are fixing the Level of Detail calculations on [From_ID] and [To_ID], instead of just [From_ID]. Call these calculations [From_SubSection_Start] and [From_SubSection_End].

From_SubSection_Start = { FIXED [From ID],[To ID] : MIN(([From Run Total]-[Value]) + (([From ID]-1)*[Section Spacing Width]))} / [Max Run Total – Adj]
From_SubSection_End = { FIXED [From ID],[To ID] : MAX([From Run Total] + (([From ID]-1)*[Section Spacing Width]))} / [Max Run Total – Adj]

And just like before, we’ll create a simple calculation to get the “width” of these sub-sections. Call this calculation [SubSection_Width].

SubSection_Width = [From_SubSection_End]-[From_SubSection_Start]

Next, we need to do the same thing, but need to calculate the start and end position for the other end, or Target, of each relationship. The calculations are the same as above except we’ll use the [To_Run_Total] instead of [From_Run_Total] and [To_ID] instead of [From_ID]. Call these calculations [To_SubSection_Start] and [To_SubSection_End].

To_SubSection_Start = { FIXED [From ID],[To ID] : MIN(([To Run Total]-[Value]) + (([To ID]-1)*[Section Spacing Width]))} / [Max Run Total – Adj]

To_SubSection_End = { FIXED [From ID],[To ID] : MAX([To Run Total] + (([To ID]-1)*[Section Spacing Width]))} / [Max Run Total – Adj]

And finally, we need a simple calculation to get the total number of points for each of our shape types. Call this calculation [Max Point].

Max Point = { FIXED [Shape Type] : MAX([Points])}

Densification

Before we move on, let’s take a look at our densification table. In this table, we have 5 fields.


Shapetype: Corresponds to the 4 sections of the chart mentioned previously.


Points: Used, along with Side, to calculate the positions of every point, in every element of the chart.


Order: Used on Path to tell Tableau how to connect our Points.


Side: Used to differentiate between the interior and exterior “lines” for the outer polygons and chords.


Line ID: Used on Detail to segment our lines appropriately. For all sections of the chart, other than the Inner_Lines, this value will be 1, since we want one continuous line for the polygons, and for the borders of the chords. For the Inner_Lines, we have values from 1 to 1,000, so we can “color” our chart with up to 1,000 lines per chord.


These fields are used in slightly different ways in each section of the chart, so we’ll talk about them more as we start building.


Building the Outer Polygons

First, let’s take another quick look at our densification table. For the outer polygons, identified by Shapetype=Outer_Poly, we have a total of 50 records. The Order field, used to tell Tableau how to connect the points, is a sequential number from 1 to 50. Then there is the Points field, which is used to calculate the position of each point around the circle. This number goes from 1 to 25, where the Side field = Min, and repeats where the Side field = Max. This will allow us to draw two parallel lines (Min and Max), and then connect them together to create those outer polygons. And lastly, the Line ID has a value of 1 because we are “drawing” one continuous “line” for each of these polygons. Clear as mud right? Here’s a quick illustration to help visualize how these fields function together.

Before we start building the calculations for this section, we need a few parameters. The first is just going to be a generic value for the radius of our circle. The second, is going to determine the thickness of these outer polygons.


Create a parameter called [Radius], set the data type to Float and set the Current value to 10. This can be any number you want, it doesn’t really matter, we just need something to determine the size of our chart.


Next, create a parameter called [Outer_Poly_Thickness], set the data type to Float, and set the Current value to 1. You can play around with this number to get the thickness you want, but I would recommend setting it somewhere around 10% of the [Radius] value.


Now for our calculations. Going back to Part 1 of the Fun with Curves in Tableau Series, we know that we need 2 inputs to plot points around a circle. We need the distance of each point from the center of the circle, and we need the position of each point around the circle. Let’s start with the distance.


In the image from earlier in this section, we see that we need two lines that follow the same path, one for Min and one for Max. So these two lines will have different distances from the center. For the Min line, we’ll just use the [Radius] of our circle. For the Max line, we’ll use the [Radius] plus our [Outer_Poly_Thickness] parameter. Create a calculated field called [Outer_Poly_Distance].

Outer_Poly_Distance = IF [Side]=”Min” THEN [Radius] ELSE [Radius]+[Outer_Poly_Thickness] END

Next, we need to calculate the position of the points. Each of our polygons are going to be different lengths, depending on the [Value] field in our data source, so we need to calculate the spacing of each point (to evenly space 25 points along the two “lines”). Earlier, we calculated the start and end position for each of these polygons, and we’ll use those to calculate the spacing, or “width”, that each point covers. Create a calculated field called [Outer_Poly_Point_Width].

Outer_Poly_Point_Width = [Section_Width]/([Max Point]-1)

And now we’ll use that, along with the start position of each polygon, to calculate the position for each of our 25 points. Point 1 will be positioned at the start of the polygon, Point 25 will be positioned at the end of the polygon, and the remaining 23 points will be equally spaced between those two points. Call this calculation [Outer_Poly_Point_Position].

Outer_Poly_Point_Position = [Section_Start]+(([Points]-1)*[Outer_Poly_Point_Width])

Now we have the two inputs needed to calculate all of our points for these outer polygons. We just need to plug them into our X and Y calculations.