ctioga2 0.11 → 0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cf1d83cc2f6ff4a0a56457aaf1343632316fc54c
4
- data.tar.gz: de6664912ac0b52b32dd6dc90520556a8a3408e1
3
+ metadata.gz: f25f506adfb25689dc8c090adcfb35beac30af3d
4
+ data.tar.gz: 1731cd6baf714e9bf480d9ea50039b9acc0ac9d4
5
5
  SHA512:
6
- metadata.gz: 658b6530f9b7a6b05159f779b6e2b82df15e22b05e75e7c61404212193c46044b622d9a9e68980225a116012004a79f7c92b62445fd1e2ebbc8322f3b83658c3
7
- data.tar.gz: 8f4d59ab2aa53836d4bd74f30696cfc2f3d4eaaff3c138d85878619f89304cad620093ba2ecd54dc240933ecc93526156fa1934138a07a6e768b0c4864077f54
6
+ metadata.gz: d7fb0ecfa3ed8cc7c2e3e8b5723b92b676ba837681eb33eb7fa19db61cf0f0131566cbf76f4a6c49c91969b074553607b3bdfcdae3df1d52510432339a22d1de
7
+ data.tar.gz: 87e302b8f937f3f638f24a0eca891cec89282aa10d7a2f7721432dbdd0e100711334efe883fd65a79dc7e72b540074336a5c5048844171b8cd8dcdf5b44554f2
data/Changelog CHANGED
@@ -1,3 +1,20 @@
1
+ ctioga2 (0.12)
2
+
3
+ * The xyz-map plot type now handles correctly inhomogeneous grids (so long
4
+ as points do not overlap)
5
+ * One can now separately choose the fill and the stroke color for
6
+ markers
7
+ * Selection of line width for axes
8
+ * A set of commands to manipulate styles (skip the next style or copy
9
+ the style of previous plots)
10
+ * Selection of error bar line width
11
+ * All lines are also arrows now (just with a different default)
12
+ * A pause command to ask for user input before quitting if there were
13
+ errors or warnings
14
+ * Improvement of error messages
15
+
16
+ -- Vincent <vincent.fourmond@9online.fr> Sun 22 Mar 18:38:49 CET 2015
17
+
1
18
  ctioga2 (0.11)
2
19
 
3
20
  * Implemented patterned fills
@@ -204,7 +204,8 @@ module CTioga2
204
204
  if(@arguments.size == 0 && args.size == 1 && args[0] == true)
205
205
  return []
206
206
  else
207
- raise ArgumentNumberMismatch, "Command #{@name} was called with #{args.size} arguments, but it takes #{@arguments.size}"
207
+ ar = args.map { |x| "'#{x}'"}
208
+ raise ArgumentNumberMismatch, "Command #{@name} was called with #{args.size} arguments: #{ar.join(", ")}, but it takes #{@arguments.size}"
208
209
  end
209
210
  end
210
211
  retval = []
@@ -50,7 +50,8 @@ Writes a manual page based on a template
50
50
  EOH
51
51
 
52
52
  WriteHTMLOptions = {
53
- 'page-menu' => CmdArg.new('text')
53
+ 'page-menu' => CmdArg.new('text'),
54
+ 'snippets' => CmdArg.new('file')
54
55
  }
55
56
 
56
57
  WriteHTMLCommands =
@@ -89,6 +89,16 @@ module CTioga2
89
89
  def write_commands(opts, out = STDOUT)
90
90
  cmds, groups = @doc.documented_commands
91
91
 
92
+ if opts['snippets']
93
+ require 'yaml'
94
+ snippets = begin
95
+ YAML.load(IO.readlines(opts['snippets']).join())
96
+ rescue Exception => e
97
+ Log::error { "Failed to load snippets file '#{opts['snippets']}'\n => #{e.inspect}" }
98
+ {}
99
+ end
100
+ end
101
+
92
102
  write_page_menu(opts, out) do |out|
93
103
  out.puts "<div class='quick-jump'>"
94
104
  out.puts "<h3>Quick jump</h3>"
@@ -120,6 +130,23 @@ module CTioga2
120
130
  for cmd in commands
121
131
  out.puts
122
132
  out.puts command_documentation(cmd)
133
+ if snippets
134
+ snpts = snippets[cmd.name]
135
+ if snpts
136
+ str = ""
137
+ for k in snpts.keys.sort
138
+ s = snpts[k]
139
+ ln = s[:line].chomp
140
+ # if ln[-1] == '\\'
141
+ # ln = ln[0..-2]
142
+ # end
143
+ # Strip links from the line
144
+ ln.gsub!(/<a[^>]+>(.*?)<\/a>/) { || $1 }
145
+ str += "<pre class='#{s[:cls]}'><a href='#{k}'>#{ln}</a></pre>\n"
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>"
148
+ end
149
+ end
123
150
  end
124
151
  end
125
152
  end
@@ -126,6 +126,19 @@ EOH
126
126
 
127
127
  VerboseLogging.describe("Makes ctioga2 more verbose", <<EOH, GeneralGroup)
128
128
  With this on, ctioga2 outputs quite a fair amount of informative messages.
129
+ EOH
130
+
131
+ Pause =
132
+ Cmd.new("pause", nil, "--pause",
133
+ [ CmdArg.new('boolean') ]) do |plotmaker, val|
134
+ plotmaker.pause_on_errors = val
135
+ end
136
+
137
+ Pause.describe("Pause on errors", <<EOH, GeneralGroup)
138
+ When this is on, the program will ask for confirmation before finishing,
139
+ when errors or warnings have been shown. This is especially useful on windows
140
+ or other environments where the terminal shuts down as soon as ctioga2
141
+ has finished.
129
142
  EOH
130
143
 
131
144
  # Write debugging information.
@@ -87,7 +87,7 @@ module CTioga2
87
87
 
88
88
  # Does the actual conversion from string to the real type
89
89
  def string_to_type(str)
90
- return @type.string_to_type(str)
90
+ return @type.string_to_type(str, @name)
91
91
  end
92
92
 
93
93
  # Returns the long option for the option parser.
@@ -16,6 +16,8 @@ require 'ctioga2/log'
16
16
  require 'ctioga2/data/datacolumn'
17
17
  require 'ctioga2/data/indexed-dtable'
18
18
 
19
+ require 'set'
20
+
19
21
  module CTioga2
20
22
 
21
23
  # \todo now, port the backend infrastructure...
@@ -57,6 +59,9 @@ module CTioga2
57
59
 
58
60
  # Cache for the indexed dtable
59
61
  @indexed_dtable = nil
62
+
63
+ # Cache for the homogeneous dtables
64
+ @homogeneous_dtables = nil
60
65
  end
61
66
 
62
67
  # Creates a
@@ -340,6 +345,204 @@ module CTioga2
340
345
  return Dataset.new(name + "_mod", result)
341
346
  end
342
347
 
348
+ # Takes a list of x and y values, and subdivise into
349
+ # non-overlapping groups.
350
+ def self.subdivise(x,y, x_idx, y_idx)
351
+
352
+ # We make a list of sets. Each element of the list represent
353
+ # one column, and in each set we store the index of of lines
354
+ # that contain data.
355
+
356
+ cols = []
357
+
358
+ x.each_index do |i|
359
+ ix = x_idx[x[i]]
360
+ iy = y_idx[y[i]]
361
+
362
+ cols[ix] ||= Set.new
363
+ cols[ix].add(iy)
364
+ end
365
+
366
+ # The return value is an array of [ [xindices] [yindices]]
367
+ ret = []
368
+
369
+ # Now, the hard part.
370
+
371
+ # We run for as long as there are sets ?
372
+ fc = 0
373
+ while fc < cols.size
374
+ # We start with the set of the current column
375
+ st = cols[fc]
376
+ # Empty, go to next column
377
+ if st.size == 0
378
+ fc += 1
379
+ next
380
+ end
381
+
382
+ # Set columns that contain the set
383
+ set_cols = [fc]
384
+ # Now, we look for restrictions on the set.
385
+ fc2 = fc + 1
386
+ while fc2 < cols.size
387
+ # if non-void intersection, we stick to that
388
+ inter = st.intersection(cols[fc2])
389
+ # p [fc, fc2, st, inter]
390
+ if inter.size > 0
391
+ st = inter
392
+ set_cols << fc2
393
+ fc2 += 1
394
+ break
395
+ end
396
+
397
+ fc2 += 1
398
+ # Try to implement other kinds of restrictions?
399
+ end
400
+
401
+ # Now, we have a decent set, we go on until the intersection
402
+ # with the set is not the set.
403
+ while fc2 < cols.size
404
+ inter = st.intersection(cols[fc2])
405
+ if inter.size > 0
406
+ if inter.size == st.size
407
+ set_cols << fc2
408
+ else
409
+ break
410
+ end
411
+ end
412
+ fc2 += 1
413
+ end
414
+
415
+ # Now, we have a set and all the indices that match.
416
+ ret << [ set_cols.dup.sort, st.to_a.sort ]
417
+ # And, now, go again through all the columns and remove the set
418
+ for c in set_cols
419
+ cols[c].subtract(st)
420
+ end
421
+ end
422
+
423
+ return ret
424
+ end
425
+
426
+ # Takes a list of indices, the corresponding vector (ie mapping
427
+ # the indices to the vector gives the actual coordinates) and
428
+ # returns a list of arrays of indices with homogeneous deltas.
429
+ def self.homogenenous_deltas_indices(indices, vector, tolerance = 1e-3)
430
+ vct = indices.map do |i|
431
+ vector[i]
432
+ end
433
+ subdiv = Utils::split_homogeneous_deltas(vct, tolerance)
434
+ rv = []
435
+ idx = 0
436
+ for s in subdiv
437
+ rv << indices[idx..idx+s.size-1]
438
+ idx += s.size
439
+ end
440
+ if idx != indices.size
441
+ error { "blundered ?" }
442
+ end
443
+ return rv
444
+ end
445
+
446
+ # Returns a series of IndexedDTable representing the XYZ data.
447
+ def homogeneous_dtables()
448
+ if @homogeneous_dtables
449
+ return @homogeneous_dtables
450
+ end
451
+ if @ys.size < 2
452
+ raise "Need at least 3 data columns in dataset '#{@name}'"
453
+ end
454
+ # We convert the index into three x,y and z arrays
455
+ x = @x.values.dup
456
+ y = @ys[0].values.dup
457
+ z = @ys[1].values.dup
458
+
459
+ xvals = x.sort.uniq
460
+ yvals = y.sort.uniq
461
+
462
+ # Now building reverse hashes to speed up the conversion:
463
+ x_index = {}
464
+ i = 0
465
+ xvals.each do |v|
466
+ x_index[v] = i
467
+ i += 1
468
+ end
469
+
470
+ y_index = {}
471
+ i = 0
472
+ yvals.each do |v|
473
+ y_index[v] = i
474
+ i += 1
475
+ end
476
+
477
+ fgrps = []
478
+ if x.size != xvals.size * yvals.size
479
+ # This is definitely not a homogeneous map
480
+ fgrps = Dataset.subdivise(x, y, x_index, y_index)
481
+ else
482
+ fgrps = [ [ x_index.values, y_index.values ] ]
483
+ end
484
+
485
+ # Now, we resplit according to the deltas:
486
+ grps = []
487
+ for grp in fgrps
488
+ xv, yv = *grp
489
+
490
+ xv_list = Dataset.homogenenous_deltas_indices(xv, xvals)
491
+ yv_list = Dataset.homogenenous_deltas_indices(yv, yvals)
492
+
493
+ for cxv in xv_list
494
+ for cyv in yv_list
495
+ grps << [ cxv, cyv]
496
+ end
497
+ end
498
+ end
499
+
500
+ # Now we construct a list of indexed dtables
501
+ rv = []
502
+ for grp in grps
503
+ xv = grp[0].sort
504
+ yv = grp[1].sort
505
+
506
+ # Build up intermediate hashes
507
+ xvh = {}
508
+ xvl = []
509
+ idx = 0
510
+ for xi in xv
511
+ val = xvals[xi]
512
+ xvh[val] = idx
513
+ xvl << val
514
+ idx += 1
515
+ end
516
+
517
+ yvh = {}
518
+ yvl = []
519
+ idx = 0
520
+ for yi in yv
521
+ val = yvals[yi]
522
+ yvh[val] = idx
523
+ yvl << val
524
+ idx += 1
525
+ end
526
+
527
+ table = Dobjects::Dtable.new(xv.size, yv.size)
528
+ # We initialize all the values to NaN
529
+ table.set(0.0/0.0)
530
+
531
+ x.each_index do |i|
532
+ ix = xvh[x[i]]
533
+ next unless ix
534
+ iy = yvh[y[i]]
535
+ next unless iy
536
+ # Y first !
537
+ table[iy, ix] = z[i]
538
+ end
539
+ rv << IndexedDTable.new(xvl, yvl, table)
540
+ end
541
+ @homogeneous_dtables = rv
542
+ return rv
543
+ end
544
+
545
+
343
546
 
344
547
  # Returns an IndexedDTable representing the XYZ
345
548
  # data. Information about errors are not included.
@@ -354,6 +557,9 @@ module CTioga2
354
557
  if @indexed_dtable
355
558
  return @indexed_dtable
356
559
  end
560
+ if @ys.size < 2
561
+ raise "Need at least 3 data columns in dataset '#{@name}'"
562
+ end
357
563
  # We convert the index into three x,y and z arrays
358
564
  x = @x.values.dup
359
565
  y = @ys[0].values.dup
@@ -377,6 +583,10 @@ module CTioga2
377
583
  i += 1
378
584
  end
379
585
 
586
+ if x.size != xvals.size * yvals.size
587
+ error {"Heterogeneous, stopping here for now"}
588
+ end
589
+
380
590
  table = Dobjects::Dtable.new(xvals.size, yvals.size)
381
591
  # We initialize all the values to NaN
382
592
  table.set(0.0/0.0)
@@ -96,7 +96,7 @@ EOH
96
96
  begin
97
97
  datasets << backend.dataset(s)
98
98
  rescue Exception => e
99
- error { "Could not load dataset #{s} -- #{e}" }
99
+ error { "Could not load dataset '#{s}' with backend '#{backend.description.name }':\n\t -> #{e}" }
100
100
  debug { "#{e.backtrace.join("\n")}" }
101
101
  end
102
102
  end
@@ -245,17 +245,19 @@ EOD
245
245
  [ 'point', 'point' ],
246
246
  Styles::ArrowStyle,
247
247
  'arrow') do |t, tail, head, style, options|
248
- style.draw_arrow(t, *( tail.to_figure_xy(t) +
249
- head.to_figure_xy(t) ))
248
+ stl = style.dup
249
+ stl.use_defaults_from(Styles::ArrowStyle::TiogaDefaults)
250
+ stl.draw_arrow(t, *( tail.to_figure_xy(t) +
251
+ head.to_figure_xy(t) ))
250
252
  end
251
253
 
252
254
  styled_primitive("line", "line",
253
255
  [ 'point', 'point' ],
254
- Styles::StrokeStyle,
256
+ Styles::ArrowStyle,
255
257
  'line'
256
258
  ) do |t, tail, head, style, options|
257
- style.draw_line(t, *( tail.to_figure_xy(t) +
258
- head.to_figure_xy(t) ))
259
+ style.draw_arrow(t, *( tail.to_figure_xy(t) +
260
+ head.to_figure_xy(t) ))
259
261
  end
260
262
 
261
263
  # @todo Do the same thing for arrows...
@@ -264,7 +266,7 @@ EOD
264
266
  Styles::OrientedLineStyle,
265
267
  'oriented-line'
266
268
  ) do |t, org, dim, style, options|
267
- style.draw_oriented_line(t, *org.to_figure_xy(t), dim)
269
+ style.draw_oriented_arrow(t, *org.to_figure_xy(t), dim)
268
270
  end
269
271
 
270
272
 
@@ -24,13 +24,6 @@ module CTioga2
24
24
  # * inclusion of curves
25
25
  # * legends
26
26
  # * a way to set/get its figure boundaries.
27
- #
28
- # @todo It would be interesting to feature several layers:
29
- # background/normal/foreground, that could be addressed just
30
- # using options to drawing commands
31
- #
32
- # @todo It would also be interesting to offer the possibility to
33
- # output non-clipped objects.
34
27
  class Subplot < Container
35
28
 
36
29
  # Various stylistic aspects of the plot, as a
@@ -83,7 +83,9 @@ module CTioga2
83
83
  end
84
84
 
85
85
  coords = options['tail'] + options['head']
86
- style.draw_arrow(t, *coords)
86
+ stl = style.dup
87
+ stl.use_defaults_from(Styles::ArrowStyle::TiogaDefaults)
88
+ stl.draw_arrow(t, *coords)
87
89
  end
88
90
  end
89
91
 
@@ -1,5 +1,5 @@
1
- # parametric2d.rb: a 2D curve whose parameters depend on Z values
2
- # copyright (c) 2006, 2007, 2008, 2009, 2010 by Vincent Fourmond
1
+ # xyz-map.rb: a heatmap
2
+ # copyright (c) 2006, 2007, 2008, 2009, 2010, 2013, 2015 by Vincent Fourmond
3
3
 
4
4
  # This program is free software; you can redistribute it and/or modify
5
5
  # it under the terms of the GNU General Public License as published by
@@ -34,7 +34,7 @@ module CTioga2
34
34
  include Dobjects
35
35
 
36
36
  # The IndexedTable object representing the underlying data
37
- attr_accessor :table
37
+ attr_accessor :tables
38
38
 
39
39
 
40
40
  # Creates a new XYZMap object with the given _dataset_ and
@@ -43,18 +43,33 @@ module CTioga2
43
43
  @dataset = dataset
44
44
  @curve_style = style
45
45
  prepare_data
46
+ @boundaries = nil
46
47
  end
47
48
 
48
49
  # Prepares the internal storage of the data, from the @dataset
49
50
  def prepare_data
50
- @table = @dataset.indexed_table
51
+ @tables = @dataset.homogeneous_dtables
52
+ info {
53
+ str = ""
54
+ for tbl in @tables
55
+ str << " - #{tbl.x_values.min}, #{tbl.y_values.min} -> #{tbl.x_values.max}, #{tbl.y_values.max} #{tbl.width}x#{tbl.height}\n"
56
+ end
57
+ "There are #{@tables.size} different homogeneous submaps in #{@dataset.name}\n#{str}"
58
+ }
59
+
51
60
  end
52
61
 
53
62
  protected :prepare_data
54
63
 
55
64
  # Returns the Types::Boundaries of this curve.
56
65
  def get_boundaries
57
- return @table.xy_boundaries
66
+ if @boundaries
67
+ return @boundaries
68
+ end
69
+ bnds = Graphics::Types::Boundaries.bounds(@dataset.x.values,
70
+ @dataset.y.values)
71
+ @boundaries = bnds
72
+ return bnds
58
73
  end
59
74
 
60
75
 
@@ -72,31 +87,35 @@ module CTioga2
72
87
 
73
88
  @curve_style.color_map ||=
74
89
  Styles::ColorMap.from_text("Red--Green")
75
-
76
- dict = @curve_style.color_map.
77
- prepare_data_display(t,@table.table,
78
- @table.table.min,
79
- @table.table.max)
80
- if @curve_style.zaxis
81
- begin
82
- @parent.style.get_axis_style(@curve_style.zaxis).
83
- set_color_map(@curve_style.color_map,
84
- @table.table.min,
85
- @table.table.max)
86
- rescue
87
- error { "Could not set Z info to non-existent axis #{@curve_style.zaxis}" }
90
+
91
+ zmin = @dataset.z.values.min
92
+ zmax = @dataset.z.values.max
93
+
94
+ for tbl in @tables
95
+ dict = @curve_style.color_map.
96
+ prepare_data_display(t,tbl.table, zmin, zmax)
97
+ if @curve_style.zaxis
98
+ begin
99
+ @parent.style.get_axis_style(@curve_style.zaxis).
100
+ set_color_map(@curve_style.color_map,
101
+ zmin,
102
+ zmax)
103
+ rescue
104
+ error { "Could not set Z info to non-existent axis #{@curve_style.zaxis}" }
105
+ end
88
106
  end
89
- end
90
107
 
91
- dict.update(@table.corner_positions)
92
- dict.update('width' => @table.width,
93
- 'height' => @table.height)
94
- dict.update('interpolate' => false)
95
- if (! @curve_style.fill.transparency) ||
96
- (@curve_style.fill.transparency < 0.99)
97
- t.show_image(dict)
98
- else
99
- info { 'Not showing map as transparency is over 0.99' }
108
+ dict.update(tbl.corner_positions)
109
+ dict.update('width' => tbl.width,
110
+ 'height' => tbl.height)
111
+ dict.update('interpolate' => false)
112
+ if (! @curve_style.fill.transparency) ||
113
+ (@curve_style.fill.transparency < 0.99)
114
+ t.show_image(dict)
115
+ # t.stroke_rect(dict['ul'][0], dict['ul'][1], dict['lr'][0] - dict['ul'][0], dict['lr'][1] - dict['ul'][1])
116
+ else
117
+ info { 'Not showing map as transparency is over 0.99' }
118
+ end
100
119
  end
101
120
  end
102
121
  end