Source: chart.js

/*
 * JBoss, Home of Professional Open Source
 * Copyright ${year}, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

(function ($, rf) {
	var defaultOptions = {

	      /**
	       * Specifies the type of chart.
	       * According to this property and xtypex ytype, proper default options corresponding to chart type
	       * are applied and data are transformed to the format expected by flot.
	       * Following values are supported: `line`,`bar`,`pie`
	       * Options is not required. If it is not used the options and data format remains the same as flot uses by default.
	       * @property charttype
	       * @default null
	       */
	      charttype: '',

	      /**
	       * Specify the data type of values plotted on x-axis
	       * Following options are supported: number,string,date
	       * @property xtype
	       * @default null
	       */
	      xtype: '',

	      /**
	       * Specify the data type of values plotted on x-axis
	       * Following options are supported: number,string,date
	       * @property xtype
	       * @default null
	       */
	      ytype: '',

	      /**
	       * Allows to zoom the chart. Supported only when line chart is used. Requires charttype to be set.
	       * @property zoom
	       * @default false
	       */
	      zoom: false,



	     /**
	      * Options customizing the chart grid.
	      * @type Object
	      * @default {clickable:true, hoverable:true}
	      * @property grid
	      *   @property clickable {boolean}
	      *   Grid accepts click events.
	      *   @property hoverable {boolean}
	      *   Grid fires mouseover event. Necessary for tooltip to work.
	      *
	      */
	     grid: {
	        clickable: true,
	        hoverable: true
	      },

	      /**
	       * Turns on tooltip (text shown when mouse points to a part of a chart)
	       * @property tooltip
	       * @type boolean
	       * @default true
	       */
	      tooltip: true,

	      /**
	       * Customizes the tooltip.
	       * @type Object
	       * @default { content: '%s [%x,%y]'}
	       * @property tooltipOpts
	       *    @property content {String}
	       *    Specify the tooltip format. Use %s for series label, %x for X values, %y for Y value
	       *    @property defaultTheme
	       */
	      tooltipOpts: {
	        content: '%s  [%x,%y]',
	        shifts: {
	          x: 20,
	          y: 0
	        },
	        defaultTheme: false
	      },

	      /**
	       * Legend properties
	       * @type Object
	       * @default {postion:'ne', sorted: 'ascending'}
	       * @property legend
	       *    @property position {String}
	       *    Defines the placement of the legend in the grid. One of ne,nw,se,sw
	       *    @property sorted {String}
	       *    Defines the order of labels in the legend. One of ascending,descending,false.
	       */
	      legend: {
	        postion:'ne',
	        sorted: 'ascending'
	      },

	      /**
	       * Customizes the horizontal axis
	       * @type Object
	       * @default {min: null, max: null,autoscaleMargin: null, axisLabel: ''}
	       * @property xaxis
	       *   @property min {Number}
	       *   Minimal value shown on axis
	       *   @property max {Number}
	       *   Maximal values show on axis
	       *   @property autoscaleMargin {Number}
	       *   It's the fraction of margin that the scaling algorithm will add
	       *   to avoid that the outermost points ends up on the grid border
	       *   @property axisLabel {String}
	       *   Axis description
	       */
	      xaxis:{
	        min: null,
	        max: null,
	        autoscaleMargin: null,
	        axisLabel: ''
	      },

	      /**
	       * Customizes the vertical axis
	       * @type Object
	       * @default {min: null, max: null,autoscaleMargin: 0.2,axisLabel: ''}
	       * @property yaxis
	       *   @property min {Number}
	       *   Minimal value shown on axis
	       *   @property max {Number}
	       *   Maximal values show on axis
	       *   @property autoscaleMargin {Number}
	       *   It's the fraction of margin that the scaling algorithm will add to
	       *   avoid that the outermost points ends up on the grid border.
	       *   @property axisLabel {String}
	       *   Axis description
	       */
	      yaxis:{
	        min: null,
	        max: null,
	        autoscaleMargin: 0.2,
	        axisLabel: ''
	      },




	      /**
	       * Data to be plotted. The format is the same used by flot. The format may differ if the charttype
	       * option is set to pie or bar.
	       * @property data
	       * @default []
	       */
	      data:[]

	    };
	    
	var pieDefaults = {
	      series: {
	          pie: {
	            show: true
	          }
	        },
	        tooltipOpts: {
	          content: ' %p.0%, %s'
	        }
	      };
	 var _plotClickServerSide = function (event, clientId) {
	      var params = {};
	      params[clientId + 'name'] = "plotclick";
	      params[clientId + 'seriesIndex'] = event.data.seriesIndex;
	      params[clientId + 'dataIndex'] = event.data.dataIndex;
	      params[clientId + 'x'] = event.data.x;
	      params[clientId + 'y'] = event.data.y;

	      rf.ajax(clientId, event, {
	        parameters: params,
	        incId: 1
	      });

	    };

    
    rf.ui = rf.ui || {};

    rf.ui.Chart = rf.BaseComponent.extendClass({
            // class name
            name:"Chart",

            init : function (componentId, options) {
            	$super.constructor.call(this, componentId, options);

                this.namespace = this.namespace || "." + RichFaces.Event.createNamespace(this.name, this.id);
                this.attachToDom();
            	this.options = $.extend(true,{},defaultOptions,options);
            	
            	this.element = $(document.getElementById(componentId));
                this.chartElement = this.element.find(".chart");
            	
            	if (this.options.charttype === 'pie') {
                    this.options = $.extend(true,{},this.options,pieDefaults);
                    //transform data from
                    // [[{data:1, label:"label1"},{data:2,label:"label2"},...]]
                    //to
                    // [{data:1, label:"label1"},{data:2,label:"label2"},...]
                    this.options.data = this.options.data[0]; //pie chart data should not be in a collection
                }
                else if (this.options.charttype === 'bar') {                    
                    if (this.options.xtype === 'string') {
                        /*
                         * data transformation:
                         * data: [["label1", value1], ["label2", value2"], …]
                         * 
                         * =>
                         * 
                         * data: [[1, value1], [2, value2], …] 
                         * ticks: [[1, "label1"], [2, "label2"], …]
                         *
                         */
                        this.options.xaxis.tickLength = 0;
                        
                        var seriesLength = this.options.data[0].data.length,
                            seriesTotal = this.options.data.length,
                            ticks = [],
                            ordered = false;
                            
                        this.options.bars = this.options.bars || {};
                        this.options.bars.barWidth = 1 / (seriesTotal + 1);
                        for (var i = 0; i < seriesLength; i++) {
                            ticks.push( [i, this.options.data[0].data[i][0]] );
                            for (var j = 0; j < seriesTotal; j++) { 
                                this.options.data[j].data[i][0] = i;
                                if (!ordered) {
                                    this.options.data[j].bars.order = j;
                                }
                            }
                            ordered = true;
                        }
                        
                        this.options.xaxis.ticks = ticks;
                    }
                    
                    
                  }
                  else if (options.charttype === 'line') {
                    if (this.options.xtype === 'string') {
                        /*
                         * data transformation:
                         * data: [["label1", value1], ["label2", value2"], …]
                         * 
                         * =>
                         * 
                         * data: [[1, value1], [2, value2], …] 
                         * ticks: [[1, "label1"], [2, "label2"], …]
                         *
                         */
                        this.options.xaxis.tickLength = 0;
                        
                        var seriesLength = this.options.data[0].data.length,
                            seriesTotal = this.options.data.length,
                            ticks = [];
                        for (var i = 0; i < seriesLength; i++) {
                            ticks.push( [i, this.options.data[0].data[i][0]] );
                            for (var j = 0; j < seriesTotal; j++) {
                                this.options.data[j].data[i][0] = i;
                            }
                        }
                        
                        this.options.xaxis.ticks = ticks;
                    }
                    if (options.zoom) {
                      this.options.selection = {mode: 'xy'};
                    }
                    if (this.options.xtype === 'date') {
                      this.options = $.extend({},this.options, dateDefaults);
                      if (this.options.xaxis.format) {
                        this.options.xaxis.timeformat = this.options.xaxis.format;
                      }
                    }
                  }
            	
                this.plot = $.plot(this.chartElement,this.options.data,this.options);
                this.__bindEventHandlers(this.chartElement,this.options);
            },

            /***************************** Public Methods  ****************************************************************/

            /**
             * Returns chart object
             *
             * @method getPlotObject
             */
            getPlotObject: function () {
              return this.plot;
            },

            /**
             * Highlights the point in chart selected by seriesIndex or point index. Does not work for pie charts.
             * @param seriesIndex {int}
             * @param pointIndex {int}
             * @method highlight
             */
            highlight: function(seriesIndex,pointIndex){
               this.plot.highlight(seriesIndex,pointIndex);
            },
            /**
             * Removes highlighting of point. If method is called without parameters it unhighlights all points.
             * @param seriesIndex {int}
             * @param pointIndex {int}
             * @method unghighlight
             */
            unhighlight: function(seriesIndex,pointIndex){
               this.plot.unhighlight(seriesIndex,pointIndex);
            },

            /***************************** Private Methods ********************************************************/
            __bindEventHandlers:function(element,options){
            	
                this.chartElement.on('plotclick', this._getPlotClickHandler(this.options, this.chartElement, _plotClickServerSide));
                this.chartElement.on('plothover', this._getPlotHoverHandler(this.options, this.chartElement));
            	if (this.options.handlers && this.options.handlers.onmouseout) {
                    this.chartElement.on('mouseout', this.options.handlers.onmouseout);
                }

                if (this.options.zoom) {
                    this.chartElement.on('plotselected', $.proxy(this._zoomFunction, this));
                }
                
            },
          //function handles plotclick event. it calls server-side, client-side and particular series handlers if set.
            _getPlotClickHandler: function (options, element, serverSideHandler) {
            	
              var clickHandler = options.handlers['onplotclick'];	
              var particularClickHandlers= options.particularSeriesHandlers['onplotclick'];
              var clientId = this.element.attr('id');
              return function (event, pos, item) {
                if (item !== null) {
                  //point in a chart clicked
                  event.data = {
                    seriesIndex: item.seriesIndex,
                    dataIndex: item.dataIndex,
                    x: item.datapoint[0],
                    y: item.datapoint[1],
                    item: item
                  };
                  if(options.charttype=="pie"){
                	  event.data.x = options.data[item.seriesIndex].label;
                	  event.data.y = item.datapoint[1][0][1];
                  }
                  else if(options.charttype=="bar" && options.xtype=="string"){
                    event.data.x = options.xaxis.ticks[item.dataIndex][1];

                  }
                } else {
                    event.data = {
                        x: pos.x,
                        y: pos.y
                    };
                }
                  //sent request only if a server-side listener attached
                  if (options.serverSideListener) {
                    //server-side
                    if (serverSideHandler) {
                      serverSideHandler(event, clientId);
                    }
                  }

                  //client-side
                  if (clickHandler) {
                	 clickHandler.call(element, event);
                  }
                  //client-side particular series handler
                  if (particularClickHandlers[event.data.seriesIndex]) {
                	  particularClickHandlers[event.data.seriesIndex].call(element, event);
                  }
              };
            },
            
            //function handles plothover event. it calls client-side and particular series handlers if set.
            _getPlotHoverHandler: function (options, element) {
              var hoverHandler = options.handlers['onplothover'];	
              var particularHoverHandlers =	options.particularSeriesHandlers['onplothover']; 
              
              return function (event, pos, item) {
                if (item !== null) {
                  //point in a chart clicked
                  event.data = {
                    seriesIndex: item.seriesIndex,
                    dataIndex: item.dataIndex,
                    x: item.datapoint[0],
                    y: item.datapoint[1],
                    item: item
                  };
                } else {
                    event.data = {
                        x: pos.x,
                        y: pos.y
                    };
                }
                  //client-side
                  if (hoverHandler) {
                    hoverHandler.call(element, event);
                  }

                  //client-side particular series handler
                  if (particularHoverHandlers[event.data.seriesIndex]) {
                	  particularHoverHandlers[event.data.seriesIndex].call(element, event);
                  }
                
              };
            },

            _zoomFunction: function (event, ranges) {
                var plot = this.getPlotObject();
                $.each(plot.getXAxes(), function(_, axis) {
                    var opts = axis.options;
                    opts.min = ranges.xaxis.from;
                    opts.max = ranges.xaxis.to;
                });
                plot.setupGrid();
                plot.draw();
                plot.clearSelection();
            },

            resetZoom: function () {
                this.plot = $.plot(this.chartElement,this.options.data,this.options);
            },

            destroy: function () {
                rf.Event.unbindById(this.id, "." + this.namespace);
                $super.destroy.call(this);
            }
        });

    // define super class link
    var $super = rf.ui.Chart.$super;
})(RichFaces.jQuery, RichFaces);