cosmos 3.7.1 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Manifest.txt +1 -0
  3. data/bin/xtce_converter +83 -0
  4. data/cosmos.gemspec +2 -0
  5. data/data/crc.txt +279 -279
  6. data/demo/config/data/crc.txt +171 -171
  7. data/demo/config/targets/INST/cmd_tlm/inst_tlm.txt +1 -1
  8. data/demo/config/targets/INST/lib/sim_inst.rb +17 -1
  9. data/demo/config/targets/INST/screens/hs.txt +4 -1
  10. data/demo/config/targets/INST/screens/limits.txt +13 -13
  11. data/install/config/data/crc.txt +113 -113
  12. data/lib/cosmos/conversions/conversion.rb +6 -0
  13. data/lib/cosmos/conversions/generic_conversion.rb +12 -0
  14. data/lib/cosmos/conversions/new_packet_log_conversion.rb +6 -0
  15. data/lib/cosmos/conversions/polynomial_conversion.rb +6 -0
  16. data/lib/cosmos/conversions/processor_conversion.rb +10 -0
  17. data/lib/cosmos/conversions/segmented_polynomial_conversion.rb +10 -0
  18. data/lib/cosmos/conversions/unix_time_conversion.rb +6 -0
  19. data/lib/cosmos/core_ext/string.rb +18 -5
  20. data/lib/cosmos/gui/line_graph/lines.rb +13 -12
  21. data/lib/cosmos/packets/limits_response.rb +4 -0
  22. data/lib/cosmos/packets/packet.rb +59 -8
  23. data/lib/cosmos/packets/packet_config.rb +679 -6
  24. data/lib/cosmos/packets/packet_item.rb +230 -0
  25. data/lib/cosmos/packets/parsers/packet_parser.rb +30 -22
  26. data/lib/cosmos/processors/new_packet_log_processor.rb +5 -0
  27. data/lib/cosmos/processors/processor.rb +5 -0
  28. data/lib/cosmos/processors/statistics_processor.rb +5 -0
  29. data/lib/cosmos/processors/watermark_processor.rb +5 -0
  30. data/lib/cosmos/script/extract.rb +1 -1
  31. data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +7 -2
  32. data/lib/cosmos/tools/tlm_grapher/data_objects/housekeeping_data_object.rb +3 -1
  33. data/lib/cosmos/tools/tlm_grapher/data_objects/xy_data_object.rb +3 -1
  34. data/lib/cosmos/tools/tlm_viewer/widgets/limits_widget.rb +10 -0
  35. data/lib/cosmos/tools/tlm_viewer/widgets/limitsbar_widget.rb +14 -6
  36. data/lib/cosmos/tools/tlm_viewer/widgets/limitscolumn_widget.rb +14 -6
  37. data/lib/cosmos/tools/tlm_viewer/widgets/rangebar_widget.rb +14 -6
  38. data/lib/cosmos/tools/tlm_viewer/widgets/rangecolumn_widget.rb +14 -6
  39. data/lib/cosmos/utilities/simulated_target.rb +1 -0
  40. data/lib/cosmos/version.rb +5 -5
  41. metadata +18 -2
@@ -296,8 +296,238 @@ module Cosmos
296
296
  hash
297
297
  end
298
298
 
299
+ def to_config(cmd_or_tlm, default_endianness)
300
+ config = ''
301
+ if cmd_or_tlm == :TELEMETRY
302
+ if self.array_size
303
+ config << " ARRAY_ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.array_size} \"#{self.description.to_s.gsub("\"", "'")}\""
304
+ elsif self.id_value
305
+ config << " ID_ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.id_value} \"#{self.description.to_s.gsub("\"", "'")}\""
306
+ else
307
+ config << " ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} \"#{self.description.to_s.gsub("\"", "'")}\""
308
+ end
309
+ else # :COMMAND
310
+ if self.array_size
311
+ config << " ARRAY_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.array_size} \"#{self.description.to_s.gsub("\"", "'")}\""
312
+ elsif self.id_value
313
+ if self.data_type == :BLOCK or self.data_type == :STRING
314
+ config << " ID_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} \"#{self.default}\" \"#{self.description.to_s.gsub("\"", "'")}\""
315
+ else
316
+ config << " ID_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.range.first} #{self.range.last} #{self.default} \"#{self.description.to_s.gsub("\"", "'")}\""
317
+ end
318
+ else
319
+ if self.data_type == :BLOCK or self.data_type == :STRING
320
+ config << " PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} \"#{self.default}\" \"#{self.description.to_s.gsub("\"", "'")}\""
321
+ else
322
+ config << " PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.range.first} #{self.range.last} #{self.default} \"#{self.description.to_s.gsub("\"", "'")}\""
323
+ end
324
+ end
325
+ end
326
+ config << " #{self.endianness}" if self.endianness != default_endianness
327
+ config << "\n"
328
+
329
+ config << " REQUIRED\n" if self.required
330
+ config << " FORMAT_STRING #{self.format_string.to_s.quote_if_necessary}\n" if self.format_string
331
+ config << " UNITS #{self.units_full.to_s.quote_if_necessary} #{self.units.to_s.quote_if_necessary}\n" if self.units
332
+ config << " OVERFLOW #{self.overflow}\n" if self.overflow != :ERROR
333
+
334
+ if @states
335
+ @states.each do |state_name, state_value|
336
+ config << " STATE #{state_name.to_s.quote_if_necessary} #{state_value.to_s.quote_if_necessary}"
337
+ if @hazardous and @hazardous[state_name]
338
+ config << " HAZARDOUS #{@hazardous[state_name].to_s.quote_if_necessary}"
339
+ end
340
+ if @state_colors and @state_colors[state_name]
341
+ config << " #{@state_colors[state_name]}"
342
+ end
343
+ config << "\n"
344
+ end
345
+ end
346
+
347
+ config << self.read_conversion.to_config(:READ) if self.read_conversion
348
+ config << self.write_conversion.to_config(:WRITE) if self.write_conversion
349
+
350
+ if self.limits
351
+ if self.limits.values
352
+ self.limits.values.each do |limits_set, limits_values|
353
+ config << " LIMITS #{limits_set} #{self.limits.persistence_setting} #{self.limits.enabled ? 'ENABLED' : 'DISABLED'} #{limits_values[0]} #{limits_values[1]} #{limits_values[2]} #{limits_values[3]} #{limits_values[4]} #{limits_values[5]}\n"
354
+ end
355
+ end
356
+ config << self.limits.response.to_config if self.limits.response
357
+ end
358
+
359
+ if @meta
360
+ @meta.each do |key, values|
361
+ config << " META #{key.to_s.quote_if_necessary} #{values.map {|a| a.to_s.quote_if_necessary}.join(" ")}\n"
362
+ end
363
+ end
364
+
365
+ config
366
+ end
367
+
368
+ def to_xtce_type(param_or_arg, xml)
369
+ # TODO: Arrays, Spline Conversions
370
+ case self.data_type
371
+ when :INT, :UINT
372
+ attrs = { :name => (self.name + '_Type') }
373
+ attrs[:initialValue] = self.default if self.default and !self.array_size
374
+ attrs[:shortDescription] = self.description if self.description
375
+ if @states and self.default and @states.key(self.default)
376
+ attrs[:initialValue] = @states.key(self.default) and !self.array_size
377
+ end
378
+ if self.data_type == :INT
379
+ signed = 'true'
380
+ encoding = 'twosCompliment'
381
+ else
382
+ signed = 'false'
383
+ encoding = 'unsigned'
384
+ end
385
+ if @states
386
+ xml['xtce'].send('Enumerated' + param_or_arg + 'Type', attrs) do
387
+ to_xtce_units(xml)
388
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => self.bit_size, :encoding => encoding)
389
+ xml['xtce'].EnumerationList do
390
+ @states.each do |state_name, state_value|
391
+ xml['xtce'].Enumeration(:value => state_value, :label => state_name)
392
+ end
393
+ end
394
+ end
395
+ else
396
+ if (self.read_conversion and self.read_conversion.class == PolynomialConversion) or (self.write_conversion and self.write_conversion.class == PolynomialConversion)
397
+ type_string = 'Float' + param_or_arg + 'Type'
398
+ else
399
+ type_string = 'Integer' + param_or_arg + 'Type'
400
+ attrs[:signed] = signed
401
+ end
402
+ xml['xtce'].send(type_string, attrs) do
403
+ to_xtce_units(xml)
404
+ if (self.read_conversion and self.read_conversion.class == PolynomialConversion) or (self.write_conversion and self.write_conversion.class == PolynomialConversion)
405
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => self.bit_size, :encoding => encoding) do
406
+ to_xtce_conversion(xml)
407
+ end
408
+ else
409
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => self.bit_size, :encoding => encoding)
410
+ end
411
+ if self.limits
412
+ if self.limits.values
413
+ self.limits.values.each do |limits_set, limits_values|
414
+ if limits_set == :DEFAULT
415
+ xml['xtce'].DefaultAlarm do
416
+ xml['xtce'].StaticAlarmRanges do
417
+ xml['xtce'].WarningRange(:minInclusive => limits_values[1], :maxInclusive => limits_values[2])
418
+ xml['xtce'].CriticalRange(:minInclusive => limits_values[0], :maxInclusive => limits_values[3])
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
424
+ end
425
+ if self.range
426
+ xml['xtce'].ValidRange(:minInclusive => self.range.first, :maxInclusive => self.range.last)
427
+ end
428
+ end # Type
429
+ end # if @states
430
+ when :FLOAT
431
+ attrs = { :name => (self.name + '_Type'), :sizeInBits => self.bit_size }
432
+ attrs[:initialValue] = self.default if self.default and !self.array_size
433
+ attrs[:shortDescription] = self.description if self.description
434
+ xml['xtce'].send('Float' + param_or_arg + 'Type', attrs) do
435
+ to_xtce_units(xml)
436
+ if (self.read_conversion and self.read_conversion.class == PolynomialConversion) or (self.write_conversion and self.write_conversion.class == PolynomialConversion)
437
+ xml['xtce'].FloatDataEncoding(:sizeInBits => self.bit_size, :encoding => 'IEEE754_1985') do
438
+ to_xtce_conversion(xml)
439
+ end
440
+ else
441
+ xml['xtce'].FloatDataEncoding(:sizeInBits => self.bit_size, :encoding => 'IEEE754_1985')
442
+ end
443
+
444
+ if self.limits
445
+ if self.limits.values
446
+ self.limits.values.each do |limits_set, limits_values|
447
+ if limits_set == :DEFAULT
448
+ xml['xtce'].DefaultAlarm do
449
+ xml['xtce'].StaticAlarmRanges do
450
+ xml['xtce'].WarningRange(:minInclusive => limits_values[1], :maxInclusive => limits_values[2])
451
+ xml['xtce'].CriticalRange(:minInclusive => limits_values[0], :maxInclusive => limits_values[3])
452
+ end
453
+ end
454
+ end
455
+ end
456
+ end
457
+ end
458
+
459
+ if self.range
460
+ xml['xtce'].ValidRange(:minInclusive => self.range.first, :maxInclusive => self.range.last)
461
+ end
462
+
463
+ end # Type
464
+ when :STRING
465
+ # TODO: COSMOS Variably sized strings are not supported in XTCE
466
+ attrs = { :name => (self.name + '_Type'), :characterWidth => 8 }
467
+ attrs[:initialValue] = self.default if self.default and !self.array_size
468
+ attrs[:shortDescription] = self.description if self.description
469
+ xml['xtce'].send('String' + param_or_arg + 'Type', attrs) do
470
+ to_xtce_units(xml)
471
+ xml['xtce'].StringDataEncoding(:encoding => 'UTF-8') do
472
+ xml['xtce'].SizeInBits do
473
+ xml['xtce'].Fixed do
474
+ xml['xtce'].FixedValue(self.bit_size.to_s)
475
+ end
476
+ end
477
+ end
478
+ end
479
+ when :BLOCK
480
+ # TODO: COSMOS Variably sized blocks are not supported in XTCE
481
+ # TODO: Write string to hex method to support initial value
482
+ attrs = { :name => (self.name + '_Type') }
483
+ attrs[:shortDescription] = self.description if self.description
484
+ #attrs[:initialValue] = self.default if self.default and !self.array_size
485
+ xml['xtce'].send('Binary' + param_or_arg + 'Type', attrs) do
486
+ to_xtce_units(xml)
487
+ xml['xtce'].BinaryDataEncoding do
488
+ xml['xtce'].SizeInBits do
489
+ xml['xtce'].FixedValue(self.bit_size.to_s)
490
+ end
491
+ end
492
+ end
493
+ when :DERIVED
494
+ raise "DERIVED data type not supported in XTCE"
495
+ end
496
+ end
497
+
498
+ def to_xtce_item(param_or_arg, xml)
499
+ xml['xtce'].send(param_or_arg, :name => self.name, "#{param_or_arg.downcase}TypeRef" => self.name + '_Type')
500
+ end
501
+
299
502
  protected
300
503
 
504
+ def to_xtce_units(xml)
505
+ if self.units
506
+ xml['xtce'].UnitSet do
507
+ xml['xtce'].Unit(self.units, :description => self.units_full)
508
+ end
509
+ else
510
+ xml['xtce'].UnitSet
511
+ end
512
+ end
513
+
514
+ def to_xtce_conversion(xml)
515
+ if self.read_conversion
516
+ conversion = self.read_conversion
517
+ else
518
+ conversion = self.write_conversion
519
+ end
520
+ if conversion and conversion.class == PolynomialConversion
521
+ xml['xtce'].DefaultCalibrator do
522
+ xml['xtce'].PolynomialCalibrator do
523
+ conversion.coeffs.each_with_index do |coeff, index|
524
+ xml['xtce'].Term(:coefficient => coeff, :exponent => index)
525
+ end
526
+ end
527
+ end
528
+ end
529
+ end
530
+
301
531
  # Convert a value into the given data type
302
532
  def convert(value, data_type)
303
533
  case data_type
@@ -68,33 +68,15 @@ module Cosmos
68
68
 
69
69
  def create_command(target_name, commands, warnings)
70
70
  packet = create_packet(target_name)
71
- warning = check_for_duplicate('Command', commands, packet)
72
- warnings << warning if warning
73
- commands[packet.target_name] ||= {}
74
- packet
71
+ PacketParser.finish_create_command(packet, commands, warnings)
75
72
  end
76
73
 
77
74
  def create_telemetry(target_name, telemetry, latest_data, warnings)
78
75
  packet = create_packet(target_name)
79
- warning = check_for_duplicate('Telemetry', telemetry, packet)
80
- warnings << warning if warning
81
-
82
- # Add received time packet items
83
- item = packet.define_item('RECEIVED_TIMESECONDS', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, '%0.6f', ReceivedTimeSecondsConversion.new)
84
- item.description = 'COSMOS Received Time (UTC, Floating point, Unix epoch)'
85
- item = packet.define_item('RECEIVED_TIMEFORMATTED', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, nil, ReceivedTimeFormattedConversion.new)
86
- item.description = 'COSMOS Received Time (Local time zone, Formatted string)'
87
- item = packet.define_item('RECEIVED_COUNT', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, nil, ReceivedCountConversion.new)
88
- item.description = 'COSMOS packet received count'
89
-
90
- unless telemetry[packet.target_name]
91
- telemetry[packet.target_name] = {}
92
- latest_data[packet.target_name] = {}
93
- end
94
- packet
76
+ PacketParser.finish_create_telemetry(packet, telemetry, latest_data, warnings)
95
77
  end
96
78
 
97
- private
79
+ #private
98
80
 
99
81
  def create_packet(target_name)
100
82
  params = @parser.parameters
@@ -108,7 +90,7 @@ module Cosmos
108
90
  Packet.new(target_name, packet_name, endianness, description)
109
91
  end
110
92
 
111
- def check_for_duplicate(type, list, packet)
93
+ def self.check_for_duplicate(type, list, packet)
112
94
  msg = nil
113
95
  if list[packet.target_name]
114
96
  if list[packet.target_name][packet.packet_name]
@@ -119,5 +101,31 @@ module Cosmos
119
101
  msg
120
102
  end
121
103
 
104
+ def self.finish_create_command(packet, commands, warnings)
105
+ warning = PacketParser.check_for_duplicate('Command', commands, packet)
106
+ warnings << warning if warning
107
+ commands[packet.target_name] ||= {}
108
+ packet
109
+ end
110
+
111
+ def self.finish_create_telemetry(packet, telemetry, latest_data, warnings)
112
+ warning = PacketParser.check_for_duplicate('Telemetry', telemetry, packet)
113
+ warnings << warning if warning
114
+
115
+ # Add received time packet items
116
+ item = packet.define_item('RECEIVED_TIMESECONDS', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, '%0.6f', ReceivedTimeSecondsConversion.new)
117
+ item.description = 'COSMOS Received Time (UTC, Floating point, Unix epoch)'
118
+ item = packet.define_item('RECEIVED_TIMEFORMATTED', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, nil, ReceivedTimeFormattedConversion.new)
119
+ item.description = 'COSMOS Received Time (Local time zone, Formatted string)'
120
+ item = packet.define_item('RECEIVED_COUNT', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, nil, ReceivedCountConversion.new)
121
+ item.description = 'COSMOS packet received count'
122
+
123
+ unless telemetry[packet.target_name]
124
+ telemetry[packet.target_name] = {}
125
+ latest_data[packet.target_name] = {}
126
+ end
127
+ packet
128
+ end
129
+
122
130
  end
123
131
  end # module Cosmos
@@ -29,6 +29,11 @@ module Cosmos
29
29
  end
30
30
  end
31
31
 
32
+ # Convert to configuration file string
33
+ def to_config
34
+ " PROCESSOR #{@name} #{self.class.name.to_s.class_name_to_filename} #{@packet_log_writer_name}\n"
35
+ end
36
+
32
37
  end # class NewPacketLogProcessor
33
38
 
34
39
  end # module Cosmos
@@ -66,6 +66,11 @@ module Cosmos
66
66
  end
67
67
  alias dup clone
68
68
 
69
+ # Convert to configuration file string
70
+ def to_config
71
+ " PROCESSOR #{@name} #{self.class.name.to_s.class_name_to_filename} #{@value_type}\n"
72
+ end
73
+
69
74
  end # class Processor
70
75
 
71
76
  end # module Cosmos
@@ -60,6 +60,11 @@ module Cosmos
60
60
  end
61
61
  alias dup clone
62
62
 
63
+ # Convert to configuration file string
64
+ def to_config
65
+ " PROCESSOR #{@name} #{self.class.name.to_s.class_name_to_filename} #{@item_name} #{@samples_to_average} #{@value_type}\n"
66
+ end
67
+
63
68
  end # class StatisticsProcessor
64
69
 
65
70
  end # module Cosmos
@@ -39,6 +39,11 @@ module Cosmos
39
39
  @results[:LOW_WATER] = nil
40
40
  end
41
41
 
42
+ # Convert to configuration file string
43
+ def to_config
44
+ " PROCESSOR #{@name} #{self.class.name.to_s.class_name_to_filename} #{@item_name} #{@value_type}\n"
45
+ end
46
+
42
47
  end # class WatermarkProcessor
43
48
 
44
49
  end # module Cosmos
@@ -25,7 +25,7 @@ module Cosmos
25
25
  end
26
26
 
27
27
  def extract_fields_from_cmd_text(text)
28
- split_string = text.split(/\s*with\s*/i, 2)
28
+ split_string = text.split(/\s+with\s+/i, 2)
29
29
  raise "ERROR: 'with' must be followed by parameters : #{text}" if split_string.length == 1 and text =~ /\s*with\s*/i
30
30
 
31
31
  # Extract target_name and cmd_name
@@ -35,7 +35,11 @@ module Cosmos
35
35
  @tlm_thread = nil
36
36
  @shutdown_tlm_thread = false
37
37
  @mode = :WITH_UNITS
38
- @polling_rate = 1.0
38
+ if options.rate
39
+ @polling_rate = options.rate
40
+ else
41
+ @polling_rate = 1.0
42
+ end
39
43
  @colorblind = false
40
44
 
41
45
  initialize_actions()
@@ -249,7 +253,7 @@ module Cosmos
249
253
  end
250
254
 
251
255
  def file_options
252
- @polling_rate = Qt::InputDialog.getDouble(self, tr("Options"), tr("Polling Rate:"), @polling_rate, 0, 1000, 1, nil)
256
+ @polling_rate = Qt::InputDialog.getDouble(self, tr("Options"), tr("Polling Rate (sec):"), @polling_rate, 0, 1000, 1, nil)
253
257
  end
254
258
 
255
259
  def update_all
@@ -531,6 +535,7 @@ module Cosmos
531
535
  end
532
536
  options.packet = split
533
537
  end
538
+ option_parser.on("-r", "--rate PERIOD", "Set the polling rate to PERIOD (unit seconds)") { |arg| options.rate = Float(arg) }
534
539
  end
535
540
 
536
541
  super(option_parser, options)
@@ -163,7 +163,9 @@ module Cosmos
163
163
  # Bail on the values if they are NaN or nil as we can't graph them
164
164
  return if x_value.nil? || y_value.nil? ||
165
165
  (x_value.respond_to?(:nan?) && x_value.nan?) ||
166
- (y_value.respond_to?(:nan?) && y_value.nan?)
166
+ (y_value.respond_to?(:nan?) && y_value.nan?) ||
167
+ (x_value.respond_to?(:infinite?) && x_value.infinite?) ||
168
+ (y_value.respond_to?(:infinite?) && y_value.infinite?)
167
169
  @formatted_x_values << packet.read(@formatted_time_item_name) if @formatted_time_item_name
168
170
 
169
171
  upper_index = nil
@@ -195,7 +195,9 @@ module Cosmos
195
195
  # Bail on the values if they are NaN or nil as we can't graph them
196
196
  return if x_value.nil? || y_value.nil? ||
197
197
  (x_value.respond_to?(:nan?) && x_value.nan?) ||
198
- (y_value.respond_to?(:nan?) && y_value.nan?)
198
+ (y_value.respond_to?(:nan?) && y_value.nan?) ||
199
+ (x_value.respond_to?(:infinite?) && x_value.infinite?) ||
200
+ (y_value.respond_to?(:infinite?) && y_value.infinite?)
199
201
 
200
202
  time_value = packet.read(@time_item_name) if @time_item_name
201
203
 
@@ -34,6 +34,16 @@ module Cosmos
34
34
  end
35
35
 
36
36
  def value=(data)
37
+ if String === data
38
+ substring = data[0..2]
39
+ if substring == "Inf".freeze
40
+ data = Float::INFINITY
41
+ elsif substring == "-In".freeze
42
+ data = -Float::INFINITY
43
+ elsif substring == "NaN".freeze
44
+ data = Float::NAN
45
+ end
46
+ end
37
47
  @value = data.to_f
38
48
  update()
39
49
  end
@@ -81,12 +81,20 @@ module Cosmos
81
81
  @low_value = red_low - 0.1 * @bar_scale
82
82
  @high_value = red_high + 0.1 * @bar_scale
83
83
 
84
- @line_pos = (@x_pad + (@value - @low_value) / @bar_scale * @bar_width).to_i
85
- if @line_pos < @x_pad
86
- @line_pos = @x_pad
87
- end
88
- if @line_pos > @x_pad + @bar_width
89
- @line_pos = @bar_width + @x_pad
84
+ if @value.is_a?(Float) && (@value.infinite? || @value.nan?)
85
+ if @value.infinite? == 1
86
+ @line_pos = @bar_width + @x_pad
87
+ else
88
+ @line_pos = @x_pad
89
+ end
90
+ else
91
+ @line_pos = (@x_pad + (@value - @low_value) / @bar_scale * @bar_width).to_i
92
+ if @line_pos < @x_pad
93
+ @line_pos = @x_pad
94
+ end
95
+ if @line_pos > @x_pad + @bar_width
96
+ @line_pos = @bar_width + @x_pad
97
+ end
90
98
  end
91
99
 
92
100
  dc.addLineColor(@line_pos, @y_pad - 3, @line_pos, @y_pad + @bar_height + 3)