mirror of
https://github.com/red-data-tools/YouPlot.git
synced 2025-09-19 02:18:08 +08:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1a5609022f | ||
![]() |
e11b5047af | ||
![]() |
9658bfa71c | ||
![]() |
f2bd99ed2e | ||
![]() |
9849898cb1 | ||
![]() |
1a3ad9553c | ||
![]() |
ccf232a742 | ||
![]() |
eb13f2583f |
@@ -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
|
||||||
|
24
lib/youplot/backends/processing.rb
Normal file
24
lib/youplot/backends/processing.rb
Normal 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
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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|
|
||||||
|
@@ -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
|
@@ -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
|
||||||
|
11
test/youplot/backends/processing_test.rb
Normal file
11
test/youplot/backends/processing_test.rb
Normal 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
|
@@ -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
|
@@ -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
|
@@ -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
|
Reference in New Issue
Block a user