ascii-charts 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
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: []