grosser-db_graph 0.1.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.
data/README.markdown ADDED
@@ -0,0 +1,44 @@
1
+ Gem/Rails plugin to generate graphs from all your date fields, beautifully simple and combineable.
2
+
3
+ Examples
4
+ ========
5
+ Weeks and months
6
+ ![weeks](http://chart.apis.google.com/chart?chxl=0:|0||||||||||10||||||||||20||||||||||30||||||||||40||||||||||50|||1:|6|16|25|35|45|54|64|74|83|93&cht=lc&chs=445x400&chdl=Product+created_at|Product+updated_at&chd=s:JTKPKLKMUNPQHQSKRNLGPPLKUHNNPFJLLPNJINQex19u5xrrNFKLD,HLJMONLJLNHPQNOLNPMMINNKPMPGKLFJLRQLKOOtsxxvw137MHOLF&chco=333300,bbbb33&chxt=x,y)
7
+ ![months](http://chart.apis.google.com/chart?chxl=0:|1|2|3|4|5|6|7|8|9|10|11|12|1:|67|98|130|161|193|224|256|287|319|350&cht=lc&chs=445x400&chdl=Product+created_at|Product+updated_at&chd=s:QOQROONPM92L,OMPPOOMPO77N&chco=884466,22dddd&chxt=x,y)
8
+
9
+
10
+ Install
11
+ =======
12
+ As Gem:
13
+ sudo gem install grosser-db_graph -s http://gems.github.com/
14
+
15
+ Or as Rails plugin:
16
+ sudo gem install gchartrb
17
+ ./script/plugins install git://github.com/grosser/db_graph.git
18
+
19
+
20
+ Usage
21
+ =====
22
+ #all hours/days/weeks/months
23
+ g = DBGraph::Line.new(:weeks)
24
+
25
+ #hours/days/weeks/months in a selected interval (e.g. in 1 year for months)
26
+ g = DBGraph::Line.new(:weeks, :at=>Time.parse('2009-01-02 14:15:16'))
27
+
28
+ g.add(User, :created_at)
29
+ g.add(Item, :sold_at, :label=>'Things we sold')
30
+ g.add(Product, :sold_at, :conditions=>['merchandise = ?',false])
31
+ ...
32
+ g.to_url --> google chart url with all your data as line graph
33
+
34
+ We got :minutes, :hours, :days, :weeks, :months and seconds/years can be added if someone needs them...
35
+
36
+ TODO
37
+ ====
38
+ - weeks/months from beginning of data (->all years)
39
+
40
+ Author
41
+ ======
42
+ [Michael Grosser](http://pragmatig.wordpress.com)
43
+ grosser.michael@gmail.com
44
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ desc "Run all specs in spec directory"
2
+ task :default do |t|
3
+ options = "--colour --format progress --loadby --reverse"
4
+ files = FileList['spec/**/*_spec.rb']
5
+ system("spec #{options} #{files}")
6
+ end
7
+
8
+
9
+ begin
10
+ require 'jeweler'
11
+ project_name = 'db_graph'
12
+ Jeweler::Tasks.new do |gem|
13
+ gem.name = project_name
14
+ gem.summary = "AR generate beautiful graphs from date fields, in 1 LOC"
15
+ gem.email = "grosser.michael@gmail.com"
16
+ gem.homepage = "http://github.com/grosser/#{project_name}"
17
+ gem.authors = ["Michael Grosser"]
18
+ gem.add_dependency ['gchartrb']
19
+ gem.add_dependency ['activesupport']
20
+ gem.add_dependency ['activerecord']
21
+ end
22
+ rescue LoadError
23
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
24
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 1
3
+ :patch: 2
4
+ :major: 0
@@ -0,0 +1,112 @@
1
+ gem 'gchartrb'
2
+ require 'google_chart'
3
+
4
+ module DBGraph
5
+ class Line
6
+ NUM_Y_LABELS = 10
7
+
8
+ attr_accessor :data
9
+
10
+ def initialize(style, options={})
11
+ @style = style.to_sym
12
+ @options = options
13
+ self.data = {}
14
+ end
15
+
16
+ def add(model, attribute, options={})
17
+ label = options.delete(:label) || "#{model} #{attribute}"
18
+ data[label] = count(model, attribute, options)
19
+ end
20
+
21
+ def count(model, attribute, options={})
22
+ scope = if @options[:at]
23
+ start, ende = interval_for(@options[:at])
24
+ model.scoped(:conditions=>["#{attribute} BETWEEN ? AND ?", start, ende]).scoped(options)
25
+ else
26
+ model.scoped(options)
27
+ end
28
+ data_type = @style.to_s.sub(/s$/,'').upcase
29
+ scope.count(:group=>"#{data_type}(#{attribute})")
30
+ end
31
+
32
+ def to_url
33
+ size = @options[:size] || '600x500'
34
+ GoogleChart::LineChart.new(size, nil, false) do |line|
35
+ for name, hash in data
36
+ line.data(name, self.class.filled_and_sorted_values(hash, x_values), random_color)
37
+ end
38
+ line.axis :x, :labels => x_labels
39
+ line.axis :y, :labels => y_labels
40
+ end.to_url
41
+ end
42
+
43
+ def x_labels
44
+ case @style
45
+ when :days then x_values.map{|x|x%5==0 ? x : ''}
46
+ when :weeks, :minutes then x_values.map{|x|x%10==0 ? x : ''}
47
+ else x_values
48
+ end
49
+ end
50
+
51
+ def y_labels
52
+ values = []
53
+ data.each{|name,hash|values += self.class.filled_and_sorted_values(hash, x_values)}
54
+ distribute_evently(values, NUM_Y_LABELS)
55
+ end
56
+
57
+ private
58
+
59
+ def x_values
60
+ case @style
61
+ when :minutes then 0..59
62
+ when :hours then 0..23
63
+ when :days then 1..31
64
+ when :weeks then 0..52 #week 0 == week 52 of last year, e.g. in 2009-01-01
65
+ when :months then 1..12
66
+ else raise "#{@style} is not supported"
67
+ end.to_a
68
+ end
69
+
70
+ def random_color
71
+ [1,2,3].map{ (('0'..'9').to_a + ('a'..'f').to_a)[rand(16)] * 2 } * ''
72
+ end
73
+
74
+ def interval_for(time)
75
+ interval_word = {:minutes=>:hour,:hours=>:day,:days=>:month,:weeks=>:year,:months=>:year}[@style]
76
+ raise "style #{@style} is not supported" unless interval_word
77
+
78
+ start = self.class.at_beginning_of_interval(time, interval_word)
79
+ ende = start + 1.send(interval_word) - 1.second
80
+ [start, ende]
81
+ end
82
+
83
+ def self.at_beginning_of_interval(time, interval_type)
84
+ #simple substraction does not (seem) to work for long intervals (summer/winter time ?)
85
+ #and at_beginning_of_hour is not defined
86
+ if [:year, :month, :week, :day].include? interval_type
87
+ time.to_date.send("at_beginning_of_#{interval_type}")
88
+ else
89
+ time - (time.to_i % 1.send(interval_type))
90
+ end
91
+ end
92
+
93
+ def self.filled_and_sorted_values(values, keys)
94
+ array = fill_up_values(values, keys)
95
+ array.sort.map(&:last)
96
+ end
97
+
98
+ #values is array or hash with integer or string keys
99
+ def self.fill_up_values(values, expected_keys)
100
+ values = values.map{|k,v| [k.to_i, v]}
101
+ expected_keys.each{|k| values += [[k,0]] unless values.assoc(k)}
102
+ values.sort
103
+ end
104
+
105
+ def distribute_evently(values, num_steps)
106
+ min = values.min
107
+ max = values.max
108
+ step = (max-min) / (num_steps-1).to_f
109
+ (0...num_steps).map{|i| (step*i + min).to_f.round}
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,218 @@
1
+ require File.join(File.dirname(__FILE__),'..','spec_helper')
2
+
3
+ describe DBGraph::Line do
4
+ describe "minutes" do
5
+ before :all do
6
+ Product.delete_all
7
+ Product.create!(:created_at=>"2009-01-01 10:59:59")
8
+ Product.create!(:created_at=>"2009-01-01 10:59:59")
9
+
10
+ Product.create!(:created_at=>"2009-01-01 11:00:00")
11
+ Product.create!(:created_at=>"2009-01-01 11:10:10")
12
+ Product.create!(:created_at=>"2009-01-01 11:10:20")
13
+ Product.create!(:created_at=>"2009-01-01 11:59:59")
14
+
15
+ Product.create!(:created_at=>"2009-01-01 12:00:00")
16
+ Product.create!(:created_at=>"2009-01-01 12:00:00")
17
+ end
18
+
19
+ it "collects from one hour when at is set" do
20
+ line = DBGraph::Line.new(:minutes, :at=>Time.parse('2009-01-01 11:15:16'))
21
+ line.count(Product, :created_at).should == {"0"=>1,"10"=>2,"59"=>1}
22
+ end
23
+
24
+ it "collects from all hours when at is not set" do
25
+ @line = DBGraph::Line.new(:minutes)
26
+ @line.count(Product, :created_at).should == {"59"=>3,"0"=>3,"10"=>2}
27
+ end
28
+ end
29
+
30
+ describe "hours" do
31
+ before :all do
32
+ Product.delete_all
33
+ Product.create!(:created_at=>"2009-01-01 23:59:59")
34
+
35
+ Product.create!(:created_at=>"2009-01-02 00:00:00")
36
+ Product.create!(:created_at=>"2009-01-02 10:11:12")
37
+ Product.create!(:created_at=>"2009-01-02 10:59:12")
38
+ Product.create!(:created_at=>"2009-01-02 13:11:12")
39
+
40
+ Product.create!(:created_at=>"2009-01-03 00:00:00")
41
+ end
42
+
43
+ it "collects from one day when at is set" do
44
+ @line = DBGraph::Line.new(:hours, :at=>Time.parse('2009-01-02 14:15:16'))
45
+ @line.count(Product, :created_at).should == {"0"=>1,"10"=>2,"13"=>1}
46
+ end
47
+
48
+ it "collects from all days when at is not set" do
49
+ @line = DBGraph::Line.new(:hours)
50
+ @line.count(Product, :created_at).should == {"23"=>1,"0"=>2,"10"=>2,"13"=>1}
51
+ end
52
+ end
53
+
54
+ describe "days" do
55
+ before :all do
56
+ Product.delete_all
57
+ Product.create!(:created_at=>"2009-03-31 23:59:59")
58
+
59
+ Product.create!(:created_at=>"2009-04-01 00:00:00")
60
+ Product.create!(:created_at=>"2009-04-10 13:11:12")
61
+ Product.create!(:created_at=>"2009-04-30 23:59:59")
62
+ Product.create!(:created_at=>"2009-04-30 23:59:59")
63
+
64
+ Product.create!(:created_at=>"2009-05-01 00:00:00")
65
+ end
66
+
67
+ it "collects from one onth when at is set" do
68
+ @line = DBGraph::Line.new(:days, :at=>Time.parse('2009-04-02 14:15:16'))
69
+ @line.count(Product, :created_at).should == {"1"=>1,"10"=>1,"30"=>2}
70
+ end
71
+
72
+ it "collects from all years when at is not set" do
73
+ @line = DBGraph::Line.new(:days)
74
+ @line.count(Product, :created_at).should == {"1"=>2,"10"=>1,"30"=>2,"31"=>1}
75
+ end
76
+ end
77
+
78
+ describe "weeks" do
79
+ before :all do
80
+ Product.delete_all
81
+ Product.create!(:created_at=>"2008-12-31 23:59:59")
82
+
83
+ Product.create!(:created_at=>"2009-01-01 00:00:00")
84
+ Product.create!(:created_at=>"2009-05-31 13:11:12")
85
+ Product.create!(:created_at=>"2009-05-31 13:11:11")
86
+ Product.create!(:created_at=>"2009-12-31 23:59:59")
87
+
88
+ Product.create!(:created_at=>"2010-01-01 00:00:00")
89
+ end
90
+
91
+ it "collects from one year when at is set" do
92
+ @line = DBGraph::Line.new(:weeks, :at=>Time.parse('2009-01-02 14:15:16'))
93
+ @line.count(Product, :created_at).should == {"0"=>1,"22"=>2,"52"=>1}
94
+ end
95
+
96
+ it "collects from all years when at is not set" do
97
+ @line = DBGraph::Line.new(:weeks)
98
+ @line.count(Product, :created_at).should == {"0"=>2,"22"=>2,"52"=>2}
99
+ end
100
+ end
101
+
102
+ describe "months" do
103
+ before :all do
104
+ Product.delete_all
105
+ Product.create!(:created_at=>"2008-12-31 23:59:59")
106
+
107
+ Product.create!(:created_at=>"2009-01-01 00:00:00")
108
+ Product.create!(:created_at=>"2009-05-31 13:11:12")
109
+ Product.create!(:created_at=>"2009-05-31 13:11:11")
110
+ Product.create!(:created_at=>"2009-12-31 23:59:59")
111
+
112
+ Product.create!(:created_at=>"2010-01-01 00:00:00")
113
+ end
114
+
115
+ it "collects from one year when at is set" do
116
+ @line = DBGraph::Line.new(:months, :at=>Time.parse('2009-01-02 14:15:16'))
117
+ @line.count(Product, :created_at).should == {"1"=>1,"5"=>2,"12"=>1}
118
+ end
119
+
120
+ it "collects from all years when at is not set" do
121
+ @line = DBGraph::Line.new(:months)
122
+ @line.count(Product, :created_at).should == {"1"=>2,"5"=>2,"12"=>2}
123
+ end
124
+ end
125
+
126
+ describe :add do
127
+ it "accepts options for count" do
128
+ Product.delete_all
129
+ 3.times{Product.create!}
130
+ 2.times{Product.create!(:created_at=>'2009-02-02')}
131
+ 3.times{Product.create!}
132
+
133
+ line = DBGraph::Line.new(:months)
134
+ line.add(Product, :created_at, :conditions=>"MONTH(created_at)=2")
135
+ line.data.values.first.should == {"2"=>2}
136
+ end
137
+
138
+ it "supports custom labels" do
139
+ line = DBGraph::Line.new(:months)
140
+ line.add(Product, :created_at, :label=>'ToC')
141
+ line.data['ToC'].should_not be_nil
142
+ end
143
+
144
+ it "adds the collected data to a hash" do
145
+ line = DBGraph::Line.new(:months)
146
+ line.add(Product, :created_at)
147
+ line.data['Product created_at'].should_not be_nil
148
+ end
149
+ end
150
+
151
+ describe :x_labels do
152
+ it "has minutes with gaps for readability" do
153
+ line = DBGraph::Line.new(:minutes)
154
+ gaps = [''] * 9
155
+ line.x_labels.should == [0,gaps,10,gaps,20,gaps,30,gaps,40,gaps,50,gaps].flatten
156
+ end
157
+
158
+ it "has all hours" do
159
+ line = DBGraph::Line.new(:hours)
160
+ line.x_labels.should == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
161
+ end
162
+
163
+ it "has days with gaps for readability" do
164
+ line = DBGraph::Line.new(:days)
165
+ gaps = ['','','','']
166
+ line.x_labels.should == [gaps,5,gaps,10,gaps,15,gaps,20,gaps,25,gaps,30,''].flatten
167
+ end
168
+
169
+ it "has weeks with gaps for readability" do
170
+ line = DBGraph::Line.new(:weeks)
171
+ gaps = [''] * 9
172
+ line.x_labels.should == [0,gaps,10,gaps,20,gaps,30,gaps,40,gaps,50,'',''].flatten
173
+ end
174
+
175
+ it "has all months" do
176
+ line = DBGraph::Line.new(:months)
177
+ line.x_labels.should == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
178
+ end
179
+ end
180
+
181
+ describe :y_labels do
182
+ it "has even values as y labels" do
183
+ Product.should_receive(:count).and_return({"0"=>1,"1"=>3,"2"=>3,"23"=>9})
184
+ line = DBGraph::Line.new(:hours)
185
+ line.add(Product, :created_at)
186
+ line.y_labels.should == [0,1,2,3,4,5,6,7,8,9]
187
+ end
188
+ end
189
+
190
+ describe :fill_up_values do
191
+ it "adds 0 for every non-filled value" do
192
+ data = [["1",2], ["3",3]]
193
+ expected_keys = [1,2,3,4,5]
194
+ filled = DBGraph::Line.send(:fill_up_values, data, expected_keys)
195
+ filled.should == [[1,2], [2,0], [3,3], [4,0], [5,0]]
196
+ end
197
+ end
198
+
199
+ describe :filled_and_sorted_values do
200
+ it "fills/sorts the values" do
201
+ data = {"1"=>2, "3"=>3, "10"=>4}
202
+ expected_keys = [1,2,3,4,5,6,7,8,9,10]
203
+ filled = DBGraph::Line.send(:filled_and_sorted_values, data, expected_keys)
204
+ filled.should == [2, 0, 3, 0, 0, 0, 0, 0, 0, 4]
205
+ end
206
+ end
207
+
208
+ describe :random_color do
209
+ before do
210
+ @line = DBGraph::Line.new(:months)
211
+ end
212
+
213
+ it "includes pairs of colors" do
214
+ @line.should_receive(:rand).and_return 0,2,10
215
+ @line.send(:random_color).should == '0022aa'
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,7 @@
1
+ # ---- requirements
2
+ require 'rubygems'
3
+ $LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
4
+ require 'activerecord'
5
+ require 'activesupport'
6
+ load File.expand_path("test_model.rb", File.dirname(__FILE__))
7
+ require 'db_graph/line'
@@ -0,0 +1,15 @@
1
+ ActiveRecord::Base.establish_connection({
2
+ :adapter => "mysql",
3
+ :database => "db_graph_test",
4
+ :user => 'root',
5
+ :password => 'root'
6
+ })
7
+
8
+ ActiveRecord::Schema.define(:version => 1) do
9
+ create_table :products, :force=>true do |t|
10
+ t.timestamps
11
+ end
12
+ end
13
+
14
+ class Product < ActiveRecord::Base
15
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grosser-db_graph
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-24 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: gchartrb
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: activerecord
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description:
46
+ email: grosser.michael@gmail.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.markdown
53
+ files:
54
+ - README.markdown
55
+ - Rakefile
56
+ - VERSION.yml
57
+ - lib/db_graph/line.rb
58
+ - spec/db_graph/line_spec.rb
59
+ - spec/spec_helper.rb
60
+ - spec/test_model.rb
61
+ has_rdoc: true
62
+ homepage: http://github.com/grosser/db_graph
63
+ post_install_message:
64
+ rdoc_options:
65
+ - --charset=UTF-8
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.2.0
84
+ signing_key:
85
+ specification_version: 2
86
+ summary: AR generate beautiful graphs from date fields, in 1 LOC
87
+ test_files:
88
+ - spec/spec_helper.rb
89
+ - spec/db_graph/line_spec.rb
90
+ - spec/test_model.rb