cmd_plot 0.0.1
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/lib/cmd_plot.rb +9 -0
- data/lib/cmd_plot/bar.rb +30 -0
- data/lib/cmd_plot/basic_data.rb +33 -0
- data/lib/cmd_plot/pcolor.rb +73 -0
- data/lib/cmd_plot/plot.rb +116 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1bdcbb0c83f03b105322d8dfe801fdf13b855ef6
|
4
|
+
data.tar.gz: 3d78c7759fa07b81841606523a16ad5922b843d7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: abcc75b12bb93b8a52cfc57dda9f7afde74cf21f5eebc54ce6631b17a9cedad643985a0b9357d14704616abdf18fe08e8bd920e09417e013568bc37f966b9356
|
7
|
+
data.tar.gz: cfc61bb3f28c11fe686c3ff4df966dacc1215f205801beb299c7e29c1b7fea899f76078c2418f478be25d7f426a88d35c46984e47cbec163ed9fceea8008ccc1
|
data/lib/cmd_plot.rb
ADDED
data/lib/cmd_plot/bar.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
class CmdPlot::Bar
|
2
|
+
def initialize(width = 140, height = 40, symbol = '°')
|
3
|
+
@width = width
|
4
|
+
@height = height
|
5
|
+
@symbol = symbol
|
6
|
+
end
|
7
|
+
|
8
|
+
def bar(height, labels)
|
9
|
+
# Calculate scaling of data to plot
|
10
|
+
max_height = height.max.to_f
|
11
|
+
max_label_length = labels.map { |l| l.length }.max
|
12
|
+
max_val_length = height.map { |l| l.to_s.length }.max
|
13
|
+
max_bar_length = (@width - max_label_length - max_val_length - 3)
|
14
|
+
# Plot each bar
|
15
|
+
for data in height.zip(labels)
|
16
|
+
label_fill_space = ' ' * (max_label_length - data[1].length)
|
17
|
+
value_fill_space = ' ' * (max_val_length - data[0].to_s.length)
|
18
|
+
bar_plot = @symbol * ((data[0] / max_height) * max_bar_length).round
|
19
|
+
puts "#{label_fill_space}#{data[1]}: #{value_fill_space}#{data[0]} #{bar_plot}"
|
20
|
+
end
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def histogram(data, bins = 20)
|
25
|
+
# Turn data into bins with counts
|
26
|
+
counts, bin_centers = BasicData.build_hist(data, bins)
|
27
|
+
# Plot as bar chart
|
28
|
+
bar(counts, bin_centers.map { |p| p.round(1).to_s })
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class BasicData
|
2
|
+
|
3
|
+
def self.linspace(min, max, n)
|
4
|
+
return Array.new(n) { |i| min.to_f + i.to_f * (max.to_f - min.to_f) / (n - 1).to_f }
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.build_hist(data, bin_spec, maxcap = nil)
|
8
|
+
min = data.min
|
9
|
+
max = data.max
|
10
|
+
if bin_spec.kind_of?(Array)
|
11
|
+
limits = bin_spec
|
12
|
+
nr_bins = limits.length - 1
|
13
|
+
else
|
14
|
+
limits = BasicData.linspace(min, max, bin_spec + 1)
|
15
|
+
nr_bins = bin_spec
|
16
|
+
end
|
17
|
+
counts = Array.new(nr_bins, 0)
|
18
|
+
for d in data
|
19
|
+
bin = limits.map { |x| d < x }.index(true)
|
20
|
+
if bin.nil?
|
21
|
+
bin = counts.length
|
22
|
+
end
|
23
|
+
if bin == 0
|
24
|
+
bin = 1
|
25
|
+
end
|
26
|
+
counts[bin - 1] += 1
|
27
|
+
end
|
28
|
+
counts = counts.map { |v| [v, maxcap].min } unless maxcap.nil?
|
29
|
+
bin_centers = limits.each_cons(2).to_a.map { |a| (a[0] + a[1]) / 2.0 }
|
30
|
+
return counts, bin_centers
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
class CmdPlot::Pcolor
|
2
|
+
|
3
|
+
#red, green, brown, blue, magenta, cyan, gray
|
4
|
+
TEXT_COLORS = [31, 32, 33, 34, 35, 36, 37]
|
5
|
+
FILL_SYMBOLS = ['░', '▒', '▓', '█']
|
6
|
+
MAX_SHADES = TEXT_COLORS.length * FILL_SYMBOLS.length
|
7
|
+
|
8
|
+
def initialize(width = 140, height = 40)
|
9
|
+
@width = width
|
10
|
+
@height = height
|
11
|
+
end
|
12
|
+
|
13
|
+
def pcolor(data, shades = nil)
|
14
|
+
# Calculate scaling of data to picture
|
15
|
+
shades = data.flatten.uniq.length if shades.nil?
|
16
|
+
shades = MAX_SHADES if shades > MAX_SHADES or shades <= 0
|
17
|
+
x_scale = data[0].length / @width.to_f
|
18
|
+
y_scale = data.length / @height.to_f
|
19
|
+
z_max = data.map { |r| r.max }.max
|
20
|
+
z_min = data.map { |r| r.min }.min
|
21
|
+
puts "x_scale: #{x_scale}, y_scale: #{y_scale}, z_min: #{z_min}, z_max: #{z_max}"
|
22
|
+
|
23
|
+
# Calculcate scaled data
|
24
|
+
scaled_data = Array.new(@height) { Array.new(@width) { 0.0 } }
|
25
|
+
for row in 0..(scaled_data.length - 1)
|
26
|
+
for col in 0..(scaled_data[row].length - 1)
|
27
|
+
n = 0.0
|
28
|
+
lower_row_idx = (row * y_scale).floor
|
29
|
+
upper_row_idx = [lower_row_idx, ((row + 1) * y_scale).floor - 1].max
|
30
|
+
lower_col_idx = (col * x_scale).floor
|
31
|
+
upper_col_idx = [lower_col_idx, ((col + 1) * x_scale).floor - 1].max
|
32
|
+
for d_row in lower_row_idx..upper_row_idx
|
33
|
+
for d_col in lower_col_idx..upper_col_idx
|
34
|
+
scaled_data[row][col] += data[d_row][d_col]
|
35
|
+
n += 1.0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
if(n > 0)
|
39
|
+
scaled_data[row][col] /= n
|
40
|
+
else
|
41
|
+
scaled_data[row][col] = nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Full picture starts out empty
|
47
|
+
pic = Array.new(@height) { Array.new(@width) { nil } }
|
48
|
+
# Run through data and fill in the plotting symbol/color
|
49
|
+
for row in 0..(scaled_data.length - 1)
|
50
|
+
for col in 0..(scaled_data[row].length - 1)
|
51
|
+
val = scaled_data[row][col]
|
52
|
+
next if val.nil?
|
53
|
+
fill = ((val - z_min) / (z_max - z_min).to_f * (shades - 1)).round
|
54
|
+
pic[row][col] = fill
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Plot the picture
|
59
|
+
for row in pic
|
60
|
+
for col in row
|
61
|
+
if col.nil?
|
62
|
+
print ' '
|
63
|
+
else
|
64
|
+
color = TEXT_COLORS[col % TEXT_COLORS.length]
|
65
|
+
sym = FILL_SYMBOLS[col / TEXT_COLORS.length]
|
66
|
+
print "\e[#{color}m#{sym}\e[0m"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
print "\n"
|
70
|
+
end
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
class CmdPlot::Plot
|
2
|
+
def initialize(width = 140, height = 40, symbol = '°')
|
3
|
+
@width = width
|
4
|
+
@height = height
|
5
|
+
@symbol = symbol
|
6
|
+
end
|
7
|
+
|
8
|
+
def plot(x, y = nil)
|
9
|
+
# If only one parameter is given treat it as the y-axis data and fill up x automatically
|
10
|
+
if y.nil?
|
11
|
+
y = x
|
12
|
+
x = BasicData::linspace(0.0, y.length, y.length)
|
13
|
+
end
|
14
|
+
# Scale x axis to the desired console resolution
|
15
|
+
x_max = x.compact.max
|
16
|
+
x_min = x.compact.min
|
17
|
+
x = x.map { |v| v.nil? ? nil : ((v - x_min) / (x_max - x_min).to_f * (@width - 1)).round }
|
18
|
+
# Scale y axis to the desired console resolution
|
19
|
+
y_max = y.compact.max
|
20
|
+
y_min = y.compact.min
|
21
|
+
y = y.map { |v| v.nil? ? nil : ((v - y_min) / (y_max - y_min).to_f * (@height - 1)).round }
|
22
|
+
|
23
|
+
# Full plot-picture starts out empty
|
24
|
+
picture = Array.new(@height) { Array.new(@width) {' '} }
|
25
|
+
# Run through all the points of the curve and fill in the plotting symbol
|
26
|
+
for point in x.zip(y)
|
27
|
+
next if point[1].nil? || point[0].nil?
|
28
|
+
picture[@height - 1 - point[1]][point[0]] = @symbol
|
29
|
+
end
|
30
|
+
|
31
|
+
# X-axis
|
32
|
+
x_ticks = BasicData::linspace(x_min, x_max, [2, @width / 15].max).map { |v|
|
33
|
+
tick_to_display(v)
|
34
|
+
}
|
35
|
+
# Y-axis
|
36
|
+
y_ticks = BasicData::linspace(y_max, y_min, [2, @height / 10].max).map { |v|
|
37
|
+
tick_to_display(v)
|
38
|
+
}
|
39
|
+
y_tick_width = y_ticks.map { |e| e.length }.max
|
40
|
+
y_tick_spacing = (@height - y_ticks.length) / (y_ticks.length - 1)
|
41
|
+
missing = @height - (y_tick_spacing * (y_ticks.length - 1) + y_ticks.length)
|
42
|
+
missing_dist = missing == 0 ? @height : y_ticks.length / missing.to_f
|
43
|
+
|
44
|
+
# Print the y-axis and plot picture
|
45
|
+
row = 0
|
46
|
+
next_tick = 0
|
47
|
+
tick_offset = 0
|
48
|
+
for line in picture
|
49
|
+
if row == (tick_offset + next_tick * (y_tick_spacing + 1))
|
50
|
+
print ' ' * (y_tick_width - y_ticks[next_tick].length)
|
51
|
+
print y_ticks[next_tick] + ' ├ '
|
52
|
+
next_tick += 1
|
53
|
+
tick_offset += 1 if ((tick_offset + 1) * missing_dist) < (next_tick + 2)
|
54
|
+
else
|
55
|
+
print ' ' * y_tick_width + ' │ '
|
56
|
+
end
|
57
|
+
puts line.join
|
58
|
+
row += 1
|
59
|
+
end
|
60
|
+
# Print x-axis
|
61
|
+
x_tick_spacing = (@width - x_ticks.length) / (x_ticks.length - 1)
|
62
|
+
missing = @width - (x_tick_spacing * (x_ticks.length - 1) + x_ticks.length)
|
63
|
+
missing_dist = missing == 0 ? @width : x_ticks.length / missing.to_f
|
64
|
+
print ' ' * y_tick_width + ' └─'
|
65
|
+
next_tick = 0
|
66
|
+
added_space = 0
|
67
|
+
for tick in x_ticks
|
68
|
+
if tick == x_ticks[-1]
|
69
|
+
puts '┴'
|
70
|
+
else
|
71
|
+
print '┴'
|
72
|
+
print '─' * x_tick_spacing
|
73
|
+
next_tick += 1
|
74
|
+
if ((added_space + 1) * missing_dist) < (next_tick + 2)
|
75
|
+
added_space += 1
|
76
|
+
print '─'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# Print x labels
|
81
|
+
total_label_length = x_ticks.map(&:length).inject(:+)
|
82
|
+
x_tick_label_spacing = (@width - total_label_length) / (x_ticks.length - 1)
|
83
|
+
missing = @width - (x_tick_label_spacing * (x_ticks.length - 1) + total_label_length)
|
84
|
+
missing_dist = missing == 0 ? @width : x_ticks.length / missing.to_f
|
85
|
+
print ' ' * y_tick_width + ' '
|
86
|
+
next_tick = 0
|
87
|
+
added_space = 0
|
88
|
+
for tick in x_ticks
|
89
|
+
if tick == x_ticks[-1]
|
90
|
+
puts tick
|
91
|
+
else
|
92
|
+
print tick
|
93
|
+
print ' ' * x_tick_label_spacing
|
94
|
+
next_tick += 1
|
95
|
+
if ((added_space + 1) * missing_dist) < (next_tick + 2)
|
96
|
+
added_space += 1
|
97
|
+
print ' '
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def tick_to_display(v)
|
105
|
+
s = v.to_s
|
106
|
+
dot = s.index('.')
|
107
|
+
dot = s.length - 1 if dot.nil?
|
108
|
+
if dot == 1 && s[0] == '0'
|
109
|
+
for dot in 1..(s.length - 1)
|
110
|
+
next if s[dot + 1] == '0' || s[dot + 1] == '.'
|
111
|
+
break
|
112
|
+
end
|
113
|
+
end
|
114
|
+
s[0..(dot+2)]
|
115
|
+
end
|
116
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cmd_plot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andreas Buschermoehle
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-11 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This is an interface to quickly visualize data on the command line. It
|
14
|
+
supports various kinds of plots (curves, bar diagram, histogram, 2D color, etc)
|
15
|
+
that are presented as text output on the console.
|
16
|
+
email: andreas@buschermoehle.org
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/cmd_plot.rb
|
22
|
+
- lib/cmd_plot/bar.rb
|
23
|
+
- lib/cmd_plot/basic_data.rb
|
24
|
+
- lib/cmd_plot/pcolor.rb
|
25
|
+
- lib/cmd_plot/plot.rb
|
26
|
+
homepage: http://rubygems.org/gems/cmd_plot
|
27
|
+
licenses:
|
28
|
+
- GPL-2.0
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 2.6.12
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: Command line plotting for quick visualization of data.
|
50
|
+
test_files: []
|