Commit pirate JS files
This commit is contained in:
679
js/TabularData.js
Normal file
679
js/TabularData.js
Normal file
@@ -0,0 +1,679 @@
|
||||
/*
|
||||
Copyright (c) 2023-forever Douglas Malnati. All rights reserved.
|
||||
|
||||
See the /faq/tos page for details.
|
||||
|
||||
(If this generated header is stamped on a file which is a 3rd party file or under a different license or copyright, then ignore this copyright statement and use that file's terms.)
|
||||
*/
|
||||
|
||||
|
||||
export class TabularData
|
||||
{
|
||||
constructor(dataTable)
|
||||
{
|
||||
this.dataTable = dataTable;
|
||||
|
||||
this.col__idx = new Map();
|
||||
|
||||
this.col__metaData = new Map();
|
||||
this.row__metaData = new WeakMap();
|
||||
|
||||
this.#CacheHeaderLocations();
|
||||
}
|
||||
|
||||
// create a new set of rows with copies of the values
|
||||
// duplicate the metadata.
|
||||
// metadata row keys will be new (objects), values will be copied
|
||||
// metadata col keys will be copied (strings), values will be copied
|
||||
Clone()
|
||||
{
|
||||
// prepare new objects
|
||||
let dataTableNew = [];
|
||||
let tdNew = new TabularData(dataTableNew);
|
||||
|
||||
// make new rows, with copies of data (including header)
|
||||
for (let rowCur of this.dataTable)
|
||||
{
|
||||
let rowNew = [... rowCur];
|
||||
|
||||
dataTableNew.push(rowNew);
|
||||
|
||||
// copy any row meta data if any
|
||||
if (this.row__metaData.has(rowCur))
|
||||
{
|
||||
tdNew.row__metaData.set(rowNew, this.row__metaData.get(rowCur));
|
||||
}
|
||||
}
|
||||
|
||||
// col meta data by big copy, keys are strings, so ok to do
|
||||
// without tying to some object
|
||||
tdNew.col__metaData = new Map(this.col__metaData);
|
||||
|
||||
// update internal data structure
|
||||
tdNew.#CacheHeaderLocations();
|
||||
|
||||
return tdNew;
|
||||
}
|
||||
|
||||
// will only set the col metadata if it's a real column.
|
||||
// this data is destroyed if the column is destroyed.
|
||||
SetColMetaData(col, metaData)
|
||||
{
|
||||
let idx = this.Idx(col);
|
||||
if (idx != undefined)
|
||||
{
|
||||
this.col__metaData.set(col, metaData);
|
||||
}
|
||||
}
|
||||
|
||||
// for valid columns, return metadata, creating if needed.
|
||||
// for invalid columns, undefined
|
||||
GetColMetaData(col)
|
||||
{
|
||||
let retVal = undefined;
|
||||
|
||||
let idx = this.Idx(col);
|
||||
if (idx != undefined)
|
||||
{
|
||||
if (this.col__metaData.has(col) == false)
|
||||
{
|
||||
this.col__metaData.set(col, {});
|
||||
}
|
||||
|
||||
retVal = this.col__metaData.get(col);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// will set the row metadata if an object or idx in range,
|
||||
// discard if numerically out of range.
|
||||
// all row metadata survives rows being moved around.
|
||||
// this data is destroyed if the row is destroyed.
|
||||
SetRowMetaData(row, metaData)
|
||||
{
|
||||
row = this.#GetRow(row);
|
||||
if (row != undefined)
|
||||
{
|
||||
this.row__metaData.set(row, metaData);
|
||||
}
|
||||
}
|
||||
|
||||
// will get the row metadata if an object or idx in range, creating if needed,
|
||||
// undefined if numerically out of range.
|
||||
GetRowMetaData(row)
|
||||
{
|
||||
let retVal = undefined;
|
||||
|
||||
row = this.#GetRow(row);
|
||||
if (row != undefined)
|
||||
{
|
||||
if (this.row__metaData.has(row) == false)
|
||||
{
|
||||
this.row__metaData.set(row, {});
|
||||
}
|
||||
|
||||
retVal = this.row__metaData.get(row);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
GetDataTable()
|
||||
{
|
||||
return this.dataTable;
|
||||
}
|
||||
|
||||
GetHeaderList()
|
||||
{
|
||||
let retVal = [];
|
||||
|
||||
if (this.dataTable.length)
|
||||
{
|
||||
// prevent caller from modifying column names directly
|
||||
retVal = [... this.dataTable[0]];
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
GetColCount()
|
||||
{
|
||||
return this.GetHeaderList().length;
|
||||
}
|
||||
|
||||
// return the number of data rows
|
||||
Length()
|
||||
{
|
||||
let retVal = 0;
|
||||
|
||||
if (this.dataTable.length)
|
||||
{
|
||||
retVal = this.dataTable.length - 1;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
#CacheHeaderLocations()
|
||||
{
|
||||
if (this.dataTable && this.dataTable.length)
|
||||
{
|
||||
this.col__idx = new Map();
|
||||
|
||||
const headerRow = this.dataTable[0];
|
||||
|
||||
for (let i = 0; i < headerRow.length; ++i)
|
||||
{
|
||||
const col = headerRow[i];
|
||||
|
||||
this.col__idx.set(col, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Idx(col)
|
||||
{
|
||||
// undefined if no present
|
||||
return this.col__idx.get(col);
|
||||
}
|
||||
|
||||
// if given a row (array) object, return that object.
|
||||
// if given a numeric index, return the row in the table at that logical index.
|
||||
#GetRow(row)
|
||||
{
|
||||
if (row == undefined || row == null) { return undefined; }
|
||||
|
||||
let retVal = undefined;
|
||||
|
||||
if (typeof row == "object")
|
||||
{
|
||||
retVal = row;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (row + 1 < this.dataTable.length)
|
||||
{
|
||||
retVal = this.dataTable[row + 1];
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// if given a row (array) object, return the value in the specified column.
|
||||
// if given a numeric index, return the value in the specified column.
|
||||
Get(row, col)
|
||||
{
|
||||
let retVal = undefined;
|
||||
|
||||
row = this.#GetRow(row);
|
||||
if (row)
|
||||
{
|
||||
retVal = row[this.Idx(col)];
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// if given a row (array) object, return the value in the specified column.
|
||||
// if given a numeric index, return the value in the specified column.
|
||||
Set(row, col, val)
|
||||
{
|
||||
if (typeof row == "object")
|
||||
{
|
||||
row[this.Idx(col)] = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.dataTable[row + 1][this.Idx(col)] = val;
|
||||
}
|
||||
}
|
||||
|
||||
// idx of data, not including header
|
||||
DeleteRowList(idxList)
|
||||
{
|
||||
// put in descending order so we don't need to recalculate indices after each delete
|
||||
idxList.sort((a, b) => (a - b));
|
||||
idxList.reverse();
|
||||
|
||||
for (let idx of idxList)
|
||||
{
|
||||
this.DeleteRow(idx);
|
||||
}
|
||||
}
|
||||
|
||||
// idx of data, not including header
|
||||
DeleteRow(idx)
|
||||
{
|
||||
this.dataTable.splice(idx + 1, 1);
|
||||
}
|
||||
|
||||
// create a new row, with empty values.
|
||||
// row will have the same number of elements as the header.
|
||||
// the row is returned to the caller and is appropriate for use with
|
||||
// the Get() and Set() API.
|
||||
AddRow()
|
||||
{
|
||||
let row = new Array(this.GetColCount());
|
||||
|
||||
this.dataTable.push(row);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
RenameColumn(colOld, colNew)
|
||||
{
|
||||
this.dataTable[0][this.Idx(colOld)] = colNew;
|
||||
|
||||
this.#CacheHeaderLocations();
|
||||
|
||||
if (colOld != colNew)
|
||||
{
|
||||
this.col__metaData.set(colNew, this.col__metaData.get(colOld));
|
||||
this.col__metaData.delete(colOld);
|
||||
}
|
||||
}
|
||||
|
||||
DeleteColumn(col)
|
||||
{
|
||||
let idx = this.Idx(col);
|
||||
|
||||
if (idx != undefined)
|
||||
{
|
||||
for (let row of this.dataTable)
|
||||
{
|
||||
row.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.#CacheHeaderLocations();
|
||||
|
||||
this.col__metaData.delete(col);
|
||||
}
|
||||
|
||||
DeleteColumnList(colList)
|
||||
{
|
||||
for (let col of colList)
|
||||
{
|
||||
this.DeleteColumn(col);
|
||||
}
|
||||
}
|
||||
|
||||
DeleteEmptyColumns()
|
||||
{
|
||||
let colList = [];
|
||||
|
||||
for (let i = 0; i < this.dataTable[0].length; ++i)
|
||||
{
|
||||
let col = this.dataTable[0][i];
|
||||
|
||||
let allBlank = true;
|
||||
|
||||
for (let j = 1; j < this.dataTable.length; ++j)
|
||||
{
|
||||
let val = this.dataTable[j][i];
|
||||
|
||||
if (val != "" && val != null)
|
||||
{
|
||||
allBlank = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allBlank)
|
||||
{
|
||||
colList.push(col);
|
||||
}
|
||||
}
|
||||
|
||||
this.DeleteColumnList(colList);
|
||||
}
|
||||
|
||||
MakeDataTableFromRowList(rowList)
|
||||
{
|
||||
let dataTable = [[... this.dataTable[0]]];
|
||||
|
||||
for (let row of rowList)
|
||||
{
|
||||
dataTable.push([... row]);
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
}
|
||||
|
||||
MakeDataTableFromRow(row)
|
||||
{
|
||||
return this.MakeDataTableFromRowList([row]);
|
||||
}
|
||||
|
||||
Extract(headerList)
|
||||
{
|
||||
const headerRow = this.dataTable[0];
|
||||
let idxList = [];
|
||||
|
||||
for (const header of headerList)
|
||||
{
|
||||
let idx = headerRow.indexOf(header);
|
||||
|
||||
idxList.push(idx);
|
||||
}
|
||||
|
||||
// build new data table
|
||||
let dataTableNew = [];
|
||||
for (const row of this.dataTable)
|
||||
{
|
||||
let rowNew = [];
|
||||
|
||||
for (const idx of idxList)
|
||||
{
|
||||
rowNew.push(row[idx]);
|
||||
}
|
||||
|
||||
dataTableNew.push(rowNew);
|
||||
}
|
||||
|
||||
return dataTableNew;
|
||||
}
|
||||
|
||||
ExtractDataOnly(headerList)
|
||||
{
|
||||
let dataTable = this.Extract(headerList);
|
||||
|
||||
return dataTable.slice(1);
|
||||
}
|
||||
|
||||
DeepCopy()
|
||||
{
|
||||
return this.Extract(this.dataTable[0]);
|
||||
}
|
||||
|
||||
ForEach(fnEachRow, reverseOrder)
|
||||
{
|
||||
if (reverseOrder == undefined) { reverseOrder = false; }
|
||||
|
||||
if (reverseOrder == false)
|
||||
{
|
||||
for (let i = 1; i < this.dataTable.length; ++i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
fnEachRow(row, i - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (let i = this.dataTable.length - 1; i >= 1; --i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
fnEachRow(row, i - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppendGeneratedColumns(colHeaderList, fnEachRow, reverseOrder)
|
||||
{
|
||||
if (reverseOrder == undefined) { reverseOrder = false; }
|
||||
|
||||
this.dataTable[0].push(... colHeaderList);
|
||||
|
||||
if (reverseOrder == false)
|
||||
{
|
||||
for (let i = 1; i < this.dataTable.length; ++i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
row.push(... fnEachRow(row));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (let i = this.dataTable.length - 1; i >= 1; --i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
row.push(... fnEachRow(row));
|
||||
}
|
||||
}
|
||||
|
||||
this.#CacheHeaderLocations();
|
||||
}
|
||||
|
||||
PrependGeneratedColumns(colHeaderList, fnEachRow, reverseOrder)
|
||||
{
|
||||
if (reverseOrder == undefined) { reverseOrder = false; }
|
||||
|
||||
this.dataTable[0].unshift(... colHeaderList);
|
||||
|
||||
if (reverseOrder == false)
|
||||
{
|
||||
for (let i = 1; i < this.dataTable.length; ++i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
row.unshift(... fnEachRow(row));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (let i = this.dataTable.length - 1; i >= 1; --i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
row.unshift(... fnEachRow(row));
|
||||
}
|
||||
}
|
||||
|
||||
this.#CacheHeaderLocations();
|
||||
}
|
||||
|
||||
GenerateModifiedColumn(colHeaderList, fnEachRow, reverseOrder)
|
||||
{
|
||||
if (reverseOrder == undefined) { reverseOrder = false; }
|
||||
|
||||
let col = colHeaderList[0];
|
||||
let colIdx = this.Idx(col);
|
||||
|
||||
if (colIdx == undefined)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let rowList = [];
|
||||
|
||||
if (reverseOrder == false)
|
||||
{
|
||||
// build new values
|
||||
for (let i = 1; i < this.dataTable.length; ++i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
let rowNew = fnEachRow(row, i - 1);
|
||||
|
||||
rowList.push(rowNew[0]);
|
||||
}
|
||||
|
||||
// update table
|
||||
for (let i = 1; i < this.dataTable.length; ++i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
row[colIdx] = rowList[i - 1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (let i = this.dataTable.length - 1; i >= 1; --i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
let rowNew = fnEachRow(row, i - 1);
|
||||
|
||||
// row[this.Idx(col)] = rowNew[0];
|
||||
rowList.push(rowNew[0]);
|
||||
}
|
||||
|
||||
for (let i = this.dataTable.length - 1; i >= 1; --i)
|
||||
{
|
||||
let row = this.dataTable[i];
|
||||
|
||||
row[colIdx] = rowList[rowList.length - i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rearranges columns, leaving row objects intact (in-place operation).
|
||||
// specified columns which don't exist will start to exist, and undefined values
|
||||
// will be present in the cells.
|
||||
SetColumnOrder(colList)
|
||||
{
|
||||
let colListNewSet = new Set(colList);
|
||||
let colListOldSet = new Set(this.GetHeaderList());
|
||||
|
||||
// figure out which old columns are no longer present
|
||||
let colListDelSet = colListOldSet.difference(colListNewSet);
|
||||
|
||||
// modify each row, in place, to have only the column values
|
||||
for (let i = 1; i < this.dataTable.length; ++i)
|
||||
{
|
||||
let row = this.#GetRow(i - 1);
|
||||
|
||||
// build a new row of values.
|
||||
// undefined for any invalid columns.
|
||||
let rowNew = [];
|
||||
for (let col of colList)
|
||||
{
|
||||
rowNew.push(this.Get(row, col));
|
||||
}
|
||||
|
||||
// wipe out contents of existing row, but keep row object
|
||||
row.length = 0;
|
||||
|
||||
// add new values into the row, in order
|
||||
row.push(... rowNew);
|
||||
}
|
||||
|
||||
// update headers in place
|
||||
this.dataTable[0].length = 0;
|
||||
this.dataTable[0].push(... colList);
|
||||
|
||||
// delete metadata from destroyed columns
|
||||
for (let col of colList)
|
||||
{
|
||||
this.col__metaData.delete(col);
|
||||
}
|
||||
|
||||
// update column index
|
||||
this.#CacheHeaderLocations();
|
||||
}
|
||||
|
||||
// Will put specified columns in the front, in this order, if they exist.
|
||||
// Columns not specified will retain their order.
|
||||
PrioritizeColumnOrder(colHeaderList)
|
||||
{
|
||||
// get reference of existing columns
|
||||
let remainingColSet = new Set(this.GetHeaderList());
|
||||
|
||||
// get list of existing priority columns
|
||||
let priorityColSet = new Set();
|
||||
for (let col of colHeaderList)
|
||||
{
|
||||
if (remainingColSet.has(col))
|
||||
{
|
||||
remainingColSet.delete(col);
|
||||
priorityColSet.add(col);
|
||||
}
|
||||
}
|
||||
|
||||
// now we have two lists of columns:
|
||||
// - priorityColSet - existing columns in the order specified
|
||||
// - remainingColSet - every other existing column other than priority column set,
|
||||
// in original order
|
||||
|
||||
// now arrange columns
|
||||
let colHeaderListNew = [... priorityColSet.values(), ... remainingColSet.values()];
|
||||
|
||||
this.SetColumnOrder(colHeaderListNew);
|
||||
}
|
||||
|
||||
FillUp(col, defaultVal)
|
||||
{
|
||||
defaultVal = defaultVal | "";
|
||||
|
||||
let idx = this.Idx(col);
|
||||
|
||||
for (let i = this.dataTable.length - 1; i >= 1; --i)
|
||||
{
|
||||
const row = this.dataTable[i];
|
||||
|
||||
let val = row[idx];
|
||||
|
||||
if (val == null)
|
||||
{
|
||||
if (i == this.dataTable.length - 1)
|
||||
{
|
||||
val = defaultVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = this.dataTable[i + 1][idx];
|
||||
}
|
||||
|
||||
row[idx] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FillDown(col, defaultVal, reverseOrder)
|
||||
{
|
||||
defaultVal = defaultVal | "";
|
||||
|
||||
let idx = this.Idx(col);
|
||||
|
||||
for (let i = 1; i < this.dataTable.length; ++i)
|
||||
{
|
||||
const row = this.dataTable[i];
|
||||
|
||||
let val = row[idx];
|
||||
|
||||
if (val == null)
|
||||
{
|
||||
if (i == 1)
|
||||
{
|
||||
val = defaultVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = this.dataTable[i - 1][idx];
|
||||
}
|
||||
|
||||
row[idx] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GetDataForCol(col)
|
||||
{
|
||||
let valList = [];
|
||||
|
||||
if (this.dataTable && this.dataTable.length && this.Idx(col) != undefined)
|
||||
{
|
||||
for (let i = 0; i < this.Length(); ++i)
|
||||
{
|
||||
valList.push(this.Get(i, col));
|
||||
}
|
||||
}
|
||||
|
||||
return valList;
|
||||
}
|
||||
|
||||
Reverse()
|
||||
{
|
||||
// reverse the whole things
|
||||
this.dataTable.reverse();
|
||||
|
||||
// swap the header (now at the bottom) to the top
|
||||
this.dataTable.unshift(this.dataTable.pop());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user