Zum Inhalt springen
SuperScheduler

Pro-ModuleGilt fürSuperScheduler Pro

Minimap und abgeleitete Kennzahlen

Rendern Sie SchedulerMinimap aus super-scheduler/minimap mit dem Control aus useSchedulerControl(); ohne Optionen zeigt sie, wie viele Ereignisse sich an jedem Tag überschneiden. Für eine geschäftliche Kennzahl wie Auslastung oder Belegung übergeben Sie eine series-Funktion, die Ihre Anwendung pro Bucket berechnet, dazu peak: 'absolute', max: 1 und eine tone-Funktion für Warn- und Gefahrenfarben. Ziehen des Auswahlfensters verschiebt die Zeitleiste, Ziehen seiner Ränder zoomt, und per Tastatur funktioniert das Auswahlfenster als Schieberegler.

Geprüft mit v0.1.0 · überarbeitet am 7. Oktober 2026.md

Ein Jahr voller Buchungen passt nicht auf den Bildschirm. Die Minimap ist ein schmaler Streifen unter (oder über) dem Planer, der die gesamte Zeitleiste auf einmal zeigt: ein Balken pro Zeitabschnitt (Bucket) und ein Auswahlfenster (Brush), das den sichtbaren Zeitraum markiert. Nutzer sehen, wo die vollen Wochen liegen, und springen direkt dorthin. Die Balken zeigen die Zahl, die Ihre Anwendung festlegt; damit wird der Streifen zu einem kompakten Diagramm für Auslastung, Belegung, Last oder Umsatz.

Die Minimap erfordert SuperScheduler Pro.

Minimap hinzufügen

SchedulerMinimap ist die React-Komponente. Sie braucht das Control-Objekt des Planers, das erst existiert, wenn der Planer gemountet ist; useSchedulerControl() liefert Ihnen control als State (zunächst null), und die Minimap akzeptiert null und wartet.

src/PlanningWithOverview.tsxtsx
import { useMemo } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import { SchedulerMinimap } from 'super-scheduler/minimap'
import 'super-scheduler/styles.css'

export function PlanningWithOverview(props: {
  resources: SuperScheduler.ResourceData[]
  events: SuperScheduler.EventData[]
}) {
  // `control` is null until the scheduler has mounted; the minimap waits for it.
  const { controlRef, control } = useSchedulerControl()
  const events = useMemo(() => props.events.slice(), [props.events])

  return (
    <>
      <SuperSchedulerComponent
        controlRef={controlRef}
        startDate="2026-01-01"
        days={365}
        scale="Day"
        cellWidth={40}
        resources={props.resources}
        events={events}
      />
      {/* Without `series`, the strip shows how many events overlap each day. */}
      <SchedulerMinimap control={control} height={32} className="planning-minimap" />
    </>
  )
}

Sie sollten einen 32 px hohen Streifen sehen, mit den Anfangsbuchstaben der Monate, einer Markierung für heute, verblassten vergangenen Tagen und einem Auswahlfenster über den sichtbaren Wochen. Ziehen Sie das Auswahlfenster, und der Planer scrollt mit.

Ohne series verwendet der Streifen eventDensity(control): die Zahl der Ereignisse, die sich mit jedem Bucket überschneiden. Gezählt wird in kurzen Hintergrundaufgaben (bis zu 8 ms oder 15.000 Ereignisse pro Aufgabe); das Ergebnis wird zwischengespeichert und nach Abschluss neu gezeichnet, sodass auch ein großer Datenbestand die Seite nie blockiert.

Wie der Streifen entsteht

Die Minimap teilt einen Zeitraum in Buckets und zeichnet einen Wert pro Bucket:

  • Zeitraum. Standardmäßig die Zeitleiste des Controls (ab startDate für days); bei unendlichem Scrollen das aktuell erzeugte Fenster. range: { start, end } legt einen anderen Zeitraum fest, zum Beispiel ein ganzes Jahr, während der Planer einen Monat zeigt.
  • Buckets. Jeweils ein Tag; eine Stunde bei scale: 'Hour' oder 'Minute'; eine Woche, wenn der Zeitraum länger als 730 Tage ist.
  • Werte. Ihre Serie liefert eine Zahl pro Bucket. Gibt es mehr Buckets als Pixel, werden benachbarte Werte zu einem Balken pro Pixelspalte gemittelt, ausgerichtet an den Gerätepixeln.
  • Höhe. Bei peak: 'relative' (Standard) entspricht der höchste Balken dem größten Wert. Bei peak: 'absolute' werden die Balken an max gemessen (Standard 1), sodass ein voller Tag immer voll aussieht.

Eigene Kennzahl einspeisen

series ist entweder ein Float32Array, das den gesamten Zeitraum abdeckt, oder eine Funktion, die den Zeitraum (start, end, buckets, bucketMs) erhält und einen Wert pro Bucket zurückgibt. Die Funktionsform passt sich an, wenn der Nutzer zoomt und sich die Bucket-Größe ändert.

Die Bibliothek weiß nicht, was „ausgelastet“ für Ihr Geschäft bedeutet; die Kennzahl ist deshalb Ihr Code. Diese hier berechnet die Auslastung: den gebuchten Anteil der verfügbaren Zeit, für beliebig viele Ressourcen.

src/utilizationSeries.tsts
import { SuperScheduler } from 'super-scheduler'
import type { MinimapRange, MinimapSeries } from 'super-scheduler/minimap'

export interface Booking {
  /** ISO wall-clock values with seconds; `end` is exclusive. */
  readonly start: string
  readonly end: string
}

/**
 * Booked share of the available time in each bucket: 0 is idle, 1 is every resource busy
 * for the whole bucket. The application decides what "capacity" means; here it is the
 * number of bookable resources.
 */
export function utilizationSeries(bookings: readonly Booking[], capacity: number): MinimapSeries {
  // Parse once; the series function runs again on every redraw.
  const spans = bookings.map((booking) => ({
    start: new SuperScheduler.Date(booking.start).getTime(),
    end: new SuperScheduler.Date(booking.end).getTime(),
  }))

  return (range: MinimapRange) => {
    const values = new Float32Array(range.buckets)
    const origin = range.start.getTime()
    const available = range.bucketMs * Math.max(1, capacity)
    for (const span of spans) {
      // Half-open [start, end): a booking ending at midnight does not touch the next day.
      const first = Math.max(0, Math.floor((span.start - origin) / range.bucketMs))
      const last = Math.min(range.buckets, Math.ceil((span.end - origin) / range.bucketMs))
      for (let i = first; i < last; i++) {
        const bucketStart = origin + i * range.bucketMs
        const overlap =
          Math.min(span.end, bucketStart + range.bucketMs) - Math.max(span.start, bucketStart)
        if (overlap > 0) values[i] = (values[i] ?? 0) + overlap / available
      }
    }
    return values
  }
}

Bei zwei Transportern, von denen einer den ganzen Tag und der andere ab Mittag gebucht ist, ergibt der Tag 0,75. Intervalle sind halboffen, wie im Planer: Eine Buchung, die um Mitternacht endet, berührt den nächsten Tag nicht.

src/FleetPlanning.tsxtsx
import { useMemo } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import { SchedulerMinimap } from 'super-scheduler/minimap'
import type { MinimapLabels, MinimapTone } from 'super-scheduler/minimap'
import { utilizationSeries } from './utilization-series'

// Module-level: the minimap receives the same functions on every render.
const tone = (value: number): MinimapTone =>
  value >= 0.95 ? 'danger' : value >= 0.8 ? 'warn' : 'base'

const LABELS: Partial<MinimapLabels> = {
  label: 'Fleet utilization overview',
  valueText: (start, end) =>
    `Showing ${start.toString('d MMM yyyy')} to ${end.toString('d MMM yyyy')}`,
}

export function FleetPlanning(props: {
  vehicles: SuperScheduler.ResourceData[]
  bookings: SuperScheduler.EventData[]
}) {
  const { controlRef, control } = useSchedulerControl()
  const events = useMemo(() => props.bookings.slice(), [props.bookings])

  // Recomputed only when the data changes; a new series function makes the strip redraw.
  const series = useMemo(
    () =>
      utilizationSeries(
        props.bookings.map((booking) => ({
          start: String(booking.start),
          end: String(booking.end),
        })),
        props.vehicles.length,
      ),
    [props.bookings, props.vehicles.length],
  )

  return (
    <>
      <SuperSchedulerComponent
        controlRef={controlRef}
        startDate="2026-01-01"
        days={365}
        scale="Day"
        cellWidth={40}
        resources={props.vehicles}
        events={events}
      />
      <SchedulerMinimap
        control={control}
        series={series}
        // 1 means full, whatever the busiest bucket is.
        peak="absolute"
        max={1}
        tone={tone}
        labels={LABELS}
        marks={{ today: true, months: true, past: true }}
        height={32}
        className="fleet-minimap"
      />
    </>
  )
}

Jetzt liest sich der Streifen wie ein Auslastungsdiagramm: ruhige Balken, die mit dem Wert kräftiger werden, Bernsteingelb ab 80 %, Rot ab 95 %, und ein Screenreader hört „Fleet utilization overview, Showing 1 Jan 2026 to 26 Jan 2026“.

Für den häufigen Fall, dass Sie Ereignisse gewichten statt zählen wollen, nimmt eventDensity(control, { weight }) eine Funktion der Ereignisdaten entgegen (Stunden, Einheiten, Gäste). Das imperative Snippet weiter unten nutzt sie.

Farbtöne, Skalierung, Markierungen und Beschriftungen

OptionStandardWirkung
height28Höhe des Streifens in Pixeln; die Monatsinitialen brauchen 24 oder mehr
peak'relative''absolute' misst die Balken an max
max1Der Wert, der einen Balken füllt, bei peak: 'absolute'
tone(value, index)überall 'base''base', 'warn' oder 'danger' pro Bucket
marksalle truetoday (eine Markierung am aktuellen Datum des Browsers), months (Trennlinien und Initialen, im Januar die Jahreszahl), past (frühere Buckets verblasst)
rangedie ZeitleisteDer Zeitraum, den der Streifen abdeckt
labelsEnglisch oder Spanischlabel (der barrierefreie Name des Auswahlfensters), zoom (Hinweis zum Ändern der Größe) und valueText(start, end)

Die Standardbeschriftungen sind Englisch, oder Spanisch, wenn die locale des Planers mit es beginnt: „Visible period“, „Drag either edge to zoom, or use + and −“ und der sichtbare Zeitraum als yyyy-MM-dd – yyyy-MM-dd. Für jede andere Sprache, auch Deutsch, geben Sie labels an.

Die Farben kommen aus Tokens, die auf das Theme des Planers zurückfallen. Setzen Sie sie auf dem Container der Minimap oder einem beliebigen Vorfahren:

TokenFallback
--super-scheduler-minimap-baseder Akzent
--super-scheduler-minimap-warn#f59e0b
--super-scheduler-minimap-danger#ef4444
--super-scheduler-minimap-pastgedämpfter Text
--super-scheduler-minimap-todayder Akzent
--super-scheduler-minimap-monthsdie Rahmenfarbe
--super-scheduler-minimap-labelgedämpfter Text
--super-scheduler-minimap-brushder Akzent

Der Streifen wird neu gezeichnet, wenn sich das Theme ändert: bei einer Änderung von class, data-theme oder data-color-scheme an <html>, an der Wurzel des Planers oder am Container, oder wenn sich das Farbschema des Systems ändert.

Bedienung des Auswahlfensters

EingabeWirkung
Auswahlfenster ziehenVerschiebt die Zeitleiste
Einen der beiden Ränder ziehenZoomt: nach außen mehr Zeit, nach innen weniger; der gegenüberliegende Rand bleibt stehen, innerhalb von Minimum und Maximum aus zoomGesture
Außerhalb des Auswahlfensters auf den Streifen klickenScrollt so, dass dieses Datum in der Mitte steht (animiert, außer wenn reduzierte Bewegung aktiv ist)
Pfeil links / rechts, Pfeil unten / obenEinen Tag früher oder später; mit Umschalt sieben Tage
Bild auf / Bild abEinen Monat früher oder später
Pos1 / EndeAnfang oder Ende des Zeitraums
+ oder =, - oder −Um die Mitte herum hinein- oder herauszoomen

Das Auswahlfenster ist ein fokussierbarer role="slider" mit aria-valuetext; das Canvas ist vor assistiven Technologien verborgen. Bei cellWidthSpec: 'Auto' sind die Griffe an den Rändern und die Zoomtasten deaktiviert, weil der Planer ohnehin die ganze Zeitleiste einpasst. Zeigerbewegungen werden einmal pro Animationsframe angewendet.

Imperative API und Freigabe

createMinimap(control, container, options) erzeugt den Streifen in einem beliebigen Element und gibt { element, update, refresh, dispose } zurück. Die Funktion wirft einen Fehler, wenn das Control noch nicht initialisiert ist; erzeugen Sie den Streifen in React daher in einem Effect, der von control abhängt:

src/ProductionOverview.tsxtsx
import { useEffect, useMemo, useRef } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import { createMinimap, eventDensity } from 'super-scheduler/minimap'

type Order = { quantity: number }

export function ProductionOverview(props: {
  lines: SuperScheduler.ResourceData[]
  orders: SuperScheduler.EventData<Order>[]
}) {
  const { controlRef, control } = useSchedulerControl()
  const host = useRef<HTMLDivElement>(null)
  const events = useMemo(() => props.orders.slice(), [props.orders])

  useEffect(() => {
    // createMinimap needs an initialized control: run it after mount, keyed on the control.
    if (control === null || host.current === null) return
    const minimap = createMinimap(control, host.current, {
      height: 28,
      // Each order weighs its quantity instead of counting 1.
      series: eventDensity(control, {
        weight: (e) => (e as SuperScheduler.EventData<Order>).quantity,
      }),
      labels: { label: 'Production load overview' },
    })
    // Releases observers and pending work; control.dispose() does it too.
    return () => minimap.dispose()
  }, [control])

  return (
    <>
      <SuperSchedulerComponent
        controlRef={controlRef}
        startDate="2026-10-01"
        days={90}
        scale="Day"
        resources={props.lines}
        events={events}
      />
      <div ref={host} className="production-minimap" />
    </>
  )
}
  • update(partialOptions) ändert Optionen und zeichnet neu;
  • refresh() fordert die Serie erneut an;
  • dispose() entfernt den Streifen und gibt seine Observer, Listener und ausstehende Arbeit frei. Das Freigeben des Controls erledigt das ebenfalls.

SchedulerMinimap übernimmt all das für Sie: Die Komponente erzeugt den Streifen, sobald control verfügbar ist, erzeugt ihn neu, wenn sich das Control ändert, und gibt ihn beim Unmount frei.

Den Streifen aktuell halten

Die Minimap zeichnet neu und ruft eine series-Funktion erneut auf, wenn:

  • sich die Ereignisse des Controls ändern (ein Ziehen, ein API-Aufruf, ein Ladevorgang);
  • ein Zoom endet, der Container seine Größe ändert oder das Theme wechselt;
  • Sie refresh() oder update() aufrufen.

Solange eine Geste läuft, warten Neuzeichnungen; sie erfolgen, sobald die Geste endet. Eine series-Funktion, die die Ereignisse des Controls selbst liest, ist daher immer aktuell. Eine Serie, die aus den Daten Ihrer Anwendung berechnet wird, ist aktuell, wenn Sie nach jeder Änderung dieser Daten eine neue Funktion übergeben, wie es useMemo im Auslastungs-Snippet tut.

SchedulerMinimap ruft bei jedem Rendern der Elternkomponente update mit seinen Props auf. Memoisieren Sie series, tone und labels (oder definieren Sie sie auf Modulebene), damit ein erneutes Rendern die Serie nicht unnötig neu berechnet.

Was Ihre Anwendung verantwortet

  • Die Kennzahl. Was als Kapazität gilt, welche Ereignisse zählen (vorläufige, stornierte, Sperren) und wie sie gewichtet werden.
  • Daten, die Sie nicht geladen haben. Die Serie sieht nur, was Ihr Code ihr übergibt. Mit bereichsweisem Laden ist womöglich nur ein Teil des Jahres im Speicher: Für eine Jahresansicht holen Sie Tagesaggregate von Ihrem Backend und übergeben sie zusammen mit einem festen range als Serie.
  • Schwellenwerte und Formulierungen. Grenzen der Farbtöne, Beschriftungen und ihre Übersetzungen.

Planung einer MietwagenflotteEin Kleinwagen fällt am Abholtag aus. Die Miete auf ein anderes Auto legen, die Aufbereitung einhalten und sehen, wo die Flotte knapp wird. Planung von FertigungsaufträgenDie Wartung wurde vorgezogen. Verschieben Sie den Auftrag und halten Sie die Arbeitsfolge ein. Liegeplatzplanung im HafenEin Schiff kommt zwölf Stunden zu spät. Verschieben Sie sein Liegefenster und nehmen Sie Schlepper und Kräne mit. Zimmerplanung im HotelIn Zimmer 104 tropft die Dusche. Buchen Sie den nächsten Gast um, sperren Sie das Zimmer für den Installateur und finden Sie die ausgebuchten Nächte.