File: plotmaker.rb

package info (click to toggle)
ctioga 1.10-1
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 1,052 kB
  • ctags: 953
  • sloc: ruby: 9,306; sh: 504; makefile: 6
file content (1652 lines) | stat: -rw-r--r-- 52,388 bytes parent folder | download
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
# plotmaker.rb: The main class for making plots
# copyright (c) 2006, 2007, 2008 by Vincent Fourmond
  
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
  
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details (in the COPYING file).


# TODO, the main one:
# 
# It currently is a pain to make complex plots with ctioga. A real
# pain. What could be done to improve the situation ?
# 
# * hide the difference between edges and axes.
# * the layout mechanism is not comfortable enough to work with, especially
#   with the need for relative positioning.
#
# Would it be possible to allow for the 'real size' to be determined
# *afterwards* ? 

# TODO, an even bigger one:
# Switch to a real command-based plotting program:
#  - any single operation that is realized by ctioga would be a command
#  - every single of these commands would take a given (fixed) number of
#    parameters (we should take care about boolean stuff)
#  - every command would be of course reachable as command-line options
#    but it could also be within files
#  - in these files, provide an additional mechanism for quickly defining
#    variables and do variable substitution.
#  - one command (plus arguments) per line, with provisions for
#    line-splitting
#  - allow some kind of 'include' directives (that would also be used for
#    cmdline inclusion of files)
#  - command-line arguments and command files could intermix (that *would*
#    be fun, since it would allow very little changes to a command-line
#    to change significantly the look of a file...!)
#  - command files would be specified using @file ?
#  - in the absence of --name commands, output would go to the named file ?
#  - LONG TERM: allow conditionals and variable
#    definition/substitution on command-line ?
#
#  Each command could take *typed* arguments. That would allow typed
#  variables along with a string-to-type conversion ? (is that useful ?)
#
#  Provide *optional* hash-like arguments that probably could not be used
#  in the command-line, but could be in the file.
#
#  Provide self-documentation in each and every command
#
#  Manipulations of a buffer stack - including mathematical
#  expressions; provide commands to only *load* a file, but not
#  necessarily import it.
#
#  Provide a way to 'save' a command-line into a command-file.
#
#  Write as many test suites as possible ??
#
#  Merge Metabuilder and Backends into the ctioga code base. There's
#  no need for extra complexity.
#
#  That requires a huge amount of work, but on the other hand, that
#  would be much more satisfactory than the current mess.
#
#  Commands would be part of "groups".
#
#  Release a new version of ctioga before that.
#
#  Don't rely on huge mess of things !
#
#  Then, I could have some small fun and write a 



# Very important for command-line parsing
require 'optparse'
require 'Tioga/tioga'
require 'Tioga/Utils'

# Information about style
require 'CTioga/styles'
# Moving arrays
# require 'CTioga/movingarrays'
# And, most important of all, elements
require 'CTioga/elements'

# The debugging facility
require 'CTioga/debug'
require 'CTioga/log'
require 'CTioga/utils'
require 'CTioga/layout'


# The backends
require 'CTioga/backends'

# Support for themes
require 'CTioga/themes'
require 'CTioga/axes'
require 'CTioga/structures'

# Legends
require 'CTioga/legends'


# for interpreting the CTIOGA environment variable
require 'shellwords'
require 'MetaBuilder/metabuilder'


module CTioga

  Version::register_svn_info('$Revision: 882 $', '$Date: 2009-02-24 15:23:12 +0100 (Tue, 24 Feb 2009) $')



  # The regular expression saying that what we see on the command-line
  # means "use default value".
  DEFAULT_RE = /^\s*(auto|default)\s*$/i
  
  # The regular expression saying that what we see on the command-line
  # means "disable".
  DISABLE_RE = /^\s*(no(ne)?|off)\s*$/i

  # The regular expression saying that what we see on the command-line
  # means "true".
  TRUE_RE = /^\s*(true|yes|on)\s*$/i

  # A small function to help quoting a string for inclusion
  # in a pdfTeX primitive.
  def self.pdftex_quote_string(str)
    return str.gsub(/([%#])|([()])|([{}~_^])|\\/) do 
      if $1
        "\\#{$1}"
      elsif $2                  # Quoting (), as they can be quite nasty !!
        "\\string\\#{$2}"
      elsif $3
        "\\string#{$3}"
      else                      # Quoting \
        "\\string\\\\"
      end
    end
  end


  # This class is responsible for reading the command-line, via it's parse
  # function, and to actually turn it into nice Tioga commands to make
  # even nicer graphes.
  class PlotMaker

    # PlotMaker is now handled (at least partially) by MetaBuilder.
    # That should save a lot of code.

    include MetaBuilder::DescriptionInclude
    extend  MetaBuilder::DescriptionExtend
    
    describe 'test', "A test class", <<EOD
A class to test visually the effects of different stuff
EOD

    include SciYAG
    include Tioga

    include Debug
    include Log
    include Utils               # For the safe_float function

    # For dimension conversion and TeX quoting.
    include Tioga::Utils

    # For the backend handling:
    include CTioga::Backends

    # Support for Themes:
    include Themes

    # Axes, edges, labels, tick labels:
    include Axes


    # these are basically the attributes which are modified directly
    # on the command-line
    attr_writer :fig_name, :cleanup, :legend

    # The command-line parser
    attr_reader :parser

    # The last Curve object on the stack
    attr_reader :last_curve

    # Whether we are trying to do real-size PDF or not. When set, it
    # is the size of the PDF
    attr_accessor :real_size

    # The current object
    attr_accessor :current_object

    # Whether we are making a PNG file
    attr_accessor :png

    # The PNG's size:
    attr_accessor :png_size

    # Whether to create separate legends
    attr_accessor :separate_legends
    
    # Whether to automatically add a legend to all curves
    attr_accessor :autolegends

    def initialize
      # The first thing to do is to setup logging, as you really
      # want to be able to communicate with users, don't you ?
      init_logger


      @args = []                # Holding the command-line
      @parser = OptionParser.new

      initialize_themes

      # Whether the plots should be interpolated.
      @interpolate = false 

      @line_width = nil

      @fig_name = "Plot"

      # Initialize the backend structure:
      init_backend_structure

      init_axes

      # now, the elements for the structure of the plot:
      @root_object = SubPlot.new
      # We start with a very simple layout
      SimpleLayout.new(@root_object)
      
      # the current object is the one to which we'll add plot elements.
      @current_object = @root_object

      # @legend is holding the text attached to the current item being
      # plotted; it has to be cleared manually after every item processed
      @legend = nil

      # general purpose variables:
      @cleanup = true           # Now, cleaning is done by default !
      @viewer = false

      # Some options we could set on the command line
      @init_funcalls = []

      # Whether to provide default legends. On by default
      @autolegends = true

      @command_line = ""

      @real_size = "12cmx12cm"

      # If set, should be the size of the TeX font
      
      @tex_fontsize = nil

      # The frame sides for the setup_real_size function
      @frame_sides = [0.1,0.9,0.9,0.1]
      # Override this, as the layout scheme makes it more or less
      # obsolete
      @frame_sides = [0,1,1,0]

      # If set, we create separate legend informations:
      @separate_legend = false

      # The array holding the styles.
      @separate_legend_styles = []
      # The size of the produced PDF in PDF points.
      @separate_legend_width = 12
      @separate_legend_height = 8

      # Whether to mark the command-line in the PDF file
      @mark = true              # On by default, really useful !!!

      # The LaTeX preamble:
      @preamble = ""

      # The last curve used:
      @last_curve = nil

      # Specifications for the --next stuff: an array, the first
      # element is the class to be created for children and the
      # rest are arguments to be added at the beginning.
      @next_specs = [SubPlot, :subplot]

      # A block to be run on both the old and new object
      # when --next is encountered.
      @next_block = nil

      # Whether we automatically start a --next stuff on each spec
      # or even on each dataset
      @auto_next = false

      # We don't make any PNG file:
      @png = false

      # The standard PNG density...
      @png_oversampling = 2

      # We don't produce SVG output by default
      @svg = false

      # The default padding;
      @default_padding = []
      4.times do 
        @default_padding << Dimension.new(0.05)
      end

      # And we use it :
      use_default_padding
      
      prepare_option_parser
    end

    # Sets the given's object padding to the current default:
    def use_default_padding(obj = nil)
      obj = current_object unless obj
      4.times do |i|
        obj.layout_preferences.padding[i] = @default_padding[i].dup
      end
      debug "Setting padding for #{identify(obj)} to " +
        obj.layout_preferences.padding.inspect
    end


    # Adds the given object to the current's stack and set it
    # as the current object, so that it will receive further
    # children
    def enter_child_object(object)
      @current_object.add_elem(object)
      self.current_object = object
    end

    # Goes out from a child object to its parent. Returns the child
    # object. Issues a warning if already at top level (in which case
    # it returns the current object)
    def leave_child_object
      prev = current_object
      if current_object.parent
        self.current_object = current_object.parent
      else
        warn "--end while in top level"
      end
      return prev
    end


    # Returns the current object's plot style
    def current_plot_style
      return current_object.plot_style
    end



    # This function reads a configuration file and executes it's
    # statements in the scope of a local module, which is then
    # included in this instance, and in the backend's instances.
    def read_config_file(file)
      f = File.open(file)
      info "Reading config file #{file}"
      lines = f.readlines
      lines << "\nend\n"
      lines.unshift "module CTiogaRC\n"
      code = lines.join
      eval code
      extend CTiogaRC
      for b_e in backends.values
        b_e.extend CTiogaRC
      end
      # This is for the compute_formula function.
      Dvector.extend CTiogaRC
    end
    
    CONFIG_FILE_NAME = ".ctiogarc"

    # Looks for a configuration file, either in the current directory
    # or in the HOME directory.
    def lookup_config_file
      if File.readable? CONFIG_FILE_NAME
        return CONFIG_FILE_NAME
      end

      if ENV.has_key?('HOME')
        home_rc = File.join(ENV['HOME'], CONFIG_FILE_NAME)
        if File.readable?(home_rc)
          return home_rc
        end
      end
      return nil
    end

    # Sets the frame of the root object. This is absolutely
    # necessary for layout computation. Failure to do so will
    # result in failed plots.
    def set_root_frame(*frames)
        @root_object.root_frame = frames
    end

    # sets up the FigureMaker object to use for real size. Uses the
    # @real_size instance variable as a source for the size
    def setup_real_size(t)
      # Get the width and height from @real_size
      sizes = @real_size.split("x").collect {|s| 
        tex_dimension_to_bp(s)
      }

      t.def_enter_page_function {
        t.page_setup(*sizes)
        t.set_frame_sides(*@frame_sides) 
        set_root_frame(sizes[0] * @frame_sides[0], 
                       sizes[0] * @frame_sides[1],
                       sizes[1] * @frame_sides[2], 
                       sizes[1] * @frame_sides[3])
      }

      # Setting label and title scale to 1
      t.title_scale = 1
      t.xlabel_scale = 1
      t.ylabel_scale = 1
    end

    # Reads a configuration file if one is found.
    def read_config
      if lookup_config_file
        read_config_file(lookup_config_file)
      end
    end


    # Push a function call onto the stack of the current element
    def add_elem_funcall(sym, *args)
      @current_object.add_funcall(TiogaFuncall.new(sym, *args))
    end

    # Push a function call onto the stack of things we should do
    # just after creating the FigureMakerobject
    def add_init_funcall(sym, *args)
      @init_funcalls << TiogaFuncall.new(sym, *args)
    end

    # Forwards the boundary settings to the current object.
    def set_bounds(which, val)
      method = ("bound_#{which}=").to_sym
      @current_object.send(method, val)
    end

    def set_range(a,b,str)
      first,last = str.split(/\s*:\s*/)
      first = first.to_f if first
      last = last.to_f if last
      set_bounds(a,first)
      set_bounds(b,last)
    end

    # Splits a text into four components, expanding if necessary:
    # * if there is only one element, all four become this one
    # * if there are two: w,h, it becomes w,w,h,h
    # * if there are three, the last element is duplicated.
    def expand_sides(txt)
      ary = txt.split(/\s*,\s*/)
      case ary.size
      when 1
        return [ary[0], ary[0], ary[0], ary[0]]
      when 2
        return [ary[0], ary[0], ary[1], ary[1]]
      when 3
        return [ary[0], ary[1], ary[2], ary[2]]
      else
        return ary
      end
    end

    def margins_from_text(txt)
      ary = expand_sides(txt).map {|s| s.to_f}
      return ary
    end

    # In the order: left, right, bottom, top, to suit Tioga's
    # default positioning of the text along the axis.
    def set_frame_margins(val)
      @frame_sides = [val[0], 1.0 - val[1], 1.0 - val[3], val[2]]
    end

    # This function prepares the parser by giving it the arguments and
    # some small help text going along with them. This function will
    # unfortunately get really large with time, but there's nothing
    # to do about it.
    def prepare_option_parser
      theme_prepare_parser(@parser)

      @parser.separator "\nLaTeX options"
      @parser.on("--use PACKAGE",
                 "Adds PACKAGE to the LaTeX preamble") do |w|
        @preamble += "\\usepackage{#{w}}\n"
      end

      @parser.on("--preamble STRING",
                 "Adds STRING to the LaTeX preamble") do |w|
        @preamble += "#{w}\n"
      end

      @parser.separator "\nGlobal look:"

      @parser.on("--[no-]background [COLOR]",
                 "Sets the background color for plots") do |c|
        # Keep it to false if only false
        c = CTioga.get_tioga_color(c) if c
        current_plot_style.background_color = c
      end

      @parser.on("--[no-]watermark [TEXT]",
                 "Writes a text as a watermark at the back ", 
                 "of the plot") do |c|
        current_plot_style.watermark_text = c
      end

      @parser.on("--watermark-color [COLOR]",
                 "Chooses the color of the watermark") do |c|
        c = CTioga.get_tioga_color(c) if c
        current_plot_style.watermark_color = c
      end

      @parser.on("--aspect-ratio [RATIO]",
                 "Sets the aspect ratio (defaults to 1)") do |a|
        a = 1.0 unless a
        a = a.to_f
        if a <= 0.0
          warn "Aspect ratio #{a} not valid, ignored" 
        else
          add_elem_funcall(:set_aspect_ratio, a)
        end
      end
      @parser.on("--golden-ratio",
                 "Sets the aspect ratio to the golden number") do |a|
        add_elem_funcall(:set_aspect_ratio, 1.61803398874989)
      end
      @parser.on("--xrange RANGE",
                 "X plotting range") do |a|
        set_range('left','right', a)
      end
      @parser.on("--yrange RANGE",
                 "y plotting range") do |a|
        set_range('bottom','top', a)
      end
      @parser.on("--margin MARGIN",
                 "Sets the margin around data", "(left,right,top,bottom",
                 "in fractions of the corresponding size)") do |v|
        current_object.plot_margins = margins_from_text(v)
      end

      @parser.on("--rescale FACTOR",
                 "Scales everything by a given factor",
                 "(useful for subplots)") do |fact|
        current_object.rescale = safe_float(fact)
      end


      # TODO: this should probably move to PlotStyle
      @parser.on("--padding PADDING",
                 "Changes the padding for the current object",
                 "and subsequent ones") do |pad|
        ary = expand_sides(pad)
        @default_padding = ary.map {|dim| Dimension.new(dim)}
        use_default_padding
      end

      axes_options(@parser)

      @parser.separator "\nSubfigures, subplots, regions..."

      @parser.on("--y2",
                 "Switch to an alternative Y axis") do
        begin
          current_object.disable_axis(:y)
        rescue
          # Purely ignore errors
          warn "Something should have happened here "+
            "that did not happen properly"
        end
        plot = SharedAxisPlot.new(:y, current_object)
        # The label will be on the right
        plot.plot_style.ylabel.side = RIGHT
        plot.plot_style.edges.yaxis.loc = RIGHT
        plot.plot_style.edges.set_edges(AXIS_HIDDEN, :left,:top,:bottom)
        current_plot_style.edges.set_edges(AXIS_LINE_ONLY, :right)
        # A simple layout will control the plot
        layout = FixedLayout.new(plot)        
        # And this layout should report its info to
        # the current one.
        #
        # Note that we add the child's *layout* to the
        # objects layout, not the child itself.
        #
        # Won't fail if the current plot does not have a layout
        if current_object.layout
          current_object.layout.add_child(layout)
        end
        enter_child_object(plot)
      end

      @parser.on("--x2",
                 "Switch to an alternative X axis") do
        begin
          current_object.disable_axis(:y)
        rescue
          # Purely ignore errors
          warn "Something should have happened here " +
            "that did not happen properly"
        end
        plot = SharedAxisPlot.new(:x, current_object)
        # The label will be on the right
        plot.xlabel.side = TOP
        plot.edges.xaxis.loc = TOP
        plot.edges.set_edges(AXIS_HIDDEN, :left,:right,:bottom)
        current_object.edges.set_edges(AXIS_LINE_ONLY, :top)
        layout = FixedLayout.new(plot)        
        # And this layout should report its info to
        # the current one.
        #
        # Note that we add the child's *layout* to the
        # objects layout, not the child itself.
        current_object.layout.add_child(layout)
        enter_child_object(plot)
      end

      @parser.on("--inset SPEC",
                 "Creates an inset with the given",
                 "specifications (x,y:w[xh] or x1,y1;x2,y2)" ) do |a|
        plot = SubPlot.new(:subplot, current_object)
        plot.show_legend = true
        enter_child_object(plot)
        # TODO - high priority !
        # implement a way to specify insets with *real* tex dimensions
        # !!!! (such as 3cm,2cm:5cm) !
        #
        # This would *really* make way for real-size graphs
        margins = Utils::inset_margins(a)
        debug "inset margins: #{margins.inspect}"
        current_object.convert_layout(FixedLayout)
        current_object.layout.position = margins
        # By default, we redirect all legends for the inset
        # to the parent.
        current_object.accept_legend = false
      end

      @parser.on("--zoom-inset SPEC",
                 "Creates an inset as with --inset " +
                 "and copies all the ",
                 "elements in the current plot there") do |a|
        plot = SubPlot.new(:subplot, current_object)
        # We redirect all legends for the inset to the parent.
        plot.accept_legend = false
        # Temporarily disable legends.
        plot.disable_legend = true
        for obj in @current_object.elements
          plot.add_elem(obj.dup)
        end
        for obj in @current_object.funcalls
          plot.add_funcall(obj.dup)
        end
        plot.disable_legend = false
        # Better add it afterwards...
        enter_child_object(plot)
        margins = Utils::inset_margins(a)
        debug "zoom margins: #{margins.inspect}"
        current_object.convert_layout(FixedLayout)
        current_object.layout.position = margins
      end

      @parser.on("--next-inset SPEC",
                 "Equivalent to --end --inset SPEC, except that",
                 "various style information are carried from",
                 "the previous inset") do |a|
        plot = SubPlot.new(:subplot, current_object)
        plot.show_legend = true
        old = leave_child_object
        enter_child_object(plot)

        # Force copy of the style:
        plot.plot_style = old.plot_style.deep_copy(plot)

        margins = Utils::inset_margins(a)
        debug "inset margins: #{margins.inspect}"
        current_object.convert_layout(FixedLayout)
        current_object.layout.position = margins
        # We redirect all legends for the inset to the parent.
        current_object.accept_legend = false
      end

      @parser.on("--subplot SPEC",
                 "Creates a subplot with the given specifications",
                 "(x,y:w[xh] or x1,y1;x2,y2)" ) do |a|
        plot = SubPlot.new(:subplot, current_object)
        enter_child_object(plot)
        margins = Utils::inset_margins(a)

        debug "subplot margins: #{margins.inspect}"
        # In contrast with --inset, we use a layout:
        current_object.layout = SimpleLayout.new(current_object)
        current_object.layout.bounding_box = margins

        # We do not redirect legends for a subplot
        current_object.accept_legend = true

      end

      @parser.on("--next-subplot SPEC",
                 "Creates a subplot with the given specifications",
                 "(x,y:w[xh] or x1,y1;x2,y2)" ) do |a|
        plot = SubPlot.new(:subplot, current_object)
        old = leave_child_object
        enter_child_object(plot)
        margins = Utils::inset_margins(a)

        # Force copy of the style:
        plot.plot_style = old.plot_style.deep_copy(plot)

        debug "subplot margins: #{margins.inspect}"
        # In contrast with --inset, we use a layout:
        current_object.layout = SimpleLayout.new(current_object)
        current_object.layout.bounding_box = margins

        # We do not redirect legends for a subplot
        current_object.accept_legend = true

      end



      @parser.on("--disable-legends",
                 "Disable the display of legends for the current " +
                 "subplot/subfigure") do
        current_object.disable_legend = true
      end

      @parser.on("--enable-legends",
                 "Reverts --disable-legends") do
        current_object.disable_legend = false
      end

      @parser.on("--[no-]forward-legends",
                 "Forwards the legends to the parent object") do |v|
        current_object.accept_legend = ! v
      end

      # Black magic is starting here !
      @parser.on("--grid SPEC",
                 "Creates a grid. SPEC is column|row=nb") do |s|
        # get the spec:
        s =~ /(\w+)=(\d+)/
        which = "#{$1}s=".to_sym
        number = $2.to_i
        if which == :columns= or which == :rows=
          current_object.layout = current_object.layout.
            convert_layout(GridLayout)

          current_plot_style.hide_axis_and_edges(:x, :y)

          # We rescale the padding by a factor of 1/number, so it looks
          # reasonable in the end.
          @default_padding.each do |dim|
            dim.scale!(1.0/number)
          end

          # We use the default padding for the chidren
          use_default_padding

          current_plot_style.set_xy_labels(nil, nil)

          current_object.layout.send(which, number)
          plot = SubPlot.new(:subplot, current_object)
          plot.accept_legend = false
          @current_object.layout.add_child(GridItemLayout.new(plot))
          @current_object.add_elem(plot)
          self.current_object = plot
          @next_spec = [SubPlot, :subplot]
          @next_block = proc do |old, new|
            new.accept_legend = false # By default, forward to the parent.
            use_default_padding(new)
          end
        else
          warn "Unrecognized spec #$1"
        end
      end

      # Black magic is starting here !
      @parser.on("--col",
                 "Starts a column of graphes with shared X axis") do
        # We convert the current object to a GridLayout. If
        # there are already plots, that really won't look good.
        # You've been warned !!!
        current_object.layout = current_object.layout.
          convert_layout(GridLayout)
#        current_object.edges.disable

        current_plot_style.hide_axis_and_edges(:x, :y)

        current_object.layout_preferences.padding[2] = Dimension.new(0.0)
        current_object.layout_preferences.padding[3] = Dimension.new(0.0)

        current_object.layout.columns = 1
        plot = SharedAxisPlot.new(:y, current_object)
        @next_spec = [SharedAxisPlot, :y]
        @current_object.layout.add_child(GridItemLayout.new(plot))
        @current_object.add_elem(plot)
        
        # We copy the X label and the title
        plot.plot_style.xlabel.label = current_plot_style.xlabel.label
        current_plot_style.xlabel.label = nil
        plot.plot_style.title.label = current_plot_style.title.label
        current_plot_style.title.label = nil
        self.current_object = plot

        # We cancel vertical padding by default:
        @default_padding[2] = Dimension.new(0.0)
        @default_padding[3] = Dimension.new(0.0)

        use_default_padding(plot)
        # Forwards the x label to the child, and cancels it here.
        # The code to be run on the old and new tings
        @next_block = proc do |old, new|
          old.plot_style.edges.
            set_axis_and_edges_style(:x, AXIS_WITH_TICKS_ONLY)
          old.plot_style.xlabel.label = nil

          # Disable title by default
          new.plot_style.title.label = nil
          use_default_padding(new)
        end
      end

      @parser.on("--next",
                 "Start the next object") do
        next_object
      end

      @parser.on("--[no-]auto-next",
                 "Automatically start a --next graph",
                 "for every element on the command-line") do |v|
        if v
          @auto_next = :element
        else
          @auto_next = false
        end
      end

      @parser.on("--auto-next-expanded",
                 "Automatically start a --next graph",
                 "for every element on the command-line",
                 "after data set expansion !") do
        @auto_next = :dataset
      end


#       @parser.on("--subframes FRAMES",
#                  "Setup the frames relative to the parent", 
#                  "for the current " +
#                  "subplot",
#                  "(distance from left,right,top,bottom)") do |v|
#         current_object.frame_margins = margins_from_text(v)
#       end

      @parser.on("--region",
                 "Start a colored region") do
        plot = Region.new(current_object)
        @current_object.add_elem(plot)
        self.current_object = plot
      end

      @parser.on("--region-color COLOR",
                 "The color of the region") do |v|
        c = CTioga.get_tioga_color(v)
        begin
          current_object.region_color = c
        rescue
          error "The current container isn't a --region !"
        end
      end

      @parser.on("--region-transparency TRANS",
                 "The transparency of the region") do |v|
        c = v.to_f
        begin
          current_object.region_transparency = c
        rescue
          error "The current container isn't a --region !"
        end
      end

      @parser.on("--region-dont-display",
                 "No curve until the next --end will actually be ",
                 "displayed: they are just used to delimit the ",
                 "region used for the fills") do
        begin
          current_object.dont_display = true
        rescue
          error "The current container isn't a --region !"
        end
      end

      @parser.on("--region-debug",
                 "Setup the fills for the curves inside the",
                 "region to help understanding what happens") do
        begin
          current_object.region_debug = true
        rescue
          error "The current container isn't a --region !"
        end
      end

      @parser.on("--region-invert-rule",
                 "Inverts the rule for choosing the filled region") do
        begin
          current_object.invert_rule = true
        rescue
          error "The current container isn't a --region !"
        end
      end

      @parser.on("--region-fill-twice",
                 "Fills the region twice, choosing opposite",
                 "rules for the boundaries.") do
        begin
          current_object.fill_twice = true
        rescue
          error "The current container isn't a --region !"
        end
      end

      @parser.on("--end",
                 "Ends the last subplot or region") do 
        leave_child_object
      end
      

      @parser.separator "\nOptions for real size output:"
      # Real size
      @parser.on("-r", "--[no-]real-size [SIZE]",
                 "Tries to produce a PDF file suitable",
                 "for inclusion " +
                 "with \\includegraphics") do |spec|
        @real_size = spec
      end
      @parser.on("--frame-margins VAL",
                 "The proportion of space to leave on", 
                 "each side for the text display" ) do |val|
        set_frame_margins(margins_from_text(val))
      end

      Legends::fill_option_parser(@parser, self)

      @parser.separator "\nTexts:"

      ## Default TeX fontsize
      @parser.on("--fontsize NB",
                 "Default TeX fontsize in points") do |size|
        @tex_fontsize = size
      end

      @parser.on("--tick-label-scale SCALE",
                 "Sets the scale of the text for tick labels") do |l|
        add_elem_funcall(:xaxis_numeric_label_scale=,Float(l))
        add_elem_funcall(:yaxis_numeric_label_scale=,Float(l))
      end


      @parser.on("--[no-]separate-legends",
                 "If set, PDF files are produced that contain",
                 "what the legend pictogram would look like") do |l|
        @separate_legends = l
        if l
          # Switches off autolegends by default
          @autolegends = false
        end
      end

      @parser.separator "\nGraphic primitives:"
      @parser.on("-d","--draw SPEC",
                 "Draw a graphic primitive") do |spec|
        add_elems_to_current(*TiogaPrimitiveMaker.parse_spec(spec, self))
      end
      @parser.on("--draw-help",
                 "Display what is known about graphic", 
                 "primitives and exit") do
        puts
        puts "Currently known graphics primitives are"
        puts
        puts TiogaPrimitiveMaker.introspect
        exit
      end

      prepare_backend_options(@parser)
                                              
      @parser.separator "\nHousekeeping and miscellaneous options:"
      @parser.on("--xpdf", 
                 "Runs xpdf to show the plot obtained") do 
        @viewer = "xpdf -z page" # With the zoom set to full page
      end

      @parser.on("--open", 
                 "Runs open to show the plot obtained") do 
        @viewer = "open"
      end

      @parser.on("--[no-]viewer [VIEWER]", 
                 "Runs VIEWER to show the plot obtained --",
                 "or doesn't show up the viewer at all") do |x|
        @viewer = x
      end
      
      @parser.on("--[no-]cleanup", 
                 "Removes all the accessory files produced") do |x|
        @cleanup = x
      end

      @parser.on("--tex-cleanup", 
                 "Removes all files produced except those",
                 "necessary " +
                 "for inclusion in a TeX document") do 
        @tex_cleanup = true
      end

      @parser.on("--clean-all", 
                 "Removes all files produced -- better use",
                 "along with " +
                 "--xpdf or --open") do 
        @clean_all = true
      end
      
      @parser.on("--save-dir DIR", 
                 "Sets the directory for output") do |x|
        add_init_funcall(:save_dir=, x)
      end

      
      @parser.on("--include FILE",
                 "Imports the file's functions into ctioga's",
                 "namespace so that they can be used", 
                 "by backends") do |file|
        read_config_file(file)
      end

      @parser.on("-n", "--name NAME",
                 "Base name") do |name|
        @fig_name = name
      end

      @parser.on("-o", "--output NAME",
                 "Produces a graph named NAME with the current output") do |name|
        output_figure(name)
      end

      @parser.on("--echo", "Prints command-line to standard output") do
        puts "Command-line used:"
        puts @command_line
      end

      @parser.on("--display-commandline",
                 "Adds the command line used to make",
                 "to graph on the bottom of the graph") do 
        add_elem_funcall(:show_marker, 
                         {
                           "x" => 0.5,
                           "y" => -0.2,
                           "text" => @command_line,
                           "scale" => 0.6,
                           "font" => Tioga::MarkerConstants::Courier,
                         }
                         )
      end

      @parser.on("--[no-]mark", 
                 "Fills the 'creator' field of the " ,
                 "produced PDF file with the command-line",
                 "when on. ") do |v|
        @mark = v
      end

      logger_options(@parser)
      @parser.on("--debug-patterns", 
                 "Produces an alignement-checking PDF file") do |v|
        @debug_patterns = v
      end

      @parser.on("--eps", 
                 "Attempt to produce an EPS file,",
                 "using pdftops, LaTeX and dvips.",
                 "The PDF output is kept in any case") do |v|
        @eps = v
      end

      @parser.on("--png SIZE", 
                 "Runs ctioga as usual, and runs convert afterwards",
                 "to produce a nice PNG file") do |s|
        @png = true
        s =~ /(\d+)x(\d+)/
        @png_size = [$1,$2]
        # We setup the real size so that it matches the number of
        # postscript points:
        @real_size = "#{$1}bpx#{$2}bp"
      end

      @parser.on("--png-oversampling NB", 
                 "How many more points to produce before downscaling") do |s|
        @png_oversampling = s.to_f
      end

      @parser.on("--svg", 
                 "Runs ctioga as usual, and runs pdf2svg afterwards",
                 "to produce a nice SVG file") do |s|
        @svg = true
      end


      @parser.on("--args FILE",
                 "Reads argument from FILE, *one per",
                 "line* and then resumes normal processing") do |v|
        File.open(v) do |f|
          unshift_cmdline_args(*f.readlines.collect {|s| s.chomp})
        end
      end

      @parser.on_tail("-h", "--help", "Show this message") do
        puts banner
        puts 
        puts @parser
        puts 
        puts "Please note that all the options can be abbreviated"
        puts "as long as they are still unique"
        exit
      end

      @parser.on_tail("--version", "Show version number") do
        puts "This is ctioga version " + Version::version
        exit
      end
    end


    # Start the next object in a complex plot (--grid, --col...)
    def next_object
      # We swicth back to the parent object
      old = leave_child_object
      a = @next_spec.dup      # We need to dup, else shift would make the
      # next curve not that happy...
      cls = a.shift
      # We push the parent object as the last element for new.
      a << current_object
      plot = cls.new(*a)
      @current_object.layout.add_child(GridItemLayout.new(plot))
      enter_child_object(plot)

      # We copy the style from the old plot to the new one:
      plot.plot_style = old.plot_style.deep_copy(plot)
        
      @next_block.call(old, plot) if @next_block
    end


    def unshift_cmdline_args(*args)
      @args.unshift(*args)
    end
    
    # the functions for plot structure...
    def subplot
      new_object = SubPlot.new(:subplot,@current_object)
      @current_object.add_elem(new_object)
      @current_object = new_object
    end

    def subfigure
      new_object = SubPlot.new(:subfigure,@current_object)
      @current_object.add_elem(new_object)
      @current_object = new_object
    end

    def end
      @current_object = @current_object.parent if @current_object.parent
    end

    def add_elems_to_current(*elems)
      for el in elems
        @current_object.add_elem(el)
      end
    end

    # This function is called whenever PlotMaker needs to process one
    # data set. It's name is given in parameter here.
    def process_data_set(set)
      # First, we get the dataset:
      begin
        data = xy_data_set(set)
      rescue Exception => ex
        error "Problem when processing set #{set} " +
          "(backend #{backend.class}): #{ex.message}"
        debug "Error backtrace: #{ex.backtrace.join "\n"}"
        warn "Simply ignoring set #{set}"
        return                  # Simply return.
      end
        
      # Scale plots if necessary:
      data.mul!(:x,x_factor) if x_factor
      data.mul!(:y,y_factor) if y_factor

      # Add an offset
      data.add!(:x,x_offset) if x_offset
      data.add!(:y,y_offset) if y_offset

      # Implement a logarithmic scale
      data.safe_log10!(:x) if x_log
      data.safe_log10!(:y) if y_log

      data.strip_nan

      # Then we create a curve object and give it style
      if histogram
        curve = Histogram2D.new(get_current_style(set)) 
      else
        curve = Curve2D.new(get_current_style(set)) 
      end
      if @separate_legends
        @separate_legend_styles << curve.style
      end
      curve.set_data(data)

      # Then we add ot to the current object:
      add_elems_to_current(curve)
      @last_curve = curve       # And we update the last_curve pointer.
    end

    # Provides the name of an output file, given its suffix and
    # optionnaly it base name (defaults to @figure_name).
    def output_filename(suffix, base = @fig_name) 
      if @figmaker.save_dir
        File.join(@figmaker.save_dir,"#{base}#{suffix}")
      else
        "#{base}#{suffix}"
      end
    end

    # Parses _args_ as command-line arguments.
    def parse_command_line_arguments(args)

      # A little trick to allow in-place modification of the
      # command-line from an option.
      @args += args

      # Runs the command to set command-line defaults. Ignored silently
      # in the files given on the command-line.

      quoted_args = args.collect do |s|
        CTioga.shell_quote_string(s)
      end.join ' '

      @command_line = "#{File.basename($0)} #{quoted_args}"

      # Read the CTIOGA environment variable 
      if ENV.key? "CTIOGA"
        ctioga_env = Shellwords.shellwords(ENV["CTIOGA"])
        @parser.parse(ctioga_env)
      end

      if respond_to? :ctioga_defaults
        send :ctioga_defaults
      end

      first = true
      done = false
      while ! done
        begin
          @parser.order!(@args) do |spec|
            # We use Backend#expand to leave room for the backend to
            # interpret the text as many different sets.
            sets = expand_spec(spec)
            info "Expanding #{spec} to #{sets.join(', ')}"
            # Auto --next: 
            next_object if (@auto_next == :element and !first)
            for set in sets
              next_object if (@auto_next == :dataset and !first)
              process_data_set(set)
              first = false
            end
          end
          done = true
        rescue OptionParser::InvalidOption => o
          # Here, we should try to be clever about options:
          new_arg = nil
          option_name = o.args.first.gsub(/^--?/,'')
          # First, we check whether it corresponds to a shortcut:
          if Shortcut.has? option_name
            new_arg = ["--short", option_name]
          # Or an option of the current backend:
          elsif backend.description.param_hash.key? option_name
            new_arg = ["--#{backend.description.name}-#{option_name}"]
          end

          if new_arg
            info "Option #{o.args.first} was reinterpreted " +
              "as #{new_arg.join ' '}"
            @args.unshift(*new_arg)
          else
            debug "Invalid option: #{o.inspect}"
            debug "Remaining args: #{@args.inspect}"
            error "Invalid option: #{o.args.first}. Please run " + 
              "ctioga --help to have a list of valid ones"
            exit 1
          end
        rescue Exception => ex
          # Do not catch normal exit !
          if ex.is_a? SystemExit
            raise
          end
          fatal "ctioga encountered the following problem: #{ex.to_s}"
          debug "Error backtrace: #{ex.backtrace.join "\n"}"
          fatal "Aborting"
          exit 1
        end
      end
    end

    # Setups up various figuremaker variables
    def setup_figure_maker(t)
      # Setting the TeX fontsize if default is not adequate
      t.tex_fontsize = @tex_fontsize if @tex_fontsize

      # add hyperref preamble to have nice PDF comments:
      if @mark
        # We should use the \pdfinfo pdftex primitive, as
        # this will allow to have less surprises...
        t.tex_preview_preamble += 
          "\n\\pdfinfo {\n  /Title (" +
          CTioga.pdftex_quote_string(@command_line) +
          ")\n" + "/Creator(" +
          CTioga.pdftex_quote_string("ctioga #{Version::version}") +
          ")\n}\n"
      end

      if @debug_patterns
        debug_patterns(t)
      end

      # We use Vincent's algorithm for major ticks when available ;-)...
      begin
        t.vincent_or_bill = true
        info "Using Vincent's algorithm for major ticks"
      rescue
        info "Using Bill's algorithm for major ticks"
      end


      return unless @root_object.number > 0 

      # Asking the theme to do something.
      theme_bod_hook(t)

      # We put the command line used in the .tex file:
      t.tex_preview_preamble += 
        "% Produced by ctioga, with the command-line\n" +
        "% #{@command_line.gsub(/\n/,"\n% ")}\n"
      
      if decimal_separator
        t.tex_preamble += <<'EOD'.gsub(',',decimal_separator) # Neat !
\def\frenchsep#1{\frenchsepma#1\endoffrenchsep} 
\def\fseat#1{\frenchsepma}
\def\frenchsepma{\futurelet\next\frenchsepmw}
\def\frenchsepmw{\ifx\next\endoffrenchsep\let\endoffrenchsep=\relax%
\else\if\next.\ifmmode\mathord,\else,\fi%
\else\next\fi\expandafter
\fseat\fi}
EOD
        t.yaxis_numeric_label_tex = t.
          yaxis_numeric_label_tex.gsub('#1','\frenchsep{#1}')
        t.xaxis_numeric_label_tex = t.
          xaxis_numeric_label_tex.gsub('#1','\frenchsep{#1}')
      end

      # Add custom preamble:
      t.tex_preview_preamble += @preamble

      # If the initialization function was found, we call it
      if respond_to? :ctioga_init
        send :ctioga_init, t
      end

      for funcall in @init_funcalls
        funcall.do(t)
      end

    end

    # Runs the code to make the actual figure.
    def do_figure(t)
      @root_object.do(t)
    end

    # Ctioga's banner
    def banner
      <<"EOBANNER" 
This is ctioga version #{Version::version}
ctioga is copyright (C) 2006, 2007, 2008 by Vincent Fourmond 

ctioga comes with absolutely NO WARRANTY. This is
free software, you are welcome to redistribute it under certain
conditions. See the COPYING file in the original tarball for
details. You are also welcome to contribute if you like it, see in
the manual page where to ask.   
EOBANNER
    end

    # Output a figure with the given name
    def output_figure(fig_name)
      # now, we plot the stuffs... if there is actually
      # something to be plotted ;-) !

      @figmaker = t = FigureMaker.new
      # Cleanup code off by default, as this could cause problems for
      # legend pictures generation/EPS generation.
      # The files will get cleaned up anyway.
      t.autocleanup = false


      # Now, we must use real_size stuff !! (default 12cmx12cm)
      # This is not part of setup_figure_maker, as it can strongly interfere
      # when ctioga is used to produce a figure.
      info "Producing PDF file of real size #{@real_size}"
      setup_real_size(t) 

      # And, after that, the generic setup of the FigureMaker object.
      setup_figure_maker(t)
      

      t.def_figure(fig_name) {
        do_figure(t)
      }

      debug "Contents of the main FigureMaker object: " +
        figmaker_options(t)

      info "Generating file '#{fig_name}.pdf'"
      if @fast
        # It isn't that fast, actually.
        info "Producing a fast version"
        # We output only the temporary PDF file
        class << t
          undef show_text
          # We replace show_text by show_marker, to at least get text
          # Doesn't work for axes and ticks... Pity ! 
          def show_text(dict)
            for key in %w{side shift position pos justification}
              dict.delete key
            end
            show_marker(dict)
          end
        end
        t.create_figure_temp_files(t.figure_index(fig_name))
        base_name = if t.save_dir
                      File.join(t.save_dir,fig_name)
                    else
                      fig_name
                    end
        # Remove the _figure.txt files
        File.rename(base_name + "_figure.pdf", base_name + ".pdf")
      else
        t.make_preview_pdf(t.figure_index(fig_name))
      end

      if @eps
        info "Attempting to create an EPS file, watch out error messages"
        # First, we convert the _figure.pdf into eps with pdftops
        cmd = "pdftops -eps #{output_filename('_figure.pdf', fig_name)}"
        debug "Running #{cmd}"
        system cmd
        f = output_filename('.tex', fig_name)
        new = output_filename('.new.tex', fig_name)
        debug "Modifying #{f}"
        orig = open(f)
        out = open(new, "w")
        for l in orig
          l.gsub!('pdftex,','') # \usepackage[pdftex,...]...
          l.gsub!('_figure.pdf', '_figure.eps')
          out.print l
        end
        orig.close
        out.close

        spawn "latex #{new}"
        spawn "dvips -o #{output_filename('.eps', fig_name)} -t unknown -T " +
          "#{@real_size.gsub(/x/,',')} #{output_filename('.new.dvi', fig_name)}"
      end

      if @png
        # We create the PNG file:
        spawn "convert -density #{(@png_oversampling * 72).to_i} "+
          "#{output_filename('.pdf', fig_name)} -resize " +
          "#{@png_size.join('x')} #{output_filename('.png', fig_name)}"
      end

      if @svg
        # We create the SVG file
        spawn "pdf2svg #{output_filename('.pdf', fig_name)} " +
          "#{output_filename('.svg', fig_name)}"
      end

      
      # Generating the separate legends if requested
      if not @separate_legend_styles.empty?
        index = 0
        info "Creating separate lengends"
        for style in @separate_legend_styles 
          name = sprintf "%s_legend_%02d", fig_name, index
          index += 1
          t.def_figure(name) {
            t.set_device_pagesize(@separate_legend_width * 10,
                                  @separate_legend_height * 10)
            t.set_frame_sides(0,1,1,0) 
            t.update_bbox(0,0)
            t.update_bbox(1,1)
            style.output_legend_pictogram(t)
          }
          # create_figure takes a number. It is part of Tioga's internals.
          t.create_figure_temp_files(t.figure_index(name))
          

          # Some post_processing to rename the files appropriately
          # and delete the unwanted _figure.txt file
          base_name = if t.save_dir
                        File.join(t.save_dir,name)
                      else
                        name
                      end
          # Remove the _figure.txt files
          begin
            File.rename(base_name + "_figure.pdf", base_name + ".pdf")
            if @eps             # Create also EPS files:
              spawn "pdftops -eps #{base_name}.pdf"
            end
            File.delete(base_name + ".tex")
            File.delete(base_name + "_figure.txt")
          rescue
            warn "Files should have been deleted, but were not found"
          end
        end
      end

      if @cleanup
        files_to_remove = %w(.tex .out .aux .log _figure.pdf _figure.txt) +
          %w(.new.tex .new.aux .new.dvi .new.log _figure.eps) # EPS-related files
      elsif @tex_cleanup
        files_to_remove = %w(.tex .out .aux .log .pdf)
      elsif @clean_all
        files_to_remove = %w(.tex .out .aux .log .pdf _figure.pdf _figure.txt) +
          %w(.new.tex .new.aux .new.dvi .new.log .eps _figure.eps) # EPS-related files

      end
      if @viewer 
        # we display the output file:
        info "Starting the viewer #{@viewer} as requested"
        pdf_file = if t.save_dir
                     File.join(t.save_dir,fig_name+".pdf")
                   else
                     fig_name+".pdf"
                   end
        system("#{@viewer} #{pdf_file}")
      end
      
      if files_to_remove
        files_to_remove.collect! {|s| 
          output_filename(s, fig_name)
        }
        files_to_remove << "pdflatex.log" 
        info "Cleaning up temporary files"
        debug "Cleaning #{files_to_remove.join ' '}"
        files_to_remove.each do |f| 
          begin
            File.delete(f)
          rescue
            debug "File #{f} absent, not deleted" 
          end
        end
      end

    end

    # Runs the plots, using the arguments given in the command-line.
    # Now delegates to some other small functions.
    def run(args)

      # First, we parse command-line arguments:
      parse_command_line_arguments(args)

      # Then, we spit the output:
      output_figure(@fig_name)

    end

  end

  # Takes a string a returns a quoted version that should be able
  # to go through shell expansion. For now, it won't work with
  # strings containing ', but that's already a good help.
  def CTioga.shell_quote_string(str)
    if str =~ /[\s"*$()\[\]{}';]/
      if str =~ /'/
        a = str.gsub(/(["$\\])/) { "\\#$1" }
        return "\"#{a}\""
      else 
        return "'#{str}'"
      end
    else
      return str
    end
  end

  # This function provides a 'command-line' interface from within Tioga !
  def CTioga.define_tioga_figure(t, name, *args)
    t.def_figure(name) {
      CTioga.show_tioga_plot(t, *args)
    }
  end

  # This function provides a 'command-line' interface from within Tioga !
  def CTioga.show_tioga_plot(t, *args)
    plot = PlotMaker.new
    plot.parse_command_line_arguments(args)
    plot.setup_figure_maker(t)
    
    width = 72 * t.
      convert_output_to_inches(t.
                               convert_figure_to_output_dx(t.convert_frame_to_figure_dx(1)))
    height = 72 * t.
      convert_output_to_inches(t.
                               convert_figure_to_output_dy(t.convert_frame_to_figure_dy(1)))

    plot.set_root_frame(0, width, height, 0)
    
    plot.do_figure(t)
  end

  
end