ascii_charts 0.9

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