PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.6.3
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.6.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.6.3, at static/code-profiler.js

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