/* global Spyral, Highcharts */
import NetworkGraph from './networkgraph';
import Util from './util.js';
/**
* The Chart class in Spyral.
* This class provides methods for creating a variety of charts.
* Charts are created using the [Highcharts Library]{@link https://api.highcharts.com/highcharts/}.
* Highcharts have many configuration options and Spyral.Chart helps to streamline the process.
* A simple example:
*
* Spyral.Chart.line({ series: [{ data: [0,2,1,3] }] })
*
* A more complex example:
*
* Spyral.Chart.column({
* title: 'Wildflowers',
* series: [{
* name: 'Ontario',
* data: [13, 39, 139, 38]
* },{
* name: 'Quebec',
* data: [14, 33, 94, 30]
* }],
* xAxis: {
* title: 'Number of Petals',
* categories: [3, 4, 5, 6]
* }
* })
*
* @memberof Spyral
* @class
*/
class Chart {
/**
* The Highcharts config object
* @typedef {Object} Spyral.Chart~HighchartsConfig
* @property {(string|object)} title
* @property {(string|object)} subtitle
* @property {Object} credits
* @property {Object} xAxis
* @property {Object} yAxis
* @property {Object} chart
* @property {Array<Spyral.Chart~HighchartsSeriesConfig>} series
* @property {Object} plotOptions
*/
/**
* The series config object
* @typedef {Object} Spyral.Chart~HighchartsSeriesConfig
* @property {Array} data
* @property {string} [name]
*/
/**
* Construct a new Chart class
* @constructor
* @param {(String|Element)} [target] An element or ID to use as the chart's target. If not specified, one will be created.
* @param {Array} data An array of data to visualize.
*/
constructor(target, data) {
if (Util.isNode(target)) {
if (target.isConnected === false) {
throw new Error('The target node does not exist within the document.');
}
} else if (Util.isString(target) === false) {
data = target;
target = undefined;
}
this.target = target;
this.data = data;
}
/**
* Create a new chart.
* See [Highcharts API]{@link https://api.highcharts.com/highcharts/} for full set of config options.
* @param {(String|Element)} [target] An element or ID to use as the chart's target. If not specified, one will be created.
* @param {Spyral.Chart~HighchartsConfig} config
* @returns {Highcharts.Chart}
*/
create(target, config) {
[target, config] = Chart._handleTargetAndConfig(target, config);
return Highcharts.chart(target, config);
}
/**
* Create a new chart.
* See [Highcharts API]{@link https://api.highcharts.com/highcharts/} for full set of config options.
* @param {(String|Element)} [target] An element or ID to use as the chart's target. If not specified, one will be created.
* @param {Spyral.Chart~HighchartsConfig} config
* @returns {Highcharts.Chart}
* @static
*/
static create(target, config) {
[target, config] = Chart._handleTargetAndConfig(target, config);
return Highcharts.chart(target, config);
}
static _handleTargetAndConfig(target, config) {
if (Util.isNode(target) === false && typeof target === 'object') {
config = target;
target = undefined;
}
if (target === undefined) {
if (typeof Spyral !== 'undefined' && Spyral.Notebook) {
target = Spyral.Notebook.getTarget();
if (target.clientHeight <= 40) {
target.style.height = '400px'; // 400 is the default Highcharts height
}
} else {
target = document.createElement('div');
document.body.appendChild(target);
}
} else {
if (Util.isNode(target) && target.isConnected === false) {
throw new Error('The target node does not exist within the document.');
}
}
// convert title and suppress if not provided
if ('title' in config) {
if (typeof config.title === 'string') {
config.title = {text: config.title};
}
} else {
config.title = false;
}
// convert subtitle and convert if not provided
if ('subtitle' in config) {
if (typeof config.subtitle === 'string') {
config.subtitle = {text: config.subtitle};
}
} else {
config.subtitle = false;
}
// convert credits
if (!('credits' in config)) {
config.credits = false;
}
// suppress xAxis title unless provided
if (!('xAxis' in config)) {config.xAxis = {};}
if (!('title' in config.xAxis)) {
config.xAxis.title = false;
} else if (typeof config.xAxis.title === 'string') {
config.xAxis.title = {text: config.xAxis.title};
}
// suppress xAxis title unless provided
if (!('yAxis' in config)) {config.yAxis = {};}
if (!('title' in config.yAxis)) {
config.yAxis.title = false;
} else if (typeof config.yAxis.title === 'string') {
config.yAxis.title = {text: config.yAxis.title};
}
return [target, config];
}
static _setDefaultChartType(config, type) {
if ('type' in config) {
config.chart.type = config.type;
delete config.type;
return;
}
// TODO: check plot options and series?
if ('chart' in config) {
if ('type' in config.chart) {return;} // already set
} else {
config.chart = {};
}
config.chart.type = type;
return config;
}
/**
* Add the provided data to the config as a series
* @param {Spyral.Chart~HighchartsConfig} config
* @param {Array} data
* @static
*/
static setSeriesData(config, data) {
if (Array.isArray(data)) {
if (Array.isArray(data[0])) {
config.series = data.map(subArray => { return {data: subArray}; });
} else {
config.series = [{data: data}];
}
}
}
/**
* Create a bar chart
* @param {Spyral.Chart~HighchartsConfig} [config]
* @returns {Highcharts.Chart}
*/
bar(config={}) {
Chart.setSeriesData(config, this.data);
return Chart.bar(this.target, config);
}
/**
* Create a bar chart
* @param {(String|Element)} [target] An element or ID to use as the chart's target. If not specified, one will be created.
* @param {Spyral.Chart~HighchartsConfig} config
* @returns {Highcharts.Chart}
* @static
*/
static bar(target, config) {
[target, config] = Chart._handleTargetAndConfig(target, config);
Chart._setDefaultChartType(config, 'bar');
return Highcharts.chart(target, config);
}
/**
* Create a column chart
* @param {Spyral.Chart~HighchartsConfig} [config]
* @returns {Highcharts.Chart}
*/
column(config={}) {
Chart.setSeriesData(config, this.data);
return Chart.column(this.target, config);
}
/**
* Create a column chart
* @param {(String|Element)} [target] An element or ID to use as the chart's target. If not specified, one will be created.
* @param {Spyral.Chart~HighchartsConfig} config
* @returns {Highcharts.Chart}
* @static
*/
static column(target, config) {
[target, config] = Chart._handleTargetAndConfig(target, config);
Chart._setDefaultChartType(config, 'column');
return Highcharts.chart(target, config);
}
/**
* Create a line chart
* @param {Spyral.Chart~HighchartsConfig} [config]
* @returns {Highcharts.Chart}
*/
line(config={}) {
Chart.setSeriesData(config, this.data);
return Chart.line(this.target, config);
}
/**
* Create a line chart
* @param {(String|Element)} [target] An element or ID to use as the chart's target. If not specified, one will be created.
* @param {Spyral.Chart~HighchartsConfig} config
* @returns {Highcharts.Chart}
* @static
*/
static line(target, config) {
[target, config] = Chart._handleTargetAndConfig(target, config);
Chart._setDefaultChartType(config, 'line');
return Highcharts.chart(target, config);
}
/**
* Create a scatter plot
* @param {Spyral.Chart~HighchartsConfig} [config]
* @returns {Highcharts.Chart}
*/
scatter(config={}) {
Chart.setSeriesData(config, this.data);
return Chart.scatter(this.target, config);
}
/**
* Create a scatter plot
* @param {(String|Element)} [target] An element or ID to use as the chart's target. If not specified, one will be created.
* @param {Spyral.Chart~HighchartsConfig} config
* @returns {Highcharts.Chart}
* @static
*/
static scatter(target, config) {
[target, config] = Chart._handleTargetAndConfig(target, config);
Chart._setDefaultChartType(config, 'scatter');
return Highcharts.chart(target, config);
}
/**
* Create a network graph
* @param {NetworkGraph~Config} [config]
* @returns {NetworkGraph}
*/
networkgraph(config={}) {
return Chart.networkgraph(this.target, config);
}
/**
* Create a network graph
* @param {(String|Element)} [target] An element or ID to use as the chart's target. If not specified, one will be created.
* @param {NetworkGraph~Config} config
* @returns {NetworkGraph}
* @static
*/
static networkgraph(target, config) {
[target, config] = Chart._handleTargetAndConfig(target, config);
return new NetworkGraph(target, config);
}
}
export default Chart;