Created
April 19, 2012 00:41
-
-
Save anonymous/2417580 to your computer and use it in GitHub Desktop.
D3 Visualization in Five Minutes by Chris Buck
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<title>D3 Visualization in Five Minutes</title> | |
<!-- Your Slides --> | |
<!-- One section is one slide --> | |
<section> | |
<!-- This is the first slide --> | |
<h2>D3 Visualization in Five Minutes</h2> | |
<footer>by Chris Buck</footer> | |
</section> | |
<section> | |
<h3>You have some data about relationships</h2> | |
<pre> | |
var matrix = [ | |
[11975, 5871, 8916, 2868], | |
[ 1951, 10048, 2060, 6171], | |
[ 8010, 16145, 8090, 8045], | |
[ 1013, 990, 940, 6907] | |
]; | |
</pre> | |
</section> | |
<section> | |
<h3>You want to make it pretty</h3> | |
<pre><code> | |
var matrix = [ | |
[11975, 5871, 8916, 2868], | |
[ 1951, 10048, 2060, 6171], | |
[ 8010, 16145, 8090, 8045], | |
[ 1013, 990, 940, 6907] | |
]; | |
</code></pre> | |
</section> | |
<section> | |
<h3>So how do we make this: </h3> | |
<div class='chart' id='chart1'> | |
</div> | |
</section> | |
<section> | |
<h3>Out of this?</h3> | |
<pre><code> | |
var matrix = [ | |
[11975, 5871, 8916, 2868], | |
[ 1951, 10048, 2060, 6171], | |
[ 8010, 16145, 8090, 8045], | |
[ 1013, 990, 940, 6907] | |
]; | |
</code></pre> | |
</section> | |
<section> | |
<h3> | |
Color Mapping | |
</h3> | |
<pre> | |
var fill = d3.scale.ordinal() | |
.domain(d3.range(4)) | |
.range(["#000000", "#FFDD89", "#957244", "#F26223"]); | |
... | |
.style("fill", function(d) { return fill(d.index); }) | |
.style("stroke", function(d) { return fill(d.index); }) | |
</pre> | |
<div class='chart' id='chart2'></div> | |
</section> | |
<section> | |
<h3> | |
SVG Scaling | |
</h3> | |
<pre> | |
var width = 540, | |
height = 540, | |
innerRadius = Math.min(width, height) * .38, | |
outerRadius = innerRadius * 1.1; | |
var svg = d3.select("#chart1") | |
.append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.append("g") | |
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); | |
</pre> | |
<div class='chart' id='chart3'></div> | |
</section> | |
<section> | |
<h3> | |
The Power of Change: enter | |
</h3> | |
<pre> | |
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); }) | |
</pre> | |
<div class='chart' id='chart4'></div> | |
</section> | |
<section> | |
<h3> | |
Events | |
</h3> | |
<pre> | |
svg.append("g") | |
.selectAll("path") | |
.data(chord.groups) | |
.enter().append("path") | |
.attr("d", d3.svg.arc().innerRacirdius(innerRadius).outerRadius(outerRadius)) | |
.on("mouseover", fade(.1)) | |
.on("mouseout", fade(1)); | |
</pre> | |
<div class='chart' id='chart5'></div> | |
</section> | |
<section> | |
<h2>End!</h2> | |
<br /> | |
<h4>More about circular / chord graphs:</h4> | |
<h6>http://circos.ca/guide/tables/img/guide-table-large.png</h6> | |
<br /> | |
<h4>Code munged for this presentation:</h4> | |
<h6>http://mbostock.github.com/d3/ex/chord.html</h6> | |
<br /> | |
<h4>This presentation is based on:</h4> | |
<h6>http://paulrouget.com/dzslides/</h6> | |
<div class='chart' id='chart6'></div> | |
</section> | |
<!-- Your Style --> | |
<!-- Define the style of your presentation --> | |
<!-- Maybe a font from http://www.google.com/webfonts ? --> | |
<link href='http://fonts.googleapis.com/css?family=Cutive' rel='stylesheet'> | |
<style> | |
html { background-color: black; } | |
body { background-color: white; border-radius: 12px} | |
/* A section is a slide. It's size is 800x600, and this will never change */ | |
section { | |
/* The font from Google */ | |
font-family: 'Cutive', serif; | |
font-size: 30px; | |
} | |
pre { | |
font-size: 22px; | |
} | |
h1 { | |
margin-top: 200px; | |
text-align: center; | |
font-size: 80px; | |
} | |
h2 { | |
margin-top: 100px; | |
text-align: center; | |
font-size: 60px; | |
} | |
h3 { | |
margin: 100px 0 50px 100px; | |
} | |
ul { | |
margin: 50px 200px; | |
} | |
p { | |
margin: 75px; | |
font-size: 50px; | |
} | |
.chart { | |
margin-left: 100px; | |
font-size: 10px; | |
} | |
blockquote { | |
height: 100%; | |
background-color: black; | |
color: white; | |
font-size: 60px; | |
padding: 50px; | |
} | |
blockquote:before { | |
content: open-quote; | |
} | |
blockquote:after { | |
content: close-quote; | |
} | |
/* Figures are displayed full-page, with the caption | |
on top of the image/video */ | |
figure { | |
background-color: black; | |
} | |
figcaption { | |
margin: 70px; | |
font-size: 50px; | |
} | |
footer { | |
position: absolute; | |
bottom: 0; | |
width: 100%; | |
padding: 40px; | |
text-align: right; | |
background-color: #F3F4F8; | |
border-top: 1px solid #CCC; | |
} | |
/* Transition effect */ | |
/* Feel free to change the transition effect for original | |
animations. See here: | |
https://developer.mozilla.org/en/CSS/CSS_transitions | |
How to use CSS3 Transitions: */ | |
section { | |
-moz-transition: left 400ms linear 0s; | |
-webkit-transition: left 400ms linear 0s; | |
-ms-transition: left 400ms linear 0s; | |
transition: left 400ms linear 0s; | |
} | |
/* Before */ | |
section { left: -150%; } | |
/* Now */ | |
section[aria-selected] { left: 0; } | |
/* After */ | |
section[aria-selected] ~ section { left: +150%; } | |
/* Incremental elements */ | |
/* By default, visible */ | |
.incremental > * { opacity: 1; } | |
/* The current item */ | |
.incremental > *[aria-selected] { opacity: 1; } | |
/* The items to-be-selected */ | |
.incremental > *[aria-selected] ~ * { opacity: 0; } | |
/* The progressbar, at the bottom of the slides, show the global | |
progress of the presentation. */ | |
#progress-bar { | |
height: 2px; | |
background: #AAA; | |
} | |
</style> | |
<script src="javascripts/d3/d3.v2.min.js"></script> | |
<script> | |
// From http://mkweb.bcgsc.ca/circos/guide/tables/ | |
var chord = d3.layout.chord() | |
.padding(.05) | |
.sortSubgroups(d3.descending) | |
.matrix([ | |
[11975, 5871, 8916, 2868], | |
[ 1951, 10048, 2060, 6171], | |
[ 8010, 16145, 8090, 8045], | |
[ 1013, 990, 940, 6907] | |
]); | |
var width = 560, | |
height = 560, | |
innerRadius = Math.min(width, height) * .38, | |
outerRadius = innerRadius * 1.1; | |
var fill = d3.scale.ordinal() | |
.domain(d3.range(4)) | |
.range(["#000000", "#FFDD89", "#957244", "#F26223"]); | |
var svgs = []; | |
for (var i=0; i< 6; i++) { | |
svgs[i] = d3.select("#chart"+(i+1)) | |
.append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.append("g") | |
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); | |
} | |
for (var i=0; i< svgs.length; i++) { | |
svgs[i] | |
.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", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius)); | |
//.on("mouseover", fade(.1,svgs[i])) | |
//.on("mouseout", fade(1,svgs[i])); | |
} | |
// Setup for rest | |
var ticks = []; | |
for (var i=0; i< svgs.length; i++) { | |
ticks[i] = svgs[i].append("g") | |
.selectAll("g") | |
.data(chord.groups) | |
.enter().append("g") | |
.selectAll("g") | |
.data(groupTicks) | |
.enter().append("g") | |
.attr("transform", function(d) { | |
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" | |
+ "translate(" + outerRadius + ",0)"; | |
}); | |
} | |
// Ticks | |
for (var i=0; i< ticks.length; i++) { | |
ticks[i].append("line") | |
.attr("x1", 1) | |
.attr("y1", 0) | |
.attr("x2", 5) | |
.attr("y2", 0) | |
.style("stroke", "#000"); | |
} | |
// Labels | |
for (var i=0; i< ticks.length; i++) { | |
ticks[i].append("text") | |
.attr("x", 8) | |
.attr("dy", ".35em") | |
.attr("text-anchor", function(d) { | |
return d.angle > Math.PI ? "end" : null; | |
}) | |
.attr("transform", function(d) { | |
return d.angle > Math.PI ? "rotate(180)translate(-16)" : null; | |
}) | |
.text(function(d) { return d.label; }); | |
} | |
// Bands | |
for (var i=0; i< svgs.length; i++) { | |
svgs[i].append("g") | |
.attr("class", "chord") | |
.selectAll("path") | |
.data(chord.chords) | |
.enter().append("path") | |
.style("fill", function(d) { return fill(d.target.index); }) | |
.attr("d", d3.svg.chord().radius(innerRadius)) | |
.style("opacity", 1); | |
} | |
/** Returns an array of tick angles and labels, given a group. */ | |
function groupTicks(d) { | |
var k = (d.endAngle - d.startAngle) / d.value; | |
return d3.range(0, d.value, 1000).map(function(v, i) { | |
return { | |
angle: v * k + d.startAngle, | |
label: i % 5 ? null : v / 1000 + "k" | |
}; | |
}); | |
} | |
/** Returns an event handler for fading a given chord group. */ | |
function fade(opacity, svgg) { | |
return function(g, i) { | |
svgg.selectAll("g.chord path") | |
.filter(function(d) { | |
return d.source.index != i && d.target.index != i; | |
}) | |
.transition() | |
.style("opacity", opacity); | |
}; | |
} | |
</script> | |
<!-- {{{{ dzslides core | |
# | |
# | |
# __ __ __ . __ ___ __ | |
# | \ / /__` | | | \ |__ /__` | |
# |__/ /_ .__/ |___ | |__/ |___ .__/ core :€ | |
# | |
# | |
# The following block of code is not supposed to be edited. | |
# But if you want to change the behavior of these slides, | |
# feel free to hack it! | |
# | |
--> | |
<div id="progress-bar"></div> | |
<!-- Default Style --> | |
<style> | |
* { margin: 0; padding: 0; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } | |
details { display: none; } | |
body { | |
width: 800px; height: 600px; | |
margin-left: -400px; margin-top: -300px; | |
position: absolute; top: 50%; left: 50%; | |
overflow: hidden; | |
} | |
section { | |
position: absolute; | |
pointer-events: none; | |
width: 100%; height: 100%; | |
} | |
section[aria-selected] { pointer-events: auto; } | |
html { overflow: hidden; } | |
body { display: none; } | |
body.loaded { display: block; } | |
.incremental {visibility: hidden; } | |
.incremental[active] {visibility: visible; } | |
#progress-bar{ | |
bottom: 0; | |
position: absolute; | |
-moz-transition: width 400ms linear 0s; | |
-webkit-transition: width 400ms linear 0s; | |
-ms-transition: width 400ms linear 0s; | |
transition: width 400ms linear 0s; | |
} | |
figure { | |
width: 100%; | |
height: 100%; | |
} | |
figure > * { | |
position: absolute; | |
} | |
figure > img, figure > video { | |
width: 100%; height: 100%; | |
} | |
</style> | |
<script> | |
var Dz = { | |
remoteWindows: [], | |
idx: -1, | |
step: 0, | |
slides: null, | |
progressBar : null, | |
params: { | |
autoplay: "1" | |
} | |
}; | |
Dz.init = function() { | |
document.body.className = "loaded"; | |
this.slides = $$("body > section"); | |
this.progressBar = $("#progress-bar"); | |
this.setupParams(); | |
this.onhashchange(); | |
this.setupTouchEvents(); | |
this.onresize(); | |
} | |
Dz.setupParams = function() { | |
var p = window.location.search.substr(1).split('&'); | |
p.forEach(function(e, i, a) { | |
var keyVal = e.split('='); | |
Dz.params[keyVal[0]] = decodeURIComponent(keyVal[1]); | |
}); | |
// Specific params handling | |
if (!+this.params.autoplay) | |
$$.forEach($$("video"), function(v){ v.controls = true }); | |
} | |
Dz.onkeydown = function(aEvent) { | |
// Don't intercept keyboard shortcuts | |
if (aEvent.altKey | |
|| aEvent.ctrlKey | |
|| aEvent.metaKey | |
|| aEvent.shiftKey) { | |
return; | |
} | |
if ( aEvent.keyCode == 37 // left arrow | |
|| aEvent.keyCode == 38 // up arrow | |
|| aEvent.keyCode == 33 // page up | |
) { | |
aEvent.preventDefault(); | |
this.back(); | |
} | |
if ( aEvent.keyCode == 39 // right arrow | |
|| aEvent.keyCode == 40 // down arrow | |
|| aEvent.keyCode == 34 // page down | |
) { | |
aEvent.preventDefault(); | |
this.forward(); | |
} | |
if (aEvent.keyCode == 35) { // end | |
aEvent.preventDefault(); | |
this.goEnd(); | |
} | |
if (aEvent.keyCode == 36) { // home | |
aEvent.preventDefault(); | |
this.goStart(); | |
} | |
if (aEvent.keyCode == 32) { // space | |
aEvent.preventDefault(); | |
this.toggleContent(); | |
} | |
if (aEvent.keyCode == 70) { // f | |
aEvent.preventDefault(); | |
this.goFullscreen(); | |
} | |
} | |
/* Touch Events */ | |
Dz.setupTouchEvents = function() { | |
var orgX, newX; | |
var tracking = false; | |
var db = document.body; | |
db.addEventListener("touchstart", start.bind(this), false); | |
db.addEventListener("touchmove", move.bind(this), false); | |
function start(aEvent) { | |
aEvent.preventDefault(); | |
tracking = true; | |
orgX = aEvent.changedTouches[0].pageX; | |
} | |
function move(aEvent) { | |
if (!tracking) return; | |
newX = aEvent.changedTouches[0].pageX; | |
if (orgX - newX > 100) { | |
tracking = false; | |
this.forward(); | |
} else { | |
if (orgX - newX < -100) { | |
tracking = false; | |
this.back(); | |
} | |
} | |
} | |
} | |
/* Adapt the size of the slides to the window */ | |
Dz.onresize = function() { | |
var db = document.body; | |
var sx = db.clientWidth / window.innerWidth; | |
var sy = db.clientHeight / window.innerHeight; | |
var transform = "scale(" + (1/Math.max(sx, sy)) + ")"; | |
db.style.MozTransform = transform; | |
db.style.WebkitTransform = transform; | |
db.style.OTransform = transform; | |
db.style.msTransform = transform; | |
db.style.transform = transform; | |
} | |
Dz.getDetails = function(aIdx) { | |
var s = $("section:nth-of-type(" + aIdx + ")"); | |
var d = s.$("details"); | |
return d ? d.innerHTML : ""; | |
} | |
Dz.onmessage = function(aEvent) { | |
var argv = aEvent.data.split(" "), argc = argv.length; | |
argv.forEach(function(e, i, a) { a[i] = decodeURIComponent(e) }); | |
var win = aEvent.source; | |
if (argv[0] === "REGISTER" && argc === 1) { | |
this.remoteWindows.push(win); | |
this.postMsg(win, "REGISTERED", document.title, this.slides.length); | |
this.postMsg(win, "CURSOR", this.idx + "." + this.step); | |
return; | |
} | |
if (argv[0] === "BACK" && argc === 1) | |
this.back(); | |
if (argv[0] === "FORWARD" && argc === 1) | |
this.forward(); | |
if (argv[0] === "START" && argc === 1) | |
this.goStart(); | |
if (argv[0] === "END" && argc === 1) | |
this.goEnd(); | |
if (argv[0] === "TOGGLE_CONTENT" && argc === 1) | |
this.toggleContent(); | |
if (argv[0] === "SET_CURSOR" && argc === 2) | |
window.location.hash = "#" + argv[1]; | |
if (argv[0] === "GET_CURSOR" && argc === 1) | |
this.postMsg(win, "CURSOR", this.idx + "." + this.step); | |
if (argv[0] === "GET_NOTES" && argc === 1) | |
this.postMsg(win, "NOTES", this.getDetails(this.idx)); | |
} | |
Dz.toggleContent = function() { | |
// If a Video is present in this new slide, play it. | |
// If a Video is present in the previous slide, stop it. | |
var s = $("section[aria-selected]"); | |
if (s) { | |
var video = s.$("video"); | |
if (video) { | |
if (video.ended || video.paused) { | |
video.play(); | |
} else { | |
video.pause(); | |
} | |
} | |
} | |
} | |
Dz.setCursor = function(aIdx, aStep) { | |
// If the user change the slide number in the URL bar, jump | |
// to this slide. | |
aStep = (aStep != 0 && typeof aStep !== "undefined") ? "." + aStep : ".0"; | |
window.location.hash = "#" + aIdx + aStep; | |
} | |
Dz.onhashchange = function() { | |
var cursor = window.location.hash.split("#"), | |
newidx = 1, | |
newstep = 0; | |
if (cursor.length == 2) { | |
newidx = ~~cursor[1].split(".")[0]; | |
newstep = ~~cursor[1].split(".")[1]; | |
if (newstep > Dz.slides[newidx - 1].$$('.incremental > *').length) { | |
newstep = 0; | |
newidx++; | |
} | |
} | |
this.setProgress(newidx, newstep); | |
if (newidx != this.idx) { | |
this.setSlide(newidx); | |
} | |
if (newstep != this.step) { | |
this.setIncremental(newstep); | |
} | |
for (var i = 0; i < this.remoteWindows.length; i++) { | |
this.postMsg(this.remoteWindows[i], "CURSOR", this.idx + "." + this.step); | |
} | |
} | |
Dz.back = function() { | |
if (this.idx == 1 && this.step == 0) { | |
return; | |
} | |
if (this.step == 0) { | |
this.setCursor(this.idx - 1, | |
this.slides[this.idx - 2].$$('.incremental > *').length); | |
} else { | |
this.setCursor(this.idx, this.step - 1); | |
} | |
} | |
Dz.forward = function() { | |
if (this.idx >= this.slides.length && | |
this.step >= this.slides[this.idx - 1].$$('.incremental > *').length) { | |
return; | |
} | |
if (this.step >= this.slides[this.idx - 1].$$('.incremental > *').length) { | |
this.setCursor(this.idx + 1, 0); | |
} else { | |
this.setCursor(this.idx, this.step + 1); | |
} | |
} | |
Dz.goStart = function() { | |
this.setCursor(1, 0); | |
} | |
Dz.goEnd = function() { | |
var lastIdx = this.slides.length; | |
var lastStep = this.slides[lastIdx - 1].$$('.incremental > *').length; | |
this.setCursor(lastIdx, lastStep); | |
} | |
Dz.setSlide = function(aIdx) { | |
this.idx = aIdx; | |
var old = $("section[aria-selected]"); | |
var next = $("section:nth-of-type("+ this.idx +")"); | |
if (old) { | |
old.removeAttribute("aria-selected"); | |
var video = old.$("video"); | |
if (video) { | |
video.pause(); | |
} | |
} | |
if (next) { | |
next.setAttribute("aria-selected", "true"); | |
var video = next.$("video"); | |
if (video && !!+this.params.autoplay) { | |
video.play(); | |
} | |
} else { | |
// That should not happen | |
this.idx = -1; | |
// console.warn("Slide doesn't exist."); | |
} | |
} | |
Dz.setIncremental = function(aStep) { | |
this.step = aStep; | |
var old = this.slides[this.idx - 1].$('.incremental > *[aria-selected]'); | |
if (old) { | |
old.removeAttribute('aria-selected'); | |
} | |
var incrementals = $$('.incremental'); | |
if (this.step <= 0) { | |
$$.forEach(incrementals, function(aNode) { | |
aNode.removeAttribute('active'); | |
}); | |
return; | |
} | |
var next = this.slides[this.idx - 1].$$('.incremental > *')[this.step - 1]; | |
if (next) { | |
next.setAttribute('aria-selected', true); | |
next.parentNode.setAttribute('active', true); | |
var found = false; | |
$$.forEach(incrementals, function(aNode) { | |
if (aNode != next.parentNode) | |
if (found) | |
aNode.removeAttribute('active'); | |
else | |
aNode.setAttribute('active', true); | |
else | |
found = true; | |
}); | |
} else { | |
setCursor(this.idx, 0); | |
} | |
return next; | |
} | |
Dz.goFullscreen = function() { | |
var html = $('html'), | |
requestFullscreen = html.requestFullscreen || html.requestFullScreen || html.mozRequestFullScreen || html.webkitRequestFullScreen; | |
if (requestFullscreen) { | |
requestFullscreen.apply(html); | |
} | |
} | |
Dz.setProgress = function(aIdx, aStep) { | |
var slide = $("section:nth-of-type("+ aIdx +")"); | |
if (!slide) | |
return; | |
var steps = slide.$$('.incremental > *').length + 1, | |
slideSize = 100 / (this.slides.length - 1), | |
stepSize = slideSize / steps; | |
this.progressBar.style.width = ((aIdx - 1) * slideSize + aStep * stepSize) + '%'; | |
} | |
Dz.postMsg = function(aWin, aMsg) { // [arg0, [arg1...]] | |
aMsg = [aMsg]; | |
for (var i = 2; i < arguments.length; i++) | |
aMsg.push(encodeURIComponent(arguments[i])); | |
aWin.postMessage(aMsg.join(" "), "*"); | |
} | |
function init() { | |
Dz.init(); | |
window.onkeydown = Dz.onkeydown.bind(Dz); | |
window.onresize = Dz.onresize.bind(Dz); | |
window.onhashchange = Dz.onhashchange.bind(Dz); | |
window.onmessage = Dz.onmessage.bind(Dz); | |
} | |
window.onload = init; | |
</script> | |
<script> // Helpers | |
if (!Function.prototype.bind) { | |
Function.prototype.bind = function (oThis) { | |
// closest thing possible to the ECMAScript 5 internal IsCallable | |
// function | |
if (typeof this !== "function") | |
throw new TypeError( | |
"Function.prototype.bind - what is trying to be fBound is not callable" | |
); | |
var aArgs = Array.prototype.slice.call(arguments, 1), | |
fToBind = this, | |
fNOP = function () {}, | |
fBound = function () { | |
return fToBind.apply( this instanceof fNOP ? this : oThis || window, | |
aArgs.concat(Array.prototype.slice.call(arguments))); | |
}; | |
fNOP.prototype = this.prototype; | |
fBound.prototype = new fNOP(); | |
return fBound; | |
}; | |
} | |
var $ = (HTMLElement.prototype.$ = function(aQuery) { | |
return this.querySelector(aQuery); | |
}).bind(document); | |
var $$ = (HTMLElement.prototype.$$ = function(aQuery) { | |
return this.querySelectorAll(aQuery); | |
}).bind(document); | |
$$.forEach = function(nodeList, fun) { | |
Array.prototype.forEach.call(nodeList, fun); | |
} | |
</script> | |
<!-- vim: set fdm=marker: }}} --> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment