Syntactic Trees in Forest

approved

by Lucianski

Renders LaTeX forest-style syntactic trees for linguistics inline. - This plugin has not been manually reviewed by Obsidian staff.

94 downloadsUpdated 1mo agoAGPL-3.0

Syntactic Trees in Forest

Render LaTeX forest-style syntactic trees inline in Obsidian notes. Node labels go through Obsidian's MathJax pipeline, so primes (X$'$), superscripts (X$^0$), subscripts and arbitrary inline math just work.

The plugin focuses on the subset of forest that linguists actually use for syntactic trees: bracket syntax, per-node options, the for tree propagator, edge labels, triangle (roof) phrases, and \draw-style movement arrows between named nodes — with TikZ-faithful to[…] semantics.


Usage

Trees live in fenced forest code blocks:

```forest
[CP
  [DP, name=who [who$_i$]]
  [C$'$
    [C [did]]
    [TP
      [DP [Mary]]
      [T$'$
        [T]
        [vP
          [DP [$t$]]
          [v$'$
            [v]
            [VP
              [V [see]]
              [DP, name=obj [$t_i$]]]]]]]]]
\draw[->] (obj) to[out=south, in=south] (who);
```

The plugin processes the source as forest-package syntax and emits an inline SVG. Labels are MathJax-rendered, so anything between $…$ typesets properly.


Bracket syntax

Every node sits in [ … ]. Children are nested directly:

[Parent [Child1] [Child2 [Grandchild]]]

To put a literal [, ], ,, or = into a label, brace-protect that chunk: [{a, b}].

Per-node options

A comma after the node content introduces a comma-separated option list:

[VP, draw, name=verb, tier=word]
OptionEffect
name=<id>Names the node so movement arrows can target it
tier=<tier>Aligns all nodes sharing a tier name to the same horizontal line
roof / triangleDraws a triangle from the parent down to this node (phrasal shorthand)
drawOutlines the node with a rectangle
circleOutlines the node with a circle
phantomReserves layout space but draws nothing — useful for alignment
parent anchor=south|centerWhere the edge leaving this node downward attaches
child anchor=north|centerWhere the edge entering this node from above attaches
edge label={...}Italic label on the edge between this node and its parent

Inheritance: for tree and for descendants

Apply options to a node and all its descendants:

[CP, for tree={parent anchor=south, child anchor=north}
  [DP] [C$'$ [C] [TP]]]

for descendants={...} does the same but excludes the node itself.

Math in labels

Anything between $…$ in a node label is rendered with MathJax:

You typeYou get
X$'$X′
X$^0$X⁰
t$_i$tᵢ
$\overline{X}$

Sizing trees and nodes

Forest's TeX-dimension size options can sit on any node — root or interior — and apply locally:

OptionMeaning
s sep=XSibling separation among this node's children
l sep=XLevel separation between this node and its children
s sep+=XAdd to the global sibling separation
l sep+=XAdd to the global level separation
inner sep=XPadding inside each node label
inner xsep=XHorizontal-only padding
inner ysep=XVertical-only padding
font size=XLabel font size

X is a TeX dimension: pt, pc, bp, cm, mm, em, ex, in, px, or a bare number (= pixels). 1 pt ≈ 1.333 px, 1 cm ≈ 37.8 px at 96 dpi.

Local overrides on inner nodes only affect that node's children:

```forest
[vP                              ← root spacing default
  [DP [Mary]]
  [v$'$, s sep=50pt, l sep=40pt  ← v's subtree breathes out, rest stays tight
    [v]
    [VP
      [V [saw]]
      [DP [John]]]]]
```

To apply to a whole subtree, use the propagator:

```forest
[CP
  [DP]
  [C$'$
    [C]
    [TP, for tree={l sep=40pt, s sep=50pt}
      [DP] [T$'$ [T] [vP [DP] [v$'$ [v] [VP]]]]]]]
```

Movement arrows

After the tree, you can add \draw commands between nodes:

[CP
  [DP, name=who [who$_i$]]
  [C$'$
    [C [did, name = c]]
    [TP
      [DP, name=subj [Mary$_j$]]
      [T$'$
        [T [did, name = t]]
        [vP
          [DP, name=vsubj [$t_j$]]
          [v$'$
            [V + v [see, name = v]]
            [VP
              [V [see, name = V]]
              [DP, name=obj [$t_i$]]]]]]]]]
\draw[->] (obj) to[out=west, in=west] (who);
\draw[->] (vsubj) to[out=west, in=east] (subj);
\draw[->] (t) to[out=west, in=w] (c);
\draw[->] (V) to[out=west, in=w] (v);

Other forms the parser recognises:

\draw[->] (a) to (b);
\draw[->, dashed] (a) to[bend left=45] (b);
\draw[->, dotted] (a) to node{move} (b);
\draw[->, solid] (a) -- (b);

Recognised within each \draw:

TokenMeaning
\draw[-> …]Style block. Recognises dashed, dotted, solid (default is dashed)
(name)Reference a node by its name= option or by its content text
to[…]Curved arrow with option block (see below)
toDefault: drape below the tree
--Straight line (TikZ syntax)
node{label}Italic label placed near the arrow's apex

Direction control: to[out=A, in=B]

Per TikZ semantics, out=A and in=B set both the attachment point on the node boundary AND the tangent direction there. The attachment is the intersection of the ray from the node's centre at angle A with the node's bounding box. So out=south attaches at the south boundary and the curve leaves heading south.

You can use compass names or polar angles (0°=east, 90°=north, 180°=west, 270°=south):

\draw[->] (a) to[out=south,      in=south]      (b);
\draw[->] (a) to[out=south west, in=south]      (b);
\draw[->] (a) to[out=east,       in=west]       (b);
\draw[->] (a) to[out=270,        in=180]        (b);
\draw[->] (a) to[out=south east, in=north west, looseness=1.4] (b);

Compass-name → angle: east=0°, north east=45°, north=90°, north west=135°, west=180°, south west=225°, south=270°, south east=315°.

Whitespace inside compound names is tolerated. An optional looseness=N (default 1) scales how strongly the tangent is enforced — higher values exaggerate the curve.

Bend modifiers

bend left=N and bend right=N rotate the tangent angles. Combined with out/in, they add N° CCW to out and subtract N° from in (for bend left), bowing the curve further to that side of the path direction:

\draw[->] (a) to[out=south, in=east]                 (b);   ← baseline
\draw[->] (a) to[out=south, in=east, bend left=20]   (b);   ← bows more left
\draw[->] (a) to[out=south, in=east, bend right=20]  (b);   ← bows more right

Used alone (without out/in), bend left=N / bend right=N produce a quadratic curve bowing to that side of the direct line.

Anchor shorthand: (name.south)

For simple cases you can also pin an endpoint to a named anchor directly: (name.south), (name.east), (name.north west), etc. This is shorthand that pins the attachment point and uses the corresponding outward tangent direction. The full TikZ list is supported:

north, south, east, west, north east, north west, south east, south west, center.

When you give neither an anchor nor out=/in=, the renderer auto-picks the side of each node facing the other endpoint.

Arrowheads

Drawn geometrically as filled triangles oriented along the curve's tangent at the endpoint — no dependence on SVG <marker> definitions, which makes them robust across themes and embedded-SVG quirks. Style mimics TikZ's default > (slim, slightly concave back).


Settings

A settings tab exposes the global visual defaults: level separation, sibling separation, subtree separation, label font size, edge stroke width, and a toggle to outline every node by default. Per-tree options (above) override these locally.


Styling

The default look is aligned with TikzJax-rendered TikZ output (Computer- Modern-style serif text, thin crisp lines, refined arrow tips). Customize further with a CSS snippet targeting any of:

SelectorWhat it controls
.forest-svgThe root SVG. Set font-family, color, font-size
.forest-label-wrapWrapper around each node label
.forest-edgesGroup containing all tree edges
.forest-edgeEach individual tree edge (line or roof)
.forest-roofThe triangle of a roof/triangle node
.forest-arrowsGroup containing all movement arrows
.forest-movementEach arrow's curve (the line, not the head)
.forest-arrowheadThe filled triangle at the arrow tip
.forest-edge-labelItalic label next to a tree edge
.forest-movement-labelItalic label near an arrow's apex
.forest-node-shapeThe bounding box drawn around draw/circle nodes

All strokes and fills resolve through currentColor, set from color: var(--text-normal) on the SVG. So one rule recolors everything:

.forest-svg { color: #003366; font-family: "EB Garamond", serif; }

Dark mode is automatic because --text-normal flips with the theme.


Installation

  1. Download main.js, manifest.json, styles.css from the release.
  2. Drop them into <your-vault>/.obsidian/plugins/obsidian-forest/.
  3. Reload plugins in Obsidian settings, then enable Forest Syntactic Trees.

Alternatively, to build from source:

npm install
npm run build

What this plugin is not

It is not a complete port of the forest LaTeX package. forest is a TikZ/PGF DSL with conditionals, dynamic trees, aggregate functions, custom handlers, externalization, etc. — none of which is sensible to reimplement in JavaScript. This plugin covers the slice syntacticians reach for daily: bracket trees, primes and bars in labels, triangles, named nodes, movement arrows, and TikZ-style direction control via out=/in= and bend.

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.