diagrammatron 0.3.0 → 0.4.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/bin/diagrammatron-edges +84 -36
- data/bin/diagrammatron-nodes +1 -1
- data/bin/diagrammatron-render +98 -90
- data/bin/diagrammatron-template +1 -1
- data/template/internal.yaml +31 -20
- data/template/root.yaml +30 -19
- data/template/svg_1.1.erb +13 -17
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32bd6ca5ad8db808b0b6c921ee0efe5b8e1a6befe4e535636f16ff98a22de139
|
4
|
+
data.tar.gz: c00027483fc54a9fab024c1e42a4e10e27b797e52e3a9217319a50542926a894
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7723bc76dfd7463a864319421074d8c17357f6c88a309efd81919c165ad836f876d48e062bf618b45e2b7fe779b1c564afaedfa6b105ae1fee9f84e5f52003d5
|
7
|
+
data.tar.gz: ed23e22fd6476e33afb4360cce23cefb0373dbd2c763611d9f61d0a20a283fe2dcd7a4bac6f137d628020db86819aacdea6387c889e5821a3ed4b958a5a2a736
|
data/bin/diagrammatron-edges
CHANGED
@@ -68,7 +68,7 @@ def work_copy(src, quiet)
|
|
68
68
|
next
|
69
69
|
end
|
70
70
|
label = node['label']
|
71
|
-
if label2idx.key?(label) && edge_nodes.
|
71
|
+
if label2idx.key?(label) && edge_nodes.member?(label)
|
72
72
|
aargh "Edge-referred label used twice: #{label}"
|
73
73
|
errors = true
|
74
74
|
end
|
@@ -424,63 +424,111 @@ def segment_order(a, b)
|
|
424
424
|
return d unless d.zero?
|
425
425
|
d = a[2].range.min <=> b[2].range.min
|
426
426
|
return d unless d.zero?
|
427
|
-
when 1 #
|
428
|
-
d = a[2].range.min <=> b[2].range.min #
|
427
|
+
when 1 # Top left, vertical, bottom right: -|_
|
428
|
+
d = a[2].range.min <=> b[2].range.min # Ascend on minimum.
|
429
|
+
return d unless d.zero?
|
430
|
+
d = a[2].length <=> b[2].length # Ascend on length.
|
429
431
|
return d unless d.zero?
|
430
432
|
when 2 # From top right, down, bottom left: _|-
|
431
|
-
d = a[2].range.
|
433
|
+
d = a[2].range.max <=> b[2].range.max # Descend on maximum.
|
432
434
|
return -d unless d.zero?
|
433
|
-
|
434
|
-
d = b[2].length <=> a[2].length
|
435
|
-
return d unless d.zero?
|
436
|
-
d = b[2].range.max <=> a[2].range.max
|
435
|
+
d = a[2].length <=> b[2].length # Ascend on length.
|
437
436
|
return d unless d.zero?
|
437
|
+
when 3 # Descending on length, range maximum. |=
|
438
|
+
d = a[2].length <=> b[2].length
|
439
|
+
return -d unless d.zero?
|
440
|
+
d = a[2].range.max <=> b[2].range.max
|
441
|
+
return -d unless d.zero?
|
438
442
|
end
|
439
443
|
a[0].edge_index <=> b[0].edge_index
|
440
444
|
end
|
441
445
|
|
442
|
-
GapState = Struct.new(:order, :cross_count) do
|
446
|
+
GapState = Struct.new(:order, :cross_count, :lut) do
|
443
447
|
def fitness(segments, k)
|
448
|
+
table = lut[k]
|
444
449
|
crossings = 0
|
445
|
-
s = segments[k]
|
446
|
-
prev = (s[1] == 1) ? s[2].range.max : s[2].range.min
|
447
450
|
order.each do |n|
|
448
|
-
|
449
|
-
if placed[2].range.min <= prev && prev <= placed[2].range.max
|
450
|
-
crossings += 1
|
451
|
-
end
|
452
|
-
v = (placed[1] == 1) ? placed[2].range.min : placed[2].range.max
|
453
|
-
if s[2].range.min <= v && v <= s[2].range.max
|
454
|
-
crossings += 1
|
455
|
-
end
|
451
|
+
crossings += table[n]
|
456
452
|
end
|
457
453
|
cross_count + crossings
|
458
454
|
end
|
459
455
|
end
|
460
456
|
|
461
|
-
def
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
457
|
+
def candidate_compare(a, b)
|
458
|
+
d = a[1] <=> b[1]
|
459
|
+
return d unless d == 0
|
460
|
+
a[0] <=> b[0]
|
461
|
+
end
|
462
|
+
|
463
|
+
def candidate_order(lut, used)
|
464
|
+
# Compute crossing sums for all remaining segments among themselves.
|
465
|
+
cands = []
|
466
|
+
lut.keys.each do |k|
|
467
|
+
next if used.include? k
|
468
|
+
total = 0
|
469
|
+
lut.each_pair do |idx, v|
|
470
|
+
total += v[k] unless used.include? idx
|
470
471
|
end
|
471
|
-
|
472
|
-
|
472
|
+
cands.push([k, total])
|
473
|
+
end
|
474
|
+
cands.sort! { |a, b| candidate_compare(a, b) }
|
475
|
+
cands.map { |a| a[0] }
|
476
|
+
end
|
477
|
+
|
478
|
+
def depth_first_search(segments, state, best)
|
479
|
+
if state.order.size == segments.size
|
480
|
+
$stderr.puts state.order.join(', '), state.cross_count
|
481
|
+
return GapState.new(state.order.clone, state.cross_count, state.lut)
|
482
|
+
end
|
483
|
+
cands = candidate_order(state.lut, state.order)
|
484
|
+
cands.each do |k|
|
485
|
+
c = state.fitness(segments, k)
|
486
|
+
next if (best.nil? ? c + 1 : best.cross_count) <= c
|
487
|
+
state.order.push(k)
|
488
|
+
best = depth_first_search(segments, GapState.new(state.order, c, state.lut), best)
|
489
|
+
state.order.pop
|
473
490
|
end
|
474
491
|
best
|
475
492
|
end
|
476
493
|
|
477
|
-
def zigzag_order(
|
494
|
+
def zigzag_order(from_right_up_to_left, from_left_up_to_right)
|
495
|
+
# Interleave the two sets.
|
496
|
+
out = []
|
497
|
+
from_right_up_to_left.each { |x| out.push x }
|
498
|
+
from_left_up_to_right.each { |x| out.push x }
|
499
|
+
return out
|
478
500
|
return segments if segments.size < 2
|
479
501
|
# DFS. Fitness is how many end segments cross the placed segments.
|
480
502
|
# Sort so that those that cross least when at start are first and vice versa.
|
481
|
-
|
503
|
+
lut = {}
|
482
504
|
segments.each_index do |k|
|
483
|
-
|
505
|
+
crossings = Hash.new(0)
|
506
|
+
lut[k] = crossings
|
507
|
+
s = segments[k]
|
508
|
+
prev = (s[1] == 1) ? s[2].range.max : s[2].range.min
|
509
|
+
segments.each_index do |n|
|
510
|
+
next if k == n
|
511
|
+
other = segments[n]
|
512
|
+
if other[2].range.min <= prev && prev <= other[2].range.max
|
513
|
+
crossings[n] = crossings[n] + 1
|
514
|
+
end
|
515
|
+
v = (other[1] == 1) ? other[2].range.min : other[2].range.max
|
516
|
+
if s[2].range.min <= v && v <= s[2].range.max
|
517
|
+
crossings[n] = crossings[n] + 1
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
$stderr.puts segments.size, lut
|
522
|
+
# Could take all crossings for k, sort, sum up to get minimum fitness for
|
523
|
+
# used set size N ignoring what set has. Since real fitness can not be
|
524
|
+
# lower, once minimal sum + cross_count exceeds best, we can not use that
|
525
|
+
# segment in any later position and can cut off search.
|
526
|
+
# Add to GapState after lut.
|
527
|
+
# Also, coult set time budget for single search and terminate search.
|
528
|
+
best = nil
|
529
|
+
cands = candidate_order(lut, [])
|
530
|
+
cands.each do |k|
|
531
|
+
best = depth_first_search(segments, GapState.new([ k ], 0, lut), best)
|
484
532
|
end
|
485
533
|
out = []
|
486
534
|
best.order.each do |k|
|
@@ -653,7 +701,7 @@ def place_edges(work)
|
|
653
701
|
chosen.each_value do |p|
|
654
702
|
(1...(p.segments.size - 1)).each do |k|
|
655
703
|
# Middle segments always have surrounding segments.
|
656
|
-
s,
|
704
|
+
s, below, above, sb, sa = p.segment_directions(k)
|
657
705
|
so = s.clone # More accurate info on actual range with end offsets.
|
658
706
|
if so.range[0] < so.range[1]
|
659
707
|
so.range[0] += sb.offset unless sb.offset.nil?
|
@@ -662,7 +710,7 @@ def place_edges(work)
|
|
662
710
|
so.range[0] += sb.offset.nil? ? 0.9999 : sb.offset
|
663
711
|
so.range[1] += sa.offset unless sa.offset.nil?
|
664
712
|
end
|
665
|
-
group = (
|
713
|
+
group = (below.negative? ? 0 : 1) + (above.negative? ? 0 : 2)
|
666
714
|
d = gaps[s.vertical]
|
667
715
|
d[s.cc] = d.fetch(s.cc, []).push([ s, group, so ])
|
668
716
|
end
|
@@ -672,7 +720,7 @@ def place_edges(work)
|
|
672
720
|
gap.sort! { |a, b| segment_order(a, b) }
|
673
721
|
gleft = gap.select { |a| a[1].zero? }
|
674
722
|
gright = gap.select { |a| a[1] == 3 }
|
675
|
-
gmiddle = zigzag_order(gap.select { |a| a[1] == 1
|
723
|
+
gmiddle = zigzag_order(gap.select() { |a| a[1] == 1 }, gap.select() { |a| a[1] == 2 })
|
676
724
|
gmiddle.each do |s|
|
677
725
|
s[1] = 1
|
678
726
|
end
|
data/bin/diagrammatron-nodes
CHANGED
@@ -312,7 +312,7 @@ def work_copy(src, quiet)
|
|
312
312
|
next
|
313
313
|
end
|
314
314
|
label = node['label']
|
315
|
-
if label2idx.key?(label) && edge_nodes.
|
315
|
+
if label2idx.key?(label) && edge_nodes.member?(label)
|
316
316
|
aargh "Edge-referred label used twice: #{label}"
|
317
317
|
errors = true
|
318
318
|
end
|
data/bin/diagrammatron-render
CHANGED
@@ -76,94 +76,98 @@ def separate_coordinates(doc)
|
|
76
76
|
[ xcoords, ycoords, ckd2count ]
|
77
77
|
end
|
78
78
|
|
79
|
-
class
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
79
|
+
class Styles
|
80
|
+
def base_styles(m, styles, group)
|
81
|
+
d = styles.dig(group, 'default') || {}
|
82
|
+
m['default'] = m.fetch('default', {}).merge(d)
|
83
|
+
styles.fetch(group, {}).each_pair do |name, values|
|
84
|
+
s = d.clone
|
85
|
+
s.merge!(values) unless name == 'default'
|
86
|
+
m[name] = m.fetch(name, {}).merge(s)
|
87
|
+
end
|
88
|
+
m
|
89
|
+
end
|
90
|
+
|
91
|
+
def initialize(template_styles, diagram_styles)
|
92
|
+
@n = base_styles(base_styles({}, template_styles, 'node'), diagram_styles, 'node')
|
93
|
+
@e = base_styles(base_styles({}, template_styles, 'edge'), diagram_styles, 'edge')
|
94
|
+
@d = base_styles(base_styles({}, template_styles, 'diagram'), diagram_styles, 'diagram')
|
95
|
+
end
|
96
|
+
|
97
|
+
def fill(mapping, type_name, item)
|
98
|
+
styles = item.fetch('style', [ 'default' ])
|
99
|
+
styles = [ styles ] unless styles.is_a?(Array)
|
100
|
+
s = {}
|
101
|
+
styles.each do |name|
|
102
|
+
ns = mapping.fetch(name, nil)
|
103
|
+
raise "No such #{type_name} style: #{name} in #{mapping.keys.join(', ')}" if ns.nil?
|
104
|
+
s.merge! ns
|
105
|
+
end
|
106
|
+
# Keep values specified explicitly.
|
107
|
+
item.merge!(s) {|key, existing, from_template| existing }
|
108
|
+
end
|
109
|
+
|
110
|
+
def apply_node_styles(node)
|
111
|
+
fill(@n, 'node', node)
|
91
112
|
end
|
92
|
-
end
|
93
113
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
def
|
99
|
-
|
100
|
-
@width_key = defaults.fetch('width_key', 'w')
|
101
|
-
@height_key = defaults.fetch('height_key', 'h')
|
102
|
-
@width_margin = defaults.fetch('width_margin', 10)
|
103
|
-
@height_margin = defaults.fetch('height_margin', 10)
|
104
|
-
@edge_gap = defaults.fetch('edge_gap', 20)
|
105
|
-
@font = FontInfo.new(template)
|
114
|
+
def apply_edge_styles(edge)
|
115
|
+
fill(@e, 'edge', edge)
|
116
|
+
end
|
117
|
+
|
118
|
+
def apply_diagram_styles(diagram)
|
119
|
+
fill(@d, 'diagram', diagram)
|
106
120
|
end
|
107
121
|
end
|
108
122
|
|
109
123
|
class SizeEstimation
|
110
|
-
attr_accessor :node, :
|
124
|
+
attr_accessor :node, :ckd2count, :doc
|
111
125
|
|
112
|
-
def initialize(
|
126
|
+
def initialize(ckd2count, doc)
|
113
127
|
@node = nil
|
114
|
-
@template = template
|
115
128
|
@ckd2count = ckd2count
|
116
|
-
@
|
129
|
+
@doc = doc
|
117
130
|
end
|
118
131
|
|
119
|
-
def
|
132
|
+
def exposed_binding
|
120
133
|
binding
|
121
134
|
end
|
122
135
|
|
123
|
-
def get_default(key, default_value = nil)
|
124
|
-
@template.fetch('defaults', {}).fetch(key, default_value)
|
125
|
-
end
|
126
|
-
|
127
136
|
def max_edges(key, edge_gap)
|
128
137
|
c = [ @node['xo'], @node['yo'], key, -1 ]
|
129
138
|
count = @ckd2count[c]
|
130
139
|
c[3] = 1
|
131
140
|
count = [ count, @ckd2count[c] ].max
|
132
141
|
return 0 if count < 2
|
133
|
-
(count - 1) *
|
142
|
+
(count - 1) * edge_gap
|
134
143
|
end
|
135
144
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
145
|
+
# font_size is the actual size.
|
146
|
+
# font_width, font_height, font_line_spacing are [0, 1] size scaling factors.
|
147
|
+
# width_margin, height_margin are in same units as fonti, space inside node.
|
148
|
+
# edge_gap is minimum space between edges at any node side.
|
149
|
+
def default_size(font_size, font_width, font_height, font_line_spacing,
|
150
|
+
width_margin, height_margin, edge_gap)
|
151
|
+
lines = @node['text']
|
152
|
+
w = 2 * width_margin + font_width * font_size * (lines.map &(:size)).max
|
153
|
+
@node['w'] = [ w, max_edges('xo', edge_gap) ].max
|
154
|
+
h = 2 * height_margin + font_height * font_size * lines.size +
|
155
|
+
font_line_spacing * font_size * (lines.size - 1)
|
156
|
+
@node['h'] = [ h, max_edges('yo', edge_gap) ].max
|
146
157
|
end
|
147
158
|
end
|
148
159
|
|
149
|
-
def estimate_sizes(doc,
|
150
|
-
$render = SizeEstimation.new(
|
151
|
-
sizes = template.fetch('sizes', {})
|
152
|
-
defaults = template.fetch('defaults', {})
|
160
|
+
def estimate_sizes(doc, ckd2count)
|
161
|
+
$render = SizeEstimation.new(ckd2count, doc)
|
153
162
|
doc['nodes'].each do |node|
|
154
|
-
label = node.fetch('label', 'unnamed')
|
155
|
-
# Substitute "text" as label if present, split to lines.
|
156
|
-
node['label'] = node.fetch('text', node.fetch('label', '')).split("\n")
|
157
163
|
$render.node = node
|
164
|
+
label = node.fetch('label', 'unnamed')
|
158
165
|
style = node.fetch('style', 'default')
|
159
|
-
code =
|
160
|
-
%(raise NotImplementedError, "No size estimator for style: #{style}"))
|
161
|
-
if sizes.key? code
|
162
|
-
code = sizes.fetch(code)
|
163
|
-
end
|
166
|
+
code = node.fetch('size_estimator',
|
167
|
+
%(raise NotImplementedError, "No size estimator for style: #{style}"))
|
164
168
|
code = code.join("\n") if code.is_a? Array
|
165
169
|
begin
|
166
|
-
eval(code, $render.
|
170
|
+
eval(code, $render.exposed_binding)
|
167
171
|
rescue StandardError => e
|
168
172
|
return aargh("Size estimate style #{style} node #{label} error #{e}", false)
|
169
173
|
end
|
@@ -172,20 +176,20 @@ def estimate_sizes(doc, template, ckd2count, defaults)
|
|
172
176
|
true
|
173
177
|
end
|
174
178
|
|
175
|
-
def maxima(doc
|
179
|
+
def maxima(doc)
|
176
180
|
xmax = Hash.new(0)
|
177
181
|
ymax = Hash.new(0)
|
178
182
|
doc.fetch('nodes', []).each do |node|
|
179
|
-
xmax[node['xo']] = [ node[
|
180
|
-
ymax[node['yo']] = [ node[
|
183
|
+
xmax[node['xo']] = [ node['w'], xmax[node['xo']] ].max
|
184
|
+
ymax[node['yo']] = [ node['h'], ymax[node['yo']] ].max
|
181
185
|
end
|
182
186
|
[ xmax, ymax ]
|
183
187
|
end
|
184
188
|
|
185
|
-
def apply_maxima(doc, xmax, ymax
|
189
|
+
def apply_maxima(doc, xmax, ymax)
|
186
190
|
doc.fetch('nodes', []).each do |node|
|
187
|
-
node[
|
188
|
-
node[
|
191
|
+
node['w'] = xmax[node['xo']]
|
192
|
+
node['h'] = ymax[node['yo']]
|
189
193
|
end
|
190
194
|
end
|
191
195
|
|
@@ -200,8 +204,8 @@ def parallel_edge_step_minima(coords)
|
|
200
204
|
c2m
|
201
205
|
end
|
202
206
|
|
203
|
-
def remap_coordinates(coords, cmax, c2min,
|
204
|
-
c =
|
207
|
+
def remap_coordinates(coords, cmax, c2min, edge_gap)
|
208
|
+
c = edge_gap
|
205
209
|
gap = 0 # How much space all edge segments need.
|
206
210
|
zero_after_decrease = false
|
207
211
|
prev_dir = -2
|
@@ -210,19 +214,19 @@ def remap_coordinates(coords, cmax, c2min, defaults)
|
|
210
214
|
case coord.direction
|
211
215
|
when -1
|
212
216
|
c += gap if -1 < prev_dir
|
213
|
-
gap =
|
217
|
+
gap = edge_gap
|
214
218
|
coord.object[coord.key] = c
|
215
219
|
when 0
|
216
|
-
gap =
|
220
|
+
gap = edge_gap / c2min[coord.integer]
|
217
221
|
if zero_after_decrease
|
218
222
|
# Edge segment is at same range as nodes.
|
219
223
|
coord.object[coord.key] = c + coord.fraction * cmax[coord.integer]
|
220
224
|
else
|
221
225
|
coord.object[coord.key] =
|
222
|
-
c + (
|
226
|
+
c + (edge_gap * coord.fraction) / c2min[coord.integer]
|
223
227
|
end
|
224
228
|
when 1
|
225
|
-
gap =
|
229
|
+
gap = edge_gap
|
226
230
|
c += cmax[coord.integer] unless prev_dir == 1
|
227
231
|
coord.object[coord.key] = c
|
228
232
|
zero_after_decrease = false
|
@@ -232,28 +236,23 @@ def remap_coordinates(coords, cmax, c2min, defaults)
|
|
232
236
|
end
|
233
237
|
|
234
238
|
class Render
|
235
|
-
attr_accessor :doc, :template
|
239
|
+
attr_accessor :doc, :template
|
236
240
|
|
237
|
-
def initialize(doc, template
|
241
|
+
def initialize(doc, template)
|
238
242
|
@doc = doc
|
239
243
|
@template = template
|
240
|
-
@defaults = defaults
|
241
244
|
end
|
242
245
|
|
243
|
-
def
|
246
|
+
def exposed_binding
|
244
247
|
binding
|
245
248
|
end
|
246
249
|
|
247
|
-
def get_default(key, default_value = nil)
|
248
|
-
@template.fetch('defaults', {}).fetch(key, default_value)
|
249
|
-
end
|
250
|
-
|
251
250
|
def dimensions
|
252
251
|
w = 0
|
253
252
|
h = 0
|
254
253
|
@doc.fetch('nodes', []).each do |node|
|
255
|
-
w = [ w, node['xo'] + node[
|
256
|
-
h = [ h, node['yo'] + node[
|
254
|
+
w = [ w, node['xo'] + node['w'] ].max
|
255
|
+
h = [ h, node['yo'] + node['h'] ].max
|
257
256
|
end
|
258
257
|
@doc.fetch('edges', []).each do |edge|
|
259
258
|
path = edge.fetch('path', nil)
|
@@ -267,9 +266,9 @@ class Render
|
|
267
266
|
end
|
268
267
|
end
|
269
268
|
|
270
|
-
def apply(doc, template
|
271
|
-
$render = Render.new(doc, template
|
272
|
-
out = ERB.new(template.fetch('template', '')).result($render.
|
269
|
+
def apply(doc, template)
|
270
|
+
$render = Render.new(doc, template)
|
271
|
+
out = ERB.new(template.fetch('template', '')).result($render.exposed_binding)
|
273
272
|
$render = nil
|
274
273
|
out
|
275
274
|
end
|
@@ -278,6 +277,7 @@ def main
|
|
278
277
|
template = nil
|
279
278
|
input = nil
|
280
279
|
output = nil
|
280
|
+
styles = nil
|
281
281
|
parser = OptionParser.new do |opts|
|
282
282
|
opts.summary_indent = ' '
|
283
283
|
opts.summary_width = 20
|
@@ -317,29 +317,37 @@ Output is the file produced by the erb-template.
|
|
317
317
|
return aargh("Key #{key} base-64 decoding failed to key #{nk}", 2)
|
318
318
|
end
|
319
319
|
end
|
320
|
-
defaults = Defaults.new(template)
|
321
320
|
|
322
321
|
doc = load_source(input)
|
323
322
|
return 2 if doc.nil?
|
324
323
|
|
324
|
+
styles = Styles.new(template.fetch('styles', {}), doc.fetch('styles', {}))
|
325
|
+
doc.fetch('nodes', []).each do |node|
|
326
|
+
styles.apply_node_styles(node)
|
327
|
+
node['text'] = node.fetch('text', node.fetch('label', '')).split("\n")
|
328
|
+
end
|
329
|
+
doc.fetch('edges', []).each { |edge| styles.apply_edge_styles(edge) }
|
330
|
+
doc['diagram'] = {} unless doc.key? 'diagram'
|
331
|
+
styles.apply_diagram_styles(doc['diagram'])
|
332
|
+
|
325
333
|
begin
|
326
334
|
xcoords, ycoords, ckd2count = separate_coordinates(doc)
|
327
335
|
rescue StandardError
|
328
336
|
return aargh('Error processing input.', 3)
|
329
337
|
end
|
330
338
|
|
331
|
-
return 4 unless estimate_sizes(doc,
|
339
|
+
return 4 unless estimate_sizes(doc, ckd2count)
|
332
340
|
|
333
341
|
# Make all rows the same height and all columns the same width.
|
334
|
-
xmax, ymax = maxima(doc
|
335
|
-
apply_maxima(doc, xmax, ymax
|
342
|
+
xmax, ymax = maxima(doc)
|
343
|
+
apply_maxima(doc, xmax, ymax)
|
336
344
|
|
337
345
|
x2min = parallel_edge_step_minima(xcoords)
|
338
346
|
y2min = parallel_edge_step_minima(ycoords)
|
339
|
-
remap_coordinates(xcoords, xmax, x2min,
|
340
|
-
remap_coordinates(ycoords, ymax, y2min,
|
347
|
+
remap_coordinates(xcoords, xmax, x2min, doc.dig('diagram', 'edge_gap'))
|
348
|
+
remap_coordinates(ycoords, ymax, y2min, doc.dig('diagram', 'edge_gap'))
|
341
349
|
|
342
|
-
dump_result(output, apply(doc, template
|
350
|
+
dump_result(output, apply(doc, template), 5)
|
343
351
|
end
|
344
352
|
|
345
353
|
exit(main) if (defined? $unit_test).nil?
|
data/bin/diagrammatron-template
CHANGED
data/template/internal.yaml
CHANGED
@@ -1,21 +1,32 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
2
|
+
styles:
|
3
|
+
diagram:
|
4
|
+
default:
|
5
|
+
edge_gap: 20
|
6
|
+
width_margin: 10
|
7
|
+
height_margin: 10
|
8
|
+
node:
|
9
|
+
default:
|
10
|
+
width_margin: 10
|
11
|
+
height_margin: 10
|
12
|
+
font_size: 16
|
13
|
+
font_ascend: 0.8
|
14
|
+
font_line_spacing: 0.2
|
15
|
+
font_height: 1
|
16
|
+
font_width: 0.5
|
17
|
+
font_fill: "#000000"
|
18
|
+
url_fill: "#000000"
|
19
|
+
fill: "#ffffff"
|
20
|
+
stroke: "#000000"
|
21
|
+
stroke_width: 2
|
22
|
+
size_estimator: |
|
23
|
+
$render.default_size($render.node['font_size'],
|
24
|
+
$render.node['font_width'], $render.node['font_height'],
|
25
|
+
$render.node['font_line_spacing'],
|
26
|
+
$render.node['width_margin'], $render.node['height_margin'],
|
27
|
+
$render.doc['diagram']['edge_gap'])
|
28
|
+
edge:
|
29
|
+
default:
|
30
|
+
stroke_width: 2
|
31
|
+
stroke: "#000000"
|
32
|
+
base64template: PD94bWwgdmVyc2lvbj0iMS4wIj8+CjwlPQp3LCBoaCA9ICRyZW5kZXIuZGltZW5zaW9ucwpoaCArPSAkcmVuZGVyLmRvYy5kaWcoJ2RpYWdyYW0nLCAnaGVpZ2h0X21hcmdpbicpCgpvdXQgPSBbCiAgJSg8c3ZnIHdpZHRoPSIje3cgKyAkcmVuZGVyLmRvYy5kaWcoJ2RpYWdyYW0nLCAnd2lkdGhfbWFyZ2luJyl9IiBoZWlnaHQ9IiN7aGh9IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiPikKXQokcmVuZGVyLmRvYy5mZXRjaCgnbm9kZXMnLCBbXSkuZWFjaCBkbyB8bm9kZXwKICB3ID0gbm9kZVsndyddLnRvX2kKICBoID0gbm9kZVsnaCddLnRvX2kKICB4ID0gbm9kZVsneG8nXS50b19pCiAgeSA9IGhoIC0gbm9kZVsneW8nXS50b19pIC0gaAogIG5vZGVzdHlsZSA9ICUoZmlsbD0iI3tub2RlWydmaWxsJ119IiBzdHJva2U9IiN7bm9kZVsnc3Ryb2tlJ119IiBzdHJva2Utd2lkdGg9IiN7bm9kZVsnc3Ryb2tlX3dpZHRoJ119IikKICBvdXQucHVzaCglKDxyZWN0ICN7bm9kZXN0eWxlfSBoZWlnaHQ9IiN7aH0iIHdpZHRoPSIje3d9IiB4PSIje3h9IiB5PSIje3l9Ii8+KSkKICB4ICs9IG5vZGVbJ3dpZHRoX21hcmdpbiddCiAgZnMgPSBub2RlWydmb250X3NpemUnXQogIGxoID0gZnMgKiAoMSArIG5vZGVbJ2ZvbnRfbGluZV9zcGFjaW5nJ10pCiAgeSArPSBub2RlWydoZWlnaHRfbWFyZ2luJ10gKyBmcyAqIG5vZGVbJ2ZvbnRfYXNjZW5kJ10gIyBCYXNlbGluZSBmb3IgZmlyc3QgbGluZS4KICB1cmwgPSBub2RlLmZldGNoKCd1cmwnLCBuaWwpCiAgdXJsLmVuY29kZSEoOnhtbCA9PiA6YXR0cikgdW5sZXNzIHVybC5uaWw/CiAgeTAgPSB5CiAgdGV4dHN0eWxlID0gJShmaWxsPSIje25vZGVbJ2ZvbnRfZmlsbCddfSIgZm9udC1mYW1pbHk9InNlcmlmIiBmb250LXNpemU9IiN7ZnN9IiBzdHJva2U9IiN7bm9kZVsnZm9udF9maWxsJ119IiBzdHJva2Utd2lkdGg9IjAiIHhtbDpzcGFjZT0icHJlc2VydmUiKQogIGxpbmtzdHlsZSA9ICUoZmlsbD0iI3tub2RlWyd1cmxfZmlsbCddfSIgZm9udC1mYW1pbHk9InNlcmlmIiBmb250LXNpemU9IiN7ZnN9IiBzdHJva2U9IiN7bm9kZVsndXJsX2ZpbGwnXX0iIHN0cm9rZS13aWR0aD0iMCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIpCiAgbm9kZVsndGV4dCddLmVhY2ggZG8gfGxpbmV8CiAgICBsaW5lLmVuY29kZSEoOnhtbCA9PiA6dGV4dCkKICAgIGlmIHVybC5uaWw/CiAgICAgIG91dC5wdXNoKCUoPHRleHQgI3t0ZXh0c3R5bGV9IHg9IiN7eH0iIHk9IiN7eTB9Ij4je2xpbmV9PC90ZXh0PikpCiAgICBlbHNlCiAgICAgIG91dC5wdXNoKCUoPGEgeGxpbms6aHJlZj0je3VybH0gdGFyZ2V0PSJfcGFyZW50Ij48dGV4dCAje2xpbmtzdHlsZX0geD0iI3t4fSIgeT0iI3t5MH0iPiN7bGluZX08L3RleHQ+PC9hPikpCiAgICBlbmQKICAgIHkwICs9IGxoICMgU2hpZnQgYmFzZWxpbmUgYnkgZnVsbCBsaW5lICsgc3BhY2luZyBoZWlnaHQuCiAgZW5kCmVuZAokcmVuZGVyLmRvYy5mZXRjaCgnZWRnZXMnLCBbXSkuZWFjaCBkbyB8ZWRnZXwKICBsaW5lc3R5bGUgPSAlKGZpbGw9Im5vbmUiIHN0cm9rZT0iI3tlZGdlWydzdHJva2UnXX0iIHN0cm9rZS13aWR0aD0iI3tlZGdlWydzdHJva2Vfd2lkdGgnXX0iKQogIHBhdGggPSBlZGdlLmZldGNoKCdwYXRoJywgbmlsKQogIG5leHQgaWYgcGF0aC5uaWw/CiAgcGF0aC5lYWNoIGRvIHxwfAogICAgcFsneG8nXSA9IHBbJ3hvJ10udG9faS50b19zCiAgICBwWyd5byddID0gKGhoIC0gcFsneW8nXSkudG9faS50b19zCiAgZW5kCiAgaWYgcGF0aC5zaXplID09IDIKICAgIG91dC5wdXNoKCUoPGxpbmUgI3tsaW5lc3R5bGV9IHgxPSIje3BhdGhbMF1bJ3hvJ119IiB4Mj0iI3twYXRoWzFdWyd4byddfSIgeTE9IiN7cGF0aFswXVsneW8nXX0iIHkyPSIje3BhdGhbMV1bJ3lvJ119Ii8+KSkKICBlbHNlCiAgICBwdHMgPSBwYXRoLm1hcCB7IHxwfCAiI3twWyd4byddfSwje3BbJ3lvJ119IiB9CiAgICBvdXQucHVzaCglKDxwb2x5bGluZSAje2xpbmVzdHlsZX0gcG9pbnRzPSIje3B0cy5qb2luKCcgJyl9Ii8+KSkKICBlbmQKZW5kCm91dC5qb2luKCJcbiIpCiU+Cjwvc3ZnPgo=
|
data/template/root.yaml
CHANGED
@@ -1,19 +1,30 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
1
|
+
styles:
|
2
|
+
diagram:
|
3
|
+
default:
|
4
|
+
edge_gap: 20
|
5
|
+
width_margin: 10
|
6
|
+
height_margin: 10
|
7
|
+
node:
|
8
|
+
default:
|
9
|
+
width_margin: 10
|
10
|
+
height_margin: 10
|
11
|
+
font_size: 16
|
12
|
+
font_ascend: 0.8
|
13
|
+
font_line_spacing: 0.2
|
14
|
+
font_height: 1
|
15
|
+
font_width: 0.5
|
16
|
+
font_fill: "#000000"
|
17
|
+
url_fill: "#000000"
|
18
|
+
fill: "#ffffff"
|
19
|
+
stroke: "#000000"
|
20
|
+
stroke_width: 2
|
21
|
+
size_estimator: |
|
22
|
+
$render.default_size($render.node['font_size'],
|
23
|
+
$render.node['font_width'], $render.node['font_height'],
|
24
|
+
$render.node['font_line_spacing'],
|
25
|
+
$render.node['width_margin'], $render.node['height_margin'],
|
26
|
+
$render.doc['diagram']['edge_gap'])
|
27
|
+
edge:
|
28
|
+
default:
|
29
|
+
stroke_width: 2
|
30
|
+
stroke: "#000000"
|
data/template/svg_1.1.erb
CHANGED
@@ -1,33 +1,28 @@
|
|
1
1
|
<?xml version="1.0"?>
|
2
2
|
<%=
|
3
3
|
w, hh = $render.dimensions
|
4
|
-
|
5
|
-
hh += hm
|
6
|
-
ma = $render.defaults.font.max_ascend
|
7
|
-
fs = $render.defaults.font.size
|
8
|
-
lh = fs + $render.defaults.font.line_spacing
|
9
|
-
wm = $render.defaults.width_margin
|
10
|
-
sw = $render.get_default('stroke_width', 5)
|
11
|
-
linestyle = %(fill="none" stroke="#000000" stroke-width="#{sw}")
|
12
|
-
textstyle = %(fill="#000000" font-family="serif" font-size="#{fs}" stroke="#000000" stroke-width="0" xml:space="preserve")
|
13
|
-
linkstyle = %(fill="#2020ff" font-family="serif" font-size="#{fs}" stroke="#2020ff" stroke-width="0" xml:space="preserve")
|
4
|
+
hh += $render.doc.dig('diagram', 'height_margin')
|
14
5
|
|
15
6
|
out = [
|
16
|
-
%(<svg width="#{w + $render.
|
7
|
+
%(<svg width="#{w + $render.doc.dig('diagram', 'width_margin')}" height="#{hh}" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">)
|
17
8
|
]
|
18
9
|
$render.doc.fetch('nodes', []).each do |node|
|
19
|
-
w = node[
|
20
|
-
h = node[
|
10
|
+
w = node['w'].to_i
|
11
|
+
h = node['h'].to_i
|
21
12
|
x = node['xo'].to_i
|
22
13
|
y = hh - node['yo'].to_i - h
|
23
|
-
nodestyle = %(fill="#{node
|
14
|
+
nodestyle = %(fill="#{node['fill']}" stroke="#{node['stroke']}" stroke-width="#{node['stroke_width']}")
|
24
15
|
out.push(%(<rect #{nodestyle} height="#{h}" width="#{w}" x="#{x}" y="#{y}"/>))
|
25
|
-
x +=
|
26
|
-
|
16
|
+
x += node['width_margin']
|
17
|
+
fs = node['font_size']
|
18
|
+
lh = fs * (1 + node['font_line_spacing'])
|
19
|
+
y += node['height_margin'] + fs * node['font_ascend'] # Baseline for first line.
|
27
20
|
url = node.fetch('url', nil)
|
28
21
|
url.encode!(:xml => :attr) unless url.nil?
|
29
22
|
y0 = y
|
30
|
-
node['
|
23
|
+
textstyle = %(fill="#{node['font_fill']}" font-family="serif" font-size="#{fs}" stroke="#{node['font_fill']}" stroke-width="0" xml:space="preserve")
|
24
|
+
linkstyle = %(fill="#{node['url_fill']}" font-family="serif" font-size="#{fs}" stroke="#{node['url_fill']}" stroke-width="0" xml:space="preserve")
|
25
|
+
node['text'].each do |line|
|
31
26
|
line.encode!(:xml => :text)
|
32
27
|
if url.nil?
|
33
28
|
out.push(%(<text #{textstyle} x="#{x}" y="#{y0}">#{line}</text>))
|
@@ -38,6 +33,7 @@ $render.doc.fetch('nodes', []).each do |node|
|
|
38
33
|
end
|
39
34
|
end
|
40
35
|
$render.doc.fetch('edges', []).each do |edge|
|
36
|
+
linestyle = %(fill="none" stroke="#{edge['stroke']}" stroke-width="#{edge['stroke_width']}")
|
41
37
|
path = edge.fetch('path', nil)
|
42
38
|
next if path.nil?
|
43
39
|
path.each do |p|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diagrammatron
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ismo Kärkkäinen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
|