rsyntaxtree 2.4.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +147 -540
- data/CITATION.cff +2 -2
- data/README.md +2 -1
- data/bin/rsyntaxtree +6 -3
- data/lib/rsyntaxtree/base_graph.rb +105 -30
- data/lib/rsyntaxtree/element.rb +1 -1
- data/lib/rsyntaxtree/elementlist.rb +95 -0
- data/lib/rsyntaxtree/json_graph.rb +27 -49
- data/lib/rsyntaxtree/notation_core.md +2 -1
- data/lib/rsyntaxtree/notation_examples.md +57 -2
- data/lib/rsyntaxtree/svg_graph.rb +219 -76
- data/lib/rsyntaxtree/tikz_generator.rb +12 -1
- data/lib/rsyntaxtree/version.rb +1 -1
- data/lib/rsyntaxtree.rb +20 -10
- data/rsyntaxtree.gemspec +2 -2
- metadata +1 -2
- data/.dockerignore +0 -5
data/CITATION.cff
CHANGED
|
@@ -11,8 +11,8 @@ repository-code: "https://github.com/yohasebe/rsyntaxtree"
|
|
|
11
11
|
# Concept DOI: always resolves to the latest archived version on Zenodo
|
|
12
12
|
doi: "10.5281/zenodo.21916150"
|
|
13
13
|
license: MIT
|
|
14
|
-
version:
|
|
15
|
-
date-released: 2026-
|
|
14
|
+
version: 2.6.0
|
|
15
|
+
date-released: 2026-11-12
|
|
16
16
|
keywords:
|
|
17
17
|
- linguistics
|
|
18
18
|
- syntax tree
|
data/README.md
CHANGED
|
@@ -144,7 +144,7 @@ older setup still fails to build `gobject-introspection`, `cairo-gobject` or
|
|
|
144
144
|
`gio2`, install those three first with
|
|
145
145
|
`-- --with-ldflags="-Wl,-undefined,dynamic_lookup"` appended, then install
|
|
146
146
|
RSyntaxTree; or skip the build entirely with the
|
|
147
|
-
[Docker image](https://
|
|
147
|
+
[Docker image](https://github.com/yohasebe/rsyntaxtree_web) or the
|
|
148
148
|
[web interface](https://yohasebe.com/rsyntaxtree).
|
|
149
149
|
|
|
150
150
|
## Usage
|
|
@@ -172,6 +172,7 @@ Usage:
|
|
|
172
172
|
| `-c, --color` | Color mode: modern, traditional, gray (black text, grey lines), off | `modern` |
|
|
173
173
|
| `-p, --polyline` | Polyline connectors: on, off | `off` |
|
|
174
174
|
| `--hide-default-connectors` | Draw no parent-to-child lines, leaving only the ones written with `+-n`: on, off | `off` |
|
|
175
|
+
| `--leaf-align` | Line the leaves up on one row: off, words (lower the words), subtrees (lower whole subtrees) | `off` |
|
|
175
176
|
| `-d, --direction` | Tree layout direction: ttb, ltr, btt | `ttb` |
|
|
176
177
|
| `--tidy` | Layout scale: off, symmetric, low, medium, high | `off` |
|
|
177
178
|
| `--hspacing` | How far apart sisters sit, all layout modes (0.5-3.0) | `1.0` |
|
data/bin/rsyntaxtree
CHANGED
|
@@ -18,7 +18,7 @@ CONFIG_VALIDATORS = {
|
|
|
18
18
|
outdir: ->(v) { FileTest.directory?(v) ? nil : "must be an existing directory path" },
|
|
19
19
|
outfilename: ->(v) { v.is_a?(String) ? nil : "must be a string" },
|
|
20
20
|
format: ->(v) { FORMATS.include?(v.to_s) || v.to_s == "lsif" ? nil : "must be #{FORMAT_LIST_MESSAGE}" },
|
|
21
|
-
leafstyle: ->(v) { OPTION_VALUES[:leafstyle].include?(v.to_s) ? nil : "must be auto, triangle, bar, or nothing" },
|
|
21
|
+
leafstyle: ->(v) { OPTION_VALUES[:leafstyle].include?(v.to_s) ? nil : "must be auto, triangle, bar, dashed, or nothing" },
|
|
22
22
|
fontstyle: ->(v) { OPTION_VALUES[:fontstyle].include?(v.to_s) ? nil : "must be sans, serif, cjk, or mono" },
|
|
23
23
|
fontsize: ->(v) { v.is_a?(Integer) && v >= 6 && v <= 26 ? nil : "must be in the range of 6-26" },
|
|
24
24
|
linewidth: ->(v) { v.is_a?(Numeric) && v >= 0.5 && v <= 3.0 ? nil : "must be in the range of 0.5-3.0" },
|
|
@@ -27,6 +27,7 @@ CONFIG_VALIDATORS = {
|
|
|
27
27
|
transparent: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
|
|
28
28
|
polyline: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
|
|
29
29
|
hide_default_connectors: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
|
|
30
|
+
leaf_align: ->(v) { OPTION_VALUES[:leaf_align].include?(v.to_s) ? nil : "must be off, words, or subtrees" },
|
|
30
31
|
mirror: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
|
|
31
32
|
shear: ->(v) { v.is_a?(Numeric) && v >= -45 && v <= 45 ? nil : "must be in the range of -45-45" },
|
|
32
33
|
vmargin: ->(v) { v.is_a?(Numeric) && v >= 0.1 && v <= 1.0 ? nil : "must be in the range of 0.1-1.0" },
|
|
@@ -109,7 +110,7 @@ opts = Optimist.options do
|
|
|
109
110
|
opt :outdir, "Output directory", default: "./", short: :o
|
|
110
111
|
opt :outfilename, "Output file base name", default: "syntree", short: :u
|
|
111
112
|
opt :format, "Output format: #{FORMAT_LIST_MESSAGE}", default: "png", short: :f
|
|
112
|
-
opt :leafstyle, "visual style of tree leaves: auto, triangle, bar, or nothing", default: "auto", short: :l
|
|
113
|
+
opt :leafstyle, "visual style of tree leaves: auto, triangle, bar, dashed, or nothing", default: "auto", short: :l
|
|
113
114
|
opt :fontstyle, "Font style: sans, serif, cjk, mono", default: "sans", short: :n
|
|
114
115
|
opt :fontsize, "Size: 6-26", default: 16, short: :s
|
|
115
116
|
opt :linewidth, "Size: 0.5-3.0", default: 1.0, short: :i
|
|
@@ -118,6 +119,7 @@ opts = Optimist.options do
|
|
|
118
119
|
opt :transparent, "Make background transparent: on or off", default: "off", short: :r
|
|
119
120
|
opt :polyline, "draw polyline connectors: on or off", default: "off", short: :p
|
|
120
121
|
opt :hide_default_connectors, "make default connectors transparent: on or off", default: "off", short: :none
|
|
122
|
+
opt :leaf_align, "line the leaves up on one row: off, words (lower the words), or subtrees (lower whole subtrees)", default: "off", short: :none
|
|
121
123
|
opt :mirror, "flip the tree horizontally (RTL linguistics convention: first word at the right edge): on or off", default: "off"
|
|
122
124
|
opt :shear, "Tilt the drawn figure, in degrees: -45 to 45, positive leaning the top to the right", default: 0.0, short: :none
|
|
123
125
|
opt :shear_plane, "The plane behind a sheared figure: on, off, a colour name, or a hex colour", default: "on", short: :none
|
|
@@ -145,7 +147,7 @@ Optimist.die :direction, "must be one of: #{OPTION_VALUES[:direction].join(', ')
|
|
|
145
147
|
Optimist.die :derivation, "must be one of: #{OPTION_VALUES[:derivation].join(', ')}" unless OPTION_VALUES[:derivation].include?(opts[:derivation])
|
|
146
148
|
Optimist.die :hyphen, "must be markup or literal" unless OPTION_VALUES[:hyphen].include?(opts[:hyphen])
|
|
147
149
|
Optimist.die :format, "must be #{FORMAT_LIST_MESSAGE}" unless FORMATS.include?(opts[:format])
|
|
148
|
-
Optimist.die :leafstyle, "must be auto, triangle, bar, or nothing" unless OPTION_VALUES[:leafstyle].include?(opts[:leafstyle])
|
|
150
|
+
Optimist.die :leafstyle, "must be auto, triangle, bar, dashed, or nothing" unless OPTION_VALUES[:leafstyle].include?(opts[:leafstyle])
|
|
149
151
|
Optimist.die :fontstyle, "must be sans, serif, cjk, or mono" unless OPTION_VALUES[:fontstyle].include?(opts[:fontstyle])
|
|
150
152
|
Optimist.die :fontsize, "must be in the range of 6-26" unless opts[:fontsize] >= 6 && opts[:fontsize] <= 26
|
|
151
153
|
Optimist.die :linewidth, "must be in the range of 0.5-3.0" unless opts[:linewidth] >= 0.5 && opts[:linewidth] <= 3.0
|
|
@@ -157,6 +159,7 @@ Optimist.die :vheight, "must be in the range of 0.5-5.0" if opts[:vheight] < 0.5
|
|
|
157
159
|
Optimist.die :transparent, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:transparent]
|
|
158
160
|
Optimist.die :polyline, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:polyline]
|
|
159
161
|
Optimist.die :hide_default_connectors, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:hide_default_connectors]
|
|
162
|
+
Optimist.die :leaf_align, "must be off, words, or subtrees" unless OPTION_VALUES[:leaf_align].include?(opts[:leaf_align])
|
|
160
163
|
Optimist.die :mirror, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:mirror]
|
|
161
164
|
Optimist.die :tidy, "must be off, symmetric, low, medium, or high" unless OPTION_VALUES[:tidy].include?(opts[:tidy])
|
|
162
165
|
Optimist.die :hspacing, "must be in the range of 0.5-3.0" if opts[:hspacing] < 0.5 || opts[:hspacing] > 3.0
|
|
@@ -67,6 +67,7 @@ module RSyntaxTree
|
|
|
67
67
|
@fontsize = params[:fontsize]
|
|
68
68
|
@mirror = params[:mirror] == true
|
|
69
69
|
@derivation = params[:derivation] == true
|
|
70
|
+
@leaf_align = !params[:leaf_align].nil? && params[:leaf_align].to_s != "off"
|
|
70
71
|
# tidy is one layout scale: "symmetric" (radical symmetrization) |
|
|
71
72
|
# "off" | "low" (packing, strict leaf positions) | "medium" (packing
|
|
72
73
|
# with cross-row tucking as long as no two leaves swap left-right
|
|
@@ -247,7 +248,43 @@ module RSyntaxTree
|
|
|
247
248
|
end
|
|
248
249
|
target.height = accum_array.max - target.vertical_indent
|
|
249
250
|
accum_array.max
|
|
250
|
-
end.tap
|
|
251
|
+
end.tap do
|
|
252
|
+
level_derivation_rows if id == 1 && @derivation
|
|
253
|
+
level_leaf_rows if id == 1 && @leaf_align && !@derivation
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Leaf alignment gives every leaf the same depth, but equal depth is not
|
|
258
|
+
# an equal row: each label is set below its own parent, so a taller parent
|
|
259
|
+
# (a label of two lines) or a shorter drop (a leaf pulled up under
|
|
260
|
+
# leafstyle nothing) leaves the words of one depth at different heights.
|
|
261
|
+
# Drawn left to right it is worse, a level being as wide as the label
|
|
262
|
+
# standing in it. Every depth is therefore given one row: it starts below
|
|
263
|
+
# the tallest label of the row above, by the largest drop any of its
|
|
264
|
+
# elements had, so each connector keeps at least the room it was given.
|
|
265
|
+
# Worked in the swapped coordinates the left-to-right layout runs in,
|
|
266
|
+
# where a column is a row.
|
|
267
|
+
def level_leaf_rows
|
|
268
|
+
elements = @element_list.get_elements
|
|
269
|
+
drop = Hash.new(0.0)
|
|
270
|
+
elements.each do |e|
|
|
271
|
+
next unless e.parent.to_i.positive?
|
|
272
|
+
|
|
273
|
+
parent = @element_list.get_id(e.parent)
|
|
274
|
+
drop[e.level] = [drop[e.level], e.vertical_indent - parent.vertical_indent - parent.content_height].max
|
|
275
|
+
end
|
|
276
|
+
tallest = Hash.new(0.0)
|
|
277
|
+
elements.each { |e| tallest[e.level] = [tallest[e.level], e.content_height].max }
|
|
278
|
+
|
|
279
|
+
top = {}
|
|
280
|
+
y = 0.0
|
|
281
|
+
tallest.keys.sort.each do |level|
|
|
282
|
+
y += drop[level]
|
|
283
|
+
top[level] = y
|
|
284
|
+
y += tallest[level]
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
elements.each { |e| e.vertical_indent = top[e.level] }
|
|
251
288
|
end
|
|
252
289
|
|
|
253
290
|
# In a derivation every premise is given at the start, so the words stand in
|
|
@@ -412,7 +449,11 @@ module RSyntaxTree
|
|
|
412
449
|
parent.triangle || (ETYPE_LEAF == child.type && child.triangle)
|
|
413
450
|
end
|
|
414
451
|
|
|
415
|
-
|
|
452
|
+
# `through` carries the connector chosen above a run of joints leaf
|
|
453
|
+
# alignment inserted, down that run to the child it reaches; `dashed`
|
|
454
|
+
# carries how far a dashed line has run so far, so that its dash goes on
|
|
455
|
+
# unbroken through each joint.
|
|
456
|
+
def draw_connector(id = 1, through = nil, dashed = 0)
|
|
416
457
|
parent = @element_list.get_id(id)
|
|
417
458
|
children = parent.children.map { |c| @element_list.get_id(c) }
|
|
418
459
|
|
|
@@ -425,40 +466,74 @@ module RSyntaxTree
|
|
|
425
466
|
return
|
|
426
467
|
end
|
|
427
468
|
|
|
428
|
-
if
|
|
429
|
-
child = children
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
end
|
|
444
|
-
when "nothing", "none"
|
|
445
|
-
if forced
|
|
446
|
-
triangle_to_parent(parent, child)
|
|
447
|
-
elsif ETYPE_LEAF != child.type
|
|
448
|
-
line_to_parent(parent, child)
|
|
449
|
-
end
|
|
450
|
-
end
|
|
451
|
-
else
|
|
452
|
-
children.each do |child|
|
|
453
|
-
line_to_parent(parent, child)
|
|
469
|
+
if through
|
|
470
|
+
child = children.first
|
|
471
|
+
draw_connector(child.id, (through if child.inserted_joint), dashed + draw_line(parent, child, through, dashed))
|
|
472
|
+
return
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
chosen = children.map do |child|
|
|
476
|
+
joints, target = past_inserted_joints(child)
|
|
477
|
+
connector = connector_for(parent, target, children.size == 1)
|
|
478
|
+
connector = :dashed if connector == :line && @leafstyle == "dashed" && ETYPE_LEAF == target.type
|
|
479
|
+
run = 0
|
|
480
|
+
case connector
|
|
481
|
+
when :triangle then triangle_to_parent(parent, target, joints)
|
|
482
|
+
when :line, :dashed then run = draw_line(parent, child, connector, 0)
|
|
483
|
+
else no_connector(parent, target, joints)
|
|
454
484
|
end
|
|
485
|
+
[child, connector, run]
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
chosen.each { |child, connector, run| draw_connector(child.id, (connector if child.inserted_joint), run) }
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# A line, dashed when it is one of the lines leafstyle `dashed` draws down
|
|
492
|
+
# to a word. Returns how long it is, for the piece below to carry on from.
|
|
493
|
+
def draw_line(parent, child, connector, dashed)
|
|
494
|
+
return 0 unless %i[line dashed].include?(connector)
|
|
495
|
+
|
|
496
|
+
line_to_parent(parent, child, dash_from: (dashed if connector == :dashed)).to_f
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Leaf alignment puts joints between a node and the child it reaches. They
|
|
500
|
+
# are there to place the child, not to change how it is joined, so the
|
|
501
|
+
# connector is chosen for the node and the child past them and carried
|
|
502
|
+
# through them: a triangle runs from the node down to the words, a line
|
|
503
|
+
# passes through each joint, and where no line is drawn none is drawn at
|
|
504
|
+
# all. Joints written into the input are nodes like any other.
|
|
505
|
+
def past_inserted_joints(child)
|
|
506
|
+
joints = []
|
|
507
|
+
while child.inserted_joint
|
|
508
|
+
joints << child
|
|
509
|
+
child = @element_list.get_id(child.children.first)
|
|
455
510
|
end
|
|
511
|
+
[joints, child]
|
|
512
|
+
end
|
|
456
513
|
|
|
457
|
-
|
|
458
|
-
|
|
514
|
+
def connector_for(parent, child, only_child)
|
|
515
|
+
return :line unless only_child
|
|
516
|
+
|
|
517
|
+
forced = triangle_asked_for?(parent, child)
|
|
518
|
+
case @leafstyle
|
|
519
|
+
when "auto"
|
|
520
|
+
forced || child.contains_phrase ? :triangle : :line
|
|
521
|
+
when "bar", "dashed"
|
|
522
|
+
forced ? :triangle : :line
|
|
523
|
+
when "nothing", "none"
|
|
524
|
+
if forced
|
|
525
|
+
:triangle
|
|
526
|
+
elsif ETYPE_LEAF != child.type
|
|
527
|
+
:line
|
|
528
|
+
else
|
|
529
|
+
:none
|
|
530
|
+
end
|
|
459
531
|
end
|
|
460
532
|
end
|
|
461
533
|
|
|
534
|
+
# A child joined to its parent by nothing at all: no mark to draw.
|
|
535
|
+
def no_connector(_parent, _child, _joints = []); end
|
|
536
|
+
|
|
462
537
|
def get_leftmost(id = 1)
|
|
463
538
|
target = @element_list.get_id(id)
|
|
464
539
|
target_indent = target.horizontal_indent
|
data/lib/rsyntaxtree/element.rb
CHANGED
|
@@ -13,7 +13,7 @@ require_relative "color_names"
|
|
|
13
13
|
|
|
14
14
|
module RSyntaxTree
|
|
15
15
|
class Element
|
|
16
|
-
attr_accessor :ink_top, :ink_bottom, :rule_name, :label_with_rule_name, :id, :parent, :type, :level, :width, :height, :content, :content_width, :text_width, :content_height, :horizontal_indent, :vertical_indent, :triangle, :enclosure, :children, :font, :fontsize, :contains_phrase, :path, :color, :raw_content, :region, :region_color
|
|
16
|
+
attr_accessor :ink_top, :ink_bottom, :rule_name, :label_with_rule_name, :id, :parent, :type, :level, :width, :height, :content, :content_width, :text_width, :content_height, :horizontal_indent, :vertical_indent, :triangle, :enclosure, :children, :font, :fontsize, :contains_phrase, :path, :color, :raw_content, :region, :region_color, :inserted_joint
|
|
17
17
|
|
|
18
18
|
# names_a_rule says the content is a mother's label rather than a leaf's
|
|
19
19
|
# text. Only a mother has a step under it for a name to sit beside, and a
|
|
@@ -99,5 +99,100 @@ module RSyntaxTree
|
|
|
99
99
|
end
|
|
100
100
|
maxlevel + 1
|
|
101
101
|
end
|
|
102
|
+
|
|
103
|
+
# Add invisible `<>` joints to put every leaf at the deepest leaf level.
|
|
104
|
+
def align_leaves(mode, fontset, fontsize, global)
|
|
105
|
+
leaves = @elements.select { |e| e.type == ETYPE_LEAF && e.children.empty? }
|
|
106
|
+
return if leaves.size < 2
|
|
107
|
+
|
|
108
|
+
next_id = @elements.map(&:id).max + 1
|
|
109
|
+
|
|
110
|
+
if mode == "subtrees"
|
|
111
|
+
root = @elements.find { |element| element.parent.zero? }
|
|
112
|
+
add_high_joints(root, next_id, fontset, fontsize, global) if root
|
|
113
|
+
else
|
|
114
|
+
deepest = leaves.map(&:level).max
|
|
115
|
+
|
|
116
|
+
leaves.each do |leaf|
|
|
117
|
+
joints = deepest - leaf.level
|
|
118
|
+
next unless joints.positive?
|
|
119
|
+
|
|
120
|
+
next_id = insert_empty_joints(leaf, joints, next_id, fontset, fontsize, global)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
renumber_preorder
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def add_high_joints(parent, next_id, fontset, fontsize, global)
|
|
130
|
+
children = parent.children.map { |id| get_id(id) }
|
|
131
|
+
heights = children.map { |child| subtree_height(child) }
|
|
132
|
+
deepest = heights.max
|
|
133
|
+
|
|
134
|
+
children.zip(heights).each do |child, height|
|
|
135
|
+
joints = deepest - height
|
|
136
|
+
if joints.positive?
|
|
137
|
+
next_id = insert_empty_joints(child, joints, next_id, fontset, fontsize, global)
|
|
138
|
+
shift_subtree_levels(child, joints)
|
|
139
|
+
end
|
|
140
|
+
next_id = add_high_joints(child, next_id, fontset, fontsize, global) unless child.children.empty?
|
|
141
|
+
end
|
|
142
|
+
next_id
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def subtree_height(element)
|
|
146
|
+
return 0 if element.children.empty?
|
|
147
|
+
|
|
148
|
+
1 + element.children.map { |id| subtree_height(get_id(id)) }.max
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def insert_empty_joints(child, count, next_id, fontset, fontsize, global)
|
|
152
|
+
parent = get_id(child.parent)
|
|
153
|
+
return next_id unless parent
|
|
154
|
+
|
|
155
|
+
count.times do
|
|
156
|
+
joint = Element.new(next_id, parent.id, WHITESPACE_BLOCK, parent.level + 1, fontset, fontsize, global)
|
|
157
|
+
joint.inserted_joint = true
|
|
158
|
+
next_id += 1
|
|
159
|
+
@elements << joint
|
|
160
|
+
insert_between(parent, child, joint)
|
|
161
|
+
parent = joint
|
|
162
|
+
end
|
|
163
|
+
next_id
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def shift_subtree_levels(element, count)
|
|
167
|
+
element.level += count
|
|
168
|
+
element.children.each { |id| shift_subtree_levels(get_id(id), count) }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def insert_between(parent, child, joint)
|
|
172
|
+
parent.children[parent.children.index(child.id)] = joint.id
|
|
173
|
+
joint.add_child(child.id)
|
|
174
|
+
joint.type = ETYPE_NODE
|
|
175
|
+
child.parent = joint.id
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def renumber_preorder
|
|
179
|
+
root = @elements.find { |element| element.parent.zero? }
|
|
180
|
+
return unless root
|
|
181
|
+
|
|
182
|
+
ordered = []
|
|
183
|
+
visit = lambda do |element|
|
|
184
|
+
ordered << element
|
|
185
|
+
element.children.each { |id| visit.call(get_id(id)) }
|
|
186
|
+
end
|
|
187
|
+
visit.call(root)
|
|
188
|
+
|
|
189
|
+
ids = ordered.each_with_index.to_h { |element, index| [element.id, index + 1] }
|
|
190
|
+
ordered.each do |element|
|
|
191
|
+
element.parent = ids.fetch(element.parent, 0)
|
|
192
|
+
element.children = element.children.map { |id| ids.fetch(id) }
|
|
193
|
+
element.id = ids.fetch(element.id)
|
|
194
|
+
end
|
|
195
|
+
@elements = ordered
|
|
196
|
+
end
|
|
102
197
|
end
|
|
103
198
|
end
|
|
@@ -56,62 +56,39 @@ module RSyntaxTree
|
|
|
56
56
|
@nodes << build_node(element)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
def line_to_parent(parent, child)
|
|
60
|
-
|
|
59
|
+
def line_to_parent(parent, child, dash_from: nil)
|
|
60
|
+
edge = { from: parent.id, to: child.id, type: "dominance", connector: "line" }
|
|
61
|
+
edge[:dashed] = true if dash_from
|
|
62
|
+
@edges << edge
|
|
63
|
+
0
|
|
61
64
|
end
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
# A triangle spans from the parent to its child, through any joints leaf
|
|
67
|
+
# alignment put between them; each edge it crosses is recorded as part of
|
|
68
|
+
# it, so the joints keep their place in the tree.
|
|
69
|
+
def triangle_to_parent(parent, child, joints = [])
|
|
70
|
+
chain_edges(parent, joints, child, "triangle")
|
|
65
71
|
end
|
|
66
72
|
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# A derivation joins a node to its daughters with one rule across all of
|
|
73
|
-
# them. Recorded as what it is: this format states that it carries how
|
|
74
|
-
# the figure was drawn, so calling these lines would be a false record.
|
|
75
|
-
if @derivation && !children.empty?
|
|
76
|
-
children.each do |child|
|
|
77
|
-
@edges << { from: parent.id, to: child.id, type: "dominance", connector: "rule" }
|
|
78
|
-
end
|
|
79
|
-
parent.children.each { |c| draw_connector(c) }
|
|
80
|
-
return
|
|
81
|
-
end
|
|
73
|
+
# Recorded rather than left out: this format carries how the figure was
|
|
74
|
+
# drawn, and a child with no mark under it is part of that.
|
|
75
|
+
def no_connector(parent, child, joints = [])
|
|
76
|
+
chain_edges(parent, joints, child, "none")
|
|
77
|
+
end
|
|
82
78
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
triangle_to_parent(parent, child)
|
|
90
|
-
else
|
|
91
|
-
line_to_parent(parent, child)
|
|
92
|
-
end
|
|
93
|
-
when "bar"
|
|
94
|
-
if forced
|
|
95
|
-
triangle_to_parent(parent, child)
|
|
96
|
-
else
|
|
97
|
-
line_to_parent(parent, child)
|
|
98
|
-
end
|
|
99
|
-
when "nothing", "none"
|
|
100
|
-
if forced
|
|
101
|
-
triangle_to_parent(parent, child)
|
|
102
|
-
elsif ETYPE_LEAF != child.type
|
|
103
|
-
line_to_parent(parent, child)
|
|
104
|
-
else
|
|
105
|
-
@edges << { from: parent.id, to: child.id, type: "dominance", connector: "none" }
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
else
|
|
109
|
-
children.each do |child|
|
|
110
|
-
line_to_parent(parent, child)
|
|
111
|
-
end
|
|
79
|
+
# A derivation joins a node to its daughters with one rule across all of
|
|
80
|
+
# them. Recorded as what it is: this format states that it carries how
|
|
81
|
+
# the figure was drawn, so calling these lines would be a false record.
|
|
82
|
+
def rule_to_parent(parent, children)
|
|
83
|
+
children.each do |child|
|
|
84
|
+
@edges << { from: parent.id, to: child.id, type: "dominance", connector: "rule" }
|
|
112
85
|
end
|
|
86
|
+
end
|
|
113
87
|
|
|
114
|
-
|
|
88
|
+
def chain_edges(parent, joints, child, connector)
|
|
89
|
+
([parent] + joints).zip(joints + [child]).each do |from, to|
|
|
90
|
+
@edges << { from: from.id, to: to.id, type: "dominance", connector: connector }
|
|
91
|
+
end
|
|
115
92
|
end
|
|
116
93
|
|
|
117
94
|
def draw_paths
|
|
@@ -331,6 +308,7 @@ module RSyntaxTree
|
|
|
331
308
|
# before 2.0 carry a separate symmetrize flag beside it; the
|
|
332
309
|
# scale is what it meant, and it is all that is written now.
|
|
333
310
|
tidy: @params[:tidy] || "off",
|
|
311
|
+
leaf_align: @params[:leaf_align],
|
|
334
312
|
mirror: @params[:mirror] == true,
|
|
335
313
|
direction: @params[:direction] || "ttb",
|
|
336
314
|
# Degrees, positive leaning the top right. The geometry below
|
|
@@ -99,7 +99,7 @@ Options (the command line spells them --like-this):
|
|
|
99
99
|
| fontsize | 6–26 |
|
|
100
100
|
| color | modern, traditional, off, gray |
|
|
101
101
|
| linewidth | every line's weight, 0.5–3.0, as a ratio of the font size |
|
|
102
|
-
| leafstyle | auto, bar, nothing — what joins a node to its leaf |
|
|
102
|
+
| leafstyle | auto, bar, dashed, nothing — what joins a node to its leaf |
|
|
103
103
|
| direction | ttb, ltr, btt |
|
|
104
104
|
| mirror | flip the finished layout, for RTL scripts |
|
|
105
105
|
| tidy | off, symmetric, low, medium, high — how tightly subtrees pack |
|
|
@@ -107,6 +107,7 @@ Options (the command line spells them --like-this):
|
|
|
107
107
|
| vheight | how far apart levels sit, 0.5–5.0 |
|
|
108
108
|
| polyline | draw each connector as two right angles rather than a slanted line |
|
|
109
109
|
| hide_default_connectors | draw no parent-to-child lines, leaving only the ones written with +-n — how a lattice or a network is drawn |
|
|
110
|
+
| leaf_align | line the leaves up on one row: off, words (lower the words) or subtrees (lower whole subtrees) |
|
|
110
111
|
| transparent | leave the background clear instead of painting it white |
|
|
111
112
|
| derivation | one rule across the daughters, categorial-grammar style |
|
|
112
113
|
| hyphen | markup (hyphens underline) or literal (hyphens are hyphens) |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
RSyntaxTree examples:
|
|
1
|
+
RSyntaxTree examples: 85 trees, every one verified to draw.
|
|
2
2
|
|
|
3
3
|
Each is the input behind a figure in the gallery at https://yohasebe.github.io/rsyntaxtree/examples.
|
|
4
4
|
The settings line names the options the gallery records for that figure;
|
|
@@ -2488,7 +2488,7 @@ own(x,<>y)
|
|
|
2488
2488
|
]
|
|
2489
2489
|
```
|
|
2490
2490
|
|
|
2491
|
-
## 080 — Terminals levelled with pass-through joints
|
|
2491
|
+
## 080 — Terminals levelled by hand with pass-through joints
|
|
2492
2492
|
|
|
2493
2493
|
Category: General
|
|
2494
2494
|
Settings: fontstyle=noto-serif tidy=low vheight=1.0
|
|
@@ -2639,3 +2639,58 @@ SYN\t#(HEAD\t|1|#)#)
|
|
|
2639
2639
|
---\
|
|
2640
2640
|
AGR\t|2|#)#)#) [bark]]]
|
|
2641
2641
|
```
|
|
2642
|
+
|
|
2643
|
+
## 087 — Terminals levelled automatically: whole subtrees drop
|
|
2644
|
+
|
|
2645
|
+
Category: General
|
|
2646
|
+
Settings: fontstyle=noto-serif leaf_align=subtrees polyline=on tidy=low vheight=1.0
|
|
2647
|
+
|
|
2648
|
+
```
|
|
2649
|
+
[S
|
|
2650
|
+
[NP [D the] [N cat]]
|
|
2651
|
+
[VP
|
|
2652
|
+
[V sat]
|
|
2653
|
+
[PP [P on] [NP [D the] [N mat]]]]]
|
|
2654
|
+
```
|
|
2655
|
+
|
|
2656
|
+
## 088 — Wh-movement with the terminals levelled
|
|
2657
|
+
|
|
2658
|
+
Category: Generative Grammar
|
|
2659
|
+
Settings: fontstyle=noto-serif leaf_align=subtrees polyline=on tidy=low vheight=1.0
|
|
2660
|
+
|
|
2661
|
+
```
|
|
2662
|
+
[CP
|
|
2663
|
+
[DP what+>1]
|
|
2664
|
+
[C' [C did]
|
|
2665
|
+
[TP
|
|
2666
|
+
[DP [D the] [NP [N child]]]
|
|
2667
|
+
[T' [T t]
|
|
2668
|
+
[VP [V find] [DP t+1]]]]]]
|
|
2669
|
+
```
|
|
2670
|
+
|
|
2671
|
+
## 089 — A Japanese sentence with its words on one row
|
|
2672
|
+
|
|
2673
|
+
Category: Multilingual
|
|
2674
|
+
Settings: fontstyle=noto-serif leaf_align=words leafstyle=dashed tidy=low vheight=1.0
|
|
2675
|
+
|
|
2676
|
+
```
|
|
2677
|
+
[S
|
|
2678
|
+
[NP [N 太郎] [P が]]
|
|
2679
|
+
[VP
|
|
2680
|
+
[NP [AdjP [Adj 古い]] [NP [N 本] [P を]]]
|
|
2681
|
+
[V 読んだ]]]
|
|
2682
|
+
```
|
|
2683
|
+
|
|
2684
|
+
## 090 — A summarised phrase deep in the tree, with the terminals levelled
|
|
2685
|
+
|
|
2686
|
+
Category: General
|
|
2687
|
+
Settings: fontstyle=noto-serif leaf_align=words leafstyle=dashed tidy=low vheight=1.0
|
|
2688
|
+
|
|
2689
|
+
```
|
|
2690
|
+
[TP
|
|
2691
|
+
[DP [D the]
|
|
2692
|
+
[NP [N man]
|
|
2693
|
+
[PP [P with] [^DP the hat]]]]
|
|
2694
|
+
[T' [T will]
|
|
2695
|
+
[VP [V give] [DP [D the] [NP [N book]]]]]]
|
|
2696
|
+
```
|