Back to list
Scatterplot
Create a chart with dots that animate when they move.
Scatterplot.svelte
See on Github
<script>
import { spring } from "svelte/motion";
import { cubicOut } from "svelte/easing";
import { extent } from "d3-array";
import { interpolateHcl } from "d3-interpolate";
import { scaleSqrt, scaleLinear } from "d3-scale";
// utility function for translating elements
const move = (x, y) => `transform: translate(${x}px, ${y}px`;
export let data = [];
// accessor functions to quickly pivot between data structures
export let xAccessor = d => d[0];
export let yAccessor = d => d[1];
export let rAccessor = d => d[2];
export let margins = {
// typical d3 margin convention
top: 20,
right: 20,
bottom: 20,
left: 20
};
let width = 1200;
$: height = width;
$: mainWidth = width - margins.right - margins.left;
$: mainHeight = height - margins.top - margins.bottom;
// the biggest constraint here:
// the number of dots has to remain static
// one workaround is to have a very long array,
// and give extra nodes no radius (r=0)
let dots = spring(
data.map((d, i) => ({
x: 0,
y: 0,
r: 0
})),
{
stiffness: 0.1,
damping: 0.9
}
);
// make me some scales!
$: xScale = scaleLinear()
.domain(extent(data, xAccessor))
.range([0, mainWidth]);
$: yScale = scaleLinear()
.domain(extent(data, yAccessor))
.range([mainHeight, 0]);
$: rScale = scaleSqrt()
.domain(extent(data, rAccessor))
.range([0, 20]);
const colorScale = scaleLinear()
.domain([0, 20])
.range(["tomato", "cornflowerblue"])
.interpolate(interpolateHcl);
// update $dots' x, y, and r attributes
// `spring` will handle the animation/interpolation
const updateData = () => {
const newDots = data.map((d, i) => ({
x: xScale(xAccessor(d)),
y: yScale(yAccessor(d)),
r: rScale(rAccessor(d))
}));
dots.set(newDots);
};
$: data, mainWidth, updateData();
</script>
<figure class="c" bind:clientWidth="{width}">
<svg {width} {height}>
<g style="{move(margins.top, margins.left)}">
{#each $dots as { x, y, r }}
<circle
style="{move(x, y)}"
r="{Math.max(0, r)}"
fill="{colorScale(r)}"></circle>
{/each}
</g>
</svg>
</figure>
Usage example:
ScatterplotWrapper.svelte
See on Github
<script>
import SimplexNoise from "simplex-noise"
import Scatterplot from "./Scatterplot.svelte"
const simplex = new SimplexNoise(0)
let iteration = 3
const createData = () => {
iteration = iteration + 1
return new Array(300)
.fill(0)
.map((_, i) => [i, i % iteration, simplex.noise2D(i, i % iteration)])
}
let data = createData()
</script>
<Scatterplot {data} />
<div class="note">Click to update</div>
<svelte:window
on:click="{() => (data = createData())}"
on:touchend="{() => (data = createData())}" />
<style>
.note {
position: absolute;
top: 0;
font-style: italic;
color: var(--text-light);
}
</style>
Click to update