- Replacement for singleton Blockly object. This defines only the methods and
-values used by block creation code.
+ Prefix and suffix for well-formed pipelines.
- Callback for displaying a table as HTML.
+ Create dynamic table from array from JSON with one table column per property.
+Each object must have the same properties.
fixCode
- Fix up runnable code if it isn't properly terminated yet.
+ Make a block by name. If the construction function returns a string, that's
+what we want; otherwise, it's a two-element list with the desired text and
+the order, so we return the first element.
@@ -2762,7 +2832,7 @@
- Set the display property of the two input toggleable panes.
-(Has to be done manually rather than in CSS because properties are being reset.)
+ Run the code generated from the user's blocks.
+Depends on the global TidyBlocksWorkspace variable.
- Create dynamic table from array from JSON with one table column per property.
-Each object must have the same properties.
+ Save the code generated from the user's workspace.
+Depends on the global TidyBlocksWorkspace variable.
- Read 'index.html', find block files, and eval those.
-Does _not_ read R files (for now).
+ Set up Blockly display by injecting XML data into blockDisplay div.
+As a side effect, sets the global TidyBlocksWorkspace variable for later use.
loadCode
- Load saved code.
-Depends on the global TidyBlocksWorkspace variable.
+ Show the text based code corresponding to selected blocks.
@@ -3432,55 +3562,6 @@
makeBlock
- Make a block by name. If the construction function returns a string, that's
-what we want; otherwise, it's a two-element list with the desired text and
-the order, so we return the first element.
+ Add two values.
@@ -3596,13 +3675,13 @@
readCSV
- Read a CSV file. Defined here to (a) load local CSV and (b) be in scope for
-'eval' of generated code.
+ Raise exception if a condition doesn't hold.
@@ -3904,7 +4107,30 @@
saveCode
- Save the code generated from the user's workspace.
-Depends on the global TidyBlocksWorkspace variable.
+ Equality.
@@ -4522,85 +4759,644 @@
- Set up Blockly display by injecting XML data into blockDisplay div.
-As a side effect, sets the global TidyBlocksWorkspace variable for later use.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
+ * Prefix and suffix for well-formed pipelines.
+ */
+const TIDYBLOCKS_START = '/* tidyblocks start */'
+const TIDYBLOCKS_END = '/* tidyblocks end */'
+
+/**
+ * Turn block of CSV text into TidyBlocksDataFrame. The parser argument should be Papa.parse;
+ * it is passed in here so that this file can be loaded both in the browser and for testing.
+ * @param {string} text Text to parse.
+ * @param {function} parser Function to turn CSV text into array of objects.
+ * @returns New dataframe with sanitized column headers.
+ */
+const csv2TidyBlocksDataFrame = (text, parser) => {
+
+ const seen = new Map() // global to transformHeader
+ const transformHeader = (name) => {
+ // Simple character fixes.
+ name = name
+ .trim()
+ .replace(/ /g, '_')
+ .replace(/[^A-Za-z0-9_]/g, '')
+
+ // Ensure header is not empty after character fixes.
+ if (name.length === 0) {
+ name = 'EMPTY'
+ }
+
+ // Name must start with underscore or letter.
+ if (! name.match(/^[_A-Za-z]/)) {
+ name = `_${name}`
+ }
+
+ // Name must be unique.
+ if (seen.has(name)) {
+ const serial = seen.get(name) + 1
+ seen.set(name, serial)
+ name = `${name}_${serial}`
+ }
+ else {
+ seen.set(name, 0)
+ }
+
+ return name
+ }
+
+ const result = parser(
+ text.trim(),
+ {
+ dynamicTyping: true,
+ header: true,
+ skipEmptyLines: true,
+ transformHeader: transformHeader
+ }
+ )
+ return new TidyBlocksDataFrame(result.data)
+}
+
+/**
+ * Get the prefix for registering blocks.
+ * @param {string} fill Comma-separated list of quoted strings identifying pipelines to wait for.
+ * @returns {string} Text to insert into generated code.
+ */
+const registerPrefix = (fill) => {
+ return `${TIDYBLOCKS_START} TidyBlocksManager.register([${fill}], () => {`
+}
+
+/**
+ * Get the suffix for registering blocks.
+ * @param {string} fill Single quoted string identifying pipeline produced.
+ * @returns {string} Text to insert into generated code.
+ */
+const registerSuffix = (fill) => {
+ return `}, [${fill}]) ${TIDYBLOCKS_END}`
+}
+
+/**
+ * Fix up runnable code if it isn't properly terminated yet.
+ * @param {string} code Pipeline code to be terminated if necessary.
+ */
+const fixCode = (code) => {
+ if (! code.endsWith(TIDYBLOCKS_END)) {
+ const suffix = registerSuffix('')
+ code += `.plot(environment, {}) ${suffix}`
+ }
+ return code
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Raise exception if a condition doesn't hold.
+ * @param {Boolean} check Condition that must be true.
+ * @param {string} message What to say if it isn't.
+ */
+const tbAssert = (check, message) => {
+ if (! check) {
+ throw new Error(message)
+ }
+}
+
+/**
+ * Check that a value is numeric.
+ * @param value What to check.
+ * @returns The input value if it passes the test.
+ */
+const tbAssertNumber = (value) => {
+ tbAssert(typeof value === 'number',
+ `Value ${value} is not a number`)
+ return value
+}
+
+/**
+ * Check that the types of two values are the same.
+ * @param left One of the values.
+ * @param right The other value.
+ */
+const tbTypeEqual = (left, right) => {
+ tbAssert(typeof left === typeof right,
+ `Values ${left} and ${right} have different types`)
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Count number of values.
+ * @param {Array} values The values to be counted.
+ * @return {number} Number of values.
+ */
+const tbCount = (values) => {
+ return values.length
+}
+
+/**
+ * Find maximum value.
+ * @param {Array} values The values to be searched.
+ * @return {number} Maximum value.
+ */
+const tbMax = (values) => {
+ return (values.length === 0)
+ ? NaN
+ : values.reduce((soFar, val) => (val > soFar) ? val : soFar)
+}
+
+/**
+ * Find mean value.
+ * @param {Array} values The values to be averaged.
+ * @return {number} Mean value.
+ */
+const tbMean = (values) => {
+ return (values.length === 0)
+ ? NaN
+ : values.reduce((total, num) => total + num, 0) / values.length
+}
+
+/**
+ * Find median value.
+ * @param {Array} values The values to be searched.
+ * @return {number} Median value.
+ */
+const tbMedian = (values) => {
+ if (values.length === 0) {
+ return NaN
+ }
+ else {
+ const temp = [...values]
+ temp.sort()
+ return temp[Math.floor(temp.length / 2)]
+ }
+}
+
+/**
+ * Find median value.
+ * @param {Array} values The values to be searched.
+ * @return {number} Minimum value.
+ */
+const tbMin = (values) => {
+ return (values.length === 0)
+ ? NaN
+ : values.reduce((soFar, val) => (val < soFar) ? val : soFar)
+}
+
+/**
+ * Find standard deviation.
+ * @param {Array} values The values to be summarized.
+ * @return {number} Standard deviation.
+ */
+const tbStd = (values) => {
+ return Math.sqrt(tbVariance(values))
+}
+
+/**
+ * Find sum.
+ * @param {Array} values The values to be added.
+ * @return {number} Total.
+ */
+const tbSum = (values) => {
+ return values.reduce((total, num) => total + num, 0)
+}
+
+/**
+ * Find variance.
+ * @param {Array} values The values to be summarized.
+ * @return {number} Variance.
+ */
+const tbVariance = (values) => {
+ if (! values) {
+ return NaN
+ }
+ const m = tbMean(values)
+ const squareDiffs = values.map(v => (v - m)**2)
+ return tbMean(squareDiffs)
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Convert row value to Boolean.
+ * @param {number{ blockId which block this is.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Boolean value.
+ */
+const tbToBoolean = (blockId, row, getValue) => {
+ return getValue(row) ? true : false
+}
+
+/**
+ * Convert row value to datetime.
+ * @param {number{ blockId which block this is.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Date object.
+ */
+const tbToDatetime = (blockId, row, getValue) => {
+ const value = getValue(row)
+ const result = new Date(value)
+ tbAssert(!isNaN(result),
+ `[block ${blockId}] cannot convert "${value}" to date`)
+ return result
+}
+
+/**
+ * Convert row value to number.
+ * @param {number{ blockId which block this is.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Numeric value.
+ */
+const tbToNumber = (blockId, row, getValue) => {
+ const value = getValue(row)
+ if (typeof value == 'boolean') {
+ return value ? 1 : 0
+ }
+ if (typeof value == 'string') {
+ return parseFloat(string)
+ }
+ return value
+}
+
+/**
+ * Convert row value to string.
+ * @param {number{ blockId which block this is.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns String value.
+ */
+const tbToString = (blockId, row, getValue) => {
+ const value = getValue(row)
+ if (typeof value == 'string') {
+ return value
+ }
+ return `${value}`
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Check if value is Boolean.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Is value Boolean?
+ */
+const tbIsBoolean = (row, getValue) => {
+ return typeof getValue(row) === 'boolean'
+}
+
+/**
+ * Check if value is number.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Is value numeric?
+ */
+const tbIsNumber = (row, getValue) => {
+ return typeof getValue(row) === 'number'
+}
+
+/**
+ * Check if value is string.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Is value string?
+ */
+const tbIsString = (row, getValue) => {
+ return typeof getValue(row) === 'string'
+}
+
+//--------------------------------------------------------------------------------
+
+/*
+ * Convert string to date object using format.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row Row containing values.
+ * @param {string} format Format to use for parsing (FIXME: IGNORED UNTIL WE CAN LOAD 'moment').
+ * @param {function} getValue How to get desired value.
+ * @returns Date corresponding to string.
+ */
+const tbParseDate = (blockId, row, format, getValue) => {
+ const value = getValue(row)
+ tbAssert(typeof value === 'string',
+ `Expected string not ${typeof value}`)
+ return new Date(value)
+}
+
+/*
+ * Extract year from value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Year as number.
+ */
+const tbToYear = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getFullYear()
+}
+
+/**
+ * Extract month from value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Month as number.
+ */
+const tbToMonth = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getMonth() + 1 // normalize to 1-12 to be consistent with days of month
+}
+
+/**
+ * Extract day of month from value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Day of month as number.
+ */
+const tbToDay = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getDate()
+}
+
+/**
+ * Extract day of week from value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Day of month as number.
+ */
+const tbToWeekDay = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getDay()
+}
+
+/**
+ * Extract hours from date value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Hours portion of value.
+ */
+const tbToHours = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getHours()
+}
+
+/**
+ * Extract minutes from date value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Minutes portion of value.
+ */
+const tbToMinutes = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getMinutes()
+}
+
+/**
+ * Extract seconds from date value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Seconds portion of value.
+ */
+const tbToSeconds = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getSeconds()
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Get a column's value from a row, failing if the column doesn't exist.
+ * @param {Object} row The row to look in.
+ * @param {string} column The field to look up.
+ * @returns The value.
+ */
+const tbGet = (blockId, row, column) => {
+ tbAssert(column in row,
+ `[block ${blockId}] no such column "${column}" (have [${Object.keys(row).join(',')}])`)
+ return row[column]
+}
+
+/**
+ * Add two values.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The sum.
+ */
+const tbAdd = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
+ return left + right
+}
+
+/**
+ * Divide two values.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The quotient.
+ */
+const tbDiv = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
+ return left / right
+}
+
+/**
+ * Calculate an exponent.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The exponentiated value.
+ */
+const tbExp = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
+ return left ** right
+}
+
+/**
+ * Find the remainder of two values.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The remainder.
+ */
+const tbMod = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
+ return left % right
+}
+
+/**
+ * Multiply two values.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The product.
+ */
+const tbMul = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
+ return left * right
+}
+
+/**
+ * Negate a value.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getValue How to get the value from the row.
+ * @returns The numerical negation.
+ */
+const tbNeg = (blockId, row, getValue) => {
+ const value = tbAssertNumber(getValue(row))
+ return - value
+}
+
+/**
+ * Subtract two values.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The difference.
+ */
+const tbSub = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
+ return left - right
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Logical conjunction of two values.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The conjunction.
+ */
+const tbAnd = (blockId, row, getLeft, getRight) => {
+ const left = tbToBoolean(row, getLeft)
+ const right = tbToBoolean(row, getRight)
+ return left && right
+}
+
+/**
+ * Logical negation of a value.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getValue How to get the value from the row.
+ * @returns The logical conjunction.
+ */
+const tbNot = (blockId, row, getValue) => {
+ const value = tbToLogical(getValue(row))
+ return ! value
+}
+
+/**
+ * Logical disjunction of two values.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The disjunction.
+ */
+const tbOr = (blockId, row, getLeft, getRight) => {
+ const left = tbToBoolean(row, getLeft)
+ const right = tbToBoolean(row, getRight)
+ return left || right
+}
+
+/**
+ * Choosing a value based on a logical condition.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getCond How to get the condition's value.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The left (right) value if the condition is true (false).
+ */
+const tbIfElse = (blockId, row, getCond, getLeft, getRight) => {
+ const cond = tbToBoolean(row, getCond)
+ return cond ? getLeft(row) : getRight(row)
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Strict greater than.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The comparison's result.
+ */
+const tbGt = (blockId, row, getLeft, getRight) => {
+ const left = getLeft(row)
+ const right = getRight(row)
+ tbTypeEqual(left, right)
+ return left > right
+}
+
+/**
+ * Greater than or equal.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The comparison's result.
+ */
+const tbGeq = (blockId, row, getLeft, getRight) => {
+ const left = getLeft(row)
+ const right = getRight(row)
+ tbTypeEqual(left, right)
+ return left >= right
+}
+
+/**
+ * Equality.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The comparison's result.
+ */
+const tbEq = (blockId, row, getLeft, getRight) => {
+ const left = getLeft(row)
+ const right = getRight(row)
+ tbTypeEqual(left, right)
+ return left === right
+}
+
+/**
+ * Inequality.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The comparison's result.
+ */
+const tbNeq = (blockId, row, getLeft, getRight) => {
+ const left = getLeft(row)
+ const right = getRight(row)
+ tbTypeEqual(left, right)
+ return left !== right
+}
+
+/**
+ * Less than or equal.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The comparison's result.
+ */
+const tbLeq = (blockId, row, getLeft, getRight) => {
+ const left = getLeft(row)
+ const right = getRight(row)
+ tbTypeEqual(left, right)
+ return left <= right
+}
+
+/**
+ * Strictly less than.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The comparison's result.
+ */
+const tbLt = (blockId, row, getLeft, getRight) => {
+ const left = getLeft(row)
+ const right = getRight(row)
+ tbTypeEqual(left, right)
+ return left < right
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Store a dataframe.
+ */
+class TidyBlocksDataFrame {
+
+ /**
+ * Construct a new dataframe.
+ * @param {Object[]} values The initial values (aliased).
+ */
+ constructor (values) {
+ this.data = values
+ }
+
+ //------------------------------------------------------------------------------
+
+ /**
+ * Filter rows, keeping those that pass a test.
+ * @param {function} op How to test rows.
+ * @returns A new dataframe.
+ */
+ filter (blockId, op) {
+ tbAssert(op, `[block ${blockId}] no operator for filter`)
+ const newData = this.data.filter(row => {
+ return op(row)
+ })
+ return new TidyBlocksDataFrame(newData)
+ }
+
+ /**
+ * Group by the values in a column, storing the result in a new _group_ column.
+ * @param {string} column The column that determines groups.
+ * @returns A new dataframe.
+ */
+ groupBy (blockId, column) {
+ tbAssert(column.length !== 0,
+ `[block ${blockId}] empty column name for grouping`)
+ const seen = new Map()
+ let groupId = 0
+ const grouped = this.data.map(row => {
+ row = {...row}
+ const value = tbGet(blockId, row, column)
+ if (! seen.has(value)) {
+ seen.set(value, groupId)
+ groupId += 1
+ }
+ row._group_ = seen.get(value)
+ return row
+ })
+ return new TidyBlocksDataFrame(grouped)
+ }
+
+ /**
+ * Create a new column by operating on existing columns.
+ * @param {string} newName New column's name.
+ * @param {function} op How to create new values from a row.
+ * @returns A new dataframe.
+ */
+ mutate (blockId, newName, op) {
+ tbAssert(newName,
+ `[block ${blockId}] empty new column name for mutate`)
+ tbAssert(op !== null,
+ `[block ${blockId}] no operator for mutate`)
+ const newData = this.data.map(row => {
+ const newRow = {...row}
+ newRow[newName] = op(row)
+ return newRow
+ })
+ return new TidyBlocksDataFrame(newData)
+ }
+
+ /**
+ * Select columns.
+ * @param {string[]} columns The names of the columns to keep.
+ * @returns A new dataframe.
+ */
+ select (blockId, columns) {
+ tbAssert(columns.length !== 0,
+ `[block ${blockId}] no columns specified for select`)
+ tbAssert(this.hasColumns(columns),
+ `[block ${blockId}] unknown column(s) [${columns}] in select`)
+ const newData = this.data.map(row => {
+ const result = {}
+ columns.forEach(key => {
+ result[key] = tbGet(blockId, row, key)
+ })
+ return result
+ })
+ return new TidyBlocksDataFrame(newData)
+ }
+
+ /**
+ * Sort data by values in specified columns.
+ * @param {string[]} columns Names of columns to sort by.
+ * @returns New data frame with sorted data.
+ */
+ sort (blockId, columns, reverse) {
+ tbAssert(columns.length !== 0,
+ `[block ${blockId}] no columns specified for sort`)
+ tbAssert(this.hasColumns(columns),
+ `[block ${blockId}] unknown column(s) [${columns}] in sort`)
+ const result = [...this.data]
+ result.sort((left, right) => {
+ return columns.reduce((soFar, col) => {
+ if (soFar !== 0) {
+ return soFar
+ }
+ if (left[col] < right[col]) {
+ return -1
+ }
+ if (left[col] > right[col]) {
+ return 1
+ }
+ return 0
+ }, 0)
+ })
+ if (reverse) {
+ result.reverse()
+ }
+ return new TidyBlocksDataFrame(result)
+ }
+
+ /**
+ * Replace internal dataframe with a summarized dataframe.
+ * @param {function} func Summarization function.
+ * @param {string} column Column to summarize.
+ * @return A new dataframe.
+ */
+ summarize (blockId, func, column) {
+ // Handle empty case.
+ if (this.data.length === 0) {
+ return new TidyBlocksDataFrame([])
+ }
+
+ // Check column access.
+ tbAssert(column,
+ `[block ${blockId}] no column specified for summarize`)
+ tbAssert(this.hasColumns(column),
+ `[block ${blockId}] unknown column(s) [${column}] in summarize`)
+
+ // Final data.
+ const result = []
+
+ // Aggregate the whole thing?
+ if (! this.hasColumns('_group_')) {
+ const values = this.getColumn(column)
+ const record = {}
+ record[column] = func(values)
+ result.push(record)
+ }
+
+ // Aggregate by groups
+ else {
+ // _group_ values in column by index.
+ const grouped = new Map()
+ this.data.forEach(row => {
+ if (grouped.has(row._group_)) {
+ grouped.get(row._group_).push(row[column])
+ }
+ else {
+ grouped.set(row._group_, [row[column]])
+ }
+ })
+
+ // Operate by group.
+ grouped.forEach((values, group) => {
+ const record = {}
+ record['_group_'] = group
+ record[column] = func(values)
+ result.push(record)
+ })
+ }
+
+ // Create new dataframe.
+ return new TidyBlocksDataFrame(result)
+ }
+
+ /**
+ * Remove grouping if present.
+ * @returns A new dataframe.
+ */
+ ungroup (blockId) {
+ tbAssert(this.hasColumns('_group_'),
+ `[block ${blockId}] cannot ungroup data that is not grouped`)
+ const newData = this.data.map(row => {
+ row = {...row}
+ delete row._group_
+ return row
+ })
+ return new TidyBlocksDataFrame(newData)
+ }
+
+ //------------------------------------------------------------------------------
+
+ /**
+ * Join two tables on equality between values in specified columns.
+ * @param {function} getDataFxn How to look up data by name.
+ * @param {string} leftTable Notification name of left table to join.
+ * @param {string} leftColumn Name of column from left table.
+ * @param {string} rightTable Notification name of right table to join.
+ * @param {string} rightColumn Name of column from right table.
+ * @returns A new dataframe.
+ */
+ join (getDataFxn, leftTableName, leftColumn, rightTableName, rightColumn) {
+
+ const _addFieldsExcept = (result, tableName, row, exceptName) => {
+ Object.keys(row)
+ .filter(key => (key != exceptName))
+ .forEach(key => {result[`${tableName}_${key}`] = row[key]})
+ }
+
+ const leftFrame = getDataFxn(leftTableName)
+ tbAssert(leftFrame.hasColumns(leftColumn),
+ `left table does not have column ${leftColumn}`)
+ const rightFrame = getDataFxn(rightTableName)
+ tbAssert(rightFrame.hasColumns(rightColumn),
+ `right table does not have column ${rightColumn}`)
+
+ const result = []
+ for (let leftRow of leftFrame.data) {
+ for (let rightRow of rightFrame.data) {
+ if (leftRow[leftColumn] === rightRow[rightColumn]) {
+ const row = {'_join_': leftRow[leftColumn]}
+ _addFieldsExcept(row, leftTableName, leftRow, leftColumn)
+ _addFieldsExcept(row, rightTableName, rightRow, rightColumn)
+ result.push(row)
+ }
+ }
+ }
+
+ return new TidyBlocksDataFrame(result)
+ }
+
+ /**
+ * Notify the pipeline manager that this pipeline has completed so that downstream joins can run.
+ * Note that this function is called at the end of a pipeline, so it does not return 'this' to support method chaining.
+ * @param {function} notifyFxn Callback functon to do notification (to decouple this class from the manager).
+ * @param {string} name Name of this pipeline.
+ */
+ notify (notifyFxn, name) {
+ notifyFxn(name, this)
+ }
+
+ //------------------------------------------------------------------------------
+
+ /**
+ * Call a plotting function. This is in this class to support method chaining
+ * and to decouple this class from the real plotting functions so that tests
+ * will run.
+ * @param {object} environment Connection to the outside world.
+ * @param {object} spec Vega-Lite specification with empty 'values' (filled in here with actual data before plotting).
+ * @returns This object.
+ */
+ plot (environment, spec) {
+ environment.displayTable(this.data)
+ if (Object.keys(spec).length !== 0) {
+ spec.data.values = this.data
+ environment.displayPlot(spec)
+ }
+ return this
+ }
+
+ //------------------------------------------------------------------------------
+
+ /**
+ * Get a column as a JavaScript array.
+ * @param {string} name Name of column to get.
+ * @returns {Array} Column as JavaScript array.
+ */
+ getColumn (name) {
+ tbAssert(this.hasColumns(name),
+ `Table does not have column ${name}`)
+ return this.data.map(row => row[name])
+ }
+
+ /**
+ * Test whether the dataframe has the specified columns.
+ * @param {string[]} names Names of column to check for.
+ * @returns {Boolean} Are columns present?
+ */
+ hasColumns (names) {
+ if (this.data.length === 0) {
+ return false
+ }
+ if (typeof names === 'string') {
+ names = [names]
+ }
+ return names.every(n => (n in this.data[0]))
+ }
+
+ /**
+ * Convert columns to numeric values.
+ * @param {string[]} columns The names of the columns to convert.
+ * @returns This object.
+ */
+ toNumber (blockId, columns) {
+ this.data.forEach(row => {
+ columns.forEach(col => {
+ row[col] = parseFloat(tbGet(blockId, row, col))
+ })
+ })
+ return this
+ }
+
+ /**
+ * Convert to string for printing.
+ */
+ toString () {
+ const str = (row, i) => {
+ return '{'
+ + Object.keys(row).map(key => `${key}: ${row[key]}`).join(', ')
+ + '}'
+ + (this.groups === null ? '' : ` @ ${this.groups[i]}`)
+ }
+ return `= ${this.data.length} =\n`
+ + this.data.map((r, i) => str(r, i)).join('\n')
+ }
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Manage execution of all data pipelines.
+ */
+class TidyBlocksManagerClass {
+
+ /**
+ * Create manager.
+ */
+ constructor () {
+ this.reset()
+ }
+
+ /**
+ * Record a newly-created block and add the ID to its tooltip.
+ * @param {block} block Newly-created block.
+ */
+ addNewBlock (block) {
+ block.tbId = this.nextBlockId
+ block.tooltip = `[${block.tbId}] ${block.tooltip}`
+ this.blocks.set(this.nextBlockId, block)
+ this.nextBlockId += 1
+ }
+
+ /**
+ * Get the number of blocks that have been created (including ones that have
+ * now been deleted).
+ */
+ getNumBlocks () {
+ return this.blocks.size
+ }
+
+ /**
+ * Get a block by serial number.
+ * @param {number} blockId Serial number of block.
+ * @returns {block} The block or null.
+ */
+ getBlock (blockId) {
+ if (this.blocks.has(blockId)) {
+ return this.blocks.get(blockId)
+ }
+ return null
+ }
+
+ /**
+ * Get the output of a completed pipeline.
+ * @param {string} name Name of completed pipeline.
+ * @return TidyBlocksDataFrame.
+ */
+ getResult (name) {
+ return this.results.get(name)
+ }
+
+ /**
+ * Notify the manager that a named pipeline has finished running.
+ * This enqueues pipeline functions to run if their dependencies are satisfied.
+ * @param {string} name Name of the pipeline that just completed.
+ * @param {Object} dataFrame The TidyBlocksDataFrame produced by the pipeline.
+ */
+ notify (name, dataFrame) {
+ this.results.set(name, dataFrame)
+ this.waiting.forEach((dependencies, func) => {
+ dependencies.delete(name)
+ if (dependencies.size === 0) {
+ this.queue.push(func)
+ }
+ })
+ }
+
+ /**
+ * Register a new pipeline function with what it depends on and what it produces.
+ * @param {string[]} depends Names of things this pipeline depends on (if it starts with a join).
+ * @param {function} func Function encapsulating pipeline to run.
+ * @param {function} produces Name of this pipeline (used to trigger things waiting for it).
+ */
+ register (depends, func, produces) {
+ if (depends.length == 0) {
+ this.queue.push(func)
+ }
+ else {
+ this.waiting.set(func, new Set(depends))
+ }
+ }
+
+ /**
+ * Reset internal state.
+ */
+ reset () {
+ this.queue = []
+ this.waiting = new Map()
+ this.results = new Map()
+ this.blocks = new Map()
+ this.nextBlockId = 0
+ }
+
+ /**
+ * Run all pipelines in an order that respects dependencies.
+ * This depends on `notify` to add pipelines to the queue.
+ * @param {object} environment How to interact with the outside world.
+ */
+ run (environment) {
+ environment.displayError('') // clear legacy errors
+ try {
+ let code = environment.getCode()
+ if (! code.includes(TIDYBLOCKS_START)) {
+ throw new Error('pipeline does not have a valid start block')
+ }
+ code = fixCode(code)
+ eval(code)
+ while (this.queue.length > 0) {
+ const func = this.queue.shift()
+ func()
+ }
+ }
+ catch (err) {
+ environment.displayError(err.message)
+ }
+ }
+
+ /**
+ * Show the manager as a string for debugging.
+ */
+ toString () {
+ return `queue ${this.queue.length} waiting ${this.waiting.length} blocks ${this.blocks.size}`
+ }
+}
+
+//--------------------------------------------------------------------------------
+
+/**
+ * Find linear model for plotting.
+ * @param {number[]} values_x X-axis values.
+ * @param {number[]} values_y Y-axis values.
+ * @returns {number[]} Slope and intercept.
+ */
+const findLineByLeastSquares = (values_x, values_y) => {
+ const len = values_x.length
+ if (len != values_y.length) {
+ throw new Error('values_x and values_y have different lengths')
+ }
+
+ // Empty case.
+ if (len === 0) {
+ return [NaN, NaN]
+ }
+
+ // Calculate the sum for each of the parts necessary.
+ let x_sum = 0
+ let y_sum = 0
+ let xy_sum = 0
+ let xx_sum = 0
+ for (let i = 0; i<len; i++) {
+ const x = values_x[i]
+ const y = values_y[i]
+ x_sum += x
+ y_sum += y
+ xx_sum += x * x
+ xy_sum += x * y
+ }
+
+ // Calculate m and b for the line equation:
+ // y = x * m + b
+ var m = (len * xy_sum - x_sum * y_sum) / (len * xx_sum - x_sum * x_sum)
+ var b = (y_sum / len) - (m * x_sum) / len
+
+ // Solve for slope and intercept.
+ return [m, b]
+}
+
+/**
+ * Singleton instance of manager.
+ */
+const TidyBlocksManager = new TidyBlocksManagerClass()
+
+// Make this file require'able if running from the command line.
+if (typeof module !== 'undefined') {
+ module.exports = {
+ csv2TidyBlocksDataFrame,
+ registerPrefix,
+ registerSuffix,
+ TidyBlocksDataFrame,
+ TidyBlocksManager
+ }
+}
+
// Names of single-column fields in various blocks (for generating validators).
const SINGLE_COLUMN_FIELDS = [
'COLUMN',
+ 'FORMAT',
'LEFT_TABLE',
'LEFT_COLUMN',
'RIGHT_TABLE',
@@ -51,6 +52,70 @@
Source: tidyblocks/gui.js
'MULTIPLE_COLUMNS'
]
+//--------------------------------------------------------------------------------
+
+/**
+ * Class to handle connections to the outside world. (A different class with
+ * the same methods is used for testing purposes.)
+ */
+class GuiEnvironment {
+
+ constructor () {
+ }
+
+ /**
+ * Get the code to run.
+ * @returns {string} The code to run.
+ */
+ getCode () {
+ return Blockly.JavaScript.workspaceToCode(TidyBlocksWorkspace)
+ }
+
+ /**
+ * Read CSV from a URL and parse to create TidyBlocks data frame.
+ * @param {string} url URL to read from.
+ */
+ readCSV (url) {
+ const request = new XMLHttpRequest()
+ request.open('GET', url, false)
+ request.send(null)
+
+ if (request.status !== 200) {
+ console.log(`ERROR: ${request.status}`)
+ return null
+ }
+ else {
+ return csv2TidyBlocksDataFrame(request.responseText, Papa.parse)
+ }
+ }
+
+ /**
+ * Display a plot.
+ * @param {Object} spec Vega-Lite spec for plot with data filled in.
+ */
+ displayPlot (spec) {
+ vegaEmbed('#plotOutput', spec, {})
+ }
+
+ /**
+ * Display a table (as HTML).
+ * @param {Object} table JSON array of uniform objects.
+ */
+ displayTable (table) {
+ document.getElementById('dataOutput').innerHTML = json2table(table)
+ }
+
+ /**
+ * Display an error.
+ * @param {string} error The message to display.
+ */
+ displayError (error) {
+ document.getElementById('error').innerHTML = `<p>${error}</p>`
+ }
+}
+
+//--------------------------------------------------------------------------------
+
/**
* Set the display property of the two input toggleable panes.
* (Has to be done manually rather than in CSS because properties are being reset.)
@@ -144,38 +209,13 @@
Source: tidyblocks/gui.js
}
}
-/**
- * Callback for displaying a plot.
- * @param {Object} spec Vega-Lite spec for plot with data filled in.
- */
-const displayPlot = (spec) => {
- vegaEmbed('#plotOutput', spec, {})
-}
-
-/**
- * Callback for displaying a table as HTML.
- * @param {Object} table JSON array of uniform objects.
- */
-const displayTable = (table) => {
- document.getElementById('dataOutput').innerHTML = json2table(table)
-}
-
-/**
- * Callback fro displaying an error online.
- * @param {string} error The message to display.
- */
-const displayError = (error) => {
- console.log(error) // FIXME display in the GUI
-}
-
/**
* Run the code generated from the user's blocks.
* Depends on the global TidyBlocksWorkspace variable.
*/
const runCode = () => {
Blockly.JavaScript.INFINITE_LOOP_TRAP = null
- TidyBlocksManager.run(() => Blockly.JavaScript.workspaceToCode(TidyBlocksWorkspace),
- displayTable, displayPlot, displayError, readCSV)
+ TidyBlocksManager.run(new GuiEnvironment())
}
/**
@@ -209,30 +249,15 @@
Source: tidyblocks/gui.js
})
}
-/**
- * Read CSV from a URL and parse to create TidyBlocks data frame.
- * @param {string} url URL to read from.
- */
-const readCSV = (url) => {
- const request = new XMLHttpRequest()
- request.open('GET', url, false)
- request.send(null)
-
- if (request.status !== 200) {
- console.log(`ERROR: ${request.status}`)
- return null
- }
- else {
- return csv2TidyBlocksDataFrame(request.responseText, Papa.parse)
- }
-}
-
/**
* Produce a human-friendly name for the type of a column.
* @param value The value whose type is checked.
* @returns The name of the type
*/
const colTypeName = (value) => {
+ if (value instanceof Date) {
+ return 'datetime'
+ }
return typeof value
}
@@ -242,6 +267,9 @@
* @param value What to check.
* @returns The input value if it passes the test.
*/
-const tbIsNumber = (value) => {
+const tbAssertNumber = (value) => {
tbAssert(typeof value === 'number',
`Value ${value} is not a number`)
return value
@@ -243,21 +244,38 @@
Source: tidyblocks/tidyblocks.js
/**
* Convert row value to Boolean.
+ * @param {number{ blockId which block this is.
* @param {Object} row Row containing values.
* @param {function} getValue How to get desired value.
* @returns Boolean value.
*/
-const tbToBoolean = (row, getValue) => {
+const tbToBoolean = (blockId, row, getValue) => {
return getValue(row) ? true : false
}
+/**
+ * Convert row value to datetime.
+ * @param {number{ blockId which block this is.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Date object.
+ */
+const tbToDatetime = (blockId, row, getValue) => {
+ const value = getValue(row)
+ const result = new Date(value)
+ tbAssert(!isNaN(result),
+ `[block ${blockId}] cannot convert "${value}" to date`)
+ return result
+}
+
/**
* Convert row value to number.
+ * @param {number{ blockId which block this is.
* @param {Object} row Row containing values.
* @param {function} getValue How to get desired value.
* @returns Numeric value.
*/
-const tbToNumber = (row, getValue) => {
+const tbToNumber = (blockId, row, getValue) => {
const value = getValue(row)
if (typeof value == 'boolean') {
return value ? 1 : 0
@@ -270,11 +288,12 @@
Source: tidyblocks/tidyblocks.js
/**
* Convert row value to string.
+ * @param {number{ blockId which block this is.
* @param {Object} row Row containing values.
* @param {function} getValue How to get desired value.
* @returns String value.
*/
-const tbToString = (row, getValue) => {
+const tbToString = (blockId, row, getValue) => {
const value = getValue(row)
if (typeof value == 'string') {
return value
@@ -284,104 +303,251 @@
Source: tidyblocks/tidyblocks.js
//--------------------------------------------------------------------------------
+/**
+ * Check if value is Boolean.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Is value Boolean?
+ */
+const tbIsBoolean = (row, getValue) => {
+ return typeof getValue(row) === 'boolean'
+}
+
+/**
+ * Check if value is number.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Is value numeric?
+ */
+const tbIsNumber = (row, getValue) => {
+ return typeof getValue(row) === 'number'
+}
+
+/**
+ * Check if value is string.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Is value string?
+ */
+const tbIsString = (row, getValue) => {
+ return typeof getValue(row) === 'string'
+}
+
+//--------------------------------------------------------------------------------
+
+/*
+ * Convert string to date object using format.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row Row containing values.
+ * @param {string} format Format to use for parsing (FIXME: IGNORED UNTIL WE CAN LOAD 'moment').
+ * @param {function} getValue How to get desired value.
+ * @returns Date corresponding to string.
+ */
+const tbParseDate = (blockId, row, format, getValue) => {
+ const value = getValue(row)
+ tbAssert(typeof value === 'string',
+ `Expected string not ${typeof value}`)
+ return new Date(value)
+}
+
+/*
+ * Extract year from value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Year as number.
+ */
+const tbToYear = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getFullYear()
+}
+
+/**
+ * Extract month from value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Month as number.
+ */
+const tbToMonth = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getMonth() + 1 // normalize to 1-12 to be consistent with days of month
+}
+
+/**
+ * Extract day of month from value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Day of month as number.
+ */
+const tbToDay = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getDate()
+}
+
+/**
+ * Extract day of week from value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Day of month as number.
+ */
+const tbToWeekDay = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getDay()
+}
+
+/**
+ * Extract hours from date value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Hours portion of value.
+ */
+const tbToHours = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getHours()
+}
+
+/**
+ * Extract minutes from date value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Minutes portion of value.
+ */
+const tbToMinutes = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getMinutes()
+}
+
+/**
+ * Extract seconds from date value.
+ * @param {Object} row Row containing values.
+ * @param {function} getValue How to get desired value.
+ * @returns Seconds portion of value.
+ */
+const tbToSeconds = (row, getValue) => {
+ const value = getValue(row)
+ tbAssert(value instanceof Date,
+ `Expected date object not "${value}"`)
+ return value.getSeconds()
+}
+
+//--------------------------------------------------------------------------------
+
/**
* Get a column's value from a row, failing if the column doesn't exist.
* @param {Object} row The row to look in.
- * @param {string} key The field to look up.
+ * @param {string} column The field to look up.
* @returns The value.
*/
-const tbGet = (row, key) => {
- tbAssert(key in row,
- `Key ${key} not in row ${Object.keys(row).join(',')}`)
- return row[key]
+const tbGet = (blockId, row, column) => {
+ tbAssert(column in row,
+ `[block ${blockId}] no such column "${column}" (have [${Object.keys(row).join(',')}])`)
+ return row[column]
}
/**
* Add two values.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The sum.
*/
-const tbAdd = (row, getLeft, getRight) => {
- const left = tbIsNumber(getLeft(row))
- const right = tbIsNumber(getRight(row))
+const tbAdd = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
return left + right
}
/**
* Divide two values.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The quotient.
*/
-const tbDiv = (row, getLeft, getRight) => {
- const left = tbIsNumber(getLeft(row))
- const right = tbIsNumber(getRight(row))
+const tbDiv = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
return left / right
}
/**
* Calculate an exponent.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The exponentiated value.
*/
-const tbExp = (row, getLeft, getRight) => {
- const left = tbIsNumber(getLeft(row))
- const right = tbIsNumber(getRight(row))
+const tbExp = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
return left ** right
}
/**
* Find the remainder of two values.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The remainder.
*/
-const tbMod = (row, getLeft, getRight) => {
- const left = tbIsNumber(getLeft(row))
- const right = tbIsNumber(getRight(row))
- return left * right
+const tbMod = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
+ return left % right
}
/**
* Multiply two values.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The product.
*/
-const tbMul = (row, getLeft, getRight) => {
- const left = tbIsNumber(getLeft(row))
- const right = tbIsNumber(getRight(row))
- return left % right
+const tbMul = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
+ return left * right
}
/**
* Negate a value.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getValue How to get the value from the row.
* @returns The numerical negation.
*/
-const tbNeg = (row, getValue) => {
- const value = tbIsNumber(getValue(row))
+const tbNeg = (blockId, row, getValue) => {
+ const value = tbAssertNumber(getValue(row))
return - value
}
/**
* Subtract two values.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The difference.
*/
-const tbSub = (row, getLeft, getRight) => {
- const left = tbIsNumber(getLeft(row))
- const right = tbIsNumber(getRight(row))
+const tbSub = (blockId, row, getLeft, getRight) => {
+ const left = tbAssertNumber(getLeft(row))
+ const right = tbAssertNumber(getRight(row))
return left - right
}
@@ -389,12 +555,13 @@
Source: tidyblocks/tidyblocks.js
/**
* Logical conjunction of two values.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The conjunction.
*/
-const tbAnd = (row, getLeft, getRight) => {
+const tbAnd = (blockId, row, getLeft, getRight) => {
const left = tbToBoolean(row, getLeft)
const right = tbToBoolean(row, getRight)
return left && right
@@ -402,38 +569,55 @@
Source: tidyblocks/tidyblocks.js
/**
* Logical negation of a value.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getValue How to get the value from the row.
* @returns The logical conjunction.
*/
-const tbNot = (row, getValue) => {
+const tbNot = (blockId, row, getValue) => {
const value = tbToLogical(getValue(row))
return ! value
}
/**
* Logical disjunction of two values.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The disjunction.
*/
-const tbOr = (row, getLeft, getRight) => {
+const tbOr = (blockId, row, getLeft, getRight) => {
const left = tbToBoolean(row, getLeft)
const right = tbToBoolean(row, getRight)
return left || right
}
+/**
+ * Choosing a value based on a logical condition.
+ * @param {number} blockId The ID of the block.
+ * @param {Object} row The row to get values from.
+ * @param {function} getCond How to get the condition's value.
+ * @param {function} getLeft How to get the left value from the row.
+ * @param {function} getRight How to get the right value from the row.
+ * @returns The left (right) value if the condition is true (false).
+ */
+const tbIfElse = (blockId, row, getCond, getLeft, getRight) => {
+ const cond = tbToBoolean(row, getCond)
+ return cond ? getLeft(row) : getRight(row)
+}
+
//--------------------------------------------------------------------------------
/**
* Strict greater than.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbGt = (row, getLeft, getRight) => {
+const tbGt = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -442,12 +626,13 @@
Source: tidyblocks/tidyblocks.js
/**
* Greater than or equal.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbGeq = (row, getLeft, getRight) => {
+const tbGeq = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -456,12 +641,13 @@
Source: tidyblocks/tidyblocks.js
/**
* Equality.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbEq = (row, getLeft, getRight) => {
+const tbEq = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -470,12 +656,13 @@
Source: tidyblocks/tidyblocks.js
/**
* Inequality.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbNeq = (row, getLeft, getRight) => {
+const tbNeq = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -484,12 +671,13 @@
Source: tidyblocks/tidyblocks.js
/**
* Less than or equal.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbLeq = (row, getLeft, getRight) => {
+const tbLeq = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -498,12 +686,13 @@
Source: tidyblocks/tidyblocks.js
/**
* Strictly less than.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbLt = (row, getLeft, getRight) => {
+const tbLt = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -532,7 +721,8 @@
Source: tidyblocks/tidyblocks.js
* @param {function} op How to test rows.
* @returns A new dataframe.
*/
- filter (op) {
+ filter (blockId, op) {
+ tbAssert(op, `[block ${blockId}] no operator for filter`)
const newData = this.data.filter(row => {
return op(row)
})
@@ -544,12 +734,14 @@
Source: tidyblocks/tidyblocks.js
* @param {string} column The column that determines groups.
* @returns A new dataframe.
*/
- groupBy (column) {
+ groupBy (blockId, column) {
+ tbAssert(column.length !== 0,
+ `[block ${blockId}] empty column name for grouping`)
const seen = new Map()
let groupId = 0
const grouped = this.data.map(row => {
row = {...row}
- const value = tbGet(row, column)
+ const value = tbGet(blockId, row, column)
if (! seen.has(value)) {
seen.set(value, groupId)
groupId += 1
@@ -566,7 +758,11 @@
Source: tidyblocks/tidyblocks.js
* @param {function} op How to create new values from a row.
* @returns A new dataframe.
*/
- mutate (newName, op) {
+ mutate (blockId, newName, op) {
+ tbAssert(newName,
+ `[block ${blockId}] empty new column name for mutate`)
+ tbAssert(op !== null,
+ `[block ${blockId}] no operator for mutate`)
const newData = this.data.map(row => {
const newRow = {...row}
newRow[newName] = op(row)
@@ -575,25 +771,20 @@
Source: tidyblocks/tidyblocks.js
return new TidyBlocksDataFrame(newData)
}
- /**
- * Reverse the order of rows.
- */
- reverse () {
- const result = [...this.data]
- result.reverse()
- return new TidyBlocksDataFrame(result)
- }
-
/**
* Select columns.
* @param {string[]} columns The names of the columns to keep.
* @returns A new dataframe.
*/
- select (columns) {
+ select (blockId, columns) {
+ tbAssert(columns.length !== 0,
+ `[block ${blockId}] no columns specified for select`)
+ tbAssert(this.hasColumns(columns),
+ `[block ${blockId}] unknown column(s) [${columns}] in select`)
const newData = this.data.map(row => {
const result = {}
columns.forEach(key => {
- result[key] = tbGet(row, key)
+ result[key] = tbGet(blockId, row, key)
})
return result
})
@@ -605,9 +796,11 @@
Source: tidyblocks/tidyblocks.js
* @param {string[]} columns Names of columns to sort by.
* @returns New data frame with sorted data.
*/
- sort (columns) {
- columns.forEach(col => tbAssert(this.hasColumn(col),
- `No such column ${col}`))
+ sort (blockId, columns, reverse) {
+ tbAssert(columns.length !== 0,
+ `[block ${blockId}] no columns specified for sort`)
+ tbAssert(this.hasColumns(columns),
+ `[block ${blockId}] unknown column(s) [${columns}] in sort`)
const result = [...this.data]
result.sort((left, right) => {
return columns.reduce((soFar, col) => {
@@ -623,6 +816,9 @@
* @param {string} column Column to summarize.
* @return A new dataframe.
*/
- summarize (func, column) {
+ summarize (blockId, func, column) {
+ // Handle empty case.
+ if (this.data.length === 0) {
+ return new TidyBlocksDataFrame([])
+ }
+
+ // Check column access.
+ tbAssert(column,
+ `[block ${blockId}] no column specified for summarize`)
+ tbAssert(this.hasColumns(column),
+ `[block ${blockId}] unknown column(s) [${column}] in summarize`)
+
+ // Final data.
const result = []
// Aggregate the whole thing?
- if (! this.hasColumn('_group_')) {
+ if (! this.hasColumns('_group_')) {
const values = this.getColumn(column)
const record = {}
record[column] = func(values)
@@ -673,9 +881,9 @@
Source: tidyblocks/tidyblocks.js
* Remove grouping if present.
* @returns A new dataframe.
*/
- ungroup () {
- tbAssert(this.hasColumn('_group_'),
- 'Cannot ungroup data that is not grouped')
+ ungroup (blockId) {
+ tbAssert(this.hasColumns('_group_'),
+ `[block ${blockId}] cannot ungroup data that is not grouped`)
const newData = this.data.map(row => {
row = {...row}
delete row._group_
@@ -704,10 +912,10 @@
Source: tidyblocks/tidyblocks.js
}
const leftFrame = getDataFxn(leftTableName)
- tbAssert(leftFrame.hasColumn(leftColumn),
+ tbAssert(leftFrame.hasColumns(leftColumn),
`left table does not have column ${leftColumn}`)
const rightFrame = getDataFxn(rightTableName)
- tbAssert(rightFrame.hasColumn(rightColumn),
+ tbAssert(rightFrame.hasColumns(rightColumn),
`right table does not have column ${rightColumn}`)
const result = []
@@ -741,18 +949,15 @@
Source: tidyblocks/tidyblocks.js
* Call a plotting function. This is in this class to support method chaining
* and to decouple this class from the real plotting functions so that tests
* will run.
- * @param {function} tableFxn Callback to display table as table.
- * @param {function} plotFxn Callback to display table graphically.
+ * @param {object} environment Connection to the outside world.
* @param {object} spec Vega-Lite specification with empty 'values' (filled in here with actual data before plotting).
* @returns This object.
*/
- plot (tableFxn, plotFxn, spec) {
- if (tableFxn !== null) {
- tableFxn(this.data)
- }
- if (plotFxn !== null) {
+ plot (environment, spec) {
+ environment.displayTable(this.data)
+ if (Object.keys(spec).length !== 0) {
spec.data.values = this.data
- plotFxn(spec)
+ environment.displayPlot(spec)
}
return this
}
@@ -765,18 +970,24 @@
Source: tidyblocks/tidyblocks.js
* @returns {Array} Column as JavaScript array.
*/
getColumn (name) {
- tbAssert(this.hasColumn(name),
+ tbAssert(this.hasColumns(name),
`Table does not have column ${name}`)
return this.data.map(row => row[name])
}
/**
- * Test whether the dataframe has the specified column.
- * @param {string} name Name of column to check for.
- * @returns {Boolean} Is column present?
+ * Test whether the dataframe has the specified columns.
+ * @param {string[]} names Names of column to check for.
+ * @returns {Boolean} Are columns present?
*/
- hasColumn (name) {
- return name in this.data[0]
+ hasColumns (names) {
+ if (this.data.length === 0) {
+ return false
+ }
+ if (typeof names === 'string') {
+ names = [names]
+ }
+ return names.every(n => (n in this.data[0]))
}
/**
@@ -784,10 +995,10 @@
Source: tidyblocks/tidyblocks.js
* @param {string[]} columns The names of the columns to convert.
* @returns This object.
*/
- toNumber (columns) {
+ toNumber (blockId, columns) {
this.data.forEach(row => {
columns.forEach(col => {
- row[col] = parseFloat(tbGet(row, col))
+ row[col] = parseFloat(tbGet(blockId, row, col))
})
})
return this
@@ -907,15 +1118,15 @@
Source: tidyblocks/tidyblocks.js
/**
* Run all pipelines in an order that respects dependencies.
* This depends on `notify` to add pipelines to the queue.
- * @param {function} getCode How to get the code to run.
- * @param {function} displayTable How to display a table (used in 'eval').
- * @param {function} displayPlot How to display a plot (used in 'eval').
- * @param {function} displayError How to display an error (used in 'eval' and here).
- * @param {function} readCSV How to read a CSV file (used in 'eval').
+ * @param {object} environment How to interact with the outside world.
*/
- run (getCode, displayTable, displayPlot, displayError, readCSV) {
+ run (environment) {
+ environment.displayError('') // clear legacy errors
try {
- let code = getCode()
+ let code = environment.getCode()
+ if (! code.includes(TIDYBLOCKS_START)) {
+ throw new Error('pipeline does not have a valid start block')
+ }
code = fixCode(code)
eval(code)
while (this.queue.length > 0) {
@@ -924,7 +1135,7 @@
diff --git a/package.json b/package.json
index 8f328fd79..dad67cdbf 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"scripts": {
"build": "parcel build index.html",
"dev": "parcel index.html",
- "jsdoc": "jsdoc --destination docs --readme README.md tidyblocks/*.js tests/*.js",
+ "jsdoc": "jsdoc --destination docs --readme README.md tidyblocks/*.js test/*.js",
"lint": "eslint blocks/*.js tidyblocks/*.js generators/js/*.js tests/*.js",
"test": "mocha",
"coverage": "nyc --reporter=html --reporter=text mocha"
diff --git a/tidyblocks/tidyblocks.js b/tidyblocks/tidyblocks.js
index 7123aa175..8c1b85afc 100644
--- a/tidyblocks/tidyblocks.js
+++ b/tidyblocks/tidyblocks.js
@@ -309,13 +309,13 @@ const tbIsString = (row, getValue) => {
/*
* Convert string to date object using format.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row Row containing values.
* @param {string} format Format to use for parsing (FIXME: IGNORED UNTIL WE CAN LOAD 'moment').
* @param {function} getValue How to get desired value.
* @returns Date corresponding to string.
*/
-const tbParseDate = (rowId, row, format, getValue) => {
+const tbParseDate = (blockId, row, format, getValue) => {
const value = getValue(row)
tbAssert(typeof value === 'string',
`Expected string not ${typeof value}`)
@@ -429,13 +429,13 @@ const tbGet = (blockId, row, column) => {
/**
* Add two values.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The sum.
*/
-const tbAdd = (rowId, row, getLeft, getRight) => {
+const tbAdd = (blockId, row, getLeft, getRight) => {
const left = tbAssertNumber(getLeft(row))
const right = tbAssertNumber(getRight(row))
return left + right
@@ -443,13 +443,13 @@ const tbAdd = (rowId, row, getLeft, getRight) => {
/**
* Divide two values.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The quotient.
*/
-const tbDiv = (rowId, row, getLeft, getRight) => {
+const tbDiv = (blockId, row, getLeft, getRight) => {
const left = tbAssertNumber(getLeft(row))
const right = tbAssertNumber(getRight(row))
return left / right
@@ -457,13 +457,13 @@ const tbDiv = (rowId, row, getLeft, getRight) => {
/**
* Calculate an exponent.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The exponentiated value.
*/
-const tbExp = (rowId, row, getLeft, getRight) => {
+const tbExp = (blockId, row, getLeft, getRight) => {
const left = tbAssertNumber(getLeft(row))
const right = tbAssertNumber(getRight(row))
return left ** right
@@ -471,13 +471,13 @@ const tbExp = (rowId, row, getLeft, getRight) => {
/**
* Find the remainder of two values.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The remainder.
*/
-const tbMod = (rowId, row, getLeft, getRight) => {
+const tbMod = (blockId, row, getLeft, getRight) => {
const left = tbAssertNumber(getLeft(row))
const right = tbAssertNumber(getRight(row))
return left % right
@@ -485,13 +485,13 @@ const tbMod = (rowId, row, getLeft, getRight) => {
/**
* Multiply two values.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The product.
*/
-const tbMul = (rowId, row, getLeft, getRight) => {
+const tbMul = (blockId, row, getLeft, getRight) => {
const left = tbAssertNumber(getLeft(row))
const right = tbAssertNumber(getRight(row))
return left * right
@@ -499,25 +499,25 @@ const tbMul = (rowId, row, getLeft, getRight) => {
/**
* Negate a value.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getValue How to get the value from the row.
* @returns The numerical negation.
*/
-const tbNeg = (rowId, row, getValue) => {
+const tbNeg = (blockId, row, getValue) => {
const value = tbAssertNumber(getValue(row))
return - value
}
/**
* Subtract two values.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The difference.
*/
-const tbSub = (rowId, row, getLeft, getRight) => {
+const tbSub = (blockId, row, getLeft, getRight) => {
const left = tbAssertNumber(getLeft(row))
const right = tbAssertNumber(getRight(row))
return left - right
@@ -527,13 +527,13 @@ const tbSub = (rowId, row, getLeft, getRight) => {
/**
* Logical conjunction of two values.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The conjunction.
*/
-const tbAnd = (rowId, row, getLeft, getRight) => {
+const tbAnd = (blockId, row, getLeft, getRight) => {
const left = tbToBoolean(row, getLeft)
const right = tbToBoolean(row, getRight)
return left && right
@@ -541,25 +541,25 @@ const tbAnd = (rowId, row, getLeft, getRight) => {
/**
* Logical negation of a value.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getValue How to get the value from the row.
* @returns The logical conjunction.
*/
-const tbNot = (rowId, row, getValue) => {
+const tbNot = (blockId, row, getValue) => {
const value = tbToLogical(getValue(row))
return ! value
}
/**
* Logical disjunction of two values.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The disjunction.
*/
-const tbOr = (rowId, row, getLeft, getRight) => {
+const tbOr = (blockId, row, getLeft, getRight) => {
const left = tbToBoolean(row, getLeft)
const right = tbToBoolean(row, getRight)
return left || right
@@ -567,14 +567,14 @@ const tbOr = (rowId, row, getLeft, getRight) => {
/**
* Choosing a value based on a logical condition.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getCond How to get the condition's value.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The left (right) value if the condition is true (false).
*/
-const tbIfElse = (rowId, row, getCond, getLeft, getRight) => {
+const tbIfElse = (blockId, row, getCond, getLeft, getRight) => {
const cond = tbToBoolean(row, getCond)
return cond ? getLeft(row) : getRight(row)
}
@@ -583,13 +583,13 @@ const tbIfElse = (rowId, row, getCond, getLeft, getRight) => {
/**
* Strict greater than.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbGt = (rowId, row, getLeft, getRight) => {
+const tbGt = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -598,13 +598,13 @@ const tbGt = (rowId, row, getLeft, getRight) => {
/**
* Greater than or equal.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbGeq = (rowId, row, getLeft, getRight) => {
+const tbGeq = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -613,13 +613,13 @@ const tbGeq = (rowId, row, getLeft, getRight) => {
/**
* Equality.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbEq = (rowId, row, getLeft, getRight) => {
+const tbEq = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -628,13 +628,13 @@ const tbEq = (rowId, row, getLeft, getRight) => {
/**
* Inequality.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbNeq = (rowId, row, getLeft, getRight) => {
+const tbNeq = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -643,13 +643,13 @@ const tbNeq = (rowId, row, getLeft, getRight) => {
/**
* Less than or equal.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbLeq = (rowId, row, getLeft, getRight) => {
+const tbLeq = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)
@@ -658,13 +658,13 @@ const tbLeq = (rowId, row, getLeft, getRight) => {
/**
* Strictly less than.
- * @param {number} rowId The ID of the block.
+ * @param {number} blockId The ID of the block.
* @param {Object} row The row to get values from.
* @param {function} getLeft How to get the left value from the row.
* @param {function} getRight How to get the right value from the row.
* @returns The comparison's result.
*/
-const tbLt = (rowId, row, getLeft, getRight) => {
+const tbLt = (blockId, row, getLeft, getRight) => {
const left = getLeft(row)
const right = getRight(row)
tbTypeEqual(left, right)