unicode-plot-0.0.4/ 0000755 0001751 0001751 00000000000 13637627123 013155 5 ustar pravi pravi unicode-plot-0.0.4/unicode_plot.gemspec 0000644 0001751 0001751 00000002322 13637627123 017205 0 ustar pravi pravi lib = 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/Rakefile 0000644 0001751 0001751 00000000354 13637627123 014624 0 ustar pravi pravi require "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/ 0000755 0001751 0001751 00000000000 13637627123 013723 5 ustar pravi pravi unicode-plot-0.0.4/lib/unicode_plot/ 0000755 0001751 0001751 00000000000 13637627123 016407 5 ustar pravi pravi unicode-plot-0.0.4/lib/unicode_plot/lineplot.rb 0000644 0001751 0001751 00000004357 13637627123 020573 0 ustar pravi pravi require '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.rb 0000644 0001751 0001751 00000001160 13637627123 021053 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000002666 13637627123 021612 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000000500 13637627123 021305 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000003261 13637627123 021703 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000006353 13637627123 017721 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000001737 13637627123 022502 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000011275 13637627123 020215 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000012124 13637627123 020423 0 ustar pravi pravi require '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.rb 0000644 0001751 0001751 00000000275 13637627123 020425 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000005322 13637627123 020721 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000002742 13637627123 020101 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000001756 13637627123 020262 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000007326 13637627123 020407 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000004633 13637627123 022011 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000016524 13637627123 020552 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000003414 13637627123 020733 0 ustar pravi pravi require '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.rb 0000644 0001751 0001751 00000002347 13637627123 021306 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000003114 13637627123 021745 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000006257 13637627123 021371 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000001232 13637627123 016732 0 ustar pravi pravi require '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.md 0000644 0001751 0001751 00000003555 13637627123 014444 0 ustar pravi pravi # 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/ 0000755 0001751 0001751 00000000000 13637627123 014134 5 ustar pravi pravi unicode-plot-0.0.4/test/helper.rb 0000644 0001751 0001751 00000000106 13637627123 015735 0 ustar pravi pravi require_relative "helper/fixture"
require_relative "helper/with_term"
unicode-plot-0.0.4/test/test-barplot.rb 0000644 0001751 0001751 00000014251 13637627123 017104 0 ustar pravi pravi class 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/ 0000755 0001751 0001751 00000000000 13637627123 015413 5 ustar pravi pravi unicode-plot-0.0.4/test/helper/fixture.rb 0000644 0001751 0001751 00000000407 13637627123 017427 0 ustar pravi pravi require '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.rb 0000644 0001751 0001751 00000000663 13637627123 017747 0 ustar pravi pravi require '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.rb 0000644 0001751 0001751 00000005762 13637627123 017137 0 ustar pravi pravi class 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.rb 0000644 0001751 0001751 00000000747 13637627123 016424 0 ustar pravi pravi require '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.rb 0000644 0001751 0001751 00000010302 13637627123 017427 0 ustar pravi pravi class 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.rb 0000644 0001751 0001751 00000002447 13637627123 020023 0 ustar pravi pravi class 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.rb 0000644 0001751 0001751 00000016453 13637627123 017275 0 ustar pravi pravi require '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.rb 0000644 0001751 0001751 00000000000 13637627123 016742 0 ustar pravi pravi unicode-plot-0.0.4/test/run-test.rb 0000644 0001751 0001751 00000000420 13637627123 016236 0 ustar pravi pravi base_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.rb 0000644 0001751 0001751 00000011733 13637627123 016716 0 ustar pravi pravi module 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.rb 0000644 0001751 0001751 00000011511 13637627123 020001 0 ustar pravi pravi class 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/Gemfile 0000644 0001751 0001751 00000000050 13637627123 014443 0 ustar pravi pravi source "https://rubygems.org/"
gemspec
unicode-plot-0.0.4/LICENSE.txt 0000644 0001751 0001751 00000002075 13637627123 015004 0 ustar pravi pravi The 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.