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 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: []