Skip to content

Instantly share code, notes, and snippets.

@GitNoise
Last active March 20, 2019 23:31
Show Gist options
  • Save GitNoise/4c42f2305a181a0286cc to your computer and use it in GitHub Desktop.
Save GitNoise/4c42f2305a181a0286cc to your computer and use it in GitHub Desktop.
Star Wars dismemberments
license: mit

Further work

Design:

  • Legend with explanation of color and size of dots.
  • Size of dot should correspond to number of dismbembered limbs.
  • Dots should be reorderd to reflect data under the "Kroppsdelar" tab.
  • Width (and height) of visualisation should be calculated from width of window. At the moment the width is set to a constant value. Alternative use media breakpoints.

Built with blockbuilder.org.

Data is gathered from www.starwars.com.

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<!-- libraries -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<!-- custom -->
<link rel="stylesheet" href='starwars.css' />
<style>
/* svg {border: 1px solid black; } */
</style>
</head>
<body>
<div class="container header">Kapade kroppsdelar i Star Wars</div>
<div class="container">
<span class="bold">There she is. See-Threepio,</span> do you copy? For the moment. Uh, we're in the main hangar across
from the ship. We're right above you. Stand by. You came in that thing? You're braver that I thought. Nice! Come
on! It's them! Blast them! Get back to the ship! Where are you going? Come back! He certainly has courage. What good
will it do us if he gets himself killed? Come on! I think we took a wrong turn.
</div>
<div class="container top">
<span class="bold">There's no lock!</span> That oughta hold it for a while. Quick, we've got to get across. Find
the control that extends the bridge. Oh, I think I just blasted it. They're coming through! Here, hold this. Here
they come! For luck!
</div>
<div id="sort" class='container' class="top">
<div id='btnTime' class='btn btn-left active' data-category="year">Kronologiskt</div>
<div id='btnEp' class='btn' data-category="ep">Avsnitt</div>
<div id='btnAlignment' class='btn' data-category="alignment">Offer</div>
<div id='btnLimb' class='btn btn-right' data-category="limb">Kroppsdel</div>
</div>
<div id="description" class='container'>
Kronologiskt ordnat data efter när filmerna släpptes.
</div>
<svg class="top" />
<div id="navigation" class="container">
<div id='btnPrev' class='btn btn-left'>&lt;&lt;</div>
<div id='btnClear' class='btn'>Återställ</div>
<div id='btnNext' class='btn btn-right'>&gt;&gt;</div>
</div>
<div id="details" class="container"></div>
<script>
var descriptions = {
year: "Kronologiskt ordnat data efter när filmerna släpptes.",
ep: "Ordnad efter den ordning som filmerna utspelar sig i.",
alignment: "Statistik över vilken tillhörighet offren har.",
limb: "Antal kroppsdelar som blivit avhuggna."
}
// Data
var movies = [
{
title: 'Episode IV: A New Hope', year: new Date(1977, 5, 22), ep: 4, dismemberment:
[
{ alignement: 'g', limb: 'ar', character: 'C-3PO', desc: 'C-3PO loses his left arm. The Jundland Wastes are not to be traveled lightly, as C-3PO learned. He loses an arm to a Tusken Raider while trying to find R2-D2.' },
{ alignement: 'n', limb: 'ar', character: 'Ponda Baba', desc: 'Ponda Baba loses his right arm. When Doctor Evazan and Ponda Baba give Luke a rough time in the Mos Eisley cantina, Obi-Wan steps in to help. He helps by cutting off Ponda Baba’s right arm.' }
]
},
{
title: 'Episode V: The Empire Strikes Back', year: new Date(1980, 5, 21), ep: 5, dismemberment:
[
{ alignement: 'n', limb: 'ar', character: 'Wampa', desc: 'Wampa loses its right arm. Wampas are no match for the Force. Luke summons the Force to retrieve his lightsaber, cuts himself down, and debilitates the wampa by, you guessed it, removing its right arm.' },
{ alignement: 'g', limb: 'ar', character: 'C-3PO', desc: 'C-3PO loses all his limbs. Threepio really does not have good luck. Or any luck. This time around he puts his nose where it doesn’t belong while on Bespin, and Ugnaughts remove his limbs and later completely disassemble him.' },
{ alignement: 'g', limb: 'ha', character: 'Like', desc: 'Luke loses his right hand. When Luke encounters Darth Vader in Cloud City, he is in no way prepared for the duel. Luke does manage to put up a fight, but Vader has no trouble chopping off Luke’s right hand.' }
]
},
{
title: 'Episode VI: Return of the Jedi', year: new Date(1983, 5, 23), ep: 6, dismemberment:
[
{ alignement: 'e', limb: 'ha', character: 'Vader', desc: 'Vader loses his right hand. Again. The next time Luke stands off against Vader, he’s more prepared. He’s wiser and more skilled. Before it’s all over, he pulls a reverse and cuts off Vader’s right hand.' }
]
},
{
title: 'Episode I: The Phantom Menace', year: new Date(1999, 5, 19), ep: 1, dismemberment:
[
{ alignement: 'e', limb: 'le', character: 'Darth Maul', desc: 'Darth Maul loses both legs. After Qui-Gon Jinn dies at the hands of Darth Maul, Obi-Wan Kenobi continues the duel. Obi-Wan manages to turn the fight around, take Maul by surprise, and chops him in two near the waist.' }
]
},
{
title: 'Episode II: Attack of the Clones', year: new Date(2002, 5, 16), ep: 2, dismemberment:
[
{ alignement: 'e', limb: 'ar', character: 'Zam Wesell', desc: 'Zam Wesell loses her right arm. After the Clawdite shape-shifter Zam Wesell makes an attempt on Padmé Amidala’s life, Obi-Wan and Anakin Skywalker chase her through Coruscant. They locate her when she ducks into a bar, and Obi-Wan uses his lightsaber to remove her right forearm before she shoots him.' },
{ alignement: 'n', limb: 'le', character: 'Acklay', desc: 'Acklay loses two legs. One of the creatures in the arena during the Battle of Geonosis was the formidable acklay. It tried to take down Obi-Wan, but he escaped its pincers by slicing off two of the acklay’s front legs with his lightsaber.' },
{ alignement: 'g', limb: 'ar', character: 'Anakin', desc: 'Anakin loses his right arm. When Anakin recovers from being hit with Force lightning from Count Dooku, he leaps back into battle only to lose more than half of his right arm to Dooku’s blade.' }
]
},
{
title: 'Episode III: Revenge of the Sith', year: new Date(2005, 5, 19), ep: 3, dismemberment:
[
{ alignement: 'e', limb: 'ha', character: 'Count Dooku', desc: 'Count Dooku loses both hands. In one of many turning points for Anakin, he exacts revenge upon Count Dooku and uses his lightsaber to cut off both the Sith’s hands. (Dooku would also lose his head.)' },
{ alignement: 'e', limb: 'ha', character: 'General Grievous', desc: 'General Grievous loses two hands. General Grievous is quite the force to be reckoned with given his multiple appendages. Still, Obi-Wan sliced off two of his robotic hands in battle.' },
{ alignement: 'g', limb: 'h', character: 'Mace Windu', desc: 'Mace Windu loses his right hand. When Mace discovers that Palpatine is the Sith they’ve been looking for, he decides to handle things himself rather than going through appropriate channels. Anakin stops Mace from killing Palpatine by removing Mace’s right hand from the rest of his body.' },
{ alignement: 'g', limb: 'ha', character: 'Anakin', desc: 'Anakin loses his left arm and both his legs. We all know how Revenge of the Sith ends, and it isn’t pretty. Anakin comes to hate Obi-Wan, and they engage in a brutal battle on Mustafar. Obi-Wan cuts off Anakin’s left arm and both legs in one swipe.' }
]
},
{ title: 'Episode VII: The Force Awakens', year: new Date(2015, 12, 18), ep: 7, dismemberment: [] }
];
var viz = (function () {
// data
var _description;
var _movies;
// scales
var _sizeScale;
var _fontScale;
var _timeScale;
var _epScale;
// size
var windowWidth = 600; // get this from window size to make it responsive
var minRange = windowWidth * 0.1;
var maxRange = windowWidth * .9;
// axis
var _timeAxis;
var _epAxis;
// drawing
var svg;
var _nodes;
// force
var _force;
var _node;
var radius;
// interaction parameters
var scale;
var param;
function init(desc, data) {
svg = d3.select("svg");
_description = desc;
_movies = data;
transformData();
initInteractions();
initScales();
scale = _timeScale;
param = "year";
radius = _sizeScale(maxRange);
initAxis();
initChrome();
initForce();
draw();
/* responsive */
d3.selectAll(".container").style({
"padding-left": minRange + "px",
"width": maxRange + "px"
});
d3.select("body")
.style("font-size", _fontScale(maxRange) + "px");
}
function transformData() {
_nodes = [];
_movies.sort(function (a, b) {
return a.ep - b.ep;
});
_movies.forEach(function (d, i) {
var nds = d3.range(1, d.dismemberment.length + 1).map(function (n, i) { return { title: d.title, year: d.year, ep: d.ep, type: d.dismemberment[i] }; });
if (nds.length == 0) nds = [{ title: d.title, year: d.year, ep: d.ep, dismemberment: d.dismemberment }];
_nodes = _nodes.concat(nds);
});
}
// navigation of nodes
function navigate(direction, limit) {
var activeNode = d3.select("g.nodes .active");
deActivateNode(activeNode.node());
var id = limit;
if (activeNode.node()) {
id = +activeNode.node().id.replace("node", "");
id = id + 1 * direction;
}
var curElem = d3.select("#node" + id);
activateNode(curElem.node());
details(curElem.datum(), "details");
}
function activateButton(elemNode) {
d3.selectAll('.btn.active').classed('active', false);
d3.select(elemNode).classed('active', true);
}
function initInteractions() {
/* Ordering/sorting of nodes */
d3.selectAll("#sort .btn")
.on("click", function () {
var category = this.dataset.category;
if (category === "year") {
scale = _timeScale;
param = category;
cluster(_timeScale, category);
}
if (category === "ep") {
scale = _epScale;
param = category;
cluster(_epScale, category);
}
if (category === "alignment") statistics("alignment");
if (category === "limb") statistics("limb");
activateButton(this)
d3.select("#description").html(_description[category]);
});
/* Stepping / selecting nodes */
d3.select('#btnClear').on("click", function () {
details(null, "details")
d3.selectAll("g.nodes .active")
.each(function () { deActivateNode(this); });
})
var directions = { forward: 1, backward: -1 }
d3.select("#btnPrev").on("click", function () {
navigate(directions.backward, _node[0].length - 1);
});
d3.select("#btnNext").on("click", function () {
navigate(directions.forward, 0);
});
}
function initScales() {
// scales
_sizeScale = d3.scale.linear()
.domain([0, 900])
.range([5, 10]);
_fontScale = d3.scale.linear()
.domain([0, 900])
.range([10, 15]);
_timeScale = d3.time.scale()
.domain([new Date(1970, 1, 1), new Date(2020, 1, 1)])
.range([minRange, maxRange]);
_epScale = d3.scale.linear()
.domain([1, 7])
.range([minRange, maxRange]);
}
function details(data, elem) {
d3.select("#" + elem).html(function () {
if (data == null) return "";
var title = '<div id="title">' + data.title + '<div id="subtitle">' + data.year.toLocaleDateString() + '</div></div>';
if (!data.type) return '<div id="legend">' + title + '<div>Inga kroppsdelar ramlade av i den här filmen!</div></div>';
var result = '<div id="legend">' +
title +
'<div style="font-weight:bold">' + data.type.character + '</div>' +
'<div>Typ: ' + alignmentTypeToText(data.type.alignement) + '</div>' +
'<div>Kroppsdel: ' + limbTypeToText(data.type.limb) + '</div>' +
'<br/>' +
'<div>' + data.type.desc + '</div>' +
'</div>';
return result;
});
}
function alignmentTypeToText(type) {
var text = "Monster eller neutral";
if (type === 'g') text = "God";
if (type === 'e') text = "Ond";
return text;
}
function limbTypeToText(type) {
var text = "";
if (type === 'he') text = "Huvud";
if (type === 'ar') text = "Arm";
if (type === 'ha') text = "Hand";
if (type === 'le') text = "Ben";
return text;
}
function initAxis() {
// axis
_timeAxis = d3.svg.axis()
.scale(_timeScale)
.orient("bottom");
_epAxis = d3.svg.axis()
.scale(_epScale)
.orient("bottom")
.ticks(movies.length)
.tickFormat(d3.format("d"));
}
// force
function tick(e) {
_node
.each(moveToCategoryCenter(e.alpha))
.attr("cx", function (d) { return d.x })
.attr("cy", function (d) { return d.y });
var q = d3.geom.quadtree(_nodes),
i = 0,
n = _nodes.length;
while (++i < n)
q.visit(collide(_nodes[i]));
}
function moveToCategoryCenter(alpha) {
return function (d, i) {
var xCenter = scale(d[param]);
var yCenter = 125;
d.x += (xCenter - d.x) * alpha;
d.y += (yCenter - d.y) * alpha * .5;
};
}
function initForce() {
_force = d3.layout.force()
.nodes(_nodes)
.on("tick", tick)
.charge(-30)
.gravity(0);
}
function deActivateNode(elem) {
if (elem)
d3.select(elem).classed("active", false);
}
function activateNode(elem) {
d3.select(elem).classed("active", true);
}
function collide(node) {
var r = radius + 16,
nx1 = node.x - r,
nx2 = node.x + r,
ny1 = node.y - r,
ny2 = node.y + r;
return function (quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = radius + radius;
if (l < r) {
l = (l - r) / l * .5;
node.x -= x *= l;
node.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
};
}
function cluster(scaleParam, paramParam) {
_node.each(
function (d) {
d.fixed = false;
})
.attr("transform", "");
svg.selectAll("text.lbls").transition().attr("x", 0);
d3.selectAll("g.axis")
.transition().duration(500)
.style("opacity", 0);
d3.select("g." + paramParam)
.transition().duration(500)
.style("opacity", 1);
_force.start();
}
function initChrome() {
svg.append("svg:image")
.attr('x', 60)
.attr('y', 0)
.attr('width', 150)
.attr('height', 100)
.attr("xlink:href", "https://upload.wikimedia.org/wikipedia/commons/c/ce/Star_wars2.svg")
.style({stroke: "black" })
var axisYPos = 200;
svg.append("g")
.attr("class", "axis year")
.attr("transform", "translate(0," + axisYPos + ")")
.call(_timeAxis);
svg.append("g")
.attr("class", "axis ep")
.attr("transform", "translate(0," + axisYPos + ")")
.style("opacity", 0)
.call(_epAxis);
}
function statistics(category) {
// stop the nodes moving
_force.stop();
_node
.each(
function (d) {
d.fixed = true;
});
var yPos = 100,
xPos = minRange + 160,
duration = 750;
var labels = {
alignment: ['Goda', "Onda", "Monster eller neutrala", 'Inga'],
limb: ['Huvud', 'Arm', 'Hand', 'Ben']
};
svg.selectAll("g.axis")
.transition().duration(duration)
.style("opacity", 0);
// bind data
var lbls = svg.selectAll(".lbls")
.data(labels[category]);
// update
lbls
.style("opacity", 0)
.transition("statistics").duration(duration)
.attr("x", xPos)
.text(function (d) { return d; })
.style("opacity", 1)
// enter
lbls
.enter()
.append("text").classed("lbls", true)
.attr({
x: 20,
y: function (d, i) { return yPos + _sizeScale(maxRange) * .5 + 25 * i; }
})
.style("text-anchor", "end")
.text(function (d) { return d; })
.transition().duration(duration)
.attr("x", xPos);
['g', 'e', 'n', 'none'].forEach(function (o, oi) {
svg.selectAll("." + o)
.transition().duration(duration).ease('sin')
.attr("transform", function (d, i) {
var x = xPos + _sizeScale(maxRange) * 3 + i * _sizeScale(maxRange) * 2.5 - d.x;
var y = yPos + 25 * oi - d.y;
return "translate(" + x + "," + y + ")";
})
});
}
// rendering
function draw() {
_node = svg.append("g").classed("nodes", true)
.selectAll("circle")
.data(_force.nodes())
.enter()
.append("circle")
.attr({
"class": function (d) { return !d.dismemberment ? d.type.alignement : "none"; },
"r": function (d) {
return d.dismemberment ? _sizeScale(maxRange) * .5 : _sizeScale(maxRange);
},
'id': function (d, i) { return 'node' + i }
})
.style({
"fill": function (d) {
if (d.dismemberment) return "white"
if (d.type.alignement === 'n') return "gray";
if (d.type.alignement === 'g') return "#CAE1FF";
else return "red";
},
"stroke": "#c8c8c8"
})
.on('mouseover', function (d) {
deActivateNode(d3.select("g.nodes .active").node());
activateNode(this);
details(d, "details");
})
initChrome();
_force.start();
}
self.description = function (desc) {
_description = desc;
}
return {
init: init,
};
})();
viz.init(descriptions, movies);
</script>
</body>
body {
margin: 0;
font-family: sans-serif;
font-size: 11px;
padding: 20px;
}
.header {
font-size: 2em;
font-weight: bold;
margin-bottom: 20px;
}
#sort {
margin-top: 20px;
}
#description {
margin-top: 20px;
}
svg {
width: 100%;
height: 250px;
}
text {
text-anchor: middle;
font-size: 1.3em;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-size: 0.9em;
}
.btn {
cursor: pointer;
border: 1px solid #7f7f7f;
display: inline-block;
padding: 5px 15px;
margin: 0px;
font-size: 1.2em;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
user-select: none; /* non-prefixed version, currently
not supported by any browser */
}
.btn-left {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.btn-right {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.top {
margin-top: 20px;
}
.left {
margin-left: 450px;
}
.z {
position: absolute;
z-index: 100;
}
.active {
color: white;
background-color: #969696;
}
#legend {
margin-top: 20px;
height: 500px;
}
#legend #title {
font-weight: bold;
border-bottom: 1px solid #7c7c7c;
}
#legend div {
padding: 5px 9px;
}
#legend #title #subtitle {
font-size: 0.8em;
}
circle.active {
stroke: black !important;
stroke-width: 3;
}
.btn:hover {
background-color: #c8c8c8;
}
.bold {
font-weight: bold;
font-size: 1.1em;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment