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

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
require_relative 'preprocessing'
require_relative 'dsv_reader'
require_relative 'command/parser'
# FIXME
@@ -30,6 +30,7 @@ module YouPlot
pass = parser.pass
output = parser.output
fmt = parser.fmt
@encoding = parser.encoding
@debug = parser.debug
if command == :colors
@@ -39,18 +40,37 @@ module YouPlot
# Sometimes the input file does not end with a newline code.
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
plot = case command
when :bar, :barplot
@backend.barplot(data, params)
@backend.barplot(data, params, fmt)
when :count, :c
@backend.barplot(data, params, count: true)
when :hist, :histogram
@backend.histogram(data, params)
when :line, :lineplot
@backend.line(data, params)
@backend.line(data, params, fmt)
when :lines, :lineplots
@backend.lines(data, params, fmt)
when :scatter, :s
@@ -72,16 +92,6 @@ module YouPlot
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

View File

@@ -8,7 +8,7 @@ module YouPlot
class Parser
attr_reader :command, :params,
:delimiter, :transpose, :headers, :pass, :output, :fmt,
:color_names, :debug
:color_names, :encoding, :debug
def initialize
@command = nil
@@ -20,6 +20,7 @@ module YouPlot
@pass = false
@output = $stderr
@fmt = 'xyy'
@encoding = nil
@debug = false
@color_names = false
end
@@ -78,8 +79,8 @@ module YouPlot
opt.on('--[no-]labels', TrueClass, 'hide the labels') do |v|
params.labels = v
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|
@fmt = v
opt.on('--encoding VAL', String, 'Specify the input encoding') do |v|
@encoding = v
end
# Optparse adds the help option, but it doesn't show up in usage.
# This is why you need the code below.
@@ -161,6 +162,9 @@ module YouPlot
parser.on_head('--xscale VAL', String, 'axis scaling') do |v|
params.xscale = v
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
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|
params.ylim = v.take(2)
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
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|
params.ylim = v.take(2)
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
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|
params.ylim = v.take(2)
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
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|
params.ylim = v.take(2)
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
parser.on_head('--xlim VAL', Array, 'plotting range for the x coordinate') do |v|

View File

@@ -3,7 +3,8 @@
require 'csv'
module YouPlot
module Preprocessing
# Read and interpret Delimiter-separated values format file or stream.
module DSVReader
module_function
def input(input, delimiter, headers, transpose)
@@ -68,19 +69,5 @@ module YouPlot
transpose2(arr)
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

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true
module YouPlot
VERSION = '0.3.0'
VERSION = '0.3.1'
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
require_relative '../test_helper'
require_relative '../../test_helper'
class YouPlotPlotTest < Test::Unit::TestCase
end

View File

@@ -2,9 +2,9 @@
require_relative '../test_helper'
class YouPlotPreprocessingTest < Test::Unit::TestCase
class YouPlotDSVReaderTest < Test::Unit::TestCase
def setup
@m = YouPlot::Preprocessing
@m = YouPlot::DSVReader
end
test :transpose2 do
@@ -124,9 +124,4 @@ class YouPlotPreprocessingTest < Test::Unit::TestCase
[2, 4],
[3, 5, 6]], false, false))
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

View File

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