# Proper Makie.jl recipe usage

**URL:** <https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502>\
**Category:** Visualization\
**Tags:** plotting, makie, recipe\
**Created:** [September 18, 2025, 11:56pm UTC](https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502 "2025-09-18T23:56:37Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [September 18, 2025, 11:56pm UTC](https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502/1 "2025-09-18T23:56:38Z")

</div>

This is the first time I am writing a recipe and I am a bit lost in all the options. Basically I want to have a few convenient plotting functions for a package I am working on. It calculates solar positions, which are just tuples of (datetime, zenith, azimuth). Fairly straightforward.

My current attempt is this:

```julia
module SolarPositionMakieExt

using Dates, Tables, Makie
using SolarPosition
import SolarPosition: sunpathplot, sunpathplot!

_elevation_from_zenith(ze) = 90 .- ze

function _normalize_input(data, t_col)
    if Tables.istable(data)
        colnames = Tables.columnnames(data)
        t = Tables.getcolumn(data, t_col)
        az = Tables.getcolumn(data, :azimuth)

        if :zenith in colnames
            ze = Tables.getcolumn(data, :zenith)
            el = _elevation_from_zenith(ze)
        elseif :elevation in colnames
            el = Tables.getcolumn(data, :elevation)
            ze = 90 .- el
        else
            error("Need either :zenith or :elevation in table input.")
        end
    elseif data isa Tuple && length(data) == 3
        t, ze, az = data
        el = _elevation_from_zenith(ze)
    else
        error("Data must be a Tables.jl source or (time, zenith, azimuth) tuple.")
    end
    return t, ze, el, az
end

@recipe(SunpathPlot) do scene
    Theme(
        coords = :polar, # :polar or :cartesian
        colormap = :twilight,
        markersize = 3,
        t_col = :datetime,
    )
end

function sunpathplot(data; coords = :polar, kwargs...)
    if coords === :polar
        fig = Figure()
        ax = PolarAxis(fig[1, 1])
    else
        fig = Figure()
        ax = Axis(fig[1, 1])
    end
    sunpathplot!(ax, data; coords = coords, kwargs...)
    return fig
end

function sunpathplot!(ax, data; coords = :polar, t_col = :datetime, kwargs...)
    t, ze, el, az = _normalize_input(data, t_col)
    vals = dayofyear.(t)

    if coords === :polar
        if !(ax isa PolarAxis)
            error("Axis must be a PolarAxis for polar coordinates")
        end

        # configure polar axis for solar paths
        ax.direction = -1
        ax.theta_0 = -π / 2
        ax.rlimits = (0, 90)
        x = deg2rad.(az)
        y = ze
    else
        if !(ax isa Axis)
            error("Axis must be a regular Axis for cartesian coordinates")
        end
        x = az
        y = el
    end

    scatter!(
        ax,
        x,
        y;
        color = vals,
        colormap = get(kwargs, :colormap, :twilight),
        markersize = get(kwargs, :markersize, 3),
    )
end

# fallback method for when no axis is provided
function sunpathplot!(data; coords = :polar, kwargs...)
    ax = current_axis()
    sunpathplot!(ax, data; coords = coords, kwargs...)
end

function Makie.plot!(sp::SunpathPlot)
    t, _, el, az = _normalize_input(sp[1][], sp.theme.t_col[])
    vals = dayofyear.(t)

    # only handle cartesian coordinates in the recipe
    scatter!(
        sp,
        az,
        el;
        color = vals,
        colormap = sp.colormap[],
        markersize = sp.markersize[],
    )

    return sp
end

end # module

```

Which I then use like this:

```julia
"""Plot solar positions using SolarPosition.jl."""

using Dates
using DataFrames
using GLMakie
using SolarPosition

# define observer location (latitude, longitude, altitude in meters)
obs = Observer(28.6, 77.2, 0.0)

# a whole year of hourly timestamps
times = DateTime(2023):Hour(1):DateTime(2024)

# compute solar positions
positions = solar_position(obs, times)

# plot positions from NamedTuple
sunpathplot(positions, coords = :polar)

```

The resulting plot looks okay:

 ![image](https://global.discourse-cdn.com/julialang/original/3X/9/8/98a627dc53c1cbc548faaeb7c41cc22a02409f38.png)

But I don’t think this is very ideal.

According to the docs, I think I should be using [`convert_arguments`](https://docs.makie.org/dev/explanations/recipes#Multiple-Argument-Conversion-with-convert_arguments).

I also need to be able to select the right axis type (`PolarAxis` or regular `Axis`) and configure it properly in each case. There isn’t really much the user would be able to configure in this type of plot. It would be nice if I could just dispatch `:polar` or `:cartesian` for the plot type and on `NamedTuple` and `Tables` for the type of data input.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [September 19, 2025, 11:12am UTC](https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502/2 "2025-09-19T11:12:16Z")

</div>

I wonder if you really need a Makie recipe? From the first glance, a regular Julia function `sunpathplot!` + `sunpathplot` should be fine. That’s basically what you have anyway…

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [September 19, 2025, 11:24am UTC](https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502/3 "2025-09-19T11:24:00Z")

</div>

I guess not. But I also wanted to follow the format to learn how to use it.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [September 19, 2025, 11:51am UTC](https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502/4 "2025-09-19T11:51:53Z")

</div>

`convert_arguments` literally converts plot input arguments to another set of arguments: basically, “if `scatter` is called with `a::A, b::B, c::C` I want it to mean exactly the same as if `scatter` was called with `x, y, z = f(a, b, c)`”. It can also choose stuff like colormap (although be careful to allow overrides), but doesn’t affect axis properties (like `direction` and `theta_0` in your example) nor does it allow choosing the axis type.

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [September 19, 2025, 3:57pm UTC](https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502/5 "2025-09-19T15:57:55Z")

</div>

Okay got it thanks. How would you structure this?

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [September 20, 2025, 10:24pm UTC](https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502/6 "2025-09-20T22:24:05Z")

</div>

I think I managed to do it.

Code can be found here: [SolarPosition.jl/ext/SolarPositionMakieExt.jl at main · JuliaSolarPV/SolarPosition.jl · GitHub](https://github.com/JuliaSolarPV/SolarPosition.jl/blob/main/ext/SolarPositionMakieExt.jl)

 ![image](https://global.discourse-cdn.com/julialang/original/3X/5/5/554d2dcebcc85643ca5966dc76b0ba1ed00867be.png)

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [September 21, 2025, 5:33am UTC](https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502/7 "2025-09-21T05:33:58Z")

</div>

Just mentioning that the way you did it with `current_axis()` etc can easily break if you plot into different axes, it’s just not how recipes are meant to work. We are still looking for ways to make plots with axis settings and colorbars etc. possible in some form of recipe. Currently recipes are just about combining plot primitives and for everything with axes and figures you need to use normal functions.

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [September 21, 2025, 12:11pm UTC](https://discourse.julialang.org/t/proper-makie-jl-recipe-usage/132502/8 "2025-09-21T12:11:36Z")

</div>

thanks. Thought I was missing something with the recipe implementation but that makes more sense. I’ll revisit this when it starts breaking and then I’ll implement your suggestion 😃
