mirror of
https://github.com/red-data-tools/YouPlot.git
synced 2025-05-07 23:51:12 +08:00
Provides a mechanism to switch back-end libraries
This commit is contained in:
parent
b72f982618
commit
7a08d6bab9
@ -3,7 +3,6 @@
|
|||||||
require 'unicode_plot'
|
require 'unicode_plot'
|
||||||
require 'youplot/version'
|
require 'youplot/version'
|
||||||
require 'youplot/preprocessing'
|
require 'youplot/preprocessing'
|
||||||
require 'youplot/plot'
|
|
||||||
require 'youplot/command'
|
require 'youplot/command'
|
||||||
|
|
||||||
module YouPlot
|
module YouPlot
|
||||||
|
167
lib/youplot/backends/unicode_plot_backend.rb
Normal file
167
lib/youplot/backends/unicode_plot_backend.rb
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'unicode_plot'
|
||||||
|
|
||||||
|
module YouPlot
|
||||||
|
# plotting functions.
|
||||||
|
module Backends
|
||||||
|
module UnicodePlotBackend
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def barplot(data, params, count: false)
|
||||||
|
headers = data.headers
|
||||||
|
series = data.series
|
||||||
|
# `uplot count`
|
||||||
|
if count
|
||||||
|
series = Preprocessing.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.
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
UnicodePlot.barplot(labels, values, **params.to_hc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def histogram(data, params)
|
||||||
|
headers = data.headers
|
||||||
|
series = data.series
|
||||||
|
params.title ||= data.headers[0] if headers
|
||||||
|
values = series[0].map(&:to_f)
|
||||||
|
UnicodePlot.histogram(values, **params.to_hc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def line(data, params)
|
||||||
|
headers = data.headers
|
||||||
|
series = data.series
|
||||||
|
if series.size == 1
|
||||||
|
# If there is only one series, it is assumed to be sequential data.
|
||||||
|
params.ylabel ||= headers[0] if headers
|
||||||
|
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]
|
||||||
|
end
|
||||||
|
x = series[0].map(&:to_f)
|
||||||
|
y = series[1].map(&:to_f)
|
||||||
|
UnicodePlot.lineplot(x, y, **params.to_hc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_method2(method1)
|
||||||
|
"#{method1}!".to_sym
|
||||||
|
end
|
||||||
|
|
||||||
|
def plot_xyy(data, method1, params)
|
||||||
|
headers = data.headers
|
||||||
|
series = data.series
|
||||||
|
method2 = get_method2(method1)
|
||||||
|
series.map! { |s| s.map(&:to_f) }
|
||||||
|
if headers
|
||||||
|
params.name ||= headers[1]
|
||||||
|
params.xlabel ||= headers[0]
|
||||||
|
end
|
||||||
|
params.ylim ||= series[1..-1].flatten.minmax # why need?
|
||||||
|
plot = UnicodePlot.public_send(method1, series[0], series[1], **params.to_hc)
|
||||||
|
2.upto(series.size - 1) do |i|
|
||||||
|
UnicodePlot.public_send(method2, plot, series[0], series[i], name: headers&.[](i))
|
||||||
|
end
|
||||||
|
plot
|
||||||
|
end
|
||||||
|
|
||||||
|
def plot_xyxy(data, method1, params)
|
||||||
|
headers = data.headers
|
||||||
|
series = data.series
|
||||||
|
method2 = get_method2(method1)
|
||||||
|
series.map! { |s| s.map(&:to_f) }
|
||||||
|
series = series.each_slice(2).to_a
|
||||||
|
params.name ||= headers[0] if headers
|
||||||
|
params.xlim = series.map(&:first).flatten.minmax # why need?
|
||||||
|
params.ylim = series.map(&:last).flatten.minmax # why need?
|
||||||
|
x1, y1 = series.shift
|
||||||
|
plot = UnicodePlot.public_send(method1, x1, y1, **params.to_hc)
|
||||||
|
series.each_with_index do |(xi, yi), i|
|
||||||
|
UnicodePlot.public_send(method2, plot, xi, yi, name: headers&.[]((i + 1) * 2))
|
||||||
|
end
|
||||||
|
plot
|
||||||
|
end
|
||||||
|
|
||||||
|
def plot_fmt(data, fmt, method1, params)
|
||||||
|
case fmt
|
||||||
|
when 'xyy'
|
||||||
|
plot_xyy(data, method1, params)
|
||||||
|
when 'xyxy'
|
||||||
|
plot_xyxy(data, method1, params)
|
||||||
|
else
|
||||||
|
raise "Unknown format: #{fmt}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def lines(data, params, fmt = 'xyy')
|
||||||
|
check_series_size(data, fmt)
|
||||||
|
plot_fmt(data, fmt, :lineplot, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def scatter(data, params, fmt = 'xyy')
|
||||||
|
check_series_size(data, fmt)
|
||||||
|
plot_fmt(data, fmt, :scatterplot, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def density(data, params, fmt = 'xyy')
|
||||||
|
check_series_size(data, fmt)
|
||||||
|
plot_fmt(data, fmt, :densityplot, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def boxplot(data, params)
|
||||||
|
headers = data.headers
|
||||||
|
series = data.series
|
||||||
|
headers ||= (1..series.size).map(&:to_s)
|
||||||
|
series.map! { |s| s.map(&:to_f) }
|
||||||
|
UnicodePlot.boxplot(headers, series, **params.to_hc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def colors(color_names = false)
|
||||||
|
UnicodePlot::StyledPrinter::TEXT_COLORS.each do |k, v|
|
||||||
|
print v
|
||||||
|
print k
|
||||||
|
unless color_names
|
||||||
|
print "\t"
|
||||||
|
print ' ●'
|
||||||
|
end
|
||||||
|
print "\033[0m"
|
||||||
|
print "\t"
|
||||||
|
end
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_series_size(data, fmt)
|
||||||
|
series = data.series
|
||||||
|
if series.size == 1
|
||||||
|
warn 'youplot: There is only one series of input data. Please check the delimiter.'
|
||||||
|
warn ''
|
||||||
|
warn " Headers: \e[35m#{data.headers.inspect}\e[0m"
|
||||||
|
warn " The first item is: \e[35m\"#{series[0][0]}\"\e[0m"
|
||||||
|
warn " The last item is : \e[35m\"#{series[0][-1]}\"\e[0m"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
if fmt == 'xyxy' && series.size.odd?
|
||||||
|
warn 'YouPlot: In the xyxy format, the number of series must be even.'
|
||||||
|
warn ''
|
||||||
|
warn " Number of series: \e[35m#{series.size}\e[0m"
|
||||||
|
warn " Headers: \e[35m#{data.headers.inspect}\e[0m"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -3,6 +3,9 @@
|
|||||||
require_relative 'preprocessing'
|
require_relative 'preprocessing'
|
||||||
require_relative 'command/parser'
|
require_relative 'command/parser'
|
||||||
|
|
||||||
|
# FIXME
|
||||||
|
require_relative 'backends/unicode_plot_backend'
|
||||||
|
|
||||||
module YouPlot
|
module YouPlot
|
||||||
Data = Struct.new(:headers, :series)
|
Data = Struct.new(:headers, :series)
|
||||||
|
|
||||||
@ -11,8 +14,9 @@ module YouPlot
|
|||||||
attr_reader :data, :fmt, :parser
|
attr_reader :data, :fmt, :parser
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@params = Params.new
|
@params = Params.new
|
||||||
@parser = Parser.new
|
@parser = Parser.new
|
||||||
|
@backend = YouPlot::Backends::UnicodePlotBackend
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
@ -28,7 +32,7 @@ module YouPlot
|
|||||||
@debug = parser.debug
|
@debug = parser.debug
|
||||||
|
|
||||||
if command == :colors
|
if command == :colors
|
||||||
Plot.colors(parser.color_names)
|
@backend.colors(parser.color_names)
|
||||||
exit
|
exit
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -39,21 +43,21 @@ module YouPlot
|
|||||||
pp @data if @debug
|
pp @data if @debug
|
||||||
plot = case command
|
plot = case command
|
||||||
when :bar, :barplot
|
when :bar, :barplot
|
||||||
Plot.barplot(data, params)
|
@backend.barplot(data, params)
|
||||||
when :count, :c
|
when :count, :c
|
||||||
Plot.barplot(data, params, count: true)
|
@backend.barplot(data, params, count: true)
|
||||||
when :hist, :histogram
|
when :hist, :histogram
|
||||||
Plot.histogram(data, params)
|
@backend.histogram(data, params)
|
||||||
when :line, :lineplot
|
when :line, :lineplot
|
||||||
Plot.line(data, params)
|
@backend.line(data, params)
|
||||||
when :lines, :lineplots
|
when :lines, :lineplots
|
||||||
Plot.lines(data, params, fmt)
|
@backend.lines(data, params, fmt)
|
||||||
when :scatter, :s
|
when :scatter, :s
|
||||||
Plot.scatter(data, params, fmt)
|
@backend.scatter(data, params, fmt)
|
||||||
when :density, :d
|
when :density, :d
|
||||||
Plot.density(data, params, fmt)
|
@backend.density(data, params, fmt)
|
||||||
when :box, :boxplot
|
when :box, :boxplot
|
||||||
Plot.boxplot(data, params)
|
@backend.boxplot(data, params)
|
||||||
else
|
else
|
||||||
raise "unrecognized plot_type: #{command}"
|
raise "unrecognized plot_type: #{command}"
|
||||||
end
|
end
|
||||||
|
@ -1,165 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'unicode_plot'
|
|
||||||
|
|
||||||
module YouPlot
|
|
||||||
# plotting functions.
|
|
||||||
module Plot
|
|
||||||
module_function
|
|
||||||
|
|
||||||
def barplot(data, params, count: false)
|
|
||||||
headers = data.headers
|
|
||||||
series = data.series
|
|
||||||
# `uplot count`
|
|
||||||
if count
|
|
||||||
series = Preprocessing.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.
|
|
||||||
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)
|
|
||||||
end
|
|
||||||
UnicodePlot.barplot(labels, values, **params.to_hc)
|
|
||||||
end
|
|
||||||
|
|
||||||
def histogram(data, params)
|
|
||||||
headers = data.headers
|
|
||||||
series = data.series
|
|
||||||
params.title ||= data.headers[0] if headers
|
|
||||||
values = series[0].map(&:to_f)
|
|
||||||
UnicodePlot.histogram(values, **params.to_hc)
|
|
||||||
end
|
|
||||||
|
|
||||||
def line(data, params)
|
|
||||||
headers = data.headers
|
|
||||||
series = data.series
|
|
||||||
if series.size == 1
|
|
||||||
# If there is only one series, it is assumed to be sequential data.
|
|
||||||
params.ylabel ||= headers[0] if headers
|
|
||||||
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]
|
|
||||||
end
|
|
||||||
x = series[0].map(&:to_f)
|
|
||||||
y = series[1].map(&:to_f)
|
|
||||||
UnicodePlot.lineplot(x, y, **params.to_hc)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_method2(method1)
|
|
||||||
"#{method1}!".to_sym
|
|
||||||
end
|
|
||||||
|
|
||||||
def plot_xyy(data, method1, params)
|
|
||||||
headers = data.headers
|
|
||||||
series = data.series
|
|
||||||
method2 = get_method2(method1)
|
|
||||||
series.map! { |s| s.map(&:to_f) }
|
|
||||||
if headers
|
|
||||||
params.name ||= headers[1]
|
|
||||||
params.xlabel ||= headers[0]
|
|
||||||
end
|
|
||||||
params.ylim ||= series[1..-1].flatten.minmax # why need?
|
|
||||||
plot = UnicodePlot.public_send(method1, series[0], series[1], **params.to_hc)
|
|
||||||
2.upto(series.size - 1) do |i|
|
|
||||||
UnicodePlot.public_send(method2, plot, series[0], series[i], name: headers&.[](i))
|
|
||||||
end
|
|
||||||
plot
|
|
||||||
end
|
|
||||||
|
|
||||||
def plot_xyxy(data, method1, params)
|
|
||||||
headers = data.headers
|
|
||||||
series = data.series
|
|
||||||
method2 = get_method2(method1)
|
|
||||||
series.map! { |s| s.map(&:to_f) }
|
|
||||||
series = series.each_slice(2).to_a
|
|
||||||
params.name ||= headers[0] if headers
|
|
||||||
params.xlim = series.map(&:first).flatten.minmax # why need?
|
|
||||||
params.ylim = series.map(&:last).flatten.minmax # why need?
|
|
||||||
x1, y1 = series.shift
|
|
||||||
plot = UnicodePlot.public_send(method1, x1, y1, **params.to_hc)
|
|
||||||
series.each_with_index do |(xi, yi), i|
|
|
||||||
UnicodePlot.public_send(method2, plot, xi, yi, name: headers&.[]((i + 1) * 2))
|
|
||||||
end
|
|
||||||
plot
|
|
||||||
end
|
|
||||||
|
|
||||||
def plot_fmt(data, fmt, method1, params)
|
|
||||||
case fmt
|
|
||||||
when 'xyy'
|
|
||||||
plot_xyy(data, method1, params)
|
|
||||||
when 'xyxy'
|
|
||||||
plot_xyxy(data, method1, params)
|
|
||||||
else
|
|
||||||
raise "Unknown format: #{fmt}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def lines(data, params, fmt = 'xyy')
|
|
||||||
check_series_size(data, fmt)
|
|
||||||
plot_fmt(data, fmt, :lineplot, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def scatter(data, params, fmt = 'xyy')
|
|
||||||
check_series_size(data, fmt)
|
|
||||||
plot_fmt(data, fmt, :scatterplot, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def density(data, params, fmt = 'xyy')
|
|
||||||
check_series_size(data, fmt)
|
|
||||||
plot_fmt(data, fmt, :densityplot, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def boxplot(data, params)
|
|
||||||
headers = data.headers
|
|
||||||
series = data.series
|
|
||||||
headers ||= (1..series.size).map(&:to_s)
|
|
||||||
series.map! { |s| s.map(&:to_f) }
|
|
||||||
UnicodePlot.boxplot(headers, series, **params.to_hc)
|
|
||||||
end
|
|
||||||
|
|
||||||
def colors(color_names = false)
|
|
||||||
UnicodePlot::StyledPrinter::TEXT_COLORS.each do |k, v|
|
|
||||||
print v
|
|
||||||
print k
|
|
||||||
unless color_names
|
|
||||||
print "\t"
|
|
||||||
print ' ●'
|
|
||||||
end
|
|
||||||
print "\033[0m"
|
|
||||||
print "\t"
|
|
||||||
end
|
|
||||||
puts
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_series_size(data, fmt)
|
|
||||||
series = data.series
|
|
||||||
if series.size == 1
|
|
||||||
warn 'youplot: There is only one series of input data. Please check the delimiter.'
|
|
||||||
warn ''
|
|
||||||
warn " Headers: \e[35m#{data.headers.inspect}\e[0m"
|
|
||||||
warn " The first item is: \e[35m\"#{series[0][0]}\"\e[0m"
|
|
||||||
warn " The last item is : \e[35m\"#{series[0][-1]}\"\e[0m"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
if fmt == 'xyxy' && series.size.odd?
|
|
||||||
warn 'YouPlot: In the xyxy format, the number of series must be even.'
|
|
||||||
warn ''
|
|
||||||
warn " Number of series: \e[35m#{series.size}\e[0m"
|
|
||||||
warn " Headers: \e[35m#{data.headers.inspect}\e[0m"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in New Issue
Block a user