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 Raster
s 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.