mini_histogram 0.2.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 651709682dbc05f4ee599aa13a501b9bcf709a8723b1e29ba9294e38a3cce633
4
- data.tar.gz: '048c5458f10507a2f97bca1b77e7d012b7eab6a005311c1dde5529faca5235fc'
3
+ metadata.gz: b95e08050cb7942fba011d3c18bcd493064695bd15206826752a9432ba147f0a
4
+ data.tar.gz: cb225c6fcc1d5da019f1037ba7559c89b6957b98e3571d13cdeb780795d06a4d
5
5
  SHA512:
6
- metadata.gz: 6081830e795b90b3a535f5b67078dd7c6ad73a5a6e48d8f9fa2fa69c98f989570bb5734fd1d2184cb4c691182cb172769f1aaae264393ff219d4d037c07bccfc
7
- data.tar.gz: 3abae7377cef46fbc1b9b8a5edc63620116d1cfbd6ac96d917e1af42ff65b27bcea1bc1dc7dc5a07c647095d65388a1b2b7263099b6616a9010357d1300c9fc7
6
+ metadata.gz: a33e73068d9f3c7c85db911a392cf9728eccecbbd6c744fd33a2aa42c2d0218b63819f9b6e8627fc1f68b33adb9551bceaa191d6f6a1dbace7b0380aadedcd36
7
+ data.tar.gz: e45c116668523722f82c41644874e21ec5ad39986c0222e3bc34edc343badb9ccf2478b7384ab99caeb7c36aafe4745cd1ac07edf46f034865f00b2ac11cca04
@@ -1,5 +1,9 @@
1
1
  ## HEAD
2
2
 
3
+ ## 0.3.0
4
+
5
+ - Generate dualing side-by-side histograms (https://github.com/zombocom/mini_histogram/pull/6)
6
+
3
7
  ## 0.2.2
4
8
 
5
9
  - Frozen string optimization in histogram/plot.rb (https://github.com/zombocom/mini_histogram/pull/5)
data/README.md CHANGED
@@ -38,7 +38,7 @@ puts histogram.weights
38
38
 
39
39
  This means that the `array` here had three items between 0.0 and 2.0, four items between 4.0 and 6.0 and three items between 10.0 and 12.0
40
40
 
41
- ## Plotting
41
+ ## Plotting [experimental]
42
42
 
43
43
  You can plot!
44
44
 
@@ -73,6 +73,72 @@ histogram = MiniHistogram.new(array)
73
73
  puts UnicodePlot.histogram(histogram)
74
74
  ```
75
75
 
76
+ ## Plotting dualing histograms [experimental]
77
+
78
+ If you're plotting multiple histograms (first, please normalize the bucket sizes), second. It can be hard to compare them vertically. Here's an example:
79
+
80
+ ```
81
+ ┌ ┐
82
+ [11.2 , 11.28) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 12
83
+ [11.28, 11.36) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 22
84
+ [11.35, 11.43) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 30
85
+ [11.43, 11.51) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 17
86
+ [11.5 , 11.58) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 13
87
+ [11.58, 11.66) ┤▇▇▇▇▇▇▇ 6
88
+ [11.65, 11.73) ┤ 0
89
+ [11.73, 11.81) ┤ 0
90
+ [11.8 , 11.88) ┤ 0
91
+ └ ┘
92
+ Frequency
93
+ ┌ ┐
94
+ [11.2 , 11.28) ┤▇▇▇▇ 3
95
+ [11.28, 11.36) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 19
96
+ [11.35, 11.43) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 17
97
+ [11.43, 11.51) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 25
98
+ [11.5 , 11.58) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 15
99
+ [11.58, 11.66) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 13
100
+ [11.65, 11.73) ┤▇▇▇▇ 3
101
+ [11.73, 11.81) ┤▇▇▇▇ 3
102
+ [11.8 , 11.88) ┤▇▇▇ 2
103
+ └ ┘
104
+ Frequency
105
+ ```
106
+
107
+ Here's the same data set plotted side-by-side:
108
+
109
+ ```
110
+ ┌ ┐ ┌ ┐
111
+ [11.2 , 11.28) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 12 [11.2 , 11.28) ┤▇▇▇▇ 3
112
+ [11.28, 11.36) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 22 [11.28, 11.36) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 19
113
+ [11.35, 11.43) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 30 [11.35, 11.43) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 17
114
+ [11.43, 11.51) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 17 [11.43, 11.51) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 25
115
+ [11.5 , 11.58) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 13 [11.5 , 11.58) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 15
116
+ [11.58, 11.66) ┤▇▇▇▇▇▇▇ 6 [11.58, 11.66) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 13
117
+ [11.65, 11.73) ┤ 0 [11.65, 11.73) ┤▇▇▇▇ 3
118
+ [11.73, 11.81) ┤ 0 [11.73, 11.81) ┤▇▇▇▇ 3
119
+ [11.8 , 11.88) ┤ 0 [11.8 , 11.88) ┤▇▇▇ 2
120
+ └ ┘ └ ┘
121
+ Frequency Frequency
122
+ ```
123
+
124
+ This method might require more scrolling in the github issue, but makes it easier to compare two distributions. Here's how you plot dualing histograms:
125
+
126
+ ```
127
+ require 'mini_histogram/plot'
128
+
129
+ a = MiniHistogram.new [11.205184, 11.223665, 11.228286, 11.23219, 11.233325, 11.234516, 11.245781, 11.248441, 11.250758, 11.255686, 11.265876, 11.26641, 11.279456, 11.281067, 11.284281, 11.287656, 11.289316, 11.289682, 11.292289, 11.294518, 11.296454, 11.299277, 11.305801, 11.306602, 11.309311, 11.318465, 11.318477, 11.322258, 11.328267, 11.334188, 11.339722, 11.340585, 11.346084, 11.346197, 11.351863, 11.35982, 11.362358, 11.364476, 11.365743, 11.368492, 11.368566, 11.36869, 11.37268, 11.374204, 11.374217, 11.374955, 11.376422, 11.377989, 11.383357, 11.383593, 11.385184, 11.394766, 11.395829, 11.398455, 11.399739, 11.401304, 11.411387, 11.411978, 11.413585, 11.413659, 11.418504, 11.419194, 11.419415, 11.421374, 11.4261, 11.427901, 11.429651, 11.434272, 11.435012, 11.440848, 11.447495, 11.456107, 11.457434, 11.467112, 11.471005, 11.473235, 11.485025, 11.485852, 11.488256, 11.488275, 11.499545, 11.509588, 11.51378, 11.51544, 11.520783, 11.52246, 11.522855, 11.5322, 11.533764, 11.544047, 11.552597, 11.558062, 11.567239, 11.569749, 11.575796, 11.588014, 11.614032, 11.615062, 11.618194, 11.635267]
130
+ b = MiniHistogram.new [11.233813, 11.240717, 11.254617, 11.282013, 11.290658, 11.303213, 11.305237, 11.305299, 11.306397, 11.313867, 11.31397, 11.314444, 11.318032, 11.328111, 11.330127, 11.333235, 11.33678, 11.337799, 11.343758, 11.347798, 11.347915, 11.349594, 11.358198, 11.358507, 11.3628, 11.366111, 11.374993, 11.378195, 11.38166, 11.384867, 11.385235, 11.395825, 11.404434, 11.406065, 11.406677, 11.410244, 11.414527, 11.421267, 11.424535, 11.427231, 11.427869, 11.428548, 11.432594, 11.433524, 11.434903, 11.437769, 11.439761, 11.443437, 11.443846, 11.451106, 11.458503, 11.462256, 11.462324, 11.464342, 11.464716, 11.46477, 11.465271, 11.466843, 11.468789, 11.475492, 11.488113, 11.489616, 11.493736, 11.496842, 11.502074, 11.511367, 11.512634, 11.515562, 11.525771, 11.531415, 11.535379, 11.53966, 11.540969, 11.541265, 11.541978, 11.545301, 11.545533, 11.545701, 11.572584, 11.578881, 11.580701, 11.580922, 11.588731, 11.594082, 11.595915, 11.613622, 11.619884, 11.632889, 11.64377, 11.645225, 11.647167, 11.648257, 11.667158, 11.670378, 11.681261, 11.734586, 11.747066, 11.792425, 11.808377, 11.812346]
131
+
132
+ dual_histogram = MiniHistogram.dual_plot do |x, y|
133
+ x.histogram = a
134
+ x.options = {}
135
+ y.histogram = b
136
+ y.options = {}
137
+ end
138
+ puts dual_histogram
139
+ ```
140
+
141
+
76
142
  ## Alternatives
77
143
 
78
144
  Alternatives to this gem include https://github.com/mrkn/enumerable-statistics/. I needed this gem to be able to calculate a "shared" or "average" edge value as seen in this PR https://github.com/mrkn/enumerable-statistics/pull/23. So that I could add histograms to derailed benchmarks: https://github.com/schneems/derailed_benchmarks/pull/169. This gem provides a `MiniHistogram.set_average_edges!` method to help there. Also this gem does not require a native extension compilation (faster to install, but performance is slower), and this gem does not extend or monkeypatch an core classes.
@@ -11,9 +11,69 @@
11
11
  # require 'mini_histogram/plot'
12
12
  # array = 50.times.map { rand(11.2..11.6) }
13
13
  # histogram = MiniHistogram.new(array)
14
- # puts histogram.plot
14
+ # puts histogram.plot => Generates a plot
15
15
  #
16
16
  class MiniHistogram
17
+
18
+ # This is an object that holds a histogram
19
+ # and it's corresponding plot options
20
+ #
21
+ # Example:
22
+ #
23
+ # x = PlotValue.new
24
+ # x.values = [1,2,3,4,5]
25
+ # x.options = {xlabel: "random"}
26
+ #
27
+ # x.plot # => Generates a histogram plot with these values and options
28
+ class PlotValue
29
+ attr_accessor :histogram, :options
30
+
31
+ def initialize
32
+ @histogram = nil
33
+ @options = {}
34
+ end
35
+
36
+ def plot
37
+ raise "@histogram cannot be empty set via `values=` or `histogram=` methods" if @histogram.nil?
38
+
39
+ @histogram.plot(**@options)
40
+ end
41
+
42
+ def values=(values)
43
+ @histogram = MiniHistogram.new(values)
44
+ end
45
+
46
+ def self.dual_plot(plot_a, plot_b)
47
+ a_lines = plot_a.to_s.lines
48
+ b_lines = plot_b.to_s.lines
49
+
50
+ max_length = a_lines.map(&:length).max
51
+
52
+ side_by_side = String.new("")
53
+ a_lines.each_index do |i|
54
+ side_by_side << a_lines[i].chomp.ljust(max_length) # Remove newline, ensure same length
55
+ side_by_side << b_lines[i]
56
+ end
57
+
58
+ return side_by_side
59
+ end
60
+ end
61
+ private_constant :PlotValue
62
+
63
+ def self.dual_plot
64
+ a = PlotValue.new
65
+ b = PlotValue.new
66
+
67
+ yield a, b
68
+
69
+ if b.options[:ylabel] == a.options[:ylabel]
70
+ b.options[:ylabel] = nil
71
+ end
72
+
73
+ MiniHistogram.set_average_edges!(a.histogram, b.histogram)
74
+ PlotValue.dual_plot(a.plot, b.plot)
75
+ end
76
+
17
77
  def plot(
18
78
  nbins: nil,
19
79
  closed: :left,
@@ -50,7 +110,7 @@ class MiniHistogram
50
110
  "\e[90m#{r_str}\e[0m"
51
111
  end
52
112
  xscale = kw.delete(:xscale)
53
- xlabel = kw.delete(:xlabel) || ValueTransformer.transform_name(xscale, "Frequency")
113
+ xlabel = kw.delete(:xlabel) || MiniUnicodePlot::ValueTransformer.transform_name(xscale, "Frequency")
54
114
  barplot(labels, counts,
55
115
  symbol: symbol,
56
116
  xscale: xscale,
@@ -58,6 +118,7 @@ class MiniHistogram
58
118
  **kw)
59
119
  end
60
120
 
121
+ ## Begin copy/pasta from unicode_plot.rb with some slight modifications
61
122
  private def barplot(
62
123
  *args,
63
124
  width: 40,
@@ -88,7 +149,7 @@ class MiniHistogram
88
149
  end
89
150
 
90
151
  xlabel ||= ValueTransformer.transform_name(xscale)
91
- plot = Barplot.new(heights, width, color, symbol, xscale,
152
+ plot = MiniUnicodePlot::Barplot.new(heights, width, color, symbol, xscale,
92
153
  border: border, xlabel: xlabel,
93
154
  **kw)
94
155
  keys.each_with_index do |key, i|
@@ -122,597 +183,600 @@ class MiniHistogram
122
183
  x.to_i == x && INT64_MIN <= x && x < INT64_MAX
123
184
  end
124
185
 
125
- module ValueTransformer
126
- PREDEFINED_TRANSFORM_FUNCTIONS = {
127
- log: Math.method(:log),
128
- ln: Math.method(:log),
129
- log10: Math.method(:log10),
130
- lg: Math.method(:log10),
131
- log2: Math.method(:log2),
132
- lb: Math.method(:log2),
133
- }.freeze
186
+ module MiniUnicodePlot
187
+ module ValueTransformer
188
+ PREDEFINED_TRANSFORM_FUNCTIONS = {
189
+ log: Math.method(:log),
190
+ ln: Math.method(:log),
191
+ log10: Math.method(:log10),
192
+ lg: Math.method(:log10),
193
+ log2: Math.method(:log2),
194
+ lb: Math.method(:log2),
195
+ }.freeze
134
196
 
135
- def transform_values(func, values)
136
- return values unless func
197
+ def transform_values(func, values)
198
+ return values unless func
137
199
 
138
- unless func.respond_to?(:call)
139
- func = PREDEFINED_TRANSFORM_FUNCTIONS[func]
140
200
  unless func.respond_to?(:call)
141
- raise ArgumentError, "func must be callable"
201
+ func = PREDEFINED_TRANSFORM_FUNCTIONS[func]
202
+ unless func.respond_to?(:call)
203
+ raise ArgumentError, "func must be callable"
204
+ end
142
205
  end
143
- end
144
206
 
145
- case values
146
- when Numeric
147
- func.(values)
148
- else
149
- values.map(&func)
207
+ case values
208
+ when Numeric
209
+ func.(values)
210
+ else
211
+ values.map(&func)
212
+ end
150
213
  end
151
- end
152
214
 
153
- module_function def transform_name(func, basename="")
154
- return basename unless func
155
- case func
156
- when String, Symbol
157
- name = func
158
- when ->(f) { f.respond_to?(:name) }
159
- name = func.name
160
- else
161
- name = "custom"
162
- end
163
- "#{basename} [#{name}]"
215
+ module_function def transform_name(func, basename="")
216
+ return basename unless func
217
+ case func
218
+ when String, Symbol
219
+ name = func
220
+ when ->(f) { f.respond_to?(:name) }
221
+ name = func.name
222
+ else
223
+ name = "custom"
224
+ end
225
+ "#{basename} [#{name}]"
226
+ end
164
227
  end
165
- end
166
228
 
167
229
 
168
- module BorderMaps
169
- BORDER_SOLID = {
170
- tl: "┌",
171
- tr: "┐",
172
- bl: "└",
173
- br: "┘",
174
- t: "─",
175
- l: "│",
176
- b: "─",
177
- r: "│"
178
- }.freeze
179
-
180
- BORDER_CORNERS = {
181
- tl: "┌",
182
- tr: "┐",
183
- bl: "└",
184
- br: "┘",
185
- t: " ",
186
- l: " ",
187
- b: " ",
188
- r: " ",
230
+ module BorderMaps
231
+ BORDER_SOLID = {
232
+ tl: "┌",
233
+ tr: "┐",
234
+ bl: "└",
235
+ br: "┘",
236
+ t: "─",
237
+ l: "│",
238
+ b: "─",
239
+ r: "│"
240
+ }.freeze
241
+
242
+ BORDER_CORNERS = {
243
+ tl: "┌",
244
+ tr: "┐",
245
+ bl: "└",
246
+ br: "┘",
247
+ t: " ",
248
+ l: " ",
249
+ b: " ",
250
+ r: " ",
251
+ }.freeze
252
+
253
+ BORDER_BARPLOT = {
254
+ tl: "┌",
255
+ tr: "┐",
256
+ bl: "└",
257
+ br: "┘",
258
+ t: " ",
259
+ l: "┤",
260
+ b: " ",
261
+ r: " ",
262
+ }.freeze
263
+ end
264
+
265
+ BORDER_MAP = {
266
+ solid: BorderMaps::BORDER_SOLID,
267
+ corners: BorderMaps::BORDER_CORNERS,
268
+ barplot: BorderMaps::BORDER_BARPLOT,
189
269
  }.freeze
190
270
 
191
- BORDER_BARPLOT = {
192
- tl: "┌",
193
- tr: "",
194
- bl: "",
195
- br: "",
196
- t: " ",
197
- l: "",
198
- b: " ",
199
- r: " ",
200
- }.freeze
201
- end
202
-
203
- BORDER_MAP = {
204
- solid: BorderMaps::BORDER_SOLID,
205
- corners: BorderMaps::BORDER_CORNERS,
206
- barplot: BorderMaps::BORDER_BARPLOT,
207
- }.freeze
208
-
209
- module StyledPrinter
210
- TEXT_COLORS = {
211
- black: "\033[30m",
212
- red: "\033[31m",
213
- green: "\033[32m",
214
- yellow: "\033[33m",
215
- blue: "\033[34m",
216
- magenta: "\033[35m",
217
- cyan: "\033[36m",
218
- white: "\033[37m",
219
- gray: "\033[90m",
220
- light_black: "\033[90m",
221
- light_red: "\033[91m",
222
- light_green: "\033[92m",
223
- light_yellow: "\033[93m",
224
- light_blue: "\033[94m",
225
- light_magenta: "\033[95m",
226
- light_cyan: "\033[96m",
227
- normal: "\033[0m",
228
- default: "\033[39m",
229
- bold: "\033[1m",
230
- underline: "\033[4m",
231
- blink: "\033[5m",
232
- reverse: "\033[7m",
233
- hidden: "\033[8m",
234
- nothing: "",
235
- }
236
-
237
- 0.upto(255) do |i|
238
- TEXT_COLORS[i] = "\033[38;5;#{i}m"
239
- end
240
-
241
- TEXT_COLORS.freeze
242
-
243
- DISABLE_TEXT_STYLE = {
244
- bold: "\033[22m",
245
- underline: "\033[24m",
246
- blink: "\033[25m",
247
- reverse: "\033[27m",
248
- hidden: "\033[28m",
249
- normal: "",
250
- default: "",
251
- nothing: "",
252
- }.freeze
253
-
254
- COLOR_ENCODE = {
255
- normal: 0b000,
256
- blue: 0b001,
257
- red: 0b010,
258
- magenta: 0b011,
259
- green: 0b100,
260
- cyan: 0b101,
261
- yellow: 0b110,
262
- white: 0b111
263
- }.freeze
271
+ module StyledPrinter
272
+ TEXT_COLORS = {
273
+ black: "\033[30m",
274
+ red: "\033[31m",
275
+ green: "\033[32m",
276
+ yellow: "\033[33m",
277
+ blue: "\033[34m",
278
+ magenta: "\033[35m",
279
+ cyan: "\033[36m",
280
+ white: "\033[37m",
281
+ gray: "\033[90m",
282
+ light_black: "\033[90m",
283
+ light_red: "\033[91m",
284
+ light_green: "\033[92m",
285
+ light_yellow: "\033[93m",
286
+ light_blue: "\033[94m",
287
+ light_magenta: "\033[95m",
288
+ light_cyan: "\033[96m",
289
+ normal: "\033[0m",
290
+ default: "\033[39m",
291
+ bold: "\033[1m",
292
+ underline: "\033[4m",
293
+ blink: "\033[5m",
294
+ reverse: "\033[7m",
295
+ hidden: "\033[8m",
296
+ nothing: "",
297
+ }
298
+
299
+ 0.upto(255) do |i|
300
+ TEXT_COLORS[i] = "\033[38;5;#{i}m"
301
+ end
264
302
 
265
- COLOR_DECODE = COLOR_ENCODE.map {|k, v| [v, k] }.to_h.freeze
266
-
267
- def print_styled(out, *args, bold: false, color: :normal)
268
- return out.print(*args) unless color?(out)
269
-
270
- str = StringIO.open {|sio| sio.print(*args); sio.close; sio.string }
271
- color = :nothing if bold && color == :bold
272
- enable_ansi = TEXT_COLORS.fetch(color, TEXT_COLORS[:default]) +
273
- (bold ? TEXT_COLORS[:bold] : "")
274
- disable_ansi = (bold ? DISABLE_TEXT_STYLE[:bold] : "") +
275
- DISABLE_TEXT_STYLE.fetch(color, TEXT_COLORS[:default])
276
- first = true
277
- StringIO.open do |sio|
278
- str.each_line do |line|
279
- sio.puts unless first
280
- first = false
281
- continue if line.empty?
282
- sio.print(enable_ansi, line, disable_ansi)
303
+ TEXT_COLORS.freeze
304
+
305
+ DISABLE_TEXT_STYLE = {
306
+ bold: "\033[22m",
307
+ underline: "\033[24m",
308
+ blink: "\033[25m",
309
+ reverse: "\033[27m",
310
+ hidden: "\033[28m",
311
+ normal: "",
312
+ default: "",
313
+ nothing: "",
314
+ }.freeze
315
+
316
+ COLOR_ENCODE = {
317
+ normal: 0b000,
318
+ blue: 0b001,
319
+ red: 0b010,
320
+ magenta: 0b011,
321
+ green: 0b100,
322
+ cyan: 0b101,
323
+ yellow: 0b110,
324
+ white: 0b111
325
+ }.freeze
326
+
327
+ COLOR_DECODE = COLOR_ENCODE.map {|k, v| [v, k] }.to_h.freeze
328
+
329
+ def print_styled(out, *args, bold: false, color: :normal)
330
+ return out.print(*args) unless color?(out)
331
+
332
+ str = StringIO.open {|sio| sio.print(*args); sio.close; sio.string }
333
+ color = :nothing if bold && color == :bold
334
+ enable_ansi = TEXT_COLORS.fetch(color, TEXT_COLORS[:default]) +
335
+ (bold ? TEXT_COLORS[:bold] : "")
336
+ disable_ansi = (bold ? DISABLE_TEXT_STYLE[:bold] : "") +
337
+ DISABLE_TEXT_STYLE.fetch(color, TEXT_COLORS[:default])
338
+ first = true
339
+ StringIO.open do |sio|
340
+ str.each_line do |line|
341
+ sio.puts unless first
342
+ first = false
343
+ continue if line.empty?
344
+ sio.print(enable_ansi, line, disable_ansi)
345
+ end
346
+ sio.close
347
+ out.print(sio.string)
283
348
  end
284
- sio.close
285
- out.print(sio.string)
286
349
  end
287
- end
288
350
 
289
- def print_color(out, color, *args)
290
- color = COLOR_DECODE[color]
291
- print_styled(out, *args, color: color)
292
- end
351
+ def print_color(out, color, *args)
352
+ color = COLOR_DECODE[color]
353
+ print_styled(out, *args, color: color)
354
+ end
293
355
 
294
- def color?(out)
295
- (out && out.tty?) || false
356
+ def color?(out)
357
+ (out && out.tty?) || false
358
+ end
296
359
  end
297
- end
298
360
 
299
- module BorderPrinter
300
- include StyledPrinter
361
+ module BorderPrinter
362
+ include StyledPrinter
301
363
 
302
- def print_border_top(out, padding, length, border=:solid, color: :light_black)
303
- return if border == :none
304
- b = BORDER_MAP[border]
305
- print_styled(out, padding, b[:tl], b[:t] * length, b[:tr], color: color)
306
- end
364
+ def print_border_top(out, padding, length, border=:solid, color: :light_black)
365
+ return if border == :none
366
+ b = BORDER_MAP[border]
367
+ print_styled(out, padding, b[:tl], b[:t] * length, b[:tr], color: color)
368
+ end
307
369
 
308
- def print_border_bottom(out, padding, length, border=:solid, color: :light_black)
309
- return if border == :none
310
- b = BORDER_MAP[border]
311
- print_styled(out, padding, b[:bl], b[:b] * length, b[:br], color: color)
370
+ def print_border_bottom(out, padding, length, border=:solid, color: :light_black)
371
+ return if border == :none
372
+ b = BORDER_MAP[border]
373
+ print_styled(out, padding, b[:bl], b[:b] * length, b[:br], color: color)
374
+ end
312
375
  end
313
- end
314
376
 
315
- class Renderer
316
- include BorderPrinter
377
+ class Renderer
378
+ include BorderPrinter
317
379
 
318
- def self.render(out, plot)
319
- new(plot).render(out)
320
- end
380
+ def self.render(out, plot)
381
+ new(plot).render(out)
382
+ end
321
383
 
322
- def initialize(plot)
323
- @plot = plot
324
- @out = nil
325
- end
384
+ def initialize(plot)
385
+ @plot = plot
386
+ @out = nil
387
+ end
326
388
 
327
- attr_reader :plot
328
- attr_reader :out
389
+ attr_reader :plot
390
+ attr_reader :out
329
391
 
330
- def render(out)
331
- @out = out
332
- init_render
392
+ def render(out)
393
+ @out = out
394
+ init_render
333
395
 
334
- render_top
335
- render_rows
336
- render_bottom
337
- end
396
+ render_top
397
+ render_rows
398
+ render_bottom
399
+ end
338
400
 
339
- private
340
-
341
- def render_top
342
- # plot the title and the top border
343
- print_title(@border_padding, plot.title, p_width: @border_length, color: :bold)
344
- puts if plot.title_given?
345
-
346
- if plot.show_labels?
347
- topleft_str = plot.decorations.fetch(:tl, "")
348
- topleft_col = plot.colors_deco.fetch(:tl, :light_black)
349
- topmid_str = plot.decorations.fetch(:t, "")
350
- topmid_col = plot.colors_deco.fetch(:t, :light_black)
351
- topright_str = plot.decorations.fetch(:tr, "")
352
- topright_col = plot.colors_deco.fetch(:tr, :light_black)
353
-
354
- if topleft_str != "" || topright_str != "" || topmid_str != ""
355
- topleft_len = topleft_str.length
356
- topmid_len = topmid_str.length
357
- topright_len = topright_str.length
358
- print_styled(out, @border_padding, topleft_str, color: topleft_col)
359
- cnt = (@border_length / 2.0 - topmid_len / 2.0 - topleft_len).round
360
- pad = cnt > 0 ? " " * cnt : ""
361
- print_styled(out, pad, topmid_str, color: topmid_col)
362
- cnt = @border_length - topright_len - topleft_len - topmid_len + 2 - cnt
363
- pad = cnt > 0 ? " " * cnt : ""
364
- print_styled(out, pad, topright_str, "\n", color: topright_col)
401
+ private
402
+
403
+ def render_top
404
+ # plot the title and the top border
405
+ print_title(@border_padding, plot.title, p_width: @border_length, color: :bold)
406
+ puts if plot.title_given?
407
+
408
+ if plot.show_labels?
409
+ topleft_str = plot.decorations.fetch(:tl, "")
410
+ topleft_col = plot.colors_deco.fetch(:tl, :light_black)
411
+ topmid_str = plot.decorations.fetch(:t, "")
412
+ topmid_col = plot.colors_deco.fetch(:t, :light_black)
413
+ topright_str = plot.decorations.fetch(:tr, "")
414
+ topright_col = plot.colors_deco.fetch(:tr, :light_black)
415
+
416
+ if topleft_str != "" || topright_str != "" || topmid_str != ""
417
+ topleft_len = topleft_str.length
418
+ topmid_len = topmid_str.length
419
+ topright_len = topright_str.length
420
+ print_styled(out, @border_padding, topleft_str, color: topleft_col)
421
+ cnt = (@border_length / 2.0 - topmid_len / 2.0 - topleft_len).round
422
+ pad = cnt > 0 ? " " * cnt : ""
423
+ print_styled(out, pad, topmid_str, color: topmid_col)
424
+ cnt = @border_length - topright_len - topleft_len - topmid_len + 2 - cnt
425
+ pad = cnt > 0 ? " " * cnt : ""
426
+ print_styled(out, pad, topright_str, "\n", color: topright_col)
427
+ end
365
428
  end
366
- end
367
429
 
368
- print_border_top(out, @border_padding, @border_length, plot.border)
369
- print(" " * @max_len_r, @plot_padding, "\n")
370
- end
430
+ print_border_top(out, @border_padding, @border_length, plot.border)
431
+ print(" " * @max_len_r, @plot_padding, "\n")
432
+ end
371
433
 
372
- # render all rows
373
- def render_rows
374
- (0 ... plot.n_rows).each {|row| render_row(row) }
375
- end
434
+ # render all rows
435
+ def render_rows
436
+ (0 ... plot.n_rows).each {|row| render_row(row) }
437
+ end
376
438
 
377
- def render_row(row)
378
- # Current labels to left and right of the row and their length
379
- left_str = plot.labels_left.fetch(row, "")
380
- left_col = plot.colors_left.fetch(row, :light_black)
381
- right_str = plot.labels_right.fetch(row, "")
382
- right_col = plot.colors_right.fetch(row, :light_black)
383
- left_len = nocolor_string(left_str).length
384
- right_len = nocolor_string(right_str).length
385
-
386
- unless color?(out)
387
- left_str = nocolor_string(left_str)
388
- right_str = nocolor_string(right_str)
389
- end
390
-
391
- # print left annotations
392
- print(" " * plot.margin)
393
- if plot.show_labels?
394
- if row == @y_lab_row
395
- # print ylabel
396
- print_styled(out, plot.ylabel, color: :normal)
397
- print(" " * (@max_len_l - plot.ylabel_length - left_len))
398
- else
399
- # print padding to fill ylabel length
400
- print(" " * (@max_len_l - left_len))
439
+ def render_row(row)
440
+ # Current labels to left and right of the row and their length
441
+ left_str = plot.labels_left.fetch(row, "")
442
+ left_col = plot.colors_left.fetch(row, :light_black)
443
+ right_str = plot.labels_right.fetch(row, "")
444
+ right_col = plot.colors_right.fetch(row, :light_black)
445
+ left_len = nocolor_string(left_str).length
446
+ right_len = nocolor_string(right_str).length
447
+
448
+ unless color?(out)
449
+ left_str = nocolor_string(left_str)
450
+ right_str = nocolor_string(right_str)
401
451
  end
402
- # print the left annotation
403
- print_styled(out, left_str, color: left_col)
404
- end
405
452
 
406
- # print left border
407
- print_styled(out, @plot_padding, @b[:l], color: :light_black)
453
+ # print left annotations
454
+ print(" " * plot.margin)
455
+ if plot.show_labels?
456
+ if row == @y_lab_row
457
+ # print ylabel
458
+ print_styled(out, plot.ylabel, color: :normal)
459
+ print(" " * (@max_len_l - plot.ylabel_length - left_len))
460
+ else
461
+ # print padding to fill ylabel length
462
+ print(" " * (@max_len_l - left_len))
463
+ end
464
+ # print the left annotation
465
+ print_styled(out, left_str, color: left_col)
466
+ end
408
467
 
409
- # print canvas row
410
- plot.print_row(out, row)
468
+ # print left border
469
+ print_styled(out, @plot_padding, @b[:l], color: :light_black)
411
470
 
412
- #print right label and padding
413
- print_styled(out, @b[:r], color: :light_black)
414
- if plot.show_labels?
415
- print(@plot_padding)
416
- print_styled(out, right_str, color: right_col)
417
- print(" " * (@max_len_r - right_len))
418
- end
419
- puts
420
- end
471
+ # print canvas row
472
+ plot.print_row(out, row)
421
473
 
422
- def render_bottom
423
- # draw bottom border and bottom labels
424
- print_border_bottom(out, @border_padding, @border_length, plot.border)
425
- print(" " * @max_len_r, @plot_padding)
426
- if plot.show_labels?
427
- botleft_str = plot.decorations.fetch(:bl, "")
428
- botleft_col = plot.colors_deco.fetch(:bl, :light_black)
429
- botmid_str = plot.decorations.fetch(:b, "")
430
- botmid_col = plot.colors_deco.fetch(:b, :light_black)
431
- botright_str = plot.decorations.fetch(:br, "")
432
- botright_col = plot.colors_deco.fetch(:br, :light_black)
433
-
434
- if botleft_str != "" || botright_str != "" || botmid_str != ""
435
- puts
436
- botleft_len = botleft_str.length
437
- botmid_len = botmid_str.length
438
- botright_len = botright_str.length
439
- print_styled(out, @border_padding, botleft_str, color: botleft_col)
440
- cnt = (@border_length / 2.0 - botmid_len / 2.0 - botleft_len).round
441
- pad = cnt > 0 ? " " * cnt : ""
442
- print_styled(out, pad, botmid_str, color: botmid_col)
443
- cnt = @border_length - botright_len - botleft_len - botmid_len + 2 - cnt
444
- pad = cnt > 0 ? " " * cnt : ""
445
- print_styled(out, pad, botright_str, color: botright_col)
474
+ #print right label and padding
475
+ print_styled(out, @b[:r], color: :light_black)
476
+ if plot.show_labels?
477
+ print(@plot_padding)
478
+ print_styled(out, right_str, color: right_col)
479
+ print(" " * (@max_len_r - right_len))
446
480
  end
447
-
448
- # abuse the print_title function to print the xlabel. maybe refactor this
449
- puts if plot.xlabel_given?
450
- print_title(@border_padding, plot.xlabel, p_width: @border_length)
481
+ puts
451
482
  end
452
- end
453
483
 
454
- def init_render
455
- @b = BORDER_MAP[plot.border]
456
- @border_length = plot.n_columns
484
+ def render_bottom
485
+ # draw bottom border and bottom labels
486
+ print_border_bottom(out, @border_padding, @border_length, plot.border)
487
+ print(" " * @max_len_r, @plot_padding)
488
+ if plot.show_labels?
489
+ botleft_str = plot.decorations.fetch(:bl, "")
490
+ botleft_col = plot.colors_deco.fetch(:bl, :light_black)
491
+ botmid_str = plot.decorations.fetch(:b, "")
492
+ botmid_col = plot.colors_deco.fetch(:b, :light_black)
493
+ botright_str = plot.decorations.fetch(:br, "")
494
+ botright_col = plot.colors_deco.fetch(:br, :light_black)
495
+
496
+ if botleft_str != "" || botright_str != "" || botmid_str != ""
497
+ puts
498
+ botleft_len = botleft_str.length
499
+ botmid_len = botmid_str.length
500
+ botright_len = botright_str.length
501
+ print_styled(out, @border_padding, botleft_str, color: botleft_col)
502
+ cnt = (@border_length / 2.0 - botmid_len / 2.0 - botleft_len).round
503
+ pad = cnt > 0 ? " " * cnt : ""
504
+ print_styled(out, pad, botmid_str, color: botmid_col)
505
+ cnt = @border_length - botright_len - botleft_len - botmid_len + 2 - cnt
506
+ pad = cnt > 0 ? " " * cnt : ""
507
+ print_styled(out, pad, botright_str, color: botright_col)
508
+ end
457
509
 
458
- # get length of largest strings to the left and right
459
- @max_len_l = plot.show_labels? && !plot.labels_left.empty? ?
460
- plot.labels_left.each_value.map {|l| nocolor_string(l).length }.max :
461
- 0
462
- @max_len_r = plot.show_labels? && !plot.labels_right.empty? ?
463
- plot.labels_right.each_value.map {|l| nocolor_string(l).length }.max :
464
- 0
465
- if plot.show_labels? && plot.ylabel_given?
466
- @max_len_l += plot.ylabel_length + 1
510
+ # abuse the print_title function to print the xlabel. maybe refactor this
511
+ puts if plot.xlabel_given?
512
+ print_title(@border_padding, plot.xlabel, p_width: @border_length)
513
+ end
467
514
  end
468
515
 
469
- # offset where the plot (incl border) begins
470
- @plot_offset = @max_len_l + plot.margin + plot.padding
516
+ def init_render
517
+ @b = BORDER_MAP[plot.border]
518
+ @border_length = plot.n_columns
519
+
520
+ # get length of largest strings to the left and right
521
+ @max_len_l = plot.show_labels? && !plot.labels_left.empty? ?
522
+ plot.labels_left.each_value.map {|l| nocolor_string(l).length }.max :
523
+ 0
524
+ @max_len_r = plot.show_labels? && !plot.labels_right.empty? ?
525
+ plot.labels_right.each_value.map {|l| nocolor_string(l).length }.max :
526
+ 0
527
+ if plot.show_labels? && plot.ylabel_given?
528
+ @max_len_l += plot.ylabel_length + 1
529
+ end
471
530
 
472
- # padding-string from left to border
473
- @plot_padding = " " * plot.padding
531
+ # offset where the plot (incl border) begins
532
+ @plot_offset = @max_len_l + plot.margin + plot.padding
474
533
 
475
- # padding-string between labels and border
476
- @border_padding = " " * @plot_offset
534
+ # padding-string from left to border
535
+ @plot_padding = " " * plot.padding
477
536
 
478
- # compute position of ylabel
479
- @y_lab_row = (plot.n_rows / 2.0).round - 1
480
- end
537
+ # padding-string between labels and border
538
+ @border_padding = " " * @plot_offset
481
539
 
482
- def print_title(padding, title, p_width: 0, color: :normal)
483
- return unless title && title != ""
484
- offset = (p_width / 2.0 - title.length / 2.0).round
485
- offset = [offset, 0].max
486
- tpad = " " * offset
487
- print_styled(out, padding, tpad, title, color: color)
488
- end
540
+ # compute position of ylabel
541
+ @y_lab_row = (plot.n_rows / 2.0).round - 1
542
+ end
489
543
 
490
- def print(*args)
491
- out.print(*args)
492
- end
544
+ def print_title(padding, title, p_width: 0, color: :normal)
545
+ return unless title && title != ""
546
+ offset = (p_width / 2.0 - title.length / 2.0).round
547
+ offset = [offset, 0].max
548
+ tpad = " " * offset
549
+ print_styled(out, padding, tpad, title, color: color)
550
+ end
493
551
 
494
- def puts(*args)
495
- out.puts(*args)
496
- end
552
+ def print(*args)
553
+ out.print(*args)
554
+ end
497
555
 
498
- def nocolor_string(str)
499
- str.to_s.gsub(/\e\[[0-9]+m/, "")
500
- end
501
- end
556
+ def puts(*args)
557
+ out.puts(*args)
558
+ end
502
559
 
503
- class Plot
504
- include StyledPrinter
505
-
506
- DEFAULT_WIDTH = 40
507
- DEFAULT_BORDER = :solid
508
- DEFAULT_MARGIN = 3
509
- DEFAULT_PADDING = 1
510
-
511
- def initialize(title: nil,
512
- xlabel: nil,
513
- ylabel: nil,
514
- border: DEFAULT_BORDER,
515
- margin: DEFAULT_MARGIN,
516
- padding: DEFAULT_PADDING,
517
- labels: true)
518
- @title = title
519
- @xlabel = xlabel
520
- @ylabel = ylabel
521
- @border = border
522
- @margin = check_margin(margin)
523
- @padding = padding
524
- @labels_left = {}
525
- @colors_left = {}
526
- @labels_right = {}
527
- @colors_right = {}
528
- @decorations = {}
529
- @colors_deco = {}
530
- @show_labels = labels
531
- @auto_color = 0
560
+ def nocolor_string(str)
561
+ str.to_s.gsub(/\e\[[0-9]+m/, "")
562
+ end
532
563
  end
533
564
 
534
- attr_reader :title
535
- attr_reader :xlabel
536
- attr_reader :ylabel
537
- attr_reader :border
538
- attr_reader :margin
539
- attr_reader :padding
540
- attr_reader :labels_left
541
- attr_reader :colors_left
542
- attr_reader :labels_right
543
- attr_reader :colors_right
544
- attr_reader :decorations
545
- attr_reader :colors_deco
546
-
547
- def title_given?
548
- title && title != ""
549
- end
565
+ class Plot
566
+ include StyledPrinter
567
+
568
+ DEFAULT_WIDTH = 40
569
+ DEFAULT_BORDER = :solid
570
+ DEFAULT_MARGIN = 3
571
+ DEFAULT_PADDING = 1
572
+
573
+ def initialize(title: nil,
574
+ xlabel: nil,
575
+ ylabel: nil,
576
+ border: DEFAULT_BORDER,
577
+ margin: DEFAULT_MARGIN,
578
+ padding: DEFAULT_PADDING,
579
+ labels: true)
580
+ @title = title
581
+ @xlabel = xlabel
582
+ @ylabel = ylabel
583
+ @border = border
584
+ @margin = check_margin(margin)
585
+ @padding = padding
586
+ @labels_left = {}
587
+ @colors_left = {}
588
+ @labels_right = {}
589
+ @colors_right = {}
590
+ @decorations = {}
591
+ @colors_deco = {}
592
+ @show_labels = labels
593
+ @auto_color = 0
594
+ end
550
595
 
551
- def xlabel_given?
552
- xlabel && xlabel != ""
553
- end
596
+ attr_reader :title
597
+ attr_reader :xlabel
598
+ attr_reader :ylabel
599
+ attr_reader :border
600
+ attr_reader :margin
601
+ attr_reader :padding
602
+ attr_reader :labels_left
603
+ attr_reader :colors_left
604
+ attr_reader :labels_right
605
+ attr_reader :colors_right
606
+ attr_reader :decorations
607
+ attr_reader :colors_deco
608
+
609
+ def title_given?
610
+ title && title != ""
611
+ end
554
612
 
555
- def ylabel_given?
556
- ylabel && ylabel != ""
557
- end
613
+ def xlabel_given?
614
+ xlabel && xlabel != ""
615
+ end
558
616
 
559
- def ylabel_length
560
- (ylabel && ylabel.length) || 0
561
- end
617
+ def ylabel_given?
618
+ ylabel && ylabel != ""
619
+ end
562
620
 
563
- def show_labels?
564
- @show_labels
565
- end
621
+ def ylabel_length
622
+ (ylabel && ylabel.length) || 0
623
+ end
624
+
625
+ def show_labels?
626
+ @show_labels
627
+ end
566
628
 
567
- def annotate!(loc, value, color: :normal)
568
- case loc
569
- when :l
570
- (0 ... n_rows).each do |row|
571
- if @labels_left.fetch(row, "") == ""
572
- @labels_left[row] = value
573
- @colors_left[row] = color
574
- break
629
+ def annotate!(loc, value, color: :normal)
630
+ case loc
631
+ when :l
632
+ (0 ... n_rows).each do |row|
633
+ if @labels_left.fetch(row, "") == ""
634
+ @labels_left[row] = value
635
+ @colors_left[row] = color
636
+ break
637
+ end
575
638
  end
576
- end
577
- when :r
578
- (0 ... n_rows).each do |row|
579
- if @labels_right.fetch(row, "") == ""
580
- @labels_right[row] = value
581
- @colors_right[row] = color
582
- break
639
+ when :r
640
+ (0 ... n_rows).each do |row|
641
+ if @labels_right.fetch(row, "") == ""
642
+ @labels_right[row] = value
643
+ @colors_right[row] = color
644
+ break
645
+ end
583
646
  end
647
+ when :t, :b, :tl, :tr, :bl, :br
648
+ @decorations[loc] = value
649
+ @colors_deco[loc] = color
650
+ else
651
+ raise ArgumentError,
652
+ "unknown location to annotate (#{loc.inspect} for :t, :b, :l, :r, :tl, :tr, :bl, or :br)"
584
653
  end
585
- when :t, :b, :tl, :tr, :bl, :br
586
- @decorations[loc] = value
587
- @colors_deco[loc] = color
588
- else
589
- raise ArgumentError,
590
- "unknown location to annotate (#{loc.inspect} for :t, :b, :l, :r, :tl, :tr, :bl, or :br)"
591
654
  end
592
- end
593
655
 
594
- def annotate_row!(loc, row_index, value, color: :normal)
595
- case loc
596
- when :l
597
- @labels_left[row_index] = value
598
- @colors_left[row_index] = color
599
- when :r
600
- @labels_right[row_index] = value
601
- @colors_right[row_index] = color
602
- else
603
- raise ArgumentError, "unknown location `#{loc}`, try :l or :r instead"
656
+ def annotate_row!(loc, row_index, value, color: :normal)
657
+ case loc
658
+ when :l
659
+ @labels_left[row_index] = value
660
+ @colors_left[row_index] = color
661
+ when :r
662
+ @labels_right[row_index] = value
663
+ @colors_right[row_index] = color
664
+ else
665
+ raise ArgumentError, "unknown location `#{loc}`, try :l or :r instead"
666
+ end
604
667
  end
605
- end
606
668
 
607
- def render(out)
608
- Renderer.render(out, self)
609
- end
669
+ def render(out)
670
+ Renderer.render(out, self)
671
+ end
610
672
 
611
- COLOR_CYCLE = [
612
- :green,
613
- :blue,
614
- :red,
615
- :magenta,
616
- :yellow,
617
- :cyan
618
- ].freeze
619
-
620
- def next_color
621
- COLOR_CYCLE[@auto_color]
622
- ensure
623
- @auto_color = (@auto_color + 1) % COLOR_CYCLE.length
624
- end
673
+ COLOR_CYCLE = [
674
+ :green,
675
+ :blue,
676
+ :red,
677
+ :magenta,
678
+ :yellow,
679
+ :cyan
680
+ ].freeze
681
+
682
+ def next_color
683
+ COLOR_CYCLE[@auto_color]
684
+ ensure
685
+ @auto_color = (@auto_color + 1) % COLOR_CYCLE.length
686
+ end
625
687
 
626
- def to_s
627
- StringIO.open do |sio|
628
- render(sio)
629
- sio.close
630
- sio.string
688
+ def to_s
689
+ StringIO.open do |sio|
690
+ render(sio)
691
+ sio.close
692
+ sio.string
693
+ end
631
694
  end
632
- end
633
695
 
634
- private def check_margin(margin)
635
- if margin < 0
636
- raise ArgumentError, "margin must be >= 0"
696
+ private def check_margin(margin)
697
+ if margin < 0
698
+ raise ArgumentError, "margin must be >= 0"
699
+ end
700
+ margin
637
701
  end
638
- margin
639
- end
640
702
 
641
- private def check_row_index(row_index)
642
- unless 0 <= row_index && row_index < n_rows
643
- raise ArgumentError, "row_index out of bounds"
703
+ private def check_row_index(row_index)
704
+ unless 0 <= row_index && row_index < n_rows
705
+ raise ArgumentError, "row_index out of bounds"
706
+ end
644
707
  end
645
708
  end
646
- end
647
709
 
648
- class Barplot < Plot
649
- include ValueTransformer
650
-
651
- MIN_WIDTH = 10
652
- DEFAULT_COLOR = :green
653
- DEFAULT_SYMBOL = "■"
654
-
655
- def initialize(bars, width, color, symbol, transform, **kw)
656
- if symbol.length > 1
657
- raise ArgumentError, "symbol must be a single character"
658
- end
659
- @bars = bars
660
- @symbol = symbol
661
- @max_freq, i = find_max(transform_values(transform, bars))
662
- @max_len = bars[i].to_s.length
663
- @width = [width, max_len + 7, MIN_WIDTH].max
664
- @color = color
665
- @symbol = symbol
666
- @transform = transform
667
- super(**kw)
668
- end
710
+ class Barplot < Plot
711
+ include ValueTransformer
669
712
 
670
- attr_reader :max_freq
671
- attr_reader :max_len
672
- attr_reader :width
713
+ MIN_WIDTH = 10
714
+ DEFAULT_COLOR = :green
715
+ DEFAULT_SYMBOL = "■"
673
716
 
674
- def n_rows
675
- @bars.length
676
- end
717
+ def initialize(bars, width, color, symbol, transform, **kw)
718
+ if symbol.length > 1
719
+ raise ArgumentError, "symbol must be a single character"
720
+ end
721
+ @bars = bars
722
+ @symbol = symbol
723
+ @max_freq, i = find_max(transform_values(transform, bars))
724
+ @max_len = bars[i].to_s.length
725
+ @width = [width, max_len + 7, MIN_WIDTH].max
726
+ @color = color
727
+ @symbol = symbol
728
+ @transform = transform
729
+ super(**kw)
730
+ end
677
731
 
678
- def n_columns
679
- @width
680
- end
732
+ attr_reader :max_freq
733
+ attr_reader :max_len
734
+ attr_reader :width
681
735
 
682
- def add_row!(bars)
683
- @bars.concat(bars)
684
- @max_freq, i = find_max(transform_values(@transform, bars))
685
- @max_len = @bars[i].to_s.length
686
- end
736
+ def n_rows
737
+ @bars.length
738
+ end
687
739
 
688
- def print_row(out, row_index)
689
- check_row_index(row_index)
690
- bar = @bars[row_index]
691
- max_bar_width = [width - 2 - max_len, 1].max
692
- val = transform_values(@transform, bar)
693
- bar_len = max_freq > 0 ?
694
- ([val, 0].max.fdiv(max_freq) * max_bar_width).round :
695
- 0
696
- bar_str = max_freq > 0 ? @symbol * bar_len : ""
697
- bar_lbl = bar.to_s
698
- print_styled(out, bar_str, color: @color)
699
- print_styled(out, " ", bar_lbl, color: :normal)
700
- pan_len = [max_bar_width + 1 + max_len - bar_len - bar_lbl.length, 0].max
701
- pad = " " * pan_len.round
702
- out.print(pad)
703
- end
740
+ def n_columns
741
+ @width
742
+ end
743
+
744
+ def add_row!(bars)
745
+ @bars.concat(bars)
746
+ @max_freq, i = find_max(transform_values(@transform, bars))
747
+ @max_len = @bars[i].to_s.length
748
+ end
749
+
750
+ def print_row(out, row_index)
751
+ check_row_index(row_index)
752
+ bar = @bars[row_index]
753
+ max_bar_width = [width - 2 - max_len, 1].max
754
+ val = transform_values(@transform, bar)
755
+ bar_len = max_freq > 0 ?
756
+ ([val, 0].max.fdiv(max_freq) * max_bar_width).round :
757
+ 0
758
+ bar_str = max_freq > 0 ? @symbol * bar_len : ""
759
+ bar_lbl = bar.to_s
760
+ print_styled(out, bar_str, color: @color)
761
+ print_styled(out, " ", bar_lbl, color: :normal)
762
+ pan_len = [max_bar_width + 1 + max_len - bar_len - bar_lbl.length, 0].max
763
+ pad = " " * pan_len.round
764
+ out.print(pad)
765
+ end
704
766
 
705
- private def find_max(values)
706
- i = j = 0
707
- max = values[i]
708
- while j < values.length
709
- if values[j] > max
710
- i, max = j, values[j]
767
+ private def find_max(values)
768
+ i = j = 0
769
+ max = values[i]
770
+ while j < values.length
771
+ if values[j] > max
772
+ i, max = j, values[j]
773
+ end
774
+ j += 1
711
775
  end
712
- j += 1
776
+ [max, i]
713
777
  end
714
- [max, i]
715
778
  end
716
779
  end
780
+ private_constant :MiniUnicodePlot
717
781
  end
718
782