Creating a poster using Karmana

using Karmana, Rasters, CairoMakie, GeoInterface

Getting the data

We'll get some real-world data to plot, since this isn't as interesting otherwise.

Raster data

Let's get some raster data to plot, in this case the precipitation data from the WorldClim project.

worldclim_bioclim_raster = Raster(WorldClim{BioClim}, :BIO13)

This looks like:

f, a, p = heatmap(worldclim_bioclim_raster; axis = (; aspect = DataAspect()))
a.title = "Precipitation in wettest month"
cb = Colorbar(f[1, 2], p; label = "Precip. (mm)")
f

That doesn't tell us much - everything is blue! Let's increase the contrast, by setting a different color range:

crange = Makie.PlotUtils.zscale(p[3][], 5000; contrast = 0.1)

This is a pretty useful function which you can use when your dataset has a lot of outliers!

p.colorrange[] = crange
f

Now that makes a lot more sense. We can see how areas in South America, East Africa, and South and Southeast Asia are all pretty rainy.

To plot the heatmap, we'll want to crop the BioClim raster to the extent of India. Here's how we can do that:

india_border = Karmana.merge_polys(Karmana.state_df[].geometry)
masked_india_raster = Rasters.mask(worldclim_bioclim_raster[:, :, 1], with = india_border)[GeoInterface.extent(india_border)]
heatmap(masked_india_raster; colorrange = crange, axis = (; aspect = DataAspect()))

Note how the raster is now cropped to the extent of India.

State-level data

Since this is not running with access to CPHS...we'll just make something up!

fake_population = rand(size(Karmana.state_df[], 1))
state_ids = Karmana.state_df[].st_cen_cd
36-element Vector{Int64}:
 28
  9
 24
 27
 15
  8
 32
 23
  5
  6
  ⋮
 25
 16
 13
 11
 17
 34
 30
 35
 31

Creating the poster

We start by creating a page; we want this poster to be a3 sized and have 2 axes.

page = create_page(
    :a3,
    "https://xkdr.github.io/Karmana.jl/dev", # the QR code,
    naxes = 2,
    landscape = true
)
page.figure

While you can provide the title and the description as keywords to create_page, we'll instead set them manually, to show how it can be done.

Plotting to the figure

But first, let's plot!

precipitation_plot = heatmap!(page.axes[1, 1], masked_india_raster; colorrange = crange)
india_outline_plot = indiaoutline!(page.axes[1, 2], :State, state_ids, fake_population; colormap = :Oranges)

page.figure

The poster is optimized for printing, so there's a lot of whitespace (1 inch) at the border.

Configuring figure attributes

In many other plotting packages, if you want to configure a figure, you have to do it at the time you create it. This is not the case in Makie - you can configure the figure's attributes at any time.

This means we don't have to worry about the initial settings too much, and we can configure the figure as we go.

Let's configure the title now! The title is a Label object, which we can access through page.supertitle:

page.supertitle
Label()

From there, it's a standard label, so we can set its attributes:

page.supertitle.text = "Does precipitation impact population?"
page.figure

The description is also a label, accessible via page.description_label:

page.description_label.text = "This answers all of your burning questions - by raining on them!"
page.figure

The description label is in the bottom row, which contains a layout called page.description_layout:

page.description_layout
GridLayout[1:1, 1:3] with 2 children
 ┣━ [1, 3] Box
 ┗━ [1, 1] Label

Finally, we can change the titles of the axes, and similarly any other axis attribute:

page.axes[1, 1].title = "Precipitation"
page.axes[1, 2].title = "Population (fake)"
page.figure

Creating a colorbar

Let's also add a colorbar, to represent the color scale of the precipitation plot:

cb = Colorbar(page.description_layout[1, 2], precipitation_plot; label = "Precip. (mm)", flipaxis = true, tickalign = 1)
page.figure

page.description_label.alignmode[] = Outside()

page.figure

Creating a legend

We can also add a legend, to represent the colors of the states. We'll place this in the right side of the layout cell which currently holds the colorbar.

To create this legend, we'll use PolyElements to represent discrete colors.

leg = Legend(
    page.description_layout[1, 2, Right()], # this is placed as a protrusion in cell `[1, 2]`.
    [ # elements
        PolyElement(; color) for color in cgrad(:Oranges)[0.125:0.25:1]
    ],
    [ # labels
        "0-25%",
        "25-50%",
        "50-75%",
        "75-100%",
    ],
    "Population"; # title
    orientation = :vertical,
    nbanks = 1,
    labelsize = 10,
    framevisible = false,
)
Legend()

This legend is protruding a bit, let's change how it's aligned vertically:

leg.valign = :bottom
page.figure

This made the legend have its bottom point aligned to the row's bottom.

The bottom of the legend is still a little above the colorbar, though - but we can fix that, by changing the padding of the legend:

leg.padding
Observable{Any}((10.0f0, 10.0f0, 8.0f0, 8.0f0))
    0 => (::Makie.var"#1685#1697")(p) @ Makie ~/.julia/packages/Makie/1dJf7/src/makielayout/blocks/legend.jl:37

This is in a (left, right, bottom, top) format, so

leg.padding[] = (leg.padding[][1], leg.padding[][2], 0f0, leg.padding[][4])
page.figure

Moving things around in the layout

You can move objects around the layout very easily. Let's swap the colorbar with the description:

page.description_layout[1, 1] = cb
page.description_layout[1, 2] = page.description_label
page.figure

This is a pretty cool thing, but the figure needs some tuning.

colsize!(page.description_layout, 2, Auto())
page.figure

Let's also spread the elements out evenly. The way to do this is to add another column to the description_layout:

page.description_layout[1, 4] = contents(page.description_layout[1, 3])[1]
page.description_layout[1, 3] = leg
page.figure

Let's also align the description to the bottom of the page,

page.description_label.valign[] = :bottom
page.figure

This isn't the most elegant solution, but you can have a colorbar label on the opposite side to the ticks.

We can do this by putting the colorbar into a new gridlayout, and the label below that:

colorbar_layout = GridLayout(page.description_layout[1, 1])
colorbar_layout[1, 1] = cb
cb.label = ""
cb_label = Label(colorbar_layout[2, 1], "Precip. (mm)", font = :bold)
page.figure

Oops! The label has squashed our colorbar. Let's fix that, and decrease the spacing:

cb_label.tellwidth = false
cb_label.tellheight = true
rowgap!(colorbar_layout, 1, 10)
page.figure

Saving posters

Generally, you will get the best output if you save your poster as a PDF, since text etc. will be vectorized.

You can do this by:

save("poster.pdf", page.figure; pt_per_unit = 1)

Here, the pt_per_unit argument enforces that the paper size must be equal to the figure's resolution. If it is not, then 1 pt is treated as equivalent to 0.75 px, which is the default scaling for the web.

That being said, large images and complex polygons with lots of points can cause the file size to increase quite a bit, up to twenty megabytes for only one IndiaOutline plot.

In order to reduce the file size, you can rasterize the plot (this only works with the CairoMakie backend).

This is done by setting the rasterize attribute of the plot to some number, which controls the pixel density. If the rasterized plot has a pixel size of (n, n), then with plot.rasterize = i, the rasterized image which "replaces" the plot in the final PDF will have a resolution of (n*i, n*i).

india_outline_plot.rasterize = 3

save("poster.pdf", page.figure; pt_per_unit = 1)

This reduces the file size to about 1 MB. You can set this to any stereotypical


This page was generated using Literate.jl.