cmd_plot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ class CmdPlot
2
+
3
+ end
4
+
5
+ require 'cmd_plot/basic_data'
6
+
7
+ require 'cmd_plot/plot'
8
+ require 'cmd_plot/bar'
9
+ require 'cmd_plot/pcolor'
@@ -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: []