Commit dabf986f authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Merge branch 'search-relabeling' into 'v1.0.0'

Search relabeling

See merge request !168
parents 333df493 72c5505c
Pipeline #81969 passed with stages
in 24 minutes and 2 seconds
import React, { useRef, useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { Select, MenuItem, Card, CardContent, CardHeader, makeStyles } from '@material-ui/core'
import { Select, MenuItem, Card, CardContent, CardHeader, makeStyles, Tooltip } from '@material-ui/core'
import * as d3 from 'd3'
import { scaleBand, scalePow } from 'd3-scale'
import { formatQuantity, nomadPrimaryColor, nomadSecondaryColor, nomadFontFamily } from '../config.js'
......@@ -13,6 +13,26 @@ function split(array, cols) {
return [array.slice(0, size), ...split(array.slice(size), cols - 1)]
}
function isSelected(d, selected, multiple) {
// Determine if the value has been selected
let isSelected
if (multiple) {
if (selected === undefined) {
isSelected = false
} else {
if (Array.isArray(selected)) {
let selections = new Set(selected)
isSelected = selections.has(d.key)
} else {
isSelected = selected === d.key
}
}
} else {
isSelected = selected === d.key
}
return isSelected
}
const useStyles = makeStyles(theme => ({
root: {
marginTop: theme.spacing(2)
......@@ -22,25 +42,23 @@ const useStyles = makeStyles(theme => ({
position: 'relative'
},
tooltip: {
textAlign: 'center',
position: 'absolute',
pointerEvents: 'none',
opacity: 0
width: '100%',
height: '100%'
},
tooltipContent: {
// copy of the material ui popper style
display: 'inline-block',
color: '#fff',
padding: '4px 8px',
fontSize: '0.625rem',
lineHeight: '1.4em',
borderRadius: '4px',
backgroundColor: '#616161'
position: 'absolute',
// backgroundColor: '#ffbb00' // Uncomment for debugging tooltips
display: 'none',
zIndex: 1,
cursor: 'pointer'
},
canvas: {
zIndex: 2
}
}))
export default function Histogram({
initialScale = 1, getValueLabel, numberOfValues, title, data, columns = 1, tooltips,
onClick, selected, card
onClick, selected, card, multiple
}) {
onClick = onClick || (() => null)
data = data || []
......@@ -48,11 +66,17 @@ export default function Histogram({
getValueLabel = getValueLabel || (value => value.name)
title = title || 'Histogram'
const [tooltipHTML, setTooltipHTML] = useState('')
const [item, setItem] = useState()
const [scale, setScale] = useState(initialScale)
const classes = useStyles()
const containerRef = useRef()
const [scale, setScale] = useState(initialScale)
const handleItemClicked = item => onClick(item)
const handleItemClicked = item => {
if (item.value !== 0) {
onClick(item)
}
}
useEffect(() => {
for (let i = data.length; i < numberOfValues; i++) {
......@@ -68,6 +92,7 @@ export default function Histogram({
const containerWidth = containerRef.current.offsetWidth
const width = containerWidth / columns - (12 * (columns - 1))
const height = columnSize * 32
const padding = 16
const x = scalePow().range([0, width]).exponent(scale)
......@@ -75,17 +100,22 @@ export default function Histogram({
const max = d3.max(data, d => d.value) || 1
x.domain([0, max])
const rectColor = d => selected === d.key ? nomadPrimaryColor.dark : nomadSecondaryColor.light
const textColor = d => selected === d.key ? '#FFF' : '#000'
const rectColor = d => isSelected(d, selected, multiple) ? nomadPrimaryColor.dark : nomadSecondaryColor.light
const textColor = d => {
if (d.value === 0) {
return '#999'
}
return isSelected(d, selected, multiple) ? '#FFF' : '#000'
}
const container = d3.select(containerRef.current)
const tooltip = container.select('.' + classes.tooltip)
.style('width', width + 'px')
.style('opacity', 0)
const tooltipContent = container.select('.' + classes.tooltipContent)
const svg = container.select('svg')
.attr('width', containerWidth)
.attr('height', height)
tooltipContent
.style('width', width + 'px')
.style('height', 27 + 'px')
const columnsG = svg
.selectAll('.column')
......@@ -110,12 +140,14 @@ export default function Histogram({
items.exit().remove()
items
.on('click', d => handleItemClicked(d))
.on('click', handleItemClicked)
let item = items.enter()
.append('g')
.attr('class', 'item')
.attr('display', d => getValueLabel(d) === '' ? 'none' : 'show')
.style('cursor', 'pointer')
.on('click', handleItemClicked)
item
.append('rect')
......@@ -161,31 +193,19 @@ export default function Histogram({
.text(d => formatQuantity(d.value))
item
.style('cursor', 'pointer')
.on('click', d => handleItemClicked(d))
item
.on('mouseover', function(d) {
d3.select(this).select('.background')
.style('opacity', 0.08)
.on('mouseenter', function(d) {
setItem(d)
if (tooltips) {
tooltip.transition()
.duration(200)
.style('opacity', 1)
tooltip
.style('left', i * (width + 12) + 'px')
.style('top', (y(d.key) + 32) + 'px')
tooltipContent.html(getValueLabel(d))
if (d.tooltip) {
tooltipContent
.style('left', i * (width) + padding + 'px')
.style('top', (y(d.key)) + 'px')
.style('display', 'block')
setTooltipHTML(d.tooltip)
}
}
})
.on('mouseout', function(d) {
d3.select(this).select('.background')
.style('opacity', 0)
if (tooltips) {
tooltip.transition()
.duration(200)
.style('opacity', 0)
}
.on('mouseleave', function(d) {
})
item = items.transition(d3.transition().duration(500))
......@@ -214,9 +234,16 @@ export default function Histogram({
const chart = <div ref={containerRef}>
<div className={classes.tooltip}>
<div className={classes.tooltipContent}></div>
<Tooltip
interactive
placement='right'
title={tooltipHTML}
>
<div className={classes.tooltipContent} onClick={() => { handleItemClicked(item) }}>
</div>
</Tooltip>
<svg className={classes.canvas}></svg>
</div>
<svg />
</div>
if (card) {
......@@ -289,5 +316,13 @@ Histogram.propTypes = {
/**
* If true the chart is wrapped in a MUI paper card with title and form.
*/
card: PropTypes.bool
card: PropTypes.bool,
/**
* If true, multiple values may be selected.
*/
multiple: PropTypes.bool
}
Histogram.defaultProps = {
multiple: false
}
import React, { useContext, useEffect } from 'react'
import PropTypes from 'prop-types'
import { Grid } from '@material-ui/core'
import { useTheme } from '@material-ui/core/styles'
import QuantityHistogram from '../search/QuantityHistogram'
import { searchContext } from '../search/SearchContext'
import { path, defsByName } from '../archive/metainfo'
export function DFTMethodVisualizations(props) {
const {info} = props
......@@ -85,70 +87,75 @@ DFTSystemVisualizations.propTypes = {
info: PropTypes.object
}
const energy_quantities = [
'energy_total',
'energy_total_T0',
'energy_free',
'energy_electrostatic',
'energy_X',
'energy_XC',
'energy_sum_eigenvalues'
]
const electronic_quantities = [
'dos_values',
'eigenvalues_values',
'volumetric_data_values',
'electronic_kinetic_energy',
'total_charge'
// 'atomic_multipole_values'
'electronic_band_structure',
'electronic_dos',
'eigenvalues_values'
]
const forces_quantities = [
'atom_forces_free',
'atom_forces_raw',
// 'atom_forces_T0',
'atom_forces',
const mechanical_quantities = [
'stress_tensor'
]
const vibrational_quantities = [
const thermal_quantities = [
'thermodynamical_property_heat_capacity_C_v',
'vibrational_free_energy_at_constant_volume',
'band_energies'
'phonon_band_structure',
'phonon_dos'
]
const magnetic_quantities = [
'spin_S2'
]
const optical_quantities = [
'excitation_energies',
'oscillator_strengths',
'transition_dipole_moments'
]
const labels = {
'energy_total': 'Total energy',
'energy_total_T0': 'Total energy (0K)',
'energy_free': 'Free energy',
'energy_electrostatic': 'Electrostatic',
'energy_X': 'Exchange',
'energy_XC': 'Exchange-correlation',
'energy_sum_eigenvalues': 'Band energy',
'dos_values': 'DOS',
'eigenvalues_values': 'Eigenvalues',
'volumetric_data_values': 'Volumetric data',
'electronic_kinetic_energy': 'Kinetic energy',
'total_charge': 'Charge',
'atom_forces_free': 'Free atomic forces',
'atom_forces_raw': 'Raw atomic forces',
'atom_forces_T0': 'Atomic forces (0K)',
'atom_forces': 'Atomic forces',
'stress_tensor': 'Stress tensor',
'electronic_band_structure': 'Electronic band structure',
'electronic_dos': 'Electronic density of states',
'phonon_band_structure': 'Phonon dispersion',
'phonon_dos': 'Phonon density of states',
'thermodynamical_property_heat_capacity_C_v': 'Heat capacity',
'vibrational_free_energy_at_constant_volume': 'Free energy (const=V)',
'band_energies': 'Band energies',
'spin_S2': 'Spin momentum operator',
'excitation_energies': 'Excitation energies',
'vibrational_free_energy_at_constant_volume': 'Helmholtz free energy',
'spin_S2': 'Angular spin momentum squared',
'oscillator_strengths': 'Oscillator strengths',
'transition_dipole_moments': 'Transition dipole moments',
'atomic_multipole_values': 'Atomic multipole values'}
'transition_dipole_moments': 'Transition dipole moments'
}
const metainfoPaths = {
'eigenvalues_values': path('eigenvalues_values'),
'stress_tensor': path('stress_tensor'),
'electronic_band_structure': 'section_run/section_single_configuration_calculation/section_k_band',
'electronic_dos': 'section_run/section_single_configuration_calculation/section_dos',
'phonon_band_structure': 'section_run/section_single_configuration_calculation/section_k_band',
'phonon_dos': 'section_run/section_single_configuration_calculation/section_dos',
'thermodynamical_property_heat_capacity_C_v': path('thermodynamical_property_heat_capacity_C_v'),
'vibrational_free_energy_at_constant_volume': path('vibrational_free_energy_at_constant_volume'),
'spin_S2': path('spin_S2'),
'oscillator_strengths': path('oscillator_strengths'),
'transition_dipole_moments': path('transition_dipole_moments')
}
function MetaInfoTooltip({def, path}) {
const theme = useTheme()
return <div style={{display: 'flex', flexDirection: 'column', padding: 2}}>
<p style={{fontSize: 14, margin: 0, padding: 0}}>Metainfo definition:</p>
<a style={{fontSize: 14, color: theme.palette.secondary.light}} href={`/fairdi/nomad/latest/gui/metainfo/${path}`}>{def.name}</a>
</div>
}
MetaInfoTooltip.propTypes = {
def: PropTypes.object,
path: PropTypes.string
}
const tooltips = {}
for (const label in labels) {
const path = metainfoPaths[label]
const realName = path.split('/').slice(-1)[0]
const metainfoDef = defsByName[realName][0]
tooltips[label] = <MetaInfoTooltip def={metainfoDef} path={path}></MetaInfoTooltip>
}
const workflowTypeLabels = {
'geometry_optimization': 'Geometry optimization',
......@@ -184,18 +191,15 @@ export function DFTPropertyVisualizations(props) {
return (
<Grid container spacing={2}>
<Grid item xs={4}>
<QuantityHistogram quantity="dft.searchable_quantities" values={energy_quantities} valueLabels={labels} title="Energy" initialScale={0.5} />
<QuantityHistogram quantity="dft.searchable_quantities" values={electronic_quantities} valueLabels={labels} title="Electronic" initialScale={0.5} />
<QuantityHistogram quantity="dft.searchable_quantities" values={magnetic_quantities} valueLabels={labels} title="Magnetic" initialScale={1} />
<Grid item xs={7}>
<QuantityHistogram quantity="dft.searchable_quantities" values={electronic_quantities} valueLabels={labels} title="Electronic" initialScale={0.5} tooltips={tooltips} multiple/>
<QuantityHistogram quantity="dft.searchable_quantities" values={mechanical_quantities} valueLabels={labels} title="Mechanical" initialScale={0.5} tooltips={tooltips} multiple/>
<QuantityHistogram quantity="dft.searchable_quantities" values={thermal_quantities} valueLabels={labels} title="Thermal" initialScale={0.5} tooltips={tooltips} multiple/>
<QuantityHistogram quantity="dft.searchable_quantities" values={optical_quantities} valueLabels={labels} title="Optical" initialScale={1} tooltips={tooltips} multiple/>
<QuantityHistogram quantity="dft.searchable_quantities" values={magnetic_quantities} valueLabels={labels} title="Magnetic" initialScale={1} tooltips={tooltips} multiple/>
</Grid>
<Grid item xs={4}>
<QuantityHistogram quantity="dft.searchable_quantities" values={forces_quantities} valueLabels={labels} title="Forces" initialScale={0.5} />
<QuantityHistogram quantity="dft.searchable_quantities" values={vibrational_quantities} valueLabels={labels} title="Vibrational" initialScale={0.5} />
<QuantityHistogram quantity="dft.searchable_quantities" values={optical_quantities} valueLabels={labels} title="Optical" initialScale={1} />
</Grid>
<Grid item xs={4}>
<QuantityHistogram quantity="dft.labels_springer_classification" title="Property classification" initialScale={1} />
<Grid item xs={5}>
<QuantityHistogram quantity="dft.labels_springer_classification" title="Functional classification" initialScale={1} multiple/>
</Grid>
<Grid item xs={12}>
<QuantityHistogram quantity="dft.workflow.workflow_type" title="Workflows" valueLabels={workflowTypeLabels} initialScale={0.25} />
......
import React, { useContext, useMemo } from 'react'
import React, { useContext, useMemo, useCallback } from 'react'
import PropTypes from 'prop-types'
import { searchContext } from './SearchContext.js'
import searchQuantities from '../../searchQuantities'
......@@ -8,7 +8,7 @@ const unprocessedLabel = 'not processed'
const unavailableLabel = 'unavailable'
export default function QuantityHistogram({
quantity, valueLabels = {}, title, values, numberOfValues,
quantity, valueLabels = {}, title, values, numberOfValues, multiple, tooltips = {},
...props
}) {
title = title || quantity
......@@ -16,9 +16,30 @@ export default function QuantityHistogram({
numberOfValues = numberOfValues || (values && values.length) || (searchQuantities[quantity] && searchQuantities[quantity].statistic_size)
const {response: {statistics, metric}, query, setQuery} = useContext(searchContext)
const statisticsData = statistics[quantity]
const handleItemClicked = item => {
setQuery({[quantity]: (query[quantity] === item.key) ? null : item.key})
}
const handleItemClicked = useCallback(item => {
if (multiple) {
// Add or remove item from query
let newQuery = query[quantity]
if (newQuery === undefined) {
newQuery = [item.key]
} else {
if (!Array.isArray(newQuery)) {
newQuery = [newQuery]
}
newQuery = new Set(newQuery)
if (newQuery.has(item.key)) {
newQuery.delete(item.key)
} else {
newQuery.add([item.key])
}
newQuery = Array.from(newQuery.values())
}
setQuery({[quantity]: newQuery})
} else {
setQuery({[quantity]: (query[quantity] === item.key) ? null : item.key})
}
}, [query, setQuery, multiple, quantity])
const data = useMemo(() => {
let data
......@@ -28,7 +49,8 @@ export default function QuantityHistogram({
data = values.map(value => ({
key: value,
name: valueLabels[value] || value,
value: statisticsData[value] ? statisticsData[value][metric] : 0
value: statisticsData[value] ? statisticsData[value][metric] : 0,
tooltip: tooltips[value]
}))
} else {
data = Object.keys(statisticsData)
......@@ -48,7 +70,7 @@ export default function QuantityHistogram({
}
}
return data
}, [metric, quantity, statistics, statisticsData, valueLabels, values])
}, [metric, quantity, statistics, statisticsData, valueLabels, values, tooltips])
return <Histogram
card data={data}
......@@ -56,6 +78,8 @@ export default function QuantityHistogram({
title={title}
onClick={handleItemClicked}
selected={query[quantity]}
multiple={multiple}
tooltips={!!tooltips}
{...props}
/>
}
......@@ -88,5 +112,17 @@ QuantityHistogram.propTypes = {
* An optional mapping between values and labels that should be used to render the
* values.
*/
valueLabels: PropTypes.object
valueLabels: PropTypes.object,
/**
* An optional mapping between values and their tooltip content.
*/
tooltips: PropTypes.object,
/**
* Whether multiple values can be appended to the same query key.
*/
multiple: PropTypes.bool
}
QuantityHistogram.defaultProps = {
multiple: false
}
......@@ -57,47 +57,33 @@ compound_types = [
'decinary'
]
_energy_quantities = [
'energy_total',
'energy_total_T0',
'energy_free',
'energy_electrostatic',
'energy_X',
'energy_XC',
'energy_sum_eigenvalues']
_electronic_quantities = [
'dos_values',
'electronic_band_structure',
'electronic_dos',
'eigenvalues_values',
'volumetric_data_values',
'electronic_kinetic_energy',
'total_charge',
# 'atomic_multipole_values'
]
_forces_quantities = [
'atom_forces_free',
'atom_forces_raw',
# 'atom_forces_T0',
'atom_forces',
'stress_tensor']
_mechanical_quantities = [
'stress_tensor'
]
_vibrational_quantities = [
_thermal_quantities = [
'thermodynamical_property_heat_capacity_C_v',
'vibrational_free_energy_at_constant_volume',
'band_energies']
'phonon_band_structure',
'phonon_dos',
]
_magnetic_quantities = [
'spin_S2'
]
_optical_quantities = [
'excitation_energies',
'oscillator_strengths',
'transition_dipole_moments'
]
_searchable_quantities = set(_energy_quantities + _electronic_quantities + _forces_quantities + _vibrational_quantities + _magnetic_quantities + _optical_quantities)
_searchable_quantities = set(_electronic_quantities + _mechanical_quantities + _thermal_quantities + _magnetic_quantities + _optical_quantities)
version_re = re.compile(r'(\d+(\.\d+(\.\d+)?)?)')
......@@ -233,7 +219,7 @@ class DFTMetadata(MSection):
searchable_quantities = Quantity(
type=str, shape=['0..*'],
description='All quantities with existence filters in the search GUI.',
a_search=Search(many_and='append', statistic_size=len(_searchable_quantities)))
a_search=Search(many_and='append', statistic_size=len(_searchable_quantities) + 15)) # Temporarily increased the statistics size while migrating from old set to new one.
geometries = Quantity(
type=str, shape=['0..*'],
......
apiVersion: v1
kind: Namespace
metadata:
name: elasticsearch
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: es-client
namespace: elasticsearch
labels:
component: elasticsearch
role: client
spec:
replicas: 2
template:
metadata:
labels:
component: elasticsearch
role: client
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app.kubernetes.io/name: es-client
app.kubernetes.io/instance: es-client
nodeSelector:
infratype: db
initContainers:
- name: init-sysctl
image: busybox:1.27.2
command:
- sysctl
- -w
- vm.max_map_count=262144
securityContext:
privileged: true
containers:
- name: es-client
image: quay.io/pires/docker-elasticsearch-kubernetes:6.2.4
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: NODE_NAME
valueFrom:
fieldRef: