ctioga2 0.12 → 0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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