diff --git a/gui/package.json b/gui/package.json index f9b1d97e9b6040e0960ace1429125796db6b7de6..39baf328e3d194cb4e41c9edc18dabe3abe40cc3 100644 --- a/gui/package.json +++ b/gui/package.json @@ -44,7 +44,8 @@ "react-router-dom": "^5.1.2", "react-scripts": "3.4.1", "react-swipeable-views": "^0.13.0", - "react-virtualized": "^9.21.2", + "react-virtualized-auto-sizer": "^1.0.2", + "react-window": "^1.8.5", "recoil": "^0.0.10", "recompose": "^0.28.2", "remark": "^12.0.1", diff --git a/gui/src/components/archive/ArchiveBrowser.js b/gui/src/components/archive/ArchiveBrowser.js index d00a2032093374dfadb78842a2947c324cd396d7..7e896154fad2b280483f2f26a9ef8c81eb7698ca 100644 --- a/gui/src/components/archive/ArchiveBrowser.js +++ b/gui/src/components/archive/ArchiveBrowser.js @@ -13,9 +13,9 @@ import { useLocation, useRouteMatch, Link } from 'react-router-dom' export const configState = atom({ key: 'config', default: { - 'showMeta': true, + 'showMeta': false, 'showCodeSpecific': false, - 'showAllDefined': true + 'showAllDefined': false } }) diff --git a/gui/src/components/archive/archiveAdaptors.js b/gui/src/components/archive/archiveAdaptors.js index a07163bed9677d37ed95719c5a938240a25f9667..4e1c48c949132575e34400426578d29d06bdafd8 100644 --- a/gui/src/components/archive/archiveAdaptors.js +++ b/gui/src/components/archive/archiveAdaptors.js @@ -6,6 +6,8 @@ import { Item, Content, Compartment, configState, List } from './ArchiveBrowser' import { Typography, Box } from '@material-ui/core' import { resolveRef, sectionDefs } from './metainfo' import { Title, metainfoAdaptorFactory, Meta } from './metainfoAdaptors' +import { Matrix, Number } from './visualizations' +import shape from '@material-ui/core/styles/shape' export default function archiveAdaptorFactory(data, sectionDef) { return new SectionAdaptor(data, sectionDef || sectionDefs['EntryArchive'], {archive: data}) @@ -95,11 +97,12 @@ function QuantityItemPreview({value, def}) { } } return <Box component="span" whiteSpace="nowrap" fontStyle="italic"> - <Typography component="span">{`[${dimensions.join(', ')}] ${typeLabel}`}</Typography> + <Typography component="span">{dimensions.map((v, i) => <span>{i > 0 && <span> × </span>}{new String(v)}</span>)} {typeLabel}</Typography> </Box> } else { return <Box component="span" whiteSpace="nowarp"> - <Typography component="span">{String(value)}</Typography> + <Number component="span" variant="body1" value={value} exp={8} /> + {def.unit && <Typography component="span"> {def.unit}</Typography>} </Box> } } @@ -112,9 +115,9 @@ function QuantityValue({value, def}) { return <Box marginTop={2} marginBottom={2} textAlign="center" fontWeight="bold" > - <Typography> - {String(value)} - </Typography> + {def.shape.length > 0 ? <Matrix values={value} shape={def.shape} invert={def.shape.length === 1} /> : <Number value={value} exp={16} variant="body2" />} + {def.shape.length > 0 && <Typography nowrap variant="caption">({def.shape.map((v, i) => <span>{i > 0 && <span> × </span>}{new String(v)}</span>)} )</Typography>} + {def.unit && <Typography nowrap>{def.unit}</Typography>} </Box> } QuantityValue.propTypes = ({ diff --git a/gui/src/components/archive/visualizations.js b/gui/src/components/archive/visualizations.js new file mode 100644 index 0000000000000000000000000000000000000000..8204a32624c1c5b28514363c130f5a84badaa594 --- /dev/null +++ b/gui/src/components/archive/visualizations.js @@ -0,0 +1,146 @@ +import React, { useRef, useLayoutEffect, useState } from 'react' +import PropTypes from 'prop-types' +import { FixedSizeGrid as Grid } from 'react-window' +import { Typography, makeStyles, FormGroup, Button, FormLabel, Grid as MuiGrid, Box } from '@material-ui/core' +import AutoSizer from 'react-virtualized-auto-sizer' + +export function Number({value, exp, variant, ...props}) { + variant = variant || 'body2' + exp = exp || 2 + const fixed = 5 + let html = '-' + if (value !== undefined && value !== null) { + if (typeof value === 'number') { + const str = value.toExponential(exp) + const [f, e] = str.split('e') + if (e <= fixed && e >= -fixed) { + html = value.toFixed(fixed).replace(/[.,]0*$/, '') + } else { + html = <span>{f.toString().replace(/0*$/, '')}·10<sup>{e}</sup></span> + } + } else { + html = value.toString() + } + } + return <Typography {...props} variant={variant} >{html}</Typography> +} +Number.propTypes = ({ + value: PropTypes.any +}) + +function MatrixPagination({length, page, onChange}) { + return <MuiGrid + spacing={2} + container + direction="row" + justify="center" + alignItems="center" + > + <MuiGrid item> + <Button disabled={page === 0} size="small" onClick={() => onChange(page - 1)}> + prev + </Button> + </MuiGrid> + <MuiGrid item> + <Typography>{page}</Typography> + </MuiGrid> + <MuiGrid item> + <Button size="small" disabled={page + 1 === length} onClick={() => onChange(page + 1)}> + next + </Button> + </MuiGrid> + </MuiGrid> +} +MatrixPagination.propTypes = ({ + length: PropTypes.number.isRequired, + page: PropTypes.number.isRequired, + onChange: PropTypes.func.isRequired +}) + +const useMatrixStyles = makeStyles(theme => ({ + root: { + display: 'flex', + flexDirection: 'row', + alignItems: 'stretch', + minWidth: 300, + justifyContent: 'center' + }, + leftBracket: { + width: theme.spacing(1), + border: 'solid 2px black', + borderRight: 'none', + marginRight: -theme.spacing(1) + }, + matrix: { + width: '100%' + }, + rightBracket: { + width: theme.spacing(1), + border: 'solid 2px black', + borderLeft: 'none', + marginLeft: -theme.spacing(1) + } +})) +export function Matrix({values, shape, invert}) { + const rootRef = useRef() + const matrixRef = useRef() + const [pages, setPages] = useState(new Array(Math.max(0, shape.length - 2)).fill(0)) + const pageLengths = [] + const classes = useMatrixStyles() + + let ii = 0 + for (let i = shape.length; i > 2; i--) { + pageLengths.push(values.length) + values = values[pages[ii++]] + } + + const columnWidth = 92 + const rowHeight = 24 + const rowCount = invert ? values.length : shape.length > 1 ? values[0].length : 1 + const columnCount = invert ? shape.length > 1 ? values[0].length : 1 : values.length + const height = Math.min(300, rowCount * rowHeight) + + useLayoutEffect(() => { + matrixRef.current.style.width = Math.min( + rootRef.current.clientWidth - 4, columnCount * columnWidth) + 'px' + }) + + let value = shape.length > 1 ? ({rowIndex, columnIndex}) => values[columnIndex][rowIndex] : ({columnIndex}) => values[columnIndex] + if (invert) { + value = shape.length > 1 ? ({rowIndex, columnIndex}) => values[rowIndex][columnIndex] : ({rowIndex}) => values[rowIndex] + } + + return <React.Fragment> + <div ref={rootRef} className={classes.root}> + <div className={classes.leftBracket} style={{height: height}}> </div> + <div ref={matrixRef} className={classes.matrix}> + <AutoSizer> + {({width}) => ( + <Grid + columnCount={columnCount} + columnWidth={columnWidth} + height={height} + rowCount={rowCount} + rowHeight={rowHeight} + width={width} + > + {({style, ...props}) => <Number style={style} value={value(props)} />} + </Grid> + )} + </AutoSizer> + </div> + <div className={classes.rightBracket} style={{height: height}}> </div> + </div> + {pages.map((page, index) => <Box margin={1} key={index}> + <MatrixPagination + length={pageLengths[index]} page={page} + onChange={(page) => setPages([...pages.slice(0, index), page, ...pages.slice(index + 1)])} + /> + </Box>)} + </React.Fragment> +} +Matrix.propTypes = ({ + values: PropTypes.array.isRequired, + shape: PropTypes.arrayOf(PropTypes.any).isRequired, + invert: PropTypes.bool +}) \ No newline at end of file diff --git a/gui/yarn.lock b/gui/yarn.lock index 4799d110507983f3dd8692fd8489e3dcf3592466..b0e196c7e3ccce4f19710c1afd171ca1d6295422 100644 --- a/gui/yarn.lock +++ b/gui/yarn.lock @@ -3258,11 +3258,6 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clsx@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" - integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== - clsx@^1.0.2, clsx@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702" @@ -3914,11 +3909,6 @@ csstype@^2.2.0, csstype@^2.5.2, csstype@^2.6.5, csstype@^2.6.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== -csstype@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.2.tgz#ee5ff8f208c8cd613b389f7b222c9801ca62b3f7" - integrity sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw== - cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -4436,14 +4426,6 @@ dom-converter@^0.2: dependencies: utila "~0.4" -dom-helpers@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" - integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - dom-helpers@^5.0.1: version "5.1.4" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.4.tgz#4609680ab5c79a45f2531441f1949b79d6587f4b" @@ -7752,7 +7734,7 @@ longest-streak@^2.0.1: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7901,6 +7883,11 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" +"memoize-one@>=3.1.1 <6": + version "5.1.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" + integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== + memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -10336,17 +10323,18 @@ react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react-virtualized@^9.21.2: - version "9.21.2" - resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.2.tgz#02e6df65c1e020c8dbf574ec4ce971652afca84e" - integrity sha512-oX7I7KYiUM7lVXQzmhtF4Xg/4UA5duSA+/ZcAvdWlTLFCoFYq1SbauJT5gZK9cZS/wdYR6TPGpX/dqzvTqQeBA== +react-virtualized-auto-sizer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd" + integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg== + +react-window@^1.8.5: + version "1.8.5" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1" + integrity sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q== dependencies: - babel-runtime "^6.26.0" - clsx "^1.0.1" - dom-helpers "^5.0.0" - loose-envify "^1.3.0" - prop-types "^15.6.0" - react-lifecycles-compat "^3.0.4" + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" react@^16.13.1: version "16.13.1"