google-chart.html 11.6 KB
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../iron-ajax/iron-ajax.html">
<link rel="import" href="../google-apis/google-legacy-loader.html">

<!--
`google-chart` encapsulates Google Charts as a web component, allowing you to easily visualize
data. From simple line charts to complex hierarchical tree maps, the chart element provides a
number of ready-to-use chart types.

    <google-chart
      type='pie'
      options='{"title": "Distribution of days in 2001Q1"}'
      cols='[{"label":"Month", "type":"string"}, {"label":"Days", "type":"number"}]'
      rows='[["Jan", 31],["Feb", 28],["Mar", 31]]'>
    </google-chart>

Height and width are specified as style attributes:

    google-chart {
      height: 300px;
      width: 50em;
    }

Data can be provided in one of three ways:

- Via the `cols` and `rows` attributes:

      cols='[{"label":"Mth", "type":"string"}, {"label":"Days", "type":"number"}]'
      rows='[["Jan", 31],["Feb", 28],["Mar", 31]]'

- Via the `data` attribute, passing in the data directly:

      data='[["Month", "Days"], ["Jan", 31], ["Feb", 28], ["Mar", 31]]'

- Via the `data` attribute, passing in the URL to a resource containing the
  data, in JSON format:

      data='http://example.com/chart-data.json'
@demo
-->
<dom-module id="google-chart">
  <link rel="import" type="css" href="google-chart.css">
  <template>
    <iron-ajax id="ajax" handle-as="json" url="{{data}}"
      on-response="_externalDataLoaded"></iron-ajax>
    <div id="chartdiv"></div>
    <google-legacy-loader on-api-load="_readyForAction"></google-legacy-loader>
  </template>
</dom-module>

<script>
(function() {
  "use strict";

  Polymer({

    is: 'google-chart',

    /**
     * Fired when the graph is displayed.
     *
     * @event google-chart-render
     */

    /**
     * Fired when the user makes a selection in the chart.
     *
     * @event google-chart-select
     * @param {object} detail
     *   @param {array} detail.selection The user-defined selection.
     */

    properties: {
      /**
       * Sets the type of the chart.
       *
       * Should be one of:
       * - `area`, `bar`, `bubble`, `candlestick`, `column`, `combo`, `geo`,
       *   `histogram`, `line`, `pie`, `scatter`, `stepped-area`, `treemap`
       *
       * See <a href="https://google-developers.appspot.com/chart/interactive/docs/gallery">Google Visualization API reference (Chart Gallery)</a> for details.
       *
       */
      type: {
        type: String,
        value: 'column',
        observer: '_typeChanged'
      },

      /**
       * Sets the options for the chart.
       *
       * Example:
       * <pre>{
       *   title: "Chart title goes here",
       *   hAxis: {title: "Categories"},
       *   vAxis: {title: "Values", minValue: 0, maxValue: 2},
       *   legend: "none"
       * };</pre>
       * See <a href="https://google-developers.appspot.com/chart/interactive/docs/gallery">Google Visualization API reference (Chart Gallery)</a>
       * for the options available to each chart type.
       *
       */
      options: {
        type: Object,
        value: function() { return {}; }
      },

      /**
       * Sets the data columns for this object.
       *
       * When specifying data with `cols` you must also specify `rows`, and
       * not specify `data`.
       *
       * Example:
       * <pre>[{label: "Categories", type: "string"},
       *  {label: "Value", type: "number"}]</pre>
       * See <a href="https://google-developers.appspot.com/chart/interactive/docs/reference#DataTable_addColumn">Google Visualization API reference (addColumn)</a>
       * for column definition format.
       *
       * @attribute cols
       * @type array
       */
      cols: {
        type: Array,
        value: function() { return []; }
      },
      /**
       * Sets the data rows for this object.
       *
       * When specifying data with `rows` you must also specify `cols`, and
       * not specify `data`.
       *
       * Example:
       * <pre>[["Category 1", 1.0],
       *  ["Category 2", 1.1]]</pre>
       * See <a href="https://google-developers.appspot.com/chart/interactive/docs/reference#addrow">Google Visualization API reference (addRow)</a>
       * for row format.
       *
       * @attribute rows
       * @type array
       */
      rows: {
        type: Array,
        value: function() { return []; }
      },

      /**
       * Sets the entire dataset for this object.
       * Can be used to provide the data directly, or to provide a URL from
       * which to request the data.
       *
       * The data format can be a two-dimensional array or the DataTable format
       * expected by Google Charts.
       * See <a href="https://google-developers.appspot.com/chart/interactive/docs/reference#DataTable">Google Visualization API reference (DataTable constructor)</a>
       * for data table format details.
       *
       * When specifying data with `data` you must not specify `cols` or `rows`.
       *
       * Example:
       * <pre>[["Categories", "Value"],
       *  ["Category 1", 1.0],
       *  ["Category 2", 1.1]]</pre>
       *
       * @attribute data
       * @type array, object, or string
       */
      data: {
        type: Object, // or array, or object
        value: function() { return []; }
      },

      /**
       * Selected datapoint(s) in the map.
       *
       * An array of objects, each with a numeric row and/or column property.
       * `row` and `column` are the zero-based row or column number of an item
       * in the data table to select.
       *
       * To select a whole column, set row to null;
       * to select a whole row, set column to null.
       *
       * Example:
       * <pre>
       *   [{row:0,column:1}, {row:1, column:null}]
       * </pre>
       *
       * @attribute selection
       * @type array
       */
      selection: {
        type: Array,
        value: function() { return []; },
        observer: '_selectionChanged'
      },
    },

    observers: [
      '_loadData(rows, cols, data)'
    ],

    _packages: null,

    _chartObject: null,

    _isReady: false,

    _canDraw: false,

    _dataTable: null,

    _chartTypes: null,

    _readyForAction: function(e, detail, sender) {
      this._loadPackageByChartType();

      google.load("visualization", "1", {
        packages: this._packages[this.type],
        callback: function() {
          this._isReady = true;
          this._loadChartTypes();
          this._loadData();
        }.bind(this)
      });
    },

    _typeChanged: function() {
      // Invalidate current chart object.
      this._chartObject = null;
      this._loadData();
    },

    _selectionChanged: function() {
      if (this._chartObject && this.setSelection) {
        this._chartObject.setSelection(this.selection);
      }
    },

    /**
     * Draws the chart.
     *
     * Called automatically on first load and whenever one of the attributes
     * changes. Can be called manually to handle e.g. page resizes.
     *
     * @method drawChart
     * @return {Object} Returns null.
     */
    drawChart: function() {
      if (this._canDraw) {
        if (!this.options) {
          this.options = {};
        }
        if (!this._chartObject) {
          var chartClass = this._chartTypes[this.type];
          if (chartClass) {
            this._chartObject = new chartClass(this.$.chartdiv);
          }
        }
        if (this._chartObject) {
          google.visualization.events.addOneTimeListener(this._chartObject,
              'ready', function() {
                  this.fire('google-chart-render');
              }.bind(this));

          google.visualization.events.addListener(this._chartObject,
              'select', function() {
                  this.selection = this._chartObject.getSelection();
                  this.fire('google-chart-select',
                      { selection: this._chartObject.getSelection() });
              }.bind(this));


          this._chartObject.draw(this._dataTable, this.options);

          if (this._chartObject.setSelection){
            this._chartObject.setSelection(this.selection);
          }
        } else {
          this.$.chartdiv.innerHTML = 'Undefined chart type';
        }
      }
      return null;
    },

    /**
     * Returns the chart serialized as an image URI.
     *
     * Call this after the chart is drawn (google-chart-render event).
     *
     * @return {string} Returns image URI.
     */
    getImageURI: function() {
      return this._chartObject.getImageURI();
    },

    _loadChartTypes: function() {
      this._chartTypes = {
        'area': google.visualization.AreaChart,
        'bar': google.visualization.BarChart,
        'bubble': google.visualization.BubbleChart,
        'candlestick': google.visualization.CandlestickChart,
        'column': google.visualization.ColumnChart,
        'combo': google.visualization.ComboChart,
        'geo': google.visualization.GeoChart,
        'histogram': google.visualization.Histogram,
        'line': google.visualization.LineChart,
        'pie': google.visualization.PieChart,
        'scatter': google.visualization.ScatterChart,
        'stepped-area': google.visualization.SteppedAreaChart,
        'table': google.visualization.Table,
        'gauge': google.visualization.Gauge,
        'treemap': google.visualization.TreeMap
      };
    },

    _loadPackageByChartType: function() {
      this._packages = {
        'area': 'corechart',
        'bar': 'corechart',
        'bubble': 'corechart',
        'candlestick': 'corechart',
        'column': 'corechart',
        'combo': 'corechart',
        'geo': 'corechart',
        'histogram': 'corechart',
        'line': 'corechart',
        'pie': 'corechart',
        'scatter': 'corechart',
        'stepped-area': 'corechart',
        'table': 'table',
        'gauge': 'gauge',
        'treemap': 'treemap'
      };
    },

    _loadData: function() {
      this._canDraw = false;
      if (this._isReady) {
        if (typeof this.data == 'string' || this.data instanceof String) {
          // Load data asynchronously, from external URL.
          this.$.ajax.generateRequest();
        } else {
          var dataTable = this._createDataTable();
          this._canDraw = true;
          if (dataTable) {
            this._dataTable = dataTable;
            this.drawChart();
          }
        }
      }
    },

    _externalDataLoaded: function(e) {
      var dataTable = this._createDataTable(e.detail.response);
      this._canDraw = true;
      this._dataTable = dataTable;
      this.drawChart();
    },

    _createDataTable: function(data) {
      var dataTable = null;

      // If a data object was not passed to this function, default to the
      // chart's data attribute. Passing a data object is necessary for
      // cases when the data attribute is a URL pointing to an external
      // data source.
      if (!data) {
        data = this.data;
      }
      if (!data)
        data = [];

      if (this.rows && this.rows.length > 0 && this.cols &&
          this.cols.length > 0) {
        // Create the data table from cols and rows.
        dataTable = new google.visualization.DataTable();
        dataTable.cols = this.cols;

        for (var i = 0; i < this.cols.length; i++) {
          dataTable.addColumn(this.cols[i]);
        }

        dataTable.addRows(this.rows);
      } else {
        // Create dataTable from the passed data or the data attribute.
        // Data can be in the form of raw DataTable data or a two
        // dimensional array.
        if (data.rows && data.cols) {
          dataTable = new google.visualization.DataTable(data);
        } else if (data.length > 0) {
          dataTable = google.visualization.arrayToDataTable(data);
        }
      }

      return dataTable;
    }
  });
})();
</script>