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