Class: CTioga::Curve2D

Inherits:
TiogaElement show all
Includes:
Dobjects
Defined in:
lib/CTioga/elements/curves.rb

Overview

The class Curve2D stores both the data and the way it should be plotted, such as it’s legend, it’s color, it’s line style and so on…

Direct Known Subclasses

Histogram2D

Instance Attribute Summary collapse

Attributes inherited from TiogaElement

#parent

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from TiogaElement

#inspect

Methods included from Log

#identify, #init_logger, #logger, #logger_options, #spawn

Methods included from Debug

#debug_figmaker, #debug_patterns, #debug_puts, #figmaker_options, #test_pattern, #test_pattern_right

Constructor Details

#initialize(style = nil, data = nil) ⇒ Curve2D

Returns a new instance of Curve2D.



45
46
47
48
49
50
# File 'lib/CTioga/elements/curves.rb', line 45

def initialize(style = nil, data = nil)
  # style is a CurveStyle object
  set_style(style) if style
  set_data(data) if data
  @line_cap = nil
end

Instance Attribute Details

#functionObject (readonly)

The underlying Function object:



35
36
37
# File 'lib/CTioga/elements/curves.rb', line 35

def function
  @function
end

#styleObject (readonly)

The CurveStyle object representing the curve’s style.



32
33
34
# File 'lib/CTioga/elements/curves.rb', line 32

def style
  @style
end

Class Method Details

.compute_boundaries(bounds) ⇒ Object

Computes the outmost boundaries of the given boundaries. Any NaN in here will happily get ignored.



124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/CTioga/elements/curves.rb', line 124

def Curve2D.compute_boundaries(bounds)
  left = Dvector.new
  right = Dvector.new
  top = Dvector.new
  bottom = Dvector.new
  bounds.each do |a|
    left.push(a[0])
    right.push(a[1])
    top.push(a[2])
    bottom.push(a[3])
  end
  return [left.min, right.max, top.max, bottom.min]
end

Instance Method Details

#close_path(t, y) ⇒ Object

A function to close the path created by make_path. Overridden in the histogram code.



215
216
217
218
219
# File 'lib/CTioga/elements/curves.rb', line 215

def close_path(t, y)
  t.append_point_to_path(@function.x.last, y)
  t.append_point_to_path(@function.x.first, y)
  t.close_path
end

#draw_error_bars(t = nil) ⇒ Object

The function that plots error bars.



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/CTioga/elements/curves.rb', line 255

def draw_error_bars(t = nil)
  # We first check that we actually need to do anything 
  t.context do 
    t.stroke_transparency = @style.error_bars_transparency || 0
    if @function.errors.key?(:xmin) or @function.errors.key?(:ymin)
      error_bar = {}                 # Just create it once, anyway
      error_bar['color'] = @style.error_bar_color
      errors = @function.errors # So we won't have to worry
      # some data should be shared
      @function.errors[:x].each_index do |i|
        error_bar['x'] = errors[:x][i]
        if errors.key?(:xmin) && 
            ((errors[:xmax][i] - errors[:xmin][i]) != 0)
          error_bar['dx_plus'] = errors[:xmax][i] - errors[:x][i]
          error_bar['dx_minus'] = errors[:x][i] - errors[:xmin][i]
          error_bar.delete('dx')
        else
          %w(dx_plus dx_minus).each do |el|
            error_bar.delete(el)
          end
          error_bar['dx'] = 0
        end
        error_bar['y'] = errors[:y][i]
        if errors.key?(:ymin) && 
            ((errors[:ymax][i] - errors[:ymin][i]) != 0)
          error_bar['dy_plus'] = errors[:ymax][i] - errors[:y][i]
          error_bar['dy_minus'] = errors[:y][i] - errors[:ymin][i]
          error_bar.delete('dy')
        else
          %w(dy_plus dy_minus).each do |el|
            error_bar.delete(el)
          end
          error_bar['dy'] = 0
        end
        if (error_bar['dx'] != 0 || error_bar['dy'] != 0) 
          t.show_error_bars(error_bar)
        end
      end
    end
  end
end

#draw_fill(t) ⇒ Object

Draws the filled region according to the :fill_type element of the style pseudo-hash. It can be:



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/CTioga/elements/curves.rb', line 223

def draw_fill(t)
  y = y_value(@style.fill_type)
  return unless y

  t.fill_transparency = @style.fill_transparency || 0
  # Now is the tricky part. To do the actual fill, we first make a
  # path according to the make_path function.
  make_path(t)

  # Then we add two line segments that go from the end to the
  # beginning.
  close_path(t, y)

  # Now the path is ready. Just strike -- or, rather, fill !
  t.fill_color = @style.fill_color
  t.fill
end

#draw_markers(t) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/CTioga/elements/curves.rb', line 171

def draw_markers(t)
  xs = @function.x
  ys = @function.y

  if @style.marker
    t.line_type = [[], 0]   # Always solid for striking markers
    t.stroke_transparency = @style.marker_transparency || 0
    t.fill_transparency = @style.marker_transparency || 0
    t.show_marker('Xs' => xs, 'Ys' => ys,
                  'marker' => @style.marker, 
                  'scale' => @style.marker_scale, 
                  'color' => @style.marker_color)
  end
end

#draw_path(t) ⇒ Object

Draw the path



157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/CTioga/elements/curves.rb', line 157

def draw_path(t)
  t.line_width = @style.linewidth if @style.linewidth
  if @style.color && @style.line_style
    t.line_type = @style.line_style
    t.stroke_transparency = @style.transparency || 0
    t.stroke_color = @style.color
    if @line_cap
      t.line_cap = @line_cap
    end
    make_path(t)
    t.stroke
  end
end

#get_boundariesObject

This function returns the bouding box of the specified graphes No margin adjustment is done here, as it can lead to very uneven graphs



110
111
112
113
114
115
116
117
118
119
120
# File 'lib/CTioga/elements/curves.rb', line 110

def get_boundaries
  top = @function.y.max
  bottom = @function.y.min
  left = @function.x.min
  right = @function.x.max

  width = (left == right) ? 1 : right - left
  height = (top == bottom) ? 1 : top - bottom

  return [left,right,top,bottom]
end

#has_legend?Boolean

Returns:

  • (Boolean)


52
53
54
55
56
57
58
# File 'lib/CTioga/elements/curves.rb', line 52

def has_legend?
  if @style.legend
    return true
  else
    return false
  end
end

#make_path(t) ⇒ Object

Creates a path for the given curve. This should be defined with care, as it will be used for instance for region coloring and stroking. The function should only append to the current path, not attempt to create a new path or empty what was done before.



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/CTioga/elements/curves.rb', line 143

def make_path(t)
  bnds = Utils::Boundaries.new(parent.effective_bounds)
  if @style.interpolate
    for f in @function.split_monotonic
      new_f = f.bound_values(*bnds.real_bounds)
      t.append_interpolant_to_path(f.make_interpolant)
    end
  else
    f = @function.bound_values(*bnds.real_bounds)
    t.append_points_to_path(f.x, f.y)
  end
end

#need_style?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/CTioga/elements/curves.rb', line 37

def need_style?
  return true
end

#parse_position(spec) ⇒ Object

This function returns the index of a point from the curve according to the given spec, a string.

  • if spec is a number >= 1, it represents the index of the point in the Function object.

  • if spec is a number <1, it represents the relative position of the point in the object (it is then multiplied by the size to get the actual index).

  • if spec is of the form ‘x,y’, the closest point belonging to the function is taken. Not implemented yet.



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/CTioga/elements/curves.rb', line 74

def parse_position(spec)
  if spec =~ /(.+),(.+)/
    raise "The (x,y) point position is not implemented yet"
  else
    val = Float(spec)
    if val < 1 
      index = (@function.size * val).round
    else
      index = val.round
    end
    index
  end
end

#plot(t = nil) ⇒ Object Also known as: do



241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/CTioga/elements/curves.rb', line 241

def plot(t = nil)
  debug "Plotting curve #{inspect}"
  t.context do

    # The fill is always first
    draw_fill(t)

    for op in CurveStyle::DrawingOrder[@style[:drawing_order]]
      self.send("draw_#{op}".to_sym, t)
    end
  end
end

#set_data(ar) ⇒ Object

Sets the data for the curve



61
62
63
# File 'lib/CTioga/elements/curves.rb', line 61

def set_data(ar)
  @function = ar
end

#set_style(style) ⇒ Object



41
42
43
# File 'lib/CTioga/elements/curves.rb', line 41

def set_style(style)
  @style = style.dup
end

#tangent(index, dir = :both) ⇒ Object

Returns the tangent of the curve at the given point, that is a vector parallel to it. dir specifies if it is a left tangent or a right tangent or an average of both. Nothing else than the latter is implemented for now.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/CTioga/elements/curves.rb', line 92

def tangent(index, dir = :both)
  before = @function.point(index - 1)
  point = @function.point(index)
  after = @function.point(index + 1)
  raise "Point invalid" unless point
  tangent = Dvector[0,0]
  if index > 0
    tangent += (point - before)
  end
  if index < (@function.size - 1)
    tangent += (after - point)
  end
  return tangent
end

#y_value(spec) ⇒ Object

Returns a y value suitable for fills/histograms or other kinds of stuff based on a specification:

  • false/nil: returns nil

  • to_y_axis: y = 0

  • to_bottom: the bottom of the plot

  • to_top: the top of the plot

  • “y = .…”: the given value.



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/CTioga/elements/curves.rb', line 193

def y_value(spec)
  case spec
  when false, nil, :old_style
    return false
  when Float                # If that is already a Float, fine !
    return spec
  when :to_y_axis
    return 0.0
  when :to_bottom
    return parent.effective_bounds[3] # bottom
  when :to_top
    return parent.effective_bounds[2] # top
  when /y\s*=\s*(.*)/
    return Float($1)
  else
    warn "Y value #{spec} not understood"
    return false                  # We don't have anything to do, then.
  end
end