# Abhängigkeiten und Verknüpfungen zwischen Ereignissen

> Ende-Anfang- und andere Verknüpfungen zwischen Ereignissen aus Ihren Daten zeichnen, gestalten, auf Klicks reagieren und Abhängigkeiten im eigenen Code durchsetzen.

Source: https://superscheduler.org/de/docs/links-dependencies/
Reviewed: 2026-10-07

Übergeben Sie links als Array von Objekten { id, from, to, type }, wobei from und to Ereignis-IDs sind; FinishToStart ist der Standardtyp, und gekrümmte Linien sind die Standarddarstellung. Gestalten Sie Verknüpfungen mit color, width, style und cssClass oder den Tokens --super-scheduler-link, und behandeln Sie Klicks mit onLinkClick. Die Bibliothek zeichnet Verknüpfungen, setzt sie aber nicht durch: Prüfen Sie Abhängigkeiten in onEventMoving und onEventMove. Verknüpfungen durch Ziehen zwischen Ereignissen anzulegen ist reserviert und in 0.1.0 nicht implementiert.

Fertigungsschritte, Drehs und Kampagnen hängen voneinander ab: Der Zuschnitt beginnt, wenn das Design fertig ist, zwei Teams starten gemeinsam. SuperScheduler zeichnet diese Abhängigkeiten als Verknüpfungen zwischen Ereignissen und hält sie verbunden, während Nutzer scrollen, zoomen und ziehen. Was eine Abhängigkeit für die Planung bedeutet (ob eine Verschiebung erlaubt ist, ob Nachfolger mitwandern), bleibt in Ihrem Code, dort, wo Ihre Regeln leben.

Verknüpfungen erfordern SuperScheduler Pro.

## Verknüpfungen aus Daten zeichnen
Eine Verknüpfung ist ein einfaches Objekt in der Prop `links`. `from` und `to` sind Ereignis-IDs (die `id` von `EventData`); `id` identifiziert die Verknüpfung selbst.

```ts
// src/links.ts
import type { SuperScheduler } from 'super-scheduler'

// `from` and `to` are event ids. Links between ids that are not loaded are not drawn.
export const LINKS: SuperScheduler.LinkData[] = [
  // Cutting can start once design has finished (the default type).
  { id: 'design-cut', from: 'design', to: 'cutting', type: 'FinishToStart' },
  // Painting and drying start together.
  { id: 'paint-dry', from: 'painting', to: 'drying', type: 'StartToStart', style: 'dashed' },
  // Inspection and packing finish together; drawn below the events, in a fixed color.
  {
    id: 'qa-pack',
    from: 'inspection',
    to: 'packing',
    type: 'FinishToFinish',
    color: '#7c3aed',
    layer: 'Below',
  },
  // A heavier line, styled further by CSS through its class.
  { id: 'ship', from: 'packing', to: 'shipping', width: 3, cssClass: 'link--critical' },
]

export const LINK_OPTIONS = {
  linkShape: 'Curved', // default; 'RightAngled' draws segments
  linkBottomMargin: 12, // clearance for links that run below events (8 px minimum for curves)
  linkLayer: 'Above', // default layer of links without their own `layer`
  links: LINKS,
} satisfies SuperScheduler.SchedulerConfig
```
Sie sollten vier Linien sehen: eine durchgezogene Kurve vom Design zum Zuschnitt, eine gestrichelte zwischen Lackieren und Trocknen, eine violette unter den Ereignissen Prüfung und Verpackung und eine dickere in den Versand.

Verknüpfungen kommen aus Ihrem Datenmodell: einer Spalte `dependsOn`, einer Tabelle mit Abhängigkeiten oder einer Regel wie „gleicher Auftrag, nächster Schritt“. Leiten Sie das Array mit `useMemo` ab, damit sich seine Identität nur ändert, wenn sich die Daten ändern. Im Code bietet `control.links` die Methoden `list`, `add(link)`, `update(link)`, `remove(link or id)`, `find(id)`, `findByFromTo(from, to)` und `findAllByFromTo(from, to)`; mit React-State aktualisieren Sie besser Ihr Array.

## Verknüpfungstypen
| `type` | Gezeichnet ab | Bis | Typische Bedeutung |
|---|---|---|---|
| `'FinishToStart'` (Standard) | dem Ende von `from` | zum Anfang von `to` | `to` beginnt, nachdem `from` beendet ist |
| `'StartToStart'` | dem Anfang von `from` | zum Anfang von `to` | beide beginnen gemeinsam, oder `to` nach dem Beginn von `from` |
| `'FinishToFinish'` | dem Ende von `from` | zum Ende von `to` | beide enden gemeinsam |
| `'StartToFinish'` | dem Anfang von `from` | zum Ende von `to` | `to` endet, wenn `from` beginnt |

Der Typ ändert, wo die Linie ansetzt und wie sie gezeichnet wird. Er ändert nicht, was Nutzer tun dürfen; siehe [Abhängigkeiten durchsetzen](#validation).

## Form und Linienführung
| Option | Standard | Wirkung |
|---|---|---|
| `linkShape` | `'Curved'` | Weiche Kurven mit einem kleinen Ring am Ursprung und einer Pfeilspitze. `'RightAngled'` zeichnet gerade Segmente. |
| `linkBottomMargin` | `17` | Abstand für Verknüpfungen, die unter Ereignissen verlaufen (bei Kurven mindestens 8 px) |
| `linkLayer` | `'Above'` | Standardebene für Verknüpfungen ohne eigenes `layer`: über oder unter den Ereignissen |
| `linkWidth` | `'Auto'` | Standardstrichstärke in Pixeln (Kurven haben 2 px) |

Verknüpfungen zwischen Enden auf derselben Seite wölben sich mit begrenzter Ausladung nach außen, sodass Verknüpfungen zu weit entfernten Zeilen keine übergroßen Schleifen bilden. Rückwärtsgerichtete Verknüpfungen nutzen den Zwischenraum zwischen den Zeilen und verlaufen unter beiden Ereignissen, wenn sich diese Zeilen überlappen. Die Linienführung weicht den beiden verknüpften Ereignissen aus, nicht jedem Ereignis dazwischen: Es handelt sich nicht um ein Routing mit Hindernisvermeidung.

## Verknüpfungen gestalten
Pro Verknüpfung ändern `color`, `width`, `style` (`'solid'`, `'dashed'`, `'dotted'`), `layer` und `cssClass` eine einzelne Verknüpfung; ein explizites `width` hat Vorrang vor dem CSS-Token für die Breite. Für alles Übrige verwenden Sie Tokens auf einem beliebigen Vorfahren oder auf einer Verknüpfungsklasse:

```css
.planning {
  --super-scheduler-link: #8b5cf6;
  --super-scheduler-link-hover: #6d28d9;
  --super-scheduler-link-width: 2;
}
.dark .planning {
  --super-scheduler-link: #b89afa;
  --super-scheduler-link-hover: #d8c6ff;
}
/* A class from your data or from onBeforeLinkRender */
.link--late {
  --super-scheduler-link: var(--super-scheduler-danger);
}
```

Verknüpfungen sind SVG-Elemente mit der Klasse `super-scheduler__link` und `data-super-scheduler-part="link"`; Pfeil und Ursprungsring übernehmen die Farbe der Verknüpfung.

Um abhängig vom Zustand der verknüpften Ereignisse zu gestalten, verwenden Sie `onBeforeLinkRender`. Der Hook erhält eine Kopie der Verknüpfung als `args.data` sowie die beiden Ereignisse als `args.from` und `args.to`. Ändern Sie `color`, `width`, `style`, `cssClass`, `layer` oder `type` an der Kopie; die Enden lassen sich nicht ändern. Der Hook läuft einmal pro gezeichneter Verknüpfung und erneut nur dann, wenn sich das Aussehen eines ihrer Ereignisse ändert.

## Wann Verknüpfungen gezeichnet werden
Eine Verknüpfung wird gezeichnet, wenn beide Ereignisse geladen sind und im selben Abschnitt des Rasters liegen (in den scrollenden Zeilen oder in denselben fixierten Zeilen oben oder unten). Sie wird nicht gezeichnet, wenn:

- eine der beiden IDs nicht geladen ist, zum Beispiel außerhalb des abgerufenen Zeitraums;
- ein Ende durch einen Filter ausgeblendet ist oder in einer zugeklappten Baumzeile liegt;
- die Zoomstufe ein Ende als schmalen Übersichtsbalken zeichnet;
- sie vollständig außerhalb des gerenderten Bereichs liegt (sie erscheint wieder, sobald sie ins Bild gescrollt wird).

Während ein Ereignis gezogen wird, folgen seine Verknüpfungen der Vorschau mit derselben Linienführung.

## Klick, Hover und Menüs
- `onLinkClick` läuft bei einem Klick, mit `args.link` (ein `SuperScheduler.Link`: `data`, `id()`, `from()`, `to()`, `type()`) und `args.originalEvent`; `args.preventDefault()` überspringt `onLinkClicked`, das danach läuft.
- `linkBubble` zeigt beim Überfahren mit dem Zeiger das `bubbleHtml` einer Verknüpfung.
- `contextMenuLink` oder das eigene `contextMenu` einer Verknüpfung öffnet sich per Rechtsklick.

Verknüpfungen nehmen Zeigereingaben nur an, wenn eine dieser Optionen konfiguriert ist; standardmäßig kommen sie dem Ziehen von Ereignissen also nie in die Quere. Ihre unsichtbare Trefferfläche ist breiter als die sichtbare Linie.

## Abhängigkeiten in Ihrer Anwendung durchsetzen
Verknüpfungen sind Zeichnungen. Nichts hindert einen Nutzer daran, den Zuschnitt vor das Ende des Designs zu verschieben, solange Ihr Code das nicht prüft. Schreiben Sie die Regel einmal als einfache Funktion:

```ts
// src/finishToStart.ts
import { SuperScheduler } from 'super-scheduler'

export interface Bounds {
  /** Ticks of the civil wall clock, as SuperScheduler.Date.getTime() returns them. */
  readonly start: number
  /** Exclusive end. */
  readonly end: number
}

export const boundsOf = (event: SuperScheduler.EventData): Bounds => ({
  start: new SuperScheduler.Date(event.start).getTime(),
  end: new SuperScheduler.Date(event.end).getTime(),
})

/**
 * Finish-to-start rule: a task may not start before each of its predecessors ends.
 * Returns the first link that `proposed` times for `taskId` would break, or null.
 * Other link types need rules of their own; links only draw what the data says.
 */
export function brokenFinishToStart(
  taskId: SuperScheduler.EventId,
  proposed: Bounds,
  links: readonly SuperScheduler.LinkData[],
  find: (id: SuperScheduler.EventId) => Bounds | null,
): SuperScheduler.LinkData | null {
  for (const link of links) {
    if ((link.type ?? 'FinishToStart') !== 'FinishToStart') continue
    if (link.to === taskId) {
      const predecessor = find(link.from)
      if (predecessor !== null && proposed.start < predecessor.end) return link
    } else if (link.from === taskId) {
      const successor = find(link.to)
      if (successor !== null && successor.start < proposed.end) return link
    }
  }
  return null
}

/** Ids of the links the current data already breaks, to draw them differently. */
export function lateLinks(
  links: readonly SuperScheduler.LinkData[],
  events: readonly SuperScheduler.EventData[],
): ReadonlySet<SuperScheduler.LinkData> {
  const byId = new Map(events.map((event) => [event.id, boundsOf(event)]))
  const late = new Set<SuperScheduler.LinkData>()
  for (const link of links) {
    const from = byId.get(link.from)
    const to = byId.get(link.to)
    if ((link.type ?? 'FinishToStart') === 'FinishToStart' && from && to && to.start < from.end)
      late.add(link)
  }
  return late
}
```
Rufen Sie sie dann aus den Hooks auf, die während und vor einer Änderung laufen:

```tsx
// src/ProductionPlan.tsx
import { useCallback, useMemo, useState } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerEventsChangeArgs, SchedulerProps, SuperScheduler } from 'super-scheduler'
import { boundsOf, brokenFinishToStart, lateLinks } from './finish-to-start'
import type { Bounds } from './finish-to-start'

export function ProductionPlan(props: {
  resources: SuperScheduler.ResourceData[]
  initialTasks: SuperScheduler.EventData[]
  /** Dependencies come from your data: the library draws them, it does not invent them. */
  initialLinks: SuperScheduler.LinkData[]
}) {
  const [tasks, setTasks] = useState(props.initialTasks)
  const [links, setLinks] = useState(props.initialLinks)
  const owned = useMemo(() => tasks.slice(), [tasks])

  // Links the data already breaks get a class; the color comes from CSS.
  const drawnLinks = useMemo(() => {
    const late = lateLinks(links, tasks)
    return links.map((link) => (late.has(link) ? { ...link, cssClass: 'link--late' } : link))
  }, [links, tasks])

  const onEventsChange = useCallback(
    (args: SchedulerEventsChangeArgs) => setTasks([...args.events]),
    [],
  )

  const config = useMemo<SchedulerProps>(() => {
    // The other tasks as the control holds them right now.
    const finder =
      (control: SuperScheduler.SchedulerApi) =>
      (id: SuperScheduler.EventId): Bounds | null => {
        const event = control.events.find(id)
        return event === null ? null : boundsOf(event.data)
      }
    return {
      linkShape: 'Curved',
      // While dragging: refuse and explain, frame by frame.
      onEventMoving(args) {
        const proposed = { start: args.start.getTime(), end: args.end.getTime() }
        const broken = brokenFinishToStart(args.e.id(), proposed, links, finder(this))
        if (broken !== null) {
          args.allowed = false
          args.message = 'This breaks a finish-to-start dependency'
        }
      },
      onEventResizing(args) {
        const proposed = { start: args.start.getTime(), end: args.end.getTime() }
        if (brokenFinishToStart(args.e.id(), proposed, links, finder(this)) !== null)
          args.allowed = false
      },
      // Before the commit: also covers keyboard moves.
      onEventMove(args) {
        const proposed = { start: args.newStart.getTime(), end: args.newEnd.getTime() }
        if (brokenFinishToStart(args.e.id(), proposed, links, finder(this)) !== null)
          args.preventDefault()
      },
      // Clicking a link offers to remove the dependency.
      onLinkClick(args) {
        const link = args.link.data
        void confirmWithUser('Remove this dependency?').then((ok) => {
          if (ok) setLinks((current) => current.filter((item) => item.id !== link.id))
        })
      },
    }
  }, [links])

  return (
    <SuperSchedulerComponent
      {...config}
      events={owned}
      links={drawnLinks}
      onEventsChange={onEventsChange}
      resources={props.resources}
      startDate="2026-10-05"
      days={14}
      scale="Day"
    />
  )
}
```
Sie sollten Folgendes sehen:

- Wenn Sie einen Schritt vor das Ende seines Vorgängers ziehen, zeigt die Ziehkarte „Not allowed · This breaks a finish-to-start dependency“, und das Ablegen wird abgelehnt;
- Verknüpfungen, die Ihre Daten bereits verletzen, erscheinen in der Gefahrenfarbe;
- ein Klick auf eine Verknüpfung fragt, ob diese Abhängigkeit entfernt werden soll.

`onEventMoving` und `onEventResizing` laufen bei jedem Frame eines Ziehvorgangs; halten Sie die Prüfung daher günstig: Schlagen Sie nur die Verknüpfungen des gezogenen Ereignisses nach, wie es `brokenFinishToStart` tut. `onEventMove` läuft einmal vor dem Übernehmen der Änderung und deckt auch Verschiebungen per Tastatur und von außen abgelegte Ereignisse ab.

> **Tip:**
> Automatisches Umplanen (Nachfolger nach hinten schieben, wenn ein Vorgänger länger wird) ist ebenfalls Anwendungslogik. Berechnen Sie in `onEventMoved` oder `onEventResized` die neuen Zeiten der Nachfolger, aktualisieren Sie Ihren Zustand und fassen Sie die gesamte Änderung in einem [Verlaufsbündel](https://superscheduler.org/de/docs/undo-redo/#commands) zusammen, damit ein einziges Rückgängig sie zurücknimmt. Kritische Pfade, Verzögerungen (Lags) und Kalender für Abhängigkeiten sind nicht Teil der Bibliothek.

## Reserviert: interaktives Anlegen von Verknüpfungen
> **Limitation:**
> Verknüpfungen durch Ziehen von einem Ereignis zu einem anderen anzulegen ist in 0.1.0 nicht implementiert. `linkCreateHandling`, `onLinkCreate`, `onLinkCreated`, `linkDotSize` und `linkPointSize` sind aus Kompatibilitätsgründen typisiert, warnen in der Entwicklung einmal und bewirken nichts; `linksLoadMethod` und `control.links.load()` laden nicht von einer URL. Die Felder `text` und `textAlignment` einer Verknüpfung werden akzeptiert, aber es wird keine Beschriftung gezeichnet.

Damit Nutzer schon heute Abhängigkeiten anlegen können, bieten Sie das in Ihrer eigenen Oberfläche an (zum Beispiel „Abhängigkeit hinzufügen“ im Detailbereich oder Kontextmenü eines Ereignisses, wobei das andere Ereignis aus einer Liste oder der aktuellen Auswahl gewählt wird) und hängen die Verknüpfung an Ihr Array an.

## Verwandte Themen
→ https://superscheduler.org/de/examples/manufacturing-orders/
→ https://superscheduler.org/de/examples/video-production/
→ https://superscheduler.org/de/examples/port-berths/
- [Ziehen, Dauer ändern und Geschäftsregeln](https://superscheduler.org/de/docs/drag-resize-rules/) zu `onEventMoving`, `onEventMove` und Ablehnungsmeldungen.
- [Rückgängig, Wiederholen und Verlauf](https://superscheduler.org/de/docs/undo-redo/), um Umplanungen rückgängig machen zu können.
