|
<!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> |