UNPKG

@randstad-design/orbit-multitheme

Version:

multitheme Front-end code based on Randstad Human Forward components

400 lines (353 loc) 11 kB
/** * charts.js * load google charts based on table element */ import ElementHelpers from '../helpers/element-helpers'; import { GoogleChartsApi } from './charts/google-charts-api'; /** * Declare constants */ const attributeBase = 'data-rs-charts'; export class Charts { constructor(element) { this.element = element; this.type = this.getType(); this.addEventHandlers(); this.initializeChart(); } /** * Declare types */ get types() { return { column: 'column', donut: 'donut', line: 'line' }; } /** * Declare attributes */ get attributes() { return { placeholder: `${attributeBase}-placeholder`, source: `${attributeBase}-source`, legend: `${attributeBase}-legend`, legendItem: `${attributeBase}-legend-item`, horizAxisLabel: `${attributeBase}-horiz-axis-label`, vertAxisLabel: `${attributeBase}-vert-axis-label`, valueLabel: `${attributeBase}-value-label` }; } /** * Add event handlers */ addEventHandlers() { window.addEventListener('resize', () => { this.createChart(); }); } /** * Get type - get type of chart from base attribute */ getType() { let type = null; const typeRaw = this.element.getAttribute(attributeBase); if ( typeof typeRaw === 'string' && typeof this.types[typeRaw.toLowerCase()] !== 'undefined' ) { type = this.types[typeRaw.toLowerCase()]; } if (type === null) { throw Error('Chart type is invalid'); } return type; } /** * Initialize chart - Load Google Maps API */ initializeChart() { // get elements this.chartElement = ElementHelpers.getElementByAttributeWithinElement( this.element, this.attributes.placeholder ); // create map, controls and markers after the API is ready const chartsApi = new GoogleChartsApi(); chartsApi .load() .then(() => { this.createChart(); this.createLegend(); }) .catch((reason) => { console.error(reason); }); } /** * Create chart - create Google Visualization chart based on type */ createChart() { // get source element const sourceElement = ElementHelpers.getElementByAttributeWithinElement( this.element, this.attributes.source ); const horizAxisLabel = this.element.getAttribute(this.attributes.horizAxisLabel); const vertAxisLabel = this.element.getAttribute(this.attributes.vertAxisLabel); const valueLabel = this.element.getAttribute(this.attributes.valueLabel); // create Google Visualization chart based on type switch (this.type) { case this.types.column: this.chart = new google.visualization.ColumnChart(this.chartElement); this.options = this.getColumnOptions(horizAxisLabel, vertAxisLabel, (valueLabel === 'none' || !valueLabel), valueLabel==='percentage'); // draw chart this.data = this.getData(sourceElement, (valueLabel === 'percentage')); this.chart.draw(this.data, this.options); break; case this.types.donut: this.options = this.getDonutOptions(valueLabel); this.data = this.getData(sourceElement); this.drawDonut(this.data, this.options, this.chartElement); break; case this.types.line: this.chart = new google.visualization.LineChart(this.chartElement); this.options = this.getLineOptions(horizAxisLabel, vertAxisLabel, (valueLabel === 'none')); // draw chart this.data = this.getData(sourceElement); this.chart.draw(this.data, this.options); break; } this.options.adjustLegendPosition = !! horizAxisLabel; } /** * Get data - parse data from source table in to data table * @param sourceTable */ getData(sourceTable, calculateAsPercentage) { this.sourceData = this.parseData(sourceTable,calculateAsPercentage); let data = google.visualization.arrayToDataTable(this.sourceData); return data; } /** * Parse data from table to array * @param sourceTable */ parseData(sourceTable, calculateAsPercentage) { let sourceData = []; // parse header text from th nodes and convert to array const thNodes = [...sourceTable.querySelectorAll('thead th')]; let headers = thNodes.map((thNode) => { return thNode.innerText; }); // push header with 'string' and 'style' for column and donut chart if (this.type !== this.types.line) { headers.push({ type: 'string', role: 'style' }); } sourceData.push(headers); // parse graph data from body let trNodes = [...sourceTable.querySelectorAll('tbody tr')]; // calulateAsPercentage is a column mode adjusting values to a % of total values let sumOfValues = 0.0; if(calculateAsPercentage) { // go through data rows, grab 1th values and sum them const get1thValueFromRow = (trNode) => { return parseFloat([...trNode.querySelectorAll('td')][1].innerText); } sumOfValues = trNodes.reduce((sumSoFar,trNode) => sumSoFar + get1thValueFromRow(trNode), sumOfValues); } trNodes.forEach((trNode, index) => { let row = [...trNode.querySelectorAll('td')].map((tdNode, index) => { // first item is used for display, other are used as values return index === 0 ? tdNode.innerText : parseFloat(tdNode.innerText); }); if(calculateAsPercentage) { // calculate from 0.0-1.0 (percentage display formatting handled elsewhere) row[1] = row[1] / (sumOfValues ? sumOfValues: 1.0); // 1.0 just on the odd chance sumOfValues was zero } // push 'color' row for column and donut chart if (this.type !== this.types.line) { row.push('color: ' + this.options.colors[index]); } sourceData.push(row); }); return sourceData; } /** * Get default options - options for all charts */ getDefaultOptions() { return { backgroundColor: 'transparent', colors: [ '#f0f0f0', '#e8e8e8', '#e0e0e0', '#dcdcdc', '#d8d8d8', '#d3d3d3', '#d0d0d0', '#c8c8c8', '#c0c0c0', '#bebebe', '#b8b8b8', '#b0b0b0', '#a9a9a9', '#a8a8a8', '#a0a0a0' ], fontSize: 18, legend: { position: 'none' }, tooltip: { textStyle: { color: '#000000', bold: false }, isHtml: false, trigger: 'selection' }, animation: { duration: 1000, easing: 'in', startup: true } }; } /** * Get options for column chart */ getColumnOptions(horizAxisLabel, vertAxisLabel, hideValueLabels, formatAsPercentage) { let options = this.getDefaultOptions(); const chartWidth = this.chartElement.offsetWidth; const chartHeight = this.chartElement.offsetHeight; options.chartArea = { width: chartWidth - 80, left: 80, top: 20, height: chartHeight - 100 }; options.hAxis = hideValueLabels ? { textPosition: 'none' } : {}; options.vAxis = hideValueLabels ? { textPosition: 'none' } : {}; horizAxisLabel && (options.hAxis.title = horizAxisLabel); vertAxisLabel && (options.vAxis.title = vertAxisLabel); formatAsPercentage && (options.vAxis.format = 'percent'); return options; } /** * Get options for donut chart */ getDonutOptions(valueLabel) { let options = this.getDefaultOptions(); const chartWidth = this.chartElement.offsetWidth; options.chartArea = { width: chartWidth, left: 0, top: 0, height: chartWidth }; options.height = chartWidth + 70; options.width = chartWidth; options.pieHole = 0.5; options.pieSliceBorderColor = 'white'; options.pieSliceText = valueLabel ? valueLabel : 'percentage'; return options; } /** * Draw the donut chart for animation */ drawDonut(data, options, chartElement) { var chart = new google.visualization.PieChart(chartElement); chart.draw(data, options); } /** * Get options for line chart */ getLineOptions(horizAxisLabel, vertAxisLabel, hideValueLabels) { let options = this.getDefaultOptions(); const chartWidth = this.chartElement.offsetWidth; const chartHeight = this.chartElement.offsetHeight; options.chartArea = { width: chartWidth - 50, left: 50, top: 20, height: chartHeight - 130 }; options.curveType = 'function'; options.hAxis = hideValueLabels ? { textPosition: 'none' } : {}; options.vAxis = hideValueLabels ? { textPosition: 'none' } : {}; horizAxisLabel && (options.hAxis.title = horizAxisLabel); vertAxisLabel && (options.vAxis.title = vertAxisLabel); return options; } /** * Create legend - create customized legend */ createLegend() { // get legend element const legend = ElementHelpers.getElementByAttributeWithinElement( this.element, this.attributes.legend ); const legendParent = legend.parentNode; // create legend based on chart type switch (this.type) { case this.types.donut: case this.types.column: this.createDonutOrColumnLegend(legend); legendParent.style.marginTop = this.options.adjustLegendPosition ? '15px' : '10px'; break; case this.types.line: this.createLineLegend(legend); this.options.adjustLegendPosition && (legendParent.style.marginTop = '-10px'); break; } } /** * Create legend for donut or column chart * @param legend */ createDonutOrColumnLegend(legend) { // delete irrelevant first object from source data array this.sourceData.shift(); // bind each value to li item this.sourceData.forEach((dataArray, index) => { if (legend !== null) { this.appendListItemsToList(index, legend, dataArray[0]); } }); } /** * Create legend for line chart * @param legend */ createLineLegend(legend) { // delete irrelevant first object from source data array this.sourceData[0].shift(); // bind each value to li item this.sourceData[0].forEach((string, index) => { if (legend !== null) { this.appendListItemsToList(index, legend, string); } }); } /** * Append list items to list - create li node and add to list * @param index * @param list * @param listItemText */ appendListItemsToList(index, list, listItemText) { // create a <li> node const node = document.createElement('li'); node.setAttribute(this.attributes.legendItem, this.options.colors[index]); // create a text node const textnode = document.createTextNode(listItemText); node.appendChild(textnode); list.appendChild(node); } /** * Get selector */ static getSelector() { return `[${attributeBase}]`; } }