Class: Cosmos::LineGraph

Inherits:
Qt::Widget show all
Defined in:
lib/cosmos/gui/line_graph/line_graph.rb,
lib/cosmos/gui/line_graph/line_graph_popups.rb,
lib/cosmos/gui/line_graph/line_graph_drawing.rb,
lib/cosmos/gui/line_graph/line_graph_scaling.rb,
ext/cosmos/ext/line_graph/line_graph.c

Overview

LineGraph class continued

Constant Summary collapse

DOUBLE_CLICK_SECONDS =

Max seconds between clicks for a double click

0.2
LABEL_TICK_SIZE =

Pixels for a label tick

3
FRAME_OFFSET =
3
LEFT_X_LABEL_WIDTH_ADJUST =
10
GRAPH_SPACER =

Spacer between items

5
@@gradient =
Qt::LinearGradient.new(Qt::PointF.new(0, 0), Qt::PointF.new(0, 1))

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent = nil) ⇒ LineGraph

Public Interface Methods



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 121

def initialize(parent = nil)
  super(parent)

  #######################################################################
  # Initialize configurable attributes to default values
  #######################################################################
  @point_size = 5
  @show_lines = true
  @show_x_grid_lines = false
  @manual_x_grid_line_scale = nil
  @show_y_grid_lines = false
  @manual_y_grid_line_scale = nil
  @show_legend = false
  @show_cursor_and_popups = true
  @frame_color = 'black'
  @frame_size = 0
  @back_color = 'white'
  @graph_back_color = 'grey'
  @label_and_border_color = 'black'
  @title = nil
  @x_axis_title = nil
  @left_y_axis_title = nil
  @right_y_axis_title = nil
  @max_x_characters = 15
  @max_y_characters = 8
  @minimum_width = 250
  @minimum_height = 100
  @error = nil
  @ordered_x_values = true
  @show_popup_x_y = false
  @unix_epoch_x_values = true
  @utc_time = false
  @legend_position = :bottom # :bottom or :right

  # Initialize the callbacks
  @draw_cursor_line_callback = nil
  @mouse_leave_callback = nil
  @mouse_left_button_press_callback = nil
  @pre_error_callback = nil
  @post_error_callback = nil

  #######################################################################
  # Initialize read-only attributes to default values
  #######################################################################

  @x_max = 1
  @x_min = -1
  @x_max_label = nil
  @x_min_label = nil
  @left_y_max = 1
  @left_y_min = -1
  @right_y_max = 1
  @right_y_min = -1
  @horizontal_lines = []

  ###########################
  # Initialize internal state
  ###########################

  # Create line data
  @lines = Lines.new

  # Painter to do all the drawing with
  @painter = nil

  # Value used to scale a x value to a x graph coordinate
  @x_scale = 0

  # Value used to scale a left y value to a y graph coordinate
  @left_y_scale = 0

  # Value used to scale a right y value to a y graph coordinate
  @right_y_scale = 0

  # Size of the font used to display text
  @font_size = 10

  # The font class used to display text
  @font = Cosmos.getFont("Helvetica", @font_size)

  # The font class used for titles
  @title_font = Cosmos.getFont("Helvetica", @font_size + 4, Qt::Font::Bold)

  # Auto scale left y-axis setting
  @left_y_auto_scale = true

  # Auto scale right y-axis setting
  @right_y_auto_scale = true

  # Auto scale x-axis setting
  @x_auto_scale = true

  # Values of x gridlines
  @x_grid_lines = []

  # Values of y gridlines on left x-axis unless no left lines
  @y_grid_lines = []

  # Distance between gridlines on the left y-axis unless no left lines
  @y_grid_line_scale = 0.1

  # Distance between gridlines on the x-axis
  @x_grid_line_scale = 0.1

  # Flag to note if the mouse in the the graph window
  @mouse_in_window = false

  # Graph right boundary in window coordinates
  @graph_right_x = 0

  # Graph left boundary in window coordinates
  @graph_left_x = 0

  # Graph top boundary in window coordinates
  @graph_top_y = 0

  # Graph bottom boundary in window coordinates
  @graph_bottom_y = 0

  # Array containing popup information
  @popups = []

  # Position of cursor line
  @cursor_line_x = nil

  # Indicates state of left mouse button
  @left_button_pressed = false

  # Flag to prevent recursion in update_graph_size
  @in_update_graph_size = false

  # Time of previous left button release
  @previous_left_button_release_time = Time.now

  # List of line colors to use
  @color_list = ['blue','red','green','darkorange', 'gold', 'purple', 'hotpink', 'lime', 'cornflowerblue', 'brown', 'coral', 'crimson', 'indigo', 'tan', 'lightblue', 'cyan', 'peru', 'maroon','orange','navy','teal','black']

  @redraw_needed = true

  setContentsMargins(0,0,0,0)

  #setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff)
  #setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff)

  # Cause this widget to have a white background which we don't have to worry about painting
  # This may leak memory - but it is only in initialize so that is ok.
  p = palette()
  p.setBrush(Qt::Palette::Active, Qt::Palette::Window, Cosmos.getBrush(Qt::white))
  setPalette(p)
  setBackgroundRole(Qt::Palette::Window)
  setAutoFillBackground(true)

  update_graph_size()

  setMouseTracking(true) # enable mouseMoveEvents even when the mouse isn't pressed
end

Instance Attribute Details

#draw_cursor_line_callbackObject

Callback called when a cursor line is drawn - call(self, graph_x, @left_button_pressed)



92
93
94
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 92

def draw_cursor_line_callback
  @draw_cursor_line_callback
end

#horizontal_linesObject (readonly)

Extra horizontal lines



115
116
117
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 115

def horizontal_lines
  @horizontal_lines
end

#left_y_maxObject (readonly)

Minimm and maximum shown left y value



111
112
113
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 111

def left_y_max
  @left_y_max
end

#left_y_minObject (readonly)

Minimm and maximum shown left y value



111
112
113
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 111

def left_y_min
  @left_y_min
end

#mouse_leave_callbackObject

Callback called when the mouse leaves a graph - call(self)



94
95
96
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 94

def mouse_leave_callback
  @mouse_leave_callback
end

#mouse_left_button_press_callbackObject

Callback called when the mouse left button is pressed - call(self)



96
97
98
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 96

def mouse_left_button_press_callback
  @mouse_left_button_press_callback
end

#post_error_callbackObject

Callback called after exception popup is displayed - call(self)



100
101
102
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 100

def post_error_callback
  @post_error_callback
end

#pre_error_callbackObject

Callback called before exception popup is displayed - call(self)



98
99
100
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 98

def pre_error_callback
  @pre_error_callback
end

#right_y_maxObject (readonly)

Minimum and maximum shown right y value



113
114
115
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 113

def right_y_max
  @right_y_max
end

#right_y_minObject (readonly)

Minimum and maximum shown right y value



113
114
115
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 113

def right_y_min
  @right_y_min
end

#x_maxObject (readonly)

Minimum and maximum shown x values



107
108
109
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 107

def x_max
  @x_max
end

#x_max_labelObject (readonly)

Minimum and maximum shown x value label



109
110
111
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 109

def x_max_label
  @x_max_label
end

#x_minObject (readonly)

Minimum and maximum shown x values



107
108
109
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 107

def x_min
  @x_min
end

#x_min_labelObject (readonly)

Minimum and maximum shown x value label



109
110
111
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 109

def x_min_label
  @x_min_label
end

Class Method Details

.attr_accessor_with_redraw(*symbols) ⇒ Object

Create attr_accessors that automatically set the redraw_needed flag when they are set



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 27

def self.attr_accessor_with_redraw(*symbols)
 symbols.each do |name|
    self.class_eval("def #{name}; @#{name}; end")
    self.class_eval(%Q{
      def #{name}=(value)
        @#{name} = value
        @redraw_needed = true
      end
    })
  end
end

Instance Method Details

#add_horizontal_line(y_value, color, axis = :LEFT) ⇒ Object

Add a horizontal line to the graph



476
477
478
479
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 476

def add_horizontal_line(y_value, color, axis = :LEFT)
  @horizontal_lines << [y_value, color, axis]
  @redraw_needed = true
end

#add_line(legend_text, y, x = nil, y_labels = nil, x_labels = nil, y_states = nil, x_states = nil, color = 'auto', axis = :LEFT, max_points_plotted = nil) ⇒ Object

Adds a line to the graph - Afterwards the graph is ready to be drawn

color = 'auto' automatically determines color from index based lookup


379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 379

def add_line(legend_text, y, x = nil, y_labels = nil, x_labels = nil, y_states = nil, x_states = nil, color = 'auto', axis = :LEFT, max_points_plotted = nil)
  @unix_epoch_x_values = false unless x

  # if color specified as auto, do lookup
  if (color == 'auto')
    # If the number of lines is less than number of available colors,
    #   choose an unused color
    if (@lines.num_lines < @color_list.length)
      unused_colors = @color_list.dup
      @lines.legend.each do |name, line_color, axis|
        unused_colors.delete(line_color)
      end
      color = unused_colors[0]
    else
      # Get an index within the color list for the next line index
      line_color_idx = @lines.num_lines % (@color_list.length)
      color = @color_list[line_color_idx]
    end
  end

  @lines.add_line(legend_text, y, x, y_labels, x_labels, y_states, x_states, color, axis, max_points_plotted)

  # Adding a line implies a redraw is needed
  @redraw_needed = true
end

#add_popups_for_lines(x_value, value_delta) ⇒ Object

Add popups for the lines on one axis



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/cosmos/gui/line_graph/line_graph_popups.rb', line 34

def add_popups_for_lines(x_value, value_delta)
  popup_values = @lines.get_left_popup_data(x_value, value_delta, @ordered_x_values, @left_y_min, @left_y_max)
  popup_values.concat(@lines.get_right_popup_data(x_value, value_delta, @ordered_x_values, @right_y_min, @right_y_max))
  popup_values.each do |x, y, x_text, y_text, item, color, axis|
    # The get_xxx_popup_data routine can return an empty array if there are
    # no values for the axis so skip any empty arrays (x is nil)
    next unless x

    # Determine position in graph coordinates
    graph_x = scale_value_to_graph_x(x)
    graph_y = scale_value_to_graph_y(y, axis)

    if @left_button_pressed
      x_text = convert_x_value_to_text(x, 25, true) unless x_text
      popup_text = "(#{item}) #{x_text}, #{y_text}"
    else
      x_text = convert_x_value_to_text(x) unless x_text
      popup_text = "#{x_text}, #{y_text}"
    end

    # Determine popup's width and height based on width/height of text
    metrics = Qt::FontMetrics.new(@font)
    popup_width = metrics.width(popup_text) + 10
    popup_height = metrics.height + 10

    # Make sure that the popup stays in the graph window on the x axis.
    graph_x_adjusted = popup_width + graph_x
    if graph_x_adjusted > self.width
      graph_x -= popup_width
      graph_x = 0 if graph_x < 0
    end

    # Make sure that the popup stays in the graph window on the y axis.
    graph_y_adjusted = popup_height + graph_y
    if graph_y_adjusted > (self.height - 1)
      graph_y = self.height - 1 - popup_height
    else
      graph_y -= popup_height
    end
    graph_y = 1 if graph_y < 1

    # Add popup
    @popups << [popup_text, graph_x, graph_y, popup_width, popup_height, color]
  end
end

#adjust_popup_positionsObject

Algorithm to not overlap popups



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/cosmos/gui/line_graph/line_graph_popups.rb', line 81

def adjust_popup_positions
  # Sort popups by graph_y
  @popups.sort! {|a,b| a[2] <=> b[2]}

  # Determine if any popups overlap vertically
  overlap = false
  index = 0
  max_index = @popups.length - 1
  @popups.each do |popup_text, graph_x, graph_y, popup_width, popup_height, color|
    break if index == max_index
    if (graph_y + popup_height) > @popups[index + 1][2] # graph_y of next popup
      overlap = true
      break
    end
    index += 1
  end

  # Handle overlap case
  if overlap
    # Determine needed statistics
    combined_height = 0
    @popups.each do |popup_text, graph_x, graph_y, popup_width, popup_height, color|
      combined_height += popup_height + 1
    end

    # Stack popups around center of canvas
    next_graph_y = ((self.height / 2) - (combined_height / 2)).to_i
    @popups.length.times do |popup_index|
      @popups[popup_index][2] = next_graph_y
      next_graph_y += @popups[popup_index][4] + 2
    end
  end
end

#auto_scale_xObject

Start auto scaling of the x axis



411
412
413
414
415
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 411

def auto_scale_x
  @x_auto_scale  = true
  @redraw_needed = true
  graph()
end

#auto_scale_y(axis) ⇒ Object

Start auto scaling of the y axis



418
419
420
421
422
423
424
425
426
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 418

def auto_scale_y(axis)
  if axis == :LEFT
    @left_y_auto_scale = true
  else # axis == :RIGHT
    @right_y_auto_scale = true
  end
  @redraw_needed = true
  graph()
end

#auto_scale_y_axis(value_range, manual_y_grid_line_scale) ⇒ Float

Determine the y minimum and maximum values for the given lines and given grid line scale

Parameters:

  • lines (Array)

    Array of lines containing the x and y values

  • manual_y_grid_line_scale (Float)

    Whether there is a manual grid line scale. The return values will be multiples of this scale if given.

Returns:

  • (Float, Float)

    The minimum and maximum values



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 132

def auto_scale_y_axis(value_range, manual_y_grid_line_scale)
  y_max = 1
  y_min = -1

  # Ensure we have a valid range of values
  if value_range.size
    y_min = value_range.first
    y_max = value_range.last

    # Add space between values and edges of graph
    diff = y_max - y_min
    if diff == 0.0 and y_min == 0.0
      # Values are all zero so simply add/subtract 1
      y_max = 1
      y_min = -1
    elsif diff == 0.0
      # Values are the same but not zero, so separate them by a multiple of their value
      y_max = y_max + y_max.abs
      y_min = y_min - y_min.abs
    else
      # Give a 5% margin
      y_max += (diff * 0.05)
      y_min -= (diff * 0.05)
    end

    # Determine rough grid line scale
    if manual_y_grid_line_scale
      y_grid_line_scale = manual_y_grid_line_scale
    else
      diff = y_max - y_min
      y_grid_line_scale = calculate_base(diff)
    end

    # Enforce non-zero scale
    y_grid_line_scale = 0.1 if y_grid_line_scale == 0

    # Now move the max and min values so they are multiples of the scale.
    y_min  = y_min - (y_min % y_grid_line_scale)
    y_min -= y_grid_line_scale if y_min == 0
    y_max  = y_max - (y_max % y_grid_line_scale) + y_grid_line_scale
  end

  return y_min, y_max
end

#build_popups_from_x_value(x_value) ⇒ Object

Builds popups associated with an x value



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/cosmos/gui/line_graph/line_graph_popups.rb', line 17

def build_popups_from_x_value(x_value)
  # Clear any existing popups
  @popups = []

  if x_value <= @x_max and x_value >= @x_min
    # Determine how near in x value that the cursor must be to a point
    value_delta = ((@x_max - @x_min).to_f / (@graph_right_x - @graph_left_x)) * 3.0 # within 3 pixels

    # Add the popups
    add_popups_for_lines(x_value, value_delta)

    # Make sure popups don't overlap
    adjust_popup_positions()
  end
end

#build_x_grid_linesObject

Builds x gridlines



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 280

def build_x_grid_lines
  @x_grid_lines = []

  @x_grid_line_scale = determine_grid_line_scale(@manual_x_grid_line_scale, @x_min, @x_max)
  if @show_x_grid_lines
    states = @lines.single_line_with_x_states
    if states
      states.each do |state_name, state_value|
        state_value_float = state_value.to_f
        max_length = 0
        if state_value_float <= @x_max and state_value_float >= @x_min
          # Add gridline for state
          @x_grid_lines << [state_value, state_name]
          max_length = state_name.length if state_name.length > max_length
        end
        if (max_length + 1) > @max_x_characters
          @max_x_characters = max_length + 1
          determine_graph_size()
        end
      end
    else
      calculate_x_grid_lines()
    end
  else
    if @x_max_label or @x_min_label
      x_max_length = @x_max_label.to_s.length
      x_min_length = @x_min_label.to_s.length
      if x_max_length > x_min_length
        max_length = x_max_length
      else
        max_length = x_min_length
      end
      if (max_length + 1) > @max_x_characters
        @max_x_characters = max_length + 1
        determine_graph_size()
      end
    end

    # Just show max and min
    @x_grid_lines << [@x_max, @x_max_label]
    @x_grid_lines << [@x_min, @x_min_label]
  end
end

#build_y_grid_lines(axis) ⇒ Object

Builds y gridlines for an axis



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 178

def build_y_grid_lines(axis)
  @y_grid_lines = []

  case axis
  when :NONE
    return # Nothing to do if there are no lines
  when :LEFT, :BOTH
    y_max        = @left_y_max
    y_min        = @left_y_min
    y_auto_scale = @left_y_auto_scale
    axis = :LEFT
  when :RIGHT
    y_max        = @right_y_max
    y_min        = @right_y_min
    y_auto_scale = @right_y_auto_scale
  end

  @y_grid_line_scale = determine_grid_line_scale(@manual_y_grid_line_scale, y_min, y_max)
  if @show_y_grid_lines
    states = @lines.unique_y_states(axis)
    if states
      states.each do |state_name, state_value|
        state_value_float = state_value.to_f
        max_length = 0
        if state_value_float <= y_max and state_value_float >= y_min
          # Add gridline for state
          @y_grid_lines << state_value
          max_length = state_name.length if state_name.length > max_length
        end
        if (max_length + 1) > @max_y_characters
          @max_y_characters = max_length + 1
          determine_graph_size()
        end
      end
    else
      calculate_y_grid_lines(y_min, y_max)
    end
  else
    # Just show max and min
    @y_grid_lines << y_max
    @y_grid_lines << y_min
  end
end

#calculate_base(value) ⇒ Object

This function is used to calculate the “base” for a value. In this case, the base is defined to be the largest power of 10 that can be used to create a scale from 0 to the value with at least 10 values. Ex: calculate_base(100) = 10

calculate_base(99)  =  1
calculate_base(10)  =  1
calculate_base(9)   =  0.1
calculate_base(1)   =  0.1
calculate_base(0.9) =  0.01
calculate_base(0.1) =  0.01
calculate_base(0)   =  1 # this one is arbitrary but we don't want to return 0


394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 394

def calculate_base(value)
  base = value.abs
  operations = 0
  if base >= 1
    while (true)
      base = base / 10
      if base < 1
        break
      end
      operations += 1
    end
  elsif base != 0
    operations = -1
    while (true)
      base = base * 10
      if base >= 1
        break
      end
      operations -= 1
    end
  else
    operations = 1
  end
  return (10**(operations - 1)).to_f
end

#calculate_scaling_factorsObject

Calculate scaling factors between value and graph coordinates



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 88

def calculate_scaling_factors
  # Determine the x conversion factor between value coordinates and graph coordinates
  if @x_max != @x_min
    @x_scale = (@graph_right_x - @graph_left_x).to_f / (@x_max - @x_min).to_f
  else
    @x_scale = 0.0
  end

  # Determine the y conversion factor between value coordinates and graph coordinates
  if @left_y_max != @left_y_min
    @left_y_scale = (@graph_bottom_y - @graph_top_y).to_f / (@left_y_max - @left_y_min).to_f
  else
    @left_y_scale = 0.0
  end
  if @right_y_max != @right_y_min
    @right_y_scale = (@graph_bottom_y - @graph_top_y).to_f / (@right_y_max - @right_y_min).to_f
  else
    @right_y_scale = 0.0
  end
end

#calculate_x_grid_linesObject

Calculate the x grid lines for the graph



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 325

def calculate_x_grid_lines
  if @manual_x_grid_line_scale
    # With manual grid lines, draw them all regardless of whether it will look nice
    @x_grid_lines << [@x_min, nil]
    grid_value = @x_min + @x_grid_line_scale
    if grid_value != @x_min
      while grid_value < (@x_max - (@x_grid_line_scale / 2.0))
        @x_grid_lines << [grid_value, nil]
        grid_value += @x_grid_line_scale
      end
    end
    @x_grid_lines << [@x_max, nil]
  else
    # Determine a nice number of gridlines to show not including edges
    metrics = Cosmos.getFontMetrics(@font)
    label_size = metrics.width("HH:MM:SS:XXX")
    # Calculate the maximum number of grid lines based on the number
    # of labels that can fit. Then add one because the far right line
    # can choose not to display its label.
    max_grid_lines = (@graph_right_x - @graph_left_x) / label_size + 1
    max_grid_lines = 2 if max_grid_lines < 2

    # Calculate all the possible values between the x minimum and x
    # maximum based on the x scale. These are all possible grid lines.
    possible_grid_lines = []
    possible_grid_lines << @x_min
    grid_value = @x_min + @x_grid_line_scale
    if grid_value != @x_min
      while grid_value < (@x_max - (@x_grid_line_scale / 2.0))
        possible_grid_lines << grid_value
        grid_value += @x_grid_line_scale
      end
    end
    possible_grid_lines << @x_max

    # Calculate the index through the possible grid lines by dividing
    # the total possible by the maximum that will fit on the graph.
    # Round up the integer math by adding 1.
    num_possible = possible_grid_lines.length
    increment = (num_possible / max_grid_lines) + 1

    # Store off all the grid lines that will be displayed
    @x_grid_lines << [@x_min, nil] # far left line
    if increment > 0
      index = increment
      while index < (num_possible - increment)
        @x_grid_lines << [possible_grid_lines[index], nil]
        index += increment
      end
      # Add the final increment as long as it doesn't reach the max
      if possible_grid_lines[index] != @x_max
        @x_grid_lines << [possible_grid_lines[index], nil]
      end
    end
    @x_grid_lines << [@x_max, nil] # far right line
  end
end

#calculate_y_grid_lines(y_min, y_max) ⇒ Object



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 222

def calculate_y_grid_lines(y_min, y_max)
  if @manual_y_grid_line_scale
    # With manual grid lines, draw them all regardless of whether it will look nice
    @y_grid_lines << y_min
    grid_value = y_min + @y_grid_line_scale
    if grid_value != y_min
      while grid_value < (y_max - (@y_grid_line_scale / 2.0))
        @y_grid_lines << grid_value
        grid_value += @y_grid_line_scale
      end
    end
    @y_grid_lines << y_max
  else
    # Determine a nice number of gridlines to show not including edges
    metrics = Cosmos.getFontMetrics(@font)
    label_size = metrics.height
    # Calculate the maximum nubmer of grid lines based on the number of
    # labels that can fit. Then add one because the top line can choose
    # not to display its label.
    max_grid_lines = (@graph_bottom_y - @graph_top_y) / label_size + 1
    max_grid_lines = 2 if max_grid_lines < 2

    # Calculate all the possible values between the y minimum and y
    # maximum based on the y scale. These are all possible grid lines.
    possible_grid_lines = []
    possible_grid_lines << y_min
    grid_value = y_min + @y_grid_line_scale
    if grid_value != y_min
      while grid_value < (y_max - (@y_grid_line_scale / 2.0))
        possible_grid_lines << grid_value
        grid_value += @y_grid_line_scale
      end
    end
    possible_grid_lines << y_max

    # Calculate the index through the possible grid lines by dividing
    # the total possible by the maximum that will fit on the graph.
    # Round up the integer math by adding 1.
    num_possible = possible_grid_lines.length
    increment = (num_possible / max_grid_lines) + 1

    @y_grid_lines << y_min
    if increment > 0
      index = increment
      while index < (num_possible - increment)
        @y_grid_lines << possible_grid_lines[index]
        index += increment
      end
      # Add the final increment as long as it doesn't reach the max
      if possible_grid_lines[index] != y_max
        @y_grid_lines << possible_grid_lines[index]
      end
    end
    @y_grid_lines << y_max
  end
end

#calculate_y_labelsObject

Calcuate the Y axis labels as well as their width and adjust the size of the graph accordingly



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 105

def calculate_y_labels
  metrics = Cosmos.getFontMetrics(@font)
  left_widths  = [metrics.width("-1.0")]
  right_widths = [0]
  @left_text = []
  @right_text = []

  @y_grid_lines.each do |value|
    value = 0 unless ((value > (@y_grid_line_scale / 2.0)) || (value < (@y_grid_line_scale / -2.0)))
    # Get text for label(s)
    case @lines.axes
    when :BOTH
      @left_text  << convert_y_value_to_text(value, @max_y_characters, :LEFT)
      left_widths << metrics.width(@left_text[-1])
      right_value = scale_left_to_right_y(value)
      @right_text  << convert_y_value_to_text(right_value, @max_y_characters, :RIGHT)
      right_widths << metrics.width(@right_text[-1])
    when :LEFT
      @left_text  << convert_y_value_to_text(value, @max_y_characters, :LEFT)
      left_widths << metrics.width(@left_text[-1])
    when :RIGHT
      @right_text << convert_y_value_to_text(value, @max_y_characters, :RIGHT)
      right_widths << metrics.width(@right_text[-1])
    end
  end

  # Also include half of width of first x label into left widths.
  value, label = @x_grid_lines[0]
  if label
    text = label.to_s
  else
    text = convert_x_value_to_text(value)
  end
  left_widths << ((metrics.width(text) / 2) - LEFT_X_LABEL_WIDTH_ADJUST)

  @graph_left_x += left_widths.max
  @graph_right_x -= (right_widths.max + GRAPH_SPACER)
end

#clear_horizontal_linesObject

Clears all horizontal lines on the graph



470
471
472
473
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 470

def clear_horizontal_lines
  @horizontal_lines = []
  @redraw_needed = true
end

#clear_linesObject

Clears all knowledge of line data - the graph is a clean slate



372
373
374
375
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 372

def clear_lines
  @lines.clear
  @redraw_needed = true
end

#convert_x_value_to_text(value, max_characters = @max_x_characters, full_date = false) ⇒ Object

Converts a x value into text with a max number of characters



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 247

def convert_x_value_to_text(value, max_characters = @max_x_characters, full_date = false)
  if !@show_popup_x_y && @unix_epoch_x_values
    # Determine if the value is a time stamp and should be converted
    if value > 1 && value < 2147483647
      time = Time.at(value)
      time = time.utc if @utc_time
      if full_date
        time.formatted # full date with day, month, year
      else
        time.formatted(false) # just hour, minutes, seconds
      end
    else
      truncate_to_max(value.to_s, max_characters, value)
    end
  else
    truncate_to_max(value.to_s, max_characters, value)
  end
end

#convert_y_value_to_text(value, max_characters, axis) ⇒ Object

Converts a y value into text with a max number of characters



267
268
269
270
271
272
273
274
275
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 267

def convert_y_value_to_text(value, max_characters, axis)
  states = @lines.unique_y_states(axis)
  if states
    text = states.key(value)
  else
    text = truncate_to_max(value.to_s, max_characters, value)
  end
  return text
end

#determine_graph_sizeObject

Determine the size of the actual graph area



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 20

def determine_graph_size
  metrics = Cosmos.getFontMetrics(@font)

  @graph_left_x = 4 * GRAPH_SPACER
  if @left_y_axis_title
    @graph_left_x += metrics.width('W') + GRAPH_SPACER
  end

  # Determine number of pixels to the right side of graph
  if @show_legend and !@lines.empty? and @legend_position == :right
    legend_x, legend_y, legend_width, legend_height = get_legend_position()
  else
    legend_width = 0
  end
  @graph_right_x = self.width - legend_width - 8 * GRAPH_SPACER
  if @right_y_axis_title
    @graph_right_x -= (metrics.width('W') + GRAPH_SPACER)
  end

  # Determine number of pixels above top of graph
  if @title
    title_metrics = Cosmos.getFontMetrics(@title_font)
    title_height = title_metrics.height
  else
    title_height = 0
  end
  half_y_max_label_height = (metrics.height / 2)
  @graph_top_y = title_height + half_y_max_label_height + GRAPH_SPACER

  # Determine number of pixels below bottom of graph
  if @x_axis_title
    x_axis_title_height = metrics.height + GRAPH_SPACER / 2
  else
    x_axis_title_height = 0
  end
  x_axis_label_height = metrics.height + GRAPH_SPACER

  legend_height = 0
  if @show_legend and !@lines.empty? and @legend_position == :bottom
    text_y = self.height - 1 - (GRAPH_SPACER * 2)
    text_height = metrics.height
    if @lines.axes == :BOTH
      left_text_y  = text_y
      right_text_y = text_y
      @lines.legend.each do |text, color, axis|
        if axis == :LEFT
          left_text_y -= text_height
        else
          right_text_y -= text_height
        end
      end
      if left_text_y < right_text_y
        text_y = left_text_y
      else
        text_y = right_text_y
      end
    else
      @lines.legend.each do |text, color, axis|
        text_y -= text_height
      end
    end
    legend_height = self.height - text_y - GRAPH_SPACER
  end

  @graph_bottom_y = self.height - 1 - (x_axis_title_height + x_axis_label_height + legend_height) - GRAPH_SPACER - GRAPH_SPACER
end

#draw_cursor_line_and_popups(dc) ⇒ Object

Draws the cursor line and popups if needed - called from drawForeground



415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 415

def draw_cursor_line_and_popups(dc)
  if @mouse_in_window == true
    # Draw the cursor line at mouse position
    x = scale_graph_to_value_x(mapFromGlobal(cursor.pos).x)
    draw_cursor_line_and_popups_at_x(dc, x)

    # Call callback when the mouse caused the cursor to draw
    saved_callback = @draw_cursor_line_callback
    @draw_cursor_line_callback = nil
    saved_callback.call(self, x, @left_button_pressed) if saved_callback
    @draw_cursor_line_callback = saved_callback
  elsif @cursor_line_x
    # Draw the cursor line at given position
    draw_cursor_line_and_popups_at_x(dc, @cursor_line_x)
  end
end

#draw_cursor_line_and_popups_at_x(dc, x) ⇒ Object

Draws the cursor line at a given position and builds the popups



433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 433

def draw_cursor_line_and_popups_at_x(dc, x)
  # Draw cursor line
  if @lines.left_y_axis? and x <= @x_max and x >= @x_min
    draw_line(dc, x, @left_y_min, x, @left_y_max, true, 0, :LEFT, @back_color)
  elsif @lines.right_y_axis? and x <= @x_max and x >= @x_min
    draw_line(dc, x, @right_y_min, x, @right_y_max, true, 0, :RIGHT, @back_color)
  end

  # Build and draw popups
  build_popups_from_x_value(x)
  draw_popups(dc)
end

#draw_error_icon(dc) ⇒ Object

Draws an icon that indicates an error has occurred



469
470
471
472
473
474
475
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 469

def draw_error_icon(dc)
  if @error
    dc.addEllipseColor(@graph_right_x - 60, 20, 40, 40, 'red')
    dc.addRectColorFill(@graph_right_x - 42, 25, 4, 20, 'red')
    dc.addRectColorFill(@graph_right_x - 42, 50, 4, 4, 'red')
  end
end

#draw_frame(dc) ⇒ Object

Draws the overall frame around the graph, legend, labels, etc



464
465
466
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 464

def draw_frame(dc)
  dc.addRectColor(0,0,self.width-1,self.height-1, @frame_color)
end

#draw_graph_background(dc) ⇒ Object

Draws the colored rectangle for the graph



66
67
68
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 66

def draw_graph_background(dc)
  dc.addRectColorFill(@graph_left_x, @graph_top_y, @graph_right_x - @graph_left_x, @graph_bottom_y - @graph_top_y, @label_and_border_color, @@gradient)
end

#draw_graph_into_back_bufferObject



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 26

def draw_graph_into_back_buffer
  # Determine the scale of the graph
  determine_graph_size()
  scale_graph()
  build_x_grid_lines()
  build_y_grid_lines(@lines.axes)
  calculate_y_labels()
  calculate_scaling_factors()

  # Draw overall graph and origin lines
  draw_graph_background(@painter)
  draw_origin_lines(@painter)

  # Draw gridlines and titles
  draw_x_axis_grid_lines(@painter)
  draw_y_axis_grid_lines(@painter)
  draw_horizontal_lines(@painter)
  draw_title(@painter)
  draw_x_axis_title(@painter)
  draw_y_axis_title(@painter, :LEFT)
  draw_y_axis_title(@painter, :RIGHT)

  draw_legend(@painter)

  draw_lines(@painter, :LEFT)
  draw_lines(@painter, :RIGHT)
end

#draw_graph_to_screenObject

Draws the graph to the screen



55
56
57
58
59
60
61
62
63
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 55

def draw_graph_to_screen
  draw_frame(@painter)

  # Draw cursor line and popups if present
  draw_cursor_line_and_popups(@painter) if @show_cursor_and_popups

  # Draw error icon if present
  draw_error_icon(@painter)
end

#draw_horizontal_lines(dc) ⇒ Object

Draws any extra horizontal lines onto the graph



278
279
280
281
282
283
284
285
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 278

def draw_horizontal_lines(dc)
  @horizontal_lines.each do |y_value, color, axis|
    y = scale_value_to_graph_y(y_value, axis)
    if (y > @graph_top_y) and (y < @graph_bottom_y)
      dc.addLineColor(@graph_left_x, y, @graph_right_x, y, color)
    end
  end
end

#draw_legend(dc) ⇒ Object

Draws the legend on the bottom of the graph



334
335
336
337
338
339
340
341
342
343
344
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 334

def draw_legend(dc)
  return if !@show_legend || @lines.empty?

  text_x, text_y, legend_width, legend_height = get_legend_position()
  if @lines.axes == :BOTH
    draw_legend_text(dc, :LEFT, text_x, text_y, legend_height)
    draw_legend_text(dc, :RIGHT, text_x + (legend_width / 2), text_y, legend_height)
  else
    draw_legend_text(dc, false, text_x, text_y, legend_height)
  end
end

#draw_legend_text(dc, specific_axis, text_x, text_y, line_height) ⇒ Object



399
400
401
402
403
404
405
406
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 399

def draw_legend_text(dc, specific_axis, text_x, text_y, line_height)
  @lines.legend.reverse_each do |legend_text, color, axis|
    if !specific_axis || specific_axis == axis
      dc.addSimpleTextAt(legend_text, text_x, text_y, color)
      text_y -= line_height
    end
  end
end

#draw_line(dc, x1, y1, x2, y2, show_line, point_size, axis, color) ⇒ Object

Draws a line between two points that is clipped to fit the visible graph if necessary



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'ext/cosmos/ext/line_graph/line_graph.c', line 341

static VALUE draw_line(VALUE self, VALUE dc, VALUE x1, VALUE y1, VALUE x2, VALUE y2, VALUE show_line, VALUE point_size, VALUE axis, VALUE color) {
  long long_graph_left_x = 0;
  long long_graph_top_y = 0;
  ID id_axis = 0;
  double double_x1 = 0.0;
  double double_y1 = 0.0;
  double double_x2 = 0.0;
  double double_y2 = 0.0;
  double double_x_min = 0.0;
  double double_y_min = 0.0;
  double double_x_max = 0.0;
  double double_y_max = 0.0;
  double double_x_scale = 0.0;
  double double_y_scale = 0.0;

  id_axis = SYM2ID(axis);
  double_x_max = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_x_max), id_method_to_f, 0));
  double_x_min = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_x_min), id_method_to_f, 0));
  double_x1 = RFLOAT_VALUE(rb_funcall(x1, id_method_to_f, 0));
  double_y1 = RFLOAT_VALUE(rb_funcall(y1, id_method_to_f, 0));
  double_x2 = RFLOAT_VALUE(rb_funcall(x2, id_method_to_f, 0));
  double_y2 = RFLOAT_VALUE(rb_funcall(y2, id_method_to_f, 0));
  double_x_scale = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_x_scale), id_method_to_f, 0));
  long_graph_left_x = FIX2INT(rb_ivar_get(self, id_ivar_graph_left_x));
  long_graph_top_y = FIX2INT(rb_ivar_get(self, id_ivar_graph_top_y));

  if (id_axis == id_LEFT) {
    double_y_max = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_left_y_max), id_method_to_f, 0));
    double_y_min = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_left_y_min), id_method_to_f, 0));
    double_y_scale = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_left_y_scale), id_method_to_f, 0));
  } else /* id_axis == id_RIGHT */ {
    double_y_max = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_right_y_max), id_method_to_f, 0));
    double_y_min = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_right_y_min), id_method_to_f, 0));
    double_y_scale = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_right_y_scale), id_method_to_f, 0));
  }

  draw_line_internal(dc, double_x1, double_y1, double_x2, double_y2, double_x_min, double_y_min, double_x_max, double_y_max, double_x_scale, double_y_scale, long_graph_left_x, long_graph_top_y, id_axis, show_line, point_size, color);

  return Qnil;
}

#draw_linesObject

Draws all lines for the given axis



385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# File 'ext/cosmos/ext/line_graph/line_graph.c', line 385

static VALUE draw_lines (VALUE self, VALUE dc, VALUE axis) {
  long long_graph_left_x = 0;
  long long_graph_top_y = 0;
  long num_lines = 0;
  long line_index = 0;
  long line_length = 0;
  long point_index = 0;
  ID id_axis = 0;
  volatile VALUE lines = Qnil;
  volatile VALUE line = Qnil;
  volatile VALUE x_values = Qnil;
  volatile VALUE y_values = Qnil;
  volatile VALUE color = Qnil;
  volatile VALUE show_lines = Qnil;
  volatile VALUE point_size = Qnil;
  double double_x1 = 0.0;
  double double_y1 = 0.0;
  double double_x2 = 0.0;
  double double_y2 = 0.0;
  double double_x_min = 0.0;
  double double_y_min = 0.0;
  double double_x_max = 0.0;
  double double_y_max = 0.0;
  double double_x_scale = 0.0;
  double double_y_scale = 0.0;

  id_axis = SYM2ID(axis);
  double_x_max = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_x_max), id_method_to_f, 0));
  double_x_min = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_x_min), id_method_to_f, 0));
  double_x_scale = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_x_scale), id_method_to_f, 0));
  long_graph_left_x = FIX2INT(rb_ivar_get(self, id_ivar_graph_left_x));
  long_graph_top_y = FIX2INT(rb_ivar_get(self, id_ivar_graph_top_y));

  if (id_axis == id_LEFT) {
    lines = rb_funcall(rb_ivar_get(self, id_ivar_lines), id_method_left, 0);
    double_y_max = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_left_y_max), id_method_to_f, 0));
    double_y_min = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_left_y_min), id_method_to_f, 0));
    double_y_scale = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_left_y_scale), id_method_to_f, 0));
  } else {
    lines = rb_funcall(rb_ivar_get(self, id_ivar_lines), id_method_right, 0);
    double_y_max = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_right_y_max), id_method_to_f, 0));
    double_y_min = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_right_y_min), id_method_to_f, 0));
    double_y_scale = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_right_y_scale), id_method_to_f, 0));
  }

  show_lines = rb_ivar_get(self, id_ivar_show_lines);
  point_size = rb_ivar_get(self, id_ivar_point_size);

  num_lines = RARRAY_LEN(lines);
  for (line_index = 0; line_index < num_lines; line_index++) {
    line = rb_ary_entry(lines, line_index);
    x_values = rb_ary_entry(line, 0);
    y_values = rb_ary_entry(line, 1);
    color = rb_ary_entry(line, 6);

    /* Get the first point of the line */
    double_x1 = RFLOAT_VALUE(rb_ary_entry(x_values, 0));
    double_y1 = RFLOAT_VALUE(rb_ary_entry(y_values, 0));

    /* Loop over each data point of the line */
    line_length = RARRAY_LEN(x_values);
    for (point_index = 0; point_index < line_length; point_index++) {
      double_x2 = RFLOAT_VALUE(rb_ary_entry(x_values, point_index));
      double_y2 = RFLOAT_VALUE(rb_ary_entry(y_values, point_index));

      draw_line_internal(dc, double_x1, double_y1, double_x2, double_y2, double_x_min, double_y_min, double_x_max, double_y_max, double_x_scale, double_y_scale, long_graph_left_x, long_graph_top_y, id_axis, show_lines, point_size, color);

      double_x1 = double_x2;
      double_y1 = double_y2;
    }
  }

  return Qnil;
}

#draw_origin_lines(dc) ⇒ Object

Draws origin lines if they fall on the graph



71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 71

def draw_origin_lines(dc)
  if (@left_y_max > 0) and (@left_y_min < 0) and @lines.axes == :LEFT
    y1 = scale_value_to_graph_y(0.0, :LEFT)
    dc.addLineColor(@graph_left_x - 3, y1, @graph_right_x, y1)
  end
  if (@right_y_max > 0) and (@right_y_min < 0) and @lines.axes == :RIGHT
    y1 = scale_value_to_graph_y(0.0, :RIGHT)
    dc.addLineColor(@graph_left_x, y1, @graph_right_x + 3, y1)
  end
  if (@x_max > 0) and (@x_min < 0)
    x1 = scale_value_to_graph_x(0.0)
    dc.addLineColor(x1, @graph_bottom_y + 3, x1, @graph_top_y)
  end
end

#draw_popups(dc) ⇒ Object

Draws popup boxes if any



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 447

def draw_popups(dc)
  unless @popups.empty?
    # Draw each popup
    @popups.each do |popup_text, popup_x, popup_y, popup_width, popup_height, popup_color|
      # Draw overall rectangle
      dc.addRectColorFill(popup_x, popup_y, popup_width, popup_height, @back_color)

      # Draw border around rectangle in line color
      dc.addRectColor(popup_x, popup_y, popup_width, popup_height, popup_color)

      # Draw popup text
      dc.addSimpleTextAt(popup_text, popup_x + 5, popup_y + popup_height - 7, @label_and_border_color)
    end # popups.each
  end # unless popups.empty?
end

#draw_title(dc) ⇒ Object

Draw the overall graph title above the graph



288
289
290
291
292
293
294
295
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 288

def draw_title(dc)
  if @title
    metrics = Cosmos.getFontMetrics(@title_font)
    dc.setFont(@title_font)
    dc.addSimpleTextAt(@title, (self.width / 2) - (metrics.width(@title) / 2), metrics.height)
    dc.setFont(@font)
  end
end

#draw_x_axis_grid_lines(dc) ⇒ Object

Draws the gridlines for the x-axis



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 195

def draw_x_axis_grid_lines(dc)
  grid_lines = []
  @x_grid_lines.each do |value, label|
    # If the line has states or is far enough away from the origin
    if @lines.x_states || ((value > (@x_grid_line_scale / 2.0)) || (value < (@x_grid_line_scale / -2.0)))
      grid_lines << draw_x_label(dc, value, label)
    else
      grid_lines << draw_x_label(dc, 0, nil)
    end
  end
  # Now draw all the grid lines so we can use a single Pen for all
  grid_lines.each do |x1, y1, x2, y2|
    dc.addLineColor(x1, y1, x2, y2, Cosmos::DASHLINE_PEN)
  end
end

#draw_x_axis_title(dc) ⇒ Object

Draws the x axis title below the graph



298
299
300
301
302
303
304
305
306
307
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 298

def draw_x_axis_title(dc)
  if @x_axis_title
    metrics = Cosmos.getFontMetrics(@font)
    text_width  = metrics.width(@x_axis_title)
    text_height = metrics.height
    dc.addSimpleTextAt(@x_axis_title,
      (((@graph_right_x - @graph_left_x) / 2) + @graph_left_x) - (text_width / 2),
      @graph_bottom_y + (2 * text_height) + GRAPH_SPACER)
  end
end

#draw_x_label(dc, value, label) ⇒ Object

This function is used to draw the x labels and returns the line positions.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 213

def draw_x_label(dc, value, label)
  if label
    text = label.to_s
  else
    text = convert_x_value_to_text(value)
  end
  metrics = Cosmos.getFontMetrics(@font)
  text_width  = metrics.width(text)
  text_height = metrics.height
  x1 = scale_value_to_graph_x(value)
  y1 = @graph_bottom_y + LABEL_TICK_SIZE
  if (x1 < @graph_left_x)
    x1 = @graph_left_x
  end
  if (x1 > @graph_right_x)
    x1 = @graph_right_x
  end
  y2 = @graph_top_y
  x2 = x1

  # Only display the label if we have room. This really only affects the
  # right side of the graph since that's where new grid lines appear. The
  # 1.25 is because we shift the far right label over a bit to eliminate
  # white space on the right side of the graph.
  if (x1 == @graph_right_x) || (x1 < (@graph_right_x - (1.25 * text_width)))
    # Shift the far right label left a bit to eliminate white space
    text_x = x1 - text_width / 4 if x1 == @graph_right_x
    text_x = x1 - text_width / 2 # center the text
    dc.addSimpleTextAt(text, text_x, @graph_bottom_y + text_height + GRAPH_SPACER)
  end
  [x1, y1, x2, y2]
end

#draw_y_axis_grid_lines(dc) ⇒ Object

Draws the gridlines for a y-axis



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 87

def draw_y_axis_grid_lines(dc)
  grid_lines = []
  @y_grid_lines.each_with_index do |value, index|
    # Don't draw gridlines that are too close to 0
    if ((value > (@y_grid_line_scale / 2.0)) || (value < (@y_grid_line_scale / -2.0)))
      grid_lines << draw_y_label(dc, value, index)
    else
      grid_lines << draw_y_label(dc, 0, index)
    end
  end
  # Now draw all the grid lines so we can use a single Pen for all
  grid_lines.each do |y|
    dc.addLineColor(@graph_left_x, y, @graph_right_x, y, Cosmos::DASHLINE_PEN)
  end
end

#draw_y_axis_title(dc, axis) ⇒ Object

Draws titles to the left and right of the Y axis



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 310

def draw_y_axis_title(dc, axis)
  metrics = Cosmos.getFontMetrics(@font)
  if axis == :LEFT
    y_axis_title = @left_y_axis_title
    graph_x = GRAPH_SPACER
  else
    y_axis_title = @right_y_axis_title
    graph_x = self.width - metrics.width('W') - GRAPH_SPACER - FRAME_OFFSET
  end

  if y_axis_title
    text_height = metrics.height
    total_height = text_height * y_axis_title.length
    start_height = (height - total_height) / 2
    max_width = metrics.width('W')
    y_axis_title.length.times do |index|
      character = y_axis_title[index..index]
      cur_width = metrics.width(character)
      dc.addSimpleTextAt(character, graph_x + ((max_width - cur_width) / 2), start_height + text_height * index)
    end
  end
end

#draw_y_label(dc, value, index) ⇒ Object

This function is used to draw the y labels.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 145

def draw_y_label(dc, value, index)
  left_value = value
  right_value = value
  left_text = @left_text[index]
  right_text = @right_text[index]

  y = nil
  if left_text
    metrics = Cosmos.getFontMetrics(@font)
    text_width = metrics.width(left_text)
    x = @graph_left_x
    y = scale_value_to_graph_y(left_value, :LEFT)
    if (y < @graph_top_y)
      y = @graph_top_y
    elsif (y > @graph_bottom_y)
      y = @graph_bottom_y
    end
    dc.addLineColor(x - LABEL_TICK_SIZE + 1, y, x, y)

    # Only display the label if we have room. This only affects the top
    # side of the graph since that's where new grid lines appear.
    if (y == @graph_top_y) || ((y - @font_size) > @graph_top_y)
      dc.addSimpleTextAt(left_text,
                         x - text_width - GRAPH_SPACER,
                         y + (@font_size / 2))
    end
  end

  if right_text
    x = @graph_right_x
    y = scale_value_to_graph_y(right_value, :RIGHT) unless y
    if (y < @graph_top_y)
      y = @graph_top_y
    elsif (y > @graph_bottom_y)
      y = @graph_bottom_y
    end
    dc.addLineColor(x, y, x + LABEL_TICK_SIZE, y)

    # Only display the label if we have room. This only affects the top
    # side of the graph since that's where new grid lines appear.
    if (y == @graph_top_y) || ((y - @font_size) > @graph_top_y)
      dc.addSimpleTextAt(right_text,
                         x + 2 * GRAPH_SPACER,
                         y + (@font_size / 2))
    end
  end
  return y
end

#get_legend_positionObject

Calculate the legend x, y, width and height



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 347

def get_legend_position
  metrics = Cosmos.getFontMetrics(@font)
  legend_width = 0
  @lines.legend.each do |legend_text, color, axis|
    text_width   = metrics.width(legend_text)
    legend_width = text_width if text_width > legend_width
  end
  legend_width += (GRAPH_SPACER * 2)
  legend_width *= 2 if @lines.axes == :BOTH

  if @legend_position == :right
    legend_height = 0
    if @show_legend && !@lines.empty?
      text_y = (self.height/2) - 1 - (GRAPH_SPACER * 2)
      text_height = metrics.height
      if @lines.axes == :BOTH
        left_count = 0
        right_count = 0
        @lines.legend.each do |text, color, axis|
          if axis == :LEFT
            left_count += 1
          else
            right_count += 1
          end
        end
        if left_count < right_count
          text_y += text_height * (right_count/2)
        else
          text_y += text_height * (left_count/2)
        end
      else
        text_y += text_height * (@lines.size/2)
      end
      legend_height = self.height - text_y - GRAPH_SPACER
    end

    legend_graph_x = self.width - legend_width - GRAPH_SPACER*3

    char_width   = metrics.width('W')
    legend_graph_x -= (char_width + GRAPH_SPACER) if @right_y_axis_title

    text_x = legend_graph_x + GRAPH_SPACER

  else # @legend_position == :bottom or default
    legend_graph_x = (self.width - legend_width) / 2

    text_x = legend_graph_x + GRAPH_SPACER
    text_y = self.height - metrics.height
  end # @legend_position
  return [text_x, text_y, legend_width, metrics.height]
end

#graphObject

Draws the graph



406
407
408
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 406

def graph
  method_missing(:update)
end

#leaveEvent(event) ⇒ Object

Handler for leaveEvent



288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 288

def leaveEvent(event)
  # Note that the mouse is no longer in the window
  @mouse_in_window = false

  # Clear cursor line
  @cursor_line_x = nil

  # Redraw the graph (to remove popups and cursor line)
  graph()

  # Call mouse_leave_callback so that cursor lines can be removed from
  # other line graphs if needed
  @mouse_leave_callback.call(self) if @mouse_leave_callback
end

#manual_scale_x(x_min, x_max, redraw_now = true) ⇒ Object

Start manual scaling of x axis



429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 429

def manual_scale_x(x_min, x_max, redraw_now = true)
  if x_min <= x_max
    @x_max = x_max
    @x_min = x_min
    @x_max_label = nil
    @x_min_label = nil
    @x_auto_scale = false
  else
    Kernel.raise ArgumentError, "GraphView Manual X Max must be greater than X Min"
  end
  @redraw_needed = true
  graph() if redraw_now
end

#manual_scale_y(y_min, y_max, axis) ⇒ Object

Start manual scaling of y axis



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 444

def manual_scale_y(y_min, y_max, axis)
  if y_min < y_max
    if axis == :LEFT
      @left_y_auto_scale = false
      @left_y_max        = y_max
      @left_y_min        = y_min
    else
      @right_y_auto_scale = false
      @right_y_max        = y_max
      @right_y_min        = y_min
    end
  else
    Kernel.raise ArgumentError, "GraphView Manual Y Max must be greater than Y Min"
  end
  @redraw_needed = true
  graph()
end

#mouseMoveEvent(event) ⇒ Object

Handler for mouseMoveEvent



304
305
306
307
308
309
310
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 304

def mouseMoveEvent(event)
  # Note that the mouse is in the window
  @mouse_in_window = true

  # Redraw the graph (to update popups and cursor line)
  graph()
end

#mousePressEvent(event) ⇒ Object

Handler for mousePressEvent



313
314
315
316
317
318
319
320
321
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 313

def mousePressEvent(event)
  # Note that the left button is pressed
  @left_button_pressed = true

  @mouse_left_button_press_callback.call(self) if @mouse_left_button_press_callback

  # Redraw the graph (to update popups)
  graph()
end

#mouseReleaseEvent(event) ⇒ Object

def mousePressEvent



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 323

def mouseReleaseEvent(event)
  left_button_release_time = Time.now

  if @error and ((left_button_release_time - @previous_left_button_release_time) < DOUBLE_CLICK_SECONDS)
    @pre_error_callback.call(self) if @pre_error_callback
    if @error.is_a? FatalError
      ExceptionDialog.new(self, @error, 'LineGraph', false, false) # don't log it, it is known
    else
      ExceptionDialog.new(self, @error, 'LineGraph', false, true) # log the error
    end
    @post_error_callback.call(self) if @post_error_callback
    @error = nil
  end

  # Note that the left button is not pressed
  @left_button_pressed = false

  # Redraw the graph (to update popups)
  graph()

  # Update Previous left button release time
  @previous_left_button_release_time = left_button_release_time
end

#paintEvent(event) ⇒ Object

Handler for paintEvent



348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 348

def paintEvent(event)
  return if @painter
  @painter = Qt::Painter.new(self)
  @painter.setFont(@font)
  # Seems like on initialization sometimes we get some weird bad conditions so check for them
  if @painter.isActive and @painter.paintEngine
    draw_graph_into_back_buffer() if @redraw_needed
    draw_graph_to_screen()
  end
  @painter.dispose
  @painter = nil
end

#remote_draw_cursor_line_at_x(x, left_button_pressed) ⇒ Object

Set the cursor position remotely



463
464
465
466
467
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 463

def remote_draw_cursor_line_at_x(x, left_button_pressed)
  @cursor_line_x = x
  @left_button_pressed = left_button_pressed
  graph()
end

#resizeEvent(event) ⇒ Object

Handler for ResizeEvent



362
363
364
365
366
367
368
369
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 362

def resizeEvent(event)
  # Update internal buffer to new graph size
  update_graph_size()

  # Force a complete redraw of the graph to handle the new size
  @redraw_needed = true
  graph()
end

#scale_graphObject

Determine the scale of the graph, either manually or automatically



110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 110

def scale_graph
  if @x_auto_scale
    @x_min, @x_max, @x_min_label, @x_max_label = @lines.x_min_max_labels
  end
  scale = @manual_y_grid_line_scale
  if @left_y_auto_scale
    @left_y_min, @left_y_max = auto_scale_y_axis(@lines.left_y_value_range, scale)
  end
  if @right_y_auto_scale
    # scale is nil if there are no left lines to allow for autoscalling
    scale = nil unless @lines.left_y_axis?
    @right_y_min, @right_y_max = auto_scale_y_axis(@lines.right_y_value_range, scale)
  end # def scale_graph
end

#scale_graph_to_value_x(x) ⇒ Object

This function converts an x coordinate on the graph to an x value



421
422
423
424
425
426
427
428
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 421

def scale_graph_to_value_x(x)
  if (@x_min - @x_max) != 0
    x_scaled = ((x - @graph_left_x).to_f / @x_scale) + @x_min
  else
    x_scaled = 0
  end
  return x_scaled
end

#scale_left_to_right_y(left_y) ⇒ Object

This function converts a left axis y value to a right axis y value



437
438
439
440
441
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 437

def scale_left_to_right_y(left_y)
  slope  = (@right_y_max - @right_y_min) / (@left_y_max - @left_y_min)
  offset = @right_y_max - (@left_y_max * slope)
  return ((left_y * slope) + offset)
end

#scale_value_to_graph_x(x) ⇒ Object

This function converts an x value to an x coordinate on the graph



283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'ext/cosmos/ext/line_graph/line_graph.c', line 283

static VALUE scale_value_to_graph_x(VALUE self, VALUE x) {
  long long_graph_left_x = 0;
  double double_x = 0.0;
  double double_x_min = 0.0;
  double double_x_scale = 0.0;

  long_graph_left_x = FIX2INT(rb_ivar_get(self, id_ivar_graph_left_x));
  double_x = RFLOAT_VALUE(rb_funcall(x, id_method_to_f, 0));
  double_x_min = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_x_min), id_method_to_f, 0));
  double_x_scale = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_x_scale), id_method_to_f, 0));

  return INT2FIX(scale_value_to_graph_x_internal(double_x, double_x_min, double_x_scale, long_graph_left_x));
}

#scale_value_to_graph_y(*args) ⇒ Object

This function converts a y value to a y coordinate on the graph



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'ext/cosmos/ext/line_graph/line_graph.c', line 236

static VALUE scale_value_to_graph_y(int argc, VALUE* argv, VALUE self) {
  volatile VALUE y = Qnil;
  ID id_axis = 0;
  long long_graph_top_y = 0;
  double double_y = 0.0;
  double double_y_max = 0.0;
  double double_y_scale = 0.0;

  switch (argc) {
    case 1:
      y = argv[0];
      id_axis = id_LEFT;
      break;
    case 2:
      y = argv[0];
      id_axis = SYM2ID(argv[1]);
      break;
    default:
      /* Invalid number of arguments given */
      rb_raise(rb_eArgError, "wrong number of arguments (%d for 1..2)", argc);
      break;
  };

  long_graph_top_y = FIX2INT(rb_ivar_get(self, id_ivar_graph_top_y));
  double_y = RFLOAT_VALUE(rb_funcall(y, id_method_to_f, 0));

  if (id_axis == id_LEFT) {
    double_y_max = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_left_y_max),   id_method_to_f, 0));
    double_y_scale = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_left_y_scale), id_method_to_f, 0));
  } else /* id_axis == id_RIGHT */ {
    double_y_max = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_right_y_max),   id_method_to_f, 0));
    double_y_scale = RFLOAT_VALUE(rb_funcall(rb_ivar_get(self, id_ivar_right_y_scale), id_method_to_f, 0));
  }

  return INT2FIX(scale_value_to_graph_y_internal(double_y, double_y_max, double_y_scale, long_graph_top_y));
}

#update_graph_sizeObject

Handles window resizes and enforces a minimum size



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 478

def update_graph_size
  unless @in_update_graph_size
    @in_update_graph_size = true

    # Get new width and height
    new_width  = self.width
    new_height = self.height

    # Enforce a minimum width and height
    if (new_width < @minimum_width)
      new_width = @minimum_width
    end
    if (new_height < @minimum_height)
      new_height = @minimum_height
    end

    # Make sure everything is the correct size
    self.resize(new_width, new_height)

    @in_update_graph_size = false
  end
end