svgling diagram gallery

Author

Kyle Rawlins

Published

November 11, 2023

Code
import svgling
from svgling import draw_tree
from svgling.figure import Caption, SideBySide, RowByRow
from svgling.core import subscript_node

This gallery provides several examples of relatively complex SVG tree diagrams rendering using the svgling package. To begin with, here is an example from Carnie (2012) that illustrates multiline nodes paired with aligned leaf nodes.

Carnie, Andrew. 2012. Syntax: A Generative Introduction, 3rd Edition. Wiley.
Code
Caption(draw_tree("TP", ("NP", "D\nThe", ("AdjP", ("AdvP", "Adv\nvery"), "Adj\nsmall"), "N\nboy"), ("VP", "V\nkissed", ("NP", "D\nthe", "N\nplatypus")),
                  leaf_nodes_align=True),
        "Tree from Carnie 2012, p. 93")

The following example illustrates Quantifier Raising (QR) in the style of Heim and Kratzer (1998), involving movement arrows, subfigures, and subtree highlighting.

Heim, Irene, and Angelika Kratzer. 1998. Semantics in Generative Grammar. Blackwell.
Code
qrtree0 = ("TP", ("DP", ("D", "every"), ("NP", ("N", "cat"))),
                                   ("VP", ("V", "likes"), ("DP", ("D", "some"), ("NP", ("N", "dog")))))
out0 = Caption(draw_tree(qrtree0), "LF input (= Surface Structure)")

qrtree1 = ("TP", (subscript_node("DP", "1"), ("D", "every"), ("NP", ("N", "cat"))),
                                   ("VP", ("V", "likes"), (subscript_node("DP", "3"), ("D", "some"), ("NP", ("N", "dog")))))
out1 = draw_tree(qrtree1)
out1.box_constituent((0,))
out1.box_constituent((1,1))
out1 = Caption(out1, "Step 1: free indexing (1 of 2 indexings)")

qrtree2 = ("TP", ("DP", ("D", "some"), ("NP", ("N", "dog"))),
                      ("TP", "3", ("TP", (subscript_node("DP", "1"), ("D", "every"), ("NP", ("N", "cat"))),
                                   ("VP", ("V", "likes"), ("DP", subscript_node("t", "3"))))))
out2 = draw_tree(qrtree2)
out2.movement_arrow((1,1,1,1), (0,))
out2.box_constituent((0,))
out2.box_constituent((1,1,0))
out2 = Caption(out2, "Step 2: QR an indexed DP (choosing the object)")

qrtree3 = ("TP", ("DP", ("D", "every"), ("NP", ("N", "cat"))),
           ("TP", "1", ("TP", ("DP", ("D", "some"), ("NP", ("N", "dog"))),
                     ("TP", "3", ("TP", ("DP", subscript_node("t", "1")), ("VP", ("V", "likes"), ("DP", subscript_node("t", "3"))))))))
out3 = draw_tree(qrtree3)
out3.movement_arrow((1,1,1,1,0), (0,))
out3.box_constituent((0,))
out3.movement_arrow((1,1,1,1,1,1), (1,1,0))
out3.box_constituent((1,1,0))
out3 = Caption(out3, "Step 3: QR an indexed DP (choosing the subject).")

Caption(RowByRow(SideBySide(out0, out1), SideBySide(out2,out3)), "Trees illustrating a QR (Quantifier Raising) derivation in the Heim & Kratzer 1998 style")

The following example, based on a tree from McCloskey (2000), demonstrates multi-headed movement arrows.

McCloskey, James. 2000. “Quantifier Float and Wh-Movement in Irish English.” Linguistic Inquiry 31: 57–84. https://doi.org/10.1162/002438900554299.
Code
mccloskey = ("FP", ("F", ("V", "put")),
             ("AgrOP", (subscript_node("DP", "Obj"), "milk"),
              ("", ("AgrO", subscript_node("t", "V")),
               ("VP", subscript_node("t", "Subj"), ("", subscript_node("t", "V"),
                                  ("VP", ("PP", "in it"), ("", ("V", subscript_node("t", "V")), subscript_node("t", "Obj"))))))))

# TODO: AgrOP should be set with a subscript O, currently not supported.
out = draw_tree(mccloskey)
out.set_edge_style((1,0,0), svgling.core.TriangleEdge())
out.set_edge_style((1,1,1,1,1,0,0), svgling.core.TriangleEdge())
out.movement_arrow((1,1,1,1,1,1,0,0), (1,1,0,0))
out.movement_arrow((1,1,0,0), (0,0,0))
out.movement_arrow((1,1,1,1,1,1,1), (1,0))
Caption(out, "Tree after ex. 58 of McCloskey (2000)")

The following example, which is based on a PCFG example from the classic Hale (2001) account of garden path sentences, illustrates how to use a custom tree parsing function together with a non-default node renderer. An nltk probabilistic parser returns a subclass of tree that uses label() normally, but also has a probability value indicating the “inside probability” of the subtree given its constituents. There’s a provided function svgling.core.ptree_split that renders such trees in an ugly but functional way; this example improves on that a bit by using subscripts.

Hale, John. 2001. “A Probabilistic Earley Parser as a Psycholinguistic Model.” In Proceedings of the Second Meeting of the North American Chapter of the Association for Computational Linguistics on Language Technologies, 1–8. NAACL ’01. USA: Association for Computational Linguistics. https://doi.org/10.3115/1073336.1073357.

The example constructs a nltk.grammar.PCFG object based on an example in Hale (2001), parses a sentence using that PCFG, and draws the resulting tree incorporating inside probabilities into the node labels.

Code
import nltk, nltk.parse
from nltk.grammar import PCFG

# note: this is a binarized version of an example grammar due to Hale in the cited paper.
hale1 = PCFG.fromstring("""
    S0 -> S '.'     [1.0]
    S -> NP VP     [1.0]
    NP -> DT NN    [0.88]
    NP -> NP VP    [0.12]
    PP -> IN NP    [1.0]
    VP -> VBD PP   [0.17]
    VP -> VBN PP   [0.75]
    VP -> VBD      [0.08]
    DT -> 'the'    [1.0]
    NN -> 'horse'  [0.5]
    NN -> 'barn'   [0.5]
    VBD -> 'fell'  [0.5]
    VBD -> 'raced' [0.5]
    VBN -> 'raced' [1.0]
    IN -> 'past'   [1.0]
    """)
# parse "The horse raced past the barn fell." using one of nltk's chart parsers:
hale1_parser = nltk.parse.pchart.InsideChartParser(hale1)
parses = list(hale1_parser.parse("the horse raced past the barn fell .".split()))

def ptree_split2(t):
    try:
        return (svgling.core.subscript_node(f"{t.label()}", f"p={t.prob()}", scale=0.85), list(t))
    except AttributeError:
        # indicate that this function doesn't handle `t`. (Leaf nodes of this tree
        # class are `str` -- this leaves them to the default node parser.)
        return None

svgling.draw_tree(parses[0], tree_split=ptree_split2)

Hybrid svg/html diagram examples

The following tree is a typical example of how compositional semantics might be integrated into a tree structure in formal semantics. This tree is not pure SVG (which doesn’t support latex code), but is rendered using svgling.html.

Code
import svgling.html
svgling.html.compat(svgling.html.Compat.USE_MARKDOWN) # needed for quarto
from svgling.html import multiline_text as ml

def math(s):
    # note: we are in markdown mode, so using delimiters with backslashes becomes harder...
    return f"${s}$"

svgling.html.draw_tree(
    ml(math(r"\text{Saw}(\iota x_e{:\:}\text{Elephant}(x),\iota x_e{:\:}\text{Rhino}(x))"), math(r"\text{Type: }t")),
    (ml(math(r"\iota x_e{:\:}\text{Elephant}(x)"), math(r"\text{Type: }e")),
         ml(math(r"\lambda f_{\langle e,t \rangle }{:\:}\iota x_e{:\:}f(x)"),
            math(r"\text{Type: }\langle \langle e,t\rangle ,e\rangle")),
         ml(math(r"\lambda x_e{:\:}\text{Elephant}(x)"), math(r"\text{Type: }\langle e,t\rangle"))),
    (ml(math(r"\lambda x_e{:\:}\text{Saw}(x,\iota x_e{:\:}\text{Rhino}(x))"), math(r"\text{Type: }\langle e,t\rangle")),
         ml(math(r"\lambda y_e{:\:}\lambda x_e{:\:}\text{Saw}(x,y)"),
            math(r"\text{Type: }\langle e,\langle e,t\rangle\rangle")),
         (ml(math(r"\iota x_e{:\:}\text{Rhino}(x)"), math(r"\text{Type: }e")),
              ml(math(r"\lambda f_{\langle e,t \rangle }{:\:}\iota x_e{:\:}f(x)"),
                 math(r"\text{Type: }\langle \langle e,t\rangle ,e\rangle")),
              ml(math(r"\lambda x_e{:\:}\text{Rhino}(x)"), math(r"\text{Type: }\langle e,t\rangle")))))
\(\text{Saw}(\iota x_e{:\:}\text{Elephant}(x),\iota x_e{:\:}\text{Rhino}(x))\)
\(\text{Type: }t\)
\(\text{Saw}(\iota x_e{:\:}\text{Elephant}(x),\iota x_e{:\:}\text{Rhino}(x))\)
\(\text{Type: }t\)
\(\iota x_e{:\:}\text{Elephant}(x)\)
\(\text{Type: }e\)
\(\iota x_e{:\:}\text{Elephant}(x)\)
\(\text{Type: }e\)
\(\lambda f_{\langle e,t \rangle }{:\:}\iota x_e{:\:}f(x)\)
\(\text{Type: }\langle \langle e,t\rangle ,e\rangle\)
\(\lambda x_e{:\:}\text{Elephant}(x)\)
\(\text{Type: }\langle e,t\rangle\)
\(\lambda x_e{:\:}\text{Saw}(x,\iota x_e{:\:}\text{Rhino}(x))\)
\(\text{Type: }\langle e,t\rangle\)
\(\lambda x_e{:\:}\text{Saw}(x,\iota x_e{:\:}\text{Rhino}(x))\)
\(\text{Type: }\langle e,t\rangle\)
\(\lambda y_e{:\:}\lambda x_e{:\:}\text{Saw}(x,y)\)
\(\text{Type: }\langle e,\langle e,t\rangle\rangle\)
\(\iota x_e{:\:}\text{Rhino}(x)\)
\(\text{Type: }e\)
\(\iota x_e{:\:}\text{Rhino}(x)\)
\(\text{Type: }e\)
\(\lambda f_{\langle e,t \rangle }{:\:}\iota x_e{:\:}f(x)\)
\(\text{Type: }\langle \langle e,t\rangle ,e\rangle\)
\(\lambda x_e{:\:}\text{Rhino}(x)\)
\(\text{Type: }\langle e,t\rangle\)

More diagrams?

I’d be excited to get both new diagram requests, and PRs for new diagrams: https://github.com/rawlins/svgling.