<div id="map-background"></div>
<svg width="1800" height="900"></svg>
<div class="slidecontainer">
Authorship <input type="range" min="0" max="1" value="0.8" class="slider" id="myRange" step="0.02" /> Map
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/[email protected]/ssvg.js"></script>
<script src="https://measure-fps.surge.sh/measureFPS.js"></script></script>
.links line {
stroke: #999;
stroke-opacity: 0.2;
}
.nodes circle {
stroke: #fff;
stroke-width: 1px;
}
.slidecontainer {
position: absolute;
top: 10px;
right: 10px;
}
#map-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -10;
background: url('http://VISAuthorNodeLink.surge.sh/map.png') 6px 6px no-repeat;
}
new SSVG({getFps: function(fps) {
const fpsElement = document.getElementById("fps-element");
fpsElement.innerHTML = fps;
}});
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var projection = d3.geoMercator()
.scale(250)
.translate( [width / 2 - 50, height / 1.5]);
var path = d3.geoPath().projection(projection);
const geoPositionForce = (function() {
const force = (alpha) => {
if(force.savedNodes && force.savedNodes.length) {
for(const node of force.savedNodes) {
if(node.lat !== undefined) {
const target = projection([node.lng, node.lat]);
const difference = {
x: target[0] - node.x,
y: target[1] - node.y
};
node.x += difference.x * alpha * force.currentStrength;
node.y += difference.y * alpha * force.currentStrength;
}
}
}
};
force.savedNodes = [];
force.strencurrentStrengthgth = 1;
force.initialize = (nodes) => {
if(!nodes || !nodes.length) {
return;
}
force.savedNodes = nodes;
};
force.strength = (strength) => {
force.currentStrength = strength;
return force;
};
return force;
});
const centeringForce = (function(cx, cy) {
const force = (alpha) => {
if(force.savedNodes && force.savedNodes.length) {
for(const node of force.savedNodes) {
const difference = {
x: cx - node.x,
y: cy - node.y
};
const differenceLength = Math.sqrt(Math.pow(difference.x, 2) + Math.pow(difference.y, 2));
const normalizedDifference = {
x: difference.x / differenceLength,
y: difference.y / differenceLength
};
node.x += normalizedDifference.x * Math.pow(differenceLength, 1.4) * alpha * force.currentStrength;
node.y += normalizedDifference.y * Math.pow(differenceLength, 1.4) * alpha * force.currentStrength;
}
}
};
force.savedNodes = [];
force.strencurrentStrengthgth = 1;
force.initialize = (nodes) => {
if(!nodes || !nodes.length) {
return;
}
force.savedNodes = nodes;
};
force.strength = (strength) => {
force.currentStrength = strength;
return force;
};
return force;
});
var diameter = 800;
var radius = diameter / 2;
var margin = 20;
function drawRing(graph) {
var plot = svg.append("g")
.attr("class", "plot")
.attr("transform", "translate(" + width/2 + ", " + height/2 + ")");
// calculate node positions
ellipseLayout(graph.nodes);
drawNodes(graph.nodes);
}
// Calculates node locations
function ellipseLayout(nodes) {
// use to scale node index to theta value
var scale = d3.scaleLinear()
.domain([0, nodes.length])
.range([0, 2 * Math.PI]);
// calculate theta for each node
nodes.forEach(function(d, i) {
// calculate polar coordinates
var theta = scale(i);
var radial = radius - margin;
// convert to cartesian coordinates
d.x = (radial + 500) * Math.sin(theta);
d.y = (radial + 65) * Math.cos(theta);
});
}
// Draws nodes with tooltips
function drawNodes(nodes) {
d3.select(".plot").selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("id", function(d, i) { return d.id; })
.attr("cx", function(d, i) { return d.x; })
.attr("cy", function(d, i) { return d.y; })
.attr("r", 2)
.style("fill", function(d, i) { return 'red' });
}
d3.csv('https://ssvg-example-resources.surge.sh/affiliationList.csv', function(err, affiliations) {
const affiliationsWithLocation = affiliations
.filter(a => a.Position);
for(const affiliation of affiliationsWithLocation) {
const splitPos = affiliation.Position.split(',');
affiliation.lat = parseFloat(splitPos[0]);
affiliation.lng = parseFloat(splitPos[1]);
}
const affiliationsByName = {};
for(const affiliation of affiliations) {
affiliationsByName[affiliation.Name] = affiliation;
}
svg.append('g')
.attr('class', 'affiliations')
.selectAll('circle')
.data(affiliationsWithLocation)
.enter().append('circle')
/*.attr('r', 5)
.attr('cx', d => projection([d.lng, d.lat])[0])
.attr('cy', d => projection([d.lng, d.lat])[1])
.style('fill', () => '#a00');*/
var color = d3.scaleOrdinal(d3.schemeCategory20);
const forceMapStrengths = {
link: 0,
charge: 0,
radial: 0,
geoPosition: 1,
mapOpacity: 1,
center: 0
};
const forceAuthorshipStrengths = {
link: 0.6,
charge: -30,
radial: 0,//0.07,
geoPosition: 0,
mapOpacity: 0,
center: 0.015
};
function forceSelection(mapPortion){
return {
link: forceMapStrengths.link * mapPortion + forceAuthorshipStrengths.link * (1 - mapPortion),
charge: forceMapStrengths.charge * mapPortion + forceAuthorshipStrengths.charge * (1 - mapPortion),
radial: forceMapStrengths.radial * mapPortion + forceAuthorshipStrengths.radial * (1 - mapPortion),
geoPosition: forceMapStrengths.geoPosition * mapPortion + forceAuthorshipStrengths.geoPosition * (1 - mapPortion),
mapOpacity: forceMapStrengths.mapOpacity * mapPortion + forceAuthorshipStrengths.mapOpacity * (1 - mapPortion),
center: forceMapStrengths.center * mapPortion + forceAuthorshipStrengths.center * (1 - mapPortion),
}
}
var slider = document.getElementById("myRange");
var forceSettings = forceSelection(slider.value);
slider.oninput = function() {
simulation.alpha(1).restart();
forceSettings = forceSelection(this.value);
applyStrengths(forceSettings);
document.getElementById('map-background').style.opacity = forceSettings.mapOpacity;
};
var simulation = d3.forceSimulation().alphaDecay(0.02);
const forceLink = d3.forceLink().id(function(d) { return d.id; });
const forceManyBody = d3.forceManyBody();
const forceCollide = d3.forceCollide().radius(function(d) {
return Math.sqrt(30 * d.paperIndex.length)
});
const forceRadial = d3.forceRadial(300, width / 2, height / 2);
const forceGeo = geoPositionForce();
const forceCenter = centeringForce(width / 2, height / 2);
function applyStrengths(strength) {
simulation.force("link", forceLink.strength(strength.link))
.force("charge", forceManyBody.strength(strength.charge))
.force('collision', forceCollide)
.force("radial", forceRadial.strength(strength.radial))
.force("center", forceCenter.strength(strength.center))
.force('geoposition', forceGeo.strength(strength.geoPosition));
}
applyStrengths(forceSettings);
d3.json("https://ssvg-example-resources.surge.sh/VisAuthors10.json", function(error, graph) {
console.log(affiliationsWithLocation);
if (error) throw error;
for(const node of graph.nodes) {
const match = affiliationsWithLocation.filter(a => a.Name === node.affiliation);
if(match && match.length > 0) {
node.lat = match[0].lat;
node.lng = match[0].lng;
node.x = projection([node.lng, node.lat])[0];
node.y = projection([node.lng, node.lat])[1];
}
}
graph.nodes.sort((a,b) => a.paperIndex.length - b.paperIndex.length);
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter().append("g");
var circle = node.append('circle')
.attr("r", d => Math.sqrt(20 * d.paperIndex.length))
.attr("fill", function(d) {
const affiliation = affiliationsByName[d.affiliation];
if(!affiliation) {
//console.log(d.affiliation, affiliationsByName);
return '#aaa';
}
const name = affiliation.NameSimple ? affiliation.NameSimple : d.affiliation;
return color(name);
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
circle.append("title")
.text(function(d) { return d.id + ', ' + d.affiliation; });
node.append('text')
.attr('text-anchor', 'middle')
.attr('dy', 5)
.attr('font-size', 16)
.attr('pointer-events', 'none')
.text(d => {
const nameParts = d.id.split(' ');
const initials = nameParts[0][0] + nameParts[nameParts.length - 1][0];
return d.paperIndex.length >= 8 ? initials : '';
});
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
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; });
svg.select('.nodes').selectAll('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y);
svg.selectAll('text')
.attr('x', d => d.x)
.attr('y', d => d.y);
}
});
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;
}
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.