Class: Cosmos::LineGraph

Inherits:
Qt::Widget
  • Object
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

Overview

Widget which displays a line graph. Graph provides decorations such as titles, labels, grid lines, and a legend. Mouse tracking is provided to allow popups on graph values. Automatic scaling is provided to scale the X and Y axis according to the data values.

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



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
277
# File 'lib/cosmos/gui/line_graph/line_graph.rb', line 122

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.sys

  # 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)



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

def draw_cursor_line_callback
  @draw_cursor_line_callback
end

#horizontal_linesObject (readonly)

Extra horizontal lines



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

def horizontal_lines
  @horizontal_lines
end

#left_y_maxObject (readonly)

Minimm and maximum shown left y value



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

def left_y_max
  @left_y_max
end

#left_y_minObject (readonly)

Minimm and maximum shown left y value



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

def left_y_min
  @left_y_min
end

#mouse_leave_callbackObject

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



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

def mouse_leave_callback
  @mouse_leave_callback
end

#mouse_left_button_press_callbackObject

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



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

def mouse_left_button_press_callback
  @mouse_left_button_press_callback
end

#post_error_callbackObject

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



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

def post_error_callback
  @post_error_callback
end

#pre_error_callbackObject

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



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

def pre_error_callback
  @pre_error_callback
end

#right_y_maxObject (readonly)

Minimum and maximum shown right y value



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

def right_y_max
  @right_y_max
end

#right_y_minObject (readonly)

Minimum and maximum shown right y value



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

def right_y_min
  @right_y_min
end

#x_maxObject (readonly)

Minimum and maximum shown x values



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

def x_max
  @x_max
end

#x_max_labelObject (readonly)

Minimum and maximum shown x value label



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

def x_max_label
  @x_max_label
end

#x_minObject (readonly)

Minimum and maximum shown x values



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

def x_min
  @x_min
end

#x_min_labelObject (readonly)

Minimum and maximum shown x value label



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

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



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

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



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

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


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

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



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
# File 'lib/cosmos/gui/line_graph/line_graph_popups.rb', line 31

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



78
79
80
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
# File 'lib/cosmos/gui/line_graph/line_graph_popups.rb', line 78

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



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

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



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

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



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
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 129

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



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

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



277
278
279
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
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 277

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



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
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 175

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


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
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 391

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



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

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



322
323
324
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
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 322

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



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
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 219

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



103
104
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
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 103

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



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

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

#clear_linesObject

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



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

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



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

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).sys
      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



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

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



17
18
19
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
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 17

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



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

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



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

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



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

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



462
463
464
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 462

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



64
65
66
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 64

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



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
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 24

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



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

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



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

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



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

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



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

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_origin_lines(dc) ⇒ Object

Draws origin lines if they fall on the graph



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

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



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

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



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

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



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

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



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

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.



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
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 211

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



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

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



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

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.



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
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 143

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



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
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/cosmos/gui/line_graph/line_graph_drawing.rb', line 345

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



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

def graph
  method_missing(:update)
end

#leaveEvent(event) ⇒ Object

Handler for leaveEvent



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

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



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

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



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

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



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

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



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

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



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

def mouseReleaseEvent(event)
  left_button_release_time = Time.now.sys

  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



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

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



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

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



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

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



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

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



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

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



434
435
436
437
438
# File 'lib/cosmos/gui/line_graph/line_graph_scaling.rb', line 434

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

#update_graph_sizeObject

Handles window resizes and enforces a minimum size



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

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