8 Commits

Author SHA1 Message Date
kojix2
1a5609022f v0.3.1 2020-12-04 13:56:06 +09:00
kojix2
e11b5047af Add yx format
A new format option for barplot and lineplot.
Use when the first column is the label and the second column is the value.
2020-12-02 18:24:22 +09:00
kojix2
9658bfa71c Remove needless space 2020-11-25 17:34:50 +09:00
kojix2
f2bd99ed2e Add the Processing module (tentative) 2020-11-25 17:31:41 +09:00
kojix2
9849898cb1 Change the test directory from uplot to youplot 2020-11-25 17:21:01 +09:00
kojix2
1a3ad9553c Rename Preprocessing to DSVReader
* DSV stands for Delimiter-separated values.
* Preprocessing is too vague.
2020-11-25 17:19:07 +09:00
kojix2
ccf232a742 Pass standard input to standard output first 2020-11-25 15:52:24 +09:00
kojix2
eb13f2583f Support for encodings other than UTF-8 2020-11-23 23:52:14 +09:00
12 changed files with 122 additions and 58 deletions

View File

@@ -2,7 +2,7 @@
require 'unicode_plot' require 'unicode_plot'
require 'youplot/version' require 'youplot/version'
require 'youplot/preprocessing' require 'youplot/dsv_reader'
require 'youplot/command' require 'youplot/command'
module YouPlot module YouPlot

View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
module YouPlot
# plotting functions.
module Backends
module Processing
module_function
def count_values(arr)
# tally was added in Ruby 2.7
if Enumerable.method_defined? :tally
arr.tally
else
# https://github.com/marcandre/backports
arr.each_with_object(Hash.new(0)) { |item, res| res[item] += 1 }
.tap { |h| h.default = nil }
end
.sort { |a, b| a[1] <=> b[1] }
.reverse
.transpose
end
end
end
end

View File

@@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'processing'
require 'unicode_plot' require 'unicode_plot'
module YouPlot module YouPlot
@@ -8,23 +9,33 @@ module YouPlot
module UnicodePlotBackend module UnicodePlotBackend
module_function module_function
def barplot(data, params, count: false) def barplot(data, params, fmt = nil, count: false)
headers = data.headers headers = data.headers
series = data.series series = data.series
# `uplot count` # `uplot count`
if count if count
series = Preprocessing.count_values(series[0]) series = Processing.count_values(series[0])
params.title = headers[0] if headers params.title = headers[0] if headers
end end
if series.size == 1 if series.size == 1
# If there is only one series, use the line number for label. # If there is only one series.use the line number for label.
params.title ||= headers[0] if headers params.title ||= headers[0] if headers
labels = Array.new(series[0].size) { |i| (i + 1).to_s } labels = Array.new(series[0].size) { |i| (i + 1).to_s }
values = series[0].map(&:to_f) values = series[0].map(&:to_f)
else else
params.title ||= headers[1] if headers # If there are 2 or more series...
labels = series[0] if fmt == 'yx'
values = series[1].map(&:to_f) # assume that the first 2 series are the y and x series respectively.
x_col = 1
y_col = 0
else
# assume that the first 2 series are the x and y series respectively.
x_col = 0
y_col = 1
end
params.title ||= headers[y_col] if headers
labels = series[x_col]
values = series[y_col].map(&:to_f)
end end
UnicodePlot.barplot(labels, values, **params.to_hc) UnicodePlot.barplot(labels, values, **params.to_hc)
end end
@@ -37,7 +48,7 @@ module YouPlot
UnicodePlot.histogram(values, **params.to_hc) UnicodePlot.histogram(values, **params.to_hc)
end end
def line(data, params) def line(data, params, fmt = nil)
headers = data.headers headers = data.headers
series = data.series series = data.series
if series.size == 1 if series.size == 1
@@ -46,14 +57,22 @@ module YouPlot
y = series[0].map(&:to_f) y = series[0].map(&:to_f)
UnicodePlot.lineplot(y, **params.to_hc) UnicodePlot.lineplot(y, **params.to_hc)
else else
# If there are 2 or more series, # If there are 2 or more series...
# assume that the first 2 series are the x and y series respectively. if fmt == 'yx'
if headers # assume that the first 2 series are the y and x series respectively.
params.xlabel ||= headers[0] x_col = 1
params.ylabel ||= headers[1] y_col = 0
else
# assume that the first 2 series are the x and y series respectively.
x_col = 0
y_col = 1
end end
x = series[0].map(&:to_f) if headers
y = series[1].map(&:to_f) params.xlabel ||= headers[x_col]
params.ylabel ||= headers[y_col]
end
x = series[x_col].map(&:to_f)
y = series[y_col].map(&:to_f)
UnicodePlot.lineplot(x, y, **params.to_hc) UnicodePlot.lineplot(x, y, **params.to_hc)
end end
end end
@@ -102,6 +121,8 @@ module YouPlot
plot_xyy(data, method1, params) plot_xyy(data, method1, params)
when 'xyxy' when 'xyxy'
plot_xyxy(data, method1, params) plot_xyxy(data, method1, params)
when 'yx'
raise "Incorrect format: #{fmt}"
else else
raise "Unknown format: #{fmt}" raise "Unknown format: #{fmt}"
end end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'preprocessing' require_relative 'dsv_reader'
require_relative 'command/parser' require_relative 'command/parser'
# FIXME # FIXME
@@ -30,6 +30,7 @@ module YouPlot
pass = parser.pass pass = parser.pass
output = parser.output output = parser.output
fmt = parser.fmt fmt = parser.fmt
@encoding = parser.encoding
@debug = parser.debug @debug = parser.debug
if command == :colors if command == :colors
@@ -39,18 +40,37 @@ module YouPlot
# Sometimes the input file does not end with a newline code. # Sometimes the input file does not end with a newline code.
while (input = Kernel.gets(nil)) while (input = Kernel.gets(nil))
input.freeze
@data = Preprocessing.input(input, delimiter, headers, transpose) # Pass the input to subsequent pipelines
case pass
when IO
pass.print(input)
else
if pass
File.open(pass, 'w') do |f|
f.print(input)
end
end
end
@data = if @encoding
input2 = input.dup.force_encoding(@encoding).encode('utf-8')
DSVReader.input(input2, delimiter, headers, transpose)
else
DSVReader.input(input, delimiter, headers, transpose)
end
pp @data if @debug pp @data if @debug
plot = case command plot = case command
when :bar, :barplot when :bar, :barplot
@backend.barplot(data, params) @backend.barplot(data, params, fmt)
when :count, :c when :count, :c
@backend.barplot(data, params, count: true) @backend.barplot(data, params, count: true)
when :hist, :histogram when :hist, :histogram
@backend.histogram(data, params) @backend.histogram(data, params)
when :line, :lineplot when :line, :lineplot
@backend.line(data, params) @backend.line(data, params, fmt)
when :lines, :lineplots when :lines, :lineplots
@backend.lines(data, params, fmt) @backend.lines(data, params, fmt)
when :scatter, :s when :scatter, :s
@@ -72,16 +92,6 @@ module YouPlot
end end
end end
case pass
when IO
pass.print(input)
else
if pass
File.open(pass, 'w') do |f|
f.print(input)
end
end
end
end end
end end
end end

View File

@@ -8,7 +8,7 @@ module YouPlot
class Parser class Parser
attr_reader :command, :params, attr_reader :command, :params,
:delimiter, :transpose, :headers, :pass, :output, :fmt, :delimiter, :transpose, :headers, :pass, :output, :fmt,
:color_names, :debug :color_names, :encoding, :debug
def initialize def initialize
@command = nil @command = nil
@@ -20,6 +20,7 @@ module YouPlot
@pass = false @pass = false
@output = $stderr @output = $stderr
@fmt = 'xyy' @fmt = 'xyy'
@encoding = nil
@debug = false @debug = false
@color_names = false @color_names = false
end end
@@ -78,8 +79,8 @@ module YouPlot
opt.on('--[no-]labels', TrueClass, 'hide the labels') do |v| opt.on('--[no-]labels', TrueClass, 'hide the labels') do |v|
params.labels = v params.labels = v
end end
opt.on('--fmt VAL', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...', 'xyy : header is like x, y1, y2, y2, y3...') do |v| opt.on('--encoding VAL', String, 'Specify the input encoding') do |v|
@fmt = v @encoding = v
end end
# Optparse adds the help option, but it doesn't show up in usage. # Optparse adds the help option, but it doesn't show up in usage.
# This is why you need the code below. # This is why you need the code below.
@@ -161,6 +162,9 @@ module YouPlot
parser.on_head('--xscale VAL', String, 'axis scaling') do |v| parser.on_head('--xscale VAL', String, 'axis scaling') do |v|
params.xscale = v params.xscale = v
end end
parser.on_head('--fmt VAL', String, 'xy : header is like x, y...', 'yx : header is like y, x...') do |v|
@fmt = v
end
when :count, :c when :count, :c
parser.on_head('--symbol VAL', String, 'character to be used to plot the bars') do |v| parser.on_head('--symbol VAL', String, 'character to be used to plot the bars') do |v|
@@ -188,6 +192,9 @@ module YouPlot
parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v| parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v|
params.ylim = v.take(2) params.ylim = v.take(2)
end end
parser.on_head('--fmt VAL', String, 'xy : header is like x, y...', 'yx : header is like y, x...') do |v|
@fmt = v
end
when :lineplots, :lines when :lineplots, :lines
parser.on_head('--canvas VAL', String) do |v| parser.on_head('--canvas VAL', String) do |v|
@@ -199,6 +206,9 @@ module YouPlot
parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v| parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v|
params.ylim = v.take(2) params.ylim = v.take(2)
end end
parser.on_head('--fmt VAL', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...', 'xyy : header is like x, y1, y2, y2, y3...') do |v|
@fmt = v
end
when :scatter, :s when :scatter, :s
parser.on_head('--canvas VAL', String) do |v| parser.on_head('--canvas VAL', String) do |v|
@@ -210,6 +220,9 @@ module YouPlot
parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v| parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v|
params.ylim = v.take(2) params.ylim = v.take(2)
end end
parser.on_head('--fmt VAL', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...', 'xyy : header is like x, y1, y2, y2, y3...') do |v|
@fmt = v
end
when :density, :d when :density, :d
parser.on_head('--grid', TrueClass) do |v| parser.on_head('--grid', TrueClass) do |v|
@@ -221,6 +234,9 @@ module YouPlot
parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v| parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v|
params.ylim = v.take(2) params.ylim = v.take(2)
end end
parser.on('--fmt VAL', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...', 'xyy : header is like x, y1, y2, y2, y3...') do |v|
@fmt = v
end
when :boxplot, :box when :boxplot, :box
parser.on_head('--xlim VAL', Array, 'plotting range for the x coordinate') do |v| parser.on_head('--xlim VAL', Array, 'plotting range for the x coordinate') do |v|

View File

@@ -3,7 +3,8 @@
require 'csv' require 'csv'
module YouPlot module YouPlot
module Preprocessing # Read and interpret Delimiter-separated values format file or stream.
module DSVReader
module_function module_function
def input(input, delimiter, headers, transpose) def input(input, delimiter, headers, transpose)
@@ -68,19 +69,5 @@ module YouPlot
transpose2(arr) transpose2(arr)
end end
end end
def count_values(arr)
# tally was added in Ruby 2.7
if Enumerable.method_defined? :tally
arr.tally
else
# https://github.com/marcandre/backports
arr.each_with_object(Hash.new(0)) { |item, res| res[item] += 1 }
.tap { |h| h.default = nil }
end
.sort { |a, b| a[1] <=> b[1] }
.reverse
.transpose
end
end end
end end

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
module YouPlot module YouPlot
VERSION = '0.3.0' VERSION = '0.3.1'
end end

View File

@@ -0,0 +1,11 @@
# frozen_string_literal: true
require_relative '../../test_helper'
class YouPlotCommandTest < Test::Unit::TestCase
test :count_values do
@m = YouPlot::Backends::Processing
assert_equal([%i[a b c], [3, 2, 1]], @m.count_values(%i[a a a b b c]))
assert_equal([%i[c b a], [3, 2, 1]], @m.count_values(%i[a b b c c c]))
end
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative '../test_helper' require_relative '../../test_helper'
class YouPlotPlotTest < Test::Unit::TestCase class YouPlotPlotTest < Test::Unit::TestCase
end end

View File

@@ -2,9 +2,9 @@
require_relative '../test_helper' require_relative '../test_helper'
class YouPlotPreprocessingTest < Test::Unit::TestCase class YouPlotDSVReaderTest < Test::Unit::TestCase
def setup def setup
@m = YouPlot::Preprocessing @m = YouPlot::DSVReader
end end
test :transpose2 do test :transpose2 do
@@ -124,9 +124,4 @@ class YouPlotPreprocessingTest < Test::Unit::TestCase
[2, 4], [2, 4],
[3, 5, 6]], false, false)) [3, 5, 6]], false, false))
end end
test :count_values do
assert_equal([%i[a b c], [3, 2, 1]], @m.count_values(%i[a a a b b c]))
assert_equal([%i[c b a], [3, 2, 1]], @m.count_values(%i[a b b c c c]))
end
end end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'test_helper' require_relative 'test_helper'
class YouPlotTest < Test::Unit::TestCase class YouPlotTest < Test::Unit::TestCase
def test_that_it_has_a_version_number def test_that_it_has_a_version_number