<script>
import { LayerCake, Svg } from 'layercake';
import { scaleOrdinal, scaleBand } from 'd3-scale';
import CirclePackForce from './_components/CirclePackForce.svelte';
import data from './_data/dots.json';
const xKey = 'category';
const rKey = 'value';
const cKey = 'category';
let groupByX = $state(true);
const seriesColors = ['#ff00cc', '#00ccff', '#ffcc00'];
const manyBodyStrength = 3;
const xStrength = 0.1;
</script>
<div class="input-container">
<label><input type="radio" bind:group={groupByX} value={true} />Group by category</label>
<label><input type="radio" bind:group={groupByX} value={false} />Clump together</label>
</div>
<div class="chart-container">
<LayerCake
x={xKey}
r={rKey}
c={cKey}
xScale={scaleBand()}
rRange={[3, 12]}
cScale={scaleOrdinal()}
cRange={seriesColors}
{data}
>
<Svg>
<CirclePackForce {manyBodyStrength} {xStrength} {groupByX} stroke="#000" />
</Svg>
</LayerCake>
</div>
<style>
.chart-container {
width: 100%;
height: 250px;
}
label {
cursor: pointer;
}
input {
margin-right: 7px;
}
</style>
<script>
import { forceSimulation, forceX, forceManyBody, forceCollide, forceCenter } from 'd3-force';
import { getLayerCakeContext } from 'layercake';
const k = getLayerCakeContext();
let {
manyBodyStrength = 5,
xStrength = 0.1,
fill,
stroke = '#fff',
strokeWidth = 1,
groupByX = true
} = $props();
let nodes = $state([]);
const simulation = forceSimulation().on('tick', () => {
nodes = simulation.nodes();
});
let currentRows;
$effect(() => {
if (k.data !== currentRows) {
currentRows = k.data;
simulation.nodes(k.data.map(d => ({ ...d })));
}
simulation
.force(
'x',
forceX()
.x(
d => {
return groupByX === true ? k.xGet(d) + k.xScale.bandwidth() / 2 : k.width / 2;
}
)
.strength(xStrength)
)
.force('center', forceCenter(k.width / 2, k.height / 2))
.force('charge', forceManyBody().strength(manyBodyStrength))
.force(
'collision',
forceCollide().radius(
d => {
return (k.rGet?.(d) ?? 5) + strokeWidth / 2;
}
)
)
.alpha(1)
.restart();
return () => simulation.stop();
});
</script>
{#each nodes as point}
<circle
class="node"
r={k.rGet?.(point) ?? 5}
fill={fill ?? k.cGet?.(point) ?? '#00bbff'}
{stroke}
stroke-width={strokeWidth}
cx={point.x}
cy={point.y}
></circle>
{/each}[
{ "category": "a", "value": 0 },
{ "category": "a", "value": 2 },
{ "category": "a", "value": 5 },
{ "category": "a", "value": 23 },
{ "category": "a", "value": 12 },
{ "category": "a", "value": 50 },
{ "category": "a", "value": 7 },
{ "category": "a", "value": 20 },
{ "category": "a", "value": 15 },
{ "category": "a", "value": 30 },
{ "category": "a", "value": 2 },
{ "category": "b", "value": 10 },
{ "category": "b", "value": 12 },
{ "category": "b", "value": 15 },
{ "category": "b", "value": 3 },
{ "category": "b", "value": 2 },
{ "category": "b", "value": 5 },
{ "category": "b", "value": 35 },
{ "category": "b", "value": 2 },
{ "category": "b", "value": 5 },
{ "category": "b", "value": 20 },
{ "category": "b", "value": 12 },
{ "category": "c", "value": 5 },
{ "category": "c", "value": 4 },
{ "category": "c", "value": 2 },
{ "category": "c", "value": 32 },
{ "category": "c", "value": 25 },
{ "category": "c", "value": 40 },
{ "category": "c", "value": 35 },
{ "category": "c", "value": 25 },
{ "category": "c", "value": 19 },
{ "category": "c", "value": 25 },
{ "category": "c", "value": 10 }
]