src/components/Chart.js
import { select } from 'd3-selection';
import Tooltip from './Tooltip';
import DataOps from './DataOps';
/**
* The Chart object is the parent class for all types of Chart.
* It is used to initialise all of the base settings universal to all charts.
*
* @class Chart
* @constructor
*/
export default class Chart {
/**
* SVG DOM object for displaying the chart
*
* @property dSvg
* @type {Object}
*/
dSvg;
/**
* SVG d3 object for d3 operations on the chart
*
* @property d3Svg
* @type {Object}
*/
d3Svg;
/**
* Default time for d3 transitions on the chart
*
* @property iTransitionTime
* @type {Number}
*/
iTransitionTime;
/**
* DOM reference to container element that wraps SVG
*
* @property dContainer
* @type {Object}
*/
dContainer;
/**
* DOM reference to loader display element
*
* @property dLoader
* @type {Object}
*/
dLoader;
/**
* Chart's tooltip object
*
* @property oTooltip
* @type {Object}
*/
oTooltip;
/**
* The current calculated width of the chart
*
* @property iWidth
* @type {Number}
*/
iWidth;
/**
* The current calculated height of the chart
*
* @property iHeight
* @type {Number}
*/
iHeight;
/**
* The current calculated inner width of the chart
*
* @property iInnerWidth
* @type {Number}
*/
iInnerWidth;
/**
* The current calculated inner height of the chart
*
* @property iInnerHeight
* @type {Number}
*/
iInnerHeight;
/**
* The width before any browser resize
*
* @property iInitialWidth
* @type {Number}
*/
iInitialWidth;
/**
* The padding for the chart within the container
*
* @property jPadding
* @type {Object}
*/
jPadding;
/**
* The chart's config object
*
* @property jConfig
* @type {Object}
*/
jConfig;
/**
* The chart's data
*
* @property aData
* @type {Array}
*/
aData;
/**
* Constructor function that sets up the local object.
*
* @method constructor
* @param {Object} jConfig JSON configuration object
* @param {Array} aData the data to be displayed
* @param {String} sContainer Optional ID to select DOM object
* @param {Object} dContainer Optional DOM object in place of ID
*/
constructor(oParams = {}) {
const { jConfig, aData, sContainer } = oParams;
let { dContainer } = oParams;
this.dSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.d3Svg = select(this.dSvg);
this.iTransitionTime = 500;
this.jPadding = { l: 5, r: 5, t: 5, b: 5 };
this.dLoader = document.createElement('div');
this.dLoader.className = 'dt-loader';
if (!dContainer && sContainer) {
dContainer = document.getElementById(sContainer);
}
if (dContainer) {
this.setContainer(dContainer);
}
if (jConfig) {
this.setConfig(jConfig);
}
if (aData) {
this.setData(aData);
}
}
/**
* Sets the local container object.
*
* @method setContainer
* @param {Object} dContainer Required DOM element
* @throws {Error} invalid DOM element
*/
setContainer(dContainer) {
if (dContainer.nodeName) {
dContainer.appendChild(this.dLoader);
this.dContainer = dContainer;
} else {
throw new Error('No valid DOM element or ID provided for chart.');
}
}
/**
* Sets the local config options for the chart.
*
* @method setConfig
* @param {Object} jConfig JSON configuration object
* @throws {Error} missing configuration
*/
setConfig(jConfig) {
if (jConfig && jConfig.toString() === '[object Object]') {
if (jConfig.aValues) {
jConfig.aValues = DataOps.addColoursToConfig(jConfig.aValues);
}
this.jConfig = jConfig;
} else {
throw new Error('No valid configuration provided for chart.');
}
}
/**
* Sets the local data for the chart.
*
* @method setData
* @param {Array} aData array of JSON objects
* @param {Boolean} bTransform transform mapped data
* @throws {Error} missing data
*/
setData(aData, bTransform = true) {
if (aData && Array.isArray(aData) === true) {
if (this.jConfig && bTransform) {
aData = DataOps.transformDataKeys(this.jConfig, aData);
}
this.aData = aData;
} else {
throw new Error('No valid data provided for chart.');
}
}
/**
* Updates the local data for the chart.
*
* @method updateData
* @param {Array} aData array of JSON objects
* @param {Boolean} bTransform transform mapped data
*/
updateData(aData, bTransform = true) {
this.setData(aData, bTransform);
this.setDimensions();
if (this.oAxis) {
this.oAxis.render();
}
if (this.renderChart) {
this.renderChart();
}
}
/**
* Updates the local config for the chart.
*
* @method updateConfig
* @param {JSON} jConfig config JSON style object
*/
updateConfig(jConfig) {
this.setConfig(jConfig);
if (this.renderChart) {
this.renderChart(true, false);
}
}
/**
* Sets the local chart dimensions based on the size of the container.
*
* @method setDimensions
* @throws {Error} missing DOM element
*/
setDimensions() {
if (this.dContainer && this.dContainer.nodeName) {
this.iWidth = this.dContainer.clientWidth;
this.iHeight = this.dContainer.clientHeight;
this.iInnerWidth = this.iWidth - this.jPadding.l - this.jPadding.r;
this.iInnerHeight = this.iHeight - this.jPadding.t - this.jPadding.b;
} else {
throw new Error('Cannot set dimensions of chart without container element.');
}
}
/**
* Check chart is ready and render.
*
* @method init
* @throws {Error} chart not ready for initialisation
* @chainable
*/
init() {
this.setDimensions();
if (this.aData && this.jConfig && !isNaN(this.iWidth) && !isNaN(this.iHeight)) {
this.iInitialWidth = this.iWidth;
this.oTooltip = new Tooltip(this.dContainer).create();
select(this.dContainer).append('div').attr('class', 'title').text(this.jConfig.sTitle);
this.dSvg.setAttribute('class', 'chart');
this.dContainer.appendChild(this.dSvg);
this.oResizeWatcher = this.oResizeWatcher || window.addEventListener('resize', () => {
this.setDimensions();
this.iResizeOffset = this.iWidth - this.iInitialWidth;
if (this.renderChart) {
this.renderChart();
}
this.oTooltip.hide();
});
this.oChartOutWatcher = this.oChartOutWatcher || this.dSvg.addEventListener('mouseout', () => {
this.oTooltip.hide();
});
if (this.renderChart) {
this.renderChart();
}
this.dContainer.removeChild(this.dLoader);
return this;
}
throw new Error('The chart is not ready for initialisation.');
}
}
module.exports = Chart;