Skip to content

Graphique en éventail

Tendances Prévision

« Quel sera le chiffre d'affaires le trimestre prochain ? » La réponse honnête n'est jamais un nombre unique - c'est une fourchette, et la fourchette est tout l'intérêt. Donnez à un dirigeant un seul chiffre et vous devinez ; donnez-lui cet éventail et vous dites la vérité sur le risque. La ligne pleine est ce qui s'est déjà produit, la ligne en pointillés est le tracé unique le plus probable, et les bandes ombrées montrent le degré de certitude de la prévision - s'élargissant à mesure qu'elles s'avancent dans le futur, car plus on regarde loin devant, moins on peut savoir.

Example
canvas · responsive
Aller plus loin : Guide Insights·Guide DevTools

Données volumineuses sur WebGPU Expérimental

Le renderer="webgpu" optionnel de FanChart peint ses marques de ligne et de bande sur le GPU tandis que les axes, étiquettes et infobulles restent sur la couche SVG. C'est conditionné par les capacités du navigateur : sur un navigateur sans WebGPU, il rétrograde automatiquement vers canvas, et getContext().renderer indique lequel a effectivement peint.

⚗️ 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 points… detecting

Comment le lire

  • Ligne pleine - historique. Les valeurs réelles que vous avez déjà.
  • Ligne en pointillés - le tracé le plus probable (la médiane de la prévision) : une meilleure estimation, jamais toute l'histoire.
  • Bandes imbriquées - confiance. De l'intérieur vers l'extérieur = 50 % / 80 % / 95 %. La valeur réelle devrait tomber dans la bande à 95 % environ 19 fois sur 20. On planifie contre la bande, pas contre la ligne.
  • Pourquoi ça s'évase. Le mois prochain est assez prévisible ; dans un an, ça ne l'est pas. L'incertitude s'accumule avec la distance, donc les bandes s'élargissent.

Lisez votre pire scénario en bas de la bande extérieure et votre meilleur scénario en haut. L'éventail est votre scénario de base / optimiste / pessimiste en une seule image - pas besoin d'onglet de scénario séparé.

Les mathématiques, en termes simples

Vous n'avez pas besoin des équations pour utiliser le graphique, mais voici ce qu'il y a sous le capot - et pourquoi vous pouvez lui faire confiance :

  • La médiane vient du lissage exponentiel de Holt-Winters. Il suit deux quantités mobiles, le niveau actuel et la tendance (pente), et les projette vers l'avant ; si la série a une saisonnalité récurrente, il la suit aussi. (Vous préférez une ligne droite ? method: "linear" ajuste plutôt une régression par moindres carrés ordinaires.)

    ℓₜ = α·yₜ + (1−α)(ℓₜ₋₁ + bₜ₋₁) · bₜ = β(ℓₜ − ℓₜ₋₁) + (1−β)bₜ₋₁ · ŷₜ₊ₕ = ℓₜ + h·bₜ

  • Les bandes viennent des erreurs passées du modèle lui-même. Il mesure de combien ses valeurs ajustées se sont trompées (la dispersion résiduelle σ) et élargit l'intervalle comme ŷ ± z·σ·√h - z = 1.96 pour 95 %, et le √h est exactement pourquoi l'éventail s'ouvre avec l'horizon h.
  • Devriez-vous lui faire confiance ? Un backtest cache les derniers points réels, les re-prévoit, et rapporte l'erreur (MAPE, RMSE). Vous obtenez un score d'honnêteté avant de miser sur le chiffre, pas après.

Tout cela tourne dans le navigateur - pas de backend de science des données, pas d'aller-retour serveur. (Power BI, à titre de comparaison, ne prévoit que sur un graphique en courbes et s'arrête là où commence la véritable modélisation.)

Construisez les données en un seul appel avec forecastFan() depuis @michi-vz/insights/forecast, ou fournissez-lui series (historique + médiane certainty:false) et des bands imbriquées.

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 :

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

<FanChart
  ref={ref}
  {...props}
  progressiveDraw={{ durationMs: 2000 }}
/>;
// ref.current?.replay() rejoue l'animation à la demande
vue
<FanChart :options="{ ...props, progressiveDraw: { durationMs: 2000 } }" />
svelte
<div use:fanChart={{ ...props, progressiveDraw: { durationMs: 2000 } }}></div>
ts
applyFanChartProps(this.c.nativeElement, {
  ...props,
  progressiveDraw: { durationMs: 2000 },
});
html
<michi-vz-fan-chart id="c"></michi-vz-fan-chart>
<script>
  const el = document.getElementById("c");
  el.progressiveDraw = { durationMs: 2000 };
  // el.replay() rejoue l'animation
</script>
  • durationMs et easing ("linear", "easeOutQuad", "easeInOutCubic", ou une fonction (t) => t personnalisée) façonnent le rythme du tracé.
  • autoplay: false rend le graphique entièrement tracé ; appelez replay() (handle de ref React, méthode du web component ou instance core) pour lancer l'animation à la demande. replayOnUpdate: true la rejoue à chaque changement de données.
  • Respecte prefers-reduced-motion : le graphique s'affiche alors entièrement tracé, instantanément.

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 d'historique, la médiane prévisionnelle et les bandes de confiance ne se dessinent que jusqu'à l'année active, et la lecture les prolonge en douceur au fil de l'avancée. Reculez le curseur et tout se rétracte en conséquence. Le survol n'inspecte jamais que ce qui est réellement tracé. 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.

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

<FanChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / stepForward()
vue
<FanChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" />
svelte
<div use:fanChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>
ts
applyFanChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });
html
<michi-vz-fan-chart id="c"></michi-vz-fan-chart>
<script>
  const el = document.getElementById("c");
  el.timeline = { speedMs: 1000, loop: true };
  // el.getTimeline() -> play() / pause() / seek(year)
</script>
  • speedMs règle le rythme, loop reboucle, autoplay: true démarre au montage, showControl: false masque la barre intégrée.
  • Le contrôleur headless reste disponible : chart.timeline() expose play() / pause() / toggle() / seek(period) / stepForward() / stepBack(), plus onStep et formatPeriod dans la config pour une UI maison.
  • Les valeurs glissent entre les années par défaut (interpolate) ; passez interpolate: false pour des coupes nettes. Avec reduced motion, la coupe est toujours nette.
  • timeline l'emporte sur progressiveDraw quand les deux sont définis sur le même graphique.

Usage

ts
import { mountFanChart } from "@michi-vz/core";
import { forecastFan } from "@michi-vz/insights/forecast";

// history = DataPoint[] of actuals; build the fan (median + 50/80/95% bands)
const item = forecastFan(history, { method: "holt-winters", horizon: 4, levels: [0.5, 0.8], level: 0.95 }, "Revenue");
const chart = mountFanChart(el, { dataSet: [item], xAxisDataType: "date_annual" });
ts
import { mountFanChart } from "@michi-vz/core";

const chart = mountFanChart(el, props); // props.dataSet = FanDataItem[]
chart.update(next);
chart.getContext(); // renderer-agnostic, LLM-ready
chart.destroy();
html
<script type="module" src="https://cdn.jsdelivr.net/npm/@michi-vz/wc/dist/michi-vz-wc.bundle.js"></script>

<michi-vz-fan-chart id="c"></michi-vz-fan-chart>
<script>
  Object.assign(document.getElementById("c"), props); // dataSet (series + bands), title, …
</script>

Forme des données

Un FanDataItem est une série de ligne familière plus des bandes imbriquées :

ts
interface FanDataItem {
  label: string;
  color?: string;
  series: DataPoint[];   // history (certainty:true) then forecast median (certainty:false → dashed)
  bands: { level: number; series: RangeDataPoint[] }[]; // drawn widest-first, graduated opacity
}

API

Les props sont typées comme FanChartProps dans @michi-vz/core et reflètent LineChartProps (width, height, margin, colors / colorsMapping, renderer, highlightItems, disabledItems, fillOpacity, et les callbacks on*). onChartDataProcessed / getContext() renvoient le ChartContext agnostique du moteur de rendu.