diff --git a/.gitignore b/.gitignore index d3ab5af..f828735 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ package-lock.json pnpm-lock.yaml /docs +node_modules diff --git a/docz/docs/03-demos/01-math/09-danfojs.md b/docz/docs/03-demos/01-math/09-danfojs.md new file mode 100644 index 0000000..6280e2e --- /dev/null +++ b/docz/docs/03-demos/01-math/09-danfojs.md @@ -0,0 +1,307 @@ +--- +title: Sheets in DanfoJS +sidebar_label: DanfoJS +pagination_prev: demos/index +pagination_next: demos/frontend/index +--- + + + + + +[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing +data from spreadsheets. + +[DanfoJS](https://danfo.jsdata.org/) is a library for processing structured +data. It uses SheetJS under the hood for reading and writing spreadsheets. + +This demo covers details elided in the official DanfoJS documentation. + +:::note Tested Deployments + +This example was last tested on 2024 January 03 against DanfoJS 1.1.2. + +::: + +:::info Browser integration + +The live demos on this page include the DanfoJS browser bundle: + +```html + +``` + +There are known issues with the documentation generator. If a demo explicitly +prints "RELOAD THIS PAGE", please reload or refresh the page. + +::: + +## DataFrames and Worksheets + +The DanfoJS `DataFrame`[^1] represents two-dimensional tabular data. It is the +starting point for most DanfoJS data processing tasks. A `DataFrame` typically +corresponds to one SheetJS worksheet[^2]. + +
SpreadsheetDanfoJS DataFrame
+ +![`pres.xlsx` data](pathname:///pres.png) + + + +``` +╔════╤═══════════════╤═══════╗ +║ │ Name │ Index ║ +╟────┼───────────────┼───────╢ +║ 0 │ Bill Clinton │ 42 ║ +╟────┼───────────────┼───────╢ +║ 1 │ GeorgeW Bush │ 43 ║ +╟────┼───────────────┼───────╢ +║ 2 │ Barack Obama │ 44 ║ +╟────┼───────────────┼───────╢ +║ 3 │ Donald Trump │ 45 ║ +╟────┼───────────────┼───────╢ +║ 4 │ Joseph Biden │ 46 ║ +╚════╧═══════════════╧═══════╝ +``` + +
+ +## DanfoJS SheetJS Integration + +:::note pass + +The official documentation inconsistently names the library object `danfo` and +`dfd`. Since `dfd` is the browser global, the demos use the name `dfd`. + +::: + +Methods to read and write spreadsheets are attached to the main `dfd` object. + +### Importing DataFrames + +`readExcel`[^3] accepts two arguments: source data and options. + +The source data must be a `string` or `File` object. Strings are interpreted as +URLs while `File` objects are treated as data. + +_Selecting a Worksheet_ + +DanfoJS will generate a dataframe from one worksheet. The parser normally uses +the first worksheet. The `sheet` property of the options object controls the +selected worksheet. It is expected to be a zero-indexed number: + +```js +const first_sheet = await dfd.readExcel(url, {sheet: 0}); +const second_sheet = await dfd.readExcel(url, {sheet: 1}); +``` + +_More Parsing Options_ + +The `parsingOptions` property of the options argument is passed directly to the +SheetJS `read` method[^4]. + +For example, the `sheetRows` property controls how many rows are extracted from +larger worksheets. To pull 3 data rows, `sheetRows` must be set to 4: + +```js +const first_three_rows = await dfd.readExcel(url, { parsingOptions: { + // see https://docs.sheetjs.com/docs/api/parse-options for details + sheetRows: 4 +} }); +``` + +#### URL source + +The following example fetches a [test file](https://sheetjs.com/pres.xlsx), +parses with SheetJS and generates a DanfoJS dataframe. + +```jsx live +function DanfoReadExcelURL() { + const [text, setText] = React.useState(""); + React.useEffect(() => { (async() => { + if(typeof dfd === "undefined") return setText("RELOAD THIS PAGE!"); + const df = await dfd.readExcel("https://sheetjs.com/pres.xlsx"); + setText("" + df.head()); + })(); }, []); + return (
{text}
); +} +``` + +#### File source + +The following example uses a file input element. The "File API"[^5] section of +the "Local File Access" demo covers the browser API in more detail. + +```jsx live +function DanfoReadExcelFile() { + const [text, setText] = React.useState("Select a spreadsheet"); + + return (<>
{text}
{ + if(typeof dfd === "undefined") return setText("RELOAD THIS PAGE!"); + + /* get first file */ + const file = e.target.files[0]; + + /* create dataframe and pretty-print the first 10 rows */ + const df = await dfd.readExcel(file); + setText("" + df.head()); + }}/>); +} +``` + +### Exporting DataFrames + +`toExcel`[^6] accepts two arguments: dataframe and options. Under the hood, it +uses the SheetJS `writeFile` method[^7]. + +_Exported File Name_ + +The relevant property for the file name depends on the platform: + +| Platform | Property | +|:---------|:-----------| +| NodeJS | `filePath` | +| Browser | `fileName` | + +The exporter will deduce the desired file format from the file extension. + +_Worksheet Name_ + +The `sheetName` property specifies the name of the worksheet in the workbook: + +```js +dfd.toExcel(df, { + fileName: "test.xlsx", // generate `test.xlsx` + // highlight-next-line + sheetName: "Export" // The name of the worksheet will be "Export" +}); +``` + +:::caution pass + +The DanfoJS integration forces the `.xlsx` file extension. Exporting to other +file formats will require [low-level operations](#generating-files). + +::: + +_More Writing Options_ + +The `writingOptions` property of the options argument is passed directly to the +SheetJS `writeFile` method[^8]. + +For example, the `compression` property enables ZIP compression for XLSX and +other formats: + +```js +dfd.toExcel(df, {fileName: "export.xlsx", writingOptions: { + // see https://docs.sheetjs.com/docs/api/write-options for details + compression: true +}}); +``` + +#### Export to File + +The following example exports a sample dataframe to a XLSX spreadsheet. + +```jsx live +function DanfoToExcel() { + if(typeof dfd === "undefined") return (RELOAD THIS PAGE); + /* sample dataframe */ + const df = new dfd.DataFrame([{Sheet:1,JS:2},{Sheet:3,JS:4}]); + return ( <>
{"Data:\n"+df.head()}
); +} +``` + +## Low-Level Operations + +DanfoJS and SheetJS provide methods for processing arrays of objects. + +```mermaid +flowchart LR + ws((SheetJS\nWorksheet)) + aoo[[array of\nobjects]] + df[(DanfoJS\nDataFrame)] + ws --> |sheet_to_json\n\n| aoo + aoo --> |\njson_to_sheet| ws + df --> |\ndfd.toJSON| aoo + aoo --> |new DataFrame\n\n| df +``` + +### Creating DataFrames + +The `DataFrame` constructor[^9] creates `DataFrame` objects from arrays of +objects. Given a SheetJS worksheet object, the `sheet_to_json` method[^10] +generates compatible arrays of objects: + +```js +function ws_to_df(ws) { + const aoo = XLSX.utils.sheet_to_json(ws); + return new dfd.DataFrame(aoo); +} +``` + +### Generating Files + +`toJSON`[^11] accepts two arguments: dataframe and options. + +The `format` key of the `options` argument dictates the result layout. The +`column` layout generates an array of objects in row-major order. The SheetJS +`json_to_sheet`[^12] method can generate a worksheet object from the result: + +```js +function df_to_ws(df) { + const aoo = dfd.toJSON(df, { format: "column" }); + return XLSX.utils.json_to_sheet(aoo); +} +``` + +The SheetJS `book_new` method creates a workbook object from the worksheet[^13] +and the `writeFile` method[^14] will generate the file: + +```js +const ws = df_to_ws(df); +const wb = XLSX.utils.book_new(ws, "Export"); +XLSX.writeFile(wb, "SheetJSDanfoJS.xlsb", { compression: true }); +``` + +The following demo exports a sample dataframe to XLSB. This operation is not +supported by the DanfoJS `toExcel` method since that method enforces XLSX. + +```jsx live +function DanfoToXLS() { + if(typeof dfd === "undefined") return (RELOAD THIS PAGE); + /* sample dataframe */ + const df = new dfd.DataFrame([{Sheet:1,JS:2},{Sheet:3,JS:4}]); + return ( <>
{"Data:\n"+df.head()}
); +} +``` + +[^1]: See ["Dataframe"](https://danfo.jsdata.org/api-reference/dataframe) in the DanfoJS documentation +[^2]: See ["Sheet Objects"](/docs/csf/sheet) +[^3]: See ["danfo.readExcel"](https://danfo.jsdata.org/api-reference/input-output/danfo.read_excel) in the DanfoJS documentation. +[^4]: See ["Reading Files"](/docs/api/parse-options/#parsing-options) for the full list of parsing options. +[^5]: See ["File API" in "Local File Access"](/docs/demos/local/file#file-api) for more details. +[^6]: See ["danfo.toExcel"](https://danfo.jsdata.org/api-reference/input-output/danfo.to_excel) in the DanfoJS documentation. +[^7]: See [`writeFile` in "Writing Files"](/docs/api/write-options) +[^8]: See ["Writing Files"](/docs/api/write-options/#writing-options) for the full list of writing options. +[^9]: See ["Creating a DataFrame"](https://danfo.jsdata.org/api-reference/dataframe/creating-a-dataframe) in the DanfoJS documentation. +[^10]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) +[^11]: See ["danfo.toJSON"](https://danfo.jsdata.org/api-reference/input-output/danfo.to_json) in the DanfoJS documentation. +[^12]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input) +[^13]: See [`book_new` in "Utilities"](/docs/api/utilities/wb) +[^14]: See [`writeFile` in "Writing Files"](/docs/api/write-options) \ No newline at end of file diff --git a/docz/docs/03-demos/01-math/11-tensorflow.md b/docz/docs/03-demos/01-math/11-tensorflow.md new file mode 100644 index 0000000..f225108 --- /dev/null +++ b/docz/docs/03-demos/01-math/11-tensorflow.md @@ -0,0 +1,440 @@ +--- +title: Sheets in TensorFlow +sidebar_label: TensorFlow.js +pagination_prev: demos/index +pagination_next: demos/frontend/index +--- + + + + + +[TensorFlow.js](https://www.tensorflow.org/js) (shortened to TF.js) is a library +for machine learning in JavaScript. + +[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing +data from spreadsheets. + +This demo uses TensorFlow.js and SheetJS to process data in spreadsheets. We'll +explore how to load spreadsheet data into TF.js datasets and how to export +results back to spreadsheets. + +- ["CSV Data Interchange"](#csv-data-interchange) uses SheetJS to process sheets + and generate CSV data that TF.js can import. + +- ["JSON Data Interchange"](#json-data-interchange) uses SheetJS to process + sheets and generate rows of objects that can be post-processed. + +:::info pass + +Live code blocks in this page use the TF.js `4.14.0` standalone build. + +For use in web frameworks, the `@tensorflow/tfjs` module should be used. + +For use in NodeJS, the native bindings module is `@tensorflow/tfjs-node`. + +::: + +:::note Tested Deployments + +Each browser demo was tested in the following environments: + +| Browser | TF.js version | Date | +|:------------|:--------------|:-----------| +| Chrome 119 | `4.14.0` | 2023-12-09 | +| Safari 16.6 | `4.14.0` | 2023-12-09 | + +::: + +## CSV Data Interchange + +`tf.data.csv`[^1] generates a Dataset from CSV data. The function expects a URL. + +:::note pass + +When this demo was last tested, there was no direct method to pass a CSV string +to the underlying parser. + +::: + +Fortunately blob URLs are supported. + +```mermaid +flowchart LR + ws((SheetJS\nWorksheet)) + csv(CSV\nstring) + url{{Data\nURL}} + dataset[(TF.js\nDataset)] + ws --> |sheet_to_csv\nSheetJS| csv + csv --> |JavaScript\nAPIs| url + url --> |tf.data.csv\nTensorFlow.js| dataset +``` + +The SheetJS `sheet_to_csv` method[^2] generates a CSV string from a worksheet +object. Using standard JavaScript techniques, a blob URL can be constructed: + +```js +function worksheet_to_csv_url(worksheet) { + /* generate CSV */ + const csv = XLSX.utils.sheet_to_csv(worksheet); + + /* CSV -> Uint8Array -> Blob */ + const u8 = new TextEncoder().encode(csv); + const blob = new Blob([u8], { type: "text/csv" }); + + /* generate a blob URL */ + return URL.createObjectURL(blob); +} +``` + +### CSV Demo + +This demo shows a simple model fitting using the "cars" dataset from TensorFlow. +The [sample XLS file](https://sheetjs.com/data/cd.xls) contains the data. The +data processing mirrors the official "Making Predictions from 2D Data" demo[^3]. + +```mermaid +flowchart LR + file[(Remote\nFile)] + subgraph SheetJS Operations + ab[(Data\nBytes)] + wb(((SheetJS\nWorkbook))) + ws((SheetJS\nWorksheet)) + csv(CSV\nstring) + end + subgraph TensorFlow.js Operations + url{{Data\nURL}} + dataset[(TF.js\nDataset)] + results((Results)) + end + file --> |fetch\n\n| ab + ab --> |read\n\n| wb + wb --> |select\nsheet| ws + ws --> |sheet_to_csv\n\n| csv + csv --> |JS\nAPI| url + url --> |tf.data.csv\nTF.js| dataset + dataset --> |fitDataset\nTF.js| results +``` + +The demo builds a model for predicting MPG from Horsepower data. It: + +- fetches +- parses the data with the SheetJS `read`[^4] method +- selects the first worksheet[^5] and converts to CSV using `sheet_to_csv`[^6] +- generates a blob URL from the CSV text +- generates a TF.js dataset with `tf.data.csv`[^7] and selects data columns +- builds a model and trains with `fitDataset`[^8] +- predicts MPG from a set of sample inputs and displays results in a table + +
Live Demo (click to show) + +:::caution pass + +In some test runs, the results did not make sense given the underlying data. +The dependent and independent variables are expected to be anti-correlated. + +**This is a known issue in TF.js and affects the official demos** + +::: + +:::caution pass + +If the live demo shows a message + +``` +ReferenceError: tf is not defined +``` + +please refresh the page. This is a known bug in the documentation generator. + +::: + +```jsx live +function SheetJSToTFJSCSV() { + const [output, setOutput] = React.useState(""); + const [results, setResults] = React.useState([]); + const [disabled, setDisabled] = React.useState(false); + + function worksheet_to_csv_url(worksheet) { + /* generate CSV */ + const csv = XLSX.utils.sheet_to_csv(worksheet); + + /* CSV -> Uint8Array -> Blob */ + const u8 = new TextEncoder().encode(csv); + const blob = new Blob([u8], { type: "text/csv" }); + + /* generate a blob URL */ + return URL.createObjectURL(blob); + } + + const doit = React.useCallback(async () => { + setResults([]); setOutput(""); setDisabled(true); + try { + /* fetch file */ + const f = await fetch("https://sheetjs.com/data/cd.xls"); + const ab = await f.arrayBuffer(); + /* parse file and get first worksheet */ + const wb = XLSX.read(ab); + const ws = wb.Sheets[wb.SheetNames[0]]; + + /* generate blob URL */ + const url = worksheet_to_csv_url(ws); + + /* feed to tf.js */ + const dataset = tf.data.csv(url, { + hasHeader: true, + configuredColumnsOnly: true, + columnConfigs:{ + "Horsepower": {required: false, default: 0}, + "Miles_per_Gallon":{required: false, default: 0, isLabel:true} + } + }); + + /* pre-process data */ + let flat = dataset + .map(({xs,ys}) =>({xs: Object.values(xs), ys: Object.values(ys)})) + .filter(({xs,ys}) => [...xs,...ys].every(v => v>0)); + + /* normalize manually :( */ + let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; + await flat.forEachAsync(({xs, ys}) => { + minX = Math.min(minX, xs[0]); maxX = Math.max(maxX, xs[0]); + minY = Math.min(minY, ys[0]); maxY = Math.max(maxY, ys[0]); + }); + flat = flat.map(({xs, ys}) => ({xs:xs.map(v => (v-minX)/(maxX - minX)),ys:ys.map(v => (v-minY)/(maxY-minY))})); + flat = flat.batch(32); + + /* build and train model */ + const model = tf.sequential(); + model.add(tf.layers.dense({inputShape: [1], units: 1})); + model.compile({ optimizer: tf.train.sgd(0.000001), loss: 'meanSquaredError' }); + await model.fitDataset(flat, { epochs: 100, callbacks: { onEpochEnd: async (epoch, logs) => { + setOutput(`${epoch}:${logs.loss}`); + }}}); + + /* predict values */ + const inp = tf.linspace(0, 1, 9); + const pred = model.predict(inp); + const xs = await inp.dataSync(), ys = await pred.dataSync(); + setResults(Array.from(xs).map((x, i) => [ x * (maxX - minX) + minX, ys[i] * (maxY - minY) + minY ])); + setOutput(""); + + } catch(e) { setOutput(`ERROR: ${String(e)}`); } finally { setDisabled(false);} + }); + return ( <> +
+ {output &&
{output}
|| <>} + {results.length && + {results.map((r,i) => )} +
HorsepowerMPG
{r[0]}{r[1].toFixed(2)}
|| <>} + ); +} +``` + +
+ +## JS Array Interchange + +[The official Linear Regression tutorial](https://www.tensorflow.org/js/tutorials/training/linear_regression) +loads data from a JSON file: + +```json +[ + { + "Name": "chevrolet chevelle malibu", + "Miles_per_Gallon": 18, + "Cylinders": 8, + "Displacement": 307, + "Horsepower": 130, + "Weight_in_lbs": 3504, + "Acceleration": 12, + "Year": "1970-01-01", + "Origin": "USA" + }, + // ... +] +``` + +In real use cases, data is stored in [spreadsheets](https://sheetjs.com/data/cd.xls) + +![cd.xls screenshot](pathname:///files/cd.png) + +Following the tutorial, the data fetching method can be adapted to handle arrays +of objects, such as those generated by the SheetJS `sheet_to_json` method[^9]. + +Differences from the official example are highlighted below: + +```js +/** + * Get the car data reduced to just the variables we are interested + * and cleaned of missing data. + */ +async function getData() { + // highlight-start + /* fetch file */ + const carsDataResponse = await fetch('https://sheetjs.com/data/cd.xls'); + /* get file data (ArrayBuffer) */ + const carsDataAB = await carsDataResponse.arrayBuffer(); + /* parse */ + const carsDataWB = XLSX.read(carsDataAB); + /* get first worksheet */ + const carsDataWS = carsDataWB.Sheets[carsDataWB.SheetNames[0]]; + /* generate array of JS objects */ + const carsData = XLSX.utils.sheet_to_json(carsDataWS); + // highlight-end + const cleaned = carsData.map(car => ({ + mpg: car.Miles_per_Gallon, + horsepower: car.Horsepower, + })) + .filter(car => (car.mpg != null && car.horsepower != null)); + + return cleaned; +} +``` + +## Low-Level Operations + +### Data Transposition + +A typical dataset in a spreadsheet will start with one header row and represent +each data record in its own row. For example, the Iris dataset might look like + +![Iris dataset](pathname:///files/iris.png) + +The SheetJS `sheet_to_json` method[^10] will translate worksheet objects into an +array of row objects: + +```js +var aoo = [ + {"sepal length": 5.1, "sepal width": 3.5, ...}, + {"sepal length": 4.9, "sepal width": 3, ...}, + ... +]; +``` + +TF.js and other libraries tend to operate on individual columns, equivalent to: + +```js +var sepal_lengths = [5.1, 4.9, ...]; +var sepal_widths = [3.5, 3, ...]; +``` + +When a `tensor2d` can be exported, it will look different from the spreadsheet: + +```js +var data_set_2d = [ + [5.1, 4.9, ...], + [3.5, 3, ...], + ... +] +``` + +This is the transpose of how people use spreadsheets! + +### Exporting Datasets to a Worksheet + +The `aoa_to_sheet` method[^11] can generate a worksheet from an array of arrays. +ML libraries typically provide APIs to pull an array of arrays, but it will be +transposed. To export multiple data sets, the data should be transposed: + +```js +/* assuming data is an array of typed arrays */ +var aoa = []; +for(var i = 0; i < data.length; ++i) { + for(var j = 0; j < data[i].length; ++j) { + if(!aoa[j]) aoa[j] = []; + aoa[j][i] = data[i][j]; + } +} +/* aoa can be directly converted to a worksheet object */ +var ws = XLSX.utils.aoa_to_sheet(aoa); +``` + +### Importing Data from a Spreadsheet + +`sheet_to_json` with the option `header:1`[^12] will generate a row-major array +of arrays that can be transposed. However, it is more efficient to walk the +sheet manually: + +```js +/* find worksheet range */ +var range = XLSX.utils.decode_range(ws['!ref']); +var out = [] +/* walk the columns */ +for(var C = range.s.c; C <= range.e.c; ++C) { + /* create the typed array */ + var ta = new Float32Array(range.e.r - range.s.r + 1); + /* walk the rows */ + for(var R = range.s.r; R <= range.e.r; ++R) { + /* find the cell, skip it if the cell isn't numeric or boolean */ + var cell = ws["!data"] ? (ws["!data"][R]||[])[C] : ws[XLSX.utils.encode_cell({r:R, c:C})]; + if(!cell || cell.t != 'n' && cell.t != 'b') continue; + /* assign to the typed array */ + ta[R - range.s.r] = cell.v; + } + out.push(ta); +} +``` + +If the data set has a header row, the loop can be adjusted to skip those rows. + +### TF.js Tensors + +A single `Array#map` can pull individual named fields from the result, which +can be used to construct TensorFlow.js tensor objects: + +```js +const aoo = XLSX.utils.sheet_to_json(worksheet); +const lengths = aoo.map(row => row["sepal length"]); +const tensor = tf.tensor1d(lengths); +``` + +`tf.Tensor` objects can be directly transposed using `transpose`: + +```js +var aoo = XLSX.utils.sheet_to_json(worksheet); +// "x" and "y" are the fields we want to pull from the data +var data = aoo.map(row => ([row["x"], row["y"]])); + +// create a tensor representing two column datasets +var tensor = tf.tensor2d(data).transpose(); + +// individual columns can be accessed +var col1 = tensor.slice([0,0], [1,tensor.shape[1]]).flatten(); +var col2 = tensor.slice([1,0], [1,tensor.shape[1]]).flatten(); +``` + +For exporting, `stack` can be used to collapse the columns into a linear array: + +```js +/* pull data into a Float32Array */ +var result = tf.stack([col1, col2]).transpose(); +var shape = tensor.shape; +var f32 = tensor.dataSync(); + +/* construct an array of arrays of the data in spreadsheet order */ +var aoa = []; +for(var j = 0; j < shape[0]; ++j) { + aoa[j] = []; + for(var i = 0; i < shape[1]; ++i) aoa[j][i] = f32[j * shape[1] + i]; +} + +/* add headers to the top */ +aoa.unshift(["x", "y"]); + +/* generate worksheet */ +var worksheet = XLSX.utils.aoa_to_sheet(aoa); +``` + +[^1]: See [`tf.data.csv`](https://js.tensorflow.org/api/latest/#data.csv) in the TensorFlow.js documentation +[^2]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) +[^3]: The ["Making Predictions from 2D Data" example](https://codelabs.developers.google.com/codelabs/tfjs-training-regression/) uses a hosted JSON file. The [sample XLS file](https://sheetjs.com/data/cd.xls) includes the same data. +[^4]: See [`read` in "Reading Files"](/docs/api/parse-options) +[^5]: See ["Workbook Object"](/docs/csf/book) +[^6]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) +[^7]: See [`tf.data.csv`](https://js.tensorflow.org/api/latest/#data.csv) in the TensorFlow.js documentation +[^8]: See [`tf.LayersModel.fitDataset`](https://js.tensorflow.org/api/latest/#tf.LayersModel.fitDataset) in the TensorFlow.js documentation +[^9]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) +[^10]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) +[^11]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) +[^12]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) diff --git a/docz/docs/03-demos/01-math/_category_.json b/docz/docs/03-demos/01-math/_category_.json new file mode 100644 index 0000000..c68ab02 --- /dev/null +++ b/docz/docs/03-demos/01-math/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Math and Statistics", + "position": 1 +} \ No newline at end of file diff --git a/docz/docs/03-demos/01-math/index.md b/docz/docs/03-demos/01-math/index.md new file mode 100644 index 0000000..3627862 --- /dev/null +++ b/docz/docs/03-demos/01-math/index.md @@ -0,0 +1,412 @@ +--- +title: Math and Statistics +pagination_prev: demos/index +pagination_next: demos/frontend/index +--- + +import DocCardList from '@theme/DocCardList'; +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + +With full support for IEEE754 doubles and singles, JavaScript is an excellent +language for mathematics and statistical analysis. It has also proven to be a +viable platform for machine learning. + +## Demos + +Demos for various libraries are included in separate pages: + + + + +## Typed Arrays + +Modern JavaScript math and statistics libraries typically use `Float64Array` or +`Float32Array` objects to efficiently store data variables. + +
Technical details (click to show) + +Under the hood, `ArrayBuffer` objects represent raw binary data. "Typed arrays" +such as `Float64Array` and `Float32Array` are objects designed for efficient +interpretation and mutation of `ArrayBuffer` data. + + +:::note pass + +`ArrayBuffer` are roughly analogous to heap-allocated memory. Typed arrays +behave like typed pointers. + +**JavaScript** + +```js +const buf = new ArrayBuffer(16); +const dbl = new Float64Array(buf); +dbl[1] = 3.14159; +const u8 = new Uint8Array(buf); +for(let i = 0; i < 8; ++i) + console.log(u8[i+8]); +``` + +**Equivalent C** + +```c +void *const buf = malloc(16); +double *const dbl = (double *)buf; +dbl[1] = 3.14159; +uint8_t *const u8 = (uint8_t *)buf; +for(uint8_t i = 0; i < 8; ++i) + printf("%u\n", u8[i+8]); +``` + +::: + +
+ +### Reading from Sheets + +Each typed array class has a `from` static method for converting data into a +typed array. `Float64Array.from` returns a `double` typed array (8 bytes per +value) and `Float32Array.from` generates a `float` typed array (4 bytes). + +```js +const column_f32 = Float32Array.from(arr); // 4-byte floats +const column_f64 = Float64Array.from(arr); // 8-byte doubles +``` + +:::info pass + +Values in the array will be coerced to the relevant data type. Unsupported +entries will be converted to quiet `NaN` values. + +::: + +#### Extracting Worksheet Data + +The SheetJS `sheet_to_json`[^1] method with the option `header: 1`[^2] generates +an array of arrays from a worksheet object. The result is in row-major order: + +```js +const aoa = XLSX.utils.sheet_to_json(worksheet, {header: 1}); +``` + +#### Categorical Variables + +Dichotomous variables are commonly represented as spreadsheet `TRUE` or `FALSE`. +The SheetJS `sheet_to_json` method will translate these values to `true` and +`false`. Typed array methods will interpret values as `1` and `0` respectively. + +Polychotomous variables must be manually mapped to numeric values. For example, +using the Iris dataset: + +![Iris dataset](pathname:///typedarray/iris.png) + +```js +[ + ["sepal length", "sepal width", "petal length", "petal width", "class"], + [5.1, 3.5, 1.4, 0.2, "Iris-setosa"], + [4.9, 3, 1.4, 0.2, "Iris-setosa"], +] +``` + +Column E (`class`) is a polychotomous variable and must be manually translated: + +```js +const aoa = XLSX.utils.sheet_to_json(worksheet, {header: 1}); + +/* index_to_class will be needed to recover the values later */ +const index_to_class = []; + +/* map from class name to number */ +const class_to_index = new Map(); + +/* loop over the data */ +for(let R = 1; R < aoa.length; ++R) { + /* Column E = SheetJS row 4 */ + const category = aoa[R][4]; + const val = class_to_index.get(category); + if(val == null) { + /* assign a new index */ + class_to_index.set(category, index_to_class.length); + aoa[R][4] = index_to_class.length; + index_to_class.push(category); + } else aoa[R][4] = val; +} +``` + +
Live Demo (click to show) + +This example fetches and parses [`iris.xlsx`](pathname:///typedarray/iris.xlsx). +The first worksheet is processed and the new data and mapping are printed. + +```jsx live +function SheetJSPolychotomy() { + const [cat, setCat] = React.useState([]); + const [aoa, setAoA] = React.useState([]); + + React.useEffect(() => { (async() => { + const ab = await (await fetch("/typedarray/iris.xlsx")).arrayBuffer(); + const wb = XLSX.read(ab); + const aoa = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], {header:1}); + + const index_to_class = []; + const class_to_index = new Map(); + for(let R = 1; R < aoa.length; ++R) { + const category = aoa[R][4]; + const val = class_to_index.get(category); + if(val == null) { + class_to_index.set(category, index_to_class.length); + aoa[R][4] = index_to_class.length; + index_to_class.push(category); + } else aoa[R][4] = val; + } + + /* display every 25 rows, skipping the header row */ + setAoA(aoa.filter((_, i) => (i % 25) == 1)); + setCat(index_to_class); + })(); }, []); + + return ( <> + Mapping
+ + {cat.map((name, i) => ())} +
IndexName
{i}{name}
+ Sample Data
+ {"ABCDE".split("").map(c => ())} + {aoa.map(row => ({row.map(col => ())}))} +
{c}
{col}
+ + ); +} +``` + +
+ +#### One Variable per Column + +It is common to store datasets where each row represents an observation and each +column represents a variable: + +![Iris dataset](pathname:///typedarray/iris.png) + +```js +var aoa = [ + ["sepal length", "sepal width", "petal length", "petal width", "class"], + [5.1, 3.5, 1.4, 0.2, "Iris-setosa"], + [4.9, 3, 1.4, 0.2, "Iris-setosa"], +] +``` + +An array `map` operation can pull data from an individual column. After mapping, +a `slice` can remove the header label. For example, the following snippet pulls +column C ("petal length") into a `Float64Array`: + +```js +const C = XLSX.utils.decode_col("C"); // Column "C" = SheetJS index 2 +const petal_length = Float64Array.from(aoa.map(row => row[C]).slice(1)); +``` + +#### One Variable per Row + +Some datasets are stored in tables where each row represents a variable and each +column represents an observation: + +
JavaScriptSpreadsheet
+ +```js +var aoa = [ + ["sepal length", 5.1, 4.9], + ["sepal width", 3.5, 3], + ["petal length", 1.4, 1.4], + ["petal width", 0.2, 0.2], + ["class", "setosa", "setosa"] +] +``` + + + +![Single column of data](pathname:///typedarray/iristr.png) + +
+ + +From the row-major array of arrays, each entry of the outer array is a row. + +Many sheets include header columns. The `slice` method can remove the header. +After removing the header, `Float64Array.from` can generate a typed array. For +example, this snippet pulls row 3 ("petal length") into a `Float64Array`: + +```js +const petal_length = Float64Array.from(aoa[2].slice(1)); +``` + +### Writing to Sheets + +The SheetJS `aoa_to_sheet`[^1] method can generate a worksheet from an array of +arrays. Similarly, `sheet_add_aoa`[^2] can add an array of arrays of data into +an existing worksheet object. The `origin` option[^3] controls where data will +be written in the worksheet. + +Neither method understands typed arrays, so data columns must be converted to +arrays of arrays. + +#### One Variable per Row + +A single typed array can be converted to a pure JS array with `Array.from`: + +```js +const arr = Array.from(column); +``` + +An array of arrays can be created from the array: + +```js +const aoa = [ + arr // this array is the first element of the array literal +]; +``` + +`aoa_to_sheet` and `sheet_add_aoa` treat this as one row. By default, data will +be written to cells in the first row of the worksheet. + +Titles can be added to data rows with an `unshift` operation, but it is more +efficient to build up the worksheet with `aoa_to_sheet`: + +```js +/* sample data */ +const data = new Float64Array([54337.95, 3.14159, 2.718281828]); +const title = "Values"; + +/* convert sample data to array */ +const arr = Array.from(data); +/* create worksheet from title (array of arrays) */ +const ws = XLSX.utils.aoa_to_sheet([ [ "Values" ] ]); +/* add data starting at B1 */ +XLSX.utils.sheet_add_aoa(ws, [ arr ], { origin: "B1" }); +``` + +![Typed Array to single row with title](pathname:///typedarray/ta-row.png) + +
Live Demo (click to hide) + +In this example, two typed arrays are exported. `aoa_to_sheet` creates the +worksheet and `sheet_add_aoa` will add the data to the sheet. + +```jsx live +function SheetJSeriesToRows() { return (); } +``` + +
+ +#### One Variable per Column + +A single typed array can be converted to a pure JS array with `Array.from`. For +columns, each value should be individually wrapped in an array: + +
JavaScriptSpreadsheet
+ +```js +var data = [ + [54337.95], + [3.14159], + [2.718281828] +]; +``` + + + +![Single column of data](pathname:///typedarray/col.png) + +
+ +`Array.from` takes a second argument. If it is a function, the function will be +called on each element and the value will be used in place of the original value +(in effect, mapping over the data). To generate a data column, each element must +be wrapped in an array literal: + +```js +var arr = Array.from(column, (value) => ([ value ])); +``` + +`aoa_to_sheet` and `sheet_add_aoa` treat this as rows with one column of data +per row. By default, data will be written to cells in column "A". + +Titles can be added to data columns with an `unshift` operation, but it is more +efficient to build up the worksheet with `aoa_to_sheet`: + +```js +/* sample data */ +const data = new Float64Array([54337.95, 3.14159, 2.718281828]); +const title = "Values"; + +/* convert sample data to array */ +const arr = Array.from(data, (value) => ([value])); +/* create worksheet from title (array of arrays) */ +const ws = XLSX.utils.aoa_to_sheet([ [ "Values" ] ]); +/* add data starting at B1 */ +XLSX.utils.sheet_add_aoa(ws, arr, { origin: "A2" }); +``` + +![Typed Array to single column with title](pathname:///typedarray/ta-col.png) + +
Live Demo (click to hide) + +In this example, two typed arrays are exported. `aoa_to_sheet` creates the +worksheet and `sheet_add_aoa` will add the data to the sheet. + +```jsx live +function SheetJSeriesToCols() { return (); } +``` + +
+ +[^1]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) +[^2]: See [`sheet_add_aoa` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) +[^3]: See [the `origin` option of `sheet_add_aoa` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) diff --git a/docz/docs/03-demos/02-frontend/19-bundler/03-vitejs.md b/docz/docs/03-demos/02-frontend/19-bundler/03-vitejs.md index 9261cbb..91ae83d 100644 --- a/docz/docs/03-demos/02-frontend/19-bundler/03-vitejs.md +++ b/docz/docs/03-demos/02-frontend/19-bundler/03-vitejs.md @@ -43,6 +43,7 @@ This demo was tested in the following environments: |:---------|:-----------| | `5.0.5` | 2023-12-04 | | `4.5.0` | 2023-12-04 | +| `3.2.7` | 2023-12-05 | ::: diff --git a/docz/docs/03-demos/02-frontend/index.md b/docz/docs/03-demos/02-frontend/index.md index bc65198..8f1f510 100644 --- a/docz/docs/03-demos/02-frontend/index.md +++ b/docz/docs/03-demos/02-frontend/index.md @@ -1,6 +1,6 @@ --- title: Web Frameworks -pagination_prev: demos/index +pagination_prev: demos/math/index pagination_next: demos/grid/index --- diff --git a/docz/docs/03-demos/03-net/01-network/index.mdx b/docz/docs/03-demos/03-net/01-network/index.mdx index 562b4b5..5ca0d7b 100644 --- a/docz/docs/03-demos/03-net/01-network/index.mdx +++ b/docz/docs/03-demos/03-net/01-network/index.mdx @@ -11,6 +11,11 @@ pagination_next: demos/net/upload/index import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; +`XMLHttpRequest` and `fetch` browser APIs enable binary data transfer between +web browser clients and web servers. Since this library works in web browsers, +server conversion work can be offloaded to the client! This demo shows a few +common scenarios involving browser APIs and popular wrapper libraries. + :::info pass This demo focuses on downloading files. Other demos cover other HTTP use cases: @@ -20,11 +25,6 @@ This demo focuses on downloading files. Other demos cover other HTTP use cases: ::: -`XMLHttpRequest` and `fetch` browser APIs enable binary data transfer between -web browser clients and web servers. Since this library works in web browsers, -server conversion work can be offloaded to the client! This demo shows a few -common scenarios involving browser APIs and popular wrapper libraries. - :::caution Third-Party Hosts and Binary Data Third-party cloud platforms such as AWS may corrupt raw binary downloads by @@ -45,7 +45,20 @@ The APIs generally have a way to control the interpretation of the downloaded data. The `arraybuffer` response type usually forces the data to be presented as an `ArrayBuffer` which can be parsed directly with the SheetJS `read` method[^1]. -For example, with `fetch`: +The following example shows the data flow using `fetch` to download files: + +```mermaid +flowchart LR + server[(Remote\nFile)] + response(Response\nobject) + subgraph SheetJS operations + ab(XLSX Data\nArrayBuffer) + wb(((SheetJS\nWorkbook))) + end + server --> |`fetch`\nGET request| response + response --> |`arrayBuffer`\n\n| ab + ab --> |`read`\n\n| wb +``` ```js /* download data into an ArrayBuffer object */ @@ -69,7 +82,8 @@ contents match the first worksheet. The table is generated using the SheetJS ### XMLHttpRequest For downloading data, the `arraybuffer` response type generates an `ArrayBuffer` -that can be viewed as an `Uint8Array` and fed to `XLSX.read` using `array` type: +that can be viewed as an `Uint8Array` and fed to the SheetJS `read` method. For +legacy browsers, the option `type: "array"` should be specified: ```js /* set up an async GET request */ @@ -122,7 +136,7 @@ function SheetJSXHRDL() { ### fetch For downloading data, `Response#arrayBuffer` resolves to an `ArrayBuffer` that -can be converted to `Uint8Array` and passed to `XLSX.read`: +can be converted to `Uint8Array` and passed to the SheetJS `read` method: ```js fetch(url).then(function(res) { @@ -215,13 +229,14 @@ $.ajax({ ### Wrapper Libraries Before `fetch` shipped with browsers, there were various wrapper libraries to -simplify `XMLHttpRequest`. Due to limitations with `fetch`, these libraries -are still relevant. +simplify `XMLHttpRequest`. Due to limitations with `fetch`, these libraries are +still relevant. #### axios [`axios`](https://axios-http.com/) presents a Promise based interface. Setting -`responseType` to `arraybuffer` ensures the return type is an ArrayBuffer: +`responseType` to `arraybuffer` ensures the return type is an ArrayBuffer. The +`data` property of the result can be passed to the SheetJS `read` method: ```js async function workbook_dl_axios(url) { @@ -491,7 +506,7 @@ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz request #### axios When the `responseType` is `"arraybuffer"`, `axios` actually captures the data -in a NodeJS Buffer. `XLSX.read` will transparently handle Buffers: +in a NodeJS Buffer. The SheetJS `read` method handles NodeJS Buffer objects: ```js title="SheetJSAxios.js" const XLSX = require("xlsx"), axios = require("axios"); @@ -548,6 +563,8 @@ Other demos show network operations in special platforms: - [React Native "Fetching Remote Data"](/docs/demos/mobile/reactnative#fetching-remote-data) - [NativeScript "Fetching Remote Files"](/docs/demos/mobile/nativescript#fetching-remote-files) +- [AngularJS "Remote Files"](/docs/demos/frontend/angularjs#remote-files) +- [Dojo Toolkit "Parsing Remote Files"](/docs/demos/frontend/dojo#parsing-remote-files) [^1]: See [`read` in "Reading Files"](/docs/api/parse-options) [^2]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output) diff --git a/docz/docs/03-demos/17-mobile/01-reactnative.md b/docz/docs/03-demos/17-mobile/01-reactnative.md index 5cd49de..ddb4a3a 100644 --- a/docz/docs/03-demos/17-mobile/01-reactnative.md +++ b/docz/docs/03-demos/17-mobile/01-reactnative.md @@ -219,17 +219,17 @@ This demo was tested in the following environments: | OS | Type | Device | RN | Date | |:-----------|:-----|:--------------------|:---------|:-----------| -| Android 34 | Sim | Pixel 3a | `0.72.7` | 2023-12-04 | -| iOS 17.0.1 | Sim | iPhone 15 Pro Max | `0.72.7` | 2023-12-04 | -| Android 29 | Real | NVIDIA Shield | `0.72.7` | 2023-12-04 | -| iOS 15.1 | Real | iPad Pro | `0.72.7` | 2023-12-04 | +| Android 34 | Sim | Pixel 3a | `0.73.1` | 2023-12-21 | +| iOS 17.2 | Sim | iPhone 15 Pro Max | `0.73.1` | 2023-12-21 | +| Android 29 | Real | NVIDIA Shield | `0.73.1` | 2023-12-21 | +| iOS 15.1 | Real | iPad Pro | `0.73.1` | 2023-12-21 | ::: 1) Create project: ```bash -npx -y react-native@0.72.7 init SheetJSRNFetch --version="0.72.7" +npx -y react-native@0.73.1 init SheetJSRNFetch --version="0.73.1" ``` 2) Install shared dependencies: @@ -249,16 +249,16 @@ curl -LO https://docs.sheetjs.com/reactnative/App.tsx **Android Testing** -4) Install or switch to Java 11[^6] +4) Install or switch to Java 17[^6] :::note pass When the demo was last tested on macOS, `java -version` displayed the following: ``` -openjdk version "11.0.21" 2023-10-17 LTS -OpenJDK Runtime Environment Zulu11.68+17-CA (build 11.0.21+9-LTS) -OpenJDK 64-Bit Server VM Zulu11.68+17-CA (build 11.0.21+9-LTS, mixed mode) +openjdk version "17.0.9" 2023-10-17 +OpenJDK Runtime Environment Temurin-17.0.9+9 (build 17.0.9+9) +OpenJDK 64-Bit Server VM Temurin-17.0.9+9 (build 17.0.9+9, mixed mode) ``` ::: @@ -274,8 +274,14 @@ npx react-native run-android If the initial launch fails with an error referencing the emulator, manually start the emulator and try again. -Gradle errors typically stem from a Java version mismatch. Run `java -version` -and verify that the Java major version is 11. +Gradle errors typically stem from a Java version mismatch: + +``` +> Failed to apply plugin 'com.android.internal.application'. + > Android Gradle plugin requires Java 17 to run. You are currently using Java 11. +``` + +This error can be resolved by installing and switching to the requested version. ::: @@ -299,7 +305,9 @@ tapping "Import data from a spreadsheet", verify that the app shows new data: :::warning pass -iOS testing requires macOS. It does not work on Windows or Linux. +**iOS testing can only be performed on Apple hardware running macOS!** + +Xcode and iOS simulators are not available on Windows or Linux. ::: @@ -349,7 +357,7 @@ npx react-native run-android 13) Close any Android / iOS emulators. -14) Enable developer code signing certificates[^7] +14) Enable developer code signing certificates[^7]. 15) Install `ios-deploy` through Homebrew: @@ -363,6 +371,67 @@ brew install ios-deploy npx react-native run-ios ``` +:::caution pass + +When this demo was last tested, the build failed with the following error: + +``` +PhaseScriptExecution failed with a nonzero exit code +``` + +This was due to an error in the `react-native` package. The script +`node_modules/react-native/scripts/react-native-xcode.sh` must be edited. + +Near the top of the script, there will be a `set` statement: + +```bash title="node_modules/react-native/scripts/react-native-xcode.sh" +# Print commands before executing them (useful for troubleshooting) +# highlight-next-line +set -x -e +DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH +``` + +The `-e` argument must be removed: + +```bash title="node_modules/react-native/scripts/react-native-xcode.sh (edit line)" +# Print commands before executing them (useful for troubleshooting) +# highlight-next-line +set -x +DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH +``` + +::: + +:::info pass + +By default, React Native generates applications that exclusively target iPhone. +On a physical iPad, a pixellated iPhone app will be run. + +The "targeted device families" setting must be changed to support iPad: + +A) Open the Xcode workspace: + +```bash +open ./ios/SheetJSRNFetch.xcworkspace +``` + +B) Select the project in the left sidebar: + +![Select the project](pathname:///reactnative/xcode-select-project.png) + +C) Select the "SheetJSRNFetch" target in the sidebar. + +![Settings](pathname:///reactnative/xcode-targets.png) + +D) Select the "Build Settings" tab in the main area. + +E) In the search bar below "Build Settings", type "tar" + +F) Look for the "Targeted Device Families" row. Change the corresponding value +to "iPhone, iPad". + +::: + ## Local Files :::warning pass @@ -987,6 +1056,6 @@ npx xlsx-cli /tmp/sheetjsw.xlsx [^3]: See ["Array Output" in "Utility Functions"](/docs/api/utilities/array#array-output) [^4]: See ["Array of Arrays Input" in "Utility Functions"](/docs/api/utilities/array#array-of-arrays-input) [^5]: React-Native commit [`5b597b5`](https://github.com/facebook/react-native/commit/5b597b5ff94953accc635ed3090186baeecb3873) added the final piece required for `fetch` support. It landed in version `0.72.0-rc.1` and is available in official releases starting from `0.72.0`. -[^6]: When the demo was last tested, the Zulu11 distribution of Java 11 was installed through the macOS Brew package manager. [Direct downloads are available at `azul.com`](https://www.azul.com/downloads/?version=java-11-lts&package=jdk#zulu) +[^6]: When the demo was last tested, the Temurin distribution of Java 17 was installed through the macOS Brew package manager by running `brew install temurin17`. [Direct downloads are available at `adoptium.net`](https://adoptium.net/temurin/releases/?version=17) [^7]: See ["Running On Device"](https://reactnative.dev/docs/running-on-device) in the React Native documentation [^8]: Follow the ["React Native CLI Quickstart"](https://reactnative.dev/docs/environment-setup) for Android (and iOS, if applicable) \ No newline at end of file diff --git a/docz/docs/03-demos/17-mobile/05-capacitor.md b/docz/docs/03-demos/17-mobile/05-capacitor.md index 8511c83..233702f 100644 --- a/docz/docs/03-demos/17-mobile/05-capacitor.md +++ b/docz/docs/03-demos/17-mobile/05-capacitor.md @@ -178,7 +178,7 @@ npx cap init sheetjs-cap com.sheetjs.cap --web-dir=dist npm run build ``` -:::note +:::note pass If prompted to create an Ionic account, type `N` and press Enter. diff --git a/docz/docs/03-demos/19-desktop/01-electron.md b/docz/docs/03-demos/19-desktop/01-electron.md index fe9e3ce..9ce26a2 100644 --- a/docz/docs/03-demos/19-desktop/01-electron.md +++ b/docz/docs/03-demos/19-desktop/01-electron.md @@ -184,11 +184,11 @@ This demo was tested in the following environments: | OS and Version | Architecture | Electron | Date | |:---------------|:-------------|:---------|:-----------| -| macOS 13.5.1 | `darwin-x64` | `26.1.0` | 2023-09-03 | +| macOS 13.5.1 | `darwin-x64` | `27.1.3` | 2023-12-09 | | macOS 14.1.2 | `darwin-arm` | `27.1.3` | 2023-12-01 | -| Windows 10 | `win10-x64` | `26.1.0` | 2023-09-03 | +| Windows 10 | `win10-x64` | `27.1.3` | 2023-12-09 | | Windows 11 | `win11-arm` | `27.1.3` | 2023-12-01 | -| Linux (HoloOS) | `linux-x64` | `27.0.0` | 2023-10-11 | +| Linux (HoloOS) | `linux-x64` | `27.1.3` | 2023-12-09 | | Linux (Debian) | `linux-arm` | `27.1.3` | 2023-12-01 | ::: @@ -247,7 +247,7 @@ The app will show. npm run make ``` -This will create a package in the `out\make` folder. +This will create a package in the `out\make` folder and a standalone binary. :::caution pass @@ -266,11 +266,13 @@ The program will run on ARM64 Windows. 5) Download [the test file `pres.numbers`](https://sheetjs.com/pres.numbers) -6) Re-launch the application in the test environment: +6) Launch the generated application: -```bash -npx -y electron . -``` +| Architecture | Command | +|:-------------|:--------------------------------------------------------------| +| `darwin-x64` | `open ./out/sheetjs-electron-darwin-x64/sheetjs-electron.app` | +| `win10-x64` | `.\out\sheetjs-electron-win32-x64\sheetjs-electron.exe` | +| `linux-x64` | `./out/sheetjs-electron-linux-x64/sheetjs-electron` | #### Electron API @@ -284,7 +286,7 @@ to write to `Untitled.xls` in the Downloads folder. :::note pass -During the most recent Linux ARM64 test, the dialog did not have a default name. +In some tests, the dialog did not have a default name. If there is no default name, enter `Untitled.xls` and click "Save". @@ -335,4 +337,4 @@ call is required to enable Developer Tools in the window. ::: -[^1]: See ["Makers"](https://www.electronforge.io/config/makers) in the Electron Forge documentation. On Linux, the demo generates `rpm` and `deb` distributables. \ No newline at end of file +[^1]: See ["Makers"](https://www.electronforge.io/config/makers) in the Electron Forge documentation. On Linux, the demo generates `rpm` and `deb` distributables. On Arch Linux and the Steam Deck, `sudo pacman -Syu rpm-tools dpkg fakeroot` installed required packages. \ No newline at end of file diff --git a/docz/docs/03-demos/19-desktop/02-nwjs.md b/docz/docs/03-demos/19-desktop/02-nwjs.md index 74ad237..eb59c52 100644 --- a/docz/docs/03-demos/19-desktop/02-nwjs.md +++ b/docz/docs/03-demos/19-desktop/02-nwjs.md @@ -115,9 +115,9 @@ This demo was tested in the following environments: |:---------------|:-------------|:---------|:-----------| | macOS 13.5.2 | `darwin-x64` | `0.78.1` | 2023-09-27 | | macOS 14.1.2 | `darwin-arm` | `0.82.0` | 2023-12-01 | -| Windows 10 | `win10-x64` | `0.78.1` | 2023-09-27 | +| Windows 10 | `win10-x64` | `0.82.0` | 2023-12-09 | | Windows 11 | `win11-arm` | `0.82.0` | 2023-12-01 | -| Linux (HoloOS) | `linux-x64` | `0.78.1` | 2023-10-11 | +| Linux (HoloOS) | `linux-x64` | `0.82.0` | 2023-12-07 | There is no official Linux ARM64 release. The community release[^1] was tested and verified on 2023-09-27. diff --git a/docz/docs/03-demos/19-desktop/03-wails.md b/docz/docs/03-demos/19-desktop/03-wails.md index 588632b..2275757 100644 --- a/docz/docs/03-demos/19-desktop/03-wails.md +++ b/docz/docs/03-demos/19-desktop/03-wails.md @@ -299,7 +299,7 @@ This demo was tested in the following environments: |:---------------|:-------------|:---------|:-----------| | macOS 13.6 | `darwin-x64` | `v2.6.0` | 2023-11-05 | | macOS 14.1.2 | `darwin-arm` | `v2.6.0` | 2023-12-01 | -| Windows 10 | `win10-x64` | `v2.5.1` | 2023-08-25 | +| Windows 10 | `win10-x64` | `v2.6.0` | 2023-12-09 | | Windows 11 | `win11-arm` | `v2.6.0` | 2023-12-01 | | Linux (HoloOS) | `linux-x64` | `v2.6.0` | 2023-10-11 | | Linux (Debian) | `linux-arm` | `v2.6.0` | 2023-12-01 | diff --git a/docz/docs/03-demos/19-desktop/05-neutralino.md b/docz/docs/03-demos/19-desktop/05-neutralino.md index f0ec717..105920d 100644 --- a/docz/docs/03-demos/19-desktop/05-neutralino.md +++ b/docz/docs/03-demos/19-desktop/05-neutralino.md @@ -192,11 +192,11 @@ This demo was tested in the following environments: | OS and Version | Architecture | Server | Client | Date | |:---------------|:-------------|:----------|:----------|:-----------| -| macOS 13.5.1 | `darwin-x64` | `v4.13.0` | `v3.11.0` | 2023-08-26 | +| macOS 13.5.1 | `darwin-x64` | `v4.14.1` | `v3.12.0` | 2023-12-13 | | macOS 14.0 | `darwin-arm` | `v4.14.1` | `v3.12.0` | 2023-10-18 | -| Windows 10 | `win10-x64` | `v4.13.0` | `v3.11.0` | 2023-08-26 | +| Windows 10 | `win10-x64` | `v4.14.1` | `v3.12.0` | 2023-12-09 | | Windows 11 | `win11-arm` | `v4.14.1` | `v3.12.0` | 2023-12-01 | -| Linux (HoloOS) | `linux-x64` | `v4.14.1` | `v3.12.0` | 2023-10-11 | +| Linux (HoloOS) | `linux-x64` | `v4.14.1` | `v3.12.0` | 2023-12-09 | | Linux (Debian) | `linux-arm` | `v4.14.1` | `v3.12.0` | 2023-12-01 | ::: diff --git a/docz/docs/03-demos/19-desktop/09-cli.md b/docz/docs/03-demos/19-desktop/09-cli.md index 3132199..57aea31 100644 --- a/docz/docs/03-demos/19-desktop/09-cli.md +++ b/docz/docs/03-demos/19-desktop/09-cli.md @@ -55,7 +55,7 @@ This demo was tested in the following deployments: | `darwin-arm` | `4.0.0-rc.2` | `18.18.0` | 2023-12-01 | | `win10-x64` | `4.0.0-rc.2` | `14.15.3` | 2023-10-09 | | `win11-arm` | `4.0.0-rc.2` | `20.10.0` | 2023-12-01 | -| `linux-x64` | `4.0.0-rc.2` | `14.15.3` | 2023-10-11 | +| `linux-x64` | `4.0.0-rc.2` | `14.15.3` | 2023-12-07 | | `linux-arm` | `4.0.0-rc.2` | `20.10.0` | 2023-12-01 | @@ -67,7 +67,7 @@ This demo was tested in the following deployments: | `darwin-arm` | `5.8.1` | `18.5.0` | 2023-12-01 | | `win10-x64` | `5.8.1` | `18.5.0` | 2023-10-09 | | `win11-arm` | `5.8.1` | `18.5.0` | 2023-12-01 | -| `linux-x64` | `5.8.1` | `18.5.0` | 2023-10-11 | +| `linux-x64` | `5.8.1` | `18.5.0` | 2023-12-07 | | `linux-arm` | `5.8.1` | `18.5.0` | 2023-12-01 | @@ -78,7 +78,7 @@ This demo was tested in the following deployments: | `darwin-x64` | `2.1.2` | `20.8.0` | 2023-10-12 | | `darwin-arm` | `2.3.0` | `21.3.0` | 2023-12-01 | | `win10-x64` | `2.1.2` | `16.20.2` | 2023-10-09 | -| `linux-x64` | `2.1.2` | `20.8.0` | 2023-10-11 | +| `linux-x64` | `2.3.0` | `21.4.0` | 2023-12-07 | | `linux-arm` | `2.3.0` | `21.3.0` | 2023-12-01 | diff --git a/docz/docs/03-demos/30-cloud/02-netsuite.md b/docz/docs/03-demos/30-cloud/02-netsuite.md index 249fabc..8f8de36 100644 --- a/docz/docs/03-demos/30-cloud/02-netsuite.md +++ b/docz/docs/03-demos/30-cloud/02-netsuite.md @@ -24,10 +24,10 @@ This demo was verified by NetSuite consultants in the following deployments: | `@NScriptType` | `@NApiVersion` | Date | |:----------------|:---------------|:-----------| -| ScheduledScript | 2.1 | 2023-08-18 | +| ScheduledScript | 2.1 | 2023-12-13 | | Restlet | 2.1 | 2023-10-05 | -| Suitelet | 2.1 | 2023-10-27 | -| MapReduceScript | 2.1 | 2023-11-16 | +| Suitelet | 2.1 | 2023-12-22 | +| MapReduceScript | 2.1 | 2023-12-07 | ::: diff --git a/docz/docs/03-demos/30-cloud/18-github.md b/docz/docs/03-demos/30-cloud/18-github.md index 7d51826..51b3090 100644 --- a/docz/docs/03-demos/30-cloud/18-github.md +++ b/docz/docs/03-demos/30-cloud/18-github.md @@ -1,5 +1,5 @@ --- -title: Data Processing in GitHub +title: Flat Data Processing in GitHub sidebar_label: GitHub pagination_prev: demos/local/index pagination_next: demos/extensions/index @@ -8,15 +8,12 @@ pagination_next: demos/extensions/index import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; -Many official data releases by governments and organizations include XLSX or -XLS files. Unfortunately some data sources do not retain older versions. - [Git](https://git-scm.com/) is a popular system for organizing a historical record of text files and changes. Git can also store and track spreadsheets. -[GitHub](https://github.com/) hosts Git repositories and provides infrastructure -to run scheduled tasks. ["Flat Data"](https://octo.github.com/projects/flat-data) -explores storing and comparing versions of structured CSV and JSON data. +GitHub hosts Git repositories and provides infrastructure to execute workflows. +The ["Flat Data" project](https://octo.github.com/projects/flat-data) explores +storing and comparing versions of structured data using GitHub infrastructure. [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing data from spreadsheets. @@ -29,7 +26,7 @@ changes over time. ["Excel to CSV"](https://octo.github.com/projects/flat-data#:~:text=Excel) is an official example that pulls XLSX workbooks from an endpoint and uses SheetJS to -parse the workbooks and generate CSV files: +parse the workbooks and generate CSV files. ::: @@ -38,8 +35,8 @@ The following diagram depicts the data dance: ```mermaid sequenceDiagram autonumber - participant R as GH Repo - participant A as GH Action + participant R as GitHub Repo + participant A as GitHub Action participant S as Data Source loop Regular Interval (cron) A->>R: clone repo @@ -56,18 +53,30 @@ sequenceDiagram ## Flat Data +Many official data releases by governments and organizations include XLSX or +XLS files. Unfortunately some data sources do not retain older versions. + +Software developers typically use version control systems such as Git to track +changes in source code. + +The "Flat Data" project starts from the idea that the same version control +systems can be used to track changes in data. Third-party data sources can be +snapshotted at regular intervals and stored in Git repositories. + +### Components + As a project from the company, the entire lifecycle uses GitHub offerings: -- GitHub offers free hosting for Git repositories -- GitHub Actions[^1] infrastructure runs tasks at regular intervals -- `githubocto/flat`[^2] library helps fetch data and automate post-processing -- `flat-postprocessing`[^3] library provides post-processing helper functions -- "Flat Viewer"[^4] displays structured CSV and JSON data from Git repositories +- GitHub.com[^1] offers free hosting for Git repositories +- GitHub Actions[^2] infrastructure runs tasks at regular intervals +- `githubocto/flat`[^3] library helps fetch data and automate post-processing +- `flat-postprocessing`[^4] library provides post-processing helper functions +- "Flat Viewer"[^5] displays structured CSV and JSON data from Git repositories :::caution pass A GitHub account is required. When the demo was last tested, "GitHub Free" -accounts had no Actions usage limits for public repositories[^5]. +accounts had no Actions usage limits for public repositories[^6]. Private GitHub repositories can be used for processing data, but the Flat Viewer will not be able to display private data. @@ -143,12 +152,12 @@ for more details. The first argument to the post-processing script is the filename. -The SheetJS `readFile` method[^6] will read the file and generate a SheetJS -workbook object[^7]. After extracting the first worksheet, `sheet_to_csv`[^8] +The SheetJS `readFile` method[^7] will read the file and generate a SheetJS +workbook object[^8]. After extracting the first worksheet, `sheet_to_csv`[^9] generates a CSV string. After generating a CSV string, the string should be written to the filesystem -using `Deno.writeFileSync`[^9]. By convention, the CSV should preserve the file +using `Deno.writeFileSync`[^10]. By convention, the CSV should preserve the file name stem and replace the extension with `.csv`: {`\ @@ -316,12 +325,13 @@ jobs: The column chart in the Index column is a histogram. -[^1]: See ["GitHub Actions documentation"](https://docs.github.com/en/actions) -[^2]: See [`githubocto/flat`](https://github.com/githubocto/flat) repo on GitHub. -[^3]: See [`githubocto/flat-postprocessing`](https://github.com/githubocto/flat-postprocessing) repo on GitHub. -[^4]: The hosted version is available at -[^5]: See ["About billing for GitHub Actions"](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions) in the GitHub documentation. -[^6]: See [`readFile` in "Reading Files"](/docs/api/parse-options) -[^7]: See ["Workbook Object"](/docs/csf/book) -[^8]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) -[^9]: See [`Deno.writeFileSync`](https://deno.land/api?s=Deno.writeFileSync) in the Deno Runtime APIs documentation. \ No newline at end of file +[^1]: See ["Repositories documentation"](https://docs.github.com/en/repositories) in the GitHub documentation. +[^2]: See ["GitHub Actions documentation"](https://docs.github.com/en/actions) in the GitHub documentation. +[^3]: See [`githubocto/flat`](https://github.com/githubocto/flat) repo on GitHub. +[^4]: See [`githubocto/flat-postprocessing`](https://github.com/githubocto/flat-postprocessing) repo on GitHub. +[^5]: The hosted version is available at +[^6]: See ["About billing for GitHub Actions"](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions) in the GitHub documentation. +[^7]: See [`readFile` in "Reading Files"](/docs/api/parse-options) +[^8]: See ["Workbook Object"](/docs/csf/book) +[^9]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) +[^10]: See [`Deno.writeFileSync`](https://deno.land/api?s=Deno.writeFileSync) in the Deno Runtime APIs documentation. \ No newline at end of file diff --git a/docz/docs/03-demos/32-extensions/12-maple.md b/docz/docs/03-demos/32-extensions/12-maple.md index f63f2f7..32ee9f6 100644 --- a/docz/docs/03-demos/32-extensions/12-maple.md +++ b/docz/docs/03-demos/32-extensions/12-maple.md @@ -28,7 +28,7 @@ flowchart LR nfile --> |ExcelTools\nImport|data ``` -:::note +:::note Tested Deployments This demo was last tested by SheetJS users on 2023 October 3 in Maple 2023. diff --git a/docz/docs/03-demos/37-bigdata/03-ml.mdx b/docz/docs/03-demos/37-bigdata/03-ml.mdx deleted file mode 100644 index ea67544..0000000 --- a/docz/docs/03-demos/37-bigdata/03-ml.mdx +++ /dev/null @@ -1,347 +0,0 @@ ---- -title: Typed Arrays and ML -pagination_prev: demos/extensions/index -pagination_next: demos/engines/index -sidebar_custom_props: - summary: Parse and serialize Uint8Array data from TensorFlow ---- - - - - - -Machine learning libraries in JS typically use "Typed Arrays". Typed Arrays are -not JS Arrays! With some data wrangling, translating between SheetJS worksheets -and typed arrays is straightforward. - -This demo covers conversions between worksheets and Typed Arrays for use with -TensorFlow.js and other ML libraries. - -:::info pass - -Live code blocks in this page load the standalone build from version `4.10.0`. - -For use in web frameworks, the `@tensorflow/tfjs` module should be used. - -For use in NodeJS, the native bindings module is `@tensorflow/tfjs-node`. - -::: - -:::note pass - -Each browser demo was tested in the following environments: - -| Browser | Date | TF.js version | -|:------------|:-----------|:--------------| -| Chrome 116 | 2023-09-02 | `4.10.0` | -| Safari 16.6 | 2023-09-02 | `4.10.0` | -| Brave 1.57 | 2023-09-02 | `4.10.0` | - -::: - -## CSV Data Interchange - -`tf.data.csv` generates a Dataset from CSV data. The function expects a URL. -Fortunately blob URLs are supported, making data import straightforward: - -```js -function worksheet_to_csv_url(worksheet) { - /* generate CSV */ - const csv = XLSX.utils.sheet_to_csv(worksheet); - - /* CSV -> Uint8Array -> Blob */ - const u8 = new TextEncoder().encode(csv); - const blob = new Blob([u8], { type: "text/csv" }); - - /* generate a blob URL */ - return URL.createObjectURL(blob); -} -``` - -
TF CSV Demo using XLSX files (click to show) - -This demo shows a simple model fitting using the "Boston Housing" dataset. The -[sample XLSX file](https://sheetjs.com/data/bht.xlsx) contains the data. - -The demo first fetches the XLSX file and generates CSV text. A blob URL is -generated and fed to `tf.data.csv`. The rest of the demo follows the official -example in the TensorFlow documentation. - -:::caution pass - -If the live demo shows a message - -``` -ReferenceError: tf is not defined -``` - -please refresh the page. This is a known bug in the documentation generator. - -::: - -```jsx live -function SheetJSToTFJSCSV() { - const [output, setOutput] = React.useState(""); - const doit = React.useCallback(async () => { - /* fetch file */ - const f = await fetch("https://sheetjs.com/data/bht.xlsx"); - const ab = await f.arrayBuffer(); - /* parse file and get first worksheet */ - const wb = XLSX.read(ab); - const ws = wb.Sheets[wb.SheetNames[0]]; - - /* generate CSV */ - const csv = XLSX.utils.sheet_to_csv(ws); - - /* generate blob URL */ - const u8 = new TextEncoder().encode(csv); - const blob = new Blob([u8], {type: "text/csv"}); - const url = URL.createObjectURL(blob); - - /* feed to tfjs */ - const dataset = tf.data.csv(url, {columnConfigs:{"medv":{isLabel:true}}}); - - /* this part mirrors the tf.data.csv docs */ - const flat = dataset.map(({xs,ys}) => ({xs: Object.values(xs), ys: Object.values(ys)})).batch(10); - const model = tf.sequential(); - model.add(tf.layers.dense({inputShape: [(await dataset.columnNames()).length - 1], units: 1})); - model.compile({ optimizer: tf.train.sgd(0.000001), loss: 'meanSquaredError' }); - let base = output; - await model.fitDataset(flat, { epochs: 10, callbacks: { onEpochEnd: async (epoch, logs) => { - setOutput(base += "\n" + epoch + ":" + logs.loss); - }}}); - model.summary(); - }); - return (
-    
-    {output}
-  
); -} -``` - -
- -In the other direction, `XLSX.read` will readily parse CSV exports. - -## JS Array Interchange - -[The official Linear Regression tutorial](https://www.tensorflow.org/js/tutorials/training/linear_regression) -loads data from a JSON file: - -```json -[ - { - "Name": "chevrolet chevelle malibu", - "Miles_per_Gallon": 18, - "Cylinders": 8, - "Displacement": 307, - "Horsepower": 130, - "Weight_in_lbs": 3504, - "Acceleration": 12, - "Year": "1970-01-01", - "Origin": "USA" - }, - { - "Name": "buick skylark 320", - "Miles_per_Gallon": 15, - "Cylinders": 8, - "Displacement": 350, - "Horsepower": 165, - "Weight_in_lbs": 3693, - "Acceleration": 11.5, - "Year": "1970-01-01", - "Origin": "USA" - }, - // ... -] -``` - -In real use cases, data is stored in [spreadsheets](https://sheetjs.com/data/cd.xls) - -![cd.xls screenshot](pathname:///files/cd.png) - -Following the tutorial, the data fetching method is easily adapted. Differences -from the official example are highlighted below: - -```js -/** - * Get the car data reduced to just the variables we are interested - * and cleaned of missing data. - */ -async function getData() { - // highlight-start - /* fetch file */ - const carsDataResponse = await fetch('https://sheetjs.com/data/cd.xls'); - /* get file data (ArrayBuffer) */ - const carsDataAB = await carsDataResponse.arrayBuffer(); - /* parse */ - const carsDataWB = XLSX.read(carsDataAB); - /* get first worksheet */ - const carsDataWS = carsDataWB.Sheets[carsDataWB.SheetNames[0]]; - /* generate array of JS objects */ - const carsData = XLSX.utils.sheet_to_json(carsDataWS); - // highlight-end - const cleaned = carsData.map(car => ({ - mpg: car.Miles_per_Gallon, - horsepower: car.Horsepower, - })) - .filter(car => (car.mpg != null && car.horsepower != null)); - - return cleaned; -} -``` - -## Low-Level Operations - -:::caution pass - -While it is more efficient to use low-level operations, JS or CSV interchange -is strongly recommended when possible. - -::: - -### Data Transposition - -A typical dataset in a spreadsheet will start with one header row and represent -each data record in its own row. For example, the Iris dataset might look like - -![Iris dataset](pathname:///files/iris.png) - -`XLSX.utils.sheet_to_json` will translate this into an array of row objects: - -```js -var aoo = [ - {"sepal length": 5.1, "sepal width": 3.5, ...}, - {"sepal length": 4.9, "sepal width": 3, ...}, - ... -]; -``` - -TF.js and other libraries tend to operate on individual columns, equivalent to: - -```js -var sepal_lengths = [5.1, 4.9, ...]; -var sepal_widths = [3.5, 3, ...]; -``` - -When a `tensor2d` can be exported, it will look different from the spreadsheet: - -```js -var data_set_2d = [ - [5.1, 4.9, ...], - [3.5, 3, ...], - ... -] -``` - -This is the transpose of how people use spreadsheets! - -#### Typed Arrays and Columns - -A single typed array can be converted to a pure JS array with `Array.from`: - -```js -var column = Array.from(dataset_typedarray); -``` - -Similarly, `Float32Array.from` generates a typed array from a normal array: - -```js -var dataset = Float32Array.from(column); -``` - -### Exporting Datasets to a Worksheet - -`XLSX.utils.aoa_to_sheet` can generate a worksheet from an array of arrays. -ML libraries typically provide APIs to pull an array of arrays, but it will -be transposed. To export multiple data sets, manually "transpose" the data: - -```js -/* assuming data is an array of typed arrays */ -var aoa = []; -for(var i = 0; i < data.length; ++i) { - for(var j = 0; j < data[i].length; ++j) { - if(!aoa[j]) aoa[j] = []; - aoa[j][i] = data[i][j]; - } -} -/* aoa can be directly converted to a worksheet object */ -var ws = XLSX.utils.aoa_to_sheet(aoa); -``` - -### Importing Data from a Spreadsheet - -`sheet_to_json` with the option `header:1` will generate a row-major array of -arrays that can be transposed. However, it is more efficient to walk the sheet -manually: - -```js -/* find worksheet range */ -var range = XLSX.utils.decode_range(ws['!ref']); -var out = [] -/* walk the columns */ -for(var C = range.s.c; C <= range.e.c; ++C) { - /* create the typed array */ - var ta = new Float32Array(range.e.r - range.s.r + 1); - /* walk the rows */ - for(var R = range.s.r; R <= range.e.r; ++R) { - /* find the cell, skip it if the cell isn't numeric or boolean */ - var cell = ws[XLSX.utils.encode_cell({r:R, c:C})]; - if(!cell || cell.t != 'n' && cell.t != 'b') continue; - /* assign to the typed array */ - ta[R - range.s.r] = cell.v; - } - out.push(ta); -} -``` - -If the data set has a header row, the loop can be adjusted to skip those rows. - -### TF.js Tensors - -A single `Array#map` can pull individual named fields from the result, which -can be used to construct TensorFlow.js tensor objects: - -```js -const aoo = XLSX.utils.sheet_to_json(worksheet); -const lengths = aoo.map(row => row["sepal length"]); -const tensor = tf.tensor1d(lengths); -``` - -`tf.Tensor` objects can be directly transposed using `transpose`: - -```js -var aoo = XLSX.utils.sheet_to_json(worksheet); -// "x" and "y" are the fields we want to pull from the data -var data = aoo.map(row => ([row["x"], row["y"]])); - -// create a tensor representing two column datasets -var tensor = tf.tensor2d(data).transpose(); - -// individual columns can be accessed -var col1 = tensor.slice([0,0], [1,tensor.shape[1]]).flatten(); -var col2 = tensor.slice([1,0], [1,tensor.shape[1]]).flatten(); -``` - -For exporting, `stack` can be used to collapse the columns into a linear array: - -```js -/* pull data into a Float32Array */ -var result = tf.stack([col1, col2]).transpose(); -var shape = tensor.shape; -var f32 = tensor.dataSync(); - -/* construct an array of arrays of the data in spreadsheet order */ -var aoa = []; -for(var j = 0; j < shape[0]; ++j) { - aoa[j] = []; - for(var i = 0; i < shape[1]; ++i) aoa[j][i] = f32[j * shape[1] + i]; -} - -/* add headers to the top */ -aoa.unshift(["x", "y"]); - -/* generate worksheet */ -var worksheet = XLSX.utils.aoa_to_sheet(aoa); -``` - diff --git a/docz/docs/03-demos/42-engines/01-duktape.md b/docz/docs/03-demos/42-engines/01-duktape.md index 8efcc89..9f7f512 100644 --- a/docz/docs/03-demos/42-engines/01-duktape.md +++ b/docz/docs/03-demos/42-engines/01-duktape.md @@ -130,11 +130,11 @@ This demo was tested in the following deployments: | Architecture | Version | Date | |:-------------|:--------|:-----------| -| `darwin-x64` | `2.7.0` | 2023-10-26 | +| `darwin-x64` | `2.7.0` | 2023-12-05 | | `darwin-arm` | `2.7.0` | 2023-10-18 | | `win10-x64` | `2.7.0` | 2023-10-27 | | `win11-arm` | `2.7.0` | 2023-12-01 | -| `linux-x64` | `2.7.0` | 2023-10-11 | +| `linux-x64` | `2.7.0` | 2023-12-07 | | `linux-arm` | `2.7.0` | 2023-12-01 | ::: diff --git a/docz/docs/03-demos/42-engines/03-rhino.md b/docz/docs/03-demos/42-engines/03-rhino.md index eeb0e9c..7edbee9 100644 --- a/docz/docs/03-demos/42-engines/03-rhino.md +++ b/docz/docs/03-demos/42-engines/03-rhino.md @@ -17,8 +17,18 @@ result is a JAR. :::caution pass -Rhino does not support Uint8Array, so certain formats like NUMBERS cannot be -parsed or written from Rhino JS code! +Rhino does not support Uint8Array, so NUMBERS files cannot be read or written. + +::: + +:::note Tested Deployments + +This demo was tested in the following deployments: + +| OpenJDK | Rhino | Date | +|:--------|:---------|:-----------| +| 21.0.1 | `1.7.14` | 2023-12-05 | +| 1.8.0 | `1.7.14` | 2023-12-05 | ::: @@ -118,12 +128,6 @@ This string can be loaded into the JS engine and processed: ## Complete Example -:::note - -This demo was tested on 2023-10-26 against Rhino 1.7.14. - -::: - 0) Ensure Java is installed. 1) Create a folder for the project: diff --git a/docz/docs/03-demos/42-engines/08-quickjs.md b/docz/docs/03-demos/42-engines/08-quickjs.md index a571123..b20635d 100644 --- a/docz/docs/03-demos/42-engines/08-quickjs.md +++ b/docz/docs/03-demos/42-engines/08-quickjs.md @@ -27,7 +27,7 @@ command-line tool for reading data from files. :::note pass Many QuickJS functions are not documented. The explanation was verified against -the latest release (commit `2788d71`). +the latest release (commit `daa35bc`). ::: @@ -262,14 +262,14 @@ This demo was tested in the following deployments: | Architecture | Git Commit | Date | |:-------------|:-----------|:-----------| -| `darwin-x64` | `2788d71` | 2023-10-26 | +| `darwin-x64` | `daa35bc` | 2023-12-09 | | `darwin-arm` | `2788d71` | 2023-10-18 | -| `win10-x64` | `2788d71` | 2023-10-09 | +| `win10-x64` | `daa35bc` | 2023-12-09 | | `win11-arm` | `03cc5ec` | 2023-12-01 | -| `linux-x64` | `2788d71` | 2023-10-11 | +| `linux-x64` | `03cc5ec` | 2023-12-07 | | `linux-arm` | `03cc5ec` | 2023-12-01 | -When the demo was tested, commit `03cc5ec` corresponded to the latest commit. +When the demo was tested, commit `daa35bc` corresponded to the latest release. ::: @@ -285,7 +285,7 @@ tests were run entirely within Windows Subsystem for Linux. ```bash git clone https://github.com/bellard/quickjs cd quickjs -git checkout 03cc5ec +git checkout daa35bc make cd .. ``` @@ -342,10 +342,10 @@ This demo was tested in the following environments: | Git Commit | Date | |:-----------|:-----------| -| `03cc5ec` | 2023-12-01 | -| `2788d71` | 2023-10-11 | +| `daa35bc` | 2023-12-09 | +| `2788d71` | 2023-12-09 | -When the demo was tested, commit `03cc5ec` corresponded to the latest commit. +When the demo was tested, commit `daa35bc` corresponded to the latest release. ::: @@ -354,7 +354,7 @@ When the demo was tested, commit `03cc5ec` corresponded to the latest commit. ```bash git clone https://github.com/bellard/quickjs cd quickjs -git checkout 03cc5ec +git checkout daa35bc make cd .. cp quickjs/qjs . diff --git a/docz/docs/03-demos/42-engines/20-chakra.md b/docz/docs/03-demos/42-engines/20-chakra.md index c7d3467..e51312a 100644 --- a/docz/docs/03-demos/42-engines/20-chakra.md +++ b/docz/docs/03-demos/42-engines/20-chakra.md @@ -135,7 +135,7 @@ This demo was tested in the following deployments: | `darwin-x64` | `c3ead3f` | 2023-11-04 | | `darwin-arm` | `c3ead3f` | 2023-10-19 | | `win10-x64` | `c3ead3f` | 2023-10-28 | -| `linux-x64` | `c3ead3f` | 2023-10-11 | +| `linux-x64` | `c3ead3f` | 2023-12-09 | ::: diff --git a/docz/docs/03-demos/42-engines/21-boa.md b/docz/docs/03-demos/42-engines/21-boa.md index 9236b3a..9318d90 100644 --- a/docz/docs/03-demos/42-engines/21-boa.md +++ b/docz/docs/03-demos/42-engines/21-boa.md @@ -124,7 +124,7 @@ This demo was tested in the following deployments: | `darwin-arm` | 2023-10-20 | | `win10-x64` | 2023-10-28 | | `win11-arm` | 2023-12-01 | -| `linux-x64` | 2023-10-11 | +| `linux-x64` | 2023-12-07 | | `linux-arm` | 2023-12-01 | ::: diff --git a/docz/docs/03-demos/42-engines/22-perl.md b/docz/docs/03-demos/42-engines/22-perl.md index 747606c..0c5e660 100644 --- a/docz/docs/03-demos/42-engines/22-perl.md +++ b/docz/docs/03-demos/42-engines/22-perl.md @@ -100,9 +100,9 @@ write_file("SheetJE.fods", $fods); ## Complete Example -:::note +:::note Tested Deployments -This demo was tested on 2023-08-26 against JE 0.066 +This demo was tested on 2023-12-05 against JE 0.066 ::: @@ -131,5 +131,5 @@ curl -LO https://sheetjs.com/data/cd.xls perl SheetJE.pl cd.xls ``` -After a short wait, the contents will be displayed in CSV form. It will also -write a file `SheetJE.fods` that can be opened in LibreOffice. \ No newline at end of file +After a short wait, the contents will be displayed in CSV form. The script will +also generate the spreadsheet `SheetJE.fods` which can be opened in LibreOffice. \ No newline at end of file diff --git a/docz/docs/03-demos/42-engines/index.md b/docz/docs/03-demos/42-engines/index.md index 4f90ca0..d99d67e 100644 --- a/docz/docs/03-demos/42-engines/index.md +++ b/docz/docs/03-demos/42-engines/index.md @@ -125,14 +125,22 @@ generates a C library and a standalone CLI tool. The simplest way to interact with the engine is to pass Base64 strings. -:::note pass +:::note Tested Environments -This demo was tested in the following deployments: +This demo was tested in the following environments: | Architecture | Commit | Date | |:-------------|:----------|:-----------| -| `darwin-x64` | `bc408b1` | 2023-11-14 | -| `linux-x64` | `a588e49` | 2023-10-11 | +| `darwin-x64` | `ef4cb2b` | 2023-12-08 | +| `darwin-arm` | `ef4cb2b` | 2023-12-08 | +| `win11-x64` | `ef4cb2b` | 2023-12-08 | +| `win11-arm` | `ef4cb2b` | 2023-12-08 | +| `linux-x64` | `ef4cb2b` | 2023-12-08 | +| `linux-arm` | `ef4cb2b` | 2023-12-08 | + +The Windows tests were run in WSL. + +Debian and WSL require the `cmake`, `python3` and `python-is-python3` packages. ::: diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md index 941981d..52ad9df 100644 --- a/docz/docs/03-demos/index.md +++ b/docz/docs/03-demos/index.md @@ -21,7 +21,7 @@ in the [issue tracker](https://git.sheetjs.com/sheetjs/docs.sheetjs.com/issues) - [`XMLHttpRequest and fetch`](/docs/demos/net/network) - [`Clipboard Data`](/docs/demos/local/clipboard) - [`Web Workers`](/docs/demos/bigdata/worker) -- [`Typed Arrays for Machine Learning`](/docs/demos/bigdata/ml) +- [`Typed Arrays`](/docs/demos/math) - [`Local File Access`](/docs/demos/local/file) - [`LocalStorage and SessionStorage`](/docs/demos/data/storageapi) - [`Web SQL Database`](/docs/demos/data/websql) diff --git a/docz/docs/06-solutions/01-input.md b/docz/docs/06-solutions/01-input.md index 9216624..6fc6a01 100644 --- a/docz/docs/06-solutions/01-input.md +++ b/docz/docs/06-solutions/01-input.md @@ -748,7 +748,7 @@ example of fetching data from a JSON Endpoint and generating a workbook. [`x-spreadsheet`](/docs/demos/grid/xs) is an interactive data grid for previewing and modifying structured data in the web browser. -["Typed Arrays and ML"](/docs/demos/ml) covers strategies for +["TensorFlow.js"](/docs/demos/math/tensorflow) covers strategies for creating worksheets from ML library exports (datasets stored in Typed Arrays).
diff --git a/docz/docs/06-solutions/05-output.md b/docz/docs/06-solutions/05-output.md index 10ba726..6febe2c 100644 --- a/docz/docs/06-solutions/05-output.md +++ b/docz/docs/06-solutions/05-output.md @@ -652,7 +652,7 @@ export default function App() { ### Example: Data Loading -["Typed Arrays and ML"](/docs/demos/ml) covers strategies for +["TensorFlow.js"](/docs/demos/math/tensorflow) covers strategies for generating typed arrays and tensors from worksheet data.
diff --git a/docz/docs/08-api/07-utilities/08-wb.md b/docz/docs/08-api/07-utilities/08-wb.md index 299d026..89fba2b 100644 --- a/docz/docs/08-api/07-utilities/08-wb.md +++ b/docz/docs/08-api/07-utilities/08-wb.md @@ -1,6 +1,7 @@ --- sidebar_position: 8 title: Workbook Helpers +hide_table_of_contents: true --- Many utility functions return worksheet objects. Worksheets cannot be written to @@ -9,10 +10,12 @@ workbook file formats directly. They must be added to a workbook object. **Create a new workbook** ```js -var workbook = XLSX.utils.book_new(); +var wb_sans_sheets = XLSX.utils.book_new(); ``` -The `book_new` utility function creates an empty workbook with no worksheets. +With no arguments, the `book_new` utility function creates an empty workbook. + +:::info pass Spreadsheet software generally require at least one worksheet and enforce the requirement in the user interface. For example, if the last worksheet is deleted @@ -21,6 +24,29 @@ in the program, Apple Numbers will automatically create a new blank sheet. The SheetJS [write functions](/docs/api/write-options) enforce the requirement. They will throw errors when trying to export empty workbooks. +::: + +_Single Worksheet_ + +:::tip pass + +Version `0.20.1` introduced the one and two argument forms of `book_new`. It is +strongly recommended to [upgrade](/docs/getting-started/installation/). + +::: + +```js +var wb_with_sheet_named_Sheet1 = XLSX.utils.book_new(worksheet); +var wb_with_sheet_named_Blatte = XLSX.utils.book_new(worksheet, "Blatte"); +``` + +`book_new` can accept one or two arguments. + +If provided, the first argument is expected to be a worksheet object. It will +be added to the new workbook. + +If provided, the second argument is the name of the worksheet. If omitted, the +default name "Sheet1" will be used. **Append a Worksheet to a Workbook** diff --git a/docz/docs/08-api/07-utilities/index.md b/docz/docs/08-api/07-utilities/index.md index 765a999..acf092a 100644 --- a/docz/docs/08-api/07-utilities/index.md +++ b/docz/docs/08-api/07-utilities/index.md @@ -21,7 +21,7 @@ flowchart LR wb(SheetJS\nWorkbook) file[(workbook\nfile)] html --> |table_to_sheet\n\n| ws - ws --> |book_new\nbook_append_sheet| wb + ws --> |book_new\n\n| wb wb --> |writeFile\n\n| file ``` diff --git a/docz/docs/08-api/index.md b/docz/docs/08-api/index.md index 304ccc9..8006646 100644 --- a/docz/docs/08-api/index.md +++ b/docz/docs/08-api/index.md @@ -116,7 +116,7 @@ _Exporting Formulae:_ _Workbook Operations:_ -- `book_new` creates an empty workbook +- `book_new` creates a workbook object - `book_append_sheet` adds a worksheet to a workbook **[Utility Functions](/docs/api/utilities)** diff --git a/docz/docs/09-miscellany/05-contributing.md b/docz/docs/09-miscellany/05-contributing.md index 1c7e268..b1fb3b0 100644 --- a/docz/docs/09-miscellany/05-contributing.md +++ b/docz/docs/09-miscellany/05-contributing.md @@ -37,15 +37,15 @@ building, reproducing official releases, and running NodeJS and browser tests. These instructions were tested on the following platforms: -| Platform | Test Date | -|:------------------------------|:-----------| -| Linux (Steam Deck Holo x64) | 2023-11-27 | -| Linux (Ubuntu 18 AArch64) | 2023-12-01 | -| MacOS 10.13.6 (x64) | 2023-09-30 | -| MacOS 14.1.2 (ARM64) | 2023-12-01 | -| Windows 10 (x64) + WSL Ubuntu | 2023-11-27 | -| Windows 11 (x64) + WSL Ubuntu | 2023-10-14 | -| Windows 11 (ARM) + WSL Ubuntu | 2023-09-18 | +| Platform | Architecture | Test Date | +|:------------------------------|:-------------|:-----------| +| Linux (Steam Deck Holo x64) | `linux-x64` | 2023-11-27 | +| Linux (Ubuntu 18 AArch64) | `linux-arm` | 2023-12-01 | +| MacOS 10.13.6 (x64) | `darwin-x64` | 2023-09-30 | +| MacOS 14.1.2 (ARM64) | `darwin-arm` | 2023-12-01 | +| Windows 10 (x64) + WSL Ubuntu | `win10-x64` | 2023-11-27 | +| Windows 11 (x64) + WSL Ubuntu | `win11-x64` | 2023-10-14 | +| Windows 11 (ARM) + WSL Ubuntu | `win11-arm` | 2023-09-18 | With some additional dependencies, the unminified scripts are reproducible and tests will pass in Windows XP with NodeJS 5.10.0. @@ -525,28 +525,28 @@ echo 'export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"' >> ~/.profile ### Reproduce official builds 5) Run `git log` and search for the commit that matches a particular release -version. For example, version `0.20.0` can be found with: +version. For example, version `0.20.1` can be found with: ```bash -git log | grep -B4 "version bump 0.20.0" +git log | grep -B4 "version bump 0.20.1" ``` The output should look like: ```bash -$ git log | grep -B4 "version bump 0.20.0" +$ git log | grep -B4 "version bump 0.20.1" # highlight-next-line -commit 955543147dac0274d20307057c5a9f3e3e5d5307 <-- this is the commit hash +commit 29d46c07a895bdfd948d15b5115529ae697ccb48 <-- this is the commit hash Author: SheetJS -Date: Fri Jun 23 05:48:47 2023 -0400 +Date: Tue Dec 5 03:19:42 2023 -0500 - version bump 0.20.0 + version bump 0.20.1 ``` 6) Switch to that commit: ```bash -git checkout 955543147dac0274d20307057c5a9f3e3e5d5307 +git checkout 29d46c07a895bdfd948d15b5115529ae697ccb48 ``` 7) Run the full build sequence @@ -593,36 +593,36 @@ The checksum for the CDN version can be computed with: ```bash -curl -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5sum - +curl -L https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js | md5sum - ``` ```bash -curl -k -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5 +curl -k -L https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js | md5 ``` ```bash -curl -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5sum - +curl -L https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js | md5sum - ``` -When the demo was last tested on macOS, against version `0.20.0`: +When the demo was last tested on macOS, against version `0.20.1`: > ```bash $ md5 dist/xlsx.full.min.js # highlight-next-line -MD5 (dist/xlsx.full.min.js) = 0b2f539797f92d35c6394274818f2c22 -$ curl -k -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5 +MD5 (dist/xlsx.full.min.js) = c5db4b1d2a1985a4ebfbaa500243f593 +$ curl -k -L https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js | md5 # highlight-next-line -0b2f539797f92d35c6394274818f2c22 +c5db4b1d2a1985a4ebfbaa500243f593 ``` The two hashes should match. diff --git a/docz/docusaurus.config.js b/docz/docusaurus.config.js index 6054b81..5cb8193 100644 --- a/docz/docusaurus.config.js +++ b/docz/docusaurus.config.js @@ -227,9 +227,11 @@ const config = { { from: '/docs/getting-started/demos/cli', to: '/docs/demos/desktop/cli/' }, { from: '/docs/getting-started/demos/desktop', to: '/docs/demos/desktop/' }, /* bigdata */ - { from: '/docs/demos/ml', to: '/docs/demos/bigdata/ml/' }, { from: '/docs/demos/worker', to: '/docs/demos/bigdata/worker/' }, { from: '/docs/demos/stream', to: '/docs/demos/bigdata/stream/' }, + /* math */ + { from: '/docs/demos/ml', to: '/docs/demos/math/' }, + { from: '/docs/demos/bigdata/ml', to: '/docs/demos/math/' }, /* installation */ { from: '/docs/installation/standalone', to: '/docs/getting-started/installation/standalone/' }, { from: '/docs/installation/frameworks', to: '/docs/getting-started/installation/frameworks/' }, diff --git a/docz/package.json b/docz/package.json index a8854ae..02b1a2e 100644 --- a/docz/package.json +++ b/docz/package.json @@ -26,7 +26,7 @@ "prism-react-renderer": "1.3.5", "react": "17.0.2", "react-dom": "17.0.2", - "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz" + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz" }, "devDependencies": { "@docusaurus/module-type-aliases": "2.4.1" diff --git a/docz/static/deno/s2c.ts b/docz/static/deno/s2c.ts index 108e2df..6d710ec 100644 --- a/docz/static/deno/s2c.ts +++ b/docz/static/deno/s2c.ts @@ -1,6 +1,6 @@ -// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.0/package/types/index.d.ts" -import { read, utils, set_cptable, version } from 'https://cdn.sheetjs.com/xlsx-0.20.0/package/xlsx.mjs'; -import * as cptable from 'https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/cpexcel.full.mjs'; +// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.1/package/types/index.d.ts" +import { read, utils, set_cptable, version } from 'https://cdn.sheetjs.com/xlsx-0.20.1/package/xlsx.mjs'; +import * as cptable from 'https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/cpexcel.full.mjs'; set_cptable(cptable); import * as Drash from "https://cdn.jsdelivr.net/gh/drashland/drash@v2.8.1/mod.ts"; diff --git a/docz/static/nwjs/index.html b/docz/static/nwjs/index.html index d86a447..dbf27fb 100644 --- a/docz/static/nwjs/index.html +++ b/docz/static/nwjs/index.html @@ -64,6 +64,10 @@ async function do_file(files) { process_wb(XLSX.read(data)); } +(async() => { + process_wb(XLSX.read(await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer())) +})(); + var drop = document.getElementById('drop'); function handleDrop(e) { diff --git a/docz/static/reactnative/xcode-select-project.png b/docz/static/reactnative/xcode-select-project.png new file mode 100644 index 0000000..c816724 Binary files /dev/null and b/docz/static/reactnative/xcode-select-project.png differ diff --git a/docz/static/reactnative/xcode-targets.png b/docz/static/reactnative/xcode-targets.png new file mode 100644 index 0000000..caf4b55 Binary files /dev/null and b/docz/static/reactnative/xcode-targets.png differ diff --git a/docz/static/tfjs/tf.min.js b/docz/static/tfjs/tf.min.js index ce8fe7c..00adab9 100644 --- a/docz/static/tfjs/tf.min.js +++ b/docz/static/tfjs/tf.min.js @@ -14,5 +14,5 @@ * limitations under the License. * ============================================================================= */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).tf=e.tf||{})}(this,(function(e){"use strict";function t(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),e}var n="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function r(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function a(e){var t,n;function r(t,n){try{var o=e[t](n),s=o.value,u=s instanceof i;Promise.resolve(u?s.v:s).then((function(n){if(u){var i="return"===t?"return":"next";if(!s.k||n.done)return r(i,n);n=e[i](n).value}a(o.done?"return":"normal",n)}),(function(e){r("throw",e)}))}catch(e){a("throw",e)}}function a(e,a){switch(e){case"return":t.resolve({value:a,done:!0});break;case"throw":t.reject(a);break;default:t.resolve({value:a,done:!1})}(t=t.next)?r(t.key,t.arg):n=null}this._invoke=function(e,a){return new Promise((function(i,o){var s={key:e,arg:a,resolve:i,reject:o,next:null};n?n=n.next=s:(t=n=s,r(e,a))}))},"function"!=typeof e.return&&(this.return=void 0)}function i(e,t){this.v=e,this.k=t}function o(){o=function(){return e};var e={},t=Object.prototype,n=t.hasOwnProperty,r=Object.defineProperty||function(e,t,n){e[t]=n.value},a="function"==typeof Symbol?Symbol:{},i=a.iterator||"@@iterator",s=a.asyncIterator||"@@asyncIterator",u=a.toStringTag||"@@toStringTag";function c(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{c({},"")}catch(e){c=function(e,t,n){return e[t]=n}}function l(e,t,n,a){var i=t&&t.prototype instanceof f?t:f,o=Object.create(i.prototype),s=new T(a||[]);return r(o,"_invoke",{value:w(e,n,s)}),o}function h(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}e.wrap=l;var p={};function f(){}function d(){}function v(){}var m={};c(m,i,(function(){return this}));var g=Object.getPrototypeOf,y=g&&g(g(E([])));y&&y!==t&&n.call(y,i)&&(m=y);var b=v.prototype=f.prototype=Object.create(m);function x(e){["next","throw","return"].forEach((function(t){c(e,t,(function(e){return this._invoke(t,e)}))}))}function k(e,t){function a(r,i,o,s){var u=h(e[r],e,i);if("throw"!==u.type){var c=u.arg,l=c.value;return l&&"object"==typeof l&&n.call(l,"__await")?t.resolve(l.__await).then((function(e){a("next",e,o,s)}),(function(e){a("throw",e,o,s)})):t.resolve(l).then((function(e){c.value=e,o(c)}),(function(e){return a("throw",e,o,s)}))}s(u.arg)}var i;r(this,"_invoke",{value:function(e,n){function r(){return new t((function(t,r){a(e,n,t,r)}))}return i=i?i.then(r,r):r()}})}function w(e,t,n){var r="suspendedStart";return function(a,i){if("executing"===r)throw new Error("Generator is already running");if("completed"===r){if("throw"===a)throw i;return C()}for(n.method=a,n.arg=i;;){var o=n.delegate;if(o){var s=I(o,n);if(s){if(s===p)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===r)throw r="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);r="executing";var u=h(e,t,n);if("normal"===u.type){if(r=n.done?"completed":"suspendedYield",u.arg===p)continue;return{value:u.arg,done:n.done}}"throw"===u.type&&(r="completed",n.method="throw",n.arg=u.arg)}}}function I(e,t){var n=t.method,r=e.iterator[n];if(void 0===r)return t.delegate=null,"throw"===n&&e.iterator.return&&(t.method="return",t.arg=void 0,I(e,t),"throw"===t.method)||"return"!==n&&(t.method="throw",t.arg=new TypeError("The iterator does not provide a '"+n+"' method")),p;var a=h(r,e.iterator,t.arg);if("throw"===a.type)return t.method="throw",t.arg=a.arg,t.delegate=null,p;var i=a.arg;return i?i.done?(t[e.resultName]=i.value,t.next=e.nextLoc,"return"!==t.method&&(t.method="next",t.arg=void 0),t.delegate=null,p):i:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,p)}function N(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function S(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function T(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(N,this),this.reset(!0)}function E(e){if(e){var t=e[i];if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var r=-1,a=function t(){for(;++r=0;--a){var i=this.tryEntries[a],o=i.completion;if("root"===i.tryLoc)return r("end");if(i.tryLoc<=this.prev){var s=n.call(i,"catchLoc"),u=n.call(i,"finallyLoc");if(s&&u){if(this.prev=0;--r){var a=this.tryEntries[r];if(a.tryLoc<=this.prev&&n.call(a,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),S(n),p}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var a=r.arg;S(n)}return a}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:E(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),p}},e}function s(e){return s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},s(e)}function u(e,t,n,r,a,i,o){try{var s=e[i](o),u=s.value}catch(e){return void n(e)}s.done?t(u):Promise.resolve(u).then(r,a)}function c(e){return function(){var t=this,n=arguments;return new Promise((function(r,a){var i=e.apply(t,n);function o(e){u(i,r,a,o,s,"next",e)}function s(e){u(i,r,a,o,s,"throw",e)}o(void 0)}))}}function l(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function h(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,o=!0,s=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return o=e.done,e},e:function(e){s=!0,i=e},f:function(){try{o||null==n.return||n.return()}finally{if(s)throw i}}}}function F(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,t||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}a.prototype["function"==typeof Symbol&&Symbol.asyncIterator||"@@asyncIterator"]=function(){return this},a.prototype.next=function(e){return this._invoke("next",e)},a.prototype.throw=function(e){return this._invoke("throw",e)},a.prototype.return=function(e){return this._invoke("return",e)};var D,M,L=function(e){return e&&e.Math==Math&&e},z=L("object"==("undefined"==typeof globalThis?"undefined":s(globalThis))&&globalThis)||L("object"==("undefined"==typeof window?"undefined":s(window))&&window)||L("object"==("undefined"==typeof self?"undefined":s(self))&&self)||L("object"==s(n)&&n)||function(){return this}()||Function("return this")(),P={},B=function(e){try{return!!e()}catch(e){return!0}},W=!B((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),U=!B((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")})),V=U,G=Function.prototype.call,j=V?G.bind(G):function(){return G.apply(G,arguments)},H={},q={}.propertyIsEnumerable,K=Object.getOwnPropertyDescriptor,X=K&&!q.call({1:2},1),Y=(H.f=X?function(e){var t=K(this,e);return!!t&&t.enumerable}:q,function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}),J=U,Z=Function.prototype,Q=Z.call,$=J&&Z.bind.bind(Q,Q),ee=J?$:function(e){return function(){return Q.apply(e,arguments)}},te=ee,ne=te({}.toString),re=te("".slice),ae=function(e){return re(ne(e),8,-1)},ie=B,oe=ae,se=Object,ue=ee("".split),ce=ie((function(){return!se("z").propertyIsEnumerable(0)}))?function(e){return"String"==oe(e)?ue(e,""):se(e)}:se,le=function(e){return null==e},he=le,pe=TypeError,fe=function(e){if(he(e))throw pe("Can't call method on "+e);return e},de=ce,ve=fe,me=function(e){return de(ve(e))},ge="object"==("undefined"==typeof document?"undefined":s(document))&&document.all,ye={all:ge,IS_HTMLDDA:void 0===ge&&void 0!==ge},be=ye.all,xe=ye.IS_HTMLDDA?function(e){return"function"==typeof e||e===be}:function(e){return"function"==typeof e},ke=xe,we=ye.all,Ie=ye.IS_HTMLDDA?function(e){return"object"==s(e)?null!==e:ke(e)||e===we}:function(e){return"object"==s(e)?null!==e:ke(e)},Ne=z,Se=xe,Te=function(e){return Se(e)?e:void 0},Ee=function(e,t){return arguments.length<2?Te(Ne[e]):Ne[e]&&Ne[e][t]},Ce=ee({}.isPrototypeOf),Ae="undefined"!=typeof navigator&&String(navigator.userAgent)||"",Re=z,_e=Ae,Oe=Re.process,Fe=Re.Deno,De=Oe&&Oe.versions||Fe&&Fe.version,Me=De&&De.v8;Me&&(M=(D=Me.split("."))[0]>0&&D[0]<4?1:+(D[0]+D[1])),!M&&_e&&(!(D=_e.match(/Edge\/(\d+)/))||D[1]>=74)&&(D=_e.match(/Chrome\/(\d+)/))&&(M=+D[1]);var Le=M,ze=Le,Pe=B,Be=!!Object.getOwnPropertySymbols&&!Pe((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&ze&&ze<41})),We=Be&&!Symbol.sham&&"symbol"==s(Symbol.iterator),Ue=Ee,Ve=xe,Ge=Ce,je=Object,He=We?function(e){return"symbol"==s(e)}:function(e){var t=Ue("Symbol");return Ve(t)&&Ge(t.prototype,je(e))},qe=String,Ke=function(e){try{return qe(e)}catch(e){return"Object"}},Xe=xe,Ye=Ke,Je=TypeError,Ze=function(e){if(Xe(e))return e;throw Je(Ye(e)+" is not a function")},Qe=Ze,$e=le,et=function(e,t){var n=e[t];return $e(n)?void 0:Qe(n)},tt=j,nt=xe,rt=Ie,at=TypeError,it=function(e,t){var n,r;if("string"===t&&nt(n=e.toString)&&!rt(r=tt(n,e)))return r;if(nt(n=e.valueOf)&&!rt(r=tt(n,e)))return r;if("string"!==t&&nt(n=e.toString)&&!rt(r=tt(n,e)))return r;throw at("Can't convert object to primitive value")},ot={exports:{}},st=!1,ut=z,ct=Object.defineProperty,lt=function(e,t){try{ct(ut,e,{value:t,configurable:!0,writable:!0})}catch(n){ut[e]=t}return t},ht=lt,pt="__core-js_shared__",ft=z[pt]||ht(pt,{}),dt=(ot.exports,ft);(ot.exports=function(e,t){return dt[e]||(dt[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.29.1",mode:"global",copyright:"© 2014-2023 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.29.1/LICENSE",source:"https://github.com/zloirock/core-js"});var vt=ot.exports,mt=fe,gt=Object,yt=function(e){return gt(mt(e))},bt=yt,xt=ee({}.hasOwnProperty),kt=Object.hasOwn||function(e,t){return xt(bt(e),t)},wt=ee,It=0,Nt=Math.random(),St=wt(1..toString),Tt=function(e){return"Symbol("+(void 0===e?"":e)+")_"+St(++It+Nt,36)},Et=vt,Ct=kt,At=Tt,Rt=Be,_t=We,Ot=z.Symbol,Ft=Et("wks"),Dt=_t?Ot.for||Ot:Ot&&Ot.withoutSetter||At,Mt=function(e){return Ct(Ft,e)||(Ft[e]=Rt&&Ct(Ot,e)?Ot[e]:Dt("Symbol."+e)),Ft[e]},Lt=j,zt=Ie,Pt=He,Bt=et,Wt=it,Ut=TypeError,Vt=Mt("toPrimitive"),Gt=function(e,t){if(!zt(e)||Pt(e))return e;var n,r=Bt(e,Vt);if(r){if(void 0===t&&(t="default"),n=Lt(r,e,t),!zt(n)||Pt(n))return n;throw Ut("Can't convert object to primitive value")}return void 0===t&&(t="number"),Wt(e,t)},jt=Gt,Ht=He,qt=function(e){var t=jt(e,"string");return Ht(t)?t:t+""},Kt=Ie,Xt=z.document,Yt=Kt(Xt)&&Kt(Xt.createElement),Jt=function(e){return Yt?Xt.createElement(e):{}},Zt=Jt,Qt=!W&&!B((function(){return 7!=Object.defineProperty(Zt("div"),"a",{get:function(){return 7}}).a})),$t=W,en=j,tn=H,nn=Y,rn=me,an=qt,on=kt,sn=Qt,un=Object.getOwnPropertyDescriptor,cn=(P.f=$t?un:function(e,t){if(e=rn(e),t=an(t),sn)try{return un(e,t)}catch(e){}if(on(e,t))return nn(!en(tn.f,e,t),e[t])},{}),ln=W&&B((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype})),hn=Ie,pn=String,fn=TypeError,dn=function(e){if(hn(e))return e;throw fn(pn(e)+" is not an object")},vn=W,mn=Qt,gn=ln,yn=dn,bn=qt,xn=TypeError,kn=Object.defineProperty,wn=Object.getOwnPropertyDescriptor,In="enumerable",Nn="configurable",Sn="writable",Tn=(cn.f=vn?gn?function(e,t,n){if(yn(e),t=bn(t),yn(n),"function"==typeof e&&"prototype"===t&&"value"in n&&Sn in n&&!n.writable){var r=wn(e,t);r&&r.writable&&(e[t]=n.value,n={configurable:Nn in n?n.configurable:r.configurable,enumerable:In in n?n.enumerable:r.enumerable,writable:!1})}return kn(e,t,n)}:kn:function(e,t,n){if(yn(e),t=bn(t),yn(n),mn)try{return kn(e,t,n)}catch(e){}if("get"in n||"set"in n)throw xn("Accessors not supported");return"value"in n&&(e[t]=n.value),e},cn),En=Y,Cn=W?function(e,t,n){return Tn.f(e,t,En(1,n))}:function(e,t,n){return e[t]=n,e},An={exports:{}},Rn=W,_n=kt,On=Function.prototype,Fn=Rn&&Object.getOwnPropertyDescriptor,Dn=_n(On,"name"),Mn={EXISTS:Dn,PROPER:Dn&&"something"===function(){}.name,CONFIGURABLE:Dn&&(!Rn||Rn&&Fn(On,"name").configurable)},Ln=xe,zn=ft,Pn=ee(Function.toString);Ln(zn.inspectSource)||(zn.inspectSource=function(e){return Pn(e)});var Bn,Wn,Un,Vn=zn.inspectSource,Gn=xe,jn=z.WeakMap,Hn=Gn(jn)&&/native code/.test(String(jn)),qn=Tt,Kn=vt("keys"),Xn=function(e){return Kn[e]||(Kn[e]=qn(e))},Yn={},Jn=Hn,Zn=z,Qn=Ie,$n=Cn,er=kt,tr=ft,nr=Xn,rr=Yn,ar="Object already initialized",ir=Zn.TypeError,or=Zn.WeakMap;if(Jn||tr.state){var sr=tr.state||(tr.state=new or);sr.get=sr.get,sr.has=sr.has,sr.set=sr.set,Bn=function(e,t){if(sr.has(e))throw ir(ar);return t.facade=e,sr.set(e,t),t},Wn=function(e){return sr.get(e)||{}},Un=function(e){return sr.has(e)}}else{var ur=nr("state");rr[ur]=!0,Bn=function(e,t){if(er(e,ur))throw ir(ar);return t.facade=e,$n(e,ur,t),t},Wn=function(e){return er(e,ur)?e[ur]:{}},Un=function(e){return er(e,ur)}}var cr={set:Bn,get:Wn,has:Un,enforce:function(e){return Un(e)?Wn(e):Bn(e,{})},getterFor:function(e){return function(t){var n;if(!Qn(t)||(n=Wn(t)).type!==e)throw ir("Incompatible receiver, "+e+" required");return n}}},lr=(An.exports,ee),hr=B,pr=xe,fr=kt,dr=W,vr=Mn.CONFIGURABLE,mr=Vn,gr=cr.enforce,yr=cr.get,br=String,xr=Object.defineProperty,kr=lr("".slice),wr=lr("".replace),Ir=lr([].join),Nr=dr&&!hr((function(){return 8!==xr((function(){}),"length",{value:8}).length})),Sr=String(String).split("String"),Tr=An.exports=function(e,t,n){"Symbol("===kr(br(t),0,7)&&(t="["+wr(br(t),/^Symbol\(([^)]*)\)/,"$1")+"]"),n&&n.getter&&(t="get "+t),n&&n.setter&&(t="set "+t),(!fr(e,"name")||vr&&e.name!==t)&&(dr?xr(e,"name",{value:t,configurable:!0}):e.name=t),Nr&&n&&fr(n,"arity")&&e.length!==n.arity&&xr(e,"length",{value:n.arity});try{n&&fr(n,"constructor")&&n.constructor?dr&&xr(e,"prototype",{writable:!1}):e.prototype&&(e.prototype=void 0)}catch(e){}var r=gr(e);return fr(r,"source")||(r.source=Ir(Sr,"string"==typeof t?t:"")),e};Function.prototype.toString=Tr((function(){return pr(this)&&yr(this).source||mr(this)}),"toString");var Er=An.exports,Cr=xe,Ar=cn,Rr=Er,_r=lt,Or=function(e,t,n,r){r||(r={});var a=r.enumerable,i=void 0!==r.name?r.name:t;if(Cr(n)&&Rr(n,i,r),r.global)a?e[t]=n:_r(t,n);else{try{r.unsafe?e[t]&&(a=!0):delete e[t]}catch(e){}a?e[t]=n:Ar.f(e,t,{value:n,enumerable:!1,configurable:!r.nonConfigurable,writable:!r.nonWritable})}return e},Fr={},Dr=Math.ceil,Mr=Math.floor,Lr=Math.trunc||function(e){var t=+e;return(t>0?Mr:Dr)(t)},zr=Lr,Pr=function(e){var t=+e;return t!=t||0===t?0:zr(t)},Br=Pr,Wr=Math.max,Ur=Math.min,Vr=function(e,t){var n=Br(e);return n<0?Wr(n+t,0):Ur(n,t)},Gr=Pr,jr=Math.min,Hr=function(e){return e>0?jr(Gr(e),9007199254740991):0},qr=Hr,Kr=function(e){return qr(e.length)},Xr=me,Yr=Vr,Jr=Kr,Zr=function(e){return function(t,n,r){var a,i=Xr(t),o=Jr(i),s=Yr(r,o);if(e&&n!=n){for(;o>s;)if((a=i[s++])!=a)return!0}else for(;o>s;s++)if((e||s in i)&&i[s]===n)return e||s||0;return!e&&-1}},Qr={includes:Zr(!0),indexOf:Zr(!1)},$r=kt,ea=me,ta=Qr.indexOf,na=Yn,ra=ee([].push),aa=function(e,t){var n,r=ea(e),a=0,i=[];for(n in r)!$r(na,n)&&$r(r,n)&&ra(i,n);for(;t.length>a;)$r(r,n=t[a++])&&(~ta(i,n)||ra(i,n));return i},ia=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],oa=aa,sa=ia.concat("length","prototype"),ua=(Fr.f=Object.getOwnPropertyNames||function(e){return oa(e,sa)},{}),ca=(ua.f=Object.getOwnPropertySymbols,Ee),la=Fr,ha=ua,pa=dn,fa=ee([].concat),da=ca("Reflect","ownKeys")||function(e){var t=la.f(pa(e)),n=ha.f;return n?fa(t,n(e)):t},va=kt,ma=da,ga=P,ya=cn,ba=function(e,t,n){for(var r=ma(t),a=ya.f,i=ga.f,o=0;oo;)ni.f(e,n=a[o++],r[n]);return e},Ee("document","documentElement")),si=dn,ui=Ja,ci=ia,li=Yn,hi=oi,pi=Jt,fi=Xn("IE_PROTO"),di=function(){},vi=function(e){return"