Scaling viz plots in GeoStats

I am making chloropleth maps of different metropolitan areas in the UK.
Here are two examples:

Now, Liverpool and London are very different in geographic size. Is there an easy way I can make these plots conform to the same (arbitrary) plot scale so that they are shown at the correct relative sizes? The geometry is specified on the same grid.

As you don’t show ticks anyway, an easy option would be to center the data of both by subtracting the mean, and then linking both axes

Thanks for sharing these user questions @TimG , they are helping us improve the user experience.

@jules suggestion to link the axes may be all you need. Makie.jl should scale things automatically if it knows that the x and y axis of both subplots mean the same thing.

Alternatively, you can try to play with the zoom, fixing some option to a common value in both plots.

Thanks for this suggestion. I don’t immediately know how to do either of the things you suggest ( :roll_eyes:) but it can be something to look forward to after Easter!

1 Like

@TimG you can call Makie.linkaxes!(ax1, ax2) to link axes ax1 and ax2 on the same figure. These axes are generated with the corresponding viz commands.

If that doesn’t solve the issue, or if you need to fix the scale in separate figures, then try to use some of the zoom options that Makie.jl provides. I believe that it is there somewhere in the docs. Couldn’t find it easily.

1 Like

For no particular reason, I wanted to keep the geometry data without transformation. Here, for the record, is what I did:

First, I found the boundingbox for each met area and find the min and max x, y values

function bbminmax(gt)
    bb = boundingbox(gt.geometry)
    minx, miny = coordinates(bb.min)[1:2]
    maxx, maxy = coordinates(bb.max)[1:2]
    return (minx, miny, maxx, maxy)

I could then find the biggest x-axis range and, separately, the biggest y-axis range of any of the met areas and position these symmetrically around the mean x, y for each map (I used a dataframe row for these parameters for each met area). I allowed 5% whitespace around the biggest dimension:

        for met in eachrow(df)
            met.minx, met.miny, met.maxx, met.maxy = bbminmax(gt |> Filter(row -> !ismissing(row.newLocalAuthority) && row.newLocalAuthority in met.boroughs))
        df.xrange = df.maxx - df.minx
        df.yrange = df.maxy - df.miny
        xaxisrange = maximum(df.xrange) * 1.05
        yaxisrange = maximum(df.yrange) * 1.05
        df.xaxismin .= (df.minx .+ df.maxx .- xaxisrange) ./ 2.0
        df.xaxismax .= (df.minx .+ df.maxx .+ xaxisrange) ./ 2.0
        df.yaxismin .= (df.miny .+ df.maxy .- yaxisrange) ./ 2.0
        df.yaxismax .= (df.miny .+ df.maxy .+ yaxisrange) ./ 2.0

Then, in the viz! I was able to use axis limits for each plot:

function citymap(gt, df)
    fig = Mke.Figure()
    ax = []
    for (i, met) in enumerate(eachrow(df))
        longt = gt |> Filter(row -> !ismissing(row.newLocalAuthority) && row.newLocalAuthority in met.boroughs)
        col = log10.(longt.rat) # Can't currently have consistent colour scale across different plots
        push!(ax, Mke.Axis(fig[div(i - 1, 2)+1, mod(i - 1, 2)+1], title=met.metname, titlesize=10.0f0, limits=(met.xaxismin, met.xaxismax, met.yaxismin, met.yaxismax),
                    xlabel="BNG Easting",  xlabelsize=8.0f0, xtickformat="{:.0f}", xticklabelsize=6.0f0,
                    ylabel="BNG Northing", ylabelsize=8.0f0, ytickformat="{:.0f}", yticklabelsize=6.0f0))
        viz!(ax[i], longt.geometry, color=col, colorscheme=:hawaii, colorrange=extrema(col), facetcolor="black", showfacets=true)
        ax[i].aspect = Mke.DataAspect()
    Mke.Colorbar(fig[1:2, 3], limits=extrema(gt.rat), colormap="hawaii", scale=log10, tickformat="{:.1f}", ticklabelsize=8.0f0,
            label="Multiple of national average funding per capita", labelsize=10.0f0)
    #    This common colorbar is not consistent with charts!

This produces this set of maps:

This is great, and almost perfect!
Major: The colour scale is not consistent between maps. The colours in each map are dependent on the map data. The single colour bar is therefore a lie! (but this is my lie, not GeoStat’s)
Minor: I’d like to be able to have slightly thinner boundary lines. I can change the colour but not the weight of these lines, I think.

1 Like

That is great @TimG ! So your solution consisted of using the min, max = coordinates.(extrema(boundingbox(geotable.geometry))) to fix the axis limits in all subplots. That works too!

Regarding the colorbar and scale, we are already working on a fix based on your feedback. We created a new package GitHub - JuliaGraphics/Colorfy.jl: Colorfy Julia objects that converts any vector of values to colors from Colors.jl. This package will be registered in the following days, and we will refactor viz and the viewer with a new approach to colormaps and colorbars that is more flexible.

1 Like

Regarding the thickness of the boundaries, did you try the segmentsize option in viz?

1 Like

I have now, and it works. Thanks!

My viz! now looks like this:

        viz!(ax[i], longt.geometry, color=col, colorscheme=:hawaii, colorrange=extrema(col), facetcolor="black", showfacets=true, segmentsize=0.3f0)

And the docs say:

segmentsize - size (or width) of segments
showfacets - enable visualization of facets
facetcolor - color of facets (e.g. edges)

My unfamiliarity with the nomenclature here meant I did not understand that segmentsize meant the line width of boundaries of facets! Presumably, segments and facets aren’t necessarily the same things?

Facets are geometries with dimension - 1. In this case, polygons have dimension 2, and the facets are segments with dimension 2 - 1 = 1.

Yes. This is algorithmically very simple. My biggest challenge was to figure out how to access the data points. Having found bb = boundingbox(gt.geometry) easily, it took me more time that I’d like to admit to figure out minx, miny = coordinates(bb.min)[1:2].
Similarly, when I initially tried to find the x, y extrema manually, it took me a long while to get to this:

rx = extrema([coordinates(i)[1] for i in vertices(gt.geometry[borough])])
ry = extrema([coordinates(i)[2] for i in vertices(gt.geometry[borough])])

Inefficient, I know, (boundingbox() is considerably faster) but it worked!

Simple usage examples for stuff like this are very helpful in making the learning curve less steep!

Appreciate if you can recommend these examples somewhere in our docs. Maybe we should add a tips and ticks section in the docs with random examples.

Happy to have a go at this if you tell me where/how.
Remember, I’m still learning!

1 Like

That is a nice contribution! Are you familiar with Documenter.jl in Julia?

You can create a file here similar to our, and then list it in the docs/make.jl file. We can take it from there. :slight_smile:

For the common colorbar, you can always set the colorranges of the plots afterwards :slight_smile:

import Makie
function unify_colorranges!(plots...)
    cranges = Makie.to_value.(getproperty.(plots, :colorrange))
    setproperty!.(plots, :colorrange, ((minimum(first.(cranges)), maximum(last.(cranges))),))

then call unify_colorranges! on the result of all your viz! calls. You can accumulate those the same way you accumulated the axes in ax.

This sounds like a great option.
I want my colorrange to be calculated nationally, so I don’t need to look-up what the range is on each map, I can just overwrite it.

So I accumulate my viz! calls as:

vis = []
push!(vis, viz!(ax[i], longt.geometry, color=col, colorscheme=:hawaii, #=colorrange=extrema(col),=# facetcolor="black", showfacets=true, segmentsize=0.3f0))

I can then simply set

setproperty!.(vis, :colorrange, (extrema(log10.(gt.rat)),))

But now, how do I get the updated viz! maps into the fig?

Because just doing the above and then


results in a plot where each of the maps still has the original colours.

All completely new to me, I’m afraid.

I have something for you to look at and judge suitability. If hopeless, that’s fine. If not, maybe I can extend. But I need to share first. In GitHub it says I need to fork before I can propose a change. Is that the right thing to do…? :man_shrugging:

That is lovely. Please feel free to proceed. You fork the repo, edit the files and then submit a PR. See the contribution page for more info:

I have now found an easy fix for this.

1 Like