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.ColorsBefore 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.figureBy 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.axes1×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.figureDynamically 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.figureYou 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.figureAdding 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.figurethis 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.figureYou can define a set number of axes, and they will be arranged in a grid.
page = create_page(:a4; naxes = 2)
page.figureAlternatively, you can also define the number of rows and columns.
page = create_page(:a4; naxes = (2, 3))
page.figureThere are multiple options for paper size, ranging from a4 to a0.
page = create_page(:a3)
page.figureYou can set any axis attribute like so:
page = create_page(:a3; landscape = false, naxes = 4)
setproperty!.(page.axes, :aspect, (DataAspect(),))
page.figureHere's what the page looks like with India plotted on each axis!
indiaoutline!.(page.axes, (:State,), (:all,))
page.figureThe 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.figureHandling 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, GeoInterfaceFirst, 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 data30×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-5Then, 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-5This 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))
figHey, 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)
figThis is clearly unrealistic, but otherwise correct!
TODOs
- Change the raster example here to plot data from BioClim
This page was generated using Literate.jl.