1 /*
  2  * JBoss, Home of Professional Open Source
  3  * Copyright ${year}, Red Hat, Inc. and individual contributors
  4  * by the @authors tag. See the copyright.txt in the distribution for a
  5  * full listing of individual contributors.
  6  *
  7  * This is free software; you can redistribute it and/or modify it
  8  * under the terms of the GNU Lesser General Public License as
  9  * published by the Free Software Foundation; either version 2.1 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * This software is distributed in the hope that it will be useful,
 13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 15  * Lesser General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Lesser General Public
 18  * License along with this software; if not, write to the Free
 19  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 20  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 21  */
 22 
 23 (function ($, rf) {
 24 	var defaultOptions = {
 25 
 26 	      /**
 27 	       * Specifies the type of chart.
 28 	       * According to this property and xtypex ytype, proper default options corresponding to chart type
 29 	       * are applied and data are transformed to the format expected by flot.
 30 	       * Following values are supported: `line`,`bar`,`pie`
 31 	       * Options is not required. If it is not used the options and data format remains the same as flot uses by default.
 32 	       * @property charttype
 33 	       * @default null
 34 	       */
 35 	      charttype: '',
 36 
 37 	      /**
 38 	       * Specify the data type of values plotted on x-axis
 39 	       * Following options are supported: number,string,date
 40 	       * @property xtype
 41 	       * @default null
 42 	       */
 43 	      xtype: '',
 44 
 45 	      /**
 46 	       * Specify the data type of values plotted on x-axis
 47 	       * Following options are supported: number,string,date
 48 	       * @property xtype
 49 	       * @default null
 50 	       */
 51 	      ytype: '',
 52 
 53 	      /**
 54 	       * Allows to zoom the chart. Supported only when line chart is used. Requires charttype to be set.
 55 	       * @property zoom
 56 	       * @default false
 57 	       */
 58 	      zoom: false,
 59 
 60 
 61 
 62 	     /**
 63 	      * Options customizing the chart grid.
 64 	      * @type Object
 65 	      * @default {clickable:true, hoverable:true}
 66 	      * @property grid
 67 	      *   @property clickable {boolean}
 68 	      *   Grid accepts click events.
 69 	      *   @property hoverable {boolean}
 70 	      *   Grid fires mouseover event. Necessary for tooltip to work.
 71 	      *
 72 	      */
 73 	     grid: {
 74 	        clickable: true,
 75 	        hoverable: true
 76 	      },
 77 
 78 	      /**
 79 	       * Turns on tooltip (text shown when mouse points to a part of a chart)
 80 	       * @property tooltip
 81 	       * @type boolean
 82 	       * @default true
 83 	       */
 84 	      tooltip: true,
 85 
 86 	      /**
 87 	       * Customizes the tooltip.
 88 	       * @type Object
 89 	       * @default { content: '%s [%x,%y]'}
 90 	       * @property tooltipOpts
 91 	       *    @property content {String}
 92 	       *    Specify the tooltip format. Use %s for series label, %x for X values, %y for Y value
 93 	       *    @property defaultTheme
 94 	       */
 95 	      tooltipOpts: {
 96 	        content: '%s  [%x,%y]',
 97 	        shifts: {
 98 	          x: 20,
 99 	          y: 0
100 	        },
101 	        defaultTheme: false
102 	      },
103 
104 	      /**
105 	       * Legend properties
106 	       * @type Object
107 	       * @default {postion:'ne', sorted: 'ascending'}
108 	       * @property legend
109 	       *    @property position {String}
110 	       *    Defines the placement of the legend in the grid. One of ne,nw,se,sw
111 	       *    @property sorted {String}
112 	       *    Defines the order of labels in the legend. One of ascending,descending,false.
113 	       */
114 	      legend: {
115 	        postion:'ne',
116 	        sorted: 'ascending'
117 	      },
118 
119 	      /**
120 	       * Customizes the horizontal axis
121 	       * @type Object
122 	       * @default {min: null, max: null,autoscaleMargin: null, axisLabel: ''}
123 	       * @property xaxis
124 	       *   @property min {Number}
125 	       *   Minimal value shown on axis
126 	       *   @property max {Number}
127 	       *   Maximal values show on axis
128 	       *   @property autoscaleMargin {Number}
129 	       *   It's the fraction of margin that the scaling algorithm will add
130 	       *   to avoid that the outermost points ends up on the grid border
131 	       *   @property axisLabel {String}
132 	       *   Axis description
133 	       */
134 	      xaxis:{
135 	        min: null,
136 	        max: null,
137 	        autoscaleMargin: null,
138 	        axisLabel: ''
139 	      },
140 
141 	      /**
142 	       * Customizes the vertical axis
143 	       * @type Object
144 	       * @default {min: null, max: null,autoscaleMargin: 0.2,axisLabel: ''}
145 	       * @property yaxis
146 	       *   @property min {Number}
147 	       *   Minimal value shown on axis
148 	       *   @property max {Number}
149 	       *   Maximal values show on axis
150 	       *   @property autoscaleMargin {Number}
151 	       *   It's the fraction of margin that the scaling algorithm will add to
152 	       *   avoid that the outermost points ends up on the grid border.
153 	       *   @property axisLabel {String}
154 	       *   Axis description
155 	       */
156 	      yaxis:{
157 	        min: null,
158 	        max: null,
159 	        autoscaleMargin: 0.2,
160 	        axisLabel: ''
161 	      },
162 
163 
164 
165 
166 	      /**
167 	       * Data to be plotted. The format is the same used by flot. The format may differ if the charttype
168 	       * option is set to pie or bar.
169 	       * @property data
170 	       * @default []
171 	       */
172 	      data:[]
173 
174 	    };
175 	    
176 	var pieDefaults = {
177 	      series: {
178 	          pie: {
179 	            show: true
180 	          }
181 	        },
182 	        tooltipOpts: {
183 	          content: ' %p.0%, %s'
184 	        }
185 	      };
186 	 var _plotClickServerSide = function (event, clientId) {
187 	      var params = {};
188 	      params[clientId + 'name'] = "plotclick";
189 	      params[clientId + 'seriesIndex'] = event.data.seriesIndex;
190 	      params[clientId + 'dataIndex'] = event.data.dataIndex;
191 	      params[clientId + 'x'] = event.data.x;
192 	      params[clientId + 'y'] = event.data.y;
193 
194 	      rf.ajax(clientId, event, {
195 	        parameters: params,
196 	        incId: 1
197 	      });
198 
199 	    };
200 
201     
202     rf.ui = rf.ui || {};
203 
204     rf.ui.Chart = rf.BaseComponent.extendClass({
205             // class name
206             name:"Chart",
207 
208             init : function (componentId, options) {
209             	$super.constructor.call(this, componentId, options);
210 
211                 this.namespace = this.namespace || "." + RichFaces.Event.createNamespace(this.name, this.id);
212                 this.attachToDom();
213             	this.options = $.extend(true,{},defaultOptions,options);
214             	
215             	this.element = $(document.getElementById(componentId));
216                 this.chartElement = this.element.find(".chart");
217             	
218             	if (this.options.charttype === 'pie') {
219                     this.options = $.extend(true,{},this.options,pieDefaults);
220                     //transform data from
221                     // [[{data:1, label:"label1"},{data:2,label:"label2"},...]]
222                     //to
223                     // [{data:1, label:"label1"},{data:2,label:"label2"},...]
224                     this.options.data = this.options.data[0]; //pie chart data should not be in a collection
225                 }
226             	else if (this.options.charttype === 'bar') {
227                     if (this.options.xtype === 'string') {
228                       //category bar chart
229                       /*transformation data from
230                        [
231                          {  data:   {"Key1":11, "Key2":21, "Key3": 31},
232                             label:  "label1",
233                             bars:   {show:true}
234                          },
235 
236                          {  data:   {"Key1":12,"Key2":22, "Key3": 32},
237                             label:  "label2",
238                             bars:   {show:true}
239                          },
240 
241                          {   data:   {"Key1":13,"Key2":23,"Key3":33},
242                             label:  "label3",
243                             bars:   {show:true}
244                          },
245                          {   data:   {"Key1":14,"Key2":24,"Key4":44},
246                            label:  "label4",
247                            bars:   {show:true}
248                          }
249                        ]
250 
251                        to the form:
252 
253 
254                        [
255                          {
256                            data:   [
257                               [0,11],[1,21],[2, 31]
258                            ],
259                            label:  "label1",
260                            bars: {
261                              show:true,
262                              order=0
263                            }
264                          },
265                          {
266                            data:   [
267                               [0,12],[1,22],[2, 32]
268                            ],
269                            label:  "label2",
270                            bars: {
271                              show:true,
272                              order=1
273                            }
274                          },
275                          {
276                            data:   [
277                               [0,13],[1,23],[2, 33]
278                            ],
279                            label:  "label3",
280                            bars: {
281                                show:true,
282                                order=2
283                            }
284                          },
285                          {
286                            data:   [
287                               [0,14],[1,24],[2, 0]
288                            ],
289                            label:  "label4",
290                            bars: {
291                              show:true,
292                              order=3
293                            }
294                          },
295                        ]
296 
297                        and create array ticks
298 
299                        ticks = [[0,"Key1"],[1,"Key2"],[2,"Key3"]] and assign this array to the this.options.xaxis
300 
301 
302                        according to the following rules:
303                        data:
304                          - select all keys used in the first series (set)
305                          - in the following series find the keys selected in first
306                          - missing consider to be 0   (ie. "Key3" in fourth series)
307                          - ignore additional          (ie. "Key5" in fourth series)
308                        keys:
309                          COUNTER=0
310                           - for each key used in the first series
311                              add couple [COUNTER, KEY_LABEL] to ticks
312                              COUNTER++
313                        order:
314                           order of the series
315                       */
316 
317 
318                       var ticks = [], keys = [], first = true, order = 0;
319 
320                       /**
321                        * Labels are mapped to numbers 0,1,2...
322                        * If a chart consists of multiple series values(from all series) with the same label
323                        * are mapped to the (one number 0,1,2..) space of width 1.
324                        * barWidth takes care of bars to not overlap
325                        * @type {number}
326                        */
327                       var barWidth =  1 / (this.options.data.length + 1);
328 
329                       for (var index in this.options.data) {//loop through data series
330                         var convertedData = [];
331                         var cnt = 0;
332                         if (first) {//the first series determine which keys (x-values are plotted)
333                           for (var key in this.options.data[index].data) {
334                             ticks.push([cnt, key]);
335                             keys.push(key);
336                             convertedData.push([cnt, this.options.data[index].data[key]]);
337                             cnt++;
338                           }
339                           first = false;
340                         }
341                         else {
342                           for (var k in keys) { //select values for first series keys only
343                             var loopKey = keys[k];
344                             if (this.options.data[index].data[loopKey]) {
345                               convertedData.push([cnt,this.options.data[index].data[loopKey]]);
346                             }
347                             else {
348                               convertedData.push([cnt, 0]);
349                             }
350                             cnt++;
351                           }
352                         }
353                         this.options.data[index].data = convertedData;
354                         var bars = {
355                           order: order,
356                           show: true
357                         };
358                         this.options.data[index].bars = bars;
359                         order++;
360 
361                       }
362 
363                       //add ticks to the options
364                       this.options.xaxis = $.extend({},this.options.xaxis, {
365                         ticks: ticks,
366                         tickLength: 0,
367                         //workaround to show display proper x-value on category bars
368                         tickFormatter: function (value, axis) {
369                           return axis.ticks[value].label;
370                         }
371                       });
372 
373 
374                       //options for all bars
375                       this.options.bars = $.extend({},this.options.bars, {
376                         show: true,
377                         barWidth: barWidth,
378                         align: 'center'
379                       });
380                     }
381                   }
382                   else if (options.charttype === 'line') {
383                     if (options.zoom) {
384                       this.options.selection = {mode: 'xy'};
385                     }
386                     if (this.options.xtype === 'date') {
387                       this.options = $.extend({},this.options, dateDefaults);
388                       if (this.options.xaxis.format) {
389                         this.options.xaxis.timeformat = this.options.xaxis.format;
390                       }
391                     }
392                   }
393             	
394                 this.plot = $.plot(this.chartElement,this.options.data,this.options);
395                 //this.options=options;
396                 this.__bindEventHandlers(this.chartElement,this.options);
397             },
398 
399             /***************************** Public Methods  ****************************************************************/
400 
401             /**
402              * Returns chart object
403              *
404              * @method getPlotObject
405              */
406             getPlotObject: function () {
407               return this.plot;
408             },
409 
410             /**
411              * Highlights the point in chart selected by seriesIndex or point index. Does not work for pie charts.
412              * @param seriesIndex {int}
413              * @param pointIndex {int}
414              * @method highlight
415              */
416             highlight: function(seriesIndex,pointIndex){
417                this.plot.highlight(seriesIndex,pointIndex);
418             },
419             /**
420              * Removes highlighting of point. If method is called without parameters it unhighlights all points.
421              * @param seriesIndex {int}
422              * @param pointIndex {int}
423              * @method unghighlight
424              */
425             unhighlight: function(seriesIndex,pointIndex){
426                this.plot.unhighlight(seriesIndex,pointIndex);
427             },
428 
429             /***************************** Private Methods ********************************************************/
430             __bindEventHandlers:function(element,options){
431             	
432                 this.chartElement.on('plotclick', this._getPlotClickHandler(this.options, this.chartElement, _plotClickServerSide));
433                 this.chartElement.on('plothover', this._getPlotHoverHandler(this.options, this.chartElement));
434             	if (this.options.handlers && this.options.handlers.onmouseout) {
435                     this.chartElement.on('mouseout', this.options.handlers.onmouseout);
436                 }
437 
438                 if (this.options.zoom) {
439                     this.chartElement.on('plotselected', $.proxy(this._zoomFunction, this));
440                 }
441                 
442             },
443           //function handles plotclick event. it calls server-side, client-side and particular series handlers if set.
444             _getPlotClickHandler: function (options, element, serverSideHandler) {
445             	
446               var clickHandler = options.handlers['onplotclick'];	
447               var particularClickHandlers= options.particularSeriesHandlers['onplotclick'];
448               var clientId = this.element.attr('id');
449               return function (event, mouse, item) {
450                 if (item !== null) {
451                   //point in a chart clicked
452                   event.data = {
453                     seriesIndex: item.seriesIndex,
454                     dataIndex: item.dataIndex,
455                     x: item.datapoint[0],
456                     y: item.datapoint[1],
457                     item: item
458                   };
459                   if(options.charttype=="pie"){
460                 	  event.data.x = options.data[item.seriesIndex].label;
461                 	  event.data.y = item.datapoint[1][0][1];
462                   }
463                   else if(options.charttype=="bar" && options.xtype=="string"){
464                     event.data.x = options.xaxis.ticks[item.dataIndex][1];
465 
466                   }
467 
468                   //sent request only if a server-side listener attached
469                   if (options.serverSideListener) {
470                     //server-side
471                     if (serverSideHandler) {
472                       serverSideHandler(event, clientId);
473                     }
474                   }
475 
476                   //client-side
477                   if (clickHandler) {
478                 	 clickHandler.call(element, event);
479                   }
480                   //client-side particular series handler
481                   if (particularClickHandlers[event.data.seriesIndex]) {
482                 	  particularClickHandlers[event.data.seriesIndex].call(element, event);
483                   }
484                 }
485               };
486             },
487             
488             //function handles plothover event. it calls client-side and particular series handlers if set.
489             _getPlotHoverHandler: function (options, element) {
490               var hoverHandler = options.handlers['onplothover'];	
491               var particularHoverHandlers =	options.particularSeriesHandlers['onplothover']; 
492               
493               return function (event, mouse, item) {
494                 if (item !== null) {
495                   //point in a chart clicked
496                   event.data = {
497                     seriesIndex: item.seriesIndex,
498                     dataIndex: item.dataIndex,
499                     x: item.datapoint[0],
500                     y: item.datapoint[1],
501                     item: item
502                   };
503 
504                   //client-side
505                   if (hoverHandler) {
506                     hoverHandler.call(element, event);
507                   }
508 
509                   //client-side particular series handler
510                   if (particularHoverHandlers[event.data.seriesIndex]) {
511                 	  particularHoverHandlers[event.data.seriesIndex].call(element, event);
512                   }
513                 }
514               };
515             },
516 
517             _zoomFunction: function (event, ranges) {
518                 var plot = this.getPlotObject();
519                 $.each(plot.getXAxes(), function(_, axis) {
520                     var opts = axis.options;
521                     opts.min = ranges.xaxis.from;
522                     opts.max = ranges.xaxis.to;
523                 });
524                 plot.setupGrid();
525                 plot.draw();
526                 plot.clearSelection();
527             },
528 
529             resetZoom: function () {
530                 this.plot = $.plot(this.chartElement,this.options.data,this.options);
531             },
532 
533             destroy: function () {
534                 rf.Event.unbindById(this.id, "." + this.namespace);
535                 $super.destroy.call(this);
536             }
537         });
538 
539     /