Skip to content

Instantly share code, notes, and snippets.

@bjorsq
Last active January 7, 2019 21:19
Show Gist options
  • Select an option

  • Save bjorsq/7e61582a03c5baad285e5c3928fc4657 to your computer and use it in GitHub Desktop.

Select an option

Save bjorsq/7e61582a03c5baad285e5c3928fc4657 to your computer and use it in GitHub Desktop.
WKW Force directed graph v 0.0.2
<svg width="1000" height="1000"><rect width="100%" height="100%" class="bg"/></svg><div class="wkwpanel"><button class="wkwpanel-close">close</button><div class="wkwpanel-content"></div></div>
/**
* Force-directed graph script for Who Knew Whom
* Uses D3 and a custom Wordpress JSON API endpoint.
*/
(function($){
var width = $(window).width(),
height = $(window).height(),
breakpoint = 1024,
svg = d3.select('svg'),
min_zoom = 0.1,
max_zoom = 7,
api_url = 'https://culturalcartography.net/wkw-api/wkw/v1/',
zoom, link, node, text, simulation;
svg.attr('width', width).attr('height', height);
//zoom = d3.behavior.zoom().scaleExtent([min_zoom,max_zoom]);
var gl = svg.append("g").attr("class", "links"),
gn = svg.append("g").attr("class", "nodes"),
gt = svg.append("g").attr("class", "labels");
var t = d3.transition().duration(750);
simulation = d3.forceSimulation()
.force("link", d3.forceLink().distance(80).id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-40))
.force("center", d3.forceCenter(width/2, height / 2));
function getData(id, callback){
d3.json(api_url + 'd3/' + id, function(error, graphdata){
if ( error ) throw error;
callback(id, graphdata);
});
}
function update(id, graph){
link = gl.selectAll("line").data(graph.links);
link.exit().remove();
link = link.enter().append("line").merge(link);
node = gn.selectAll("circle").data(graph.nodes.reverse(), function(d) { return d.id; });
node.exit().transition()
.attr("r", 0)
.remove();
node = node.enter().append("circle")
.merge(node)
.attr("class", function(node) { hasbio = node.bio?' bio':'';return 'nodes level'+node.level+hasbio+' '+node.id+'-name';})
.attr("r", function(d) { return (30 / (d.level*d.level)); })
//.call(function(d) { d.transition().attr("r", (30 / (d.level*d.level))); });
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.on('click', function(d){getData(d.id, update);});
text = gt.selectAll("text").data(graph.nodes.reverse());
text.exit().remove();
text = text.enter().append("text").attr("text-anchor", "middle")
.merge(text)
.text(function(node) { return node.name })
.attr('id', function(node) { return node.id+'-text'})
.attr("class", function(node) { hasbio = node.bio?' bio':'';return 'texts level'+node.level+hasbio+' '+node.id+'-name';})
.attr("x", 0)
.attr("dy", function(node) { return -(40/node.level);});
text.on('click', clickName);
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
simulation.alpha(1).restart();
showBio(id);
}
function ticked() {
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
function clickName(selectedNode){
getData(selectedNode.id, update);
}
function showBio(id)
{
d3.json(api_url + 'name/' + id, function(error, data){
if ( error ) throw error;
$('.wkwpanel-content').empty();
$('.wkwpanel-content').append($('<h1/>').text(data.name));
var bio = $('<div class="wkwpanel-content-bio"/>');
bio.append(data.content)
if ( data.linked && data.linked.length ) {
var list = $('<ul class="inline-list"/>');
for (var i = 0; i < data.linked.length; i++) {
if (data.linked[i].url){
list.append('<li><a href="'+data.linked[i].url+'" data-nameid="'+data.linked[i].id+'">'+data.linked[i].name+'</a></li>');
} else {
list.append('<li><span data-nameid="'+data.linked[i].id+'">'+data.linked[i].name+'</span></li>');
}
}
bio.append($('<h2/>').html(data.name+' knew&hellip;')).append(list);
}
$('.wkwpanel-content').append(bio);
$('.wkwpanel').show();
moveGraph('left');
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
$('.wkwpanel').on('click', '.wkwpanel-close',function() {
$('.wkwpanel').hide();
moveGraph();
});
$('.wkwpanel').on('click', 'a[data-nameid]',function(e) {
getData($(this).data('nameid'), update);
e.preventDefault();
});
$('.wkwpanel').on('mouseover', 'a[data-nameid]',function(e) {
$('.'+$(this).data('nameid')+'-name').addClass('hover');
});
$('.wkwpanel').on('mouseout', 'a[data-nameid]',function(e) {
$('.'+$(this).data('nameid')+'-name').removeClass('hover');
});
function moveGraph(dir) {
if ( width > breakpoint ) {
if (svg.attr('data-align') !== dir) {
svg.attr('data-align', dir);
d3.select({}).transition()
.tween("center.move", function() {
var i = (dir === 'left')? d3.interpolateArray([width/2, height/2],[width/3, height/2]): d3.interpolateArray([width/3, height/2],[width/2, height/2]);
return function(t) {
var c = i(t);
simulation.force('center', d3.forceCenter(c[0], c[1])).restart();
};
});
}
}
}
function resizeGraph() {
width = $(window).width();
height = $(window).height();
svg.attr('width', width).attr('height', height);
align = svg.attr('data-align');
if ( align === 'left' && width > breakpoint ) {
simulation.force('center', d3.forceCenter(width/3, height/2)).restart();
} else {
simulation.force('center', d3.forceCenter(width/2, height/2)).restart();
}
}
var resizeTimer;
$(window).on('resize', function(e) {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
resizeGraph();
}, 250);
});
getData(3717, update);
})(jQuery);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
$background:#19aa5a;
$circle-fill:#0fc;
$circle-stroke:#19aa5a;
$circle-stroke-width:1.5px;
$text-fill:#0fc;
$text-stroke:#19aa5a;
$line-colour:#ccc;
$line-opacity:0.6;
$level1-colour:#0fc;
$level2-colour:mix($level1-colour,$background,66%);
$level3-colour:mix($level1-colour,$background,33%);
/* panel */
$panel-background:rgba(0,0,0,0.2);
$panel-colour:#fff;
$close-button-colour:#fff;
$close-button-background:transparent;
$close-button-border:none;
$scrollbar-track:lighten($background, 1%);
$scrollbar-thumb:darken($background, 5%);
$scrollbar-thumb-hover:darken($background, 10%);
/* Handsome Pro Regular font */
@font-face {
font-family: 'HandsomeProRegular';
src: url('https://culturalcartography.net/wp-content/themes/wkw-theme/font/handsomepro-regular-webfont.eot');
src: url('https://culturalcartography.net/wp-content/themes/wkw-theme/font/handsomepro-regular-webfont.eot?#iefix') format('embedded-opentype'),
url('https://culturalcartography.net/wp-content/themes/wkw-theme/font/handsomepro-regular-webfont.woff') format('woff'),
url('https://culturalcartography.net/wp-content/themes/wkw-theme/font/handsomepro-regular-webfont.ttf') format('truetype'),
url('https://culturalcartography.net/wp-content/themes/wkw-theme/font/handsomepro-regular-webfont.svg#HandsomeProRegular') format('svg');
font-weight: normal;
font-style: normal;
}
:root {
box-sizing: border-box;
}
*,
::before,
::after {
box-sizing: inherit;
}
.bg {
fill:$background;
}
.links line {
stroke: $line-colour;
stroke-opacity: $line-opacity;
stroke-width:.5px;
}
.texts {
font-family: 'HandsomeProRegular', sans-serif;
font-size:1em;
fill:$text-fill;
stroke:$text-stroke;
stroke-width:.01em;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.texts.bio {
cursor:pointer;
}
.texts.level1 {
font-size:3em;
fill:$level1-colour;
opacity:1;
}
.texts.hover,
.nodes circle.hover {
fill:white;
}
.texts.level2 {
font-size:1em;
}
.texts.level3 {
display:none;
}
.nodes circle {
stroke:$circle-stroke;
stroke-width: $circle-stroke-width;
opacity:1;
}
.nodes.level1 {
fill:$level1-colour;
}
.nodes.level2 {
fill:$level2-colour;
cursor:move;
}
.nodes.level3 {
fill:$level3-colour;
}
ul.inline-list {
padding:0;
margin:0;
li {
padding:0;
margin:0;
display: inline;
list-style: none;
&:after {
content: ", ";
}
&:last-child:after {
content: "";
}
}
}
.wkwpanel {
position:fixed;
display:none;
top:0;
right:0;
bottom:0;
left:0;
transition: left .5s;
button.wkwpanel-close {
background:$close-button-background;
border:$close-button-border;
margin:0;
border-radius:.2em;
color:$close-button-colour;
position:absolute;
right:1.5em;
top:1.5em;
cursor: pointer;
font-size: 2em;
height: 1em;
width: 1em;
text-indent: 10em;
overflow: hidden;
outline:none;
&::after {
position: absolute;
line-height: 0.5;
top: 0.22em;
left: 0.18em;
text-indent: 0;
content: "\00D7";
}
}
.wkwpanel-content {
margin:2em;
padding:2em;
border-radius:2em;
background-color:$panel-background;
color:$panel-colour;
line-height:1.5;
h1, h2 {
font-family: 'HandsomeProRegular', sans-serif;
line-height:1;
margin:.3em 0;
}
h1 {
font-size:3em;
}
h2 {
font-size:2em;
}
a, a:hover, a:visited, a:visited:hover, a:active {
color:#fff;
text-decoration:none;
}
a:hover, a:visited:hover {
text-decoration:underline;
}
.wkwpanel-content-bio {
&::-webkit-scrollbar {
width: 10px;
}
&::-webkit-scrollbar-track {
background: $scrollbar-track;
border-radius: 10px;
}
/* Handle */
&::-webkit-scrollbar-thumb {
border-radius: 10px;
background: $scrollbar-thumb;
}
/* Handle on hover */
&::-webkit-scrollbar-thumb:hover {
background: $scrollbar-thumb-hover;
}
overflow:auto;
padding-right:1em;
height:calc(100vh - 12em);
}
}
}
@media screen and (min-width:1024px) {
.wkwpanel {
left:50%;
}
}
@media screen and (min-width:1280px) {
.wkwpanel {
left:66%;
}
}

WKW Force directed graph v 0.0.2

This version also uses jQuery to manioulate screen elements, show the biography panel, and has some responsiveness built in.

A Pen by Peter Edwards on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment