xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>
<script src="https://npmcdn.com/babel-core@5.8.34/browser.min.js"></script>
<script type="text/javascript" src="https://gka.github.io/chroma.js/vendor/chroma-js/chroma.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
svg {
width: 100%;
height: 100%;
}
/* blend options taken from visual cinnamon tutorial: https://www.visualcinnamon.com/2016/05/beautiful-color-blending-svg-d3.html */
/*Set isolate on the group element*/
svg {isolation: isolate;}
/*Set blend mode on SVG element: e.g. screen, multiply*/
/* path { mix-blend-mode: multiply; } */
</style>
</head>
<body>
<svg></svg>
<script type="text/babel">
var width = 800;
var height = 800;
var yellow = chroma('#FFFEDE').saturate(3);
var navy = '#000e20';
var blue = chroma(navy).brighten(.5).saturate();
var colors = chroma.scale([blue, navy])
.mode('hsl')
.colors(6);
d3.csv('data.csv', (data) => {
var attendees = _.map(data, d => {
return {
name: d['First Name'],
first: d.first.split(', '),
favorite: d.favorite.split(', '),
version: parseInt(d.version.replace('v', '')),
};
});
// create links between the different types
// of firsts, favorites, and versions
var firsts = _.chain(attendees)
.map('first').flatten()
.countBy().toPairs()
.sortBy(d => -d[1])
.filter(d => d[0] !== 'N/A')
.map(0).value();
var favorites = _.chain(attendees)
.map('favorite').flatten()
.countBy().toPairs()
.sortBy(d => -d[1])
.filter(d => d[0])
.map(0).value();
var versions = _.chain(attendees)
.map('version').flatten()
.countBy().toPairs()
// .sortBy(d => -d[1])
.filter(d => d[1] > 1)
.map(0).value();
/**************************************
** calculate nodes and links
**************************************/
// radius should be attendee's first experience with dataviz
var startRad = width * .01;
var perRad = width / firsts.length * .3;
var radiusScale = d3.scaleOrdinal().domain(firsts)
.range(_.times(firsts.length, (i) => (i + 1) * perRad + startRad));
var startAngle = -.5 * Math.PI;
// angle is their current fav d3 API
var perAngle = (2 * Math.PI) / favorites.length;
var angleScale = d3.scaleOrdinal().domain(favorites)
.range(_.times(favorites.length, (i) => i * perAngle + startAngle));
// YAH LOOPS
var points = [];
_.each(attendees, attendee => {
_.each(attendee.favorite, favorite => {
var angle = angleScale(favorite);
_.each(attendee.first, first => {
var radius = radiusScale(first);
points.push({
name: attendee.name,
favorite,
first,
version: attendee.version,
focusX: Math.cos(angle) * radius,
focusY: Math.sin(angle) * radius,
});
});
});
});
// also loop through the favorites to
// make an actual circle
var outside = [];
var favoriteRad = width * .45;
_.each(favorites, favorite => {
var angle = angleScale(favorite);
outside.push({
fx: Math.cos(angle) * favoriteRad,
fy: Math.sin(angle) * favoriteRad,
});
});
var nodes = _.union(points, outside);
var simulation = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody())
// .force("collide", d3.forceCollide(2))
.force("x", d3.forceX().x(d => d.focusX))
.force("y", d3.forceY().y(d => d.focusY))
.on("tick", ticked);
var voronoi = d3.voronoi()
.x(d => d.x)
.y(d => d.y);
/**************************************
** draw the circles and links
**************************************/
var svg = d3.select('svg')
.append('g')
.attr('transform', 'translate(' + [width / 2, height / 2] + ')');
// motion blur taken from https://www.visualcinnamon.com/2016/05/real-life-motion-effects-d3-visualization.html
var defs = svg.append("defs");
defs.append("filter")
.attr("id", "motionFilter")
.attr('width', '300%')
.attr('height', '300%')
.attr('x', '-100%')
.attr('y', '-100%')
.append("feGaussianBlur")
.attr("in", "SourceGraphic")
.attr("stdDeviation", "2");
var pathContainer = svg.append('g');
var paths, triangles;
var circles = svg.selectAll('g')
.data(points)
.enter().append('g');
// first the blur
circles.append('circle')
.attr('fill', yellow)
.attr('r', d => (4 / d.version - 1) * 3.5)
.attr('opacity', .25)
.style("filter", "url(#motionFilter)");
// then the actual star
circles.append('circle')
.attr('fill', yellow)
.attr('r', d => (4 / d.version - 1) * .75);
function ticked() {
circles.attr('transform', (d) =>
'translate(' + [d.x, d.y] + ')');
triangles = voronoi.triangles(nodes);
// now create the triangles
paths = pathContainer.selectAll('path')
.data(triangles);
paths.exit().remove();
paths.enter().append('path')
.merge(paths)
.attr('d', d => {
return 'M' + _.map(d, function(point) {
return point.x + ',' + point.y;
}).join(' L') + 'Z';
}).attr('fill', (d, i) => colors[i % 6])
.attr('stroke', (d, i) => colors[i % 6])
.attr('opacity', .85);
}
});
</script>
</body>
Modified http://gka.github.io/chroma.js/vendor/chroma-js/chroma.min.js to a secure url
https://d3js.org/d3.v4.min.js
https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js
https://npmcdn.com/babel-core@5.8.34/browser.min.js
https://gka.github.io/chroma.js/vendor/chroma-js/chroma.min.js