Skip to content
Star Trek Chord Viz
import pandas as pd
import numpy as np
# Read the CSV file into a pandas DataFrame
df = pd.read_csv('hero_network.csv', usecols=['hero1', 'hero2'])
# Get unique heroes
unique_heroes = sorted(set(df['hero1']).union(set(df['hero2'])))
heroes_dict = {hero: i for i, hero in enumerate(unique_heroes)}
# Create an empty matrix filled with zeros using numpy for better performance
matrix = np.zeros((len(unique_heroes), len(unique_heroes)), dtype=int)
# Fill in the matrix based on the relationships in the DataFrame
for _, row in df.iterrows():
hero1_index = heroes_dict[row['hero1']]
hero2_index = heroes_dict[row['hero2']]
matrix[hero1_index, hero2_index] += 1
matrix[hero2_index, hero1_index] += 1
# Display the matrix for a chord diagram
matrix
matrix.shapefrom IPython.display import display, HTML
import json
import numpy as np
# Convert the numpy ndarray into a list of lists, which is JSON serializable
matrix_list = matrix.tolist()
matrix_json = json.dumps(matrix_list)
# HTML template for the Jupyter notebook
html_template = f"""
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {{
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
text-align: center;
}}
.chord-diagram {{
fill-opacity: .67;
}}
.chord-diagram path {{
stroke: #000;
stroke-width: .25px;
}}
.chord-diagram text {{
font-size: 10px;
}}
</style>
<svg class="chord-diagram"></svg>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
// Chord chart data
var matrix = {matrix_json};
var width = 500,
height = 500,
outerRadius = Math.min(width, height) / 2 - 10,
innerRadius = outerRadius - 24;
var svg = d3.select(".chord-diagram")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var chord = d3.chord()
.padAngle(0.05)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var ribbon = d3.ribbon()
.radius(innerRadius);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var g = svg.selectAll(".group")
.data(chord(matrix).groups)
.enter().append("g")
.attr("class", "group");
g.append("path")
.style("fill", function(d) {{ return color(d.index); }})
.style("stroke", function(d) {{ return d3.rgb(color(d.index)).darker(); }})
.attr("d", arc);
g.append("text")
.each(function(d) {{ d.angle = (d.startAngle + d.endAngle) / 2; }})
.attr("dy", ".35em")
.attr("transform", function(d) {{
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (innerRadius + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
}})
.style("text-anchor", function(d) {{ return d.angle > Math.PI ? "end" : null; }})
.text(function(d) {{ return "Hero " + d.index; }});
svg.selectAll(".chord")
.data(chord(matrix).chords)
.enter().append("path")
.attr("class", "chord")
.style("fill", function(d) {{ return color(d.source.index); }})
.attr("d", ribbon);
</script>
"""
# Display the HTML template in the notebook
display(HTML(html_template))from IPython.display import display, HTML
# HTML template for the Jupyter notebook
html_template = """
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v6.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
// create the svg area
const svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", 600)
.attr("height", 600)
.append("g")
.attr("transform", "translate(300,300)")
// create a matrix
const matrix = [
[0,0,0,17,8,4,0,15,3,0,0], //TOS
[19,0,0,0,0,0,0,3,1,0,0], //TAS
[2,0,0,5,0,0,0,0,0,0,0], //TNG
[12,0,17,0,3,0,0,0,0,0,0], //DS9
[3,0,8,4,0,0,0,0,0,0,0], //VOY
[4,0,4,0,0,0,0,0,0,0,0], //ENT
[8,0,0,0,0,0,0,0,0,0,0], //DIS
[4,3,15,7,5,1,0,0,0,0,0], //LD
[13,1,0,0,0,0,1,5,0,0,0], //SNW
[1,0,3,1,2,0,0,0,0,0,0], //PRO
[0,0,19,1,4,0,0,0,0,0,0] //PIC
];
// 10 groups, so create a vector of 10 colors
const colors = [ "#ff595e", "#ff924c", "#ffca3a", "#c5ca30","#8ac926", "#36949d", "#1982c4", "#4267ac","#565aa0", "#6a4c93", "#2379"
]
const names = ["TOS", "TAS", "TNG", "DS9", "VOY", "ENT", "DIS", "LD","SNW","PRO","PIC"];
// give this matrix to d3.chord(): it will calculates all the info we need to draw arc and ribbon
const res = d3.chord()
.padAngle(0.05)
.sortSubgroups(d3.descending)
(matrix)
// add the groups on the outer part of the circle
svg
.datum(res)
.append("g")
.selectAll("g")
.data(function(d) { return d.groups; })
.join("g")
.append("path")
.style("fill", (d,i) => colors[i])
.style("stroke", "black")
.attr("d", d3.arc()
.innerRadius(200)
.outerRadius(210)
)
// Add the labels around the chord diagram
svg.append("g")
.selectAll("text")
.data(res.groups)
.join("text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", ".35em")
.attr("transform", d => `
rotate(${(d.angle * 180 / Math.PI - 90)})
translate(${210 + 10})
${d.angle > Math.PI ? "rotate(180)" : ""}
`)
.attr("text-anchor", d => d.angle > Math.PI ? "end" : null)
.text((d, i) => names[i])
.style("font-family", "sans-serif")
.style("font-size", 10);
// Add the links between groups
svg
.datum(res)
.append("g")
.selectAll("path")
.data(d => d)
.join("path")
.attr("d", d3.ribbon()
.radius(200)
)
.style("fill", d => colors[d.source.index]) // colors depend on the source group. Change to target otherwise.
.style("stroke", "none"); // Removed the black outline on chords
</script>
"""
# Display the HTML template in the notebook
display(HTML(html_template))