geoptima 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/csv_chart +244 -125
- data/bin/show_geoptima +1 -1
- data/examples/csv_chart.rb +244 -125
- data/examples/show_geoptima.rb +1 -1
- data/lib/geoptima/chart.rb +18 -0
- data/lib/geoptima/data.rb +1 -35
- data/lib/geoptima/daterange.rb +40 -0
- data/lib/geoptima/version.rb +1 -1
- metadata +5 -4
data/bin/csv_chart
CHANGED
@@ -8,8 +8,10 @@ require 'geoptima/chart'
|
|
8
8
|
require 'geoptima/version'
|
9
9
|
require 'geoptima/options'
|
10
10
|
require 'fileutils'
|
11
|
+
require 'geoptima/daterange'
|
11
12
|
|
12
|
-
Geoptima::assert_version("0.0
|
13
|
+
Geoptima::assert_version("0.1.0")
|
14
|
+
Geoptima::Chart.available? || puts("No charting libraries available") || exit(-1)
|
13
15
|
|
14
16
|
$export_dir = '.'
|
15
17
|
$diversity = 40.0
|
@@ -17,14 +19,22 @@ $diversity = 40.0
|
|
17
19
|
$files = Geoptima::Options.process_args do |option|
|
18
20
|
option.m {$merge_all = true}
|
19
21
|
option.a {$create_all = true}
|
22
|
+
option.t {$time_split = true}
|
20
23
|
option.D {$export_dir = ARGV.shift}
|
21
24
|
option.N {$merged_name = ARGV.shift}
|
22
25
|
option.S {$specfile = ARGV.shift}
|
23
26
|
option.P {$diversity = ARGV.shift.to_f}
|
27
|
+
option.T do
|
28
|
+
$time_range = Geoptima::DateRange.new(*(ARGV.shift.split(/[\,]+/).map do |t|
|
29
|
+
DateTime.parse t
|
30
|
+
end))
|
31
|
+
end
|
24
32
|
end
|
25
33
|
|
26
34
|
FileUtils.mkdir_p $export_dir
|
27
35
|
|
36
|
+
$create_all = true unless($specfile)
|
37
|
+
$merge_all = true if($time_split)
|
28
38
|
$help = true unless($files.length>0)
|
29
39
|
if $help
|
30
40
|
puts <<EOHELP
|
@@ -33,44 +43,46 @@ Usage: csv_chart <-dham> <-S specfile> <-N name> <-D dir> files...
|
|
33
43
|
-h print this help #{cw $help}
|
34
44
|
-a automatically create charts for all properties #{cw $create_all}
|
35
45
|
-m merge all files into single stats #{cw $merge_all}
|
46
|
+
-t merge and split by time (days) #{cw $time_split}
|
36
47
|
-N use specified name for merged dataset: #{$merged_name}
|
37
48
|
-D export charts to specified directory: #{$export_dir}
|
38
49
|
-S use chart specification in specified file: #{$specfile}
|
39
50
|
-P diversity threshold in percentage for automatic reports: #{$diversity}
|
51
|
+
-T set time-range filter: #{$time_range}
|
40
52
|
Files to import: #{$files.join(', ')}
|
41
53
|
EOHELP
|
42
54
|
exit
|
43
55
|
end
|
44
56
|
|
45
57
|
class Stats
|
46
|
-
attr_reader :
|
47
|
-
def initialize(
|
48
|
-
@file = file
|
58
|
+
attr_reader :name, :stats, :data, :numerical
|
59
|
+
def initialize(name)
|
49
60
|
@name = name
|
50
|
-
@
|
51
|
-
@
|
52
|
-
@
|
53
|
-
@numerical = fields.map{|h| true}
|
54
|
-
end
|
55
|
-
def add_header(h)
|
56
|
-
@headers << h
|
57
|
-
@stats << {}
|
58
|
-
@data << []
|
59
|
-
@numerical << true
|
60
|
-
@headers.length - 1
|
61
|
+
@stats = {}
|
62
|
+
@data = []
|
63
|
+
@numerical = true
|
61
64
|
end
|
62
|
-
def add(field
|
63
|
-
puts "\tAdding field '#{field}'
|
65
|
+
def add(field)
|
66
|
+
puts "\tAdding field '#{field}' for property #{name}" if($debug)
|
64
67
|
if field && field =~ /\w/
|
65
|
-
@numerical
|
66
|
-
puts "\tField[#{index}]: #{field}" if($debug)
|
67
|
-
stats = @stats[index]
|
68
|
+
@numerical &&= is_number?(field)
|
68
69
|
stats[field] ||= 0
|
69
70
|
stats[field] += 1
|
70
|
-
|
71
|
-
@data[index] << field
|
71
|
+
@data << field
|
72
72
|
end
|
73
73
|
end
|
74
|
+
def length
|
75
|
+
@stats.length
|
76
|
+
end
|
77
|
+
def numerical?
|
78
|
+
@numerical
|
79
|
+
end
|
80
|
+
def diversity
|
81
|
+
100.0 * @stats.length.to_f / @data.length.to_f
|
82
|
+
end
|
83
|
+
def diverse?
|
84
|
+
@stats.length>500 || diversity > $diversity
|
85
|
+
end
|
74
86
|
def is_number?(field)
|
75
87
|
is_integer?(field) || is_float?(field)
|
76
88
|
end
|
@@ -80,36 +92,104 @@ class Stats
|
|
80
92
|
def is_float?(field)
|
81
93
|
field.to_f.to_s == field
|
82
94
|
end
|
83
|
-
def
|
84
|
-
|
95
|
+
def to_s
|
96
|
+
"#{name}[#{length}]"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class StatsManager
|
101
|
+
attr_reader :name, :headers, :stats
|
102
|
+
def initialize(name)
|
103
|
+
@name = name
|
104
|
+
@headers = []
|
105
|
+
@stats = {}
|
106
|
+
end
|
107
|
+
def time_index
|
108
|
+
@time_index ||= @headers.index('Time') || @headers.index('Timestamp')
|
109
|
+
end
|
110
|
+
def time_stats
|
111
|
+
@time_stats ||= get_stats('Time') || get_stats('Timestamp')
|
112
|
+
end
|
113
|
+
def set_headers(headers)
|
114
|
+
@headers = []
|
115
|
+
headers.each {|h| add_header(h)}
|
116
|
+
$specs && $specs.add_stats(self,headers)
|
117
|
+
end
|
118
|
+
def add_header(h)
|
119
|
+
if @headers.index(h)
|
120
|
+
puts "Stats header already exists: #{h}"
|
121
|
+
else
|
122
|
+
@headers << h
|
123
|
+
@stats[h] ||= Stats.new(h)
|
124
|
+
end
|
125
|
+
@headers.index(h)
|
126
|
+
end
|
127
|
+
def get_stats(header)
|
128
|
+
stats[header] || stats[header.downcase]
|
129
|
+
end
|
130
|
+
def add_all(fields,headers)
|
131
|
+
fields.each_with_index do |field,index|
|
132
|
+
add(field,headers[index])
|
133
|
+
end
|
134
|
+
$specs && $specs.add_fields(self,fields)
|
85
135
|
end
|
86
|
-
def
|
87
|
-
|
136
|
+
def add(field,header)
|
137
|
+
puts "\tAdding field '#{field}' for property #{header}" if($debug)
|
138
|
+
add_header(header) unless(@stats[header])
|
139
|
+
@stats[header].add(field)
|
88
140
|
end
|
89
|
-
def
|
90
|
-
@stats
|
141
|
+
def length
|
142
|
+
@stats.length
|
91
143
|
end
|
92
|
-
def
|
93
|
-
|
144
|
+
def to_s
|
145
|
+
"Stats[#{length}]: #{@stats.inspect}"
|
94
146
|
end
|
95
147
|
end
|
96
148
|
|
97
|
-
$stats = {}
|
98
|
-
|
99
149
|
module Geoptima
|
100
150
|
class StatSpec
|
101
|
-
attr_reader :header, :index, :indices, :fields, :options, :proc
|
151
|
+
attr_reader :header, :index, :indices, :fields, :options, :proc, :groups
|
102
152
|
def initialize(header,*fields,&block)
|
103
153
|
@header = header
|
104
154
|
@fields = fields
|
105
155
|
@proc = block
|
156
|
+
@groups = {}
|
106
157
|
if @fields[-1].is_a?(Hash)
|
107
158
|
@options = @fields.pop
|
108
159
|
else
|
109
160
|
@options = {}
|
110
161
|
end
|
162
|
+
if @options[:group]
|
163
|
+
case @options[:group].to_s.intern
|
164
|
+
when :months
|
165
|
+
group_by {|t| t.strftime("%Y-%m")}
|
166
|
+
when :days
|
167
|
+
group_by {|t| t.strftime("%Y-%m-%d")}
|
168
|
+
else
|
169
|
+
group_by {|t| t.strftime("%Y-%m-%d %H")}
|
170
|
+
end
|
171
|
+
end
|
111
172
|
puts "Created StatSpec: #{self}"
|
112
173
|
end
|
174
|
+
def group_by(&block)
|
175
|
+
@group = block
|
176
|
+
end
|
177
|
+
def add(stats_manager,fields)
|
178
|
+
if @group
|
179
|
+
begin
|
180
|
+
time = DateTime.parse(fields[stats_manager.time_index])
|
181
|
+
if $time_range.nil? || $time_range.include?(time)
|
182
|
+
key = @group.call(time)
|
183
|
+
ghead = "#{header} #{key}"
|
184
|
+
@groups[key] = ghead
|
185
|
+
stats_manager.add(map(fields),ghead)
|
186
|
+
end
|
187
|
+
rescue ArgumentError
|
188
|
+
puts "Error: Unable to process time field[#{time}]: #{$!}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
stats_manager.add(map(fields),header)
|
192
|
+
end
|
113
193
|
def mk_range(val)
|
114
194
|
if val =~ /\w/
|
115
195
|
div = options[:div].to_i
|
@@ -120,7 +200,7 @@ module Geoptima
|
|
120
200
|
val
|
121
201
|
end
|
122
202
|
end
|
123
|
-
def map(values)
|
203
|
+
def map(values,filter=nil)
|
124
204
|
if @indices
|
125
205
|
puts "StatSpec[#{self}]: #{options.inspect}" if($debug)
|
126
206
|
vals = @indices.map{|i| values[i]}
|
@@ -133,12 +213,14 @@ module Geoptima
|
|
133
213
|
val
|
134
214
|
end
|
135
215
|
end
|
136
|
-
def prepare_indices(
|
137
|
-
if
|
216
|
+
def prepare_indices(stats_manager,headers)
|
217
|
+
if headers.index(header)
|
138
218
|
puts "Header '#{header}' already exists, cannot create #{self}"
|
219
|
+
@index = nil
|
220
|
+
@indices = nil
|
139
221
|
else
|
140
|
-
@index =
|
141
|
-
@indices = @fields.map{|h|
|
222
|
+
@index = stats_manager.add_header(header)
|
223
|
+
@indices = @fields.map{|h| headers.index(h) || headers.index(h.downcase) }
|
142
224
|
puts "#{self}: Header[#{@index}], Indices[#{@indices.join(',')}]" if($debug)
|
143
225
|
if @indices.compact.length < @fields.length
|
144
226
|
puts "Unable to find some headers for #{self}, ignoring stats"
|
@@ -152,35 +234,60 @@ module Geoptima
|
|
152
234
|
end
|
153
235
|
class ChartSpec
|
154
236
|
attr_reader :chart_type, :header, :options
|
155
|
-
def initialize(
|
237
|
+
def initialize(header,options={})
|
156
238
|
@header = header
|
157
|
-
@chart_type = chart_type
|
239
|
+
@chart_type = options[:chart_type] || :histogram
|
158
240
|
@options = options
|
159
241
|
end
|
160
|
-
def process(
|
161
|
-
puts "Charting #{header} using
|
162
|
-
|
163
|
-
|
164
|
-
|
242
|
+
def process(stats_manager)
|
243
|
+
puts "Charting #{header} using headers: #{stats_manager.headers.inspect}"
|
244
|
+
stat_spec = $specs.stat_specs.find{|o| o.header == header}
|
245
|
+
stats = stats_manager.get_stats(header)
|
246
|
+
grouped_stats = {}
|
247
|
+
if stat_spec
|
248
|
+
stat_spec.groups.each do |name,header|
|
249
|
+
gs = stats_manager.get_stats(header)
|
250
|
+
grouped_stats[name] = gs
|
251
|
+
stats ||= gs
|
252
|
+
end
|
253
|
+
end
|
254
|
+
unless stats
|
165
255
|
puts "Cannot find statistics for '#{header}' - ignoring chart"
|
166
256
|
return
|
167
257
|
end
|
168
|
-
puts "Charting #{header}
|
169
|
-
puts "Charting #{header} with diversity #{stats.diversity
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
258
|
+
puts "Charting #{header} with options #{options.inspect} and stats: #{stats}"
|
259
|
+
puts "Charting #{header} with diversity #{stats.diversity}"
|
260
|
+
if grouped_stats.length > 0
|
261
|
+
title = options[:title]
|
262
|
+
title ||= "#{header} Distribution"
|
263
|
+
options.merge!( :title => title, :width => 1024 )
|
264
|
+
value_map = {}
|
265
|
+
groups = grouped_stats.keys.sort
|
266
|
+
groups.each_with_index do |name,index|
|
267
|
+
gs = grouped_stats[name]
|
268
|
+
hist = gs.stats
|
269
|
+
hist.keys.each do |k|
|
270
|
+
value_map[k] ||= [].fill(0,0...groups.length)
|
271
|
+
value_map[k][index] = hist[k]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
legends = value_map.keys.sort
|
275
|
+
g = Geoptima::Chart.draw_grouped_chart legends, groups, value_map, options
|
175
276
|
else
|
176
|
-
|
277
|
+
hist = stats.stats
|
278
|
+
title = options[:title]
|
279
|
+
if options[:top]
|
280
|
+
keys = hist.keys.sort{|a,b| hist[b] <=> hist[a]}[0..(options[:top].to_i)]
|
281
|
+
title ||= "#{header} Top #{options[:top]}"
|
282
|
+
else
|
283
|
+
keys = hist.keys.sort{|a,b| a.to_i <=> b.to_i}
|
284
|
+
end
|
285
|
+
values = keys.map{|k| hist[k]}
|
286
|
+
title ||= "#{header} Distribution"
|
287
|
+
options.merge!( :title => title, :width => 1024 )
|
288
|
+
g = Geoptima::Chart.send "draw_#{chart_type}_chart", stats_manager.name, keys, values, options
|
177
289
|
end
|
178
|
-
|
179
|
-
title ||= "#{header} Distribution"
|
180
|
-
options.merge!( :title => title, :width => 1024 )
|
181
|
-
legend = $merge_all ? "ALL" : stats.name
|
182
|
-
g = Geoptima::Chart.send "draw_#{chart_type}_chart", stats.name, keys, values, options
|
183
|
-
g.write("#{$export_dir}/Chart_#{stats.name}_#{header}_distribution.png")
|
290
|
+
g.write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}_#{chart_type}_distribution.png")
|
184
291
|
end
|
185
292
|
def to_s
|
186
293
|
"#{chart_type.upcase}-#{header}"
|
@@ -194,126 +301,138 @@ module Geoptima
|
|
194
301
|
instance_eval(File.open(specfile).read)
|
195
302
|
end
|
196
303
|
def category_chart(header,options={})
|
197
|
-
chart(
|
304
|
+
chart(header, options.merge(:chart_type => :category))
|
198
305
|
end
|
199
306
|
def histogram_chart(header,options={})
|
200
|
-
chart(
|
307
|
+
chart(header, options.merge(:chart_type => :histgram))
|
201
308
|
end
|
202
309
|
def line_chart(header,options={})
|
203
|
-
chart(
|
310
|
+
chart(header, options.merge(:chart_type => :line))
|
204
311
|
end
|
205
|
-
def chart(
|
206
|
-
@chart_specs << ChartSpec.new(
|
312
|
+
def chart(header,options={})
|
313
|
+
@chart_specs << ChartSpec.new(header,options)
|
207
314
|
end
|
208
315
|
def stats(header,*fields,&block)
|
209
316
|
@stat_specs << StatSpec.new(header,*fields,&block)
|
210
317
|
end
|
211
|
-
def add_stats(
|
318
|
+
def add_stats(stats_manager,headers)
|
212
319
|
stat_specs.each do |stat_spec|
|
213
|
-
stat_spec.prepare_indices(
|
320
|
+
stat_spec.prepare_indices(stats_manager,headers)
|
214
321
|
end
|
215
322
|
end
|
216
|
-
def add_fields(
|
217
|
-
|
218
|
-
|
219
|
-
stat_spec.map(fields),
|
220
|
-
stat_spec.index
|
221
|
-
)
|
323
|
+
def add_fields(stats_manager,fields)
|
324
|
+
stat_specs.each do |stat_spec|
|
325
|
+
stat_spec.add(stats_manager,fields)
|
222
326
|
end
|
223
327
|
end
|
224
328
|
def to_s
|
225
|
-
"
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
$specfile && $specs = Geoptima::StatsSpecs.new($specfile)
|
231
|
-
|
232
|
-
$files.each do |file|
|
233
|
-
lines = 0
|
234
|
-
filename = File.basename(file)
|
235
|
-
(names = filename.split(/[_\.]/)).pop
|
236
|
-
name = $merged_name || names.join('_')
|
237
|
-
puts "About to read file #{file}"
|
238
|
-
File.open(file).each do |line|
|
239
|
-
lines += 1
|
240
|
-
fields=line.chomp.split(/\t/)
|
241
|
-
if $stats[file]
|
242
|
-
puts "Processing line: #{line}" if($debug)
|
243
|
-
fields.each_with_index do |field,index|
|
244
|
-
$stats[file].add(field,index)
|
245
|
-
end
|
246
|
-
$specs && $specs.add_fields($stats[file],fields)
|
247
|
-
elsif($merge_all && $stats.length>0)
|
248
|
-
file = $stats.values[0].file
|
249
|
-
else
|
250
|
-
$stats[file] = Stats.new(filename,name,fields)
|
251
|
-
$specs && $specs.add_stats($stats[file])
|
329
|
+
"Stats[#{@stat_specs.join(', ')}] AND Charts[#{@chart_specs.join(', ')}]"
|
252
330
|
end
|
253
331
|
end
|
254
332
|
end
|
255
333
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
end
|
261
|
-
else
|
262
|
-
stats.headers.each_with_index do |header,index|
|
263
|
-
puts "Charting #{header} with diversity #{stats.diversity(index)}"
|
334
|
+
def create_all(name,stats_manager)
|
335
|
+
stats_manager.headers.each do |header|
|
336
|
+
stats = stats_manager.stats[header]
|
337
|
+
puts "Charting #{header} with diversity #{stats.diversity}"
|
264
338
|
case header
|
265
339
|
when 'signal.strength'
|
266
340
|
Geoptima::Chart.draw_line_chart(
|
267
|
-
|
268
|
-
|
269
|
-
stats.data
|
341
|
+
stats_manager.name,
|
342
|
+
stats_manager.time_stats.data,
|
343
|
+
stats.data.map{|f| v=f.to_i; (v>-130 && v<0) ? v : nil},
|
270
344
|
:title => 'Signal Strength',
|
271
345
|
:maximum_value => -30,
|
272
346
|
:minimum_value => -130,
|
273
347
|
:width => 1024
|
274
|
-
).write("#{$export_dir}/Chart_#{
|
348
|
+
).write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}.png")
|
275
349
|
|
276
|
-
hist = stats.stats
|
350
|
+
hist = stats.stats
|
277
351
|
keys = hist.keys.sort{|a,b| a.to_i <=> b.to_i}
|
278
352
|
values = keys.map{|k| hist[k]}
|
279
353
|
Geoptima::Chart.draw_histogram_chart(
|
280
|
-
|
354
|
+
stats_manager.name, keys, values,
|
281
355
|
:title => 'Signal Strength Distribution',
|
282
356
|
:width => 1024
|
283
|
-
).write("#{$export_dir}/Chart_#{
|
357
|
+
).write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}_distribution.png")
|
284
358
|
|
285
359
|
when 'Event'
|
286
|
-
hist = stats.stats
|
360
|
+
hist = stats.stats
|
287
361
|
keys = hist.keys.sort{|a,b| a.to_i <=> b.to_i}
|
288
362
|
values = keys.map{|k| hist[k]}
|
289
363
|
Geoptima::Chart.draw_category_chart(
|
290
|
-
|
364
|
+
stats_manager.name, keys, values,
|
291
365
|
:title => "#{header} Distribution",
|
292
366
|
:width => 1024
|
293
|
-
).write("#{$export_dir}/Chart_#{
|
367
|
+
).write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}_distribution.png")
|
294
368
|
|
295
369
|
else
|
296
|
-
if stats.diverse?
|
370
|
+
if stats.diverse?
|
297
371
|
puts "Ignoring high diversity field #{header}"
|
298
372
|
else
|
299
|
-
puts "Charting field: #{header} with length #{stats.length
|
300
|
-
hist = stats.stats
|
373
|
+
puts "Charting field: #{header} with length #{stats.length} and diversity #{stats.diversity}"
|
374
|
+
hist = stats.stats
|
301
375
|
keys = hist.keys.sort{|a,b| a.to_i <=> b.to_i}
|
302
376
|
values = keys.map{|k| hist[k]}
|
303
|
-
args = [
|
377
|
+
args = [stats_manager.name, keys, values, {
|
304
378
|
:title => "#{header} Distribution",
|
305
379
|
:width => 1024}]
|
306
|
-
g = (stats.length
|
380
|
+
g = (stats.length > 50) ?
|
307
381
|
Geoptima::Chart.draw_line_chart(*args) :
|
308
|
-
(stats.length
|
382
|
+
(stats.length > 10 || stats.numerical?) ?
|
309
383
|
Geoptima::Chart.draw_histogram_chart(*args) :
|
310
|
-
(stats.length
|
384
|
+
(stats.length > 1) ?
|
311
385
|
Geoptima::Chart.draw_category_chart(*args) :
|
312
386
|
nil
|
313
|
-
g && g.write("#{$export_dir}/Chart_#{
|
387
|
+
g && g.write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}_distribution.png")
|
314
388
|
end
|
315
389
|
end
|
316
390
|
end
|
391
|
+
end
|
392
|
+
|
393
|
+
#
|
394
|
+
# Now run the actual program, reading the specifation file and then the CSV files
|
395
|
+
#
|
396
|
+
|
397
|
+
$stats_managers = {}
|
398
|
+
|
399
|
+
$specfile && $specs = Geoptima::StatsSpecs.new($specfile)
|
400
|
+
|
401
|
+
$files.each do |file|
|
402
|
+
lines = 0
|
403
|
+
headers = nil
|
404
|
+
filename = File.basename(file)
|
405
|
+
(names = filename.split(/[_\.]/)).pop
|
406
|
+
name = $merge_all ? ($merged_name || 'All') : names.join('_')
|
407
|
+
$stats_managers[name] ||= StatsManager.new(name)
|
408
|
+
puts "About to read file #{file}"
|
409
|
+
File.open(file).each do |line|
|
410
|
+
lines += 1
|
411
|
+
fields=line.chomp.split(/\t/)
|
412
|
+
if headers
|
413
|
+
puts "Processing line: #{line}" if($debug)
|
414
|
+
$stats_managers[name].add_all(fields,headers)
|
415
|
+
else
|
416
|
+
headers = fields
|
417
|
+
if headers.length<2
|
418
|
+
puts "Too few headers, rejecting #{file}"
|
419
|
+
break
|
420
|
+
end
|
421
|
+
$stats_managers[name].set_headers(headers)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# Finally output all charts specified
|
427
|
+
|
428
|
+
$stats_managers.each do |name,stats_manager|
|
429
|
+
if $specs
|
430
|
+
$specs.chart_specs.each do |chart_spec|
|
431
|
+
chart_spec.process(stats_manager)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
if $create_all
|
435
|
+
create_all name, stats_manager
|
317
436
|
end
|
318
437
|
end
|
319
438
|
|
data/bin/show_geoptima
CHANGED
data/examples/csv_chart.rb
CHANGED
@@ -8,8 +8,10 @@ require 'geoptima/chart'
|
|
8
8
|
require 'geoptima/version'
|
9
9
|
require 'geoptima/options'
|
10
10
|
require 'fileutils'
|
11
|
+
require 'geoptima/daterange'
|
11
12
|
|
12
|
-
Geoptima::assert_version("0.0
|
13
|
+
Geoptima::assert_version("0.1.0")
|
14
|
+
Geoptima::Chart.available? || puts("No charting libraries available") || exit(-1)
|
13
15
|
|
14
16
|
$export_dir = '.'
|
15
17
|
$diversity = 40.0
|
@@ -17,14 +19,22 @@ $diversity = 40.0
|
|
17
19
|
$files = Geoptima::Options.process_args do |option|
|
18
20
|
option.m {$merge_all = true}
|
19
21
|
option.a {$create_all = true}
|
22
|
+
option.t {$time_split = true}
|
20
23
|
option.D {$export_dir = ARGV.shift}
|
21
24
|
option.N {$merged_name = ARGV.shift}
|
22
25
|
option.S {$specfile = ARGV.shift}
|
23
26
|
option.P {$diversity = ARGV.shift.to_f}
|
27
|
+
option.T do
|
28
|
+
$time_range = Geoptima::DateRange.new(*(ARGV.shift.split(/[\,]+/).map do |t|
|
29
|
+
DateTime.parse t
|
30
|
+
end))
|
31
|
+
end
|
24
32
|
end
|
25
33
|
|
26
34
|
FileUtils.mkdir_p $export_dir
|
27
35
|
|
36
|
+
$create_all = true unless($specfile)
|
37
|
+
$merge_all = true if($time_split)
|
28
38
|
$help = true unless($files.length>0)
|
29
39
|
if $help
|
30
40
|
puts <<EOHELP
|
@@ -33,44 +43,46 @@ Usage: csv_chart <-dham> <-S specfile> <-N name> <-D dir> files...
|
|
33
43
|
-h print this help #{cw $help}
|
34
44
|
-a automatically create charts for all properties #{cw $create_all}
|
35
45
|
-m merge all files into single stats #{cw $merge_all}
|
46
|
+
-t merge and split by time (days) #{cw $time_split}
|
36
47
|
-N use specified name for merged dataset: #{$merged_name}
|
37
48
|
-D export charts to specified directory: #{$export_dir}
|
38
49
|
-S use chart specification in specified file: #{$specfile}
|
39
50
|
-P diversity threshold in percentage for automatic reports: #{$diversity}
|
51
|
+
-T set time-range filter: #{$time_range}
|
40
52
|
Files to import: #{$files.join(', ')}
|
41
53
|
EOHELP
|
42
54
|
exit
|
43
55
|
end
|
44
56
|
|
45
57
|
class Stats
|
46
|
-
attr_reader :
|
47
|
-
def initialize(
|
48
|
-
@file = file
|
58
|
+
attr_reader :name, :stats, :data, :numerical
|
59
|
+
def initialize(name)
|
49
60
|
@name = name
|
50
|
-
@
|
51
|
-
@
|
52
|
-
@
|
53
|
-
@numerical = fields.map{|h| true}
|
54
|
-
end
|
55
|
-
def add_header(h)
|
56
|
-
@headers << h
|
57
|
-
@stats << {}
|
58
|
-
@data << []
|
59
|
-
@numerical << true
|
60
|
-
@headers.length - 1
|
61
|
+
@stats = {}
|
62
|
+
@data = []
|
63
|
+
@numerical = true
|
61
64
|
end
|
62
|
-
def add(field
|
63
|
-
puts "\tAdding field '#{field}'
|
65
|
+
def add(field)
|
66
|
+
puts "\tAdding field '#{field}' for property #{name}" if($debug)
|
64
67
|
if field && field =~ /\w/
|
65
|
-
@numerical
|
66
|
-
puts "\tField[#{index}]: #{field}" if($debug)
|
67
|
-
stats = @stats[index]
|
68
|
+
@numerical &&= is_number?(field)
|
68
69
|
stats[field] ||= 0
|
69
70
|
stats[field] += 1
|
70
|
-
|
71
|
-
@data[index] << field
|
71
|
+
@data << field
|
72
72
|
end
|
73
73
|
end
|
74
|
+
def length
|
75
|
+
@stats.length
|
76
|
+
end
|
77
|
+
def numerical?
|
78
|
+
@numerical
|
79
|
+
end
|
80
|
+
def diversity
|
81
|
+
100.0 * @stats.length.to_f / @data.length.to_f
|
82
|
+
end
|
83
|
+
def diverse?
|
84
|
+
@stats.length>500 || diversity > $diversity
|
85
|
+
end
|
74
86
|
def is_number?(field)
|
75
87
|
is_integer?(field) || is_float?(field)
|
76
88
|
end
|
@@ -80,36 +92,104 @@ class Stats
|
|
80
92
|
def is_float?(field)
|
81
93
|
field.to_f.to_s == field
|
82
94
|
end
|
83
|
-
def
|
84
|
-
|
95
|
+
def to_s
|
96
|
+
"#{name}[#{length}]"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class StatsManager
|
101
|
+
attr_reader :name, :headers, :stats
|
102
|
+
def initialize(name)
|
103
|
+
@name = name
|
104
|
+
@headers = []
|
105
|
+
@stats = {}
|
106
|
+
end
|
107
|
+
def time_index
|
108
|
+
@time_index ||= @headers.index('Time') || @headers.index('Timestamp')
|
109
|
+
end
|
110
|
+
def time_stats
|
111
|
+
@time_stats ||= get_stats('Time') || get_stats('Timestamp')
|
112
|
+
end
|
113
|
+
def set_headers(headers)
|
114
|
+
@headers = []
|
115
|
+
headers.each {|h| add_header(h)}
|
116
|
+
$specs && $specs.add_stats(self,headers)
|
117
|
+
end
|
118
|
+
def add_header(h)
|
119
|
+
if @headers.index(h)
|
120
|
+
puts "Stats header already exists: #{h}"
|
121
|
+
else
|
122
|
+
@headers << h
|
123
|
+
@stats[h] ||= Stats.new(h)
|
124
|
+
end
|
125
|
+
@headers.index(h)
|
126
|
+
end
|
127
|
+
def get_stats(header)
|
128
|
+
stats[header] || stats[header.downcase]
|
129
|
+
end
|
130
|
+
def add_all(fields,headers)
|
131
|
+
fields.each_with_index do |field,index|
|
132
|
+
add(field,headers[index])
|
133
|
+
end
|
134
|
+
$specs && $specs.add_fields(self,fields)
|
85
135
|
end
|
86
|
-
def
|
87
|
-
|
136
|
+
def add(field,header)
|
137
|
+
puts "\tAdding field '#{field}' for property #{header}" if($debug)
|
138
|
+
add_header(header) unless(@stats[header])
|
139
|
+
@stats[header].add(field)
|
88
140
|
end
|
89
|
-
def
|
90
|
-
@stats
|
141
|
+
def length
|
142
|
+
@stats.length
|
91
143
|
end
|
92
|
-
def
|
93
|
-
|
144
|
+
def to_s
|
145
|
+
"Stats[#{length}]: #{@stats.inspect}"
|
94
146
|
end
|
95
147
|
end
|
96
148
|
|
97
|
-
$stats = {}
|
98
|
-
|
99
149
|
module Geoptima
|
100
150
|
class StatSpec
|
101
|
-
attr_reader :header, :index, :indices, :fields, :options, :proc
|
151
|
+
attr_reader :header, :index, :indices, :fields, :options, :proc, :groups
|
102
152
|
def initialize(header,*fields,&block)
|
103
153
|
@header = header
|
104
154
|
@fields = fields
|
105
155
|
@proc = block
|
156
|
+
@groups = {}
|
106
157
|
if @fields[-1].is_a?(Hash)
|
107
158
|
@options = @fields.pop
|
108
159
|
else
|
109
160
|
@options = {}
|
110
161
|
end
|
162
|
+
if @options[:group]
|
163
|
+
case @options[:group].to_s.intern
|
164
|
+
when :months
|
165
|
+
group_by {|t| t.strftime("%Y-%m")}
|
166
|
+
when :days
|
167
|
+
group_by {|t| t.strftime("%Y-%m-%d")}
|
168
|
+
else
|
169
|
+
group_by {|t| t.strftime("%Y-%m-%d %H")}
|
170
|
+
end
|
171
|
+
end
|
111
172
|
puts "Created StatSpec: #{self}"
|
112
173
|
end
|
174
|
+
def group_by(&block)
|
175
|
+
@group = block
|
176
|
+
end
|
177
|
+
def add(stats_manager,fields)
|
178
|
+
if @group
|
179
|
+
begin
|
180
|
+
time = DateTime.parse(fields[stats_manager.time_index])
|
181
|
+
if $time_range.nil? || $time_range.include?(time)
|
182
|
+
key = @group.call(time)
|
183
|
+
ghead = "#{header} #{key}"
|
184
|
+
@groups[key] = ghead
|
185
|
+
stats_manager.add(map(fields),ghead)
|
186
|
+
end
|
187
|
+
rescue ArgumentError
|
188
|
+
puts "Error: Unable to process time field[#{time}]: #{$!}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
stats_manager.add(map(fields),header)
|
192
|
+
end
|
113
193
|
def mk_range(val)
|
114
194
|
if val =~ /\w/
|
115
195
|
div = options[:div].to_i
|
@@ -120,7 +200,7 @@ module Geoptima
|
|
120
200
|
val
|
121
201
|
end
|
122
202
|
end
|
123
|
-
def map(values)
|
203
|
+
def map(values,filter=nil)
|
124
204
|
if @indices
|
125
205
|
puts "StatSpec[#{self}]: #{options.inspect}" if($debug)
|
126
206
|
vals = @indices.map{|i| values[i]}
|
@@ -133,12 +213,14 @@ module Geoptima
|
|
133
213
|
val
|
134
214
|
end
|
135
215
|
end
|
136
|
-
def prepare_indices(
|
137
|
-
if
|
216
|
+
def prepare_indices(stats_manager,headers)
|
217
|
+
if headers.index(header)
|
138
218
|
puts "Header '#{header}' already exists, cannot create #{self}"
|
219
|
+
@index = nil
|
220
|
+
@indices = nil
|
139
221
|
else
|
140
|
-
@index =
|
141
|
-
@indices = @fields.map{|h|
|
222
|
+
@index = stats_manager.add_header(header)
|
223
|
+
@indices = @fields.map{|h| headers.index(h) || headers.index(h.downcase) }
|
142
224
|
puts "#{self}: Header[#{@index}], Indices[#{@indices.join(',')}]" if($debug)
|
143
225
|
if @indices.compact.length < @fields.length
|
144
226
|
puts "Unable to find some headers for #{self}, ignoring stats"
|
@@ -152,35 +234,60 @@ module Geoptima
|
|
152
234
|
end
|
153
235
|
class ChartSpec
|
154
236
|
attr_reader :chart_type, :header, :options
|
155
|
-
def initialize(
|
237
|
+
def initialize(header,options={})
|
156
238
|
@header = header
|
157
|
-
@chart_type = chart_type
|
239
|
+
@chart_type = options[:chart_type] || :histogram
|
158
240
|
@options = options
|
159
241
|
end
|
160
|
-
def process(
|
161
|
-
puts "Charting #{header} using
|
162
|
-
|
163
|
-
|
164
|
-
|
242
|
+
def process(stats_manager)
|
243
|
+
puts "Charting #{header} using headers: #{stats_manager.headers.inspect}"
|
244
|
+
stat_spec = $specs.stat_specs.find{|o| o.header == header}
|
245
|
+
stats = stats_manager.get_stats(header)
|
246
|
+
grouped_stats = {}
|
247
|
+
if stat_spec
|
248
|
+
stat_spec.groups.each do |name,header|
|
249
|
+
gs = stats_manager.get_stats(header)
|
250
|
+
grouped_stats[name] = gs
|
251
|
+
stats ||= gs
|
252
|
+
end
|
253
|
+
end
|
254
|
+
unless stats
|
165
255
|
puts "Cannot find statistics for '#{header}' - ignoring chart"
|
166
256
|
return
|
167
257
|
end
|
168
|
-
puts "Charting #{header}
|
169
|
-
puts "Charting #{header} with diversity #{stats.diversity
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
258
|
+
puts "Charting #{header} with options #{options.inspect} and stats: #{stats}"
|
259
|
+
puts "Charting #{header} with diversity #{stats.diversity}"
|
260
|
+
if grouped_stats.length > 0
|
261
|
+
title = options[:title]
|
262
|
+
title ||= "#{header} Distribution"
|
263
|
+
options.merge!( :title => title, :width => 1024 )
|
264
|
+
value_map = {}
|
265
|
+
groups = grouped_stats.keys.sort
|
266
|
+
groups.each_with_index do |name,index|
|
267
|
+
gs = grouped_stats[name]
|
268
|
+
hist = gs.stats
|
269
|
+
hist.keys.each do |k|
|
270
|
+
value_map[k] ||= [].fill(0,0...groups.length)
|
271
|
+
value_map[k][index] = hist[k]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
legends = value_map.keys.sort
|
275
|
+
g = Geoptima::Chart.draw_grouped_chart legends, groups, value_map, options
|
175
276
|
else
|
176
|
-
|
277
|
+
hist = stats.stats
|
278
|
+
title = options[:title]
|
279
|
+
if options[:top]
|
280
|
+
keys = hist.keys.sort{|a,b| hist[b] <=> hist[a]}[0..(options[:top].to_i)]
|
281
|
+
title ||= "#{header} Top #{options[:top]}"
|
282
|
+
else
|
283
|
+
keys = hist.keys.sort{|a,b| a.to_i <=> b.to_i}
|
284
|
+
end
|
285
|
+
values = keys.map{|k| hist[k]}
|
286
|
+
title ||= "#{header} Distribution"
|
287
|
+
options.merge!( :title => title, :width => 1024 )
|
288
|
+
g = Geoptima::Chart.send "draw_#{chart_type}_chart", stats_manager.name, keys, values, options
|
177
289
|
end
|
178
|
-
|
179
|
-
title ||= "#{header} Distribution"
|
180
|
-
options.merge!( :title => title, :width => 1024 )
|
181
|
-
legend = $merge_all ? "ALL" : stats.name
|
182
|
-
g = Geoptima::Chart.send "draw_#{chart_type}_chart", stats.name, keys, values, options
|
183
|
-
g.write("#{$export_dir}/Chart_#{stats.name}_#{header}_distribution.png")
|
290
|
+
g.write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}_#{chart_type}_distribution.png")
|
184
291
|
end
|
185
292
|
def to_s
|
186
293
|
"#{chart_type.upcase}-#{header}"
|
@@ -194,126 +301,138 @@ module Geoptima
|
|
194
301
|
instance_eval(File.open(specfile).read)
|
195
302
|
end
|
196
303
|
def category_chart(header,options={})
|
197
|
-
chart(
|
304
|
+
chart(header, options.merge(:chart_type => :category))
|
198
305
|
end
|
199
306
|
def histogram_chart(header,options={})
|
200
|
-
chart(
|
307
|
+
chart(header, options.merge(:chart_type => :histgram))
|
201
308
|
end
|
202
309
|
def line_chart(header,options={})
|
203
|
-
chart(
|
310
|
+
chart(header, options.merge(:chart_type => :line))
|
204
311
|
end
|
205
|
-
def chart(
|
206
|
-
@chart_specs << ChartSpec.new(
|
312
|
+
def chart(header,options={})
|
313
|
+
@chart_specs << ChartSpec.new(header,options)
|
207
314
|
end
|
208
315
|
def stats(header,*fields,&block)
|
209
316
|
@stat_specs << StatSpec.new(header,*fields,&block)
|
210
317
|
end
|
211
|
-
def add_stats(
|
318
|
+
def add_stats(stats_manager,headers)
|
212
319
|
stat_specs.each do |stat_spec|
|
213
|
-
stat_spec.prepare_indices(
|
320
|
+
stat_spec.prepare_indices(stats_manager,headers)
|
214
321
|
end
|
215
322
|
end
|
216
|
-
def add_fields(
|
217
|
-
|
218
|
-
|
219
|
-
stat_spec.map(fields),
|
220
|
-
stat_spec.index
|
221
|
-
)
|
323
|
+
def add_fields(stats_manager,fields)
|
324
|
+
stat_specs.each do |stat_spec|
|
325
|
+
stat_spec.add(stats_manager,fields)
|
222
326
|
end
|
223
327
|
end
|
224
328
|
def to_s
|
225
|
-
"
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
$specfile && $specs = Geoptima::StatsSpecs.new($specfile)
|
231
|
-
|
232
|
-
$files.each do |file|
|
233
|
-
lines = 0
|
234
|
-
filename = File.basename(file)
|
235
|
-
(names = filename.split(/[_\.]/)).pop
|
236
|
-
name = $merged_name || names.join('_')
|
237
|
-
puts "About to read file #{file}"
|
238
|
-
File.open(file).each do |line|
|
239
|
-
lines += 1
|
240
|
-
fields=line.chomp.split(/\t/)
|
241
|
-
if $stats[file]
|
242
|
-
puts "Processing line: #{line}" if($debug)
|
243
|
-
fields.each_with_index do |field,index|
|
244
|
-
$stats[file].add(field,index)
|
245
|
-
end
|
246
|
-
$specs && $specs.add_fields($stats[file],fields)
|
247
|
-
elsif($merge_all && $stats.length>0)
|
248
|
-
file = $stats.values[0].file
|
249
|
-
else
|
250
|
-
$stats[file] = Stats.new(filename,name,fields)
|
251
|
-
$specs && $specs.add_stats($stats[file])
|
329
|
+
"Stats[#{@stat_specs.join(', ')}] AND Charts[#{@chart_specs.join(', ')}]"
|
252
330
|
end
|
253
331
|
end
|
254
332
|
end
|
255
333
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
end
|
261
|
-
else
|
262
|
-
stats.headers.each_with_index do |header,index|
|
263
|
-
puts "Charting #{header} with diversity #{stats.diversity(index)}"
|
334
|
+
def create_all(name,stats_manager)
|
335
|
+
stats_manager.headers.each do |header|
|
336
|
+
stats = stats_manager.stats[header]
|
337
|
+
puts "Charting #{header} with diversity #{stats.diversity}"
|
264
338
|
case header
|
265
339
|
when 'signal.strength'
|
266
340
|
Geoptima::Chart.draw_line_chart(
|
267
|
-
|
268
|
-
|
269
|
-
stats.data
|
341
|
+
stats_manager.name,
|
342
|
+
stats_manager.time_stats.data,
|
343
|
+
stats.data.map{|f| v=f.to_i; (v>-130 && v<0) ? v : nil},
|
270
344
|
:title => 'Signal Strength',
|
271
345
|
:maximum_value => -30,
|
272
346
|
:minimum_value => -130,
|
273
347
|
:width => 1024
|
274
|
-
).write("#{$export_dir}/Chart_#{
|
348
|
+
).write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}.png")
|
275
349
|
|
276
|
-
hist = stats.stats
|
350
|
+
hist = stats.stats
|
277
351
|
keys = hist.keys.sort{|a,b| a.to_i <=> b.to_i}
|
278
352
|
values = keys.map{|k| hist[k]}
|
279
353
|
Geoptima::Chart.draw_histogram_chart(
|
280
|
-
|
354
|
+
stats_manager.name, keys, values,
|
281
355
|
:title => 'Signal Strength Distribution',
|
282
356
|
:width => 1024
|
283
|
-
).write("#{$export_dir}/Chart_#{
|
357
|
+
).write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}_distribution.png")
|
284
358
|
|
285
359
|
when 'Event'
|
286
|
-
hist = stats.stats
|
360
|
+
hist = stats.stats
|
287
361
|
keys = hist.keys.sort{|a,b| a.to_i <=> b.to_i}
|
288
362
|
values = keys.map{|k| hist[k]}
|
289
363
|
Geoptima::Chart.draw_category_chart(
|
290
|
-
|
364
|
+
stats_manager.name, keys, values,
|
291
365
|
:title => "#{header} Distribution",
|
292
366
|
:width => 1024
|
293
|
-
).write("#{$export_dir}/Chart_#{
|
367
|
+
).write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}_distribution.png")
|
294
368
|
|
295
369
|
else
|
296
|
-
if stats.diverse?
|
370
|
+
if stats.diverse?
|
297
371
|
puts "Ignoring high diversity field #{header}"
|
298
372
|
else
|
299
|
-
puts "Charting field: #{header} with length #{stats.length
|
300
|
-
hist = stats.stats
|
373
|
+
puts "Charting field: #{header} with length #{stats.length} and diversity #{stats.diversity}"
|
374
|
+
hist = stats.stats
|
301
375
|
keys = hist.keys.sort{|a,b| a.to_i <=> b.to_i}
|
302
376
|
values = keys.map{|k| hist[k]}
|
303
|
-
args = [
|
377
|
+
args = [stats_manager.name, keys, values, {
|
304
378
|
:title => "#{header} Distribution",
|
305
379
|
:width => 1024}]
|
306
|
-
g = (stats.length
|
380
|
+
g = (stats.length > 50) ?
|
307
381
|
Geoptima::Chart.draw_line_chart(*args) :
|
308
|
-
(stats.length
|
382
|
+
(stats.length > 10 || stats.numerical?) ?
|
309
383
|
Geoptima::Chart.draw_histogram_chart(*args) :
|
310
|
-
(stats.length
|
384
|
+
(stats.length > 1) ?
|
311
385
|
Geoptima::Chart.draw_category_chart(*args) :
|
312
386
|
nil
|
313
|
-
g && g.write("#{$export_dir}/Chart_#{
|
387
|
+
g && g.write("#{$export_dir}/Chart_#{stats_manager.name}_#{header}_distribution.png")
|
314
388
|
end
|
315
389
|
end
|
316
390
|
end
|
391
|
+
end
|
392
|
+
|
393
|
+
#
|
394
|
+
# Now run the actual program, reading the specifation file and then the CSV files
|
395
|
+
#
|
396
|
+
|
397
|
+
$stats_managers = {}
|
398
|
+
|
399
|
+
$specfile && $specs = Geoptima::StatsSpecs.new($specfile)
|
400
|
+
|
401
|
+
$files.each do |file|
|
402
|
+
lines = 0
|
403
|
+
headers = nil
|
404
|
+
filename = File.basename(file)
|
405
|
+
(names = filename.split(/[_\.]/)).pop
|
406
|
+
name = $merge_all ? ($merged_name || 'All') : names.join('_')
|
407
|
+
$stats_managers[name] ||= StatsManager.new(name)
|
408
|
+
puts "About to read file #{file}"
|
409
|
+
File.open(file).each do |line|
|
410
|
+
lines += 1
|
411
|
+
fields=line.chomp.split(/\t/)
|
412
|
+
if headers
|
413
|
+
puts "Processing line: #{line}" if($debug)
|
414
|
+
$stats_managers[name].add_all(fields,headers)
|
415
|
+
else
|
416
|
+
headers = fields
|
417
|
+
if headers.length<2
|
418
|
+
puts "Too few headers, rejecting #{file}"
|
419
|
+
break
|
420
|
+
end
|
421
|
+
$stats_managers[name].set_headers(headers)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# Finally output all charts specified
|
427
|
+
|
428
|
+
$stats_managers.each do |name,stats_manager|
|
429
|
+
if $specs
|
430
|
+
$specs.chart_specs.each do |chart_spec|
|
431
|
+
chart_spec.process(stats_manager)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
if $create_all
|
435
|
+
create_all name, stats_manager
|
317
436
|
end
|
318
437
|
end
|
319
438
|
|
data/examples/show_geoptima.rb
CHANGED
data/lib/geoptima/chart.rb
CHANGED
@@ -20,6 +20,12 @@ end
|
|
20
20
|
|
21
21
|
module Geoptima
|
22
22
|
class Chart
|
23
|
+
def self.libs
|
24
|
+
$chart_libs
|
25
|
+
end
|
26
|
+
def self.available?
|
27
|
+
$chart_libs.length>0
|
28
|
+
end
|
23
29
|
DEFAULT_OPTIONS = {:show_points => true, :show_lines => true, :title => nil, :width => 800, :margins => 20, :font_size => 14}
|
24
30
|
attr_reader :chart_type
|
25
31
|
attr_accessor :chart, :data, :options
|
@@ -55,6 +61,18 @@ module Geoptima
|
|
55
61
|
options[:filename] && g.write(options[:filename])
|
56
62
|
g
|
57
63
|
end
|
64
|
+
def self.draw_grouped_chart(legends,keys,values,options={})
|
65
|
+
puts "Creating a chart with legends #{legends.inspect} for #{keys.length} keys"
|
66
|
+
chart_type = (options[:chart_type]==:line) ? :line : :bar
|
67
|
+
g = make_chart(chart_type, options)
|
68
|
+
legends.each do |legend|
|
69
|
+
g.data(legend, values[legend])
|
70
|
+
end
|
71
|
+
g.minimum_value = 0
|
72
|
+
g.labels = keys.inject({}){|a,v| a[a.length] = v;a}
|
73
|
+
options[:filename] && g.write(options[:filename])
|
74
|
+
g
|
75
|
+
end
|
58
76
|
def self.draw_category_chart(legend,keys,values,options={})
|
59
77
|
puts "Creating category chart with keys: #{keys.join(',')}"
|
60
78
|
puts "Creating category chart with values: #{values.join(',')}"
|
data/lib/geoptima/data.rb
CHANGED
@@ -2,21 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'multi_json'
|
5
|
-
require '
|
6
|
-
if $use_dateperformance
|
7
|
-
begin
|
8
|
-
require 'date/performance'
|
9
|
-
require 'date/memoize'
|
10
|
-
class DateTime
|
11
|
-
def >(other) ; self - other > 0 ; end
|
12
|
-
def <(other) ; self - other < 0 ; end
|
13
|
-
def >=(other); self - other >= 0; end
|
14
|
-
def <=(other); self - other <= 0; end
|
15
|
-
end
|
16
|
-
rescue LoadError
|
17
|
-
puts "No date-performance gem installed, some features will run slower"
|
18
|
-
end
|
19
|
-
end
|
5
|
+
require 'geoptima/daterange'
|
20
6
|
|
21
7
|
#
|
22
8
|
# The Geoptima Module provides support for the Geoptima Client JSON file format
|
@@ -43,26 +29,6 @@ module Geoptima
|
|
43
29
|
end
|
44
30
|
end
|
45
31
|
|
46
|
-
class DateRange
|
47
|
-
attr_reader :min, :max, :range
|
48
|
-
def initialize(min,max)
|
49
|
-
@min = min
|
50
|
-
@max = max
|
51
|
-
@range = Range.new(min,max)
|
52
|
-
end
|
53
|
-
if ENV['RUBY_VERSION'] =~ /1\.8/
|
54
|
-
puts "Defining Range.include? to wrap for 1.8"
|
55
|
-
def include?(time)
|
56
|
-
@range.include?(time)
|
57
|
-
end
|
58
|
-
else
|
59
|
-
puts "Defining Range.include? to perform inequality tests for 1.9"
|
60
|
-
def include?(time)
|
61
|
-
(time >= min) && (time <= @max)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
32
|
# The Geoptima::Event class represents and individual record or event
|
67
33
|
class Event
|
68
34
|
KNOWN_HEADERS={
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
if $use_dateperformance
|
5
|
+
begin
|
6
|
+
require 'date/performance'
|
7
|
+
require 'date/memoize'
|
8
|
+
class DateTime
|
9
|
+
def >(other) ; self - other > 0 ; end
|
10
|
+
def <(other) ; self - other < 0 ; end
|
11
|
+
def >=(other); self - other >= 0; end
|
12
|
+
def <=(other); self - other <= 0; end
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
puts "No date-performance gem installed, some features will run slower"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Geoptima
|
20
|
+
class DateRange
|
21
|
+
attr_reader :min, :max, :range
|
22
|
+
def initialize(min,max)
|
23
|
+
@min = min
|
24
|
+
@max = max
|
25
|
+
@range = Range.new(min,max)
|
26
|
+
end
|
27
|
+
if ENV['RUBY_VERSION'] =~ /1\.8/
|
28
|
+
puts "Defining Range.include? to wrap for 1.8"
|
29
|
+
def include?(time)
|
30
|
+
@range.include?(time)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
puts "Defining Range.include? to perform inequality tests for 1.9"
|
34
|
+
def include?(time)
|
35
|
+
(time >= min) && (time <= @max)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
data/lib/geoptima/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geoptima
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.9
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Craig Taverner
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-03-
|
18
|
+
date: 2012-03-22 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: multi_json
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- lib/geoptima/data.rb
|
79
79
|
- lib/geoptima/chart.rb
|
80
80
|
- lib/geoptima/options.rb
|
81
|
+
- lib/geoptima/daterange.rb
|
81
82
|
- lib/geoptima.rb
|
82
83
|
- examples/show_geoptima_sos.rb
|
83
84
|
- examples/show_geoptima.rb
|