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

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

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

/**
 * @ojcomponent oj.ojThematicMap
 * @augments oj.dvtBaseComponent
 * @since 0.7
 *  
 * @classdesc
 * <h3 id="thematicMapOverview-section">
 *   JET Thematic Map Component
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#thematicMapOverview-section"></a>
 * </h3>
 * 
 * <p>Thematic Map component for JET. Thematic maps are used to display data corresponding to a geographic location
 * or region, such as election data for a state or sales by territory for a product.</p>
 * 
 * {@ojinclude "name":"warning"}
 * 
 * <pre class="prettyprint">
 * <code>
 * <div data-bind="ojComponent: {
 *   component: 'ojThematicMap',
 *   basemap: 'usa',
 *   areaLayers: [{
 *     layer: 'states',
 *     areaDataLayer: {
 *       areas: [{color:'#003366', location:'FL'},
 *               {color:'#CC3300', location:'TX'},
 *               {color:'#99CC33', location:'CA'}]
 *     }
 *   }]
 * }"/>
 * </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. Alternate visualizations should
 *    be considered if identifying data changes is important, since all data items will generally move and resize on any data
 *    change.
 * </p>
 * 
 * <h4>Style Attributes</h4>
 * <p>Use the highest level options property available. For example, consider using  attributes on 
 *    <code class="prettyprint">styleDefaults.dataAreaDefaults</code> or 
 *    <code class="prettyprint">styleDefaults.dataMarkerDefaults</code>, instead of attributes on the individual items. The component can 
 *    take advantage of these higher level attributes to apply the style properties on containers, saving expensive DOM 
 *    calls.
 * </p>
 * 
 * {@ojinclude "name":"rtl"}
 * 
 * <h3 id="basemap-section">
 *   Built-in Maps
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#basemap-section"></a>
 * </h3>
 * 
 * <p>
 *  Thematic map has several built-in maps which can be chosen using the
 *  [basemap]{@link oj.ojThematicMap#basemap} and [layer]{@link oj.ojThematicMap#areaLayers.layer} 
 *  attributes in the options object.
 * </p>
 * 
 * <p>
 *  To use one of the built-in maps, set the [basemap]{@link oj.ojThematicMap#basemap} 
 *  attribute to one of the supported values:
 *  <ul>
 *    <li>africa</li>
 *    <li>asia</li>
 *    <li>australia</li>
 *    <li>europe</li>
 *    <li>northAmerica</li>
 *    <li>southAmerica</li>
 *    <li>usa</li>
 *    <li>world</li>
 *  </ul>
 * </p>
 * 
 * <p>
 *  Create an area layer object and pass it in an array to the 
 *  [areaLayers]{@link oj.ojThematicMap#areaLayers} attribute and set the area layer object's 
 *  [layer]{@link oj.ojThematicMap#areaLayers.layer} attribute to one of the 
 *  supported values for that basemap.
 *  <ul>
 *    <li>usa
 *    <ul>
 *      <li>states</li>
 *      <li>counties</li>
 *    </ul>
 *  </li>
 *  <li>africa, asia, australia, europe, northAmerica, southAmerica, world
 *    <ul>
 *      <li>countries</li>
 *      <li>continents (world basemap)</li>
 *      <li>continent (all other basemaps)</li>
 *    </ul>
 *  </li>
 * </ul>
 * </p>
 * 
 * <p>
 *  Each built-in map layer has a list of supported location IDs that should be 
 *  set as the value for an [area]{@link oj.ojThematicMap#areaLayers.areaDataLayer.areas} or 
 *  [marker]{@link oj.ojThematicMap#areaLayers.areaDataLayer.markers} object's 
 *  [location]{@link oj.ojThematicMap#areaLayers.areaDataLayer.areas.location} attribute.
 * </p>
 * <p>
 *  Continent basemaps support the following IDs:
 *  <ul>
 *    <li>Africa: AF</li>
 *    <li>Asia: AS</li>
 *    <li>Australia: AU</li>
 *    <li>Europe: EU</li>
 *    <li>North America: NA</li>
 *    <li>South America: SA</li>
 *  </ul>
 * </p>
 * <p>
 *  All other basemaps support the following IDs. See the Thematic Map <a href="../uiComponents-thematicMap-basemaps.html">Basemap Demo</a> for an example.
 *  <ul>
 *    <li><a href="dvt/thematicMap/africa-countries-ids.json">Africa Countries</a></li>
 *    <li><a href="dvt/thematicMap/asia-countries-ids.json">Asia Countries</a></li>
 *    <li><a href="dvt/thematicMap/australia-countries-ids.json">Australia Countries</a></li>
 *    <li><a href="dvt/thematicMap/europe-countries-ids.json">Europe Countries</a></li>
 *    <li><a href="dvt/thematicMap/northAmerica-countries-ids.json">North America Countries</a></li>
 *    <li><a href="dvt/thematicMap/southAmerica-countries-ids.json">South America Countries</a></li>
 *    <li><a href="dvt/thematicMap/usa-counties-ids.json">USA Counties</a></li>
 *    <li><a href="dvt/thematicMap/usa-states-ids.json">USA States</a></li>
 *    <li><a href="dvt/thematicMap/world-countries-ids.json">World Countries</a></li>
 *  </ul>
 * </p>
 * 
 * <h3 id="cities-section">
 *   Built-in Cities
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#cities-section"></a>
 * </h3>
 * 
 * <p>
 *  Thematic map also supports a set of built-in cities for each basemap that can
 *  be used in a marker object's [location]{@link oj.ojThematicMap#pointDataLayers.markers.location}
 *  attribute instead of passing in longitude and latitude coordinates.
 *  See the Thematic Map <a href="../uiComponents-thematicMap-points.html">Points Demo</a> for an example.
 *  <ul>
 *    <li><a href="dvt/thematicMap/africa-cities-ids.json">Africa Cities</a></li>
 *    <li><a href="dvt/thematicMap/asia-cities-ids.json">Asia Cities</a></li>
 *    <li><a href="dvt/thematicMap/australia-cities-ids.json">Australia Cities</a></li>
 *    <li><a href="dvt/thematicMap/europe-cities-ids.json">Europe Cities</a></li>
 *    <li><a href="dvt/thematicMap/northAmerica-cities-ids.json">North America Cities</a></li>
 *    <li><a href="dvt/thematicMap/southAmerica-cities-ids.json">South America Cities</a></li>
 *    <li><a href="dvt/thematicMap/usa-cities-ids.json">USA Cities</a></li>
 *    <li><a href="dvt/thematicMap/world-cities-ids.json">World Cities</a></li>
 *  </ul>
 * </p>
 * 
 * @desc Creates a JET Thematic Map.
 * @example <caption>Initialize the Thematic Map with no options specified:</caption>
 * $(".selector").ojThematicMap();
 * 
 * @example <caption>Initialize the Thematic Map with some options:</caption>
 * $(".selector").ojThematicMap({basemap: 'usa'});
 * 
 * @example <caption>Initialize the Thematic Map via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
 * <div data-bind="ojComponent: {component: 'ojThematicMap'}">
 */
oj.__registerWidget('oj.ojThematicMap', $['oj']['dvtBaseComponent'],
  {
    widgetEventPrefix: "oj",
    options: {
      /**
       * Triggered after thematic map resources are loaded and the component has rendered.
       * 
       * @example <caption>Initialize the component with the <code class="prettyprint">select</code> callback specified:</caption>
       * $(".selector").ojThematicMap({
       *   "load": function(){}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojselect</code> event:</caption>
       * $(".selector").on("ojload", function(){});
       * 
       * @expose 
       * @event 
       * @memberof oj.ojThematicMap
       * @instance
       */
      load: 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.
       * 
       * @example <caption>Initialize the component with the <code class="prettyprint">optionChange</code> callback:</caption>
       * $(".selector").ojThematicMap({
       *   '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.ojThematicMap
       * @instance
       */
      optionChange: null
    },
    _currentLocale : '',
    _loadedBasemaps : [],
    _supportedLocales: {
        'ar' : 'ar',
        'cs' : 'cs',
        'da' : 'da',
        'de' : 'de',
        'el' : 'el',
        'es' : 'es',
        'fi' : 'fi',
        'fr' : 'fr',
        'fr-ca' : 'fr_CA',
        'he' : 'iw',
        'hu' : 'hu',
        'it' : 'it', 
        'ja' : 'ja', 
        'ko' : 'ko',
        'nl' : 'nl',
        'no' : 'no',
        'pl' : 'pl',
        'pt' : 'pt_BR',
        'pt-pt' : 'pt',
        'ro' : 'ro',
        'ru' : 'ru',
        'sk' : 'sk',
        'sv' : 'sv',
        'th' : 'th',
        'tr' : 'tr',
        'zh-hans' : 'zh_CN', 
        'zh-hant' : 'zh_TW'},
    
    //** @inheritdoc */
    _ComponentCreate : function() {
      this._checkBasemaps = [];
      this._initiallyRendered = false;
      this._dataLayersToUpdate = [];
      this._super();
    },
    
    //** @inheritdoc */
    _CreateDvtComponent: function(context, callback, callbackObj) {
      return dvt.DvtThematicMap.newInstance(context, callback, callbackObj);
    },
    
    //** @inheritdoc */
    _ConvertLocatorToSubId : function(locator) {
      var subId = locator['subId'];
      
      // Convert the supported locators
      if(subId == 'oj-thematicmap-area') {
        // dataLayerId:area[index]
        subId = locator['dataLayer'] + ':area[' + locator['index'] + ']';
      }
      else if(subId == 'oj-thematicmap-marker') {
        // dataLayerId:marker[index]
        subId = locator['dataLayer'] + ':marker[' + locator['index'] + ']';
      }
      else if(subId == 'oj-thematicmap-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(':area') > 0) {
        // dataLayerId:area[index]
        locator['subId'] = 'oj-thematicmap-area';
        locator['dataLayer'] = subId.substring(0, subId.indexOf(':'));
        locator['index'] = this._GetFirstIndex(subId);
      }
      else if(subId.indexOf(':marker') > 0) {
        // dataLayerId:marker[index]
        locator['subId'] = 'oj-thematicmap-marker';
        locator['dataLayer'] = subId.substring(0, subId.indexOf(':'));
        locator['index'] = this._GetFirstIndex(subId);
      }
      else if(subId == 'tooltip') {
        locator['subId'] = 'oj-thematicmap-tooltip';
      }
    
      return locator;
    },
    
    //** @inheritdoc */
    _GetComponentStyleClasses: function() {
      var styleClasses = this._super();
      styleClasses.push('oj-thematicmap');
      return styleClasses;
    },
    
    //** @inheritdoc */
    _GetChildStyleClasses: function() {
      var styleClasses = this._super();
      styleClasses['oj-dvtbase oj-thematicmap'] = {'path': 'animationDuration', 'property': 'animation-duration'};
      styleClasses['oj-thematicmap-arealayer'] = [
        {'path': 'styleDefaults/areaStyle', 'property': 'CSS_BACKGROUND_PROPERTIES'},
        {'path': 'styleDefaults/labelStyle', 'property': 'CSS_TEXT_PROPERTIES'}
      ];
      styleClasses['oj-thematicmap-area'] = {'path': 'styleDefaults/dataAreaDefaults/borderColor', 'property': 'border-top-color'};
      styleClasses['oj-thematicmap-area oj-hover'] = {'path': 'styleDefaults/dataAreaDefaults/hoverColor', 'property': 'border-top-color'};
      styleClasses['oj-thematicmap-area oj-selected'] = [
        {'path': 'styleDefaults/dataAreaDefaults/selectedInnerColor', 'property': 'border-top-color'},
        {'path': 'styleDefaults/dataAreaDefaults/selectedOuterColor', 'property': 'border-bottom-color'}];
      styleClasses['oj-thematicmap-marker'] = [
        {'path': 'styleDefaults/dataMarkerDefaults/labelStyle', 'property': 'CSS_TEXT_PROPERTIES'},
        {'path': 'styleDefaults/dataMarkerDefaults/color', 'property': 'background-color'},
        {'path': 'styleDefaults/dataMarkerDefaults/opacity', 'property': 'opacity'},
        {'path': 'styleDefaults/dataMarkerDefaults/borderColor', 'property': 'border-top-color'}
      ];
      return styleClasses;
    },
    
    //** @inheritdoc */
    _GetEventTypes : function() {
      return ['load', 'optionChange'];
    },
    
    //** @inheritdoc */
    _setOptions : function (options, flags) {
      // determine if option change is a data layer update and save data to call data layer update API instead of render in _Render
      var numUpdates = Object.keys(options).length;
      var newAreaLayers = options['areaLayers'];
      var oldAreaLayers = this.options['areaLayers'];
      var pointDataLayers = options['pointDataLayers'];
      if (numUpdates == 1 && newAreaLayers && oldAreaLayers && oldAreaLayers.length > 0) {
        for (var i = 0; i < newAreaLayers.length; i++) {
          var newAreaLayer = newAreaLayers[i];
          var currAreaLayer = oldAreaLayers[i];
          var updateDataLayer = true;
          for (var areaLayerKey in newAreaLayer) {
            // check to see if option update is a data layer update by seeing if any other area layer property is changed
            if (areaLayerKey != 'areaDataLayer' && newAreaLayer[areaLayerKey] != currAreaLayer[areaLayerKey]) {
              updateDataLayer = updateDataLayer && false;
            }
          }
          if (updateDataLayer && !oj.Object.compareValues(currAreaLayer['areaDataLayer'], newAreaLayer['areaDataLayer'])) {
            this._dataLayersToUpdate.push({'options': newAreaLayer['areaDataLayer'], 'parentLayer': newAreaLayer['layer'], 'isADL': true});
          }
        }
      }
      else if (numUpdates == 1 && pointDataLayers && this.options['pointDataLayers'] && this.options['pointDataLayers'].length > 0) {
        for (var i = 0; i < pointDataLayers.length; i++) {
          if (!oj.Object.compareValues(this.options['pointDataLayers'][i], pointDataLayers[i])) {
            this._dataLayersToUpdate.push({'options': pointDataLayers[i], 'parentLayer': pointDataLayers[i]['id'], 'isADL': false});
          }
        }
      }
      this._super(options, flags);
     },
    
    //** @inheritdoc */
    _Render: function() {
      // For thematic map, we must ensure that all basemaps are loaded before rendering.  If basemaps are still loading,
      // return and wait for the load listener to call _Render again.
      this._loadBasemap();
      for (var i = 0; i < this._checkBasemaps.length; i++) {
        if (!this._loadedBasemaps[this._checkBasemaps[i]])
          return;
      }
      this._checkBasemaps = [];
      
      // parse the top level selection map and populate data layer selection option 
      this._updateDataLayerSelection();
      
      // call custom renderers
      var areaLayers = this.options['areaLayers'];
      if (areaLayers) {
        for (var i = 0; i < areaLayers.length; i++) {
          var areaDataLayer = areaLayers[i]['areaDataLayer'];
          if (areaDataLayer) {
            var renderer = areaDataLayer['_templateRenderer'];
            if (renderer)
              areaDataLayer['renderer'] = this._getTemplateRenderer(renderer, areaDataLayer['markers']);
          }
        }
      }
      var pointDataLayers = this.options['pointDataLayers'];
      if (pointDataLayers) {
        for (var i = 0; i < pointDataLayers.length; i++) {
          var pointDataLayer = pointDataLayers[i];
          if (pointDataLayer) {
            var renderer = pointDataLayer['_templateRenderer'];
            if (renderer) {
              pointDataLayer['renderer'] = this._getTemplateRenderer(renderer, pointDataLayer['markers']);
            }
          }
        }
      }
      
      // callback function for getting the context needed for custom renderers
      this.options['_contextHandler'] = this._getContextHandler();
      
      // do data layer updates only if we've already initially rendered the thematic map
      if (this._initiallyRendered && this._dataLayersToUpdate.length > 0) {
        // Fix 18498656: If the component is not attached to a visible subtree of the DOM, rendering will fail because 
        // getBBox calls will not return the correct values.  Log an error message in this case and avoid rendering.
        // Note: Checking offsetParent() does not work here since it returns false for position: fixed.
        if(!this._context.isReadyToRender()) {
          oj.Logger.error(this.getResource('messageNotReadyToRender')['summary']);
        } else {
          for (var i = 0; i < this._dataLayersToUpdate.length; i++) {
            var dl = this._dataLayersToUpdate[i];
            this._component.updateLayer(dl['options'], dl['parentLayer'], dl['isADL']);
          }
          this._dataLayersToUpdate = [];
        }
      } else {
        // Delegate to the super to call the shared JS component for actual rendering.
        this._super();
        this._initiallyRendered = true;
      }
      
      // Send event once basemaps have been loaded and rendering is complete
      this._trigger('load', null, null);
    },
    
    /**
     * Creates a callback function that will be used by a data item as a custom renderer
     * @param {Function} templateFunction template function used to render knockout template
     * @return {Function} a function that will be used by a data item as a custom renderer
     * @private
     * @instance
     * @memberof oj.ojThematicMap
     */  
    _getTemplateRenderer: function(templateFunction) {
      var thisRef = this;
      var templateHandlerFunc = function (context) {      
        var dummyDiv = document.createElement("div");
        dummyDiv.style.display = "none";
        dummyDiv._dvtcontext = thisRef._context;
        thisRef.element.append(dummyDiv);
        templateFunction({'parentElement':dummyDiv, 'data': context['data']});
        var elem = dummyDiv.children[0];
        if (elem) {
          // The dummy div can be removed for custom svg elements, but need to be
          // kept around for stamped DVTs so the oj components aren't removed.
          if (elem.namespaceURI === 'http://www.w3.org/2000/svg') {
            $(dummyDiv).remove();
            return elem;
          } else {
            return thisRef._GetDvtComponent(elem);
          }
        }
        return null;  
      };
      return templateHandlerFunc;
    },
    
    /**
     * Creates a callback function that will be used by a data item to populate context for its custom renderer
     * @return {Function} context handler callback used to create context for a custom renderer
     * @private
     * @instance
     * @memberof oj.ojThematicMap
     */  
    _getContextHandler: function() {
     var thisRef = this;
     var contextHandlerFunc = function (parentElement, rootElement, data, state, previousState) {
       var context = {
         'component': oj.Components.getWidgetConstructor(thisRef.element),
         'parentElement': parentElement,
         'rootElement': rootElement,
         'data': data,
         'state': state,
         'previousState': previousState, 
         'id': data['id'],
         'label': data['label'],
         'color': data['color'],
         'location': data['location'],
         'x': data['x'],
         'y': data['y']
       };
       return context;      
     }
     return contextHandlerFunc;
    },
    
    /**
     * Renders the default hover effect for a data item.
     * @param {Object} context A context object with the following properties:
     * <ul>
     *  <li>{Function} component: A reference to the Thematic Map widget constructor so non public methods cannot be called.</li>
     *  <li>{Object} data: The data object for a stamped data visualization in the data layer.</li>
     *  <li>{SVGElement} parentElement: An element that is part of a displayed subtree on the DOM. Modifications of the parentElement are not supported.</li>
     *  <li>{SVGElement} rootElement: Null on initial rendering or the current data item SVG element.</li> 
     *  <li>{Object} state: An object that reflects the current state of the data item with the following boolean properties: hovered, selected, focused.</li> 
     *  <li>{Object} previousState: An object that reflects the previous state of the data item with the following boolean properties: hovered, selected, focused.</li>
     *  <li>{string} id: The id of the hovered item.</li>
     *  <li>{string} label: The data label of the hovered item.</li>
     *  <li>{string} color: The color of the hovered item.</li>
     *  <li>{string} location: The location id of the hovered item which can be null if x/y are set instead.</li>
     *  <li>{string} x: The x coordinate of the hovered item which can be null if location is set instead.</li>
     *  <li>{string} y: The y coordinate of the hovered item which can be null if location is set instead.</li>
     * </ul>
     * @expose
     * @instance
     * @memberof oj.ojThematicMap
     */
    renderDefaultHover: function (context) {    
      if (!context['previousState'] || context['state']['hovered'] != context['previousState']['hovered']) {
        this._component.processDefaultHoverEffect(context['id'], context['state']['hovered']);
      }
    },

    /**
     * Renders the default selection effect for a data item.
     * @param {Object} context A context object with the following properties:
     * <ul>
     *  <li>{Function} component: A reference to the Thematic Map widget constructor so non public methods cannot be called.</li>
     *  <li>{Object} data: The data object for a stamped data visualization in the data layer.</li>
     *  <li>{SVGElement} parentElement: An element that is part of a displayed subtree on the DOM. Modifications of the parentElement are not supported.</li>
     *  <li>{SVGElement} rootElement: Null on initial rendering or the current data item SVG element.</li> 
     *  <li>{Object} state: An object that reflects the current state of the data item with the following boolean properties: hovered, selected, focused.</li> 
     *  <li>{Object} previousState: An object that reflects the previous state of the data item with the following boolean properties: hovered, selected, focused.</li>
     *  <li>{string} id: The id of the hovered item.</li>
     *  <li>{string} label: The data label of the hovered item.</li>
     *  <li>{string} color: The color of the hovered item.</li>
     *  <li>{string} location: The location id of the hovered item which can be null if x/y are set instead.</li>
     *  <li>{string} x: The x coordinate of the hovered item which can be null if location is set instead.</li>
     *  <li>{string} y: The y coordinate of the hovered item which can be null if location is set instead.</li>
     * </ul>
     * @expose
     * @instance
     * @memberof oj.ojThematicMap
     */  
    renderDefaultSelection: function (context) {
      if (!context['previousState'] || context['state']['selected'] != context['previousState']['selected']) {
        this._component.processDefaultSelectionEffect(context['id'], context['state']['selected']);
      }
    },  

    /**
     * Renders the default focus effect for a data item.
     * @param {Object} context A context object with the following properties:
     * <ul>
     *  <li>{Function} component: A reference to the Thematic Map widget constructor so non public methods cannot be called.</li>
     *  <li>{Object} data: The data object for a stamped data visualization in the data layer.</li>
     *  <li>{SVGElement} parentElement: An element that is part of a displayed subtree on the DOM. Modifications of the parentElement are not supported.</li>
     *  <li>{SVGElement} rootElement: Null on initial rendering or the current data item SVG element.</li> 
     *  <li>{Object} state: An object that reflects the current state of the data item with the following boolean properties: hovered, selected, focused.</li> 
     *  <li>{Object} previousState: An object that reflects the previous state of the data item with the following boolean properties: hovered, selected, focused.</li>
     *  <li>{string} id: The id of the hovered item.</li>
     *  <li>{string} label: The data label of the hovered item.</li>
     *  <li>{string} color: The color of the hovered item.</li>
     *  <li>{string} location: The location id of the hovered item which can be null if x/y are set instead.</li>
     *  <li>{string} x: The x coordinate of the hovered item which can be null if location is set instead.</li>
     *  <li>{string} y: The y coordinate of the hovered item which can be null if location is set instead.</li>
     * </ul>
     * @expose
     * @instance
     * @memberof oj.ojThematicMap
     */  
    renderDefaultFocus: function (context) {
      if (!context['previousState'] || context['state']['focused'] != context['previousState']['focused']) {
        this._component.processDefaultFocusEffect(context['id'], context['state']['focused']);
      }
    },
  
    /**
     * Loads the basemaps and resource bundles.
     * @private
     */
    _loadBasemap: function() {
      var basemap = this.options['basemap'];
      if (basemap) {
        var locale = oj.Config.getLocale();
        if (locale !== this._currentLocale) {
          this._currentLocale = locale;
          this._loadedBasemaps = [];
        }
        
        // Track basemaps that need to be loaded before rendering
        basemap = basemap.charAt(0).toUpperCase() + basemap.slice(1);

        var areaLayers = this.options['areaLayers'];
        // load area layer basemaps
        if (areaLayers) {
          for (var i = 0; i < areaLayers.length; i++) {
            var layer = areaLayers[i]['layer'];
            if (layer) {
              layer = layer.charAt(0).toUpperCase() + layer.slice(1);
              this._loadBasemapHelper(basemap, layer, locale);
            }
          }
        }

        // load city basemap
        var pointDataLayers = this.options['pointDataLayers'];
        if (pointDataLayers && pointDataLayers.length > 0)
          this._loadBasemapHelper(basemap, 'Cities', locale);
      }
    },
    
    /**
     * Utility function for loading resource bundles by url.
     * @param {string} url The url of the resource to load
     * @private
     */
    _loadResourceByUrl: function(url) {
      // resource is already loaded or function tried to load this resource but failed
      if (this._loadedBasemaps[url])
        return;

      var resolvedUrl = oj.Config.getResourceUrl(url);
      var thisRef = this;
      var loadedBundles = this._loadedBasemaps;
      $.getScript(resolvedUrl, function(data, textStatus, jqxhr) {
        loadedBundles[url] = true;
        thisRef._Render();
      });
    },
    
    _loadBasemapHelper: function(basemap, layer, locale) {
      var relativeUrl = 'resources/internal-deps/dvt/thematicMap/basemaps/DvtBaseMap' + basemap + layer + '.js';
      if (this._checkBasemaps.indexOf(relativeUrl) == -1) {
        this._checkBasemaps.push(relativeUrl);
        this._loadResourceByUrl(relativeUrl);
      }

      if (locale.indexOf('en') === -1) {
        /* 
         * Split locale by subtags and try to load resource bundle that satisfies
         * Locale syntax defined by BCP 47 and should be one of the following formats:
         * 1)language_script_region
         * 2)language_region
         * 3)language_script
         * 4)language
         * 5)default (en)
         */
        var splitLocale = locale.toLowerCase().split('-');
        var localeList = [splitLocale[0]];
        if (splitLocale.length > 1) {
          localeList.unshift(splitLocale[0]+'-'+splitLocale[1]);
        }
        if (splitLocale.length > 2) {
          localeList.unshift(splitLocale[0]+'-'+splitLocale[2], splitLocale[0]+'-'+splitLocale[1]+'-'+splitLocale[2]);
        }

        var bundleName = 'resources/internal-deps/dvt/thematicMap/resourceBundles/' + basemap + layer + 'Bundle';
        // Go thru list of supported DVT languages
        for (var i = 0; i < localeList.length; i++) {
          if (this._supportedLocales[localeList[i]]) {
            var bundleUrl = bundleName + "_" + this._supportedLocales[localeList[i]] + ".js";
            if (this._checkBasemaps.indexOf(bundleUrl) == -1) {
              this._checkBasemaps.push(bundleUrl);
              this._loadResourceByUrl(bundleUrl);
            }
            break;
          }
        }
      }
    },
    
    //** @inheritdoc */
    _HandleEvent: function(event) {
      var type = event && event.getType ? event.getType() : null, selection, id;
      if (type === dvt.DvtSelectionEvent.TYPE) {
        selection = {};
        id = event.getParamValue('clientId');
        selection[id] = event.getSelection();
        if (this.options['selection']) {
          for (var dataLayerId in this.options['selection']) {
            if (id !== dataLayerId)
              selection[dataLayerId] = this.options['selection'][dataLayerId];
          }
        }
        this._UserOptionChange('selection', selection);
      }
      else {
        this._super(event);
      }
    },
    
    //** @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['DvtUtilBundle.THEMATIC_MAP'] = translations['componentName'];
      return ret;
    },
    
    /**
     * Updates the options object with the current data layer selection states 
     * @memberof oj.ojThematicMap
     * @instance
     * @private
     */
    _updateDataLayerSelection: function() {
      var selection = this.options['selection'];
      if (selection) {
        var pdls = this.options['pointDataLayers'];
        if (pdls) {
          for (var i = 0; i < pdls.length; i++) {
            if (selection[pdls[i]['id']])
              pdls[i]['selection'] = selection[pdls[i]['id']];
          }
        }

        var als = this.options['areaLayers'];
        if (als) {
          for (var i = 0; i < als.length; i++) {
            // JET Thematic Map does not support nesting of point data layers within an area layer
            var adl = als[i]['areaDataLayer'];
            if (adl && selection[adl['id']])
              adl['selection'] = selection[adl['id']];
          }
        }
      }
    },
    
    /**
     * Returns an object with the following properties for automation testing verification of the area with
     * the specified data layer id and index.
     * 
     * @param {string} dataLayerId 
     * @param {number} index  
     * @property {string} color
     * @property {string} label
     * @property {boolean} selected
     * @property {string} tooltip
     * @property {Function} getColor <b>Deprecated</b>: Use <code class="prettyprint">color</code> instead.
     * @property {Function} getLabel <b>Deprecated</b>: Use <code class="prettyprint">label</code> instead.
     * @property {Function} getTooltip <b>Deprecated</b>: Use <code class="prettyprint">tooltip</code> instead.
     * @property {Function} isSelected <b>Deprecated</b>: Use <code class="prettyprint">selected</code> instead.
     * @return {Object|null} An object containing properties for the area, or null if none exists.
     * @expose
     * @instance
     * @memberof oj.ojThematicMap
     */   
    getArea : function(dataLayerId, index) {
      var ret = this._component.getAutomation().getData(dataLayerId, 'area', index);
    
      // 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 marker with
     * the specified data layer id and index.
     * 
     * @param {string} dataLayerId 
     * @param {number} index
     * @property {string} color
     * @property {string} label
     * @property {boolean} selected
     * @property {string} tooltip
     * @property {Function} getColor <b>Deprecated</b>: Use <code class="prettyprint">color</code> instead.
     * @property {Function} getLabel <b>Deprecated</b>: Use <code class="prettyprint">label</code> instead.
     * @property {Function} getTooltip <b>Deprecated</b>: Use <code class="prettyprint">tooltip</code> instead.
     * @property {Function} isSelected <b>Deprecated</b>: Use <code class="prettyprint">selected</code> instead.
     * @return {Object|null} An object containing properties for the marker, or null if none exists.
     * @expose
     * @instance
     * @memberof oj.ojThematicMap
     */    
    getMarker : function(dataLayerId, index) {
      var ret = this._component.getAutomation().getData(dataLayerId, 'marker', index);
    
      // Bug 20884377: Provide backwards compatibility for getters until 1.2.0.
      this._AddAutomationGetters(ret);
      
      return ret;
    },
    
    /** 
     * {@ojinclude "name":"nodeContextDoc"}
     * @param {!Element} node - {@ojinclude "name":"nodeContextParam"}
     * @returns {Object|null} {@ojinclude "name":"nodeContextReturn"}
     * 
     * @example {@ojinclude "name":"nodeContextExample"}
     * 
     * @expose
     * @instance
     * @memberof oj.ojThematicMap
     */
  getContextByNode: function(node) {
    // context objects are documented with @ojnodecontext
    var context = this.getSubIdByNode(node);
    if (context && context['subId'] !== 'oj-thematicmap-tooltip')
      return context;

    return null;
  }
});