Skip to content

Barres comparables ​

Comparaison

Est-ce que ça s'est amélioré ou dégradé ? Placez avant et après côte à côte sur une barre par étiquette, et l'écart qui s'est réduit (ou creusé) est la première chose que voit le lecteur.

Example
  • 2019 (avant, teinte pâle)
  • 2024 (après, plein)
canvas · responsive
Aller plus loin : Guide Insights·Guide DevTools

Le graphique ci-dessus est le même moteur dans chaque framework - seul le code d'intégration ci-dessous diffère.

Données volumineuses sur WebGPU Expérimental ​

ComparableHorizontalBarChart dispose d'un renderer="webgpu" optionnel qui peint les deux sous-barres par ligne comme des rectangles instanciés 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 · ~120 lignes… detecting

Faire defiler les annees ​

Donnez un date à chaque ligne et activez timeline : le graphique en barres devient un récit année par année, avec son propre bouton lecture et son curseur, un instantané de l'écart avant/après à la fois. Désactivé par défaut - sans opt-in, rien ne change.

Appuyez sur le bouton lecture sous le graphique : les données défilent année après année, un instantané à la fois. Faites glisser le curseur pour sauter à une année.

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

<ComparableHorizontalBarChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / seekIndex(i) / stepForward()
vue
<ComparableHorizontalBarChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" />
svelte
<div use:comparableHorizontalBarChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>
ts
applyComparableHorizontalBarChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });
html
<michi-vz-comparable-horizontal-bar-chart id="c"></michi-vz-comparable-horizontal-bar-chart>
<script>
  const el = document.getElementById("c");
  el.timeline = { speedMs: 1000, loop: true };
  // el.getTimeline() -> play() / pause() / seek(year) / seekIndex(i)
</script>
  • speedMs règle le rythme, loop reboucle, autoplay: true démarre au montage, showControl: false masque la barre intégrée.
  • Les valeurs glissent d'une période à l'autre par défaut (interpolate) ; ajustez le mouvement avec tweenMs et easing, ou passez interpolate: false pour des coupes nettes. Avec reduced motion, la coupe est toujours nette.
  • Le contrôleur headless reste disponible : chart.timeline() expose play() / pause() / toggle() / seek(period) / seekIndex(i) / stepForward() / stepBack(), plus onStep et formatPeriod dans la config pour une UI maison.
  • seek(period) cherche d'abord la période, en comparant le texte : seek(2021) et seek("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é.
  • Un filter (top-N, tri) s'applique toujours à l'intérieur de chaque période : un « top 5 par an » fonctionne d'emblée.
  • Les lignes sans date restent visibles à chaque période.

Rayon des coins ​

Chaque barre est dessinée avec des coins de 5px par défaut. Sur un petit écran, les barres s'amincissent - les demi-bandes de la mise en page grouped peuvent ne faire qu'environ 10px d'épaisseur - et un rayon de 5px sur une barre de 10px en fait une pilule. barRadius fixe le rayon en px : 0 pour des coins carrés, de 2 à 4 pour des coins presque carrés.

barRadius: 5 (par défaut)
barRadius: 2
barRadius: 0

Même graphique, mêmes données ; seul barRadius change. Changez de moteur de rendu : svg et canvas arrondissent les coins de façon identique.

tsx
<ComparableHorizontalBarChart {...props} layout="grouped" barRadius={2} />
vue
<ComparableHorizontalBarChart :options="{ ...props, layout: 'grouped', barRadius: 2 }" />
svelte
<div use:comparableHorizontalBarChart={{ ...props, layout: "grouped", barRadius: 2 }}></div>
ts
applyComparableHorizontalBarChartProps(this.c.nativeElement, { ...props, layout: "grouped", barRadius: 2 });
html
<michi-vz-comparable-horizontal-bar-chart layout="grouped" bar-radius="2"></michi-vz-comparable-horizontal-bar-chart>
  • Le rayon dessiné ne dépasse jamais la moitié de la largeur ou de la hauteur de la barre, en svg comme en canvas.
  • Sans barRadius, l'aspect historique de 5px est conservé. Une valeur négative donne des coins carrés.
  • Le moteur de rendu expérimental webgpu dessine des coins carrés, quelle que soit la valeur.

Usage ​

tsx
import { ComparableHorizontalBarChart } from "@michi-vz/react";

export default () => <ComparableHorizontalBarChart {...props} />; // props = the chart options
vue
<script setup>
import { ComparableHorizontalBarChart } from "@michi-vz/vue";
</script>

<template>
  <ComparableHorizontalBarChart :options="props" />
</template>
svelte
<script>
  import { comparableHorizontalBarChart } from "@michi-vz/svelte";
</script>

<div use:comparableHorizontalBarChart={props}></div>
ts
// main.ts - register the elements once
import "@michi-vz/angular";
import { applyComparableHorizontalBarChartProps } from "@michi-vz/angular";

// component (uses CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-comparable-horizontal-bar-chart #c></michi-vz-comparable-horizontal-bar-chart>
applyComparableHorizontalBarChartProps(this.c.nativeElement, props);
html
<script type="module" src="https://cdn.jsdelivr.net/npm/@michi-vz/wc/dist/michi-vz-wc.bundle.js"></script>

<michi-vz-comparable-horizontal-bar-chart id="c"></michi-vz-comparable-horizontal-bar-chart>
<script>
  Object.assign(document.getElementById("c"), props); // dataSet/series, title, …
</script>
ts
import { mountComparableHorizontalBarChart } from "@michi-vz/core";

const chart = mountComparableHorizontalBarChart(el, props);
chart.update(next);
chart.getContext(); // renderer-agnostic, LLM-ready
chart.destroy();

API ​

Les props sont typées comme ComparableHorizontalBarChartProps dans @michi-vz/core. Communes à tous les graphiques : width, height, margin, colors / colorsMapping, renderer ("svg", "canvas", ou expérimentalement "webgpu"), highlightItems, disabledItems, et les callbacks on*. onChartDataProcessed / getContext() renvoient le ChartContext agnostique du moteur de rendu.

Notes de comportement ​

Deux sous-barres par ligne ​

Chaque ligne dessine valueBased (pâle) et valueCompared (plein), divergeant depuis x=0 ; la sous-barre la plus courte est dessinée au-dessus, si bien que les deux valeurs restent visibles quel que soit le sens de l'évolution. colorsBasedMapping donne à la sous-barre valueBased sa propre couleur par étiquette : associez une teinte claire opaque à valueBasedOpacity: 1 (comme dans la démo ci-dessus) pour le contraste avant/après le plus net dans les deux thèmes. valueBasedOpacity / valueComparedOpacity définissent leur opacité de remplissage. Une sous-barre dont le remplissage résolu est transparent est ignorée (les consommateurs cachent une moitié via CSS). minBarWidth (5 par défaut) impose un plancher à une barre non nulle pour que les valeurs proches de zéro restent visibles.

patternsMapping - remplissages hachurés / image ​

patternsMapping: Record<label, imageSrc> remplit la sous-barre valeur de base avec une image en mosaïque au lieu d'une couleur unie. createHatchPattern({ color, angle?, spacing?, strokeWidth? }) (exporté depuis @michi-vz/core et @michi-vz/react) renvoie une URI de données SVG à hachures diagonales pour le cas courant. Le moteur de rendu canvas la met en mosaïque via ctx.createPattern et effectue un nouveau rendu une fois l'image chargée.

Axe des valeurs (x) ​

xAxisPredefinedDomain: [min, max] fixe la plage de l'axe des valeurs (alias de xAxisDomain). showZeroLineForXAxis dessine une ligne pleine à x=0 (graphiques divergents) ; showGrid bascule les lignes de grille verticales (désactivées par défaut). xAxisFormat formate les étiquettes des graduations.

Colonne d'étiquettes (y) ​

Les étiquettes de catégorie de l'axe des y vivent dans une colonne gauche de tickHtmlWidth px de large (100 par défaut, avec points de suspension). padding.left décale la zone de tracé (barres + axe des valeurs) vers la droite SANS déplacer les étiquettes - ouvrant de la place pour une colonne d'étiquettes large. horizontalTickPosition: { x, y } déplace légèrement les étiquettes pour les aligner avec une légende externe. hideTickLabels les masque entièrement (quand les noms de catégorie vivent plutôt dans une légende).

Infobulle ​

tooltipFormatter(datum, dataSet, type) reçoit la ligne survolée, toutes les lignes, et le type de sous-barre survolée ("based" | "compared"). Elle renvoie une chaîne HTML ; le wrapper React accepte en plus un nœud React (converti en HTML statique). L'infobulle intégrée est consciente des bords (elle bascule près des bords droit/haut).

Chargement / absence de données + interaction ​

isLoading et isNodata pilotent la superposition (React : isLoadingComponent / isNodataComponent). Survoler met en évidence une ligne (les autres s'estompent) et mouseleave la réinitialise ; les barres sont arrondies (rayon 5 par défaut, réglé par barRadius) avec une bordure de 1px.

Autorités de couleur côté consommateur : le contexte porte legendData ({ label, color, dataLabelSafe }) pour qu'un système de couleur par injection CSS puisse indexer des règles par étiquette ; onChartDataProcessed n'est émis que lorsque le contexte change (réémettre un contexte inchangé à chaque rendu peut boucler un consommateur qui déclenche une action à chaque appel).