Commit 599f5819 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

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

Overview improvements

See merge request !260
parents f2369025 00edbd00
Pipeline #93273 passed with stages
in 35 minutes and 46 seconds
......@@ -23,6 +23,7 @@ import ElectronicStructureOverview from '../visualization/ElectronicStructureOve
import VibrationalOverview from '../visualization/VibrationalOverview'
import { ApiDialog } from '../ApiDialogButton'
import { Structure } from '../visualization/Structure'
import NoData from '../visualization/NoData'
import Actions from '../Actions'
import Quantity from '../Quantity'
import { RecoilRoot } from 'recoil'
......@@ -169,32 +170,40 @@ export default function DFTEntryOverview({data}) {
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) {
const scc = sccs[i]
if (!e_dos && scc.section_dos) {
if (scc.section_dos[scc.section_dos.length - 1].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 (sccs) {
for (let i = sccs.length - 1; i > -1; --i) {
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 (!e_bs && scc.section_k_band) {
if (scc.section_k_band[scc.section_k_band.length - 1].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 (!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 (section_method !== false) {
let iMethod = scc.single_configuration_to_calculation_method_ref
if (section_method === null) {
section_method = iMethod
} else if (iMethod !== section_method) {
section_method = false
if (section_method !== false) {
let iMethod = scc.single_configuration_to_calculation_method_ref
if (section_method === null) {
section_method = iMethod
} else if (iMethod !== section_method) {
section_method = false
}
}
}
}
......@@ -295,9 +304,17 @@ export default function DFTEntryOverview({data}) {
}
// Get method details. Any referenced core_setttings will also be taken
// into account
// into account. If there were no SCCs from which the method could be
// selected, simply select the last available method.
if (section_method) {
section_method = resolveRef(section_method, archive)
} else {
const methods = section_run.section_method
if (methods) {
section_method = methods[methods.length - 1]
}
}
if (section_method) {
const refs = section_method?.section_method_to_method_refs
if (refs) {
for (const ref of refs) {
......@@ -321,17 +338,19 @@ export default function DFTEntryOverview({data}) {
// Get the representative system by looping over systems
let reprSys = null
const systems = section_run.section_system
for (let i = systems.length - 1; i > -1; --i) {
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
if (systems) {
for (let i = systems.length - 1; i > -1; --i) {
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
}
structs.original = reprSys
break
}
structs.original = reprSys
break
}
}
......@@ -363,13 +382,7 @@ export default function DFTEntryOverview({data}) {
// Figure out which actions are available for this entry
const actions = useMemo(() => {
const buttons = [
{
tooltip: 'Show the API access code',
onClick: (event) => { setShowAPIDialog(!showAPIDialog) },
content: 'API access'
}
]
const buttons = []
if (encyclopediaEnabled && data?.encyclopedia?.material?.material_id) {
buttons.push(
{
......@@ -379,6 +392,13 @@ export default function DFTEntryOverview({data}) {
}
)
}
buttons.push(
{
tooltip: 'Show the API access code',
onClick: (event) => { setShowAPIDialog(!showAPIDialog) },
content: 'API access'
}
)
return buttons
}, [data, showAPIDialog])
......@@ -489,7 +509,10 @@ export default function DFTEntryOverview({data}) {
</Box>
</Grid>
<Grid item xs={7} style={{marginTop: '-2rem'}}>
<Structure systems={structures} aspectRatio={1.5} />
{(loading || !_.isEmpty(structures))
? <Structure systems={structures} materialType={data?.dft?.system} aspectRatio={1.5}/>
: <NoData aspectRatio={1.5}/>
}
</Grid>
</Grid>
</PropertyCard>
......
......@@ -27,7 +27,7 @@ import Plot from '../visualization/Plot'
import { convertSI, distance, mergeObjects } from '../../utils'
import { withErrorHandler } from '../ErrorHandler'
function BandStructure({data, layout, aspectRatio, className, classes, onRelayout, onAfterPlot, onRedraw, onRelayouting, onReset, unitsState}) {
function BandStructure({data, layout, aspectRatio, className, classes, unitsState, ...other}) {
const [finalData, setFinalData] = useState(undefined)
const [pathSegments, setPathSegments] = useState(undefined)
const units = useRecoilValue(unitsState)
......@@ -246,11 +246,7 @@ function BandStructure({data, layout, aspectRatio, className, classes, onRelayou
layout={finalLayout}
aspectRatio={aspectRatio}
floatTitle={'Band structure'}
onRelayout={onRelayout}
onAfterPlot={onAfterPlot}
onRedraw={onRedraw}
onRelayouting={onRelayouting}
onReset={onReset}
{...other}
>
</Plot>
</Box>
......@@ -263,11 +259,6 @@ BandStructure.propTypes = {
aspectRatio: PropTypes.number,
classes: PropTypes.object,
className: PropTypes.string,
onAfterPlot: PropTypes.func,
onRedraw: PropTypes.func,
onRelayout: PropTypes.func,
onRelayouting: PropTypes.func,
onReset: PropTypes.func,
unitsState: PropTypes.object // Recoil atom containing the unit configuration
}
......
......@@ -27,7 +27,7 @@ import Plot from '../visualization/Plot'
import { convertSI, convertSILabel, mergeObjects } from '../../utils'
import { withErrorHandler } from '../ErrorHandler'
function DOS({data, layout, resetLayout, aspectRatio, className, classes, onRelayout, onAfterPlot, onRedraw, onRelayouting, onReset, unitsState}) {
function DOS({data, layout, resetLayout, aspectRatio, className, classes, unitsState, ...other}) {
const [finalData, setFinalData] = useState(undefined)
const units = useRecoilValue(unitsState)
......@@ -128,11 +128,7 @@ function DOS({data, layout, resetLayout, aspectRatio, className, classes, onRela
resetLayout={resetLayout}
aspectRatio={aspectRatio}
floatTitle="Density of states"
onRelayout={onRelayout}
onAfterPlot={onAfterPlot}
onRedraw={onRedraw}
onRelayouting={onRelayouting}
onReset={onReset}
{...other}
>
</Plot>
</Box>
......@@ -146,11 +142,6 @@ DOS.propTypes = {
aspectRatio: PropTypes.number,
classes: PropTypes.object,
className: PropTypes.string,
onAfterPlot: PropTypes.func,
onRedraw: PropTypes.func,
onRelayout: PropTypes.func,
onRelayouting: PropTypes.func,
onReset: PropTypes.func,
unitsState: PropTypes.object // Recoil atom containing the unit configuration
}
......
......@@ -15,13 +15,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState, useCallback } from 'react'
import React, { useState, useCallback, useMemo } from 'react'
import { Subject } from 'rxjs'
import PropTypes from 'prop-types'
import {
Box,
Typography
} from '@material-ui/core'
import DOS from './DOS'
import NoData from './NoData'
import BandStructure from './BandStructure'
import BrillouinZone from './BrillouinZone'
import { RecoilRoot } from 'recoil'
......@@ -38,6 +40,10 @@ function ElectronicStructureOverview({data, range, className, classes, raiseErro
yaxis: {range: range}
})
// RxJS subject for efficiently propagating y axis changes between DOS and BS
const bsYSubject = useMemo(() => new Subject(), [])
const dosYSubject = useMemo(() => new Subject(), [])
// Styles
const useStyles = makeStyles((theme) => {
return {
......@@ -45,7 +51,7 @@ function ElectronicStructureOverview({data, range, className, classes, raiseErro
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
alignItems: 'flex-start',
width: '100%',
height: '100%',
flexWrap: 'wrap'
......@@ -66,58 +72,50 @@ function ElectronicStructureOverview({data, range, className, classes, raiseErro
// Synchronize panning between BS/DOS plots
const handleBSRelayouting = useCallback((event) => {
if (data.dos) {
let update = {
yaxis: {
autorange: false,
range: [event['yaxis.range[0]'], event['yaxis.range[1]']]
}
}
setDosLayout(update)
let update = {yaxis: {range: [event['yaxis.range[0]'], event['yaxis.range[1]']]}}
bsYSubject.next(update)
}
}, [data])
}, [data, bsYSubject])
const handleDOSRelayouting = useCallback((event) => {
if (data.bs) {
let update = {
yaxis: {
autorange: false,
range: [event['yaxis.range[0]'], event['yaxis.range[1]']]
}
}
setBsLayout(update)
let update = {yaxis: {range: [event['yaxis.range[0]'], event['yaxis.range[1]']]}}
dosYSubject.next(update)
}
}, [data])
}, [data, dosYSubject])
return (
<RecoilRoot>
<Box className={style.row}>
{data.bs
? <Box className={style.bs}>
<Typography variant="subtitle1" align='center'>Band structure</Typography>
<BandStructure
<Box className={style.bs}>
<Typography variant="subtitle1" align='center'>Band structure</Typography>
{data.bs
? <BandStructure
data={data?.bs?.section_k_band}
layout={bsLayout}
aspectRatio={1.2}
unitsState={unitsState}
onRelayouting={handleBSRelayouting}
onReset={() => { setDosLayout({yaxis: {range: range}}) }}
layoutSubject={dosYSubject}
></BandStructure>
</Box>
: null
}
{data.dos
? <Box className={style.dos}>
<Typography variant="subtitle1" align='center'>Density of states</Typography>
<DOS
: <NoData aspectRatio={1.2}/>
}
</Box>
<Box className={style.dos}>
<Typography variant="subtitle1" align='center'>Density of states</Typography>
{data.dos
? <DOS
data={data.dos.section_dos}
layout={dosLayout}
aspectRatio={0.6}
onRelayouting={handleDOSRelayouting}
onReset={() => { setBsLayout({yaxis: {range: range}}) }}
unitsState={unitsState}
layoutSubject={bsYSubject}
></DOS>
</Box>
: null
}
: <NoData aspectRatio={0.6}/>
}
</Box>
{data.bs
? <Box className={style.bz}>
<Typography variant="subtitle1" align='center'>Brillouin zone</Typography>
......
......@@ -15,7 +15,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState, useCallback, useMemo } from 'react'
import React, { useCallback, useMemo } from 'react'
import { Subject } from 'rxjs'
import PropTypes from 'prop-types'
import {
Box,
......@@ -28,7 +29,8 @@ import { Structure } from '../visualization/Structure'
import { ErrorHandler, withErrorHandler } from '../ErrorHandler'
function GeoOptOverview({data, className, classes}) {
const [step, setStep] = useState(0)
// RxJS subject for efficiently propagating changes in structure information
const positionsSubject = useMemo(() => new Subject(), [])
// Styles
const useStyles = makeStyles((theme) => {
......@@ -129,8 +131,8 @@ function GeoOptOverview({data, className, classes}) {
// Handles hover event on the plot to update the currently shown structure
const handleHover = useCallback((event) => {
setStep(event.points[0].x)
}, [])
positionsSubject.next(data.structures[event.points[0].x].positions)
}, [data, positionsSubject])
return (
<Box className={style.root}>
......@@ -150,10 +152,10 @@ function GeoOptOverview({data, className, classes}) {
<Box className={style.structure}>
<Typography variant="subtitle1" align='center'>Optimization trajectory</Typography>
<Structure
system={data.structures[step]}
system={data.structures[0]}
aspectRatio={0.75}
options={{view: {fitMargin: 0.75}}}
positionsOnly={true}
positionsSubject={positionsSubject}
></Structure>
</Box>
</Box>
......
/*
* Copyright The NOMAD Authors.
*
* This file is part of NOMAD. See https://nomad-lab.eu for further info.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react'
import {
Box,
makeStyles
} from '@material-ui/core'
import PropTypes from 'prop-types'
import clsx from 'clsx'
/**
* Component that is used as a replacement for data that is not available.
* Notice that this is different from invalid data (this should display an
* ErrorCard) or from a placeholder!
*/
export default function NoData({aspectRatio, className, classes}) {
const useStyles = makeStyles(theme => {
return {
root: {
},
outerContainer: {
height: 0,
overflow: 'hidden',
paddingBottom: `${100 / aspectRatio}%`,
position: 'relative'
},
innerContainer: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%'
},
box: {
width: '100%',
height: '100%',
boxSizing: 'border-box',
padding: theme.spacing(2)
},
background: {
backgroundColor: '#f3f3f3',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
message: {
color: theme.palette.primary.main,
opacity: 0.5,
userSelect: 'none'
}
}
})
const styles = useStyles(classes)
const content = <Box className={styles.box}>
<Box className={styles.background}>
<div className={styles.message}>NO DATA</div>
</Box>
</Box>
return aspectRatio
? <div className={clsx(className, styles.root)}>
<div className={styles.outerContainer}>
<div className={styles.innerContainer}>
{content}
</div>
</div>
</div>
: content
}
NoData.propTypes = {
aspectRatio: PropTypes.number,
className: PropTypes.string,
classes: PropTypes.string
}
......@@ -37,7 +37,7 @@ import Plotly from 'plotly.js-cartesian-dist-min'
import clsx from 'clsx'
import { mergeObjects } from '../../utils'
export default function Plot({data, layout, config, floatTitle, capture, aspectRatio, className, classes, onRelayout, onAfterPlot, onRedraw, onRelayouting, onHover, onReset}) {
export default function Plot({data, layout, config, floatTitle, capture, aspectRatio, className, classes, onRelayout, onAfterPlot, onRedraw, onRelayouting, onHover, onReset, layoutSubject}) {
// States
const [float, setFloat] = useState(false)
const [captureSettings, setCaptureSettings] = useState()
......@@ -206,6 +206,14 @@ export default function Plot({data, layout, config, floatTitle, capture, aspectR
node.on('plotly_hover', onHover)
}
// Subscribe to the layout change publisher if one is given
if (layoutSubject) {
layoutSubject.subscribe(layout => {
let oldLayout = canvasRef.current.layout
Plotly.relayout(canvasRef.current, mergeObjects(layout, oldLayout))
})
}
// Update canvas element
canvasRef.current = node
// eslint-disable-next-line react-hooks/exhaustive-deps
......@@ -280,7 +288,13 @@ Plot.propTypes = {
onRedraw: PropTypes.func,
onRelayouting: PropTypes.func,
onHover: PropTypes.func,
onReset: PropTypes.func
onReset: PropTypes.func,
/**
* A RxJS Subject for efficient, non-persistent, layout changes that bypass
* rendering of the component. Should send messages that contain the new
* layout object.
*/
layoutSubject: PropTypes.any
}
Plot.defaultProps = {
aspectRatio: 9 / 16,
......
......@@ -17,7 +17,7 @@
*/
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles } from '@material-ui/core/styles'
import { makeStyles, fade } from '@material-ui/core/styles'
import {
Box,
Checkbox,
......@@ -47,7 +47,20 @@ import clsx from 'clsx'
* Used to show atomistic systems in an interactive 3D viewer based on the
* 'materia'-library.
*/
export const Structure = withErrorHandler(({className, classes, system, systems, options, viewer, captureName, aspectRatio, positionsOnly, sizeLimit}) => {
export const Structure = withErrorHandler(({
className,
classes,
system,
systems,
options,
materialType,
viewer,
captureName,
aspectRatio,
positionsOnly,
sizeLimit,
positionsSubject}
) => {
// States
const [anchorEl, setAnchorEl] = React.useState(null)
const [fullscreen, setFullscreen] = useState(false)
......@@ -87,6 +100,14 @@ export const Structure = withErrorHandler(({className, classes, system, systems,
marginBottom: theme.spacing(1),
height: '2rem'
},
toggle: {
color: fade(theme.palette.action.active, 0.87)
},
selected: {
'&$selected': {
color: fade(theme.palette.action.active, 0.87)
}
},
title: {
marginBottom: theme.spacing(1)
},
......@@ -173,8 +194,13 @@ export const Structure = withErrorHandler(({className, classes, system, systems,
if (refCanvas.current) {
refViewer.current.changeHostElement(refCanvas.current, false, false)
}
if (positionsSubject && refViewer.current) {
positionsSubject.subscribe((positions) => {
refViewer.current.setPositions(positions)
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}, [positionsSubject])
const loadSystem = useCallback((system, refViewer) => {
// If the cell all zeroes, positions are assumed to be cartesian.
......@@ -185,10 +211,9 @@ export const Structure = withErrorHandler(({className, classes, system, systems,
}