diagrammatron 0.4.2 → 0.5.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 +100 -136
- data/bin/diagrammatron-get +4 -5
- data/bin/diagrammatron-nodes +18 -40
- data/bin/diagrammatron-place +20 -21
- data/bin/diagrammatron-prune +15 -7
- data/bin/diagrammatron-render +65 -17
- data/bin/diagrammatron-schema +107 -0
- data/bin/diagrammatron-subset +188 -0
- data/bin/dot_json2diagrammatron +14 -25
- data/lib/common.rb +50 -2
- data/lib/edges.yaml +45 -0
- data/lib/nodes.yaml +36 -0
- data/lib/place.yaml +65 -0
- data/lib/render.yaml +111 -0
- data/lib/subset.yaml +55 -0
- data/template/internal.yaml +3 -1
- data/template/root.yaml +2 -0
- data/template/svg_1.1.erb +39 -36
- metadata +36 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c51dc3c8502bd1626d2a78bccd7e772e60fb25a26e6327879f42144849db598
|
4
|
+
data.tar.gz: 78548d0ad0af86031afecb81bf4fedca42da58420d0de3a0d5063f07cb68538e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03d48ecd6c7e586007dcb470655fff6814e21d32f1921dcb828b35602bea208e48f58d9ba2ea5cdf5f16fb8f633a242a9d43a599c21fbf61d5ffaa631282c827
|
7
|
+
data.tar.gz: 5ed263d065a49d69a031fa140ac0159d58301da26f4b51807573e5b8eefbeca6238301f6953cca5c8b44423a83fec18903a7e9d960a5b3fcf1d4702f1b671233
|
data/bin/diagrammatron-edges
CHANGED
@@ -6,54 +6,32 @@
|
|
6
6
|
|
7
7
|
require_relative '../lib/common'
|
8
8
|
require 'optparse'
|
9
|
-
require 'yaml'
|
10
9
|
require 'set'
|
11
|
-
require 'pathname'
|
12
10
|
|
13
11
|
|
14
12
|
def work_copy(src, quiet)
|
15
13
|
work = { edges: {}, nodes: [] }
|
16
|
-
# Expected nodes, edges. Other pass-through.
|
17
14
|
label2idx = {}
|
18
15
|
errors = false
|
19
16
|
edge_nodes = Set.new
|
20
|
-
edges = src
|
21
|
-
unedges = []
|
17
|
+
edges = src['edges']
|
22
18
|
selfedges = []
|
23
19
|
edges.each_index do |k|
|
24
20
|
edge = edges[k]
|
25
|
-
labels = edge
|
26
|
-
if labels.
|
27
|
-
|
28
|
-
elsif labels.size == 2
|
29
|
-
if labels.first == labels.last
|
30
|
-
selfedges.push(k)
|
31
|
-
else
|
32
|
-
edge_nodes.add labels.first
|
33
|
-
edge_nodes.add labels.last
|
34
|
-
work[:edges][k] = { idx: k, between: [ labels[0], labels[1] ] }
|
35
|
-
end
|
21
|
+
labels = edge['between']
|
22
|
+
if labels.first == labels.last
|
23
|
+
selfedges.push(k)
|
36
24
|
else
|
37
|
-
|
38
|
-
|
25
|
+
edge_nodes.add labels.first
|
26
|
+
edge_nodes.add labels.last
|
27
|
+
work[:edges][k] = { idx: k, between: [ labels[0], labels[1] ] }
|
39
28
|
end
|
40
29
|
end
|
41
30
|
labeled_nodes = Set.new
|
42
|
-
|
43
|
-
nodes = src.fetch('nodes', [])
|
31
|
+
nodes = src['nodes']
|
44
32
|
subsets = {}
|
45
33
|
nodes.each_index do |k|
|
46
34
|
node = nodes[k]
|
47
|
-
unless node.key? 'sid'
|
48
|
-
aargh "Node without sid: #{node.fetch('label', k + 1)}"
|
49
|
-
errors = true
|
50
|
-
next
|
51
|
-
end
|
52
|
-
unless node.key?('xo') && node.key?('yo')
|
53
|
-
aargh "Node without xo or yo: #{node.fetch('label', k + 1)}"
|
54
|
-
errors = true
|
55
|
-
next
|
56
|
-
end
|
57
35
|
sid = node['sid']
|
58
36
|
subsets[sid] = [] unless subsets.key? sid
|
59
37
|
subsets[sid].push(k)
|
@@ -63,10 +41,6 @@ def work_copy(src, quiet)
|
|
63
41
|
xo: node['xo'] * 2, # Make room for edge coordinates.
|
64
42
|
yo: node['yo'] * 2
|
65
43
|
})
|
66
|
-
unless node.key? 'label'
|
67
|
-
unlabeled.push k
|
68
|
-
next
|
69
|
-
end
|
70
44
|
label = node['label']
|
71
45
|
if label2idx.key?(label) && edge_nodes.member?(label)
|
72
46
|
aargh "Edge-referred label used twice: #{label}"
|
@@ -80,12 +54,14 @@ def work_copy(src, quiet)
|
|
80
54
|
aargh "Edges refer to missing node labels: #{missing.to_a.join(' ')}"
|
81
55
|
errors = true
|
82
56
|
end
|
57
|
+
unless selfedges.empty?
|
58
|
+
info = selfedges.map { |k| "#{edges[k]['between'].first} (#{k})" }
|
59
|
+
aargh "Edges from node to itself: #{info.join(' ')}"
|
60
|
+
errors = true
|
61
|
+
end
|
83
62
|
return nil if errors
|
84
63
|
unused = labeled_nodes - edge_nodes
|
85
|
-
[ [ unused.to_a, 'unconnected labeled nodes' ]
|
86
|
-
[ unlabeled, 'unlabeled nodes' ],
|
87
|
-
[ selfedges, 'edges from node to itself' ],
|
88
|
-
[ unedges, 'edges without end-points' ]
|
64
|
+
[ [ unused.to_a, 'unconnected labeled nodes' ]
|
89
65
|
].each do |x|
|
90
66
|
next if quiet || x.first.empty?
|
91
67
|
aargh("Note, #{x.last}: #{x.first.join(' ')}")
|
@@ -125,7 +101,7 @@ Segment = Struct.new(:vertical, :cc, :range, :edge_index, :at_node, :segment_ind
|
|
125
101
|
node_subset.each do |n|
|
126
102
|
node = work[:nodes][n]
|
127
103
|
next unless cc == node[ck]
|
128
|
-
|
104
|
+
2.times do |k|
|
129
105
|
return true if range[i0] < node[rk] && node[rk] < range[i1]
|
130
106
|
next if at_node[k]
|
131
107
|
return true if range[k] == node[rk]
|
@@ -158,6 +134,10 @@ Segment = Struct.new(:vertical, :cc, :range, :edge_index, :at_node, :segment_ind
|
|
158
134
|
def increase?
|
159
135
|
range[0] < range[1]
|
160
136
|
end
|
137
|
+
|
138
|
+
def within(s)
|
139
|
+
s.range.min < range.min && range.max < s.range.max
|
140
|
+
end
|
161
141
|
end
|
162
142
|
|
163
143
|
def segment(x0, y0, x1, y1)
|
@@ -415,40 +395,16 @@ def path_order_at_side(a, b, conn)
|
|
415
395
|
d
|
416
396
|
end
|
417
397
|
|
418
|
-
def
|
419
|
-
d = a[
|
398
|
+
def length_order(a, b)
|
399
|
+
d = a[2].length <=> b[2].length
|
420
400
|
return d unless d.zero?
|
421
|
-
case a[1]
|
422
|
-
when 0 # Ascending on length, range minimum. =|
|
423
|
-
d = a[2].length <=> b[2].length
|
424
|
-
return d unless d.zero?
|
425
|
-
d = a[2].range.min <=> b[2].range.min
|
426
|
-
return d unless d.zero?
|
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.
|
431
|
-
return d unless d.zero?
|
432
|
-
when 2 # From top right, down, bottom left: _|-
|
433
|
-
d = a[2].range.max <=> b[2].range.max # Descend on maximum.
|
434
|
-
return -d unless d.zero?
|
435
|
-
d = a[2].length <=> b[2].length # Ascend on length.
|
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?
|
442
|
-
end
|
443
401
|
a[0].edge_index <=> b[0].edge_index
|
444
402
|
end
|
445
403
|
|
446
|
-
def
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
from_left_up_to_right.each { |x| out.push x }
|
451
|
-
out
|
404
|
+
def min_order(a, b)
|
405
|
+
d = a[2].range.min <=> b[2].range.min
|
406
|
+
return d unless d.zero?
|
407
|
+
a[0].edge_index <=> b[0].edge_index
|
452
408
|
end
|
453
409
|
|
454
410
|
def overlaps_set(c, others)
|
@@ -458,47 +414,59 @@ def overlaps_set(c, others)
|
|
458
414
|
false
|
459
415
|
end
|
460
416
|
|
461
|
-
def
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
417
|
+
def layered_order(u_shaped, z_shaped)
|
418
|
+
u_shaped.sort! { |a, b| length_order(a, b) }
|
419
|
+
z_shaped.sort! { |a, b| min_order(a, b) }
|
420
|
+
out = []
|
421
|
+
layer = []
|
422
|
+
until u_shaped.empty?
|
423
|
+
rejects = []
|
424
|
+
found = false
|
425
|
+
u_shaped.each do |a|
|
426
|
+
unless overlaps_set(a[2], layer)
|
427
|
+
found = true
|
428
|
+
# Creates crossing by putting in front of shorter segment fully within?
|
429
|
+
rejects.each do |r|
|
430
|
+
if r[2].within(a[2])
|
431
|
+
found = false
|
432
|
+
break
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
if found
|
437
|
+
layer.push a
|
438
|
+
found = false
|
439
|
+
else
|
440
|
+
rejects.push a
|
441
|
+
end
|
442
|
+
end
|
443
|
+
u_shaped = rejects
|
444
|
+
unless layer.empty?
|
445
|
+
out.concat layer
|
446
|
+
out.push nil
|
447
|
+
layer = []
|
472
448
|
end
|
473
|
-
next if fit
|
474
|
-
cands.push(cands.last + 1)
|
475
|
-
off[cands.last] = [ a ]
|
476
449
|
end
|
477
|
-
|
478
|
-
|
479
|
-
|
450
|
+
until z_shaped.empty?
|
451
|
+
rejects = []
|
452
|
+
found = false
|
453
|
+
z_shaped.each do |a|
|
454
|
+
if overlaps_set(a[2], layer)
|
455
|
+
rejects.push a
|
456
|
+
else
|
457
|
+
layer.push a
|
458
|
+
found = true
|
459
|
+
end
|
460
|
+
end
|
461
|
+
z_shaped = rejects
|
462
|
+
unless layer.empty?
|
463
|
+
out.concat layer
|
464
|
+
out.push nil
|
465
|
+
layer = []
|
480
466
|
end
|
481
467
|
end
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
def group_stacked_offsets(group)
|
486
|
-
return 0 if group.empty?
|
487
|
-
group.each_index do |k|
|
488
|
-
group[k][0].offset = k + 1
|
489
|
-
end
|
490
|
-
return group.size
|
491
|
-
%q(
|
492
|
-
# This produces narrower gaps but they may be less clear.
|
493
|
-
prev = group[0]
|
494
|
-
prev[0].offset = 1
|
495
|
-
(1...group.size).each do |k|
|
496
|
-
g = group[k]
|
497
|
-
g[0].offset = prev[0].offset + (g[2].range_overlap(prev[2]) ? 1 : 0)
|
498
|
-
prev = g
|
499
|
-
end
|
500
|
-
prev[0].offset
|
501
|
-
)
|
468
|
+
out.concat(layer) unless layer.empty?
|
469
|
+
out
|
502
470
|
end
|
503
471
|
|
504
472
|
def direct_range(paths)
|
@@ -631,36 +599,22 @@ def place_edges(work)
|
|
631
599
|
end
|
632
600
|
gaps.each_value do |direction|
|
633
601
|
direction.each_value do |gap|
|
634
|
-
#gap.sort! { |a, b| segment_order(a, b) }
|
635
602
|
gleft = gap.select { |a| a[1].zero? }
|
636
|
-
|
603
|
+
grul = gap.select { |a| a[1] == 1 }
|
604
|
+
glur = gap.select { |a| a[1] == 2 }
|
637
605
|
gright = gap.select { |a| a[1] == 3 }
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
group_minimal_offsets(gleft),
|
651
|
-
group_stacked_offsets(gmiddle),
|
652
|
-
0,
|
653
|
-
group_minimal_offsets(gright)
|
654
|
-
]
|
655
|
-
before = [ 0 ]
|
656
|
-
denominator = 1 + c[0]
|
657
|
-
(1...c.size).each do |k|
|
658
|
-
denominator += c[k]
|
659
|
-
before.push(c[k - 1] + before.last)
|
660
|
-
end
|
661
|
-
gap.each do |sg|
|
662
|
-
sg[0].offset = c[sg[1]] + 1 - sg[0].offset if sg[1] > 1
|
663
|
-
sg[0].offset = Rational(sg[0].offset + before[sg[1]], denominator)
|
606
|
+
all = layered_order(gleft, grul)
|
607
|
+
all.push nil
|
608
|
+
all.concat(layered_order(gright, glur).reverse)
|
609
|
+
# Give each rational offset using layer index + 1 and layer count + 2.
|
610
|
+
denominator = 2 + all.count(&:nil?)
|
611
|
+
layer = 1
|
612
|
+
all.each do |sg|
|
613
|
+
if sg.nil?
|
614
|
+
layer += 1
|
615
|
+
else
|
616
|
+
sg[0].offset = Rational(layer, denominator)
|
617
|
+
end
|
664
618
|
end
|
665
619
|
end
|
666
620
|
end
|
@@ -759,6 +713,8 @@ end
|
|
759
713
|
def main
|
760
714
|
input = nil
|
761
715
|
output = nil
|
716
|
+
input_schema = 'edges'
|
717
|
+
output_schema = 'place'
|
762
718
|
quiet = false
|
763
719
|
parser = OptionParser.new do |opts|
|
764
720
|
opts.summary_indent = ' '
|
@@ -779,13 +735,21 @@ def main
|
|
779
735
|
$stdout.puts %(#{opts}
|
780
736
|
|
781
737
|
Input YAML file is expected to be the output of diagrammatron-nodes.
|
738
|
+
|
739
|
+
Input YAML file schema is returned by:
|
740
|
+
diagrammatron-schema #{input_schema}
|
741
|
+
|
742
|
+
Output YAML file schema is returned by:
|
743
|
+
diagrammatron-schema #{output_schema}
|
744
|
+
|
745
|
+
There can be other fields present but they are ignored and retained.
|
782
746
|
)
|
783
747
|
exit 0
|
784
748
|
end
|
785
749
|
end
|
786
750
|
parser.parse! ARGV
|
787
751
|
|
788
|
-
doc =
|
752
|
+
doc = load_verified(input, input_schema)
|
789
753
|
return 2 if doc.nil?
|
790
754
|
|
791
755
|
begin
|
@@ -797,7 +761,7 @@ Input YAML file is expected to be the output of diagrammatron-nodes.
|
|
797
761
|
|
798
762
|
place_edges(work)
|
799
763
|
prepare_output(doc, work)
|
800
|
-
|
764
|
+
save_verified(output, doc, 4, output_schema)
|
801
765
|
end
|
802
766
|
|
803
767
|
exit(main) if (defined? $unit_test).nil?
|
data/bin/diagrammatron-get
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
# Copyright © 2021
|
4
|
+
# Copyright © 2021-2023 Ismo Kärkkäinen
|
5
5
|
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
6
|
|
7
7
|
require_relative '../lib/common'
|
8
8
|
require 'optparse'
|
9
|
-
require 'pathname'
|
10
9
|
|
11
10
|
|
12
11
|
def template(name = nil)
|
@@ -28,8 +27,8 @@ def main
|
|
28
27
|
end
|
29
28
|
opts.on('-h', '--help', 'Print this help and exit.') do
|
30
29
|
$stdout.puts %(#{opts}
|
31
|
-
This is used to easily access template files included in the gem without
|
32
|
-
to clone the original repository.
|
30
|
+
This is used to easily access template files included in the gem without
|
31
|
+
the need to clone the original repository.
|
33
32
|
|
34
33
|
Without arguments, lists all templates, template root, and content files
|
35
34
|
included in the gem.
|
@@ -41,7 +40,7 @@ Given a name of a included file, saves it to --output.
|
|
41
40
|
end
|
42
41
|
parser.parse! ARGV
|
43
42
|
|
44
|
-
if ARGV.
|
43
|
+
if ARGV.empty?
|
45
44
|
# List all files in templates directory.
|
46
45
|
Dir.entries(template).sort.each do |name|
|
47
46
|
next if name.start_with? '.'
|
data/bin/diagrammatron-nodes
CHANGED
@@ -6,9 +6,7 @@
|
|
6
6
|
|
7
7
|
require_relative '../lib/common'
|
8
8
|
require 'optparse'
|
9
|
-
require 'yaml'
|
10
9
|
require 'set'
|
11
|
-
require 'pathname'
|
12
10
|
|
13
11
|
|
14
12
|
def vertical(work)
|
@@ -139,7 +137,7 @@ def shifts(count)
|
|
139
137
|
side = (count / 2.0).ceil
|
140
138
|
side = ((side / 4) + ((side % 4).positive? ? 1 : 0)) * 4
|
141
139
|
xs = Array.new(side) { |index| Integer((index - side / 2).round) }
|
142
|
-
|
140
|
+
4.times { |k| xs.push(-xs[k]) }
|
143
141
|
ys = Array.new(xs)
|
144
142
|
xs.rotate!(side / 2 - 1)
|
145
143
|
ys.rotate!(side - 1) # First half-way to offset with xs, then like xs.
|
@@ -281,36 +279,23 @@ def work_copy(src, quiet)
|
|
281
279
|
errors = false
|
282
280
|
edge_nodes = Set.new
|
283
281
|
edges = src.fetch('edges', [])
|
284
|
-
unedges = []
|
285
282
|
selfedges = []
|
286
283
|
edges.each_index do |k|
|
287
284
|
edge = edges[k]
|
288
|
-
labels = edge
|
289
|
-
if labels.
|
290
|
-
|
291
|
-
elsif labels.size == 2
|
292
|
-
if labels.first == labels.last
|
293
|
-
selfedges.push(k)
|
294
|
-
else
|
295
|
-
edge_nodes.add labels.first
|
296
|
-
edge_nodes.add labels.last
|
297
|
-
work[:edges].push({ idx: k, between: [ labels[0], labels[1] ] })
|
298
|
-
end
|
285
|
+
labels = edge['between']
|
286
|
+
if labels.first == labels.last
|
287
|
+
selfedges.push(k)
|
299
288
|
else
|
300
|
-
|
301
|
-
|
289
|
+
edge_nodes.add labels.first
|
290
|
+
edge_nodes.add labels.last
|
291
|
+
work[:edges].push({ idx: k, between: [ labels[0], labels[1] ] })
|
302
292
|
end
|
303
293
|
end
|
304
294
|
labeled_nodes = Set.new
|
305
|
-
unlabeled = []
|
306
295
|
nodes = src.fetch('nodes', [])
|
307
296
|
nodes.each_index do |k|
|
308
297
|
work[:nodes].push({ idx: k })
|
309
298
|
node = nodes[k]
|
310
|
-
unless node.key? 'label'
|
311
|
-
unlabeled.push k
|
312
|
-
next
|
313
|
-
end
|
314
299
|
label = node['label']
|
315
300
|
if label2idx.key?(label) && edge_nodes.member?(label)
|
316
301
|
aargh "Edge-referred label used twice: #{label}"
|
@@ -327,9 +312,7 @@ def work_copy(src, quiet)
|
|
327
312
|
return nil if errors
|
328
313
|
unused = labeled_nodes - edge_nodes
|
329
314
|
[ [ unused.to_a, 'unconnected labeled nodes' ],
|
330
|
-
[
|
331
|
-
[ selfedges, 'edges from node to itself' ],
|
332
|
-
[ unedges, 'edges without end-points' ]
|
315
|
+
[ selfedges, 'edges from node to itself' ]
|
333
316
|
].each do |x|
|
334
317
|
next if quiet || x.first.empty?
|
335
318
|
aargh("Note, #{x.last}: #{x.first.join(' ')}")
|
@@ -351,6 +334,8 @@ def prepare_output(doc, work)
|
|
351
334
|
end
|
352
335
|
|
353
336
|
def main
|
337
|
+
input_schema = 'nodes'
|
338
|
+
output_schema = 'edges'
|
354
339
|
input = nil
|
355
340
|
output = nil
|
356
341
|
algo = 'pathlength'
|
@@ -378,22 +363,15 @@ def main
|
|
378
363
|
$stdout.puts %(
|
379
364
|
Algorithm names are: #{$algorithms.keys.sort.join(' ')}
|
380
365
|
|
381
|
-
Input YAML file is
|
382
|
-
|
383
|
-
|
384
|
-
- label: something
|
385
|
-
- label: another
|
386
|
-
- ignored: "Since no label. Still placed."
|
387
|
-
- label: "Unused and ok. Still placed."
|
388
|
-
edges:
|
389
|
-
- between: [ something, another ]
|
390
|
-
- between: [ something, something ] # Ignored.
|
391
|
-
- between: [ ] # Ignored.
|
392
|
-
- ignored: "Since no between."
|
393
|
-
...
|
366
|
+
Input YAML file schema is returned by:
|
367
|
+
diagrammatron-schema #{input_schema}
|
368
|
+
|
394
369
|
There can be other fields present but they are ignored. The nodes will
|
395
370
|
receive values xo and yo that indicate horizontal and vertical coordinates.
|
396
371
|
|
372
|
+
Output YAML file schema is returned by:
|
373
|
+
diagrammatron-schema #{output_schema}
|
374
|
+
|
397
375
|
Output is the input file with 'xo', 'yo' and 'sid' added to each node.
|
398
376
|
The 'xo' and 'yo' indicate which unique x- and y-coordinate the value is.
|
399
377
|
The 'sid' indicates the sub-diagram consisting of connected nodes.
|
@@ -408,7 +386,7 @@ The 'sid' indicates the sub-diagram consisting of connected nodes.
|
|
408
386
|
end
|
409
387
|
algo = $algorithms[algo]
|
410
388
|
|
411
|
-
doc =
|
389
|
+
doc = load_verified(input, input_schema)
|
412
390
|
return 2 if doc.nil?
|
413
391
|
|
414
392
|
begin
|
@@ -420,7 +398,7 @@ The 'sid' indicates the sub-diagram consisting of connected nodes.
|
|
420
398
|
|
421
399
|
algo.call(work)
|
422
400
|
prepare_output(doc, work)
|
423
|
-
|
401
|
+
save_verified(output, doc, 4, output_schema)
|
424
402
|
end
|
425
403
|
|
426
404
|
exit(main) if (defined? $unit_test).nil?
|
data/bin/diagrammatron-place
CHANGED
@@ -1,18 +1,16 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
# Copyright © 2021
|
4
|
+
# Copyright © 2021-2023 Ismo Kärkkäinen
|
5
5
|
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
6
|
|
7
7
|
require_relative '../lib/common'
|
8
8
|
require 'optparse'
|
9
|
-
require 'yaml'
|
10
9
|
require 'set'
|
11
|
-
require 'pathname'
|
12
10
|
|
13
|
-
|
11
|
+
|
12
|
+
def info(msg)
|
14
13
|
$stderr.puts(msg) unless $QUIET
|
15
|
-
return_value
|
16
14
|
end
|
17
15
|
|
18
16
|
BoundingBox = Struct.new(:xmin, :ymin, :xmax, :ymax) do
|
@@ -91,22 +89,19 @@ end
|
|
91
89
|
|
92
90
|
def work_copy(src)
|
93
91
|
work = { edges: {}, nodes: {} }
|
94
|
-
|
95
|
-
nodes = src.fetch('nodes', [])
|
92
|
+
nodes = src['nodes']
|
96
93
|
nodes.each_index do |k|
|
97
94
|
node = nodes[k]
|
98
|
-
sid = node
|
99
|
-
xo = node
|
100
|
-
yo = node
|
101
|
-
next if sid.nil? || xo.nil? || yo.nil?
|
95
|
+
sid = node['sid']
|
96
|
+
xo = node['xo']
|
97
|
+
yo = node['yo']
|
102
98
|
work[:nodes][sid] = work[:nodes].fetch(sid, []).push(Node.new(k, sid, xo, yo))
|
103
99
|
end
|
104
|
-
edges = src
|
100
|
+
edges = src['edges']
|
105
101
|
edges.each_index do |k|
|
106
102
|
edge = edges[k]
|
107
|
-
path = edge
|
108
|
-
sid = edge
|
109
|
-
next if path.nil? || sid.nil?
|
103
|
+
path = edge['path']
|
104
|
+
sid = edge['sid']
|
110
105
|
work[:edges][sid] = work[:edges].fetch(sid, []).push(Edge.new(k, sid, path))
|
111
106
|
end
|
112
107
|
work[:subsets] = work[:nodes].keys.to_set.merge(work[:edges].keys.to_set).to_a
|
@@ -140,7 +135,7 @@ def area_order(bbs)
|
|
140
135
|
end
|
141
136
|
order.sort! do |a, b|
|
142
137
|
d = area_compare(a[1], b[1])
|
143
|
-
|
138
|
+
d.zero? ? (a[0] <=> b[0]) : d
|
144
139
|
end
|
145
140
|
order
|
146
141
|
end
|
@@ -362,6 +357,7 @@ $QUIET = false
|
|
362
357
|
def main
|
363
358
|
input = nil
|
364
359
|
output = nil
|
360
|
+
input_output_schema = 'edges'
|
365
361
|
algo = 'tallwide'
|
366
362
|
parser = OptionParser.new do |opts|
|
367
363
|
opts.summary_indent = ' '
|
@@ -389,10 +385,13 @@ def main
|
|
389
385
|
$stdout.puts %(
|
390
386
|
Algorithm names are: #{$algorithms.keys.sort.join(' ')}
|
391
387
|
|
392
|
-
Input YAML file is
|
388
|
+
Input and output YAML file schema is returned by:
|
389
|
+
diagrammatron-schema #{input_output_schema}
|
390
|
+
|
391
|
+
There can be other fields present but they are ignored and retained.
|
393
392
|
|
394
|
-
Output is the input file with 'xo' and '
|
395
|
-
|
393
|
+
Output is the input file with 'xo', 'yo', and 'path' modified to remove
|
394
|
+
overlap between sub-diagrams and edges.
|
396
395
|
)
|
397
396
|
exit 0
|
398
397
|
end
|
@@ -415,7 +414,7 @@ do not overlap.
|
|
415
414
|
end
|
416
415
|
algo = $algorithms[algo]
|
417
416
|
|
418
|
-
doc =
|
417
|
+
doc = load_verified(input, input_output_schema)
|
419
418
|
return 2 if doc.nil?
|
420
419
|
|
421
420
|
begin
|
@@ -427,7 +426,7 @@ do not overlap.
|
|
427
426
|
|
428
427
|
algo.call(work)
|
429
428
|
prepare_output(doc, work)
|
430
|
-
|
429
|
+
save_verified(output, doc, 4, input_output_schema)
|
431
430
|
end
|
432
431
|
|
433
432
|
exit(main) if (defined? $unit_test).nil?
|