Commit 4c928a87 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added interactive quantity bar charts to the search.

parent 612c4c50
Pipeline #45215 passed with stages
in 30 minutes and 43 seconds
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core'
import { withStyles, Typography } from '@material-ui/core'
import * as d3 from 'd3'
import { scaleBand, scaleLinear } from 'd3-scale'
import chroma from 'chroma-js'
import repoColor from '@material-ui/core/colors/deepPurple'
class QuantityHistogram extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
data: PropTypes.object.isRequired
data: PropTypes.object,
onSelectionChanged: PropTypes.func.isRequired
}
static styles = theme => ({
......@@ -21,9 +23,14 @@ class QuantityHistogram extends React.Component {
constructor(props) {
super(props)
this.container = React.createRef()
this.svgEl = React.createRef()
}
state = {
selected: undefined
}
componentDidMount() {
this.updateChart()
}
......@@ -32,8 +39,28 @@ class QuantityHistogram extends React.Component {
this.updateChart()
}
handleItemClicked(item) {
const isSelected = this.state.selected === item.name
let selected
if (isSelected) {
selected = undefined
} else {
selected = item.name
}
this.setState({selected: selected})
this.props.onSelectionChanged(selected)
}
updateChart() {
const { width, height } = this.props
if (!this.props.data) {
return
}
const { selected } = this.state
const width = this.container.current.offsetWidth
const height = Object.keys(this.props.data).length * 32
const data = Object.keys(this.props.data).map(key => ({
name: key,
......@@ -49,6 +76,8 @@ class QuantityHistogram extends React.Component {
heatmapScale.domain([0, d3.max(data, d => d.value)], 10, 'log')
let svg = d3.select(this.svgEl.current)
svg.attr('width', width)
svg.attr('height', height)
let withData = svg
.selectAll('g')
......@@ -56,6 +85,9 @@ class QuantityHistogram extends React.Component {
withData.exit().remove()
const rectColor = d => selected === d.name ? repoColor[500] : heatmapScale(d.value)
const textColor = d => selected === d.name ? '#FFF' : '#000'
let item = withData.enter()
.append('g')
......@@ -65,7 +97,10 @@ class QuantityHistogram extends React.Component {
.attr('y', d => y(d.name))
.attr('width', d => x(d.value) - x(0))
.attr('height', y.bandwidth())
.style('fill', d => heatmapScale(d.value))
.style('fill', rectColor)
.style('stroke', '#000')
.style('stroke-width', '1px')
.style('shape-rendering', 'geometricPrecision')
item
.append('text')
......@@ -74,6 +109,7 @@ class QuantityHistogram extends React.Component {
.attr('x', x(0) + 4)
.attr('y', d => y(d.name) + 4)
.attr('text-anchor', 'start')
.style('fill', textColor)
.text(d => d.name)
item
......@@ -83,8 +119,13 @@ class QuantityHistogram extends React.Component {
.attr('y', d => y(d.name) - 4)
.attr('x', d => x(d.value) - 4)
.attr('text-anchor', 'end')
.style('fill', textColor)
.text(d => '' + d.value)
item
.style('cursor', 'pointer')
.on('click', d => this.handleItemClicked(d))
const t = d3.transition().duration(500)
item = withData.transition(t)
......@@ -94,30 +135,29 @@ class QuantityHistogram extends React.Component {
.attr('y', d => y(d.name))
.attr('width', d => x(d.value) - x(0))
.attr('height', y.bandwidth())
.style('fill', d => heatmapScale(d.value))
.style('fill', rectColor)
item
.select('.name')
.text(d => d.name)
.attr('y', d => y(d.name) + 4)
.style('fill', textColor)
item
.select('.value')
.text(d => '' + d.value)
.attr('y', d => y(d.name) - 4)
.attr('x', d => x(d.value) - 4)
.style('fill', textColor)
}
render() {
const { classes } = this.props
const { classes, title } = this.props
return (
<div className={classes.root}>
<svg
width={this.props.width}
height={this.props.height}
ref={this.svgEl} >
</svg>
<div className={classes.root} ref={this.container}>
<Typography variant="body1">{title}</Typography>
<svg ref={this.svgEl} />
</div>
)
}
......
......@@ -8,7 +8,7 @@ import TablePagination from '@material-ui/core/TablePagination'
import TableRow from '@material-ui/core/TableRow'
import Paper from '@material-ui/core/Paper'
import { TableHead, LinearProgress, FormControl, FormControlLabel, Checkbox, FormGroup,
FormLabel, IconButton, MuiThemeProvider, Typography, Tooltip, TableSortLabel, ExpansionPanelDetails, ExpansionPanelSummary, ExpansionPanel } from '@material-ui/core'
FormLabel, IconButton, MuiThemeProvider, Typography, Tooltip, TableSortLabel, ExpansionPanelDetails, ExpansionPanelSummary, ExpansionPanel, Grid } from '@material-ui/core'
import { compose } from 'recompose'
import { withErrors } from './errors'
import AnalyticsIcon from '@material-ui/icons/Settings'
......@@ -61,6 +61,15 @@ class Repo extends React.Component {
},
clickableRow: {
cursor: 'pointer'
},
quantityGrid: {
minWidth: 524,
maxWidth: 924,
margin: 'auto',
width: '100%'
},
quantity: {
marginTop: theme.spacing.unit * 2
}
})
......@@ -117,7 +126,7 @@ class Repo extends React.Component {
update(changes) {
changes = changes || {}
const { page, rowsPerPage, owner, sortedBy, sortOrder, atoms } = {...this.state, ...changes}
const { page, rowsPerPage, owner, sortedBy, sortOrder, atoms, system, crystal_system, code_name, xc_functional, basis_set } = {...this.state, ...changes}
this.setState({loading: true, ...changes})
this.props.api.search({
......@@ -126,7 +135,12 @@ class Repo extends React.Component {
owner: owner || 'all',
order_by: sortedBy,
order: (sortOrder === 'asc') ? 1 : -1,
atoms: atoms
atoms: atoms,
system: system,
crystal_system: crystal_system,
code_name: code_name,
xc_functional: xc_functional,
basis_set: basis_set
}).then(data => {
const { pagination: { total, page, per_page }, results, aggregations } = data
this.setState({
......@@ -185,6 +199,12 @@ class Repo extends React.Component {
this.update({atoms: selection})
}
handleQuantitySelectionChanged(quantity, selection) {
const update = {}
update[quantity] = selection
this.update(update)
}
renderCell(key, rowConfig, calc) {
const value = calc[key]
if (rowConfig.render) {
......@@ -196,9 +216,16 @@ class Repo extends React.Component {
render() {
const { classes, user } = this.props
const { data, aggregations, rowsPerPage, page, total, loading, sortedBy, sortOrder, openCalc } = this.state
const { data, rowsPerPage, page, total, loading, sortedBy, sortOrder, openCalc } = this.state
const emptyRows = rowsPerPage - Math.min(rowsPerPage, total - (page - 1) * rowsPerPage)
const aggregations = this.state.aggregations || {}
const quantity = (key, title) => (<QuantityHistogram
classes={{root: classes.quantity}} title={title || key} width={300}
data={aggregations[key]}
onSelectionChanged={(selection) => this.handleQuantitySelectionChanged(key, selection)}/>)
const ownerLabel = {
all: 'All calculations',
user: 'Your calculations',
......@@ -233,9 +260,20 @@ class Repo extends React.Component {
aggregations={aggregations ? aggregations.atoms : null}
onSelectionChanged={(selection) => this.handleElementSelectionChanged(selection)}
/>
{ aggregations
? <QuantityHistogram width={500} height={300} data={aggregations.system} />
: ''}
<Grid container spacing={24} className={classes.quantityGrid}>
<Grid item xs={4}>
{quantity('system')}
{quantity('crystal_system', 'crystal system')}
</Grid>
<Grid item xs={4}>
{quantity('basis_set', 'basis set')}
{quantity('xc_functional', 'XC functionals')}
</Grid>
<Grid item xs={4}>
{quantity('code_name', 'code')}
</Grid>
</Grid>
</ExpansionPanelDetails>
</ExpansionPanel>
......
......@@ -217,8 +217,8 @@ aggregations = {
'system': 10,
'crystal_system': 10,
'code_name': len(parsing.parsers),
'xc_functional': 10,
'authors': 10
'basis_set': 10,
'xc_functional': 10
}
""" The available aggregations in :func:`aggregate_search` and their maximum aggregation size """
......
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