Linuchs: SVG: Linie mit Pfeilspitze versehen / Problem mit z-index

Moin,

ich habe Rechtecke, die per Pfeil verbunden werden sollen (Elter zu Kind), die SVG-line habe ich schon, aber wie bekomme ich Pfeile dran?

Habe hier SVG-Tutorial für Pfeilspitzen gefunden, aber spezialiert für polyline, bei line funktioniert es nicht (Pfeilspitze wird ignoriert):

// SVG Pfeilspitze
document.write( `
  <marker id="pf1"
    viewBox="0 0 10 10" refX="0" refY="5"
    markerUnits="strokeWidth"
    markerWidth="15" markerHeight="15"
    orient="auto">
    <path d="M 0,0 l 10,5 l -10,5 z" />
  </marker>
` );

...

document.write( "<line x1='"+xElter+"' y1='"+yElter+"' x2='"+xKind+"' y2='"+yKind+"' style='stroke:#f00;stroke-width:1;' marker-end='url(#pf1)' \/>\n" );

noch'n Problem:

Die SVG-Grafik muss später erzeugt werden als die Rechtecke, also liegt die Grafik darüber und die Links können nicht angeklickt werden.

position:absolute;z-index:-5; hilft nicht, SVG bleibt vorne.

Gruß, Linuchs

  1. Hi,

    ich habe Rechtecke, die per Pfeil verbunden werden sollen (Elter zu Kind), die SVG-line habe ich schon, aber wie bekomme ich Pfeile dran?

    Die SVG-Grafik muss später erzeugt werden als die Rechtecke,

    Hm. Die Rechtecke sind nicht Bestandteil des svg? Wenn ja, warum nicht?

    also liegt die Grafik darüber und die Links können nicht angeklickt werden.

    Der Link an sich kann kein Grund sein, daß die Rechtecke nicht Teil der Graphik sind - svg kann auch Links.

    cu,
    Andreas a/k/a MudGuard

    1. Hallo Andreas,

      Hm. Die Rechtecke sind nicht Bestandteil des svg? Wenn ja, warum nicht?

      Ich nutze die Möglichkeit, HTML-Objekte (fieldset) mit Pfeilen zu verbinden. Die Objekte haben eine id, damit bestimme ich die Positionen, die in der SVG-Grafik mit Pfeil (bisher nur line) verbunden werden sollen.

      Ist das ungewöhnlich?

      Mit diesen Angaben erzeuge und positioniere (margin-left abhängig vom geb_datum) ich per JS die Rechtecke mit Personendaten:

      <script>
      var csv_string  = `lfd;personen_id;geb_name;vor_name;beruf;geb_datum;geb_ort;sterbe_datum;gestorben;sterbe_ort;alter;kind_id;geschlecht
      ;3;Sohn;Sohn;Beruf;1948-12-15;Bremen;;;;72;;;
      - ;9;Vater;Vater;Beruf;1921-05-26;Bremen;1973-01-01;;Bremen;51;3;m;
      - - ;15;Opa;Opa;Beruf;1887-10-24;Moorhausen;1959-01-01;;Bremen;71;9;m;
      ...
      

      Der Link an sich kann kein Grund sein, daß die Rechtecke nicht Teil der Graphik sind - svg kann auch Links.

      SVG soll nur die Verbindungen zeichnen, die Links sind (verdeckt von SVG) in den HTML-fieldsets.

      Gruß, Linuchs

  2. Hallo Linuchs,

    ein Tutorial dazu haben wir auch.

    Grundsätzlich funktioniert das so, wie Du es machst. Ich habe ein Fiddle dazu gebaut. Wenn es bei Dir nicht funktioniert, ist noch was anderes faul, was man an deinem Posting nicht ablesen kann. Hast Du eine Demo-Seite damit online?

    Die Nummer mit vorne/hinten und z-index, müsste ich mir live anschauen. Mit Stapelkontexten kann man schon mal durcheinander kommen.

    Ob document.write der richtige Weg ist, darüber kann man streiten. Ich würd's nicht tun, sondern per Script die Elemente per API erzeugen und dann passend ins SVG einhängen.

    Das geht mit document.createElementNS("http://www.w3.org/2000/svg", "line") - die Normalvariante ohne NS würde ein HTMLUnknownElement erzeugen. Aber mit Namespace bekommst Du ein SVGLineElement (Wiki-Artikel).

    Stroke und Stroke-Width sind übrigens eigene Attribute des line Elements, die kannst Du, musst Du aber nicht per CSS definieren. Und wenn per CSS, dann am Besten über's Stylesheet. Gib der line eine Klasse, z.B. Arrow, und schreibe ins Stylesheet

    line.arrow {
      stroke: black;
      stroke-width: 2;
      marker-end: url(#pf1);
    }
    

    Dann brauchst Du im DOM nur noch <line class="arrow" x1="..." y1="..." x2="..." y2="..." />

    Links kann man übrigens auch als ForeignObject ins SVG einbauen:

    <svg>
    <foreignObject class="link" x="200" y="100">
      <a href="...">Klick</a>
    </foreignObject>
    </svg>
    
    foreignObject.link {
       width: 1px; height: 1px;
       overflow: visible;
    }
    foreignObject.link a {
      padding: 0.5em 1em;    /* Beispielsweise */
      border: 1px solid red;
    }
    

    Das hat den Vorteil, dass die Koordinaten des a-Elements basierend auf der Viewbox des SVG bestimmt werden. Es hat den Nachteil, dass seine Größe ebenfalls auf dieser Basis bestimmt wird, d.h. wenn das SVG sich vergrößert oder verkleinert, wächst oder schrumpft das a Element mit.

    Die Größe des foreignObject auf 1x1 zu setzen und mit overflow:visible den Inhalt einfach hinaushängen zu lassen, ist ein Trick, den ich bei StackOverflow gefunden habe. Eine automatische Größenfestlegung des foreignObject basierend auf dem Inhalt gibt's nicht, und wenn Du es einfach "auf Vorrat" zu groß machst, dann könnte es andere Links überlappen.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      danke für dein fiddle.

      Hast Du eine Demo-Seite damit online?

      Nein, es sind echte Daten, die Eingabe - also Verknüpfung von Eltern und Kinder im Stammbaum - war kompliziert genug, zumal Vornamen nicht immer eindeutig sind. Als Johannes getauft und als Hans auf dem Grabstein.

      Gibt es hier persönliche Nachrichten?

      und wenn Du es einfach "auf Vorrat" zu groß machst, dann könnte es andere Links überlappen.

      Jede Person hat einen eigenen waagerechten Bereich, der margin-left hängt vom Geburtsjahr ab, with entspricht den Lebensjahren. Da würden sich sogar noch Urgroßeltern und ihre Urenkel überlappen.

      Den fiddle studiere ich gerade wg. der Pfeilspitzen.

      Gruß, Linuchs

      1. Hallo Linuchs,

        Gibt es hier persönliche Nachrichten?

        Sicher.

        (1) Klicke unter einem Beitrag von mir "Nachricht an den Autor"

        oder

        (2) Klicke rechts oben auf "Post" und erstelle eine neue Nachricht.

        Rolf

        --
        sumpsi - posui - obstruxi
      2. Hallo Rolf,

        prima, die Pfeilspitze ist dran an der Linie. Rot kommt von der Mutter, blau vom Vater:

        Aber sie schießt übers Ziel hinaus, die Länge der Linie soll inklusive Pfeilspitze sein. Da muss wohl noch was abgezogen werden. Habe leider keinen Schimmer, an welcher Schraube ich drehen muss, aber vermutlich hier irgendwo?

        <marker id="pf1" viewbox="0 0 2 2"
                  refX="0" refY="1"
                  markerUnits="userSpaceOnUse"
                  markerWidth="15" markerHeight="15"
                  orient="auto">
                  <path d="M 0,0 l 2,1 l -2,1 z" />
        </marker>
        
        1. Hallo Linuchs,

          das liegt am refX/refY. Das SVG zeichnet einen Linienzug von 0,0 (links oben) über 2,1 (rechts mitte) nach 0,2 (links unten) und schließt ihn dann wieder (z Kommando).

          Der Referenzpunkt liegt bei 0,1, also in der Mitte der Schließlinie. Die Pfeilspitze ist aber rechts in der Mitte, bei 2,1. Setze den Referenzpunkt entsprechend.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. Habe zwar nichts verstanden, aber durch Try and Error die Pfeilspitze wie gewünscht auf die Linie bekommen.

            Insbesondere sind mir die unbenannten Maßeinheiten ein Rätsel. Pixel scheinen das nicht zu sein?

            1. Hallo Linuchs,

              nein, das sind SVG-Einheiten. Wie groß eine SVG Einheit ist, wird durch die Viewbox festgelegt. Die Werte bei SVG-Einheiten sind floats.

              <svg viewbox="0 0 500 500">
                 <line x1="50" y1="50"  x1="450" y1="450"></line>
              </svg>
              

              zeichnet eine Diagonale von Punkt (50,50) nach (450,450) im Koordinatensystem des SVG-Elements. Wie groß das auf dem Bildschirm ist, hängt davon ab, welche width und height Du dem SVG-Element gibst. Wenn Du width und height Attribute des SVG-Elements nimmst, sind das Pixel. Wenn Du es mit CSS machst, kannst Du beliebige CSS Längeneinheiten verwenden.

              Falls das Seitenverhältnis der Größen von Viewbox und SVG-Element nicht übereinstimmt, hast Du noch das preserveAspectRatio Attribut, um festzulegen, wie sich die Viewbox in die Elementbox einpassen soll.

              Der Marker hat eine eigene Viewbox, in deinem Fall war das "0 0 10 10", und ich hatte das auf "0 0 2 2" herunterskaliert. Dann sieht das so aus:

              SVG Dreieck

              Im Path-Befehl heißt ein Großbuchstabe "absolute Koordinaten" und ein Kleinbuchstabe "relative Koordinaten". Die Koordinatenwerte bewegen sich jetzt in der Viewbox des Markers. M0,0 bedeutet also: Geh nach (0,0), ohne zu zeichnen. l2,1 zeichnet eine Linie zu dem Punkt, der 2 rechts und 1 tiefer liegt, also (2,1). Dann kommt l-2,1, das geht zu dem Punkt, der 2 links und 1 tiefer liegt, also (0,2). Und zum Schluss z um den Pfad zu schließen, das geht demnach nach (0,0) zurück. Statt mit relativen Koordinaten hätte man auch mit absoluten Werten zeichnen können: "M0,0 L2,1 L0,2 Z". Die Attribute refX und refY bezeichnen den Punkt im Bild, der mit dem Endpunkt der Linie zusammenfallen soll, an die der Marker gehängt wird. Deswegen refX="2" und refY="1", weil da die Pfeilspitze ist.

              Wie groß die Viewbox des Markers nun tatsächlich im SVG ist, hängt an den marker...-Attributen des marker-Elements. Mit markerUnits="userSpaceOnUse" legst Du fest, dass der Marker die angegebene Breite und Höhe in Viewbox-Einheiten des SVG-Elements hat[1]. Die Alternative ist "strokeWidth". Bei einer stroke-width von 1 ist das Ergebnis unverändert, aber wenn du die stroke-width der Linie auf 2 erhöhst, führt markerUnits="strokeWidth" dazu, dass sich die Pfeilspitze proportional vergrößert, also 30x30 groß ist.

              Alle Klarheiten beseitigt?

              Rolf

              --
              sumpsi - posui - obstruxi

              1. Es mag noch weitere Schachtelungen von Viewboxen geben, die das verkomplizieren, das schau ich jetzt nicht nach ↩︎

  3. Das svg-Element hatte versehentlich noch ein z-index:3. Mit z-index:-1 liegt es wie gewünscht hinter den HTML-Objekten.