Movie collaborations between Avengers in the MCU - will get used in tutorial
forked from nbremer's block: Chord Gradients - Final example - Avenger Movie Collaborations
Movie collaborations between Avengers in the MCU - will get used in tutorial
forked from nbremer's block: Chord 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); |
/* ======================================================================== | |
* Bootstrap: tooltip.js v3.1.1 | |
* http://getbootstrap.com/javascript/#tooltip | |
* Inspired by the original jQuery.tipsy by Jason Frame | |
* ======================================================================== | |
* Copyright 2011-2014 Twitter, Inc. | |
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | |
* ======================================================================== */ | |
+function ($) { | |
'use strict'; | |
// TOOLTIP PUBLIC CLASS DEFINITION | |
// =============================== | |
var Tooltip = function (element, options) { | |
this.type = | |
this.options = | |
this.enabled = | |
this.timeout = | |
this.hoverState = | |
this.$measure = | |
this.$element = null | |
this.init('tooltip', element, options) | |
} | |
Tooltip.DEFAULTS = { | |
animation: true, | |
mouseOffset: 0, | |
measure: false, | |
followMouse: false, | |
placement: 'top', | |
selector: false, | |
template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', | |
trigger: 'hover focus', | |
title: '', | |
delay: 0, | |
html: false, | |
container: false | |
} | |
Tooltip.prototype.init = function (type, element, options) { | |
this.enabled = true | |
this.type = type | |
this.$element = $(element) | |
this.options = this.getOptions(options) | |
if (this.options.measure) { | |
this.$measure = $(this.options.measure) | |
} | |
var triggers = this.options.trigger.split(' ') | |
for (var i = triggers.length; i--;) { | |
var trigger = triggers[i] | |
if (trigger == 'click') { | |
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) | |
} else if (trigger != 'manual') { | |
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' | |
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' | |
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) | |
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) | |
} | |
} | |
this.options.selector ? | |
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : | |
this.fixTitle() | |
} | |
Tooltip.prototype.getDefaults = function () { | |
return Tooltip.DEFAULTS | |
} | |
Tooltip.prototype.getOptions = function (options) { | |
options = $.extend({}, this.getDefaults(), this.$element.data(), options) | |
if (options.delay && typeof options.delay == 'number') { | |
options.delay = { | |
show: options.delay, | |
hide: options.delay | |
} | |
} | |
return options | |
} | |
Tooltip.prototype.getDelegateOptions = function () { | |
var options = {} | |
var defaults = this.getDefaults() | |
this._options && $.each(this._options, function (key, value) { | |
if (defaults[key] != value) options[key] = value | |
}) | |
return options | |
} | |
Tooltip.prototype.enter = function (obj) { | |
var self = obj instanceof this.constructor ? | |
obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) | |
clearTimeout(self.timeout) | |
self.hoverState = 'in' | |
if (!self.options.delay || !self.options.delay.show) return self.show() | |
self.timeout = setTimeout(function () { | |
if (self.hoverState == 'in') self.show() | |
}, self.options.delay.show) | |
} | |
Tooltip.prototype.leave = function (obj) { | |
var self = obj instanceof this.constructor ? | |
obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) | |
clearTimeout(self.timeout) | |
self.hoverState = 'out' | |
if (!self.options.delay || !self.options.delay.hide) return self.hide() | |
self.timeout = setTimeout(function () { | |
if (self.hoverState == 'out') self.hide() | |
}, self.options.delay.hide) | |
} | |
Tooltip.prototype.show = function () { | |
var e = $.Event('show.bs.' + this.type) | |
if (this.hasContent() && this.enabled) { | |
this.$element.trigger(e) | |
if (e.isDefaultPrevented()) return | |
var that = this; | |
var $tip = this.tip() | |
this.setContent() | |
if (this.options.animation) $tip.addClass('fade') | |
var placement = typeof this.options.placement == 'function' ? | |
this.options.placement.call(this, $tip[0], this.$element[0]) : | |
this.options.placement | |
var autoToken = /\s?auto?\s?/i | |
var autoPlace = autoToken.test(placement) | |
if (autoPlace) placement = placement.replace(autoToken, '') || 'top' | |
$tip | |
.detach() | |
.css({ top: 0, left: 0, display: 'block' }) | |
.addClass(placement) | |
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) | |
var pos = this.getPosition() | |
var actualWidth = $tip[0].offsetWidth | |
var actualHeight = $tip[0].offsetHeight | |
if (autoPlace) { | |
placement = this.getAutoPlace($tip, placement, pos, actualWidth, actualHeight) | |
} | |
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) | |
this.applyPlacement(calculatedOffset, placement) | |
this.hoverState = null | |
var complete = function() { | |
that.$element.trigger('shown.bs.' + that.type) | |
} | |
if (this.options.followMouse) { | |
$(this.options.container).on('mousemove', $.proxy(this.follow, this)); | |
} | |
$.support.transition && this.$tip.hasClass('fade') ? | |
$tip | |
.one($.support.transition.end, complete) | |
.emulateTransitionEnd(150) : | |
complete() | |
} | |
} | |
Tooltip.prototype.getAutoPlace = function($tip, placement, pos, actualWidth, actualHeight) { | |
var $parent = this.$element.parent() | |
var orgPlacement = placement | |
var docScroll = document.documentElement.scrollTop || document.body.scrollTop | |
var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth() | |
var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight() | |
var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left | |
placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' : | |
placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' : | |
placement == 'right' && pos.left + actualWidth > parentWidth ? 'left' : | |
placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' : | |
placement | |
$tip | |
.removeClass(orgPlacement) | |
.addClass(placement) | |
return placement | |
} | |
Tooltip.prototype.follow = function(e) { | |
var $tip = this.tip() | |
var pos = this.getPosition() | |
var actualWidth = $tip[0].offsetWidth | |
var actualHeight = $tip[0].offsetHeight | |
var placement = typeof this.options.placement == 'function' ? | |
this.options.placement.call(this, $tip[0], this.$element[0]) : | |
this.options.placement | |
var autoToken = /\s?auto?\s?/i | |
var autoPlace = autoToken.test(placement) | |
if (autoPlace) placement = placement.replace(autoToken, '') || 'top' | |
pos.left = e.pageX + this.options.mouseOffset; | |
pos.top = e.pageY + this.options.mouseOffset; | |
var placement = this.getAutoPlace($tip, placement, pos, actualWidth, actualHeight) | |
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight, e) | |
this.applyPlacement(calculatedOffset, placement) | |
} | |
Tooltip.prototype.applyPlacement = function (offset, placement) { | |
var replace | |
var $tip = this.tip() | |
var width = $tip[0].offsetWidth | |
var height = $tip[0].offsetHeight | |
// manually read margins because getBoundingClientRect includes difference | |
var marginTop = parseInt($tip.css('margin-top'), 10) | |
var marginLeft = parseInt($tip.css('margin-left'), 10) | |
// we must check for NaN for ie 8/9 | |
if (isNaN(marginTop)) marginTop = 0 | |
if (isNaN(marginLeft)) marginLeft = 0 | |
offset.top = offset.top + marginTop | |
offset.left = offset.left + marginLeft | |
// $.fn.offset doesn't round pixel values | |
// so we use setOffset directly with our own function B-0 | |
$.offset.setOffset($tip[0], $.extend({ | |
using: function (props) { | |
$tip.css({ | |
top: Math.round(props.top), | |
left: Math.round(props.left) | |
}) | |
} | |
}, offset), 0) | |
$tip.addClass('in') | |
// check to see if placing tip in new offset caused the tip to resize itself | |
var actualWidth = $tip[0].offsetWidth | |
var actualHeight = $tip[0].offsetHeight | |
if (placement == 'top' && actualHeight != height) { | |
replace = true | |
offset.top = offset.top + height - actualHeight | |
} | |
if (/bottom|top/.test(placement)) { | |
var delta = 0 | |
if (offset.left < 0) { | |
delta = offset.left * -2 | |
offset.left = 0 | |
$tip.offset(offset) | |
actualWidth = $tip[0].offsetWidth | |
actualHeight = $tip[0].offsetHeight | |
} | |
this.replaceArrow(delta - width + actualWidth, actualWidth, 'left') | |
} else { | |
this.replaceArrow(actualHeight - height, actualHeight, 'top') | |
} | |
if (replace) $tip.offset(offset) | |
} | |
Tooltip.prototype.replaceArrow = function (delta, dimension, position) { | |
if(this.options.mouseOffset > 0 && position === 'top') { | |
this.arrow().css(position, this.options.mouseOffset + 10 + 'px') | |
return; | |
} | |
this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + '%') : '') | |
} | |
Tooltip.prototype.setContent = function () { | |
var $tip = this.tip() | |
var title = this.getTitle() | |
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) | |
$tip.removeClass('fade in top bottom left right') | |
} | |
Tooltip.prototype.hide = function () { | |
var that = this | |
var $tip = this.tip() | |
var e = $.Event('hide.bs.' + this.type) | |
function complete() { | |
if (that.hoverState != 'in') $tip.detach() | |
that.$element.trigger('hidden.bs.' + that.type) | |
} | |
$(this.options.container).off('mousemove'); | |
this.$element.trigger(e) | |
if (e.isDefaultPrevented()) return | |
$tip.removeClass('in') | |
$.support.transition && this.$tip.hasClass('fade') ? | |
$tip | |
.one($.support.transition.end, complete) | |
.emulateTransitionEnd(150) : | |
complete() | |
this.hoverState = null | |
return this | |
} | |
Tooltip.prototype.fixTitle = function () { | |
var $e = this.$element | |
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { | |
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '') | |
} | |
} | |
Tooltip.prototype.hasContent = function () { | |
return this.getTitle() | |
} | |
Tooltip.prototype.getPosition = function () { | |
var el = this.$element[0] | |
if(this.$element.prop('tagName') == 'AREA') { | |
var position = this.$element.attr('coords').split(','); | |
var x = parseInt(position[0], 0) + parseInt(this.$measure.offset().left, 0); | |
var y = parseInt(position[1], 0) + parseInt(this.$measure.offset().top, 0); | |
return {bottom: 0, height: el.offsetWidth, left: x, right: 0, top: y, width: el.offsetWidth} | |
} | |
return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : { | |
width: el.offsetWidth, | |
height: el.offsetHeight | |
}, this.$element.offset()) | |
} | |
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight, follow) { | |
if (typeof follow !== 'undefined') { | |
return placement == 'bottom' ? { top: follow.pageY + this.options.mouseOffset, left: follow.pageX - (actualWidth / 2) } : | |
placement == 'top' ? { top: follow.pageY - actualHeight - this.options.mouseOffset, left: follow.pageX - (actualWidth / 2) } : | |
placement == 'left' ? { top: follow.pageY - this.options.mouseOffset, left: follow.pageX - (actualWidth + this.options.mouseOffset) } : | |
{ top: follow.pageY - this.options.mouseOffset, left: follow.pageX + this.options.mouseOffset } | |
} | |
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : | |
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : | |
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : | |
{ top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } | |
} | |
Tooltip.prototype.getTitle = function () { | |
var title | |
var $e = this.$element | |
var o = this.options | |
title = $e.attr('data-original-title') | |
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) | |
return title | |
} | |
Tooltip.prototype.tip = function () { | |
return this.$tip = this.$tip || $(this.options.template) | |
} | |
Tooltip.prototype.arrow = function () { | |
return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow') | |
} | |
Tooltip.prototype.validate = function () { | |
if (!this.$element[0].parentNode) { | |
this.hide() | |
this.$element = null | |
this.options = null | |
} | |
} | |
Tooltip.prototype.enable = function () { | |
this.enabled = true | |
} | |
Tooltip.prototype.disable = function () { | |
this.enabled = false | |
} | |
Tooltip.prototype.toggleEnabled = function () { | |
this.enabled = !this.enabled | |
} | |
Tooltip.prototype.toggle = function (e) { | |
var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this | |
self.tip().hasClass('in') ? self.leave(self) : self.enter(self) | |
} | |
Tooltip.prototype.destroy = function () { | |
clearTimeout(this.timeout) | |
this.hide().$element.off('.' + this.type).removeData('bs.' + this.type) | |
} | |
// TOOLTIP PLUGIN DEFINITION | |
// ========================= | |
var old = $.fn.tooltip | |
$.fn.tooltip = function (option) { | |
return this.each(function () { | |
var $this = $(this) | |
var data = $this.data('bs.tooltip') | |
var options = typeof option == 'object' && option | |
if (!data && option == 'destroy') return | |
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) | |
if (typeof option == 'string') data[option]() | |
}) | |
} | |
$.fn.tooltip.Constructor = Tooltip | |
// TOOLTIP NO CONFLICT | |
// =================== | |
$.fn.tooltip.noConflict = function () { | |
$.fn.tooltip = 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)%6; | |
} | |
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>Collaborations between MCU Avengers</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> | |
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">Movie collaborations between Avengers in the MCU</div> | |
<div class = "explanation texts"> | |
<p style="margin-bottom: 0em;">The visualization below shows how often the original Avengers have appeared in MCU movies together since Iron Man | |
in 2008 and counting up to Thor: Ragnarok scheduled in 2017. The thicker a line, the more movies in which both characters appeared.</p> | |
<p style="margin-top: 0.5em;">Because all six have appeared in both Avengers movies, the minimum number of co-appearances is 2. | |
Iron Man and Black Widow are the Avengers who have appeared most often together, with 5 movies.</p></div> | |
<div id = "chart"></div> | |
<div class="credit texts">Created by Nadieh Bremer | <a href="http://www.visualcinnamon.com/">VisualCinnamon.com</a></div> | |
<div class = "notes texts"> | |
The 12 MCU movies taken into account are: Iron Man, Iron Man 2, Iron Man 3, Captain America: The First Avenger, Captain America: The Winter Soldier, | |
Captain America: Civil War, Thor, Thor: The Dark World, Thor: Ragnarok, The Incredible Hulk, The Avengers & Avengers: Age of Ultron</div> | |
<script src="script.js"></script> | |
</body> | |
</html> |
//Add timeout so it won't run before the blog page knows the dimensions | |
setTimeout(function() { | |
//////////////////////////////////////////////////////////// | |
//////////////////////// 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 = ["Black Widow","Captain America","Hawkeye","the Hulk","Iron Man","Thor"], | |
colors = ["#301E1E", "#083E77", "#342350", "#567235", "#8B161C", "#DF7C00"], | |
opacityDefault = 0.8; | |
var matrix = [ | |
[0,4,3,2,5,2], //Black Widow | |
[4,0,3,2,4,3], //Captain America | |
[3,3,0,2,3,3], //Hawkeye | |
[2,2,2,0,3,3], //The Hulk | |
[5,4,3,3,0,2], //Iron Man | |
[2,3,3,3,2,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> appeared together in <span style='font-weight:900'>" + d.source.value + "</span> movies </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 | |
}, 500); |