Polar Visualizations

At the moment, all our plots have used the typical Cartesian coordinates. In this tutorial, we explore data visualizations that use the polar coordinate system.

1. Pizza Plot

Let us start with the very common pizza plot (pie chart). To construct such plot, we must map a data column to angles. Moreover, we need to compute the cumulative angle, in order to rotate the slices by the right amount. Note that this plot does not have any coordinate axes, as we don't use x and y when specifying the pizza.

The quick way of producing this plot is by using the preset mark Pizza:

using Vizagrams
using DataFrames
using Random

Random.seed!(4)
df = DataFrame(x=[1, 2, 3, 5, 1, 2], y=[10, 10, 20, 10, 20, 30],
    c=["a", "b", "a", "a", "b", "a"],d=rand([0,1],6),e=rand([1,2,3],6),
    k=["key1","key2","key3","key4","key5","key6"]
)

plt = Plot(
    figsize=(300,250),
    data=df,
    encodings=(
        color=(field=:k,datatype=:n),
        angle = (field = :x, datatype=:q, scale_domain=(0,sum(df.x)), scale_range=(0,2π)),
        r = (field=:y, scale_range=(80,120))
    ),
    graphic = Pizza()
)

draw(plt)
kkey1key2key3key4key5key6

Note that we must adjust the scale for the angle so that the domain goes from 0 to the sum of values of x and ranging from 0 to 2π. We must also adjust the scale for the radius variable r, so that our pizza is large enough compared to the figsize.

We can replicate the pizza plot using Slice marks instead. This makes the construction process more explicit and allows us to more easily create new types of visualizations.

plt = Plot(
    figsize=(300,250),
    data=df,
    encodings=(
        color=(field=:k,datatype=:n),
        angle = (field = :x, datatype=:q, scale_domain=(0,sum(df.x)), scale_range=(0,2π)),
        r = (field=:y, scale_range=(80,120)),
    ),
    graphic = data -> begin
        acc = cumsum(vcat(0,getscale(plt,:angle)(df.x)[begin:end-1]))
        data = hconcat(data,acc=acc)
        ∑() do row
            S(:fill=>row.color)Slice(rmajor=row.r,ang=row.angle,θ=row.acc,rminor=20)
        end(data)
    end
)

draw(plt)
kkey1key2key3key4key5key6

Let us explain the graphic expression:

graphic = data -> begin
    acc = cumsum(vcat(0,getscale(plt,:angle)(df.x)[begin:end-1]))
    acc = StructArray(acc=acc)
    data = hconcat(data,acc)
    ∑() do row
        S(:fill=>row.color)Slice(rmajor=row.r,ang=row.angle,θ=row.acc,rminor=20)
    end(data)
end

The main hurdle to specify the pizza plot is to compute the cumulative angle. This must be done over the whole dataset, before we draw each slice. To do this, we simply start our graphic expression with data -> .... Then, we compute the cumulative angle, and append it to the data. We then can simply loop over each row drawing the slices.

The advantage of such approach is that we can now easily add labels. We add a new variable text to our specification, compute the the x and y positions for the labels, and add the TextMark.

plt = Plot(
    figsize=(300,250),
    data=df,
    encodings=(
        color=(field=:k,datatype=:n),
        angle = (field = :x, datatype=:q, scale_domain=(0,sum(df.x)), scale_range=(0,2π)),
        r = (field=:y, scale_range=(80,120)),
        text = (field=:y, scale=IdScale()),
    ),
    graphic = data -> begin
        acc = cumsum(vcat(0,getscale(plt,:angle)(df.x)[begin:end-1]))
        data = hconcat(data,acc=acc)
        ∑() do row
            r = row.r + 15
            x = r*cos(row.acc-π/2+row.angle/2)
            y = r*sin(row.acc-π/2+row.angle/2)
            S(:fill=>row.color,:stroke=>:white)Slice(rmajor=row.r,ang=row.angle,θ=row.acc,rminor=20) +
            S(:fill=>row.color)T(x,y)TextMark(text=row.text,fontsize=8)
        end(data)
    end
)

draw(plt)
kkey1key2key3key4key5key6302010201010

2. Radar Plot

Our next examples are radar plots. These are distinct from pizza plots in the sense that they actually use polar coordinates. In order to user polar coordinates in a graphic specification, we must simply pass coordinate = :polar through config. While the Cartesian coordinates require the x and y encoding variables, the polar coordinate requires the radius variable r and the angle. Vizagrams then computes the x and y values automatically, which can be used in graphic expressions.

Let us first do a simple example.

plt = Plot(
    figsize=(300,300),
    data=df,
    config=(coordinate=:polar,),
    encodings=(
        r = (
            field=:y,
            datatype=:q,
            scale_domain =(0,maximum(df[!,:y])), scale_range=(0,150)),
        angle = (
            field=:k,
            datatype=:n,
            scale_range=collect(range(0,2π,length=length(unique(df.k))+1))[begin:end-1]
            ),
    ),
    graphic = Polygon() + S(:fill=>:steelblue,:opacity=>1)Circle(r=10)
);
draw(plt)
Example block output

Using the Polygon mark with the Circle mark we can produce the radar plot. Other marks could also be used in order to produce other types of visualizations. In the next example, we change the radius scale range so that the frame resembles a donut. Note that we also use guide=(a_tick_flag=:in,) within the config specification in order place the ticks to be in the inner radius.

plt = Plot(
    figsize=(300,300),
    data=df,
    config=(
        guide=(a_tick_flag=:in,),
        coordinate=:polar,),
    encodings=(
        r = (
            field=:y,
            datatype=:q,
            scale_domain =(0,maximum(df[!,:y])+10), scale_range=(70,150)),
        angle = (
            field=:k,
            datatype=:n,
            scale_range=collect(range(0,2π,length=length(unique(df.k))+1))[begin:end-1]
            ),
        color=(field=:d,datatype=:n),
        size=(field=:e,datatype=:q),
    ),
    graphic = Line() + S(:opacity=>1)Circle()
);
draw(plt)
Example block output