Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
nomad-lab
nomad-FAIR
Commits
ab463485
Commit
ab463485
authored
Apr 10, 2020
by
Markus Scheidgen
Browse files
Refactored the search visualization tabs.
parent
a2752c46
Pipeline
#72695
failed with stages
in 31 minutes and 39 seconds
Changes
7
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
gui/src/components/dft/DFTMethodVisualizations.js
0 → 100644
View file @
ab463485
import
React
from
'
react
'
import
PropTypes
from
'
prop-types
'
import
{
Grid
}
from
'
@material-ui/core
'
import
{
Quantity
}
from
'
../search/QuantityHistogram
'
import
SearchContext
from
'
../search/SearchContext
'
export
default
class
DFTMethodVisualizations
extends
React
.
Component
{
static
propTypes
=
{
info
:
PropTypes
.
object
}
static
contextType
=
SearchContext
.
type
componentDidMount
()
{
const
{
setStatisticsToRefresh
}
=
this
.
context
setStatisticsToRefresh
(
'
dft.labels_springer_compound_class
'
)
}
render
()
{
const
{
info
}
=
this
.
props
const
{
state
:
{
response
:
{
statistics
},
usedMetric
}}
=
this
.
context
if
(
statistics
.
code_name
&&
info
)
{
// filter based on known codes, since elastic search might return 0 aggregations on
// obsolete code names
const
filteredCodeNames
=
{}
const
defaultValue
=
{
code_runs
:
0
}
defaultValue
[
usedMetric
]
=
0
info
.
codes
.
forEach
(
key
=>
{
filteredCodeNames
[
key
]
=
statistics
.
code_name
[
key
]
||
defaultValue
})
statistics
.
code_name
=
filteredCodeNames
}
return
(
<
Grid
container
spacing
=
{
24
}
>
<
Grid
item
xs
=
{
8
}
>
<
Quantity
quantity
=
"
dft.code_name
"
title
=
"
Code
"
scale
=
{
0.25
}
metric
=
{
usedMetric
}
sort
columns
=
{
2
}
/
>
<
/Grid
>
<
Grid
item
xs
=
{
4
}
>
<
Quantity
quantity
=
"
dft.basis_set
"
title
=
"
Basis set
"
scale
=
{
0.25
}
metric
=
{
usedMetric
}
sort
/>
<
Quantity
quantity
=
"
dft.xc_functional
"
title
=
"
XC functionals
"
scale
=
{
0.5
}
metric
=
{
usedMetric
}
sort
/>
<
/Grid
>
<
/Grid
>
)
}
}
gui/src/components/dft/DFT
SearchByPropertyAggreg
ations.js
→
gui/src/components/dft/DFT
PropertyVisualiz
ations.js
View file @
ab463485
...
...
@@ -3,9 +3,8 @@ import PropTypes from 'prop-types'
import
{
Grid
}
from
'
@material-ui/core
'
import
{
Quantity
}
from
'
../search/QuantityHistogram
'
import
SearchContext
from
'
../search/SearchContext
'
import
{
withApi
}
from
'
../api
'
class
DFTSearchByPropertyAggreg
ations
extends
React
.
Component
{
export
default
class
DFTPropertyVisualiz
ations
extends
React
.
Component
{
static
propTypes
=
{
info
:
PropTypes
.
object
}
...
...
@@ -38,21 +37,19 @@ class DFTSearchByPropertyAggregations extends React.Component {
return
(
<
Grid
container
spacing
=
{
24
}
>
<
Grid
item
xs
=
{
4
}
>
<
Quantity
quantity
=
"
dft.quantities_energy
"
title
=
"
Energy
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.quantities_forces
"
title
=
"
Forces
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.quantities_electronic
"
title
=
"
Electronic
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.quantities_energy
"
title
=
"
Energy
"
scale
=
{
1
}
metric
=
{
usedMetric
}
sort
tooltips
/>
<
Quantity
quantity
=
"
dft.quantities_electronic
"
title
=
"
Electronic
"
scale
=
{
1
}
metric
=
{
usedMetric
}
sort
tooltips
/>
<
/Grid
>
<
Grid
item
xs
=
{
4
}
>
<
Quantity
quantity
=
"
dft.quantities_
magnetic
"
title
=
"
Magnetic
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.quantities_vibrational
"
title
=
"
Vibrational
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.quantities_optical
"
title
=
"
Optical
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.quantities_
forces
"
title
=
"
Forces
"
scale
=
{
1
}
metric
=
{
usedMetric
}
sort
tooltips
/>
<
Quantity
quantity
=
"
dft.quantities_vibrational
"
title
=
"
Vibrational
"
scale
=
{
1
}
metric
=
{
usedMetric
}
sort
tooltips
/>
<
Quantity
quantity
=
"
dft.quantities_optical
"
title
=
"
Optical
"
scale
=
{
1
}
metric
=
{
usedMetric
}
sort
tooltips
/>
<
/Grid
>
<
Grid
item
xs
=
{
4
}
>
<
Quantity
quantity
=
"
dft.labels_springer_classification
"
title
=
"
Springer classification
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.labels_springer_classification
"
title
=
"
Springer classification
"
scale
=
{
1
}
metric
=
{
usedMetric
}
tooltips
/>
<
Quantity
quantity
=
"
dft.quantities_magnetic
"
title
=
"
Magnetic
"
scale
=
{
1
}
metric
=
{
usedMetric
}
sort
tooltips
/>
<
/Grid
>
<
/Grid
>
)
}
}
export
default
withApi
(
false
,
false
)(
DFTSearchByPropertyAggregations
)
gui/src/components/dft/DFTS
earchAggreg
ations.js
→
gui/src/components/dft/DFTS
ystemVisualiz
ations.js
View file @
ab463485
...
...
@@ -3,9 +3,8 @@ import PropTypes from 'prop-types'
import
{
Grid
}
from
'
@material-ui/core
'
import
{
Quantity
}
from
'
../search/QuantityHistogram
'
import
SearchContext
from
'
../search/SearchContext
'
import
{
withApi
}
from
'
../api
'
class
DFTSearchAggreg
ations
extends
React
.
Component
{
export
default
class
DFTSystemVisualiz
ations
extends
React
.
Component
{
static
propTypes
=
{
info
:
PropTypes
.
object
}
...
...
@@ -38,21 +37,16 @@ class DFTSearchAggregations extends React.Component {
return
(
<
Grid
container
spacing
=
{
24
}
>
<
Grid
item
xs
=
{
4
}
>
<
Quantity
quantity
=
"
dft.co
de_nam
e
"
title
=
"
Co
d
e
"
scale
=
{
0.25
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.co
mpound_typ
e
"
title
=
"
Co
mpound typ
e
"
scale
=
{
1
}
metric
=
{
usedMetric
}
sort
/>
<
/Grid
>
<
Grid
item
xs
=
{
4
}
>
<
Quantity
quantity
=
"
dft.basis_set
"
title
=
"
Basis set
"
scale
=
{
0.25
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.xc_functional
"
title
=
"
XC functionals
"
scale
=
{
0.5
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.compound_type
"
title
=
"
Compound type
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.system
"
title
=
"
System type
"
scale
=
{
0.25
}
metric
=
{
usedMetric
}
sort
/>
<
Quantity
quantity
=
"
dft.crystal_system
"
title
=
"
Crystal system
"
scale
=
{
1
}
metric
=
{
usedMetric
}
sort
/>
<
/Grid
>
<
Grid
item
xs
=
{
4
}
>
<
Quantity
quantity
=
"
dft.system
"
title
=
"
System type
"
scale
=
{
0.25
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.crystal_system
"
title
=
"
Crystal system
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.labels_springer_compound_class
"
title
=
"
Springer compound class
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
Quantity
quantity
=
"
dft.labels_springer_compound_class
"
title
=
"
Springer compound
"
scale
=
{
1
}
metric
=
{
usedMetric
}
/
>
<
/Grid
>
<
/Grid
>
)
}
}
export
default
withApi
(
false
,
false
)(
DFTSearchAggregations
)
gui/src/components/domains.js
View file @
ab463485
import
React
from
'
react
'
import
DFTSearchAggregations
from
'
./dft/DFTSearchAggregations
'
import
DFTEntryOverview
from
'
./dft/DFTEntryOverview
'
import
DFTEntryCards
from
'
./dft/DFTEntryCards
'
import
EMSSearchAggregations
from
'
./ems/EMS
SearchAggreg
ations
'
import
EMSSearchAggregations
from
'
./ems/EMS
Visualiz
ations
'
import
EMSEntryOverview
from
'
./ems/EMSEntryOverview
'
import
EMSEntryCards
from
'
./ems/EMSEntryCards
'
import
DFTSearchByPropertyAggregations
from
'
./dft/DFTSearchByPropertyAggregations
'
import
DFTSystemVisualizations
from
'
./dft/DFTSystemVisualizations
'
import
DFTPropertyVisualizations
from
'
./dft/DFTPropertyVisualizations
'
import
DFTMethodVisualizations
from
'
./dft/DFTMethodVisualizations
'
import
EMSVisualizations
from
'
./ems/EMSVisualizations
'
/* eslint-disable react/display-name */
...
...
@@ -20,21 +22,26 @@ export const domains = ({
entryTitle
:
data
=>
data
.
dft
&&
data
.
dft
.
code_name
?
data
.
dft
.
code_name
+
'
run
'
:
'
Code run
'
,
searchPlaceholder
:
'
enter atoms, codes, functionals, or other quantity values
'
,
/**
* A component that is used to render the search aggregations. The components needs
* to work with props: aggregations (the aggregation data from the api),
* searchValues (currently selected search values), metric (the metric key to use),
* onChange (callback to propagate searchValue changes).
*/
SearchAggregations
:
DFTSearchAggregations
,
/**
* A component that is used to render the search aggregations by property.
*/
SearchByPropertyAggregations
:
DFTSearchByPropertyAggregations
,
/**
* Metrics are used to show values for aggregations. Each metric has a key (used
* for API calls), a label (used in the select form), and result string (to show
* the overall amount in search results).
* A set of components and metadata that is used to present tabs of search visualizations
* in addition to the globally available elements and users view.
*/
searchVisualizations
:
{
'
system
'
:
{
render
:
props
=>
<
DFTSystemVisualizations
{...
props
}
/>
,
label
:
'
System
'
,
description
:
'
Shows histograms on system metadata
'
},
'
method
'
:
{
render
:
props
=>
<
DFTMethodVisualizations
{...
props
}
/>
,
label
:
'
Method
'
,
description
:
'
Shows histograms on method metadata
'
},
'
properties
'
:
{
render
:
props
=>
<
DFTPropertyVisualizations
{...
props
}
/>
,
label
:
'
Properties
'
,
description
:
'
Shows histograms on the availability of key properties
'
}
},
searchMetrics
:
{
code_runs
:
{
label
:
'
Entries
'
,
...
...
@@ -157,6 +164,13 @@ export const domains = ({
entryLabelPlural
:
'
entries
'
,
entryTitle
:
()
=>
'
Experiment
'
,
searchPlaceholder
:
'
enter atoms, experimental methods, or other quantity values
'
,
searchVisualizations
:
{
'
metadata
'
:
{
render
:
props
=>
<
EMSVisualizations
{...
props
}
/>
,
label
:
'
Metadata
'
,
description
:
'
Shows histograms on system metadata
'
}
},
/**
* A component that is used to render the search aggregations. The components needs
* to work with props: aggregations (the aggregation data from the api),
...
...
gui/src/components/ems/EMS
SearchAggreg
ations.js
→
gui/src/components/ems/EMS
Visualiz
ations.js
View file @
ab463485
...
...
@@ -3,7 +3,7 @@ import { Grid } from '@material-ui/core'
import
{
Quantity
}
from
'
../search/QuantityHistogram
'
import
SearchContext
from
'
../search/SearchContext
'
class
EMSSearchAggreg
ations
extends
React
.
Component
{
export
default
class
EMSVisualiz
ations
extends
React
.
Component
{
static
contextType
=
SearchContext
.
type
render
()
{
...
...
@@ -23,5 +23,3 @@ class EMSSearchAggregations extends React.Component {
)
}
}
export
default
EMSSearchAggregations
gui/src/components/search/QuantityHistogram.js
View file @
ab463485
...
...
@@ -9,6 +9,14 @@ import SearchContext from '../search/SearchContext'
const
unprocessedLabel
=
'
not processed
'
const
unavailableLabel
=
'
unavailable
'
function
split
(
array
,
cols
)
{
if
(
cols
===
1
)
{
return
[
array
]
}
const
size
=
Math
.
ceil
(
array
.
length
/
cols
)
return
[
array
.
slice
(
0
,
size
),
...
split
(
array
.
slice
(
size
),
cols
-
1
)]
}
const
_mapping
=
{
'
energy_total
'
:
'
Total energy
'
,
'
energy_total_T0
'
:
'
Total energy (0K)
'
,
...
...
@@ -35,19 +43,6 @@ const _mapping = {
'
oscillator_strengths
'
:
'
Oscillator strengths
'
,
'
transition_dipole_moments
'
:
'
Transition dipole moments
'
}
function
mapKey
(
key
,
short
=
true
)
{
let
name
=
key
const
maxLength
=
17
if
(
key
in
_mapping
)
{
name
=
_mapping
[
key
]
}
if
(
name
.
length
>
maxLength
&&
short
)
{
return
name
.
substring
(
0
,
maxLength
)
+
'
...
'
}
else
{
return
name
}
}
class
QuantityHistogramUnstyled
extends
React
.
Component
{
static
propTypes
=
{
classes
:
PropTypes
.
object
.
isRequired
,
...
...
@@ -57,20 +52,27 @@ class QuantityHistogramUnstyled extends React.Component {
metric
:
PropTypes
.
string
.
isRequired
,
value
:
PropTypes
.
string
,
onChanged
:
PropTypes
.
func
.
isRequired
,
defaultScale
:
PropTypes
.
number
defaultScale
:
PropTypes
.
number
,
sort
:
PropTypes
.
bool
,
tooltips
:
PropTypes
.
bool
,
columns
:
PropTypes
.
number
}
static
styles
=
theme
=>
({
root
:
{},
content
:
{
paddingTop
:
0
paddingTop
:
0
,
position
:
'
relative
'
},
tooltip
:
{
textAlign
:
'
center
'
,
position
:
'
absolute
'
position
:
'
absolute
'
,
pointerEvents
:
'
none
'
,
opacity
:
0
},
tooltipContent
:
{
display
:
'
inline
'
,
// copy of the material ui popper style
display
:
'
inline-block
'
,
color
:
'
#fff
'
,
padding
:
'
4px 8px
'
,
fontSize
:
'
0.625rem
'
,
...
...
@@ -84,7 +86,6 @@ class QuantityHistogramUnstyled extends React.Component {
constructor
(
props
)
{
super
(
props
)
this
.
container
=
React
.
createRef
()
this
.
svgEl
=
React
.
createRef
()
}
state
=
{
...
...
@@ -110,147 +111,204 @@ class QuantityHistogramUnstyled extends React.Component {
}
updateChart
()
{
const
{
classes
,
sort
,
tooltips
}
=
this
.
props
if
(
!
this
.
props
.
data
)
{
return
}
const
{
classes
}
=
this
.
props
const
{
scalePower
}
=
this
.
state
const
selected
=
this
.
props
.
value
const
width
=
this
.
container
.
current
.
offsetWidth
const
left
=
this
.
container
.
current
.
offsetLeft
const
top
=
this
.
container
.
current
.
offsetTop
const
height
=
Object
.
keys
(
this
.
props
.
data
).
length
*
32
const
data
=
Object
.
keys
(
this
.
props
.
data
)
.
map
(
key
=>
({
key
:
key
,
name
:
map
Key
(
key
)
,
name
:
_
map
ping
[
key
]
||
key
,
value
:
this
.
props
.
data
[
key
][
this
.
props
.
metric
]
}))
// keep the data sorting, but put unavailable and not processed to the end
const
unavailableIndex
=
data
.
findIndex
(
d
=>
d
.
name
===
unavailableLabel
)
const
unprocessedIndex
=
data
.
findIndex
(
d
=>
d
.
name
===
unprocessedLabel
)
if
(
unavailableIndex
!==
-
1
)
{
data
.
push
(
data
.
splice
(
unavailableIndex
,
1
)[
0
])
if
(
sort
)
{
data
.
sort
((
a
,
b
)
=>
{
const
nameA
=
a
.
name
const
nameB
=
b
.
name
if
(
nameA
===
nameB
)
{
return
0
}
if
(
nameA
===
unprocessedLabel
)
{
return
1
}
if
(
nameB
===
unprocessedLabel
)
{
return
-
1
}
if
(
nameA
===
unavailableLabel
)
{
return
1
}
if
(
nameB
===
unavailableLabel
)
{
return
-
1
}
return
nameA
.
localeCompare
(
nameB
)
})
}
else
{
// keep the data sorting, but put unavailable and not processed to the end
const
unavailableIndex
=
data
.
findIndex
(
d
=>
d
.
name
===
unavailableLabel
)
const
unprocessedIndex
=
data
.
findIndex
(
d
=>
d
.
name
===
unprocessedLabel
)
if
(
unavailableIndex
!==
-
1
)
{
data
.
push
(
data
.
splice
(
unavailableIndex
,
1
)[
0
])
}
if
(
unprocessedIndex
!==
-
1
)
{
data
.
push
(
data
.
splice
(
unprocessedIndex
,
1
)[
0
])
}
}
if
(
unprocessedIndex
!==
-
1
)
{
data
.
push
(
data
.
splice
(
unprocessedIndex
,
1
)[
0
])
const
columns
=
this
.
props
.
columns
||
1
const
columnSize
=
Math
.
ceil
(
data
.
length
/
columns
)
for
(
let
i
=
data
.
length
;
i
<
columnSize
*
columns
;
i
++
)
{
data
.
push
({
key
:
`empty
${
i
}
`
,
name
:
''
,
value
:
0
})
}
const
columnsData
=
split
(
data
,
columns
)
const
{
scalePower
}
=
this
.
state
const
selected
=
this
.
props
.
value
const
containerWidth
=
this
.
container
.
current
.
offsetWidth
const
width
=
containerWidth
/
columns
-
(
12
*
(
columns
-
1
))
const
height
=
columnSize
*
32
const
y
=
scaleBand
().
rangeRound
([
0
,
height
]).
padding
(
0.1
)
const
x
=
scalePow
().
range
([
0
,
width
]).
exponent
(
scalePower
)
// we use at least the domain 0..1, because an empty domain causes a weird layout
const
max
=
d3
.
max
(
data
,
d
=>
d
.
value
)
||
1
x
.
domain
([
0
,
max
])
y
.
domain
(
data
.
map
(
d
=>
d
.
name
))
const
tooltip
=
d3
.
select
(
this
.
container
.
current
)
.
append
(
'
div
'
)
.
attr
(
'
class
'
,
classes
.
tooltip
)
.
style
(
'
width
'
,
width
+
'
px
'
)
.
style
(
'
opacity
'
,
0
)
const
tooltipContent
=
tooltip
.
append
(
'
div
'
)
.
attr
(
'
class
'
,
classes
.
tooltipContent
)
let
svg
=
d3
.
select
(
this
.
svgEl
.
current
)
svg
.
attr
(
'
width
'
,
width
)
svg
.
attr
(
'
height
'
,
height
)
let
withData
=
svg
.
selectAll
(
'
.item
'
)
.
data
(
data
,
data
=>
data
.
name
)
withData
.
exit
().
remove
()
const
rectColor
=
d
=>
selected
===
d
.
key
?
nomadPrimaryColor
.
main
:
nomadSecondaryColor
.
light
const
textColor
=
d
=>
selected
===
d
.
key
?
'
#FFF
'
:
'
#000
'
let
item
=
withData
.
enter
()
const
container
=
d3
.
select
(
this
.
container
.
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
)
const
columnsG
=
svg
.
selectAll
(
'
.column
'
)
.
data
(
columnsData
.
map
((
_
,
i
)
=>
`column
${
i
}
`
))
columnsG
.
exit
().
remove
()
columnsG
.
enter
()
.
append
(
'
g
'
)
.
attr
(
'
class
'
,
'
item
'
)
item
.
append
(
'
rect
'
)
.
attr
(
'
class
'
,
'
bar
'
)
.
attr
(
'
x
'
,
x
(
0
))
.
attr
(
'
y
'
,
d
=>
y
(
d
.
name
))
.
attr
(
'
width
'
,
d
=>
x
(
d
.
value
)
-
x
(
0
))
.
attr
(
'
height
'
,
y
.
bandwidth
())
.
style
(
'
fill
'
,
rectColor
)
// .style('stroke', '#000')
// .style('stroke-width', '1px')
.
style
(
'
shape-rendering
'
,
'
geometricPrecision
'
)
item
.
append
(
'
text
'
)
.
attr
(
'
class
'
,
'
name
'
)
.
attr
(
'
dy
'
,
'
.75em
'
)
.
attr
(
'
x
'
,
x
(
0
)
+
4
)
.
attr
(
'
y
'
,
d
=>
y
(
d
.
name
)
+
4
)
.
attr
(
'
text-anchor
'
,
'
start
'
)
.
style
(
'
fill
'
,
textColor
)
.
text
(
d
=>
d
.
name
)
item
.
append
(
'
text
'
)
.
attr
(
'
class
'
,
'
value
'
)
.
attr
(
'
dy
'
,
y
.
bandwidth
())
.
attr
(
'
y
'
,
d
=>
y
(
d
.
name
)
-
4
)
.
attr
(
'
x
'
,
d
=>
width
-
4
)
.
attr
(
'
text-anchor
'
,
'
end
'
)
.
style
(
'
fill
'
,
textColor
)
.
text
(
d
=>
formatQuantity
(
d
.
value
))
item
.
style
(
'
cursor
'
,
'
pointer
'
)
.
on
(
'
click
'
,
d
=>
this
.
handleItemClicked
(
d
))
item
.
on
(
'
mouseover
'
,
function
(
d
)
{
tooltip
.
transition
()
.
duration
(
200
)
.
style
(
'
opacity
'
,
0.9
)
tooltip
.
style
(
'
left
'
,
left
+
'
px
'
)
.
style
(
'
top
'
,
(
y
(
d
.
name
)
+
top
+
32
)
+
'
px
'
)
tooltipContent
.
html
(
mapKey
(
d
.
key
,
false
))
})
.
on
(
'
mouseout
'
,
function
(
d
)
{
tooltip
.
transition
()
.
duration
(
500
)
.
style
(
'
opacity
'
,
0
)
})
const
t
=
d3
.
transition
().
duration
(
500
)
item
=
withData
.
transition
(
t
)
item
.
select
(
'
.bar
'
)
.
attr
(
'
y
'
,
d
=>
y
(
d
.
name
))
.
attr
(
'
width
'
,
d
=>
x
(
d
.
value
)
-
x
(
0
))
.
attr
(
'
height
'
,
y
.
bandwidth
())
.
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
=>
formatQuantity
(
d
.
value
))
.
attr
(
'
y
'
,
d
=>
y
(
d
.
name
)
-
4
)
.
attr
(
'
x
'
,
width
-
4
)
.
style
(
'
fill
'
,
textColor
)
.
attr
(
'
id
'
,
d
=>
d
)
.
attr
(
'
class
'
,
'
column
'
)
.
attr
(
'
transform
'
,
(
d
,
i
)
=>
`translate(
${
i
*
(
width
+
12
)}
, 0)`
)
columnsData
.
forEach
((
data
,
i
)
=>
{
const
y
=
scaleBand
().
rangeRound
([
0
,
height
]).
padding
(
0.1
)
y
.
domain
(
data
.
map
(
d
=>
d
.
name
))
const
items
=
svg
.
select
(
'
#column
'
+
i
)
.
selectAll
(
'
.item
'
)
.
data
(
data
,
d
=>
d
.
name
)
items
.
exit
().
remove
()
let
item
=
items
.
enter
()
.
append
(
'
g
'
)
.
attr
(
'
class
'
,
'
item
'
)
.
attr
(
'
display
'
,
d
=>
d
.
name
===
''
?
'
none
'
:
'
show
'
)
item
.
append
(
'
rect
'
)
.
attr
(
'
x
'
,
x
(
0
))
.
attr
(
'
y
'
,
d
=>
y
(
d
.
name
))
.
attr
(
'
width
'
,
width
)
.
attr
(
'
class
'
,
'
background
'
)
.
style
(
'
opacity
'
,
0
)
.
attr
(
'
height
'
,
y
.
bandwidth
())
item
.
append
(
'
rect
'
)
.
attr
(
'
class
'
,
'
bar
'
)
.
attr
(
'
x
'
,
x
(
0
))
.
attr
(
'
y
'
,
d
=>
y
(
d
.
name
))
.
attr
(
'
width
'
,
d
=>
x
(
d
.
value
)
-
x
(
0
))
.
attr
(
'
height
'
,
y
.
bandwidth
())
.
style
(
'
fill
'
,
rectColor
)
// .style('stroke', '#000')
// .style('stroke-width', '1px')
.
style
(
'
shape-rendering
'
,
'
geometricPrecision
'
)
item
.
append
(
'
text
'
)
.
attr
(
'
class
'
,
'
name
'
)
.
attr
(
'
dy
'
,
'
.75em
'
)
.
attr
(
'
x
'
,
x
(
0
)
+
4
)
.
attr
(
'
y
'
,
d
=>
y
(
d
.
name
)
+
4
)
.
attr
(
'
text-anchor
'
,
'
start
'
)
.
style
(
'
fill
'
,
textColor
)
.
text
(
d
=>
d
.
name
)
item
.
append
(
'
text
'
)
.
attr
(
'
class
'
,
'
value
'
)
.
attr
(
'
dy
'
,
y
.
bandwidth
())
.
attr
(
'
y
'
,
d
=>
y
(
d
.
name
)
-
4
)
.
attr
(
'
x
'
,
d
=>
width
-
4
)
.
attr
(
'
text-anchor
'
,
'
end
'
)
.
style
(
'
fill
'
,
textColor
)
.
text
(
d
=>
formatQuantity
(
d
.
value
))
item
.
style
(
'
cursor
'
,
'
pointer
'
)
.
on
(
'
click
'
,
d
=>
this
.
handleItemClicked
(
d
))
item
.
on
(
'
mouseover
'
,
function
(
d
)
{
d3
.
select
(
this
).
select
(
'
.background
'
)
.
style
(
'
opacity
'
,
0.08
)
if
(
tooltips
)
{
tooltip
.
transition
()
.
duration
(
200
)
.
style
(
'
opacity
'
,
1
)
tooltip
.
style
(
'
left
'
,
i
*
(
width
+
12
)
+
'
px
'
)