@evidence-dev/component-utilities
Advanced tools
Comparing version 0.0.0-4e94b57a to 0.0.0-545afc8f
# @evidence-dev/component-utilities | ||
## 1.1.0 | ||
### Minor Changes | ||
- 121c7868: Adds formatting control to components | ||
## 1.0.0 | ||
@@ -4,0 +10,0 @@ |
@@ -1,6 +0,4 @@ | ||
# License | ||
MIT License | ||
Copyright \(c\) 2021 Evidence | ||
Copyright \(c\) 2023 Evidence | ||
@@ -7,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files \(the "Software"\), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
{ | ||
"name": "@evidence-dev/component-utilities", | ||
"version": "0.0.0-4e94b57a", | ||
"version": "0.0.0-545afc8f", | ||
"description": "", | ||
@@ -13,2 +13,4 @@ "main": "index.js", | ||
"devDependencies": { | ||
"@faker-js/faker": "^8.0.2", | ||
"@vitest/ui": "^0.34.2", | ||
"vitest": "^0.31.1" | ||
@@ -15,0 +17,0 @@ }, |
@@ -7,2 +7,3 @@ import ssf from 'ssf'; | ||
import { standardizeDateString } from './dateParsing'; | ||
import { inferValueType } from './inferColumnTypes'; | ||
@@ -49,2 +50,29 @@ const AXIS_FORMATTING_CONTEXT = 'axis'; | ||
/** | ||
* Returns an Evidence format object to be used in the applyFormatting function | ||
* @param {string} formatString string containing an Excel-style format code, or a format name matching a built-in or custom format | ||
* @param {string} valueType optional - a string representing the data type within the column that will be formatted ('number', 'date', 'boolean', or 'string) | ||
* @returns a format object based on the formatString matching a built-in or custom format name, or a new custom format object containing an Excel-style format code | ||
*/ | ||
export function getFormatObjectFromString(formatString, valueType = undefined) { | ||
let potentialFormatTag = formatString; | ||
let customFormats = getCustomFormats(); | ||
let matchingFormat = [...BUILT_IN_FORMATS, ...customFormats].find( | ||
(format) => format.formatTag?.toLowerCase() === potentialFormatTag?.toLowerCase() | ||
); | ||
let newFormat = {}; | ||
if (matchingFormat) { | ||
return matchingFormat; | ||
} else { | ||
newFormat = { | ||
formatTag: 'custom', | ||
formatCode: potentialFormatTag | ||
}; | ||
if (valueType) { | ||
newFormat.valueType = valueType; | ||
} | ||
return newFormat; | ||
} | ||
} | ||
export const formatValue = (value, columnFormat = undefined, columnUnitSummary = undefined) => { | ||
@@ -140,2 +168,3 @@ try { | ||
} | ||
let result = undefined; | ||
@@ -205,1 +234,14 @@ if (columnFormat) { | ||
} | ||
/** | ||
* Formats a value to whichever format is passed in | ||
* @param {*} value the value to be formatted | ||
* @param {string} format string containing an Excel-style format code, or a format name matching a built-in or custom format | ||
* @returns a formatted value | ||
*/ | ||
export function fmt(value, format) { | ||
let formatObj = getFormatObjectFromString(format); | ||
let valueType = inferValueType(value); | ||
formatObj.valueType = valueType; | ||
return formatValue(value, formatObj); | ||
} |
@@ -1,132 +0,80 @@ | ||
import { tidy, complete } from '@tidyjs/tidy'; | ||
import { tidy, complete, mutate } from '@tidyjs/tidy'; | ||
import getDistinctValues from './getDistinctValues'; | ||
import { findInterval, vectorSeq } from './helpers/getCompletedData.helpers.js'; | ||
function getDiffs(arr) { | ||
var diffs = []; | ||
for (var i = 1; i < arr.length; i++) diffs.push(arr[i] - arr[i - 1]); | ||
return diffs; | ||
} | ||
/** | ||
* This function fills missing data points in the given data array for a specific series. | ||
* | ||
* @param {Record<string, unknown>[]} data - The data as an array of objects. | ||
* @param {string} x - The property used as x-axis. | ||
* @param {string} y - The property used as y-axis. | ||
* @param {string} series - The specific series in the data to be filled. | ||
* @param {boolean} [nullsZero=false] - A flag indicating whether nulls should be replaced with zero. | ||
* @param {boolean} [fillX=false] - A flag indicating whether the x-axis values should be filled (based on the found interval distance). | ||
* @return {Record<string, unknown>[]} An array containing the filled data objects. | ||
*/ | ||
export default function getCompletedData(data, x, y, series, nullsZero = false, fillX = false) { | ||
const groups = Array.from(data).reduce((a, v) => { | ||
if (series) { | ||
if (!a[v[series]]) a[v[series]] = []; | ||
a[v[series]].push(v); | ||
} else { | ||
if (!a.default) a.default = []; | ||
a.default.push(v); | ||
} | ||
return a; | ||
}, {}); | ||
function gcd(a, b) { | ||
// Get greatest common divisor of the differences between values | ||
if (a < b) return gcd(b, a); | ||
const output = []; | ||
for (const value of Object.values(groups)) { | ||
let xIsDate = value[0]?.[x] instanceof Date; | ||
// base case | ||
if (Math.abs(b) < 0.001) return a; | ||
else return gcd(b, a - Math.floor(a / b) * b); | ||
} | ||
function extent(values, valueof) { | ||
let min; | ||
let max; | ||
if (valueof === undefined) { | ||
for (const value of values) { | ||
if (value != null) { | ||
if (min === undefined) { | ||
if (value >= value) min = max = value; | ||
} else { | ||
if (min > value) min = value; | ||
if (max < value) max = value; | ||
} | ||
} | ||
const nullySpec = { series: null }; | ||
if (nullsZero) { | ||
nullySpec[y] = 0; | ||
} else { | ||
// Ensure null for consistency | ||
nullySpec[y] = null; | ||
} | ||
} else { | ||
let index = -1; | ||
for (let value of values) { | ||
if ((value = valueof(value, ++index, values)) != null) { | ||
if (min === undefined) { | ||
if (value >= value) min = max = value; | ||
} else { | ||
if (min > value) min = value; | ||
if (max < value) max = value; | ||
} | ||
} | ||
} | ||
} | ||
return [min, max]; | ||
} | ||
function vectorSeq(values, period) { | ||
let min = extent(values)[0]; | ||
let max = extent(values)[1]; | ||
const expandKeys = {}; | ||
if (fillX) { | ||
/** @type {Array<number>} */ | ||
let xDistinct; | ||
const sequence = []; | ||
let value = min; | ||
while (value <= max) { | ||
sequence.push(Math.round((value + Number.EPSILON) * 100000000) / 100000000); | ||
value += period; | ||
} | ||
if (xIsDate) | ||
xDistinct = getDistinctValues( | ||
value.map((d) => ({ [x]: d[x].getTime() })), | ||
x | ||
); | ||
else xDistinct = getDistinctValues(value, x); | ||
return sequence; | ||
} | ||
/** @type {number} */ | ||
let interval = findInterval(xDistinct); | ||
function findInterval(arr) { | ||
if (arr.length === 1) { | ||
return; | ||
} | ||
// Sort array ascending | ||
arr.sort(function (a, b) { | ||
return a - b; | ||
}); | ||
// Array of all possible x values | ||
expandKeys[x] = vectorSeq(xDistinct, interval); | ||
} | ||
if (series) { | ||
expandKeys[series] = series; | ||
} | ||
// 1. Multiply array by 100 | ||
arr = arr.map(function (x) { | ||
return x * 100000000; | ||
}); | ||
const tidyFuncs = []; | ||
// 2. Get diffs | ||
arr = getDiffs(arr); | ||
// 3. Calculate greatest common divisor of diffs and divide by 100 | ||
let interval = arr.reduce(gcd) / 100000000; | ||
interval = Math.round((interval + Number.EPSILON) * 100000000) / 100000000; | ||
return interval; | ||
} | ||
export default function getCompletedData(data, x, y, series, nullsZero = false, fillX = false) { | ||
let xDistinct = getDistinctValues(data, x); | ||
let interval; | ||
let filledData; | ||
if (series) { | ||
if (fillX) { | ||
interval = findInterval(xDistinct); | ||
if (nullsZero) { | ||
filledData = tidy( | ||
data, | ||
complete({ [x]: vectorSeq(xDistinct, interval), [series]: series }, { [y]: 0 }) | ||
); | ||
} else { | ||
filledData = tidy( | ||
data, | ||
complete({ [x]: vectorSeq(xDistinct, interval), [series]: series }) | ||
); | ||
} | ||
if (Object.keys(expandKeys).length === 0) { | ||
tidyFuncs.push(complete([x], nullySpec)); | ||
// empty object, no special configuration | ||
} else { | ||
filledData = tidy( | ||
data, | ||
complete( | ||
[x, series], | ||
// Nully values in the x and series columns to be treated as nulls | ||
{ [series]: null, [x]: null } | ||
) | ||
tidyFuncs.push(complete(expandKeys, nullySpec)); | ||
} | ||
if (xIsDate) { | ||
tidyFuncs.push( | ||
mutate({ | ||
[x]: (val) => new Date(val[x]) | ||
}) | ||
); | ||
} | ||
} else { | ||
if (fillX) { | ||
interval = findInterval(xDistinct); | ||
if (nullsZero) { | ||
filledData = tidy(data, complete({ [x]: vectorSeq(xDistinct, interval) }, { [y]: 0 })); | ||
} else { | ||
filledData = tidy(data, complete({ [x]: vectorSeq(xDistinct, interval) })); | ||
} | ||
} else { | ||
filledData = tidy(data, complete([x])); | ||
} | ||
output.push(...tidy(value, ...tidyFuncs)); | ||
} | ||
return filledData; | ||
return output; | ||
} |
export default function getDistinctValues(data, column) { | ||
let distinctValues = []; | ||
const distinctValueSet = new Set(); | ||
data.forEach((d) => { | ||
distinctValueSet.add(d[column]); | ||
}); | ||
distinctValues = [...distinctValueSet]; | ||
return distinctValues; | ||
const set = new Set(data.map((val) => val[column])); | ||
return Array.from(set); | ||
} |
@@ -17,3 +17,3 @@ // To-do, replace with import from db-commons | ||
const inferValueType = function (columnValue) { | ||
export const inferValueType = function (columnValue) { | ||
if (typeof columnValue === 'number') { | ||
@@ -20,0 +20,0 @@ return EvidenceType.NUMBER; |
194824
37
6980
3