Julia Community 🟣

Cover image for How to quickly turn your Julia code into a web app with Genie Builder
Pere Giménez
Pere Giménez

Posted on

How to quickly turn your Julia code into a web app with Genie Builder

Data scientists don't usually start writing full-blown web apps from the get-go. Instead, we begin with the typical including data cleaning, exploratory analysis, testing of several models, etc., and only when we're confident in the quality of the insights extracted from the data do we start thinking about publicly sharing them with a web application. The Genie Framework comes in very handy for this last step as it allows you to implement an interactive web UI so that:

  • You can share your insights with just a link.
  • Other people can re-run the analysis and fine-tune the insights through the controls in the UI.

All of this without writing a single line of HTML or Javascript thanks to Genie Builder's no-code editor.

In this week's tutorial, I'll show you how to go from your existing Julia code to a Genie app in just a few steps. The code for the example app can be downloaded here, take a look at the figure below to see what it looks like in a browser.

Image description

Table of contents:

  1. Writing the data analysis code
  2. Creating a new app in Genie Builder
  3. Designing the UI in Genie Builder's no-code editor
  4. Implementing the app's logic
  5. Running the app outside of Genie Builder

1. Writing the data analysis code

To illustrate the Genie workflow, we'll write some code to extract insights from the German Credit Risk dataset. This dataset contains information about 1000 people, where each person is described by 20 features such as age, sex or credit amount. Additionally, every person has been manually labelled as "good credit risk" or "bad credit risk" according to the feature values.

Say we want to extract the following insights from the dataset:

  • Number and monetary amount in good and bad credits.
  • Credit amounts and creditor ages.
  • Number of credits by age range.

To calculate these metrics, save the code below to a script germancredits.jl and execute it in a Julia REPL to make sure everything is working.

 using DataFrames, Dates, OrderedCollections

 function good_bad_credits_stats(data::DataFrame)
     good_credits_count = data[data.Good_Rating .== true, [:Good_Rating]] |> nrow
     bad_credits_count = data[data.Good_Rating .== false, [:Good_Rating]] |> nrow
     good_credits_amount = data[data.Good_Rating .== true, [:Amount]] |> Array |> sum
     bad_credits_amount = data[data.Good_Rating .== false, [:Amount]] |> Array |> sum

     (; good_credits_count, bad_credits_count, good_credits_amount, bad_credits_amount)
 end

 function credit_age_amount(data::DataFrame; good_rating::Bool)
     data[data.Good_Rating .== good_rating, [:Age, :Amount]]
 end

 function credit_no_by_age(data::DataFrame; good_rating::Bool)
     age_stats::LittleDict{Int,Int} = LittleDict()
     for x in 20:10:90
       age_stats[x] = data[(data.Age . [x:x+10]) .& (data.Good_Rating .== good_rating), [:Good_Rating]] |> nrow
     end
     age_stats
 end

 # testing the functions
 using CSV
 data = CSV.File("german_credits.csv") |> DataFrame
 @show good_bad_credits_stats(data)
 @show credit_age_amount(data, good_rating=true)
 @show credit_no_by_age(data, good_rating=true)
Enter fullscreen mode Exit fullscreen mode

Now that the analysis code is finished and working, it's time to make it into a Genie app.

2. Creating a new app in Genie Builder

We'll use the Genie Builder plugin for VSCode to build the app. After installing the plugin, start the GB server and create a new app named GermanCredits. Click yes when asked if you want to start it, and the no-code editor will appear.

In the workspace tab, you'll find that the newly-created app's code is split into two files: app.jl and app.jl.html. Of the two, app.jl is the one to be manually edited whereas app.jl.html is written to by the no-code editor. At this point, you can add the script germancredits.jl to the workspace to have all your files in one place.

To avoid conflicts, it is recommended to wrap the code in a module and remove any testing code. So, edit germancredits.jl to make it look like this:

 module GermanCredits
     export good_bad_credits_stats, credit_age_amount_duration, credit_data_by_age
     using DataFrames, Dates, OrderedCollections

     function good_bad_credits_stats(data::DataFrame)
         ...
     end
     function credit_age_amount(data::DataFrame; good_rating::Bool)
         ...
     end
     function credit_no_by_age(data::DataFrame; good_rating::Bool)
         ...
     end
 end

Enter fullscreen mode Exit fullscreen mode

To install the required packages DataFrames, Dates and OrderedCollections within the app, launch Genie Builder's Package Manager by right-clicking on the app's name on the left sidebar. You'll see the installation progress in the REPL, and the package versions will be added to the Project.toml file.

Next, we'll design the UI to display the data insights.

3. Designing the UI in Genie Builder's no-code editor

For the German Credits UI, we want to have:

  • Page title header.
  • Badges showing the number of credits and their monetary amount.
  • A range control to filter the age of the people whose data is displayed.
  • A bar chart displaying the number of credits by age.
  • A scatter plot displaying age vs. amount for each credit.

To build the UI with the no-code editor, look for the components you need in the right sidebar and drag them onto the canvas. Start with the header, rows and columns to set the page layout:

Next, add the following components:

  • Paragraph (x4).
  • Range.
  • Bar.
  • Scatter.

Finally, let's make the font of the number badges bigger. Click on the </> icon and change the class of the <p> element to bignumber. Then, add a new CSS style .bignumber{font-size:40px; text-align:center;}.

To preview the UI, click on the monitor icon next to the GET tab in the left sidebar. Note that nothing works yet, you still have to implement the app's logic and bind the UI components to the Julia code.

4. Implementing the app's logic

To make the app interactive you have to implement its logic by:

  • Exposing reactive variables from the Julia code to the UI.
  • Writing reactive code to handle user interaction.
  • Binding the exposed variables to the UI components.

The file app.jl in the workspace is the main entry point to the app, and it's where you'll write the logic. This is what the file looks like by default:

 using GenieFramework
 @genietools

 @handlers begin
   @out message = "Hello World!"
   @onchange isready begin
     @show "App is loaded"
   end
 end

 @page("/", "app.jl.html")
Enter fullscreen mode Exit fullscreen mode

To start with a clean app, delete the contents of the block delimited by the @handlers macro. Then, include the GermanCredits package and load the dataset by adding the following code before it:

 include("germancredits.jl")
 using .GermanCredits
 using GenieFramework, CSV, DataFrames, OrderedCollections

 const data = CSV.File("german_credits.csv") |> DataFrame
Enter fullscreen mode Exit fullscreen mode

Next, we'll add the reactive variables and code for each element in the UI. Since we want to filter the data to show only a certain age range, let's start with the range selector.

Range selector

In Genie apps, reactivity is based around two concepts:

  • Variables are exposed to the UI by tagging them with the @in macro if they take their value from the UI, or with the @out macro if they output their value to the UI.
  • The code to handle interactions is written in the block delimited by the @onchange macro, which watches a variable and whenever it changes executes the code in the block.

To filter the data via the range selector in the UI, we need:

  • A variable of type RangeData to store the range selected in the UI.
  • A DataFrame to hold the filtered data.
  • A function call to update the available data when an age range is selected.

To implement these elements, add the following code inside the @handlers macro:

 @handlers begin
     @in age_range::RangeData{Int} = RangeData(18:90)
     @out filtered_data = DataFrame()
     @onchange age_range begin
         filtered_data = data[(age_range.range.start .<= data[!, :Age] .<= age_range.range.stop), :]
     end
 end
Enter fullscreen mode Exit fullscreen mode

Note that the variables have default values so that the UI can be rendered when the page loads. Now, go to the no code editor, select the Range component and assign the age_range variable to its Data Binding field.

That's it for the range selector. Any time you move one end of the slider in the UI, the contents of filtered_data will be updated. Let's continue with the next element to show the updated insights.

Number badges

To display the number and amount in credits, you'll need:

  • Variables to store the quantities.
  • A call to good_bad_credits_stats to update the variables when a new range is selected.
 @handlers begin
     ...
     @out good_credits_count = 0 
     @out bad_credits_count = 0
     @out good_credits_amount = 0
     @out bad_credits_amount = 0
     @onchange age_range begin
         ...
         good_credits_count, bad_credits_count, good_credits_amount,
         bad_credits_amount = good_bad_credits_stats(filtered_data)
     end
 end
Enter fullscreen mode Exit fullscreen mode

To show the number badges in the UI, assign each variable to the Text Binding field of a Paragraph component.

Number of credits by age plot

We'll plot the credits by age with a bar plot indexed by the age ranges in 10 year increments. For this, you'll need:

  • An array to hold the x axis labels.
  • An array of PlotData to hold the plot data.
  • A variable of type PlotLayout to specify the plot layout.
  • Reactive code to update the plots with the values calculated by credit_no_by_age.
 @handlers begin
     ...
     @out age_slots = ["20-30", "30-40", "40-50", "50-60", "60-70", "70-80", "80-90"]
     @out credit_no_by_age_plot = PlotData[]
     @out credit_no_by_age_plot_layout = PlotLayout(barmode="group", showlegend = true)
     @onchange age_range begin
         ...
         credit_no_by_age_plot = [
             PlotData(
                 x = age_slots, 
                 y = collect(values(credit_no_by_age(filtered_data; good_rating = true))),
                 name = "Good credits", 
                 plot = StipplePlotly.Charts.PLOT_TYPE_BAR, 
                 marker = PlotDataMarker(color = "#72C8A9")
             ),
             PlotData(
                 x = age_slots, 
                 y = collect(values(credit_no_by_age(filtered_data; good_rating = false))),
                 name = "Bad credits", 
                 plot = StipplePlotly.Charts.PLOT_TYPE_BAR, 
                 marker = PlotDataMarker(color = "#BD5631")
             )
         ]
     end
 end
Enter fullscreen mode Exit fullscreen mode

Age vs. credit amount scatter plot

For this plot, you'll require:

  • An array of PlotData to hold the plot data.
  • A variable of type PlotLayout to specify the plot layout.
 @handlers begin
     ...
     @out age_amount_plot_layout = PlotLayout(showlegend = true)
     @out age_amount_plot = PlotData[]

     @onchangeany age_range begin
         ...
         dgood = credit_age_amount(filtered_data; good_rating = true)
         dbad = credit_age_amount(filtered_data; good_rating = false)
         age_amount_plot = [
             PlotData(
                 x = dgood.Age, 
                 y = dgood.Amount, 
                 name = "Good credits", 
                 mode = "markers",
                 marker = PlotDataMarker(size=18, opacity= 0.4, color = "#72C8A9", symbol="circle")
             ),
             PlotData(
                 x = dbad.Age, 
                 y = dbad.Amount, 
                 name = "Bad credits", 
                 mode = "markers",
                 marker = PlotDataMarker(size=18, opacity= 0.4, color = "#BD5631", symbol="cross")
             )
         ]
     end
 end
Enter fullscreen mode Exit fullscreen mode

And with this last component, you've finished your Genie app!

5. Running the app outside of Genie Builder

You can run your app locally within VSCode from the Genie Builder Apps menu in the sidebar. However, if you want to run your app outside of Genie Builder, the app's files are located in the folder ~/.julia/geniebuilder/apps/GermanCredits/. Before running the app, you'll have to tell Genie to start a server by adding the following line at the end of app.jl:

 Server.isrunning() || Server.up()
Enter fullscreen mode Exit fullscreen mode

Then, open a REPL with julia --project in the app's folder and type include("app.jl") to launch the app.

Latest comments (0)