diagrammatron 0.3.0 → 0.4.1
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 +101 -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: f620125fb02691c986e3f340cc3248fbab02ecc99cd075ed9b0a9dfd2159a541
|
4
|
+
data.tar.gz: b384432f4b082379eedce471810cd8ad011bc1e016dc5410c00b99e3b1187b70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edd885e247f16df32a67c6524cbfabfddaf9c6901fce6104c373fb6c7bd107c734fb275be133c09f06f128bade0bd34e9c626f87e14e097b580817a970277020
|
7
|
+
data.tar.gz: 3b7febb11202fc30fd0ba522d276e6f41ab4c25d72b2bf468c11ce8bd4b7f3c27001e7520e3e68705518e4b5646bb6708152ab766059cbd6b646f10b703e272c
|
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,101 @@ 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
|
+
found = false
|
102
|
+
styles.each do |name|
|
103
|
+
ns = mapping.fetch(name, nil)
|
104
|
+
next if ns.nil?
|
105
|
+
found = true
|
106
|
+
s.merge! ns
|
107
|
+
end
|
108
|
+
s.merge!(mapping['default']) unless found # Merge default at least.
|
109
|
+
# Keep values specified explicitly.
|
110
|
+
item.merge!(s) {|key, existing, from_template| existing }
|
111
|
+
end
|
112
|
+
|
113
|
+
def apply_node_styles(node)
|
114
|
+
fill(@n, 'node', node)
|
91
115
|
end
|
92
|
-
end
|
93
116
|
|
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)
|
117
|
+
def apply_edge_styles(edge)
|
118
|
+
fill(@e, 'edge', edge)
|
119
|
+
end
|
120
|
+
|
121
|
+
def apply_diagram_styles(diagram)
|
122
|
+
fill(@d, 'diagram', diagram)
|
106
123
|
end
|
107
124
|
end
|
108
125
|
|
109
126
|
class SizeEstimation
|
110
|
-
attr_accessor :node, :
|
127
|
+
attr_accessor :node, :ckd2count, :doc
|
111
128
|
|
112
|
-
def initialize(
|
129
|
+
def initialize(ckd2count, doc)
|
113
130
|
@node = nil
|
114
|
-
@template = template
|
115
131
|
@ckd2count = ckd2count
|
116
|
-
@
|
132
|
+
@doc = doc
|
117
133
|
end
|
118
134
|
|
119
|
-
def
|
135
|
+
def exposed_binding
|
120
136
|
binding
|
121
137
|
end
|
122
138
|
|
123
|
-
def get_default(key, default_value = nil)
|
124
|
-
@template.fetch('defaults', {}).fetch(key, default_value)
|
125
|
-
end
|
126
|
-
|
127
139
|
def max_edges(key, edge_gap)
|
128
140
|
c = [ @node['xo'], @node['yo'], key, -1 ]
|
129
141
|
count = @ckd2count[c]
|
130
142
|
c[3] = 1
|
131
143
|
count = [ count, @ckd2count[c] ].max
|
132
144
|
return 0 if count < 2
|
133
|
-
(count - 1) *
|
145
|
+
(count - 1) * edge_gap
|
134
146
|
end
|
135
147
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
148
|
+
# font_size is the actual size.
|
149
|
+
# font_width, font_height, font_line_spacing are [0, 1] size scaling factors.
|
150
|
+
# width_margin, height_margin are in same units as fonti, space inside node.
|
151
|
+
# edge_gap is minimum space between edges at any node side.
|
152
|
+
def default_size(font_size, font_width, font_height, font_line_spacing,
|
153
|
+
width_margin, height_margin, edge_gap)
|
154
|
+
lines = @node['text']
|
155
|
+
w = 2 * width_margin + font_width * font_size * (lines.map &(:size)).max
|
156
|
+
@node['w'] = [ w, max_edges('xo', edge_gap) ].max
|
157
|
+
h = 2 * height_margin + font_height * font_size * lines.size +
|
158
|
+
font_line_spacing * font_size * (lines.size - 1)
|
159
|
+
@node['h'] = [ h, max_edges('yo', edge_gap) ].max
|
146
160
|
end
|
147
161
|
end
|
148
162
|
|
149
|
-
def estimate_sizes(doc,
|
150
|
-
$render = SizeEstimation.new(
|
151
|
-
sizes = template.fetch('sizes', {})
|
152
|
-
defaults = template.fetch('defaults', {})
|
163
|
+
def estimate_sizes(doc, ckd2count)
|
164
|
+
$render = SizeEstimation.new(ckd2count, doc)
|
153
165
|
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
166
|
$render.node = node
|
167
|
+
label = node.fetch('label', 'unnamed')
|
158
168
|
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
|
169
|
+
code = node.fetch('size_estimator',
|
170
|
+
%(raise NotImplementedError, "No size estimator for style: #{style}"))
|
164
171
|
code = code.join("\n") if code.is_a? Array
|
165
172
|
begin
|
166
|
-
eval(code, $render.
|
173
|
+
eval(code, $render.exposed_binding)
|
167
174
|
rescue StandardError => e
|
168
175
|
return aargh("Size estimate style #{style} node #{label} error #{e}", false)
|
169
176
|
end
|
@@ -172,20 +179,20 @@ def estimate_sizes(doc, template, ckd2count, defaults)
|
|
172
179
|
true
|
173
180
|
end
|
174
181
|
|
175
|
-
def maxima(doc
|
182
|
+
def maxima(doc)
|
176
183
|
xmax = Hash.new(0)
|
177
184
|
ymax = Hash.new(0)
|
178
185
|
doc.fetch('nodes', []).each do |node|
|
179
|
-
xmax[node['xo']] = [ node[
|
180
|
-
ymax[node['yo']] = [ node[
|
186
|
+
xmax[node['xo']] = [ node['w'], xmax[node['xo']] ].max
|
187
|
+
ymax[node['yo']] = [ node['h'], ymax[node['yo']] ].max
|
181
188
|
end
|
182
189
|
[ xmax, ymax ]
|
183
190
|
end
|
184
191
|
|
185
|
-
def apply_maxima(doc, xmax, ymax
|
192
|
+
def apply_maxima(doc, xmax, ymax)
|
186
193
|
doc.fetch('nodes', []).each do |node|
|
187
|
-
node[
|
188
|
-
node[
|
194
|
+
node['w'] = xmax[node['xo']]
|
195
|
+
node['h'] = ymax[node['yo']]
|
189
196
|
end
|
190
197
|
end
|
191
198
|
|
@@ -200,8 +207,8 @@ def parallel_edge_step_minima(coords)
|
|
200
207
|
c2m
|
201
208
|
end
|
202
209
|
|
203
|
-
def remap_coordinates(coords, cmax, c2min,
|
204
|
-
c =
|
210
|
+
def remap_coordinates(coords, cmax, c2min, edge_gap)
|
211
|
+
c = edge_gap
|
205
212
|
gap = 0 # How much space all edge segments need.
|
206
213
|
zero_after_decrease = false
|
207
214
|
prev_dir = -2
|
@@ -210,19 +217,19 @@ def remap_coordinates(coords, cmax, c2min, defaults)
|
|
210
217
|
case coord.direction
|
211
218
|
when -1
|
212
219
|
c += gap if -1 < prev_dir
|
213
|
-
gap =
|
220
|
+
gap = edge_gap
|
214
221
|
coord.object[coord.key] = c
|
215
222
|
when 0
|
216
|
-
gap =
|
223
|
+
gap = edge_gap / c2min[coord.integer]
|
217
224
|
if zero_after_decrease
|
218
225
|
# Edge segment is at same range as nodes.
|
219
226
|
coord.object[coord.key] = c + coord.fraction * cmax[coord.integer]
|
220
227
|
else
|
221
228
|
coord.object[coord.key] =
|
222
|
-
c + (
|
229
|
+
c + (edge_gap * coord.fraction) / c2min[coord.integer]
|
223
230
|
end
|
224
231
|
when 1
|
225
|
-
gap =
|
232
|
+
gap = edge_gap
|
226
233
|
c += cmax[coord.integer] unless prev_dir == 1
|
227
234
|
coord.object[coord.key] = c
|
228
235
|
zero_after_decrease = false
|
@@ -232,28 +239,23 @@ def remap_coordinates(coords, cmax, c2min, defaults)
|
|
232
239
|
end
|
233
240
|
|
234
241
|
class Render
|
235
|
-
attr_accessor :doc, :template
|
242
|
+
attr_accessor :doc, :template
|
236
243
|
|
237
|
-
def initialize(doc, template
|
244
|
+
def initialize(doc, template)
|
238
245
|
@doc = doc
|
239
246
|
@template = template
|
240
|
-
@defaults = defaults
|
241
247
|
end
|
242
248
|
|
243
|
-
def
|
249
|
+
def exposed_binding
|
244
250
|
binding
|
245
251
|
end
|
246
252
|
|
247
|
-
def get_default(key, default_value = nil)
|
248
|
-
@template.fetch('defaults', {}).fetch(key, default_value)
|
249
|
-
end
|
250
|
-
|
251
253
|
def dimensions
|
252
254
|
w = 0
|
253
255
|
h = 0
|
254
256
|
@doc.fetch('nodes', []).each do |node|
|
255
|
-
w = [ w, node['xo'] + node[
|
256
|
-
h = [ h, node['yo'] + node[
|
257
|
+
w = [ w, node['xo'] + node['w'] ].max
|
258
|
+
h = [ h, node['yo'] + node['h'] ].max
|
257
259
|
end
|
258
260
|
@doc.fetch('edges', []).each do |edge|
|
259
261
|
path = edge.fetch('path', nil)
|
@@ -267,9 +269,9 @@ class Render
|
|
267
269
|
end
|
268
270
|
end
|
269
271
|
|
270
|
-
def apply(doc, template
|
271
|
-
$render = Render.new(doc, template
|
272
|
-
out = ERB.new(template.fetch('template', '')).result($render.
|
272
|
+
def apply(doc, template)
|
273
|
+
$render = Render.new(doc, template)
|
274
|
+
out = ERB.new(template.fetch('template', '')).result($render.exposed_binding)
|
273
275
|
$render = nil
|
274
276
|
out
|
275
277
|
end
|
@@ -278,6 +280,7 @@ def main
|
|
278
280
|
template = nil
|
279
281
|
input = nil
|
280
282
|
output = nil
|
283
|
+
styles = nil
|
281
284
|
parser = OptionParser.new do |opts|
|
282
285
|
opts.summary_indent = ' '
|
283
286
|
opts.summary_width = 20
|
@@ -317,29 +320,37 @@ Output is the file produced by the erb-template.
|
|
317
320
|
return aargh("Key #{key} base-64 decoding failed to key #{nk}", 2)
|
318
321
|
end
|
319
322
|
end
|
320
|
-
defaults = Defaults.new(template)
|
321
323
|
|
322
324
|
doc = load_source(input)
|
323
325
|
return 2 if doc.nil?
|
324
326
|
|
327
|
+
styles = Styles.new(template.fetch('styles', {}), doc.fetch('styles', {}))
|
328
|
+
doc.fetch('nodes', []).each do |node|
|
329
|
+
styles.apply_node_styles(node)
|
330
|
+
node['text'] = node.fetch('text', node.fetch('label', '')).split("\n")
|
331
|
+
end
|
332
|
+
doc.fetch('edges', []).each { |edge| styles.apply_edge_styles(edge) }
|
333
|
+
doc['diagram'] = {} unless doc.key? 'diagram'
|
334
|
+
styles.apply_diagram_styles(doc['diagram'])
|
335
|
+
|
325
336
|
begin
|
326
337
|
xcoords, ycoords, ckd2count = separate_coordinates(doc)
|
327
338
|
rescue StandardError
|
328
339
|
return aargh('Error processing input.', 3)
|
329
340
|
end
|
330
341
|
|
331
|
-
return 4 unless estimate_sizes(doc,
|
342
|
+
return 4 unless estimate_sizes(doc, ckd2count)
|
332
343
|
|
333
344
|
# Make all rows the same height and all columns the same width.
|
334
|
-
xmax, ymax = maxima(doc
|
335
|
-
apply_maxima(doc, xmax, ymax
|
345
|
+
xmax, ymax = maxima(doc)
|
346
|
+
apply_maxima(doc, xmax, ymax)
|
336
347
|
|
337
348
|
x2min = parallel_edge_step_minima(xcoords)
|
338
349
|
y2min = parallel_edge_step_minima(ycoords)
|
339
|
-
remap_coordinates(xcoords, xmax, x2min,
|
340
|
-
remap_coordinates(ycoords, ymax, y2min,
|
350
|
+
remap_coordinates(xcoords, xmax, x2min, doc.dig('diagram', 'edge_gap'))
|
351
|
+
remap_coordinates(ycoords, ymax, y2min, doc.dig('diagram', 'edge_gap'))
|
341
352
|
|
342
|
-
dump_result(output, apply(doc, template
|
353
|
+
dump_result(output, apply(doc, template), 5)
|
343
354
|
end
|
344
355
|
|
345
356
|
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.1
|
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
|
|