mountain-goat 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +5 -4
  3. data/generators/mg/mg_generator.rb +2 -2
  4. data/generators/mg/templates/update_mountain_goat_tables_v2.rb +73 -0
  5. data/lib/mountain-goat/controllers/mg/choices_controller.rb +81 -0
  6. data/lib/mountain-goat/controllers/mg/goals_controller.rb +136 -0
  7. data/lib/mountain-goat/controllers/mg/records_controller.rb +46 -0
  8. data/lib/mountain-goat/controllers/mg/tests_controller.rb +139 -0
  9. data/lib/mountain-goat/mg_core.rb +403 -0
  10. data/lib/mountain-goat/models/mg/choice.rb +50 -0
  11. data/lib/mountain-goat/models/mg/gi_meta.rb +10 -0
  12. data/lib/mountain-goat/models/mg/goal.rb +183 -0
  13. data/lib/mountain-goat/models/mg/goal_meta_type.rb +20 -0
  14. data/lib/mountain-goat/models/mg/gs_meta.rb +10 -0
  15. data/lib/mountain-goat/models/mg/record.rb +59 -0
  16. data/lib/mountain-goat/models/mg/test.rb +31 -0
  17. data/lib/mountain-goat/public/g-funnel.js +168 -0
  18. data/lib/mountain-goat/switch_choice.rb +32 -0
  19. data/lib/mountain-goat/version.rb +1 -1
  20. data/lib/mountain-goat/views/mountain_goat/layouts/.tmp_mountain_goat.html.erb.50680~ +76 -0
  21. data/lib/mountain-goat/views/mountain_goat/mg/choices/_choice_form.html.erb +37 -0
  22. data/lib/mountain-goat/views/mountain_goat/mg/choices/edit.html.erb +13 -0
  23. data/lib/mountain-goat/views/mountain_goat/mg/choices/index.html.erb +34 -0
  24. data/lib/mountain-goat/views/mountain_goat/mg/choices/new.html.erb +15 -0
  25. data/lib/mountain-goat/views/mountain_goat/mg/choices/show.html.erb +30 -0
  26. data/lib/mountain-goat/views/mountain_goat/mg/goals/.tmp__goal_form.html.erb.5027~ +0 -0
  27. data/lib/mountain-goat/views/mountain_goat/mg/goals/.tmp__goal_meta_type_form.html.erb.39992~ +0 -0
  28. data/lib/mountain-goat/views/mountain_goat/mg/goals/.tmp_edit.html.erb.55874~ +0 -0
  29. data/lib/mountain-goat/views/mountain_goat/mg/goals/.tmp_index.html.erb.97274~ +36 -0
  30. data/lib/mountain-goat/views/mountain_goat/mg/goals/_goal_form.html.erb +28 -0
  31. data/lib/mountain-goat/views/mountain_goat/mg/goals/_goal_meta_type_form.html.erb +34 -0
  32. data/lib/mountain-goat/views/mountain_goat/mg/goals/edit.html.erb +12 -0
  33. data/lib/mountain-goat/views/mountain_goat/mg/goals/index.html.erb +37 -0
  34. data/lib/mountain-goat/views/mountain_goat/mg/goals/new.html.erb +12 -0
  35. data/lib/mountain-goat/views/mountain_goat/mg/goals/show.html.erb +32 -0
  36. data/lib/mountain-goat/views/mountain_goat/mg/records/_record.html.erb +16 -0
  37. data/lib/mountain-goat/views/mountain_goat/mg/records/_records.html.erb +5 -0
  38. data/lib/mountain-goat/views/mountain_goat/mg/records/_records_form.html.erb +21 -0
  39. data/lib/mountain-goat/views/mountain_goat/mg/records/edit.html.erb +13 -0
  40. data/lib/mountain-goat/views/mountain_goat/mg/records/index.html.erb +17 -0
  41. data/lib/mountain-goat/views/mountain_goat/mg/records/new.html.erb +13 -0
  42. data/lib/mountain-goat/views/mountain_goat/mg/records/show.html.erb +14 -0
  43. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__chart.html.erb.13419~ +18 -0
  44. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__funnel.html.erb.60493~ +18 -0
  45. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__report_item_form.html.erb.87420~ +10 -0
  46. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__report_item_pivot_form.html.erb.77056~ +14 -0
  47. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp_edit.html.erb.31048~ +19 -0
  48. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp_new.html.erb.36371~ +17 -0
  49. data/lib/mountain-goat/views/mountain_goat/mg/report_items/_funnel.html.erb +13 -0
  50. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp__report_form.html.erb.76535~ +21 -0
  51. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp__report_report_items.html.erb.26030~ +5 -0
  52. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp_edit.html.erb.78064~ +36 -0
  53. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp_index.html.erb.74591~ +23 -0
  54. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp_show.html.erb.58427~ +21 -0
  55. data/lib/mountain-goat/views/mountain_goat/mg/tests/_test_form.html.erb +25 -0
  56. data/lib/mountain-goat/views/mountain_goat/mg/tests/edit.html.erb +12 -0
  57. data/lib/mountain-goat/views/mountain_goat/mg/tests/index.html.erb +48 -0
  58. data/lib/mountain-goat/views/mountain_goat/mg/tests/new.html.erb +12 -0
  59. data/lib/mountain-goat/views/mountain_goat/mg/tests/show.html.erb +63 -0
  60. metadata +69 -6
  61. data/lib/mountain-goat/metric_tracking.rb +0 -401
  62. data/lib/mountain-goat/switch_variant.rb +0 -32
@@ -0,0 +1,10 @@
1
+ class Mg::GiMeta < ActiveRecord::Base
2
+ set_table_name :mg_gi_metas
3
+
4
+ belongs_to :mg_goal_meta_type, :class_name => "Mg::GoalMetaType"
5
+ belongs_to :mg_record, :class_name => "Mg::Record"
6
+
7
+ validates_presence_of :mg_goal_meta_type_id
8
+ validates_presence_of :mg_record_id
9
+
10
+ end
@@ -0,0 +1,183 @@
1
+ # Mg::Goal represents a target for user interaction (E.g. Sign-up)
2
+ #
3
+ # Attributes
4
+ # goal_type:: A symbol uniquely identifying this goal type (for code interactions)
5
+ # name:: A name for this goal
6
+ # deleted_at:: Is this goal deleted? (MG Console)
7
+ # is_hidden:: Is this goal hidden? (MG Console)
8
+ class Mg::Goal < ActiveRecord::Base
9
+ set_table_name :mg_goals
10
+
11
+ # ActiveRecord Associations
12
+ has_many :mg_records, :class_name => "Mg::Record", :foreign_key => "mg_goal_id"
13
+ has_many :mg_goal_meta_types, :class_name => "Mg::GoalMetaType", :foreign_key => "mg_goal_id"
14
+ has_many :gi_metas, :through => :mg_goal_meta_types, :class_name => "Mg::GiMeta", :foreign_key => "mg_goal_id"
15
+ has_many :gs_metas, :through => :mg_goal_meta_types, :class_name => "Mg::GsMeta", :foreign_key => "mg_goal_id"
16
+ has_many :report_items, :as => :reportable, :class_name => "Mg::ReportItem"
17
+
18
+ # Secondary Associations
19
+ accepts_nested_attributes_for :mg_goal_meta_types, :reject_if => lambda { |a| a[:name].blank? || a[:var].blank? || a[:meta_type].blank? }, :allow_destroy => true
20
+
21
+ # Validations
22
+ validates_presence_of :name
23
+ validates_format_of :goal_type, :with => /[a-z0-9_]{3,50}/i, :message => "must be between 3 and 30 characters, alphanumeric with underscores"
24
+ validates_uniqueness_of :goal_type
25
+
26
+ # Member Functions
27
+
28
+ # Helper function to retrieve a goal by symbol
29
+ def self.by_type(s)
30
+ Mg::Goal.find( :first, :conditions => { :mg_goal_type => s.to_s } )
31
+ end
32
+
33
+ # Get all records for given meta (e.g. for "Referer": { "Youtube" => Record1, } ...)
34
+ def records_for_meta(var)
35
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
36
+ return {} if gmt.nil?
37
+ gmt.meta.map { |m| { m.data => m.record } }
38
+ end
39
+
40
+ # Get all records for a given meta value (e.g. for "Referer", "Facebook": [ Record1, Record2, Record3 ] )
41
+ def records_for_meta_val(var, data)
42
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
43
+ return [] if gmt.nil?
44
+ gmt.meta.find(:all, :conditions => { :data => data } ).map { |m| m.record }
45
+ end
46
+
47
+ # Get all records pivoted by given meta (e.g. "Youtube" => [ Date, Date, Date ], "Facebook" => [ Date, Date, Date ])
48
+ def records_pivot(pivot)
49
+ res = {}
50
+ gmt_pivot = self.mg_goal_meta_types.find_by_var( pivot.to_s )
51
+ return {} if gmt_pivot.nil?
52
+ gmt_pivot.meta.map { |c| { :created_at => c.created_at, :pivot => c.data } }.each do |c|
53
+ if !res.include?(c[:pivot])
54
+ res[c[:pivot]] = []
55
+ end
56
+
57
+ res[c[:pivot]].push c[:created_at]
58
+ end
59
+
60
+ res.each { |k,v| v.sort! }
61
+ res
62
+ end
63
+
64
+ def records_for_meta_val_pivot(var, data, pivot)
65
+ res = {}
66
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
67
+ gmt_pivot = self.mg_goal_meta_types.find_by_var( pivot.to_s )
68
+ return {} if gmt.nil? || gmt_pivot.nil?
69
+ gmt.meta.find(:all, :select => "`#{gmt.meta.table_name}`.created_at, gm.data AS pivot", :conditions => { :data => data }, :joins => "LEFT JOIN `#{gmt_pivot.meta.table_name}` gm ON gm.mg_goal_meta_type_id = #{gmt_pivot.id} AND gm.mg_record_id = `#{gmt.meta.table_name}`.mg_record_id").each do |c|
70
+ if !res.include?(c.pivot)
71
+ res[c.pivot] = []
72
+ end
73
+
74
+ res[c.pivot].push c.created_at
75
+ end
76
+
77
+ res.each { |k,v| v.sort! }
78
+ res
79
+ end
80
+
81
+ # Pivot by given meta data by value (TODO: Document better)
82
+ def records_for_meta_val_pivot_item(var, data, pivot, item)
83
+ res = {}
84
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
85
+ gmt_pivot = self.mg_goal_meta_types.find_by_var( pivot.to_s )
86
+ gmt_item = self.mg_goal_meta_types.find_by_var( item.to_s )
87
+ return {} if gmt.nil? || gmt_pivot.nil? || gmt_item.nil?
88
+ gmt.meta.find(:all, :select => "`#{gmt.meta.table_name}`.created_at, gm.data AS pivot, gi.data as item", :conditions => { :data => data }, :joins => "LEFT JOIN `#{gmt_pivot.meta.table_name}` gm ON gm.mg_goal_meta_type_id = #{gmt_pivot.id} AND gm.mg_record_id = `#{gmt.meta.table_name}`.mg_record_id LEFT JOIN `#{gmt_item.meta.table_name}` gi ON gi.mg_goal_meta_type_id = #{gmt_item.id} AND gi.mg_record_id = `#{gmt.meta.table_name}`.mg_record_id").each do |c|
89
+ if !res.include?(c.pivot)
90
+ res[c.pivot] = []
91
+ end
92
+
93
+ if gmt_item.meta_type == 'gi_meta'
94
+ c.item.to_i.times { res[c.pivot].push c.created_at }
95
+ else
96
+ res[c.pivot].push c.created_at if c.item == 'yes' #what else?
97
+ end
98
+ end
99
+
100
+ res.each { |k,v| v.sort! }
101
+ res.delete_if { |k,v| v.count == 0 }
102
+ res
103
+ end
104
+
105
+ # Get recent records based on start date
106
+ def recent_records(time_frame)
107
+ self.mg_records.find( :all, :conditions => [ "CREATED_AT > ?", time_frame.ago ] )
108
+ end
109
+
110
+ # Tally we have given a reward
111
+ def tally_reward_given( reward )
112
+ self.transaction do
113
+ self.update_attribute(:rewards_total, 0) if self.rewards_total.nil? #we should merge this with the next line, but whatever
114
+ Mg::Goal.update_counters(self.id, :rewards_given => 1)
115
+ Mg::Goal.update_counters(self.id, :rewards_total => reward)
116
+ end
117
+
118
+ return self.reload
119
+ end
120
+
121
+ #For Reportable
122
+
123
+ # Title in report charts
124
+ def reportable_title(pivot)
125
+ if pivot.nil?
126
+ return self.name
127
+ elsif pivot.instance_of?(Mg::GoalMetaType)
128
+ return "#{self.name} by #{pivot.name}"
129
+ end
130
+ end
131
+
132
+ #
133
+ def reportable_chart_items(pivot)
134
+ #let's look for pageviews by day by source
135
+ #rallies_for_meta_val( :clique_id, @clique.id )
136
+ #logger.debug "sources: #{sources.inspect}"
137
+ #We now have a map of { source => [ date1, date2, ... ], ... }
138
+
139
+ #Now, we just need to insert missing data (and group)
140
+ #Let's transpose that into { source => [ :x => day0, y => count ] }
141
+ if pivot.nil?
142
+ return Analytics.pivot_by_date( { :"Records" => self.mg_records.map { |r| r.created_at } }, self.created_at )
143
+ elsif pivot.instance_of?(Mg::GoalMetaType)
144
+ sources = self.records_pivot( pivot.var )
145
+ logger.warn "sources: #{sources}"
146
+ return Analytics.pivot_by_date(sources, self.created_at)
147
+ end
148
+ end
149
+
150
+ #
151
+ def reportable_gerbil_chart(pivot)
152
+ #chart = GerbilCharts::Charts::LineChart.new( :width => 350, :height => 200, :style => 'brushmetal.css', :circle_data_points => true )
153
+
154
+ if pivot.nil?
155
+ data = Analytics.pivot_by_date( { :"Records" => self.mg_records.map { |r| r.created_at } }, self.created_at )
156
+ elsif pivot.instance_of?(Mg::GoalMetaType)
157
+ sources = self.records_pivot( pivot.var )
158
+ data = Analytics.pivot_by_date(sources, self.created_at)
159
+ end
160
+ #logger.warn "data: #{data.inspect}"
161
+ #logger.warn "ts: #{data.map { |line| line[1].map { |d| d[:x] }.to_a }[0].inspect}"
162
+ #logger.warn "val: #{data.map { |line| [ line[0] ] + line[1].map { |d| d[:y] }.to_a }[0].inspect}"
163
+ #chart.modelgroup = GerbilCharts::Models::SimpleTimeSeriesModelGroup.new(
164
+ # :title => "Rallies",
165
+ # :timeseries => data.map { |line| line[1].map { |d| d[:x].to_time }.to_a }[0],
166
+ # :models => data.map { |line| [ line[0].to_s ] + line[1].map { |d| d[:y] }.to_a }
167
+ #)
168
+ #fields = { }
169
+ #fields_simple = []
170
+ #fields.merge!({ d[:x].to_time.to_i => d[:x].to_s } ); fields_simple.push(d[:x])
171
+ graph = SVG::Graph::TimeSeries.new( :height => 350, :width => 700, :show_data_labels => false, :x_label_format => "%m/%d/%y", :graph_title => self.reportable_title(pivot), :show_graph_title => true, :show_data_values => false, :show_data_points => false, :area_fill => true )
172
+ data.each do |line|
173
+ res = []
174
+ line[1].each { |d| res.push(d[:x].strftime('%m/%d/%y')).push(d[:y]) }
175
+ graph.add_data :data => res, :title => line[0].to_s
176
+ end
177
+
178
+ #logger.warn "Fields: #{fields.inspect}"
179
+ #logger.warn "Fields Simple: #{fields_simple.inspect}"
180
+
181
+ graph
182
+ end
183
+ end
@@ -0,0 +1,20 @@
1
+ class Mg::GoalMetaType < ActiveRecord::Base
2
+ set_table_name :mg_goal_meta_types
3
+
4
+ belongs_to :mg_goal, :class_name => "Mg::Goal"
5
+ has_many :gi_metas, :dependent => :destroy, :class_name => "Mg::GiMeta", :foreign_key => "mg_goal_meta_type_id"
6
+ has_many :gs_metas, :dependent => :destroy, :class_name => "Mg::GsMeta", :foreign_key => "mg_goal_meta_type_id"
7
+
8
+ validates_presence_of :name
9
+ validates_presence_of :var
10
+ validates_presence_of :meta_type
11
+
12
+ def meta
13
+ case self.meta_type
14
+ when 'gi_meta', 'ci_meta'
15
+ return self.gi_metas
16
+ when 'gs_meta', 'cs_meta'
17
+ return self.gs_metas
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ class Mg::GsMeta < ActiveRecord::Base
2
+ set_table_name :mg_gs_metas
3
+
4
+ belongs_to :mg_goal_meta_type, :class_name => "Mg::GoalMetaType"
5
+ belongs_to :mg_record, :class_name => "Mg::Record"
6
+
7
+ validates_presence_of :mg_goal_meta_type_id
8
+ validates_presence_of :mg_record_id
9
+
10
+ end
@@ -0,0 +1,59 @@
1
+ class Mg::Record < ActiveRecord::Base
2
+ set_table_name :mg_records
3
+
4
+ belongs_to :mg_goal, :class_name => "Mg::Goal"
5
+ has_many :mg_goal_meta_types, :through => :mg_goal, :class_name => "Mg::GoalMetaType"
6
+ #has_many :mg_gi_metas, :through => :mg_goal_meta_types
7
+ #has_many :mg_gs_metas, :through => :mg_goal_meta_types
8
+
9
+ #Set meta data for applicable options-- don't store nil data (waste of space)
10
+ def set_meta_data(options)
11
+ options.each do |option|
12
+ gmt = self.mg_goal_meta_types.find_by_var(option[0].to_s)
13
+
14
+ #Create cmt if it doesn't current exist (unless nil)
15
+ if gmt.nil? && !option[1].nil? && ( option[1].is_a?(Integer) || option[1].is_a?(String) )
16
+ #infer type
17
+ meta_type = "gi_meta" if option[1].is_a?(Integer)
18
+ meta_type = "gs_meta" if option[1].is_a?(String)
19
+ gmt = mg_goal.mg_goal_meta_types.create!(:name => option[0].to_s, :var => option[0].to_s, :meta_type => meta_type)
20
+ end
21
+
22
+ if !gmt.nil? #only if we can do it
23
+ gmt.meta.create!(:mg_record_id => self.id, :data => option[1]) if !option[1].nil?
24
+ end
25
+ end
26
+ end
27
+
28
+ def meta_for( var )
29
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
30
+ return nil if gmt.nil?
31
+ m = gmt.meta.find_by_mg_record_id( self.id )
32
+ return nil if m.nil?
33
+ return m.data
34
+ end
35
+
36
+ def all_metas
37
+ res = {}
38
+ self.mg_goal_meta_types.each do |gmt|
39
+ r = res.count
40
+ begin
41
+ if gmt.var =~ /(\w+)[_]id/i
42
+ if Kernel.const_get($1.classify)
43
+ item = Kernel.const_get($1.classify).find( self.meta_for( gmt.var ) )
44
+ if item.respond_to?(:name)
45
+ res.merge!({ $1 => item.name })
46
+ elsif item.respond_to?(:title)
47
+ res.merge!({ $1 => item.title })
48
+ end
49
+ end
50
+ end
51
+ rescue
52
+ end
53
+
54
+ res.merge!({ gmt.var => self.meta_for( gmt.var ) }) if res.count == r
55
+ end
56
+
57
+ res
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ # Mg::Test represents something you are 'a/b testing'
2
+ #
3
+ # Attributes
4
+ # test_type:: Symbol uniquely identifying this test (for code interaction)
5
+ # title:: Title of the test (E.g. Banner text)
6
+ # tally_each_serve:: Should we count each view by a user as a hit, or just first-serve to that user?
7
+ # is_switch:: Are we implementing a code-switch as opposed to a text substitution
8
+ # deleted_at:: Is this test deleted? (MG Console)
9
+ # is_hidden:: Is this test hidden? (MG Console)
10
+ class Mg::Test < ActiveRecord::Base
11
+ set_table_name :mg_tests
12
+
13
+ # ActiveRecord Associations
14
+ has_many :mg_choices, :class_name => "Mg::Choice", :foreign_key => "mg_test_id"
15
+
16
+ # Validations
17
+ validates_format_of :test_type, :with => /[a-z0-9_]{3,50}/i, :message => "must be between 3 and 30 characters, alphanumeric with underscores"
18
+ validates_uniqueness_of :test_type
19
+
20
+ # Member Functions
21
+
22
+ # Get total reward of all choices for this test
23
+ def total_reward
24
+ self.mg_choices.map { |choice| choice.reward || 0 }.sum
25
+ end
26
+
27
+ # Get total served of all choices for this test
28
+ def total_served
29
+ self.mg_choices.map { |choice| choice.served || 0 }.sum
30
+ end
31
+ end
@@ -0,0 +1,168 @@
1
+ /*
2
+ * g.Raphael 0.4.1 - Charting library, based on Raphaël
3
+ *
4
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
5
+ * 2011 Geoffrey Hayes (http://github.com/hayesgm)
6
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
7
+ */
8
+ Raphael.fn.g.funnelchart = function (cx, cy, cw, ch, values, opts) {
9
+ opts = opts || {};
10
+ var paper = this,
11
+ sectors = [],
12
+ covers = this.set(),
13
+ chart = this.set(),
14
+ series = this.set(),
15
+ insetLabels = this.set(),
16
+ order = [],
17
+ len = values.length,
18
+ angle = 0,
19
+ total = 0,
20
+ others = 0,
21
+ cut = 9,
22
+ defcut = true,
23
+ max = 0.0,
24
+ sh = ch / ( len + 0.0 );
25
+ chart.covers = covers;
26
+
27
+ function trap(x0, y0, w0, w1, h) {
28
+ path = ['M', x0, y0, 'H', x0 + w0, 'L', ( w0 - w1 ) / 2.0 + x0 + w1, y0 + h, 'H', ( w0 - w1 ) / 2.0 + x0, 'Z'];
29
+ return path;
30
+ };
31
+
32
+ var labeltext;
33
+ for (var i = 0; i < len; i++) {
34
+ total += values[i];
35
+ max = max > values[i] ? max : values[i] + 0.0;
36
+
37
+ if (opts.legend && opts.legend.length > i)
38
+ labeltext = opts.legend[i];
39
+
40
+ values[i] = {value: values[i], order: i, valueOf: function () { return this.value; }, labeltext: labeltext};
41
+ }
42
+
43
+ //values.sort(function (a, b) {
44
+ // return b.value - a.value;
45
+ //});
46
+
47
+ for (i = 0; i < len; i++) {
48
+
49
+ var sx0 = ( cx + (cw / 2.0 ) - cw * ( values[i] / ( 2.0 * max ) ) );
50
+ var sy0 = cy + sh * i;
51
+ var w0 = values[i] * cw / max;
52
+ var w1 = ( i < len - 1 ) ? values[i + 1] * cw / max : 0;
53
+
54
+ if (opts.init) {
55
+ var ipath = trap(sx0, sy0, w0, w1, sh).join(",");
56
+ }
57
+ //console.log([sx0, sy0, w0, w1, sh]);
58
+ var path = trap(sx0, sy0, w0, w1, sh);
59
+ var p = this.path(path).attr({fill: opts.colors && opts.colors[i] || this.g.colors[i] || "#666", stroke: opts.stroke || "#fff", "stroke-width": (opts.strokewidth == null ? 1 : opts.strokewidth), "stroke-linejoin": "round"});
60
+ if (values[i].labeltext)
61
+ p.insetLabel = this.text(sx0 + w0 / 2.0, sy0 + sh / 3.0, values[i].labeltext + ( i > 0 ? ( " " + ( values[i] - values[i - 1] / values[i - 1] ) + "%" ) : "")).attr({font: "20px Helvetica", stroke: "#fff", fill: "#eee", "fill-opacity": 0.95, "stroke-opacity": 0 });
62
+
63
+ p.value = values[i];
64
+ sectors.push(p);
65
+ series.push(p);
66
+ opts.init && p.animate({path: path.join(",")}, (+opts.init - 1) || 1000, ">");
67
+ }
68
+
69
+ for (i = 0; i < len; i++) {
70
+ p = paper.path(sectors[i].attr("path")).attr(this.g.shim);
71
+ opts.href && opts.href[i] && p.attr({href: opts.href[i]});
72
+ p.attr = function () {};
73
+ covers.push(p);
74
+ series.push(p);
75
+ }
76
+
77
+ chart.hover = function (fin, fout) {
78
+ fout = fout || function () {};
79
+ var that = this;
80
+ for (var i = 0; i < len; i++) {
81
+ (function (sector, cover, j) {
82
+ var o = {
83
+ sector: sector,
84
+ cover: cover,
85
+ cx: cx,
86
+ cy: cy,
87
+ cw: cw,
88
+ ch: ch,
89
+ value: values[j],
90
+ total: total,
91
+ label: that.labels && that.labels[j]
92
+ };
93
+ cover.mouseover(function () {
94
+ fin.call(o);
95
+ }).mouseout(function () {
96
+ fout.call(o);
97
+ });
98
+ })(series[i], covers[i], i);
99
+ }
100
+ return this;
101
+ };
102
+ // x: where label could be put
103
+ // y: where label could be put
104
+ // value: value to show
105
+ // total: total number to count %
106
+ chart.each = function (f) {
107
+ var that = this;
108
+ for (var i = 0; i < len; i++) {
109
+ (function (sector, cover, j) {
110
+ var o = {
111
+ sector: sector,
112
+ cover: cover,
113
+ cx: cx,
114
+ cy: cy,
115
+ x: sector.middle.x,
116
+ y: sector.middle.y,
117
+ mangle: sector.mangle,
118
+ r: r,
119
+ value: values[j],
120
+ total: total,
121
+ label: that.labels && that.labels[j]
122
+ };
123
+ f.call(o);
124
+ })(series[i], covers[i], i);
125
+ }
126
+ return this;
127
+ };
128
+ chart.inject = function (element) {
129
+ element.insertBefore(covers[0]);
130
+ };
131
+ var legend = function (labels, otherslabel, mark, dir) {
132
+ var x = cx + cw * 1.7,
133
+ y = cy,
134
+ h = y + 10;
135
+ labels = labels || [];
136
+ dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "east";
137
+ mark = paper.g.markers[mark && mark.toLowerCase()] || "disc";
138
+ chart.labels = paper.set();
139
+ for (var i = 0; i < len; i++) {
140
+ var clr = series[i].attr("fill"),
141
+ j = values[i].order,
142
+ txt;
143
+ values[i].others && (labels[j] = otherslabel || "Others");
144
+ labels[j] = paper.g.labelise(labels[j], values[i], total);
145
+ chart.labels.push(paper.set());
146
+ chart.labels[i].push(paper.g[mark](x + 5, h, 5).attr({fill: clr, stroke: "none"}));
147
+ chart.labels[i].push(txt = paper.text(x + 20, h, labels[j] || values[j]).attr(paper.g.txtattr).attr({fill: opts.legendcolor || "#000", "text-anchor": "start"}));
148
+ covers[i].label = chart.labels[i];
149
+ h += txt.getBBox().height * 1.2;
150
+ }
151
+ var bb = chart.labels.getBBox(),
152
+ tr = {
153
+ east: [0, -bb.height / 2],
154
+ west: [-bb.width - cw - 20, -bb.height / 2],
155
+ north: [-cw - bb.width / 2, -cw - bb.height - 10],
156
+ south: [- ( cw / 2 ) - bb.width / 2, ( cw / 2 ) + 10]
157
+ }[dir];
158
+ chart.labels.translate.apply(chart.labels, tr);
159
+ chart.push(chart.labels);
160
+ };
161
+ if (opts.legend) {
162
+ legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
163
+ }
164
+ chart.push(series, covers);
165
+ chart.series = series;
166
+ chart.covers = covers;
167
+ return chart;
168
+ };