Radardiagram
Vergelijking
Welke optie wint, en waar? Leg een paar kandidaten over dezelfde set criteria en elk wordt een veelhoek die je in één oogopslag kunt lezen - de pieken tonen elke sterkte, de dalen tonen elke zwakte, en de overlappingen tonen precies waar ze van plaats wisselen.
De grafiek hierboven is dezelfde engine in elk framework - alleen de integratiecode hieronder verschilt.
Wanneer kies je deze
- Scorekaarten. Leveranciersevaluaties, kandidaatbeoordelingen, productbenchmarks: een paar opties over dezelfde criteria, elk een veelhoek waarvan de pieken en dalen de sterktes en zwaktes zijn.
- Balans versus specialisatie. Een rondere veelhoek is de allrounder; een puntige zet alles op twee assen. Dat vormverhaal is wat tabellen niet kunnen vertellen.
- Houd het bij een paar entiteiten en 5 tot 12 assen. Voor een precieze vergelijking op één criterium leest een staafdiagram de exacte waarden af; de radar leest profielen.
De buitenste ring afronden naar een mooie waarde
Zonder maxValue ligt de buitenste ring op de grootste waarde in de data, dus een maximum van 73 labelt de ringen 18, 37, 55 en 73. Zet niceMaxValue aan en de buitenste ring rondt in plaats daarvan naar boven af tot de volgende "mooie" tick, op dezelfde stappen van 1, 2 en 5 × 10ⁿ die een d3-as gebruikt, zodat de ringen 20, 40, 60 en 80 tonen.
<RadarChart {...props} niceMaxValue /><RadarChart :options="{ ...props, niceMaxValue: true }" /><div use:radarChart={{ ...props, niceMaxValue: true }}></div>applyRadarChartProps(this.c.nativeElement, { ...props, niceMaxValue: true });<michi-vz-radar-chart id="c"></michi-vz-radar-chart>
<script>
// een property, geen attribuut: accepteert true of een aantal ticks
document.getElementById("c").niceMaxValue = true;
</script>truegebruiktrings(standaard 4) als aantal ticks. Een getal bepaalt het aantal ticks zelf, bijvoorbeeldniceMaxValue: 5om aan te sluiten bij een as met vijf ticks elders op de pagina.- Een expliciete
maxValuewint altijd:niceMaxValuerondt alleen de buitenste ring af die de grafiek anders uit de data zou halen. Een maximum dat al op een tick ligt blijft staan (100 blijft 100), en data die volledig nul of negatief is houdt de standaard buitenste ring van 1. - Alleen de buitenste ring is gegarandeerd rond. De binnenste ringen zijn gelijke delen ervan, dus ze zijn rond wanneer het afgeronde maximum deelbaar is door
rings. 80 over 4 ringen geeft 20, 40, 60, 80, terwijl 50 over 4 ringen 12,5, 25, 37,5, 50 geeft. getContext().maxValuegeeft de afgeronde waarde terug. De afronding is ook geëxporteerd als pure helper,niceRadarMax(dataMax, tickCount)uit@michi-vz/core.
Veel data op WebGPU Experimenteel
RadarChart heeft een optionele renderer="webgpu" die de veelhoekvullingen en polmarkers tekent als GPU-instanced marks, terwijl assen, labels en tooltips op de SVG-laag blijven. Dit is capability-gated: in een browser zonder WebGPU wordt automatisch teruggevallen op canvas, en getContext().renderer rapporteert wat daadwerkelijk is getekend.
Speel door de jaren heen
RadarChart gebruikt date al voor de legacy vorm per as { date, value }, dus de timeline-tag heet in plaats daarvan period. Geef elke serie-rij een period en zet timeline aan: de momentopname van een jaar bestaat uit de rijen die die period delen, en elke veelhoek glijdt tussen jaren. Standaard uit - zonder opt-in verandert er niets. Dit is interactief jaar-voor-jaar stappen, niet de eenmalige intro verderop.
{ label: "Vienna", period: "2021", values: [72, 65, 40, 88 /* … */] }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.
const ref = useRef<RadarChartHandle>(null);
<RadarChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / seekIndex(i) / stepForward()<RadarChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" /><div use:radarChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>applyRadarChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });<michi-vz-radar-chart id="c"></michi-vz-radar-chart>
<script>
const el = document.getElementById("c");
el.timeline = { speedMs: 1000, loop: true };
// el.getTimeline() -> play() / pause() / seek(year) / seekIndex(i)
</script>speedMsbepaalt het tempo,loopbegint opnieuw,autoplay: truestart bij mounten,showControl: falseverbergt de ingebouwde balk.- Waarden glijden standaard tussen periodes (
interpolate); stem de beweging af mettweenMseneasing, of zetinterpolate: falsevoor harde overgangen. Met reduced motion is de overgang altijd hard. - De headless controller is altijd beschikbaar:
chart.timeline()biedtplay() / pause() / toggle() / seek(period) / seekIndex(i) / stepForward() / stepBack(), plusonStepenformatPeriodin de config voor eigen UI. seek(period)zoekt eerst de periode en vergelijkt als tekst, dusseek(2021)enseek("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.- Series zonder
periodblijven in elke periode zichtbaar. timelinewint het vanprogressiveDrawals beide zijn ingesteld - de onthulanimatie verderop blijft uit zolang de timeline de regie heeft.
Onthulanimatie
De grafiek tekent zichzelf van links naar rechts bij het mounten, waarbij de elementen na elkaar verschijnen voordat ze op hun plek vallen. Standaard uit - een grafiek kiest ervoor met de progressiveDraw-prop.
Elke lijn groeit van het eerste naar het laatste jaar; het label volgt de punt en komt tot stilstand bij het lijneinde. Met reduced motion ingeschakeld verschijnt de grafiek meteen volledig getekend.
progressiveDraw: true gebruikt de standaardinstellingen (1200 ms, easeInOutCubic). Een configuratieobject verfijnt het gedrag:
const ref = useRef<RadarChartHandle>(null);
<RadarChart
ref={ref}
{...props}
progressiveDraw={{ durationMs: 2000 }}
/>;
// ref.current?.replay() speelt de animatie opnieuw af<RadarChart :options="{ ...props, progressiveDraw: { durationMs: 2000 } }" /><div use:radarChart={{ ...props, progressiveDraw: { durationMs: 2000 } }}></div>applyRadarChartProps(this.c.nativeElement, {
...props,
progressiveDraw: { durationMs: 2000 },
});<michi-vz-radar-chart id="c"></michi-vz-radar-chart>
<script>
const el = document.getElementById("c");
el.progressiveDraw = { durationMs: 2000 };
// el.replay() speelt de animatie opnieuw af
</script>durationMseneasing("linear", "easeOutQuad", "easeInOutCubic", of een eigen(t) => t-functie) bepalen het tempo.autoplay: falserendert de grafiek volledig getekend; roepreplay()aan (React-ref-handle, webcomponent-methode of de core-instantie) om de animatie op aanvraag te starten.replayOnUpdate: trueherhaalt de animatie bij elke datawijziging.- Respecteert
prefers-reduced-motion: de grafiek verschijnt dan meteen volledig getekend. - Onthulanimatie is een eenmalige animatie bij mounten; speel door de jaren hierboven stapt in plaats daarvan jaar voor jaar door de data.
Hover en tooltips
Hoveren over een pool roept tooltipFormatter aan met het datapunt van de pool plus date - het aslabel van de gehoverde pool - en dat is nu consistent op elke renderer (svg, canvas, webgpu):
tooltipFormatter: (item) => `${item.date}: ${item.value}`,De standaard tooltip-markup (zonder tooltipFormatter) blijft ongewijzigd. Hover volgt de cursor terwijl die over een veelhoek beweegt, in plaats van eenmalig te vuren bij binnenkomst, en highlightItems dempt elke andere serie - gedempte series worden niet gehittest, dus alleen de actieve serie reageert op hover, net als bij canvas en webgpu. disabledItems is een ander mechanisme: het verwijdert een serie volledig uit de grafiek in plaats van hem te dempen, dus een uitgeschakelde serie zat nooit in het rendermodel en kon sowieso niet gehittest worden. Klikken pint de tooltip nu alleen nog als de klik een echte pool oplevert; een klik die niets raakt (lege ruimte, of een gedempte serie, die niet gehittest wordt) pint niet meer, terwijl voorheen op svg een klik ergens op een veelhoek - gedempt inbegrepen - de tooltip altijd pinde.
Op svg vuurt onHighlightItem nu bij elke mousemove die een hit oplevert, en met [] bij elke beweging die dat niet doet - zonder deduplicatie of throttling. Als jouw onHighlightItem-handler het gehoverde label wegschrijft naar een store of andere state die terugkomt in de props van de grafiek, moet je zelf debouncen of dedupliceren, want elke dispatch die de props bereikt triggert een re-render. Dit is niets nieuws voor de bibliotheek: canvas en webgpu vuurden al in dit tempo bij radar-hover, en andere grafieken ook (bijv. Area, Pie) - svg-radar-hover sluit hier nu gewoon bij aan.
Gebruik
import { RadarChart } from "@michi-vz/react";
export default () => <RadarChart {...props} />; // props = the chart options<script setup>
import { RadarChart } from "@michi-vz/vue";
</script>
<template>
<RadarChart :options="props" />
</template><script>
import { radarChart } from "@michi-vz/svelte";
</script>
<div use:radarChart={props}></div>// main.ts - register the elements once
import "@michi-vz/angular";
import { applyRadarChartProps } from "@michi-vz/angular";
// component (uses CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-radar-chart #c></michi-vz-radar-chart>
applyRadarChartProps(this.c.nativeElement, props);<script type="module" src="https://cdn.jsdelivr.net/npm/@michi-vz/wc/dist/michi-vz-wc.bundle.js"></script>
<michi-vz-radar-chart id="c"></michi-vz-radar-chart>
<script>
Object.assign(document.getElementById("c"), props); // dataSet/series, title, …
</script>import { mountRadarChart } from "@michi-vz/core";
const chart = mountRadarChart(el, props);
chart.update(next);
chart.getContext(); // renderer-agnostic, LLM-ready
chart.destroy();API
Props zijn getypeerd als RadarChartProps 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() retourneren de renderer-agnostische ChartContext.
