Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HiveStatistics/sources/HeuristicLab.Services.Hive.Statistics/3.3/App_Code/ChartHelper.cshtml @ 11222

Last change on this file since 11222 was 11222, checked in by mroscoe, 10 years ago
File size: 23.2 KB
Line 
1@* HeuristicLab
2 * Copyright (C) 2002-2013 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
3 *
4 * This file is part of HeuristicLab.
5 *
6 * HeuristicLab is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * HeuristicLab is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
18 *@
19
20@helper AjaxDataRenderer()
21{
22    <script>
23        var ajaxDataRenderer = function (url, plot, options) {
24            var ret = null;
25            $.ajax({
26                async: false,
27                url: url,
28                dataType: "json",
29                success: function (data) {
30                    ret = data;
31                }
32            });
33            return ret;
34        };
35    </script>
36}
37
38@helper LineChartTime(string destinationTag, string url, string title = "", double? minY = null, double? maxY = null, string axisYFormat = null) {
39  <script>
40    var @(destinationTag)Plot = $.jqplot("@destinationTag", "@url", {
41      title: "@title",
42      highlighter: {
43        show: true,
44        sizeAdjust: 7.5
45      },
46      seriesDefaults: {
47        markerOptions: { show: false }
48      },
49      dataRenderer: ajaxDataRenderer,
50      axes: {
51        xaxis: {
52          renderer: $.jqplot.DateAxisRenderer,
53          pad: 0
54        },
55        yaxis: {
56          @if (axisYFormat != null)
57          {<text>
58          tickOptions: {
59            formatString: "@axisYFormat",
60          },
61          </text>}
62          autoscale: true,
63          pad: 0,
64          @if (minY != null)
65          {
66            @:min: @minY,
67          }
68          @if (maxY != null)
69          {
70            @:max: @maxY,
71          }
72        },
73      },
74      gridPadding: { left: 50, right: 10 },
75      cursor: {
76        show: true,
77        showTooltip: false,
78        zoom: true,
79        clickReset: false,
80        dblClickReset: false,
81        constrainZoomTo: 'x'
82      }
83    });
84       
85    $(window).resize(function() {
86      @(destinationTag)Plot.replot({ resetAxes: true });
87    });
88  </script>
89}
90
91@helper RefreshChart(string destinationTag, string url, string startDate, string endDate, double? minY=null, double? maxY=null) {
92  <text>
93  $.ajax({url: "@(new HtmlString(url))?start=" + @startDate + "&end=" + @endDate, datatype: "json", success: function(result) {
94    for (var i = 0; i < result.length; i++) {
95      @(destinationTag)Plot.series[i].data = result[i];
96    }
97    //Resets only the xaxis, still need to resize y with given min/max
98    @(destinationTag)Plot.replot({resetAxes:true});
99    @if(minY != null) {
100      //If min Y was provided set the plot's min Y value
101      @:@(destinationTag)Plot.axes.yaxis.min = @minY;
102    }
103    @if(maxY != null) {
104      //If max Y was provided set the plot's max Y value
105      @:@(destinationTag)Plot.axes.yaxis.max = @maxY;
106    }
107    @(destinationTag)Plot.axes.yaxis.reset();
108    //A final replot to redraw possible new Y axis values
109    @(destinationTag)Plot.replot({resetAxes:['xaxis']});
110@*    @(destinationTag)Plot.replot({ resetAxes:true });*@
111  }});
112  </text>
113}
114
115@helper ResizeCharts()
116{
117  <text>
118  function resizeCharts(caller) {
119    //If current plot is collapsed
120    if($(caller).css("display") == "none") {
121      //Display the contents of chartContainer
122      ResizeOpenClose($(caller).siblings(".collapse"));
123      //Reset barWidth for bar charts
124      $.each(window[caller.id].series, function(index, series) {
125        series.barWidth = undefined;
126      });
127      //Replot the chart with same name as element id
128      window[caller.id].replot({ resetAxes: false });
129      //Hide the contents of the current div
130      ResizeOpenClose($(caller).siblings(".collapse"));
131    }
132    //Current plot is expanded
133    else {
134      //Reset barWidth for bar charts
135      $.each(window[caller.id].series, function(index, series) {
136        series.barWidth = undefined;
137      });
138      //Replot the chart with same name as element id
139      window[caller.id].replot({ resetAxes: false });
140    }
141  }
142
143  $(window).resize(function() {
144    $("div.jqplot-target",".chartContainer").each(function() {
145      var potentialTab = $(this).parent().parent().parent();
146      //If the section is tabular and currently hidden
147      if(potentialTab.hasClass("tabSection") && potentialTab.css("display") == "none") {
148        ResizeOpenCloseTabular(potentialTab);
149        resizeCharts(this);
150        ResizeOpenCloseTabular(potentialTab);
151      }
152      else {
153        resizeCharts(this);
154      }
155    });
156  });
157  </text>
158}
159
160@helper NumberPages(string records, string limit, string container, string functionName, string currentPage = null)
161{
162  <text>
163    if(@(records).length > @limit) {
164      $("#@container").append('<label class="pageTitle">Page: </label>')
165      var pages = Math.floor(@(records).length/@(limit)) + 1;
166      for(var i=0; i < pages; i++) {
167        $("#" + "@container").append('<a id="@(container)Page' + (i + 1) + '" class="page">' + (i + 1) + '</a>')
168      }
169      if(@currentPage != null) {
170        $("#@(container)Page" + @currentPage).css('color','#F7921D');
171      }
172      else {
173        $("#@(container)Page1").css('color','#F7921D');
174      }
175      $(".page").click(function () {
176        pageNumber = $(this).html();
177        @(functionName)();
178      });
179      if(@currentPage != null) {
180        @(records).splice(0,(@(currentPage)-1)*@(limit));
181        if (@(records).length > @(limit)) {
182          @(records).splice(@(limit), @(records).length - @(limit));
183        }
184      }
185      else {
186        @(records).splice(@(limit), @(records).length - @(limit));
187      }
188    }
189  </text>
190}
191
192@helper TasksForUser(string destinationTag, string url, string functionName, string userName, string limit, string startDate = null, string endDate = null, string jobId = null, string taskState = null, string pageNumber = null)
193{
194  <text>
195    var GetRequest = "?userName=" + @(userName);
196    @if (startDate != null)
197    {
198      @:if(@(startDate)!=null) {
199        @:GetRequest += "&start=" + @startDate;
200      @:}
201    }
202    @if (endDate != null)
203    {
204      @:if(@(endDate)!=null) {
205        @:GetRequest += "&end=" + @endDate;
206      @:}
207    }
208    @if (jobId != null)
209    {
210      @:if(@(jobId)!=null) {
211        @:GetRequest += "&jobId=" + @jobId;
212      @:}
213    }
214    @if (taskState != null)
215    {
216      @:if(@(taskState)!=null) {
217        @:GetRequest += "&taskState=" + @taskState;
218      @:}
219    }
220    $.ajax({
221      async: false, url: "@(new HtmlString(url))" + GetRequest, datatype: "json", success: function(result) {
222      if(@(userName) == null) {
223        $("#@(destinationTag)").append(
224          '<section class="chartContainer">' +
225            '<h1 class="title">Please select a user!</h1>' +
226          '</section>'
227        )
228      }
229      else if(result.length==0) {
230        $("#@(destinationTag)").append(
231          '<section class="chartContainer">' +
232            '<h1 class="title">' + @userName + ' has no tasks for the specified filters!</h1>' +
233          '</section>'
234        )
235      }
236      else {
237        var waitTime;
238        var transferTime;
239        var runTime;
240        var seriesDescriptions = ["Time waiting","Time transferring","Time calculating"];
241
242        //Checks if multipage display, if it is then trims results to
243        //the results for the page to be displayed
244        @ChartHelper.NumberPages("result", limit, destinationTag, functionName, pageNumber)
245
246        //Globally accesible, for use when resizing, eliminates extra DB request
247        window["numberTasks"] = result.length;
248
249        //Set display of all errors to none, errors matching the tasks will be reset below
250        $("#@(destinationTag)").find(".errorContainer, .errorTitle, .errorTask, .errorMessage").css('display','none');
251
252        //Set variable for tracking jobId and open first job grouping
253        var currentJob = result[0].JobId;
254        $("#@(destinationTag)").append('<section class="jobTaskGroup" id="' + result[0].JobId + '"><h1>' + result[0].JobName + '</h1></section>');
255
256        //For each result create a seperate collapsable section with a chart and info label
257        for(var i = 0; i < result.length; i++){
258          if(currentJob != result[i].JobId) {
259            $("#@(destinationTag)").append('<section class="jobTaskGroup" id="' + result[i].JobId + '"><h1>' + result[i].JobName + '</h1></section>');
260            currentJob = result[i].JobId;
261          }
262          $("#" + currentJob).append(
263            '<section class="chartContainer">' +
264              '<h1 class="title" id="@(destinationTag)' + result[i].TaskId + 'PlotTitle">Task ' + result[i].TaskId + '</h1>' +
265              '<button class="collapse" onclick="CollapseSection(this)">+</button>' +
266              '<div id="@(destinationTag)Plot' + i + '"></div>' +
267              '<label id="@(destinationTag)PlotInfo' + i + '"></label>' +
268              '<a id="' + result[i].TaskId + '" class="moreInfo" onclick="MoreTaskInfo(this)">More Info</a>' +
269            '</section>'
270          )
271          //Re-enables the error display if any of the tasks on the page have Ids matching
272          //those of errors
273          $(".errorTask","#" + "@(destinationTag)").each(function() {
274            if($(this).html()==result[i].TaskId) {
275              $("#@(destinationTag)" + result[i].TaskId + "PlotTitle").css("color","red");
276              $("#@(destinationTag)" + result[i].TaskId + "PlotTitle").append(" - ERROR");
277              $(".errorContainer, .errorTitle, .underline","#@(destinationTag)").css('display','inline-block');
278              $(this).css('display','inline-block');
279              $(this).next().css('display','inline-block');
280            }
281          });
282          waitTime = [result[i].TotalWaiting];
283          transferTime = [result[i].TotalTransfer];
284          runTime = [result[i].TotalRuntime];
285          window["@(destinationTag)Plot" + i] = $.jqplot("@(destinationTag)Plot" + i, [waitTime,transferTime,runTime], {
286            seriesDefaults:{
287              renderer:$.jqplot.BarRenderer,
288              shadowAngle: 135,
289              pointLabels: {show: true, formatString: '%.3f'}
290            },
291            series:[
292              {label:'Waiting'},
293              {label:'Transferring'},
294              {label:'Calculating'}
295            ],
296            legend: {
297              show: true,
298              location: 'e',
299              placement: 'outside'
300            },
301            axes: {
302              xaxis: {
303                renderer: $.jqplot.CategoryAxisRenderer,
304                showLabel: false,
305                pad: 0
306              },
307              yaxis: {
308                pad: 0
309              }
310            },
311            cursor: {
312              showTooltip: false
313            }
314          });
315          /* Bind a datalistener to each chart and display details of clicked
316            upon data in the label below the chart */
317          $("#" + "@(destinationTag)Plot" + i).bind('jqplotDataClick', function (ev, seriesIndex, pointIndex, data) {
318            $(this).next("label").html(seriesDescriptions[seriesIndex] + ": " + data[1]);
319          });
320
321          CollapsedByDefault(document.getElementById("@(destinationTag)Plot" + i));
322        }
323      }
324    }});
325  </text>
326}
327
328@helper SetStreamingProperties(int refresh, int chartLength, int upperY)
329{
330  <text>
331  //Refresh time (in millisec)
332  var refreshRate = @(refresh);
333  //Number of data points on chart
334  var chartSize = @(chartLength);
335  //Amount to add to max Y value
336  var upperYBuffer = @(upperY);
337
338  //Used to return a string containing the names of the series
339  //to be used in plot creation
340  function GetSeries(numberData,dataName){
341    var result = "[";
342    for(i=0; i < numberData; i++) {
343      if(i < numberData -1) {
344        result += dataName + i + ",";
345      }
346      else {
347        result += dataName + i + "]";
348      }
349    }
350    return result;
351  }
352  </text>
353}
354
355@helper CreateStreamChart(string dataName, string destinationTag, string url, string title, string format = null, double? maxY = null)
356{
357  <text>
358  //Get current time, used to create initial values
359  var @(dataName)CurrentDate = (new Date()).getTime();
360
361  var @(dataName)Data = [];
362  var @(dataName)CurrentValue = [];
363
364  //Get the most recent value(s) from the given URL
365  $.ajax({
366    async: false, url: '@(url)', datatype: "json", success: function (result) {
367      for(i = 0; i < result.length; i++) {
368        @(dataName)CurrentValue[i] = result[i];
369      }
370    }
371  });
372 
373  //Create a chartSize worth of data using CurrentDate and CurrentValue
374  //for each CurrentValue
375  for(i = 0; i < @(dataName)CurrentValue.length; i++) {
376    window["@(dataName)Data" + i] = [];
377    for (j = 0; j < chartSize; j++) {
378      window[ "@(dataName)Data" + i].push([@(dataName)CurrentDate - (chartSize - 1 - j) * refreshRate, @(dataName)CurrentValue[i]]);
379    }
380  }
381
382  //Options for the chart to be created
383  var @(destinationTag)PlotOptions = {
384    title: "@title",
385    axes: {
386      xaxis: {
387        numberTicks: 4,
388        renderer: $.jqplot.DateAxisRenderer,
389        tickOptions: { formatString: '%H:%M:%S' },
390        min: window["@(dataName)Data" + 0][0][0],
391        max: window["@(dataName)Data" + 0][window["@(dataName)Data" + 0].length - 1][0]
392      },
393      yaxis: {
394        @if (format != null)
395        {<text>
396        tickOptions: {
397            formatString: "@format",
398        },
399        </text>}
400        min: 0,
401        @if (maxY != null)
402        {
403          @:max: @maxY,
404        }
405        else
406        {
407          @:max: window["@(dataName)Data" + 0][window["@(dataName)Data" + 0].length - 1][1] + upperYBuffer,
408        }
409        numberTicks: 6
410      }
411    },
412    seriesDefaults: {
413      rendererOptions: { smooth: true },
414      markerOptions: {
415        show: false
416      }
417    }
418  };
419
420  //Declares the jqPlot variable, evals the string of series returned
421  //from getSeries which allows for any number of series
422  window[ "@(destinationTag)Plot"] = $.jqplot('@destinationTag', eval(GetSeries(@(dataName)CurrentValue.length,"@(dataName)Data")), @(destinationTag)PlotOptions);
423  </text>
424}
425
426@helper UpdateStreamChart(string dataName, string destinationTag, string url, string fixedY = null)
427{
428  <text>
429  //If the data is beyond chartSize use shift to trim one off the end
430  for(i = 0; i < @(dataName)CurrentValue.length; i++) {
431    if (window["@(dataName)Data" + i].length > chartSize - 1) {
432      window["@(dataName)Data" + i].shift();
433    }
434  }
435
436  //Get the up-to-date data, each result assigned to it's own array
437  $.ajax({
438    async: false, url: '@(url)', datatype: "json", success: function (result) {
439      for(i = 0; i < result.length; i++) {
440        window[ "@(dataName)Data" + i].push([(new Date()).getTime(), result[i]]);
441      }
442    }
443  });
444
445  //If the plot exists currently destroy it
446  if ( @(destinationTag)Plot) {
447    @(destinationTag)Plot.destroy();
448  }
449
450  //Assign series
451  for(i = 0; i < @(dataName)CurrentValue.length; i++) {
452    @(destinationTag)Plot.series[i].data = window["@(dataName)Data" + i];
453  }
454
455  //Recalculate x and possibly y axis max and mins
456  @(destinationTag)PlotOptions.axes.xaxis.min = window["@(dataName)Data" + 0][0][0];
457  @(destinationTag)PlotOptions.axes.xaxis.max = window["@(dataName)Data" + 0][window["@(dataName)Data" + 0].length - 1][0];
458  @if (fixedY == null)
459  {
460    @:@(destinationTag)PlotOptions.axes.yaxis.max = window["@(dataName)Data" + 0][window["@(dataName)Data" + 0].length - 1][1] + upperYBuffer;
461  }
462
463  //Re-assigns the jqPlot variable, evals the string of series returned
464  //from getSeries which allows for any number of series
465  @(destinationTag)Plot = $.jqplot('@destinationTag', eval(GetSeries(@(dataName)CurrentValue.length,"@(dataName)Data")), @(destinationTag)PlotOptions);
466  </text>
467}
468
469@helper SlaveInfoChart(string destinationTag, string url, string limit, bool singleSlave, string startDate = null, string endDate = null, string userName = null, string functionName = null, string pageNumber = null, string slaveId = null)
470{
471  <text>
472  var GetRequest = "";
473  @if (startDate != null)
474  {
475    @:if(@(startDate)!=null) {
476      @:if(GetRequest == "") {
477        @:GetRequest += "?start=" + @startDate;
478      @:}
479      @:else {
480        @:GetRequest += "&start=" + @startDate;
481      @:}
482    @:}
483  }
484  @if (endDate != null)
485  {
486    @:if(@(endDate)!=null) {
487      @:if(GetRequest == "") {
488        @:GetRequest += "?end=" + @endDate;
489      @:}
490      @:else {
491        @:GetRequest += "&end=" + @endDate;
492      @:}
493    @:}
494  }
495  @if (userName != null)
496  {
497    @:if(@(userName)!=null) {
498      @:if(GetRequest == "") {
499        @:GetRequest += "?userName=" + @userName;
500      @:}
501      @:else {
502        @:GetRequest += "&userName=" + @userName;
503      @:}
504    @:}
505  }
506  @if (slaveId != null)
507  {
508    @:if(@(slaveId)!=null) {
509      @:if(GetRequest == "") {
510        @:GetRequest += "?slaveId=" + @slaveId;
511      @:}
512      @:else {
513        @:GetRequest += "&slaveId=" + @slaveId;
514      @:}
515    @:}
516  }
517  $.ajax({
518    async: false, url: "@(new HtmlString(url))" + GetRequest, datatype: "json", success: function(result) {
519
520    //Set chart names for use in creation below, must be set identically in
521    //ResizeSlaves
522    var slaveChartNames = ["TotalUsedCores","TotalUsedMemory","CPUUtilization"];
523
524    var destTag = eval("@(destinationTag)");
525    if(typeof eval("@(destinationTag)") != "string") {
526      destTag = "@(destinationTag)";
527    }
528
529    if(result.length == 0) {
530      @if (!singleSlave) {
531        @:$('#' + destTag).html("");
532      }
533      $('#' + destTag).append(
534        '<section class="chartContainer">' +
535          '<h1 class="title">No slave information for the specified filters!</h1>' +
536        '</section>'
537      )
538    }
539    else {
540      var time = new Date();
541
542      @if (!singleSlave)
543      {
544        @:$('#' + destTag).html("");
545
546        //Checks if multipage display, if it is then trims results to
547        //the results for the page to be displayed
548        @ChartHelper.NumberPages("result", limit, destinationTag, functionName, pageNumber)
549      }
550
551      for(i = 0; i < result.length; i++) {
552        var coreSeries = [];
553        coreSeries[0] = [];
554        coreSeries[1] = [];
555        var memorySeries = [];
556        memorySeries[0] = [];
557        memorySeries[1] = [];
558        var cpuSeries = [];
559        cpuSeries[0] = [];
560        $('#' + destTag).append(
561          '<section class="chartContainer">' +
562            '<h1 class="title" id="' + destTag + result[i][0].SlaveID + 'PlotTitle">Slave ' + result[i][0].ClientName + '</h1>' +
563            '<button class="collapse" onclick="CollapseSection(this)">+</button>' +
564            '<div id="' + destTag + slaveChartNames[0] + 'Plot' + i + '"></div>' +
565            '<div id="' + destTag + slaveChartNames[1] + 'Plot' + i + '"></div>' +
566            '<div id="' + destTag + slaveChartNames[2] + 'Plot' + i + '"></div>' +
567            '<a id="' + result[i][0].SlaveID + '" class="moreInfo" onclick="MoreSlaveInfo(this)">More Info</a>' +
568          '</section>');
569        for(j = 0; j < result[i].length; j++) {
570          time.setTime(result[i][j].Time.replace(/\D/g,''));
571          coreSeries[0].push([time.toUTCString(),result[i][j].TotalCores]);
572          coreSeries[1].push([time.toUTCString(),result[i][j].UsedCores]);
573          memorySeries[0].push([time.toUTCString(),(result[i][j].TotalMemory / 1000)]);
574          memorySeries[1].push([time.toUTCString(),(result[i][j].UsedMemory / 1000)]);
575          cpuSeries[0].push([time.toUTCString(),result[i][j].CPUUtilization]);
576        }
577        if(result[i].length > 1) {
578          @ChartHelper.LineChartGivenSeries("destTag + slaveChartNames[0]", "i", "coreSeries", "Total/Used Cores")
579          @ChartHelper.LineChartGivenSeries("destTag + slaveChartNames[1]", "i", "memorySeries", "Total/Used Memory", 0)
580          @ChartHelper.LineChartGivenSeries("destTag + slaveChartNames[2]", "i", "cpuSeries", "CPU Utilization", 0, 100, "%.1f%%")
581        }
582        else {
583          @ChartHelper.BarChartGivenSeries("destTag + slaveChartNames[0]", "i", "coreSeries", "Total/Used Cores")
584          @ChartHelper.BarChartGivenSeries("destTag + slaveChartNames[1]", "i", "memorySeries", "Total/Used Memory", 0)
585          @ChartHelper.BarChartGivenSeries("destTag + slaveChartNames[2]", "i", "cpuSeries", "CPU Utilization", 0, 100, "%.1f%%")
586        }
587        for(k = 0; k < slaveChartNames.length; k++) {
588          CollapsedByDefault(document.getElementById(destTag + slaveChartNames[k] + "Plot" + i));
589        }
590      }
591    }
592  }});
593  </text>
594}
595
596@helper LineChartGivenSeries(string destinationTag, string chartId, string series, string title, double? minY = null, double? maxY = null, string axisYFormat = null)
597{
598  <text>
599  window[@(destinationTag) + "Plot" + @(chartId)] = $.jqplot(@(destinationTag) + "Plot" + @(chartId), @series, {
600    title: "@title",
601    axes: {
602      xaxis: {
603        renderer: $.jqplot.DateAxisRenderer,
604        tickOptions:{formatString:'%b %#d, %y'},
605        pad: 0
606      },
607      yaxis: {
608        @if (axisYFormat != null)
609        {
610          <text>
611          tickOptions: {
612              formatString: "@axisYFormat"
613          },
614          </text>
615        }
616        pad: 0,
617        @if (minY != null)
618        {
619            @:min: @minY,
620        }
621        @if (maxY != null)
622        {
623            @:max: @maxY,
624        }
625      }
626    },
627    gridPadding: { left: 50, right: 10 },
628    cursor: {
629      show: true,
630      showTooltip: false,
631      zoom: true,
632      clickReset: false,
633      dblClickReset: false,
634      constrainZoomTo: 'x'
635    }
636  });
637  </text>
638}
639
640@helper BarChartGivenSeries(string destinationTag, string chartId, string series, string title, double? minY = null, double? maxY = null, string axisYFormat = null)
641{
642  <text>
643  window[@(destinationTag) + "Plot" + @(chartId)] = $.jqplot(@(destinationTag) + "Plot" + @(chartId), @series, {
644    title: "@title",
645    seriesDefaults:{
646      renderer:$.jqplot.BarRenderer,
647      shadowAngle: 135,
648      pointLabels: {show: true, formatString: '%.2f'}
649    },
650    axes: {
651      xaxis: {
652        renderer: $.jqplot.CategoryAxisRenderer,
653        tickOptions:{formatString:'%b %#d, %y'},
654        pad: 0
655      },
656      yaxis: {
657        @if (axisYFormat != null)
658        {
659          <text>
660          tickOptions: {
661              formatString: "@axisYFormat"
662          },
663          </text>
664        }
665        pad: 0,
666        @if (minY != null)
667        {
668            @:min: @minY,
669        }
670        @if (maxY != null)
671        {
672            @:max: @maxY,
673        }
674      }
675    },
676    gridPadding: { left: 50, right: 10 },
677    cursor: {
678      show: true,
679      showTooltip: false,
680      zoom: true,
681      clickReset: false,
682      dblClickReset: false,
683      constrainZoomTo: 'x'
684    }
685  });
686  </text>
687}
Note: See TracBrowser for help on using the repository browser.