├── .eslintrc
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build
├── build.js
├── ci.sh
├── karma.conf.js
└── release.sh
├── circle.yml
├── dist
├── vue-charts.common.js
├── vue-charts.js
├── vue-charts.min.js
└── vue-charts.min.js.gz
├── examples
├── basic.html
├── events.html
├── geochart.html
├── redraw.html
└── sets.html
├── package.json
├── src
├── components
│ └── chart.js
├── main.js
└── utils
│ ├── eventsBinder.js
│ ├── googleChartsLoader.js
│ ├── index.js
│ ├── makeDeferred.js
│ └── propsWatcher.js
└── test
└── unit
├── .eslintrc
└── specs
├── index.js
└── utils.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | 'root': true,
3 |
4 | 'env': {
5 | 'browser': true,
6 | 'node': true
7 | },
8 |
9 | 'globals': {
10 | '_': true,
11 | 'google': true,
12 | 'Vue': true
13 | },
14 |
15 | 'extends': 'standard'
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 | coverage
3 | node_modules
4 | .DS_Store
5 | *.log
6 | *.swp
7 | *~
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*
2 | *.log
3 | *.swp
4 | *.yml
5 | bower.json
6 | coverage
7 | config
8 | dist/*.map
9 | lib
10 | test
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v0.2.1 / 2016-10-24
2 | - Fixed linting errors
3 |
4 | # v0.2.0 / 2016-10-23
5 | - Merged https://github.com/haydenbbickerton/vue-charts/pull/14
6 | - Ready for vue 2 (thanks to [syshen!](https://github.com/syshen))
7 |
8 | # v0.1.13 / 2016-04-06
9 | - es6 tweaks
10 |
11 | # v0.1.12 / 2016-04-06
12 | - Fixed https://github.com/haydenbbickerton/vue-charts/issues/4
13 |
14 | # v0.1.1 / 2016-04-01
15 | - Fixed https://github.com/haydenbbickerton/vue-charts/issues/3
16 |
17 | # v0.1.0 / 2016-03-09
18 | - Added event handling
19 |
20 | # v0.0.5 / 2016-02-15
21 | - "First" release
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Hayden Bickerton
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-charts
2 | [](https://www.npmjs.com/package/vue-charts)
3 | [](https://circleci.com/gh/haydenbbickerton/vue-charts/tree/master)
4 | [](http://standardjs.com)
5 | [](LICENSE)
6 |
7 | Google Charts plugin for Vue.js
8 |
9 | ## Demo
10 | - [Basic Line Chart](https://haydenbbickerton.github.io/vue-charts/basic.html)
11 | - [Multiple Sets of Data, with auto-update](https://haydenbbickerton.github.io/vue-charts/sets.html)
12 | - [Events](https://haydenbbickerton.github.io/vue-charts/events.html)
13 | - [Redraw on window resize](https://haydenbbickerton.github.io/vue-charts/redraw.html)
14 |
15 | ## Installation
16 |
17 | ```shell
18 | npm install --save-dev vue-charts
19 | ```
20 |
21 | ### Usage
22 |
23 | ```js
24 | Vue.use(VueCharts)
25 | ```
26 | ```html
27 |
28 |
34 | ```
35 |
36 | ## Props
37 |
38 |
39 |
40 |
41 | Name |
42 | Default |
43 | Type |
44 | Description |
45 |
46 |
47 |
48 |
49 | packages |
50 | ['corechart'] |
51 | Array |
52 | Google Chart Packages to load. |
53 |
54 |
55 | version |
56 | current |
57 | String |
58 | Google Chart Version to load. |
59 |
60 |
61 | chart-type |
62 | LineChart |
63 | String |
64 | The type of chart to create. |
65 |
66 |
67 | columns |
68 | none, required |
69 | Array |
70 | Required. Chart columns. |
71 |
72 |
73 | rows |
74 | none |
75 | Array |
76 | Chart rows. |
77 |
78 |
79 | chart-events |
80 | none |
81 | Object |
82 | Google Charts Events. See Events Example |
83 |
84 |
85 | options |
86 | none |
87 | Object |
88 | Google Charts Options |
89 |
90 |
91 |
92 |
93 |
94 | # Credits
95 |
96 | This plugin is heavily based off of:
97 |
98 | - [vue-plugin-boilerplate](https://github.com/kazupon/vue-plugin-boilerplate)
99 | - [vue-google-maps](https://github.com/GuillaumeLeclerc/vue-google-maps/)
100 | - [react-google-charts](https://github.com/RakanNimer/react-google-charts)
101 |
102 | # License
103 |
104 | [MIT](http://opensource.org/licenses/MIT)
105 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var zlib = require('zlib')
3 | var rollup = require('rollup')
4 | var uglify = require('uglify-js')
5 | var babel = require('rollup-plugin-babel')
6 | var replace = require('rollup-plugin-replace')
7 | var pack = require('../package.json')
8 | var version = process.env.VERSION || pack.version
9 | var banner =
10 | '/*!\n' +
11 | ' * ' + pack.name + ' v' + version + '\n' +
12 | ' * (c) ' + new Date().getFullYear() + ' ' + pack.author.name + '\n' +
13 | ' * Released under the ' + pack.license + ' License.\n' +
14 | ' */'
15 |
16 | // update main file
17 | var main = fs
18 | .readFileSync('src/main.js', 'utf-8')
19 | .replace(/plugin\.version = '[\d\.]+'/, "plugin.version = '" + pack.version + "'")
20 | fs.writeFileSync('src/main.js', main)
21 |
22 | // CommonJS build.
23 | // this is used as the "main" field in package.json
24 | // and used by bundlers like Webpack and Browserify.
25 | rollup.rollup({
26 | entry: 'src/main.js',
27 | plugins: [
28 | babel({
29 | presets: ['es2015-rollup']
30 | })
31 | ]
32 | })
33 | .then(function (bundle) {
34 | return write('dist/' + pack.name + '.common.js', bundle.generate({
35 | format: 'cjs',
36 | banner: banner
37 | }).code)
38 | })
39 | // Standalone Dev Build
40 | .then(function () {
41 | return rollup.rollup({
42 | entry: 'src/main.js',
43 | plugins: [
44 | replace({
45 | 'process.env.NODE_ENV': "'development'"
46 | }),
47 | babel({
48 | presets: ['es2015-rollup']
49 | })
50 | ]
51 | })
52 | .then(function (bundle) {
53 | return write('dist/' + pack.name + '.js', bundle.generate({
54 | format: 'umd',
55 | banner: banner,
56 | moduleName: classify(pack.name)
57 | }).code)
58 | })
59 | })
60 | .then(function () {
61 | // Standalone Production Build
62 | return rollup.rollup({
63 | entry: 'src/main.js',
64 | plugins: [
65 | replace({
66 | 'process.env.NODE_ENV': "'production'"
67 | }),
68 | babel({
69 | presets: ['es2015-rollup']
70 | })
71 | ]
72 | })
73 | .then(function (bundle) {
74 | var code = bundle.generate({
75 | format: 'umd',
76 | moduleName: classify(pack.name)
77 | }).code
78 | var minified = banner + '\n' + uglify.minify(code, {
79 | fromString: true
80 | }).code
81 | return write('dist/' + pack.name + '.min.js', minified)
82 | })
83 | .then(zip)
84 | })
85 | .catch(logError)
86 |
87 | function toUpper (_, c) {
88 | return c ? c.toUpperCase() : ''
89 | }
90 |
91 | const classifyRE = /(?:^|[-_\/])(\w)/g
92 | function classify (str) {
93 | return str.replace(classifyRE, toUpper)
94 | }
95 |
96 | function write (dest, code) {
97 | return new Promise(function (resolve, reject) {
98 | fs.writeFile(dest, code, function (err) {
99 | if (err) return reject(err)
100 | console.log(blue(dest) + ' ' + getSize(code))
101 | resolve()
102 | })
103 | })
104 | }
105 |
106 | function zip () {
107 | return new Promise(function (resolve, reject) {
108 | fs.readFile('dist/' + pack.name + '.min.js', function (err, buf) {
109 | if (err) return reject(err)
110 | zlib.gzip(buf, function (err, buf) {
111 | if (err) return reject(err)
112 | write('dist/' + pack.name + '.min.js.gz', buf).then(resolve)
113 | })
114 | })
115 | })
116 | }
117 |
118 | function getSize (code) {
119 | return (code.length / 1024).toFixed(2) + 'kb'
120 | }
121 |
122 | function logError (e) {
123 | console.log(e)
124 | }
125 |
126 | function blue (str) {
127 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
128 | }
129 |
--------------------------------------------------------------------------------
/build/ci.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | if [ -z "$CI_PULL_REQUEST" ]
3 | then
4 | npm run lint
5 | npm run unit
6 | else
7 | npm test
8 | fi
--------------------------------------------------------------------------------
/build/karma.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function (config) {
4 | config.set({
5 | browsers: ['PhantomJS'],
6 | frameworks: ['mocha', 'sinon-chai'],
7 | files: [
8 | '../node_modules/babel-polyfill/dist/polyfill.js',
9 | '../test/unit/specs/index.js'
10 | ],
11 | preprocessors: {
12 | '../test/unit/specs/index.js': ['webpack', 'sourcemap']
13 | },
14 | webpack: {
15 | devtool: 'source-map',
16 | resolve: {
17 | alias: {
18 | 'src': path.resolve(__dirname, '../src')
19 | }
20 | },
21 | module: {
22 | loaders: [{
23 | test: /\.js$/,
24 | exclude: /node_modules|vue\/dist/,
25 | loader: 'babel',
26 | query: {
27 | presets: ['es2015'],
28 | plugins: [
29 | ['babel-plugin-espower']
30 | ]
31 | }
32 | }],
33 | postLoaders: [{
34 | test: /\.json$/,
35 | loader: 'json'
36 | }, {
37 | test: /\.js$/,
38 | exclude: /test|node_modules|vue\/dist/,
39 | loader: 'istanbul-instrumenter'
40 | }]
41 | }
42 | },
43 | webpackMiddleware: {
44 | noInfo: true
45 | },
46 | browserDisconnectTimeout: 5000,
47 | reporters: [
48 | 'mocha', 'coverage'
49 | ],
50 | coverageReporter: {
51 | reporters: [
52 | {type: 'lcov', dir: '../test/unit/coverage'},
53 | {type: 'text-summary', dir: '../test/unit/coverage'}]
54 | }
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/build/release.sh:
--------------------------------------------------------------------------------
1 | # Lifted from https://github.com/vuejs/vue-router/blob/e37a1ce49fa01016bec9f88584de2a89ad3881ef/build/release.sh
2 |
3 | set -e
4 | echo "Enter release version: "
5 | read VERSION
6 |
7 | read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r
8 | echo # (optional) move to a new line
9 | if [[ $REPLY =~ ^[Yy]$ ]]
10 | then
11 | echo "Releasing $VERSION ..."
12 |
13 | # run tests
14 | npm test 2>/dev/null
15 |
16 | # build
17 | VERSION=$VERSION npm run build
18 |
19 | # # commit
20 | git add -A
21 | git commit -m "[build] $VERSION"
22 | npm version $VERSION --message "[release] $VERSION"
23 |
24 | # # publish
25 | git push origin refs/tags/v$VERSION
26 | git push
27 | npm publish
28 | fi
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 5
4 |
5 | general:
6 | branches:
7 | only:
8 | - master
9 |
10 | test:
11 | override:
12 | - bash ./build/ci.sh
--------------------------------------------------------------------------------
/dist/vue-charts.common.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * vue-charts v0.2.1
3 | * (c) 2016 Hayden Bickerton
4 | * Released under the MIT License.
5 | */
6 | 'use strict';
7 |
8 | var _ = require('lodash');
9 | _ = 'default' in _ ? _['default'] : _;
10 |
11 | /*
12 | This lets us resolve the promise outside the
13 | promise function itself.
14 | */
15 | function makeDeferred() {
16 | var resolvePromise = null;
17 | var rejectPromise = null;
18 |
19 | var promise = new Promise(function (resolve, reject) {
20 | resolvePromise = resolve;
21 | rejectPromise = reject;
22 | });
23 |
24 | return {
25 | promise: promise,
26 | resolve: resolvePromise,
27 | reject: rejectPromise
28 | };
29 | }
30 |
31 | function eventsBinder(vue, googleChart, events) {
32 | // Loop through our events, create a listener for them, and
33 | // attach our callback function to that event.
34 | for (var event in events) {
35 | var eventName = event;
36 | var eventCallback = events[event];
37 |
38 | if (eventName === 'ready') {
39 | // The chart is already ready, so this event missed it's chance.
40 | // We'll call it manually.
41 | eventCallback();
42 | } else {
43 | google.visualization.events.addListener(googleChart, eventName, eventCallback);
44 | }
45 | }
46 | }
47 |
48 | var isLoading = false;
49 | var isLoaded = false;
50 |
51 | // Our main promise
52 | var googlePromise = makeDeferred();
53 |
54 | function googleChartsLoader() {
55 | var packages = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['corechart'];
56 | var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'current';
57 | var mapsApiKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
58 | var language = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'en';
59 |
60 | if (!Array.isArray(packages)) {
61 | throw new TypeError('packages must be an array');
62 | }
63 |
64 | if (version !== 'current' && typeof version !== 'number' && version !== 'upcoming') {
65 | throw new TypeError('version must be a number, "upcoming" or "current"');
66 | }
67 |
68 | // Google only lets you load it once, so we'll only run once.
69 | if (isLoading || isLoaded) {
70 | return googlePromise.promise;
71 | }
72 |
73 | isLoading = true;
74 |
75 | var script = document.createElement('script');
76 | script.setAttribute('src', 'https://www.gstatic.com/charts/loader.js');
77 |
78 | script.onreadystatechange = script.onload = function () {
79 | // After the 'loader.js' is loaded, load our version and packages
80 | var options = {
81 | packages: packages,
82 | language: language
83 | };
84 |
85 | if (mapsApiKey) {
86 | options['mapsApiKey'] = mapsApiKey;
87 | }
88 |
89 | google.charts.load(version, options);
90 |
91 | // After we've loaded Google Charts, resolve our promise
92 | google.charts.setOnLoadCallback(function () {
93 | isLoading = false;
94 | isLoaded = true;
95 | googlePromise.resolve();
96 | });
97 | };
98 |
99 | // Insert our script into the DOM
100 | document.getElementsByTagName('head')[0].appendChild(script);
101 |
102 | return googlePromise.promise;
103 | }
104 |
105 | function propsWatcher(vue, props) {
106 | /*
107 | Watch our props. Every time they change, redraw the chart.
108 | */
109 | _.each(props, function (_ref, attribute) {
110 | var type = _ref.type;
111 |
112 | vue.$watch(attribute, function () {
113 | vue.drawChart();
114 | }, {
115 | deep: _.isObject(type)
116 | });
117 | });
118 | }
119 |
120 | var chartDeferred = makeDeferred();
121 |
122 | var props = {
123 | packages: {
124 | type: Array,
125 | default: function _default() {
126 | return ['corechart'];
127 | }
128 | },
129 | version: {
130 | default: 'current'
131 | },
132 | mapsApiKey: {
133 | default: false
134 | },
135 | language: {
136 | type: String,
137 | default: 'en'
138 | },
139 | chartType: {
140 | type: String,
141 | default: function _default() {
142 | return 'LineChart';
143 | }
144 | },
145 | chartEvents: {
146 | type: Object,
147 | default: function _default() {
148 | return {};
149 | }
150 | },
151 | columns: {
152 | required: true,
153 | type: Array
154 | },
155 | rows: {
156 | type: Array,
157 | default: function _default() {
158 | return [];
159 | }
160 | },
161 | options: {
162 | type: Object,
163 | default: function _default() {
164 | return {
165 | chart: {
166 | title: 'Chart Title',
167 | subtitle: 'Subtitle'
168 | },
169 | hAxis: {
170 | title: 'X Label'
171 | },
172 | vAxis: {
173 | title: 'Y Label'
174 | },
175 | width: '400px',
176 | height: '300px',
177 | animation: {
178 | duration: 500,
179 | easing: 'out'
180 | }
181 | };
182 | }
183 | }
184 | };
185 |
186 | var Chart = {
187 | name: 'vue-chart',
188 | props: props,
189 | render: function render(h) {
190 | var self = this;
191 | return h('div', { class: 'vue-chart-container' }, [h('div', {
192 | attrs: {
193 | id: self.chartId,
194 | class: 'vue-chart'
195 | }
196 | })]);
197 | },
198 | data: function data() {
199 | return {
200 | chart: null,
201 | /*
202 | We put the uid in the DOM element so the component can be used multiple
203 | times in the same view. Otherwise Google Charts will only make one chart.
204 | The X is prepended because there must be at least
205 | 1 character in id - https://www.w3.org/TR/html5/dom.html#the-id-attribute
206 | */
207 | chartId: 'X' + this._uid,
208 | wrapper: null,
209 | dataTable: [],
210 | hiddenColumns: []
211 | };
212 | },
213 |
214 | events: {
215 | redrawChart: function redrawChart() {
216 | this.drawChart();
217 | }
218 | },
219 | mounted: function mounted() {
220 | var self = this;
221 | googleChartsLoader(self.packages, self.version, self.mapsApiKey, self.language).then(self.drawChart).then(function () {
222 | // we don't want to bind props because it's a kind of "computed" property
223 | var watchProps = props;
224 | delete watchProps.bounds;
225 |
226 | // watching properties
227 | propsWatcher(self, watchProps);
228 |
229 | // binding events
230 | eventsBinder(self, self.chart, self.chartEvents);
231 | }).catch(function (error) {
232 | throw error;
233 | });
234 | },
235 |
236 | methods: {
237 | /**
238 | * Initialize the datatable and add the initial data.
239 | *
240 | * @link https://developers.google.com/chart/interactive/docs/reference#DataTable
241 | * @return object
242 | */
243 | buildDataTable: function buildDataTable() {
244 | var self = this;
245 |
246 | var dataTable = new google.visualization.DataTable();
247 |
248 | _.each(self.columns, function (value) {
249 | dataTable.addColumn(value);
250 | });
251 |
252 | if (!_.isEmpty(self.rows)) {
253 | dataTable.addRows(self.rows);
254 | }
255 |
256 | return dataTable;
257 | },
258 |
259 |
260 | /**
261 | * Update the datatable.
262 | *
263 | * @return void
264 | */
265 | updateDataTable: function updateDataTable() {
266 | var self = this;
267 |
268 | // Remove all data from the datatable.
269 | self.dataTable.removeRows(0, self.dataTable.getNumberOfRows());
270 | self.dataTable.removeColumns(0, self.dataTable.getNumberOfColumns());
271 |
272 | // Add
273 | _.each(self.columns, function (value) {
274 | self.dataTable.addColumn(value);
275 | });
276 |
277 | if (!_.isEmpty(self.rows)) {
278 | self.dataTable.addRows(self.rows);
279 | }
280 | },
281 |
282 |
283 | /**
284 | * Initialize the wrapper
285 | *
286 | * @link https://developers.google.com/chart/interactive/docs/reference#chartwrapper-class
287 | *
288 | * @return object
289 | */
290 | buildWrapper: function buildWrapper(chartType, dataTable, options, containerId) {
291 | var wrapper = new google.visualization.ChartWrapper({
292 | chartType: chartType,
293 | dataTable: dataTable,
294 | options: options,
295 | containerId: containerId
296 | });
297 |
298 | return wrapper;
299 | },
300 |
301 |
302 | /**
303 | * Build the chart.
304 | *
305 | * @return void
306 | */
307 | buildChart: function buildChart() {
308 | var self = this;
309 |
310 | // If dataTable isn't set, build it
311 | var dataTable = _.isEmpty(self.dataTable) ? self.buildDataTable() : self.dataTable;
312 |
313 | self.wrapper = self.buildWrapper(self.chartType, dataTable, self.options, self.chartId);
314 |
315 | // Set the datatable on this instance
316 | self.dataTable = self.wrapper.getDataTable();
317 |
318 | // After chart is built, set it on this instance and resolve the promise.
319 | google.visualization.events.addOneTimeListener(self.wrapper, 'ready', function () {
320 | self.chart = self.wrapper.getChart();
321 | chartDeferred.resolve();
322 | });
323 | },
324 |
325 |
326 | /**
327 | * Draw the chart.
328 | *
329 | * @return Promise
330 | */
331 | drawChart: function drawChart() {
332 | var self = this;
333 |
334 | // We don't have any (usable) data, or we don't have columns. We can't draw a chart without those.
335 | if (!_.isEmpty(self.rows) && !_.isObjectLike(self.rows) || _.isEmpty(self.columns)) {
336 | return;
337 | }
338 |
339 | if (_.isNull(self.chart)) {
340 | // We haven't built the chart yet, so JUST. DO. IT!
341 | self.buildChart();
342 | } else {
343 | // Chart already exists, just update the data
344 | self.updateDataTable();
345 | }
346 |
347 | // Chart has been built/Data has been updated, draw the chart.
348 | self.wrapper.draw();
349 |
350 | // Return promise. Resolves when chart finishes loading.
351 | return chartDeferred.promise;
352 | }
353 | }
354 | };
355 |
356 | function install(Vue) {
357 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
358 |
359 | Vue.component('vue-chart', Chart);
360 | }
361 |
362 | module.exports = install;
--------------------------------------------------------------------------------
/dist/vue-charts.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * vue-charts v0.2.1
3 | * (c) 2016 Hayden Bickerton
4 | * Released under the MIT License.
5 | */
6 | (function (global, factory) {
7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('lodash')) :
8 | typeof define === 'function' && define.amd ? define(['lodash'], factory) :
9 | global.VueCharts = factory(global._);
10 | }(this, function (_) { 'use strict';
11 |
12 | _ = 'default' in _ ? _['default'] : _;
13 |
14 | /*
15 | This lets us resolve the promise outside the
16 | promise function itself.
17 | */
18 | function makeDeferred() {
19 | var resolvePromise = null;
20 | var rejectPromise = null;
21 |
22 | var promise = new Promise(function (resolve, reject) {
23 | resolvePromise = resolve;
24 | rejectPromise = reject;
25 | });
26 |
27 | return {
28 | promise: promise,
29 | resolve: resolvePromise,
30 | reject: rejectPromise
31 | };
32 | }
33 |
34 | function eventsBinder(vue, googleChart, events) {
35 | // Loop through our events, create a listener for them, and
36 | // attach our callback function to that event.
37 | for (var event in events) {
38 | var eventName = event;
39 | var eventCallback = events[event];
40 |
41 | if (eventName === 'ready') {
42 | // The chart is already ready, so this event missed it's chance.
43 | // We'll call it manually.
44 | eventCallback();
45 | } else {
46 | google.visualization.events.addListener(googleChart, eventName, eventCallback);
47 | }
48 | }
49 | }
50 |
51 | var isLoading = false;
52 | var isLoaded = false;
53 |
54 | // Our main promise
55 | var googlePromise = makeDeferred();
56 |
57 | function googleChartsLoader() {
58 | var packages = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['corechart'];
59 | var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'current';
60 | var mapsApiKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
61 | var language = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'en';
62 |
63 | if (!Array.isArray(packages)) {
64 | throw new TypeError('packages must be an array');
65 | }
66 |
67 | if (version !== 'current' && typeof version !== 'number' && version !== 'upcoming') {
68 | throw new TypeError('version must be a number, "upcoming" or "current"');
69 | }
70 |
71 | // Google only lets you load it once, so we'll only run once.
72 | if (isLoading || isLoaded) {
73 | return googlePromise.promise;
74 | }
75 |
76 | isLoading = true;
77 |
78 | var script = document.createElement('script');
79 | script.setAttribute('src', 'https://www.gstatic.com/charts/loader.js');
80 |
81 | script.onreadystatechange = script.onload = function () {
82 | // After the 'loader.js' is loaded, load our version and packages
83 | var options = {
84 | packages: packages,
85 | language: language
86 | };
87 |
88 | if (mapsApiKey) {
89 | options['mapsApiKey'] = mapsApiKey;
90 | }
91 |
92 | google.charts.load(version, options);
93 |
94 | // After we've loaded Google Charts, resolve our promise
95 | google.charts.setOnLoadCallback(function () {
96 | isLoading = false;
97 | isLoaded = true;
98 | googlePromise.resolve();
99 | });
100 | };
101 |
102 | // Insert our script into the DOM
103 | document.getElementsByTagName('head')[0].appendChild(script);
104 |
105 | return googlePromise.promise;
106 | }
107 |
108 | function propsWatcher(vue, props) {
109 | /*
110 | Watch our props. Every time they change, redraw the chart.
111 | */
112 | _.each(props, function (_ref, attribute) {
113 | var type = _ref.type;
114 |
115 | vue.$watch(attribute, function () {
116 | vue.drawChart();
117 | }, {
118 | deep: _.isObject(type)
119 | });
120 | });
121 | }
122 |
123 | var chartDeferred = makeDeferred();
124 |
125 | var props = {
126 | packages: {
127 | type: Array,
128 | default: function _default() {
129 | return ['corechart'];
130 | }
131 | },
132 | version: {
133 | default: 'current'
134 | },
135 | mapsApiKey: {
136 | default: false
137 | },
138 | language: {
139 | type: String,
140 | default: 'en'
141 | },
142 | chartType: {
143 | type: String,
144 | default: function _default() {
145 | return 'LineChart';
146 | }
147 | },
148 | chartEvents: {
149 | type: Object,
150 | default: function _default() {
151 | return {};
152 | }
153 | },
154 | columns: {
155 | required: true,
156 | type: Array
157 | },
158 | rows: {
159 | type: Array,
160 | default: function _default() {
161 | return [];
162 | }
163 | },
164 | options: {
165 | type: Object,
166 | default: function _default() {
167 | return {
168 | chart: {
169 | title: 'Chart Title',
170 | subtitle: 'Subtitle'
171 | },
172 | hAxis: {
173 | title: 'X Label'
174 | },
175 | vAxis: {
176 | title: 'Y Label'
177 | },
178 | width: '400px',
179 | height: '300px',
180 | animation: {
181 | duration: 500,
182 | easing: 'out'
183 | }
184 | };
185 | }
186 | }
187 | };
188 |
189 | var Chart = {
190 | name: 'vue-chart',
191 | props: props,
192 | render: function render(h) {
193 | var self = this;
194 | return h('div', { class: 'vue-chart-container' }, [h('div', {
195 | attrs: {
196 | id: self.chartId,
197 | class: 'vue-chart'
198 | }
199 | })]);
200 | },
201 | data: function data() {
202 | return {
203 | chart: null,
204 | /*
205 | We put the uid in the DOM element so the component can be used multiple
206 | times in the same view. Otherwise Google Charts will only make one chart.
207 | The X is prepended because there must be at least
208 | 1 character in id - https://www.w3.org/TR/html5/dom.html#the-id-attribute
209 | */
210 | chartId: 'X' + this._uid,
211 | wrapper: null,
212 | dataTable: [],
213 | hiddenColumns: []
214 | };
215 | },
216 |
217 | events: {
218 | redrawChart: function redrawChart() {
219 | this.drawChart();
220 | }
221 | },
222 | mounted: function mounted() {
223 | var self = this;
224 | googleChartsLoader(self.packages, self.version, self.mapsApiKey, self.language).then(self.drawChart).then(function () {
225 | // we don't want to bind props because it's a kind of "computed" property
226 | var watchProps = props;
227 | delete watchProps.bounds;
228 |
229 | // watching properties
230 | propsWatcher(self, watchProps);
231 |
232 | // binding events
233 | eventsBinder(self, self.chart, self.chartEvents);
234 | }).catch(function (error) {
235 | throw error;
236 | });
237 | },
238 |
239 | methods: {
240 | /**
241 | * Initialize the datatable and add the initial data.
242 | *
243 | * @link https://developers.google.com/chart/interactive/docs/reference#DataTable
244 | * @return object
245 | */
246 | buildDataTable: function buildDataTable() {
247 | var self = this;
248 |
249 | var dataTable = new google.visualization.DataTable();
250 |
251 | _.each(self.columns, function (value) {
252 | dataTable.addColumn(value);
253 | });
254 |
255 | if (!_.isEmpty(self.rows)) {
256 | dataTable.addRows(self.rows);
257 | }
258 |
259 | return dataTable;
260 | },
261 |
262 |
263 | /**
264 | * Update the datatable.
265 | *
266 | * @return void
267 | */
268 | updateDataTable: function updateDataTable() {
269 | var self = this;
270 |
271 | // Remove all data from the datatable.
272 | self.dataTable.removeRows(0, self.dataTable.getNumberOfRows());
273 | self.dataTable.removeColumns(0, self.dataTable.getNumberOfColumns());
274 |
275 | // Add
276 | _.each(self.columns, function (value) {
277 | self.dataTable.addColumn(value);
278 | });
279 |
280 | if (!_.isEmpty(self.rows)) {
281 | self.dataTable.addRows(self.rows);
282 | }
283 | },
284 |
285 |
286 | /**
287 | * Initialize the wrapper
288 | *
289 | * @link https://developers.google.com/chart/interactive/docs/reference#chartwrapper-class
290 | *
291 | * @return object
292 | */
293 | buildWrapper: function buildWrapper(chartType, dataTable, options, containerId) {
294 | var wrapper = new google.visualization.ChartWrapper({
295 | chartType: chartType,
296 | dataTable: dataTable,
297 | options: options,
298 | containerId: containerId
299 | });
300 |
301 | return wrapper;
302 | },
303 |
304 |
305 | /**
306 | * Build the chart.
307 | *
308 | * @return void
309 | */
310 | buildChart: function buildChart() {
311 | var self = this;
312 |
313 | // If dataTable isn't set, build it
314 | var dataTable = _.isEmpty(self.dataTable) ? self.buildDataTable() : self.dataTable;
315 |
316 | self.wrapper = self.buildWrapper(self.chartType, dataTable, self.options, self.chartId);
317 |
318 | // Set the datatable on this instance
319 | self.dataTable = self.wrapper.getDataTable();
320 |
321 | // After chart is built, set it on this instance and resolve the promise.
322 | google.visualization.events.addOneTimeListener(self.wrapper, 'ready', function () {
323 | self.chart = self.wrapper.getChart();
324 | chartDeferred.resolve();
325 | });
326 | },
327 |
328 |
329 | /**
330 | * Draw the chart.
331 | *
332 | * @return Promise
333 | */
334 | drawChart: function drawChart() {
335 | var self = this;
336 |
337 | // We don't have any (usable) data, or we don't have columns. We can't draw a chart without those.
338 | if (!_.isEmpty(self.rows) && !_.isObjectLike(self.rows) || _.isEmpty(self.columns)) {
339 | return;
340 | }
341 |
342 | if (_.isNull(self.chart)) {
343 | // We haven't built the chart yet, so JUST. DO. IT!
344 | self.buildChart();
345 | } else {
346 | // Chart already exists, just update the data
347 | self.updateDataTable();
348 | }
349 |
350 | // Chart has been built/Data has been updated, draw the chart.
351 | self.wrapper.draw();
352 |
353 | // Return promise. Resolves when chart finishes loading.
354 | return chartDeferred.promise;
355 | }
356 | }
357 | };
358 |
359 | function install(Vue) {
360 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
361 |
362 | Vue.component('vue-chart', Chart);
363 | }
364 |
365 | return install;
366 |
367 | }));
--------------------------------------------------------------------------------
/dist/vue-charts.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * vue-charts v0.2.1
3 | * (c) 2016 Hayden Bickerton
4 | * Released under the MIT License.
5 | */
6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("lodash")):"function"==typeof define&&define.amd?define(["lodash"],t):e.VueCharts=t(e._)}(this,function(e){"use strict";function t(){var e=null,t=null,a=new Promise(function(a,r){e=a,t=r});return{promise:a,resolve:e,reject:t}}function a(e,t,a){for(var r in a){var n=r,i=a[r];"ready"===n?i():google.visualization.events.addListener(t,n,i)}}function r(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["corechart"],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"current",a=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"en";if(!Array.isArray(e))throw new TypeError("packages must be an array");if("current"!==t&&"number"!=typeof t&&"upcoming"!==t)throw new TypeError('version must be a number, "upcoming" or "current"');if(o||u)return s.promise;o=!0;var n=document.createElement("script");return n.setAttribute("src","https://www.gstatic.com/charts/loader.js"),n.onreadystatechange=n.onload=function(){var n={packages:e,language:r};a&&(n.mapsApiKey=a),google.charts.load(t,n),google.charts.setOnLoadCallback(function(){o=!1,u=!0,s.resolve()})},document.getElementsByTagName("head")[0].appendChild(n),s.promise}function n(t,a){e.each(a,function(a,r){var n=a.type;t.$watch(r,function(){t.drawChart()},{deep:e.isObject(n)})})}function i(e){arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};e.component("vue-chart",d)}e="default"in e?e.default:e;var o=!1,u=!1,s=t(),c=t(),l={packages:{type:Array,default:function(){return["corechart"]}},version:{default:"current"},mapsApiKey:{default:!1},language:{type:String,default:"en"},chartType:{type:String,default:function(){return"LineChart"}},chartEvents:{type:Object,default:function(){return{}}},columns:{required:!0,type:Array},rows:{type:Array,default:function(){return[]}},options:{type:Object,default:function(){return{chart:{title:"Chart Title",subtitle:"Subtitle"},hAxis:{title:"X Label"},vAxis:{title:"Y Label"},width:"400px",height:"300px",animation:{duration:500,easing:"out"}}}}},d={name:"vue-chart",props:l,render:function(e){var t=this;return e("div",{class:"vue-chart-container"},[e("div",{attrs:{id:t.chartId,class:"vue-chart"}})])},data:function(){return{chart:null,chartId:"X"+this._uid,wrapper:null,dataTable:[],hiddenColumns:[]}},events:{redrawChart:function(){this.drawChart()}},mounted:function(){var e=this;r(e.packages,e.version,e.mapsApiKey,e.language).then(e.drawChart).then(function(){var t=l;delete t.bounds,n(e,t),a(e,e.chart,e.chartEvents)}).catch(function(e){throw e})},methods:{buildDataTable:function(){var t=this,a=new google.visualization.DataTable;return e.each(t.columns,function(e){a.addColumn(e)}),e.isEmpty(t.rows)||a.addRows(t.rows),a},updateDataTable:function(){var t=this;t.dataTable.removeRows(0,t.dataTable.getNumberOfRows()),t.dataTable.removeColumns(0,t.dataTable.getNumberOfColumns()),e.each(t.columns,function(e){t.dataTable.addColumn(e)}),e.isEmpty(t.rows)||t.dataTable.addRows(t.rows)},buildWrapper:function(e,t,a,r){var n=new google.visualization.ChartWrapper({chartType:e,dataTable:t,options:a,containerId:r});return n},buildChart:function(){var t=this,a=e.isEmpty(t.dataTable)?t.buildDataTable():t.dataTable;t.wrapper=t.buildWrapper(t.chartType,a,t.options,t.chartId),t.dataTable=t.wrapper.getDataTable(),google.visualization.events.addOneTimeListener(t.wrapper,"ready",function(){t.chart=t.wrapper.getChart(),c.resolve()})},drawChart:function(){var t=this;if((e.isEmpty(t.rows)||e.isObjectLike(t.rows))&&!e.isEmpty(t.columns))return e.isNull(t.chart)?t.buildChart():t.updateDataTable(),t.wrapper.draw(),c.promise}}};return i});
--------------------------------------------------------------------------------
/dist/vue-charts.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haydenbbickerton/vue-charts/f8bb783dd2476d854389678d0abe45b35ad8014b/dist/vue-charts.min.js.gz
--------------------------------------------------------------------------------
/examples/basic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue-Charts Basic Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/examples/events.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue-Charts Basic Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/examples/geochart.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue-Charts Basic Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
18 |
19 |
20 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/examples/redraw.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue-Charts Redraw Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/examples/sets.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue-Charts Basic Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Multiple Data Sets
24 |
25 |
26 |
27 |
36 |
37 |
38 |
39 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
260 |
261 |
262 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-charts",
3 | "description": "Google Charts component for Vue.js",
4 | "version": "0.2.1",
5 | "author": {
6 | "name": "Hayden Bickerton",
7 | "email": "haydenbbickerton@gmail.com"
8 | },
9 | "homepage": "https://github.com/haydenbbickerton/vue-charts#readme",
10 | "jsnext:main": "src/main.js",
11 | "license": "MIT",
12 | "main": "dist/vue-charts.common.js",
13 | "files": [
14 | "dist/vue-charts.js",
15 | "dist/vue-charts.min.js",
16 | "dist/vue-charts.common.js",
17 | "src"
18 | ],
19 | "scripts": {
20 | "build": "node build/build.js",
21 | "clean": "rm -rf dist/*",
22 | "lint": "eslint src/** build/**.js",
23 | "unit": "karma start build/karma.conf.js --single-run",
24 | "test": "npm run lint && npm run unit"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/haydenbbickerton/vue-charts.git"
29 | },
30 | "bugs": {
31 | "url": "https://github.com/haydenbbickerton/vue-charts/issues"
32 | },
33 | "dependencies": {
34 | "lodash": "^4.3.0",
35 | "vue": "^2.0.3"
36 | },
37 | "devDependencies": {
38 | "babel-core": "^6.2.1",
39 | "babel-loader": "^6.2.0",
40 | "babel-plugin-espower": "^2.0.0",
41 | "babel-plugin-external-helpers": "^6.8.0",
42 | "babel-polyfill": "^6.7.4",
43 | "babel-preset-es2015": "^6.16.0",
44 | "babel-preset-es2015-rollup": "^1.0.0",
45 | "chai": "^3.5.0",
46 | "chai-as-promised": "^5.3.0",
47 | "chromedriver": "^2.21.2",
48 | "eslint": "^2.9.0",
49 | "eslint-config-standard": "^5.3.1",
50 | "eslint-loader": "^1.1.1",
51 | "eslint-plugin-promise": "^1.1.0",
52 | "eslint-plugin-standard": "^1.3.2",
53 | "function-bind": "^1.1.0",
54 | "istanbul-instrumenter-loader": "^0.1.3",
55 | "json-loader": "^0.5.4",
56 | "karma": "^0.13.22",
57 | "karma-coverage": "^0.5.5",
58 | "karma-mocha": "^0.2.2",
59 | "karma-mocha-reporter": "^2.0.2",
60 | "karma-phantomjs-launcher": "^1.0.0",
61 | "karma-sinon-chai": "^1.2.0",
62 | "karma-sourcemap-loader": "^0.3.7",
63 | "karma-spec-reporter": "0.0.26",
64 | "karma-webpack": "^1.7.0",
65 | "lodash": "^4.3.0",
66 | "lolex": "^1.4.0",
67 | "mocha": "^2.4.5",
68 | "mocha-loader": "^0.7.1",
69 | "nightwatch": "^0.8.18",
70 | "phantomjs-prebuilt": "^2.1.7",
71 | "rollup": "^0.21.1",
72 | "rollup-plugin-babel": "^2.2.0",
73 | "rollup-plugin-replace": "^1.1.0",
74 | "sinon": "^1.17.3",
75 | "sinon-chai": "^2.8.0",
76 | "uglify-js": "^2.6.1",
77 | "webpack": "^1.12.9"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/components/chart.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import {
3 | eventsBinder,
4 | googleChartsLoader as loadCharts,
5 | makeDeferred,
6 | propsWatcher
7 | } from '../utils/index'
8 |
9 | const chartDeferred = makeDeferred()
10 |
11 | let props = {
12 | packages: {
13 | type: Array,
14 | default: () => {
15 | return ['corechart']
16 | }
17 | },
18 | version: {
19 | default: 'current'
20 | },
21 | mapsApiKey: {
22 | default: false
23 | },
24 | language: {
25 | type: String,
26 | default: 'en'
27 | },
28 | chartType: {
29 | type: String,
30 | default: () => {
31 | return 'LineChart'
32 | }
33 | },
34 | chartEvents: {
35 | type: Object,
36 | default: () => {
37 | return {}
38 | }
39 | },
40 | columns: {
41 | required: true,
42 | type: Array
43 | },
44 | rows: {
45 | type: Array,
46 | default: () => {
47 | return []
48 | }
49 | },
50 | options: {
51 | type: Object,
52 | default: () => {
53 | return {
54 | chart: {
55 | title: 'Chart Title',
56 | subtitle: 'Subtitle'
57 | },
58 | hAxis: {
59 | title: 'X Label'
60 | },
61 | vAxis: {
62 | title: 'Y Label'
63 | },
64 | width: '400px',
65 | height: '300px',
66 | animation: {
67 | duration: 500,
68 | easing: 'out'
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
75 | export default {
76 | name: 'vue-chart',
77 | props: props,
78 | render (h) {
79 | const self = this
80 | return h('div', {class: 'vue-chart-container'}, [
81 | h('div', {
82 | attrs: {
83 | id: self.chartId,
84 | class: 'vue-chart'
85 | }
86 | })
87 | ])
88 | },
89 | data () {
90 | return {
91 | chart: null,
92 | /*
93 | We put the uid in the DOM element so the component can be used multiple
94 | times in the same view. Otherwise Google Charts will only make one chart.
95 |
96 | The X is prepended because there must be at least
97 | 1 character in id - https://www.w3.org/TR/html5/dom.html#the-id-attribute
98 | */
99 | chartId: 'X' + this._uid,
100 | wrapper: null,
101 | dataTable: [],
102 | hiddenColumns: []
103 | }
104 | },
105 | events: {
106 | redrawChart () {
107 | this.drawChart()
108 | }
109 | },
110 | mounted () {
111 | let self = this
112 | loadCharts(self.packages, self.version, self.mapsApiKey, self.language)
113 | .then(self.drawChart)
114 | .then(() => {
115 | // we don't want to bind props because it's a kind of "computed" property
116 | const watchProps = props
117 | delete watchProps.bounds
118 |
119 | // watching properties
120 | propsWatcher(self, watchProps)
121 |
122 | // binding events
123 | eventsBinder(self, self.chart, self.chartEvents)
124 | })
125 | .catch((error) => {
126 | throw error
127 | })
128 | },
129 | methods: {
130 | /**
131 | * Initialize the datatable and add the initial data.
132 | *
133 | * @link https://developers.google.com/chart/interactive/docs/reference#DataTable
134 | * @return object
135 | */
136 | buildDataTable () {
137 | let self = this
138 |
139 | let dataTable = new google.visualization.DataTable()
140 |
141 | _.each(self.columns, (value) => {
142 | dataTable.addColumn(value)
143 | })
144 |
145 | if (!_.isEmpty(self.rows)) {
146 | dataTable.addRows(self.rows)
147 | }
148 |
149 | return dataTable
150 | },
151 |
152 | /**
153 | * Update the datatable.
154 | *
155 | * @return void
156 | */
157 | updateDataTable () {
158 | let self = this
159 |
160 | // Remove all data from the datatable.
161 | self.dataTable.removeRows(0, self.dataTable.getNumberOfRows())
162 | self.dataTable.removeColumns(0, self.dataTable.getNumberOfColumns())
163 |
164 | // Add
165 | _.each(self.columns, (value) => {
166 | self.dataTable.addColumn(value)
167 | })
168 |
169 | if (!_.isEmpty(self.rows)) {
170 | self.dataTable.addRows(self.rows)
171 | }
172 | },
173 |
174 | /**
175 | * Initialize the wrapper
176 | *
177 | * @link https://developers.google.com/chart/interactive/docs/reference#chartwrapper-class
178 | *
179 | * @return object
180 | */
181 | buildWrapper (chartType, dataTable, options, containerId) {
182 | let wrapper = new google.visualization.ChartWrapper({
183 | chartType: chartType,
184 | dataTable: dataTable,
185 | options: options,
186 | containerId: containerId
187 | })
188 |
189 | return wrapper
190 | },
191 |
192 | /**
193 | * Build the chart.
194 | *
195 | * @return void
196 | */
197 | buildChart () {
198 | let self = this
199 |
200 | // If dataTable isn't set, build it
201 | let dataTable = _.isEmpty(self.dataTable) ? self.buildDataTable() : self.dataTable
202 |
203 | self.wrapper = self.buildWrapper(self.chartType, dataTable, self.options, self.chartId)
204 |
205 | // Set the datatable on this instance
206 | self.dataTable = self.wrapper.getDataTable()
207 |
208 | // After chart is built, set it on this instance and resolve the promise.
209 | google.visualization.events.addOneTimeListener(self.wrapper, 'ready', () => {
210 | self.chart = self.wrapper.getChart()
211 | chartDeferred.resolve()
212 | })
213 | },
214 |
215 | /**
216 | * Draw the chart.
217 | *
218 | * @return Promise
219 | */
220 | drawChart () {
221 | let self = this
222 |
223 | // We don't have any (usable) data, or we don't have columns. We can't draw a chart without those.
224 | if ((!_.isEmpty(self.rows) && !_.isObjectLike(self.rows)) || _.isEmpty(self.columns)) {
225 | return
226 | }
227 |
228 | if (_.isNull(self.chart)) {
229 | // We haven't built the chart yet, so JUST. DO. IT!
230 | self.buildChart()
231 | } else {
232 | // Chart already exists, just update the data
233 | self.updateDataTable()
234 | }
235 |
236 | // Chart has been built/Data has been updated, draw the chart.
237 | self.wrapper.draw()
238 |
239 | // Return promise. Resolves when chart finishes loading.
240 | return chartDeferred.promise
241 | }
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Chart from './components/chart'
2 |
3 | function install (Vue, options = {}) {
4 | Vue.component('vue-chart', Chart)
5 | }
6 |
7 | export default install
8 |
--------------------------------------------------------------------------------
/src/utils/eventsBinder.js:
--------------------------------------------------------------------------------
1 | export function eventsBinder (vue, googleChart, events) {
2 | // Loop through our events, create a listener for them, and
3 | // attach our callback function to that event.
4 | for (let event in events) {
5 | let eventName = event
6 | let eventCallback = events[event]
7 |
8 | if (eventName === 'ready') {
9 | // The chart is already ready, so this event missed it's chance.
10 | // We'll call it manually.
11 | eventCallback()
12 | } else {
13 | google.visualization.events.addListener(googleChart, eventName, eventCallback)
14 | }
15 | }
16 | }
17 |
18 | export default eventsBinder
19 |
--------------------------------------------------------------------------------
/src/utils/googleChartsLoader.js:
--------------------------------------------------------------------------------
1 | import makeDeferred from './makeDeferred'
2 | let isLoading = false
3 | let isLoaded = false
4 |
5 | // Our main promise
6 | let googlePromise = makeDeferred()
7 |
8 | export function googleChartsLoader (packages = ['corechart'], version = 'current', mapsApiKey = false, language = 'en') {
9 | if (!Array.isArray(packages)) {
10 | throw new TypeError('packages must be an array')
11 | }
12 |
13 | if (version !== 'current' && typeof version !== 'number' && version !== 'upcoming') {
14 | throw new TypeError('version must be a number, "upcoming" or "current"')
15 | }
16 |
17 | // Google only lets you load it once, so we'll only run once.
18 | if (isLoading || isLoaded) {
19 | return googlePromise.promise
20 | }
21 |
22 | isLoading = true
23 |
24 | let script = document.createElement('script')
25 | script.setAttribute('src', 'https://www.gstatic.com/charts/loader.js')
26 |
27 | script.onreadystatechange = script.onload = () => {
28 | // After the 'loader.js' is loaded, load our version and packages
29 | var options = {
30 | packages: packages,
31 | language: language
32 | }
33 |
34 | if (mapsApiKey) {
35 | options['mapsApiKey'] = mapsApiKey
36 | }
37 |
38 | google.charts.load(version, options)
39 |
40 | // After we've loaded Google Charts, resolve our promise
41 | google.charts.setOnLoadCallback(() => {
42 | isLoading = false
43 | isLoaded = true
44 | googlePromise.resolve()
45 | })
46 | }
47 |
48 | // Insert our script into the DOM
49 | document.getElementsByTagName('head')[0].appendChild(script)
50 |
51 | return googlePromise.promise
52 | }
53 |
54 | export default googleChartsLoader
55 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export {eventsBinder} from './eventsBinder'
2 | export {googleChartsLoader} from './googleChartsLoader'
3 | export {makeDeferred} from './makeDeferred'
4 | export {propsWatcher} from './propsWatcher'
5 |
--------------------------------------------------------------------------------
/src/utils/makeDeferred.js:
--------------------------------------------------------------------------------
1 | /*
2 | This lets us resolve the promise outside the
3 | promise function itself.
4 | */
5 | export function makeDeferred () {
6 | let resolvePromise = null
7 | let rejectPromise = null
8 |
9 | let promise = new Promise((resolve, reject) => {
10 | resolvePromise = resolve
11 | rejectPromise = reject
12 | })
13 |
14 | return {
15 | promise: promise,
16 | resolve: resolvePromise,
17 | reject: rejectPromise
18 | }
19 | }
20 |
21 | export default makeDeferred
22 |
--------------------------------------------------------------------------------
/src/utils/propsWatcher.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | export function propsWatcher (vue, props) {
4 | /*
5 | Watch our props. Every time they change, redraw the chart.
6 | */
7 | _.each(props, ({type: type}, attribute) => {
8 | vue.$watch(attribute, () => {
9 | vue.drawChart()
10 | }, {
11 | deep: _.isObject(type)
12 | })
13 | })
14 | }
15 |
16 | export default propsWatcher
17 |
--------------------------------------------------------------------------------
/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | 'env': {
3 | 'browser': true,
4 | 'node': true,
5 | 'mocha': true
6 | },
7 |
8 | 'globals': {
9 | '_': true,
10 | 'google': true,
11 | 'Vue': true,
12 | 'VueCharts': true,
13 | 'isIE': true,
14 | 'isIE9': true,
15 | 'describe': true,
16 | 'it': true,
17 | 'beforeEach': true,
18 | 'afterEach': true,
19 | 'expect': true,
20 | 'spyOn': true,
21 | 'wait': true
22 | },
23 |
24 | 'extends': 'standard',
25 | 'rules': {
26 | 'padded-blocks': 0
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/test/unit/specs/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueCharts from 'src/main'
3 |
4 | Vue.use(VueCharts)
5 |
6 | require('./utils')
7 |
--------------------------------------------------------------------------------
/test/unit/specs/utils.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai'
2 | import chaiAsPromised from 'chai-as-promised'
3 | import {
4 | googleChartsLoader as loadCharts,
5 | makeDeferred
6 | } from 'src/utils/index'
7 |
8 | chai.use(chaiAsPromised)
9 | const expect = chai.expect
10 |
11 | describe('Utils', function () {
12 |
13 | /**
14 | * makeDeferred
15 | *
16 | */
17 | describe('Making deferred promises', function () {
18 | let deferred
19 |
20 | beforeEach(() => {
21 | deferred = makeDeferred()
22 | })
23 |
24 | it('should be a promise', () => {
25 | return expect(deferred.promise).to.be.a('Promise')
26 | })
27 |
28 | it('can be resolved', () => {
29 | deferred.resolve()
30 | return expect(deferred.promise).to.eventually.be.fulfilled
31 | })
32 |
33 | it('can be rejected', () => {
34 | deferred.reject()
35 | return expect(deferred.promise).to.eventually.be.rejected
36 | })
37 | })
38 |
39 | /**
40 | * googleChartsLoader
41 | *
42 | */
43 | describe('Loading google charts library', function () {
44 | this.timeout(15000) // Give time for calls to Google API's and whatnot
45 | let chartsLoader
46 |
47 | beforeEach(() => {
48 | chartsLoader = loadCharts(['corechart'], 'current')
49 | })
50 |
51 | it('packges must be an array', () => {
52 | return expect(() => loadCharts('corechart')).to.throw(TypeError)
53 | })
54 |
55 | it('version must be a number, or "current"', () => {
56 | return expect(() => loadCharts(['corechart'], 'forty-three')).to.throw(TypeError)
57 | })
58 |
59 | it('should be a promise', () => {
60 | return expect(chartsLoader).to.be.a('Promise')
61 | })
62 |
63 | it('can be resolved', () => {
64 | return expect(chartsLoader).to.eventually.be.fulfilled
65 | })
66 | })
67 | })
68 |
--------------------------------------------------------------------------------