-
Notifications
You must be signed in to change notification settings - Fork 1
/
ukaz.tcl
3703 lines (3160 loc) · 95.5 KB
/
ukaz.tcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package require snit
package require Tk 8.6
package provide ukaz 2.1
namespace eval ukaz {
variable ns [namespace current]
##### General functions ###############
proc lremove {list element} {
lsearch -all -inline -not -exact $list $element
}
##### Functions for geometric operations (clipping) ############
namespace eval geometry {
proc polylineclip {cdata range} {
variable xmin [dict get $range xmin]
variable xmax [dict get $range xmax]
variable ymin [dict get $range ymin]
variable ymax [dict get $range ymax]
if {$xmin > $xmax} { lassign [list $xmin $xmax] xmax xmin }
if {$ymin > $ymax} { lassign [list $ymin $ymax] ymax ymin }
set result {}
set piece {}
# clip infinity of first point
set x1 Inf
set y1 Inf
while {[indefinite $x1 $y1]} {
set cdata [lassign $cdata x1 y1]
if {[llength $cdata]<2} {
return {}
}
}
foreach {x2 y2} $cdata {
# clip total indefinite points
if {[indefinite $x2 $y2]} {
# end last line
if {$piece != {}} {
lappend result $piece
}
set piece {}
continue
}
lassign [cohensutherland $x1 $y1 $x2 $y2] clipline type
switch $type {
rightclip {
# second point was clipped
if {$piece == {}} {
# it is the first line segment
# make single segment
lappend result $clipline
} else {
lappend piece {*}[lrange $clipline 2 3]
lappend result $piece
set piece {}
}
}
leftclip {
# first point was clipped, begin new line
set piece $clipline
}
noclip {
# append as given
# if we are the first, include 1st point
if {[llength $piece]==0} {
set piece [list $x1 $y1]
}
lappend piece $x2 $y2
}
empty {
# end last line
if {$piece != {}} {
lappend result $piece
}
set piece {}
}
bothclip {
# create line on it's own
# end last line
if {$piece != {}} {
lappend result $piece
}
set piece {}
lappend result $clipline
}
}
# advance
set x1 $x2
set y1 $y2
}
# end last line
if {$piece != {}} {
lappend result $piece
}
return $result
}
proc cohensutherland {x1 y1 x2 y2} {
variable xmin
variable xmax
variable ymin
variable ymax
set codeleft [pointcode $x1 $y1]
set coderight [pointcode $x2 $y2]
if {($codeleft | $coderight) == 0} {
return [list [list $x1 $y1 $x2 $y2] noclip]
}
if {($codeleft & $coderight) != 0} {
return {{} empty}
}
# if we are here, one of the points must be clipped
set left false
set right false
for {set iter 0} {$iter<20} {incr iter} {
if {$codeleft != 0} {
# left point is outside
set left true
lassign [intersect $x1 $y1 $x2 $y2] x1 y1
set codeleft [pointcode $x1 $y1]
} else {
# right point outside
set right true
lassign [intersect $x2 $y2 $x1 $y1] x2 y2
set coderight [pointcode $x2 $y2]
}
if {($codeleft & $coderight) != 0} {
return {{} empty}
}
if {($codeleft | $coderight) == 0} {
if {$left && $right} {
return [list [list $x1 $y1 $x2 $y2] bothclip]
}
if {$left} {
return [list [list $x1 $y1 $x2 $y2] leftclip]
}
if {$right} {
return [list [list $x1 $y1 $x2 $y2] rightclip]
}
return "Can't happen $x1 $y1 $x2 $y2"
}
}
return "Infinite loop $x1 $y1 $x2 $y2 "
}
proc pointcode {x y} {
variable xmin
variable xmax
variable ymin
variable ymax
expr {(($x<$xmin)?1:0) |
(($x>$xmax)?2:0) |
(($y<$ymin)?4:0) |
(($y>$ymax)?8:0) }
}
proc intersect {x1 y1 x2 y2} {
variable xmin
variable xmax
variable ymin
variable ymax
# check for infinity
if {$y1 == Inf} {
return [list $x2 $ymax]
}
if {$y1 == -Inf} {
return [list $x2 $ymin]
}
if {$x1 == Inf} {
return [list $xmax $y2]
}
if {$x1 == -Inf} {
return [list $xmin $y2]
}
if {$y1>$ymax} {
return [list [expr {$x1+($x2-$x1)*($ymax-$y1)/($y2-$y1)}] $ymax]
}
if {$y1<$ymin} {
return [list [expr {$x1+($x2-$x1)*($ymin-$y1)/($y2-$y1)}] $ymin]
}
if {$x1>$xmax} {
return [list $xmax [expr {$y1+($y2-$y1)*($xmax-$x1)/($x2-$x1)}]]
}
return [list $xmin [expr {$y1+($y2-$y1)*($xmin-$x1)/($x2-$x1)}]]
}
proc indefinite {x y} {
expr { ($x!=$x) || ($y != $y) || (abs($x) == Inf && abs($y) == Inf)}
}
proc pointclipz {cdata zdata range} {
# remove all points which are NaN or outside
# the clip region
set xmin [dict get $range xmin]
set xmax [dict get $range xmax]
set ymin [dict get $range ymin]
set ymax [dict get $range ymax]
set zmin [dict get $range zmin]
set zmax [dict get $range zmax]
set result {}
set resultz {}
set clipinfo {}
set clipid 0
foreach {x y} $cdata z $zdata {
if {$x!=$x || $y!=$y || $x<$xmin || $x>$xmax || $y<$ymin || $y>$ymax || $z!=$z} {
dict incr clipinfo $clipid
continue
}
lappend result $x $y
lappend resultz $z
incr clipid
}
list $result $resultz $clipinfo
}
proc pointclip {cdata range} {
# remove all points which are NaN or outside
# the clip region
set xmin [dict get $range xmin]
set xmax [dict get $range xmax]
set ymin [dict get $range ymin]
set ymax [dict get $range ymax]
set result {}
set clipinfo {}
set clipid 0
foreach {x y} $cdata {
if {$x!=$x || $y!=$y || $x<$xmin || $x>$xmax || $y<$ymin || $y>$ymax} {
dict incr clipinfo $clipid
continue
}
lappend result $x $y
incr clipid
}
list $result $clipinfo
}
}
############## Functions for colormaps ############################
variable colormaps {}
proc mkcolormap {name map} {
# Create a colormap from a list of colors in float format with attached
# gradient stops from 0 to 1.
#
# example: mkcolormap redgreen {0 {1.0 0.0 0.0} 1 {0.0 1.0 0.0} }
# The colormap is a long list of interpolated colors in hex format (#xxyyzz)
variable colormaps
if {[dict exists $colormaps $name]} {
return -code error "Colormap $name already exists"
}
set maxcols 2000 ;# should be 2000 for real maps
set cmap {}
set map [lassign $map stop fcolor]
if {$stop != 0} {
return -code error "First color must be at index 0.0"
}
for {set i 0} {$i < $maxcols} {incr i} {
set frac [expr {double($i) / ($maxcols - 1)}]
if {($frac > $stop) || ($i == 0)} {
# advance
set oldstop $stop
set oldfcolor $fcolor
set map [lassign $map stop fcolor]
}
lappend cmap [interpol_color $oldstop $oldfcolor $stop $fcolor $frac]
}
if {$stop != 1.0 } {
return -code error "Final color stop must be 1.0"
}
dict set colormaps $name $cmap
}
proc getcolor {map stop} {
set maplength [llength $map]
if {$stop > 1} { return [lindex $map end] }
if {$stop < 0} { return [lindex $map 0] }
set index [expr {min(max(int($maplength*$stop), 0),$maplength - 1)}]
return [lindex $map $index]
}
proc getcolormap {name} {
variable colormaps
dict get $colormaps $name
}
proc interpol_color {x0 color0 x1 color1 frac} {
set w1 [expr {double($frac - $x0)/($x1 - $x0)}]
set w0 [expr {1.0 - $w1}]
foreach c0 $color0 c1 $color1 {
lappend fcolor [expr {$c0*$w0 + $c1*$w1}]
}
set icolor [lmap f $fcolor {expr {min(max(int($f*255), 0),255)}}]
set xcolor [join [lmap i $icolor {format %02x $i}] ""]
return "#$xcolor"
}
proc testcolormap {name} {
variable colormaps
toplevel .ctest
wm title .ctest "Colormap $name"
set cmap [dict get $colormaps $name]
set width 400
set height 50
pack [canvas .ctest.c -width $width -height $height] -expand yes -fill both
for {set x 0} {$x < $width} {incr x} {
set frac [expr {double($x) / ($width - 1)}]
.ctest.c create rectangle $x 0 $x $height -outline {} -fill [getcolor $cmap $frac]
}
tkwait window .ctest
}
# add a few standard colormaps
mkcolormap rgb {0 {1.0 0.0 0.0} 0.5 {0.0 1.0 0.0} 1.0 {0.0 0.0 1.0}}
mkcolormap jet {
0 { 0 0 0.5 }
0.125 { 0 0 1 }
0.325 { 0 1 1 }
0.625 { 1 1 0 }
0.875 { 1 0 0 }
1 { 0.5 0 0 }
}
mkcolormap gray {0 {0 0 0} 1 {1 1 1}}
mkcolormap hot {
0 {0 0 0}
0.25 {1 0 0}
0.7 { 1 1 0}
1 { 1 1 1 }
}
proc cutoff_log {x} {
# expr throws error if log is NaN
if {$x <= 0} { return -Inf }
return [expr {log($x)}]
}
proc clipvalue {v min max} {
expr {max(min($v, $max),$min)}
}
proc compute_rasterpixelsize {coords {overprint 1.1}} {
# sort by y coordinate, then by x
# since sort is stable, it remains
# sorted for equal x. then count, how often the
# direction of the minor coordinate changes
#
# first filter NaNs
set fdata {}
foreach {x y} $coords {
if {$x != $x || $y != $y} { continue }
lappend fdata $x $y
}
set sydata [lsort -stride 2 -real -index 1 $fdata]
set sxdata [lsort -stride 2 -real -index 0 $sydata]
set xmin [lindex $sxdata 0]
set xmax [lindex $sxdata end-1]
set ymin [lindex $sydata 1]
set ymax [lindex $sydata end]
puts "$xmin < x < $xmax, $ymin < y < $ymax"
set N [expr {[llength $fdata]/2}]
set dirchange 0
set oldy [lindex $sxdata 0 1]
# count direction changes
foreach {x y} $sxdata {
if {$y < $oldy} { incr dirchange }
set oldy $y
}
# puts "$dirchange direction changes found"
if {$dirchange > $N/2} {
set dirchange [expr {$N - $dirchange}]
}
set Nx [expr {$dirchange + 1}]
set Ny [expr {max($N/ $Nx, 1)}]
# puts "Assuming $Nx x $Ny raster"
set xsize [expr {double($xmax - $xmin)/max($Nx-1,1)*$overprint}]
set ysize [expr {double($ymax - $ymin)/max($Ny-1,1)*$overprint}]
return [list $xsize $ysize]
}
############## Functions for deferred execution ############################
variable Requests {}
proc defer {cmd} {
# defer cmd to idle time. Multiple requests are merged
variable ns
variable Requests
if {[dict size $Requests] == 0} {
after idle ${ns}::doRequests
}
dict set Requests $cmd 1
}
proc doRequests {} {
variable Requests
# first clear Requests, so that new requests are only recorded
# during execution and do not interfere with the execution
set ReqCopy $Requests
set Requests {}
dict for {cmd val} $ReqCopy {
if {[catch {uplevel #0 $cmd} err]} {
# ignore background errors, just log to stderr
puts stderr $err
}
}
}
########## Functions for math on data ##############################
proc parsedata_using {fdata args} {
# read column data
# analogous to "using" in gnuplot
# the elements of formatlist are interpreted as expr-String with embedded $0, $1, ...
# return as flat a list
set ncomments 0
set nblanks 0
set ndata 0
set skip 0
variable parseerrors {}
set 0 0
set lno 0
set result {}
# $0 contains the linenumber, initially it's 0
foreach line [split $fdata \n] {
# make list
incr lno
set cols [regexp -all -inline {[^[:space:]]+} $line]
# puts "$0: $cols"
if {[regexp {^[[:space:]]*#} $line]} {
# it is a comment starting with "#"
#puts "Comment $line"
incr ncomments
continue
}
if {[llength $cols]==0} {
# blank line
#puts "Blank line"
incr nblanks
continue
}
# puts $line
# extract the columns and put them as double into $ind
# if possible
namespace eval formula [list set 0 $0]
namespace eval formula [list set lno $lno]
for {set ind 1} {$ind<=[llength $line]} {incr ind} {
set indtext [lindex $line [expr {$ind - 1}]]
if {[string is double -strict $indtext]} {
namespace eval formula [list set $ind $indtext]
}
}
set thisline {}
set err {}
foreach fmt $args {
if {[catch {namespace eval formula [list expr $fmt]} datum]} {
set err $datum
break
}
lappend thisline $datum
}
namespace delete formula
if {$err != {}} {
lappend parseerrors "Line $lno: $err"
incr skip
} else {
lappend result $thisline
incr 0
incr ndata
}
}
variable parseinfo [list $ndata $ncomments $nblanks $skip]
# return as a list of lists
return $result
}
proc transformdata_using {data using} {
# read file the same way as gnuplot does
lassign [split $using :] xformat yformat
if {[string is integer -strict $xformat] && $xformat >=0} {
set xformat "\$$xformat"
}
if {[string is integer -strict $yformat] && $yformat >=0} {
set yformat "\$$yformat"
}
set fd [open $data r]
set fdata [read $fd]
close $fd
concat {*}[parsedata_using $fdata $xformat $yformat]
}
############## Functions for intervals ##################
proc calcdatarange {data zdata} {
# compute min/max and corresponding log min/max
# for dataset
# unfortunately, four cases for log on/off must be considered
# indexes into list are logx & logy
set xmin {{+Inf +Inf} {+Inf +Inf}}
set xmax {{-Inf -Inf} {-Inf -Inf}}
set ymin {{+Inf +Inf} {+Inf +Inf}}
set ymax {{-Inf -Inf} {-Inf -Inf}}
foreach {x y} $data {
set xfin [list [expr {isfinite($x)}] [expr {islogfinite($x)}]]
set yfin [list [expr {isfinite($y)}] [expr {islogfinite($y)}]]
foreach logx {0 1} {
foreach logy {0 1} {
if {[lindex $xfin $logx] && [lindex $yfin $logy]} {
if {$x<[lindex $xmin $logx $logy]} { lset xmin $logx $logy $x}
if {$x>[lindex $xmax $logx $logy]} { lset xmax $logx $logy $x}
if {$y<[lindex $ymin $logx $logy]} { lset ymin $logx $logy $y}
if {$y>[lindex $ymax $logx $logy]} { lset ymax $logx $logy $y}
}
}
}
}
# For z values, i.e. color code, finiteness is independent
# from x & y log values
set zmin {+Inf +Inf}
set zmax {-Inf -Inf}
foreach z $zdata {
set zfin [list [expr {isfinite($z)}] [expr {islogfinite($z)}]]
foreach logz {0 1} {
if {[lindex $zfin $logz]} {
if {$z<[lindex $zmin $logz]} { lset zmin $logz $z}
if {$z>[lindex $zmax $logz]} { lset zmax $logz $z}
}
}
}
dict create xmin $xmin ymin $ymin xmax $xmax ymax $ymax zmin $zmin zmax $zmax
}
proc combine_range_1D {min1 max1 min2 max2} {
set min [expr {min($min1, $min2)}]
set max [expr {max($max1, $max2)}]
list $min $max
}
proc combine_range {range1 range2} {
if {$range1 == {}} { return $range2 }
if {$range2 == {}} { return $range1 }
set result {}
foreach key {xmin ymin} {
set l1 [dict get $range1 $key]
set l2 [dict get $range2 $key]
foreach logx {0 1} lx1 $l1 lx2 $l2 {
foreach logy {0 1} v1 $lx1 v2 $lx2 {
lset l1 $logx $logy [expr {min($v1, $v2)}]
}
}
dict set result $key $l1
}
foreach key {xmax ymax} {
set l1 [dict get $range1 $key]
set l2 [dict get $range2 $key]
foreach logx {0 1} lx1 $l1 lx2 $l2 {
foreach logy {0 1} v1 $lx1 v2 $lx2 {
lset l1 $logx $logy [expr {max($v1, $v2)}]
}
}
dict set result $key $l1
}
set l1 [dict get $range1 zmin]
set l2 [dict get $range2 zmin]
foreach logz {0 1} v1 $l1 v2 $l2 {
lset l1 $logz [expr {min($v1, $v2)}]
}
dict set result zmin $l1
set l1 [dict get $range1 zmax]
set l2 [dict get $range2 zmax]
foreach logz {0 1} v1 $l1 v2 $l2 {
lset l1 $logz [expr {max($v1, $v2)}]
}
dict set result zmax $l1
return $result
}
proc combine_y2range {range1 range2 logx logy logy2 logz} {
# range1 corresponds to y axis
# range2 corresponds to y2 axis
# x axis and z axis are combined
# y and y2 are distributed
# case 1: only y axis
if {$range2 == {}} {
set result {}
dict set result xmin [lindex [dict get $range1 xmin] $logx $logy]
dict set result xmax [lindex [dict get $range1 xmax] $logx $logy]
dict set result ymin [lindex [dict get $range1 ymin] $logx $logy]
dict set result ymax [lindex [dict get $range1 ymax] $logx $logy]
dict set result y2min +Inf
dict set result y2max -Inf
dict set result zmin [lindex [dict get $range1 zmin] $logz]
dict set result zmax [lindex [dict get $range1 zmax] $logz]
return $result
}
# case 2: only y2 axis. Rename second range
if {$range1 == {}} {
set result {}
dict set result xmin [lindex [dict get $range2 xmin] $logx $logy2]
dict set result xmax [lindex [dict get $range2 xmax] $logx $logy2]
dict set result ymin +Inf
dict set result ymax -Inf
dict set result y2min [lindex [dict get $range2 ymin] $logx $logy2]
dict set result y2max [lindex [dict get $range2 ymax] $logx $logy2]
dict set result zmin [lindex [dict get $range2 zmin] $logz]
dict set result zmax [lindex [dict get $range2 zmax] $logz]
return $result
}
# case 3: both y and y2 are set. Rename y2 correctly
# and combine the x- and z-range
# combine x
set result {}
set l1 [dict get $range1 xmin]
set l2 [dict get $range2 xmin]
set xmin1 [lindex $l1 $logx $logy]
set xmin2 [lindex $l2 $logx $logy2]
set l1 [dict get $range1 xmax]
set l2 [dict get $range2 xmax]
set xmax1 [lindex $l1 $logx $logy]
set xmax2 [lindex $l2 $logx $logy2]
lassign [combine_range_1D $xmin1 $xmax1 $xmin2 $xmax2] xmin xmax
dict set result xmin $xmin
dict set result xmax $xmax
# combine z
set zmin1 [lindex [dict get $range1 zmin] $logz]
set zmax1 [lindex [dict get $range1 zmax] $logz]
set zmin2 [lindex [dict get $range2 zmin] $logz]
set zmax2 [lindex [dict get $range2 zmax] $logz]
lassign [combine_range_1D $zmin1 $zmax1 $zmin2 $zmax2] zmin zmax
dict set result zmin $zmin
dict set result zmax $zmax
# copy y
dict set result ymin [lindex [dict get $range1 ymin] $logx $logy]
dict set result ymax [lindex [dict get $range1 ymax] $logx $logy]
# copy y2
dict set result y2min [lindex [dict get $range2 ymin] $logx $logy2]
dict set result y2max [lindex [dict get $range2 ymax] $logx $logy2]
return $result
}
proc sanitize_range {rmin rmax} {
# make a range with a finite span
if {$rmin > $rmax} {
# range contains not a single valid point
# just return a default
lassign {1.0 2.0} rmin rmax
}
if {$rmin == $rmax} {
# range contains only one single point
# expand range by a small width
set rm $rmin
if {$rm != 0} {
# if it is a finite number, make a range
# with a relative size of +/- 0.1 %
set rmin [expr {$rm*0.999}]
set rmax [expr {$rm*1.001}]
if {$rm < 0} {
lassign [list $rmin $rmax] rmax rmin
}
} else {
# if the only valid number is 0
# make a fixed range
lassign {-0.001 0.001} rmin rmax
}
}
return [list $rmin $rmax]
}
proc compute_rangetransform {r1min r1max r2min r2max} {
set mul [expr {($r2max - $r2min)/($r1max -$r1min)}]
set add [expr {$r2min-$r1min*$mul}]
list $mul $add
}
############ Function for automatic axis scaling ##########
proc compute_ticlist {min max tics log widen formatcmd} {
# automatically compute sensible values
# for the tics position, if not requested otherwise
lassign $tics ticrequest spec
switch $ticrequest {
off {
return [list {} $min $max]
}
list {
set ticlist {}
foreach v $spec {
if {[string is double -strict $v]} {
lappend ticlist [{*}$formatcmd $v] $v
} elseif {[llength $v]==2} {
lassign $v text pos
lappend ticlist $text $pos
}
}
return [list $ticlist $min $max]
}
every {
# put a tic mark at integer multiples of spec
set ticbase $spec
}
auto {
# automatic placement. In log case,
# put a mark at every power of ten
# and subdivide for small span
if {$log} {
set decades [expr {log10($max)-log10($min)}]
if {$decades<=0.5} {
set minor {1 2 3 4 5 6 7 8 9}
} elseif {$decades<=2} {
set minor {1 2 3 4 5}
} elseif {$decades<=3} {
set minor {1 2 5}
} elseif {$decades<=5} {
set minor {1 5}
} else {
set minor {1}
}
set expmin [expr {entier(floor(log10($min)))}]
set expmax [expr {entier(floor(log10($max)))}]
# the range is between 10^expmin and 10^(expmax+1)
# if widening downwards, look for the largest
# tic that is smaller or equal to the required minimum
if {[dict get $widen min]} {
foreach mantisse $minor {
set tic [expr {$mantisse*10.0**$expmin}]
if {$tic <= $min} {
set wmin $tic
}
}
set min $wmin
}
set ticlist {}
for {set exp $expmin} {$exp <= $expmax} {incr exp} {
set base [expr {10.0**$exp}]
foreach mantisse $minor {
set tic [expr {$mantisse*$base}]
if {$tic >= $min && $tic <=$max} {
lappend ticlist [{*}$formatcmd $tic] $tic
}
}
}
# if widening upwards, look for a tic >= the requested max
# unles it has been reached before
if {[dict get $widen max] && [lindex $ticlist end]<$max} {
lappend minor 10
foreach mantisse $minor {
set tic [expr {$mantisse*10.0**$expmax}]
if {$tic >= $max} {
set max $tic
lappend ticlist [{*}$formatcmd $tic] $tic
break
}
}
}
return [list $ticlist $min $max]
} else {
# automatic placement. In linear case,
# compute value as a multiple
# of 1, 2 or 5 times a power of ten
set exp [expr {log10(abs($max - $min))}]
set base [expr {pow(10, floor($exp)-1)}]
set xfrac [expr {fmod($exp, 1.0)}]
if {$xfrac < 0 } {set xfrac [expr {$xfrac+1.0}]}
# Exponent und Bruchteil des Zehnerlogarithmus
set xb 10
if {$xfrac <= 0.70} { set xb 5}
if {$xfrac <= 0.31} { set xb 2}
set ticbase [expr {$xb*$base}]
}
}
default {
error "Unknown tic mode $ticrequest"
}
}
# if we are here, place marks at regular intervals
# at integer multiples of ticbase
# if we should widen, update min & max
if {[dict get $widen min] && !$log} {
set start [expr {entier(floor(double($min)/double($ticbase)))}]
set min [expr {$ticbase*$start}]
} else {
set start [expr {entier(ceil(double($min)/double($ticbase)))}]
}
if {[dict get $widen max]} {
set stop [expr {entier(ceil(double($max)/double($ticbase)))}]
set max [expr {$ticbase*$stop}]
} else {
set stop [expr {entier(floor(double($max)/double($ticbase)))}]
}
set ticlist {}
for {set i $start} {$i<=$stop} {incr i} {
set v [expr {$i*$ticbase}]
# if {$log && $v<=0} { continue }
lappend ticlist [{*}$formatcmd $v] $v
}
return [list $ticlist $min $max]
}
######### Functions for parsing gnuplot style commands ###########
proc initparsearg {{defaultdict {}}} {
# checks whether args is a valid dictionary
upvar 1 args procargs
if {[catch {dict size $procargs}]} {
return -code error -level 2 "Malformed argument list: $procargs"
}
variable parsearg_default $defaultdict
variable parsearg_result {}
}
proc parsearg {option default {validoptions {}}} {
# read argument from args, set to default
# if unset in args. option can have alternative
# names. Return true if the option was set
# from the arguments, false if the default was substituted
# if validoptions is not empty, throw an error if the
# supplied value does not match one of these
upvar 1 args procargs
set optname [lindex $option 0]
upvar 1 $optname resvar
set success false
foreach name $option {
if {[dict exists $procargs $name]} {
set resvar [dict get $procargs $name]
dict unset procargs $name
set success true
}
}
variable parsearg_default
variable parsearg_result
if {!$success} {
# set to default. First check the default dict
# then use the hardcoded default
if {[dict exists $parsearg_default $optname]} {
set resvar [dict get $parsearg_default $optname]
} else {
set resvar $default
}
} else {
if {[llength $validoptions] != 0 && $resvar ni $validoptions} {
set optlist [join [lmap x $validoptions {expr {($x eq "")?"empty string":"\"$x\""}}] ", "]
return -code error "$optname can't be \"$resvar\", must be one of: $optlist"
}
}
dict set parsearg_result $optname $resvar
return $success
}
proc errorargs {} {
# call at the end to err on unknown options
upvar 1 args procargs
if {[llength $procargs] != 0} {
return -code error -level 2 "Unknown argument(s) $procargs"
}
}
proc parsearg_asdict {} {
variable parsearg_result
return $parsearg_result
}
########### Functions for drawing marks on a canvas ##############
proc shape-circles {can coord color size width dash varying tag} {
set ids {}
foreach {x y} $coord {*}$varying {
set r [expr {5.0*$size}]
lappend ids [$can create oval \
[expr {$x-$r}] [expr {$y-$r}] \
[expr {$x+$r}] [expr {$y+$r}] \
-outline $color -fill "" -width $width -dash $dash -tag $tag]
}
return $ids
}
proc shape-filled-circles {can coord color size width dash varying tag} {
set ids {}
foreach {x y} $coord {*}$varying {
set r [expr {5.0*$size}]
lappend ids [$can create oval \
[expr {$x-$r}] [expr {$y-$r}] \
[expr {$x+$r}] [expr {$y+$r}] \
-outline "" -fill $color -tag $tag]
}
return $ids
}
proc shape-squares {can coord color size width dash varying tag} {
set ids {}
foreach {x y} $coord {*}$varying {
set s [expr {5.0*$size}]
lappend ids [$can create rectangle \
[expr {$x-$s}] [expr {$y-$s}] [expr {$x+$s}] [expr {$y+$s}] \
-outline $color -fill "" -width $width -dash $dash -tag $tag]
}
return $ids
}