Commit | Line | Data |
---|---|---|
22bc3e48 | 1 | (function (angular, $, _) { |
22bc3e48 TO |
2 | |
3 | // example: | |
4 | // scope.myAbtest = new CrmMailingAB(); | |
5 | // <crm-mailing-ab-block-mailing="{fromAddressA: 1, fromAddressB: 1}" crm-abtest="myAbtest" /> | |
1d4d0279 | 6 | var simpleDirectives = { |
ef5d18a1 TO |
7 | crmMailingAbBlockMailing: '~/crmMailingAB/joint-mailing.html', |
8 | crmMailingAbBlockSetup: '~/crmMailingAB/setup.html' | |
1d4d0279 TO |
9 | }; |
10 | _.each(simpleDirectives, function (templateUrl, directiveName) { | |
efd95528 | 11 | angular.module('crmMailingAB').directive(directiveName, function ($parse, crmMailingABCriteria) { |
1d4d0279 TO |
12 | var scopeDesc = {crmAbtest: '@'}; |
13 | scopeDesc[directiveName] = '@'; | |
14 | ||
15 | return { | |
16 | scope: scopeDesc, | |
17 | templateUrl: templateUrl, | |
18 | link: function (scope, elm, attr) { | |
19 | var model = $parse(attr.crmAbtest); | |
20 | scope.abtest = model(scope.$parent); | |
21 | scope.crmMailingConst = CRM.crmMailing; | |
22 | scope.crmMailingABCriteria = crmMailingABCriteria; | |
5d8901af | 23 | scope.ts = CRM.ts(null); |
22bc3e48 | 24 | |
1d4d0279 TO |
25 | var fieldsModel = $parse(attr[directiveName]); |
26 | scope.fields = fieldsModel(scope.$parent); | |
27 | } | |
28 | }; | |
29 | }); | |
cdb373b4 TO |
30 | }); |
31 | ||
32 | // example: <div crm-mailing-ab-slider ng-model="abtest.ab.group_percentage"></div> | |
efd95528 | 33 | angular.module('crmMailingAB').directive('crmMailingAbSlider', function () { |
cdb373b4 TO |
34 | return { |
35 | require: '?ngModel', | |
36 | scope: {}, | |
ef5d18a1 | 37 | templateUrl: '~/crmMailingAB/slider.html', |
cdb373b4 TO |
38 | link: function (scope, element, attrs, ngModel) { |
39 | var TEST_MIN = 1, TEST_MAX = 50; | |
40 | var sliders = $('.slider-test,.slider-win', element); | |
41 | var sliderTests = $('.slider-test', element); | |
42 | var sliderWin = $('.slider-win', element); | |
43 | ||
5d8901af | 44 | scope.ts = CRM.ts(null); |
cdb373b4 TO |
45 | scope.testValue = 0; |
46 | scope.winValue = 100; | |
47 | ||
48 | // set the base value (following a GUI event) | |
49 | function setValue(value) { | |
50 | value = Math.min(TEST_MAX, Math.max(TEST_MIN, value)); | |
51 | scope.$apply(function () { | |
52 | ngModel.$setViewValue(value); | |
53 | scope.testValue = value; | |
54 | scope.winValue = 100 - (2 * scope.testValue); | |
55 | sliderTests.slider('value', scope.testValue); | |
56 | sliderWin.slider('value', scope.winValue); | |
57 | }); | |
58 | } | |
59 | ||
60 | sliders.slider({ | |
61 | min: 0, | |
62 | max: 100, | |
63 | range: 'min', | |
64 | step: 1 | |
65 | }); | |
66 | sliderTests.slider({ | |
67 | slide: function slideTest(event, ui) { | |
68 | event.preventDefault(); | |
69 | setValue(ui.value); | |
70 | } | |
71 | }); | |
72 | sliderWin.slider({ | |
73 | slide: function slideWinner(event, ui) { | |
74 | event.preventDefault(); | |
75 | setValue(Math.round((100 - ui.value) / 2)); | |
76 | } | |
77 | }); | |
78 | ||
79 | ngModel.$render = function () { | |
80 | scope.testValue = ngModel.$viewValue; | |
81 | scope.winValue = 100 - (2 * scope.testValue); | |
82 | sliderTests.slider('value', scope.testValue); | |
83 | sliderWin.slider('value', scope.winValue); | |
84 | }; | |
85 | } | |
86 | }; | |
87 | }); | |
360aaa75 TO |
88 | |
89 | // FIXME: This code is long and hasn't been fully working for me, but I've moved it into a spot | |
90 | // where it at least fits in a bit better. | |
91 | ||
92 | // example: <div crm-mailing-ab-stats="{split_count: 6, criteria:'Open'}" crm-abtest="myabtest" /> | |
93 | // options (see also: Mailing.graph_stats API) | |
94 | // - split_count: int | |
95 | // - criteria: string | |
96 | // - target_date: string, date | |
97 | // - target_url: string | |
98 | angular.module('crmMailingAB').directive('crmMailingAbStats', function (crmApi, $parse, crmNow) { | |
99 | return { | |
100 | scope: { | |
101 | crmMailingAbStats: '@', | |
102 | crmAbtest: '@' | |
103 | }, | |
104 | template: '<div class="crm-mailing-ab-stats"></div>', | |
105 | link: function (scope, element, attrs) { | |
106 | var abtestModel = $parse(attrs.crmAbtest); | |
107 | var optionModel = $parse(attrs.crmMailingAbStats); | |
108 | var options = angular.extend({}, optionModel(scope.$parent), { | |
109 | criteria: 'Open', // e.g. 'Open', 'Total Unique Clicks' | |
110 | split_count: 5 | |
111 | }); | |
112 | ||
113 | scope.$watch(attrs.crmAbtest, refresh); | |
114 | function refresh() { | |
115 | var now = crmNow(); | |
116 | var abtest = abtestModel(scope.$parent); | |
117 | if (!abtest) { | |
118 | console.log('failed to draw stats - missing abtest'); | |
119 | return; | |
120 | } | |
121 | ||
122 | scope.graph_data = [ | |
123 | {}, | |
124 | {}, | |
125 | {}, | |
126 | {}, | |
127 | {} | |
128 | ]; | |
129 | var keep_cnt = 0; | |
130 | ||
131 | for (var i = 1; i <= options.split_count; i++) { | |
132 | var result = crmApi('MailingAB', 'graph_stats', { | |
133 | id: abtest.ab.id, | |
134 | target_date: abtest.ab.declare_winning_time ? abtest.ab.declare_winning_time : now, | |
135 | target_url: null, // FIXME | |
136 | criteria: options.criteria, | |
137 | split_count: options.split_count, | |
138 | split_count_select: i | |
139 | }); | |
175762a3 | 140 | /*jshint -W083 */ |
360aaa75 TO |
141 | result.then(function (data) { |
142 | var temp = 0; | |
143 | keep_cnt++; | |
144 | for (var key in data.values.A) { | |
145 | temp = key; | |
146 | } | |
147 | var t = data.values.A[temp].time.split(" "); | |
148 | var m = t[0]; | |
149 | var year = t[2]; | |
150 | var day = t[1].substr(0, t[1].length - 3); | |
f2bad133 | 151 | var t1, hur, hour, min; |
175762a3 | 152 | if (_.isEmpty(t[3])) { |
f2bad133 TO |
153 | t1 = t[4].split(":"); |
154 | hur = t1[0]; | |
360aaa75 TO |
155 | if (t[5] == "AM") { |
156 | hour = hur; | |
157 | if (hour == 12) { | |
158 | hour = 0; | |
159 | } | |
160 | } | |
161 | if (t[5] == "PM") { | |
162 | hour = parseInt(hur) + 12; | |
163 | } | |
f2bad133 | 164 | min = t1[1]; |
360aaa75 TO |
165 | } |
166 | else { | |
f2bad133 TO |
167 | t1 = t[3].split(":"); |
168 | hur = t1[0]; | |
360aaa75 TO |
169 | if (t[4] == "AM") { |
170 | hour = hur; | |
171 | if (hour == 12) { | |
172 | hour = 0; | |
173 | } | |
174 | } | |
175 | if (t[4] == "PM") { | |
176 | hour = parseInt(hur) + 12; | |
177 | } | |
f2bad133 | 178 | min = t1[1]; |
360aaa75 TO |
179 | } |
180 | var month = 0; | |
181 | switch (m) { | |
182 | case "January": | |
183 | month = 0; | |
184 | break; | |
185 | case "February": | |
186 | month = 1; | |
187 | break; | |
188 | case "March": | |
189 | month = 2; | |
190 | break; | |
191 | case "April": | |
192 | month = 3; | |
193 | break; | |
194 | case "May": | |
195 | month = 4; | |
196 | break; | |
197 | case "June": | |
198 | month = 5; | |
199 | break; | |
200 | case "July": | |
201 | month = 6; | |
202 | break; | |
203 | case "August": | |
204 | month = 7; | |
205 | break; | |
206 | case "September": | |
207 | month = 8; | |
208 | break; | |
209 | case "October": | |
210 | month = 9; | |
211 | break; | |
212 | case "November": | |
213 | month = 10; | |
214 | break; | |
215 | case "December": | |
216 | month = 11; | |
217 | break; | |
218 | ||
219 | } | |
220 | var tp = new Date(year, month, day, hour, min, 0, 0); | |
221 | scope.graph_data[temp - 1] = { | |
222 | time: tp, | |
223 | x: data.values.A[temp].count, | |
224 | y: data.values.B[temp].count | |
225 | }; | |
226 | ||
227 | if (keep_cnt == options.split_count) { | |
228 | scope.graphload = true; | |
f2bad133 | 229 | data = scope.graph_data; |
360aaa75 TO |
230 | |
231 | // set up a colour variable | |
232 | var color = d3.scale.category10(); | |
233 | ||
234 | // map one colour each to x, y and z | |
235 | // keys grabs the key value or heading of each key value pair in the json | |
236 | // but not time | |
237 | color.domain(d3.keys(data[0]).filter(function (key) { | |
238 | return key !== "time"; | |
239 | })); | |
240 | ||
241 | // create a nested series for passing to the line generator | |
242 | // it's best understood by console logging the data | |
243 | var series = color.domain().map(function (name) { | |
244 | return { | |
245 | name: name, | |
246 | values: data.map(function (d) { | |
247 | return { | |
248 | time: d.time, | |
249 | score: +d[name] | |
250 | }; | |
251 | }) | |
252 | }; | |
253 | }); | |
254 | ||
255 | // Set the dimensions of the canvas / graph | |
256 | var margin = { | |
257 | top: 30, | |
258 | right: 20, | |
259 | bottom: 40, | |
260 | left: 75 | |
261 | }, | |
262 | width = 550 - margin.left - margin.right, | |
263 | height = 350 - margin.top - margin.bottom; | |
264 | ||
265 | // Set the ranges | |
266 | //var x = d3.time.scale().range([0, width]).domain([0,10]); | |
267 | var x = d3.time.scale().range([0, width]); | |
268 | var y = d3.scale.linear().range([height, 0]); | |
269 | ||
270 | // Define the axes | |
271 | var xAxis = d3.svg.axis().scale(x) | |
272 | .orient("bottom").ticks(10); | |
273 | ||
274 | var yAxis = d3.svg.axis().scale(y) | |
275 | .orient("left").ticks(5); | |
276 | ||
277 | // Define the line | |
278 | // Note you plot the time / score pair from each key you created ealier | |
279 | var valueline = d3.svg.line() | |
280 | .x(function (d) { | |
281 | return x(d.time); | |
282 | }) | |
283 | .y(function (d) { | |
284 | return y(d.score); | |
285 | }); | |
286 | ||
287 | // Adds the svg canvas | |
288 | var svg = d3.select($('.crm-mailing-ab-stats', element)[0]) | |
289 | .append("svg") | |
290 | .attr("width", width + margin.left + margin.right) | |
291 | .attr("height", height + margin.top + margin.bottom) | |
292 | .append("g") | |
293 | .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
294 | ||
295 | // Scale the range of the data | |
296 | x.domain(d3.extent(data, function (d) { | |
297 | return d.time; | |
298 | })); | |
299 | ||
300 | // note the nested nature of this you need to dig an additional level | |
301 | y.domain([ | |
302 | d3.min(series, function (c) { | |
303 | return d3.min(c.values, function (v) { | |
304 | return v.score; | |
305 | }); | |
306 | }), | |
307 | d3.max(series, function (c) { | |
308 | return d3.max(c.values, function (v) { | |
309 | return v.score; | |
310 | }); | |
311 | }) | |
312 | ]); | |
313 | svg.append("text") // text label for the x axis | |
314 | .attr("x", width / 2) | |
315 | .attr("y", height + margin.bottom) | |
316 | .style("text-anchor", "middle") | |
317 | .text("Time"); | |
318 | ||
319 | svg.append("text") // text label for the x axis | |
320 | .style("text-anchor", "middle") | |
321 | .text(scope.winnercriteria).attr("transform",function (d) { | |
f2bad133 | 322 | return "rotate(-90)"; |
360aaa75 TO |
323 | }).attr("x", -height / 2) |
324 | .attr("y", -30); | |
325 | ||
326 | // create a variable called series and bind the date | |
327 | // for each series append a g element and class it as series for css styling | |
f2bad133 | 328 | series = svg.selectAll(".series") |
360aaa75 TO |
329 | .data(series) |
330 | .enter().append("g") | |
331 | .attr("class", "series"); | |
332 | ||
333 | // create the path for each series in the variable series i.e. x, y and z | |
334 | // pass each object called x, y nad z to the lne generator | |
335 | series.append("path") | |
336 | .attr("class", "line") | |
337 | .attr("d", function (d) { | |
338 | // console.log(d); // to see how d3 iterates through series | |
339 | return valueline(d.values); | |
340 | }) | |
341 | .style("stroke", function (d) { | |
342 | return color(d.name); | |
343 | }); | |
344 | ||
345 | // Add the X Axis | |
346 | svg.append("g") // Add the X Axis | |
347 | .attr("class", "x axis") | |
348 | .attr("transform", "translate(0," + height + ")") | |
349 | .call(xAxis) | |
350 | .selectAll("text") | |
351 | .attr("transform", function (d) { | |
352 | return "rotate(-30)"; | |
353 | }); | |
354 | ||
355 | // Add the Y Axis | |
356 | svg.append("g") // Add the Y Axis | |
357 | .attr("class", "y axis") | |
358 | .call(yAxis); | |
359 | } | |
360 | }); | |
361 | } | |
362 | } | |
363 | } // link() | |
364 | }; | |
365 | }); | |
22bc3e48 | 366 | })(angular, CRM.$, CRM._); |