Skip to content

Commit

Permalink
Add polling_interval option to Charts
Browse files Browse the repository at this point in the history
Fixes #544
  • Loading branch information
Julien Castelain committed Mar 7, 2018
1 parent 3afc46f commit caef9ca
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 116 deletions.
4 changes: 3 additions & 1 deletion packages/clay-charts/src/Chart.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ChartBase from './ChartBase';
import DataComponent from './DataComponent';
import Component from 'metal-component';
import Soy from 'metal-soy';
import templates from './Chart.soy.js';
Expand All @@ -11,7 +12,8 @@ import templates from './Chart.soy.js';
class Chart extends Component {}

Object.assign(Chart.prototype, ChartBase);
Chart.STATE = ChartBase.STATE;
Object.assign(Chart.prototype, DataComponent);
Chart.STATE = Object.assign(ChartBase.STATE, DataComponent.STATE);

Soy.register(Chart, templates);

Expand Down
101 changes: 35 additions & 66 deletions packages/clay-charts/src/ChartBase.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {bb, d3} from 'billboard.js';
import {isFunction, isString} from 'metal';
import {Config} from 'metal-state';
import {isServerSide} from 'metal';
import types from './utils/types';
Expand Down Expand Up @@ -77,6 +76,7 @@ const ChartBase = {
}

this._resolveData(this.data).then(data => {
this._setupPolling();
this._resolvedData = data;
const config = this._constructChartConfig();
this.bbChart = bb.generate(config);
Expand All @@ -103,6 +103,10 @@ const ChartBase = {
return;
}

if (this._pollingInterval) {
clearInterval(this._pollingInterval);
}

if (this.bbChart) {
this.bbChart.destroy();
}
Expand Down Expand Up @@ -460,27 +464,7 @@ const ChartBase = {
* @protected
*/
_handleDataChanged(event) {
this._resolveData(event.newVal).then(val => {
const prevVal = this._createDataArray(this._resolvedData);

this._resolvedData = val;

const data = this._constructDataConfig(false);
const newVal = data.columns;
const removedIds = this._resolveRemovedData(newVal, prevVal);

if (removedIds.length) {
data.unload = removedIds;
}

this.bbChart.load(data);

if (data.xs) {
this.bbChart.xs(this._mapXSValues(data.xs));
}

this.emit('dataResolved', data);
});
this._updateData(event.newVal);
},

/**
Expand Down Expand Up @@ -557,24 +541,6 @@ const ChartBase = {
}, {});
},

/**
* Check's the data or columns option and resolves this `data_` accordingly.
* @return {Promise}
* @param {?Array|Function|String} data the data to resolve.
* @protected
*/
_resolveData(data) {
if (Array.isArray(data)) {
return Promise.resolve(data);
} else if (isFunction(data)) {
return data().then(val => val);
} else if (isString(data)) {
return fetch(data, {cors: 'cors'})
.then(res => res.json())
.then(res => res.data);
}
},

/**
* Determines which ids should be passed to the unload property.
* @static
Expand Down Expand Up @@ -604,6 +570,35 @@ const ChartBase = {
_setColumns(columns) {
this.data = columns;
},

/**
* Updates the chart's data.
* @param {Object} data The new data to load
* @protected
*/
_updateData(data) {
this._resolveData(data).then(val => {
const prevVal = this._createDataArray(this._resolvedData);

this._resolvedData = val;

const data = this._constructDataConfig(false);
const newVal = data.columns;
const removedIds = this._resolveRemovedData(newVal, prevVal);

if (removedIds.length) {
data.unload = removedIds;
}

this.bbChart.load(data);

if (data.xs) {
this.bbChart.xs(this._mapXSValues(data.xs));
}

this.emit('dataResolved', data);
});
},
};

/**
Expand Down Expand Up @@ -798,32 +793,6 @@ ChartBase.STATE = {
valueFn: '_getColumns',
},

/**
* Data that will be rendered to the chart.
* @instance
* @memberof ChartBase
* @type {?Array|undefined}
* @default undefined
*/
data: Config.oneOfType([
Config.arrayOf(
Config.shapeOf({
axis: Config.oneOf(['y', 'y2']),
class: Config.string(),
color: Config.string(),
data: Config.array().required(),
hide: Config.bool(),
id: Config.required().string(),
name: Config.string(),
regions: Config.array(),
type: Config.oneOf(types.all),
x: Config.string(),
})
),
Config.func(),
Config.string(),
]),

/**
* Configuration options for donut chart.
* @instance
Expand Down
91 changes: 91 additions & 0 deletions packages/clay-charts/src/DataComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {isFunction, isObject, isString} from 'metal';
import {Config} from 'metal-state';
import types from './utils/types';

/**
* DataComponent prototype.
*
* @mixin
*/
const DataComponent = {
/**
* Check's the data or columns option and resolves this `data_` accordingly.
* @return {Promise}
* @param {?Array|Function|String} data the data to resolve.
* @protected
*/
_resolveData(data) {
if (Array.isArray(data) || (isObject(data) && !isFunction(data))) {
return Promise.resolve(data);
} else if (isFunction(data)) {
return data().then(val => val);
} else if (isString(data)) {
return fetch(data, {cors: 'cors'})
.then(res => res.json())
.then(res => res.data);
} else {
Promise.reject(`Invalid type for data: ${data}`);
}
},

/**
* Sets up the polling interval.
*/
_setupPolling() {
if (this.pollingInterval) {
if (this._pollingInterval) {
clearInterval(this._pollingInterval);
}

this._pollingInterval = setInterval(() => {
this._updateData(this.data);
}, this.pollingInterval);
}
},
};

/**
* State definition.
* @static
* @type {!Object}
*/
DataComponent.STATE = {
/**
* Data that will be rendered to the chart.
* @instance
* @memberof ChartBase
* @type {?Array|undefined}
* @default undefined
*/
data: Config.oneOfType([
Config.arrayOf(
Config.shapeOf({
axis: Config.oneOf(['y', 'y2']),
class: Config.string(),
color: Config.string(),
data: Config.array().required(),
hide: Config.bool(),
id: Config.required().string(),
name: Config.string(),
regions: Config.array(),
type: Config.oneOf(types.all),
x: Config.string(),
})
),
Config.object(),
Config.func(),
Config.string(),
]),

/**
* Set an interval (in ms) to fetch the data.
* @instance
* @memberof ChartBase
* @type {?Number}
* @default undefined
*/
pollingInterval: Config.number(),
};

export {DataComponent};
export default DataComponent;
53 changes: 21 additions & 32 deletions packages/clay-charts/src/Geomap.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {isFunction, isObject, isString} from 'metal';
import Component from 'metal-component';
import DataComponent from './DataComponent';
import Soy from 'metal-soy';
import {Config} from 'metal-state';
import {isServerSide} from 'metal';
Expand All @@ -19,6 +19,8 @@ class Geomap extends Component {
return;
}

this._setupPolling();

const w =
typeof this._width === 'string' ? this._width : `${this._width}px`;
const h =
Expand Down Expand Up @@ -55,7 +57,7 @@ class Geomap extends Component {

this._onDataLoadHandler = this._onDataLoad.bind(this);

this._resolveData()
this._resolveData(this.data)
.then(val => {
this._onDataLoadHandler.apply(this, [null, val]);
})
Expand All @@ -72,6 +74,10 @@ class Geomap extends Component {
return;
}

if (this._pollingInterval) {
clearTimeout(this._pollingInterval);
}

if (this.svg) {
this.svg.remove();
}
Expand Down Expand Up @@ -173,25 +179,18 @@ class Geomap extends Component {
}

/**
* @return {Promise}
* @inheritDoc
* @param {Object} data The updated data
* @protected
*/
_resolveData() {
return new Promise((resolve, reject) => {
if (isString(this.data)) {
d3.json(this.data, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
} else if (isObject(this.data) && !isFunction(this.data)) {
resolve(this.data);
} else if (isFunction(this.data)) {
resolve(this.data());
}
});
_updateData(data) {
this._resolveData(data)
.then(val => {
this._onDataLoadHandler.apply(this, [null, val]);
})
.catch(err => {
this._onDataLoadHandler.apply(this, [err, null]);
});
}
}

Expand Down Expand Up @@ -263,20 +262,10 @@ Geomap.STATE = {
selected: '#4b9bff',
value: 'pop_est',
}),

/**
* Geo-json data
* @instance
* @memberof Geomap
* @type {?Function|?Object|?String}
* @default undefined
*/
data: Config.oneOfType([
Config.func(),
Config.object(),
Config.string(),
]).required(),
};

Object.assign(Geomap.prototype, DataComponent);
Object.assign(Geomap.STATE, DataComponent.STATE);

export {Geomap};
export default Geomap;
25 changes: 25 additions & 0 deletions packages/clay-charts/src/__tests__/Chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ describe('Chart', () => {
expect(config.data.columns[0][2]).toBe(2);
expect(config.data.columns[0][3]).toBe(3);
expect(chart._resolvedData).toBe(data);

done();
});
});
Expand Down Expand Up @@ -218,4 +219,28 @@ describe('Chart', () => {
done();
});
});

it('should support a pollingInterval', done => {
const data = [
{
id: 'data1',
data: [1, 2, 3, 4],
},
{
id: 'data2',
data: [5, 6, 7, 8],
},
];

const chart = new Chart({
data: data,
pollingInterval: 500,
});

chart.on('chartReady', () => {
expect(chart._resolvedData).toBe(data);
expect(chart._pollingInterval).toBeDefined();
done();
});
});
});
Loading

0 comments on commit caef9ca

Please sign in to comment.