Source: renderers/BaseRenderer.js

/**
 * @namespace Oracle
 */

/**
 * @namespace Oracle.BDD
 */

/**
 * @namespace Oracle.BDD.Portlets
 */

/**
 * @namespace Oracle.BDD.Portlets.Visualization
 */

/**
 * @namespace Oracle.BDD.Portlets.Visualization.Renderers
 * 
 */
Ext.ns('Oracle.BDD.Portlets.Visualization.Renderers');

/**
 * Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer is the JavaScript class from which all
 * Custom Visualization renderer classes must extend.  Subclasses of the BaseRenderer must implement
 * the init() function which servers as the starting point for querying and visualizing data.
 * <p>
 * The init component is expected to call the executeQuery() function, supplying it with a queryConfig
 * object acquired with the {{this.getQueryConfig(queryName)}} function.  
 * <p>
 * Additionally, the executeQuery() function is supplied with a callback function which takes an 
 * Oracle.BDD.Portlets.Visualization.Model.QueryResult object as its first argument. This function is 
 * responsible for the initial rendering of the visualization using the returned data.
 * <p>
 * In this example, Oracle.BDD.Portlets.Visualization.Renderers.DonutPieChart renderer class is defined.
 * It contains the required init() function implementation that is called when the renderer is first
 * initialized.  
 * <p>
 * In the init() function, the query named "eql" is executed and the results are passed to the 
 * renderVisualization function.  The renderVisualization is then responsible for rendering the
 * visualization with the provided data.
 * 
 * <pre>
 * Oracle.BDD.Portlets.Visualization.Renderers.DonutPieChart = Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer.extend({
 *		
 *    init: function(queryConfig) {
 *       
 *       var queryConfig = this.getQueryConfig("eql");	
 *       this.executeQuery(queryConfig, true, this.renderVisualization);
 *
 *   },
 *	
 *   renderVisualization: function(queryResults) {
 *   
 *      // rendering code here
 *      
 *   }
 * });
 * </pre>
 * @class Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer
 * @extends Ext.Container
 */
Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer = Ext.extend(Ext.Container, {
              
    /**
	 * Displays the provided error message in the portlet container.
	 * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer# 
	 * @param {string} err Error message to display.  Optional.  Displays default error
	 * message if none of provided.
	 * @param {boolean} clear Indicates if canvas should be cleared
	 */
    displayError: function(err, clear) {
    	if (clear) this.getCanvas().clear();
        this.fireEvent('displaymessage', this, err, true);       
    },
        
    /**
	 * Displays the provided message in the portlet container.
	 * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer#  
	 * @param {string} msg Message to display
	 * @param {boolean} clear Indicates if canvas should be cleared  
	 */
    displayMessage: function(msg, clear) {
    	if (clear) this.getCanvas().clear();
    	this.fireEvent('displaymessage', this, msg, false);       
    },
    
    /** 
     * Executes a query against the Dgraph, updates the renderer's data records and then executes the provided callback.  
     * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer# 
     * @param {QueryConfig|string} queryConfig A QueryConfig object defining the query to execute or the name of 
     * eql query to execute
	 * @param {boolean} mask boolean indicating if the load mask should display until query response is received. 
     * @param {function} callback The callback function to execute after data are updated.  Optional. 
     * @param {primative|Object|Object[]} callbackArgs An array, object, or primative to pass through to the callback function as
     * argument
     * @param {function} errorCallback The callback function to execute in the case of an error response from query.  If not specified,
     * the default error message is rendered to the canvas. Optional. 
     * @param {primative|Object|Object[]} errorCallbackArgs An array, object, or primative to pass through to the error callback function as
     * argument
     */
    executeQuery: function(queryObj, mask, callback, callbackArgs, errorCallback, errorCallbackArgs) {
    	
    	if (Ext.isString(queryObj)) {
    		queryObj = this.getQueryConfig(queryObj);
    	}
    	    	
    	if (!this._baseQuery || !this._baseQuery.hasOwnProperty("callback")) {
    		this._baseQuery = { 
    				queryName: queryObj.getQueryName(), 
    				callback: callback, 
    				callbackArgs: callbackArgs
    		}
    	}
    	
        this.fireEvent('executequery', this, queryObj, mask, callback, callbackArgs, errorCallback, errorCallbackArgs);
    },
    
    /** 
     * Returns the singleton Oracle.BDD.Portlets.Visualization.Model.Canvas object
     * representing the drawing space on which the visualization is rendered.
     * If the canvas has already rendered, then a reference to the existing canvas
     * obj is return (and the config argument is ignored); otherwise a newly
     * initialized canvas object is returned.
     * 
	 * <p>
	 * The Canvas configuration object has two properties that may be
	 * configured:
	 * <p>
	 * margins: Set the margins of the canvas
	 * tagName: By 
	 * 
	 * For example, to set margins and configure the canvas root to
	 * be a <DIV> element instead of the default \<G\> wrapped in an \<SVG\> element:
	 * <p>
	 * <pre>
	 * var canvas = getCanvas({
	 *    margins: {left: 50, right: 50, top: 100, bottom: 100},
	 *    tagName: 'div'
	 * })
	 * </pre>
	 * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer#
	 * @param {Object} config Canvas configuration object. Optional.
	 * @param {boolean} force Forces the re-initialization even if canvas has been rendered. Optional.
	 * @return {Canvas} The canvas
	 */
	getCanvas : function(config, force) {

    	if (!this._canvas || force || (config && !this._canvas.isRendered())) {
    		
    		if (this._canvas) this._canvas.clear();
    		
    		config = config || {};
    		
        	config.rendererId = this.id;
        	config.container = this.container;
        	config.width = this.width;
        	config.height = this.height;
        	
        	this._canvas = new Oracle.BDD.Portlets.Visualization.Model.Canvas(config);
    	} 
    	
    	return this._canvas;
    },
    
    /** 
	 * Returns a singleton {@link Oracle.BDD.Portlets.Visualization.Util.ColorManager} 
	 * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer#
	 * @param {Object} config Color Manager configuration object. Optional.
	 * @return {ColorManager} The default ColorMamanger
	 * <pre>
	 * Color Manager configuration example: 
	 * 
     * var colorMgr = getColorManager({
     *    library: ["#6B486B", "#718BAE", "#ABBE23"],
     *    scaleDomain: ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]
     * });
     * </pre>
    */
    getColorManager: function(config) {
    	
    	/** 
    	 * Returns a singleton Color Manager.
    	 */
    	if (!this._colorManager) {
    		this._colorManager = new Oracle.BDD.Portlets.Visualization.Util.ColorManager(config);
    	} else if (config) {
    		if (config.library) this._colorManager.setLibrary(config.library);
    		if (config.scaleDomain) this._colorManager.setScaleDomain(config.scaleDomain);
    	}
    
    	return this._colorManager;
    },
       
    /** 
	 * Returns the {@link Oracle.BDD.Portlets.Visualization.Model.QueryConfig} of the given name.
	 * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer#
	 * @param {string} queryName The queryName.
	 * @return {QueryConfig} The QueryConfig
	 */
	getQueryConfig : function(queryName) {

    	var config = {
    			queryName: queryName
    	};
    	
    	if (queryName && this._queryTokens.hasOwnProperty(queryName)) {
    		config.tokens = this._queryTokens[queryName];
    	} else if(this._baseQuery && this._baseQuery.tokens) {
    		config.tokens = this._baseQuery.tokens
    	}
    	  	
    	return new Oracle.BDD.Portlets.Visualization.Model.QueryConfig(config);
    },
    
    /**
     * <b>Required implementation.</b>  All subclasses of the BaseRenderer must
     * implement this function.  This function is called when the renderer is
     * first initialized and serves as the starting point for the renderer to executes
     * its initial query for data and define how to respond to its results.  This is
     * accomplished by including a call to executeQuery in the implementation. 
     * <p>
     * For example:
     * <pre>
     *    init: function() {
     *       
     *       var queryConfig = this.getQueryConfig("eql");
     *       this.executeQuery(queryConfig, true, this.renderVisualization);
     *
     *   },
     *	
     *   renderVisualization: function(queryResults) {
     *   
     *      // rendering code here
     *      
     *   }
     * </pre>
     * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer# 
     * @abstract
     */
    init: function() {
    	this.displayError(this.resources.getMessage("cvp.view.error.noinit"), true);
    },
      	    
    /** 
     * Convenience method to add a refinement and execute the query in one call.  See {@linkcode #addRefinement} and 
     * {@linkcode #executeQuery}
     * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer# 
     * @param {QueryConfig} queryConfig A QueryConfig object defining the query to execute
     * @param {string} tokenName The name of the attribute token the attribute of which the refinement is to apply
     * @param {string|Number|Object[]} value The value(s) to refine by
     * @param {function} callback The callback function to execute after data are updated.  Optional. 
     * @param {primative|Object|Object[]} callbackArgs An array, object, or primative to pass through to the callback function as
     * argument
     * @param {function} errorCallback The callback function to execute in the case of an error response from query.  If not specified,
     * the default error message is rendered to the canvas. Optional. 
     * @param {primative|Object|Object[]} errorCallbackArgs An array, object, or primative to pass through to the error callback function as
     * argument
     */
    refine: function(queryConfig, tokenName, value, callback, callbackArgs, errorCallback, errorCallbackArgs) {
        
    	queryConfig.addRefinement(tokenName, value);
        this.executeQuery(queryConfig, true, callback, callbackArgs, errorCallback, errorCallbackArgs);
    	
    },
    
    /**
     * Utility function to truncate text that exceeds the provided width and appends ellipses to the text.
     * This would normally be accomplished with CSS (text-overflow: ellipsis;), but SVG elements do not
     * support this.
     * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer#
     * @param {SVGElement} svgElmt The SVG element containing the text to truncate
     * @param {Number} width The width to constrain the text to
     * @param {Number} padding  Padding to apply.  Optional.
     */
    truncateText: function(svgElmt, width, padding) {
		var padding = padding || 0;
        var elmt = d3.select(svgElmt);
		if (elmt && elmt.node().getComputedTextLength) {
			var textLength = elmt.node().getComputedTextLength(),
				text = elmt.text();
			while (textLength > (width - (2 * padding)) && text.length > 0) {
				text = text.slice(0, -1);
				elmt.text(text + '...');
				textLength = elmt.node().getComputedTextLength();
			}
		}         
	},
		
	/**
	 * Utility function to return a value or array of values as a String
	 * formatted for use within an EQL WHERE or HAVING clause.  For example,
	 * the values array ["blue', "red", "green"] returns as {"blue', "red", "green"}.
	 * @memberof Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer#
	 * @param {Object|Object[]} values Value or values to convert
	 * @param {String} type The datatype of the attribute
	 * @param {boolean} isSingleAssign 
	 * @return{string} The formatted string
	 */
	valuesToEql: function(values, type, isSingleAssign) {
		
		var returnValues = [];		
		var iterValues = isSingleAssign ? [values] : values;
		var iso = d3.time.format.iso;
		
		Ext.each(iterValues, function(v) {					
			if (type === 'mdex:string-set' || type === 'mdex:string') {
				returnValues.push("'" + v + "'");
			} else if (type === 'mdex:dateTime-set' || type === 'mdex:dateTime') {
				returnValues.push("TO_DATETIME('" + iso(v) + "')");
			} else if (type === 'mdex:duration-set' || type === 'mdex:duration') {
				returnValues.push("TO_DURATION('" + v + "')");
			} else if (type === 'mdex:time-set' || type === 'mdex:time') {
				returnValues.push("TO_TIME('" + v + "')");
			} else if (type === 'mdex:geocode-set' || type === 'mdex:geocode') {
				returnValues.push("TO_GEOCODE('" + v + "')");
			} else {
				returnValues.push(v);
			}
		});
		
		return isSingleAssign ? returnValues[0] : "{" + returnValues.join(',') + "}";
		
	},

   	    			    
	/** Private functions.  Direct use of these functions is NOT supported **/
	
    /** 
	* @private
	*/
	NS_BDD_PREFIX: "http://www.oracle.com/bdd/",
    
	/** 
	* @private
	*/
	constructor: function (config) {
			    	
	    Ext.apply(this, config);

	    this._baseQuery= {};
	    this._canvas= undefined;
	    this._queryTokens= {};
		
	    /* Declare namespace for bdd:tooltip attribute */
	    d3.ns.prefix.bdd= this.NS_BDD_PREFIX;
			    
	    this.cls =  "visualization-portlet-renderer";
	    		    	
	    this.addEvents({
	        'executequery': true,
	        'displaymessage' : true
	    });
			    	    	   	    	
	    Oracle.BDD.Portlets.Visualization.Renderers.BaseRenderer.superclass.constructor.call(this);
			    	
	},
					    
	/** 
	* @private
	*/
	executeRenderer: function(viewModel, callback, callbackArgs) {
			   	
		var queryResults = new Oracle.BDD.Portlets.Visualization.Model.QueryResults(viewModel);
		 
	    if(viewModel.queryConfig) {
	    	this._queryTokens[viewModel.queryConfig.queryName] = viewModel.queryConfig.tokens;
	    }

	    if (this._baseQuery && viewModel.queryConfig.queryName === this._baseQuery.queryName) {	    	
	    	this._baseQuery.queryResults = queryResults;
	    }
	    
	    this._baseQuery.tokens = queryResults.getQueryConfig().getTokens();
	    
	    this.executeQueryCallback(queryResults, callback, callbackArgs)
	},

	
	/** 
	* @private
	*/
	executeQueryCallback: function(queryResults, callback, callbackArgs) {
		
		if (!callback && this._baseQuery) {			
	    	callback = callback || this._baseQuery.callback;
			callbackArgs = callbackArgs || this._baseQuery.callbackArgs;
	    } 
		
		var args = [queryResults].concat(callbackArgs);
		
	    if (callback && queryResults) {
	    	callback.apply(this, args);
	    	this.initializeTooltips();
	    }
	    		    	
	},
	
	
    /**
	 * Resize behavior.  If newWidth or newheight differs from the dimensions of the
	 * canvas, then the canvas is cleared, resized and the visualization re-rendered.
	 * @private 
	 * @param {Number} newWidth The new width to set the canvas
	 * @param {Number} newHeight The new height to set the canvas
	 */
    handleResize: function(newWidth, newHeight) {
    	    	
		var canvasRoot = Ext.get(this.id + "_wrapper");
		var canvasWidth = canvasRoot ? canvasRoot.getComputedWidth() : newWidth;
		var canvasHeight = canvasRoot ? canvasRoot.getComputedHeight() : newHeight;
		if (canvasWidth != newWidth || canvasHeight != newHeight && this._baseQuery.queryResults) {			
			this.getCanvas().clear(); 
			this._canvas = undefined;
			this.width = newWidth;
			this.height = newHeight;
			this.executeQueryCallback(this._baseQuery.queryResults, this._baseQuery.callback, this._baseQuery.callbackArgs);
		}     
    },
			    
			    
	/** 
	* @private
	*/
	initializeTooltips: function() {
			    	
	    var self = this;
	    var tipNodes = Ext.get(this.id).query('[*|tooltip]');
			    	
	    Ext.each(tipNodes, function(node, idx, allElmts) {
			    		
	        var nodeNS = node.getAttributeNodeNS(self.NS_BDD_PREFIX, "tooltip");
			    		
	        if (nodeNS) {
	            var value = nodeNS.value;
				    		
	            var tooltip = new Ext.ToolTip ({
	                target: node,
	                renderTo: Ext.getBody(),
	                html: value,
	                listeners: {
	                    beforeshow: function (tip) {
				             			
	                        var val = tip.target.getAttribute("tooltip", self.NS_BDD_PREFIX);
	                        tip.update(val);
				             		    
	                        return !Ext.isEmpty(val);
	                    }
	                }
	            }); 
	        }
	    });  	
	},
	
	/** 
	* @private
	*/
	initializeRenderer: function(tokens) {
		
		this._queryTokens = {};	
		
		this._baseQuery = this._baseQuery || {};
		this._baseQuery.tokens = tokens;
		
		this.init();
	}
});
//@ sourceURL=BaseRenderer.js