Skip to content

Commit

Permalink
Merge pull request #68 from flatironinstitute/export-chain-csvs
Browse files Browse the repository at this point in the history
frontend: export chain csvs
  • Loading branch information
magland authored Jun 14, 2024
2 parents a584857 + 5166803 commit ef33eab
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 5 deletions.
1 change: 1 addition & 0 deletions gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@monaco-editor/react": "^4.6.0",
"@mui/icons-material": "^5.15.17",
"@mui/material": "^5.15.17",
"jszip": "^3.10.1",
"monaco-editor": "^0.48.0",
"plotly.js": "^2.33.0",
"react": "^18.2.0",
Expand Down
61 changes: 56 additions & 5 deletions gui/src/app/SamplerOutputView/SamplerOutputView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import TabWidget from "../TabWidget/TabWidget"
import TracePlotsView from "./TracePlotsView"
import SummaryView from "./SummaryView"
import HistsView from "./HistsView"
import JSZip from 'jszip'

type SamplerOutputViewProps = {
width: number
Expand Down Expand Up @@ -142,13 +143,33 @@ const DrawsView: FunctionComponent<DrawsViewProps> = ({ width, height, draws, pa
const csvText = prepareCsvText(draws, paramNames, drawChainIds, drawNumbers);
downloadTextFile(csvText, 'draws.csv');
}, [draws, paramNames, drawChainIds, drawNumbers]);
const handleExportToMultipleCsvs = useCallback(async () => {
const uniqueChainIds = Array.from(new Set(drawChainIds));
const csvTexts = prepareMultipleCsvsText(draws, paramNames, drawChainIds, uniqueChainIds);
const blob = await createZipBlobForMultipleCsvs(csvTexts, uniqueChainIds);
const fileName = 'SP-draws.zip';
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
}, [draws, paramNames, drawChainIds]);
return (
<div style={{position: 'absolute', width, height, overflow: 'auto'}}>
<SmallIconButton
icon={<Download />}
label="Export to .csv"
onClick={handleExportToCsv}
/>
<div>
<SmallIconButton
icon={<Download />}
label="Export to single .csv"
onClick={handleExportToCsv}
/>
&nbsp;
<SmallIconButton
icon={<Download />}
label="Export to multiple .csv"
onClick={handleExportToMultipleCsvs}
/>
</div>
<table className="draws-table">
<thead>
<tr>
Expand Down Expand Up @@ -191,12 +212,42 @@ const DrawsView: FunctionComponent<DrawsViewProps> = ({ width, height, draws, pa
}

const prepareCsvText = (draws: number[][], paramNames: string[], drawChainIds: number[], drawNumbers: number[]) => {
// draws: Each element of draws is a column corresponding to a parameter, across all chains
// paramNames: The paramNames array contains the names of the parameters in the same order that they appear in the draws array
// drawChainIds: The drawChainIds array contains the chain id for each row in the draws array
// uniqueChainIds: The uniqueChainIds array contains the unique chain ids
const lines = draws[0].map((_, i) => {
return [`${drawChainIds[i]}`, `${drawNumbers[i]}`, ...paramNames.map((_, j) => draws[j][i])].join(',')
})
return [['Chain', 'Draw', ...paramNames].join(','), ...lines].join('\n')
}

const prepareMultipleCsvsText = (draws: number[][], paramNames: string[], drawChainIds: number[], uniqueChainIds: number[]) => {
// See the comments in prepareCsvText for the meaning of the arguments.
// Whereas prepareCsvText returns a CSV that represents a long-form table,
// this function returns multiple CSVs, one for each chain.
return uniqueChainIds.map(chainId => {
const drawIndicesForChain = drawChainIds.map((id, i) => id === chainId ? i : -1).filter(i => i >= 0);
const lines = drawIndicesForChain.map(i => {
return paramNames.map((_, j) => draws[j][i]).join(',')
})

return [paramNames.join(','), ...lines].join('\n')
})
}

const createZipBlobForMultipleCsvs = async (csvTexts: string[], uniqueChainIds: number[]) => {
const zip = new JSZip();
// put them all in a folder called 'draws'
const folder = zip.folder('draws');
if (!folder) throw new Error('Failed to create folder');
csvTexts.forEach((text, i) => {
folder.file(`chain_${uniqueChainIds[i]}.csv`, text);
});
const blob = await zip.generateAsync({type: 'blob'});
return blob;
}

const downloadTextFile = (text: string, filename: string) => {
const blob = new Blob([text], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
Expand Down
32 changes: 32 additions & 0 deletions gui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3530,6 +3530,11 @@ ignore@^5.2.0, ignore@^5.2.4:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==

immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==

import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
Expand Down Expand Up @@ -4006,6 +4011,16 @@ json5@^2.2.3:
object.assign "^4.1.4"
object.values "^1.1.6"

jszip@^3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
dependencies:
lie "~3.3.0"
pako "~1.0.2"
readable-stream "~2.3.6"
setimmediate "^1.0.5"

katex@^0.16.0:
version "0.16.10"
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.10.tgz#6f81b71ac37ff4ec7556861160f53bc5f058b185"
Expand Down Expand Up @@ -4038,6 +4053,13 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"

lie@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
dependencies:
immediate "~3.0.5"

lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
Expand Down Expand Up @@ -5138,6 +5160,11 @@ p-locate@^5.0.0:
dependencies:
p-limit "^3.0.2"

pako@~1.0.2:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==

parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
Expand Down Expand Up @@ -6000,6 +6027,11 @@ set-function-name@^2.0.1, set-function-name@^2.0.2:
functions-have-names "^1.2.3"
has-property-descriptors "^1.0.2"

setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==

[email protected]:
version "0.0.1"
resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170"
Expand Down

0 comments on commit ef33eab

Please sign in to comment.