+ The value for view is the result from DataView.toJSON.
+
+
+
On the data
+
+
+
+
+
On the charts
+
+
+
+
Customizing with JavaScript
+
+
+
+
+
Making Dashboards with google-chart-dashboard
+
+
Control Charts with google-chart-control
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Unbound Charts and Controls
+
+ Uncontrolled charts are drawn with the full dataset.
+ Unconnected controls are hidden.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Resizing Charts
+
+
Via iron-media-query
+
+ Using this element allows you to easily create different views for desktop and mobile.
+
+
+
+
+
+
+
Via iron-resize
+
+ When you need precise control over the size of the chart, you'll want to use the iron-resizable-behavior.
+
+
+
+
+ Hello, world!
+
+
+
+
+
Changing Data
+
+ These charts' values will change every 3 seconds.
+
+
+
+
+
+
+
+
+
+
+
Selection Demo
+
+
+
+ Selected row: [[selectionDemoRow]]
+
+
+
+
Action Demo
+
+ We can create actions for the tooltips.
+
+
+
+
+ Action ID: [[actionDemoId]]
+
+
+
+
Event Demo
+
+ We just listen for mouseover on this chart to update the text.
+
+
+
+
+ Moused over row: [[eventDemoRow]]
+
+
+
+
Chart Image Data URI
+
+ Mirrors the above event demo chart (also on select) using its image URI.
+
+
+
+
Tweaking charts with a google-chart-editor
+
+ A ChartEditor can be placed around a google-chart with
+ a google-chart-editor element.
+ Its options, type, and src property changes
+ are reflected to the contained chart as well as to the element's properties.
+ A google-chart-editor may contain anything but only the first
+ google-chart element will be used for editing purposes.
+
+
+
Data Source via URL text box
+
+
+
+ Open Editor
+
+
Data Source via custom element
+
+
+ Which would you like?
+
+
+
+
+
+ Open Editor
+
+
+
\ No newline at end of file
diff --git a/demo/gallery-demo.html b/demo/gallery-demo.html
new file mode 100644
index 0000000..21213c5
--- /dev/null
+++ b/demo/gallery-demo.html
@@ -0,0 +1,260 @@
+
+
+
+
+
+
+
+
+
+
+
+
Area Chart
+
+
+
Area Chart (Stepped)
+
+
+
Bar Chart
+
+
+
Bar Chart (Material)
+
+
+
Bubble Chart
+
+
+
+
+
Candlestick Chart
+
+
+
+
+
Column Chart (default type)
+
+
+
Combo Chart
+
+
+
+
+
Geo Chart
+
+
+
+
+
Histogram
+
+
+
Line Chart
+
+
+
Line Chart (Material)
+
+
+
Org Chart
+
+
+
+
+
Pie Chart
+
+
+
Sankey Diagram
+
+
+
+
+
+
+
Scatter Chart
+
+
+
Scatter Chart (Material)
+
+
+
Table
+
+
+
Timeline
+
+
+
+
+
Word Tree
+
+
+
+
+
Gauges
+
+
+
+
+
Tree Map
+
+
+
+ Go Up And Draw
+
+
+
\ No newline at end of file
diff --git a/demo/google-chart-demo.html b/demo/google-chart-demo.html
new file mode 100644
index 0000000..fec24b3
--- /dev/null
+++ b/demo/google-chart-demo.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+ The google-chart-query works in the same way as a google-chart-data element.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/google-chart-api.html b/google-chart-api.html
new file mode 100644
index 0000000..9408160
--- /dev/null
+++ b/google-chart-api.html
@@ -0,0 +1 @@
+
diff --git a/google-chart-control.html b/google-chart-control.html
new file mode 100644
index 0000000..9f85467
--- /dev/null
+++ b/google-chart-control.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
Loading control...
+
+
+
diff --git a/google-chart-control.js b/google-chart-control.js
new file mode 100644
index 0000000..5fe1f2b
--- /dev/null
+++ b/google-chart-control.js
@@ -0,0 +1,173 @@
+(() => {
+
+/**
+ * Supported control type short hand values.
+ * @enum {string}
+ */
+const ControlTypes = {
+ 'category': 'CategoryFilter',
+ 'filter': 'StringFilter',
+ 'range': 'NumberRangeFilter',
+ 'range-chart': 'ChartRangeFilter',
+ 'range-date': 'DateRangeFilter',
+};
+
+const loader = new GoogleChartLoader(['controls']);
+
+Polymer({
+ is: 'google-chart-control',
+ properties: {
+ /**
+ * The type of control we should draw.
+ * This can be a string in the `ControlTypes` object or any string corresponding to
+ * a valid control name.
+ * @type {string}
+ * @attribute type
+ */
+ type: {
+ type: String,
+ value: 'range',
+ },
+ /**
+ * The options of the specific control.
+ * @type {!Object}
+ * @attribute options
+ */
+ options: {
+ type: Object,
+ value: () => ({}),
+ },
+ /**
+ * The state of the specific control.
+ * @type {Object|undefined}
+ * @attribute state
+ */
+ state: {
+ type: Object,
+ notify: true,
+ },
+ /**
+ * True when the control has been drawn and is ready for interaction.
+ * @type {boolean}
+ * @attribute drawn
+ */
+ drawn: {
+ type: Boolean,
+ notify: true,
+ readOnly: true,
+ value: false,
+ },
+ /**
+ * The label of the column in the data to control.
+ * Either `label` or `index` should be set, not both.
+ * @type {string}
+ * @attribute label
+ */
+ label: {
+ type: String,
+ value: null,
+ observer: '_labelChanged',
+ },
+ /**
+ * The index of the column in the data to control.
+ * Either `label` or `index` should be set, not both.
+ * @type {number}
+ * @attribute index
+ */
+ index: {
+ type: Number,
+ value: -1,
+ observer: '_indexChanged',
+ },
+ /**
+ * Specifies the group for the chart in a Dashboard.
+ * @type {string}
+ * @attribute group
+ */
+ group: {
+ type: String,
+ },
+ /**
+ * Internal promise for creating a `ChartWrapper`.
+ * Should not be used externally.
+ * @type {!Promise}
+ * @attribute wrapper
+ */
+ wrapper: {
+ type: String,
+ readOnly: true,
+ notify: true,
+ computed: '_computeWrapper(type)',
+ },
+ },
+ observers: [
+ '_draw(options.*)',
+ '_draw(state.*)',
+ ],
+
+ /**
+ * Update the options with the index properties.
+ * Only one of index or label should be set.
+ * @param {number} index the column index to control
+ */
+ _indexChanged(index) {
+ this.set('options.filterColumnIndex', index >= 0 ? index : undefined);
+ },
+
+ /**
+ * Update the options with the label properties.
+ * Only one of index or label should be set.
+ * @param {?string} label the column label to control
+ */
+ _labelChanged(label) {
+ this.set('options.filterColumnLabel', label || undefined);
+ },
+
+ _draw() {
+ if (!this.drawn || !this.wrapper || this._dontReact) {
+ this._dontReact = false;
+ return;
+ }
+ this.wrapper.then(w => {
+ requestAnimationFrame(() => {
+ w.setState(this.state);
+ w.setOptions(this.options);
+ w.draw();
+ });
+ });
+ },
+
+ /**
+ * Creates a `ControlWrapper` for the specified `type`.
+ * @param {string} type the type of the `Control`
+ * @return {!Promise}
+ */
+ _computeWrapper(type) {
+ this._setDrawn(false);
+ return loader.visualization.then(v => {
+ const w = new v.ControlWrapper({
+ 'controlType': ControlTypes[this.type] || this.type,
+ 'container': this.$.control,
+ 'options': this.options,
+ 'state': this.state ,
+ });
+ v.events.addOneTimeListener(w, 'ready', () => {
+ this._dontReact = true;
+ loader.moveStyles(this);
+ this._setDrawn(true);
+ this.state = w.getState();
+ this.fire('google-chart-ready', w.getControl());
+ // We draw it a second time so that the ranges render correctly...
+ this._draw();
+ });
+ v.events.addListener(w, 'statechange', () => {
+ this._dontReact = true;
+ this.state = w.getState();
+ this.fire('google-chart-statechange', this.state);
+ });
+ return w;
+ });
+ },
+});
+
+})();
diff --git a/google-chart-dashboard.html b/google-chart-dashboard.html
new file mode 100644
index 0000000..2310b3e
--- /dev/null
+++ b/google-chart-dashboard.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/google-chart-dashboard.js b/google-chart-dashboard.js
new file mode 100644
index 0000000..73d916c
--- /dev/null
+++ b/google-chart-dashboard.js
@@ -0,0 +1,223 @@
+(() => {
+
+const loader = new GoogleChartLoader(['controls']);
+
+Polymer({
+ is: 'google-chart-dashboard',
+ properties: {
+ /**
+ * The data we should draw.
+ * This can be a `DataTable`, `DataView`, or a 2D Array.
+ * @type {!DataTable|!Array|undefined}
+ * @attribute data
+ */
+ data: {
+ type: Object,
+ notify: true,
+ },
+
+ /**
+ * The current selection in the dashboard.
+ * @type {!Array<{col:number,row:number}>}
+ * @attribute selection
+ */
+ selection: {
+ type: Array,
+ notify: true,
+ readOnly: true,
+ value: () => [],
+ },
+
+ /**
+ * Indicates if the dashboard has finished drawing.
+ * @type {boolean}
+ * @attribute drawn
+ */
+ drawn: {
+ type: Boolean,
+ notify: true,
+ readOnly: true,
+ value: false,
+ },
+
+ /**
+ * Whether the dashboard has been bound.
+ * @type {!Promise}
+ * @attribute wrappers
+ */
+ bound: {
+ type: Boolean,
+ readOnly: true,
+ value: false,
+ },
+
+ /**
+ * Internal promise for the `Dashboard` creation.
+ * @type {!Promise}
+ * @attribute dashboard
+ */
+ dashboard: {
+ type: Object,
+ readOnly: true,
+ },
+
+ /**
+ * Internal object tracking the chart and control groups.
+ * @type {!Object,
+ * charts: !Array}>}
+ * @attribute groups
+ */
+ groups: {
+ type: Object,
+ readOnly: true,
+ },
+ },
+
+ listeners: {
+ 'google-chart-data-change': '_onDataChanged',
+ 'google-chart-select': '_onSelectChanged',
+ },
+ observers: [
+ '_bindDashboard(dashboard, groups)',
+ '_drawInitialDashboard(bound, data)',
+ ],
+ _uncontrolledCharts: [],
+
+ /**
+ * Let's iterate through all the charts and controls, building their bind groups.
+ * Those without a specified group are put into a default group.
+ * @return {!Object,
+ * charts: !Array}>}
+ */
+ _createGroups() {
+ const $ = q => Polymer.dom(this).querySelectorAll(q);
+ const groups = {};
+ const getGroup = id => {
+ id = id || '__DEFAULT';
+ if (!groups[id]) {
+ groups[id] = {controls:[], charts:[]};
+ }
+ return groups[id];
+ };
+ const wrapper = el => new Promise(resolve => {
+ const wrapperChanged = () => {
+ resolve(el);
+ el.removeEventListener('wrapper-changed', wrapperChanged);
+ };
+ el.addEventListener('wrapper-changed', wrapperChanged);
+ });
+ $('google-chart-control').forEach(
+ control => getGroup(control.group).controls.push(wrapper(control)));
+ $('google-chart').forEach(
+ chart => getGroup(chart.group).charts.push(wrapper(chart)));
+ return groups;
+ },
+
+ /**
+ * After the dashboard is attached, we can start looking for charts and controls.
+ * We'll go ahead and create the Dashboard, too.
+ */
+ attached() {
+ this._setGroups(this._createGroups());
+ this._setDashboard(loader.visualization.then(v => new v.Dashboard(this.$.dashboard)));
+ },
+
+ /**
+ * Once we have the groups of charts and controls, we need to bind them wrappers.
+ * For each group, if we have at least one chart and control, we're good.
+ * If there are no controls in a group, add the chart to the uncontrolled list.
+ * (These charts will get the full dataset to be drawn with, later.)
+ * If there are no charts in a group, set the `unconnected` class on the control.
+ * (The styling for unconnected controls will hide them.)
+ * @param {!google.visualization.Dashboard} dashboard
+ * @param {!Object>,
+ * charts: !Array}>>} groups the chart and control binding groups
+ * @return {!Array>} a promise for the completed binding phase
+ */
+ _bindDashboard(dashboard, groups) {
+ if (!dashboard || !groups) {
+ return;
+ }
+ const wrappers = [];
+ for (const id in groups) {
+ const group = groups[id];
+ Promise.all([Promise.all(group.charts), Promise.all(group.controls)]).then(cc => {
+ const [charts, controls] = cc;
+ // Bind controls charts if the are specified
+ if (charts.length && controls.length) {
+ // We need to resolve the dashboard and all the wrappers before binding.
+ wrappers.push(Promise.all([
+ Promise.all(controls.map(c => c.wrapper)),
+ Promise.all(charts.map(c => c.wrapper)),
+ ]));
+ } else if (charts.length) {
+ this._uncontrolledCharts.push(...charts);
+ } else if (controls.length) {
+ // Add the class `unconnected` to unconnected controls.
+ // `google-chart-control` should hide itself when this class is added.
+ controls.forEach(c => {
+ Polymer.dom(c).classList.add('unconnected');
+ });
+ }
+ });
+ }
+ Promise.all([dashboard, wrappers]).then(dww => {
+ const [d, ww] = dww;
+ return Promise.all(ww.map(w => w.then(cc => d.bind(cc[0], cc[1]))));
+ }).then(() => this._setBound(true));
+ },
+
+ /**
+ * Bindings are configured, now we need to draw data changes.
+ * For all the uncontrolled charts, just set the data on them.
+ * @param {!Promise} dashboard
+ * @param {!Promise} a promise for the completed binding phase
+ * @param {!google.visualization.DataTable}
+ */
+ _drawInitialDashboard(bound, data) {
+ if (!bound || !data) {
+ return;
+ }
+ this._setDrawn(false);
+ this._uncontrolledCharts.forEach(c => {
+ c.data = data;
+ });
+ this.dashboard.then(d => {
+ d.draw(data);
+ this._setDrawn(true);
+ });
+ },
+
+ /**
+ * Handle data updates fired from within the dashboard.
+ * We'll stop it because no other element should be interested.
+ * @param {!Event} evt the `google-chart-data-change` event
+ */
+ _onDataChanged(evt) {
+ evt.stopPropagation();
+ this.data = evt.detail;
+ },
+
+ /**
+ * Stop and re-fire the select event in the context of the dashboard.
+ * Chart select events are different from a dashboard.
+ * (they are based on the chart's data slice, not the full dataset)
+ * If someone is listening for a select on the Dashboard, they should get
+ * a reference to the Dashboard's dataset.
+ * If the select event comes from an uncontrolled chart,
+ * the selection value will remain unchanged.
+ * @param {!Event} evt the `google-chart-select` event
+ */
+ _onSelectChanged(evt) {
+ evt.stopPropagation();
+ this.dashboard.then(d => {
+ this._setSelection(d.getSelection());
+ this.fire('google-chart-select', this.selection, {node: this.parentNode});
+ });
+ },
+});
+
+})();
diff --git a/google-chart-data.html b/google-chart-data.html
new file mode 100644
index 0000000..7a98dac
--- /dev/null
+++ b/google-chart-data.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/google-chart-data.js b/google-chart-data.js
new file mode 100644
index 0000000..58af23b
--- /dev/null
+++ b/google-chart-data.js
@@ -0,0 +1,129 @@
+Polymer({
+ is: 'google-chart-data',
+ properties: {
+ /**
+ * The main data property of the element.
+ * Can be either a `DataTable` or a `DataView` if `view` is set.
+ * @type {?google.visualization.IDataTable}
+ * @attribute data
+ */
+ data: {
+ type: Object,
+ notify: true,
+ readOnly: true,
+ observer: '_onDataChanged'
+ },
+ /**
+ * Can be either a 2D-`Array` or `Object` `DataTable` format.
+ * @type {(Array|Object)}
+ * @attribute value
+ */
+ value: {
+ type: Array,
+ },
+ /**
+ * An array specifying the column definitions.
+ * This should only be used with `rows`, not `value`.
+ * @type {Array