class AsciiCharts::Chart

Constants

DEFAULT_MAX_Y_VALS
DEFAULT_MIN_Y_VALS
INFINITY
STEPS

Attributes

data[R]
options[R]

Public Class Methods

new(data, options={}) click to toggle source

data is a sorted array of [x, y] pairs

# File lib/ascii_charts/chart.rb, line 12
def initialize(data, options={})
  if (data[0].length == 2)
    @data = data # treat as array of points
  else
    @data = series_to_points(data) # treat as array of series
  end

  @options = options
end

Public Instance Methods

all_ints() click to toggle source
# File lib/ascii_charts/chart.rb, line 158
def all_ints
  if !defined? @all_ints
    scan_data
  end
  @all_ints
end
draw() click to toggle source
# File lib/ascii_charts/chart.rb, line 239
def draw
  lines.join("\n")
end
from_step(val) click to toggle source
# File lib/ascii_charts/chart.rb, line 84
def from_step(val)
  if 0 == val
    [0, 0]
  else
    order = Math.log10(val).floor.to_i
    num = (val / (10 ** order))
    [num, order]
  end
end
lines() click to toggle source
# File lib/ascii_charts/chart.rb, line 235
def lines
  raise "lines must be overridden"
end
max_xval_width() click to toggle source
# File lib/ascii_charts/chart.rb, line 191
def max_xval_width
  if !defined? @max_xval_width
    scan_data
  end
  @max_xval_width
end
max_yval() click to toggle source
# File lib/ascii_charts/chart.rb, line 144
def max_yval
  if !defined? @max_yval
    scan_data
  end
  @max_yval
end
max_yval_width() click to toggle source
# File lib/ascii_charts/chart.rb, line 198
def max_yval_width
  if !defined? @max_yval_width
    scan_y_range
  end
  @max_yval_width
end
min_yval() click to toggle source
# File lib/ascii_charts/chart.rb, line 151
def min_yval
  if !defined? @min_yval
    scan_data
  end
  @min_yval
end
nearest_step(val) click to toggle source
# File lib/ascii_charts/chart.rb, line 103
def nearest_step(val)
  num, order = self.from_step(val)
  self.to_step(2, order) ##@@
end
next_step_down(val) click to toggle source
# File lib/ascii_charts/chart.rb, line 118
def next_step_down(val)
  num, order = self.from_step(val)
  next_index = STEPS.index(num.to_i) - 1
  if -1 == next_index
    STEPS.size - 1
    order -= 1
  end
  self.to_step(STEPS[next_index], order)
end
next_step_up(val) click to toggle source
# File lib/ascii_charts/chart.rb, line 108
def next_step_up(val)
  num, order = self.from_step(val)
  next_index = STEPS.index(num.to_i) + 1
  if STEPS.size == next_index
    next_index = 0
    order += 1
  end
  self.to_step(STEPS[next_index], order)
end
round_value(val) click to toggle source

round to nearest step size, making sure we curtail precision to same order of magnitude as the step size to avoid 0.4 + 0.2 = 0.6000000000000001

# File lib/ascii_charts/chart.rb, line 129
def round_value(val)
  remainder = val % self.step_size
  unprecised = if (remainder * 2) >= self.step_size
                 (val - remainder) + self.step_size
               else
                 val - remainder
               end
  if self.step_size < 1
    precision = -Math.log10(self.step_size).floor
    (unprecised * (10 ** precision)).to_i.to_f / (10 ** precision)
  else
    unprecised
  end
end
rounded_data() click to toggle source
# File lib/ascii_charts/chart.rb, line 34
def rounded_data
  @rounded_data ||= self.data.map do |point|
    point.each_with_index.map do |coord, i|
      if i == 0
        coord
      else
        round_value(coord)
      end
    end
  end
end
scan_data() click to toggle source
# File lib/ascii_charts/chart.rb, line 165
def scan_data
  @max_yval = 0
  @min_yval = 0
  @all_ints = true

  @max_xval_width = 1

  self.data.each do |point|
    if (xw = point[0].to_s.length) > @max_xval_width
      @max_xval_width = xw
    end

    point[1..-1].each do |yval|
      if yval > @max_yval
        @max_yval = yval
      end
      if yval < @min_yval
        @min_yval = yval
      end
      if @all_ints && !yval.is_a?(Integer)
        @all_ints = false
      end
    end
  end
end
scan_y_range() click to toggle source
# File lib/ascii_charts/chart.rb, line 205
def scan_y_range
  @max_yval_width = 1

  self.y_range.each do |yval|
    if (yw = yval.to_s.length) > @max_yval_width
      @max_yval_width = yw
    end
  end
end
series_to_points(arr_of_series) click to toggle source
# File lib/ascii_charts/chart.rb, line 22
def series_to_points(arr_of_series)
  points = []
  (0..(arr_of_series[0].length - 1)).each do |i|
    point = []
    (0..(arr_of_series.length - 1)).each do |series|
      point.push(arr_of_series[series][i])
    end
    points.push(point)
  end
  points
end
step_size() click to toggle source
# File lib/ascii_charts/chart.rb, line 46
def step_size
  if !defined? @step_size
    if self.options[:y_step_size]
      @step_size = self.options[:y_step_size]
    else
      max_y_vals = self.options[:max_y_vals] || DEFAULT_MAX_Y_VALS
      min_y_vals = self.options[:max_y_vals] || DEFAULT_MIN_Y_VALS
      y_span = (self.max_yval - self.min_yval).to_f

      step_size = self.nearest_step( y_span.to_f / (self.data.size + 1) )

      if @all_ints && (step_size < 1)
        step_size = 1
      else
        while (y_span / step_size) < min_y_vals
          candidate_step_size = self.next_step_down(step_size)
          if @all_ints && (candidate_step_size < 1) ## don't go below one
            break
          end
          step_size = candidate_step_size
        end
      end

      #go up if we undershot, or were never over
      while (y_span / step_size) > max_y_vals
        step_size = self.next_step_up(step_size)
      end
      @step_size = step_size
    end
    if !@all_ints && @step_size.is_a?(Integer)
      @step_size = @step_size.to_f
    end
  end
  @step_size
end
to_step(num, order) click to toggle source
# File lib/ascii_charts/chart.rb, line 94
def to_step(num, order)
  s = num * (10 ** order)
  if order < 0
    s.to_f
  else
    s
  end
end
to_string() click to toggle source
# File lib/ascii_charts/chart.rb, line 243
def to_string
  draw
end
y_range() click to toggle source
# File lib/ascii_charts/chart.rb, line 215
def y_range
  if !defined? @y_range
    @y_range = []
    first_y = self.round_value(self.min_yval)
    if first_y > self.min_yval
      first_y = first_y - self.step_size
    end
    last_y = self.round_value(self.max_yval)
    if last_y < self.max_yval
      last_y = last_y + self.step_size
    end
    current_y = first_y
    while current_y <= last_y
      @y_range << current_y
      current_y = self.round_value(current_y + self.step_size) ## to avoid fp arithmetic oddness
    end
  end
  @y_range
end