├── tests
├── __init__.py
├── conftest.py
├── _test_system.py
└── test_units.py
├── Procfile
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ └── config.yml
└── workflows
│ ├── cache_citations.yml
│ └── ci.yml
├── htm_dashboard
├── __init__.py
├── cache_citations.py
├── export.py
├── infos.py
├── layout.py
├── new_property_form.py
├── citations.json
├── graph.py
├── callbacks.py
└── tab.py
├── requirements.txt
├── Dockerfile
├── LICENSE
├── README.md
├── .gitignore
└── app.py
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn app:server
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [RemDelaporteMathurin]
4 |
5 |
--------------------------------------------------------------------------------
/htm_dashboard/__init__.py:
--------------------------------------------------------------------------------
1 | ACTIVE_GROUPS = [
2 | "diffusivity",
3 | "solubility",
4 | "permeability",
5 | "recombination_coeff",
6 | "dissociation_coeff",
7 | ]
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | contact_links:
2 | - name: Suggest new properties
3 | url: https://github.com/RemDelaporteMathurin/h-transport-materials/issues/new/choose
4 | about: Please suggest your property here.
5 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | h-transport-materials~=0.18
2 | numpy<2
3 | dash==2.9.3
4 | dash-bootstrap-components==1.1.0
5 | dash-bootstrap-templates==1.0.7
6 | dash_daq==0.5.0
7 | plotly==5.8.0
8 | jinja2==3.1.1
9 | gunicorn
10 | pandas==1.5.2
11 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from selenium.webdriver.chrome.options import Options
2 |
3 |
4 | def pytest_setup_options():
5 | options = Options()
6 | options.add_argument("--headless")
7 | options.add_argument("--disable-gpu")
8 | return options
9 |
--------------------------------------------------------------------------------
/htm_dashboard/cache_citations.py:
--------------------------------------------------------------------------------
1 | import h_transport_materials as htm
2 | from datetime import date
3 | import json
4 |
5 | citations_data = {"date": str(date.today()), "dois": {}}
6 |
7 | for i, prop in enumerate(htm.database):
8 | if prop.doi not in citations_data["dois"]:
9 | citations_data["dois"][prop.doi] = prop.nb_citations
10 | print(i)
11 |
12 | with open("htm_dashboard/citations.json", "w") as outfile:
13 | json.dump(citations_data, outfile, indent=4)
14 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM continuumio/miniconda3:4.9.2
2 |
3 | COPY app.py .
4 | ADD htm_dashboard ./htm_dashboard
5 | COPY requirements.txt .
6 |
7 | RUN pip install -r requirements.txt
8 |
9 | ENV PORT 8080
10 |
11 | EXPOSE 8080
12 |
13 | # Run the web service on container startup. Here we use the gunicorn
14 | # webserver, with one worker process and 8 threads.
15 | # For environments with multiple CPU cores, increase the number of workers
16 | # to be equal to the cores available.
17 | # Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run
18 | # to handle instance scaling. For more details see
19 | # https://cloud.google.com/run/docs/quickstarts/build-and-deploy/python
20 | CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:server
21 |
--------------------------------------------------------------------------------
/.github/workflows/cache_citations.yml:
--------------------------------------------------------------------------------
1 | name: refresh map
2 |
3 | on:
4 | schedule:
5 | - cron: "00 00 1 * *" # runs at 00:00 every 1st of month
6 |
7 | jobs:
8 | getdataandrefreshmap:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: checkout repo content
12 | uses: actions/checkout@v3 # checkout the repository content to github runner.
13 | - name: setup python
14 | uses: actions/setup-python@v4
15 | with:
16 | python-version: 3.8 #install the python needed
17 | - name: Install dependencies
18 | run: |
19 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
20 | - name: execute py script
21 | run: |
22 | python htm_dashboard/cache_citations.py
23 | git config user.name github-actions
24 | git config user.email github-actions@github.com
25 | git add .
26 | git commit -m "updated citations file"
27 | git push
--------------------------------------------------------------------------------
/tests/_test_system.py:
--------------------------------------------------------------------------------
1 | from app import app
2 |
3 | # dash_duo as a function argument
4 | def test_001_child_with_0(dash_duo):
5 | # 3. define your app inside the test function
6 | # 4. host the app locally in a thread, all dash server configs could be
7 | # passed after the first app argument
8 | dash_duo.start_server(app)
9 | # # 5. use wait_for_* if your target element is the result of a callback,
10 | # # keep in mind even the initial rendering can trigger callbacks
11 | dash_duo.wait_for_element_by_id("graph_diffusivity", timeout=4)
12 | # # 6. use this form if its present is expected at the action point
13 | # assert dash_duo.find_element("#graph_diffusivity").figure is not None
14 | assert dash_duo.get_logs() == []
15 | # # 7. to make the checkpoint more readable, you can describe the
16 | # # acceptance criterion as an assert message after the comma.
17 | # assert dash_duo.get_logs() == [], "browser console should contain no error"
18 | # # 8. visual testing with percy snapshot
19 | # dash_duo.percy_snapshot("test_001_child_with_0-layout")
20 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | - push
5 | - pull_request
6 |
7 | permissions:
8 | contents: read
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: Install Chrome and chromedriver
18 | uses: browser-actions/setup-chrome@v1
19 | with:
20 | chrome-version: "132.0.6821.2"
21 |
22 | - name: Set up Python 3.10
23 | uses: actions/setup-python@v3
24 | with:
25 | python-version: "3.10"
26 | - name: Install dependencies
27 | run: |
28 | python -m pip install --upgrade pip
29 | pip install pytest dash[testing]
30 | pip install -r requirements.txt
31 |
32 | - name: Test with pytest
33 | run: |
34 | pytest tests
35 |
36 | #--cov htm_dashboard --cov-report xml --cov-report term
37 |
38 | # - name: Upload to codecov
39 | # run: |
40 | # curl -Os https://uploader.codecov.io/latest/linux/codecov
41 |
42 | # chmod +x codecov
43 | # ./codecov
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Rémi Delaporte-Mathurin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/htm_dashboard/export.py:
--------------------------------------------------------------------------------
1 | import h_transport_materials as htm
2 | import json
3 | from jinja2 import Template
4 |
5 |
6 | type_to_database = {
7 | "diffusivity": "htm.diffusivities",
8 | "solubility": "htm.solubilities",
9 | "permeability": "htm.permeabilities",
10 | "recombination_coeff": "htm.recombination_coeffs",
11 | "dissociation_coeff": "htm.dissociation_coeffs",
12 | }
13 |
14 |
15 | python_template = Template(
16 | """import h_transport_materials as htm
17 | import matplotlib.pyplot as plt
18 | import numpy as np
19 |
20 | filtered_{{group}} = (
21 | {{database}}.filter(material={{materials}})
22 | .filter(author={{authors}})
23 | .filter(isotope={{isotopes}})
24 | .filter(year=np.arange({{yearmin}}, {{yearmax}}, step=1).tolist())
25 | )
26 |
27 | htm.plotting.plot(filtered_{{group}})
28 |
29 | plt.legend()
30 | plt.xlabel("1/T (K$^{-1}$)")
31 | plt.yscale("log")
32 | plt.show()
33 |
34 | """
35 | )
36 |
37 |
38 | def generate_python_code(materials, authors, isotopes, yearmin, yearmax, group):
39 | python_code = python_template.render(
40 | group=group,
41 | database=type_to_database[group],
42 | materials=[mat.lower() for mat in materials],
43 | authors=[author.lower() for author in authors],
44 | isotopes=[iso.lower() for iso in isotopes],
45 | yearmin="{}".format(yearmin),
46 | yearmax="{}".format(yearmax),
47 | )
48 | return python_code
49 |
--------------------------------------------------------------------------------
/tests/test_units.py:
--------------------------------------------------------------------------------
1 | from htm_dashboard.callbacks import create_make_download_data_function
2 | import h_transport_materials as htm
3 |
4 | from contextvars import copy_context
5 | from dash._callback_context import context_value
6 | from dash._utils import AttributeDict
7 | from datetime import datetime
8 |
9 | from htm_dashboard.callbacks import (
10 | create_make_download_data_function,
11 | make_citations_graph,
12 | )
13 |
14 |
15 | def test_export_groups():
16 | """Tests the export to json callback"""
17 | export_fun = create_make_download_data_function("diffusivity")
18 |
19 | def run_callback():
20 | context_value.set(
21 | AttributeDict(
22 | **{
23 | "triggered_inputs": [
24 | {"prop_id": "extract_button_diffusivity.n_clicks"}
25 | ]
26 | }
27 | )
28 | )
29 | return export_fun(
30 | 1,
31 | material_filter=["tungsten"],
32 | author_filter=["frauenfelder"],
33 | isotope_filter=["H", "D", "T"],
34 | year_filter=None,
35 | )
36 |
37 | ctx = copy_context()
38 | output = ctx.run(run_callback)
39 | assert type(output["content"]) == str
40 |
41 |
42 | def test_citation_graphs_per_year_same_year():
43 | current_year = datetime.now().year
44 |
45 | group = htm.PropertiesGroup([htm.Property(year=current_year)])
46 | make_citations_graph(group, per_year=True)
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # h-transport-materials-dashboard
2 |
3 | Finding material properties for hydrogen transport is often tedious as many different values can be found in literature.
4 | The goal of this tool is to help scientists visualise material properties and compare them.
5 |
6 | This web app relies on the hydrogen transport materials database [HTM](https://github.com/RemDelaporteMathurin/h-transport-materials).
7 |
8 | :point_right: [The app!](https://htm-dashboard-uan5l4xr6a-od.a.run.app/)
9 |
10 | :warning: Disclaimer:
11 | This web app is built for visualising properties.
12 | For integrations in workflows and applications, we recommend using the python API [HTM](https://github.com/RemDelaporteMathurin/h-transport-materials)
13 |
14 | 
15 |
16 |
17 | ## Features
18 | - Quickly visualise the HTM database
19 | - Add your own properties
20 | - Compute mean curves of properties groups
21 | - Extract data to JSON
22 | - Extract python script (using the htm API)
23 |
24 | ## Stats
25 | - How many times were the papers cited?
26 | - When were the papers published?
27 | - Repartition by material, isotope, author.
28 |
29 | 
30 |
31 | ## Contributing
32 |
33 | - "I want to contribute to the database" :point_right: [go to the database repository](https://github.com/RemDelaporteMathurin/h-transport-materials)
34 | - "I want to contribute to the web app" :point_right: you are at the right place! Feel free to [open an issue](https://github.com/RemDelaporteMathurin/h-transport-materials-dashboard/issues/new)
35 |
--------------------------------------------------------------------------------
/htm_dashboard/infos.py:
--------------------------------------------------------------------------------
1 | import h_transport_materials
2 | from dash import html
3 |
4 | try:
5 | version = h_transport_materials.__version__
6 | except AttributeError:
7 | version = "[unknown]"
8 |
9 | text_infos = [
10 | html.Div(
11 | [
12 | "Finding material properties for ",
13 | html.B("hydrogen transport"),
14 | " is often tedious as many different values can be found in literature. ",
15 | "The goal of this tool is to help scientists visualise material properties and compare them.",
16 | ]
17 | ),
18 | html.Br(),
19 | html.Div(
20 | "On the left-hand side of the screen, properties can be filtered by material, author, isotope or by year of publication."
21 | ),
22 | html.Br(),
23 | html.Div(
24 | [
25 | html.B("Compute mean curve"),
26 | ": calculates the mean curve of the displayed properties.",
27 | ]
28 | ),
29 | html.Br(),
30 | html.Div(
31 | [
32 | html.B("Add property"),
33 | ": adds a custom diffusivity or solubility to the dataset.",
34 | ]
35 | ),
36 | html.Br(),
37 | html.Div(
38 | [html.B("Extract data"), ": downloads the displayed properties to a JSON file."]
39 | ),
40 | html.Br(),
41 | html.Div(
42 | [
43 | html.B("Python"),
44 | ": generates a python script using h-transport-materials to plot the displayed data.",
45 | ]
46 | ),
47 | html.Br(),
48 | html.Div(
49 | "This dashboard relies on the library H-transport-materials (HTM) v{} and Plotly dash.".format(
50 | version
51 | )
52 | ),
53 | ]
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 |
132 | .DS_Store
133 |
--------------------------------------------------------------------------------
/htm_dashboard/layout.py:
--------------------------------------------------------------------------------
1 | from .infos import text_infos
2 | from .new_property_form import make_form
3 | from htm_dashboard import ACTIVE_GROUPS
4 | from .tab import make_tab
5 |
6 | from dash import html
7 | import dash_bootstrap_components as dbc
8 | from dash_bootstrap_templates import ThemeSwitchAIO
9 |
10 |
11 | def make_modal_add_property(property: str):
12 |
13 | modal = dbc.Modal(
14 | [
15 | dbc.ModalHeader(dbc.ModalTitle(html.H2(f"Add a {property}"))),
16 | dbc.ModalBody(make_form(property)),
17 | dbc.ModalFooter(
18 | [
19 | html.Div("", id=f"error_message_new_{property}"),
20 | dbc.Button(
21 | "Submit",
22 | id=f"submit_new_{property}",
23 | color="primary",
24 | n_clicks="0",
25 | ),
26 | ]
27 | ),
28 | ],
29 | id=f"modal_add_{property}",
30 | is_open=False,
31 | # size="lg",
32 | )
33 | return modal
34 |
35 |
36 | template_theme1 = "plotly_white"
37 | template_theme2 = "plotly_dark"
38 | url_theme1 = dbc.themes.MINTY
39 | url_theme2 = dbc.themes.CYBORG
40 | theme_switch = ThemeSwitchAIO(aio_id="theme", themes=[url_theme1, url_theme2])
41 |
42 | header = dbc.Row(
43 | [
44 | dbc.Col(
45 | [
46 | html.H2("H-transport properties dashboard"),
47 | html.H5("Rémi Delaporte-Mathurin"),
48 | ],
49 | width=9,
50 | ),
51 | dbc.Col(
52 | html.Div(
53 | [
54 | html.A(
55 | [
56 | "Infos",
57 | html.Img(
58 | src="https://dash.gallery/dash-world-cell-towers/assets/question-circle-solid.svg",
59 | height=20,
60 | style={"margin-left": "5px"},
61 | ),
62 | ],
63 | style={
64 | "margin-right": "45px",
65 | "cursor": "pointer",
66 | },
67 | id="open-sm",
68 | ),
69 | dbc.Modal(
70 | [
71 | dbc.ModalHeader(
72 | dbc.ModalTitle(
73 | html.H2(
74 | "Welcome to the H-transport materials dashboard!"
75 | )
76 | )
77 | ),
78 | dbc.ModalBody(text_infos),
79 | dbc.ModalFooter("Contact: rdelaportemathurin@gmail.com"),
80 | ],
81 | id="modal-infos",
82 | is_open=False,
83 | size="lg",
84 | ),
85 | html.A(
86 | [
87 | "View it on GitHub",
88 | html.Img(
89 | src="https://cdn-icons-png.flaticon.com/512/25/25231.png",
90 | height=40,
91 | ),
92 | ],
93 | href="https://github.com/RemDelaporteMathurin/h-transport-materials-dashboard",
94 | target="_blank", # opens in a new tab
95 | ),
96 | theme_switch,
97 | ]
98 | ),
99 | align="end",
100 | width=3,
101 | ),
102 | ],
103 | align="end",
104 | )
105 |
106 | layout = dbc.Container(
107 | [
108 | header,
109 | html.Hr(),
110 | dbc.Tabs(
111 | id="tabs-example-graph",
112 | children=[make_tab(group) for group in ACTIVE_GROUPS],
113 | ),
114 | ]
115 | + [make_modal_add_property(group) for group in ACTIVE_GROUPS],
116 | fluid=True,
117 | className="dbc bg-opacity-10 bg-black mb-2",
118 | )
119 |
--------------------------------------------------------------------------------
/htm_dashboard/new_property_form.py:
--------------------------------------------------------------------------------
1 | import dash_bootstrap_components as dbc
2 | from dash import html
3 | import h_transport_materials as htm
4 |
5 |
6 | def make_form(property_type: str):
7 |
8 | if property_type == "diffusivity":
9 | pre_exp_label = f"D_0 ({htm.Diffusivity().units:~P})"
10 | act_energy_label = "E_D (eV)"
11 | elif property_type == "solubility":
12 | pre_exp_label = f"S_0 (Sievert: {htm.Solubility(law='sievert').units:~P}, Henry: {htm.Solubility(law='henry').units:~P})"
13 | act_energy_label = "E_S (eV)"
14 | elif property_type == "permeability":
15 | pre_exp_label = f"P_0 (Sievert: {htm.Permeability(law='sievert').units:~P}, Henry: {htm.Permeability(law='henry').units:~P})"
16 | act_energy_label = "E_P (eV)"
17 | elif property_type == "recombination_coeff":
18 | pre_exp_label = f"Kr_0 ({htm.RecombinationCoeff().units:~P})"
19 | act_energy_label = "E_Kr (eV)"
20 | elif property_type == "dissociation_coeff":
21 | pre_exp_label = f"Kd_0 ({htm.DissociationCoeff().units:~P})"
22 | act_energy_label = "E_Kd (eV)"
23 |
24 | preexponential_input = html.Div(
25 | [
26 | dbc.Label(pre_exp_label, width=10),
27 | dbc.Col(
28 | dbc.Input(
29 | type="number",
30 | id=f"new_{property_type}_pre_exp",
31 | placeholder="Enter pre-exponential factor",
32 | required=True,
33 | ),
34 | width=10,
35 | ),
36 | ],
37 | )
38 |
39 | activation_energy_input = html.Div(
40 | [
41 | dbc.Label(act_energy_label, width=2),
42 | dbc.Col(
43 | dbc.Input(
44 | type="number",
45 | id=f"new_{property_type}_act_energy",
46 | placeholder="Enter activation energy",
47 | required=True,
48 | ),
49 | width=10,
50 | ),
51 | ],
52 | )
53 |
54 | author_input = html.Div(
55 | [
56 | dbc.Label("Author", width=2),
57 | dbc.Col(
58 | dbc.Input(
59 | type="text",
60 | id=f"new_{property_type}_author",
61 | placeholder="Enter author",
62 | required=True,
63 | ),
64 | width=10,
65 | ),
66 | ],
67 | )
68 |
69 | year_input = html.Div(
70 | [
71 | dbc.Label("Year", width=2),
72 | dbc.Col(
73 | dbc.Input(
74 | type="number",
75 | id=f"new_{property_type}_year",
76 | placeholder="Enter year of publication",
77 | required=True,
78 | ),
79 | width=10,
80 | ),
81 | ],
82 | )
83 |
84 | isotope_input = html.Div(
85 | [
86 | dbc.Label("Isotope", width=2),
87 | dbc.Col(
88 | dbc.RadioItems(
89 | id=f"new_{property_type}_isotope",
90 | value="H",
91 | options=[
92 | {"label": "H", "value": "H"},
93 | {"label": "D", "value": "D"},
94 | {"label": "T", "value": "T"},
95 | ],
96 | ),
97 | width=10,
98 | ),
99 | ],
100 | )
101 |
102 | material_input = html.Div(
103 | [
104 | dbc.Label("Material", width=2),
105 | dbc.Col(
106 | dbc.Input(
107 | type="text",
108 | id=f"new_{property_type}_material",
109 | placeholder="Enter material",
110 | required=True,
111 | ),
112 | width=10,
113 | ),
114 | ],
115 | )
116 |
117 | temperature_input = html.Div(
118 | [
119 | dbc.Label("Temperature range", width="auto"),
120 | dbc.Row(
121 | [
122 | dbc.Col(
123 | dbc.Input(
124 | type="number",
125 | id=f"new_{property_type}_range_low",
126 | placeholder="300 K",
127 | ),
128 | width=3,
129 | ),
130 | dbc.Col(
131 | dbc.Input(
132 | type="number",
133 | id=f"new_{property_type}_range_high",
134 | placeholder="1200 K",
135 | ),
136 | width=3,
137 | ),
138 | ]
139 | ),
140 | ],
141 | )
142 |
143 | inputs = [
144 | preexponential_input,
145 | activation_energy_input,
146 | author_input,
147 | year_input,
148 | isotope_input,
149 | material_input,
150 | temperature_input,
151 | ]
152 | if property_type in ["solubility", "permeability"]:
153 | solubility_law_input = html.Div(
154 | [
155 | dbc.Label("Law", width=2),
156 | dbc.Col(
157 | dbc.RadioItems(
158 | id=f"new_{property_type}_law",
159 | value="sievert",
160 | options=[
161 | {"label": "Sievert", "value": "sievert"},
162 | {"label": "Henry", "value": "henry"},
163 | ],
164 | ),
165 | width=10,
166 | ),
167 | ],
168 | )
169 | inputs.insert(0, solubility_law_input)
170 | form = dbc.Form(inputs)
171 | return form
172 |
--------------------------------------------------------------------------------
/htm_dashboard/citations.json:
--------------------------------------------------------------------------------
1 | {
2 | "date": "2025-12-01",
3 | "dois": {
4 | "10.1016/S1359-6454(98)00333-4": 271,
5 | "null": 0,
6 | "10.1149/1.2428079": 18,
7 | "10.1016/S0022-3115(97)00146-3": 42,
8 | "10.1016/0956-7151(94)90329-8": 35,
9 | "10.1088/0305-4608/13/6/013": 91,
10 | "10.2320/matertrans1989.32.1109": 73,
11 | "10.1016/0036-9748(82)90354-4": 41,
12 | "10.1016/0001-6160(86)90219-1": 94,
13 | "10.1111/j.1551-2916.2004.00003.x": 74,
14 | "10.1103/PhysRevB.69.024302": 79,
15 | "10.1111/j.1151-2916.1977.tb15493.x": 139,
16 | "10.1103/PhysRevB.4.330": 279,
17 | "10.1116/1.1318061": 122,
18 | "10.1016/0022-3115(87)90349-7": 24,
19 | "10.1016/0022-3115(90)90127-9": 28,
20 | "10.1016/S0022-3115(98)00878-2": 18,
21 | "10.2320/jinstmet1952.46.3_285": 21,
22 | "10.1016/j.fusengdes.2010.03.063": 13,
23 | "10.1080/14786437608228179": 148,
24 | "10.1016/S0022-3697(73)80022-8": 99,
25 | "10.1016/j.nme.2022.101256": 0,
26 | "10.1016/S0022-3115(98)00276-1": 18,
27 | "10.1016/j.jnucmat.2016.01.035": 15,
28 | "10.5772/34469": 8,
29 | "10.13182/FST41-872": 62,
30 | "10.1016/j.jnucmat.2021.152904": 20,
31 | "10.1016/j.jnucmat.2007.03.114": 82,
32 | "10.1016/j.nme.2021.101062": 6,
33 | "10.1016/j.nme.2019.01.030": 26,
34 | "10.1016/j.jnucmat.2006.07.011": 34,
35 | "10.1039/D0TA10576G": 28,
36 | "10.13538/j.1001-8042/nst.25.040602": 0,
37 | "10.1016/j.net.2018.10.008": 4,
38 | "10.1016/j.fusengdes.2008.05.016": 44,
39 | "10.1016/j.jnucmat.2004.04.220": 34,
40 | "10.1016/S0920-3796(89)80124-3": 40,
41 | "10.1021/j100869a013": 57,
42 | "10.1021/i160051a015": 57,
43 | "10.1016/0022-3115(88)90301-7": 29,
44 | "10.1016/0920-3796(91)90003-9": 114,
45 | "10.1016/0022-3115(87)90006-7": 40,
46 | "10.1016/0022-3115(92)90504-E": 45,
47 | "10.1016/0022-3115(83)90069-7": 42,
48 | "10.1016/0022-3115(84)90198-3": 54,
49 | "10.1016/0022-3115(85)90127-8": 28,
50 | "10.1002/bbpc.19900940612": 20,
51 | "10.1016/j.fusengdes.2005.06.364": 73,
52 | "10.1016/j.fusengdes.2015.05.060": 8,
53 | "10.1016/j.jnucmat.2010.12.126": 28,
54 | "10.13182/FST60-1163": 15,
55 | "10.1016/j.fusengdes.2012.03.004": 20,
56 | "10.1585/pfr.7.2405074": 1,
57 | "https://doi.org/10.1016/0022-5088(86)90682-X": 6,
58 | "10.1016/j.fusengdes.2023.114136": 2,
59 | "10.1021/j100612a013": 74,
60 | "10.1016/0022-1902(79)80077-9": 26,
61 | "10.1007/s11661-998-1011-3": 111,
62 | "10.1016/S0022-3115(98)00038-5": 126,
63 | "10.1016/j.ijhydene.2013.01.091": 77,
64 | "10.1016/0022-3115(72)90065-7": 186,
65 | "10.1016/0022-3115(67)90047-5": 370,
66 | "10.1016/0022-3115(89)90446-7": 61,
67 | "10.1016/0022-3115(90)90274-Q": 95,
68 | "10.1016/0022-3115(67)90190-0": 75,
69 | "10.2172/10191406": 12,
70 | "10.1016/0022-3115(89)90265-1": 136,
71 | "10.1016/0022-3115(88)90247-4": 90,
72 | "10.1063/1.4813919": 48,
73 | "10.1016/0022-3115(93)90332-S": 3,
74 | "10.1016/B978-0-12-803581-8.11754-0": 9,
75 | "10.1016/B978-0-12-522660-8.50010-6": 86,
76 | "10.2320/matertrans1960.26.869": 15,
77 | "10.1515/ijmr-1958-490502": 5,
78 | "10.1016/0022-3115(93)90392-C": 9,
79 | "10.1016/j.matlet.2004.12.037": 52,
80 | "10.2355/tetsutohagane1955.64.5_615": 3,
81 | "10.1063/5.0204192": 1,
82 | "10.1016/0022-3115(85)90056-X": 44,
83 | "10.2172/4583045": 18,
84 | "10.1080/15361055.2020.1725368": 6,
85 | "https://doi.org/10.1016/j.jnucmat.2023.154851": 2,
86 | "10.1016/S0022-3115(09)80083-4": 12,
87 | "10.1016/0022-3697(82)90104-4": 48,
88 | "10.1063/1.1669720": 90,
89 | "10.1016/0022-3115(74)90263-3": 19,
90 | "10.1016/0001-6160(75)90057-7": 97,
91 | "10.1016/0022-5088(91)90195-A": 24,
92 | "10.1088/0305-4608/13/10/015": 134,
93 | "10.1021/j100723a033": 311,
94 | "10.1007/BF02651617": 20,
95 | "10.2172/4413386": 4,
96 | "10.1103/PhysRevB.43.6968": 21,
97 | "https://doi.org/10.1016/S0022-3115(02)01370-3": 13,
98 | "10.1016/0036-9748(79)90391-0": 22,
99 | "10.2172/6420120": 10,
100 | "10.1116/1.1492699": 510,
101 | "10.1016/j.jnucmat.2014.09.003": 26,
102 | "10.1063/1.3386515": 195,
103 | "10.1557/JMR.2010.0036": 252,
104 | "10.1063/1.1725575": 68,
105 | "10.1007/BF00715727": 47,
106 | "10.1016/S0022-3115(01)00486-X": 51,
107 | "10.1088/1402-4896/ab4b42": 29,
108 | "10.1016/j.actamat.2015.04.052": 207,
109 | "10.13182/FST92-A29837": 150,
110 | "10.1080/15361055.2019.1705727": 5,
111 | "10.1016/j.jnucmat.2018.04.021": 4,
112 | "10.1016/j.fusengdes.2016.09.003": 21,
113 | "https://doi.org/10.1016/j.fusengdes.2016.03.045": 18,
114 | "10.1088/0031-8949/2009/T138/014052": 24,
115 | "10.13182/FST11-A12707": 17,
116 | "10.1016/j.fusengdes.2019.03.083": 9,
117 | "10.1016/j.jnucmat.2020.152675": 7,
118 | "10.3390/membranes12060579": 8,
119 | "10.1016/B978-0-08-056033-5.00116-6": 117,
120 | "10.1016/0022-3115(88)90038-4": 143,
121 | "10.1238/Physica.Topical.094a00121": 17,
122 | "10.1016/S0022-3115(00)00188-4": 36,
123 | "10.1016/S0022-3115(00)00314-7": 10,
124 | "10.1016/j.fusengdes.2005.07.019": 47,
125 | "10.1016/S0022-3115(97)00021-4": 153,
126 | "10.1016/j.fusengdes.2017.04.022": 16,
127 | "10.1016/0001-6160(86)90123-9": 265,
128 | "10.1016/0920-3796(94)00434-X": 14,
129 | "10.1016/0921-5093(89)90857-5": 101,
130 | "10.3938/jkps.59.3019": 16,
131 | "10.13182/FST85-A24629": 29,
132 | "https://doi.org/10.1016/0022-3115(91)90091-K": 16,
133 | "10.13182/FST57-3-185": 6,
134 | "10.1016/0022-3115(87)90476-4": 51,
135 | "10.1016/0022-3115(80)90219-6": 41,
136 | "10.1016/j.jnucmat.2015.02.040": 62,
137 | "10.1016/0022-3115(82)90214-8": 36,
138 | "10.13182/FST84-A23218": 9,
139 | "10.1016/0022-3115(85)90461-1": 20,
140 | "10.1016/S0022-3115(01)00715-2": 30,
141 | "10.1016/j.jnucmat.2007.03.239": 17,
142 | "10.1016/S0920-3796(00)00305-7": 16,
143 | "https://doi.org/10.1016/j.jnucmat.2016.04.054": 44,
144 | "10.2172/5361604": 0,
145 | "https://doi.org/10.1016/0022-3115(80)90397-9": 26,
146 | "10.1063/1.439496": 36,
147 | "https://doi.org/10.1016/0022-3115(93)90427-Z": 8,
148 | "https://doi.org/10.1016/0022-3115(90)90125-7": 16,
149 | "https://doi.org/10.1016/j.jnucmat.2023.154484": 10,
150 | "10.1063/1.1708243": 99,
151 | "10.1016/j.nme.2016.09.001": 10,
152 | "10.1111/j.1151-2916.1978.tb09284.x": 66
153 | }
154 | }
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | from htm_dashboard.layout import layout
2 | from htm_dashboard import ACTIVE_GROUPS
3 | import htm_dashboard.callbacks as cb
4 |
5 | import dash
6 | import dash_bootstrap_components as dbc
7 | from dash_bootstrap_templates import ThemeSwitchAIO
8 |
9 | # stylesheet with the .dbc class
10 | dbc_css = "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates/dbc.min.css"
11 |
12 | app = dash.Dash(__name__, external_stylesheets=[dbc.themes.MINTY, dbc_css])
13 |
14 | server = app.server
15 |
16 | app.layout = layout
17 |
18 |
19 | @app.callback(
20 | dash.Output("modal-infos", "is_open"),
21 | dash.Input("open-sm", "n_clicks"),
22 | dash.State("modal-infos", "is_open"),
23 | )
24 | def toggle_modal(n1, is_open):
25 | if n1:
26 | return not is_open
27 | return is_open
28 |
29 |
30 | for group in ACTIVE_GROUPS:
31 |
32 | app.callback(
33 | dash.Output(f"graph_nb_citations_{group}", "figure"),
34 | dash.Input(f"graph_{group}", "figure"),
35 | dash.Input(f"per_year_citations_{group}", "on"),
36 | dash.State(f"material_filter_{group}", "value"),
37 | dash.State(f"isotope_filter_{group}", "value"),
38 | dash.State(f"author_filter_{group}", "value"),
39 | dash.State(f"year_filter_{group}", "value"),
40 | )(cb.create_make_citations_figure_function(group))
41 |
42 | app.callback(
43 | dash.Output(f"material_filter_{group}", "value"),
44 | dash.Input(f"add_all_materials_{group}", "n_clicks"),
45 | )(cb.create_add_all_materials_function(group))
46 |
47 | app.callback(
48 | dash.Output(f"author_filter_{group}", "value"),
49 | dash.Input(f"add_all_authors_{group}", "n_clicks"),
50 | )(cb.create_add_all_authors_function(group))
51 |
52 | app.callback(
53 | dash.Output(f"graph_{group}", "figure"),
54 | dash.Input(f"material_filter_{group}", "value"),
55 | dash.Input(f"isotope_filter_{group}", "value"),
56 | dash.Input(f"author_filter_{group}", "value"),
57 | dash.Input(f"year_filter_{group}", "value"),
58 | dash.Input(f"mean_button_{group}", "n_clicks"),
59 | dash.Input(f"colour-by_{group}", "value"),
60 | dash.Input(ThemeSwitchAIO.ids.switch("theme"), "value"),
61 | )(cb.create_update_graph_function(group))
62 |
63 | app.callback(
64 | dash.Output(f"graph_prop_per_year_{group}", "figure"),
65 | dash.Input(f"graph_{group}", "figure"),
66 | dash.State(f"material_filter_{group}", "value"),
67 | dash.State(f"isotope_filter_{group}", "value"),
68 | dash.State(f"author_filter_{group}", "value"),
69 | dash.State(f"year_filter_{group}", "value"),
70 | )(cb.create_update_entries_per_year_graph_function(group))
71 |
72 | app.callback(
73 | dash.Output(f"graph_materials_{group}", "figure"),
74 | dash.Input(f"graph_{group}", "figure"),
75 | dash.State(f"material_filter_{group}", "value"),
76 | dash.State(f"isotope_filter_{group}", "value"),
77 | dash.State(f"author_filter_{group}", "value"),
78 | dash.State(f"year_filter_{group}", "value"),
79 | )(cb.create_update_piechart_material_function(group))
80 |
81 | app.callback(
82 | dash.Output(f"graph_isotopes_{group}", "figure"),
83 | dash.Input(f"graph_{group}", "figure"),
84 | dash.State(f"material_filter_{group}", "value"),
85 | dash.State(f"isotope_filter_{group}", "value"),
86 | dash.State(f"author_filter_{group}", "value"),
87 | dash.State(f"year_filter_{group}", "value"),
88 | )(cb.create_update_piechart_isotopes_function(group))
89 |
90 | app.callback(
91 | dash.Output(f"graph_authors_{group}", "figure"),
92 | dash.Input(f"graph_{group}", "figure"),
93 | dash.State(f"material_filter_{group}", "value"),
94 | dash.State(f"isotope_filter_{group}", "value"),
95 | dash.State(f"author_filter_{group}", "value"),
96 | dash.State(f"year_filter_{group}", "value"),
97 | )(cb.create_update_piechart_authors_function(group))
98 |
99 | app.callback(
100 | dash.Output(f"download-text_{group}", "data"),
101 | dash.Input(f"extract_button_{group}", "n_clicks"),
102 | dash.Input(f"material_filter_{group}", "value"),
103 | dash.Input(f"isotope_filter_{group}", "value"),
104 | dash.Input(f"author_filter_{group}", "value"),
105 | dash.Input(f"year_filter_{group}", "value"),
106 | prevent_initial_call=True,
107 | )(cb.create_make_download_data_function(group))
108 |
109 | app.callback(
110 | dash.Output(f"download-python_{group}", "data"),
111 | dash.Input(f"python_button_{group}", "n_clicks"),
112 | dash.Input(f"material_filter_{group}", "value"),
113 | dash.Input(f"isotope_filter_{group}", "value"),
114 | dash.Input(f"author_filter_{group}", "value"),
115 | dash.Input(f"year_filter_{group}", "value"),
116 | prevent_initial_call=True,
117 | )(cb.make_download_python_callback(group))
118 |
119 | app.callback(
120 | dash.Output(f"modal_add_{group}", "is_open"),
121 | dash.Input(f"add_property_{group}", "n_clicks"),
122 | dash.Input(f"submit_new_{group}", "n_clicks"),
123 | dash.State(f"modal_add_{group}", "is_open"),
124 | dash.State(f"new_{group}_pre_exp", "value"),
125 | dash.State(f"new_{group}_act_energy", "value"),
126 | dash.State(f"new_{group}_author", "value"),
127 | dash.State(f"new_{group}_year", "value"),
128 | dash.State(f"new_{group}_isotope", "value"),
129 | dash.State(f"new_{group}_material", "value"),
130 | prevent_initial_call=True,
131 | )(cb.make_toggle_modal_function(group))
132 |
133 | # add property form
134 | # since an additional parameter is needed for solubility and permeability
135 | # we need to check the group
136 | if group in ["solubility", "permeability"]:
137 | app.callback(
138 | dash.Output(f"material_filter_{group}", "options"),
139 | dash.Output(f"author_filter_{group}", "options"),
140 | dash.Output(f"error_message_new_{group}", "children"),
141 | dash.Input(f"submit_new_{group}", "n_clicks"),
142 | dash.Input(f"material_filter_{group}", "value"),
143 | dash.State(f"new_{group}_pre_exp", "value"),
144 | dash.State(f"new_{group}_act_energy", "value"),
145 | dash.State(f"new_{group}_author", "value"),
146 | dash.State(f"new_{group}_year", "value"),
147 | dash.State(f"new_{group}_isotope", "value"),
148 | dash.State(f"new_{group}_material", "value"),
149 | dash.State(f"new_{group}_range_low", "value"),
150 | dash.State(f"new_{group}_range_high", "value"),
151 | dash.State(f"new_{group}_law", "value"),
152 | prevent_initial_call=True,
153 | )(cb.make_add_property(group))
154 | else:
155 | app.callback(
156 | dash.Output(f"material_filter_{group}", "options"),
157 | dash.Output(f"author_filter_{group}", "options"),
158 | dash.Output(f"error_message_new_{group}", "children"),
159 | dash.Input(f"submit_new_{group}", "n_clicks"),
160 | dash.Input(f"material_filter_{group}", "value"),
161 | dash.State(f"new_{group}_pre_exp", "value"),
162 | dash.State(f"new_{group}_act_energy", "value"),
163 | dash.State(f"new_{group}_author", "value"),
164 | dash.State(f"new_{group}_year", "value"),
165 | dash.State(f"new_{group}_isotope", "value"),
166 | dash.State(f"new_{group}_material", "value"),
167 | dash.State(f"new_{group}_range_low", "value"),
168 | dash.State(f"new_{group}_range_high", "value"),
169 | prevent_initial_call=True,
170 | )(cb.make_add_property(group))
171 |
172 | app.callback(
173 | dash.Output(f"table_{group}", "data"),
174 | dash.Input(f"graph_{group}", "figure"),
175 | dash.State(f"material_filter_{group}", "value"),
176 | dash.State(f"isotope_filter_{group}", "value"),
177 | dash.State(f"author_filter_{group}", "value"),
178 | dash.State(f"year_filter_{group}", "value"),
179 | )(cb.create_update_table_data_function(group))
180 |
181 | if __name__ == "__main__":
182 | # app.run_server(debug=True, host="0.0.0.0", port=8080)
183 | app.run_server(debug=True)
184 |
--------------------------------------------------------------------------------
/htm_dashboard/graph.py:
--------------------------------------------------------------------------------
1 | import plotly.graph_objects as go
2 | import plotly.io as pio
3 | import h_transport_materials as htm
4 | import numpy as np
5 | import plotly.express as px
6 | import json
7 | from datetime import datetime
8 |
9 |
10 | TEMPLATE_LIGHT = "plotly_white"
11 | TEMPLATE_DARK = "cyborg"
12 |
13 | pio.templates.default = TEMPLATE_LIGHT
14 |
15 |
16 | colour_cycle = px.colors.qualitative.Plotly
17 |
18 | type_to_database = {
19 | "diffusivity": htm.diffusivities,
20 | "solubility": htm.solubilities,
21 | "permeability": htm.permeabilities,
22 | "recombination_coeff": htm.recombination_coeffs,
23 | "dissociation_coeff": htm.dissociation_coeffs,
24 | }
25 |
26 |
27 | def add_mean_value(group: htm.PropertiesGroup, fig: go.Figure):
28 | mean_prop = group.mean()
29 | label = "Mean value"
30 | T = np.linspace(300, 1200, num=500) * htm.ureg.K
31 | hovertemplate = (
32 | "%{text}
"
33 | + "1/T: %{x:,.2e} K-1
"
34 | + "T: %{customdata:.0f} K
"
35 | )
36 | if isinstance(group[0], htm.Solubility):
37 | hovertemplate += (
38 | "S: %{y:,.2e}"
39 | + f"{mean_prop.units:~H}
"
40 | + f"S_0: {mean_prop.pre_exp:.2e~H}
"
41 | + f"E_S : {mean_prop.act_energy:.2f~H}"
42 | )
43 | elif isinstance(group[0], htm.Diffusivity):
44 | hovertemplate += (
45 | "D: %{y:,.2e} "
46 | + f"{mean_prop.units:~H}
"
47 | + f"D_0: {mean_prop.pre_exp:.2e~H}
"
48 | + f"E_D : {mean_prop.act_energy:.2f~H}"
49 | )
50 | elif isinstance(group[0], htm.RecombinationCoeff):
51 | hovertemplate += (
52 | "Kr: %{y:,.2e}"
53 | + f"{mean_prop.units:~H}
"
54 | + f"Kr_0: {mean_prop.pre_exp:.2e~H}
"
55 | + f"E_Kr : {mean_prop.act_energy:.2f~H}"
56 | )
57 | elif isinstance(group[0], htm.DissociationCoeff):
58 | hovertemplate += (
59 | "Kd: %{y:,.2e}"
60 | + f"{mean_prop.units:~H}
"
61 | + f"Kd_0: {mean_prop.pre_exp:.2e~H}
"
62 | + f"E_Kd : {mean_prop.act_energy:.2f~H}"
63 | )
64 | hovertemplate += ""
65 | fig.add_trace(
66 | go.Scatter(
67 | x=1 / T.magnitude,
68 | y=mean_prop.value(T).magnitude,
69 | name=label,
70 | mode="lines",
71 | text=[label] * len(T),
72 | line=dict(color="black", width=4),
73 | customdata=T,
74 | hovertemplate=hovertemplate,
75 | )
76 | )
77 |
78 |
79 | def make_group_of_properties(
80 | type_of_prop: str, materials=[], authors=[], isotopes=[], years=None
81 | ):
82 |
83 | if len(materials) * len(authors) * len(isotopes) == 0:
84 | filtered_group = []
85 | else:
86 | database = type_to_database[type_of_prop]
87 | filtered_group = (
88 | database.filter(material=materials)
89 | .filter(author=[author.lower() for author in authors])
90 | .filter(isotope=[isotope.lower() for isotope in isotopes])
91 | )
92 | if years:
93 | filtered_group = filtered_group.filter(
94 | year=np.arange(years[0], years[1] + 1, step=1).tolist()
95 | )
96 |
97 | return filtered_group
98 |
99 |
100 | def update_axes(fig, group_of_properties):
101 | if len(group_of_properties) == 0:
102 | return
103 |
104 | if isinstance(group_of_properties[0], htm.Solubility):
105 | all_units = np.unique([f"{S.units:~H}" for S in group_of_properties]).tolist()
106 | if len(all_units) == 1:
107 | yticks_suffix = all_units[0].replace("particle", " H")
108 | title_units = f"({yticks_suffix})"
109 | else:
110 | # if the group contains mixed units, display nothing
111 | title_units = "(mixed units)"
112 | yticks_suffix = ""
113 | ylabel = f"Solubility {title_units}"
114 | elif isinstance(group_of_properties[0], htm.Diffusivity):
115 | ylabel = "Diffusivity"
116 | yticks_suffix = f" {group_of_properties[0].units:~H}"
117 | elif isinstance(group_of_properties[0], htm.Permeability):
118 | ylabel = f"Permeability {group_of_properties[0].units:~H}"
119 | yticks_suffix = ""
120 | elif isinstance(group_of_properties[0], htm.RecombinationCoeff):
121 | ylabel = "Recombination coefficient"
122 | yticks_suffix = " m4/s"
123 | elif isinstance(group_of_properties[0], htm.DissociationCoeff):
124 | ylabel = f"Dissociation coefficient {group_of_properties[0].units:~H}"
125 | yticks_suffix = ""
126 |
127 | xticks_suffix = " K-1"
128 |
129 | fig.update_yaxes(
130 | title_text=ylabel, type="log", tickformat=".0e", ticksuffix=yticks_suffix
131 | )
132 | fig.update_xaxes(title_text="1/T", tickformat=".2e", ticksuffix=xticks_suffix)
133 |
134 |
135 | def make_hovertemplate(prop):
136 | # TODO refactor this
137 | if isinstance(prop, htm.Solubility):
138 | return (
139 | "%{text}
"
140 | + prop.material.name
141 | + "
"
142 | + "1/T: %{x:,.2e} K-1
"
143 | + "T: %{customdata:.0f} K
"
144 | + "S: %{y:,.2e} "
145 | + f"{prop.units:~H}
"
146 | + f"S_0: {prop.pre_exp:.2e~H}
"
147 | + f"E_S : {prop.act_energy:.2f~H}"
148 | + ""
149 | )
150 | elif isinstance(prop, htm.Diffusivity):
151 | return (
152 | "%{text}
"
153 | + prop.material.name
154 | + "
"
155 | + "1/T: %{x:,.2e} K-1
"
156 | + "T: %{customdata:.0f} K
"
157 | + "D: %{y:,.2e} "
158 | + f"{prop.units:~H}
"
159 | + f"D_0: {prop.pre_exp:.2e~H}
"
160 | + f"E_D : {prop.act_energy:.2f~H}"
161 | + ""
162 | )
163 | else:
164 | return (
165 | "%{text}
"
166 | + prop.material.name
167 | + "
"
168 | + "1/T: %{x:,.2e} K-1
"
169 | + "T: %{customdata:.0f} K
"
170 | + "value: %{y:,.2e} "
171 | + f"{prop.units:~H}
"
172 | + f"pre-exp: {prop.pre_exp:.2e~H}
"
173 | + f"act. energy : {prop.act_energy:.2f~H}"
174 | + ""
175 | )
176 |
177 |
178 | def make_figure_prop_per_year(
179 | group, step, selected_years=[1950, int(datetime.today().year)]
180 | ):
181 |
182 | counts, bins = np.histogram([prop.year for prop in group])
183 |
184 | bins_center = 0.5 * (bins[:-1] + bins[1:])
185 | selected = [
186 | i
187 | for i, year in enumerate(bins_center)
188 | if selected_years[0] <= year <= selected_years[1]
189 | ]
190 |
191 | fig = go.Figure(
192 | [
193 | go.Bar(
194 | x=bins_center,
195 | y=counts,
196 | selectedpoints=selected,
197 | )
198 | ]
199 | )
200 | template = []
201 | for i, year in enumerate(bins[:-1]):
202 | year_1 = year
203 | year_2 = bins[i + 1]
204 | template.append(
205 | f"
{year_1:.0f} - {year_2:.0f}" + "" + "%{y}"
206 | )
207 |
208 | fig.update_layout(bargap=0)
209 | fig.update_traces(
210 | hovertemplate=template,
211 | selector=dict(type="bar"),
212 | )
213 | fig.update_yaxes(title_text="Nb of properties")
214 | return fig
215 |
216 |
217 | def make_citations_graph(group: htm.PropertiesGroup, per_year: bool = True):
218 | references = []
219 | nb_citations = []
220 | dois = []
221 | with open("htm_dashboard/citations.json") as f:
222 | citation_data = json.load(f)
223 | for prop in group:
224 | if prop.doi in citation_data["dois"]:
225 | nb_citations_prop = citation_data["dois"][prop.doi]
226 | else:
227 | nb_citations_prop = prop.nb_citations
228 | author = prop.author
229 | year = prop.year
230 |
231 | label = "{} ({})".format(author.capitalize(), year)
232 |
233 | if label not in references:
234 |
235 | references.append(label)
236 | if per_year:
237 | current_year = datetime.now().year
238 | nb_citations.append(nb_citations_prop / (current_year - year + 1))
239 | else:
240 | nb_citations.append(nb_citations_prop)
241 |
242 | if prop.doi is None:
243 | dois.append("none")
244 | else:
245 | dois.append(prop.doi)
246 |
247 | # sort values
248 | references = [val_y for _, val_y in sorted(zip(nb_citations, references))]
249 | dois = [val_y for _, val_y in sorted(zip(nb_citations, dois))]
250 | nb_citations = sorted(nb_citations)
251 |
252 | bar = go.Bar(
253 | x=nb_citations,
254 | y=references,
255 | orientation="h",
256 | customdata=dois,
257 | hovertemplate="DOI " + ": %{customdata}
" + "",
258 | )
259 | fig = go.Figure(bar)
260 | if per_year:
261 | x_label = "Average number of citations per year"
262 | else:
263 | x_label = "Number of citations"
264 | fig.update_xaxes(title=x_label)
265 | return fig
266 |
267 |
268 | def make_piechart_materials(prop_group):
269 | list_of_mats = [prop.material.name for prop in prop_group]
270 | labels = np.unique(list_of_mats).tolist()
271 |
272 | values = [list_of_mats.count(mat) for mat in labels]
273 |
274 | colours = []
275 | prop_to_color = htm.plotting.get_prop_to_color(
276 | prop_group, colour_by="material", colour_cycle=colour_cycle
277 | )
278 | for mat in labels:
279 | for prop in prop_group:
280 | if prop.material == mat:
281 | colours.append(prop_to_color[prop])
282 | break
283 | assert len(colours) == len(labels)
284 |
285 | fig = go.Figure(
286 | data=[
287 | go.Pie(
288 | labels=labels,
289 | values=values,
290 | marker_colors=colours,
291 | )
292 | ]
293 | )
294 | return fig
295 |
296 |
297 | def make_piechart_isotopes(prop_group):
298 | list_of_isotopes = [prop.isotope for prop in prop_group]
299 | labels = ["H", "D", "T"]
300 |
301 | values = [list_of_isotopes.count(isotope) for isotope in labels]
302 |
303 | fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
304 | return fig
305 |
306 |
307 | def make_piechart_author(prop_group):
308 | list_of_authors = [prop.author for prop in prop_group]
309 | labels = np.unique(list_of_authors).tolist()
310 |
311 | colours = []
312 | prop_to_color = htm.plotting.get_prop_to_color(
313 | prop_group, colour_by="author", colour_cycle=colour_cycle
314 | )
315 | for author in labels:
316 | for prop in prop_group:
317 | if prop.author == author:
318 | colours.append(prop_to_color[prop])
319 | break
320 | assert len(colours) == len(labels)
321 |
322 | values = [list_of_authors.count(isotope) for isotope in labels]
323 |
324 | labels = [lab.capitalize() for lab in labels]
325 |
326 | fig = go.Figure(
327 | data=[
328 | go.Pie(
329 | labels=labels,
330 | values=values,
331 | marker_colors=colours,
332 | )
333 | ]
334 | )
335 | return fig
336 |
--------------------------------------------------------------------------------
/htm_dashboard/callbacks.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import json
3 | import dash
4 | import plotly.io as pio
5 |
6 | from .export import generate_python_code
7 |
8 | from .tab import materials_options, TABLE_KEYS
9 |
10 | from .graph import (
11 | make_group_of_properties,
12 | make_piechart_author,
13 | make_piechart_isotopes,
14 | make_piechart_materials,
15 | add_mean_value,
16 | make_figure_prop_per_year,
17 | make_citations_graph,
18 | TEMPLATE_DARK,
19 | TEMPLATE_LIGHT,
20 | )
21 |
22 | import h_transport_materials as htm
23 |
24 |
25 | type_to_database = {
26 | "diffusivity": htm.diffusivities,
27 | "solubility": htm.solubilities,
28 | "permeability": htm.permeabilities,
29 | "recombination_coeff": htm.recombination_coeffs,
30 | "dissociation_coeff": htm.dissociation_coeffs,
31 | }
32 |
33 |
34 | def create_make_citations_figure_function(group):
35 | def make_citations_figure(
36 | figure,
37 | per_year,
38 | material_filter,
39 | isotope_filter,
40 | author_filter,
41 | year_filter,
42 | ):
43 | properties_group = make_group_of_properties(
44 | type_of_prop=group,
45 | materials=material_filter,
46 | authors=author_filter,
47 | isotopes=isotope_filter,
48 | years=year_filter,
49 | )
50 |
51 | return make_citations_graph(properties_group, per_year=per_year)
52 |
53 | return make_citations_figure
54 |
55 |
56 | def create_add_all_materials_function(group):
57 | def add_all_materials(n_clicks):
58 | if n_clicks:
59 | return materials_options
60 | else:
61 | return dash.no_update
62 |
63 | return add_all_materials
64 |
65 |
66 | def create_add_all_authors_function(group):
67 | def add_all_authors(n_clicks):
68 | if n_clicks:
69 | return np.unique(
70 | [prop.author.capitalize() for prop in type_to_database[group]]
71 | ).tolist()
72 | else:
73 | return dash.no_update
74 |
75 | return add_all_authors
76 |
77 |
78 | def create_update_entries_per_year_graph_function(group):
79 | def update_entries_per_year_graph(
80 | figure, material_filter, isotope_filter, author_filter, year_filter
81 | ):
82 | all_time_properties = make_group_of_properties(
83 | type_of_prop=group,
84 | materials=material_filter,
85 | authors=author_filter,
86 | isotopes=isotope_filter,
87 | )
88 | return make_figure_prop_per_year(
89 | all_time_properties, step=5, selected_years=year_filter
90 | )
91 |
92 | return update_entries_per_year_graph
93 |
94 |
95 | def create_update_graph_function(group):
96 | def update_graph(
97 | material_filter,
98 | isotope_filter,
99 | author_filter,
100 | year_filter,
101 | mean_button,
102 | colour_by,
103 | toggle_light,
104 | ):
105 | properties_group = make_group_of_properties(
106 | type_of_prop=group,
107 | materials=material_filter,
108 | authors=author_filter,
109 | isotopes=isotope_filter,
110 | years=year_filter,
111 | )
112 |
113 | if toggle_light:
114 | pio.templates.default = TEMPLATE_LIGHT
115 | else:
116 | pio.templates.default = TEMPLATE_DARK
117 |
118 | figure = htm.plotting.plot_plotly(properties_group, colour_by=colour_by)
119 | changed_id = [p["prop_id"] for p in dash.callback_context.triggered][0]
120 | if changed_id == f"mean_button_{group}.n_clicks":
121 | add_mean_value(properties_group, figure)
122 |
123 | return figure
124 |
125 | return update_graph
126 |
127 |
128 | def create_make_download_data_function(group):
129 | def make_download_data(
130 | n_clicks,
131 | material_filter,
132 | isotope_filter,
133 | author_filter,
134 | year_filter,
135 | ):
136 | changed_id = [p["prop_id"] for p in dash.callback_context.triggered][0]
137 | if changed_id == f"extract_button_{group}.n_clicks":
138 | properties_group = make_group_of_properties(
139 | type_of_prop=group,
140 | materials=material_filter,
141 | authors=author_filter,
142 | isotopes=isotope_filter,
143 | years=year_filter,
144 | )
145 | data = {"data": []}
146 | for prop in properties_group:
147 | data["data"].append(prop.to_json())
148 |
149 | data["htm_version"] = htm.__version__
150 | return dict(
151 | content=json.dumps(data, indent=2),
152 | filename="data.json",
153 | )
154 |
155 | return make_download_data
156 |
157 |
158 | def make_download_python_callback(group):
159 | def download_python(
160 | n_clicks,
161 | material_filter,
162 | isotope_filter,
163 | author_filter,
164 | year_filter,
165 | ):
166 | changed_id = [p["prop_id"] for p in dash.callback_context.triggered][0]
167 | if changed_id == f"python_button_{group}.n_clicks":
168 | return dict(
169 | content=generate_python_code(
170 | materials=material_filter,
171 | isotopes=isotope_filter,
172 | authors=author_filter,
173 | yearmin=year_filter[0],
174 | yearmax=year_filter[1],
175 | group=group,
176 | ),
177 | filename="script.py",
178 | )
179 |
180 | return download_python
181 |
182 |
183 | def make_toggle_modal_function(group):
184 | def toggle_modal(
185 | n1,
186 | n2,
187 | is_open,
188 | new_prop_pre_exp,
189 | new_prop_act_energy,
190 | new_prop_author,
191 | new_prop_year,
192 | new_prop_isotope,
193 | new_prop_material,
194 | ):
195 | if is_open and None in [
196 | new_prop_pre_exp,
197 | new_prop_act_energy,
198 | new_prop_author,
199 | new_prop_year,
200 | new_prop_isotope,
201 | new_prop_material,
202 | ]:
203 | return is_open
204 | if n1 or n2:
205 | return not is_open
206 | return is_open
207 |
208 | return toggle_modal
209 |
210 |
211 | def make_add_property(group):
212 | def add_property(
213 | n_clicks,
214 | material_filter,
215 | new_pre_exp,
216 | new_act_energy,
217 | new_author,
218 | new_year,
219 | new_isotope,
220 | new_material,
221 | new_range_low,
222 | new_range_high,
223 | new_sol_law=None,
224 | ):
225 | changed_id = [p["prop_id"] for p in dash.callback_context.triggered][0]
226 | if changed_id == f"submit_new_{group}.n_clicks":
227 | if None in [
228 | new_pre_exp,
229 | new_act_energy,
230 | new_author,
231 | new_year,
232 | new_isotope,
233 | new_material,
234 | ]:
235 | return dash.no_update, dash.no_update, "Error!"
236 | if (new_range_low, new_range_high) == (None, None):
237 | (new_range_low, new_range_high) = (300, 1200)
238 |
239 | if group == "diffusivity":
240 | new_property = htm.Diffusivity(
241 | D_0=new_pre_exp,
242 | E_D=new_act_energy,
243 | )
244 | elif group == "solubility":
245 | new_property = htm.Solubility(
246 | S_0=new_pre_exp,
247 | E_S=new_act_energy,
248 | law=new_sol_law,
249 | )
250 | elif group == "recombination_coeff":
251 | new_property = htm.RecombinationCoeff(
252 | pre_exp=new_pre_exp,
253 | act_energy=new_act_energy,
254 | )
255 |
256 | new_property.author = new_author.lower()
257 | new_property.year = new_year
258 | new_property.isotope = new_isotope
259 | # TODO find a way to find potentially already existing material (like tungsten)
260 | new_property.material = htm.Material(name=new_material)
261 | new_property.range = (new_range_low, new_range_high)
262 |
263 | type_to_database[group].append(new_property)
264 |
265 | all_authors = np.unique(
266 | [
267 | prop.author.capitalize()
268 | for prop in type_to_database[group]
269 | if prop.material in material_filter
270 | ]
271 | ).tolist()
272 | all_materials = [prop.material.name.lower() for prop in type_to_database[group]]
273 | all_families = [
274 | p.family for prop in type_to_database[group] for p in prop.material.parents
275 | ]
276 | all_materials = np.unique(all_materials + all_families).tolist()
277 |
278 | return all_materials, all_authors, ""
279 |
280 | return add_property
281 |
282 |
283 | def create_update_piechart_material_function(group):
284 | def update_piechart_material(
285 | figure,
286 | material_filter,
287 | isotope_filter,
288 | author_filter,
289 | year_filter,
290 | ):
291 | properties_group = make_group_of_properties(
292 | type_of_prop=group,
293 | materials=material_filter,
294 | authors=author_filter,
295 | isotopes=isotope_filter,
296 | years=year_filter,
297 | )
298 | return make_piechart_materials(properties_group)
299 |
300 | return update_piechart_material
301 |
302 |
303 | def create_update_piechart_isotopes_function(group):
304 | def update_piechart_isotope(
305 | figure,
306 | material_filter,
307 | isotope_filter,
308 | author_filter,
309 | year_filter,
310 | ):
311 | properties_group = make_group_of_properties(
312 | type_of_prop=group,
313 | materials=material_filter,
314 | authors=author_filter,
315 | isotopes=isotope_filter,
316 | years=year_filter,
317 | )
318 | return make_piechart_isotopes(properties_group)
319 |
320 | return update_piechart_isotope
321 |
322 |
323 | def create_update_piechart_authors_function(group):
324 | def update_piechart_author(
325 | figure,
326 | material_filter,
327 | isotope_filter,
328 | author_filter,
329 | year_filter,
330 | ):
331 | properties_group = make_group_of_properties(
332 | type_of_prop=group,
333 | materials=material_filter,
334 | authors=author_filter,
335 | isotopes=isotope_filter,
336 | years=year_filter,
337 | )
338 | return make_piechart_author(properties_group)
339 |
340 | return update_piechart_author
341 |
342 |
343 | def create_update_table_data_function(group):
344 | def update_table_data(
345 | figure, material_filter, isotope_filter, author_filter, year_filter
346 | ):
347 | data = []
348 |
349 | properties_group = make_group_of_properties(
350 | type_of_prop=group,
351 | materials=material_filter,
352 | authors=author_filter,
353 | isotopes=isotope_filter,
354 | years=year_filter,
355 | )
356 |
357 | for prop in properties_group:
358 | entry = {}
359 | for key in TABLE_KEYS:
360 | if hasattr(prop, key):
361 | val = getattr(prop, key)
362 | if key == "range":
363 | if val is None:
364 | val = "none"
365 | else:
366 | val = f"{val[0]:.0f~P}-{val[1]:.0f~P}"
367 | elif key == "material":
368 | val = f"{val.name}"
369 | elif key == "pre_exp":
370 | val = f"{val: .2e~P}"
371 | elif key == "act_energy":
372 | val = f"{val:.2f~P}"
373 | elif key == "doi":
374 | entry[key] = prop.source
375 | if prop.bibsource:
376 | if prop.doi:
377 | clickable_doi = (
378 | f"[{prop.doi}](https://doi.org/{prop.doi})"
379 | )
380 | val = clickable_doi
381 |
382 | entry[key] = val
383 |
384 | data.append(entry)
385 |
386 | return data
387 |
388 | return update_table_data
389 |
--------------------------------------------------------------------------------
/htm_dashboard/tab.py:
--------------------------------------------------------------------------------
1 | from dash import dcc, dash_table
2 | from dash import html
3 | import dash_bootstrap_components as dbc
4 | import dash_daq as daq
5 |
6 | import h_transport_materials as htm
7 | import numpy as np
8 | import json
9 |
10 | materials_options = list(set([prop.material.name for prop in htm.database]))
11 | materials_options += list(set([prop.material.family for prop in htm.database]))
12 |
13 | isotope_options = ["H", "D", "T"]
14 |
15 | pretty_label = {
16 | "diffusivity": "Diffusivity",
17 | "solubility": "Solubility",
18 | "permeability": "Permeability",
19 | "recombination_coeff": "Recombination coeff.",
20 | "dissociation_coeff": "Dissociation coeff.",
21 | }
22 |
23 |
24 | def make_tab(property: str):
25 | """_summary_
26 |
27 | Args:
28 | property (str): _description_
29 |
30 | Returns:
31 | dbc.Tab: the tab
32 | """
33 |
34 | assert property in [
35 | "diffusivity",
36 | "solubility",
37 | "permeability",
38 | "recombination_coeff",
39 | "dissociation_coeff",
40 | ]
41 |
42 | property_to_group = {
43 | "diffusivity": htm.diffusivities,
44 | "solubility": htm.solubilities,
45 | "permeability": htm.permeabilities,
46 | "recombination_coeff": htm.recombination_coeffs,
47 | "dissociation_coeff": htm.dissociation_coeffs,
48 | }
49 |
50 | all_properties = property_to_group[property]
51 |
52 | initial_material = "tungsten"
53 |
54 | authors_options = np.unique(
55 | [
56 | prop.author.capitalize()
57 | for prop in all_properties
58 | if prop.material == "tungsten"
59 | ]
60 | ).tolist()
61 |
62 | years_options = [prop.year for prop in all_properties]
63 | min_year = min(years_options)
64 | max_year = max(years_options)
65 |
66 | table = make_table(property)
67 |
68 | table_tab = dbc.Tab([table], label="Table")
69 |
70 | graph_tab = dbc.Tab(
71 | [
72 | dbc.Card(
73 | [
74 | dbc.Row(
75 | [
76 | dbc.Col(
77 | [
78 | html.Label("Colour by:"),
79 | dcc.Dropdown(
80 | ["property", "material", "author", "isotope"],
81 | "property",
82 | id=f"colour-by_{property}",
83 | style=dict(width="150px"),
84 | ),
85 | ]
86 | ),
87 | dbc.Col(
88 | [
89 | dcc.Graph(
90 | id=f"graph_{property}",
91 | style={"height": "600px"},
92 | )
93 | ],
94 | width=10,
95 | ),
96 | ],
97 | ),
98 | ],
99 | body=True,
100 | className="mb-2",
101 | )
102 | ],
103 | label="Graph",
104 | )
105 |
106 | sub_tabs = dbc.Tabs([graph_tab, table_tab], id=f"subtabs_{property}")
107 |
108 | controls = dbc.Card(
109 | [
110 | html.Label("Filter by material:"),
111 | dcc.Dropdown(
112 | options=materials_options,
113 | value=[initial_material],
114 | multi=True,
115 | id=f"material_filter_{property}",
116 | ),
117 | html.Div(
118 | dbc.Button(
119 | "All",
120 | id=f"add_all_materials_{property}",
121 | style={"font-size": "12px"},
122 | )
123 | ),
124 | html.Br(),
125 | dbc.Label("Filter by isotope:"),
126 | dbc.Checklist(
127 | value=isotope_options,
128 | options=[{"label": i, "value": i} for i in isotope_options],
129 | inline=True,
130 | id=f"isotope_filter_{property}",
131 | ),
132 | html.Br(),
133 | html.Label("Filter by author:"),
134 | dcc.Dropdown(
135 | value=authors_options,
136 | options=authors_options,
137 | multi=True,
138 | id=f"author_filter_{property}",
139 | ),
140 | html.Div(
141 | dbc.Button(
142 | "All",
143 | id=f"add_all_authors_{property}",
144 | style={"font-size": "12px"},
145 | )
146 | ),
147 | html.Br(),
148 | html.Label("Filter by year:"),
149 | dcc.RangeSlider(
150 | id=f"year_filter_{property}",
151 | min=min_year,
152 | max=max_year,
153 | step=1,
154 | value=[min_year, max_year],
155 | marks={
156 | int(i): str(i)
157 | for i in np.arange(min_year, max_year)
158 | if int(i) % 10 == 0
159 | },
160 | tooltip={
161 | "placement": "bottom",
162 | "always_visible": True,
163 | },
164 | ),
165 | html.Br(),
166 | html.Div(
167 | [
168 | dbc.Button(
169 | "Compute mean curve",
170 | id=f"mean_button_{property}",
171 | color="primary",
172 | style={"margin": "5px"},
173 | n_clicks="0",
174 | ),
175 | dbc.Button(
176 | "Add property",
177 | id=f"add_property_{property}",
178 | color="primary",
179 | style={"margin": "5px"},
180 | n_clicks="0",
181 | ),
182 | dbc.Button(
183 | [
184 | "Extract data",
185 | dcc.Download(id=f"download-text_{property}"),
186 | ],
187 | id=f"extract_button_{property}",
188 | color="primary",
189 | style={"margin": "5px"},
190 | n_clicks="0",
191 | ),
192 | dbc.Button(
193 | [
194 | "Python",
195 | dcc.Download(id=f"download-python_{property}"),
196 | ],
197 | id=f"python_button_{property}",
198 | color="primary",
199 | style={"margin": "5px"},
200 | n_clicks_timestamp="0",
201 | ),
202 | ]
203 | ),
204 | ],
205 | body=True,
206 | )
207 | with open("htm_dashboard/citations.json") as f:
208 | citation_data = json.load(f)
209 | date_citations = citation_data["date"]
210 | graph_prop_per_year = dbc.Card(
211 | [
212 | dbc.CardBody(
213 | [
214 | html.H4("Number of properties per year", className="card-title"),
215 | dcc.Graph(id=f"graph_prop_per_year_{property}"),
216 | ]
217 | )
218 | ],
219 | className="mb-2",
220 | )
221 |
222 | graph_citations = dbc.Card(
223 | [
224 | dbc.CardBody(
225 | [
226 | html.H4("Number of citations", className="card-title"),
227 | html.H6(
228 | f"source: Crossref {date_citations}", className="card-subtitle"
229 | ),
230 | dbc.Row(
231 | [
232 | dbc.Col(
233 | [
234 | daq.BooleanSwitch(
235 | label="Per year",
236 | on=False,
237 | id=f"per_year_citations_{property}",
238 | ),
239 | ],
240 | width=1,
241 | ),
242 | dbc.Col(
243 | [dcc.Graph(id=f"graph_nb_citations_{property}")],
244 | width=11,
245 | ),
246 | ],
247 | align="center",
248 | ),
249 | ]
250 | )
251 | ],
252 | className="mb-2",
253 | )
254 |
255 | piechart_materials = dbc.Card(
256 | [
257 | dbc.CardBody(
258 | [
259 | html.H4("Repartition by materials", className="card-title"),
260 | dcc.Graph(id=f"graph_materials_{property}"),
261 | ]
262 | )
263 | ],
264 | className="mb-2",
265 | )
266 | piechart_isotopes = dbc.Card(
267 | [
268 | dbc.CardBody(
269 | [
270 | html.H4("Repartition by isotopes", className="card-title"),
271 | dcc.Graph(id=f"graph_isotopes_{property}"),
272 | ]
273 | )
274 | ],
275 | className="mb-2",
276 | )
277 | piechart_authors = dbc.Card(
278 | [
279 | dbc.CardBody(
280 | [
281 | html.H4("Repartition by authors", className="card-title"),
282 | dcc.Graph(id=f"graph_authors_{property}"),
283 | ]
284 | )
285 | ],
286 | className="mb-2",
287 | )
288 |
289 | tab = dbc.Tab(
290 | label=pretty_label[property],
291 | children=[
292 | dbc.Row(
293 | [
294 | dbc.Col(
295 | [controls],
296 | width=3,
297 | style={"overflow-y": "auto", "maxHeight": "600px"},
298 | ),
299 | dbc.Col([sub_tabs]),
300 | ],
301 | ),
302 | dbc.Row(
303 | [
304 | dbc.Col([graph_prop_per_year], width=3),
305 | dbc.Col([graph_citations], width=4),
306 | dbc.Col([piechart_materials], width=4),
307 | ],
308 | justify="evenly",
309 | ),
310 | dbc.Row(
311 | [
312 | dbc.Col([piechart_isotopes], width=4),
313 | dbc.Col([piechart_authors], width=4),
314 | ],
315 | justify="evenly",
316 | ),
317 | ],
318 | )
319 | return tab
320 |
321 |
322 | TABLE_KEYS = [
323 | "material",
324 | "isotope",
325 | "pre_exp",
326 | "act_energy",
327 | "range",
328 | "author",
329 | "note",
330 | "doi",
331 | ]
332 |
333 | prop_key_to_label = {
334 | "diffusivity": {"pre_exp": "D_0", "act_energy": "E_D"},
335 | "solubility": {"pre_exp": "S_0", "act_energy": "E_S"},
336 | "permeability": {"pre_exp": "P_0", "act_energy": "E_P"},
337 | "recombination_coeff": {"pre_exp": "Kr_0", "act_energy": "E_Kr"},
338 | "dissociation_coeff": {"pre_exp": "Kd_0", "act_energy": "E_Kd"},
339 | }
340 |
341 | key_to_label = {
342 | "material": "Material",
343 | "range": "Range",
344 | "author": "Author",
345 | "doi": "DOI",
346 | "note": "Notes",
347 | "isotope": "Isotope",
348 | }
349 |
350 |
351 | def make_table_labels(property):
352 | labels = []
353 | for key in TABLE_KEYS:
354 | if key in key_to_label:
355 | labels.append(key_to_label[key])
356 | else:
357 | labels.append(prop_key_to_label[property][key])
358 | return labels
359 |
360 |
361 | def make_table(property):
362 |
363 | table = dash_table.DataTable(
364 | id=f"table_{property}",
365 | columns=[
366 | (
367 | {
368 | "name": label,
369 | "id": key,
370 | "presentation": "markdown",
371 | } # markdown is needed to have clickable links
372 | if key == "doi"
373 | else {"name": label, "id": key}
374 | )
375 | for key, label in zip(TABLE_KEYS, make_table_labels(property))
376 | ],
377 | data=[],
378 | page_size=10,
379 | editable=False,
380 | cell_selectable=True,
381 | # filter_action="native",
382 | sort_action="native",
383 | style_table={"overflowX": "auto"},
384 | style_cell_conditional=[
385 | # Here, "whiteSpace": 'normal' is needed to have line breaks in the notes
386 | {"if": {"column_id": "note"}, "width": "200px", "whiteSpace": "normal"},
387 | ],
388 | )
389 |
390 | return table
391 |
--------------------------------------------------------------------------------