charts/shared/wrangle.js (154 lines of code) (raw):

import { xFormatting, capitalizeFirstLetter } from './toolbelt'; import { schema, contains, merge } from 'newsroom-dojo/dist/index.js' //import dataTools from "../dataTools" //import ColorScale from "./colorscale" import dataTools from "./dt.js"; import { ColorScale } from "./colorscale.js"; import { max as d3Max } from 'd3-array'; import { timeParse as d3TimeParse, timeFormat as d3TimeFormat } from 'd3-time-format'; // Define constants for frequently used values const SPECIAL_KEYS = ["template", "options", "chartId"]; const CONVERT_TO_NUMBER = ['marginleft','marginright','margintop','marginbottom', 'numCols', 'height', 'maxHeight', 'opacity']; const SCALE_KEYS = ['xScale', 'yScale', 'zScale']; const SCALE_TYPES = ['scaleLinear', 'scaleSqrt', 'scalePow', 'scaleLog', 'scaleTime', 'scaleSequential', 'scaleQuantize', 'scaleThreshold', 'scaleOrdinal', 'scaleBand']; const BOOLEAN_KEYS = ['enableShowMore','aria', 'forceCentre','enableSearch', 'enableSort', 'enableScroll', 'zero_line_x', 'zero_line_y', 'lineLabelling', "autoSort", "scaleByAllMax", "hideKey", "beeswarm", "invertY", "breaks", "zeroLineX", "zeroLineY", "rangeFormat", "dataLabels"]; const AXIS_TYPES = ['stackedbar', 'linechart', 'smallmultiples', 'stackedarea', 'bubble', 'scatterplot', 'lollipop', 'verticalbar', 'horizontalbar', 'horizontalgroupedbar']; /** * Data preprocessing function * @param {Object} data - The input data object. * @param {Object} chart - The chart configuration object. * @returns {Object} - The processed settings object. */ export async function wrangle(data, chart) { const settings = initializeSettings(data); if (data.columns && data.data) { const columns = data.columns.length ? data.columns : await schema(data.data); settings.columns = columns; settings.columnMap = new Map(columns.map(d => [d.column, d])); console.log("---- New schema info ----", columns, "---- Ends ----"); } processSettings(settings); if (settings.dropdown?.length) { settings.dropdown = settings.dropdown.map(dropdown => { const mergedDropdown = merge({ label: "", values: "", tooltip: "", colours: "", data: "" }, dropdown); if (!mergedDropdown.label && mergedDropdown.data) { mergedDropdown.label = mergedDropdown.data; if (!mergedDropdown.values) { mergedDropdown.values = mergedDropdown.data; } } return mergedDropdown; }); } updateAxisSettings(chart, settings); applyChartSettings(chart, settings); if (settings.type === "table" && data.userkey) { processTableGraphics(data.userkey, settings, data); } if (contains(AXIS_TYPES, settings.type)) { settings.xFormat = xFormatting(settings); console.log("xFormat", settings.xFormat) } processDateSettings(settings); return settings; } /** * Initializes settings from the input data. * @param {Object} data - The input data object. * @returns {Object} - The initialized settings object. */ function initializeSettings(data) { const settings = { modules: {}, height: 0, width: 0, svgWidth: 0, svgHeight: 0, featuresWidth: 0, featuresHeight: 0, isMobile: null, colors: null, datum: [] }; Object.keys(data).forEach(key => { if (SPECIAL_KEYS.includes(key)) { Object.entries(data[key][0]).forEach(([specialKey, value]) => { settings[specialKey.replace('-', '')] = value; }); } else { settings[key === 'key' ? 'userkey' : key] = data[key]; if (key === 'data') { processData(data[key], settings); } } }); return settings; } /** * Processes the data and updates settings. * @param {Array} data - The data array. * @param {Object} settings - The settings object to be updated. */ function processData(data, settings) { const dataKeys = Object.keys(data[0]); settings.keys = dataKeys; data.forEach(row => { dataKeys.forEach(cell => { if (row[cell] === "0" || row[cell] === "0.0") { row[cell] = 0; } if (row[cell] === "") { row[cell] = null; } if (typeof row[cell] === "string" && row[cell].includes(",") && !isNaN(row[cell].replace(/,/g, ""))) { row[cell] = +row[cell].replace(/,/g, ""); } row[cell] = (typeof row[cell] === "string" && !isNaN(row[cell])) ? +row[cell] : row[cell]; }); }); } /** * Processes settings by converting values to appropriate types. * @param {Object} settings - The settings object to be processed. */ function processSettings(settings) { Object.keys(settings).forEach(setting => { if (contains(CONVERT_TO_NUMBER, setting)) { settings[setting] = isNaN(settings[setting]) ? settings[setting] : +settings[setting]; } if (contains(['minY', 'maxY', 'minX', 'maxX','minZ', 'maxZ'], setting) && settings[setting] !== "") { // console.log("wrangle",setting, settings[setting]) settings[setting] = isNaN(settings[setting]) ? settings[setting] : +settings[setting]; } if (contains(BOOLEAN_KEYS, setting)) { console.log(setting) settings[setting] = settings[setting].toLowerCase() !== 'false'; console.log(settings[setting]) } if (contains(SCALE_KEYS, setting)) { settings[setting] = contains(SCALE_TYPES, settings[setting]) ? settings[setting] : 'scaleLinear'; } }); } /** * Updates axis settings based on the chart configuration. * @param {Object} chart - The chart configuration object. * @param {Object} settings - The settings object to be updated. */ function updateAxisSettings(chart, settings) { if (chart.axis) { chart.axis.forEach(setting => { if (!settings[setting.name]) { settings[setting.name] = settings.keys[setting.default]; } }); } } /** * Applies additional chart settings. * @param {Object} chart - The chart configuration object. * @param {Object} settings - The settings object to be updated. */ function applyChartSettings(chart, settings) { if (chart.settings) { chart.settings.forEach(setting => { settings[setting.name] = dataTools[setting.name](settings); }); } } /** * Processes table graphics for the settings object. * @param {Array} userKeys - The user keys array. * @param {Object} settings - The settings object to be updated. * @param {Object} data - The input data object. */ function processTableGraphics(userKeys, settings, data) { userKeys.forEach(key => { key.format = key.format || []; if (contains('bar', key.format)) { const range = data.data.map(item => item[key.key]); const max = d3Max(range); const colors = key.colours?.includes(",") ? key.colours.split(',') : [key.colours || "red"]; const domain = key.values?.includes(",") ? key.values.split(',') : [key.values || ""]; const scale = key.scale || 'Linear'; key.graphics = { type: "bar", max: max, colour: new ColorScale({ type: capitalizeFirstLetter(scale), domain: domain, colors: colors }) }; } }); } /** * Processes date settings for the settings object. * @param {Object} settings - The settings object to be updated. */ function processDateSettings(settings) { if (settings.dateFormat) { console.log(`Set date format to ${settings.dateFormat}`); settings.parseTime = settings.dateFormat ? d3TimeParse(settings.dateFormat) : null; } if (settings.xAxisDateFormat) { settings.xAxisDateFormat = settings.xAxisDateFormat ? d3TimeFormat(settings.xAxisDateFormat) : d3TimeFormat("%d %b '%y"); } }