Commit 6cd2d4f7 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Merge branch 'overview-alternative' into 'v0.10.0'

Overview alternative

See merge request !262
parents 6a69a801 935245f7
Pipeline #94009 passed with stages
in 13 minutes and 16 seconds
......@@ -20,7 +20,7 @@ import PropTypes from 'prop-types'
import { Tooltip, IconButton, Button, Box, makeStyles } from '@material-ui/core'
import clsx from 'clsx'
export default function Actions({actions, variant, size, justifyContent, className, classes}) {
export default function Actions({actions, color, variant, size, justifyContent, className, classes}) {
const actionsStyles = makeStyles((theme) => ({
root: {
display: 'flex',
......@@ -28,7 +28,6 @@ export default function Actions({actions, variant, size, justifyContent, classNa
justifyContent: justifyContent
},
iconButton: {
backgroundColor: 'white',
marginRight: theme.spacing(1)
}
}))
......@@ -37,6 +36,7 @@ export default function Actions({actions, variant, size, justifyContent, classNa
return <Tooltip key={idx} title={value.tooltip}>
{variant === 'icon'
? <IconButton
color={color}
size={size}
className={styles.iconButton}
onClick={value.onClick}
......@@ -45,6 +45,7 @@ export default function Actions({actions, variant, size, justifyContent, classNa
{value.content}
</IconButton>
: <Button
color={color}
variant={variant}
size={size}
className={styles.iconButton}
......@@ -63,6 +64,7 @@ export default function Actions({actions, variant, size, justifyContent, classNa
Actions.propTypes = {
actions: PropTypes.array,
color: PropTypes.string,
variant: PropTypes.string,
size: PropTypes.string,
justifyContent: PropTypes.string,
......
......@@ -136,7 +136,11 @@ class Quantity extends React.Component {
value = 'unavailable'
}
if ((!value || value === 'unavailable') && hideIfUnavailable) {
if (value === 'unavailable') {
value = ''
}
if (!value && hideIfUnavailable) {
return ''
}
......
......@@ -35,6 +35,7 @@ import Markdown from '../Markdown'
import { UnitSelector } from './UnitSelector'
import { convertSI } from '../../utils'
import { conversionMap } from '../../units'
import { electronicRange } from '../../config'
export const configState = atom({
key: 'config',
......@@ -378,17 +379,25 @@ QuantityValue.propTypes = ({
function Overview({section, def}) {
// States
const [mode, setMode] = useState('bs')
const units = useRecoilValue(unitsState)
// Styles
const useStyles = makeStyles(
{
bands: {
width: '30rem',
height: '30rem'
height: '30rem',
margin: 'auto'
},
structure: {
width: '20rem',
height: '20rem',
margin: 'auto'
},
dos: {
width: '20rem',
height: '40rem'
height: '40rem',
margin: 'auto'
},
radio: {
display: 'flex',
......@@ -412,80 +421,97 @@ function Overview({section, def}) {
let tmpIndex = tmp.indexOf('/')
let index = tmpIndex === -1 ? tmp : tmp.slice(0, tmpIndex)
let system
// The section is incomplete, we leave the overview empty
if (!section.atom_species) {
return null
}
const nAtoms = section.atom_species.length
// Loading exact same system, no need to reload visualizer
if (sectionPath === visualizedSystem.sectionPath && index === visualizedSystem.index) {
// Loading same system with different positions
} else if (sectionPath === visualizedSystem.sectionPath && nAtoms === visualizedSystem.nAtoms) {
system = {
positions: convertSI(section.atom_positions, 'meter', {length: 'angstrom'}, false)
}
// Loading a completely new system. When trying to visualize the system for
// the first time, check the system size and for large systems ask the user
// for permission.
} else {
system = {
'species': section.atom_species,
'cell': section.lattice_vectors ? convertSI(section.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
'positions': convertSI(section.atom_positions, 'meter', {length: 'angstrom'}, false),
'pbc': section.configuration_periodic_dimensions
}
let system = {
'species': section.atom_species,
'cell': section.lattice_vectors ? convertSI(section.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
'positions': convertSI(section.atom_positions, 'meter', {length: 'angstrom'}, false),
'pbc': section.configuration_periodic_dimensions
}
visualizedSystem.sectionPath = sectionPath
visualizedSystem.index = index
visualizedSystem.nAtoms = nAtoms
return <Structure
aspectRatio={1}
className={style.structure}
viewer={viewer}
system={system}
positionsOnly={true}
></Structure>
// Structure visualization for idealized_structure
} else if (def.name === 'IdealizedStructure') {
// The section is incomplete, we leave the overview empty
if (!section.atom_labels) {
return null
}
const system = {
species: section.atom_labels,
cell: section.lattice_vectors ? convertSI(section.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
positions: section.atom_positions,
fractional: true,
pbc: section.periodicity
}
return <Structure
system={system}
className={style.structure}
viewer={viewer}
aspectRatio={1}>
</Structure>
// Band structure plot for section_k_band
} else if (def.name === 'KBand') {
return <>
{mode === 'bs'
? <Box>
<BandStructure
return section.band_structure_kind !== 'vibrational'
? <>
{mode === 'bs'
? <Box>
<BandStructure
className={style.bands}
data={section}
layout={{yaxis: {autorange: false, range: convertSI(electronicRange, 'electron_volt', units, false)}}}
aspectRatio={1}
unitsState={unitsState}
></BandStructure>
</Box>
: <BrillouinZone
viewer={bzViewer}
className={style.bands}
data={section}
aspectRatio={1}
unitsState={unitsState}
></BandStructure>
</Box>
: <BrillouinZone
viewer={bzViewer}
></BrillouinZone>
}
<FormControl component="fieldset" className={style.radio}>
<RadioGroup row aria-label="position" name="position" defaultValue="bs" onChange={toggleMode} className={style.radio}>
<FormControlLabel
value="bs"
control={<Radio color="primary" />}
label="Band structure"
labelPlacement="end"
/>
<FormControlLabel
value="bz"
control={<Radio color="primary" />}
label="Brillouin zone"
labelPlacement="end"
/>
</RadioGroup>
</FormControl>
</>
: <Box>
<BandStructure
className={style.bands}
data={section}
aspectRatio={1}
></BrillouinZone>
}
<FormControl component="fieldset" className={style.radio}>
<RadioGroup row aria-label="position" name="position" defaultValue="bs" onChange={toggleMode} className={style.radio}>
<FormControlLabel
value="bs"
control={<Radio color="primary" />}
label="Band structure"
labelPlacement="end"
/>
<FormControlLabel
value="bz"
control={<Radio color="primary" />}
label="Brillouin zone"
labelPlacement="end"
/>
</RadioGroup>
</FormControl>
</>
unitsState={unitsState}
></BandStructure>
</Box>
// DOS plot for section_dos
} else if (def.name === 'Dos') {
return <DOS
className={style.dos}
layout={section.dos_kind === 'vibrational' ? undefined : {yaxis: {autorange: false, range: convertSI(electronicRange, 'electron_volt', units, false)}}}
data={section}
aspectRatio={1 / 2}
unitsState={unitsState}
......
......@@ -126,6 +126,25 @@ export function resolveRef(ref, data) {
}
}
/**
* Converts a reference given in the /section/<index>/subsection format (used
* in the metainfo) to the /section:<index>/subsection format (used by the
* archive browser).
* @param {*} ref The reference to convert.
*/
export function refPath(ref) {
try {
const segments = ref.split('/').filter(segment => segment !== '')
const reducer = (current, segment) => {
return isNaN(segment) ? `${current}/${segment}` : `${current}:${segment}`
}
return segments.reduce(reducer)
} catch (e) {
console.log('could not convert the path: ' + ref)
throw e
}
}
export function metainfoDef(name) {
return packageDefs['nomad.metainfo.metainfo']._sections[name]
}
......
......@@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useContext, useState, useEffect, useMemo } from 'react'
import React, { useContext, useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { Box, Card, CardContent, Grid, Typography, Link, makeStyles, Divider } from '@material-ui/core'
import { apiContext } from '../api'
......@@ -32,7 +32,7 @@ import { DOI } from '../search/DatasetList'
import { domains } from '../domains'
import { errorContext } from '../errors'
import { authorList, convertSI, mergeObjects } from '../../utils'
import { resolveRef } from '../archive/metainfo'
import { resolveRef, refPath } from '../archive/metainfo'
import _ from 'lodash'
import {appBase, encyclopediaEnabled, normalizeDisplayValue} from '../../config'
......@@ -51,10 +51,10 @@ const useHeaderStyles = makeStyles(theme => ({
}))
function Header({title, actions}) {
const classes = useHeaderStyles()
return <Box className={classes.root}>
const styles = useHeaderStyles()
return <Box className={styles.root}>
<Box flexGrow={1}>
<Typography className={classes.title}>
<Typography className={styles.title}>
{title}
</Typography>
</Box>
......@@ -106,7 +106,7 @@ const useSidebarCardStyles = makeStyles(theme => ({
function SidebarCard({title, actions, children}) {
const classes = useSidebarCardStyles()
return <CardContent className={classes.content}>
<Header title={title} actions={actions}></Header>
{(title || actions) && <Header title={title} actions={actions}></Header>}
{children}
</CardContent>
}
......@@ -130,12 +130,15 @@ const useStyles = makeStyles(theme => ({
sidebar: {
paddingRight: theme.spacing(3)
},
actions: {
marginBottom: theme.spacing(2)
},
divider: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1)
},
materialText: {
height: '100%',
display: 'flex',
justifyContent: 'space-between',
flexDirection: 'column'
}
}))
......@@ -143,17 +146,18 @@ const useStyles = makeStyles(theme => ({
* Shows an informative overview about the selected entry.
*/
export default function DFTEntryOverview({data}) {
const classes = useStyles()
const {api} = useContext(apiContext)
const {raiseError} = useContext(errorContext)
const [electronicStructure, setElectronicStructure] = useState(null)
const [vibrationalData, setVibrationalData] = useState(null)
const [geoOpt, setGeoOpt] = useState(null)
const [structures, setStructures] = useState(null)
const materialType = data?.encyclopedia?.material?.material_type
const [method, setMethod] = useState(null)
const [loading, setLoading] = useState(true)
const [showAPIDialog, setShowAPIDialog] = useState(false)
const materialType = data?.encyclopedia?.material?.material_type
const styles = useStyles()
// When loaded for the first time, start downloading the archive. Once
// finished, determine the final layout based on it's contents. TODO: When we
......@@ -162,6 +166,7 @@ export default function DFTEntryOverview({data}) {
useEffect(() => {
api.archive(data.upload_id, data.calc_id).then(archive => {
let structs = {}
const url = `/entry/id/${data.upload_id}/${data.calc_id}/archive`
// Figure out what properties are present by looping over the SCCS. This
// information will eventually be directly available in the ES index.
......@@ -175,25 +180,23 @@ export default function DFTEntryOverview({data}) {
const scc = sccs[i]
if (!e_dos && scc.section_dos) {
const first_dos = scc.section_dos[scc.section_dos.length - 1]
if (!_.isEmpty(first_dos)) {
if (first_dos.dos_kind !== 'vibrational') {
e_dos = {
'section_system': scc.single_configuration_calculation_to_system_ref,
'section_method': scc.single_configuration_calculation_to_system_ref,
'section_dos': scc.section_dos[scc.section_dos.length - 1]
}
if (first_dos.dos_kind !== 'vibrational') {
e_dos = {
'section_system': scc.single_configuration_calculation_to_system_ref,
'section_method': scc.single_configuration_calculation_to_system_ref,
'section_dos': scc.section_dos[scc.section_dos.length - 1],
'path': `${url}/section_run/section_single_configuration_calculation:${i}/section_dos:${scc.section_dos.length - 1}`
}
}
}
if (!e_bs && scc.section_k_band) {
const first_band = scc.section_k_band[scc.section_k_band.length - 1]
if (!_.isEmpty(first_band)) {
if (first_band.band_structure_kind !== 'vibrational') {
e_bs = {
'section_system': scc.single_configuration_calculation_to_system_ref,
'section_method': scc.single_configuration_calculation_to_system_ref,
'section_k_band': scc.section_k_band[scc.section_k_band.length - 1]
}
if (first_band.band_structure_kind !== 'vibrational') {
e_bs = {
'section_system': scc.single_configuration_calculation_to_system_ref,
'section_method': scc.single_configuration_calculation_to_system_ref,
'section_k_band': scc.section_k_band[scc.section_k_band.length - 1],
'path': `${url}/section_run/section_single_configuration_calculation:${i}/section_k_band:${scc.section_k_band.length - 1}`
}
}
}
......@@ -243,13 +246,13 @@ export default function DFTEntryOverview({data}) {
let sys = calc?.single_configuration_calculation_to_system_ref
sys = resolveRef(sys, archive)
trajectory.push({
'species': sys.atom_species,
'cell': sys.lattice_vectors ? convertSI(sys.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
'positions': convertSI(sys.atom_positions, 'meter', {length: 'angstrom'}, false),
'pbc': sys.configuration_periodic_dimensions
species: sys.atom_species,
cell: sys.lattice_vectors ? convertSI(sys.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
positions: convertSI(sys.atom_positions, 'meter', {length: 'angstrom'}, false),
pbc: sys.configuration_periodic_dimensions
})
}
if (!failed && energies.length > 2) {
if (!failed) {
energies = convertSI(energies, 'joule', {energy: 'electron_volt'}, false)
const e_criteria_wf = section_wf?.section_geometry_optimization?.input_energy_difference_tolerance
const sampling_method = section_run?.section_sampling_method
......@@ -265,14 +268,16 @@ export default function DFTEntryOverview({data}) {
let v_bs = null
if (scc) {
v_bs = {
'section_system': scc.single_configuration_calculation_to_system_ref,
'section_method': scc.single_configuration_calculation_to_system_ref,
'section_k_band': scc.section_k_band[scc.section_k_band.length - 1]
section_system: scc.single_configuration_calculation_to_system_ref,
section_method: scc.single_configuration_calculation_to_system_ref,
section_k_band: scc.section_k_band[scc.section_k_band.length - 1],
path: `${url}/${refPath(scc_ref)}/section_k_band:${scc.section_k_band.length - 1}`
}
v_dos = {
'section_system': scc.single_configuration_calculation_to_system_ref,
'section_method': scc.single_configuration_calculation_to_system_ref,
'section_dos': scc.section_dos[scc.section_dos.length - 1]
section_system: scc.single_configuration_calculation_to_system_ref,
section_method: scc.single_configuration_calculation_to_system_ref,
section_dos: scc.section_dos[scc.section_dos.length - 1],
path: `${url}/${refPath(scc_ref)}/section_dos:${scc.section_dos.length - 1}`
}
}
......@@ -285,19 +290,25 @@ export default function DFTEntryOverview({data}) {
if (sequence) {
const properties = sequence.section_thermodynamical_properties && sequence.section_thermodynamical_properties[0]
if (properties) {
heat_capacity = properties.thermodynamical_property_heat_capacity_C_v
free_energy = properties.vibrational_free_energy_at_constant_volume
heat_capacity = {
thermodynamical_property_heat_capacity_C_v: properties.thermodynamical_property_heat_capacity_C_v,
path: `${url}/section_run/section_frame_sequence:${sequences.length - 1}/section_thermodynamical_properties/thermodynamical_property_heat_capacity_C_v`
}
free_energy = {
vibrational_free_energy_at_constant_volume: properties.vibrational_free_energy_at_constant_volume,
path: `${url}/section_run/section_frame_sequence:${sequences.length - 1}/section_thermodynamical_properties/vibrational_free_energy_at_constant_volume`
}
temperature = properties.thermodynamical_property_temperature
}
}
if (v_dos || v_bs || free_energy || heat_capacity) {
setVibrationalData({
'dos': v_dos,
'bs': v_bs,
'free_energy': free_energy,
'heat_capacity': heat_capacity,
'temperature': temperature
dos: v_dos,
bs: v_bs,
free_energy: free_energy,
heat_capacity: heat_capacity,
temperature: temperature
})
}
}
......@@ -343,10 +354,11 @@ export default function DFTEntryOverview({data}) {
const sys = systems[i]
if (!reprSys && sys.is_representative) {
const reprSys = {
'species': sys.atom_species,
'cell': sys.lattice_vectors ? convertSI(sys.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
'positions': convertSI(sys.atom_positions, 'meter', {length: 'angstrom'}, false),
'pbc': sys.configuration_periodic_dimensions
species: sys.atom_species,
cell: sys.lattice_vectors ? convertSI(sys.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
positions: convertSI(sys.atom_positions, 'meter', {length: 'angstrom'}, false),
pbc: sys.configuration_periodic_dimensions,
path: `${url}/section_run/section_system:${i}`
}
structs.original = reprSys
break
......@@ -358,11 +370,12 @@ export default function DFTEntryOverview({data}) {
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,
'positions': idealSys.atom_positions,
'fractional': true,
'pbc': idealSys.periodicity
species: idealSys.atom_labels,
cell: idealSys.lattice_vectors ? convertSI(idealSys.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
positions: idealSys.atom_positions,
fractional: true,
pbc: idealSys.periodicity,
path: `${url}/section_metadata/encyclopedia/material/idealized_structure`
}
structs.conventional = ideal
}
......@@ -380,36 +393,12 @@ export default function DFTEntryOverview({data}) {
const quantityProps = {data: data, loading: !data}
const domain = data.domain && domains[data.domain]
// Figure out which actions are available for this entry
const actions = useMemo(() => {
const buttons = []
if (encyclopediaEnabled && data?.encyclopedia?.material?.material_id) {
buttons.push(
{
tooltip: 'View more information about this material',
content: 'Material',
href: `${appBase}/encyclopedia/#/material/${data.encyclopedia.material.material_id}`
}
)
}
buttons.push(
{
tooltip: 'Show the API access code',
onClick: (event) => { setShowAPIDialog(!showAPIDialog) },
content: 'API access'
}
)
return buttons
}, [data, showAPIDialog])
return (
<RecoilRoot>
<Grid container spacing={0} className={classes.root}>
<Grid container spacing={0} className={styles.root}>
{/* Left column */}
<Grid item xs={4} className={classes.sidebar}>
<ApiDialog data={data} open={showAPIDialog} onClose={() => { setShowAPIDialog(false) }}></ApiDialog>
<Actions className={classes.actions} justifyContent='flex-start' variant='contained' size='medium' actions={actions}></Actions>
<Grid item xs={4} className={styles.sidebar}>
<SidebarCard title='Method'>
<Quantity flex>
<Quantity quantity="dft.code_name" label='code name' noWrap {...quantityProps}/>
......@@ -430,7 +419,7 @@ export default function DFTEntryOverview({data}) {
{method?.relativity_method && <Quantity quantity="relativity_method" label='relativity method' noWrap data={method}/>}
</Quantity>
</SidebarCard>
<Divider className={classes.divider} />
<Divider className={styles.divider} />
<SidebarCard title='Author metadata'>
<Quantity flex>
<Quantity quantity='comment' placeholder='no comment' {...quantityProps} />
......@@ -459,8 +448,8 @@ export default function DFTEntryOverview({data}) {
</Quantity>
</Quantity>
</SidebarCard>
<Divider className={classes.divider}/>
<SidebarCard title='Processing information'>
<Divider className={styles.divider}/>
<SidebarCard>
<Quantity column style={{maxWidth: 350}}>
<Quantity quantity="mainfile" noWrap ellipsisFront withClipboard {...quantityProps}/>
<Quantity quantity="calc_id" label={`${domain ? domain.entryLabel : 'entry'} id`} noWrap withClipboard {...quantityProps}/>
......@@ -485,6 +474,19 @@ export default function DFTEntryOverview({data}) {
</Quantity>
</Quantity>
</SidebarCard>
<ApiDialog data={data} open={showAPIDialog} onClose={() => { setShowAPIDialog(false) }}></ApiDialog>
<Actions
justifyContent='flex-end'
variant='outlined'
color='primary'
size='medium'
actions={[{
tooltip: 'Show the API access code',
onClick: (event) => { setShowAPIDialog(!showAPIDialog) },
content: 'API'
}]}
>
</Actions>
</Grid>
{/* Right column */}
......@@ -492,7 +494,7 @@ export default function DFTEntryOverview({data}) {
<PropertyCard title="Material">
<Grid container spacing={1}>
<Grid item xs={5}>
<Box>
<Box className={styles.materialText}>
<Quantity column>
<Quantity quantity="formula" label='formula' noWrap {...quantityProps}/>
<Quantity quantity="dft.system" label='material type' noWrap {...quantityProps