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

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