# Radar plot in Plots.jl or Makie.jl?

**URL:** <https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576>\
**Category:** Visualization\
**Tags:** plotting, plots, makie\
**Created:** [October 11, 2022, 1:11pm UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576 "2022-10-11T13:11:36Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![sylvaticus](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sylvaticus/32/203883_2.png) [@sylvaticus](https://discourse.julialang.org/u/sylvaticus)\
**Post date:** [October 11, 2022, 1:11pm UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/1 "2022-10-11T13:11:36Z")

</div>

Is there a simple way to make a radar plot (like a polar plot but categorical) using Plots.jl or Makie.jl ?

I can see these should be possible with a package named [ECharts](https://randyzwitch.com/ECharts.jl/radar), but it doesn’t support VSCode and seems a bit abandoned and it forces very old version of the other packages…

---

<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:** [October 11, 2022, 4:10pm UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/2 "2022-10-11T16:10:07Z")

</div>

Nothing premade yet in Makie, but the primitives look not very complicated if someone wanted to add it 🙂 The thing is more that the axis is unusual, and I guess it would be fastest to just plot this into a normal Axis where the decorations are hidden, than to build a completely new Axis object with projections etc.

---

<div class="post-metadata">

**Author:** ![empet](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/empet/32/221303_2.png) [@empet](https://discourse.julialang.org/u/empet)\
**Post date:** [October 11, 2022, 7:37pm UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/3 "2022-10-11T19:37:36Z")

</div>

With PlotlyJS.jl you can get a radar plot, as a polar plot with categorical theta:

```julia
using PlotlyJS
categories = String["processing cost","mechanical properties",
                    "chemical stability", "thermal stability", "device integration"]
fig = Plot()
addtraces!(fig, 
           scatterpolar(r=[1, 5, 2, 2, 3],
                        theta=categories,
                        fill="toself",
                        name="Product A"))
addtraces!(fig, 
           scatterpolar(r=[4, 3, 2.5, 1, 2],
                        theta=categories,
                        fill="toself",
                        name="Product B"))
relayout!(fig, width =600, polar=attr(radialaxis=attr(visible=true, range=[0, 5])),
          showlegend=false)
display(fig)

```

 ![radar-plot](https://global.discourse-cdn.com/julialang/original/3X/0/5/05379573ff99d760c9445100d8bbeebcb69a7774.png)

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [October 11, 2022, 10:30pm UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/4 "2022-10-11T22:30:00Z")

</div>

FWIW, find here a home-made Plots.jl solution:

```julia
using Random, Measures, Plots; gr()

# INPUT DATA:
Random.seed!(1789)
n = 8
R1, R2 = rand(n+1), rand(n+1)
R1[n+1], R2[n+1] = R1[1], R2[1]
labels = 'C' .* string.(1:n)

# PLOT:
θ = LinRange(0, 2pi, n+1)
z = 1.15*exp.(im*2π*(0:n-1)/n)
plot(θ, R1, proj=:polar, ms=3, m=:o, c=:blue, fill=(true,:blues), fa=0.4, lims=(0,1), xaxis=false, margin=5mm)
plot!(θ, R2, proj=:polar, ms=3, m=:o, c=:red, fill=(true,:reds), fa=0.4, lims=(0,1), xaxis=false)
annotate!(real.(z), imag.(z), text.(labels,12,"Computer Modern"))

```

 ![Plots_gr_radial_categorical_plots](https://global.discourse-cdn.com/julialang/original/3X/4/b/4b7a2ef2b60813c5e1df8cddc71d929d1e03a8a0.png)

---

<div class="post-metadata">

**Author:** ![sylvaticus](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sylvaticus/32/203883_2.png) [@sylvaticus](https://discourse.julialang.org/u/sylvaticus)\
**Post date:** [October 12, 2022, 7:26am UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/5 "2022-10-12T07:26:50Z")

</div>

> [@rafael.guerra](#):
>
> `using Random, Measures, Plots`

There is still a problem with real world data: the alignment of the label should be conditional to the angle:

 ![image](https://global.discourse-cdn.com/julialang/original/3X/2/9/2910b48c763269995d912fced19bc81fb376ca50.png)  
Any way to impose it ?

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [October 12, 2022, 7:54am UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/6 "2022-10-12T07:54:23Z")

</div>

> [@sylvaticus](#):
>
> Any way to impose it ?

Yes, we can create a `justifyme()` function:

```julia
using Random, Measures, Plots; gr()

# INPUT DATA:
Random.seed!(1789)
n = 8
R1, R2 = rand(n+1), rand(n+1)
R1[n+1], R2[n+1] = R1[1], R2[1]
labels = "Long Category" .* string.(1:n)

# PLOT:
justifyme(θ) = (0≤θ<π/2 || 3π/2<θ≤2π) ? :left : (π/2<θ<3π/2) ? :right : :center
θ = LinRange(0, 2pi, n+1)
z = 1.15*exp.(im*2π*(0:n-1)/n)
plot(θ, R1, proj=:polar, ms=3, m=:o, c=:blue, fill=(true,:blues), fa=0.4, lims=(0,1), xaxis=false, margin=10mm)
plot!(θ, R2, proj=:polar, ms=3, m=:o, c=:red, fill=(true,:reds), fa=0.4, lims=(0,1), xaxis=false)
annotate!(real.(z), imag.(z), text.(labels,12,justifyme.(θ[1:n]),"Computer Modern"))

```

 ![Plots_gr_radial_categorical_plots](https://global.discourse-cdn.com/julialang/original/3X/4/a/4aa54225b1e7e6d74e9f1eeaaa999a854bbee85b.png)

---

<div class="post-metadata">

**Author:** ![davide](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/davide/32/52136_2.png) [@davide](https://discourse.julialang.org/u/davide)\
**Post date:** [October 24, 2022, 10:06am UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/7 "2022-10-24T10:06:00Z")

</div>

Hi @sylvaticus , this is what I did using Makie.jl (and using also @rafael.guerra’s `justifyme()` function. It can be refined and a recipe would be better, but had no time to study how to.

```julia
using CairoMakie, Colors, Random

# For horizintal and vertical text alignment:
justifyme(θ) = (0≤θ<π/2 || 3π/2<θ≤2π) ? :left : (π/2<θ<3π/2) ? :right : :center
justifymeV(θ) = π/4≤θ≤3π/4 ? :bottom : 5π/4<θ≤7π/4 ? :top : :center

# Radar plot function:
function radarplot(ax::Axis, v; p_grid = maximum(v) * (1.0:5.0) / 5.0, title = "", labels = eachindex(v), labelsize = 1, points=true, spokeswidth= 1.5, spokescolor=:salmon, fillalpha=0.2, linewidth=1.5)
    # Axis attributes
    ax.xgridvisible = false
    ax.ygridvisible = false
    ax.xminorgridvisible = false
    ax.yminorgridvisible = false
    ax.leftspinevisible = false
    ax.rightspinevisible = false
    ax.bottomspinevisible = false
    ax.topspinevisible = false
    ax.xminorticksvisible = false
    ax.yminorticksvisible = false
    ax.xticksvisible = false
    ax.yticksvisible = false
    ax.xticklabelsvisible = false
    ax.yticklabelsvisible = false
    ax.aspect = DataAspect()
    ax.title = title
    #
    l = length(v)
    rad = (0:(l-1)) * 2π / l
    # Point coordinates
    x = v .* cos.(rad)
    y = v .* sin.(rad)
    if p_grid != 0
        # Coordinates for radial grid
        xa = maximum(p_grid) * cos.(rad) * 1.1
        ya = maximum(p_grid) * sin.(rad) * 1.1
        # Coordinates for polar grid text
        radC = (rad[Int(round(l / 2))] + rad[1 + Int(round(l / 2))]) / 2.0
        xc = p_grid * cos(radC)
        yc = p_grid * sin(radC)
        for i in p_grid
            poly!(ax, Circle(Point2f(0, 0), i), color = :transparent, strokewidth=1, strokecolor=ax.xgridcolor)
        end
        text!(ax, xc, yc, text=string.(p_grid), textsize = 12, align = (:center, :baseline), color=ax.xlabelcolor)
        arrows!(ax, zeros(l), zeros(l), xa, ya, color=ax.xgridcolor, linestyle=:solid, arrowhead=' ')
        if length(labels) == l
            for i in eachindex(rad)
                text!(ax, xa[i], ya[i], text=string(labels[i]), textsize = labelsize, markerspace = :data, align = (justifyme(rad[i]), justifymeV(rad[i])), color=ax.xlabelcolor)
            end
        elseif length(labels) > 1
            printstyled("WARNING! Labels omitted: they don't match with the points ($(length(labels)) vs $l).\n", bold=true, color=:yellow)
        end
    end
    pp = poly!(ax, [(x[i], y[i]) for i in eachindex(x)], strokecolor=RGB{Float32}(0.4, 0.4, 0.4))
    cc = to_value(pp.color)
    m_color = RGBA{Float32}(comp1(cc), comp2(cc), comp3(cc), fillalpha)
    s_color = RGB{Float32}(comp1(cc), comp2(cc), comp3(cc))
    pp.color = m_color
    pp.strokecolor = s_color
    pp.strokewidth= linewidth
    arrows!(ax, zeros(l), zeros(l), x, y, color=spokescolor, linewidth=spokeswidth, arrowhead=' ')
    if points
        scatter!(ax, x, y)
    end
    ax
end

# INPUT DATA:
Random.seed!(1789)
n = 8
R1, R2 = rand(n), rand(n)
labels = "Cat." .* string.(1:n)

fig = Figure()
ax=Axis(fig[1,1])
radarplot(ax, R1; labels=labels, spokeswidth = 0, p_grid=0.2:0.2:1.0, labelsize=0.07)
radarplot(ax, R2; title="My Radar", labels="", spokeswidth = 0, p_grid=0)

```

 ![radarplot](https://global.discourse-cdn.com/julialang/original/3X/2/9/2908906be0aaef7988ec33a3f1e9a14efa9e95cd.jpeg)

What is inconvenient is the label size: in order to get them always displayed in the boundigbox, and not truncated, I had to specify the size in data space (`markerspace = :data`), instead of in pixel space (which would allow to keep text size independent of data scaling; see [documenatation page)](https://docs.makie.org/stable/examples/plotting_functions/text/#pixel_space_text)

---

<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:** [October 24, 2022, 10:22am UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/8 "2022-10-24T10:22:07Z")

</div>

A trick which I use for the same problem in Axis3 is to calculate the label anchor positions in pixels and then plot them into the container scene of the Axis (this stretches further than the inner part). That’s also how the open PR for a polar axis implemented it I think:

> <https://github.com/MakieOrg/Makie.jl/pull/2014>
>
> \# Description
> 
> This PR implements a \`PolarAxis\` layoutable which should be abl…e to serve as a template for a more general \`NonlinearAxis\` layoutable.
> 
> \`\`\`julia
> rs, θs = LinRange(0, 1, 100), LinRange(0, 2π, 100)
> field = \[exp(r) + cos(θ) for r in rs, θ in θs\];
> 
> fig = Figure()
> po = PolarAxis(fig\[1, 1\])
> surface!(po.scene, rs, θs, field; shading = false, colormap = :RdBu)
> autolimits!(po)
> 
> record(fig, "test.mp4", LinRange(0, 2\*pi, 600); framerate=60) do i
> po.θ\_0\[\] = i
> end
> \`\`\`
> 
> https://user-images.githubusercontent.com/32143268/171508924-0059d611-1d75-4709-9895-60095c5c4ef1.mp4
> 
> \## To-dos
> 
> \- \[X\] Angular tick labels
> \- \[x\] Radial tick labels
> \- \[x\] Tighter bounding box around the plot
> \- \[x\] Figure out how to enable \`plot!(po, ...)\`
> \- \[x\] Fix stack overflow error when \`MakieLayout.can\_be\_current\_axis(::PolarAxis) = true\`
> \- \[\] Better update semantics, hook up zooming
> \- \[x\] Poly drawn around the plot to simulate clipping
> 
> \## Type of change
> 
> Delete options that do not apply:
> 
> \- \[X\] New feature (non-breaking change which adds functionality)
> 
> 
> \## Checklist
> 
> \- \[x\] Added an entry in NEWS.md (for new features and breaking changes)
> \- \[\] Added or changed relevant sections in the documentation
> \- \[\] Added unit tests for new algorithms, conversion methods, etc.
> \- \[\] Added reference image tests for new plotting functions, recipes, visual options, etc.
> 
> Fixes #309, #521

---

<div class="post-metadata">

**Author:** ![joa-quim](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joa-quim/32/227_2.png) [@joa-quim](https://discourse.julialang.org/u/joa-quim)\
**Post date:** [October 24, 2022, 3:03pm UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/9 "2022-10-24T15:03:21Z")

</div>

FYI, I’ve added a radar function to GMT.jl too (master)

```julia
using GMT
radar([10.5 20.5 30.6 40.9 46], axeslimts=[15, 25, 50, 90, 50],
      labels=["Spoons","Forks","Knifes","Dishes","Oranges"],
      annotall=true, marker=:circ, fill=true, show=true)

```

 ![GMTjl_tmp](https://global.discourse-cdn.com/julialang/original/3X/9/2/92f5068c5670fbe98e2c9a92ff236b41ebe41ec6.png)

---

<div class="post-metadata">

**Author:** ![davide](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/davide/32/52136_2.png) [@davide](https://discourse.julialang.org/u/davide)\
**Post date:** [October 25, 2022, 7:45am UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/10 "2022-10-25T07:45:17Z")

</div>

Thanks @jules, I will look at it.  
For the moment a quick (and improper) fix I am using is to set label size proportional to data and resolution: it is quite insensitive to data and resolution scaling but it works only when there is just one axis/panel:

```julia
labelsize = maximum(v) * 40 / minimum(to_value(ax.scene.parent.camera.resolution))

```

where `v` is the vector of values to plot.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [October 13, 2024, 4:45am UTC](https://discourse.julialang.org/t/radar-plot-in-plots-jl-or-makie-jl/88576/11 "2024-10-13T04:45:10Z")

</div>

Now that [Continuation of polar axis by ffreyer · Pull Request #2990 · MakieOrg/Makie.jl · GitHub](https://github.com/MakieOrg/Makie.jl/pull/2990) has been merged, it probably wouldn’t be too hard to create a Radar plot type.
