chart-03-accent-column-callout

Histogramme à colonne accentuée et info-bulle

Donner la forme d'une série en quelques colonnes flottantes sans axe ni graduation, et n'en chiffrer qu'une seule — la courante — par une info-bulle sombre ancrée à sa colonne.

chartslideweb700×238px11 benchmarks

Télécharger le .zip

Le fragment RÉEL, dans son propre document, sur le sol de sa charte — pas une image. Ce que vous voyez est exactement ce que vous collerez.

Employer quand

Une série courte (4 à 7 périodes) sert de contexte à UNE valeur qui, elle, doit être lue exactement : mois en cours, dernier trimestre, pic à commenter.

Éviter quand

Plusieurs valeurs doivent être lues précisément (il faudrait autant d'info-bulles, et l'accent perd tout sens), ou les écarts entre colonnes sont faibles : sans axe, un écart de 5 % ne se voit pas.

Notes

Deux bornes protègent l'accent, et elles tirent en sens inverse : la colonne accentuée doit être la plus haute (sinon l'œil accentue la mauvaise), et son info-bulle — ancrée à la COLONNE, donc libre de sortir de la carte — doit rester dans le cadre et sous l'en-tête. C'est ce couple qui rend le pattern rejouable sur d'autres valeurs : dès que la série change, l'un des deux casse avant le rendu. Le rayon est mesuré en BAS : c'est le seul endroit où l'on voit si les colonnes flottent ou si elles sont plantées sur une ligne de base absente. L'info-bulle monte au niveau de l'en-tête : c'est voulu, la colonne accentuée est haute et la bulle la surmonte. Ce qui est mesuré n'est donc pas un dégagement vertical (il n'y en a pas) mais le dégagement HORIZONTAL avec ses deux voisins de ligne, titre à gauche et pilule de période à droite.

Mots-clés

histogrammebarrescolonnesaccentinfo-bulletooltipserieperiodedashboardsans-axeverre

Benchmarks — la géométrie, en assertions vérifiables

Rejouables par node bin/check.mjs chart-03-accent-column-callout : le pattern se prouve, il ne se croit pas.

assertionmesureattendu
une seule colonne accentuéedocument.querySelectorAll('.vl-callout__col--on').length1 ± 0
une seule info-bulle, et elle est portée par la colonne accentuéedocument.querySelectorAll('.vl-callout__tip').length + (document.querySelector('.vl-callout__col--on .vl-callout__tip') ? 0 : 10)1 ± 0
la colonne accentuée est la plus haute de la sériebox('.vl-callout__col--on .vl-callout__bar').h - Math.max(...[...document.querySelectorAll('.vl-callout__col:not(.vl-callout__col--on) .vl-callout__bar')].map((e) => e.getBoundingClientRect().height))≥ 20
le pas vaut environ le double de la largeur de colonne (le vide fait lire l'accent)(box('.vl-callout__col:nth-child(2)').left - box('.vl-callout__col:nth-child(1)').left) / box('.vl-callout__col').w2.22 ± 0.3
les colonnes flottent : le rayon vaut ≈ 0,43 × la largeur, en haut ET en basnum('.vl-callout__bar','borderBottomLeftRadius') / box('.vl-callout__bar').w0.4219 ± 0.06
la série remplit sa carte : la plus haute colonne dépasse 55 % de la hauteurbox('.vl-callout__col--on .vl-callout__bar').h / H≥ 0.55
l'info-bulle reste dans la carte (ancrée à la colonne, elle peut en sortir)box('.vl-callout__box').right - W≤ 0
l'info-bulle partage la ligne d'en-tête sans percuter le titre ni la pilule de périodeMath.min(box('.vl-callout__box').left - box('.vl-callout__title').right, box('.vl-callout__pill').left - box('.vl-callout__box').right)≥ 8
contraste du texte de l'info-bulle sur son aplat ≥ 7contrast('.vl-callout__box')≥ 7
contraste des légendes de période ≥ 4,5 malgré leur petit corpscontrast('.vl-callout__cap')≥ 4.5
aucun débordement du contenu hors de la carteoverflow('.vl-callout')≤ 0

Le code

fragment — patterns/chart-03-accent-column-callout.html Télécharger
<!-- chart-03-accent-column-callout — fragment autonome. Couleurs via --vl-* uniquement, aucune valeur en dur. -->
<div class="vl-callout">
  <div class="vl-callout__head">
    <h2 class="vl-callout__title">Expense statistic</h2>
    <span class="vl-callout__pill">Monthly</span>
  </div>
  <div class="vl-callout__plot">
    <span class="vl-callout__col">
      <span class="vl-callout__bar" style="height:77px"></span>
      <span class="vl-callout__cap">MAY</span>
    </span>
    <span class="vl-callout__col">
      <span class="vl-callout__bar" style="height:63px"></span>
      <span class="vl-callout__cap">JUN</span>
    </span>
    <span class="vl-callout__col vl-callout__col--on">
      <span class="vl-callout__bar" style="height:140px">
        <span class="vl-callout__tip">
          <span class="vl-callout__dot"></span>
          <span class="vl-callout__box">$45k</span>
        </span>
      </span>
      <span class="vl-callout__cap">JUL</span>
    </span>
    <span class="vl-callout__col">
      <span class="vl-callout__bar" style="height:87px"></span>
      <span class="vl-callout__cap">AUG</span>
    </span>
    <span class="vl-callout__col">
      <span class="vl-callout__bar" style="height:73px"></span>
      <span class="vl-callout__cap">SEP</span>
    </span>
  </div>
</div>

<style>
/* Un histogramme où UNE seule colonne est accentuée — la courante — et où l'info-bulle ne
   commente qu'elle. Ni axe, ni graduation, ni valeur sur les autres colonnes : la série donne
   une forme, l'accent donne le chiffre. Les colonnes sont arrondies EN HAUT ET EN BAS (elles
   flottent, elles ne sont pas plantées sur une ligne de base), et le pas est à peu près le
   double de la largeur de colonne — c'est ce vide qui rend l'accent lisible sans le grossir.
   L'info-bulle est ancrée à la colonne, pas à la carte : elle suit sa valeur quand elle change. */
.vl-callout {
  position: relative; width: 700px; height: 238px; padding: 24px 28px;
  display: flex; flex-direction: column;
  border-radius: var(--vl-radius-card);
  background-color: var(--vl-surface-glass);
  backdrop-filter: blur(var(--vl-blur));
  box-shadow: inset 0 1px 0 var(--vl-sheen);
  font-family: Inter, -apple-system, "Helvetica Neue", Arial, sans-serif;
  color: var(--vl-ink);
}
.vl-callout__head { display: flex; align-items: center; justify-content: space-between; }
.vl-callout__title { margin: 0; font-size: 15px; font-weight: 500; letter-spacing: -0.01em; }
.vl-callout__pill {
  display: inline-flex; align-items: center; padding: 7px 15px; border-radius: 999px;
  font-size: 11px; font-weight: 500;
  background-color: var(--vl-surface-glass-strong);
  box-shadow: inset 0 1px 0 var(--vl-sheen);
}
.vl-callout__plot { flex: 1; display: flex; align-items: flex-end; justify-content: space-between; padding: 8px 6px 0; }
.vl-callout__col { width: 64px; display: inline-flex; flex-direction: column; align-items: center; }
.vl-callout__bar {
  position: relative; width: 64px; border-radius: 27px;
  background-color: var(--vl-surface-glass-strong);
  box-shadow: inset 0 1px 0 var(--vl-sheen);
}
.vl-callout__col--on .vl-callout__bar {
  background-color: var(--vl-blue);
  background-image: linear-gradient(180deg, var(--vl-blue-light) 0%, var(--vl-blue) 100%);
  box-shadow: none;
}
.vl-callout__cap { margin-top: 10px; font-size: 10px; font-weight: 500; letter-spacing: 0.04em; color: var(--vl-ink-muted); }
.vl-callout__col--on .vl-callout__cap { color: var(--vl-ink); font-weight: 600; }
.vl-callout__tip { position: absolute; left: 10px; top: -34px; display: flex; align-items: center; gap: 8px; }
.vl-callout__dot { width: 9px; height: 9px; border-radius: 50%; background-color: var(--vl-surface-dark); }
.vl-callout__box {
  padding: 6px 12px; border-radius: 999px; white-space: nowrap;
  background-color: var(--vl-surface-dark); color: var(--vl-ink-invert);
  font-size: 11px; font-weight: 500;
}
</style>
tokens de la charte — systems/ref-13-glass-fintech-dashboard.json Télécharger
:root {
  --vl-bg: #B4BEDD;
  --vl-grad-1: #A3B0DA;
  --vl-grad-2: #98A9D6;
  --vl-grad-3: #C8CEDA;
  --vl-surface-glass: rgba(255, 255, 255, 0.45);
  --vl-surface-glass-strong: rgba(255, 255, 255, 0.68);
  --vl-surface-glass-soft: rgba(255, 255, 255, 0.24);
  --vl-surface-dark: #101625;
  --vl-sheen: rgba(255, 255, 255, 0.45);
  --vl-blue: #4F7CF7;
  --vl-blue-light: #8FB6FA;
  --vl-violet: #8B5CF6;
  --vl-violet-deep: #5B7CF0;
  --vl-violet-halo: rgba(123, 106, 240, 0.42);
  --vl-ink: #1A1F2E;
  --vl-ink-muted: #565C72;
  --vl-ink-invert: #FFFFFF;
  --vl-ink-invert-muted: rgba(255, 255, 255, 0.72);
  --vl-radius-card: 28px;
  --vl-radius-sub: 18px;
  --vl-gap: 22px;
  --vl-margin: 44px;
  --vl-blur: 18px;
}
les métadonnées — patterns/chart-03-accent-column-callout.json (télécharger)
{
  "name": "Histogramme à colonne accentuée et info-bulle",
  "family": "chart",
  "ref": "ref-13-glass-fintech-dashboard",
  "intent": "Donner la forme d'une série en quelques colonnes flottantes sans axe ni graduation, et n'en chiffrer qu'une seule — la courante — par une info-bulle sombre ancrée à sa colonne.",
  "when_to_use": "Une série courte (4 à 7 périodes) sert de contexte à UNE valeur qui, elle, doit être lue exactement : mois en cours, dernier trimestre, pic à commenter.",
  "avoid_when": "Plusieurs valeurs doivent être lues précisément (il faudrait autant d'info-bulles, et l'accent perd tout sens), ou les écarts entre colonnes sont faibles : sans axe, un écart de 5 % ne se voit pas.",
  "media": [
    "slide",
    "web"
  ],
  "tags": [
    "histogramme",
    "barres",
    "colonnes",
    "accent",
    "info-bulle",
    "tooltip",
    "serie",
    "periode",
    "dashboard",
    "sans-axe",
    "verre"
  ],
  "vars": [
    {
      "name": "--vl-surface-glass",
      "role": "verre de la carte",
      "default": "rgba(255, 255, 255, 0.45)"
    },
    {
      "name": "--vl-surface-glass-strong",
      "role": "colonnes neutres et pilule de période",
      "default": "rgba(255, 255, 255, 0.68)"
    },
    {
      "name": "--vl-blue",
      "role": "pied du dégradé de la colonne accentuée",
      "default": "#4F7CF7"
    },
    {
      "name": "--vl-blue-light",
      "role": "sommet du dégradé de la colonne accentuée",
      "default": "#8FB6FA"
    },
    {
      "name": "--vl-surface-dark",
      "role": "aplat de l'info-bulle et de son point de repère",
      "default": "#101625"
    }
  ],
  "slots": [
    "title",
    "period",
    "columns",
    "callout"
  ],
  "geometry": {
    "root": ".vl-callout",
    "frame": [
      700,
      238
    ],
    "ratios": {
      "col_over_width": 0.0914,
      "pitch_over_col": 2.219,
      "bar_radius_over_col": 0.4219,
      "tall_over_height": 0.5882
    },
    "type_px": {
      "title": 15,
      "pill": 11,
      "cap": 10,
      "tip": 11
    },
    "pad_ratio": {
      "top": 0.0343,
      "x": 0.04,
      "bottom": 0.0343
    }
  },
  "benchmarks": [
    {
      "name": "une seule colonne accentuée",
      "measure": "document.querySelectorAll('.vl-callout__col--on').length",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "une seule info-bulle, et elle est portée par la colonne accentuée",
      "measure": "document.querySelectorAll('.vl-callout__tip').length + (document.querySelector('.vl-callout__col--on .vl-callout__tip') ? 0 : 10)",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "la colonne accentuée est la plus haute de la série",
      "measure": "box('.vl-callout__col--on .vl-callout__bar').h - Math.max(...[...document.querySelectorAll('.vl-callout__col:not(.vl-callout__col--on) .vl-callout__bar')].map((e) => e.getBoundingClientRect().height))",
      "min": 20
    },
    {
      "name": "le pas vaut environ le double de la largeur de colonne (le vide fait lire l'accent)",
      "measure": "(box('.vl-callout__col:nth-child(2)').left - box('.vl-callout__col:nth-child(1)').left) / box('.vl-callout__col').w",
      "expect": 2.22,
      "tol": 0.3
    },
    {
      "name": "les colonnes flottent : le rayon vaut ≈ 0,43 × la largeur, en haut ET en bas",
      "measure": "num('.vl-callout__bar','borderBottomLeftRadius') / box('.vl-callout__bar').w",
      "expect": 0.4219,
      "tol": 0.06
    },
    {
      "name": "la série remplit sa carte : la plus haute colonne dépasse 55 % de la hauteur",
      "measure": "box('.vl-callout__col--on .vl-callout__bar').h / H",
      "min": 0.55
    },
    {
      "name": "l'info-bulle reste dans la carte (ancrée à la colonne, elle peut en sortir)",
      "measure": "box('.vl-callout__box').right - W",
      "max": 0
    },
    {
      "name": "l'info-bulle partage la ligne d'en-tête sans percuter le titre ni la pilule de période",
      "measure": "Math.min(box('.vl-callout__box').left - box('.vl-callout__title').right, box('.vl-callout__pill').left - box('.vl-callout__box').right)",
      "min": 8
    },
    {
      "name": "contraste du texte de l'info-bulle sur son aplat ≥ 7",
      "measure": "contrast('.vl-callout__box')",
      "min": 7
    },
    {
      "name": "contraste des légendes de période ≥ 4,5 malgré leur petit corps",
      "measure": "contrast('.vl-callout__cap')",
      "min": 4.5
    },
    {
      "name": "aucun débordement du contenu hors de la carte",
      "measure": "overflow('.vl-callout')",
      "max": 0
    }
  ],
  "notes": "Deux bornes protègent l'accent, et elles tirent en sens inverse : la colonne accentuée doit être la plus haute (sinon l'œil accentue la mauvaise), et son info-bulle — ancrée à la COLONNE, donc libre de sortir de la carte — doit rester dans le cadre et sous l'en-tête. C'est ce couple qui rend le pattern rejouable sur d'autres valeurs : dès que la série change, l'un des deux casse avant le rendu. Le rayon est mesuré en BAS : c'est le seul endroit où l'on voit si les colonnes flottent ou si elles sont plantées sur une ligne de base absente. L'info-bulle monte au niveau de l'en-tête : c'est voulu, la colonne accentuée est haute et la bulle la surmonte. Ce qui est mesuré n'est donc pas un dégagement vertical (il n'y en a pas) mais le dégagement HORIZONTAL avec ses deux voisins de ligne, titre à gauche et pilule de période à droite.",
  "id": "chart-03-accent-column-callout"
}

À côté