logo

How to Create Art Styles in Julia 📂Julia

How to Create Art Styles in Julia

Overview

RecipesBase.jl is a package that allows users to create their own styles for new plots, similar to how ggplot works in the R programming language, with its own unique syntax1 separate from the base Julia. Let’s learn through examples.

Code

using Plots
using DataFrames

df = DataFrame(x = 1:10, y = rand(10))
plot(df)

@userplot TimeEvolution
@recipe function f(te::TimeEvolution)
    df = te.args[1]
    linealpha --> 0.5

    column_names = names(df)
    for (column_index, column_name) ∈ enumerate(column_names)
        @series begin
            label --> column_name
            df[:,column_index]
        end
    end
end

timeevolution(df); png("1")
timeevolution(df, legend = :left); png("2")

First, running the above code results in the following error:

julia> df = DataFrame(x = 1:10, y = rand(10))
10×2 DataFrame
 Row │ x      y
     │ Int64  Float64   
─────┼──────────────────
   1 │     1  0.636532
   2 │     2  0.463764
   3 │     3  0.604559
   4 │     4  0.654089
   5 │     5  0.883409
   6 │     6  0.91667
   7 │     7  0.0609783
   8 │     8  0.602259
   9 │     9  0.460372
  10 │    10  0.479944

julia> plot(df)
ERROR: Cannot convert DataFrame to series data for plotting

This is because plot() is not defined by default to draw pictures by taking a dataframe.

@userplot and @recipe

@userplot TimeEvolution
@recipe function f(te::TimeEvolution)
    ...
end

In the example, we will draw a line chart directly with the time series data.

  • @userplot: Specifies the name of the function that will inherit the properties of the plot() function. Note that although case-sensitive here, the resulting function can only use lowercase.
  • @recipe: Specifies the style of the plot explicitly. The name of the function that follows is usually conventionally f.

f(te::TimeEvolution)

df = te.args[1]
linealpha --> 0.5

column_names = names(df)
for (column_index, column_name) ∈ enumerate(column_names)
    @series begin
        label --> column_name
        df[:,column_index]
    end
end

Let’s understand the above code line by line.

df = te.args[1]

It considers the first argument received as df. In this example, since it’s assumed that the argument provided is a dataframe, the abbreviation df is used. Keep in mind that f is not the function we will be using, and its argument te is also not directly utilized.

linealpha --> 0.5

The transparency of the lines to be drawn in this plot is set to 0.5. Note that this value is given with -->, not linealpha = 0.5, which is completely different from the common Julia syntax.

column_names = names(df)
for (column_index, column_name) ∈ enumerate(column_names)
    ...
end

Even if plot() direct maps a 2D array to a plot, the labels are automatically given as y1, y2, etc. To prevent this, labels will be given separately by fetching the column names of the dataframe as shown above. The enumerate() function is used to iterate through both the index and the column names simultaneously. For more details, see the explanation on enumerate().

@series

for ...
    @series begin
        label --> column_name
        df[:,column_index]
    end
end

The @series macro specifies the data and its style that will be repeatedly drawn. The label of the column name is given with label --> column_name, and by writing df[:,column_index] in the last row, the data from that column name will be drawn. Note that the data to be drawn must be in the last row.

Results

As a result, we can now draw pictures in the style we specified with timeevolution() or timeevolution!().

timeevolution(df)

1.png

It can be confirmed that even with a dataframe inserted, the picture is drawn successfully without error. The transparency of the line is 0.5, and the label is also exactly carried over from the column names in the dataframe.

timeevolution(df, legend = :left)

2.png

We have arbitrarily adjusted the position of the legend. Despite no mention of legend when defining f, it can be confirmed to apply well. This is because timeevolution() inherits the rest of the elements of plot().

Environment

  • OS: Windows
  • julia: v1.6.3