Aller au contenu

Étape 4 : curseur, infobulle et légende ​

Trois types d'objets réagissent au pointeur : un curseur qui donne la valeur sous lui sur chaque axe, une infobulle qui liste les valeurs des séries, et une légende qui montre et cache des séries.

ts
import {
  D3PAutoDomain, D3PAxis, D3PAxisDomain, D3PChart, D3PLineRenderer, D3PSeries, getGlobalBus,
  D3PAutoDomain, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PChart, D3PCursorIndicator, d3pDomLegend,
  d3pDomTooltip, D3PLegendBundle, D3PLineRenderer, D3PSeries, D3PTooltip, getGlobalBus,
} from 'd3pict'

const bus = getGlobalBus()

const container = document.getElementById('app')! // any element with a size: the chart fills it

const temperature = new D3PSeries({ bus, name: 'temperature', data: [[0, 12], [4, 9], [8, 14], [12, 21], [16, 24], [20, 17], [24, 13]] })
const feelsLike = new D3PSeries({ bus, name: 'feels like', data: [[0, 10], [4, 6], [8, 12], [12, 22], [16, 25], [20, 15], [24, 11]] })

const hours = new D3PAxisDomain({ bus, domain: [0, 24] })
const degrees = new D3PAxisDomain({ bus, domain: [0, 30] })
const auto = new D3PAutoDomain({ bus })
auto.linkSeries(temperature, [hours, degrees])
auto.linkSeries(feelsLike, [hours, degrees])

const x = new D3PAxis({ bus, name: 'hour', domain: hours, props: { tickCount: 6, tickFormat: (v) => `${Math.round(v)} h`, grid: { enabled: true } } })
const y = new D3PAxis({ bus, name: 'temperature', domain: degrees, position: 'left', props: { tickFormat: (v) => `${Math.round(v * 10) / 10} °C`, grid: { enabled: true } } })
// one renderer per series; its style is a prop
const line = new D3PLineRenderer({ bus, series: temperature, props: { style: { color: '#e15759', curve: 'monotone', strokeWidth: 2.5 } } })
const feelsLine = new D3PLineRenderer({ bus, series: feelsLike, props: { style: { color: '#4e79a7', curve: 'monotone' } } })

const objects = [x, y, line, feelsLine]
// what the pointer shows: a crosshair, a tooltip, a legend
const cursor = new D3PCursorIndicator({ bus })
const tooltip = new D3PTooltip({ bus })
const legend = new D3PLegendBundle({ bus })

const objects = [x, y, line, feelsLine, cursor, tooltip, legend]

const chart = new D3PChart({ container, bus })
const stopArea = d3pAutoInteractionArea(chart) // before the objects: the cursor, the tooltip and the tools ask for an interaction area
objects.forEach((o) => o.addToChart(chart))
const stopTooltip = d3pDomTooltip(tooltip)
const stopLegend = d3pDomLegend(legend)

Ce qui a changé :

  • D3PCursorIndicator dessine un réticule qui suit le pointeur, avec la valeur de chaque axe sur l'axe.
  • D3PTooltip liste la valeur de chaque série sous le pointeur.
  • D3PLegendBundle liste les séries ; un clic sur une entrée cache sa courbe.
  • Core seulement : une zone d'interaction. Les événements du pointeur viennent d'une zone d'interaction, un rectangle transparent sur le tracé. En TypeScript pur, d3pAutoInteractionArea(chart) la crée quand quelque chose la demande, et doit être appelé avant l'attache des objets. Les graphiques Vue et React le font pour vous.
  • Le HTML est à vous. L'infobulle et la légende calculent ce qu'il faut montrer et donnent un élément où le mettre. d3pDomTooltip et d3pDomLegend le remplissent d'un contenu simple par défaut ; les adaptateurs Vue et React ont des composants pour cela, qui acceptent aussi votre contenu (voir Vue et React).

Les étiquettes du curseur ​

Le curseur écrit la valeur sous le pointeur avec le tickFormat de l'axe. Cette valeur n'est pas une graduation : elle a beaucoup de décimales, donc un format comme (v) => `${Math.round(v)} %` garde l'étiquette courte. Une étiquette n'est jamais plus large que la place que son axe lui laisse : un texte plus long passe à la ligne, et se termine par une ellipse là où même cela ne tient pas. props.axisLabelStyle.maxWidth (160 px) abaisse la limite ; valueFormatter écrit les étiquettes vous-même.

À chaque mouvement, chaque série trouve le point le plus proche du pointeur, et son marker le marque : un point sur une courbe, un contour autour d'une barre. Les markers appartiennent aux rendus : donnez-en un autre avec marker, ou aucun avec marker: false.

ts
new D3PLineRenderer({ bus, series, marker: false })

L'infobulle, le curseur et les markers lisent le même événement du pointeur, et le travail n'est fait qu'une fois.