ascii-charts 0.9.2

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ascii_charts.rb +280 -0
  3. metadata +45 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 37f2687ef985f077a6fd0c418dc272f315011659
4
+ data.tar.gz: 4c41ca6725e12c7e54545b7e8bbed86e9117d0aa
5
+ SHA512:
6
+ metadata.gz: 100e0e015ced299ab784f67d95c9e89b49bfd49ff975a72dde4df35e1e9147e8be13547d2d3113408dd2ee00177d40e197ad6bb8fec89cb947dce6960e29eee9
7
+ data.tar.gz: 3a04b4a533974a996f864be098a67fa2d7ca57b2526c22b22c9b13d9bb650db962866321202b113ddd51105503ec8c132365f8581d8f887beb111640803facfc
@@ -0,0 +1,280 @@
1
+ module AsciiCharts
2
+
3
+ VERSION = '0.9.2'
4
+
5
+ class Chart
6
+
7
+ attr_reader :options, :data
8
+
9
+ DEFAULT_MAX_Y_VALS = 20
10
+ DEFAULT_MIN_Y_VALS = 10
11
+
12
+ #data is a sorted array of [x, y] pairs
13
+
14
+ def initialize(data, options={})
15
+ @data = data
16
+ @options = options
17
+ end
18
+
19
+ def rounded_data
20
+ @rounded_data ||= self.data.map{|pair| [pair[0], self.round_value(pair[1])]}
21
+ end
22
+
23
+ def step_size
24
+ if !defined? @step_size
25
+ if self.options[:y_step_size]
26
+ @step_size = self.options[:y_step_size]
27
+ else
28
+ max_y_vals = self.options[:max_y_vals] || DEFAULT_MAX_Y_VALS
29
+ min_y_vals = self.options[:min_y_vals] || DEFAULT_MIN_Y_VALS
30
+ y_span = (self.max_yval - self.min_yval).to_f
31
+ step_size = self.nearest_step( y_span.to_f / (self.data.size + 1) )
32
+
33
+ if @all_ints && (step_size < 1)
34
+ step_size = 1
35
+ else
36
+ while (y_span / step_size) < min_y_vals
37
+ candidate_step_size = self.next_step_down(step_size)
38
+ if @all_ints && (candidate_step_size < 1) ## don't go below one
39
+ break
40
+ end
41
+ step_size = candidate_step_size
42
+ end
43
+ end
44
+
45
+ #go up if we undershot, or were never over
46
+ while (y_span / step_size) > max_y_vals
47
+ step_size = self.next_step_up(step_size)
48
+ end
49
+ @step_size = step_size
50
+ end
51
+ if !@all_ints && @step_size.is_a?(Integer)
52
+ @step_size = @step_size.to_f
53
+ end
54
+ end
55
+ @step_size
56
+ end
57
+
58
+ STEPS = [1, 2, 5]
59
+
60
+ def from_step(val)
61
+ if 0 == val
62
+ [0, 0]
63
+ else
64
+ order = Math.log10(val).floor.to_i
65
+ num = (val / (10 ** order))
66
+ [num, order]
67
+ end
68
+ end
69
+
70
+ def to_step(num, order)
71
+ s = num * (10 ** order)
72
+ if order < 0
73
+ s.to_f
74
+ else
75
+ s
76
+ end
77
+ end
78
+
79
+ def nearest_step(val)
80
+ num, order = self.from_step(val)
81
+ self.to_step(2, order) ##@@
82
+ end
83
+
84
+ def next_step_up(val)
85
+ num, order = self.from_step(val)
86
+ next_index = STEPS.index(num.to_i) + 1
87
+ if STEPS.size == next_index
88
+ next_index = 0
89
+ order += 1
90
+ end
91
+ self.to_step(STEPS[next_index], order)
92
+ end
93
+
94
+ def next_step_down(val)
95
+ num, order = self.from_step(val)
96
+ next_index = STEPS.index(num.to_i) - 1
97
+ if -1 == next_index
98
+ STEPS.size - 1
99
+ order -= 1
100
+ end
101
+ self.to_step(STEPS[next_index], order)
102
+ end
103
+
104
+ #round to nearest step size, making sure we curtail precision to same order of magnitude as the step size to avoid 0.4 + 0.2 = 0.6000000000000001
105
+ def round_value(val)
106
+ remainder = val % self.step_size
107
+ unprecised = if (remainder * 2) >= self.step_size
108
+ (val - remainder) + self.step_size
109
+ else
110
+ val - remainder
111
+ end
112
+ if self.step_size < 1
113
+ precision = -Math.log10(self.step_size).floor
114
+ (unprecised * (10 ** precision)).to_i.to_f / (10 ** precision)
115
+ else
116
+ unprecised
117
+ end
118
+ end
119
+
120
+ def max_yval
121
+ if !defined? @max_yval
122
+ scan_data
123
+ end
124
+ @max_yval
125
+ end
126
+
127
+ def min_yval
128
+ if !defined? @min_yval
129
+ scan_data
130
+ end
131
+ @min_yval
132
+ end
133
+
134
+ def all_ints
135
+ if !defined? @all_ints
136
+ scan_data
137
+ end
138
+ @all_ints
139
+ end
140
+
141
+ def scan_data
142
+ @max_yval = -Float::INFINITY
143
+ @min_yval = Float::INFINITY
144
+ @all_ints = true
145
+
146
+ @max_xval_width = 1
147
+
148
+ self.data.each do |pair|
149
+ if pair[1] > @max_yval
150
+ @max_yval = pair[1]
151
+ end
152
+ if pair[1] < @min_yval
153
+ @min_yval = pair[1]
154
+ end
155
+ if @all_ints && !pair[1].is_a?(Integer)
156
+ @all_ints = false
157
+ end
158
+
159
+ if (xw = pair[0].to_s.length) > @max_xval_width
160
+ @max_xval_width = xw
161
+ end
162
+ end
163
+ end
164
+
165
+ def max_xval_width
166
+ if !defined? @max_xval_width
167
+ scan_data
168
+ end
169
+ @max_xval_width
170
+ end
171
+
172
+ def max_yval_width
173
+ if !defined? @max_yval_width
174
+ scan_y_range
175
+ end
176
+ @max_yval_width
177
+ end
178
+
179
+ def scan_y_range
180
+ @max_yval_width = 1
181
+
182
+ self.y_range.each do |yval|
183
+ if (yw = yval.to_s.length) > @max_yval_width
184
+ @max_yval_width = yw
185
+ end
186
+ end
187
+ end
188
+
189
+ def y_range
190
+ if !defined? @y_range
191
+ @y_range = []
192
+ first_y = self.round_value(self.min_yval)
193
+ if first_y > self.min_yval
194
+ first_y = first_y - self.step_size
195
+ end
196
+ last_y = self.round_value(self.max_yval)
197
+ if last_y < self.max_yval
198
+ last_y = last_y + self.step_size
199
+ end
200
+ current_y = first_y
201
+ while current_y <= last_y
202
+ @y_range << current_y
203
+ current_y = self.round_value(current_y + self.step_size) ## to avoid fp arithmetic oddness
204
+ end
205
+ end
206
+ @y_range
207
+ end
208
+
209
+ def lines
210
+ raise "lines must be overridden"
211
+ end
212
+
213
+ def draw
214
+ lines.join("\n")
215
+ end
216
+
217
+ def to_string
218
+ draw
219
+ end
220
+
221
+ end
222
+
223
+ class Cartesian < Chart
224
+
225
+ def lines
226
+ if self.data.size == 0
227
+ return [[' ', self.options[:title], ' ', '|', '+-', ' ']]
228
+ end
229
+
230
+ lines = [' ']
231
+
232
+ bar_width = self.max_xval_width + 1
233
+
234
+ lines << (' ' * self.max_yval_width) + ' ' + self.rounded_data.map{|pair| pair[0].to_s.center(bar_width)}.join('')
235
+
236
+ self.y_range.each_with_index do |current_y, i|
237
+ yval = current_y.to_s
238
+ bar = if 0 == i
239
+ '+'
240
+ else
241
+ '|'
242
+ end
243
+ current_line = [(' ' * (self.max_yval_width - yval.length) ) + "#{current_y}#{bar}"]
244
+
245
+ self.rounded_data.each do |pair|
246
+ marker = if (0 == i) && options[:hide_zero]
247
+ '-'
248
+ else
249
+ '*'
250
+ end
251
+ filler = if 0 == i
252
+ '-'
253
+ else
254
+ ' '
255
+ end
256
+ comparison = if self.options[:bar]
257
+ current_y <= pair[1]
258
+ else
259
+ current_y == pair[1]
260
+ end
261
+ if comparison
262
+ current_line << marker.center(bar_width, filler)
263
+ else
264
+ current_line << filler * bar_width
265
+ end
266
+ end
267
+ lines << current_line.join('')
268
+ current_y = current_y + self.step_size
269
+ end
270
+ lines << ' '
271
+ if self.options[:title]
272
+ lines << self.options[:title].center(lines[1].length)
273
+ end
274
+ lines << ' '
275
+ lines.reverse
276
+ end
277
+
278
+ end
279
+
280
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ascii-charts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.2
5
+ platform: ruby
6
+ authors:
7
+ - Ben Lund
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Library to draw simple ASCII charts (x,y graph plots and histograms)
14
+ email: ben@benlund.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/ascii_charts.rb
20
+ homepage: http://github.com/benlund/ascii_charts
21
+ licenses:
22
+ - MIT
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.6.8
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Very simple API, your data may already in the correct format to be plotted.
44
+ Dynamically scales the y-axis. Simple configuration options, including chart title.
45
+ test_files: []