Skip to content

Instantly share code, notes, and snippets.

@jchonglee
Last active December 25, 2015 19:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jchonglee/7027692 to your computer and use it in GitHub Desktop.
Save jchonglee/7027692 to your computer and use it in GitHub Desktop.
Star Wars graph. Listen to this while viewing: http://www.youtube.com/watch?v=kuovuHvaxlU
/* Graph Stylesheet for Why-Visualization */
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.text, .text-hover {
pointer-events: none;
font: 0.3em sans-serif;
stroke-width: 1.0px;
}
.text {
opacity: 0.7;
stroke: #555;
}
.text-hover {
opacity: 1.0;
stroke: #222;
}
.link {
stroke: #888;
stroke-opacity: 0.3;
stroke-width: 1.0px;
}
.link-hover {
stroke: #888;
opacity: .8;
stroke-width: 2.0px;
}
.fade {
stroke: #888;
opacity: .1;
stroke-width: 1.0px;
}
div#graph {
width: 1200px;
height: 1200px;
margin: auto auto;
}
<!doctype html>
<html>
<head>
<title>Test Graph</title>
<link rel="stylesheet" href="starwhy.css">
</head>
<body>
<div id="graph"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="starwhy.js"></script>
<script>
var graph = JSON.parse('{"nodes": [{"group": 5, "type": "thing", "name": "The Force", "img": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSd4aKUMuAb27X2cA3DMwGuvMEt6wr0x9kYusEFLPo02BQ9104swQ"}, {"group": 1, "type": "sith lord", "name": "Darth Vader", "img": "http://images3.wikia.nocookie.net/__cb20130508083822/scifi/images/6/6a/DarthVader083111.jpg"}, {"group": 1, "type": "princess", "name": "Leia Organa", "img": "http://2.bp.blogspot.com/-IsD3IHKT9lk/T23RAe6ms1I/AAAAAAAAE_8/qBG7RIerigs/s1600/princess+leia.jpg"}, {"group": 1, "type": "jedi", "name": "Luke Skywalker", "img": "http://blogs.dailymail.com/nerdliving/files/2011/09/luke-150x150.jpg"}, {"group": 1, "name": "Obi Wan Kenobi", "nickname": "Ben", "type": "jedi", "img": "http://1.bp.blogspot.com/-AMs5cqQwjtg/UTZO22gF2gI/AAAAAAAAATM/M0hgT09XqW4/s1600/alec-guinness-as-ben-obi-wan-kenobi-in-star.jpg"}, {"group": 1, "name": "R2D2", "type": "droid", "specialization": "navigation", "img": "https://si0.twimg.com/profile_images/3303885663/d80bbd27448860d78cc8a6f77236dcf6.jpeg"}, {"group": 1, "name": "C3PO", "type": "droid", "specialization": "human cyborg relations", "img": "http://img.geocaching.com/track/large/c36a8b88-865c-4798-b6a3-36233db56d64.jpg"}, {"group": 1, "type": "smuggler", "name": "Han Solo", "img": "http://img.moviepilot.com/assets/tarantulaV2/project_images_square_big/1360175160_han_solo_movie-oo.jpg"}, {"group": 1, "type": "bounty hunter", "name": "Greedo", "img": "http://images3.wikia.nocookie.net/__cb20111104205227/starwars/images/thumb/c/c6/Greedo.jpg/500px-Greedo.jpg"}, {"group": 1, "type": "crime lord", "name": "Jabba the Hutt", "img": "http://static.giantbomb.com/uploads/square_small/0/7092/202939-jabba.jpg"}, {"group": 1, "type": "smuggler", "name": "Chewbacca", "img": "http://t3.gstatic.com/images?q=tbn:ANd9GcR9hykKAnqvBlyS5L-7_WTvDHXruaUdGrwVbHiA5Fyqz8oy59qqoQ"}, {"group": 1, "type": "pilot", "name": "Biggs Darklighter", "img": "http://2.bp.blogspot.com/-v7FE76fUyD8/TiXFqsEAwMI/AAAAAAAAJSY/xX3WLjXP6kA/s1600/4.jpg"}, {"group": 1, "type": "pilot", "name": "Jek Tono Porkins", "img": "http://www.purplefalcon.com/images/blog/star-wars-whiskey/Jek-Tono-Porkins.jpeg"}, {"group": 1, "type": "pilot", "name": "Wedge Antilles", "img": "http://static.comicvine.com/uploads/square_small/1/15317/491397-wedge_antilles.jpg"}, {"group": 1, "type": "imperial", "name": "Grand Moff Tarkin", "img": "http://images3.wikia.nocookie.net/__cb20081126032741/fanfiction/images/e/eb/Tarkin1.jpg"}, {"group": 4, "type": "star fighter", "name": "TIE Advanced x1", "img": "http://images1.wikia.nocookie.net/__cb20080120220950/starwars/images/6/65/Tiex1-headon.jpg"}, {"group": 4, "type": "freighter", "name": "Millenium Falcon", "img": "http://www.digital-polyphony.com/Celebrity-Image-Star-Wars---Millenium-Falcon-73013.jpg"}, {"group": 4, "type": "star fighter", "name": "X-wing Fighter", "img": "http://icons.iconarchive.com/icons/jonathan-rey/star-wars-vehicles/256/X-Wing-02-icon.png"}, {"group": 2, "type": "group", "name": "Rebel Alliance", "img": "http://store.shadowvexindustries.com/image/cache/data/Stickers/RebelAllianceLogo-500x500.png"}, {"group": 2, "type": "group", "name": "Galactic Empire", "img": "http://fc07.deviantart.net/fs70/f/2010/174/b/8/Galactic_Empire_Symbol_by_William_of_Orange.jpg"}, {"group": 2, "type": "group", "name": "Red Squadron", "img": "http://i26.photobucket.com/albums/c134/Kouroe/red-squadron-logo-2.png"}, {"group": 3, "type": "location", "name": "Death Star", "img": "http://3.bp.blogspot.com/_X3lNYc1hcaE/TJfE_JCNHNI/AAAAAAAAAyc/RzllST9oJVA/s1600/death_star_2.jpg"}, {"group": 3, "type": "planet", "name": "Alderaan", "img": "http://images3.wikia.nocookie.net/__cb20061211013807/starwars/images/4/4a/Alderaan.jpg"}, {"group": 3, "type": "moon", "name": "Yavin IV", "img": "http://static.giantbomb.com/uploads/square_small/0/7941/287645-250px_yavin_4.jpg"}, {"group": 3, "type": "planet", "name": "Tatooine", "img": "http://swc.fs2downloads.com/media/screenshots/Misc/Brand-X/Planets%20-%20Tatooine.jpg"}, {"group": 5, "type": "thing", "name": "Technical Schematics", "img": "http://images2.wikia.nocookie.net/__cb20090224141147/starwars/images/3/3f/Deathstar_blueprint.jpg"}], "links": [{"source": 1, "target": 0, "value": 1, "label": "uses"}, {"source": 3, "target": 0, "value": 1, "label": "uses"}, {"source": 4, "target": 0, "value": 1, "label": "uses"}, {"source": 1, "target": 4, "value": 1, "label": "kills"}, {"source": 2, "target": 1, "value": 1, "label": "father"}, {"source": 3, "target": 1, "value": 1, "label": "father"}, {"source": 1, "target": 15, "value": 1, "label": "flies"}, {"source": 1, "target": 19, "value": 1, "label": "member"}, {"source": 1, "target": 21, "value": 1, "label": "stationed"}, {"source": 4, "target": 1, "value": 1, "label": "trains"}, {"source": 21, "target": 22, "value": 1, "label": "destroys"}, {"source": 14, "target": 21, "value": 1, "label": "commands"}, {"source": 25, "target": 21, "value": 1, "label": "describe"}, {"source": 3, "target": 21, "value": 1, "label": "destroys"}, {"source": 2, "target": 22, "value": 1, "label": "lives"}, {"source": 14, "target": 22, "reason": "to crush rebellion", "value": 1, "label": "harms"}, {"source": 14, "target": 25, "value": 1, "label": "searching"}, {"source": 14, "target": 19, "value": 1, "label": "member"}, {"source": 5, "target": 25, "value": 1, "label": "stores"}, {"source": 2, "target": 25, "value": 1, "label": "smuggling"}, {"source": 3, "target": 25, "method": "accidentally during droid cleaning", "value": 1, "label": "discovers"}, {"source": 2, "target": 3, "value": 1, "label": "brother"}, {"source": 2, "target": 7, "reason": "smoldering chemistry", "value": 1, "label": "hates"}, {"source": 2, "target": 18, "value": 1, "label": "member"}, {"source": 2, "target": 5, "reason": "message in a droid", "value": 1, "label": "uses"}, {"source": 5, "target": 6, "value": 1, "label": "friends"}, {"source": 6, "target": 5, "value": 1, "label": "friends"}, {"source": 3, "target": 2, "value": 1, "label": "sister"}, {"source": 3, "target": 5, "value": 1, "label": "owns"}, {"source": 3, "target": 6, "value": 1, "label": "owns"}, {"source": 3, "target": 18, "value": 1, "label": "member"}, {"source": 3, "target": 20, "value": 1, "label": "member"}, {"source": 3, "target": 11, "value": 1, "label": "friends"}, {"source": 3, "target": 24, "value": 1, "label": "lives"}, {"source": 4, "target": 3, "value": 1, "label": "trains"}, {"source": 4, "target": 18, "value": 1, "label": "member"}, {"source": 4, "target": 24, "value": 1, "label": "lives"}, {"source": 18, "target": 23, "value": 1, "label": "based"}, {"source": 11, "target": 3, "value": 1, "label": "friends"}, {"source": 11, "target": 20, "value": 1, "label": "member"}, {"source": 11, "target": 18, "value": 1, "label": "member"}, {"source": 12, "target": 18, "value": 1, "label": "member"}, {"source": 12, "target": 20, "value": 1, "label": "member"}, {"source": 13, "target": 18, "value": 1, "label": "member"}, {"source": 13, "target": 20, "value": 1, "label": "member"}, {"source": 20, "target": 17, "value": 1, "label": "flies"}, {"source": 7, "target": 2, "reason": "smoldering chemistry", "value": 1, "label": "hates"}, {"source": 7, "target": 15, "value": 1, "label": "damages"}, {"source": 7, "target": 8, "method": "shot first", "value": 1, "label": "kills"}, {"source": 7, "target": 16, "value": 1, "label": "captains"}, {"source": 7, "target": 10, "value": 1, "label": "friends"}, {"source": 9, "target": 7, "label": "wants", "reason": "dumping cargo", "value": 1, "method": "bounty"}, {"source": 9, "target": 24, "value": 1, "label": "lives"}, {"source": 10, "target": 7, "reason": "life debt", "value": 1, "label": "friends"}, {"source": 10, "target": 16, "value": 1, "label": "flies"}]}');
var why = WhyGraph(graph);
</script>
</body>
/*
* why.js
* A visualization framework for the D3 of our member-product graphs.
*
* Author: Jason Chong Lee <jason@cobrain.com>
* Date: Tue Oct 08 14:17:06 2013 -0400
*
* Edited: Thu Oct 17 2013
* Desc: Added edges to graph; Changed data to Star Wars sample data
*/
function WhyGraph(graphson, options) {
// Set object properties
this.graph = graphson;
//this.url = url;
this.width = 1200;
this.height = 1200;
this.options = options;
this.force = null;
this.svg = null;
this.link = null;
this.label = null;
this.labelpaths = null;
this.defs = null;
this.node = null;
this.scale = 15;
this.gCharge = -5000;
this.gLink = 70;
this.linkedByIndex = {};
this.init = function init() {
// Initialize the force from options
this.force = this.init_force();
// Initialize the svg canvas to draw on
this.svg = d3.select("#graph").append("svg")
.attr("width", this.width)
.attr("height", this.height);
// Initialize the link function
this.link = this.svg.selectAll(".link")
.data(this.graph.links)
.enter().append("line")
.attr("class", "link");
// Initialize the path for the labels
this.labelpaths = svg.selectAll(".edgepath")
.data(this.graph.links)
.enter()
.append('path')
.attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
'class':'edgepath',
'fill-opacity':0,
'stroke-opacity':0,
'fill':'blue',
'stroke':'red',
'id':function(d,i) {return 'edgepath'+i}})
.style("pointer-events", "none");
// Initialize the labels
this.label = this.svg.selectAll(".edgelabel")
.data(this.graph.links)
.enter().append("text")
.style("pointer-events", "none")
.attr({'class':'edgelabel',
'id':function(d,i){return 'edgelabel'+i},
'dx':80,
'dy':0,
'font-size':10,
'fill':'#aaa'});
this.label.append("textPath")
.attr('xlink:href',function(d,i) {return '#edgepath'+i})
.style("pointer-events", "none")
.text(function(d,i){return d.label;});
// Fill linkedByIndex with all da links
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
console.log(this.link);
// Initialize the defs function
this.defs = this.svg.append("svg:defs");
this.defs.append("svg:clipPath")
.attr("id", "clipper")
.append("svg:circle")
.attr("r", scale);
// Initialize the node function
this.node = svg.selectAll(".node")
.data(this.graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(this.force.drag)
.on("mouseover", this.mouse_change(.3))
.on("mouseout", this.mouse_change(1));
this.node.append("circle")
.attr("class", "node")
.attr("r", scale + 1)
.attr("opacity", .8)
.style("fill", function(d) { return d3.rgb(0,0,0); })
.style("stroke", "black")
.style("stroke-width", 0);
this.node.append("image")
.attr("clip-path", "url(#clipper)")
.attr("xlink:href", function(d) {return d.img;})
.attr("x", -scale)
.attr("y", -scale)
.attr("width", scale * 2)
.attr("height", scale * 2)
.on("mouseover", this.node_mouseover)
.on("mouseout", this.node_mouseout);
this.node.append("text")
.attr("class", "text")
.attr("dx", function(d) { return -12; })
.attr("dy", function(d) { return 25; })
.text(function(d) { return d.name; });
this.node.append("title")
.text(function(d) { return d.name; });
this.force.on("tick", this.force_tick(this.link, this.node));
};
this.init_force = function init_force() {
var force = d3.layout.force()
.charge(this.gCharge)
.linkDistance(this.gLink)
.size([this.width, this.height]);
force.nodes(this.graph.nodes)
.links(this.graph.links)
.start();
return force
};
this.node_mouseover = function node_mouseover(d) {
d3.select(this)
.transition()
.attr("transform", "scale(2.0)");
d3.select(this.parentNode)
.select("circle")
.transition()
.attr("r", scale * 2 + 4);
d3.select(this.parentNode)
.select("text")
.transition()
.attr("class","text-hover")
.attr("dy", scale * 3);
//debugger;
link.attr("class",function(e) {return e.source.index == d.index || e.target.index == d.index ? "link-hover" : "link";});
};
this.node_mouseout = function node_mouseout(d) {
d3.select(this)
.transition()
.attr("transform", "scale(1.0)");
d3.select(this.parentNode)
.select("circle")
.transition()
.attr("r", scale + 1);
d3.select(this.parentNode)
.select("text")
.transition()
.attr("class","text")
.attr("dy", scale * 2);
link.attr("class",function(e) {return "link";});
};
this.is_connected = function is_connected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
this.mouse_change = function mouse_change(opacity) {
return function(d) {
node.style("stroke-opacity", function(o) {
thisOpacity = is_connected(d, o) ? 1 : opacity;
this.setAttribute('fill-opacity', thisOpacity);
d3.select(this).select("image").attr("opacity", thisOpacity);
return thisOpacity;
});
link.style("stroke-opacity", function(o) {
return o.source == d || o.target == d ? 1 : opacity;
});
};
}
this.force_tick = function force_tick(link, node) {
return function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
labelpaths.attr('d', function(d) { var path='M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y;
//console.log(d)
return path});
label.attr("transform",function(d) {
if (d.target.x<d.source.x){
bbox = this.getBBox();
rx = bbox.x+bbox.width/2;
ry = bbox.y+bbox.height/2;
return "rotate(180 "+rx+" "+ry+")";
}
else {
return "rotate(0)";
}
});
}
};
this.init();
return this;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment