Skip to content

Instantly share code, notes, and snippets.

@trebor
Last active December 13, 2017 19:45
Show Gist options
  • Save trebor/d511852291dd666792f78b6a3a52e5f3 to your computer and use it in GitHub Desktop.
Save trebor/d511852291dd666792f78b6a3a52e5f3 to your computer and use it in GitHub Desktop.
N Little Circles
license: mit

An update of Nohmapp's D3.v4 update of Enjalot's Three Little Circles Tributary.io

This gist covers:

  • adding new data
  • changing existing data
  • removing data
  • transitions
  • data stored in SVG groups
  • dynamic scales

Note that the circles alwasy exactly fit into the display area.

A demo can be found on bl.ocks.org.

It was built using blockbuilder.org.

<!DOCTYPE html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
margin:0;
position:fixed;
top:0;
right:0;
bottom:0;left:0;
}
.buttons {
position: fixed;
top: 0px;
left: 0px;
}
button {
font-size: 24px;
color: #888;
border-radius: 5px;
margin: 4px;
}
</style>
</head>
<body>
<script>
const WIDTH = 960
const HEIGHT = 500;
const MIN_RAD = 10;
const MAX_RAD = 30;
const STROKE = 4;
const svg = d3.select("body").append("svg")
.attr("width", WIDTH)
.attr("height", HEIGHT);
let nextId = 0;
const data = d3.range(5).map(createDatum);
function addCircle() {
data.push(createDatum());
render();
}
function removeCircle() {
if (data.length > 0) {
const idx = Math.floor(Math.random() * data.length);
data.splice(idx, 1);
render();
}
}
function changeCircle() {
if (data.length > 0) {
const idx = Math.floor(Math.random() * data.length);
data[idx] = {
...createDatum(),
id: data[idx].id,
};
render();
}
}
function createDatum() {
return {
id: nextId++,
v1: Math.round(Math.random() * 5000),
v2: Math.round(Math.random() * 73),
size: Math.round(Math.random() * 50),
};
}
const radius = d3.scaleLinear()
.range([MIN_RAD, MAX_RAD]);
const x = d3.scaleLinear()
.range([MAX_RAD + STROKE / 2, (WIDTH - MAX_RAD) - STROKE / 2]);
const y = d3.scaleLinear()
.range([MAX_RAD + STROKE / 2, (HEIGHT - MAX_RAD) - STROKE / 2]);
// initial render
render();
function render() {
const t = d3.transition().duration(1000);
radius.domain(d3.extent(data, d => d.size));
x.domain(d3.extent(data, d => d.v1));
y.domain(d3.extent(data, d => d.v2));
const updates = svg.selectAll('g').data(data, d => d.id);
const enters = updates.enter();
const exits = updates.exit();
// entering groups
const groupEnters = enters
.append('g')
.attr("transform", 'translate(' + [WIDTH / 2, HEIGHT / 2] + ')');
// updating groups
groupEnters
.merge(updates)
.transition(t)
.attr("transform", ({v1, v2}) => 'translate(' + [x(v1), y(v2)] + ')');
// entering circles
groupEnters.append('circle')
.attr('stroke', '#888')
.attr('fill', '#ccc')
.attr('stroke-width', STROKE)
.attr('r', 0);
// entering and updating circles
groupEnters
.merge(updates)
.select('circle')
.transition(t)
.attr('r', d => radius(d.size));
// exiting circles
exits.select('circle')
.transition(t)
.attr('r', 0);
// entering text
groupEnters.append("text")
.style('font-size', '0px')
.attr('text-anchor', 'middle');
// entering and updating text
groupEnters.merge(updates)
.select('text')
.transition(t)
.attr('dy', '0.33em')
.style('font-size', '20px')
.text(d => d.id);
// exiting text
exits.select('text')
.transition(t)
.style('font-size', '0px');
exits
.transition(t)
.remove();
}
</script>
<div class="buttons">
<button onclick="addCircle()">Add Circle</button>
<button onclick="changeCircle()">Change Circle</button>
<button onclick="removeCircle()">Remove Circle</button>
</div>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment