class TTY::Sparkline

Responsible for drawing sparkline in a terminal

@api public

Constants

BARS
EMPTY
MAX_BUFFER_SIZE
NEWLINE
NON_NUMERIC_CONVERSIONS
SPACE
VERSION

Attributes

cursor[R]

The drawing cursor

@return [TTY::Cursor]

@api public

height[R]

The chart height in terminal lines

@return [Integer]

@api public

left[R]

The left position

@return [Integer]

@api public

max[RW]

The custom maximum value used for scaling bars

@return [Numeric]

@api public

min[RW]

The custom minimum value used for scaling bars

@return [Numeric]

@api public

top[R]

The top position

@return [Integer]

@api public

width[R]

The chart maximum width in terminal columns

@return [Integer]

@api public

Public Class Methods

new(data = [], top: nil, left: nil, height: 1, width: nil, min: nil, max: nil, bars: BARS, buffer_size: MAX_BUFFER_SIZE, non_numeric: :empty) click to toggle source

Create a Sparkline instance

@param [Array<Numeric>] data

the data to chart

@param [Integer] top

the top position

@param [Integer] left

the left position

@param [Integer] height

the height in terminal lines

@param [Integer] width

the maximum width in terminal columns

@param [Numeric] min

the custom minimum value

@param [Numeric] max

the custom maximum value

@param [Array<String>] bars

the bars used for display

@param [Integer] buffer_size

the maximum buffer size

@param [Symbol] non_numeric

the replacement for a non-numeric value

@api public

# File lib/tty/sparkline.rb, line 99
def initialize(data = [], top: nil, left: nil, height: 1, width: nil,
               min: nil, max: nil, bars: BARS, buffer_size: MAX_BUFFER_SIZE,
               non_numeric: :empty)
  check_minmax(min, max) if min && max
  check_non_numeric(non_numeric)

  @data = Array(data).dup
  @cached_data_size = @data.size
  @top = top
  @left = left
  @height = height
  @width = width
  @min = min
  @max = max
  @bars = bars
  @num_of_bars = bars.size
  @buffer_size = buffer_size
  @non_numeric = non_numeric
  @filter = ->(value) { value.is_a?(::Numeric) }
  @cursor = TTY::Cursor
end

Public Instance Methods

<<(*nums)
Alias for: push
append(*nums)
Alias for: push
push(*nums) click to toggle source

Append value(s)

@example

sparkline.push(1, 2, 3, 4)

@example

sparkline << 1 << 2 << 3 << 4

@param [Array<Numeric>] nums

@return [self]

@api public

# File lib/tty/sparkline.rb, line 134
def push(*nums)
  @data.push(*nums)
  @cached_data_size += nums.size

  if (overflow = @cached_data_size - @buffer_size) > 0
    @data.shift(overflow)
    @cached_data_size -= overflow
  end

  self
end
Also aliased as: append, <<
render(min: nil, max: nil) { |value, bar, x, y| ... } click to toggle source

Render data as a sparkline chart

@example

sparkline.render

@param [Integer] min

the minimum value to display

@param [Integer] max

the maximum value to display

@return [String]

the rendered sparkline chart

@api public

# File lib/tty/sparkline.rb, line 171
def render(min: nil, max: nil)
  return EMPTY if @data.empty?

  buffer = []
  calc_min, calc_max = data_minmax(min, max)
  check_minmax(calc_min, calc_max)

  height.times do |y|
    buffer << position(y) if position?
    @data[data_range].each.with_index do |value, x|
      bar_index = clamp_and_scale(value, calc_min, calc_max)
      bar = convert_to_bar(bar_index, height - 1 - y)
      bar = yield(value, bar, x, y) if block_given?
      buffer << bar
    end
    buffer << NEWLINE unless y == height - 1
  end

  buffer.join
end
size() click to toggle source

The number of values

@return [Integer]

@api public

# File lib/tty/sparkline.rb, line 153
def size
  @cached_data_size
end

Private Instance Methods

check_minmax(min, max) click to toggle source

Check maximum isn't less than minimum

@raise [Error]

@api private

# File lib/tty/sparkline.rb, line 214
def check_minmax(min, max)
  return if min <= max

  raise Error, "maximum value cannot be less than minimum"
end
check_non_numeric(type) click to toggle source

Check whether non_numeric has a valid conversion type

@param [Symbol] type

the type of conversion

@raise [Error]

@api private

# File lib/tty/sparkline.rb, line 228
def check_non_numeric(type)
  return if NON_NUMERIC_CONVERSIONS.include?(type)

  raise Error, "unknown non_numeric value: #{type.inspect}"
end
clamp_and_scale(value, min, max) click to toggle source

Clamp value and scale it against height and number of bars

@param [Object] value

the value to clamp and scale

@param [Integer] min

the minimum value

@param [Integer] max

the maximum value

@return [Integer]

@api private

# File lib/tty/sparkline.rb, line 283
def clamp_and_scale(value, min, max)
  return value unless value.is_a?(Numeric)

  clamped_value = value > max ? max : (value < min ? min : value)
  reduced_value = max == min ? clamped_value : clamped_value - min
  reduced_max = max == min ? (max.zero? ? 1 : max) : max - min
  reduced_value * height * (@num_of_bars - 1) / reduced_max
end
convert_non_numeric() click to toggle source

Convert non-numeric value into display string

@return [String]

@api private

# File lib/tty/sparkline.rb, line 319
def convert_non_numeric
  case @non_numeric
  when :empty
    SPACE
  when :ignore
    EMPTY
  when :minimum
    @bars[0]
  end
end
convert_to_bar(bar_index, offset) click to toggle source

Convert an index to a bar representation

@param [Integer] bar_index

the bar index within bars

@param [Integer] offset

the offset from the bottom

@return [String]

the rendered bar

@api private

# File lib/tty/sparkline.rb, line 303
def convert_to_bar(bar_index, offset)
  return convert_non_numeric unless bar_index.is_a?(Numeric)

  if bar_index >= offset * @num_of_bars
    bar_index -= offset * @num_of_bars
    @bars[bar_index >= @num_of_bars ? -1 : bar_index]
  else
    SPACE
  end
end
data_minmax(min, max) click to toggle source

Find the minimum and maximum value in the data

@param [Integer] min

the custom minimum value

@param [Integer] max

the custom maximum value

@return [Array<Numeric, Numeric>]

@api private

# File lib/tty/sparkline.rb, line 204
def data_minmax(min, max)
  calc_min, calc_max = @data.select(&@filter).minmax
  [min || @min || calc_min, max || @max || calc_max]
end
data_range() click to toggle source

Find a range of data values matching width

@return [Range]

@api private

# File lib/tty/sparkline.rb, line 263
def data_range
  start_index = 0
  if width && width < @cached_data_size
    start_index = @cached_data_size - width
  end
  start_index..-1
end
position(offset = 0) click to toggle source

Find a position at which to display this chart

@return [String]

@api private

# File lib/tty/sparkline.rb, line 248
def position(offset = 0)
  if left && top
    cursor.move_to(left, top + offset)
  elsif left
    cursor.column(left)
  elsif top
    cursor.row(top + offset)
  end
end
position?() click to toggle source

Check whether or not to position this chart

@return [Boolean]

@api private

# File lib/tty/sparkline.rb, line 239
def position?
  top || left
end