Source: src/main/javascript/oracle/oj/ojchart/ojchart.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/**
 * Copyright (c) 2014, Oracle and/or its affiliates.
 * All rights reserved.
 */

/**
 * @ojcomponent oj.ojChart
 * @augments oj.dvtBaseComponent
 * @since 0.7
 * 
 * @classdesc
 * <h3 id="chartOverview-section">
 *   JET Chart Component
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#chartOverview-section"></a>
 * </h3>
 * 
 * <p>Chart component for JET with support for bar, line, area, combination, pie, scatter, bubble, and funnel
 * charts.</p>
 * 
 * {@ojinclude "name":"warning"}
 * 
 * <pre class="prettyprint">
 * <code>
 * <div data-bind="ojComponent: {
 *   component: 'ojChart',
 *   type: 'bar',
 *   series: [{name: 'Q1 Sales', items: [50, 60, 20]}],  
 *   groups: ['Phones', 'Tablets', 'Laptops']
 * }"/>
 * </code>
 * </pre>
 * 
 * {@ojinclude "name":"a11yKeyboard"}
 * 
 * <h3 id="touch-section">
 *   Touch End User Information
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#touch-section"></a>
 * </h3>
 * 
 * {@ojinclude "name":"touchDoc"}
 * 
 * <h3 id="keyboard-section">
 *   Keyboard End User Information
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#keyboard-section"></a>
 * </h3>
 * 
 * {@ojinclude "name":"keyboardDoc"}
 * 
 * <h3 id="perf-section">
 *   Performance
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#perf-section"></a>
 * </h3>
 * 
 * <h4>Animation</h4>
 * <p>Animation should only be enabled for visualizations of small to medium data sets. When animating changes to larger 
 *    data sets or when animating between data sets, it's recommended to turn off the 
 *    <code class="prettyprint">styleDefaults.animationIndicators</code>, since they effectively double the amount of 
 *    work required for the animation.
 * </p>
 * 
 * <h4>Data Set Size</h4>
 * <p>As a rule of thumb, it's recommended that applications only set usable data densities on the chart. For example, 
 *    it's not recommended to show more than 500 bars on a 500 pixel wide chart, since the  bars will be unusably thin. 
 *    While there are several optimizations within the chart to deal with large data sets, it's always more efficient to 
 *    reduce the data set size as early as possible. Future optimizations will focus on improving end user experience as 
 *    well as developer productivity for common use cases.
 * </p>
 * 
 * <h4>Style Attributes</h4>
 * <p>Use the highest level options property available. For example, consider using  attributes on 
 *    <code class="prettyprint">styleDefaults</code> or <code class="prettyprint">series</code>, instead of attributes 
 *    on the data items. The chart can take advantage of these higher level attributes to apply the style properties on 
 *    containers, saving expensive DOM calls.
 * </p>
 * 
 * {@ojinclude "name":"rtl"}
 * 
 * @desc Creates a JET Chart.
 * @example <caption>Initialize the Chart with no options specified:</caption>
 * $(".selector").ojChart();
 * 
 * @example <caption>Initialize the Chart with some options:</caption>
 * $(".selector").ojChart({type: 'bar'});
 * 
 * @example <caption>Initialize the Chart via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
 * <div data-bind="ojComponent: {component: 'ojChart'}">
 */
oj.__registerWidget('oj.ojChart', $['oj']['dvtBaseComponent'],
  {
    widgetEventPrefix: "oj",
    options: {
      /**
       * Triggered when a category of data items is hidden or shown. 
       * 
       * @property {Object} ui event payload
       * @property {Object} ui.category the category that was filtered on
       * @property {string} ui.type specifies whether the category is being filtered 'in' or 'out'
       * 
       * @example <caption>Initialize the component with the <code class="prettyprint">categoryFilter</code> callback specified:</caption>
       * $(".selector").ojChart({
       *   "categoryFilter": function(event, ui){}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojcategoryfilter</code> event:</caption>
       * $(".selector").on("ojcategoryfilter", function(event, ui){});
       * 
       * @expose 
       * @event 
       * @memberof oj.ojChart
       * @instance
       * @deprecated Use the <code class="prettyprint">optionChange</code> listener to detect changes to the <code class="prettyprint">hiddenCategories</code> property instead.
       */
      categoryFilter: null,
      /**
       * Triggered when a category of data items is highlighted. 
       * 
       * @property {Object} ui event payload
       * @property {Array} ui.categories the categories that are being highlighted
       * @property {string} ui.type specifies whether highlighting is being turned 'on' or 'off'
       * 
       * @example <caption>Initialize the component with the <code class="prettyprint">categoryHighlight</code> callback specified:</caption>
       * $(".selector").ojChart({
       *   "categoryHighlight": function(event, ui){}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojcategoryhighlight</code> event:</caption>
       * $(".selector").on("ojcategoryhighlight", function(event, ui){});
       * 
       * @expose 
       * @event 
       * @memberof oj.ojChart
       * @instance
       * @deprecated Use the <code class="prettyprint">optionChange</code> listener to detect changes to the <code class="prettyprint">highlightedCategories</code> property instead.
       */
      categoryHighlight: null,
    
      /**
       * Fired whenever a supported component option changes, whether due to user interaction or programmatic 
       * intervention. If the new value is the same as the previous value, no event will be fired.
       * 
       * @property {Object} data event payload
       * @property {string} data.option the name of the option that changed, i.e. "value"
       * @property {Object} data.previousValue an Object holding the previous value of the option
       * @property {Object} data.value an Object holding the current value of the option
       * @property {Object} ui.optionMetadata information about the option that is changing
       * @property {string} ui.optionMetadata.writeback <code class="prettyprint">"shouldWrite"</code> or
       *                    <code class="prettyprint">"shouldNotWrite"</code>.  For use by the JET writeback mechanism.
       * @property {string} ui.optionMetadata.endGroup the end group of a marquee selection on a chart with categorical axis
       * @property {string} ui.optionMetadata.startGroup the start group of a marquee selection on a chart with categorical axis
       * @property {number} ui.optionMetadata.xMax the maximum x value of a marquee selection 
       * @property {number} ui.optionMetadata.xMin the minimum x value of a marquee selection 
       * @property {number} ui.optionMetadata.yMax the maximum y value of a marquee selection 
       * @property {number} ui.optionMetadata.yMin the minimum y value of a marquee selection 
       * 
       * @example <caption>Initialize the component with the <code class="prettyprint">optionChange</code> callback:</caption>
       * $(".selector").ojChart({
       *   'optionChange': function (event, data) {} 
       * });
       * 
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojoptionchange</code> event:</caption>
       * $(".selector").on({
       *   'ojoptionchange': function (event, data) {
       *       window.console.log("option changing is: " + data['option']);
       *   };
       * });
       * 
       * @expose 
       * @event 
       * @memberof oj.ojChart
       * @instance
       */
      optionChange: null,
      
      /**
       * Triggered during a selection gesture, such as a change in the marquee selection rectangle.
       * 
       * @property {Object} ui event payload
       * @property {Array} ui.items an array containing objects describing the selected data items
       * @property {string} ui.items.id the id of the data item, if one was specified
       * @property {string} ui.items.group the group of the data item
       * @property {string} ui.items.series the series of the data item
       * @property {string} ui.endGroup the end group of a marquee selection on a chart with categorical axis
       * @property {string} ui.startGroup the start group of a marquee selection on a chart with categorical axis
       * @property {number} ui.xMax the maximum x value of a marquee selection 
       * @property {number} ui.xMin the minimum x value of a marquee selection 
       * @property {number} ui.yMax the maximum y value of a marquee selection 
       * @property {number} ui.yMin the minimum y value of a marquee selection 
       * 
       * @example <caption>Initialize the component with the <code class="prettyprint">selectInput</code> callback specified:</caption>
       * $(".selector").ojChart({
       *   "selectInput": function(event, ui){}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojselectinput</code> event:</caption>
       * $(".selector").on("ojselectinput", function(event, ui){});
       * 
       * @expose 
       * @event 
       * @memberof oj.ojChart
       * @instance
       */
      selectInput: null,
      
      /**
       * Triggered after the viewport is changed due to a zoom or scroll operation.
       * 
       * @property {Object} ui event payload
       * @property {string} ui.endGroup the end group of the new viewport on a chart with categorical axis
       * @property {string} ui.startGroup the start group of the new viewport on a chart with categorical axis
       * @property {number} ui.xMax the maximum x value of the new viewport
       * @property {number} ui.xMin the minimum x value of the new viewport
       * @property {number} ui.yMax the maximum y value of the new viewport
       * @property {number} ui.yMin the minimum y value of the new viewport
       * 
       * @example <caption>Initialize the component with the <code class="prettyprint">viewportChange</code> callback specified:</caption>
       * $(".selector").ojChart({
       *   "viewportChange": function(event, ui){}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojviewportchange</code> event:</caption>
       * $(".selector").on("ojviewportchange", function(event, ui){});
       * 
       * @expose 
       * @event 
       * @memberof oj.ojChart
       * @instance
       */
      viewportChange: null,
      
      /**
       * Triggered during a viewport change gesture, such as a drag operation on the overview window. Note: There are
       * situations where the component cannot determine whether the viewport change gesture is still in progress, such
       * as with mouse wheel zoom interactions. Standard viewportChange events are fired in these cases.
       * 
       * @property {Object} ui event payload
       * @property {string} ui.endGroup the end group of the new viewport on a chart with categorical axis
       * @property {string} ui.startGroup the start group of the new viewport on a chart with categorical axis
       * @property {number} ui.xMax the maximum x value of the new viewport
       * @property {number} ui.xMin the minimum x value of the new viewport
       * @property {number} ui.yMax the maximum y value of the new viewport
       * @property {number} ui.yMin the minimum y value of the new viewport
       * 
       * @example <caption>Initialize the component with the <code class="prettyprint">viewportChangeInput</code> callback specified:</caption>
       * $(".selector").ojChart({
       *   "viewportChangeInput": function(event, ui){}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojviewportchangeinput</code> event:</caption>
       * $(".selector").on("ojviewportchangeinput", function(event, ui){});
       * 
       * @expose 
       * @event 
       * @memberof oj.ojChart
       * @instance
       */
      viewportChangeInput: null,
      
      /**
       * Triggered during a drill gesture (double click if selection is enabled, single click otherwise).
       * 
       * @property {Object} ui event payload
       * @property {string} ui.id the id of the drilled object
       * @property {string} ui.series the series name of the drilled object, if applicable
       * @property {string} ui.group the group name of the drilled object, if applicable
       * 
       * @example <caption>Initialize the component with the <code class="prettyprint">drill</code> callback specified:</caption>
       * $(".selector").ojChart({
       *   "drill": function(event, ui){}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojdrill</code> event:</caption>
       * $(".selector").on("ojdrill", function(event, ui){});
       * 
       * @expose 
       * @event 
       * @memberof oj.ojChart
       * @instance
       */
      drill: null
    },
    
    //** @inheritdoc */
    _CreateDvtComponent: function(context, callback, callbackObj) {
      return dvt.DvtChart.newInstance(context, callback, callbackObj);
    },

    //** @inheritdoc */
    _ConvertLocatorToSubId : function(locator) {
      var subId = locator['subId'];
      
      // Convert the supported locators
      if(subId == 'oj-chart-item') {
        // dataItem[seriesIndex][itemIndex]
        subId = 'dataItem[' + locator['seriesIndex'] + '][' + locator['itemIndex'] + ']';
      }
      else if(subId == 'oj-chart-group') {
        // group[index1][index2]...[indexN]
        subId = 'group' + this._GetStringFromIndexPath(locator['indexPath']);
      }
      else if(subId == 'oj-chart-series') {
        // series[index]
        subId = 'series[' + locator['index'] + ']';
      }
      else if(subId == 'oj-chart-axis-title') {
        // xAxis/yAxis/y2Axis:title
        subId = locator['axis'] + ':title';
      }
      else if(subId == 'oj-chart-reference-object') {
        // xAxis/yAxis/y2Axis:referenceObject[index]
        subId = locator['axis'] + ':referenceObject[' + locator['index'] + ']';
      }
      else if(subId == 'oj-legend-section') {
        // legend:section[sectionIndex0][sectionIndex1]...[sectionIndexN]
        subId = 'legend:section' + this._GetStringFromIndexPath(locator['indexPath']);
      }
      else if(subId == 'oj-legend-item') {
        // legend:section[sectionIndex0][sectionIndex1]...[sectionIndexN]:item[itemIndex]
        subId = 'legend:section' + this._GetStringFromIndexPath(locator['sectionIndexPath']);
        subId += ':item[' + locator['itemIndex'] + ']';
      }
      else if(subId == 'oj-chart-tooltip') {
        subId = 'tooltip';
      }
      // Return the converted result or the original subId if a supported locator wasn't recognized. We will remove
      // support for the old subId syntax in 1.2.0.
      return subId;
    },
    
    //** @inheritdoc */
    _ConvertSubIdToLocator : function(subId) {
      var locator = {};
      
      if(subId.indexOf('dataItem') == 0) {
        // dataItem[seriesIndex][itemIndex]
        var indexPath = this._GetIndexPath(subId);
        
        locator['subId'] = 'oj-chart-item';
        locator['seriesIndex'] = indexPath[0];
        locator['itemIndex'] = indexPath[1];
      }
      else if(subId.indexOf('group') == 0) {
        // group[index1][index2]...[indexN]
        locator['subId'] = 'oj-chart-group';
        locator['indexPath'] = this._GetIndexPath(subId);
      }
      else if(subId.indexOf('series') == 0) {
        // series[index]
        locator['subId'] = 'oj-chart-series';
        locator['index'] = this._GetFirstIndex(subId);
      }
      else if(subId.indexOf('Axis:title') > 0) {
        // xAxis/yAxis/y2Axis:title
        locator['subId'] = 'oj-chart-axis-title';
        locator['axis'] = subId.substring(0, subId.indexOf(':'));
      }
      else if(subId.indexOf('Axis:referenceObject') > 0) {
        // xAxis/yAxis/y2Axis:referenceObject[index]
        locator['subId'] = 'oj-chart-reference-object';
        locator['axis'] = subId.substring(0, subId.indexOf(':'));
        locator['index'] = this._GetFirstIndex(subId);
      }
      else if(subId.indexOf('legend') == 0) {
        if(subId.indexOf(':item') > 0) {
          // legend:section[sectionIndex0][sectionIndex1]...[sectionIndexN]:item[itemIndex]
          var itemStartIndex = subId.indexOf(':item');
          var sectionSubstr = subId.substring(0, itemStartIndex);
          var itemSubstr = subId.substring(itemStartIndex);
          
          locator['subId'] = 'oj-legend-item';
          locator['sectionIndexPath'] = this._GetIndexPath(sectionSubstr);
          locator['itemIndex'] = this._GetFirstIndex(itemSubstr);
        }
        else if(subId.indexOf('section') == 0) {
          // legend:section[sectionIndex0][sectionIndex1]...[sectionIndexN]
          locator['subId'] = 'oj-legend-section';
          locator['indexPath'] = this._GetIndexPath(subId);
        }
      }
      else if(subId == 'tooltip') {
        locator['subId'] = 'oj-chart-tooltip';
      }
    
      return locator;
    },

    //** @inheritdoc */
    _ProcessStyles: function() {
      this._super();
      if (!this.options['styleDefaults'])
        this.options['styleDefaults'] = {};
      if (!this.options['styleDefaults']['colors']) {
        var handler = new oj.ColorAttributeGroupHandler();
        // override default colors with css attribute group colors
        this.options['styleDefaults']['colors'] = handler.getValueRamp();
      }
    },
    
    //** @inheritdoc */
    _GetComponentStyleClasses: function() {
      var styleClasses = this._super();
      styleClasses.push('oj-chart');
      return styleClasses;
    },
    
    //** @inheritdoc */
    _GetChildStyleClasses: function() {
      var styleClasses = this._super();
      styleClasses['oj-chart-footnote'] = {'path': 'footnote/style', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-chart-slice-label'] = {'path': 'styleDefaults/sliceLabelStyle', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-chart-subtitle'] = {'path': 'subtitle/style', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-chart-title'] = {'path': 'title/style', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-chart-xaxis-tick-label'] = {'path': 'xAxis/tickLabel/style', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-chart-xaxis-title'] = {'path': 'xAxis/titleStyle', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-chart-yaxis-tick-label'] = {'path': 'yAxis/tickLabel/style', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-chart-yaxis-title'] = {'path': 'yAxis/titleStyle', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-chart-y2axis-tick-label'] = {'path': 'y2Axis/tickLabel/style', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-chart-y2axis-title'] = {'path': 'y2Axis/titleStyle', 'property': 'CSS_TEXT_PROPERTIES'};
      
      styleClasses['oj-chart-stock-falling'] = {'path': 'styleDefaults/stockFallingColor', 'property': 'background-color'};
      styleClasses['oj-chart-stock-range'] = {'path': 'styleDefaults/stockRangeColor', 'property': 'background-color'};
      styleClasses['oj-chart-stock-rising'] = {'path': 'styleDefaults/stockRisingColor', 'property': 'background-color'};
      
      // Images
      styleClasses['oj-chart-pan-icon'] = {'path': '_resources/panUp', 'property': 'CSS_URL'};
      styleClasses['oj-chart-pan-icon oj-active'] = {'path': '_resources/panDown', 'property': 'CSS_URL'};
      styleClasses['oj-chart-select-icon'] = {'path': '_resources/selectUp', 'property': 'CSS_URL'};
      styleClasses['oj-chart-select-icon oj-active'] = {'path': '_resources/selectDown', 'property': 'CSS_URL'};
      styleClasses['oj-chart-zoom-icon'] = {'path': '_resources/zoomUp', 'property': 'CSS_URL'};
      styleClasses['oj-chart-zoom-icon oj-active'] = {'path': '_resources/zoomDown', 'property': 'CSS_URL'};
      
      // Legend
      styleClasses['oj-legend'] = {'path': 'legend/textStyle', 'property': 'CSS_TEXT_PROPERTIES'};
      styleClasses['oj-legend-title'] = {'path': 'legend/titleStyle', 'property': 'CSS_TEXT_PROPERTIES'};
      
      styleClasses['oj-legend-section-close-icon'] = {'path': 'legend/_resources/closedEnabled', 'property': 'CSS_URL'};
      styleClasses['oj-legend-section-close-icon oj-hover'] = {'path': 'legend/_resources/closedOver', 'property': 'CSS_URL'};
      styleClasses['oj-legend-section-close-icon oj-active'] = {'path': 'legend/_resources/closedDown', 'property': 'CSS_URL'};
      
      styleClasses['oj-legend-section-open-icon'] = {'path': 'legend/_resources/openEnabled', 'property': 'CSS_URL'};
      styleClasses['oj-legend-section-open-icon oj-hover'] = {'path': 'legend/_resources/openOver', 'property': 'CSS_URL'};
      styleClasses['oj-legend-section-open-icon oj-active'] = {'path': 'legend/_resources/openDown', 'property': 'CSS_URL'};
      
      return styleClasses;
    },
    
    //** @inheritdoc */
    _GetEventTypes : function() {
      return ['categoryFilter', 'categoryHighlight', 'drill', 'optionChange', 'selectInput', 
              'viewportChange', 'viewportChangeInput'];
    },
    
    //** @inheritdoc */
    _GetTranslationMap: function() {
      // The translations are stored on the options object.
      var translations = this.options['translations'];
    
      // Safe to modify super's map because function guarentees a new map is returned
      var ret = this._super();
      ret['DvtChartBundle.DEFAULT_GROUP_NAME'] = this._GetTranslatedResource('labelDefaultGroupName', ['groupName']);
      ret['DvtChartBundle.LABEL_SERIES'] = translations['labelSeries'];
      ret['DvtChartBundle.LABEL_GROUP'] = translations['labelGroup'];
      ret['DvtChartBundle.LABEL_VALUE'] = translations['labelValue'];
      ret['DvtChartBundle.LABEL_TARGET_VALUE'] = translations['labelTargetValue'];
      ret['DvtChartBundle.LABEL_X'] = translations['labelX'];
      ret['DvtChartBundle.LABEL_Y'] = translations['labelY'];
      ret['DvtChartBundle.LABEL_Z'] = translations['labelZ'];
      ret['DvtChartBundle.LABEL_PERCENTAGE'] = translations['labelPercentage'];
      ret['DvtChartBundle.LABEL_LOW'] = translations['labelLow'];
      ret['DvtChartBundle.LABEL_HIGH'] = translations['labelHigh'];
      ret['DvtChartBundle.LABEL_OPEN'] = translations['labelOpen'];
      ret['DvtChartBundle.LABEL_CLOSE'] = translations['labelClose'];
      ret['DvtChartBundle.LABEL_VOLUME'] = translations['labelVolume'];
      ret['DvtChartBundle.LABEL_MIN'] = translations['labelMin'];
      ret['DvtChartBundle.LABEL_MAX'] = translations['labelMax'];
      ret['DvtChartBundle.LABEL_OTHER'] = translations['labelOther'];
      ret['DvtChartBundle.PAN'] = translations['tooltipPan'];
      ret['DvtChartBundle.MARQUEE_SELECT'] = translations['tooltipSelect'];
      ret['DvtChartBundle.MARQUEE_ZOOM'] = translations['tooltipZoom'];
      ret['DvtUtilBundle.CHART'] = translations['componentName'];
      
      // Modify groupName for backwards compatibility
      translations['labelDefaultGroupName'] = translations['labelDefaultGroupName'].replace('{groupName}', '{0}');
      
      return ret;
    },
  
    //** @inheritdoc */
    _HandleEvent: function(event) {
      // TODO: Use DvtOptionChangeEvent whenever possible
      
      var type = event && event.getType ? event.getType() : null;
      if (type === dvt.DvtSelectionEvent.TYPE || type === dvt.DvtSelectionEvent.TYPE_INPUT) {
        var selection = event.getSelection();
        if (selection) {
          // Convert the graph selection context into the JET context
          var selectedItems = [];
          for (var i = 0; i < selection.length; i++) {
            var selectedItem = {'id': selection[i].getId(),
              'series': selection[i].getSeries(),
              'group': selection[i].getGroup()};
            selectedItems.push(selectedItem);
          }
          
          var selectPayload = {
            'endGroup': event.getEndGroup(), 'startGroup': event.getStartGroup(),
            'xMax': event.getXMax(), 'xMin': event.getXMin(),
            'yMax': event.getYMax(), 'yMin': event.getYMin()
          };
          
          // Update the options selection state if this is a non-input event
          if(type === dvt.DvtSelectionEvent.TYPE) {
            this._UserOptionChange('selection', selectedItems, selectPayload);
          } else {
            selectPayload['items'] = selectedItems;
            this._trigger('selectInput', null, selectPayload);
          }
        }
      }
      else if (type === dvt.DvtCategoryHideShowEvent.TYPE_HIDE || type === dvt.DvtCategoryHideShowEvent.TYPE_SHOW) {
        var filterType = (type === dvt.DvtCategoryHideShowEvent.TYPE_HIDE) ? 'out' : 'in';
        this._trigger('categoryFilter', null, {'category': event.getCategory(), 'type': filterType});
        this._UserOptionChange('hiddenCategories', event['hiddenCategories']);
      }
      else if (type === dvt.DvtCategoryRolloverEvent.TYPE_OVER || type === dvt.DvtCategoryRolloverEvent.TYPE_OUT) {
        var highlightType = (type === dvt.DvtCategoryRolloverEvent.TYPE_OVER) ? 'on' : 'off';
        this._trigger('categoryHighlight', null, {'categories': event['categories'], 'type': highlightType});
        this._UserOptionChange('highlightedCategories', event['categories']);
      }
      else if (type === dvt.DvtChartViewportChangeEvent.TYPE || type === dvt.DvtChartViewportChangeEvent.TYPE_INPUT) {
        var viewportChangePayload = {'endGroup': event.getEndGroup(), 'startGroup': event.getStartGroup(),
                                     'xMax': event.getXMax(), 'xMin': event.getXMin(),
                                     'yMax': event.getYMax(), 'yMin': event.getYMin()};
          
        // Maintain the viewport state
        // TODO we should be firing option change event for this, but it doesn't support nested props yet.
        if(type === dvt.DvtChartViewportChangeEvent.TYPE) {  
          // Ensure the axis options both exist
          if(!this.options['xAxis'])
            this.options['xAxis'] = {};
            
          if(!this.options['yAxis'])
            this.options['yAxis'] = {};
          
          // X-Axis: Clear the start and end group because min/max more accurate.
          this.options['xAxis']['viewportStartGroup'] = null;
          this.options['xAxis']['viewportEndGroup'] = null;
          if(event.getXMin() != null && event.getXMax() != null) {
            this.options['xAxis']['viewportMin'] = event.getXMin();
            this.options['xAxis']['viewportMax'] = event.getXMax();
          }
          
          // Y-Axis
          if(event.getYMin() != null && event.getYMax() != null) {
            this.options['yAxis']['viewportMin'] = event.getYMin();
            this.options['yAxis']['viewportMax'] = event.getYMax();
          }
        }
          
        this._trigger(type === dvt.DvtChartViewportChangeEvent.TYPE ? 'viewportChange' : 'viewportChangeInput', null, viewportChangePayload);
      }
      else if (type === dvt.DvtDrillEvent.TYPE) {
        this._trigger('drill', null, {'id': event.getId(), 'series': event.getSeries(), 'group': event.getGroup()});
      }
      else {
        this._super(event);
      }
    },
    
    //** @inheritdoc */
    _LoadResources: function() {
      // Ensure the resources object exists
      if (this.options['_resources'] == null)
        this.options['_resources'] = {};

      var resources = this.options['_resources'];

      // Add images
      // TODO these should be defined in the skin instead
      resources['overviewGrippy'] = oj.Config.getResourceUrl('resources/internal-deps/dvt/chart/drag_horizontal.png');

      // Add cursors
      resources['panCursorDown'] = oj.Config.getResourceUrl('resources/internal-deps/dvt/chart/hand-closed.cur');
      resources['panCursorUp'] = oj.Config.getResourceUrl('resources/internal-deps/dvt/chart/hand-open.cur');
    },
    
    /**
     * Returns the chart title. 
     * @return {String} The chart title
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getTitle: function() {
      var auto = this._component.getAutomation();
      return auto.getTitle();
    },
    
    /**
     * Returns the group corresponding to the given index
     * @param {String} groupIndex the group index
     * @return {String} The group name corresponding to the given group index
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getGroup: function(groupIndex) {
      var auto = this._component.getAutomation();
      return auto.getGroup(groupIndex);
    },
    
    /**
     * Returns the series corresponding to the given index
     * @param {String} seriesIndex the series index
     * @return {String} The series name corresponding to the given series index
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getSeries: function(seriesIndex) {
      var auto = this._component.getAutomation();
      return auto.getSeries(seriesIndex);
    },
    
    /**
     * Returns number of groups in the chart data
     * @return {Number} The number of groups
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getGroupCount: function() {
      var auto = this._component.getAutomation();
      return auto.getGroupCount();
    },
    
    /**
     * Returns number of series in the chart data
     * @return {Number} The number of series
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getSeriesCount: function() {
      var auto = this._component.getAutomation();
      return auto.getSeriesCount();
    },
    
    /**
     * Returns an object with the following properties for automation testing verification of the data item with
     * the specified series and group indices.
     * 
     * @param {number} seriesIndex
     * @param {number} groupIndex
     * @property {string} borderColor
     * @property {string} color
     * @property {number} close The closing value for a stock item
     * @property {string} group The group id.
     * @property {number} high The high value for a range or stock item
     * @property {string} label
     * @property {number} low  The low value for a range or stock item
     * @property {number} open The opening value for a stock item
     * @property {boolean} selected
     * @property {string} series The series id.
     * @property {number} targetValue The target value for a funnel slice.
     * @property {string} tooltip
     * @property {number} value
     * @property {number} volume  The volume of a stock item
     * @property {number} x
     * @property {number} y
     * @property {number} z 
     * @property {Function} getBorderColor <b>Deprecated</b>: Use <code class="prettyprint">borderColor</code> instead.
     * @property {Function} getColor <b>Deprecated</b>: Use <code class="prettyprint">color</code> instead.
     * @property {Function} getGroup <b>Deprecated</b>: Use <code class="prettyprint">group</code> instead.
     * @property {Function} getLabel <b>Deprecated</b>: Use <code class="prettyprint">label</code> instead.
     * @property {Function} getSeries <b>Deprecated</b>: Use <code class="prettyprint">series</code> instead.
     * @property {Function} getTargetValue <b>Deprecated</b>: Use <code class="prettyprint">targetValue</code> instead.
     * @property {Function} getTooltip <b>Deprecated</b>: Use <code class="prettyprint">tooltip</code> instead.
     * @property {Function} getValue <b>Deprecated</b>: Use <code class="prettyprint">value</code> instead.
     * @property {Function} getX <b>Deprecated</b>: Use <code class="prettyprint">x</code> instead.
     * @property {Function} getY <b>Deprecated</b>: Use <code class="prettyprint">y</code> instead.
     * @property {Function} getZ <b>Deprecated</b>: Use <code class="prettyprint">z</code> instead.
     * @property {Function} isSelected <b>Deprecated</b>: Use <code class="prettyprint">selected</code> instead.
     * @return {Object|null} An object containing properties for the data item, or null if none exists.
     * @expose
     * @instance
     * @memberof oj.ojChart
     */    
    getDataItem: function(seriesIndex, groupIndex) {
      var ret = this._component.getAutomation().getDataItem(seriesIndex, groupIndex);
      
      // Bug 20884377: Provide backwards compatibility for getters until 1.2.0.
      this._AddAutomationGetters(ret);
      
      return ret;
    },
    
    /**
     * Returns an object with the following properties for automation testing verification of the chart legend.
     * 
     * @property {Object} bounds An object containing the bounds of the legend.
     * @property {number} bounds.x
     * @property {number} bounds.y
     * @property {number} bounds.width
     * @property {number} bounds.height
     * @property {string} title
     * @property {Function} getBounds <b>Deprecated</b>: Use <code class="prettyprint">bounds</code> instead.
     * @property {Function} getTitle <b>Deprecated</b>: Use <code class="prettyprint">title</code> instead.
     * @return {Object} An object containing properties for the chart legend.
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getLegend: function() {
      var ret = this._component.getAutomation().getLegend();
      
      // Bug 20884377: Provide backwards compatibility for getters until 1.2.0.
      this._AddAutomationGetters(ret);
      
      return ret;
    },
    
    /**
     * Returns an object with the following properties for automation testing verification of the chart plot area.
     * 
     * @property {Object} bounds An object containing the bounds of the plot area.
     * @property {number} bounds.x
     * @property {number} bounds.y
     * @property {number} bounds.width
     * @property {number} bounds.height
     * @property {Function} getBounds <b>Deprecated</b>: Use <code class="prettyprint">bounds</code> instead.
     * @return {Object} An object containing properties for the chart plot area.
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getPlotArea: function() {
      var ret = this._component.getAutomation().getPlotArea();
      
      // Bug 20884377: Provide backwards compatibility for getters until 1.2.0.
      this._AddAutomationGetters(ret);
      
      return ret;
    },
    
    /**
     * Returns an object with the following properties for automation testing verification of the x axis.
     * 
     * @property {Object} bounds An object containing the bounds of the legend.
     * @property {number} bounds.x
     * @property {number} bounds.y
     * @property {number} bounds.width
     * @property {number} bounds.height
     * @property {string} title
     * @property {Function(number, number)} getPreferredSize Returns the preferred size of the axis, given the available 
     *   width and height. This value can be passed into the <code class="prettyprint">size</code> and 
     *   <code class="prettyprint">maxSize</code> options of the axis. A re-render must be triggered by calling 
     *   <code class="prettyprint">refresh</code> after invoking this function.
     * @property {number} getPreferredSize.width
     * @property {number} getPreferredSize.height
     * @property {Function} getBounds <b>Deprecated</b>: Use <code class="prettyprint">bounds</code> instead.
     * @property {Function} getTitle <b>Deprecated</b>: Use <code class="prettyprint">title</code> instead.
     * @return {Object} An object containing properties for the x axis.
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getXAxis: function() {
      var ret = this._component.getAutomation().getXAxis();
      
      // Bug 20884377: Provide backwards compatibility for getters until 1.2.0.
      this._AddAutomationGetters(ret);
      
      return ret;
    },
    
    /**
     * Returns an object with the following properties for automation testing verification of the y axis.
     * 
     * @property {Object} bounds An object containing the bounds of the legend.
     * @property {number} bounds.x
     * @property {number} bounds.y
     * @property {number} bounds.width
     * @property {number} bounds.height
     * @property {string} title
     * @property {Function(number, number)} getPreferredSize Returns the preferred size of the axis, given the available 
     *   width and height. This value can be passed into the <code class="prettyprint">size</code> and 
     *   <code class="prettyprint">maxSize</code> options of the axis. A re-render must be triggered by calling 
     *   <code class="prettyprint">refresh</code> after invoking this function.
     * @property {number} getPreferredSize.width
     * @property {number} getPreferredSize.height
     * @property {Function} getBounds <b>Deprecated</b>: Use <code class="prettyprint">bounds</code> instead.
     * @property {Function} getTitle <b>Deprecated</b>: Use <code class="prettyprint">title</code> instead.
     * @return {Object} An object containing properties for the y axis.
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getYAxis: function() {
      var ret = this._component.getAutomation().getYAxis();
      
      // Bug 20884377: Provide backwards compatibility for getters until 1.2.0.
      this._AddAutomationGetters(ret);
      
      return ret;
    },
    
    /**
     * Returns an object with the following properties for automation testing verification of the y2 axis.
     * 
     * @property {Object} bounds An object containing the bounds of the legend.
     * @property {number} bounds.x
     * @property {number} bounds.y
     * @property {number} bounds.width
     * @property {number} bounds.height
     * @property {string} title
     * @property {Function(number, number)} getPreferredSize Returns the preferred size of the axis, given the available 
     *   width and height. This value can be passed into the <code class="prettyprint">size</code> and 
     *   <code class="prettyprint">maxSize</code> options of the axis. A re-render must be triggered by calling 
     *   <code class="prettyprint">refresh</code> after invoking this function.
     * @property {number} getPreferredSize.width
     * @property {number} getPreferredSize.height
     * @property {Function} getBounds <b>Deprecated</b>: Use <code class="prettyprint">bounds</code> instead.
     * @property {Function} getTitle <b>Deprecated</b>: Use <code class="prettyprint">title</code> instead.
     * @return {Object} An object containing properties for the y2 axis.
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getY2Axis: function() {
      var ret = this._component.getAutomation().getY2Axis();
      
      // Bug 20884377: Provide backwards compatibility for getters until 1.2.0.
      this._AddAutomationGetters(ret);
      
      return ret;
    },
    
    /**
     * Returns the x, y, and y2 axis values at the specified X and Y coordinate.
     * @param {Number} x The X coordinate relative to the component.
     * @param {Number} y The Y coordinate relative to the component.
     * @return {Object} An object containing the "x", "y", and "y2" axis values.
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getValuesAt: function(x, y) {
      return this._component.getValuesAt(x, y);
    },
    
    /** 
     * {@ojinclude "name":"nodeContextDoc"}
     * @param {!Element} node - {@ojinclude "name":"nodeContextParam"}
     * @returns {Object|null} {@ojinclude "name":"nodeContextReturn"}
     * 
     * @example {@ojinclude "name":"nodeContextExample"}
     * 
     * @expose
     * @instance
     * @memberof oj.ojChart
     */
    getContextByNode: function(node)
    {
      // context objects are documented with @ojnodecontext
      var context = this.getSubIdByNode(node);
      if (context && context['subId'] !== 'oj-chart-tooltip')
        return context;
    
      return null;
    }
  });