References:
Beeswarm Inspired by Beeswarm
Toggle inspired by Pure CSS toggle buttons
Color palette inspired by Material Motion
Built with blockbuilder.org
license: mit |
References:
Beeswarm Inspired by Beeswarm
Toggle inspired by Pure CSS toggle buttons
Color palette inspired by Material Motion
Built with blockbuilder.org
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
body { | |
background-color: #1D1D1D; | |
bottom: 0; | |
font-family: 'Helvetica', 'Arial', sans-serif; | |
left: 0; | |
margin: 0; | |
position: fixed; | |
right: 0; | |
top: 0; | |
} | |
.value { | |
stroke: #C61661; | |
} | |
.voronoi { | |
stroke: #F4AFC9; | |
fill: none; | |
} | |
.axis .domain, | |
.axis .tick line { | |
stroke: #FFF; | |
} | |
.axis .tick text { | |
fill: #FFF; | |
} | |
.voronoi-toggle { | |
position: absolute; | |
right: 20px; | |
top: 20px; | |
} | |
.toggle-label { | |
color: #666; | |
display: inline-block; | |
padding-left: 5px; | |
vertical-align: top; | |
} | |
.voronoi-toggle .toggle + .toggle-button { | |
display: inline-block; | |
} | |
</style> | |
<style> | |
.toggle { | |
display: none; | |
} | |
.toggle, .toggle:after, .toggle:before, .toggle *, .toggle *:after, .toggle *:before, .toggle + .toggle-button { | |
box-sizing: border-box; | |
} | |
.toggle::-moz-selection, .toggle:after::-moz-selection, .toggle:before::-moz-selection, .toggle *::-moz-selection, .toggle *:after::-moz-selection, .toggle *:before::-moz-selection, .toggle + .toggle-button::-moz-selection { | |
background: none; | |
} | |
.toggle::selection, .toggle:after::selection, .toggle:before::selection, .toggle *::selection, .toggle *:after::selection, .toggle *:before::selection, .toggle + .toggle-button::selection { | |
background: none; | |
} | |
.toggle + .toggle-button { | |
outline: 0; | |
display: block; | |
width: 2.5em; | |
height: 1.25em; | |
position: relative; | |
cursor: pointer; | |
-webkit-user-select: none; | |
-moz-user-select: none; | |
-ms-user-select: none; | |
user-select: none; | |
} | |
.toggle + .toggle-button:after, .toggle + .toggle-button:before { | |
position: relative; | |
display: block; | |
content: ""; | |
width: 50%; | |
height: 100%; | |
} | |
.toggle + .toggle-button:after { | |
left: 0; | |
} | |
.toggle + .toggle-button:before { | |
display: none; | |
} | |
.toggle:checked + .toggle-button:after { | |
left: 50%; | |
} | |
.toggle + .toggle-button { | |
background: #666; | |
border-radius: 1.25em; | |
padding: 2px; | |
-webkit-transition: all .4s ease; | |
transition: all .4s ease; | |
} | |
.toggle + .toggle-button:after { | |
border-radius: 50%; | |
background: #fff; | |
-webkit-transition: all .2s ease; | |
transition: all .2s ease; | |
} | |
.toggle:checked + .toggle-button { | |
background: #F4AFC9; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="voronoi-toggle"> | |
<input class="toggle" id="toggle-1" type="checkbox" onclick="toggleVoronoi()" /> | |
<label class="toggle-button" for="toggle-1"></label> | |
<div class="toggle-label">Show voronoi</div> | |
</div> | |
<script> | |
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; | |
function getRandomData(numberPoints) { | |
return d3.range(numberPoints).map(function(){ | |
return { | |
value: Math.floor(Math.random() * 10 * 10 * 10 * 10 * 10), | |
label: makeLabel() | |
}; | |
}); | |
} | |
function makeLabel() { | |
return ALPHABET[Math.floor(Math.random() * ALPHABET.length)] + | |
ALPHABET[Math.floor(Math.random() * ALPHABET.length)]; | |
} | |
</script> | |
<script> | |
// DATA MANIPULATION | |
const data = getRandomData(50); | |
const extent = d3.extent(data, function(d){return d.value;}); | |
// SETUP CHART AND FUNCTORS | |
const width = 960; | |
const height = 500; | |
const margin = {top: 50, bottom: 50}; | |
const svg = d3.select('body').append('svg') | |
.attr('width', 960) | |
.attr('height', 500) | |
const g = svg.append('g') | |
.attr('transform', `translate(0,${margin.top})`) | |
const yScale = d3.scaleLog() | |
.domain(extent) | |
.rangeRound([height - margin.top - margin.bottom, 0]); | |
const yAxis = d3.axisLeft() | |
.scale(yScale) | |
.ticks(14, '.0s'); | |
const simulation = d3.forceSimulation(data) | |
.force('x', d3.forceY(function(d) {return yScale(d.value);}).strength(1)) | |
.force('y', d3.forceX(width / 2)) | |
.force('collide', d3.forceCollide(8)) | |
.stop(); | |
for (var i = 0; i < 120; ++i){ | |
simulation.tick(); | |
}; | |
const voronoiCells = d3.voronoi() | |
.extent([[0, -margin.top], [width, height + margin.top]]) | |
.x(function(d) {return d.x;}) | |
.y(function(d) {return d.y;}) | |
.polygons(data); | |
// RENDER | |
g.append('g') | |
.attr('class', 'axis') | |
.attr('transform', `translate(${width * 0.4},0)`) | |
.call(yAxis); | |
const cells = g | |
.append('g').attr('class', 'cells') | |
.selectAll('.cells') | |
.data(voronoiCells).enter() | |
.append('g').attr('class', 'cell'); | |
cells.append('circle') | |
.attr('class', 'value') | |
.attr('cx', function(d) {return d.data.x;}) | |
.attr('cy', function(d) {return d.data.y;}) | |
.attr('fill', 'none') | |
.attr('r', 6) | |
.attr('stroke-width', 2) | |
.on('mouseover', function(d){console.log(d.data)}); | |
// OPTIONAL / DEBUGGING | |
cells.append('path') | |
.attr('class', 'voronoi') | |
.attr('opacity', 0) | |
.attr('d', function(d) {return 'M' + d.join('L') + 'Z';}) | |
// FOR FUN | |
setInterval(function(){ | |
const newData = getRandomData(50); | |
const newExtent = d3.extent(newData, function(d){return d.value;}); | |
yScale.domain(newExtent); | |
yAxis.scale(yScale); | |
const newSimulation = d3.forceSimulation(newData) | |
.force('x', d3.forceY(function(d) {return yScale(d.value);}).strength(1)) | |
.force('y', d3.forceX(width / 2)) | |
.force('collide', d3.forceCollide(8)) | |
.stop(); | |
for (var i = 0; i < 120; ++i){ | |
newSimulation.tick(); | |
}; | |
const newVoronoiCells = d3.voronoi() | |
.extent([[0, -margin.top], [width, height + margin.top]]) | |
.x(function(d) {return d.x;}) | |
.y(function(d) {return d.y;}) | |
.polygons(newData); | |
// // RENDER | |
g.selectAll('.axis').transition().duration(1000) | |
.call(yAxis); | |
d3.selectAll('.cell .value').data(newVoronoiCells) | |
.transition().duration(1000) | |
.attr('cx', function(d) {return d.data.x;}) | |
.attr('cy', function(d) {return d.data.y;}) | |
d3.selectAll('.cell .voronoi').data(newVoronoiCells) | |
.transition().delay(500).duration(1) | |
.attr('d', function(d) {return 'M' + d.join('L') + 'Z';}) | |
}, 1500) | |
</script> | |
<script> | |
function toggleVoronoi() { | |
d3.selectAll('.voronoi') | |
.transition() | |
.attr('opacity', function(){ | |
return +this.getAttribute('opacity') ? 0 : 1; | |
}) | |
} | |
</script> | |
</body> |