Graphique radar
Comparaison
Quelle option l'emporte, et sur quels points ? Superposez quelques candidats sur le même ensemble de critères et chacun devient un polygone que vous lisez en un coup d'œil - les pointes montrent chaque force, les creux montrent chaque faiblesse, et les chevauchements montrent exactement où ils échangent leur place.
Le graphique ci-dessus utilise le même moteur dans chaque framework - seul le code d'intégration ci-dessous diffère.
Quand le choisir
- Grilles d'évaluation. Évaluations de fournisseurs, appréciations de candidats, comparatifs de produits : quelques options sur les mêmes critères, chacune un polygone dont les pointes et les creux sont ses forces et ses faiblesses.
- Équilibre contre spécialisation. Un polygone plus rond est le généraliste ; un polygone pointu mise tout sur deux axes. Cette histoire de forme, aucun tableau ne peut la raconter.
- Limitez-vous à quelques entités et 5 à 12 axes. Pour une comparaison précise sur un seul critère, une barre comparable donne des valeurs exactes ; le radar donne des profils.
Arrondir l'anneau extérieur à une valeur ronde
Sans maxValue, l'anneau extérieur se place sur la plus grande valeur des données : un maximum de 73 étiquette les anneaux 18, 37, 55 et 73. Avec niceMaxValue, l'anneau extérieur est arrondi vers le haut à la graduation « ronde » suivante, sur les mêmes pas de 1, 2 et 5 × 10ⁿ qu'un axe d3, et les anneaux affichent 20, 40, 60 et 80.
<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>
// une propriété, pas un attribut : elle accepte true ou un nombre de graduations
document.getElementById("c").niceMaxValue = true;
</script>trueutiliserings(4 par défaut) comme nombre de graduations. Un nombre fixe lui-même le nombre de graduations, par exempleniceMaxValue: 5pour s'aligner sur un axe à cinq graduations ailleurs sur la page.- Un
maxValueexplicite l'emporte toujours :niceMaxValuen'arrondit que l'anneau extérieur que le graphique tirerait sinon des données. Un maximum déjà sur une graduation ne bouge pas (100 reste 100), et des données toutes nulles ou négatives gardent l'anneau extérieur par défaut de 1. - Seul l'anneau extérieur est garanti rond. Les anneaux intérieurs en sont des fractions égales : ils sont ronds quand le maximum arrondi se divise exactement par
rings. 80 sur 4 anneaux donne 20, 40, 60, 80, alors que 50 sur 4 anneaux donne 12,5, 25, 37,5, 50. getContext().maxValuerenvoie la valeur arrondie. L'arrondi est aussi exporté comme fonction pure,niceRadarMax(dataMax, tickCount)depuis@michi-vz/core.
Données volumineuses sur WebGPU Expérimental
RadarChart possède un renderer="webgpu" optionnel qui dessine les remplissages des polygones et les marqueurs de pôles comme des marques rendues par le GPU tandis que les axes, les libellés et les infobulles restent sur la couche SVG. Il est conditionné aux capacités du navigateur : sur un navigateur sans WebGPU, il bascule automatiquement sur canvas, et getContext().renderer indique lequel a effectivement dessiné.
Faire defiler les annees
RadarChart utilise déjà date pour l'ancienne forme par axe { date, value }, donc l'étiquette du timeline s'appelle period à la place. Étiquetez chaque ligne de série avec une period et activez timeline : l'instantané d'une année est constitué des lignes partageant cette period, et chaque polygone glisse d'une année à l'autre. Désactivé par défaut - sans opt-in, rien ne change. C'est un défilement interactif année par année, pas l'entrée ponctuelle plus bas.
{ label: "Vienna", period: "2021", values: [72, 65, 40, 88 /* … */] }Appuyez sur le bouton lecture sous le graphique : les données défilent année par année, un instantané à la fois. Faites glisser le curseur pour sauter à une année.
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>speedMsrègle le rythme,loopreboucle,autoplay: truedémarre au montage,showControl: falsemasque la barre intégrée.- Les valeurs glissent d'une période à l'autre par défaut (
interpolate) ; ajustez le mouvement avectweenMseteasing, ou passezinterpolate: falsepour des coupes nettes. Avec reduced motion, la coupe est toujours nette. - Le contrôleur headless reste disponible :
chart.timeline()exposeplay() / pause() / toggle() / seek(period) / seekIndex(i) / stepForward() / stepBack(), plusonStepetformatPerioddans la config pour une UI maison. seek(period)cherche d'abord la période, en comparant le texte :seek(2021)etseek("2021")vont tous les deux sur 2021, que vos dates soient des nombres ou des chaînes. Un nombre ne compte comme une position (0 = la première) que si aucune période ne correspond, et une chaîne qui ne correspond à rien ne fait rien.seekIndex(i)va toujours par position, comme le curseur intégré.- Les séries sans
periodrestent visibles à chaque période. timelinel'emporte surprogressiveDrawquand les deux sont définis - l'animation de révélation plus bas reste inactive tant que le timeline garde la main.
Animation de révélation
Le graphique se dessine de gauche à droite au montage, révélant ses éléments dans l'ordre avant de se stabiliser. Désactivé par défaut - un graphique l'active avec la prop progressiveDraw.
Chaque ligne grandit de la première à la dernière année ; l'étiquette suit la pointe puis se pose à l'extrémité de la ligne. Avec reduced motion activé, le graphique s'affiche entièrement tracé, instantanément.
progressiveDraw: true applique les réglages par défaut (1200 ms, easeInOutCubic). Un objet de configuration affine le comportement :
const ref = useRef<RadarChartHandle>(null);
<RadarChart
ref={ref}
{...props}
progressiveDraw={{ durationMs: 2000 }}
/>;
// ref.current?.replay() rejoue l'animation à la demande<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() rejoue l'animation
</script>durationMseteasing("linear", "easeOutQuad", "easeInOutCubic", ou une fonction(t) => tpersonnalisée) façonnent le rythme du tracé.autoplay: falserend le graphique entièrement tracé ; appelezreplay()(handle de ref React, méthode du web component ou instance core) pour lancer l'animation à la demande.replayOnUpdate: truela rejoue à chaque changement de données.- Respecte
prefers-reduced-motion: le graphique s'affiche alors entièrement tracé, instantanément. - L'animation de révélation est une entrée ponctuelle ; le défilement par année ci-dessus avance plutôt année par année.
Survol et infobulles
Survoler un pôle appelle tooltipFormatter avec l'élément de données du pôle plus date - le libellé d'axe du pôle survolé - et c'est désormais cohérent sur chaque moteur de rendu (svg, canvas, webgpu) :
tooltipFormatter: (item) => `${item.date} : ${item.value}`,Le balisage par défaut de l'infobulle (sans tooltipFormatter) reste inchangé. Le survol suit le curseur pendant qu'il se déplace sur un polygone au lieu de se déclencher une seule fois à l'entrée, et highlightItems atténue toutes les autres séries - les séries atténuées ne sont pas testées au survol, donc seule la série active répond, à l'image de canvas et webgpu. disabledItems est un mécanisme différent : il retire une série du graphique au lieu de l'atténuer, une série désactivée n'a donc jamais existé dans le modèle de rendu et ne pouvait pas être testée au survol de toute façon. Le clic n'épingle plus l'infobulle que lorsqu'il résout un pôle réel ; un clic qui ne touche rien (espace vide, ou une série atténuée, qui n'est pas testée) n'épingle plus rien, alors qu'auparavant sur svg un clic n'importe où sur un polygone - y compris atténué - épinglait l'infobulle.
Sur svg, onHighlightItem se déclenche désormais à chaque mousemove qui résout un pôle, et avec [] à chaque déplacement qui n'en résout aucun - sans déduplication ni limitation de fréquence. Si votre gestionnaire onHighlightItem écrit le libellé survolé dans un store ou un autre état qui revient dans les props du graphique, il vous appartient de le debouncer ou de le dédupliquer, puisque chaque envoi qui atteint les props déclenche un nouveau rendu. Ce n'est pas nouveau pour la bibliothèque : canvas et webgpu se déclenchaient déjà à ce rythme sur le radar, et d'autres graphiques aussi (par ex. Area, Pie) - le survol svg du radar s'aligne simplement sur eux désormais.
Utilisation
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
Les props sont typées comme RadarChartProps dans @michi-vz/core. Communes à tous les graphiques : width, height, margin, colors / colorsMapping, renderer ("svg", "canvas", ou "webgpu" expérimental), highlightItems, disabledItems, et les callbacks on*. onChartDataProcessed / getContext() renvoient le ChartContext indépendant du moteur de rendu.
