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.
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.
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.96pour 95 %, et le√hest exactement pourquoi l'éventail s'ouvre avec l'horizonh. - 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-luiseries(historique + médianecertainty:false) et desbandsimbriqué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 :
const ref = useRef<FanChartHandle>(null);
<FanChart
ref={ref}
{...props}
progressiveDraw={{ durationMs: 2000 }}
/>;
// ref.current?.replay() rejoue l'animation à la demande<FanChart :options="{ ...props, progressiveDraw: { durationMs: 2000 } }" /><div use:fanChart={{ ...props, progressiveDraw: { durationMs: 2000 } }}></div>applyFanChartProps(this.c.nativeElement, {
...props,
progressiveDraw: { durationMs: 2000 },
});<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>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.
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.
const ref = useRef<FanChartHandle>(null);
<FanChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / stepForward()<FanChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" /><div use:fanChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>applyFanChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });<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>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.
Usage
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" });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();<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 :
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.
