Barrierefreie Gesundheits-Dashboards mit React und D3.js erstellen
Über einfache Diagramme hinaus. Erstellen Sie komplexe, interaktive und WCAG-konforme Gesundheitsdaten-Visualisierungen wie Schlafphasen-Diagramme mit React-Hooks.
Auf dieser Seite
Die Herausforderung: Gesundheitsdaten sind oft komplex und vielschichtig. Eine Rohdaten-Tabelle mit Schlafzeiten oder Nährwerten kann überwältigend sein. Visualisierungen wandeln diese Daten in verwertbare Erkenntnisse um. Allerdings stellt das Erstellen von Diagrammen, die nicht nur optisch ansprechend, sondern auch dynamisch und für Benutzer mit Behinderungen zugänglich sind, eine erhebliche technische Herausforderung dar. Viele Diagramm-Bibliotheken bieten einen Kompromiss zwischen Anpassungsmöglichkeiten und Barrierefreiheit.
Unsere Lösung: Durch die Nutzung von D3.js für seine beispiellose Kontrolle über SVG-Elemente und in Kombination mit der effizienten Zustandsverwaltung und Komponentenarchitektur von React können wir benutzerdefinierte Visualisierungen erstellen, die keine Kompromisse bei der Barrierefreiheit eingehen. Wir lassen D3 die komplexen Berechnungen und das Zeichnen übernehmen, während React die DOM-Struktur und Benutzerinteraktionen verwaltet.
Voraussetzungen:
- Solides Verständnis von React (einschließlich Hooks).
- Grundlegende Vertrautheit mit D3.js-Konzepten (Selektionen, Skalen, Formen).
- Node.js und npm (oder yarn) installiert.
- Praktische Kenntnisse von SVG und HTML.
Das Problem verstehen: Die konkurrierenden Philosophien von D3 und React
Die Kernherausforderung bei der Verwendung von D3 mit React ergibt sich aus einem fundamentalen Konflikt:
- D3.js ist imperativ. Es manipuliert den DOM direkt. Sie wählen ein Element aus und geben Befehle, um es zu ändern.
- React ist deklarativ. Sie beschreiben den gewünschten Zustand Ihrer Benutzeroberfläche, und React ermittelt den effizientesten Weg zur Aktualisierung des DOM.
Wenn beide Bibliotheken versuchen, dieselben DOM-Elemente zu kontrollieren, entsteht Chaos. Die Lösung besteht darin, eine klare Grenze zu ziehen. In unserem Ansatz wird React den Hauptsvg-Container rendern, und D3 erhält die Kontrolle über die Elemente innerhalb dieses Containers. Wir verwenden Reacts useRef, um eine stabile Referenz auf diesen Container zu erstellen, und useEffect, um unseren D3-Code auszuführen, sobald sich die Daten oder Komponenten-Requisiten ändern.
Voraussetzungen: Einrichten Ihres React-Projekts
Zunächst erstellen wir eine neue React-Anwendung und installieren D3.js.
npx create-react-app health-viz-d3-react
cd health-viz-d3-react
npm install d3
Für dieses Tutorial werden wir hauptsächlich innerhalb des src-Verzeichnisses arbeiten.
Schritt 1: Erstellen eines zugänglichen Schlafphasen-Diagramms
Unsere erste Visualisierung wird ein Schlafphasen-Diagramm sein, oft als Hypnogramm bezeichnet. Es zeigt den Verlauf der Schlafphasen (Wach, REM, Leicht, Tief) über den Verlauf einer Nacht. Dies ist komplexer als ein einfaches Balkendiagramm, da jeder Balken aus mehreren farbigen Segmenten besteht.
Was wir tun
Wir erstellen eine SleepChart-Komponente, die ein Array von Schlafdaten erhält. Wir verwenden D3, um die Positionen und Farben jedes Schlafphasen-Segments zu berechnen, und verwenden dann React, um sie als SVG-<rect>-Elemente zu rendern. Dieser Ansatz, bei dem D3 berechnet und React rendert, ist oft einfacher zu verwalten für Barrierefreiheit und Zustandsaktualisierungen.
Implementierung
Zunächst definieren wir einige Beispieldaten. Erstellen Sie eine Datei src/data/sleepData.js:
// src/data/sleepData.js
export const sleepData = [
{ time: '22:00', stage: 'Wach', duration: 15 },
{ time: '22:15', stage: 'Leicht', duration: 30 },
{ time: '22:45', stage: 'Tief', duration: 60 },
{ time: '23:45', stage: 'Leicht', duration: 45 },
{ time: '00:30', stage: 'REM', duration: 30 },
{ time: '01:00', stage: 'Leicht', duration: 60 },
{ time: '02:00', stage: 'Tief', duration: 75 },
{ time: '03:15', stage: 'Leicht', duration: 45 },
{ time: '04:00', stage: 'REM', duration: 45 },
{ time: '04:45', stage: 'Leicht', duration: 30 },
{ time: '05:15', stage: 'Wach', duration: 10 },
];
export const sleepStageColors = {
'Wach': '#f0a500',
'REM': '#f44336',
'Leicht': '#3f51b5',
'Tief': '#1a237e',
};
Erstellen Sie nun die SleepChart-Komponente in src/components/SleepChart.js:
// src/components/SleepChart.js
import React, { useMemo } from 'react';
import * as d3 from 'd3';
import { sleepStageColors } from '../data/sleepData';
const SleepChart = ({ data, width = 600, height = 150 }) => {
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const { segments, xScale } = useMemo(() => {
if (!data || data.length === 0) return { segments: [], xScale: null };
let cumulativeDuration = 0;
const processedSegments = data.map(d => {
const start = cumulativeDuration;
cumulativeDuration += d.duration;
return { ...d, start, end: cumulativeDuration };
});
const totalDuration = cumulativeDuration;
const xScale = d3.scaleLinear()
.domain([0, totalDuration])
.range([0, innerWidth]);
return { segments: processedSegments, xScale };
}, [data, innerWidth]);
return (
<svg
width={width}
height={height}
role="figure"
aria-labelledby="sleep-chart-title"
aria-describedby="sleep-chart-desc"
>
<title id="sleep-chart-title">Schlafphasen-Diagramm</title>
<desc id="sleep-chart-desc">
Eine Visualisierung der Schlafphasen während der Nacht, die Perioden von Wach-, REM-, Leicht- und Tiefschlaf zeigt.
</desc>
<g transform={`translate(${margin.left}, ${margin.top})`}>
{segments.map((segment, i) => (
<g key={i} role="listitem">
<rect
x={xScale(segment.start)}
y={0}
width={xScale(segment.end) - xScale(segment.start)}
height={innerHeight}
fill={sleepStageColors[segment.stage]}
role="presentation"
/>
<title>
{`Phase: ${segment.stage}, Dauer: ${segment.duration} Minuten, Zeit: ${segment.time}`}
</title>
</g>
))}
{/* Bei Bedarf können hier Achsen hinzugefügt werden */}
</g>
</svg>
);
};
export default SleepChart;
Verwenden Sie diese Komponente schließlich in Ihrer App.js:
// src/App.js
import React from 'react';
import SleepChart from './components/SleepChart';
import { sleepData } from './data/sleepData';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<h1>Gesundheitsdaten-Visualisierungen</h1>
</header>
<main>
<h2>Schlafanalyse</h2>
<SleepChart data={sleepData} />
</main>
</div>
);
}
export default App;
Wie es funktioniert
- Datenverarbeitung: Wir verwenden
useMemo, um die Roh-Schlafdaten zu verarbeiten. Dieser Hook berechnet die Start- und Endposition (in kumulativen Minuten) für jedes Schlafsegment und erstellt eine D3-lineare Skala (xScale), um diese Minuten auf Pixelwerte auf dem Bildschirm abzubilden.useMemostellt sicher, dass diese Berechnung nur ausgeführt wird, wenn sich die Daten oder Breite ändern. - React-Rendering: Anstatt D3s
.data().enter().append()-Muster verwenden wir Reacts vertraute.map()-Methode, um über unsere verarbeitetensegmentszu iterieren. Für jedes Segment rendern wir ein SVG-<rect>-Element. - Barrierefreiheit zuerst:
- Das
<svg>-Element hatrole="figure"und ist mit einem<title>und<desc>überaria-labelledbyundaria-describedbyverknüpft. Dies bietet Screenreadern eine übergeordnete Zusammenfassung des Diagramms. - Jedes Segment ist in ein
<g>mitrole="listitem"gewrappt, um anzuzeigen, dass es Teil einer Sammlung ist. - Entscheidend ist, dass jedes Segment ein
<title>-Element enthält. Dies fungiert als nativer Tooltip beim Hover und bietet ein Label für Screenreader.
- Das
Schritt 2: Eine interaktive und zugängliche Ernährungs-Heatmap
Als Nächstes werden wir eine Heatmap erstellen, um Ernährungsdaten zu visualisieren, wie z.B. die tägliche Makronährstoffzufuhr über eine Woche. Dies führt Interaktivität und komplexere Barrierefreiheits-Überlegungen ein.
Was wir tun
Wir erstellen eine NutritionHeatmap-Komponente, die Nährwerte in einem Raster anzeigt. Die Farbe jeder Zelle repräsentiert ihren Wert. Wir fügen Tooltips beim Hover hinzu und machen jede Zelle fokussierbar und über die Tastatur bedienbar, wobei die Benutzeroberfläche aktualisiert wird, um Details für die ausgewählte Zelle anzuzeigen.
Implementierung
Zunächst die Daten in src/data/nutritionData.js:
// src/data/nutritionData.js
export const nutritionData = [
{ day: 'Mo', nutrient: 'Protein (g)', value: 120 },
{ day: 'Mo', nutrient: 'Kohlenhydrate (g)', value: 150 },
{ day: 'Mo', nutrient: 'Fett (g)', value: 60 },
{ day: 'Di', nutrient: 'Protein (g)', value: 110 },
{ day: 'Di', nutrient: 'Kohlenhydrate (g)', value: 200 },
{ day: 'Di', nutrient: 'Fett (g)', value: 55 },
// ... Daten für andere Tage hinzufügen
{ day: 'So', nutrient: 'Protein (g)', value: 130 },
{ day: 'So', nutrient: 'Kohlenhydrate (g)', value: 220 },
{ day: 'So', nutrient: 'Fett (g)', value: 70 },
];
Nun die NutritionHeatmap-Komponente in src/components/NutritionHeatmap.js:
// src/components/NutritionHeatmap.js
import React, { useMemo, useState } from 'react';
import * as d3 from 'd3';
const NutritionHeatmap = ({ data, width = 700, height = 250 }) => {
const margin = { top: 50, right: 50, bottom: 50, left: 100 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const [activeCell, setActiveCell] = useState(null);
const { xScale, yScale, colorScale, allX, allY } = useMemo(() => {
const allX = [...new Set(data.map(d => d.day))];
const allY = [...new Set(data.map(d => d.nutrient))];
const xScale = d3.scaleBand()
.domain(allX)
.range([0, innerWidth])
.padding(0.05);
const yScale = d3.scaleBand()
.domain(allY)
.range([0, innerHeight])
.padding(0.05);
const colorScale = d3.scaleSequential()
.interpolator(d3.interpolateYlGnBu)
.domain([0, d3.max(data, d => d.value)]);
return { xScale, yScale, colorScale, allX, allY };
}, [data, innerWidth, innerHeight]);
const handleKeyDown = (e, cell) => {
if (e.key === 'Enter' || e.key === ' ') {
setActiveCell(cell);
}
};
return (
<div>
<svg width={width} height={height} role="application">
<g transform={`translate(${margin.left}, ${margin.top})`}>
{data.map((d, i) => (
<rect
key={i}
x={xScale(d.day)}
y={yScale(d.nutrient)}
width={xScale.bandwidth()}
height={yScale.bandwidth()}
fill={colorScale(d.value)}
onMouseEnter={() => setActiveCell(d)}
onMouseLeave={() => setActiveCell(null)}
onFocus={() => setActiveCell(d)}
onBlur={() => setActiveCell(null)}
onKeyDown={(e) => handleKeyDown(e, d)}
tabIndex={0}
aria-label={`${d.day}, ${d.nutrient}: ${d.value}`}
/>
))}
{/* Achsen hinzufügen */}
{/* X-Achse */}
<g>
{allX.map(day => (
<text key={day} x={xScale(day) + xScale.bandwidth() / 2} y={-5} textAnchor="middle">
{day}
</text>
))}
</g>
{/* Y-Achse */}
<g>
{allY.map(nutrient => (
<text key={nutrient} x={-5} y={yScale(nutrient) + yScale.bandwidth() / 2} textAnchor="end" dominantBaseline="middle">
{nutrient}
</text>
))}
</g>
</g>
</svg>
<div role="status" aria-live="polite" className="tooltip-aria">
{activeCell ? `${activeCell.day}, ${activeCell.nutrient}: ${activeCell.value}` : 'Fahren Sie mit der Maus über eine Zelle oder fokussieren Sie sie für Details.'}
</div>
</div>
);
};
export default NutritionHeatmap;
Aktualisieren Sie App.js, um die neue Komponente einzubinden:
// src/App.js
import React from 'react';
import SleepChart from './components/SleepChart';
import NutritionHeatmap from './components/NutritionHeatmap';
import { sleepData } from './data/sleepData';
import { nutritionData } from './data/nutritionData';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<h1>Gesundheitsdaten-Visualisierungen</h1>
</header>
<main>
<h2>Schlafanalyse</h2>
<SleepChart data={sleepData} />
<hr />
<h2>Wöchentliche Ernährung</h2>
<NutritionHeatmap data={nutritionData} />
</main>
</div>
);
}
export default App;
Und fügen Sie einige einfache Stile in src/App.css hinzu:
/* src/App.css */
.tooltip-aria {
margin-top: 10px;
font-weight: bold;
}
rect:hover, rect:focus {
stroke: black;
stroke-width: 2px;
outline: none;
}
Wie es funktioniert
- React-Zustand für Interaktivität: Wir verwenden
useState, um denactiveCellim Auge zu behalten. Dieser Zustand wird beionMouseEnter- undonFocus-Ereignissen aktualisiert. - Skalen für ein Raster: D3s
scaleBandeignet sich perfekt für die Erstellung der X- und Y-Achsen unseres Rasters, wobei die Tage und Nährstoffe gleichmäßig verteilt werden.scaleSequentialerstellt einen sanften Farbverlauf für unsere Werte. - Tastatur-Barrierefreiheit:
- Jedes
<rect>hattabIndex={0}, was es über die Tastatur fokussierbar macht. - Ein
onKeyDown-Handler lauscht auf "Enter" oder "Leertaste", um eine Zelle auszuwählen, und imitiert eine Klickaktion. aria-labelbietet ein beschreibendes Label für jede Zelle, das Screenreader beim Fokus ansagen.
- Jedes
- Live-Region für Screenreader: Der
divmitrole="status"undaria-live="polite"ist entscheidend. Er fungiert als "Live-Region." Wann immer sich sein Inhalt ändert (d.h. wennactiveCellaktualisiert wird), werden Screenreader den neuen Text ansagen, ohne dass der Benutzer den Fokus vom Diagramm wegbewegen muss. Dies stellt sicher, dass die detaillierten Informationen allen Benutzern vermittelt werden.
Fazit
Durch sorgfältige Abgrenzung der Rollen von React und D3 können wir leistungsstarke, benutzerdefinierte Datenvisualisierungen erstellen, die sowohl hochinteraktiv als auch vollständig zugänglich sind. React eignet sich hervorragend für die Verwaltung des Zustands und das Rendern der Komponentenstruktur, während D3 die mathematischen und geometrischen Werkzeuge bietet, die für komplexe Diagramme erforderlich sind. Der Schlüssel liegt darin, D3 die Berechnungen und React das Rendering übernehmen zu lassen, wobei die endgültige Ausgabe mit den notwendigen ARIA-Attributen angereichert wird, um die WCAG-Standards zu erfüllen.
Nächste Schritte für Leser:
- Achsen hinzufügen: Verbessern Sie die Diagramme durch D3-gesteuerte Achsen.
- Brushing und Zoomen implementieren: Erkunden Sie D3s Brush- und Zoom-Funktionalitäten, um Benutzern die Auswahl oder Vergrößerung von Teilen der Daten zu ermöglichen.
- Echte Daten abrufen: Verbinden Sie Ihre Komponenten mit einer Live-API, um dynamische Gesundheitsdaten zu visualisieren.
Ressourcen
- Offizielle D3.js-Dokumentation
- Offizielle React-Dokumentation
- Richtlinien für barrierefreie Webinhalte (WCAG)
- ARIA in HTML
Schnellreferenz
| Aspekt | Wichtige Informationen |
|---|---|
| Thema | Entwicklung |
| Relevanz | Patientenaufklärung und -sensibilisierung |
| Maßnahme | Besprechen Sie dies mit Ihrem Arzt |
| Häufigkeit | Nach Empfehlung Ihres Behandlungsteams |
| Evidenz | Basierend auf aktuellen klinischen Richtlinien |
Häufig gestellte Fragen
F: Wie weiß ich, ob dies auf mich zutrifft? A: Besprechen Sie Ihre persönliche und familiäre Krankengeschichte mit Ihrem Arzt, um Ihre individuellen Risikofaktoren zu bewerten und geeignete nächste Schritte zu bestimmen.
F: Was sollte ich meinen Arzt fragen? A: Gute Fragen sind: "Was bedeuten meine Ergebnisse?", "Was sind meine Optionen?", "Welche Lebensstiländerungen würden helfen?" und "Wann sollte ich erneut vorstellig werden?"
F: Gibt es Lebensstiländerungen, die helfen können? A: Die meisten Gesundheitszustände profitieren von grundlegenden Maßnahmen: ausgewogene Ernährung, regelmäßige körperliche Aktivität (150+ Minuten/Woche), erholsamer Schlaf (7-9 Stunden), Stressbewältigung und Verzicht auf Tabak.
Haftungsausschluss
Dieser Artikel dient nur zu Bildungszwecken und stellt keine medizinische Beratung dar. Konsultieren Sie immer einen qualifizierten Arzt, bevor Sie Entscheidungen über Ihre Gesundheit, Ihre Behandlung oder Ihre Pflege treffen. Individuelle Umstände variieren, und was für eine Person angemessen ist, ist möglicherweise nicht für eine andere Person geeignet.
Wenn Sie einen medizinischen Notfall haben, rufen Sie sofort 911 (oder Ihre örtliche Notrufnummer) an.