Merge pull request #4696 from colemanw/CRM-15669
[civicrm-core.git] / js / angular-crmMailingAB / directives.js
CommitLineData
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._);