Tutorials
Published on October 20, 2022
Welcome to a new Genie app tutorial! Today, we’ll learn to build a simple app for exploratory data analysis. The app will let us upload CSV files and visualize the data distribution in a histogram, and we’ll interact through selectors to choose the CSV file and column to be plotted. Take a look at the final app:
The full code and some sample CSV files can be downloaded from this GitHub repository.
We’ll use GenieFramework.jl
, DataFrames.jl
and CSV.jl
to build the app. To install them, enter Pkg mode with ]
and type in the following command:
> add GenieFramework DataFrames CSV pkg
The app’s code will have a main file dashboard.jl
for the logic, ui.jl
for the UI, and an uploads
folder to store the datasets:
CSVAnalysis
dashboard.jl
├── ui.jl
├── uploads/ └──
Let us start by creating the dashboard.jl
file and adding the following skeleton code:
using GenieFramework, DataFrames, CSV
@genietools
#
# Logic goes here
#
@page("/", "ui.jl")
|| Server.up() Server.isrunning()
The @genietools
macro sets up the Genie environment and the @page
macro renders the app’s UI, whereas the logic will be implemented in between. For the ui.jl
file this will be the skeleton code:
[#
# UI definition goes here
#
|> string ]
The app has four components: an upload box, two selectors, and a histogram plot. To implement each component, we’ll need to:
ui.jl
.The upload box lets us select a .csv file and store it on the server side. The file is transmitted via an HTTP POST
request, with the file attached in the header. To enable this request, add the following configuration lines to dashboard.jl
:
"Access-Control-Allow-Origin"] = "*"
Genie.config.cors_headers["Access-Control-Allow-Headers"] = "Content-Type"
Genie.config.cors_headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,OPTIONS"
Genie.config.cors_headers[= ["*"] Genie.config.cors_allowed_origins
Next, we’ll create the storage folder if it doesn’t already exist, and give our app page a title.
const FILE_PATH = "upload"
mkpath(FILE_PATH)@out title = "CSV Analysis"
The @out
macro makes it so the code in the UI can access the value of a variable. Before adding more logic, let us proceed with the UI.
The UI is structured via Julia functions that implement various HTML and Javascript components. To add the page title and upload box, write in ui.jl
the following:
["{{title}}")
heading(
row([="col-md-12", [
cell(class="Upload Dataset", accpt=".csv", multiple=true, method="POST", url="http://localhost:8000/", field__name="csv_file")
uploader(label
])
])|> string ]
While heading
, row
and cell
simply generate the HTML code for headers and dividers, the uploader
function generates the Javascript allowing us to pick and upload a file. The url
argument points to the address where the POST
request will be submitted, and so we’ll require a route on the server that handles the request. For this, add the following code to dashboard.jl
:
"/", method = POST) do
route(= Genie.Requests.filespayload()
files for f in files
write(joinpath(FILE_PATH, f[2].name), f[2].data)
end
if length(files) == 0
@info "No file uploaded"
end
return "Upload finished"
end
The route
function sets up a listener function at a path that will be executed for incoming requests. Instead of defining a standalone function that stores the file in the request header, we’ve used Julia’s do end
syntax which builds an anonymous function with the code in the block and passes it to route
.
The upload box is now finished and working. Give it a try by typing include("dashboard.jl")
in the REPL and opening a browser on http://localhost:8000.
Once we have the .csv file uploaded, we want to access its contents and choose one column for plotting. We’ll do the choosing through two selectors, which rely on several variables to store:
Let’s add to dashboard.jl
the variables with default values as
@in selected_file = "iris.csv"
@in selected_column = "petal.length"
@out upfiles = readdir(FILE_PATH)
@out columns = ["petal.length", "petal.width", "sepal.length", "sepal.width", "variety"]
Note that we tag the selection variables with @in
as they’ll be storing values sent from the UI.
Now, we define the UI with
row([="st-module", [
cell(class"File")
h6(:selected_file; options=:upfiles)
Stipple.select(
])="st-module", [
cell(class"Column")
h6(:selected_column; options=:columns)
Stipple.select( ])
The selectors are generated by Stipple.select
, which takes as first argument the symbol name of the variable where the selected value will be stored.
As of now, when the user makes a selection the chosen values are stored in the corresponding variables. All we need to do now is use these values to generate the histogram plot.
Since all the interactive elements are already implemented, we’ll add to dashboard.jl
the reactive code that
For the plotting, we’ll use a PlotData
struct that can be parameterized to different plot types such as scatter plots or histograms. We’ll define the variable storing the plot as
@out irisplot = PlotData()
In Genie, reactive code is implemented within a handler defined with the @handlers
macro. In it, we use the @onchangeany
macro to act whenever a variable changes in the UI as follows:
@handlers begin
@onchangeany isready, selected_file, selected_column begin
= readdir(FILE_PATH)
upfiles = CSV.read(joinpath(FILE_PATH, selected_file), DataFrame)
data = names(data)
columns if selected_column in names(data)
= PlotData(x=data[!, selected_column], plot=StipplePlotly.Charts.PLOT_TYPE_HISTOGRAM)
irisplot end
end
end
Notice the isready
variable, which is provided by Genie to indicate when the browser has finished loading the UI. Finally, the code for the UI element to display the plot is
row([="st-module", [
cell(class"Histogram")
h5(:irisplot)
plot(
]) ])
That’s it, we’re done! Now you can run the full app and add some more functionality if you’d like. Here are some ideas:
I hope you found the tutorial useful, if you have any questions or suggestions, please let me know on Discord.