      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     /