Graphique en courbes
Tendances
« Comment cela a-t-il évolué dans le temps, et où ne puis-je pas faire confiance aux données ? » Une série ou cinquante, avec les périodes manquantes affichées en pointillés afin qu'un trou dans les données ne soit jamais lu comme une vraie baisse - plus un rendu canvas optionnel (décimé par LTTB pour les gros volumes) lorsque les points se comptent par milliers.
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
- Suivre la trajectoire d'un indicateur clé. Chiffre d'affaires par mois, utilisateurs par semaine, latence par heure - partout où « comment cela a-t-il évolué ? » est la question, la courbe y répond plus vite qu'aucun tableau.
- Comparer une poignée de séries sur une même échelle, en gardant les trous honnêtes : une période manquante s'affiche en pointillés, jamais comme une fausse baisse sur laquelle un dirigeant pourrait agir.
- Les gros volumes aussi. Des milliers de points restent fluides grâce au rendu canvas optionnel (décimation LTTB). Mais si l'histoire est une prévision plutôt qu'un historique, le graphique en éventail montre l'étendue, pas seulement la ligne.
Données volumineuses sur WebGPU Expérimental
Le renderer="webgpu" optionnel du graphique en courbes dessine la géométrie des lignes/marqueurs sur 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, avec repli automatique sur canvas lorsque WebGPU n'est pas disponible.
Détection des lacunes
Une période manquante s'affiche sous forme de segment en pointillés - définissez-le point par point avec certainty: false, ou laissez detectGaps la déduire automatiquement. Ici, une série saute une période de reporting :
Chronologie continue et graduations sans données
Par défaut, l'axe des x reste fidèle au temps de deux manières : la première et la dernière période ne sont jamais supprimées (même lorsqu'elles tombent sur un mois « non rond » que d3 aurait sinon ignoré), et les libellés trop nombreux s'inclinent à -45° puis s'éclaircissent jusqu'à environ 5 - en conservant toujours les deux extrémités.
Activez fillPeriodTicks et l'axe trace une graduation pour chaque période de la plage, pas seulement celles qui ont des données. Les mois sans valeur s'affichent estompés ; survolez-en un pour afficher l'explication de la lacune. Basculez l'option :
Toggle it on: the empty months appear as faded ticks. Hover one to see its "no data" tooltip. First and last periods are always kept, either way.
Personnalisez-le : noDataTickTooltip(epochMs) renvoie le texte de l'infobulle (chaîne simple ou HTML nettoyé), et noDataTickColor (ou la variable CSS --michi-vz-tick-nodata) définit la couleur estompée.
<LineChart
{...props}
xAxisDataType="date_monthly"
fillPeriodTicks
noDataTickTooltip={() => "No data reported for this month"}
noDataTickColor="#c0392b"
/><LineChart :options="{ ...props, fillPeriodTicks: true, noDataTickTooltip: () => 'No data' }" /><div use:lineChart={{ ...props, fillPeriodTicks: true, noDataTickTooltip: () => 'No data' }}></div>applyLineChartProps(this.c.nativeElement, {
...props,
fillPeriodTicks: true,
noDataTickTooltip: () => "No data",
});<michi-vz-line-chart id="c" fill-period-ticks no-data-tick-color="#c0392b"></michi-vz-line-chart>
<script>
document.getElementById("c").noDataTickTooltip = () => "No data reported";
</script>Faire defiler les annees
Les données couvrent déjà plusieurs années, il n'y a donc rien à taguer. Activez timeline : le bouton lecture et le curseur propres au graphique parcourent ces années - à chaque étape, la ligne ne se dessine que jusqu'à l'année active, et la lecture la prolonge en douceur au fil de l'avancée. Reculez le curseur et la ligne se rétracte en conséquence. Le survol n'inspecte jamais que ce qui est réellement tracé. C'est la version interactive, année par année : faites glisser ou avancez pas à pas dans les vraies années des données. Pour un balayage cinématique en une seule fois, voir Tracé progressif et étiquettes de pointe ci-dessous. Désactivé par défaut - sans opt-in, rien ne change.
Appuyez sur le bouton lecture sous le graphique : le graphique se dessine plus loin jusqu'à chaque année au fil de la lecture. Faites glisser le curseur pour sauter à une année.
const ref = useRef<LineChartHandle>(null);
<LineChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / stepForward()<LineChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" /><div use:lineChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>applyLineChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });<michi-vz-line-chart id="c"></michi-vz-line-chart>
<script>
const el = document.getElementById("c");
el.timeline = { speedMs: 1000, loop: true };
// el.getTimeline() -> play() / pause() / seek(year)
</script>speedMsrègle le rythme,loopreboucle,autoplay: truedémarre au montage,showControl: falsemasque la barre intégrée.- Le contrôleur headless reste disponible :
chart.timeline()exposeplay() / pause() / toggle() / seek(period) / stepForward() / stepBack(), plusonStepetformatPerioddans la config pour une UI maison. - Les valeurs glissent entre les années par défaut (
interpolate) ; passezinterpolate: falsepour des coupes nettes. Avec reduced motion, la coupe est toujours nette. timelinel'emporte surprogressiveDrawquand les deux sont définis sur le même graphique.tipLabel: truedans la configtimelinegarde une étiquette qui suit la pointe de la ligne pendant qu'elle grandit au fil de la lecture - la même étiquette que celle deprogressiveDraw, pilotée par le curseur au lieu du balayage unique.
Tracé progressif et étiquettes de pointe
Laissez le graphique raconter son histoire dans l'ordre : avec progressiveDraw, chaque ligne se dessine de la première à la dernière année, accompagnée d'une étiquette optionnelle qui suit la pointe de la ligne en affichant le nom de la série et sa valeur courante, avant de se poser à côté de l'extrémité. Ceci est un balayage cinématique unique au montage ; pour un défilement interactif année par année avec un curseur, voir Faire defiler les annees ci-dessus. Désactivé par défaut - sans opt-in, rien ne change.
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<LineChartHandle>(null);
<LineChart
ref={ref}
{...props}
progressiveDraw={{ durationMs: 2000, tipLabel: true }}
/>;
// ref.current?.replay() rejoue l'animation à la demande<LineChart :options="{ ...props, progressiveDraw: { durationMs: 2000, tipLabel: true } }" /><div use:lineChart={{ ...props, progressiveDraw: { durationMs: 2000, tipLabel: true } }}></div>applyLineChartProps(this.c.nativeElement, {
...props,
progressiveDraw: { durationMs: 2000, tipLabel: true },
});<michi-vz-line-chart id="c"></michi-vz-line-chart>
<script>
const el = document.getElementById("c");
el.progressiveDraw = { durationMs: 2000, tipLabel: true };
// el.replay() rejoue l'animation
</script>durationMseteasing("linear", "easeOutQuad", "easeInOutCubic", ou une fonction(t) => tpersonnalisée) façonnent le rythme du tracé.tipLabel: truedessine l'étiquette mobile ;{ content: "name" | "value" | "both", format }restreint ou réécrit son texte. La valeur affichée est toujours un vrai point de données, jamais un nombre interpolé.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. - Pendant le tracé, le réticule et les infobulles s'arrêtent au bord révélé : le survol n'inspecte jamais des données pas encore dessinées. Le renderer webgpu ignore l'animation.
Utilisation
import { LineChart } from "@michi-vz/react";
export default () => <LineChart {...props} />; // props = the chart options<script setup>
import { LineChart } from "@michi-vz/vue";
</script>
<template>
<LineChart :options="props" />
</template><script>
import { lineChart } from "@michi-vz/svelte";
</script>
<div use:lineChart={props}></div>// main.ts - register the elements once
import "@michi-vz/angular";
import { applyLineChartProps } from "@michi-vz/angular";
// component (uses CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-line-chart #c></michi-vz-line-chart>
applyLineChartProps(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-line-chart id="c"></michi-vz-line-chart>
<script>
Object.assign(document.getElementById("c"), props); // dataSet/series, title, …
</script>import { mountLineChart } from "@michi-vz/core";
const chart = mountLineChart(el, props);
chart.update(next);
chart.getContext(); // renderer-agnostic, LLM-ready
chart.destroy();États de chargement et d'absence de données
Passez isLoading pendant que votre récupération de données est en cours ; le moteur affiche une superposition .mv-loading et définit data-mv-state="loading" sur l'hôte.
Lorsque la récupération n'aboutit à rien, isNodata prend le relais. Le prédicat par défaut considère qu'un dataSet vide (ou que toutes les séries sont vides) signifie l'absence de données - vous pouvez le remplacer par un booléen ou une fonction :
// boolean shortcut
<LineChart isLoading={query.isPending} isNodata={query.data?.length === 0} noDataLabel="No data available" />
// function predicate
<LineChart isNodata={(ds) => ds.every(s => s.data.length === 0)} />En React, les props isNodataComponent et isLoadingComponent acceptent n'importe quel ReactNode. Le moteur reste monté (donc onChartDataProcessed continue de se déclencher) ; votre nœud est rendu en superposition au-dessus de l'hôte du graphique, et suppressDefaultOverlay est activé automatiquement afin que le message intégré .mv-nodata soit masqué :
<LineChart
isLoading={isPending}
isLoadingComponent={<Spinner />}
isNodata={isEmpty}
isNodataComponent={<p className="no-data">No results for this selection.</p>}
/>Pour le JS natif / les autres frameworks, la superposition intégrée est affichée par défaut. Désactivez-la et affichez votre propre nœud à côté de l'hôte du graphique :
const chart = mountLineChart(el, { ...props, suppressDefaultOverlay: true });
// render your overlay next to el when data-mv-state === "nodata"Configuration des axes
| Prop | Valeur par défaut | Effet |
|---|---|---|
yTicks | 10 | Nombre d'intervalles de graduation de l'axe y |
showGridLines | true | Grille horizontale (y) en pointillés |
showVerticalGridLines | false | Grille verticale (x) en pointillés |
highlightZeroLine | true | Trace y = 0 sous forme de ligne pleine |
La couleur de la ligne zéro reprend par défaut la couleur de la grille (--michi-vz-grid). Personnalisez-la indépendamment :
.my-chart-host {
--michi-vz-zero-line: #e53935; /* solid red zero line */
--michi-vz-grid: #e0e0e0; /* dashed gridlines stay grey */
}<LineChart
yTicks={5}
showGridLines={true}
showVerticalGridLines={false}
highlightZeroLine={true}
/>Famille de police
Passez fontFamily pour garder les libellés SVG et le texte canvas synchronisés. Le moteur écrit --michi-vz-font-family sur l'hôte du graphique ; les éléments SVG <text> et le chemin ctx.font du canvas lisent tous deux ce style calculé, donc aucun embarquement de police n'est nécessaire - la famille doit simplement déjà être chargée par la page.
<LineChart fontFamily="Inter, sans-serif" />mountLineChart(el, { ...props, fontFamily: "Inter, sans-serif" });Couleurs et données de légende
Les couleurs des courbes suivent le contrat CSS data-label-safe. Chaque élément de série porte un attribut data-label-safe (le libellé de série assaini) ; vous le ciblez en CSS pour définir la couleur du trait. Le moteur de rendu canvas sonde ces styles calculés au moment du rendu, de sorte que les mêmes règles CSS pilotent les deux moteurs de rendu.
onChartDataProcessed (et getContext()) émettent un tableau legendData sur le ChartContext. Chaque entrée a la forme { label, color, order, disabled?, dataLabelSafe }. Une autorité de couleur (par exemple un composant fournisseur) peut lire ces entrées et émettre le CSS correspondant :
<LineChart
onChartDataProcessed={(ctx) => {
ctx.legendData?.forEach(({ dataLabelSafe, color }) => {
// write `.line[data-label-safe="${dataLabelSafe}"] { stroke: ${color} }` into a <style> tag
});
}}
/>API
Les props sont typées comme LineChartProps 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.
