Your First App — Crystal Toolkit documentation

# Your First App

This page is intended for users who want to make their own web app to explore materials science data.

Important

Crystal Toolkit is built on top of Dash by Plotly. Dash is a framework to build rich, interactive web apps purely from Python with no JavaScript knowledge required. If you’re not familiar with the Dash framework, please read their documentation and try their example app first.

## Example App

Each component has a self-contained example app in the crystal_toolkit.apps.examples folder. If you prefer to learn by seeing, take a look at one of these! Otherwise, building an app is explained step-by-step below.

## Preamble

Every Crystal Toolkit app needs the following basic structure:

    import dash
    from dash import html

    # create Dash app as normal
    app = dash.Dash()

    # create your layout
    app.layout = html.Span(["Hello scientist!"])

    # allow app to be run using "python app.py"
    # in production, deploy behind gunicorn or similar
    # see Dash documentation for more information
    if __name__ == "__main__":
        app.run_server(debug=True, port=8050)

Save this code as `app.py` and it can be run with `python app.py`.

Visiting http://localhost:8050 in your web browser should then give a blank page with the text “Hello scientist!”.

## Adding an MPComponent

To display information such as crystal structures or phase diagrams, we have to add an MPComponent to our layout. To illustrate, we’ll use a simple crystal structure.

    # as explained in "preamble" section in documentation
    import dash
    from dash import html
    from pymatgen.core.lattice import Lattice
    from pymatgen.core.structure import Structure

    import crystal_toolkit.components as ctc

    app = dash.Dash()

    # create our crystal structure using pymatgen
    structure = Structure(Lattice.cubic(4.2), ["Na", "K"], [[0, 0, 0], [0.5, 0.5, 0.5]])

    # create the Crystal Toolkit component
    structure_component = ctc.StructureMoleculeComponent(structure, id="hello_structure")

    # add the component's layout to our app's layout
    my_layout = html.Div([structure_component.layout()])

    # as explained in "preamble" section in documentation
    ctc.register_crystal_toolkit(app=app, layout=my_layout)
    if __name__ == "__main__":
        app.run_server(debug=True, port=8050)

The important thing here is that we instantiated the `structure_component` outside the layout, and then called its `.layout()` method to get the Dash layout to insert as part of the `app.layout`.

All `MPComponents` follow this basic structure:

- They can be instantiated with an `MSONable` object as their only argument. Here, it was a `Structure` object. There may be additional arguments to customize the component.

- They have at least one `.layout()` method to generate the Dash layout for that component, although some components have additional (optional) `layout` methods.

## Interactivity

For the purposes of interactivity, we can use callbacks as in any other Dash app.

The important addition from an `MPComponent` is that they an `id()` method to access the ids of the component itself or of any sub-layouts inside that component. The canonical layout is a dcc.Store() containing the serialized representation of your Python object, such as the crystallographic structure. By updating the contents of this store with a new object, the visual state of the component will also be updated.

This is best illustrated by example. We will add a button that shows a random crystal structure when clicked.

    import dash
    from dash import html

    # standard Dash imports for callbacks (interactivity)
    from dash.dependencies import Input, Output
    from pymatgen.core.lattice import Lattice
    from pymatgen.core.structure import Structure

    import crystal_toolkit.components as ctc

    # don't run callbacks on page load
    app = dash.Dash(prevent_initial_callbacks=True)

    # now we give a list of structures to pick from
    structures = [
        Structure(Lattice.hexagonal(5, 3), ["Na", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]),
        Structure(Lattice.cubic(5), ["K", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]),
    ]

    # we show the first structure by default
    structure_component = ctc.StructureMoleculeComponent(structures[0], id="my_structure")

    # and we create a button for user interaction
    my_button = html.Button("Swap Structure", id="change_structure_button")

    # now we have two entries in our app layout,
    # the structure component's layout and the button
    my_layout = html.Div([structure_component.layout(), my_button])

    ctc.register_crystal_toolkit(app=app, layout=my_layout)

    # for the interactivity, we use a standard Dash callback
    @app.callback(
        Output(structure_component.id(), "data"),
        [Input("change_structure_button", "n_clicks")],
    )
    def update_structure(n_clicks):
        return structures[n_clicks % 2]

    if __name__ == "__main__":
        app.run_server(debug=True, port=8050)

The two features here that make this slightly different from a regular Dash app are:

1. The structure object is stored and accessed via `structure_component.id()` and its `data` prop. The .id() method ensures that each Dash component has a unique id, even if multiple of the same MPComponent are present on the same page.

2. We can return the object directly (as a `Structure` object) via the callback, without needing to serialize or deserialize it.

Note that due to the dynamic nature of a Crystal Toolkit app, callback exceptions are suppressed on app load. For debugging of static layouts, you might want to re-enable callback exceptions.

## Linking Components Together

# ToDo

## Running in Production

Note

This section is optional for getting an app working.

Long-running callbacks (> 0.1 ms) can make a web app feel slow and sluggish. Since callbacks do not rely on any external state, they are easy to cache.

Caching is supported by many Crystal Toolkit components, but by default caching is in-memory only and not thread safe.

Any Flask-Caching backend is supported, we recommend `RedisCache`:

    # ... define your Dash "app" variable first

    from flask_caching import Cache

    cache = Cache(
        app.server,
        config={
            "CACHE_TYPE": "redis",
            "CACHE_REDIS_URL": os.environ.get("REDIS_URL", "localhost:6379"),
        },
    )

    # and tell crystal toolkit about the cache
    ctc.register_crystal_toolkit(app, layout, cache=cache)

Additionally, you should run the app using `gunicorn` rather than using `python app.py` directly so that there are multiple workers available to handle callbacks – this will result in a huge performance improvement.

All of the recommendations in the main Dash documentation also apply.