Skip to content

Instantly share code, notes, and snippets.

@danhammer
Last active May 6, 2019 03:33
Show Gist options
  • Select an option

  • Save danhammer/daea28ba8dff57e0a9d8d11b00b00ca3 to your computer and use it in GitHub Desktop.

Select an option

Save danhammer/daea28ba8dff57e0a9d8d11b00b00ca3 to your computer and use it in GitHub Desktop.
star wars
height: 1000
license: mit

This Chord diagram shows the final result of the example used in my blog on Data-based orientations for gradients in a d3.js Chord Diagram

What you see here are movie collaborations between the Avengers in the MCU (up until & including Thor Ragnarok). Because collaborations are symmetrical, the chords are all as thick at the beginnen as at the end. Therefore, I wanted each chord to represent both the Avengers that it connects through a gradient. Because all the chords run in different directions, I had to set up the orientation of each gradient by using the chord.chords() dataset

In this end result I've made the Avenger names wrap around the arcs (as explained in another blog on Placing Texts on Arcs with D3.js ) and added a simple tooltip on a hover

You can find the steps leading up to this one here

forked from nbremer's block: Data based orientations in SVG Gradients - Final example - Avenger Movie Collaborations

/* ========================================================================
* Bootstrap: popover.js v3.1.1
* http://getbootstrap.com/javascript/#popovers
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// POPOVER PUBLIC CLASS DEFINITION
// ===============================
var Popover = function (element, options) {
this.init('popover', element, options)
}
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
placement: 'right',
trigger: 'click',
content: '',
template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
})
// NOTE: POPOVER EXTENDS tooltip.js
// ================================
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
Popover.prototype.constructor = Popover
Popover.prototype.getDefaults = function () {
return Popover.DEFAULTS
}
Popover.prototype.setContent = function () {
var $tip = this.tip()
var title = this.getTitle()
var content = this.getContent()
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
$tip.find('.popover-content')[ // we use append for html objects to maintain js events
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
](content)
$tip.removeClass('fade top bottom left right in')
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
}
Popover.prototype.hasContent = function () {
return this.getTitle() || this.getContent()
}
Popover.prototype.getContent = function () {
var $e = this.$element
var o = this.options
return $e.attr('data-content')
|| (typeof o.content == 'function' ?
o.content.call($e[0]) :
o.content)
}
Popover.prototype.arrow = function () {
return this.$arrow = this.$arrow || this.tip().find('.arrow')
}
Popover.prototype.tip = function () {
if (!this.$tip) this.$tip = $(this.options.template)
return this.$tip
}
// POPOVER PLUGIN DEFINITION
// =========================
var old = $.fn.popover
$.fn.popover = function (option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.popover')
var options = typeof option == 'object' && option
if (!data && option == 'destroy') return
if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.popover.Constructor = Popover
// POPOVER NO CONFLICT
// ===================
$.fn.popover.noConflict = function () {
$.fn.popover = old
return this
}
}(jQuery);
////////////////////////////////////////////////////////////
//////////// Custom Chord Layout Function //////////////////
/////// Places the Chords in the visually best order ///////
///////////////// to reduce overlap ////////////////////////
////////////////////////////////////////////////////////////
//////// Slightly adjusted by Nadieh Bremer ////////////////
//////////////// VisualCinnamon.com ////////////////////////
////////////////////////////////////////////////////////////
////// Original from the d3.layout.chord() function ////////
///////////////// from the d3.js library ///////////////////
//////////////// Created by Mike Bostock ///////////////////
////////////////////////////////////////////////////////////
customChordLayout = function() {
var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
function relayout() {
var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
var numSeq;
chords = [];
groups = [];
k = 0, i = -1;
while (++i < n) {
x = 0, j = -1, numSeq = [];
while (++j < n) {
x += matrix[i][j];
}
groupSums.push(x);
//////////////////////////////////////
////////////// New part //////////////
//////////////////////////////////////
for(var m = 0; m < n; m++) {
numSeq[m] = (n+(i-1)-m)%n;
}
subgroupIndex.push(numSeq);
//////////////////////////////////////
////////// End new part /////////////
//////////////////////////////////////
k += x;
}//while
k = (τ - padding * n) / k;
x = 0, i = -1;
while (++i < n) {
x0 = x, j = -1;
while (++j < n) {
var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
subgroups[di + "-" + dj] = {
index: di,
subindex: dj,
startAngle: a0,
endAngle: a1,
value: v
};
}//while
groups[di] = {
index: di,
startAngle: x0,
endAngle: x,
value: (x - x0) / k
};
x += padding;
}//while
i = -1;
while (++i < n) {
j = i - 1;
while (++j < n) {
var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
if (source.value || target.value) {
chords.push(source.value < target.value ? {
source: target,
target: source
} : {
source: source,
target: target
});
}//if
}//while
}//while
if (sortChords) resort();
}//function relayout
function resort() {
chords.sort(function(a, b) {
return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
});
}
chord.matrix = function(x) {
if (!arguments.length) return matrix;
n = (matrix = x) && matrix.length;
chords = groups = null;
return chord;
};
chord.padding = function(x) {
if (!arguments.length) return padding;
padding = x;
chords = groups = null;
return chord;
};
chord.sortGroups = function(x) {
if (!arguments.length) return sortGroups;
sortGroups = x;
chords = groups = null;
return chord;
};
chord.sortSubgroups = function(x) {
if (!arguments.length) return sortSubgroups;
sortSubgroups = x;
chords = null;
return chord;
};
chord.sortChords = function(x) {
if (!arguments.length) return sortChords;
sortChords = x;
if (chords) resort();
return chord;
};
chord.chords = function() {
if (!chords) relayout();
return chords;
};
chord.groups = function() {
if (!groups) relayout();
return groups;
};
return chord;
};
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>shift7 character connectivity</title>
<!-- D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<!-- Google Fonts -->
<link href='https://fonts.googleapis.com/css?family=Bangers' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Oswald' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Lato:400,900' rel='stylesheet' type='text/css'>
<!-- jQuery -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<!-- Two scripts to make the Bootstrap tooltip follow the mouse movement
taken from https://github.com/ghophp/bootstrap-popover-move -->
<script src="bootstrap.tooltip.js"></script>
<script src="bootstrap.popover.js"></script>
<script src="d3.layout.chord.sort.js"></script>
<style>
.stripe {
width: 200px;
padding:80px 0px;
position: absolute;
height: 0vh;
min-height: 100px;
display: flex;
justify-content: center;
align-items: center;
}
#light {
background: #e8e8e8;
}
#dark {
background: #131313;
}
.container {
display: flex;
justify-content: space-around;
align-items: flex-end;
width: 200px;
margin: 0 auto;
background: #fff;
border-radius: 10px;
}
.person {
width: 196px;
height: 196px;
position: relative;
overflow: hidden;
}
/* Finn */
#finn .body {
position: absolute;
width: 100px;
height: 150px;
background: transparent;
top: 130px;
left: 50%;
transform: translateX(-50%);
border-radius: 40px;
z-index: 1;
overflow: hidden;
}
#finn .body:before {
content: "";
position: absolute;
background: #000102;
left: 50%;
transform: translateX(-50%);
width: 50%;
height: 100%;
}
#finn .face {
position: absolute;
width: 80px;
height: 75px;
background: #965c48;
border-radius: 50%;
top: 55px;
left: 50%;
transform: translateX(-50%);
z-index: 3;
}
#finn .hair {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 43px;
width: 80px;
height: 80px;
background: #33292a;
border-radius: 50%;
}
#finn .ear {
position: absolute;
background: #965c48;
width: 26px;
height: 26px;
border-radius: 100%;
left: 50px;
top: 76px;
}
#finn .ear.right {
left: 120px;
}
#finn .ear:before {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 16px;
height: 16px;
background: #744737;
border-radius: 100%;
}
#finn .neck {
position: absolute;
z-index: 2;
width: 30px;
height: 50px;
border-radius: 12px;
left: 50%;
transform: translateX(-50%);
top: 90px;
background: #744737;
}
#finn .eyes {
position: absolute;
top: 30px;
width: 10px;
height: 10px;
background: #2f1d16;
border-radius: 50%;
left: 25%;
transform: translateX(-50%);
}
#finn .eyes:after {
content: "";
position: absolute;
width: 10px;
height: 10px;
background: #2f1d16;
top: 0;
border-radius: 50%;
right: -40px;
}
#finn .nose {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 8px;
height: 14px;
background: #744737;
top: 31px;
border-radius: 4px;
}
#finn .mouth {
position: absolute;
background: #2f1d16;
width: 30px;
height: 4px;
border-radius: 0 0 5px 5px;
top: 55px;
left: 50%;
transform: translateX(-50%);
}
#finn .jacket {
background: #d1a868;
position: absolute;
top: 0;
width: 30%;
height: 100%;
}
#finn .jacket-left {
left: 0;
}
#finn .jacket-right {
right: 0;
}
#finn .jacket:after {
content: "";
width: 6px;
position: absolute;
top: 0;
height: 110%;
background: #a27631;
}
#finn .jacket-left:after {
left: 100%;
transform: translateX(-2px);
}
#finn .jacket-right:after {
right: 100%;
transform: translateX(2px);
}
#finn .jacket-left:before {
content: "";
background: #a61c19;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 20px;
}
#finn .jacket-right:before {
content: "";
background: #a61c19;
position: absolute;
top: 20px;
left: 0;
width: 100%;
height: 10px;
}
/* Rey */
#rey .body {
position: absolute;
width: 100px;
height: 150px;
background: transparent;
top: 130px;
left: 50%;
transform: translateX(-50%);
border-radius: 40px;
z-index: 3;
overflow: hidden;
}
#rey .fold-left {
position: absolute;
top: 0px;
left: -30px;
width: 100%;
height: 100%;
background: #bdb5b4;
transform: rotate(-30deg);
}
#rey .fold-right {
position: absolute;
top: 0px;
right: -30px;
width: 100%;
height: 100%;
background: #b0a8a7;
transform: rotate(30deg);
}
#rey .face {
position: absolute;
width: 80px;
height: 80px;
background: #f0beaf;
border-radius: 50%;
top: 50px;
left: 50%;
transform: translateX(-50%);
z-index: 3;
}
#rey .hair {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 40px;
width: 80px;
height: 50px;
background: #363435;
border-radius: 50% 50% 0 0;
}
#rey .hair:before {
content: "";
position: absolute;
width: 30px;
height: 30px;
border-radius: 50%;
left: 50%;
top: -10px;
background: #363435;
transform: translateX(-50%);
}
#rey .bangs {
position: absolute;
left: 50%;
top: 40px;
width: 60px;
height: 20px;
background: #363435;
z-index: 4;
transform: translateX(-50%);
border-radius: 50% 50% 50% 50%;
}
#rey .hair-left {
position: absolute;
background: #363435;
height: 30px;
width: 4px;
top: 66px;
left: 56px;
z-index: 6;
border-radius: 0% 0% 5px 5px;
transform: rotate(8deg);
}
#rey .hair-right {
position: absolute;
background: #363435;
height: 30px;
width: 4px;
top: 66px;
right: 56px;
z-index: 6;
border-radius: 0% 0% 5px 5px;
transform: rotate(-8deg);
}
#rey .neck {
position: absolute;
z-index: 2;
width: 30px;
height: 60px;
border-radius: 12px;
left: 50%;
transform: translateX(-50%);
top: 110px;
background: #e89b84;
}
#rey .eyes {
position: absolute;
top: 34px;
width: 10px;
height: 10px;
background: #424b54;
border-radius: 50%;
left: 25%;
transform: translateX(-50%);
}
#rey .eyes:after {
content: "";
position: absolute;
width: 10px;
height: 10px;
background: #424b54;
top: 0;
border-radius: 50%;
right: -40px;
}
#rey .nose {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 8px;
height: 14px;
background: #db9b99;
top: 36px;
border-radius: 4px;
}
#rey .mouth {
position: absolute;
background: #424b54;
width: 30px;
height: 8px;
border-radius: 0 0 100px 100px;
top: 60px;
left: 50%;
transform: translateX(-50%);
}
#rey .lightsaber {
position: absolute;
bottom: -30px;
left: 30%;
width: 10px;
height: 0;
background: #4fc3f7;
z-index: 10;
border-radius: 4px 4px 0 0;
transform: rotate(30deg);
animation-name: saber;
animation-duration: 6s;
animation-timing-function: linear;
animation-delay: 1s;
animation-direction: normal;
animation-iteration-count: infinite;
transform-origin: left bottom;
}
@keyframes saber {
0% {
height: 0;
}
5% {
height: 160px;
}
55% {
height: 160px;
}
60% {
height: 0;
}
100% {
height: 0;
}
}
/* Poe */
#poe .body {
position: absolute;
width: 100px;
height: 150px;
background: #ea6b26;
top: 130px;
left: 50%;
transform: translateX(-50%);
border-radius: 40px;
z-index: 1;
overflow: hidden;
}
#poe .flight-suit {
position: absolute;
left: 50%;
width: 80px;
top: 130px;
height: 150px;
transform: translateX(-50%);
background: #d2d5c2;
z-index: 2;
border-radius: 15px 15px 0 0;
overflow: hidden;
}
#poe .flight-suit:before {
content: "";
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 36px;
height: 20px;
background: #ea6b26;
border-radius: 0 0 6px 6px;
}
#poe .flight-suit:after {
content: "";
position: absolute;
top: 16px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 30px;
background: #fffffe;
border-radius: 2px;
}
#poe .tube {
position: absolute;
background: #35302d;
width: 6px;
height: 40px;
top: 30px;
left: 24px;
}
#poe .buttons {
position: absolute;
width: 32px;
height: 20px;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 10;
}
#poe .b1 {
position: absolute;
top: 0;
left: 0;
width: 6px;
height: 6px;
background: #61a1c5;
}
#poe .b2 {
position: absolute;
top: 0;
left: 8px;
width: 14px;
height: 20px;
background: #454545;
}
#poe .b3 {
position: absolute;
top: 0;
right: 0;
width: 8px;
height: 12px;
background: #61a1c5;
}
#poe .helmet {
position: absolute;
width: 80px;
height: 45px;
background: #35302d;
border-radius: 50% 50% 0 0;
top: 35px;
left: 50%;
transform: translateX(-50%);
z-index: 4;
overflow: hidden;
}
#poe .helmet:before {
content: "";
position: absolute;
width: 8px;
height: 100%;
top: 0;
left: 26px;
background: #b9444d;
}
#poe .helmet:after {
content: "";
position: absolute;
width: 24px;
height: 80%;
background: #ccc7c4;
right: 10px;
top: 0;
border-radius: 0 0 12px 12px;
}
#poe .helmet-triangle {
position: absolute;
top: 0;
left: 2px;
width: 0;
height: 0;
border-style: solid;
border-width: 0 20px 50px 0;
border-color: transparent #b9444d transparent transparent;
}
#poe .helmet-back {
position: absolute;
width: 80px;
height: 80px;
background: #35302d;
border-radius: 50% 50% 10px 10px;
top: 35px;
left: 50%;
transform: translateX(-50%);
z-index: 2;
}
#poe .helmet-back:before {
content: "";
position: absolute;
bottom: 16px;
width: 10px;
height: 40px;
border-radius: 50%;
background: #35302d;
left: -5px;
}
#poe .helmet-back:after {
content: "";
position: absolute;
bottom: 16px;
width: 10px;
height: 40px;
border-radius: 50%;
background: #35302d;
right: -5px;
}
#poe .helmet-face {
position: absolute;
width: 70px;
height: 14px;
left: 50%;
transform: translateX(-50%);
top: 110px;
z-index: 10;
}
#poe .helmet-face:before {
content: "";
position: absolute;
left: -3px;
top: 0;
width: 25px;
height: 100%;
background: #35302d;
border-radius: 0 50% 5px 100%;
transform: rotate(25deg);
}
#poe .helmet-face:after {
content: "";
position: absolute;
right: -3px;
top: 0;
width: 25px;
height: 100%;
background: #35302d;
border-radius: 50% 0 100% 5px;
transform: rotate(-25deg);
}
#poe .helmet-ornament {
position: absolute;
background: #b9444d;
width: 20px;
height: 20px;
border-radius: 50%;
top: 14px;
left: 48px;
z-index: 10;
}
#poe .helmet-ornament:before {
content: "";
width: 16px;
height: 16px;
position: absolute;
top: -4px;
left: 50%;
transform: translateX(-50%);
background: #ccc7c4;
border-radius: 50%;
}
#poe .helmet-ornament:after {
content: "";
position: absolute;
top: 2px;
left: 50%;
transform: translateX(-50%);
width: 4px;
height: 16px;
background: #b9444d;
border-radius: 2px;
}
#poe .face {
position: absolute;
width: 70px;
height: 75px;
background: #e1ae96;
border-radius: 50%;
top: 55px;
left: 50%;
transform: translateX(-50%);
z-index: 4;
overflow: hidden;
}
#poe .visor {
position: absolute;
width: 100%;
height: 22px;
top: 20px;
background: rgba(245, 124, 0, 0.3);
}
#poe .neck {
position: absolute;
z-index: 3;
width: 30px;
height: 50px;
border-radius: 12px;
left: 50%;
transform: translateX(-50%);
top: 90px;
background: #d68f6e;
}
#poe .eyes {
position: absolute;
top: 30px;
width: 10px;
height: 10px;
background: #424b54;
border-radius: 50%;
left: 25%;
transform: translateX(-50%);
}
#poe .eyes:after {
content: "";
position: absolute;
width: 10px;
height: 10px;
background: #424b54;
top: 0;
border-radius: 50%;
right: -35px;
}
#poe .nose {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 8px;
height: 14px;
background: #d68f6e;
top: 31px;
border-radius: 4px;
}
#poe .mouth {
position: absolute;
background: #424b54;
width: 30px;
height: 6px;
border-radius: 0 0 50% 50%;
top: 55px;
left: 50%;
transform: translateX(-50%);
}
#poe .jacket {
background: #ea6b26;
position: absolute;
top: 0;
width: 30%;
height: 100%;
}
#poe .jacket-left {
left: 0;
}
#poe .jacket-right {
right: 0;
}
#poe .jacket:after {
content: "";
width: 6px;
position: absolute;
top: 0;
height: 110%;
background: #9b400f;
}
#poe .jacket-left:after {
left: 100%;
transform: translateX(-2px);
}
#poe .jacket-right:after {
right: 100%;
transform: translateX(2px);
}
#poe .jacket-left:before {
content: "";
background: #a61c19;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 20px;
}
#poe .jacket-right:before {
content: "";
background: #a61c19;
position: absolute;
top: 20px;
left: 0;
width: 100%;
height: 10px;
}
body {
font-size: 14px;
font-family: 'Lato', sans-serif;
text-align: left;
color: #757575;
cursor: default;
}
.title {
margin-top: 20px;
margin-bottom: 10px;
margin-left: 20px;
font-size:32px;
font-family: 'Oswald', sans-serif;
color: #2B2B2B;
}
.texts {
margin-left: 20px;
margin-right: 20px;
line-height: 140%;
}
.credit {
color: #9E9E9E;
font-size: 10px;
margin-bottom: 0.5em;
}
.notes {
color: #9E9E9E;
font-size: 10px;
}
.popover {
pointer-events: none;
}
#chart{
font-size: 16px;
font-family: 'Bangers', sans-serif;
text-align: center;
fill: #2B2B2B;
}
@media (min-width: 600px) {
#chart{
font-size: 20px;
}
}
</style>
</head>
<body>
<div class = "title">Connections in <i>Star Wars: The Force Awakens</i></div>
<div class="credit texts"> Created by Dan Hammer | <a href="http://www.danham.me/r/">danham.me/r</a></div>
<div class = "explanation texts">
<p style="margin-bottom: 0em;">The visualization below shows the common links between characters in Star Wars: The Force Awakens. The thicker a line, the deeper the connection between characters, as identified by USC.</p>
<div id = "chart"></div>
<section class="stripe" id="light">
<div class="container">
<div class="person" id="rey">
<div class="hair"></div>
<div class="face">
<div class="eyes"></div>
<div class="nose"></div>
<div class="mouth"></div>
</div>
<div class="bangs"></div>
<div class="neck"></div>
<div class="body">
<div class="fold-right"></div>
<div class="fold-left"></div>
</div>
<div class="lightsaber"></div>
</div>
</div>
</section>
<div class = "notes texts">
</div>
<script src="script.js"></script>
</body>
</html>
////////////////////////////////////////////////////////////
//////////////////////// Set-Up ////////////////////////////
////////////////////////////////////////////////////////////
var margin = {left:20, top:20, right:20, bottom:20},
width = Math.min(window.innerWidth, 700) - margin.left - margin.right,
height = Math.min(window.innerWidth, 700) - margin.top - margin.bottom,
innerRadius = Math.min(width, height) * .39,
outerRadius = innerRadius * 1.1;
var Names = ["Finn","Rey","Han","Poe","Kylo Ren","Leia"],
colors = ["#301E1E", "#083E77", "#342350", "#567235", "#8B161C", "#DF7C00"],
opacityDefault = 0.8;
var matrix = [
[0,11.5,6,2.8,0.8,0.8], //Black Widow
[11.5,0,6,0.1,1.5,0.3], //Captain America
[6,6,0,0.5,1.6,2.7], //Hawkeye
[2.8,0.1,0.5,0,1.1,0.7], //The Hulk
[0.8,1.5,1.6,1.1,0,0], //Iron Man
[0.8,0.3,2.7,0.7,0,0], //Thor
];
////////////////////////////////////////////////////////////
/////////// Create scale and layout functions //////////////
////////////////////////////////////////////////////////////
var colors = d3.scale.ordinal()
.domain(d3.range(Names.length))
.range(colors);
//A "custom" d3 chord function that automatically sorts the order of the chords in such a manner to reduce overlap
var chord = customChordLayout()
.padding(.15)
.sortChords(d3.descending) //which chord should be shown on top when chords cross. Now the biggest chord is at the bottom
.matrix(matrix);
var arc = d3.svg.arc()
.innerRadius(innerRadius*1.01)
.outerRadius(outerRadius);
var path = d3.svg.chord()
.radius(innerRadius);
////////////////////////////////////////////////////////////
////////////////////// Create SVG //////////////////////////
////////////////////////////////////////////////////////////
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + (width/2 + margin.left) + "," + (height/2 + margin.top) + ")");
////////////////////////////////////////////////////////////
/////////////// Create the gradient fills //////////////////
////////////////////////////////////////////////////////////
//Function to create the id for each chord gradient
function getGradID(d){ return "linkGrad-" + d.source.index + "-" + d.target.index; }
//Create the gradients definitions for each chord
var grads = svg.append("defs").selectAll("linearGradient")
.data(chord.chords())
.enter().append("linearGradient")
.attr("id", getGradID)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", function(d,i) { return innerRadius * Math.cos((d.source.endAngle-d.source.startAngle)/2 + d.source.startAngle - Math.PI/2); })
.attr("y1", function(d,i) { return innerRadius * Math.sin((d.source.endAngle-d.source.startAngle)/2 + d.source.startAngle - Math.PI/2); })
.attr("x2", function(d,i) { return innerRadius * Math.cos((d.target.endAngle-d.target.startAngle)/2 + d.target.startAngle - Math.PI/2); })
.attr("y2", function(d,i) { return innerRadius * Math.sin((d.target.endAngle-d.target.startAngle)/2 + d.target.startAngle - Math.PI/2); })
//Set the starting color (at 0%)
grads.append("stop")
.attr("offset", "0%")
.attr("stop-color", function(d){ return colors(d.source.index); });
//Set the ending color (at 100%)
grads.append("stop")
.attr("offset", "100%")
.attr("stop-color", function(d){ return colors(d.target.index); });
////////////////////////////////////////////////////////////
////////////////// Draw outer Arcs /////////////////////////
////////////////////////////////////////////////////////////
var outerArcs = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("g")
.attr("class", "group")
.on("mouseover", fade(.1))
.on("mouseout", fade(opacityDefault));
outerArcs.append("path")
.style("fill", function(d) { return colors(d.index); })
.attr("d", arc)
.each(function(d,i) {
//Search pattern for everything between the start and the first capital L
var firstArcSection = /(^.+?)L/;
//Grab everything up to the first Line statement
var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
//Replace all the comma's so that IE can handle it
newArc = newArc.replace(/,/g , " ");
//If the end angle lies beyond a quarter of a circle (90 degrees or pi/2)
//flip the end and start position
if (d.endAngle > 90*Math.PI/180 & d.startAngle < 270*Math.PI/180) {
var startLoc = /M(.*?)A/, //Everything between the first capital M and first capital A
middleLoc = /A(.*?)0 0 1/, //Everything between the first capital A and 0 0 1
endLoc = /0 0 1 (.*?)$/; //Everything between the first 0 0 1 and the end of the string (denoted by $)
//Flip the direction of the arc by switching the start en end point (and sweep flag)
//of those elements that are below the horizontal line
var newStart = endLoc.exec( newArc )[1];
var newEnd = startLoc.exec( newArc )[1];
var middleSec = middleLoc.exec( newArc )[1];
//Build up the new arc notation, set the sweep-flag to 0
newArc = "M" + newStart + "A" + middleSec + "0 0 0 " + newEnd;
}//if
//Create a new invisible arc that the text can flow along
svg.append("path")
.attr("class", "hiddenArcs")
.attr("id", "arc"+i)
.attr("d", newArc)
.style("fill", "none");
});
////////////////////////////////////////////////////////////
////////////////// Append Names ////////////////////////////
////////////////////////////////////////////////////////////
//Append the label names on the outside
outerArcs.append("text")
.attr("class", "titles")
.attr("dy", function(d,i) { return (d.endAngle > 90*Math.PI/180 & d.startAngle < 270*Math.PI/180 ? 25 : -16); })
.append("textPath")
.attr("startOffset","50%")
.style("text-anchor","middle")
.attr("xlink:href",function(d,i){return "#arc"+i;})
.text(function(d,i){ return Names[i]; });
////////////////////////////////////////////////////////////
////////////////// Draw inner chords ///////////////////////
////////////////////////////////////////////////////////////
svg.selectAll("path.chord")
.data(chord.chords)
.enter().append("path")
.attr("class", "chord")
.style("fill", function(d){ return "url(#" + getGradID(d) + ")"; })
.style("opacity", opacityDefault)
.attr("d", path)
.on("mouseover", mouseoverChord)
.on("mouseout", mouseoutChord);
////////////////////////////////////////////////////////////
////////////////// Extra Functions /////////////////////////
////////////////////////////////////////////////////////////
//Returns an event handler for fading a given chord group.
function fade(opacity) {
return function(d,i) {
svg.selectAll("path.chord")
.filter(function(d) { return d.source.index !== i && d.target.index !== i; })
.transition()
.style("opacity", opacity);
};
}//fade
//Highlight hovered over chord
function mouseoverChord(d,i) {
//Decrease opacity to all
svg.selectAll("path.chord")
.transition()
.style("opacity", 0.1);
//Show hovered over chord with full opacity
d3.select(this)
.transition()
.style("opacity", 1);
//Define and show the tooltip over the mouse location
$(this).popover({
placement: 'auto top',
container: 'body',
mouseOffset: 10,
followMouse: true,
trigger: 'hover',
html : true,
content: function() {
return "<p style='font-size: 11px; text-align: center;'><span style='font-weight:900'>" + Names[d.source.index] +
"</span> and <span style='font-weight:900'>" + Names[d.target.index] +
"</span> are connected with <span style='font-weight:900'>" + d.source.value + "</span> weight </p>"; }
});
$(this).popover('show');
}//mouseoverChord
//Bring all chords back to default opacity
function mouseoutChord(d) {
//Hide the tooltip
$('.popover').each(function() {
$(this).remove();
});
//Set opacity back to default for all
svg.selectAll("path.chord")
.transition()
.style("opacity", opacityDefault);
}//function mouseoutChord
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment