diagrammatron 0.4.1 → 0.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f620125fb02691c986e3f340cc3248fbab02ecc99cd075ed9b0a9dfd2159a541
4
- data.tar.gz: b384432f4b082379eedce471810cd8ad011bc1e016dc5410c00b99e3b1187b70
3
+ metadata.gz: 7f4b7974989ef344d2eaaae0c54f82567b8ed9edd9c7fb5d2dfc3e405895efa7
4
+ data.tar.gz: efdd52da27d648f9da7fed309ba668eae6237eeddd51a703c47c46c45c394435
5
5
  SHA512:
6
- metadata.gz: edd885e247f16df32a67c6524cbfabfddaf9c6901fce6104c373fb6c7bd107c734fb275be133c09f06f128bade0bd34e9c626f87e14e097b580817a970277020
7
- data.tar.gz: 3b7febb11202fc30fd0ba522d276e6f41ab4c25d72b2bf468c11ce8bd4b7f3c27001e7520e3e68705518e4b5646bb6708152ab766059cbd6b646f10b703e272c
6
+ metadata.gz: e235a20fb0fb4013e4f0006731743830f84500519d2ab4262b04caa9bae0962bd7da5127cd30e44c9e6bf891b735a02f10fe193149047fa6b54ec0bd6185f2e5
7
+ data.tar.gz: e6c4c3db889c83d23390ee874379215747fb4336fbb3f1f2f543226ab72788fe948a6394e500ea89ed4d4a013126a324b10fa5b49bf73be9233cd94e9781a024
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2021 Ismo Kärkkäinen
1
+ Copyright (c) 2021-2023 Ismo Kärkkäinen
2
2
 
3
3
  The Universal Permissive License (UPL), Version 1.0
4
4
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021, 2022 Ismo Kärkkäinen
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'
@@ -158,6 +158,10 @@ Segment = Struct.new(:vertical, :cc, :range, :edge_index, :at_node, :segment_ind
158
158
  def increase?
159
159
  range[0] < range[1]
160
160
  end
161
+
162
+ def within(s)
163
+ s.range.min < range.min && range.max < s.range.max
164
+ end
161
165
  end
162
166
 
163
167
  def segment(x0, y0, x1, y1)
@@ -415,126 +419,16 @@ def path_order_at_side(a, b, conn)
415
419
  d
416
420
  end
417
421
 
418
- def segment_order(a, b)
419
- d = a[1] <=> b[1]
422
+ def length_order(a, b)
423
+ d = a[2].length <=> b[2].length
420
424
  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
425
  a[0].edge_index <=> b[0].edge_index
444
426
  end
445
427
 
446
- GapState = Struct.new(:order, :cross_count, :lut) do
447
- def fitness(segments, k)
448
- table = lut[k]
449
- crossings = 0
450
- order.each do |n|
451
- crossings += table[n]
452
- end
453
- cross_count + crossings
454
- end
455
- end
456
-
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
471
- end
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
490
- end
491
- best
492
- end
493
-
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
500
- return segments if segments.size < 2
501
- # DFS. Fitness is how many end segments cross the placed segments.
502
- # Sort so that those that cross least when at start are first and vice versa.
503
- lut = {}
504
- segments.each_index do |k|
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)
532
- end
533
- out = []
534
- best.order.each do |k|
535
- out.push segments[k]
536
- end
537
- out
428
+ def min_order(a, b)
429
+ d = a[2].range.min <=> b[2].range.min
430
+ return d unless d.zero?
431
+ a[0].edge_index <=> b[0].edge_index
538
432
  end
539
433
 
540
434
  def overlaps_set(c, others)
@@ -544,47 +438,59 @@ def overlaps_set(c, others)
544
438
  false
545
439
  end
546
440
 
547
- def group_minimal_offsets(group)
548
- return 0 if group.empty?
549
- off = { 1 => [] }
550
- cands = off.keys
551
- group.each do |a|
552
- fit = false
553
- cands.each do |k|
554
- next if overlaps_set(a[2], off[k])
555
- off[k].push(a)
556
- fit = true
557
- break
441
+ def layered_order(u_shaped, z_shaped)
442
+ u_shaped.sort! { |a, b| length_order(a, b) }
443
+ z_shaped.sort! { |a, b| min_order(a, b) }
444
+ out = []
445
+ layer = []
446
+ until u_shaped.empty?
447
+ rejects = []
448
+ found = false
449
+ u_shaped.each do |a|
450
+ unless overlaps_set(a[2], layer)
451
+ found = true
452
+ # Creates crossing by putting in front of shorter segment fully within?
453
+ rejects.each do |r|
454
+ if r[2].within(a[2])
455
+ found = false
456
+ break
457
+ end
458
+ end
459
+ end
460
+ if found
461
+ layer.push a
462
+ found = false
463
+ else
464
+ rejects.push a
465
+ end
466
+ end
467
+ u_shaped = rejects
468
+ unless layer.empty?
469
+ out.concat layer
470
+ out.push nil
471
+ layer = []
558
472
  end
559
- next if fit
560
- cands.push(cands.last + 1)
561
- off[cands.last] = [ a ]
562
473
  end
563
- off.each_pair do |offset, fitting|
564
- fitting.each do |sg|
565
- sg[0].offset = offset
474
+ until z_shaped.empty?
475
+ rejects = []
476
+ found = false
477
+ z_shaped.each do |a|
478
+ if overlaps_set(a[2], layer)
479
+ rejects.push a
480
+ else
481
+ layer.push a
482
+ found = true
483
+ end
484
+ end
485
+ z_shaped = rejects
486
+ unless layer.empty?
487
+ out.concat layer
488
+ out.push nil
489
+ layer = []
566
490
  end
567
491
  end
568
- off.keys.max
569
- end
570
-
571
- def group_stacked_offsets(group)
572
- return 0 if group.empty?
573
- group.each_index do |k|
574
- group[k][0].offset = k + 1
575
- end
576
- return group.size
577
- %q(
578
- # This produces narrower gaps but they may be less clear.
579
- prev = group[0]
580
- prev[0].offset = 1
581
- (1...group.size).each do |k|
582
- g = group[k]
583
- g[0].offset = prev[0].offset + (g[2].range_overlap(prev[2]) ? 1 : 0)
584
- prev = g
585
- end
586
- prev[0].offset
587
- )
492
+ out.concat(layer) unless layer.empty?
493
+ out
588
494
  end
589
495
 
590
496
  def direct_range(paths)
@@ -717,29 +623,22 @@ def place_edges(work)
717
623
  end
718
624
  gaps.each_value do |direction|
719
625
  direction.each_value do |gap|
720
- gap.sort! { |a, b| segment_order(a, b) }
721
626
  gleft = gap.select { |a| a[1].zero? }
627
+ grul = gap.select { |a| a[1] == 1 }
628
+ glur = gap.select { |a| a[1] == 2 }
722
629
  gright = gap.select { |a| a[1] == 3 }
723
- gmiddle = zigzag_order(gap.select() { |a| a[1] == 1 }, gap.select() { |a| a[1] == 2 })
724
- gmiddle.each do |s|
725
- s[1] = 1
726
- end
727
- gap = gleft + gmiddle + gright
728
- c = [
729
- group_minimal_offsets(gleft),
730
- group_stacked_offsets(gmiddle),
731
- 0,
732
- group_minimal_offsets(gright)
733
- ]
734
- before = [ 0 ]
735
- denominator = 1 + c[0]
736
- (1...c.size).each do |k|
737
- denominator += c[k]
738
- before[k] = c[k - 1] + before[k - 1]
739
- end
740
- gap.each do |sg|
741
- sg[0].offset = c[sg[1]] + 1 - sg[0].offset if sg[1] > 1
742
- sg[0].offset = Rational(sg[0].offset + before[sg[1]], denominator)
630
+ all = layered_order(gleft, grul)
631
+ all.push nil
632
+ all.concat(layered_order(gright, glur).reverse)
633
+ # Give each rational offset using layer index + 1 and layer count + 2.
634
+ denominator = 2 + all.count { |x| x.nil? }
635
+ layer = 1
636
+ all.each do |sg|
637
+ if sg.nil?
638
+ layer += 1
639
+ else
640
+ sg[0].offset = Rational(layer, denominator)
641
+ end
743
642
  end
744
643
  end
745
644
  end
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021, 2022 Ismo Kärkkäinen
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'
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021, 2022 Ismo Kärkkäinen
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'
@@ -78,8 +78,13 @@ end
78
78
 
79
79
  class Styles
80
80
  def base_styles(m, styles, group)
81
- d = styles.dig(group, 'default') || {}
82
- m['default'] = m.fetch('default', {}).merge(d)
81
+ d = styles.dig(group, 'default')
82
+ if d.nil?
83
+ d = m.fetch('default', {}) # No default in styles.
84
+ else
85
+ m['default'] = m.fetch('default', {}).merge(d)
86
+ d = m['default']
87
+ end
83
88
  styles.fetch(group, {}).each_pair do |name, values|
84
89
  s = d.clone
85
90
  s.merge!(values) unless name == 'default'
@@ -107,7 +112,7 @@ class Styles
107
112
  end
108
113
  s.merge!(mapping['default']) unless found # Merge default at least.
109
114
  # Keep values specified explicitly.
110
- item.merge!(s) {|key, existing, from_template| existing }
115
+ item.merge!(s) { |key, existing, from_template| existing || from_template }
111
116
  end
112
117
 
113
118
  def apply_node_styles(node)
@@ -276,6 +281,27 @@ def apply(doc, template)
276
281
  out
277
282
  end
278
283
 
284
+ def reverse_depth_order(a, b)
285
+ d = b['depth'] <=> a['depth']
286
+ return d unless d.zero?
287
+ d = a['index'] <=> b['index']
288
+ return d unless d.zero?
289
+ a['kind'] <=> b['kind']
290
+ end
291
+
292
+ def reverse_depth_sort(items)
293
+ arr = []
294
+ items.each_index do |k|
295
+ arr.push({
296
+ 'depth' => items[k].fetch('depth', 0),
297
+ 'index' => k,
298
+ 'kind' => items[k].fetch('kind', 'unknown')
299
+ })
300
+ end
301
+ arr.sort! { |a, b| reverse_depth_order(a, b) }
302
+ arr.map { |x| items[x['index']] }
303
+ end
304
+
279
305
  def main
280
306
  template = nil
281
307
  input = nil
@@ -350,6 +376,30 @@ Output is the file produced by the erb-template.
350
376
  remap_coordinates(xcoords, xmax, x2min, doc.dig('diagram', 'edge_gap'))
351
377
  remap_coordinates(ycoords, ymax, y2min, doc.dig('diagram', 'edge_gap'))
352
378
 
379
+ doc['nodes'] = reverse_depth_sort(doc.fetch('nodes', []))
380
+ doc['edges'] = reverse_depth_sort(doc.fetch('edges', []))
381
+ all = doc.fetch('nodes', []).map do |a|
382
+ {
383
+ 'kind' => 'node',
384
+ 'depth' => a.fetch('depth', 0),
385
+ 'item' => a
386
+ }
387
+ end
388
+ all.concat(doc.fetch('edges', []).map do |a|
389
+ {
390
+ 'kind' => 'edge',
391
+ 'depth' => a.fetch('depth', 0),
392
+ 'item' => a
393
+ }
394
+ end)
395
+ all = reverse_depth_sort(all)
396
+ doc['all'] = all.map do |x|
397
+ {
398
+ 'kind' => x['kind'],
399
+ 'item' => x['item']
400
+ }
401
+ end
402
+
353
403
  dump_result(output, apply(doc, template), 5)
354
404
  end
355
405
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021, 2022 Ismo Kärkkäinen
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'
@@ -21,11 +21,15 @@ def add_field(doc, field_name, content)
21
21
  end
22
22
 
23
23
  def missing(doc)
24
- %w[template].each do |key|
24
+ %w[template styles].each do |key|
25
25
  next if doc.key? key
26
26
  next if doc.key? "base64#{key}"
27
27
  return aargh("#{key} is missing", 4)
28
28
  end
29
+ %w[node edge diagram].each do |key|
30
+ v = doc.dig('styles', key, 'default')
31
+ return aargh("styles #{key} default is missing", 4) if v.nil?
32
+ end
29
33
  nil
30
34
  end
31
35
 
@@ -50,15 +54,24 @@ def main
50
54
  Pairs all parameter field-names with content-files contents, starting with
51
55
  either given root YAML file or with an empty root.
52
56
 
53
- Any field name either in root document or in parameters is trusted to be
54
- base-64 encoded without further checking.
55
-
56
57
  Outputs a YAML-file that case be used with diagrammatron-render as a template.
57
58
  All fields are base64-encoded for safety. diagrammatron-render will decode
58
59
  them and rename the fields by removing the base64 prefix.
59
60
 
60
- Presence of "defaults", "sizes", and "template" fields is checked for.
61
+ Root document is expected to have at least the following:
62
+ styles:
63
+ node:
64
+ defaults: {}
65
+ edge:
66
+ defaults: {}
67
+ diagram:
68
+ defaults: {}
69
+
70
+ Presence of "template" field is checked for.
61
71
  Extra fields are not restricted in any manner.
72
+
73
+ Any field name in parameters starting with "base64" is trusted to be base-64
74
+ encoded without further checking.
62
75
  )
63
76
  exit 0
64
77
  end
@@ -19,6 +19,7 @@ styles:
19
19
  fill: "#ffffff"
20
20
  stroke: "#000000"
21
21
  stroke_width: 2
22
+ depth: 0
22
23
  size_estimator: |
23
24
  $render.default_size($render.node['font_size'],
24
25
  $render.node['font_width'], $render.node['font_height'],
@@ -29,4 +30,5 @@ styles:
29
30
  default:
30
31
  stroke_width: 2
31
32
  stroke: "#000000"
32
- base64template: PD94bWwgdmVyc2lvbj0iMS4wIj8+CjwlPQp3LCBoaCA9ICRyZW5kZXIuZGltZW5zaW9ucwpoaCArPSAkcmVuZGVyLmRvYy5kaWcoJ2RpYWdyYW0nLCAnaGVpZ2h0X21hcmdpbicpCgpvdXQgPSBbCiAgJSg8c3ZnIHdpZHRoPSIje3cgKyAkcmVuZGVyLmRvYy5kaWcoJ2RpYWdyYW0nLCAnd2lkdGhfbWFyZ2luJyl9IiBoZWlnaHQ9IiN7aGh9IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiPikKXQokcmVuZGVyLmRvYy5mZXRjaCgnbm9kZXMnLCBbXSkuZWFjaCBkbyB8bm9kZXwKICB3ID0gbm9kZVsndyddLnRvX2kKICBoID0gbm9kZVsnaCddLnRvX2kKICB4ID0gbm9kZVsneG8nXS50b19pCiAgeSA9IGhoIC0gbm9kZVsneW8nXS50b19pIC0gaAogIG5vZGVzdHlsZSA9ICUoZmlsbD0iI3tub2RlWydmaWxsJ119IiBzdHJva2U9IiN7bm9kZVsnc3Ryb2tlJ119IiBzdHJva2Utd2lkdGg9IiN7bm9kZVsnc3Ryb2tlX3dpZHRoJ119IikKICBvdXQucHVzaCglKDxyZWN0ICN7bm9kZXN0eWxlfSBoZWlnaHQ9IiN7aH0iIHdpZHRoPSIje3d9IiB4PSIje3h9IiB5PSIje3l9Ii8+KSkKICB4ICs9IG5vZGVbJ3dpZHRoX21hcmdpbiddCiAgZnMgPSBub2RlWydmb250X3NpemUnXQogIGxoID0gZnMgKiAoMSArIG5vZGVbJ2ZvbnRfbGluZV9zcGFjaW5nJ10pCiAgeSArPSBub2RlWydoZWlnaHRfbWFyZ2luJ10gKyBmcyAqIG5vZGVbJ2ZvbnRfYXNjZW5kJ10gIyBCYXNlbGluZSBmb3IgZmlyc3QgbGluZS4KICB1cmwgPSBub2RlLmZldGNoKCd1cmwnLCBuaWwpCiAgdXJsLmVuY29kZSEoOnhtbCA9PiA6YXR0cikgdW5sZXNzIHVybC5uaWw/CiAgeTAgPSB5CiAgdGV4dHN0eWxlID0gJShmaWxsPSIje25vZGVbJ2ZvbnRfZmlsbCddfSIgZm9udC1mYW1pbHk9InNlcmlmIiBmb250LXNpemU9IiN7ZnN9IiBzdHJva2U9IiN7bm9kZVsnZm9udF9maWxsJ119IiBzdHJva2Utd2lkdGg9IjAiIHhtbDpzcGFjZT0icHJlc2VydmUiKQogIGxpbmtzdHlsZSA9ICUoZmlsbD0iI3tub2RlWyd1cmxfZmlsbCddfSIgZm9udC1mYW1pbHk9InNlcmlmIiBmb250LXNpemU9IiN7ZnN9IiBzdHJva2U9IiN7bm9kZVsndXJsX2ZpbGwnXX0iIHN0cm9rZS13aWR0aD0iMCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIpCiAgbm9kZVsndGV4dCddLmVhY2ggZG8gfGxpbmV8CiAgICBsaW5lLmVuY29kZSEoOnhtbCA9PiA6dGV4dCkKICAgIGlmIHVybC5uaWw/CiAgICAgIG91dC5wdXNoKCUoPHRleHQgI3t0ZXh0c3R5bGV9IHg9IiN7eH0iIHk9IiN7eTB9Ij4je2xpbmV9PC90ZXh0PikpCiAgICBlbHNlCiAgICAgIG91dC5wdXNoKCUoPGEgeGxpbms6aHJlZj0je3VybH0gdGFyZ2V0PSJfcGFyZW50Ij48dGV4dCAje2xpbmtzdHlsZX0geD0iI3t4fSIgeT0iI3t5MH0iPiN7bGluZX08L3RleHQ+PC9hPikpCiAgICBlbmQKICAgIHkwICs9IGxoICMgU2hpZnQgYmFzZWxpbmUgYnkgZnVsbCBsaW5lICsgc3BhY2luZyBoZWlnaHQuCiAgZW5kCmVuZAokcmVuZGVyLmRvYy5mZXRjaCgnZWRnZXMnLCBbXSkuZWFjaCBkbyB8ZWRnZXwKICBsaW5lc3R5bGUgPSAlKGZpbGw9Im5vbmUiIHN0cm9rZT0iI3tlZGdlWydzdHJva2UnXX0iIHN0cm9rZS13aWR0aD0iI3tlZGdlWydzdHJva2Vfd2lkdGgnXX0iKQogIHBhdGggPSBlZGdlLmZldGNoKCdwYXRoJywgbmlsKQogIG5leHQgaWYgcGF0aC5uaWw/CiAgcGF0aC5lYWNoIGRvIHxwfAogICAgcFsneG8nXSA9IHBbJ3hvJ10udG9faS50b19zCiAgICBwWyd5byddID0gKGhoIC0gcFsneW8nXSkudG9faS50b19zCiAgZW5kCiAgaWYgcGF0aC5zaXplID09IDIKICAgIG91dC5wdXNoKCUoPGxpbmUgI3tsaW5lc3R5bGV9IHgxPSIje3BhdGhbMF1bJ3hvJ119IiB4Mj0iI3twYXRoWzFdWyd4byddfSIgeTE9IiN7cGF0aFswXVsneW8nXX0iIHkyPSIje3BhdGhbMV1bJ3lvJ119Ii8+KSkKICBlbHNlCiAgICBwdHMgPSBwYXRoLm1hcCB7IHxwfCAiI3twWyd4byddfSwje3BbJ3lvJ119IiB9CiAgICBvdXQucHVzaCglKDxwb2x5bGluZSAje2xpbmVzdHlsZX0gcG9pbnRzPSIje3B0cy5qb2luKCcgJyl9Ii8+KSkKICBlbmQKZW5kCm91dC5qb2luKCJcbiIpCiU+Cjwvc3ZnPgo=
33
+ depth: 0
34
+ base64template: PD94bWwgdmVyc2lvbj0iMS4wIj8+CjwlPQp3LCBoaCA9ICRyZW5kZXIuZGltZW5zaW9ucwpoaCArPSAkcmVuZGVyLmRvYy5kaWcoJ2RpYWdyYW0nLCAnaGVpZ2h0X21hcmdpbicpCgpvdXQgPSBbCiAgJSg8c3ZnIHdpZHRoPSIje3cgKyAkcmVuZGVyLmRvYy5kaWcoJ2RpYWdyYW0nLCAnd2lkdGhfbWFyZ2luJyl9IiBoZWlnaHQ9IiN7aGh9IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiPikKXQokcmVuZGVyLmRvY1snYWxsJ10uZWFjaCBkbyB8aXRlbXwKICBpZiBpdGVtWydraW5kJ10gPT0gJ25vZGUnCiAgICBub2RlID0gaXRlbVsnaXRlbSddCiAgICB3ID0gbm9kZVsndyddLnRvX2kKICAgIGggPSBub2RlWydoJ10udG9faQogICAgeCA9IG5vZGVbJ3hvJ10udG9faQogICAgeSA9IGhoIC0gbm9kZVsneW8nXS50b19pIC0gaAogICAgbm9kZXN0eWxlID0gJShmaWxsPSIje25vZGVbJ2ZpbGwnXX0iIHN0cm9rZT0iI3tub2RlWydzdHJva2UnXX0iIHN0cm9rZS13aWR0aD0iI3tub2RlWydzdHJva2Vfd2lkdGgnXX0iKQogICAgb3V0LnB1c2goJSg8cmVjdCAje25vZGVzdHlsZX0gaGVpZ2h0PSIje2h9IiB3aWR0aD0iI3t3fSIgeD0iI3t4fSIgeT0iI3t5fSIvPikpCiAgICB4ICs9IG5vZGVbJ3dpZHRoX21hcmdpbiddCiAgICBmcyA9IG5vZGVbJ2ZvbnRfc2l6ZSddCiAgICBsaCA9IGZzICogKDEgKyBub2RlWydmb250X2xpbmVfc3BhY2luZyddKQogICAgeSArPSBub2RlWydoZWlnaHRfbWFyZ2luJ10gKyBmcyAqIG5vZGVbJ2ZvbnRfYXNjZW5kJ10gIyBCYXNlbGluZSBmb3IgZmlyc3QgbGluZS4KICAgIHVybCA9IG5vZGUuZmV0Y2goJ3VybCcsIG5pbCkKICAgIHVybC5lbmNvZGUhKDp4bWwgPT4gOmF0dHIpIHVubGVzcyB1cmwubmlsPwogICAgeTAgPSB5CiAgICB0ZXh0c3R5bGUgPSAlKGZpbGw9IiN7bm9kZVsnZm9udF9maWxsJ119IiBmb250LWZhbWlseT0ic2VyaWYiIGZvbnQtc2l6ZT0iI3tmc30iIHN0cm9rZT0iI3tub2RlWydmb250X2ZpbGwnXX0iIHN0cm9rZS13aWR0aD0iMCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIpCiAgICBsaW5rc3R5bGUgPSAlKGZpbGw9IiN7bm9kZVsndXJsX2ZpbGwnXX0iIGZvbnQtZmFtaWx5PSJzZXJpZiIgZm9udC1zaXplPSIje2ZzfSIgc3Ryb2tlPSIje25vZGVbJ3VybF9maWxsJ119IiBzdHJva2Utd2lkdGg9IjAiIHhtbDpzcGFjZT0icHJlc2VydmUiKQogICAgbm9kZVsndGV4dCddLmVhY2ggZG8gfGxpbmV8CiAgICAgIGxpbmUuZW5jb2RlISg6eG1sID0+IDp0ZXh0KQogICAgICBpZiB1cmwubmlsPwogICAgICAgIG91dC5wdXNoKCUoPHRleHQgI3t0ZXh0c3R5bGV9IHg9IiN7eH0iIHk9IiN7eTB9Ij4je2xpbmV9PC90ZXh0PikpCiAgICAgIGVsc2UKICAgICAgICBvdXQucHVzaCglKDxhIHhsaW5rOmhyZWY9I3t1cmx9IHRhcmdldD0iX3BhcmVudCI+PHRleHQgI3tsaW5rc3R5bGV9IHg9IiN7eH0iIHk9IiN7eTB9Ij4je2xpbmV9PC90ZXh0PjwvYT4pKQogICAgICBlbmQKICAgICAgeTAgKz0gbGggIyBTaGlmdCBiYXNlbGluZSBieSBmdWxsIGxpbmUgKyBzcGFjaW5nIGhlaWdodC4KICAgIGVuZAogIGVsc2UKICAgIGVkZ2UgPSBpdGVtWydpdGVtJ10KICAgIGxpbmVzdHlsZSA9ICUoZmlsbD0ibm9uZSIgc3Ryb2tlPSIje2VkZ2VbJ3N0cm9rZSddfSIgc3Ryb2tlLXdpZHRoPSIje2VkZ2VbJ3N0cm9rZV93aWR0aCddfSIpCiAgICBwYXRoID0gZWRnZS5mZXRjaCgncGF0aCcsIG5pbCkKICAgIG5leHQgaWYgcGF0aC5uaWw/CiAgICBwYXRoLmVhY2ggZG8gfHB8CiAgICAgIHBbJ3hvJ10gPSBwWyd4byddLnRvX2kudG9fcwogICAgICBwWyd5byddID0gKGhoIC0gcFsneW8nXSkudG9faS50b19zCiAgICBlbmQKICAgIGlmIHBhdGguc2l6ZSA9PSAyCiAgICAgIG91dC5wdXNoKCUoPGxpbmUgI3tsaW5lc3R5bGV9IHgxPSIje3BhdGhbMF1bJ3hvJ119IiB4Mj0iI3twYXRoWzFdWyd4byddfSIgeTE9IiN7cGF0aFswXVsneW8nXX0iIHkyPSIje3BhdGhbMV1bJ3lvJ119Ii8+KSkKICAgIGVsc2UKICAgICAgcHRzID0gcGF0aC5tYXAgeyB8cHwgIiN7cFsneG8nXX0sI3twWyd5byddfSIgfQogICAgICBvdXQucHVzaCglKDxwb2x5bGluZSAje2xpbmVzdHlsZX0gcG9pbnRzPSIje3B0cy5qb2luKCcgJyl9Ii8+KSkKICAgIGVuZAogIGVuZAplbmQKb3V0LmpvaW4oIlxuIikKJT4KPC9zdmc+Cg==
data/template/root.yaml CHANGED
@@ -18,6 +18,7 @@ styles:
18
18
  fill: "#ffffff"
19
19
  stroke: "#000000"
20
20
  stroke_width: 2
21
+ depth: 0
21
22
  size_estimator: |
22
23
  $render.default_size($render.node['font_size'],
23
24
  $render.node['font_width'], $render.node['font_height'],
@@ -28,3 +29,4 @@ styles:
28
29
  default:
29
30
  stroke_width: 2
30
31
  stroke: "#000000"
32
+ depth: 0
data/template/svg_1.1.erb CHANGED
@@ -6,45 +6,48 @@ hh += $render.doc.dig('diagram', 'height_margin')
6
6
  out = [
7
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">)
8
8
  ]
9
- $render.doc.fetch('nodes', []).each do |node|
10
- w = node['w'].to_i
11
- h = node['h'].to_i
12
- x = node['xo'].to_i
13
- y = hh - node['yo'].to_i - h
14
- nodestyle = %(fill="#{node['fill']}" stroke="#{node['stroke']}" stroke-width="#{node['stroke_width']}")
15
- out.push(%(<rect #{nodestyle} height="#{h}" width="#{w}" x="#{x}" y="#{y}"/>))
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.
20
- url = node.fetch('url', nil)
21
- url.encode!(:xml => :attr) unless url.nil?
22
- y0 = y
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|
26
- line.encode!(:xml => :text)
27
- if url.nil?
28
- out.push(%(<text #{textstyle} x="#{x}" y="#{y0}">#{line}</text>))
29
- else
30
- out.push(%(<a xlink:href=#{url} target="_parent"><text #{linkstyle} x="#{x}" y="#{y0}">#{line}</text></a>))
9
+ $render.doc['all'].each do |item|
10
+ if item['kind'] == 'node'
11
+ node = item['item']
12
+ w = node['w'].to_i
13
+ h = node['h'].to_i
14
+ x = node['xo'].to_i
15
+ y = hh - node['yo'].to_i - h
16
+ nodestyle = %(fill="#{node['fill']}" stroke="#{node['stroke']}" stroke-width="#{node['stroke_width']}")
17
+ out.push(%(<rect #{nodestyle} height="#{h}" width="#{w}" x="#{x}" y="#{y}"/>))
18
+ x += node['width_margin']
19
+ fs = node['font_size']
20
+ lh = fs * (1 + node['font_line_spacing'])
21
+ y += node['height_margin'] + fs * node['font_ascend'] # Baseline for first line.
22
+ url = node.fetch('url', nil)
23
+ url.encode!(:xml => :attr) unless url.nil?
24
+ y0 = y
25
+ textstyle = %(fill="#{node['font_fill']}" font-family="serif" font-size="#{fs}" stroke="#{node['font_fill']}" stroke-width="0" xml:space="preserve")
26
+ linkstyle = %(fill="#{node['url_fill']}" font-family="serif" font-size="#{fs}" stroke="#{node['url_fill']}" stroke-width="0" xml:space="preserve")
27
+ node['text'].each do |line|
28
+ line.encode!(:xml => :text)
29
+ if url.nil?
30
+ out.push(%(<text #{textstyle} x="#{x}" y="#{y0}">#{line}</text>))
31
+ else
32
+ out.push(%(<a xlink:href=#{url} target="_parent"><text #{linkstyle} x="#{x}" y="#{y0}">#{line}</text></a>))
33
+ end
34
+ y0 += lh # Shift baseline by full line + spacing height.
31
35
  end
32
- y0 += lh # Shift baseline by full line + spacing height.
33
- end
34
- end
35
- $render.doc.fetch('edges', []).each do |edge|
36
- linestyle = %(fill="none" stroke="#{edge['stroke']}" stroke-width="#{edge['stroke_width']}")
37
- path = edge.fetch('path', nil)
38
- next if path.nil?
39
- path.each do |p|
40
- p['xo'] = p['xo'].to_i.to_s
41
- p['yo'] = (hh - p['yo']).to_i.to_s
42
- end
43
- if path.size == 2
44
- out.push(%(<line #{linestyle} x1="#{path[0]['xo']}" x2="#{path[1]['xo']}" y1="#{path[0]['yo']}" y2="#{path[1]['yo']}"/>))
45
36
  else
46
- pts = path.map { |p| "#{p['xo']},#{p['yo']}" }
47
- out.push(%(<polyline #{linestyle} points="#{pts.join(' ')}"/>))
37
+ edge = item['item']
38
+ linestyle = %(fill="none" stroke="#{edge['stroke']}" stroke-width="#{edge['stroke_width']}")
39
+ path = edge.fetch('path', nil)
40
+ next if path.nil?
41
+ path.each do |p|
42
+ p['xo'] = p['xo'].to_i.to_s
43
+ p['yo'] = (hh - p['yo']).to_i.to_s
44
+ end
45
+ if path.size == 2
46
+ out.push(%(<line #{linestyle} x1="#{path[0]['xo']}" x2="#{path[1]['xo']}" y1="#{path[0]['yo']}" y2="#{path[1]['yo']}"/>))
47
+ else
48
+ pts = path.map { |p| "#{p['xo']},#{p['yo']}" }
49
+ out.push(%(<polyline #{linestyle} points="#{pts.join(' ')}"/>))
50
+ end
48
51
  end
49
52
  end
50
53
  out.join("\n")
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.1
4
+ version: 0.4.3
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-31 00:00:00.000000000 Z
11
+ date: 2023-02-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14