UNPKG

@ndbx/runtime

Version:

The `@ndbx/runtime` package provides a runtime environment to embed NodeBox visualizations directly into React applications. NodeBox is a powerful tool for creating interactive and generative visualizations, and this runtime allows you to integrate those

171 lines (151 loc) 5.82 kB
/** * Replace specific data values in the dataset. * * This node replaces the specified values in the input table by a given new value. * If no attribute name is specified, values will be replaced in all attributes. * * Values to replace: * - null: Represents an explicitly assigned absence of a value. * - undefined: Represents a missing value. The variable has not been assigned. * In the raw data, the property is missing in the row object. * - NaN: Not-a-number. Represents an invalid numeric value in an attribute with variable type number. * - Empty: Values that represent 'nothing' but are valid attribute values. * This includes empty strings (""), empty arrays ([]), or empty objects ({}) * - <regex>: Uses the specified regular expression to match text to be replaced. * - <all>: Replaces all the above. * * Match inside: * - disabled: matching and replacing is performed on the attribute value as a whole. * - enabled: the node will also match and replace (multiple) parts inside text values (only with regex). * * @category Data */ import { fn2 } from "project:Utilities"; export default function (node) { const dataIn = node.tableIn({ name: "data", label: "Input Data" }); // Parameters const attrNameIn = node.stringIn({ name: "attrName", label: "Attributes to replace", }); const matchTypeIn = node.stringIn({ name: "matchType", label: "Values to replace", value: "<all>", choices: ["null", "undefined", "NaN", "empty", "<regex>", "<all>"], }); const regexIn = node.stringIn({ name: "regex", label: "Regular Expression", }); const replaceTypeIn = node.stringIn({ name: "replaceType", label: "New value type", value: "<value>", choices: ["<value>", "null", "undefined", "NaN"], }); const replaceValueIn = node.stringIn({ name: "replaceValue", label: "New value", }); const matchInsideIn = node.booleanIn({ name: "matchInside", label: "Match and replace inside text values (only with regex)", value: false, }); const dataOut = node.tableOut({ name: "dataOut", label: "Output Data" }); // Helper function to check if a value matches the replacement type function matchValue(value, matchType, regex) { const isNothingType = value === null || value === undefined || (typeof value === "number" && isNaN(value)) || value === "" || (Array.isArray(value) && value.length === 0) || (typeof value === "object" && !Object.keys(value).length); switch (matchType) { case "null": return value === null; case "undefined": return value === undefined; case "NaN": return typeof value === "number" && isNaN(value); case "empty": return value === "" || (Array.isArray(value) && value.length === 0) || (typeof value === "object" && !value); case "<regex>": return regex ? regex.test(value) : false; case "<all>": return isNothingType || (regex && typeof value === "string" && regex.test(value)); default: return false; } } // Helper function to determine the replacement value based on the type function setNewValue(newValueType, newValue) { switch (newValueType) { case "null": return null; case "undefined": return undefined; case "NaN": return NaN; case "<value>": default: return newValue; } } // Function to replace data values function replaceData(data, attributes, matchType, regex, replaceType, replaceValue, matchInside) { const setValue = setNewValue(replaceType, replaceValue); return data.map((row) => { const newRow = { ...row }; const newValue = typeof setValue === "function" ? setValue(row) : setValue; //apply expression to datarow attributes.forEach((attr) => { if (newRow.hasOwnProperty(attr)) { const value = newRow[attr]; // Match and replace text inside strings only if regex is provided and matchInside is enabled if (matchType === "<regex>" && regex && typeof value === "string" && matchInside && regex) { // Replace inside text newRow[attr] = value.replace(regex, newValue); } else if ( matchType === "<all>" && regex && typeof value === "string" && value !== null && value !== undefined && value != "" && matchInside && regex ) { // Replace inside text newRow[attr] = value.replace(regex, newValue); } // Replace the entire value if it matches base types or regex else if (matchValue(value, matchType, regex)) { newRow[attr] = newValue; } } }); return newRow; }); } // Main processing node.onRender = () => { const data = structuredClone(dataIn.value ? dataIn.value : []); const attributes = attrNameIn.value === undefined || attrNameIn.value === "" ? Object.keys(data[0] || {}) : attrNameIn.value.split(",").map((attr) => attr.trim()); const matchType = matchTypeIn.value; const regex = regexIn.value ? new RegExp(regexIn.value, "g") : null; const replaceType = replaceTypeIn.value; const replaceValue = fn2(replaceValueIn); const matchInside = matchInsideIn.value; // Validate regex for <regex> or <all> when regex is expected if ((matchType === "<regex>" || (matchType === "<all>" && regexIn.value)) && !regex) { throw new Error("The provide regular expression is not valid."); } const updatedData = replaceData(data, attributes, matchType, regex, replaceType, replaceValue, matchInside); dataOut.set(updatedData); }; }