SettRaziel/ruby_visualization

View on GitHub
lib/math/time_line.rb

Summary

Maintainability
A
0 mins
Test Coverage
require_relative "interpolation"

# This class collects all data values of the z dimension of a {DataSeries} for
# a given pair of coordinates (x,y). For a given resolution size the values
# are assigned to the nearest value of value_boundaries to be drawn in a
# terminal or window.
class Timeline
  # @return [Hash] the occurence of a boundary value in the z dimension as the
  #  result of being the nearest index for a collected value at position z
  attr_reader :mapped_values

  # initialization
  # @param [VisMetaData] meta_data the meta information of the regarded data series
  # @param [DataSeries] data_series the data series which should be used
  # @param [Hash] parameters a hash containing the required parameter
  def initialize(meta_data, data_series, parameters)
    check_and_set_ysize(parameters[:y_size])
    check_dataset_dimension(meta_data)

    values = collect_values(meta_data, data_series,
                            parameters[:x], parameters[:y])

    determine_extrema(values) # extrema of time values

    determine_value_boundaries # ordinate values for each line

    create_output(determine_nearest_index(values))
  end

  private
  # @return [Array] the values for each output line
  attr :value_bondaries
  # @return [Hash] the extreme values of the timeline
  attr :extrema
  # @return [Integer] resolution of the value scale
  attr :lines

  # method to check and set the number of values for the y-dimension
  # constraint: at least 5 values in y
  # @param [Fixnum] y_size number of values in y
  # @raise [RangeError] if the number of y values is less than 5
  def check_and_set_ysize(y_size)
    if (y_size < 5)
      raise RangeError, " Error : invalid y_value of timeline (min.: 5)".red
    end
    @lines = y_size
  end

  # method to check for correct dataset dimensions
  # @param [VisMetaData] meta_data the required meta data
  # @raise [RangeError] if one of the datasets has an incorrect dimension
  def check_dataset_dimension(meta_data)
    if (!TerminalVis.data_repo.dataset_dimension_correct?(meta_data))
      raise RangeError, " Error: dimension of at least one dataset is incorrect".red
    end
  end

  # this method collects all data values d(x,y) in z
  # @param [VisMetaData] meta_data the meta information of the regarded data series
  # @param [DataSeries] data_series the data series which should be used
  # @param [Float] x x-coordinate of the regarded point
  # @param [Float] y y-coordinate of the regarded point
  # @return [Array] the collected values d(x,y)[z]
  def collect_values(meta_data, data_series, x, y)
    values = Array.new()

    data_series.series.each { |data_set|
      values << TerminalVis::Interpolation::BilinearInterpolation.
                               bilinear_interpolation(meta_data,data_set, x, y)
    }

    return values
  end

  # this method determines the extreme values of the collected data d(x,y)[z]
  # @param [Array] values the collected values d(x,y)[z]
  def determine_extrema(values)
    @extrema = {
      :maximum => values[0],
      :minimum => values[0]
    }

    values.each { |value|
      @extrema[:maximum] = value if (value > @extrema[:maximum])
      @extrema[:minimum] = value if (value < @extrema[:minimum])
    }
  end

  # this method determines the value boundaries based on the vertical number
  # of lines
  def determine_value_boundaries
    delta = (@extrema[:maximum] - @extrema[:minimum]).abs
    upper_boundary = @extrema[:maximum] + delta / 20.0 # 5 % variance
    lower_boundary = @extrema[:minimum] - delta / 20.0 # 5 % variance

    delta = (upper_boundary - lower_boundary).abs / @lines
    @value_bondaries = [lower_boundary.round(5)]
    while (lower_boundary.round(5) < upper_boundary.round(5))
      lower_boundary += delta
      @value_bondaries << lower_boundary.round(5)
    end
  end

  # this method calculates for every collected value the index of the interval
  # values @value_boundaries with the least distance
  # @param [Array] values the collected values d(x,y)[z]
  # @return [Hash] mapping of (value => index)
  def determine_nearest_index(values)
    mapped_values = Hash.new()

    values.each { |value|
      mapped_values[value] = [get_nearest_index(value), check_extrema(value)]
    }

    return mapped_values
  end

  # helper methode to calculate the nearest index for the collected data values
  # @param [Float] value data value
  # @return [Integer] the nearest index
  def get_nearest_index(value)
    delta = value
    nearest_index = 0

    @value_bondaries.each_index { |index|
      tmp_delta = (@value_bondaries[index] - value).abs
      if (tmp_delta < delta)
        delta = tmp_delta
        nearest_index = index
      end
    }

    return nearest_index
  end

  # this method creates the basic output which can be visualized.
  # @param [Hash] mapped_values mapping of (value => index)
  def create_output(mapped_values)
    output = Hash.new(@lines)

    @value_bondaries.each_index { |index|
      line = Array.new()
      mapped_values.values.each { |value|
        if (value[0] == index)
          line << value[1]
        else
          line << [:miss]
        end
      }
      output[@value_bondaries[index]] = line
    }

    @mapped_values = output
  end

  # method to determine if the value is an extreme value and return the value
  # if it is an extreme value
  # @param [Float] value the considered value
  # @return [Array] an array containing Symbol and the value if it is an
  #  extreme value
  def check_extrema(value)
    return [:minimum, value] if (value == @extrema[:minimum])
    return [:maximum, value] if (value == @extrema[:maximum])
    return [:hit]
  end

end