PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.5.3
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.5.3
1.9.5 1.9.4 1.9.3 trunk 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6 1.6.1 1.6.10 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 All 40 releases
code-profiler / static / code-profiler.js

code-profiler.js in Code Profiler – WordPress Performance Profiling and Debugging Made Easy 1.5.3, at static/code-profiler.js

817 lines 20.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 +=====================================================================+
3 | ____ _ ____ __ _ _ |
4 | / ___|___ __| | ___ | _ \ _ __ ___ / _(_) | ___ _ __ |
5 | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| |
6 | | |__| (_) | (_| | __/ | __/| | | (_) | _| | | __/ | |
7 | \____\___/ \__,_|\___| |_| |_| \___/|_| |_|_|\___|_| |
8 | |
9 | (c) Jerome Bruandet ~ https://code-profiler.com/ |
10 +=====================================================================+
11 */
12
13 // =====================================================================
14 // Generic stuff
15
16 // TipTip tooltip
17 jQuery( document ).ready(function( $ ) {
18 'use strict';
19 $('.code-profiler-tip').tipTip( {
20 'attribute': 'data-tip',
21 'fadeIn': 50,
22 'fadeOut': 50,
23 'delay': 100,
24 'maxWidth' : '350px'
25 });
26
27 // We remove the '&action=delete_profiles...' query string because
28 // if the user reloaded the page that would throw an error:
29 var url = window.location.href;
30 window.history.replaceState({},
31 document.title,
32 url.replace( /&action=delete_profiles&.+/, '')
33 );
34
35 });
36
37 // Append/clear the search query to/from the query string
38 function cpjs_search_query() {
39
40 'use strict';
41 var query;
42 if ( jQuery('#search_id-search-input').val().length > 0 ) {
43 query = '&s=' + jQuery('#search_id-search-input').val();
44 // Case sensitivity
45 if ( jQuery('#case-search-input').is(':checked') === true ) {
46 query += '&c=1';
47 }
48 jQuery('#profile-form').attr('action', jQuery('#profile-form').attr('action')+ query );
49
50 } else {
51 jQuery('#profile-form').attr('action','');
52 var url = window.location.href;
53 window.history.replaceState({},
54 document.title,
55 url.replace( /&s=[^&$]+/, '')
56 );
57 var url = window.location.href;
58 window.history.replaceState({},
59 document.title,
60 url.replace( /&c=1/, '')
61 );
62 }
63 }
64
65 // =====================================================================
66 // Summary page
67
68 function cpjs_front_or_backend( item ) {
69 'use strict';
70 // Enable/disable frontend/backend select box
71 // depending on user's choice
72 if ( item == 1 ) {
73 jQuery('#p-frontend').show();
74 jQuery('#p-backend').hide();
75 jQuery('#p-custom').hide();
76 jQuery('#id-frontend').focus();
77 jQuery('#user-unauthenticated').prop('disabled', false);
78 } else if ( item == 2 ) {
79 jQuery('#p-backend').show();
80 jQuery('#p-frontend').hide();
81 jQuery('#p-custom').hide();
82 jQuery('#id-backend').focus();
83 jQuery('#user-unauthenticated').prop('disabled', true);
84 jQuery('#user-authenticated').prop('checked', true);
85 jQuery('#user-name').prop('disabled', false);
86 } else {
87 jQuery('#p-custom').show();
88 jQuery('#p-frontend').hide();
89 jQuery('#p-backend').hide();
90 jQuery('#id-custom').focus();
91 jQuery('#user-unauthenticated').prop('disabled', false);
92 }
93 }
94
95 function cpjs_authenticated( id ) {
96 if ( id == 1 ) {
97 jQuery('#user-name').prop('disabled', false);
98 jQuery('#user-name').focus();
99 } else {
100 jQuery('#user-name').prop('disabled', true);
101 }
102 }
103
104 function cpjs_show_adv_settings() {
105 jQuery('#cp-advanced-settings').slideDown();
106 jQuery('#button-adv-settings').prop('disabled', true);
107 }
108
109 function cpjs_get_post( id ) {
110 if ( id == 1 ) {
111 jQuery('#post-value').prop('disabled', false);
112 jQuery('#post-value').focus();
113 } else {
114 jQuery('#post-value').prop('disabled', true);
115 }
116 }
117
118 // =====================================================================
119 // Log page: filter and delete the log.
120
121 function cpjs_filter_log() {
122 'use strict';
123 // Create bitmask
124 var bitmask = 0;
125 if ( document.cplogform.info.checked == true ) { bitmask += 1; }
126 if ( document.cplogform.warn.checked == true ) { bitmask += 2; }
127 if ( document.cplogform.error.checked == true ) { bitmask += 4; }
128
129 // Clear the textarea
130 document.cplogform.cptxtlog.value = '';
131
132 // Browser through our array and return only selected verbosity
133 var cp_count = 0;
134 var i = 0;
135 for ( i = 0; i < cplog_array.length; ++i ) {
136 var line = decodeURIComponent( cplog_array[i] );
137 var line_array = line.split('~~', 2 );
138 if ( line_array[0] & bitmask ) {
139 document.cplogform.cptxtlog.value += line_array[1];
140 ++cp_count;
141 }
142 }
143 if ( cp_count == 0 ) {
144 document.cplogform.cptxtlog.value = '\n > ' + cpi18n.empty_log;
145 }
146 }
147
148 function cpjs_delete_log() {
149 'use strict';
150 if ( confirm( cpi18n.delete_log ) ) {
151 return true;
152 }
153 return false;
154 }
155 // =====================================================================
156 // AJAX call to start the profiler.
157
158 function cpjs_start_profiler() {
159 'use strict';
160 // Get the nonce
161 var cp_nonce = jQuery('#cp_nonce').val();
162 if ( cp_nonce == '') {
163 alert( cpi18n.missing_nonce );
164 return;
165 }
166
167 // Get the scan type (frontend/backend)
168 var post;
169 var where = jQuery('input[name="where"]:checked').val();
170 if ( where == 'frontend') {
171 post = jQuery('#id-frontend').val();
172
173 } else if ( where == 'backend') {
174 post = jQuery('#id-backend').val();
175
176 } else if ( where == 'custom') {
177 post = jQuery('#id-custom').val().trim(); // Trim user input
178
179 } else {
180 alert( cpi18n.missing_frontbackend );
181 return;
182 }
183 if ( post == '') {
184 alert( cpi18n.missing_post );
185 return;
186 }
187
188 // Get the profile's name
189 var profile = jQuery('input[name="profile"]').val();
190 if ( profile == '') {
191 alert( cpi18n.missing_profilename );
192 jQuery('input[name="profile"]').focus();
193 return;
194 }
195
196 var user = jQuery('input[name="user"]:checked').val();
197 if ( user == '') {
198 alert( cpi18n.missing_userauth );
199 return;
200 }
201
202 var username = jQuery('#user-name').val().trim(); // Trim user input
203 if ( user == 'authenticated') {
204 if ( username == '') {
205 alert( cpi18n.missing_username );
206 jQuery('#user-name').focus();
207 return;
208 }
209 }
210
211 var ua = jQuery('#ua-id').val();
212 if ( ua == 'undefined') {
213 ua = 'FireFox';
214 }
215
216 // GET or POST
217 var method = jQuery('input[name="method"]:checked').val();
218 var payload = '';
219 if ( method == 'post') {
220 // Payload
221 payload = jQuery('#post-value').val();
222 } else {
223 method = 'get';
224 }
225
226 var cookies = jQuery('#cp-cookies').val();
227
228 // Change buttons status and add animated image with status message
229 jQuery('#start-profile').prop('disabled', true);
230 jQuery('#cp-progress-div').slideDown();
231 jQuery('#progress-gif').slideDown();
232 jQuery('#progress-text').slideDown();
233 jQuery('#code-profiler-error').slideUp();
234 jQuery('#cp-span-progress').css('width', '30%');
235
236 var data = {
237 'action': 'codeprofiler_start_profiler',
238 'cp_nonce': cp_nonce,
239 'where': where,
240 'post': post,
241 'profile': profile,
242 'user': user,
243 'username': username,
244 'cookies': cookies,
245 'method': method,
246 'payload': payload,
247 'ua': ua
248 };
249 // Send the request via AJAX
250 jQuery.ajax( {
251 type: 'POST',
252 url: ajaxurl,
253 data: data,
254 dataType: 'json',
255 success: function( response ) {
256
257 if (typeof response === 'undefined') {
258 cpjs_start_error('<p>'+ cpi18n.unknown_error +'</p>');
259 return;
260 }
261
262 if ( response.status != 'success') {
263 cpjs_start_error('<p>'+ response.message +'</p>');
264 return;
265 }
266
267 // ------------------------------------------------------------
268 // Inform the user we're moving to step 2
269 jQuery('#cp-span-progress').css('width', '70%');
270 jQuery('#progress-text').html( cpi18n.preparing_report );
271
272 data['action'] = 'codeprofiler_prepare_report';
273 data['microtime'] = response.microtime;
274 jQuery.ajax( {
275 type: 'POST',
276 url: ajaxurl,
277 data: data,
278 dataType: 'json',
279 success: function( response ) {
280
281 if (typeof response === 'undefined') {
282 cpjs_start_error('<p>'+ cpi18n.unknown_error +'</p>');
283 return;
284 }
285
286 if ( response.status != 'success') {
287 cpjs_start_error('<p>'+ response.message +'</p>');
288 return;
289 }
290 var cp_uri;
291 if (response.cp_profile !== 'undefined' && response.cp_profile != '') {
292 cp_uri = '&cptab=profiles_list&action=view_profile&id=' + response.cp_profile + '&section=1';
293 } else {
294 cp_uri = '&cptab=profiles_list';
295 }
296
297 // All good, redirect to the Profiles List tab
298 jQuery('#cp-span-progress').css('width', '100%');
299 window.location.href = window.location.href + cp_uri;
300 return;
301
302 },
303
304 // Display non-200 HTTP response
305 error: function( xhr, status, err ) {
306 if ( err != '') {
307 if ( xhr.status != 0 ) {
308 var message = cpi18n.http_error +' '+ xhr.status +' '+ err;
309 // Timeout
310 if ( xhr.status == 503 || xhr.status == 504 ) {
311 cpjs_start_error('<p>'+ message +'<br />'+ cpi18n.timeout_error +'.</p>');
312 // Redirections, not found
313 } else if ( xhr.status == 301 || xhr.status == 302 || xhr.status == 404 ) {
314 cpjs_start_error('<p>'+ message +'<br />'+ cpi18n.notfound_error +'.</p>');
315 // Forbidden
316 } else if ( xhr.status == 403 ) {
317 cpjs_start_error('<p>'+ message +'<br />'+ cpi18n.forbidden_error +'.</p>');
318 // Internal error
319 } else if ( xhr.status == 500 ) {
320 cpjs_start_error('<p>'+ message +'<br />'+ cpi18n.internal_error +'.<br />'+ cpi18n.timeout_error +'.</p>');
321 } else {
322 cpjs_start_error('<p>'+ cpi18n.http_error +' '+ xhr.status +' '+ err +'.</p>');
323 }
324 return;
325
326 } else {
327 cpjs_start_error('<p>'+ cpi18n.unknown_error +' '+ err +'.</p>');
328 return;
329 }
330 }
331 return;
332 }
333 });
334 // ------------------------------------------------------------
335 },
336
337 // Display non-200 HTTP response
338 error: function( xhr, status, err ) {
339 if ( err != '') {
340 if ( xhr.status != 0 ) {
341 var message = cpi18n.http_error +' '+ xhr.status +' '+ err;
342 // Timeout
343 if ( xhr.status == 503 || xhr.status == 504 ) {
344 cpjs_start_error('<p>'+ message +'<br />'+ cpi18n.timeout_error +'.</p>');
345 // Redirections, not found
346 } else if ( xhr.status == 301 || xhr.status == 302 || xhr.status == 404 ) {
347 cpjs_start_error('<p>'+ message +'<br />'+ cpi18n.notfound_error +'.</p>');
348 // Forbidden
349 } else if ( xhr.status == 403 ) {
350 cpjs_start_error('<p>'+ message +'<br />'+ cpi18n.forbidden_error +'.</p>');
351 // Internal error
352 } else if ( xhr.status == 500 ) {
353 cpjs_start_error('<p>'+ message +'<br />'+ cpi18n.internal_error +'.<br />'+ cpi18n.timeout_error +'.</p>');
354 } else {
355 cpjs_start_error('<p>'+ cpi18n.http_error +' '+ xhr.status +' '+ err +'.</p>');
356 }
357 return;
358
359 } else {
360 cpjs_start_error('<p>'+ cpi18n.unknown_error +' '+ err +'.</p>');
361 return;
362 }
363 }
364 return;
365 }
366 });
367 }
368
369 // Display error
370 function cpjs_start_error( error ) {
371 'use strict';
372 jQuery('#start-profile').prop('disabled', false);
373 jQuery('#progress-gif').slideUp();
374 jQuery('#progress-text').slideUp();
375 jQuery('#cp-span-progress').css('width', '0%');
376 jQuery('#code-profiler-error').html( error );
377 jQuery('#code-profiler-error').slideDown();
378 jQuery('#cp-progress-div').slideUp();
379
380 }
381
382 // =====================================================================
383 // Profiles List page.
384
385 function cpjs_delete_profile() {
386 'use strict';
387 if ( confirm( cpi18n.delete_profile ) ) {
388 return true;
389 }
390 return false;
391 }
392
393 // Hide/show the edit box and buttons when renaming a profile.
394 function cpjs_toggle_name( row ) {
395 if ( jQuery('#profile_div_'+ row).css('display') == 'none') {
396 jQuery('#profile_name_'+ row).hide();
397 jQuery('#profile_div_'+ row).show();
398 jQuery('#edit-'+ row).val( jQuery('#profile_name_'+ row).html() );
399 jQuery('#edit-'+ row).focus();
400 } else {
401 jQuery('#profile_div_'+ row).hide();
402 jQuery('#profile_name_'+ row).show();
403 }
404 }
405
406 // Edit the profile name via AJAX endpoint.
407 function cpjs_edit_name( profile, id, cp_nonce, row ) {
408
409 'use strict';
410
411 if ( cp_nonce == '') {
412 alert( cpi18n.missing_nonce );
413 return;
414 }
415 if ( profile == '') {
416 alert( cpi18n.missing_profileid );
417 return;
418 }
419
420 var new_name = jQuery('#edit-'+ id).val().trim();
421 if ( new_name == '') {
422 alert( cpi18n.missing_profilename );
423 return;
424 }
425
426 jQuery('#profile_spinner_'+ row).addClass('is-active');
427
428 var data = {
429 'action': 'codeprofiler_rename',
430 'cp_nonce': cp_nonce,
431 'new_name': new_name,
432 'profile': profile,
433 };
434
435 // Send the request via AJAX
436 jQuery.ajax( {
437 type: 'POST',
438 url: ajaxurl,
439 data: data,
440 dataType: 'json',
441 success: function( response ) {
442 jQuery('#profile_spinner_'+ row ).removeClass('is-active');
443
444 if (typeof response === 'undefined') {
445 alert( cpi18n.unknown_error );
446 return;
447 }
448
449 if ( response.status != 'success') {
450 alert( response.message );
451 return;
452 }
453
454 // Success: update the name in the table
455 jQuery('#profile_name_'+ id).html( response.newname );
456 cpjs_toggle_name(row);
457 },
458 // Display non-200 HTTP response
459 error: function( xhr, status, err ) {
460 jQuery('#profile_spinner_'+ row ).removeClass('is-active');
461 if ( err != '') {
462 if ( xhr.status != 0 ) {
463 var message = cpi18n.http_error +' '+ xhr.status +' '+ err;
464 // Timeout
465 if ( xhr.status == 503 || xhr.status == 504 ) {
466 alert( message +'<br />'+ cpi18n.timeout_error );
467 // Redirections, not found
468 } else if ( xhr.status == 301 || xhr.status == 302 || xhr.status == 404 ) {
469 alert( message +'<br />'+ cpi18n.notfound_error );
470 // Forbidden
471 } else if ( xhr.status == 403 ) {
472 alert( message +'<br />'+ cpi18n.forbidden_error );
473 // Internal error
474 } else if ( xhr.status == 500 ) {
475 alert( message +'<br />'+ cpi18n.internal_error +'.<br />'+ cpi18n.timeout_error );
476 } else {
477 alert( cpi18n.http_error +' '+ xhr.status +' '+ err );
478 }
479 return;
480
481 } else {
482 alert( cpi18n.unknown_error +' '+ err );
483 return;
484 }
485 }
486 return;
487 }
488 });
489 }
490
491 // =====================================================================
492 // Plugins/Theme chart.
493
494 function cpjs_plugins_chart( caxis, clabel, cdata, ctotal_time ) {
495 'use strict';
496 // Switch between vertical and horizontal bars
497 var _indexAxis = caxis;
498 document.getElementById('htov').onclick = function() {
499 if ( _indexAxis == 'x') {
500 _indexAxis = 'y';
501 } else {
502 _indexAxis = 'x';
503 }
504 myChart.destroy();
505 myChart = new Chart(ctx, {
506 type: 'bar',
507 options: {
508 animation: {
509 duration: 1000,
510 onComplete: function() {
511 document.getElementById('download-png-img').href = myChart.toBase64Image();
512 }
513 },
514 indexAxis: _indexAxis,
515 plugins: chartPlugins
516 },
517 plugins:[ plugin],
518 data: chartData
519 });
520 };
521
522 var ctx = document.getElementById('myChart').getContext('2d');
523
524 var chartData = {
525 labels: clabel,
526 datasets: [{
527 label: cpi18n.exec_sec_plugins,
528 backgroundColor: 'rgba(204, 0, 0, .5)',
529 borderColor: '#c00',
530 borderWidth: 2,
531 hoverBackgroundColor: '#B45252',
532 data: cdata
533 }]
534 };
535
536 const total_time = ctotal_time;
537 const footer = (tooltipItems) => {
538 var sum;
539 tooltipItems.forEach(function(tooltipItem) {
540 if ( _indexAxis == 'x') {
541 sum = Math.round( ( tooltipItem.parsed.y / total_time ) * 100 );
542 } else {
543 sum = Math.round( ( tooltipItem.parsed.x / total_time ) * 100 );
544 }
545 });
546 return sum + cpi18n.pc_plugins;
547 };
548
549 var chartPlugins = {
550 title: {
551 display: true,
552 text: cpi18n.exec_tot_plugins_1 +' (' + cpi18n.chart_total +' '+ ctotal_time +')'
553 },
554 legend: {
555 display: false
556 },
557 tooltip: {
558 borderWidth: 1,
559 borderColor: '#666',
560 displayColors: false,
561 backgroundColor: '#F5F5B5',
562 titleColor:'#666',
563 padding: 8,
564 footerColor: '#666',
565 callbacks: {
566 footer: footer,
567 labelTextColor: function(context) {
568 return '#543453';
569 }
570 }
571 }
572 }
573
574 // We want a background colour for the downloaded PNG file
575 const plugin = {
576 id: 'custom_canvas_background_color',
577 beforeDraw: (chart) => {
578 const ctx = chart.canvas.getContext('2d');
579 ctx.save();
580 ctx.globalCompositeOperation = 'destination-over';
581 ctx.fillStyle = '#f0f0f1',
582 ctx.fillRect(0, 0, chart.width, chart.height);
583 ctx.restore();
584 }
585 }
586
587 var myChart = new Chart(ctx, {
588 type: 'bar',
589 data: chartData,
590 options: {
591 animation: {
592 duration: 700,
593 onComplete: function() {
594 jQuery('#cp-footer-buttons').slideDown(200);
595 document.getElementById('download-png-img').href = myChart.toBase64Image();
596 }
597 },
598 indexAxis: _indexAxis,
599 plugins: chartPlugins
600 },
601 plugins:[plugin]
602 });
603 }
604
605 // =====================================================================
606 // File I/O chart.
607
608 function cpjs_iostats_chart( caxis, clabel, cdata, ctotal_calls ) {
609 'use strict';
610 // Switch between vertical and horizontal bars
611 var _indexAxis = caxis;
612 document.getElementById('htov').onclick = function() {
613 if ( _indexAxis == 'x') {
614 _indexAxis = 'y';
615 } else {
616 _indexAxis = 'x';
617 }
618 myChart.destroy();
619 myChart = new Chart(ctx, {
620 type: 'line',
621 options: {
622 animation: {
623 duration: 1000,
624 onComplete: function() {
625 document.getElementById('download-png-img').href = myChart.toBase64Image();
626 }
627 },
628 indexAxis: _indexAxis,
629 plugins: chartPlugins
630 },
631 plugins:[ plugin],
632 data: chartData
633 });
634 };
635
636 var ctx = document.getElementById('myChart').getContext('2d');
637
638 var chartData = {
639 labels: clabel,
640 datasets: [{
641 label: cpi18n.iolist_total_calls,
642 backgroundColor: 'rgba(34, 113, 177, .5)',
643 borderColor: '#2271B1',
644 borderWidth: 2,
645 hoverBackgroundColor: '#FF0000',
646 data: cdata,
647 fill: 'origin',
648 radius: 6,
649 pointBackgroundColor: '#B45252'
650 }]
651 };
652 var chartPlugins = {
653 title: {
654 display: true,
655 text: cpi18n.io_calls +' (' + cpi18n.chart_total +' '+ ctotal_calls +')'
656 },
657 legend: {
658 display: false
659 },
660 tooltip: {
661 borderWidth: 1,
662 borderColor: '#666',
663 displayColors: false,
664 backgroundColor: '#F5F5B5',
665 titleColor:'#666',
666 padding: 8,
667 footerColor: '#666',
668 callbacks: {
669 labelTextColor: function(context) {
670 return '#543453';
671 }
672 }
673 }
674 }
675 // We want a background colour for the downloaded PNG file
676 const plugin = {
677 id: 'custom_canvas_background_color',
678 beforeDraw: (chart) => {
679 const ctx = chart.canvas.getContext('2d');
680 ctx.save();
681 ctx.globalCompositeOperation = 'destination-over';
682 ctx.fillStyle = '#f0f0f1',
683 ctx.fillRect(0, 0, chart.width, chart.height);
684 ctx.restore();
685 }
686 }
687
688 var myChart = new Chart(ctx, {
689 type: 'line',
690 data: chartData,
691 options: {
692 animation: {
693 duration: 700,
694 onComplete: function() {
695 jQuery('#cp-footer-buttons').slideDown(200);
696 document.getElementById('download-png-img').href = myChart.toBase64Image();
697 }
698 },
699 indexAxis: _indexAxis,
700 plugins: chartPlugins
701 },
702 plugins:[plugin]
703 });
704
705 }
706 // =====================================================================
707 // Disk I/O chart.
708
709 function cpjs_diskio_chart( caxis, clabel, cdata ) {
710 'use strict';
711 // Switch between vertical and horizontal bars
712 var _indexAxis = caxis;
713 document.getElementById('htov').onclick = function() {
714 if ( _indexAxis == 'x') {
715 _indexAxis = 'y';
716 } else {
717 _indexAxis = 'x';
718 }
719 myChart.destroy();
720 myChart = new Chart(ctx, {
721 type: 'line',
722 options: {
723 animation: {
724 duration: 1000,
725 onComplete: function() {
726 document.getElementById('download-png-img').href = myChart.toBase64Image();
727 }
728 },
729 indexAxis: _indexAxis,
730 plugins: chartPlugins
731 },
732 plugins:[ plugin],
733 data: chartData
734 });
735 };
736 var ctx = document.getElementById('myChart').getContext('2d');
737
738 var chartData = {
739 labels: clabel,
740 datasets: [{
741 label: cpi18n.disk_io_bytes,
742 backgroundColor: 'rgba(143, 240, 164, .5)',
743 borderColor: '#41B141',
744 borderWidth: 2,
745 hoverBackgroundColor: '#FF0000',
746 data: cdata,
747 fill: 'origin',
748 radius: 6,
749 pointBackgroundColor: '#B45252'
750 }]
751 };
752 var chartPlugins = {
753 title: {
754 display: true,
755 text: cpi18n.disk_io_title,
756 },
757 legend: {
758 display: false
759 },
760 tooltip: {
761 borderWidth: 1,
762 borderColor: '#666',
763 displayColors: false,
764 backgroundColor: '#F5F5B5',
765 titleColor:'#666',
766 padding: 8,
767 footerColor: '#666',
768 callbacks: {
769 labelTextColor: function(context) {
770 return '#543453';
771 }
772 }
773 }
774 }
775 // We want a background colour for the downloaded PNG file
776 const plugin = {
777 id: 'custom_canvas_background_color',
778 beforeDraw: (chart) => {
779 const ctx = chart.canvas.getContext('2d');
780 ctx.save();
781 ctx.globalCompositeOperation = 'destination-over';
782 ctx.fillStyle = '#f0f0f1',
783 ctx.fillRect(0, 0, chart.width, chart.height);
784 ctx.restore();
785 }
786 }
787
788 var myChart = new Chart(ctx, {
789 type: 'line',
790 data: chartData,
791 options: {
792 animation: {
793 duration: 700,
794 onComplete: function() {
795 jQuery('#cp-footer-buttons').slideDown(200);
796 document.getElementById('download-png-img').href = myChart.toBase64Image();
797 }
798 },
799 indexAxis: _indexAxis,
800 plugins: chartPlugins
801 },
802 plugins:[plugin]
803 });
804 }
805
806 // =====================================================================
807 // Settings page
808
809 function cpjs_copy_textarea( id ) {
810 document.getElementById(id).select();
811 document.execCommand('copy');
812 alert( cpi18n.text_copied );
813 }
814
815 // =====================================================================
816 // EOF
817