Source: charts/ccc/Chart.js

/*globals pvc*/

/**
 * Copyright (C) 2005-2016 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * A base class for charts
 *
 * @module alfresco/charts/ccc/Chart
 * @extends external:dijit/_WidgetBase
 * @mixes external:dojo/_TemplatedMixin
 * @mixes module:alfresco/core/Core
 * @mixes module:alfresco/core/CoreWidgetProcessing
 * @abstract
 *
 * @author Erik Winlöf
 */
define(["dojo/_base/declare",
   "dijit/_WidgetBase",
   "dijit/_TemplatedMixin",
   "dojo/text!./templates/Chart.html",
   "alfresco/core/Core",
   "alfresco/core/CoreWidgetProcessing",
   "alfresco/core/DomElementUtils",
   "dojo/_base/lang",
   "dojo/on",
   "dojo/dom-geometry",
   "dojo/dom-style"],
      function(declare, _WidgetBase, _TemplatedMixin, template,
               AlfCore, CoreWidgetProcessing, DomElementUtils, lang, dojoOn, domGeom, domStyle) {

         return declare([_WidgetBase, _TemplatedMixin, AlfCore, CoreWidgetProcessing, DomElementUtils], {

            /**
             * The base css class to use for this widget
             *
             * @instance
             * @type {string}
             * @default
             */
            baseClass: "alfresco-charts-ccc-Chart",

            /**
             * The Protovis class that will be wrapped inside this widget.
             * Note! MUST be overridden by a sub class.
             *
             * @instance
             * @type {string}
             */
            pvcChartType: null,

            /**
             * Any additional configuration attributes for the chart can be set here
             * 
             * <p>Options set here will override anything set via another named property.</p>
             *
             * @instance
             * @type {object}
             */
            chartConfig: null,

            /**
             * The charts container element (will be set by dojo)
             * @instance
             * @type {HTMLElement}
             */
            chartNode: null,

            /**
             * The topic to publish when requesting chart data
             *
             * @instance
             * @type {string}
             */
            dataTopic: null,

            /**
             * The initial payload of the [dataTopic]{@link module:alfresco/charts/ccc/Chart#dataTopic}.
             *
             * @instance
             */
            dataTopicPayload: {},

            /**
             * The topic to publish when chart data item has been clicked.
             *
             * @instance
             * @type {string}
             */
            clickTopic: null,

            /**
             * The chart title
             *
             * @instance
             * @type {string}
             */
            title: null,

            /**
             * The position of the chart title
             *
             * @instance
             * @type {string}
             */
            titlePosition: "bottom",

            /**
             * The width in pixels of the chart. A null value indicates 100%
             *
             * @instance
             * @type {number|null}
             * @default
             */
            width: null,

            /**
             * The height in pixels.
             *
             * @instance
             * @type {number}
             * @default
             */
            height: 400,

            /**
             * Decides if a legend shall be displayed and if so how it should appear.
             *
             * For more details see:
             * {@link http://www.webdetails.pt/ctools/charts/jsdoc/symbols/pvc.options.panels.LegendPanel.html}
             *
             * @instance
             * @type {boolean|pvc.options.panels.LegendPanel}
             * @default
             */
            legend: false,

            /**
             * Indicates if the chart's visual elements can be selected by the user, by clicking on them or using the
             * rubber-band.
             *
             * @instance
             * @type {boolean}
             * @default
             */
            selectable: false,

            /**
             * Indicates if the chart's visual elements are automatically highlighted when the user hovers over them
             * with the mouse.
             *
             * @instance
             * @type {boolean}
             * @default
             */
            hoverable: false,

            /**
             * Indicates if tooltips are enabled and contains additional tooltip presentation options.
             *
             * For more details see:
             * {@link http://www.webdetails.pt/ctools/charts/jsdoc/symbols/pvc.options.Tooltip.html}
             *
             * @instance
             * @type {pvc.options.Tooltip}
             * @default { enabled: true }
             */
            tooltip: {
               enabled: true
            },

            /**
             * An array of dimensions readers.
             * Can be specified to customize the translation process of the data source.
             *
             * For more details see:
             * {@link http://www.webdetails.pt/ctools/charts/jsdoc/symbols/pvc.options.DimensionsReader.html}
             *
             * @instance
             * @type {null|pvc.options.DimensionsReader}
             * @default
             */
            readers: null,

            /**
             * A map whose keys are the dimension type names and whose values are the dimension type options.
             *
             * For more details see:
             * {@link http://www.webdetails.pt/ctools/charts/jsdoc/symbols/pvc.options.DimensionType.html}
             *
             * @instance
             * @type {null|pvc.options.DimensionType}
             * @default
             */
            dimensions: null,

            /**
             * Extension points for the chart, will vary depending on the CCC2 class.
             *
             * For more details visit:
             * {@link http://www.webdetails.pt/ctools/charts/jsdoc/symbols/pvc.options.ext.ChartExtensionPoints.html}
             */
            extensionPoints: null,

            /**
             * Variable for storing the current data
             *
             * @instance
             * @type {object}
             */
            _currentData: null,

            /**
             * Variable for storing meta data about the current data
             *
             * @instance
             * @type {object}
             */
            _currentDataDescriptor: null,

            /**
             * Declare the dependencies on "legacy" JS files that this widget is wrapping.
             *
             * @instance
             * @type {string[]}
             */
            nonAmdDependencies: [
               "/js/lib/ctools/jquery.js",
               "/js/lib/ctools/protovis.js",
               "/js/lib/ctools/protovis-msie.js",
               "/js/lib/ctools/jquery.tipsy.js",
               "/js/lib/ctools/tipsy.js",
               "/js/lib/ctools/def.js",
               "/js/lib/ctools/pvc.js"
            ],

            /**
             * An array of the CSS files to use with this widget.
             *
             * @instance cssRequirements {Array}
             * @type {object[]}
             * @default [{cssFile:"./css/Chart.css"}]
             */
            cssRequirements: [
               {cssFile:"./css/Chart.css"},
               {cssFile:"/js/lib/ctools/tipsy.css"}
            ],

            /**
             * The HTML template to use for the widget.
             *
             * @instance
             * @type {string}
             */
            templateString: template,

            /**
             * Subscribes to the data topic
             *
             * @instance
             */
            postMixInProperties: function alfresco_charts_ccc_Chart__postMixInProperties() {
               if (this.dataTopic) {
                  // Subscribe to the topics that will be published on by the ReportService when retrieving data
                  // that this widget requests...
                  this.alfSubscribe(this.dataTopic + "_SUCCESS", lang.hitch(this, this.onDataLoadSuccess));
                  this.alfSubscribe(this.dataTopic + "_FAILURE", lang.hitch(this, this.onDataLoadFailure));
               }
            },

            /**
             * Creates and returns the chart config
             *
             * @instance
             * @return {object}
             */
            createChartConfig: function(){
               var config = {};

               // Common configurable properties
               config.canvas = this.chartNode;

               config.width = this._getWidth();
               config.height = this.height;

               config.title = this.title;
               config.titlePosition = this.titlePosition;

               config.legend = this.legend;

               config.selectable = this.selectable;
               config.hoverable = this.hoverable;

               if (this.readers) {
                  config.readers = this.readers;
               }
               if (this.dimensions) {
                  config.dimensions = this.dimensions;
               }
               if (this.extensionPoints) {
                  config.extensionPoints = this.extensionPoints;
               }

               if (this.clickTopic)
               {
                  config.clickable = true;
                  config.clickAction = lang.hitch(this, this.onItemClick);
               }

               config.tooltip = this.tooltip;

               var styles = this.resolveCssStyles(this.baseClass + "--color", [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21], {
                  backgroundColor: ["rgba(0, 0, 0, 0)", "transparent"]
               });
               config.colors = styles.backgroundColor;

               // Set any additional chart options
               if (typeof this.chartConfig === "object")
               {
                  for (var p in this.chartConfig)
                  {
                     if (this.chartConfig.hasOwnProperty(p))
                     {
                        config[p] = this.chartConfig[p];
                     }
                  }
               }

               return config;
            },

            /**
             * Creates the CCC chart.
             *
             * @instance
             */
            createChart: function alfresco_charts_ccc_Chart__createChart(){
               this.chart = new pvc[this.pvcChartType](this.createChartConfig());
            },

            /**
             * Called when a chart item is clicked, will publish a topic with the name defined in the
             * [clickTopic]{@link module:alfresco/charts/ccc/Chart#clickTopic}
             *
             * @param scene {object}
             */
            onItemClick: function(scene){
               this.alfPublish(this.clickTopic, scene.atoms.category.rawValue);
            },

            /**
             * Sets up topic subscriptions and makes sure the chart is resized when the window is resized.
             *
             * @instance
             */
            postCreate: function alfresco_charts_ccc_Chart__postCreate() {
               if (this.dataTopic) {
                  // Set a response topic that is scoped to this widget...
                  var dataTopicPayload = this.dataTopicPayload || {};
                  dataTopicPayload.alfResponseTopic = this.dataTopic;
                  this.alfPublish(this.dataTopic, dataTopicPayload);
               }

               var me = this;
               var lastResizeEvent;
               var skippedResizeEvents = 0;

               function doResize() {
                  // Avoid re-rendering until the chart has been rendered a first time
                  if (me.chart) {
                     me._renderChart();
                  }
               }

               function onResize() {
                  if (lastResizeEvent) {
                     clearTimeout(lastResizeEvent);
                     skippedResizeEvents++;
                     if (skippedResizeEvents > 1) {
                        skippedResizeEvents = 0;
                        me._renderChart();
                        return;
                     }
                  }
                  lastResizeEvent = window.setTimeout(doResize, 100);
               }
               dojoOn(window, "resize", onResize);
            },

            /**
             * Shows the data in the chart
             *
             * @instance
             * @param data {object} The data to display
             * @param dataDescriptor {object} Metadata description about the data 
             */
            showData: function(data, dataDescriptor){
               this._currentData = data;
               this._currentDataDescriptor = dataDescriptor;
               this._renderChart();
            },

            /**
             * Renders the chart.
             *
             * @instance
             */
            _renderChart: function(){
               if (this._getWidth()) {
                  this._performRenderChart();
                  return;
               }

               // This element has not been added to the dom yet and has therefor no width, wait until its set
               var me = this;
               var timeoutId;
               function callPerformRenderChartWhenReady(){
                  if (me._getWidth()) {
                     clearTimeout(timeoutId);
                     me._performRenderChart();
                  }
               }
               timeoutId = window.setInterval(callPerformRenderChartWhenReady, 100);
            },

            /**
             * Performs the actual rendering of the chart.
             *
             * @instance
             */
            _performRenderChart: function(){
               this.createChart();
               this.chart.setData(this._currentData, this._currentDataDescriptor);
               this.chart.render(true, true, false);
            },

            /**
             * Calculates the width (in pixels) of an element.
             *
             * @return {null|number}
             * @private
             */
            _getWidth: function(){
               try {
                  var style = domStyle.getComputedStyle(this.domNode);
                  var s = domGeom.getContentBox(this.domNode, style);
                  var w = (s.w + "").split(".")[0];
                  w = w.split("px")[0];
                  w = parseInt(w, 10);
                  return w;
               }
               catch(e) {
                  return null;
               }
            },

            /**
             * Called when chart is loaded.
             *
             * @param payload {object}
             */
            onDataLoadSuccess: function(payload){
               this.showData(payload.response.data, payload.response.dataDescriptor);
            },

            /**
             * Called when chart data failed to load.
             *
             * @param payload {object}
             */
            onDataLoadFailure: function(/*jshint unused:false*/payload){
               this.showData({}, {});
            }

         });
      });