Plotting Atomic Orbitals with Makie

Hello, I am trying to write a function to produce an interactive plot for Atomic Orbitals using Makie.jl.

The ultimate goal is to plot molecular orbitals, but if I can do atomic ones the extension must be trivial.

First of all, I am using a structure called BasisFunction (the variable bf in the code below) along with a function that can take this struct and compute the value of this orbital for a position (x,y,z).

Currently, the code looks like this

function isoplot(bf, min, max, N)
    fig = Figure()
    lscene = LScene(fig[1:5,1:4])

    # Menus
    menu = Menu(fig, options = collect(-bf.l:1:bf.l), tellwidth = false)
    fig[1,5] = grid!(
        hcat(Label(fig, L"m_l", width = nothing, textsize = 30.0f0), menu),
        tellheight = false,
        width = 300
    )

    # Sliders
    lsgrid = labelslidergrid!(
        fig,
        ["Isovalue"],
        [0:0.01:0.5],
        formats = [x -> "$x"],
        tellheight = false
    )
    fig[2,5] = lsgrid.layout

    # Toggles
    toggles = [
        Toggle(fig, active = true)
    ]
    tlabels = [
        Label(fig, "Show axis")
    ]
    fig[3,5] = grid!(hcat(toggles, tlabels), tellheight = false)

    # Buttons
    fig[4, 5] = buttongrid = GridLayout(tellwidth = false)
    button = buttongrid[1,1] =[Button(fig, label = "Save")]
    on(button[1].clicks) do n
        lscene.scene.center = false
        save("atomicorbital.png", lscene.scene)
    end

    set_close_to!(lsgrid.sliders[1], 0.48)
    menu.selection[] = 0

    iv = lsgrid.sliders[1].value
    negiv = lift(iv) do x
        -x
    end

    r = LinRange(min, max, N)
    wf = lift(menu.selection) do ml
        [basis_function_eval(bf, x, y, z, Integer(ml[])) for x = r, y = r, z = r]
    end

    volume!(r, r, r, wf, algorithm = :iso, isorange = 0.01, 
            isovalue =  iv, show_axis=toggles[1].active, colormap = colormap("reds"), colorrange = (0, 0.5))
    volume!(r, r, r, wf, algorithm = :iso, isorange = 0.01, 
            isovalue = negiv, show_axis=toggles[1].active, colormap = colormap("blues"), colorrange = (0, -0.5))

    return fig
end

Plotting the function is somewhat straightforward, most of this mess is trying to set up layoutables. The current result looks like for Hygroen (1p function from cc-pvdz basis)

I am happy with the result, but there are several points where it could improve:

  1. The overall organization: is there an easier way to have several widgets on a single column? I did not find many examples mixing menus, sliders, etc.
  2. The show axis toggle does not work, did I get my events/observables right?
  3. The save button works, but it closes the window. It would be great if I could have an input for the file path, but maybe that can go in the function call. Is there a way to change the dpi, just for saving?
  4. The main problem right now is the overlap of the two plots. In order to have both negative and positive parts showing, I used volume! twice. It looks fine, but the negative plot is always on top of the positive, no matter the perspective. Can that be fixed?

    In this picture, the positive (red) part of the function is closer to the came, still the blue one is shown on top of it.

I appreciate any comments, suggestions or other examples you could send me.
Thank you!

5 Likes
  1. You’re fine. At the moment Makie just has something like if show_axis[]; add_axis!(). You’d need something like
on(toggles[1].active) do visible
    ax.scene.plots[1].plots[1].visible[] = visible
    ax.scene.plots[1].plots[2].visible[] = visible
end

and have show_axis = true to toggle them.

  1. img = Makie.colorbuffer(fig.scene.current_screens[1]) works without closing the screen in GLMakie. (Edit: The inline display in vscode doesn’t work for me with this, but the image seems correct.)
  2. That should be working in the latest release. (But only for iso and 3d contours)
1 Like

There is also discussion here about object stacking

What about the organization do you not like at the moment? You’re not necessarily using the easiest syntax, but there’s also nothing inherently wrong with it. But for example, I wouldn’t put the axis over so many rows and columns, to match with the menu. It’s easier to nest GridLayouts so they’re independent (why should the rows of the gui part have anything to do with the size of the plot).

Here’s an example:

f = Figure()

LScene(f[1, 1])
scatter!(randn(100, 3))
gui_grid = f[1, 2] = GridLayout(width = 200, tellheight = false)

Label(gui_grid[1, 1][1, 1], "Label")
Menu(gui_grid[1, 1][1, 2])

ls = labelslider!(f, "slider", 1:10)
gui_grid[2, 1] = ls.layout

togglegrid = gui_grid[3, 1] = GridLayout(tellwidth = false)
Toggle(togglegrid[1, 1])
Label(togglegrid[1, 2], "show axis")

Button(gui_grid[4, 1], label = "save", tellwidth = false)

f

2 Likes

Thank you, this type of organization is what I was looking for. I did not know how to nest things.