Skip to content

Bellendiagram ​

Samenstelling

"Hoe groot is elk onderdeel, en hoeveel ervan is al gerealiseerd?" Een bellenwolk beantwoordt de vraag naar omvang in één oogopslag: elke cirkel heeft een grootte op basis van waarde (oppervlakte, niet straal), en een zwaartekrachtsimulatie trekt ze samen tot een net cluster, zodat de grote bellen duidelijk domineren. Net als bij de treemap kan elke bel een tweedelige splitsing dragen - een volle gerealiseerde kern binnen een lichtere onbenutte ring - zodat je grootte en voortgang samen afleest.

Example
canvas · responsive
Ga dieper: Insights-gids·DevTools-gids

Geen splitsing nodig? Laat partial weg voor een nette proportionele wolk, één kleur per categorie:

Example
canvas · responsive
Ga dieper: Insights-gids·DevTools-gids

Het cluster wordt opgebouwd met d3-force: bellen vallen richting het midden (gravity) en duwen elkaar uit elkaar zodat ze nooit overlappen (botsing). De simulatie wordt synchroon afgerond, zodat SVG en canvas exact dezelfde, reproduceerbare layout renderen.

Wanneer kies je deze ​

  • Omvang in één oogopslag. Producten, markten, zoekwoorden als een wolk met verschillende groottes - wanneer "welke zijn groot?" meer telt dan een exacte rangschikking, geeft het cluster meteen antwoord.
  • Kansenkaarten. Met de splitsing is een grote bel met een dunne gerealiseerde kern geld dat op tafel ligt - de blik waarmee je een portefeuille op onbenut potentieel scant.
  • Moet positie iets betekenen (twee numerieke assen, correlatie), dan is een spreidingsdiagram de juiste keuze; de bellenwolk ruilt positie in voor compactheid.

Zware datasets op WebGPU Experimenteel ​

BubbleChart heeft een optionele renderer="webgpu" die de bellenwolk tekent als GPU-instanced cirkels, terwijl labels en tooltips op de SVG-laag blijven. Dit is capability-gated: in een browser zonder WebGPU schakelt het automatisch terug naar canvas, en getContext().renderer meldt welke renderer daadwerkelijk heeft getekend.

Net als de spreidingsdiagram-pagina leent de demo hieronder van de deeltjesfysica: ~1.500 gereconstrueerde energieclusters uit één gesimuleerd botsingsevent, één bel per cluster, gekleurd per subdetector. Het handjevol harde deposities torent boven duizenden zachte uit, en de zwaartekracht-packing maakt van het hele energiebudget van het event één leesbare wolk.

⚗️ Experimental - not yet stable. WebGPU rendering is an opt-in preview. It needs a WebGPU-capable browser (Chrome / Edge, or Safari 26+); everywhere else it falls back to canvas automatically. Axes, labels and tooltips stay on the SVG layer - only the data marks are painted on the GPU.
Heavy-data demo · ~1.500 gesimuleerde energieclusters… detecting

Speel door de jaren heen ​

Geef elke bel een date en zet timeline aan: de bellenwolk wordt een verhaal per jaar, met een eigen afspeelknop en scrubber die telkens de groottes van één periode toont. Standaard uit - zonder opt-in verandert er niets.

Druk op de afspeelknop onder de grafiek: de data stapt door de jaren, één momentopname per keer. Sleep de scrubber om naar een jaar te springen.

tsx
const ref = useRef<BubbleChartHandle>(null);

<BubbleChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / seekIndex(i) / stepForward()
vue
<BubbleChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" />
svelte
<div use:bubbleChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>
ts
applyBubbleChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });
html
<michi-vz-bubble-chart id="c"></michi-vz-bubble-chart>
<script>
  const el = document.getElementById("c");
  el.timeline = { speedMs: 1000, loop: true };
  // el.getTimeline() -> play() / pause() / seek(year) / seekIndex(i)
</script>
  • speedMs bepaalt het tempo, loop begint opnieuw, autoplay: true start bij mounten, showControl: false verbergt de ingebouwde balk.
  • Waarden glijden standaard tussen periodes (interpolate); stem de beweging af met tweenMs en easing, of zet interpolate: false voor harde overgangen. Met reduced motion is de overgang altijd hard.
  • De headless controller is altijd beschikbaar: chart.timeline() biedt play() / pause() / toggle() / seek(period) / seekIndex(i) / stepForward() / stepBack(), plus onStep en formatPeriod in de config voor eigen UI.
  • seek(period) zoekt eerst de periode en vergelijkt als tekst, dus seek(2021) en seek("2021") komen allebei op 2021 uit, of je datums nu getallen of strings zijn. Een getal telt alleen als positie (0 = de eerste) als geen periode past, en een string die nergens bij past doet niets. seekIndex(i) gaat altijd op positie, net als de ingebouwde scrubber.
  • Een filter (top-N, sortering) blijft binnen elke periode gelden, dus blijven per jaar alleen de top 5 bellen over.
  • Bellen zonder date blijven in elke periode zichtbaar.

Gebruik ​

tsx
import { BubbleChart } from "@michi-vz/react";

export default () => <BubbleChart {...props} />; // props = the chart options
vue
<script setup>
import { BubbleChart } from "@michi-vz/vue";
</script>

<template>
  <BubbleChart :options="props" />
</template>
svelte
<script>
  import { bubbleChart } from "@michi-vz/svelte";
</script>

<div use:bubbleChart={props}></div>
ts
// main.ts - register the elements once
import "@michi-vz/angular";
import { applyBubbleChartProps } from "@michi-vz/angular";

// component (uses CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-bubble-chart #c></michi-vz-bubble-chart>
applyBubbleChartProps(this.c.nativeElement, props);
html
<script type="module" src="https://cdn.jsdelivr.net/npm/@michi-vz/wc/dist/michi-vz-wc.bundle.js"></script>

<michi-vz-bubble-chart id="c"></michi-vz-bubble-chart>
<script>
  Object.assign(document.getElementById("c"), props); // dataSet, splitLabels, …
</script>
ts
import { mountBubbleChart } from "@michi-vz/core";

const chart = mountBubbleChart(el, props);
chart.update(next);
chart.getContext(); // renderer-agnostic, LLM-ready
chart.destroy();

Datavorm ​

Elk dataSet-item is één bel: een label, een value (oppervlakte), een optionele partial (het gerealiseerde subdeel) en een optionele color.

ts
const props = {
  splitLabels: ["Realized", "Untapped"],
  showLegend: true,
  gravity: 0.09, // higher = tighter cluster
  dataSet: [
    { label: "Germany", value: 120, partial: 64 }, // 53% realized
    { label: "United States", value: 152, partial: 88 },
    { label: "China", value: 168, partial: 51 },
  ],
};

Korte code voor kleine bellen. Geef een item een code (een ISO-code bijvoorbeeld) en die wordt in de bel getekend wanneer het volledige label niet past maar de code wel; een bel die voor geen van beide groot genoeg is toont het label afgekapt met een beletselteken, en een bel met een straal onder 16px toont niets. De code reist ook mee in de context en de tooltip.

Zwaartekracht & de splitsing ​

gravity bepaalt hoe sterk bellen naar het midden worden getrokken (hoger = strakker), padding de ruimte ertussen, en fillRatio hoeveel van het plot de wolk vult. De splitsing werkt hetzelfde als bij de treemap: partial snijdt een areaal-getrouwe gerealiseerde kern uit (straal r·√(partial/value)), en de rest oogt als een lichtere tint van dezelfde kleurtoon - een volle kleur onder een witte sluier, zodat het werkt op zowel lichte als donkere achtergronden. Benoem de onderdelen met splitLabels.

Slepen en terugveren. Met draggable: true kan de kijker een bel oppakken: ze volgt de aanwijzer, de botsingskracht duwt de buren opzij, en bij het loslaten trekt de zwaartekracht van de wolk alles weer op zijn plaats. De berekende lay-out blijft onaangeroerd, zodat de volgende data- of formaatupdate de wolk opnieuw pakt.

Klikken. onBubbleClick(bubble, event) vuurt bij een klik op een bel, in svg en via de canvas-hittest, met dezelfde context die de tooltip-formatter krijgt. De klik pint de tooltip nog steeds; slepen telt nooit als een klik.

ts
const props = { draggable: true, onBubbleClick: (b) => go(b.code ?? b.label) };

API ​

Props zijn getypeerd als BubbleChartProps in @michi-vz/core. Gedeeld door alle grafieken: width, height, margin, colors / colorsMapping, renderer ("svg", "canvas", of experimenteel "webgpu"), highlightItems, disabledItems en de on*-callbacks. onChartDataProcessed / getContext() geven de renderer-onafhankelijke ChartContext terug. Volledige referentie: Bubble API.