Bug: Live query chart always zero
[phpmyadmin/tyronm.git] / js / tbl_zoom_plot.js
blobb8c4259640e066cb86faa44c908fc107ab069687
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  ** @fileoverview JavaScript functions used on tbl_select.php
4  **
5  ** @requires    jQuery
6  ** @requires    js/functions.js
7  **/
10 /**
11  **  Display Help/Info
12  **/
13 function displayHelp() {
14     var msgbox = PMA_ajaxShowMessage(PMA_messages['strDisplayHelp'],10000);
15     msgbox.click(function() {
16         PMA_ajaxRemoveMessage(msgbox);
17     });
20 /**
21  ** Extend the array object for max function
22  ** @param array
23  **/
24 Array.max = function (array) {
25     return Math.max.apply( Math, array );
28 /**
29  ** Extend the array object for min function
30  ** @param array
31  **/
32 Array.min = function (array) {
33     return Math.min.apply( Math, array );
36 /**
37  ** Checks if a string contains only numeric value
38  ** @param n: String (to be checked) 
39  **/
40 function isNumeric(n) {
41     return !isNaN(parseFloat(n)) && isFinite(n);
44 /**
45  ** Checks if an object is empty
46  ** @param n: Object (to be checked) 
47  **/
48 function isEmpty(obj) {
49     var name;
50     for (name in obj) {
51         return false;
52     }
53     return true;
56 /**
57  ** Converts a timestamp into the format of its field type
58  ** @param val  Integer Timestamp
59  ** @param type String  Field type(datetime/timestamp/time/date)
60  **/
61 function getDate(val,type) {
62     if(type.toString().search(/datetime/i) != -1 || type.toString().search(/timestamp/i) != -1) {
63     return Highcharts.dateFormat('%Y-%m-%e %H:%M:%S', val)
64     }    
65     else if(type.toString().search(/time/i) != -1) {
66         return Highcharts.dateFormat('%H:%M:%S', val)
67     }    
68     else if (type.toString().search(/date/i) != -1) {
69         return Highcharts.dateFormat('%Y-%m-%e', val)
70     }    
73 /**
74  ** Converts a date/time into timestamp
75  ** @param val  String Date
76  ** @param type Sring  Field type(datetime/timestamp/time/date)
77  **/
78 function getTimeStamp(val,type) {
79     if(type.toString().search(/datetime/i) != -1 || type.toString().search(/timestamp/i) != -1) {
80     return getDateFromFormat(val,'yyyy-MM-dd HH:mm:ss', val)
81     }    
82     else if(type.toString().search(/time/i) != -1) {
83     return getDateFromFormat('1970-01-01 ' + val,'yyyy-MM-dd HH:mm:ss')
84     }    
85     else if (type.toString().search(/date/i) != -1) {
86     return getDateFromFormat(val,'yyyy-MM-dd')
87     }    
90 /**
91  ** Classifies the field type into numeric,timeseries or text
92  ** @param field: field type (as in database structure)
93  **/ 
94 function getType(field) {
95     if(field.toString().search(/int/i) != -1 || field.toString().search(/decimal/i) != -1 || field.toString().search(/year/i) != -1)
96         return 'numeric';
97     else if(field.toString().search(/time/i) != -1 || field.toString().search(/date/i) != -1)
98         return 'time';
99     else
100         return 'text';
102 /** 
103  ** Converts a categorical array into numeric array
104  ** @param array categorical values array
105  **/
106 function getCord(arr) {
107     var newCord = new Array();
108     var original = $.extend(true,[],arr);
109     var arr = jQuery.unique(arr).sort();
110     $.each(original, function(index,value) {
111         newCord.push(jQuery.inArray(value,arr));
112     });
113     return [newCord,arr,original];
117  ** Scrolls the view to the display section
118  **/
119 function scrollToChart() {
120    var x = $('#dataDisplay').offset().top - 100; // 100 provides buffer in viewport
121    $('html,body').animate({scrollTop: x}, 500);
125  ** Handlers for panning feature
126  **/
127 function includePan(currentChart) {
128     var mouseDown;
129     var lastX;
130     var lastY;
131     var chartWidth = $('#resizer').width() - 3;
132     var chartHeight = $('#resizer').height() - 20;
133     $('#querychart').mousedown(function() {
134         mouseDown = 1;
135     });
137     $('#querychart').mouseup(function() {
138         mouseDown = 0;
139     });
140     $('#querychart').mousemove(function(e) {
141         if (mouseDown == 1) {
142             if (e.pageX > lastX) {
143         var xExtremes = currentChart.xAxis[0].getExtremes();
144                 var diff = (e.pageX - lastX) * (xExtremes.max - xExtremes.min) / chartWidth;
145                 currentChart.xAxis[0].setExtremes(xExtremes.min - diff, xExtremes.max - diff);
146             }
147             else if (e.pageX < lastX) {
148         var xExtremes = currentChart.xAxis[0].getExtremes();
149                 var diff = (lastX - e.pageX) * (xExtremes.max - xExtremes.min) / chartWidth;
150                 currentChart.xAxis[0].setExtremes(xExtremes.min + diff, xExtremes.max + diff);
151             }
153             if (e.pageY > lastY) {
154         var yExtremes = currentChart.yAxis[0].getExtremes();
155                 var ydiff = 1.0 * (e.pageY - lastY) * (yExtremes.max - yExtremes.min) / chartHeight;
156                 currentChart.yAxis[0].setExtremes(yExtremes.min + ydiff, yExtremes.max + ydiff);
157             }
158             else if (e.pageY < lastY) {
159         var yExtremes = currentChart.yAxis[0].getExtremes();
160                 var ydiff = 1.0 * (lastY - e.pageY) * (yExtremes.max - yExtremes.min) / chartHeight;
161                 currentChart.yAxis[0].setExtremes(yExtremes.min - ydiff, yExtremes.max - ydiff);
162             }
163         }
164         lastX = e.pageX;
165         lastY = e.pageY;
166     });
169 $(document).ready(function() {
171    /**
172     ** Set a parameter for all Ajax queries made on this page.  Don't let the
173     ** web server serve cached pages
174     **/
175     $.ajaxSetup({
176         cache: 'false'
177     });
179     var cursorMode = ($("input[name='mode']:checked").val() == 'edit') ? 'crosshair' : 'pointer'; 
180     var currentChart = null;
181     var currentData = null;
182     var xLabel = $('#tableid_0').val();
183     var yLabel = $('#tableid_1').val();
184     var xType = $('#types_0').val();
185     var yType = $('#types_1').val();
186     var dataLabel = $('#dataLabel').val();
187     var lastX;
188     var lastY;
189     var zoomRatio = 1;
192     // Get query result 
193     var data = jQuery.parseJSON($('#querydata').html());
195     /**
196      ** Input form submit on field change
197      **/
198     $('#tableid_0').change(function() {
199           $('#zoom_search_form').submit();
200     })
202     $('#tableid_1').change(function() {
203           $('#zoom_search_form').submit();
204     })
206     $('#tableid_2').change(function() {
207           $('#zoom_search_form').submit();
208     })
210     $('#tableid_3').change(function() {
211           $('#zoom_search_form').submit();
212     })
214     /**
215      * Input form validation
216      **/ 
217     $('#inputFormSubmitId').click(function() {
218         if ($('#tableid_0').get(0).selectedIndex == 0 || $('#tableid_1').get(0).selectedIndex == 0)         
219         PMA_ajaxShowMessage(PMA_messages['strInputNull']);
220     else if (xLabel == yLabel) 
221             PMA_ajaxShowMessage(PMA_messages['strSameInputs']);
222     });
224     /**
225       ** Prepare a div containing a link, otherwise it's incorrectly displayed 
226       ** after a couple of clicks
227       **/
228     $('<div id="togglesearchformdiv"><a id="togglesearchformlink"></a></div>')
229     .insertAfter('#zoom_search_form')
230     // don't show it until we have results on-screen
231     .hide();
233     $('#togglesearchformlink')
234         .html(PMA_messages['strShowSearchCriteria'])
235         .bind('click', function() {
236             var $link = $(this);
237             $('#zoom_search_form').slideToggle();
238             if ($link.text() == PMA_messages['strHideSearchCriteria']) {
239                 $link.text(PMA_messages['strShowSearchCriteria']);
240             } else {
241                 $link.text(PMA_messages['strHideSearchCriteria']);
242             }
243          // avoid default click action
244         return false;
245      });
246     
247     /** 
248      ** Set dialog properties for the data display form
249      **/
250     $("#dataDisplay").dialog({
251         autoOpen: false,
252     title: 'Data point content',
253         modal: false, //false otherwise other dialogues like timepicker may not function properly
254         height: $('#dataDisplay').height() + 80,
255         width: $('#dataDisplay').width() + 80
256     });
258     /*
259      * Handle submit of zoom_display_form 
260      */
261      
262     $("#submitForm").click(function(event) {
263     
264         //Prevent default submission of form
265         event.preventDefault();
266     
267     //Find changed values by comparing form values with selectedRow Object
268     var newValues = new Array();//Stores the values changed from original
269         var it = 4;
270         var xChange = false;
271         var yChange = false;
272     for (key in selectedRow) {
273         if (key != 'where_clause'){
274         var oldVal = selectedRow[key];
275         var newVal = ($('#fields_null_id_' + it).attr('checked')) ? null : $('#fieldID_' + it).val();
276         if (oldVal != newVal){
277             selectedRow[key] = newVal;
278             newValues[key] = newVal;
279             if(key == xLabel) {
280             xChange = true;
281                data[currentData][xLabel] = newVal;
282             }
283             else if(key == yLabel) {
284             yChange = true;
285                data[currentData][yLabel] = newVal;
286             }
287         }
288         }
289         it++    
290     }//End data update
291         
292     //Update the chart series and replot
293         if (xChange || yChange) {
294         var newSeries = new Array();
295         newSeries[0] = new Object();
296             newSeries[0].marker = {
297                 symbol: 'circle'
298             };
299         //Logic similar to plot generation, replot only if xAxis changes or yAxis changes. Code includes a lot of checks so as to replot only when necessary
300             if(xChange) {
301               xCord[currentData] = selectedRow[xLabel];
302         if(xType == 'numeric') {
303             currentChart.series[0].data[currentData].update({ x : selectedRow[xLabel] });
304             currentChart.xAxis[0].setExtremes(Array.min(xCord) - 6,Array.max(xCord) + 6);
305                 }
306         else if(xType == 'time') {
307             currentChart.series[0].data[currentData].update({ x : getTimeStamp(selectedRow[xLabel],$('#types_0').val())});
308         }
309         else {
310             var tempX = getCord(xCord);
311             var tempY = getCord(yCord);
312             var i = 0;
313                 newSeries[0].data = new Array();
314             xCord = tempX[2];
315             yCord = tempY[2];
317                 $.each(data,function(key,value) {
318                         if(yType != 'text')
319                  newSeries[0].data.push({ name: value[dataLabel], x: tempX[0][i], y: value[yLabel], marker: {fillColor: colorCodes[i % 8]} , id: i } );
320             else
321                             newSeries[0].data.push({ name: value[dataLabel], x: tempX[0][i], y: tempY[0][i], marker: {fillColor: colorCodes[i % 8]} , id: i } );
322                     i++;   
323                     });
324             currentSettings.xAxis.labels = { formatter : function() {
325                 if(tempX[1][this.value] && tempX[1][this.value].length > 10)
326                     return tempX[1][this.value].substring(0,10)
327                 else 
328                     return tempX[1][this.value];    
329                         }
330                     }
331              currentSettings.series = newSeries;
332                     currentChart = PMA_createChart(currentSettings);
333         }
335         }
336             if(yChange) {
338               yCord[currentData] = selectedRow[yLabel];
339         if(yType == 'numeric') {
340             currentChart.series[0].data[currentData].update({ y : selectedRow[yLabel] });
341             currentChart.yAxis[0].setExtremes(Array.min(yCord) - 6,Array.max(yCord) + 6);
342                 }
343         else if(yType =='time') {
344             currentChart.series[0].data[currentData].update({ y : getTimeStamp(selectedRow[yLabel],$('#types_1').val())});
345         }
346         else {
347             var tempX = getCord(xCord);
348             var tempY = getCord(yCord);
349             var i = 0;
350                 newSeries[0].data = new Array();
351             xCord = tempX[2];
352             yCord = tempY[2];
354                 $.each(data,function(key,value) {
355             if(xType != 'text' )
356                             newSeries[0].data.push({ name: value[dataLabel], x: value[xLabel], y: tempY[0][i], marker: {fillColor: colorCodes[i % 8]} , id: i } );
357             else
358                             newSeries[0].data.push({ name: value[dataLabel], x: tempX[0][i], y: tempY[0][i], marker: {fillColor: colorCodes[i % 8]} , id: i } );
359                     i++;   
360                     });
361             currentSettings.yAxis.labels = { formatter : function() {
362                 if(tempY[1][this.value] && tempY[1][this.value].length > 10)
363                     return tempY[1][this.value].substring(0,10)
364                 else 
365                     return tempY[1][this.value];    
366                         }
367                     }
368              currentSettings.series = newSeries;
369                     currentChart = PMA_createChart(currentSettings); 
370         }
371         }
372         currentChart.series[0].data[currentData].select();
373         }
374     //End plot update    
376     //Generate SQL query for update
377     if (!isEmpty(newValues)) {
378             var sql_query = 'UPDATE `' + window.parent.table + '` SET ';
379         for (key in newValues) {
380             if(key != 'where_clause') {
381                 sql_query += '`' + key + '`=' ;
382             var value = newValues[key];
383             if(!isNumeric(value) && value != null) 
384                 sql_query += '\'' + value + '\' ,';
385             else
386                 sql_query += value + ' ,';
387             }
388         }
389         sql_query = sql_query.substring(0, sql_query.length - 1);
390         sql_query += ' WHERE ' + PMA_urldecode(data[currentData]['where_clause']);
391         
392         //Post SQL query to sql.php    
393         $.post('sql.php', {
394                 'token' : window.parent.token,
395                 'db' : window.parent.db,
396                 'ajax_request' : true,
397                 'sql_query' : sql_query,
398             'inline_edit' : false
399             }, function(data) {
400                 if(data.success == true) {
401                     $('#sqlqueryresults').html(data.sql_query);
402                 $("#sqlqueryresults").trigger('appendAnchor');
403                 }
404                 else 
405                     PMA_ajaxShowMessage(data.error);
406         })//End $.post
407     }//End database update
408         $("#dataDisplay").dialog("close");    
409     });//End submit handler 
411     /*
412      * Generate plot using Highcharts
413      */ 
415     if (data != null) {
416         $('#zoom_search_form')
417          .slideToggle()
418          .hide();
419         $('#togglesearchformlink')
420          .text(PMA_messages['strShowSearchCriteria'])
421     $('#togglesearchformdiv').show();
422         var selectedRow;
423         var colorCodes = ['#FF0000','#00FFFF','#0000FF','#0000A0','#FF0080','#800080','#FFFF00','#00FF00','#FF00FF'];
424         var series = new Array();
425         var xCord = new Array();
426         var yCord = new Array();
427     var tempX, tempY;
428         var it = 0;
429         var xMax; // xAxis extreme max 
430         var xMin; // xAxis extreme min 
431         var yMax; // yAxis extreme max 
432         var yMin; // yAxis extreme min 
434         // Set the basic plot settings
435         var currentSettings = {
436             chart: {
437                 renderTo: 'querychart',
438                 type: 'scatter',
439             //zoomType: 'xy',
440             width:$('#resizer').width() -3,
441                 height:$('#resizer').height()-20 
442         },
443         credits: {
444                 enabled: false 
445             },
446         exporting: { enabled: false },
447             label: { text: $('#dataLabel').val() },
448         plotOptions: {
449             series: {
450                 allowPointSelect: true,
451                     cursor: 'pointer',
452             showInLegend: false,
453                     dataLabels: {
454                         enabled: false,
455                     },
456                 point: {
457                         events: {
458                             click: function() {
459                     var id = this.id;
460                 var fid = 4;
461                 currentData = id;
462                 // Make AJAX request to tbl_zoom_select.php for getting the complete row info
463                 var post_params = {
464                                     'ajax_request' : true,
465                                     'get_data_row' : true,
466                                     'db' : window.parent.db,
467                                     'table' : window.parent.table,
468                                     'where_clause' : data[id]['where_clause'],
469                                     'token' : window.parent.token,
470                                 }
471                                 $.post('tbl_zoom_select.php', post_params, function(data) {
472                     // Row is contained in data.row_info, now fill the displayResultForm with row values
473                     for ( key in data.row_info) { 
474                     if (data.row_info[key] == null)
475                         $('#fields_null_id_' + fid).attr('checked', true);
476                     else
477                         $('#fieldID_' + fid).val(data.row_info[key]);
478                     fid++;
479                      }
480                      selectedRow = new Object();
481                      selectedRow = data.row_info;
482                                 });
484                     $("#dataDisplay").dialog("open");    
485                             },
486                         }
487                 }
488             }
489         },
490         tooltip: {
491             formatter: function() {
492                 return this.point.name;
493             }
494         },
495             title: { text: 'Query Results' },
496         xAxis: {
497             title: { text: $('#tableid_0').val() },
498         events: {
499                     setExtremes: function(e){
500                         this.resetZoom.show();
501                     }
502                 }
504             },
505             yAxis: {
506         min: null,
507             title: { text: $('#tableid_1').val() },
508         endOnTick: false,
509                 startOnTick: false,
510         events: {
511                     setExtremes: function(e){
512                         this.resetZoom.show();
513                     }
514                 }
515         },
516         }
518         $('#resizer').resizable({
519             resize: function() {
520                 currentChart.setSize(
521                     this.offsetWidth -3,
522                     this.offsetHeight -20,
523                     false
524                 );
525             }
526         });
527         
528     // Classify types as either numeric,time,text
529     xType = getType(xType);
530     yType = getType(yType);
532     //Set the axis type based on the field
533     currentSettings.xAxis.type = (xType == 'time') ? 'datetime' : 'linear';
534     currentSettings.yAxis.type = (yType == 'time') ? 'datetime' : 'linear';
536         // Formulate series data for plot
537         series[0] = new Object();
538         series[0].data = new Array();
539     series[0].marker = {
540             symbol: 'circle'
541         };
542     if (xType != 'text' && yType != 'text') {
543         $.each(data,function(key,value) {
544         var xVal = (xType == 'numeric') ? value[xLabel] : getTimeStamp(value[xLabel],$('#types_0').val());
545         var yVal = (yType == 'numeric') ? value[yLabel] : getTimeStamp(value[yLabel],$('#types_1').val());
546                 series[0].data.push({ name: value[dataLabel], x: xVal, y: yVal, marker: {fillColor: colorCodes[it % 8]} , id: it } );
547         xCord.push(value[xLabel]);
548         yCord.push(value[yLabel]);
549             it++;   
550             });
551         if(xType == 'numeric') {
552             currentSettings.xAxis.max = Array.max(xCord) + 6
553             currentSettings.xAxis.min = Array.min(xCord) - 6
554         }
555         else {
556             currentSettings.xAxis.labels = { formatter : function() {
557             return getDate(this.value, $('#types_0').val());
558         }}
559             }
560         if(yType == 'numeric') {
561             currentSettings.yAxis.max = Array.max(yCord) + 6
562             currentSettings.yAxis.min = Array.min(yCord) - 6
563         }
564         else {
565             currentSettings.yAxis.labels = { formatter : function() {
566             return getDate(this.value, $('#types_1').val());
567         }}
568             }
570         }
571     
572     else if (xType =='text' && yType !='text') {
573         $.each(data,function(key,value) {
574         xCord.push(value[xLabel]);
575         yCord.push(value[yLabel]);
576         });
577         
578         tempX = getCord(xCord);    
579         $.each(data,function(key,value) {
580         var yVal = (yType == 'numeric') ? value[yLabel] : getTimeStamp(value[yLabel],$('#types_1').val());
581                 series[0].data.push({ name: value[dataLabel], x: tempX[0][it], y: yVal, marker: {fillColor: colorCodes[it % 8]} , id: it } );
582             it++;   
583             });
584         
585         currentSettings.xAxis.labels = { formatter : function() {
586             if(tempX[1][this.value] && tempX[1][this.value].length > 10)
587                 return tempX[1][this.value].substring(0,10)
588             else 
589             return tempX[1][this.value];
590                 } 
591             }
592         if(yType == 'numeric') {
593             currentSettings.yAxis.max = Array.max(yCord) + 6
594             currentSettings.yAxis.min = Array.min(yCord) - 6
595         }
596         else {
597             currentSettings.yAxis.labels = { formatter : function() {
598             return getDate(this.value, $('#types_1').val());
599         }}
600             }
601         xCord = tempX[2];
602     }
603      
604     else if (xType !='text' && yType =='text') {
605         $.each(data,function(key,value) {
606         xCord.push(value[xLabel]);
607         yCord.push(value[yLabel]);
608         });
609         tempY = getCord(yCord);    
610         $.each(data,function(key,value) {
611         var xVal = (xType == 'numeric') ? value[xLabel] : getTimeStamp(value[xLabel],$('#types_0').val());
612                 series[0].data.push({ name: value[dataLabel], y: tempY[0][it], x: xVal, marker: {fillColor: colorCodes[it % 8]} , id: it } );
613             it++;   
614             });
615         if(xType == 'numeric') {
616             currentSettings.xAxis.max = Array.max(xCord) + 6
617             currentSettings.xAxis.min = Array.min(xCord) - 6
618         }
619         else {
620             currentSettings.xAxis.labels = { formatter : function() {
621             return getDate(this.value, $('#types_0').val());
622         }}
623             }
624         currentSettings.yAxis.labels = { formatter : function() {
625             if(tempY[1][this.value] && tempY[1][this.value].length > 10)
626                 return tempY[1][this.value].substring(0,10)
627             else 
628                     return tempY[1][this.value];
629             }
630             }
631         yCord = tempY[2];
632     }
633     
634     else if (xType =='text' && yType =='text') {
635         $.each(data,function(key,value) {
636         xCord.push(value[xLabel]);
637         yCord.push(value[yLabel]);
638         });
639         tempX = getCord(xCord);    
640         tempY = getCord(yCord);    
641         $.each(data,function(key,value) {
642                 series[0].data.push({ name: value[dataLabel], x: tempX[0][it], y: tempY[0][it], marker: {fillColor: colorCodes[it % 8]} , id: it } );
643             it++;   
644             });
645         currentSettings.xAxis.labels = { formatter : function() {
646             if(tempX[1][this.value] && tempX[1][this.value].length > 10)
647                 return tempX[1][this.value].substring(0,10)
648             else 
649                     return tempX[1][this.value];
650             }
651             }
652         currentSettings.yAxis.labels = { formatter : function() {
653             if(tempY[1][this.value] && tempY[1][this.value].length > 10)
654                 return tempY[1][this.value].substring(0,10)
655             else 
656                     return tempY[1][this.value];
657             }
658         }
659         xCord = tempX[2];
660         yCord = tempY[2];
662     }
664     currentSettings.series = series;
665         currentChart = PMA_createChart(currentSettings);
666     xMin = currentChart.xAxis[0].getExtremes().min;
667         xMax = currentChart.xAxis[0].getExtremes().max;
668         yMin = currentChart.yAxis[0].getExtremes().min;
669         yMax = currentChart.yAxis[0].getExtremes().max;
670     includePan(currentChart); //Enable panning feature
671         var setZoom = function() {
672         var newxm = xMin + (xMax - xMin) * (1 - zoomRatio) / 2;
673         var newxM = xMax - (xMax - xMin) * (1 - zoomRatio) / 2;
674         var newym = yMin + (yMax - yMin) * (1 - zoomRatio) / 2;
675         var newyM = yMax - (yMax - yMin) * (1 - zoomRatio) / 2;
676             currentChart.xAxis[0].setExtremes(newxm,newxM);
677             currentChart.yAxis[0].setExtremes(newym,newyM);
678     };
679     //Enable zoom feature
680      $("#querychart").mousewheel(function(objEvent, intDelta) {
681             if (intDelta > 0) {
682                 if (zoomRatio > 0.1) {
683                     zoomRatio = zoomRatio - 0.1;
684                     setZoom();
685                 }
686             }
687             else if (intDelta < 0) {
688                 zoomRatio = zoomRatio + 0.1;
689                 setZoom();
690             }
691         });
692         //Add reset zoom feature
693         currentChart.yAxis[0].resetZoom = currentChart.xAxis[0].resetZoom = $('<a href="#">Reset zoom</a>')
694         .appendTo(currentChart.container)
695         .css({
696             position: 'absolute',
697             top: 10,
698             right: 20,
699             display: 'none'
700         })
701         .click(function(){
702             currentChart.xAxis[0].setExtremes(null, null)
703             currentChart.yAxis[0].setExtremes(null, null)
704             this.style.display = 'none'
705         });
706     scrollToChart();
707     }