ctioga2 0.12 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +12 -0
  3. data/lib/ctioga2/commands/doc/doc.rb +34 -0
  4. data/lib/ctioga2/commands/doc/help.rb +3 -0
  5. data/lib/ctioga2/commands/doc/html.rb +1 -1
  6. data/lib/ctioga2/commands/doc/man.rb +1 -1
  7. data/lib/ctioga2/commands/doc/markup.rb +6 -0
  8. data/lib/ctioga2/commands/general-commands.rb +13 -0
  9. data/lib/ctioga2/commands/interpreter.rb +1 -1
  10. data/lib/ctioga2/commands/parsers/file.rb +9 -2
  11. data/lib/ctioga2/commands/parsers/old-file.rb +1 -1
  12. data/lib/ctioga2/data/backends/backends/gnuplot.rb +1 -1
  13. data/lib/ctioga2/data/backends/backends/text.rb +2 -25
  14. data/lib/ctioga2/data/datacolumn.rb +34 -0
  15. data/lib/ctioga2/data/dataset.rb +11 -13
  16. data/lib/ctioga2/data/filters.rb +24 -5
  17. data/lib/ctioga2/data/stack.rb +3 -2
  18. data/lib/ctioga2/graphics/elements.rb +7 -0
  19. data/lib/ctioga2/graphics/elements/histogram.rb +22 -1
  20. data/lib/ctioga2/graphics/elements/subplot.rb +11 -3
  21. data/lib/ctioga2/graphics/legends/area.rb +2 -2
  22. data/lib/ctioga2/graphics/legends/items.rb +2 -2
  23. data/lib/ctioga2/graphics/styles/background.rb +1 -1
  24. data/lib/ctioga2/graphics/styles/image.rb +1 -0
  25. data/lib/ctioga2/graphics/styles/plot-types.rb +19 -0
  26. data/lib/ctioga2/graphics/styles/plot.rb +2 -2
  27. data/lib/ctioga2/graphics/styles/styles.rb +1 -1
  28. data/lib/ctioga2/graphics/styles/texts.rb +5 -2
  29. data/lib/ctioga2/graphics/subplot-commands.rb +1 -1
  30. data/lib/ctioga2/graphics/types/boundaries.rb +7 -0
  31. data/lib/ctioga2/graphics/types/dimensions.rb +2 -2
  32. data/lib/ctioga2/graphics/types/grid.rb +5 -0
  33. data/lib/ctioga2/graphics/types/point.rb +2 -2
  34. data/lib/ctioga2/plotmaker.rb +46 -2
  35. data/lib/ctioga2/ruby.rb +1 -1
  36. data/lib/ctioga2/utils.rb +100 -7
  37. data/lib/ctioga2/version.rb +2 -2
  38. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f25f506adfb25689dc8c090adcfb35beac30af3d
4
- data.tar.gz: 1731cd6baf714e9bf480d9ea50039b9acc0ac9d4
3
+ metadata.gz: c0bb20ba501e3fd5bda7270ed5ef5330e0dd27c1
4
+ data.tar.gz: 67b4272430de8d90aa5b539fb7ac6d8c21a9e111
5
5
  SHA512:
6
- metadata.gz: d7fb0ecfa3ed8cc7c2e3e8b5723b92b676ba837681eb33eb7fa19db61cf0f0131566cbf76f4a6c49c91969b074553607b3bdfcdae3df1d52510432339a22d1de
7
- data.tar.gz: 87e302b8f937f3f638f24a0eca891cec89282aa10d7a2f7721432dbdd0e100711334efe883fd65a79dc7e72b540074336a5c5048844171b8cd8dcdf5b44554f2
6
+ metadata.gz: 108ec5f7a5549506ba8ca7472949bbffae2d8fe4180cf1eff08bf0722a86a971ab3375a8a4594defa94e2a5e289e81909d7a6d0f8f7f0096e7a3708bab334d10
7
+ data.tar.gz: fec86d2c4348f8c9862f419e1c4ba4aa0b00fd81801134ac2c6b6fe75012815cc8966f3417e6f6bcfc1f4ad94e958f72aa5b225cb76f3d1d2921303d6b3c3a28
data/Changelog CHANGED
@@ -1,3 +1,15 @@
1
+ ctioga2 (0.13)
2
+
3
+ * Change the stroke width when using draw-marker
4
+ * Customizable outut PDF resolution
5
+ * Can now get the standard deviation as error bars using avg-dup-last
6
+ * Handling of histograms with 'holes' in X values (closes: SF issue #1)
7
+ * Various improvements in the emacs mode, including contextual help
8
+ * Automatic generation of dependencies (makefile format)
9
+ * Many bug fixes
10
+
11
+ -- Vincent <vincent.fourmond@9online.fr> Thu 11 Jun 23:01:19 CEST 2015
12
+
1
13
  ctioga2 (0.12)
2
14
 
3
15
  * The xyz-map plot type now handles correctly inhomogeneous grids (so long
@@ -90,6 +90,40 @@ module CTioga2
90
90
  print_commandline_options(*self.documented_commands)
91
91
  end
92
92
 
93
+ # Displays help on a given command
94
+ def display_help_on(cmd, options)
95
+ if ! cmd.is_a? Command
96
+ cd = Interpreter::commands[cmd]
97
+ raise "Unkown command '#{cmd}'" unless cd
98
+ cmd = cd
99
+ end
100
+ puts text_doc(cmd)
101
+ end
102
+
103
+ # Returns a string that represents a plain text documentation
104
+ def text_doc(cmd, options = {})
105
+
106
+ size ||= 80
107
+
108
+ str = "Synopsis: "
109
+ str << cmd.name
110
+ for arg in cmd.arguments
111
+ str << " #{arg.type.name}"
112
+ end
113
+
114
+ os = ""
115
+ for k,v in cmd.optional_arguments
116
+ os << " /#{k}=#{v.type.name}"
117
+ end
118
+ s2 = WordWrapper.wrap(os, size-4) # 4 for the spaces
119
+ str << "\nOptions: #{s2.join("\n ")}"
120
+ shrt = MarkedUpText.new(self, cmd.short_description).to_s
121
+ mup = MarkedUpText.new(self, cmd.long_description).to_s
122
+ s2 = WordWrapper.wrap(mup.to_s, size)
123
+ return "#{cmd.name} -- #{shrt}\n#{str}\n#{s2.join("\n")}"
124
+ end
125
+
126
+
93
127
  protected
94
128
 
95
129
 
@@ -140,6 +140,9 @@ module CTioga2
140
140
  # Formats one entry of the commands
141
141
  def format_one_entry(cmd)
142
142
  sh, long, desc = cmd.option_strings
143
+
144
+ # Hmmm...
145
+ # desc = MarkedUpText.new(@doc, desc).to_s
143
146
 
144
147
  str = "#{leading_spaces}%2s%1s %-#{@options_column_width}s" %
145
148
  [ sh, (sh ? "," : " "), long]
@@ -144,7 +144,7 @@ module CTioga2
144
144
  ln.gsub!(/<a[^>]+>(.*?)<\/a>/) { || $1 }
145
145
  str += "<pre class='#{s[:cls]}'><a href='#{k}'>#{ln}</a></pre>\n"
146
146
  end
147
- out.puts "<h5 id='#snippets-h5-#{cmd.name}' onclick='toggleExamples(this);'>Examples...</h5>\n<div id='#snippets-#{cmd.name}' class='snippets'>#{str}</div>"
147
+ out.puts "<h5 id='snippets-h5-#{cmd.name}' onclick='toggleExamples(this);'>Examples...</h5>\n<div id='snippets-#{cmd.name}' class='snippets'>#{str}</div>"
148
148
  end
149
149
  end
150
150
  end
@@ -48,7 +48,7 @@ module CTioga2
48
48
  passed_header = false
49
49
  if input.is_a? String
50
50
  filename = input
51
- input = File::open(input)
51
+ input = Utils::open(input)
52
52
  elsif input.respond_to? :path
53
53
  filename = input.path
54
54
  else
@@ -280,6 +280,12 @@ module CTioga2
280
280
  flush_element
281
281
  end
282
282
 
283
+ def to_s
284
+ return @elements.map do |x|
285
+ x.to_s
286
+ end.join("")
287
+ end
288
+
283
289
  protected
284
290
 
285
291
  # A few constants to help writing out the paragraph markup
@@ -42,7 +42,20 @@ module CTioga2
42
42
  Prints helps about short and long options available when run from the
43
43
  command-line.
44
44
  EOH
45
+
46
+ # Display help on the command-line
47
+ HelpOnCommand =
48
+ Cmd.new("help-on", nil,
49
+ "--help-on", [CmdArg.new('text') ]) do |plotmaker, cmd, options|
50
+ plotmaker.interpreter.doc.display_help_on(cmd, options)
51
+ exit
52
+ end
45
53
 
54
+ HelpOnCommand.describe("Prints help text about the given command",
55
+ <<EOH, GeneralGroup)
56
+ Prints help about the given command
57
+ EOH
58
+
46
59
  # Prints the version of ctioga2 used
47
60
  PrintVersion = Cmd.new("version", '-V', "--version", []) do |plotmaker|
48
61
  puts "This is ctioga2 version #{CTioga2::Version::version}"
@@ -224,7 +224,7 @@ module CTioga2
224
224
  dir = File::dirname(file)
225
225
  base = File::basename(file)
226
226
 
227
- Dir::chdir(dir) do
227
+ Utils::chdir(dir) do
228
228
  @file_parser.run_command_file(base, self)
229
229
  end
230
230
  end
@@ -44,7 +44,7 @@ module CTioga2
44
44
 
45
45
  # Runs a command file targeting the given _interpreter_.
46
46
  def run_command_file(file, interpreter)
47
- f = open(file)
47
+ f = Utils::open(file)
48
48
  parse_io_object(f, interpreter)
49
49
  end
50
50
 
@@ -208,8 +208,15 @@ module CTioga2
208
208
  end
209
209
  else
210
210
  nxt = all_args.shift
211
+ if ! nxt
212
+ fatal { "Missing option text for option '#{o}'"}
213
+ end
211
214
  if nxt =~ /^\s*=\s*$/
212
- opts[o] = all_args.shift
215
+ nxt = all_args.shift
216
+ if ! nxt
217
+ fatal { "Missing option text for option '#{o}'"}
218
+ end
219
+ opts[o] = nxt
213
220
  else
214
221
  opts[o] = nxt.gsub(/^\s*=/,'')
215
222
  end
@@ -52,7 +52,7 @@ module CTioga2
52
52
 
53
53
  # Runs a command file targeting the given _interpreter_.
54
54
  def run_command_file(file, interpreter)
55
- f = open(file)
55
+ f = Utils::open(file)
56
56
  parse_io_object(f, interpreter)
57
57
  end
58
58
 
@@ -78,7 +78,7 @@ EOD
78
78
  date = File::mtime(filename)
79
79
  # Get it from the cache !
80
80
  debug { "Running gnuplot on file #{filename}" }
81
- f = File.open(filename)
81
+ f = Utils::open(filename)
82
82
  # We open a bidirectionnal connection to gnuplot:
83
83
  gnuplot = IO.popen("gnuplot", "r+")
84
84
  output = ""
@@ -39,15 +39,6 @@ module CTioga2
39
39
 
40
40
  class TextBackend < Backend
41
41
 
42
- # A constant holding a relation extension -> command to
43
- # decompress (to be fed to sprintf with the filename as argument)
44
- UNCOMPRESSORS = {
45
- ".gz" => "gunzip -c %s",
46
- ".bz2" => "bunzip2 -c %s",
47
- ".lzma" => "unlzma -c %s",
48
- ".lz" => "unlzma -c %s",
49
- ".xz" => "unxz -c %s",
50
- }
51
42
 
52
43
  include Dobjects
53
44
 
@@ -171,23 +162,9 @@ EOD
171
162
  return $stdin
172
163
  elsif file =~ /(.*?)\|\s*$/ # A pipe
173
164
  return IO.popen($1)
174
- elsif not File.readable?(file)
175
- # Try to find a compressed version
176
- for ext,method in UNCOMPRESSORS
177
- if File.readable? "#{file}#{ext}"
178
- info { "Using compressed file #{file}#{ext} in stead of #{file}" }
179
- return IO.popen(method % "#{file}#{ext}")
180
- end
181
- end
182
- else
183
- for ext, method in UNCOMPRESSORS
184
- if file =~ /#{ext}$/
185
- info { "Taking file #{file} as a compressed file" }
186
- return IO.popen(method % file)
187
- end
188
- end
165
+ else
166
+ return Utils::open(file)
189
167
  end
190
- return File::open(file)
191
168
  end
192
169
 
193
170
  # A line is invalid if it is blank or starts
@@ -283,6 +283,40 @@ module CTioga2
283
283
  v.replace(v.convolve(kernel,middle)) if v
284
284
  end
285
285
  end
286
+
287
+ # Resize all the columns to the given size.
288
+ def resize!(new_size)
289
+ for v in all_vectors
290
+ v.resize(new_size) if v
291
+ end
292
+ end
293
+
294
+ # Averages over the given indices, and puts the result at the
295
+ # target index.
296
+ #
297
+ # Different averaging modes are available.
298
+ def average_over(istart, iend, itgt, mode = :avg)
299
+ case mode
300
+ when :avg
301
+ # Stupidly average over all the values
302
+ for v in all_vectors
303
+ if v
304
+ av = Utils::average_vector(v, istart, iend)
305
+ v[itgt] = av[0]
306
+ end
307
+ end
308
+ when :stddev
309
+ # Ignore errors, and set them from the standard deviation
310
+ @min_values ||= @values.dup
311
+ @max_values ||= @values.dup
312
+
313
+ av = Utils::average_vector(@values, istart, iend, 2)
314
+ @values[itgt] = av[0]
315
+ stddev = (av[1] - av[0]**2)**0.5
316
+ @min_values[itgt] = av[0] - stddev
317
+ @max_values[itgt] = av[0] + stddev
318
+ end
319
+ end
286
320
 
287
321
  protected
288
322
 
@@ -269,18 +269,19 @@ module CTioga2
269
269
  # Average all the non-X values of successive data points that
270
270
  # have the same X values. It is a naive version that also
271
271
  # averages the error columns.
272
- def average_duplicates!
272
+ def average_duplicates!(mode = :avg)
273
273
  last_x = nil
274
274
  last_x_first_idx = 0
275
275
  xv = @x.values
276
276
  i = 0
277
277
  vectors = all_vectors
278
+ nb_x = 0
278
279
  while i < xv.size
279
280
  x = xv[i]
280
281
  if ((last_x == x) && (i != (xv.size - 1)))
281
282
  # Do nothing
282
283
  else
283
- if last_x_first_idx < (i - 1) ||
284
+ if last_x_first_idx <= (i - 1) ||
284
285
  ((last_x == x) && (i == (xv.size - 1)))
285
286
  if i == (xv.size - 1)
286
287
  e = i
@@ -288,24 +289,21 @@ module CTioga2
288
289
  e = i-1
289
290
  end # The end of the slice.
290
291
 
291
- ## \todo In real, to do this properly, one would
292
- # have to write a proper function in DataColumn that
293
- # does averaging over certain indices possibly more
294
- # cleverly than the current way to do.
295
- for v in vectors
296
- subv = v[last_x_first_idx..e]
297
- ave = subv.sum/subv.size
298
- v.slice!(last_x_first_idx+1, e - last_x_first_idx)
299
- v[last_x_first_idx] = ave
292
+ # Now, we delegate to the columns the task of averaging.
293
+ @x.average_over(last_x_first_idx, e, nb_x, :avg)
294
+ for c in @ys
295
+ c.average_over(last_x_first_idx, e, nb_x, mode)
300
296
  end
301
- i -= e - last_x_first_idx
297
+ nb_x += 1
302
298
  end
303
299
  last_x = x
304
300
  last_x_first_idx = i
305
301
  end
306
302
  i += 1
307
303
  end
308
-
304
+ for c in all_columns
305
+ c.resize!(nb_x)
306
+ end
309
307
  end
310
308
 
311
309
  # Applies formulas to values. Formulas are like text-backend
@@ -123,10 +123,30 @@ Install the {command: cherry-pick-last} command as a dataset hook (see
123
123
  false for subsequent datasets will be removed.
124
124
  EOH
125
125
 
126
+ AVGDupModeRE = {
127
+ /naive|avg|average/i => :avg,
128
+ /stddev/i => :stddev,
129
+ }
130
+
131
+ AVGDupMode =
132
+ CmdType.new('average-mode',
133
+ {:type => :re_list,
134
+ :list => AVGDupModeRE}, <<EOD)
135
+ How the {command: avg-dup-last} command :
136
+
137
+ * @naive@ or @average@ (the default) treats all columns (values and
138
+ error bars) the same way, and average everythin
139
+ * @stddev@ ignores the original errors, and sets the new errors to the
140
+ standard deviation of the values
141
+ EOD
142
+
143
+
144
+
126
145
  AverageDupOperation =
127
146
  Cmd.new("avg-dup-last", nil, "--avg-dup-last",
128
- [], {}) do |plotmaker|
129
- plotmaker.data_stack.last.average_duplicates!
147
+ [], {'mode' => CmdArg.new('average-mode')}) do |plotmaker, opts|
148
+ mode = opts['mode'] || :avg
149
+ plotmaker.data_stack.last.average_duplicates!(mode)
130
150
  end
131
151
 
132
152
  AverageDupOperation.describe("Average successive elements with identical X values",
@@ -135,9 +155,8 @@ Averages successive points with identical X values. This algorithm is
135
155
  naive with respect to the min/max values and averages them just as
136
156
  well, whereas one might expect something more clever.
137
157
 
138
- To average over identical X values when they are not successive in the
139
- dataset, you might want to hand it over to {command: sort-last} first.
140
-
158
+ To average over all X values when they are not successive in the
159
+ dataset, you should use {command: sort-last} first.
141
160
  EOH
142
161
 
143
162
  AverageDupFilter =
@@ -78,8 +78,8 @@ module CTioga2
78
78
  end
79
79
 
80
80
  csv.describe("reads CSV files",
81
- <<"EOH", "backend-text")
82
- Now parse the following data files as CSV.
81
+ <<"EOH", "backend-text")
82
+ Now parse the following data files as CSV. Equivalent to
83
83
 
84
84
  # text /separator=/[,;]/
85
85
  EOH
@@ -373,6 +373,7 @@ EOH
373
373
  }) do |plotmaker,opts|
374
374
  ds = plotmaker.data_stack.specified_dataset(opts)
375
375
  if opts['save']
376
+ # Writing
376
377
  out = open(opts['save'], 'w')
377
378
  else
378
379
  out = STDOUT
@@ -69,6 +69,13 @@ EOH
69
69
  cmd.describe("Sets the #{x.to_s.upcase} range",
70
70
  <<EOH, PlotCoordinatesGroup)
71
71
  Sets the range of the #{x.to_s.upcase} coordinates.
72
+
73
+ *Important note:* when the axis is in log range (using
74
+ {command: #{x.to_s.upcase}log}), the numbers you give are not the or
75
+ {command: ylog} values, but their @log10@, so that to
76
+ display #{x.to_s.upcase} values from @1e-2@ to @1e3@, use:
77
+
78
+ # #{x.to_s}yrange -2:3
72
79
  EOH
73
80
  CoordinateRelatedCommands << cmd
74
81
 
@@ -193,6 +193,25 @@ module CTioga2
193
193
  return parent.gp_cache[:histograms]
194
194
  end
195
195
 
196
+ # Computes the number of histograms to be displayed in total
197
+ # -- or, at least, the total number of slots.
198
+ def compute_hist_number(x_values)
199
+ case @histogram_style.compute_dx
200
+ when nil, false
201
+ return x_values.size
202
+ when :mindx
203
+ xv = Dvector.new(x_values.to_a.sort)
204
+ x1 = xv[0..-2]
205
+ x2 = xv[1..-1]
206
+ x2.sub!(x1)
207
+ x2.abs!
208
+ subs = (xv.max - xv.min)/(x2.min)
209
+ return subs.round+1
210
+ else
211
+ raise "Invalid compute-dx type: #{@histogram_style.compute_dx}"
212
+ end
213
+ end
214
+
196
215
  # Returns the cached metrics of all the histograms,
197
216
  # recomputing it in the process.
198
217
  def get_cached_metrics(t)
@@ -228,7 +247,9 @@ module CTioga2
228
247
  # values (which means in particular that they won't be
229
248
  # positioned at the exact X value, but that's already the
230
249
  # case anyway).
231
- width = (x_values.max - x_values.min)/(x_values.size - 1).to_f
250
+ number = compute_hist_number(x_values)
251
+
252
+ width = (x_values.max - x_values.min)/(number - 1).to_f
232
253
  if width.nan? || width == 0.0
233
254
  # Only 1 X value, we use a width of 1
234
255
  # ??
@@ -131,6 +131,12 @@ module CTioga2
131
131
  bounds[k] ||= Types::SimpleRange.new(nil,nil)
132
132
  bounds[k].override(b)
133
133
  end
134
+ for k, b in bounds
135
+ if ! b.valid?
136
+ error { "Invalid computed range, you probably have only empty datasets" }
137
+ bounds[k] = Types::SimpleRange.new(0.0,1.0)
138
+ end
139
+ end
134
140
  return bounds
135
141
  end
136
142
 
@@ -158,6 +164,7 @@ module CTioga2
158
164
  def real_do(t)
159
165
  # First thing, we setup the boundaries
160
166
  @computed_boundaries = compute_boundaries
167
+ p @computed_boundaries
161
168
 
162
169
  real_boundaries = get_boundaries
163
170
 
@@ -176,15 +183,16 @@ module CTioga2
176
183
  dx = t.convert_figure_to_output_dx(1.0)
177
184
  dy = t.convert_figure_to_output_dy(1.0)
178
185
 
179
- frm = [0.0, dx/@style.frame_real_size,
180
- -dy/@style.frame_real_size, 0.0]
186
+ frm = [0.0, dx/(@style.frame_real_size * t.scaling_factor),
187
+ -dy/(@style.frame_real_size * t.scaling_factor), 0.0]
181
188
  @computed_boundaries = {
182
189
  :bottom => Types::SimpleRange.new(frm[0], frm[1]),
183
190
  :left => Types::SimpleRange.new(frm[2], frm[3]),
184
191
  }
185
192
  t.set_bounds(frm)
186
193
  else
187
- t.set_bounds(real_boundaries.to_a)
194
+ bnds = real_boundaries.to_a
195
+ t.set_bounds(bnds)
188
196
  end
189
197
 
190
198
  # First, gather up all elements by depth
@@ -148,10 +148,10 @@ module CTioga2
148
148
  w, h = size(t, container)
149
149
  case @legend_type
150
150
  when :left, :right
151
- return [width + t.convert_figure_to_output_dx(w)/10,
151
+ return [width + t.convert_figure_to_output_dx(w)/t.scaling_factor,
152
152
  height]
153
153
  when :top, :bottom
154
- return [width, height + t.convert_figure_to_output_dy(h)/10]
154
+ return [width, height + t.convert_figure_to_output_dy(h)/t.scaling_factor]
155
155
  when :inside
156
156
  return [width, height]
157
157
  end
@@ -78,9 +78,9 @@ module CTioga2
78
78
  info = t.get_text_size(legend_name)
79
79
 
80
80
  if info.key? 'width'
81
- width += t.convert_output_to_figure_dx(10*info['width'])
81
+ width += t.convert_output_to_figure_dx(t.scaling_factor*info['width'])
82
82
 
83
- h = t.convert_output_to_figure_dy(10*info['height'])
83
+ h = t.convert_output_to_figure_dy(t.scaling_factor*info['height'])
84
84
  if h > height
85
85
  height = h
86
86
  end
@@ -95,7 +95,7 @@ EOD
95
95
  Cmd.new('background', nil, '--background',
96
96
  [ CmdArg.new('color-or-false') ]) do |plotmaker, color|
97
97
  PlotStyle.current_plot_style(plotmaker).
98
- background.background_color = color
98
+ background.style.background_color = color
99
99
  end
100
100
 
101
101
  BackgroundColorCmd.describe("Background color for the plot",
@@ -62,6 +62,7 @@ EOD
62
62
  r = Types::Rect.new(tl, br)
63
63
  ul, ll, lr = r.make_corners(t, (@auto_rotate == nil ? true : @auto_rotate), @aspect_ratio || :ignore,
64
64
  info['width']*1.0/info['height'])
65
+
65
66
 
66
67
  dict = info.dup
67
68
  dict.merge!('ul' => ul,
@@ -137,6 +137,21 @@ How to specify that histograms should be stacked. Can be:
137
137
  * next, in which case the following histograms get stacked on a new slot
138
138
  EOD
139
139
 
140
+ ComputeDxRE = {
141
+ /^no(ne)?$/i => false,
142
+ /^min(dx)?$/i => :mindx,
143
+ }
144
+
145
+ ComputeDx =
146
+ CmdType.new('compute-dx', {:type => :re_list,
147
+ :list => ComputeDxRE}, <<EOD)
148
+ This controls how the histograms treats unevenly spaced X values:
149
+ * @none@: ignores the problem, and treats the points as if they were all
150
+ evenly spaced
151
+ * @min@, @mindx@: considers that all slots have the size of the
152
+ smallest variation of X values
153
+ EOD
154
+
140
155
 
141
156
  # This class defines various informations about the look of
142
157
  # histograms.
@@ -151,6 +166,10 @@ EOD
151
166
  # Specs for cumulative
152
167
  typed_attribute :cumulative, 'cumulative-histograms'
153
168
 
169
+ # Whether one should assume evenly spaced X points or be more
170
+ # clever.
171
+ typed_attribute :compute_dx, 'compute-dx'
172
+
154
173
  def set_from_hash(hash, name = "%s")
155
174
  super
156
175
 
@@ -82,7 +82,7 @@ module CTioga2
82
82
 
83
83
  # If not nil, then the boundaries are computed from the real
84
84
  # dimensions of the plot frame, using the given number as a
85
- # conversion factor from the output dimensions.
85
+ # conversion factor from postscript points.
86
86
  attr_accessor :frame_real_size
87
87
 
88
88
 
@@ -478,7 +478,7 @@ EOH
478
478
  if u =~ /([\d.]+)?\s*(cm|in|bp|pt|mm)/
479
479
  nb = $1 ? $1.to_f : 1.0
480
480
  un = $2
481
- style.frame_real_size = 10 * nb *
481
+ style.frame_real_size = nb *
482
482
  Types::Dimension::DimensionConversion.fetch(un)
483
483
  else
484
484
  raise 'Invalid unit'
@@ -256,7 +256,7 @@ EOD
256
256
  CmdArg.new('file'),
257
257
  ], {}
258
258
  ) do |plotmaker, file|
259
- File.open(file) do |f|
259
+ Utils::open(file) do |f|
260
260
  str = f.read
261
261
  StyleSheet.style_sheet.update_from_string(str)
262
262
  end
@@ -245,8 +245,9 @@ module CTioga2
245
245
 
246
246
 
247
247
  # The style for a string marker. Hmmm, this is somewhat
248
- # redundant with TiogaPrimitiveCall::MarkerOptions and I don't
249
- # like that.
248
+ # redundant with MarkerStyle and I don't like that.
249
+ #
250
+ # Worse than that, it's not the same options !
250
251
  class MarkerStringStyle < BasicStyle
251
252
 
252
253
  # The angle of the text
@@ -272,6 +273,8 @@ module CTioga2
272
273
  typed_attribute :stroke_color, 'color-or-false'
273
274
  typed_attribute :fill_color, 'color-or-false'
274
275
 
276
+ typed_attribute :stroke_width, 'float'
277
+
275
278
 
276
279
  # A number between 1 to 14 -- a PDF font
277
280
  typed_attribute :font, "pdf-font"
@@ -117,7 +117,7 @@ EOH
117
117
  Cmd.new("root-plot",nil,"--root-plot",
118
118
  [
119
119
  ], Elements::TiogaElement::StyleBaseOptions) do |plotmaker, opts|
120
- Log::debug { "Starting a subplot with specs #{box.inspect}" }
120
+ Log::debug { "Explicitly starting the root plot, options #{opts.inspect}" }
121
121
  opts['id'] ||= 'root'
122
122
  plotmaker.root_object.
123
123
  enter_subobject(Elements::Subplot.new(nil,plotmaker.root_object, opts))
@@ -44,6 +44,13 @@ module CTioga2
44
44
  end
45
45
  end
46
46
 
47
+ # Checks if the range is valid, that is both elements are
48
+ # finite numbers
49
+ def valid?
50
+ return (Utils::finite_number?(@first) and
51
+ Utils::finite_number?(@last))
52
+ end
53
+
47
54
  # Minimum value
48
55
  def min
49
56
  @first < @last ? @first : @last
@@ -134,7 +134,7 @@ module CTioga2
134
134
 
135
135
  case @type
136
136
  when :bp
137
- return t.send("convert_output_to_figure_d#{orientation}", @value) * 10
137
+ return t.send("convert_output_to_figure_d#{orientation}", @value) * t.scaling_factor
138
138
  when :dy
139
139
  return t.send("default_text_height_d#{orientation}") * @value
140
140
  when :frame
@@ -158,7 +158,7 @@ module CTioga2
158
158
  def to_bp(t, orientation = nil)
159
159
  orientation ||= @orientation
160
160
  return t.send("convert_figure_to_output_d#{orientation}",
161
- to_figure(t, orientation)) / 10.0
161
+ to_figure(t, orientation)) / t.scaling_factor
162
162
  end
163
163
 
164
164
  # Converts the Dimension to the *frame* coordinates of the
@@ -131,6 +131,11 @@ module CTioga2
131
131
  @hscales, @vscales = nup.split(/\s*x\s*/).map { |x|
132
132
  x.split(/\s*,\s*/).map { |y| y.to_f }
133
133
  }
134
+ if @hscales.size == 1
135
+ @hscales = [1] * @hscales[0].to_i
136
+ elsif @vscales.size == 1
137
+ @vscales = [1] * @vscales[0].to_i
138
+ end
134
139
  @nup = [@hscales.size, @vscales.size]
135
140
  else
136
141
  @nup = nup.split(/\s*x\s*/).map { |s| s.to_i }
@@ -128,8 +128,8 @@ module CTioga2
128
128
  xl, yt = @tl.to_figure_xy(t)
129
129
  xr, yb = @br.to_figure_xy(t)
130
130
 
131
- return [t.convert_figure_to_output_dx(xr - xl) * 10,
132
- t.convert_figure_to_output_dy(yb - yt) * 10]
131
+ return [t.convert_figure_to_output_dx(xr - xl) * t.scaling_factor,
132
+ t.convert_figure_to_output_dy(yb - yt) * t.scaling_factor]
133
133
  end
134
134
 
135
135
  # Returns an array of [ul, ll, lr] coordinates. If an aspect
@@ -226,6 +226,12 @@ module CTioga2
226
226
  # errors. Useful on windows, where the windows closes before one
227
227
  # has a chance to see anything.
228
228
  attr_accessor :pause_on_errors
229
+
230
+ # If not empty, a Makefile file where the dependencies are written out
231
+ attr_accessor :makefile_dependencies
232
+
233
+ # The resolution of the PDF file
234
+ attr_accessor :pdf_resolution
229
235
 
230
236
 
231
237
  # The first instance of PlotMaker created
@@ -368,7 +374,7 @@ module CTioga2
368
374
  end
369
375
 
370
376
  # We always cd into the target directory for creading the
371
- Dir.chdir(path.dirname) do
377
+ Utils::chdir(path.dirname) do
372
378
  fn = path.basename.to_s
373
379
 
374
380
  efn = fn.gsub(/[.\s]/) do |x|
@@ -405,6 +411,14 @@ module CTioga2
405
411
 
406
412
  file = path.to_s + ".pdf"
407
413
 
414
+ if @makefile_dependencies
415
+ File.open(@makefile_dependencies, "w") do |f|
416
+ deps = Utils::used_files.values
417
+ # TODO: handle spaces
418
+ f.puts "#{file}: #{deps.join(" ")}"
419
+ end
420
+ end
421
+
408
422
  # Feed it
409
423
  @postprocess.process_file(file, last)
410
424
  return file
@@ -451,7 +465,7 @@ module CTioga2
451
465
 
452
466
  # Creates a new FigureMaker object and returns it
453
467
  def create_figure_maker
454
- t = Tioga::FigureMaker.new
468
+ t = Tioga::FigureMaker.new(@pdf_resolution || 100)
455
469
  t.tex_preamble += @latex_preamble
456
470
  t.autocleanup = @cleanup
457
471
 
@@ -624,6 +638,25 @@ EOD
624
638
  <<EOH, OutputSetupGroup)
625
639
  Sets the size of the output PDF file, in real units. Takes arguments in the
626
640
  form of 12cm x 3in (spaces can be omitted).
641
+ EOH
642
+
643
+ ResolutionCommand =
644
+ Cmd.new("resolution", false,"--resolution",
645
+ [ CmdArg.new('float') ],
646
+ ) do |plotmaker, size, options|
647
+ plotmaker.pdf_resolution = size
648
+ end
649
+
650
+ ResolutionCommand.describe('Sets the output resolution',
651
+ <<EOH, OutputSetupGroup)
652
+ By default, ctioga2 has a resolution of 1/100th of a postscript
653
+ point. This is clearly enough for most tasks, but you can increase it
654
+ should you need, or decrease it to generate possibly a little more
655
+ jaggy but less large PDF files.
656
+
657
+ The number given is the number of output points per postscript point.
658
+
659
+ Better change that at the beginning of the plot.
627
660
  EOH
628
661
 
629
662
  CleanupCommand =
@@ -816,6 +849,17 @@ want any information to leak.
816
849
  Please note that this will not log the values of the CTIOGA2_PRE and
817
850
  CTIOGA2_POST variables, so you might still get a different output if
818
851
  you make heavy use of those.
852
+ EOH
853
+
854
+ DepsCommand =
855
+ Cmd.new("dependencies",nil,"--dependencies",
856
+ [CmdArg.new('file') ]) do |plotmaker,val|
857
+ plotmaker.makefile_dependencies = val
858
+ end
859
+
860
+ DepsCommand.describe('Save dependencies',
861
+ <<EOH, OutputSetupGroup)
862
+ Saves the dependencies as a Makefike into the given file name.
819
863
  EOH
820
864
 
821
865
  end
@@ -36,7 +36,7 @@ module CTioga2
36
36
  end
37
37
 
38
38
  def self.run_file(file)
39
- File.open(file) do |f|
39
+ Utils::open(file) do |f|
40
40
  run_code(f.read)
41
41
  end
42
42
  end
@@ -59,6 +59,11 @@ module CTioga2
59
59
  end
60
60
  end
61
61
 
62
+ # Returns true if the argument is a finite number
63
+ def self.finite_number?(flt)
64
+ return (flt.is_a?(Numeric) and flt.to_f.finite?)
65
+ end
66
+
62
67
  # Converts a number to a float while trying to stay as lenient as
63
68
  # possible
64
69
  def self.txt_to_float(txt)
@@ -160,6 +165,24 @@ module CTioga2
160
165
  end
161
166
  end
162
167
 
168
+ # Returns the nth first modes
169
+ def self.average_vector(vect, istart, iend, n = 1)
170
+ rv = [0] * n
171
+ nb = 0
172
+ istart.upto(iend) do |i|
173
+ v = 1
174
+ y = vect[i]
175
+ n.times do |j|
176
+ v *= y
177
+ rv[j] += v
178
+ end
179
+ nb += 1
180
+ end
181
+
182
+ return rv.map do |v|
183
+ v/nb
184
+ end
185
+ end
163
186
 
164
187
  # Groups the given strings by prefixes
165
188
 
@@ -176,6 +199,76 @@ module CTioga2
176
199
  return sets_by_prefix
177
200
  end
178
201
 
202
+ # An instrumentized version of Dir::chdir
203
+ def self.chdir(dir, &blk)
204
+ @current_dirs ||= []
205
+ @current_dirs << Pathname.new(dir)
206
+ Dir::chdir(dir, &blk)
207
+ @current_dirs.pop
208
+ end
209
+
210
+ # A constant holding a relation extension -> command to decompress
211
+ # (to be fed to sprintf with the filename as argument)
212
+ UNCOMPRESSORS = {
213
+ ".gz" => "gunzip -c %s",
214
+ ".bz2" => "bunzip2 -c %s",
215
+ ".lzma" => "unlzma -c %s",
216
+ ".lz" => "unlzma -c %s",
217
+ ".xz" => "unxz -c %s",
218
+ }
219
+
220
+ # This opens a file for reading, keeping track of the opened files
221
+ # and possibly transparently decompressing files when
222
+ # applicable. Returns the file object, or runs the given block
223
+ # with it, closing the file at the end.
224
+ def self.open(file)
225
+ if not File.readable?(file)
226
+ # Try to find a compressed version
227
+ for ext,method in UNCOMPRESSORS
228
+ if File.readable? "#{file}#{ext}"
229
+ info { "Using compressed file #{file}#{ext} in stead of #{file}" }
230
+ ff = "#{file}#{ext}"
231
+ handle = IO.popen(method % ff)
232
+ break
233
+ end
234
+ end
235
+ else
236
+ for ext, method in UNCOMPRESSORS
237
+ if file =~ /#{ext}$/
238
+ info { "Taking file #{file} as a compressed file" }
239
+ ff = file
240
+ handle = IO.popen(method % file)
241
+ break
242
+ end
243
+ end
244
+ end
245
+ if ! handle
246
+ ff = file
247
+ handle = File::open(file)
248
+ end
249
+
250
+ # Unwrap directory
251
+ @used_files ||= {}
252
+
253
+ @current_dirs ||= []
254
+ rf = (@current_dirs + [file]).inject :+
255
+ rff = (@current_dirs + [ff]).inject :+
256
+ # The files referenced
257
+ @used_files[rf] = rff
258
+ if block_given?
259
+ yield handle
260
+ handle.close
261
+ else
262
+ return handle
263
+ end
264
+ end
265
+
266
+
267
+ # Returns the list of files that were read by ctioga2
268
+ def self.used_files()
269
+ return @used_files || {}
270
+ end
271
+
179
272
 
180
273
 
181
274
  NaturalSubdivisions = [1.0, 2.0, 5.0, 10.0]
@@ -470,8 +563,7 @@ module CTioga2
470
563
  # Watched text names
471
564
  attr_accessor :watched_names
472
565
 
473
- # A left, bottom, right, up bounding box (in output coordinates
474
- # divided by 10)
566
+ # A left, bottom, right, up bounding box (postscript points)
475
567
  attr_accessor :bb
476
568
 
477
569
  def initialize
@@ -497,11 +589,11 @@ module CTioga2
497
589
  return margins
498
590
  end
499
591
  left, top, right, bottom = *margins.to_frame_coordinates(t)
500
-
501
- xl = 0.1 * t.convert_page_to_output_x(t.convert_frame_to_page_x(left))
502
- xr = 0.1 * t.convert_page_to_output_x(t.convert_frame_to_page_x(right))
503
- yt = 0.1 * t.convert_page_to_output_y(t.convert_frame_to_page_y(top))
504
- yb = 0.1 * t.convert_page_to_output_y(t.convert_frame_to_page_y(bottom))
592
+ scl = 1/t.scaling_factor
593
+ xl = scl * t.convert_page_to_output_x(t.convert_frame_to_page_x(left))
594
+ xr = scl * t.convert_page_to_output_x(t.convert_frame_to_page_x(right))
595
+ yt = scl * t.convert_page_to_output_y(t.convert_frame_to_page_y(top))
596
+ yb = scl * t.convert_page_to_output_y(t.convert_frame_to_page_y(bottom))
505
597
 
506
598
  vals = [ xl - @bb[0], @bb[2] - xr, @bb[3] - yt, yb - @bb[1]].map do |x|
507
599
  x += padding
@@ -605,6 +697,7 @@ class Hash
605
697
  end
606
698
 
607
699
 
700
+
608
701
  class String
609
702
  # Splits a string into substrings at the given regexp, but only if
610
703
  # the splitting occurs at top-level with respect to parentheses.
@@ -2,7 +2,7 @@
2
2
  module CTioga2
3
3
 
4
4
  module Version
5
- GIT_VERSION = '0.12'
6
- GIT_DATE = 'Mon 23 Mar 21:27:39 CET 2015'
5
+ GIT_VERSION = '0.13'
6
+ GIT_DATE = 'Thu 11 Jun 23:03:37 CEST 2015'
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ctioga2
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.12'
4
+ version: '0.13'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Fourmond <vincent.fourmond@9online.fr>
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-23 00:00:00.000000000 Z
11
+ date: 2015-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tioga
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.18'
19
+ version: '1.19'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.18'
26
+ version: '1.19'
27
27
  description: |
28
28
  ctioga2 is a command-driven plotting program that produces
29
29
  high quality PDF files. It can be used both from the command-line
30
30
  and using command files (at the same time).
31
31
 
32
- It is based on Tioga (http://tioga.rubyforge.org).
32
+ It is based on Tioga (http://tioga.sourceforge.net).
33
33
  email: vincent.fourmond@9online.fr
34
34
  executables:
35
35
  - ctioga2