ctioga2 0.11 → 0.12

Sign up to get free protection for your applications and to get access to all the features.
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