Commit 725ab1dc authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Integrated structure toggles into the structure component.

parent 65e7cbc5
Pipeline #92800 passed with stages
in 25 minutes and 29 seconds
......@@ -25,7 +25,7 @@ import Browser, { Item, Content, Compartment, List, Adaptor } from './Browser'
import { resolveRef, rootSections } from './metainfo'
import { Title, metainfoAdaptorFactory, DefinitionLabel } from './MetainfoBrowser'
import { Matrix, Number } from './visualizations'
import Structure from '../visualization/Structure'
import { Structure } from '../visualization/Structure'
import BrillouinZone from '../visualization/BrillouinZone'
import BandStructure from '../visualization/BandStructure'
import { ErrorHandler } from '../ErrorHandler'
......
......@@ -18,12 +18,11 @@
import React, { useContext, useState, useEffect, useMemo } from 'react'
import PropTypes from 'prop-types'
import { Box, Card, CardContent, Grid, Typography, Link, makeStyles, Divider } from '@material-ui/core'
import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'
import { apiContext } from '../api'
import ElectronicStructureOverview from '../visualization/ElectronicStructureOverview'
import VibrationalOverview from '../visualization/VibrationalOverview'
import { ApiDialog } from '../ApiDialogButton'
import Structure from '../visualization/Structure'
import { Structure } from '../visualization/Structure'
import Actions from '../Actions'
import Quantity from '../Quantity'
import { Link as RouterLink } from 'react-router-dom'
......@@ -148,7 +147,7 @@ export default function DFTEntryOverview({data}) {
const [electronicStructure, setElectronicStructure] = useState(null)
const [vibrationalData, setVibrationalData] = useState(null)
const [geoOpt, setGeoOpt] = useState(null)
const [shownSystem, setShownSystem] = useState('original')
// const [shownSystem, setShownSystem] = useState('original')
const [structures, setStructures] = useState(null)
const materialType = data?.encyclopedia?.material?.material_type
const [method, setMethod] = useState(null)
......@@ -160,14 +159,14 @@ export default function DFTEntryOverview({data}) {
// have more information stored in the ES index, it can be used to select
// which parts of the Archive should be downloaded to reduce bandwidth.
useEffect(() => {
api.archive(data.upload_id, data.calc_id).then(data => {
let structs = new Map()
api.archive(data.upload_id, data.calc_id).then(archive => {
let structs = {}
// Figure out what properties are present by looping over the SCCS. This
// information will eventually be directly available in the ES index.
let e_dos = null
let e_bs = null
const section_run = data.section_run[0]
const section_run = archive.section_run[0]
let section_method = null
const sccs = section_run.section_single_configuration_calculation
for (let i = sccs.length - 1; i > -1; --i) {
......@@ -207,7 +206,7 @@ export default function DFTEntryOverview({data}) {
}
// See if there are workflow results
const section_wf = data.section_workflow
const section_wf = archive.section_workflow
if (section_wf) {
const wfType = section_wf.workflow_type
......@@ -222,7 +221,7 @@ export default function DFTEntryOverview({data}) {
let failed = false
for (let i = 0; i < calculations.length; ++i) {
let ref = calculations[i]
const calc = resolveRef(ref, data)
const calc = resolveRef(ref, archive)
const e = calc?.energy_total
if (e === undefined) {
failed = true
......@@ -233,7 +232,7 @@ export default function DFTEntryOverview({data}) {
}
energies.push(e - initialEnergy)
let sys = calc?.single_configuration_calculation_to_system_ref
sys = resolveRef(sys, data)
sys = resolveRef(sys, archive)
trajectory.push({
'species': sys.atom_species,
'cell': sys.lattice_vectors ? convertSI(sys.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
......@@ -252,7 +251,7 @@ export default function DFTEntryOverview({data}) {
} else if (wfType === 'phonon') {
// Find phonon dos and dispersion
const scc_ref = section_wf.calculation_result_ref
const scc = resolveRef(scc_ref, data)
const scc = resolveRef(scc_ref, archive)
let v_dos = null
let v_bs = null
if (scc) {
......@@ -298,12 +297,12 @@ export default function DFTEntryOverview({data}) {
// Get method details. Any referenced core_setttings will also be taken
// into account
if (section_method) {
section_method = resolveRef(section_method, data)
section_method = resolveRef(section_method, archive)
const refs = section_method?.section_method_to_method_refs
if (refs) {
for (const ref of refs) {
if (ref.method_to_method_kind === 'core_settings') {
section_method = mergeObjects(resolveRef(ref.method_to_method_ref, data), section_method)
section_method = mergeObjects(resolveRef(ref.method_to_method_ref, archive), section_method)
}
}
}
......@@ -331,14 +330,14 @@ export default function DFTEntryOverview({data}) {
'positions': convertSI(sys.atom_positions, 'meter', {length: 'angstrom'}, false),
'pbc': sys.configuration_periodic_dimensions
}
structs.set('original', reprSys)
structs.original = reprSys
break
}
}
// Get the conventional (=normalized) system, if present
let idealSys = data?.section_metadata?.encyclopedia?.material?.idealized_structure
if (idealSys && data.system === 'bulk') {
let idealSys = archive?.section_metadata?.encyclopedia?.material?.idealized_structure
if (idealSys && data?.dft?.system === 'bulk') {
const ideal = {
'species': idealSys.atom_labels,
'cell': idealSys.lattice_vectors ? convertSI(idealSys.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
......@@ -346,9 +345,8 @@ export default function DFTEntryOverview({data}) {
'fractional': true,
'pbc': idealSys.periodicity
}
structs.set('conventional', ideal)
structs.conventional = ideal
}
setStructures(structs)
setLoading(false)
}).catch(error => {
......@@ -363,25 +361,6 @@ export default function DFTEntryOverview({data}) {
const quantityProps = {data: data, loading: !data}
const domain = data.domain && domains[data.domain]
// Create toggle buttons for each structure option
const structureToggles = useMemo(() => {
if (structures) {
const toggles = []
structures.forEach((value, key) => {
toggles.push(<ToggleButton key={key} value={key} aria-label={key}>{key}</ToggleButton>)
})
return toggles
}
return null
}, [structures])
// Enforce at least one structure view option
const handleStructureChange = (event, value) => {
if (value !== null) {
setShownSystem(value)
}
}
// Figure out which actions are available for this entry
const actions = useMemo(() => {
const buttons = [
......@@ -414,7 +393,14 @@ export default function DFTEntryOverview({data}) {
<Quantity flex>
<Quantity quantity="dft.code_name" label='code name' noWrap {...quantityProps}/>
<Quantity quantity="dft.code_version" label='code version' noWrap {...quantityProps}/>
<Quantity quantity="electronic_structure_method" label='electronic structure method' loading={loading} description="The used electronic structure method." noWrap data={method}/>
<Quantity
quantity="electronic_structure_method"
label='electronic structure method'
loading={loading}
description="The used electronic structure method."
noWrap
data={method}
/>
<Quantity quantity="dft.xc_functional" label='xc functional family' noWrap {...quantityProps}/>
<Quantity quantity="dft.xc_functional_names" label='xc functional names' noWrap {...quantityProps}/>
<Quantity quantity="dft.basis_set" label='basis set type' noWrap {...quantityProps}/>
......@@ -501,21 +487,8 @@ export default function DFTEntryOverview({data}) {
</Quantity>
</Box>
</Grid>
<Grid item xs={7}>
<>
{structureToggles?.length > 1 &&
<ToggleButtonGroup
size="small"
exclusive
value={shownSystem}
onChange={handleStructureChange}
aria-label="text formatting"
>
{structureToggles}
</ToggleButtonGroup>
}
<Structure system={structures && structures.get(shownSystem)} aspectRatio={1.5} />
</>
<Grid item xs={7} style={{marginTop: '-2rem'}}>
<Structure systems={structures} aspectRatio={1.5} />
</Grid>
</Grid>
</PropertyCard>
......
......@@ -25,7 +25,7 @@ import {
import { RecoilRoot } from 'recoil'
import { makeStyles } from '@material-ui/core/styles'
import Plot from '../visualization/Plot'
import Structure from '../visualization/Structure'
import { Structure } from '../visualization/Structure'
import { ErrorHandler, withErrorHandler } from '../ErrorHandler'
function GeoOptOverview({data, className, classes}) {
......
......@@ -179,7 +179,6 @@ export default function Plot({data, layout, config, floatTitle, capture, aspectR
// When the canvas reference is instantiated for the first time, create a
// new plot.
if (canvasRef.current === undefined) {
console.log('Loaded!')
Plotly.newPlot(node, data, finalLayout, finalConfig)
if (firstUpdate.current) {
firstUpdate.current = false
......@@ -188,7 +187,6 @@ export default function Plot({data, layout, config, floatTitle, capture, aspectR
// When the reference changes for the second time, react instead to save
// some time
} else {
console.log('Redraw!')
let oldLayout = canvasRef.current.layout
let oldData = canvasRef.current.data
Plotly.react(node, oldData, oldLayout, finalConfig)
......
......@@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState, useEffect, useRef, useCallback } from 'react'
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles } from '@material-ui/core/styles'
import {
......@@ -26,6 +26,7 @@ import {
Typography,
FormControlLabel
} from '@material-ui/core'
import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'
import {
MoreVert,
Fullscreen,
......@@ -46,7 +47,7 @@ import clsx from 'clsx'
* Used to show atomistic systems in an interactive 3D viewer based on the
* 'materia'-library.
*/
function Structure({className, classes, system, options, viewer, captureName, aspectRatio, positionsOnly, sizeLimit}) {
export const Structure = withErrorHandler(({className, classes, system, systems, options, viewer, captureName, aspectRatio, positionsOnly, sizeLimit}) => {
// States
const [anchorEl, setAnchorEl] = React.useState(null)
const [fullscreen, setFullscreen] = useState(false)
......@@ -58,6 +59,8 @@ function Structure({className, classes, system, options, viewer, captureName, as
const [accepted, setAccepted] = useState(false)
const [nAtoms, setNAtoms] = useState(false)
const [loading, setLoading] = useState(true)
const [shownSystem, setShownSystem] = useState(null)
const [finalSystem, setFinalSystem] = useState(system)
// Variables
const open = Boolean(anchorEl)
......@@ -80,15 +83,34 @@ function Structure({className, classes, system, options, viewer, captureName, as
flexDirection: 'row',
zIndex: 1
},
toggles: {
marginBottom: theme.spacing(1),
height: '2rem'
},
title: {
marginBottom: theme.spacing(1)
},
viewerCanvas: {
flex: 1,
zIndex: 0,
minHeight: 0, // added min-height: 0 to allow the item to shrink to fit inside the container.
marginBottom: theme.spacing(2)
marginBottom: theme.spacing(1)
}
}
})
const style = useStyles(classes)
const styles = useStyles(classes)
useEffect(() => {
setFinalSystem(system)
}, [system])
useEffect(() => {
if (systems) {
const firstSystem = Object.keys(systems)[0]
setFinalSystem(systems[firstSystem])
setShownSystem(firstSystem)
}
}, [systems])
// In order to properly detect changes in a reference, a reference callback is
// used. This is the recommended way to monitor reference changes as a simple
......@@ -115,7 +137,7 @@ function Structure({className, classes, system, options, viewer, captureName, as
view: {
autoResize: true,
autoFit: true,
fitMargin: 0.5
fitMargin: 0.6
},
bonds: {
enabled: true
......@@ -200,12 +222,12 @@ function Structure({className, classes, system, options, viewer, captureName, as
// Called whenever the given system changes. If positionsOnly is true, only
// updates the positions. Otherwise reloads the entire structure.
useEffect(() => {
if (system === undefined || system === null) {
if (!finalSystem) {
return
}
if (!accepted) {
const nAtoms = system.positions.length
const nAtoms = finalSystem.positions.length
setNAtoms(nAtoms)
if (nAtoms > 300) {
setShowPrompt(true)
......@@ -214,12 +236,12 @@ function Structure({className, classes, system, options, viewer, captureName, as
}
if (positionsOnly && !!(refViewer?.current?.structure)) {
refViewer.current.setPositions(system.positions)
refViewer.current.setPositions(finalSystem.positions)
return
}
loadSystem(system, refViewer)
}, [system, positionsOnly, loadSystem, accepted])
loadSystem(finalSystem, refViewer)
}, [finalSystem, positionsOnly, loadSystem, accepted])
// Viewer settings
useEffect(() => {
......@@ -261,6 +283,33 @@ function Structure({className, classes, system, options, viewer, captureName, as
refViewer.current.render()
}, [])
const structureToggles = useMemo(() => {
if (systems) {
const toggles = []
for (let key in systems) {
toggles.push(<ToggleButton key={key} value={key} aria-label={key}>{key}</ToggleButton>)
}
return toggles
}
return null
}, [systems])
// Enforce at least one structure view option
const handleStructureChange = (event, value) => {
if (value !== null) {
setShownSystem(value)
setFinalSystem(systems[value])
}
}
if (showPrompt) {
return <ErrorCard
message={`Visualization is by default disabled for systems with more than ${sizeLimit} atoms. Do you wish to enable visualization for this system with ${nAtoms} atoms?`}
className={styles.error}
actions={[{label: 'Yes', onClick: e => { setShowPrompt(false); loadSystem(finalSystem, refViewer); setAccepted(true) }}]}
>
</ErrorCard>
}
if (loading) {
return <Placeholder variant="rect" aspectRatio={aspectRatio}></Placeholder>
}
......@@ -273,95 +322,96 @@ function Structure({className, classes, system, options, viewer, captureName, as
{tooltip: 'Options', onClick: openMenu, content: <MoreVert/>}
]
const content = <Box className={style.container}>
{showPrompt
? <ErrorCard
message={`Visualization is by default disabled for systems with more than ${sizeLimit} atoms. Do you wish to enable visualization for this system with ${nAtoms} atoms?`}
className={style.error}
actions={[{label: 'Yes', onClick: e => { setShowPrompt(false); loadSystem(system, refViewer); setAccepted(true) }}]}
const content = <Box className={styles.container}>
{fullscreen && <Typography className={styles.title} variant="h6">Structure</Typography>}
{systems && <ToggleButtonGroup
className={styles.toggles}
size="small"
exclusive
value={shownSystem}
onChange={handleStructureChange}
>
{structureToggles}
</ToggleButtonGroup>
}
<div className={styles.viewerCanvas} ref={refCanvas}></div>
<div className={styles.header}>
<Actions actions={actions}></Actions>
<Menu
id='settings-menu'
anchorEl={anchorEl}
getContentAnchorEl={null}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
keepMounted
open={open}
onClose={closeMenu}
>
</ErrorCard>
: <>
{fullscreen && <Typography variant="h6">Structure</Typography>}
<div className={style.viewerCanvas} ref={refCanvas}></div>
<div className={style.header}>
<Actions actions={actions}></Actions>
<Menu
id='settings-menu'
anchorEl={anchorEl}
getContentAnchorEl={null}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
keepMounted
open={open}
onClose={closeMenu}
>
<MenuItem key='show-bonds'>
<FormControlLabel
control={
<Checkbox
checked={showBonds}
onChange={(event) => { setShowBonds(!showBonds) }}
color='primary'
/>
}
label='Show bonds'
<MenuItem key='show-bonds'>
<FormControlLabel
control={
<Checkbox
checked={showBonds}
onChange={(event) => { setShowBonds(!showBonds) }}
color='primary'
/>
</MenuItem>
<MenuItem key='show-axis'>
<FormControlLabel
control={
<Checkbox
checked={showLatticeConstants}
onChange={(event) => { setShowLatticeConstants(!showLatticeConstants) }}
color='primary'
/>
}
label='Show lattice constants'
}
label='Show bonds'
/>
</MenuItem>
<MenuItem key='show-axis'>
<FormControlLabel
control={
<Checkbox
checked={showLatticeConstants}
onChange={(event) => { setShowLatticeConstants(!showLatticeConstants) }}
color='primary'
/>
</MenuItem>
<MenuItem key='show-cell'>
<FormControlLabel
control={
<Checkbox
checked={showCell}
onChange={(event) => { setShowCell(!showCell) }}
color='primary'
/>
}
label='Show simulation cell'
}
label='Show lattice constants'
/>
</MenuItem>
<MenuItem key='show-cell'>
<FormControlLabel
control={
<Checkbox
checked={showCell}
onChange={(event) => { setShowCell(!showCell) }}
color='primary'
/>
</MenuItem>
<MenuItem key='wrap'>
<FormControlLabel
control={
<Checkbox
checked={wrap}
onChange={(event) => { setWrap(!wrap) }}
color='primary'
/>
}
label='Wrap positions'
}
label='Show simulation cell'
/>
</MenuItem>
<MenuItem key='wrap'>
<FormControlLabel
control={
<Checkbox
checked={wrap}
onChange={(event) => { setWrap(!wrap) }}
color='primary'
/>
</MenuItem>
</Menu>
</div>
</>
}
}
label='Wrap positions'
/>
</MenuItem>
</Menu>
</div>
</Box>
return <Box className={clsx(style.root, className)} >
return <Box className={clsx(styles.root, className)} >
<Floatable float={fullscreen} onFloat={toggleFullscreen} aspectRatio={aspectRatio}>
{content}
</Floatable>
</Box>
}
}, 'Could not load structure.')
Structure.propTypes = {
className: PropTypes.string,
classes: PropTypes.object,
viewer: PropTypes.object, // Optional shared viewer instance.
system: PropTypes.object, // The system to display in the native materia-format
systems: PropTypes.object, // Set of systems that can be switched
options: PropTypes.object, // Viewer options
captureName: PropTypes.string, // Name of the file that the user can download
aspectRatio: PropTypes.number, // Fixed aspect ratio for the viewer canvas
......@@ -373,5 +423,3 @@ Structure.defaultProps = {
captureName: 'structure',
sizeLimit: 300
}
export default withErrorHandler(Structure, 'Could not load structure.')
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment