# frozen_string_literal: true require_relative 'processing' require 'unicode_plot' module YouPlot # plotting functions. module Backends module UnicodePlotBackend module_function def barplot(data, params, fmt = nil, count: false) headers = data.headers series = data.series # `uplot count` if count 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. params.title ||= headers[0] if headers labels = Array.new(series[0].size) { |i| (i + 1).to_s } values = series[0].map(&:to_f) else # 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 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, fmt = nil) 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... 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 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 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) when 'yx' raise "Incorrect format: #{fmt}" 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