youplot 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +148 -0
- data/exe/uplot +6 -0
- data/exe/youplot +6 -0
- data/lib/youplot.rb +9 -0
- data/lib/youplot/backends/unicode_plot_backend.rb +167 -0
- data/lib/youplot/command.rb +88 -0
- data/lib/youplot/command/params.rb +37 -0
- data/lib/youplot/command/parser.rb +261 -0
- data/lib/youplot/preprocessing.rb +86 -0
- data/lib/youplot/version.rb +5 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d270d30bd2d7d0766d6e26704383ec25fd742d2ed12b8e3738f91c985e2b4f80
|
4
|
+
data.tar.gz: '01149b5a5bd05dbbfc544e207ea4345c602468f68e39d3965025c4f7c1ddb13f'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 80c2894983ca5cc3a9e9833a95f7d11a8928db454fecc0b97b585a4903dd0ae77cf89a1929dc4cb526dc6e7b0f8b0019d5d0769ab6f1a63ab4acb3db8a125c35
|
7
|
+
data.tar.gz: 4c6b532a8b95ab2b9ba8aa5def4b368edd93fe5e9af224d1f7fc4f87a4b40d0a07cf7f874565c10f7630d257e393fb8175ec087e6da9e4740b4e31e76c3d1d12
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 kojix2
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# YouPlot
|
2
|
+
|
3
|
+

|
4
|
+
[](https://badge.fury.io/rb/youplot)
|
5
|
+
[](https://rubydoc.info/gems/youplot)
|
6
|
+
[](LICENSE.txt)
|
7
|
+
|
8
|
+
Create ASCII charts on the terminal with data from standard streams in the pipeline.
|
9
|
+
|
10
|
+
:bar_chart: Powered by [UnicodePlot](https://github.com/red-data-tools/unicode_plot.rb)
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
```
|
15
|
+
gem install youplot
|
16
|
+
```
|
17
|
+
|
18
|
+
## Screenshots
|
19
|
+
|
20
|
+
**histogram**
|
21
|
+
|
22
|
+
```sh
|
23
|
+
ruby -r numo/narray -e "puts Numo::DFloat.new(1000).rand_norm.to_a" \
|
24
|
+
| uplot hist --nbins 15
|
25
|
+
```
|
26
|
+
|
27
|
+
<img src="https://i.imgur.com/wpsoGJq.png" width="75%" height="75%">
|
28
|
+
|
29
|
+
```sh
|
30
|
+
echo -e "from numpy import random;" \
|
31
|
+
"n = random.randn(10000);" \
|
32
|
+
"print('\\\n'.join(str(i) for i in n))" \
|
33
|
+
| python \
|
34
|
+
| uplot hist --nbins 20
|
35
|
+
```
|
36
|
+
|
37
|
+
<img src="https://i.imgur.com/97R2MQx.png" width="75%" height="75%">
|
38
|
+
|
39
|
+
**scatter**
|
40
|
+
|
41
|
+
```sh
|
42
|
+
curl -s https://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv \
|
43
|
+
| cut -f1-4 -d, \
|
44
|
+
| uplot scatter -H -d, -t IRIS
|
45
|
+
```
|
46
|
+
|
47
|
+
<img src="https://i.imgur.com/STX7bFT.png" width="75%" height="75%">
|
48
|
+
|
49
|
+
**line**
|
50
|
+
|
51
|
+
```sh
|
52
|
+
curl -s https://www.mhlw.go.jp/content/pcr_positive_daily.csv \
|
53
|
+
| cut -f2 -d, \
|
54
|
+
| uplot line -w 50 -h 15 -t 'PCR positive tests' --xlabel Date --ylabel number
|
55
|
+
```
|
56
|
+
|
57
|
+
<img src="https://i.imgur.com/PVl5dsa.png" width="75%" height="75%">
|
58
|
+
|
59
|
+
**box**
|
60
|
+
|
61
|
+
```sh
|
62
|
+
curl -s https://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv \
|
63
|
+
| cut -f1-4 -d, \
|
64
|
+
| uplot box -H -d, -t IRIS
|
65
|
+
```
|
66
|
+
|
67
|
+
<img src="https://i.imgur.com/sNI4SmN.png" width="75%" height="75%">
|
68
|
+
|
69
|
+
**colors**
|
70
|
+
|
71
|
+
```sh
|
72
|
+
uplot colors
|
73
|
+
```
|
74
|
+
|
75
|
+
<img src="https://i.imgur.com/LxyHQsz.png">
|
76
|
+
|
77
|
+
## Usage
|
78
|
+
|
79
|
+
`uplot --help`
|
80
|
+
|
81
|
+
```
|
82
|
+
Program: YouPlot (Tools for plotting on the terminal)
|
83
|
+
Version: 0.2.7 (using UnicodePlot 0.0.4)
|
84
|
+
Source: https://github.com/kojix2/youplot
|
85
|
+
|
86
|
+
Usage: uplot <command> [options] <in.tsv>
|
87
|
+
|
88
|
+
Commands:
|
89
|
+
barplot bar
|
90
|
+
histogram hist
|
91
|
+
lineplot line
|
92
|
+
lineplots lines
|
93
|
+
scatter s
|
94
|
+
density d
|
95
|
+
boxplot box
|
96
|
+
colors show the list of available colors
|
97
|
+
|
98
|
+
count c baplot based on the number of occurrences
|
99
|
+
(slower than `sort | uniq -c | sort -n -k1`)
|
100
|
+
|
101
|
+
Options:
|
102
|
+
-O, --pass [VAL] file to output standard input data to [stdout]
|
103
|
+
for inserting YouPlot in the middle of Unix pipes
|
104
|
+
-o, --output VAL file to output results to [stderr]
|
105
|
+
-d, --delimiter VAL use DELIM instead of TAB for field delimiter
|
106
|
+
-H, --headers specify that the input has header row
|
107
|
+
-T, --transpose transpose the axes of the input data
|
108
|
+
-t, --title VAL print string on the top of plot
|
109
|
+
-x, --xlabel VAL print string on the bottom of the plot
|
110
|
+
-y, --ylabel VAL print string on the far left of the plot
|
111
|
+
-w, --width VAL number of characters per row
|
112
|
+
-h, --height VAL number of rows
|
113
|
+
-b, --border VAL specify the style of the bounding box
|
114
|
+
-m, --margin VAL number of spaces to the left of the plot
|
115
|
+
-p, --padding VAL space of the left and right of the plot
|
116
|
+
-c, --color VAL color of the drawing
|
117
|
+
--[no-]labels hide the labels
|
118
|
+
--fmt VAL xyxy : header is like x1, y1, x2, y2, x3, y3...
|
119
|
+
xyy : header is like x, y1, y2, y2, y3...
|
120
|
+
```
|
121
|
+
|
122
|
+
Use `--help` to print command-specific options.
|
123
|
+
|
124
|
+
`uplot hist --help`
|
125
|
+
|
126
|
+
```
|
127
|
+
Usage: uplot histogram [options] <in.tsv>
|
128
|
+
|
129
|
+
Options for histogram:
|
130
|
+
--symbol VAL character to be used to plot the bars
|
131
|
+
--closed VAL
|
132
|
+
-n, --nbins VAL approximate number of bins
|
133
|
+
|
134
|
+
Options:
|
135
|
+
...
|
136
|
+
```
|
137
|
+
|
138
|
+
## Development
|
139
|
+
|
140
|
+
Let's keep it simple.
|
141
|
+
|
142
|
+
## Contributing
|
143
|
+
|
144
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/kojix2/youplot](https://github.com/kojix2/youplot).
|
145
|
+
|
146
|
+
## License
|
147
|
+
|
148
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
data/exe/uplot
ADDED
data/exe/youplot
ADDED
data/lib/youplot.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'unicode_plot'
|
4
|
+
|
5
|
+
module YouPlot
|
6
|
+
# plotting functions.
|
7
|
+
module Backends
|
8
|
+
module UnicodePlotBackend
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def barplot(data, params, count: false)
|
12
|
+
headers = data.headers
|
13
|
+
series = data.series
|
14
|
+
# `uplot count`
|
15
|
+
if count
|
16
|
+
series = Preprocessing.count_values(series[0])
|
17
|
+
params.title = headers[0] if headers
|
18
|
+
end
|
19
|
+
if series.size == 1
|
20
|
+
# If there is only one series, use the line number for label.
|
21
|
+
params.title ||= headers[0] if headers
|
22
|
+
labels = Array.new(series[0].size) { |i| (i + 1).to_s }
|
23
|
+
values = series[0].map(&:to_f)
|
24
|
+
else
|
25
|
+
params.title ||= headers[1] if headers
|
26
|
+
labels = series[0]
|
27
|
+
values = series[1].map(&:to_f)
|
28
|
+
end
|
29
|
+
UnicodePlot.barplot(labels, values, **params.to_hc)
|
30
|
+
end
|
31
|
+
|
32
|
+
def histogram(data, params)
|
33
|
+
headers = data.headers
|
34
|
+
series = data.series
|
35
|
+
params.title ||= data.headers[0] if headers
|
36
|
+
values = series[0].map(&:to_f)
|
37
|
+
UnicodePlot.histogram(values, **params.to_hc)
|
38
|
+
end
|
39
|
+
|
40
|
+
def line(data, params)
|
41
|
+
headers = data.headers
|
42
|
+
series = data.series
|
43
|
+
if series.size == 1
|
44
|
+
# If there is only one series, it is assumed to be sequential data.
|
45
|
+
params.ylabel ||= headers[0] if headers
|
46
|
+
y = series[0].map(&:to_f)
|
47
|
+
UnicodePlot.lineplot(y, **params.to_hc)
|
48
|
+
else
|
49
|
+
# If there are 2 or more series,
|
50
|
+
# assume that the first 2 series are the x and y series respectively.
|
51
|
+
if headers
|
52
|
+
params.xlabel ||= headers[0]
|
53
|
+
params.ylabel ||= headers[1]
|
54
|
+
end
|
55
|
+
x = series[0].map(&:to_f)
|
56
|
+
y = series[1].map(&:to_f)
|
57
|
+
UnicodePlot.lineplot(x, y, **params.to_hc)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_method2(method1)
|
62
|
+
"#{method1}!".to_sym
|
63
|
+
end
|
64
|
+
|
65
|
+
def plot_xyy(data, method1, params)
|
66
|
+
headers = data.headers
|
67
|
+
series = data.series
|
68
|
+
method2 = get_method2(method1)
|
69
|
+
series.map! { |s| s.map(&:to_f) }
|
70
|
+
if headers
|
71
|
+
params.name ||= headers[1]
|
72
|
+
params.xlabel ||= headers[0]
|
73
|
+
end
|
74
|
+
params.ylim ||= series[1..-1].flatten.minmax # why need?
|
75
|
+
plot = UnicodePlot.public_send(method1, series[0], series[1], **params.to_hc)
|
76
|
+
2.upto(series.size - 1) do |i|
|
77
|
+
UnicodePlot.public_send(method2, plot, series[0], series[i], name: headers&.[](i))
|
78
|
+
end
|
79
|
+
plot
|
80
|
+
end
|
81
|
+
|
82
|
+
def plot_xyxy(data, method1, params)
|
83
|
+
headers = data.headers
|
84
|
+
series = data.series
|
85
|
+
method2 = get_method2(method1)
|
86
|
+
series.map! { |s| s.map(&:to_f) }
|
87
|
+
series = series.each_slice(2).to_a
|
88
|
+
params.name ||= headers[0] if headers
|
89
|
+
params.xlim = series.map(&:first).flatten.minmax # why need?
|
90
|
+
params.ylim = series.map(&:last).flatten.minmax # why need?
|
91
|
+
x1, y1 = series.shift
|
92
|
+
plot = UnicodePlot.public_send(method1, x1, y1, **params.to_hc)
|
93
|
+
series.each_with_index do |(xi, yi), i|
|
94
|
+
UnicodePlot.public_send(method2, plot, xi, yi, name: headers&.[]((i + 1) * 2))
|
95
|
+
end
|
96
|
+
plot
|
97
|
+
end
|
98
|
+
|
99
|
+
def plot_fmt(data, fmt, method1, params)
|
100
|
+
case fmt
|
101
|
+
when 'xyy'
|
102
|
+
plot_xyy(data, method1, params)
|
103
|
+
when 'xyxy'
|
104
|
+
plot_xyxy(data, method1, params)
|
105
|
+
else
|
106
|
+
raise "Unknown format: #{fmt}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def lines(data, params, fmt = 'xyy')
|
111
|
+
check_series_size(data, fmt)
|
112
|
+
plot_fmt(data, fmt, :lineplot, params)
|
113
|
+
end
|
114
|
+
|
115
|
+
def scatter(data, params, fmt = 'xyy')
|
116
|
+
check_series_size(data, fmt)
|
117
|
+
plot_fmt(data, fmt, :scatterplot, params)
|
118
|
+
end
|
119
|
+
|
120
|
+
def density(data, params, fmt = 'xyy')
|
121
|
+
check_series_size(data, fmt)
|
122
|
+
plot_fmt(data, fmt, :densityplot, params)
|
123
|
+
end
|
124
|
+
|
125
|
+
def boxplot(data, params)
|
126
|
+
headers = data.headers
|
127
|
+
series = data.series
|
128
|
+
headers ||= (1..series.size).map(&:to_s)
|
129
|
+
series.map! { |s| s.map(&:to_f) }
|
130
|
+
UnicodePlot.boxplot(headers, series, **params.to_hc)
|
131
|
+
end
|
132
|
+
|
133
|
+
def colors(color_names = false)
|
134
|
+
UnicodePlot::StyledPrinter::TEXT_COLORS.each do |k, v|
|
135
|
+
print v
|
136
|
+
print k
|
137
|
+
unless color_names
|
138
|
+
print "\t"
|
139
|
+
print ' ●'
|
140
|
+
end
|
141
|
+
print "\033[0m"
|
142
|
+
print "\t"
|
143
|
+
end
|
144
|
+
puts
|
145
|
+
end
|
146
|
+
|
147
|
+
def check_series_size(data, fmt)
|
148
|
+
series = data.series
|
149
|
+
if series.size == 1
|
150
|
+
warn 'youplot: There is only one series of input data. Please check the delimiter.'
|
151
|
+
warn ''
|
152
|
+
warn " Headers: \e[35m#{data.headers.inspect}\e[0m"
|
153
|
+
warn " The first item is: \e[35m\"#{series[0][0]}\"\e[0m"
|
154
|
+
warn " The last item is : \e[35m\"#{series[0][-1]}\"\e[0m"
|
155
|
+
exit 1
|
156
|
+
end
|
157
|
+
if fmt == 'xyxy' && series.size.odd?
|
158
|
+
warn 'YouPlot: In the xyxy format, the number of series must be even.'
|
159
|
+
warn ''
|
160
|
+
warn " Number of series: \e[35m#{series.size}\e[0m"
|
161
|
+
warn " Headers: \e[35m#{data.headers.inspect}\e[0m"
|
162
|
+
exit 1
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'preprocessing'
|
4
|
+
require_relative 'command/parser'
|
5
|
+
|
6
|
+
# FIXME
|
7
|
+
require_relative 'backends/unicode_plot_backend'
|
8
|
+
|
9
|
+
module YouPlot
|
10
|
+
Data = Struct.new(:headers, :series)
|
11
|
+
|
12
|
+
class Command
|
13
|
+
attr_accessor :params
|
14
|
+
attr_reader :data, :fmt, :parser
|
15
|
+
|
16
|
+
def initialize(argv = ARGV)
|
17
|
+
@argv = argv
|
18
|
+
@params = Params.new
|
19
|
+
@parser = Parser.new
|
20
|
+
@backend = YouPlot::Backends::UnicodePlotBackend
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
parser.parse_options(@argv)
|
25
|
+
command = parser.command
|
26
|
+
params = parser.params
|
27
|
+
delimiter = parser.delimiter
|
28
|
+
transpose = parser.transpose
|
29
|
+
headers = parser.headers
|
30
|
+
pass = parser.pass
|
31
|
+
output = parser.output
|
32
|
+
fmt = parser.fmt
|
33
|
+
@debug = parser.debug
|
34
|
+
|
35
|
+
if command == :colors
|
36
|
+
@backend.colors(parser.color_names)
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sometimes the input file does not end with a newline code.
|
41
|
+
while (input = Kernel.gets(nil))
|
42
|
+
input.freeze
|
43
|
+
@data = Preprocessing.input(input, delimiter, headers, transpose)
|
44
|
+
pp @data if @debug
|
45
|
+
plot = case command
|
46
|
+
when :bar, :barplot
|
47
|
+
@backend.barplot(data, params)
|
48
|
+
when :count, :c
|
49
|
+
@backend.barplot(data, params, count: true)
|
50
|
+
when :hist, :histogram
|
51
|
+
@backend.histogram(data, params)
|
52
|
+
when :line, :lineplot
|
53
|
+
@backend.line(data, params)
|
54
|
+
when :lines, :lineplots
|
55
|
+
@backend.lines(data, params, fmt)
|
56
|
+
when :scatter, :s
|
57
|
+
@backend.scatter(data, params, fmt)
|
58
|
+
when :density, :d
|
59
|
+
@backend.density(data, params, fmt)
|
60
|
+
when :box, :boxplot
|
61
|
+
@backend.boxplot(data, params)
|
62
|
+
else
|
63
|
+
raise "unrecognized plot_type: #{command}"
|
64
|
+
end
|
65
|
+
|
66
|
+
case output
|
67
|
+
when IO
|
68
|
+
plot.render(output)
|
69
|
+
else
|
70
|
+
File.open(output, 'w') do |f|
|
71
|
+
plot.render(f)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
case pass
|
76
|
+
when IO
|
77
|
+
pass.print(input)
|
78
|
+
else
|
79
|
+
if pass
|
80
|
+
File.open(pass, 'w') do |f|
|
81
|
+
f.print(input)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YouPlot
|
4
|
+
class Command
|
5
|
+
# UnicodePlot parameters.
|
6
|
+
# * Normally in a Ruby program, you might use hash for the parameter object.
|
7
|
+
# * Here, I use Struct for 2 safety reason.
|
8
|
+
# * The keys are static in Struct.
|
9
|
+
# * Struct does not conflict with keyword arguments. Hash dose.
|
10
|
+
Params = Struct.new(
|
11
|
+
# Sort me!
|
12
|
+
:title,
|
13
|
+
:width,
|
14
|
+
:height,
|
15
|
+
:border,
|
16
|
+
:margin,
|
17
|
+
:padding,
|
18
|
+
:color,
|
19
|
+
:xlabel,
|
20
|
+
:ylabel,
|
21
|
+
:labels,
|
22
|
+
:symbol,
|
23
|
+
:xscale,
|
24
|
+
:nbins,
|
25
|
+
:closed,
|
26
|
+
:canvas,
|
27
|
+
:xlim,
|
28
|
+
:ylim,
|
29
|
+
:grid,
|
30
|
+
:name
|
31
|
+
) do
|
32
|
+
def to_hc
|
33
|
+
to_h.compact
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require_relative 'params'
|
5
|
+
|
6
|
+
module YouPlot
|
7
|
+
class Command
|
8
|
+
class Parser
|
9
|
+
attr_reader :command, :params,
|
10
|
+
:delimiter, :transpose, :headers, :pass, :output, :fmt,
|
11
|
+
:color_names, :debug
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@command = nil
|
15
|
+
@params = Params.new
|
16
|
+
|
17
|
+
@delimiter = "\t"
|
18
|
+
@transpose = false
|
19
|
+
@headers = nil
|
20
|
+
@pass = false
|
21
|
+
@output = $stderr
|
22
|
+
@fmt = 'xyy'
|
23
|
+
@debug = false
|
24
|
+
@color_names = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_default_parser
|
28
|
+
OptionParser.new do |opt|
|
29
|
+
opt.program_name = 'YouPlot'
|
30
|
+
opt.version = YouPlot::VERSION
|
31
|
+
opt.summary_width = 24
|
32
|
+
opt.on_tail('') # Add a blank line at the end
|
33
|
+
opt.separator('')
|
34
|
+
opt.on('Common options:')
|
35
|
+
opt.on('-O', '--pass [VAL]', 'file to output standard input data to [stdout]',
|
36
|
+
'for inserting YouPlot in the middle of Unix pipes') do |v|
|
37
|
+
@pass = v || $stdout
|
38
|
+
end
|
39
|
+
opt.on('-o', '--output VAL', 'file to output results to [stderr]') do |v|
|
40
|
+
@output = v
|
41
|
+
end
|
42
|
+
opt.on('-d', '--delimiter VAL', String, 'use DELIM instead of TAB for field delimiter') do |v|
|
43
|
+
@delimiter = v
|
44
|
+
end
|
45
|
+
opt.on('-H', '--headers', TrueClass, 'specify that the input has header row') do |v|
|
46
|
+
@headers = v
|
47
|
+
end
|
48
|
+
opt.on('-T', '--transpose', TrueClass, 'transpose the axes of the input data') do |v|
|
49
|
+
@transpose = v
|
50
|
+
end
|
51
|
+
opt.on('-t', '--title VAL', String, 'print string on the top of plot') do |v|
|
52
|
+
params.title = v
|
53
|
+
end
|
54
|
+
opt.on('-x', '--xlabel VAL', String, 'print string on the bottom of the plot') do |v|
|
55
|
+
params.xlabel = v
|
56
|
+
end
|
57
|
+
opt.on('-y', '--ylabel VAL', String, 'print string on the far left of the plot') do |v|
|
58
|
+
params.ylabel = v
|
59
|
+
end
|
60
|
+
opt.on('-w', '--width VAL', Integer, 'number of characters per row') do |v|
|
61
|
+
params.width = v
|
62
|
+
end
|
63
|
+
opt.on('-h', '--height VAL', Numeric, 'number of rows') do |v|
|
64
|
+
params.height = v
|
65
|
+
end
|
66
|
+
opt.on('-b', '--border VAL', String, 'specify the style of the bounding box') do |v|
|
67
|
+
params.border = v.to_sym
|
68
|
+
end
|
69
|
+
opt.on('-m', '--margin VAL', Numeric, 'number of spaces to the left of the plot') do |v|
|
70
|
+
params.margin = v
|
71
|
+
end
|
72
|
+
opt.on('-p', '--padding VAL', Numeric, 'space of the left and right of the plot') do |v|
|
73
|
+
params.padding = v
|
74
|
+
end
|
75
|
+
opt.on('-c', '--color VAL', String, 'color of the drawing') do |v|
|
76
|
+
params.color = v =~ /\A[0-9]+\z/ ? v.to_i : v.to_sym
|
77
|
+
end
|
78
|
+
opt.on('--[no-]labels', TrueClass, 'hide the labels') do |v|
|
79
|
+
params.labels = v
|
80
|
+
end
|
81
|
+
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|
|
82
|
+
@fmt = v
|
83
|
+
end
|
84
|
+
# Optparse adds the help option, but it doesn't show up in usage.
|
85
|
+
# This is why you need the code below.
|
86
|
+
opt.on('--help', 'print sub-command help menu') do
|
87
|
+
puts opt.help
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
opt.on('--debug', TrueClass, 'print preprocessed data') do |v|
|
91
|
+
@debug = v
|
92
|
+
end
|
93
|
+
yield opt if block_given?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def main_parser
|
98
|
+
@main_parser ||= create_default_parser do |main_parser|
|
99
|
+
# Here, help message is stored in the banner.
|
100
|
+
# Because help of main_parser may be referred by `sub_parser`.
|
101
|
+
|
102
|
+
main_parser.banner = \
|
103
|
+
<<~MSG
|
104
|
+
|
105
|
+
Program: YouPlot (Tools for plotting on the terminal)
|
106
|
+
Version: #{YouPlot::VERSION} (using UnicodePlot #{UnicodePlot::VERSION})
|
107
|
+
Source: https://github.com/kojix2/youplot
|
108
|
+
|
109
|
+
Usage: uplot <command> [options] <in.tsv>
|
110
|
+
|
111
|
+
Commands:
|
112
|
+
barplot bar draw a horizontal barplot
|
113
|
+
histogram hist draw a horizontal histogram
|
114
|
+
lineplot line draw a line chart
|
115
|
+
lineplots lines draw a line chart with multiple series
|
116
|
+
scatter s draw a scatter plot
|
117
|
+
density d draw a density plot
|
118
|
+
boxplot box draw a horizontal boxplot
|
119
|
+
colors show the list of available colors
|
120
|
+
|
121
|
+
count c draw a baplot based on the number of
|
122
|
+
occurrences (slow)
|
123
|
+
|
124
|
+
General options:
|
125
|
+
--help print command specific help menu
|
126
|
+
--version print the version of YouPlot
|
127
|
+
MSG
|
128
|
+
|
129
|
+
# Actually, main_parser can take common optional arguments.
|
130
|
+
# However, these options dose not be shown in the help menu.
|
131
|
+
# I think the main help should be simple.
|
132
|
+
main_parser.on('--help', 'print sub-command help menu') do
|
133
|
+
puts main_parser.banner
|
134
|
+
puts
|
135
|
+
exit
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def sub_parser
|
141
|
+
@sub_parser ||= create_default_parser do |parser|
|
142
|
+
parser.banner = <<~MSG
|
143
|
+
|
144
|
+
Usage: YouPlot #{command} [options] <in.tsv>
|
145
|
+
|
146
|
+
Options for #{command}:
|
147
|
+
MSG
|
148
|
+
|
149
|
+
case command
|
150
|
+
|
151
|
+
# If you type only `uplot` in the terminal.
|
152
|
+
when nil
|
153
|
+
warn main_parser.banner
|
154
|
+
warn "\n"
|
155
|
+
exit 1
|
156
|
+
|
157
|
+
when :barplot, :bar
|
158
|
+
parser.on_head('--symbol VAL', String, 'character to be used to plot the bars') do |v|
|
159
|
+
params.symbol = v
|
160
|
+
end
|
161
|
+
parser.on_head('--xscale VAL', String, 'axis scaling') do |v|
|
162
|
+
params.xscale = v
|
163
|
+
end
|
164
|
+
|
165
|
+
when :count, :c
|
166
|
+
parser.on_head('--symbol VAL', String, 'character to be used to plot the bars') do |v|
|
167
|
+
params.symbol = v
|
168
|
+
end
|
169
|
+
|
170
|
+
when :histogram, :hist
|
171
|
+
parser.on_head('-n', '--nbins VAL', Numeric, 'approximate number of bins') do |v|
|
172
|
+
params.nbins = v
|
173
|
+
end
|
174
|
+
parser.on_head('--closed VAL', String) do |v|
|
175
|
+
params.closed = v
|
176
|
+
end
|
177
|
+
parser.on_head('--symbol VAL', String, 'character to be used to plot the bars') do |v|
|
178
|
+
params.symbol = v
|
179
|
+
end
|
180
|
+
|
181
|
+
when :lineplot, :line
|
182
|
+
parser.on_head('--canvas VAL', String, 'type of canvas') do |v|
|
183
|
+
params.canvas = v
|
184
|
+
end
|
185
|
+
parser.on_head('--xlim VAL', Array, 'plotting range for the x coordinate') do |v|
|
186
|
+
params.xlim = v.take(2)
|
187
|
+
end
|
188
|
+
parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v|
|
189
|
+
params.ylim = v.take(2)
|
190
|
+
end
|
191
|
+
|
192
|
+
when :lineplots, :lines
|
193
|
+
parser.on_head('--canvas VAL', String) do |v|
|
194
|
+
params.canvas = v
|
195
|
+
end
|
196
|
+
parser.on_head('--xlim VAL', Array, 'plotting range for the x coordinate') do |v|
|
197
|
+
params.xlim = v.take(2)
|
198
|
+
end
|
199
|
+
parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v|
|
200
|
+
params.ylim = v.take(2)
|
201
|
+
end
|
202
|
+
|
203
|
+
when :scatter, :s
|
204
|
+
parser.on_head('--canvas VAL', String) do |v|
|
205
|
+
params.canvas = v
|
206
|
+
end
|
207
|
+
parser.on_head('--xlim VAL', Array, 'plotting range for the x coordinate') do |v|
|
208
|
+
params.xlim = v.take(2)
|
209
|
+
end
|
210
|
+
parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v|
|
211
|
+
params.ylim = v.take(2)
|
212
|
+
end
|
213
|
+
|
214
|
+
when :density, :d
|
215
|
+
parser.on_head('--grid', TrueClass) do |v|
|
216
|
+
params.grid = v
|
217
|
+
end
|
218
|
+
parser.on_head('--xlim VAL', Array, 'plotting range for the x coordinate') do |v|
|
219
|
+
params.xlim = v.take(2)
|
220
|
+
end
|
221
|
+
parser.on_head('--ylim VAL', Array, 'plotting range for the y coordinate') do |v|
|
222
|
+
params.ylim = v.take(2)
|
223
|
+
end
|
224
|
+
|
225
|
+
when :boxplot, :box
|
226
|
+
parser.on_head('--xlim VAL', Array, 'plotting range for the x coordinate') do |v|
|
227
|
+
params.xlim = v.take(2)
|
228
|
+
end
|
229
|
+
|
230
|
+
when :colors
|
231
|
+
parser.on_head('-n', '--names', 'show color names only', TrueClass) do |v|
|
232
|
+
@color_names = v
|
233
|
+
end
|
234
|
+
|
235
|
+
else
|
236
|
+
warn "uplot: unrecognized command '#{command}'"
|
237
|
+
exit 1
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def parse_options(argv = ARGV)
|
243
|
+
begin
|
244
|
+
main_parser.order!(argv)
|
245
|
+
rescue OptionParser::ParseError => e
|
246
|
+
warn "uplot: #{e.message}"
|
247
|
+
exit 1
|
248
|
+
end
|
249
|
+
|
250
|
+
@command = argv.shift&.to_sym
|
251
|
+
|
252
|
+
begin
|
253
|
+
sub_parser.parse!(argv)
|
254
|
+
rescue OptionParser::ParseError => e
|
255
|
+
warn "uplot: #{e.message}"
|
256
|
+
exit 1
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module YouPlot
|
6
|
+
module Preprocessing
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def input(input, delimiter, headers, transpose)
|
10
|
+
arr = parse_as_csv(input, delimiter)
|
11
|
+
headers = get_headers(arr, headers, transpose)
|
12
|
+
series = get_series(arr, headers, transpose)
|
13
|
+
if headers.nil?
|
14
|
+
Data.new(headers, series)
|
15
|
+
else
|
16
|
+
if headers.include?(nil)
|
17
|
+
warn "\e[35mHeaders contains nil in it.\e[0m"
|
18
|
+
elsif headers.include? ''
|
19
|
+
warn "\e[35mHeaders contains \"\" in it.\e[0m"
|
20
|
+
end
|
21
|
+
h_size = headers.size
|
22
|
+
s_size = series.size
|
23
|
+
if h_size == s_size
|
24
|
+
Data.new(headers, series)
|
25
|
+
elsif h_size > s_size
|
26
|
+
warn "\e[35mThe number of headers is greater than the number of series.\e[0m"
|
27
|
+
exit 1
|
28
|
+
elsif h_size < s_size
|
29
|
+
warn "\e[35mThe number of headers is less than the number of series.\e[0m"
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_as_csv(input, delimiter)
|
36
|
+
CSV.parse(input, col_sep: delimiter)
|
37
|
+
.delete_if do |i|
|
38
|
+
i == [] or i.all? nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Transpose different sized ruby arrays
|
43
|
+
# https://stackoverflow.com/q/26016632
|
44
|
+
def transpose2(arr)
|
45
|
+
Array.new(arr.map(&:length).max) { |i| arr.map { |e| e[i] } }
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_headers(arr, headers, transpose)
|
49
|
+
if headers
|
50
|
+
if transpose
|
51
|
+
arr.map(&:first)
|
52
|
+
else
|
53
|
+
arr[0]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_series(arr, headers, transpose)
|
59
|
+
if transpose
|
60
|
+
if headers
|
61
|
+
arr.map { |row| row[1..-1] }
|
62
|
+
else
|
63
|
+
arr
|
64
|
+
end
|
65
|
+
elsif headers
|
66
|
+
transpose2(arr[1..-1])
|
67
|
+
else
|
68
|
+
transpose2(arr)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def count_values(arr)
|
73
|
+
# tally was added in Ruby 2.7
|
74
|
+
if Enumerable.method_defined? :tally
|
75
|
+
arr.tally
|
76
|
+
else
|
77
|
+
# https://github.com/marcandre/backports
|
78
|
+
arr.each_with_object(Hash.new(0)) { |item, res| res[item] += 1 }
|
79
|
+
.tap { |h| h.default = nil }
|
80
|
+
end
|
81
|
+
.sort { |a, b| a[1] <=> b[1] }
|
82
|
+
.reverse
|
83
|
+
.transpose
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: youplot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- kojix2
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: unicode_plot
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: test-unit
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: "Create ASCII charts on the terminal with data from standard streams
|
98
|
+
in the \npipeline. \n"
|
99
|
+
email:
|
100
|
+
- 2xijok@gmail.com
|
101
|
+
executables:
|
102
|
+
- uplot
|
103
|
+
- youplot
|
104
|
+
extensions: []
|
105
|
+
extra_rdoc_files: []
|
106
|
+
files:
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- exe/uplot
|
110
|
+
- exe/youplot
|
111
|
+
- lib/youplot.rb
|
112
|
+
- lib/youplot/backends/unicode_plot_backend.rb
|
113
|
+
- lib/youplot/command.rb
|
114
|
+
- lib/youplot/command/params.rb
|
115
|
+
- lib/youplot/command/parser.rb
|
116
|
+
- lib/youplot/preprocessing.rb
|
117
|
+
- lib/youplot/version.rb
|
118
|
+
homepage: https://github.com/kojix2/youplot
|
119
|
+
licenses:
|
120
|
+
- MIT
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 2.4.0
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubygems_version: 3.1.4
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: Create Ascii charts on your terminal.
|
141
|
+
test_files: []
|