diff --git a/Project.toml b/Project.toml index 503d6fa8..45ba6a56 100644 --- a/Project.toml +++ b/Project.toml @@ -26,9 +26,9 @@ GeoMakie = "0.6.5, 0.7" JuliaFormatter = "1" NCDatasets = "0.13.1, 0.14" OrderedCollections = "1.3" +Reexport = "1" SafeTestsets = "0.1" Statistics = "1" -Reexport = "1" Test = "1" julia = "1.9" diff --git a/README.md b/README.md index 55403281..e3923e51 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Check out the [documentation](https://CliMA.github.io/ClimaAnalysis.jl) for more - Window variables within given ranges (e.g., select times between 10 and 100 days) - Perform mathematical operations between output variables. - Extract dimensions from conventional names (e.g., `times`). +- Interactively explore the content of a simulation with `ClimaExplorer`. ## ClimaAnalysis.jl Developer Guidelines diff --git a/climaexplorer/Project.toml b/climaexplorer/Project.toml new file mode 100644 index 00000000..d43134e6 --- /dev/null +++ b/climaexplorer/Project.toml @@ -0,0 +1,14 @@ +name = "ClimaExplorer" +authors = ["Gabriele Bozzola "] +version = "0.0.1" + +[deps] +Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +ClimaAnalysis = "29b5916a-a76c-4e73-9657-3c8fd22e65e6" +GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6" + +[compat] +Bonito = "3" +ClimaAnalysis = "0.5.5" +GeoMakie = "0.6.5, 0.7" diff --git a/climaexplorer/assets/interactive.css b/climaexplorer/assets/interactive.css new file mode 100644 index 00000000..3bbcc096 --- /dev/null +++ b/climaexplorer/assets/interactive.css @@ -0,0 +1,12 @@ +.container { + display: flex; +} +.column { + padding: 10px; +} +.left { + flex: 0 1 20%; /* Flex-grow: 0, Flex-shrink: 1, Flex-basis: 20% */ +} +.right { + flex: 1; +} diff --git a/climaexplorer/explorer.jl b/climaexplorer/explorer.jl new file mode 100644 index 00000000..0e9f1ae0 --- /dev/null +++ b/climaexplorer/explorer.jl @@ -0,0 +1,12 @@ +length(ARGS) == 1 || error("Usage: julia --project=climaexplorer explorer.jl ") + +include(joinpath(@__DIR__, "libexplorer.jl")) + +path = first(ARGS) + +app = BonitoApp(path) + +server = Server(app, "0.0.0.0", 8080) +println(server) +route!(server, "/" => app) +wait(server) diff --git a/climaexplorer/libexplorer.jl b/climaexplorer/libexplorer.jl new file mode 100644 index 00000000..abbce2b9 --- /dev/null +++ b/climaexplorer/libexplorer.jl @@ -0,0 +1,78 @@ +import ClimaAnalysis +import Bonito: Observable, App, Slider, Dropdown, DOM, Server, route!, wait, Asset +import CairoMakie +import GeoMakie + +function BonitoApp(path) + app = App() do + climastyle = Asset(joinpath(@__DIR__, "assets", "interactive.css")) + simdir = ClimaAnalysis.SimDir(path) + variables = ClimaAnalysis.available_vars(simdir) |> collect + vars_menu = Dropdown(variables) + short_name = vars_menu.value.val + # TODO: Add Dropdown menus for reduction and period + + # reductions = ClimaAnalysis.available_reductions(simdir; short_name) |> collect + # reductions_menu = Dropdown(reductions) + # reduction = reductions_menu.value.val + # periods = ClimaAnalysis.available_periods(simdir; short_name, reduction) |> collect + # periods_menu = Dropdown(periods) + + # Set up initial var + reduction = Observable(first(ClimaAnalysis.available_reductions(simdir; short_name))) + period = Observable(first(ClimaAnalysis.available_periods(simdir; short_name, reduction = reduction.val))) + var = get(simdir; short_name, reduction = reduction.val, period = period.val) + time_slider = Slider(ClimaAnalysis.times(var)) + level_slider = Slider(ClimaAnalysis.altitudes(var)) + + var = map(vars_menu.value) do short_name + reduction[] = first(ClimaAnalysis.available_reductions(simdir; short_name)) + period[] = first(ClimaAnalysis.available_periods(simdir; short_name, reduction = reduction.val)) + var = get(simdir; short_name, reduction = reduction.val, period = period.val) + time_slider.values[] = ClimaAnalysis.times(var) + level_slider.values[] = ClimaAnalysis.altitudes(var) + setindex!(time_slider, first(ClimaAnalysis.times(var))) + setindex!(level_slider, first(ClimaAnalysis.altitudes(var))) + var + end + + var = map(time_slider) do time + short_name = vars_menu.value.val + # Trigger level_slidel + setindex!(level_slider, level_slider.value.val) + ClimaAnalysis.slice(get(simdir; short_name, reduction = reduction.val, period = period.val); time, z = level_slider.value.val) + end + + var = map(level_slider) do level + short_name = vars_menu.value.val + ClimaAnalysis.slice(get(simdir; short_name, reduction = reduction.val, period = period.val); time = time_slider.value.val, z = level) + end + + fig = map(var) do sliced_var + fig = CairoMakie.Figure(size=(1200, 760), fontsize=30) + ClimaAnalysis.Visualize.contour2D_on_globe!(fig, sliced_var) + # ClimaAnalysis.Visualize.plot!(fig, sliced_var) + fig + end + + return DOM.div( + DOM.h3("Output: ", path), + DOM.div( + DOM.div( + climastyle, + DOM.h4("Short name"), + vars_menu, + DOM.h4("Reduction: ", reduction), + DOM.h4("Period: ", period), + DOM.h4("Time: ", time_slider.value), + time_slider, + DOM.h4("Altitude: ", level_slider.value), + level_slider; + class = "column left", + ), + DOM.div(fig, class = "column right"), + class = "container" + )) + end + return app +end diff --git a/docs/make.jl b/docs/make.jl index b2cf0ee6..db2e25e0 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -29,6 +29,7 @@ makedocs(; checkdocs = :exports, pages = [ "Home" => "index.md", + "Explorer" => "explorer.md", "APIs" => "api.md", "How do I?" => "howdoi.md", ], diff --git a/docs/src/explorer.md b/docs/src/explorer.md new file mode 100644 index 00000000..e08d6679 --- /dev/null +++ b/docs/src/explorer.md @@ -0,0 +1,42 @@ +`ClimaExplorer` +============= + +`ClimaExplorer` is an interactive utility to visualize the output of `CliMA` +simulations. `ClimaExplorer` uses `ClimaAnalysis.jl` and `Bonito.jl` to start a +local server and dynamically serve plots. + +How to install +=============== + +To install `ClimaExplorer`, first clone the `ClimaAnalysis.jl` repo +```sh +git clone https://github.com/CliMA/ClimaAnalysis.jl.git +``` +Then, instantiate the `climaexplorer` environment +```sh +julia --project=ClimaAnalysis.jl/climaexplorer -e 'using Pkg; Pkg.instantiate()' +``` + +How to use +========== + +Once you installed `ClimaExplorer`, you can start the server with +```sh +julia --project=ClimaAnalysis.jl/climaexplorer ClimaAnalysis.jl/climaexplorer/explorer.jl PATH_OF_OUTPUT +``` +where `PATH_OF_OUTPUT` is the path where the output files live. +The first time you do so, this will take a little while. + +If everything went correctly, you should see a message like +``` +Server: + isrunning: true + listen_url: http://localhost:8080 + online_url: http://localhost:8080 + http routes: 1 + / => App + websocket routes: 0 +``` +This means that the server has started. Visit `http://localhost:8080` to start +inspecting the output. Note that the port number `8080` might be different. +To close the server, press `Ctrl-C`.