unicode-plot-0.0.4/0000755000175100017510000000000013637627123013155 5ustar pravipraviunicode-plot-0.0.4/unicode_plot.gemspec0000644000175100017510000000232213637627123017205 0ustar pravipravilib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "unicode_plot/version" Gem::Specification.new do |spec| spec.name = "unicode_plot" version_components = [ UnicodePlot::Version::MAJOR.to_s, UnicodePlot::Version::MINOR.to_s, UnicodePlot::Version::MICRO.to_s, UnicodePlot::Version::TAG ] spec.version = version_components.compact.join(".") spec.authors = ["mrkn"] spec.email = ["mrkn@mrkn.jp"] spec.summary = %q{Plot your data by Unicode characters} spec.description = %q{Plot your data by Unicode characters} spec.homepage = "https://github.com/red-data-tools/unicode_plot.rb" spec.license = "MIT" spec.files = ["README.md", "Rakefile", "Gemfile", "#{spec.name}.gemspec"] spec.files << "LICENSE.txt" spec.files.concat Dir.glob("lib/**/*.rb") spec.test_files.concat Dir.glob("test/**/*.rb") spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f) } spec.require_paths = ["lib"] spec.add_runtime_dependency "enumerable-statistics", ">= 2.0.1" spec.add_development_dependency "bundler", ">= 1.17" spec.add_development_dependency "rake" spec.add_development_dependency "test-unit" end unicode-plot-0.0.4/Rakefile0000644000175100017510000000035413637627123014624 0ustar pravipravirequire "bundler/gem_helper" base_dir = File.expand_path("..", __FILE__) helper = Bundler::GemHelper.new(base_dir) helper.install spec = helper.gemspec desc "Run test" task :test do ruby("test/run-test.rb") end task default: :test unicode-plot-0.0.4/lib/0000755000175100017510000000000013637627123013723 5ustar pravipraviunicode-plot-0.0.4/lib/unicode_plot/0000755000175100017510000000000013637627123016407 5ustar pravipraviunicode-plot-0.0.4/lib/unicode_plot/lineplot.rb0000644000175100017510000000435713637627123020573 0ustar pravipravirequire 'date' module UnicodePlot class Lineplot < GridPlot end module_function def lineplot(*args, canvas: :braille, color: :auto, name: "", **kw) case args.length when 1 # y only y = Array(args[0]) x = Array(1 .. y.length) when 2 # x and y x = Array(args[0]) y = Array(args[1]) else raise ArgumentError, "wrong number of arguments" end case x[0] when Time, Date if x[0].is_a? Time d = x.map(&:to_f) else origin = Date.new(1, 1, 1) d = x.map {|xi| xi - origin } end plot = lineplot(d, y, canvas: canvas, color: color, name: name, **kw) xmin, xmax = x.minmax plot.annotate!(:bl, xmin.to_s, color: :light_black) plot.annotate!(:br, xmax.to_s, color: :light_black) plot else plot = Lineplot.new(x, y, canvas, **kw) lineplot!(plot, x, y, color: color, name: name) end end module_function def lineplot!(plot, *args, color: :auto, name: "") case args.length when 1 # y only y = Array(args[0]) x = Array(1 .. y.length) when 2 # x and y x = Array(args[0]) y = Array(args[1]) if x.length == 1 && y.length == 1 # intercept and slope intercept = x[0] slope = y[0] xmin = plot.origin_x xmax = plot.origin_x + plot.plot_width ymin = plot.origin_y ymax = plot.origin_y + plot.plot_height x = [xmin, xmax] y = [intercept + xmin*slope, intercept + xmax*slope] end else raise ArgumentError, "wrong number of arguments" end case x[0] when Time, Date if x[0].is_a? Time d = x.map(&:to_f) else origin = Date.new(1, 1, 1) d = x.map {|xi| xi - origin } end lineplot!(plot, d, y, color: color, name: name) else color = color == :auto ? plot.next_color : color plot.annotate!(:r, name.to_s, color: color) unless name.nil? || name == "" plot.lines!(x, y, color) end plot end end unicode-plot-0.0.4/lib/unicode_plot/dot_canvas.rb0000644000175100017510000000116013637627123021053 0ustar pravipravimodule UnicodePlot class DotCanvas < LookupCanvas DOT_SIGNS = [ [ 0b10, 0b01 ].freeze ].freeze DOT_DECODE = [ -' ', # 0b00 -'.', # 0b01 -"'", # 0b10 -':', # 0b11 ].freeze X_PIXEL_PER_CHAR = 1 Y_PIXEL_PER_CHAR = 2 def initialize(width, height, **kw) super(width, height, X_PIXEL_PER_CHAR, Y_PIXEL_PER_CHAR, **kw) end def lookup_encode(x, y) DOT_SIGNS[x][y] end def lookup_decode(code) DOT_DECODE[code] end end end unicode-plot-0.0.4/lib/unicode_plot/lookup_canvas.rb0000644000175100017510000000266613637627123021612 0ustar pravipravimodule UnicodePlot class LookupCanvas < Canvas def initialize(width, height, x_pixel_per_char, y_pixel_per_char, fill_char=0, **kw) super(width, height, width * x_pixel_per_char, height * y_pixel_per_char, fill_char, x_pixel_per_char: x_pixel_per_char, y_pixel_per_char: y_pixel_per_char, **kw) end def pixel!(pixel_x, pixel_y, color) unless 0 <= pixel_x && pixel_x <= pixel_width && 0 <= pixel_y && pixel_y <= pixel_height return color end pixel_x -= 1 unless pixel_x < pixel_width pixel_y -= 1 unless pixel_y < pixel_height tx = pixel_x.fdiv(pixel_width) * width char_x = tx.floor + 1 char_x_off = pixel_x % x_pixel_per_char + 1 char_x += 1 if char_x < tx.round + 1 && char_x_off == 1 char_y = (pixel_y.fdiv(pixel_height) * height).floor + 1 char_y_off = pixel_y % y_pixel_per_char + 1 index = index_at(char_x - 1, char_y - 1) if index @grid[index] |= lookup_encode(char_x_off - 1, char_y_off - 1) @colors[index] |= COLOR_ENCODE[color] end end def print_row(out, row_index) unless 0 <= row_index && row_index < height raise ArgumentError, "row_index out of bounds" end y = row_index (0 ... width).each do |x| print_color(out, color_at(x, y), lookup_decode(char_at(x, y))) end end end end unicode-plot-0.0.4/lib/unicode_plot/densityplot.rb0000644000175100017510000000050013637627123021305 0ustar pravipravimodule UnicodePlot module_function def densityplot(x, y, color: :auto, grid: false, name: "", **kw) plot = GridPlot.new(x, y, :density, grid: grid, **kw) scatterplot!(plot, x, y, color: color, name: name) end module_function def densityplot!(plot, x, y, **kw) scatterplot!(plot, x, y, **kw) end end unicode-plot-0.0.4/lib/unicode_plot/braille_canvas.rb0000644000175100017510000000326113637627123021703 0ustar pravipravimodule UnicodePlot class BrailleCanvas < Canvas X_PIXEL_PER_CHAR = 2 Y_PIXEL_PER_CHAR = 4 BRAILLE_SIGNS = [ [ 0x2801, 0x2802, 0x2804, 0x2840, ].freeze, [ 0x2808, 0x2810, 0x2820, 0x2880 ].freeze ].freeze def initialize(width, height, **kw) super(width, height, width * X_PIXEL_PER_CHAR, height * Y_PIXEL_PER_CHAR, "\u{2800}", x_pixel_per_char: X_PIXEL_PER_CHAR, y_pixel_per_char: Y_PIXEL_PER_CHAR, **kw) end def pixel!(pixel_x, pixel_y, color) unless 0 <= pixel_x && pixel_x <= pixel_width && 0 <= pixel_y && pixel_y <= pixel_height return color end pixel_x -= 1 unless pixel_x < pixel_width pixel_y -= 1 unless pixel_y < pixel_height tx = pixel_x.fdiv(pixel_width) * width char_x = tx.floor + 1 char_x_off = pixel_x % X_PIXEL_PER_CHAR + 1 char_x += 1 if char_x < tx.round + 1 && char_x_off == 1 char_y = (pixel_y.fdiv(pixel_height) * height).floor + 1 char_y_off = pixel_y % Y_PIXEL_PER_CHAR + 1 index = index_at(char_x - 1, char_y - 1) if index @grid[index] = (@grid[index].ord | BRAILLE_SIGNS[char_x_off - 1][char_y_off - 1]).chr(Encoding::UTF_8) @colors[index] |= COLOR_ENCODE[color] end color end def print_row(out, row_index) unless 0 <= row_index && row_index < height raise ArgumentError, "row_index out of bounds" end y = row_index (0 ... width).each do |x| print_color(out, color_at(x, y), char_at(x, y)) end end end end unicode-plot-0.0.4/lib/unicode_plot/plot.rb0000644000175100017510000000635313637627123017721 0ustar pravipravimodule UnicodePlot class Plot include StyledPrinter DEFAULT_WIDTH = 40 DEFAULT_BORDER = :solid DEFAULT_MARGIN = 3 DEFAULT_PADDING = 1 def initialize(title: nil, xlabel: nil, ylabel: nil, border: DEFAULT_BORDER, margin: DEFAULT_MARGIN, padding: DEFAULT_PADDING, labels: true) @title = title @xlabel = xlabel @ylabel = ylabel @border = border @margin = check_margin(margin) @padding = padding @labels_left = {} @colors_left = {} @labels_right = {} @colors_right = {} @decorations = {} @colors_deco = {} @show_labels = labels @auto_color = 0 end attr_reader :title attr_reader :xlabel attr_reader :ylabel attr_reader :border attr_reader :margin attr_reader :padding attr_reader :labels_left attr_reader :colors_left attr_reader :labels_right attr_reader :colors_right attr_reader :decorations attr_reader :colors_deco def title_given? title && title != "" end def xlabel_given? xlabel && xlabel != "" end def ylabel_given? ylabel && ylabel != "" end def ylabel_length ylabel&.length || 0 end def show_labels? @show_labels end def annotate!(loc, value, color: :normal) case loc when :l (0 ... n_rows).each do |row| if @labels_left.fetch(row, "") == "" @labels_left[row] = value @colors_left[row] = color break end end when :r (0 ... n_rows).each do |row| if @labels_right.fetch(row, "") == "" @labels_right[row] = value @colors_right[row] = color break end end when :t, :b, :tl, :tr, :bl, :br @decorations[loc] = value @colors_deco[loc] = color else raise ArgumentError, "unknown location to annotate (#{loc.inspect} for :t, :b, :l, :r, :tl, :tr, :bl, or :br)" end end def annotate_row!(loc, row_index, value, color: :normal) case loc when :l @labels_left[row_index] = value @colors_left[row_index] = color when :r @labels_right[row_index] = value @colors_right[row_index] = color else raise ArgumentError, "unknown location `#{loc}`, try :l or :r instead" end end def render(out=$stdout, newline: true) Renderer.render(out, self, newline) end COLOR_CYCLE = [ :green, :blue, :red, :magenta, :yellow, :cyan ].freeze def next_color COLOR_CYCLE[@auto_color] ensure @auto_color = (@auto_color + 1) % COLOR_CYCLE.length end def to_s StringIO.open do |sio| render(sio, newline: false) sio.close sio.string end end private def check_margin(margin) if margin < 0 raise ArgumentError, "margin must be >= 0" end margin end private def check_row_index(row_index) unless 0 <= row_index && row_index < n_rows raise ArgumentError, "row_index out of bounds" end end end end unicode-plot-0.0.4/lib/unicode_plot/value_transformer.rb0000644000175100017510000000173713637627123022502 0ustar pravipravimodule UnicodePlot module ValueTransformer PREDEFINED_TRANSFORM_FUNCTIONS = { log: Math.method(:log), ln: Math.method(:log), log10: Math.method(:log10), lg: Math.method(:log10), log2: Math.method(:log2), lb: Math.method(:log2), }.freeze def transform_values(func, values) return values unless func unless func.respond_to?(:call) func = PREDEFINED_TRANSFORM_FUNCTIONS[func] unless func.respond_to?(:call) raise ArgumentError, "func must be callable" end end case values when Numeric func.(values) else values.map(&func) end end module_function def transform_name(func, basename="") return basename unless func case func when String, Symbol name = func when ->(f) { f.respond_to?(:name) } name = func.name else name = "custom" end "#{basename} [#{name}]" end end end unicode-plot-0.0.4/lib/unicode_plot/canvas.rb0000644000175100017510000001127513637627123020215 0ustar pravipravimodule UnicodePlot class Canvas include BorderPrinter def self.create(canvas_type, width, height, **kw) case canvas_type when :ascii AsciiCanvas.new(width, height, **kw) when :braille BrailleCanvas.new(width, height, **kw) when :density DensityCanvas.new(width, height, **kw) when :dot DotCanvas.new(width, height, **kw) else raise ArgumentError, "unknown canvas type: #{canvas_type}" end end def initialize(width, height, pixel_width, pixel_height, fill_char, origin_x: 0, origin_y: 0, plot_width: 1, plot_height: 1, x_pixel_per_char: 1, y_pixel_per_char: 1) @width = width @height = height @pixel_width = check_positive(pixel_width, :pixel_width) @pixel_height = check_positive(pixel_height, :pixel_height) @origin_x = origin_x @origin_y = origin_y @plot_width = plot_width @plot_height = plot_height @x_pixel_per_char = x_pixel_per_char @y_pixel_per_char = y_pixel_per_char @grid = Array.new(@width * @height, fill_char) @colors = Array.new(@width * @height, COLOR_ENCODE[:normal]) end attr_reader :width attr_reader :height attr_reader :pixel_width attr_reader :pixel_height attr_reader :origin_x attr_reader :origin_y attr_reader :plot_width attr_reader :plot_height attr_reader :x_pixel_per_char attr_reader :y_pixel_per_char def show(out) b = BorderMaps::BORDER_SOLID border_length = width print_border_top(out, "", border_length, :solid, color: :light_black) out.puts (0 ... height).each do |row_index| print_styled(out, b[:l], color: :light_black) print_row(out, row_index) print_styled(out, b[:r], color: :light_black) out.puts end print_border_bottom(out, "", border_length, :solid, color: :light_black) end def print(out) (0 ... height).each do |row_index| print_row(out, row_index) out.puts if row_index < height - 1 end end def char_at(x, y) @grid[index_at(x, y)] end def color_at(x, y) @colors[index_at(x, y)] end def index_at(x, y) return nil unless 0 <= x && x < width && 0 <= y && y < height y * width + x end def point!(x, y, color) unless origin_x <= x && x <= origin_x + plot_width && origin_y <= y && y <= origin_y + plot_height return color end plot_offset_x = x - origin_x pixel_x = plot_offset_x.fdiv(plot_width) * pixel_width plot_offset_y = y - origin_y pixel_y = pixel_height - plot_offset_y.fdiv(plot_height) * pixel_height pixel!(pixel_x.floor, pixel_y.floor, color) end def points!(x, y, color = :normal) if x.length != y.length raise ArgumentError, "x and y must be the same length" end unless x.length > 0 raise ArgumentError, "x and y must not be empty" end (0 ... x.length).each do |i| point!(x[i], y[i], color) end end # digital differential analyzer algorithm def line!(x1, y1, x2, y2, color) if (x1 < origin_x && x2 < origin_x) || (x1 > origin_x + plot_width && x2 > origin_x + plot_width) return color end if (y1 < origin_y && y2 < origin_y) || (y1 > origin_y + plot_height && y2 > origin_y + plot_height) return color end toff = x1 - origin_x px1 = toff.fdiv(plot_width) * pixel_width toff = x2 - origin_x px2 = toff.fdiv(plot_width) * pixel_width toff = y1 - origin_y py1 = pixel_height - toff.fdiv(plot_height) * pixel_height toff = y2 - origin_y py2 = pixel_height - toff.fdiv(plot_height) * pixel_height dx = px2 - px1 dy = py2 - py1 nsteps = dx.abs > dy.abs ? dx.abs : dy.abs inc_x = dx.fdiv(nsteps) inc_y = dy.fdiv(nsteps) cur_x = px1 cur_y = py1 pixel!(cur_x.floor, cur_y.floor, color) 1.upto(nsteps) do |i| cur_x += inc_x cur_y += inc_y pixel!(cur_x.floor, cur_y.floor, color) end color end def lines!(x, y, color = :normal) if x.length != y.length raise ArgumentError, "x and y must be the same length" end unless x.length > 0 raise ArgumentError, "x and y must not be empty" end (0 ... (x.length - 1)).each do |i| line!(x[i], y[i], x[i+1], y[i+1], color) end end private def check_positive(value, name) return value if value > 0 raise ArgumentError, "#{name} has to be positive" end end end unicode-plot-0.0.4/lib/unicode_plot/boxplot.rb0000644000175100017510000001212413637627123020423 0ustar pravipravirequire 'enumerable/statistics' module UnicodePlot class Boxplot < Plot MIN_WIDTH = 10 DEFAULT_COLOR = :green def initialize(data, width, color, min_x, max_x, **kw) if min_x == max_x min_x -= 1 max_x += 1 end width = [width, MIN_WIDTH].max @data = [data.percentile([0, 25, 50, 75, 100])] @color = color @width = [width, MIN_WIDTH].max @min_x = min_x @max_x = max_x super(**kw) end attr_reader :min_x attr_reader :max_x def n_data @data.length end def n_rows 3 * @data.length end def n_columns @width end def add_series!(data) mi, ma = data.minmax @data << data.percentile([0, 25, 50, 75, 100]) @min_x = [mi, @min_x].min @max_x = [ma, @max_x].max end def print_row(out, row_index) check_row_index(row_index) series = @data[(row_index / 3.0).to_i] series_row = row_index % 3 min_char = ['╷', '├' , '╵'][series_row] line_char = [' ', '─' , ' '][series_row] left_box_char = ['┌', '┤' , '└'][series_row] line_box_char = ['─', ' ' , '─'][series_row] median_char = ['┬', '│' , '┴'][series_row] right_box_char = ['┐', '├' , '┘'][series_row] max_char = ['╷', '┤' , '╵'][series_row] line = (0 ... @width).map { ' ' } # Draw shapes first - this is most important, # so they'll always be drawn even if there's not enough space transformed = transform(series) line[transformed[0] - 1] = min_char line[transformed[1] - 1] = left_box_char line[transformed[2] - 1] = median_char line[transformed[3] - 1] = right_box_char line[transformed[4] - 1] = max_char (transformed[0] ... (transformed[1] - 1)).each do |i| line[i] = line_char end (transformed[1] ... (transformed[2] - 1)).each do |i| line[i] = line_box_char end (transformed[2] ... (transformed[3] - 1)).each do |i| line[i] = line_box_char end (transformed[3] ... (transformed[4] - 1)).each do |i| line[i] = line_char end print_styled(out, line.join(''), color: @color) end private def transform(values) values.map do |val| val = (val - @min_x).fdiv(@max_x - @min_x) * @width val.round(half: :even).clamp(1, @width).to_i end end end module_function def boxplot(*args, data: nil, border: :corners, color: Boxplot::DEFAULT_COLOR, width: Plot::DEFAULT_WIDTH, xlim: [0, 0], **kw) case args.length when 0 data = Hash(data) text = data.keys data = data.values when 1 data = args[0] when 2 text = Array(args[0]) data = args[1] else raise ArgumentError, "wrong number of arguments" end case data[0] when Numeric data = [data] when Array # do nothing else data = data.to_ary end text ||= Array.new(data.length, "") unless text.length == data.length raise ArgumentError, "wrong number of text" end unless xlim.length == 2 raise ArgumentError, "xlim must be a length 2 array" end min_x, max_x = Utils.extend_limits(data.map(&:minmax).flatten, xlim) width = [width, Boxplot::MIN_WIDTH].max plot = Boxplot.new(data[0], width, color, min_x, max_x, border: border, **kw) (1 ... data.length).each do |i| plot.add_series!(data[i]) end mean_x = (min_x + max_x) / 2.0 min_x_str = (Utils.roundable?(min_x) ? min_x.round : min_x).to_s mean_x_str = (Utils.roundable?(mean_x) ? mean_x.round : mean_x).to_s max_x_str = (Utils.roundable?(max_x) ? max_x.round : max_x).to_s plot.annotate!(:bl, min_x_str, color: :light_black) plot.annotate!(:b, mean_x_str, color: :light_black) plot.annotate!(:br, max_x_str, color: :light_black) text.each_with_index do |name, i| plot.annotate_row!(:l, i*3+1, name) if name.length > 0 end plot end module_function def boxplot!(plot, *args, **kw) case args.length when 1 data = args[0] name = kw[:name] || "" when 2 name = args[0] data = args[1] else raise ArgumentError, "worng number of arguments" end if data.empty? raise ArgumentError, "Can't append empty array to boxplot" end plot.add_series!(data) plot.annotate_row!(:l, (plot.n_data - 1)*3+1, name) if name && name != "" min_x = plot.min_x max_x = plot.max_x mean_x = (min_x + max_x) / 2.0 min_x_str = (Utils.roundable?(min_x) ? min_x.round : min_x).to_s mean_x_str = (Utils.roundable?(mean_x) ? mean_x.round : mean_x).to_s max_x_str = (Utils.roundable?(max_x) ? max_x.round : max_x).to_s plot.annotate!(:bl, min_x_str, color: :light_black) plot.annotate!(:b, mean_x_str, color: :light_black) plot.annotate!(:br, max_x_str, color: :light_black) plot end end unicode-plot-0.0.4/lib/unicode_plot/version.rb0000644000175100017510000000027513637627123020425 0ustar pravipravimodule UnicodePlot VERSION = "0.0.4" module Version numbers, TAG = VERSION.split("-", 2) MAJOR, MINOR, MICRO = numbers.split(".", 3).map(&:to_i) STRING = VERSION end end unicode-plot-0.0.4/lib/unicode_plot/grid_plot.rb0000644000175100017510000000532213637627123020721 0ustar pravipravimodule UnicodePlot class GridPlot < Plot MIN_WIDTH = 5 MIN_HEIGHT = 2 DEFAULT_HEIGHT = 15 def initialize(x, y, canvas, width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, xlim: [0, 0], ylim: [0, 0], grid: true, **kw) if x.length != y.length raise ArgumentError, "x and y must be the same length" end unless x.length > 0 raise ArgumentError, "x and y must not be empty" end unless xlim.length == 2 && ylim.length == 2 raise ArgumentError, "xlim and ylim must be 2-length arrays" end width = [width, MIN_WIDTH].max height = [height, MIN_HEIGHT].max min_x, max_x = Utils.extend_limits(x, xlim) min_y, max_y = Utils.extend_limits(y, ylim) origin_x = min_x origin_y = min_y plot_width = max_x - origin_x plot_height = max_y - origin_y @canvas = Canvas.create(canvas, width, height, origin_x: origin_x, origin_y: origin_y, plot_width: plot_width, plot_height: plot_height) super(**kw) min_x_str = (Utils.roundable?(min_x) ? min_x.round : min_x).to_s max_x_str = (Utils.roundable?(max_x) ? max_x.round : max_x).to_s min_y_str = (Utils.roundable?(min_y) ? min_y.round : min_y).to_s max_y_str = (Utils.roundable?(max_y) ? max_y.round : max_y).to_s annotate_row!(:l, 0, max_y_str, color: :light_black) annotate_row!(:l, height-1, min_y_str, color: :light_black) annotate!(:bl, min_x_str, color: :light_black) annotate!(:br, max_x_str, color: :light_black) if grid if min_y < 0 && 0 < max_y step = plot_width.fdiv(width * @canvas.x_pixel_per_char - 1) min_x.step(max_x, by: step) do |i| @canvas.point!(i, 0, :normal) end end if min_x < 0 && 0 < max_x step = plot_height.fdiv(height * @canvas.y_pixel_per_char - 1) min_y.step(max_y, by: step) do |i| @canvas.point!(0, i, :normal) end end end end def origin_x @canvas.origin_x end def origin_y @canvas.origin_y end def plot_width @canvas.plot_width end def plot_height @canvas.plot_height end def n_rows @canvas.height end def n_columns @canvas.width end def points!(x, y, color) @canvas.points!(x, y, color) end def lines!(x, y, color) @canvas.lines!(x, y, color) end def print_row(out, row_index) @canvas.print_row(out, row_index) end end end unicode-plot-0.0.4/lib/unicode_plot/utils.rb0000644000175100017510000000274213637627123020101 0ustar pravipravimodule UnicodePlot module Utils module_function def extend_limits(values, limits) mi, ma = limits.minmax.map(&:to_f) if mi == 0 && ma == 0 mi, ma = values.minmax.map(&:to_f) end diff = ma - mi if diff == 0 ma = mi + 1 mi = mi - 1 end if limits == [0, 0] plotting_range_narrow(mi, ma) else [mi, ma] end end def plotting_range_narrow(xmin, xmax) diff = xmax - xmin xmax = round_up_subtick(xmax, diff) xmin = round_down_subtick(xmin, diff) [xmin.to_f, xmax.to_f] end def float_round_log10(x, m) if x == 0 0.0 elsif x > 0 x.round(ceil_neg_log10(m) + 1).to_f else -(-x).round(ceil_neg_log10(m) + 1).to_f end end def round_up_subtick(x, m) if x == 0 0.0 elsif x > 0 x.ceil(ceil_neg_log10(m) + 1) else -(-x).floor(ceil_neg_log10(m) + 1) end end def round_down_subtick(x, m) if x == 0 0.0 elsif x > 0 x.floor(ceil_neg_log10(m) + 1) else -(-x).ceil(ceil_neg_log10(m) + 1) end end def ceil_neg_log10(x) if roundable?(-Math.log10(x)) (-Math.log10(x)).ceil else (-Math.log10(x)).floor end end INT64_MIN = -9223372036854775808 INT64_MAX = 9223372036854775807 def roundable?(x) x.to_i == x && INT64_MIN <= x && x < INT64_MAX end end end unicode-plot-0.0.4/lib/unicode_plot/layout.rb0000644000175100017510000000175613637627123020262 0ustar pravipravimodule UnicodePlot class GridLayout DEFAULT_WIDTH = 80 def initialize(n_rows, n_columns, width: Layout::DEFAULT_WIDTH) @n_rows = n_rows @n_columns = n_columns @width = width end def [](i, j) @plots[i * n_cols + j] end def []=(i, j, plot) @plots[i * n_cols + j] = plot end def <<(plot) @plots << plot end def render(out) buffers = [] (0 ... n_rows).each do |i| (0 ... n_columns).each do |j| StringIO.open do |sio| def sio.tty?; true; end render_cell(sio, i, j) sio.close buffers << sio.string end end end end def render_cell(out, i, j) plot = self[i, j] return unless plot plot.width = cell_width end end module_function def grid_layout(n_rows, n_cols, *plots, **kw) grid = GridLayout.new(n_rows, n_cols, **kw) plots.each do |plot| grid << plot end grid end end unicode-plot-0.0.4/lib/unicode_plot/barplot.rb0000644000175100017510000000732613637627123020407 0ustar pravipravimodule UnicodePlot class Barplot < Plot include ValueTransformer MIN_WIDTH = 10 DEFAULT_COLOR = :green DEFAULT_SYMBOL = "■" def initialize(bars, width, color, symbol, transform, **kw) if symbol.length > 1 raise ArgumentError, "symbol must be a single character" end @bars = bars @symbol = symbol @max_freq, i = find_max(transform_values(transform, bars)) @max_len = bars[i].to_s.length @width = [width, max_len + 7, MIN_WIDTH].max @color = color @symbol = symbol @transform = transform super(**kw) end attr_reader :max_freq attr_reader :max_len attr_reader :width def n_rows @bars.length end def n_columns @width end def add_row!(bars) @bars.concat(bars) @max_freq, i = find_max(transform_values(@transform, bars)) @max_len = @bars[i].to_s.length end def print_row(out, row_index) check_row_index(row_index) bar = @bars[row_index] max_bar_width = [width - 2 - max_len, 1].max val = transform_values(@transform, bar) bar_len = max_freq > 0 ? ([val, 0].max.fdiv(max_freq) * max_bar_width).round : 0 bar_str = max_freq > 0 ? @symbol * bar_len : "" bar_lbl = bar.to_s print_styled(out, bar_str, color: @color) print_styled(out, " ", bar_lbl, color: :normal) pan_len = [max_bar_width + 1 + max_len - bar_len - bar_lbl.length, 0].max pad = " " * pan_len.round out.print(pad) end private def find_max(values) i = j = 0 max = values[i] while j < values.length if values[j] > max i, max = j, values[j] end j += 1 end [max, i] end end module_function def barplot(*args, width: Plot::DEFAULT_WIDTH, color: Barplot::DEFAULT_COLOR, symbol: Barplot::DEFAULT_SYMBOL, border: :barplot, xscale: nil, xlabel: nil, data: nil, **kw) case args.length when 0 data = Hash(data) keys = data.keys.map(&:to_s) heights = data.values when 2 keys = Array(args[0]) heights = Array(args[1]) else raise ArgumentError, "invalid arguments" end unless keys.length == heights.length raise ArgumentError, "The given vectors must be of the same length" end unless heights.min >= 0 raise ArgumentError, "All values have to be positive. Negative bars are not supported." end xlabel ||= ValueTransformer.transform_name(xscale) plot = Barplot.new(heights, width, color, symbol, xscale, border: border, xlabel: xlabel, **kw) keys.each_with_index do |key, i| plot.annotate_row!(:l, i, key) end plot end module_function def barplot!(plot, *args, data: nil, **kw) case args.length when 0 data = Hash(data) keys = data.keys.map(&:to_s) heights = data.values when 2 keys = Array(args[0]) heights = Array(args[1]) else raise ArgumentError, "invalid arguments" end unless keys.length == heights.length raise ArgumentError, "The given vectors must be of the same length" end if keys.empty? raise ArgumentError, "Can't append empty array to barplot" end cur_idx = plot.n_rows plot.add_row!(heights) keys.each_with_index do |key, i| plot.annotate_row!(:l, cur_idx + i, key) end plot end end unicode-plot-0.0.4/lib/unicode_plot/styled_printer.rb0000644000175100017510000000463313637627123022011 0ustar pravipravimodule UnicodePlot module StyledPrinter TEXT_COLORS = { black: "\033[30m", red: "\033[31m", green: "\033[32m", yellow: "\033[33m", blue: "\033[34m", magenta: "\033[35m", cyan: "\033[36m", white: "\033[37m", gray: "\033[90m", light_black: "\033[90m", light_red: "\033[91m", light_green: "\033[92m", light_yellow: "\033[93m", light_blue: "\033[94m", light_magenta: "\033[95m", light_cyan: "\033[96m", normal: "\033[0m", default: "\033[39m", bold: "\033[1m", underline: "\033[4m", blink: "\033[5m", reverse: "\033[7m", hidden: "\033[8m", nothing: "", } 0.upto(255) do |i| TEXT_COLORS[i] = "\033[38;5;#{i}m" end TEXT_COLORS.freeze DISABLE_TEXT_STYLE = { bold: "\033[22m", underline: "\033[24m", blink: "\033[25m", reverse: "\033[27m", hidden: "\033[28m", normal: "", default: "", nothing: "", }.freeze COLOR_ENCODE = { normal: 0b000, blue: 0b001, red: 0b010, magenta: 0b011, green: 0b100, cyan: 0b101, yellow: 0b110, white: 0b111 }.freeze COLOR_DECODE = COLOR_ENCODE.map {|k, v| [v, k] }.to_h.freeze def print_styled(out, *args, bold: false, color: :normal) return out.print(*args) unless color?(out) str = StringIO.open {|sio| sio.print(*args); sio.close; sio.string } color = :nothing if bold && color == :bold enable_ansi = TEXT_COLORS.fetch(color, TEXT_COLORS[:default]) + (bold ? TEXT_COLORS[:bold] : "") disable_ansi = (bold ? DISABLE_TEXT_STYLE[:bold] : "") + DISABLE_TEXT_STYLE.fetch(color, TEXT_COLORS[:default]) first = true StringIO.open do |sio| str.each_line do |line| sio.puts unless first first = false continue if line.empty? sio.print(enable_ansi, line, disable_ansi) end sio.close out.print(sio.string) end end def print_color(out, color, *args) color = COLOR_DECODE[color] print_styled(out, *args, color: color) end def color?(out) out&.tty? || false end end end unicode-plot-0.0.4/lib/unicode_plot/renderer.rb0000644000175100017510000001652413637627123020552 0ustar pravipravimodule UnicodePlot module BorderMaps BORDER_SOLID = { tl: "┌", tr: "┐", bl: "└", br: "┘", t: "─", l: "│", b: "─", r: "│" }.freeze BORDER_CORNERS = { tl: "┌", tr: "┐", bl: "└", br: "┘", t: " ", l: " ", b: " ", r: " ", }.freeze BORDER_BARPLOT = { tl: "┌", tr: "┐", bl: "└", br: "┘", t: " ", l: "┤", b: " ", r: " ", }.freeze end BORDER_MAP = { solid: BorderMaps::BORDER_SOLID, corners: BorderMaps::BORDER_CORNERS, barplot: BorderMaps::BORDER_BARPLOT, }.freeze module BorderPrinter include StyledPrinter def print_border_top(out, padding, length, border=:solid, color: :light_black) return if border == :none b = BORDER_MAP[border] print_styled(out, padding, b[:tl], b[:t] * length, b[:tr], color: color) end def print_border_bottom(out, padding, length, border=:solid, color: :light_black) return if border == :none b = BORDER_MAP[border] print_styled(out, padding, b[:bl], b[:b] * length, b[:br], color: color) end end class Renderer include BorderPrinter def self.render(out, plot, newline) new(plot).render(out, newline) end def initialize(plot) @plot = plot @out = nil end attr_reader :plot attr_reader :out def render(out, newline) @out = out init_render render_top render_rows render_bottom out.puts if newline end private def render_top # plot the title and the top border print_title(@border_padding, plot.title, p_width: @border_length, color: :bold) puts if plot.title_given? if plot.show_labels? topleft_str = plot.decorations.fetch(:tl, "") topleft_col = plot.colors_deco.fetch(:tl, :light_black) topmid_str = plot.decorations.fetch(:t, "") topmid_col = plot.colors_deco.fetch(:t, :light_black) topright_str = plot.decorations.fetch(:tr, "") topright_col = plot.colors_deco.fetch(:tr, :light_black) if topleft_str != "" || topright_str != "" || topmid_str != "" topleft_len = topleft_str.length topmid_len = topmid_str.length topright_len = topright_str.length print_styled(out, @border_padding, topleft_str, color: topleft_col) cnt = (@border_length / 2.0 - topmid_len / 2.0 - topleft_len).round pad = cnt > 0 ? " " * cnt : "" print_styled(out, pad, topmid_str, color: topmid_col) cnt = @border_length - topright_len - topleft_len - topmid_len + 2 - cnt pad = cnt > 0 ? " " * cnt : "" print_styled(out, pad, topright_str, "\n", color: topright_col) end end print_border_top(out, @border_padding, @border_length, plot.border) print(" " * @max_len_r, @plot_padding, "\n") end # render all rows def render_rows (0 ... plot.n_rows).each {|row| render_row(row) } end def render_row(row) # Current labels to left and right of the row and their length left_str = plot.labels_left.fetch(row, "") left_col = plot.colors_left.fetch(row, :light_black) right_str = plot.labels_right.fetch(row, "") right_col = plot.colors_right.fetch(row, :light_black) left_len = nocolor_string(left_str).length right_len = nocolor_string(right_str).length unless color?(out) left_str = nocolor_string(left_str) right_str = nocolor_string(right_str) end # print left annotations print(" " * plot.margin) if plot.show_labels? if row == @y_lab_row # print ylabel print_styled(out, plot.ylabel, color: :normal) print(" " * (@max_len_l - plot.ylabel_length - left_len)) else # print padding to fill ylabel length print(" " * (@max_len_l - left_len)) end # print the left annotation print_styled(out, left_str, color: left_col) end # print left border print_styled(out, @plot_padding, @b[:l], color: :light_black) # print canvas row plot.print_row(out, row) #print right label and padding print_styled(out, @b[:r], color: :light_black) if plot.show_labels? print(@plot_padding) print_styled(out, right_str, color: right_col) print(" " * (@max_len_r - right_len)) end puts end def render_bottom # draw bottom border and bottom labels print_border_bottom(out, @border_padding, @border_length, plot.border) print(" " * @max_len_r, @plot_padding) if plot.show_labels? botleft_str = plot.decorations.fetch(:bl, "") botleft_col = plot.colors_deco.fetch(:bl, :light_black) botmid_str = plot.decorations.fetch(:b, "") botmid_col = plot.colors_deco.fetch(:b, :light_black) botright_str = plot.decorations.fetch(:br, "") botright_col = plot.colors_deco.fetch(:br, :light_black) if botleft_str != "" || botright_str != "" || botmid_str != "" puts botleft_len = botleft_str.length botmid_len = botmid_str.length botright_len = botright_str.length print_styled(out, @border_padding, botleft_str, color: botleft_col) cnt = (@border_length / 2.0 - botmid_len / 2.0 - botleft_len).round pad = cnt > 0 ? " " * cnt : "" print_styled(out, pad, botmid_str, color: botmid_col) cnt = @border_length - botright_len - botleft_len - botmid_len + 2 - cnt pad = cnt > 0 ? " " * cnt : "" print_styled(out, pad, botright_str, color: botright_col) end # abuse the print_title function to print the xlabel. maybe refactor this puts if plot.xlabel_given? print_title(@border_padding, plot.xlabel, p_width: @border_length) end end def init_render @b = BORDER_MAP[plot.border] @border_length = plot.n_columns # get length of largest strings to the left and right @max_len_l = plot.show_labels? && !plot.labels_left.empty? ? plot.labels_left.each_value.map {|l| nocolor_string(l).length }.max : 0 @max_len_r = plot.show_labels? && !plot.labels_right.empty? ? plot.labels_right.each_value.map {|l| nocolor_string(l).length }.max : 0 if plot.show_labels? && plot.ylabel_given? @max_len_l += plot.ylabel_length + 1 end # offset where the plot (incl border) begins @plot_offset = @max_len_l + plot.margin + plot.padding # padding-string from left to border @plot_padding = " " * plot.padding # padding-string between labels and border @border_padding = " " * @plot_offset # compute position of ylabel @y_lab_row = (plot.n_rows / 2.0).round - 1 end def print_title(padding, title, p_width: 0, color: :normal) return unless title && title != "" offset = (p_width / 2.0 - title.length / 2.0).round offset = [offset, 0].max tpad = " " * offset print_styled(out, padding, tpad, title, color: color) end def print(*args) out.print(*args) end def puts(*args) out.puts(*args) end def nocolor_string(str) str.to_s.gsub(/\e\[[0-9]+m/, "") end end end unicode-plot-0.0.4/lib/unicode_plot/histogram.rb0000644000175100017510000000341413637627123020733 0ustar pravipravirequire 'enumerable/statistics' module UnicodePlot module_function def histogram(x, nbins: nil, closed: :left, symbol: "▇", **kw) hist = x.histogram(*[nbins].compact, closed: closed) edge, counts = hist.edge, hist.weights labels = [] bin_width = edge[1] - edge[0] pad_left, pad_right = 0, 0 (0 ... edge.length).each do |i| val1 = Utils.float_round_log10(edge[i], bin_width) val2 = Utils.float_round_log10(val1 + bin_width, bin_width) a1 = val1.to_s.split('.', 2).map(&:length) a2 = val2.to_s.split('.', 2).map(&:length) pad_left = [pad_left, a1[0], a2[0]].max pad_right = [pad_right, a1[1], a2[1]].max end l_str = hist.closed == :right ? "(" : "[" r_str = hist.closed == :right ? "]" : ")" counts.each_with_index do |n, i| val1 = Utils.float_round_log10(edge[i], bin_width) val2 = Utils.float_round_log10(val1 + bin_width, bin_width) a1 = val1.to_s.split('.', 2).map(&:length) a2 = val2.to_s.split('.', 2).map(&:length) labels[i] = "\e[90m#{l_str}\e[0m" + (" " * (pad_left - a1[0])) + val1.to_s + (" " * (pad_right - a1[1])) + "\e[90m, \e[0m" + (" " * (pad_left - a2[0])) + val2.to_s + (" " * (pad_right - a2[1])) + "\e[90m#{r_str}\e[0m" end xscale = kw.delete(:xscale) xlabel = kw.delete(:xlabel) || ValueTransformer.transform_name(xscale, "Frequency") barplot(labels, counts, symbol: symbol, xscale: xscale, xlabel: xlabel, **kw) end end unicode-plot-0.0.4/lib/unicode_plot/scatterplot.rb0000644000175100017510000000234713637627123021306 0ustar pravipravimodule UnicodePlot class Scatterplot < GridPlot end module_function def scatterplot(*args, canvas: :braille, color: :auto, name: "", **kw) case args.length when 1 # y only y = Array(args[0]) x = Array(1 .. y.length) when 2 # x and y x = Array(args[0]) y = Array(args[1]) else raise ArgumentError, "worng number of arguments" end plot = Scatterplot.new(x, y, canvas, **kw) scatterplot!(plot, x, y, color: color, name: name) end module_function def scatterplot!(plot, *args, color: :auto, name: "") case args.length when 1 # y only y = Array(args[0]) x = Array(1 .. y.length) when 2 # x and y x = Array(args[0]) y = Array(args[1]) else raise ArgumentError, "worng number of arguments" end color = color == :auto ? plot.next_color : color plot.annotate!(:r, name.to_s, color: color) unless name.nil? || name == "" plot.points!(x, y, color) plot end end unicode-plot-0.0.4/lib/unicode_plot/density_canvas.rb0000644000175100017510000000311413637627123021745 0ustar pravipravimodule UnicodePlot class DensityCanvas < Canvas DENSITY_SIGNS = [" ", "░", "▒", "▓", "█"].freeze MIN_WIDTH = 5 MIN_HEIGHT = 5 X_PIXEL_PER_CHAR = 1 Y_PIXEL_PER_CHAR = 2 def initialize(width, height, **kw) width = [width, MIN_WIDTH].max height = [height, MIN_HEIGHT].max @max_density = 1 super(width, height, width * X_PIXEL_PER_CHAR, height * Y_PIXEL_PER_CHAR, 0, x_pixel_per_char: X_PIXEL_PER_CHAR, y_pixel_per_char: Y_PIXEL_PER_CHAR, **kw) end def pixel!(pixel_x, pixel_y, color) unless 0 <= pixel_x && pixel_x <= pixel_width && 0 <= pixel_y && pixel_y <= pixel_height return color end pixel_x -= 1 unless pixel_x < pixel_width pixel_y -= 1 unless pixel_y < pixel_height char_x = (pixel_x.fdiv(pixel_width) * width).floor char_y = (pixel_y.fdiv(pixel_height) * height).floor index = index_at(char_x, char_y) @grid[index] += 1 @max_density = [@max_density, @grid[index]].max @colors[index] |= COLOR_ENCODE[color] color end def print_row(out, row_index) unless 0 <= row_index && row_index < height raise ArgumentError, "row_index out of bounds" end y = row_index den_sign_count = DENSITY_SIGNS.length val_scale = (den_sign_count - 1).fdiv(@max_density) (0 ... width).each do |x| den_index = (char_at(x, y) * val_scale).round print_color(out, color_at(x, y), DENSITY_SIGNS[den_index]) end end end end unicode-plot-0.0.4/lib/unicode_plot/ascii_canvas.rb0000644000175100017510000000625713637627123021371 0ustar pravipravimodule UnicodePlot class AsciiCanvas < LookupCanvas ASCII_SIGNS = [ [ 0b100_000_000, 0b000_100_000, 0b000_000_100 ].freeze, [ 0b010_000_000, 0b000_010_000, 0b000_000_010 ].freeze, [ 0b001_000_000, 0b000_001_000, 0b000_000_001 ].freeze ].freeze ASCII_LOOKUP = { 0b101_000_000 => '"', 0b111_111_111 => '@', #0b011_110_011 => '$', 0b010_000_000 => '\'', 0b010_100_010 => '(', 0b010_001_010 => ')', 0b000_010_000 => '*', 0b010_111_010 => '+', 0b000_010_010 => ',', 0b000_100_100 => ',', 0b000_001_001 => ',', 0b000_111_000 => '-', 0b000_000_010 => '.', 0b000_000_100 => '.', 0b000_000_001 => '.', 0b001_010_100 => '/', 0b010_100_000 => '/', 0b001_010_110 => '/', 0b011_010_010 => '/', 0b001_010_010 => '/', 0b110_010_111 => '1', #0b111_010_100 => '7', 0b010_000_010 => ':', 0b111_000_111 => '=', #0b010_111_101 => 'A', #0b011_100_011 => 'C', #0b110_101_110 => 'D', #0b111_110_100 => 'F', #0b011_101_011 => 'G', #0b101_111_101 => 'H', 0b111_010_111 => 'I', #0b011_001_111 => 'J', #0b101_110_101 => 'K', 0b100_100_111 => 'L', #0b111_111_101 => 'M', #0b101_101_101 => 'N', #0b111_101_111 => 'O', #0b111_111_100 => 'P', 0b111_010_010 => 'T', #0b101_101_111 => 'U', 0b101_101_010 => 'V', #0b101_111_111 => 'W', 0b101_010_101 => 'X', 0b101_010_010 => 'Y', 0b110_100_110 => '[', 0b010_001_000 => '\\', 0b100_010_001 => '\\', 0b110_010_010 => '\\', 0b100_010_011 => '\\', 0b100_010_010 => '\\', 0b011_001_011 => ']', 0b010_101_000 => '^', 0b000_000_111 => '_', 0b100_000_000 => '`', #0b000_111_111 => 'a', #0b100_111_111 => 'b', #0b001_111_111 => 'd', #0b001_111_010 => 'f', #0b100_111_101 => 'h', #0b100_101_101 => 'k', 0b110_010_011 => 'l', #0b000_111_101 => 'n', 0b000_111_100 => 'r', #0b000_101_111 => 'u', 0b000_101_010 => 'v', 0b011_110_011 => '{', 0b010_010_010 => '|', 0b100_100_100 => '|', 0b001_001_001 => '|', 0b110_011_110 => '}', }.freeze ascii_lookup_key_order = [ 0x0002, 0x00d2, 0x0113, 0x00a0, 0x0088, 0x002a, 0x0100, 0x0197, 0x0012, 0x0193, 0x0092, 0x0082, 0x008a, 0x0054, 0x0004, 0x01d2, 0x01ff, 0x0124, 0x00a8, 0x0056, 0x0001, 0x01c7, 0x0052, 0x0080, 0x0009, 0x00cb, 0x0007, 0x003c, 0x0111, 0x0140, 0x0024, 0x0127, 0x0192, 0x0010, 0x019e, 0x01a6, 0x01d7, 0x0155, 0x00a2, 0x00ba, 0x0112, 0x0049, 0x00f3, 0x0152, 0x0038, 0x016a ] ASCII_DECODE = [' '] 1.upto(0b111_111_111) do |i| min_key = ascii_lookup_key_order.min_by {|k| (i ^ k).digits(2).sum } ASCII_DECODE[i] = ASCII_LOOKUP[min_key] end ASCII_DECODE.freeze PIXEL_PER_CHAR = 3 def initialize(width, height, **kw) super(width, height, PIXEL_PER_CHAR, PIXEL_PER_CHAR, **kw) end def lookup_encode(x, y) ASCII_SIGNS[x][y] end def lookup_decode(code) ASCII_DECODE[code] end end end unicode-plot-0.0.4/lib/unicode_plot.rb0000644000175100017510000000123213637627123016732 0ustar pravipravirequire 'stringio' require 'unicode_plot/version' require 'unicode_plot/utils' require 'unicode_plot/styled_printer' require 'unicode_plot/value_transformer' require 'unicode_plot/renderer' require 'unicode_plot/canvas' require 'unicode_plot/braille_canvas' require 'unicode_plot/density_canvas' require 'unicode_plot/lookup_canvas' require 'unicode_plot/ascii_canvas' require 'unicode_plot/dot_canvas' require 'unicode_plot/plot' require 'unicode_plot/grid_plot' require 'unicode_plot/barplot' require 'unicode_plot/boxplot' require 'unicode_plot/densityplot' require 'unicode_plot/lineplot' require 'unicode_plot/histogram' require 'unicode_plot/scatterplot' unicode-plot-0.0.4/README.md0000644000175100017510000000355513637627123014444 0ustar pravipravi# UnicodePlot - Plot your data by Unicode characters UnicodePlot provides the feature to make charts with Unicode characters. ## Install ```console $ gem install unicode_plot ``` ## Usage ```ruby require 'unicode_plot' x = 0.step(3*Math::PI, by: 3*Math::PI / 30) y_sin = x.map {|xi| Math.sin(xi) } y_cos = x.map {|xi| Math.cos(xi) } plot = UnicodePlot.lineplot(x, y_sin, name: "sin(x)", width: 40, height: 10) UnicodePlot.lineplot!(plot, x, y_cos, name: "cos(x)") plot.render($stdout) puts ``` You can get the results below by running the above script: ## Supported charts ### barplot ```ruby plot = UnicodePlot.barplot(data: {'foo': 20, 'bar': 50}, title: "Bar") plot.render($stdout) ``` ### boxplot ```ruby plot = UnicodePlot.boxplot(data: {foo: [1, 3, 5], bar: [3, 5, 7]}, title: "Box") plot.render($stdout) ``` ### densityplot ```ruby x = Array.new(500) { 20*rand - 10 } + Array.new(500) { 6*rand - 3 } y = Array.new(1000) { 30*rand - 10 } plot = UnicodePlot.densityplot(x, y, title: "Density") plot.render($stdout) ``` ### histogram ```ruby x = Array.new(100) { rand(10) } + Array.new(100) { rand(30) + 10 } plot = UnicodePlot.histogram(x, title: "Histogram") plot.render($stdout) ``` ### lineplot See [Usage](#usage) section above. ### scatterplot ```ruby x = Array.new(50) { rand(20) - 10 } y = x.map {|xx| xx*rand(30) - 10 } plot = UnicodePlot.scatterplot(x, y, title: "Scatter") plot.render($stdout) ``` ## Acknowledgement This library is strongly inspired by [UnicodePlot.jl](https://github.com/Evizero/UnicodePlots.jl). ## License MIT License ## Author - [Kenta Murata](https://github.com/mrkn) unicode-plot-0.0.4/test/0000755000175100017510000000000013637627123014134 5ustar pravipraviunicode-plot-0.0.4/test/helper.rb0000644000175100017510000000010613637627123015735 0ustar pravipravirequire_relative "helper/fixture" require_relative "helper/with_term" unicode-plot-0.0.4/test/test-barplot.rb0000644000175100017510000001425113637627123017104 0ustar pravipraviclass BarplotTest < Test::Unit::TestCase include Helper::Fixture include Helper::WithTerm sub_test_case("UnicodePlot.barplot") do test("errors") do assert_raise(ArgumentError) do UnicodePlot.barplot([:a], [-1, 2]) end assert_raise(ArgumentError) do UnicodePlot.barplot([:a, :b], [-1, 2]) end end test("colored") do data = { bar: 23, foo: 37 } plot = UnicodePlot.barplot(data: data) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/default.txt").read, output) plot = UnicodePlot.barplot([:bar, :foo], [23, 37]) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/default.txt").read, output) end test("not colored") do data = { bar: 23, foo: 37 } plot = UnicodePlot.barplot(data: data) output = StringIO.open do |sio| sio.print(plot) sio.close sio.string end assert_equal(fixture_path("barplot/nocolor.txt").read, output) end test("mixed") do data = { bar: 23.0, 2.1 => 10, foo: 37.0 } plot = UnicodePlot.barplot(data: data) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/default_mixed.txt").read, output) end sub_test_case("xscale: :log10") do test("default") do plot = UnicodePlot.barplot( [:a, :b, :c, :d, :e], [0, 1, 10, 100, 1000], title: "Logscale Plot", xscale: :log10 ) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/log10.txt").read, output) end test("with custom label") do plot = UnicodePlot.barplot( [:a, :b, :c, :d, :e], [0, 1, 10, 100, 1000], title: "Logscale Plot", xlabel: "custom label", xscale: :log10 ) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/log10_label.txt").read, output) end end sub_test_case("with parameters") do test("parameters1") do plot = UnicodePlot.barplot( ["Paris", "New York", "Moskau", "Madrid"], [2.244, 8.406, 11.92, 3.165], title: "Relative sizes of cities", xlabel: "population [in mil]", color: :blue, margin: 7, padding: 3 ) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/parameters1.txt").read, output) end test("parameters1_nolabels") do plot = UnicodePlot.barplot( ["Paris", "New York", "Moskau", "Madrid"], [2.244, 8.406, 11.92, 3.165], title: "Relative sizes of cities", xlabel: "population [in mil]", color: :blue, margin: 7, padding: 3, labels: false ) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/parameters1_nolabels.txt").read, output) end test("parameters2") do plot = UnicodePlot.barplot( ["Paris", "New York", "Moskau", "Madrid"], [2.244, 8.406, 11.92, 3.165], title: "Relative sizes of cities", xlabel: "population [in mil]", color: :yellow, border: :solid, symbol: "=", width: 60 ) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/parameters2.txt").read, output) end end test("ranges") do plot = UnicodePlot.barplot(2..6, 11..15) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/ranges.txt").read, output) end test("all zeros") do plot = UnicodePlot.barplot([5, 4, 3, 2, 1], [0, 0, 0, 0, 0]) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/edgecase_zeros.txt").read, output) end test("one large") do plot = UnicodePlot.barplot([:a, :b, :c, :d], [1, 1, 1, 1000000]) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/edgecase_onelarge.txt").read, output) end end sub_test_case("UnicodePlot.barplot!") do test("errors") do plot = UnicodePlot.barplot([:bar, :foo], [23, 37]) assert_raise(ArgumentError) do UnicodePlot.barplot!(plot, ["zoom"], [90, 80]) end assert_raise(ArgumentError) do UnicodePlot.barplot!(plot, ["zoom", "boom"], [90]) end UnicodePlot.barplot!(plot, "zoom", 90.1) end test("return value") do plot = UnicodePlot.barplot([:bar, :foo], [23, 37]) assert_same(plot, UnicodePlot.barplot!(plot, ["zoom"], [90])) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/default2.txt").read, output) plot = UnicodePlot.barplot([:bar, :foo], [23, 37]) assert_same(plot, UnicodePlot.barplot!(plot, "zoom", 90)) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/default2.txt").read, output) plot = UnicodePlot.barplot([:bar, :foo], [23, 37]) assert_same(plot, UnicodePlot.barplot!(plot, data: { zoom: 90 })) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/default2.txt").read, output) end test("ranges") do plot = UnicodePlot.barplot(2..6, 11..15) assert_same(plot, UnicodePlot.barplot!(plot, 9..10, 20..21)) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("barplot/ranges2.txt").read, output) end end end unicode-plot-0.0.4/test/helper/0000755000175100017510000000000013637627123015413 5ustar pravipraviunicode-plot-0.0.4/test/helper/fixture.rb0000644000175100017510000000040713637627123017427 0ustar pravipravirequire 'pathname' module Helper module Fixture def fixture_dir test_dir = Pathname(File.expand_path("../..", __FILE__)) test_dir.join("fixtures") end def fixture_path(*components) fixture_dir.join(*components) end end end unicode-plot-0.0.4/test/helper/with_term.rb0000644000175100017510000000066313637627123017747 0ustar pravipravirequire 'stringio' module Helper module WithTerm def with_term(tty=true) sio = StringIO.new def sio.tty?; true; end if tty orig_stdout, $stdout = $stdout, sio orig_env = ENV.to_h.dup ENV['TERM'] = 'xterm-256color' result = yield sio.close [result, sio.string] ensure $stdout.close $stdout = orig_stdout ENV.replace(orig_env) if orig_env end end end unicode-plot-0.0.4/test/test-boxplot.rb0000644000175100017510000000576213637627123017137 0ustar pravipraviclass BoxplotTest < Test::Unit::TestCase include Helper::Fixture include Helper::WithTerm sub_test_case("UnicodePlot.boxplot") do sub_test_case("print to tty") do test("without name") do plot = UnicodePlot.boxplot([1, 2, 3, 4, 5]) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("boxplot/default.txt").read, output) end test("with name") do plot = UnicodePlot.boxplot("series1", [1, 2, 3, 4, 5]) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("boxplot/default_name.txt").read, output) end end sub_test_case("with parameters") do def setup @plot = UnicodePlot.boxplot("series1", [1, 2, 3, 4, 5], title: "Test", xlim: [-1, 8], color: :blue, width: 50, border: :solid, xlabel: "foo") end test("print to tty") do _, output = with_term { @plot.render($stdout, newline: false) } assert_equal(fixture_path("boxplot/default_parameters.txt").read, output) end test("print to non-tty IO") do output = StringIO.open do |sio| @plot.render(sio, newline: false) sio.close sio.string end assert_equal(fixture_path("boxplot/default_parameters_nocolor.txt").read, output) end end data([5, 6, 10, 20, 40].map.with_index {|max_x, i| ["max_x: #{max_x}", [i + 1, max_x]] }.to_h) test("with scaling") do i, max_x = data plot = UnicodePlot.boxplot([1, 2, 3, 4, 5], xlim: [0, max_x]) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("boxplot/scale#{i}.txt").read, output) end test("multi-series") do plot = UnicodePlot.boxplot(["one", "two"], [ [1, 2, 3, 4, 5], [2, 3, 4, 5, 6, 7, 8, 9] ], title: "Multi-series", xlabel: "foo", color: :yellow) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("boxplot/multi1.txt").read, output) assert_same(plot, UnicodePlot.boxplot!(plot, "one more", [-1, 2, 3, 4, 11])) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("boxplot/multi2.txt").read, output) assert_same(plot, UnicodePlot.boxplot!(plot, [4, 2, 2.5, 4, 14], name: "last one")) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("boxplot/multi3.txt").read, output) end end end unicode-plot-0.0.4/test/test-plot.rb0000644000175100017510000000074713637627123016424 0ustar pravipravirequire 'stringio' class TestPlot < Test::Unit::TestCase sub_test_case("#render") do test("render to $stdout when no arguments") do sio = StringIO.new UnicodePlot.barplot(data: {a: 23, b: 37}).render(sio) begin save_stdout, $stdout = $stdout, StringIO.new UnicodePlot.barplot(data: {a: 23, b: 37}).render assert do sio.string == $stdout.string end ensure $stdout = save_stdout end end end end unicode-plot-0.0.4/test/test-histogram.rb0000644000175100017510000001030213637627123017427 0ustar pravipraviclass HistogramTest < Test::Unit::TestCase include Helper::Fixture include Helper::WithTerm sub_test_case("UnicodePlot.histogram") do def setup @x = fixture_path("randn.txt").read.lines.map(&:to_f) end test("default") do plot = UnicodePlot.histogram(@x) _, output = with_term { plot.render($stdout) } assert_equal("\n", output[-1]) assert_equal(fixture_path("histogram/default.txt").read, output.chomp) end test("nocolor") do plot = UnicodePlot.histogram(@x) output = StringIO.open do |sio| plot.render(sio) sio.close sio.string end assert_equal("\n", output[-1]) assert_equal(fixture_path("histogram/default_nocolor.txt").read, output.chomp) end test("losed: :left") do plot = UnicodePlot.histogram(@x, closed: :left) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("histogram/default.txt").read, output) end test("x 100") do x100 = @x.map {|a| a * 100 } plot = UnicodePlot.histogram(x100) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("histogram/default_1e2.txt").read, output) end test("x0.01") do x100 = @x.map {|a| a * 0.01 } plot = UnicodePlot.histogram(x100) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("histogram/default_1e-2.txt").read, output) end test("xscale: :log10") do plot = UnicodePlot.histogram(@x, xscale: :log10) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("histogram/log10.txt").read, output) end test("xscale: :log10 with custom label") do plot = UnicodePlot.histogram(@x, xscale: :log10, xlabel: "custom label") _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("histogram/log10_label.txt").read, output) end test("nbins: 5, closed: :right") do plot = UnicodePlot.histogram(@x, nbins: 5, closed: :right) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("histogram/hist_params.txt").read, output) end test("with title, xlabel, color, margin, and padding") do plot = UnicodePlot.histogram(@x, title: "My Histogram", xlabel: "Absolute Frequency", color: :blue, margin: 7, padding: 3) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("histogram/parameters1.txt").read, output) end test("with title, xlabel, color, margin, padding, and labels: false") do plot = UnicodePlot.histogram(@x, title: "My Histogram", xlabel: "Absolute Frequency", color: :blue, margin: 7, padding: 3, labels: false) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("histogram/parameters1_nolabels.txt").read, output) end test("with title, xlabel, color, border, symbol, and width") do plot = UnicodePlot.histogram(@x, title: "My Histogram", xlabel: "Absolute Frequency", color: :yellow, border: :solid, symbol: "=", width: 50) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("histogram/parameters2.txt").read, output) end test("issue #24") do assert_nothing_raised do UnicodePlot.histogram([1, 2]) end end end end unicode-plot-0.0.4/test/test-densityplot.rb0000644000175100017510000000244713637627123020023 0ustar pravipraviclass DensityplotTest < Test::Unit::TestCase include Helper::Fixture include Helper::WithTerm def setup randn = fixture_path("randn_1338_2000.txt").read.each_line.map(&:to_f) @dx = randn[0, 1000].compact @dy = randn[1000, 1000].compact assert_equal(1000, @dx.length) assert_equal(1000, @dy.length) end test("default") do plot = UnicodePlot.densityplot(@dx, @dy) dx2 = @dx.map {|x| x + 2 } dy2 = @dy.map {|y| y + 2 } assert_same(plot, UnicodePlot.densityplot!(plot, dx2, dy2)) _, output = with_term { plot.render($stdout, newline: false) } expected = fixture_path("scatterplot/densityplot.txt").read assert_equal(output, expected) end test("parameters") do plot = UnicodePlot.densityplot(@dx, @dy, name: "foo", color: :red, title: "Title", xlabel: "x") dx2 = @dx.map {|x| x + 2 } dy2 = @dy.map {|y| y + 2 } assert_same(plot, UnicodePlot.densityplot!(plot, dx2, dy2, name: "bar")) _, output = with_term { plot.render($stdout, newline: false) } expected = fixture_path("scatterplot/densityplot_parameters.txt").read assert_equal(output, expected) end end unicode-plot-0.0.4/test/test-lineplot.rb0000644000175100017510000001645313637627123017275 0ustar pravipravirequire 'date' class LineplotTest < Test::Unit::TestCase include Helper::Fixture include Helper::WithTerm sub_test_case("UnicodePlot.lineplot") do def setup @x = [-1, 1, 3, 3, -1] @y = [2, 0, -5, 2, -5] end test("ArgumentError") do assert_raise(ArgumentError) { UnicodePlot.lineplot() } assert_raise(ArgumentError) { UnicodePlot.lineplot(Math.method(:sin), @x, @y) } assert_raise(ArgumentError) { UnicodePlot.lineplot([], 0, 3) } assert_raise(ArgumentError) { UnicodePlot.lineplot([], @x) } assert_raise(ArgumentError) { UnicodePlot.lineplot([]) } assert_raise(ArgumentError) { UnicodePlot.lineplot([1, 2], [1, 2, 3]) } assert_raise(ArgumentError) { UnicodePlot.lineplot([1, 2, 3], [1, 2]) } assert_raise(ArgumentError) { UnicodePlot.lineplot([1, 2, 3], 1..2) } assert_raise(ArgumentError) { UnicodePlot.lineplot(1..3, [1, 2]) } assert_raise(ArgumentError) { UnicodePlot.lineplot(1..3, 1..2) } end sub_test_case("with numeric array") do test("default") do plot = UnicodePlot.lineplot(@x, @y) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/default.txt").read, output) plot = UnicodePlot.lineplot(@x.map(&:to_f), @y) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/default.txt").read, output) plot = UnicodePlot.lineplot(@x, @y.map(&:to_f)) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/default.txt").read, output) end test("y only") do plot = UnicodePlot.lineplot(@y) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/y_only.txt").read, output) end test("range") do plot = UnicodePlot.lineplot(6..10) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/range1.txt").read, output) plot = UnicodePlot.lineplot(11..15, 6..10) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/range2.txt").read, output) end end test("axis scaling and offsets") do plot = UnicodePlot.lineplot( @x.map {|x| x * 1e+3 + 15 }, @y.map {|y| y * 1e-3 - 15 } ) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/scale1.txt").read, output) plot = UnicodePlot.lineplot( @x.map {|x| x * 1e-3 + 15 }, @y.map {|y| y * 1e+3 - 15 } ) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/scale2.txt").read, output) tx = [-1.0, 2, 3, 700000] ty = [1.0, 2, 9, 4000000] plot = UnicodePlot.lineplot(tx, ty) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/scale3.txt").read, output) plot = UnicodePlot.lineplot(tx, ty, width: 5, height: 5) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/scale3_small.txt").read, output) end test("dates") do d = [*Date.new(1999, 12, 31) .. Date.new(2000, 1, 30)] v = 0.step(3*Math::PI, by: 3*Math::PI / 30) y1 = v.map(&Math.method(:sin)) plot = UnicodePlot.lineplot(d, y1, name: "sin", height: 5, xlabel: "date") _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/dates1.txt").read, output) y2 = v.map(&Math.method(:cos)) assert_same(plot, UnicodePlot.lineplot!(plot, d, y2, name: "cos")) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/dates2.txt").read, output) end test("line with intercept and slope") do plot = UnicodePlot.lineplot(@y) assert_same(plot, UnicodePlot.lineplot!(plot, -3, 1)) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/slope1.txt").read, output) assert_same(plot, UnicodePlot.lineplot!(plot, -4, 0.5, color: :cyan, name: "foo")) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/slope2.txt").read, output) end test("limits") do plot = UnicodePlot.lineplot(@x, @y, xlim: [-1.5, 3.5], ylim: [-5.5, 2.5]) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/limits.txt").read, output) end test("nogrid") do plot = UnicodePlot.lineplot(@x, @y, grid: false) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/nogrid.txt").read, output) end test("color: :blue") do plot = UnicodePlot.lineplot(@x, @y, color: :blue, name: "points1") _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/blue.txt").read, output) end test("parameters") do plot = UnicodePlot.lineplot(@x, @y, name: "points1", title: "Scatter", xlabel: "x", ylabel: "y") _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/parameters1.txt").read, output) assert_same(plot, UnicodePlot.lineplot!(plot, [0.5, 1, 1.5], name: "points2")) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/parameters2.txt").read, output) assert_same(plot, UnicodePlot.lineplot!(plot, [-0.5, 0.5, 1.5], [0.5, 1, 1.5], name: "points3")) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/parameters3.txt").read, output) output = StringIO.open do |sio| plot.render(sio, newline: false) sio.close sio.string end assert_equal(fixture_path("lineplot/nocolor.txt").read, output) end test("canvas size") do plot = UnicodePlot.lineplot(@x, @y, title: "Scatter", canvas: :dot, width: 10, height: 5) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("lineplot/canvassize.txt").read, output) end # TODO: functions # TODO: stairs end end unicode-plot-0.0.4/test/test-result.rb0000644000175100017510000000000013637627123016742 0ustar pravipraviunicode-plot-0.0.4/test/run-test.rb0000644000175100017510000000042013637627123016236 0ustar pravipravibase_dir = File.expand_path("../..", __FILE__) lib_dir = File.join(base_dir, "lib") test_dir = File.join(base_dir, "test") $LOAD_PATH.unshift(lib_dir) require "test/unit" require "unicode_plot" require_relative "helper" exit Test::Unit::AutoRunner.run(true, test_dir) unicode-plot-0.0.4/test/test-canvas.rb0000644000175100017510000001173313637627123016716 0ustar pravipravimodule CanvasTestCases include Helper::Fixture include Helper::WithTerm CANVAS_CLASSES = { ascii: UnicodePlot::AsciiCanvas, braille: UnicodePlot::BrailleCanvas, density: UnicodePlot::DensityCanvas, dot: UnicodePlot::DotCanvas }.freeze def self.included(mod) mod.module_eval do def setup # seed!(1337) # x1, y1 = rand(20), rand(20) # x2, y2 = rand(50), rand(50) @x1 = [0.226582, 0.504629, 0.933372, 0.522172, 0.505208, 0.0997825, 0.0443222, 0.722906, 0.812814, 0.245457, 0.11202, 0.000341996, 0.380001, 0.505277, 0.841177, 0.326561, 0.810857, 0.850456, 0.478053, 0.179066] @y1 = [0.44701, 0.219519, 0.677372, 0.746407, 0.735727, 0.574789, 0.538086, 0.848053, 0.110351, 0.796793, 0.987618, 0.801862, 0.365172, 0.469959, 0.306373, 0.704691, 0.540434, 0.405842, 0.805117, 0.014829] @x2 = [0.486366, 0.911547, 0.900818, 0.641951, 0.546221, 0.036135, 0.931522, 0.196704, 0.710775, 0.969291, 0.32546, 0.632833, 0.815576, 0.85278, 0.577286, 0.887004, 0.231596, 0.288337, 0.881386, 0.0952668, 0.609881, 0.393795, 0.84808, 0.453653, 0.746048, 0.924725, 0.100012, 0.754283, 0.769802, 0.997368, 0.0791693, 0.234334, 0.361207, 0.1037, 0.713739, 0.510725, 0.649145, 0.233949, 0.812092, 0.914384, 0.106925, 0.570467, 0.594956, 0.118498, 0.699827, 0.380363, 0.843282, 0.28761, 0.541469, 0.568466] @y2 = [0.417777, 0.774845, 0.00230619, 0.907031, 0.971138, 0.0524795, 0.957415, 0.328894, 0.530493, 0.193359, 0.768422, 0.783238, 0.607772, 0.0261113, 0.0849032, 0.461164, 0.613067, 0.785021, 0.988875, 0.131524, 0.0657328, 0.466453, 0.560878, 0.925428, 0.238691, 0.692385, 0.203687, 0.441146, 0.229352, 0.332706, 0.113543, 0.537354, 0.965718, 0.437026, 0.960983, 0.372294, 0.0226533, 0.593514, 0.657878, 0.450696, 0.436169, 0.445539, 0.0534673, 0.0882236, 0.361795, 0.182991, 0.156862, 0.734805, 0.166076, 0.1172] canvas_class = CANVAS_CLASSES[self.class::CANVAS_NAME] @canvas = canvas_class.new(40, 10, origin_x: 0, origin_y: 0, plot_width: 1, plot_height: 1) end test("empty") do if self.class::CANVAS_NAME == :braille _, output = with_term { @canvas.show($stdout) } assert_equal(fixture_path("canvas/empty_braille_show.txt").read, output) else _, output = with_term { @canvas.show($stdout) } assert_equal(fixture_path("canvas/empty_show.txt").read, output) end end sub_test_case("with drawing") do def setup super @canvas.line!(0, 0, 1, 1, :blue) @canvas.points!(@x1, @y1, :white) @canvas.pixel!(2, 4, :cyan) @canvas.points!(@x2, @y2, :red) @canvas.line!(0, 1, 0.5, 0, :green) @canvas.point!(0.05, 0.3, :cyan) @canvas.lines!([1, 2], [2, 1]) @canvas.line!(0, 0, 9, 9999, :yellow) @canvas.line!(0, 0, 1, 1, :blue) @canvas.line!(0.1, 0.7, 0.9, 0.6, :red) end test("print_row") do _, output = with_term { @canvas.print_row($stdout, 2) } assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_printrow.txt").read, output) end test("print") do _, output = with_term { @canvas.print($stdout) } assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_print.txt").read, output) end test("print_nocolor") do _, output = with_term(false) { @canvas.print($stdout) } assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_print_nocolor.txt").read, output) end test("sow") do _, output = with_term { @canvas.show($stdout) } assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_show.txt").read, output) end test("show_nocolor") do _, output = with_term(false) { @canvas.show($stdout) } assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_show_nocolor.txt").read, output) end end end end end class BrailleCanvasTest < Test::Unit::TestCase CANVAS_NAME = :braille include CanvasTestCases end class AsciiCanvasTest < Test::Unit::TestCase CANVAS_NAME = :ascii include CanvasTestCases end class DensityCanvasTest < Test::Unit::TestCase CANVAS_NAME = :density include CanvasTestCases end class DotCanvasTest < Test::Unit::TestCase CANVAS_NAME = :dot include CanvasTestCases end unicode-plot-0.0.4/test/test-scatterplot.rb0000644000175100017510000001151113637627123020001 0ustar pravipraviclass ScatterplotTest < Test::Unit::TestCase include Helper::Fixture include Helper::WithTerm def setup @x = [-1, 1, 3, 3, -1] @y = [2, 0, -5, 2, -5] end test("errors") do assert_raise(ArgumentError) do UnicodePlot.scatterplot() end assert_raise(ArgumentError) do UnicodePlot.scatterplot([1, 2], [1, 2, 3]) end assert_raise(ArgumentError) do UnicodePlot.scatterplot([1, 2, 3], [1, 2]) end assert_raise(ArgumentError) do UnicodePlot.scatterplot(1..3, 1..2) end end test("default") do plot = UnicodePlot.scatterplot(@x, @y) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/default.txt").read, output) end test("y only") do plot = UnicodePlot.scatterplot(@y) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/y_only.txt").read, output) end test("one range") do plot = UnicodePlot.scatterplot(6..10) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/range1.txt").read, output) end test("two ranges") do plot = UnicodePlot.scatterplot(11..15, 6..10) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/range2.txt").read, output) end test("scale1") do x = @x.map {|a| a * 1e3 + 15 } y = @y.map {|a| a * 1e-3 - 15 } plot = UnicodePlot.scatterplot(x, y) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/scale1.txt").read, output) end test("scale2") do x = @x.map {|a| a * 1e-3 + 15 } y = @y.map {|a| a * 1e3 - 15 } plot = UnicodePlot.scatterplot(x, y) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/scale2.txt").read, output) end test("scale3") do miny = -1.2796649117521434e218 maxy = -miny plot = UnicodePlot.scatterplot([1], [miny], xlim: [1, 1], ylim: [miny, maxy]) _, output = with_term { plot.render($stdout, newline: false) } expected = fixture_path("scatterplot/scale3.txt").read assert_equal(expected, output) end test("limits") do plot = UnicodePlot.scatterplot(@x, @y, xlim: [-1.5, 3.5], ylim: [-5.5, 2.5]) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/limits.txt").read, output) end test("nogrid") do plot = UnicodePlot.scatterplot(@x, @y, grid: false) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/nogrid.txt").read, output) end test("blue") do plot = UnicodePlot.scatterplot(@x, @y, color: :blue, name: "points1") _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/blue.txt").read, output) end test("parameters") do plot = UnicodePlot.scatterplot(@x, @y, name: "points1", title: "Scatter", xlabel: "x", ylabel: "y") _, output = with_term { plot.render($stdout, newline: false) } expected = fixture_path("scatterplot/parameters1.txt").read assert_equal(expected, output) assert_same(plot, UnicodePlot.scatterplot!(plot, [0.5, 1, 1.5], name: "points2")) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/parameters2.txt").read, output) assert_same(plot, UnicodePlot.scatterplot!(plot, [-0.5, 0.5, 1.5], [0.5, 1, 1.5], name: "points3")) _, output = with_term { plot.render($stdout, newline: false) } assert_equal(fixture_path("scatterplot/parameters3.txt").read, output) output = StringIO.open do |sio| plot.render(sio, newline: false) sio.close sio.string end assert_equal(fixture_path("scatterplot/nocolor.txt").read, output) end test("canvas size") do plot = UnicodePlot.scatterplot(@x, @y, title: "Scatter", canvas: :dot, width: 10, height: 5) _, output = with_term { plot.render($stdout, newline: false) } expected = fixture_path("scatterplot/canvassize.txt").read assert_equal(expected, output) end end unicode-plot-0.0.4/Gemfile0000644000175100017510000000005013637627123014443 0ustar pravipravisource "https://rubygems.org/" gemspec unicode-plot-0.0.4/LICENSE.txt0000644000175100017510000000207513637627123015004 0ustar pravipraviThe MIT License (MIT) Copyright © 2019 Kenta Murata Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.