Basic demo

First, we'll load the packages.

using Karmana
using Makie, CairoMakie # backend for Makie.jl - necessary if you want to save a plot
using Makie.Colors

Before you run this code, make sure that you have read the Makie tutorial at docs.makie.org first!

Creating a page

First, create a page of the appropriate size with xKDR poster formatting using the create_page function. This returns a NamedTuple with a figure, some axes, and a customized layout. You must provide the page size you want to the function, as a Symbol (:a4-:a0 are supported for now).

page = create_page(:a4)
(figure = Scene (595px, 842px):
  2 Plots:
    ├ Image{Tuple{Vector{Float32}, Vector{Float32}, Matrix{ColorTypes.RGBA{Float32}}}}
    └ Image{Tuple{Vector{Float32}, Vector{Float32}, Matrix{Float32}}}
  6 Child Scenes:
    ├ Scene (595px, 842px)
    ├ Scene (595px, 842px)
    ├ Scene (595px, 842px)
    ├ Scene (595px, 842px)
    ├ Scene (595px, 842px)
    └ Scene (595px, 842px), supertitle = Label(), axis_layout = GridLayout[1, 1] (1 children), axes = Union{Nothing, Axis}[Axis (0 plots);;], description_layout = GridLayout[1, 3] (2 children), description_label = Label())

page is a NamedTuple with several elements, you can see the description of each in the documentation for create_page.

To access the actual figure object, use page.figure.

page.figure

By default, the create_page function creates only one axis. It returns all axes as a matrix, so that it's easy and intuitive to refer to them in a grid.

page.axes
1×1 Matrix{Union{Nothing, Axis}}:
 Axis (0 plots)

Plotting using indiaoutline!

The IndiaOutline plot type was designed specifically for CPHS state, HR, or district level data, and automates a lot of the data munging you may have to do otherwise. You can call it in one of the following ways:

  • Provide an admin level Symbol, a vector of IDs for that admin level, and a vector of values for those IDs
  • Provide a DataFrame, its ID column as a symbol, and its value column as a symbol
  • Provide an

What we're doing below is the first option, which is also the most versatile. We're accessing

outline_plt = indiaoutline!(
    page.axes[1, 1],
    :HR,                      # admin level symbol
    [70, 71, 72, 73, 74, 75], # IDs
    rand(6);                  # values
    HR = (strokecolor = to_color(:blue),),
    colormap = :Reds
)
Combined{Karmana.indiaoutline, Tuple{Symbol, Vector{Int64}, Vector{Float64}}}

Now, we display the page again (assuming you're using CairoMakie, graphs do not update interactively)

page.figure

Dynamically updating attributes

You can alter the attributes of an IndiaOutline plot after the fact, using the standard Makie attribute updating syntax. For example, you can change the colormap like this:

outline_plt.plots[2].colormap[] = :Oranges

page.figure

You can also change the attributes of the IndiaOutline plot itself, like this: (note the nested attributes here, if you want to know more, read the docstring).

outline_plt.State.strokewidth[]    = 0.2 #* 2 * 2
outline_plt.HR.strokewidth[]       = 0.2 #* 2 * 2
outline_plt.HR.strokecolor[]       = to_color(:blue)
outline_plt.District.strokewidth[] = 0.1

page.figure

Adding a colorbar

There's also a description, and you might note the space between that and the QR code, which is another layout cell which you can use for a legend, or a colorbar. Here's how you can add a colorbar there (note that it should be horizontal):

cb = Colorbar(
    page.description_layout[1, 2], # this is the empty layout cell into which you can place a colorbar``
    outline_plt.plots[2].plots[1]; # this is the plot - TODO make this easier
    flipaxis = false,              # this keeps the colorbar's axis at the bottom
    vertical = false               # this sets the colorbar's orientation to be horizontal
)

page.figure

this is how to save a page object, if you want to save("my_map.png", page.figure; px_per_unit = 3) save("my_map.pdf", page.figure; pt_per_unit = 1)

Using create_page

Multiple axes default to landscape, but that specific behaviour can be changed on the basis of paper size.

page = create_page(:a4; landscape = true)
page.figure

You can define a set number of axes, and they will be arranged in a grid.

page = create_page(:a4; naxes = 2)
page.figure

Alternatively, you can also define the number of rows and columns.

page = create_page(:a4; naxes = (2, 3))
page.figure

There are multiple options for paper size, ranging from a4 to a0.

page = create_page(:a3)
page.figure

You can set any axis attribute like so:

page = create_page(:a3; landscape = false, naxes = 4)
setproperty!.(page.axes, :aspect, (DataAspect(),))
page.figure

Here's what the page looks like with India plotted on each axis!

indiaoutline!.(page.axes, (:State,), (:all,))
page.figure

The function is integrated well with Makie's themes, and we could make our own for e.g. BQ or BS, following their style.

Any plots on this new page follow the theme with which it was created.

page = with_theme(theme_black()) do
    create_page(:a3)
end
page.figure

Handling raster data

Most raster data in Julia is handled by the Raster type from the Rasters.jl package. Makie.jl supports plotting Rasters efficiently using the heatmap function; however, if you want to display your data in 3D, you can also use surface!.

First, we load Rasters (and GeoInterface, which is a common interface for vector data):

using Rasters, GeoInterface

First, we extract India's bounding box (I could have hardcoded this, but was too lazy).

This code merges all of the state geometries of India (including union territories) and finds their bounding box.

india_bbox = GeoInterface.extent(Karmana.merge_polys(Karmana.state_df[].geometry))
Extent(X = (68.1862489922762, 97.41529266790748), Y = (6.755952899354297, 37.07826805983657))

Now, we create a raster with 30x30 pixels, and fill it with a field of a known function.

First, we create the field:

field = Makie.peaks(30) # this is essentially a convenience function which generates a matrix of data
30×30 Matrix{Float64}:
  6.67128e-5    0.000188386   0.000477409  …  -3.96948e-5   -5.86419e-6
  0.000136151   0.000383396   0.000966167     -9.05965e-5   -1.30519e-5
  0.000252543   0.000712281   0.00179322      -0.000162474  -1.7594e-5
  0.000421863   0.00120079    0.00304432      -0.000194303   6.93014e-6
  0.000620633   0.00180685    0.00467374       7.19879e-6    0.000132566
  0.000759417   0.00232673    0.0062895    …   0.000857423   0.000506944
  0.000634168   0.00226024    0.00681228       0.00303841    0.0013693
 -0.000112254   0.000688868   0.00412076       0.00741665    0.00302545
 -0.00197025   -0.00369952   -0.00497761       0.0147914     0.00576477
 -0.00543002   -0.0122729    -0.0239838        0.0255296     0.00973264
  ⋮                                        ⋱                
 -0.00542621   -0.0134082    -0.0294841        0.0172027     0.0062353
 -0.00247389   -0.0056841    -0.0110764        0.011138      0.00393403
 -0.000810505  -0.0014173    -0.00117495       0.00706689    0.00242112
 -5.12497e-5    0.000417891   0.00272461       0.00434524    0.00144462
  0.000187825   0.000872457   0.00327249   …   0.00254112    0.000823374
  0.000192843   0.000736749   0.00248293       0.00138752    0.000440701
  0.000129633   0.000464649   0.00150027       0.000697613   0.00021836
  7.00109e-5    0.00024357    0.000769592      0.000320123   9.91668e-5
  3.22354e-5    0.000110329   0.000344311      0.000133419   4.10297e-5

Then, we can create the raster, using Rasters.X and Rasters.Y to indicate dimensions.

mydims = (X(LinRange(india_bbox.X..., 30)), Y(LinRange(india_bbox.Y..., 30)))
india_raster = Raster(field, mydims)
30×30 Raster{Float64,2} with dimensions: 
  X Sampled{Float64} LinRange{Float64}(68.1862, 97.4153, 30) ForwardOrdered Regular Points,
  Y Sampled{Float64} LinRange{Float64}(6.75595, 37.0783, 30) ForwardOrdered Regular Points
extent: Extent(X = (68.1862489922762, 97.41529266790748), Y = (6.755952899354297, 37.07826805983657))missingval: missingparent:
           6.75595      7.80155      …  36.0327       37.0783
 68.1862   6.67128e-5   0.000188386     -3.96948e-5   -5.86419e-6
 69.1941   0.000136151  0.000383396     -9.05965e-5   -1.30519e-5
 70.202    0.000252543  0.000712281     -0.000162474  -1.7594e-5
 71.2099   0.000421863  0.00120079      -0.000194303   6.93014e-6
 72.2178   0.000620633  0.00180685   …   7.19879e-6    0.000132566
  ⋮                                  ⋱   ⋮            
 92.3758  -5.12497e-5   0.000417891      0.00434524    0.00144462
 93.3837   0.000187825  0.000872457      0.00254112    0.000823374
 94.3916   0.000192843  0.000736749      0.00138752    0.000440701
 95.3995   0.000129633  0.000464649  …   0.000697613   0.00021836
 96.4074   7.00109e-5   0.00024357       0.000320123   9.91668e-5
 97.4153   3.22354e-5   0.000110329      0.000133419   4.10297e-5

This is the raster. We can plot it using heatmap, surface, or even contour! Any Makie plot type which is SurfaceLike() will work.

First, let's make sure that the raster looks the same as the original data:

fig, ax1, plt1 = surface(field; axis = (title = "Original data", type = Axis3))
ax2, plt2 = surface(fig[1, 2], india_raster; axis = (title = "Raster", type = Axis3))
fig

Hey, these look practically identical - except for the x and y values on the left plot, which are actually the bounding box for India!

Now, let's plot the raster using heatmap:

fig, ax, plt = heatmap(india_raster; axis = (title = "Heatmap", aspect = DataAspect()))

Note the inclusion of aspect = DataAspect() in that heatmap call - this ensures that the pixels of the map reflect physical reality.

indiaoutline!(ax, :State, :all; merge_column = :st_nm, external_merge_column = :st_nm)
fig

This is clearly unrealistic, but otherwise correct!

TODOs

  • Change the raster example here to plot data from BioClim

This page was generated using Literate.jl.