benjann / geoplot

Stata module to draw maps
MIT License
33 stars 3 forks source link

Two minor issues and one bigger #13

Closed Monefield closed 1 year ago

Monefield commented 1 year ago

Hi Ben First of all thanks for this fantastic Stata module! There is so many new and useful functionalities when it comes to using maps in Stata. However, I have encountered a couple of minor issues, when it comes to labels and legends.

1) Label issue When I use the "order()" option to specify the order of the different categories the label dissappears. This happens either if I use the label-option or instead use the variable's value label. As soon as I use the "order()" option the labels are replaced with "Y"s.

2) Legend issue This issue relates to the distance between the categories. If I place the categories in one row and I use 6 (or more) categories the distance between the last two categories gets very small (whether I use the "colgap"-option or not).

3) Embedded polygons The final problem relates to embedded polygons. When I use the spmap module I found this workaround which does work, but somehow it doesn't seem to work with geoplot. Is there some new workaround to make these enclaves (polygons that are completely surrounded by one other polygon) visible?

I have attached a txt-file containing code replicating the first two issues. For replicating the third issue you'll need a shapefile with a completely surrounded polygon. You can download a shapefile of the administrative borders of Denmark here. The problem relates to the "kommune" of Frederiksberg located inside the "kommune" of Copenhagen.

Finally a request: Would it be possible to use x and y coordinates when placing a zoomed in area instead of degrees? I think that would be easier to work with and more precise if I have to allign two or more areas.

Kind regards Morten

Issues.txt

benjann commented 1 year ago

Issue 3: In geoplot the default name for such embedding is called _PLEVEL (for plot level or so). The coding is 0 = regular, 1 = enclave, 2 = exclave, 3 enclave within exclave, 4 = exclave within enclave, etc. You can also use a different name but then you need to declare it using geoframe set in the shape frame (e.g. geograme set plevel _EMBEDDED). Anyhow, it is usually no fun to code such a variable manually; you can do this automatically using geoframe generate plevel. The command will find all embeddings and flag them in variable _PLEVEL, which will then be picked up by geoplot.

After downloading the shape files from the source you provided, I type:

spshape2dta DNK_adm2, saving(DNK)
geoframe create data/DNK
encode NAME_2, gen(name)
geoplot (area DNK i.name if inlist(_ID,10,25)) // -if- selects Frederiksberg and Copenhagen
Graph

Apparently, Frederiksberg is covered by Copenhagen. I now type:

geoframe generate plevel
geoplot (area DNK i.name if inlist(_ID,10,25))
Graph1

Problem solved.

Note that I applied geoframe generate plevel to the whole dataset including all communes and it reports that there are some further nested polygons (possibly some communes have exclaves?). You could inspect _PLEVEL in frame DNK_shp to find out which polygons these are.

Remark: geoframe generate plevel is computationally expensive (not a big issue though in a dataset as small as the one in this example) so that it is usually a good idea to store the data after generating the _PLEVEL variable (it is the shape frame that needs to be stored, i.e. DNK_shp in the example).

benjann commented 1 year ago

Issue 1: geoplot compiles an order() option for the legend which will be overridden if you manually specify legend(order()). In some cases this may actually be useful, but generally it will be difficult to specify order() in a way such that it does what you want. I should probably include a warning about the use of order() in the help file. geoplot does not offer support for detailed reordering of the keys from a single layer (it assumes that the variable has been coded in a meaningful order). However, the there is an option to reverse the order (suboption reverse in legend() or also in label()). In your example, type

geoplot (area regions pop98_cat3, discrete label(1 "< 1 mil" 2 "1-3 mil" 3 "> 3 mil")) ///
, legend(position(6) outside row(1) colgap(5) reverse)

or

geoplot (area regions pop98_cat3, discrete label(1 "< 1 mil" 2 "1-3 mil" 3 "> 3 mil", reverse)) ///
, legend(position(6) outside row(1) colgap(5)) 

And here's the solution using order(), although this is not what I would suggest:

geoplot (area regions pop98_cat3, discrete) ///
, legend(position(6) outside row(1) colgap(5) order(1 "< 1 mil" 2 "1-3 mil" 3 "> 3 mil"))
benjann commented 1 year ago

Issue 2: This seems to be an issue in Stata's legend option. However, it works correctly if you use option horizontal rather than row(1). That is, in your example, type

geoplot (area regions pop98_cat6, discrete color(Reds) lcolor(gray)) ///
, legend(position(6) outside colgap(5) horizontal)

You could also type:

geoplot (area regions pop98_cat6, discrete color(Reds) lcolor(gray)) ///
, legend(position(6) outside colgap(5) row(1) nocolfirst)

I should probably also include a warning in the help about the use of rows() and cols(). geoplot sets legend options such as order(), rows(), cols(), and colfirst depending on the chosen layout (i.e. depending on geoplots legend suboptions layout(), bottom, horizontal, and reverse). Overriding these options manually can have unintended effects.

benjann commented 1 year ago

Finally a request: Would it be possible to use x and y coordinates when placing a zoomed in area instead of degrees? I think that would be easier to work with and more precise if I have to allign two or more areas.

You mean something like

zoom(layers: scale x y)

rather than

zoom(layers: scale offset angle)

where x and y are the absolute coordinates of, say, the midpoint of the bounding box?

Monefield commented 1 year ago

Issue 3: In geoplot the default name for such embedding is called _PLEVEL (for plot level or so). The coding is 0 = regular, 1 = enclave, 2 = exclave, 3 enclave within exclave, 4 = exclave within enclave, etc. You can also use a different name but then you need to declare it using geoframe set in the shape frame (e.g. geograme set plevel _EMBEDDED). Anyhow, it is usually no fun to code such a variable manually; you can do this automatically using geoframe generate plevel. The command will find all embeddings and flag them in variable _PLEVEL, which will then be picked up by geoplot.

After downloading the shape files from the source you provided, I type:

spshape2dta DNK_adm2, saving(DNK)
geoframe create data/DNK
encode NAME_2, gen(name)
geoplot (area DNK i.name if inlist(_ID,10,25)) // -if- selects Frederiksberg and Copenhagen
Graph

Apparently, Frederiksberg is covered by Copenhagen. I now type:

geoframe generate plevel
geoplot (area DNK i.name if inlist(_ID,10,25))
Graph1

Problem solved.

Note that I applied geoframe generate plevel to the whole dataset including all communes and it reports that there are some further nested polygons (possibly some communes have exclaves?). You could inspect _PLEVEL in frame DNK_shp to find out which polygons these are.

Remark: geoframe generate plevel is computationally expensive (not a big issue though in a dataset as small as the one in this example) so that it is usually a good idea to store the data after generating the _PLEVEL variable (it is the shape frame that needs to be stored, i.e. DNK_shp in the example).

Monefield commented 1 year ago

Thank you very much

Issue 3: In geoplot the default name for such embedding is called _PLEVEL (for plot level or so). The coding is 0 = regular, 1 = enclave, 2 = exclave, 3 enclave within exclave, 4 = exclave within enclave, etc. You can also use a different name but then you need to declare it using geoframe set in the shape frame (e.g. geograme set plevel _EMBEDDED). Anyhow, it is usually no fun to code such a variable manually; you can do this automatically using geoframe generate plevel. The command will find all embeddings and flag them in variable _PLEVEL, which will then be picked up by geoplot. After downloading the shape files from the source you provided, I type:

spshape2dta DNK_adm2, saving(DNK)
geoframe create data/DNK
encode NAME_2, gen(name)
geoplot (area DNK i.name if inlist(_ID,10,25)) // -if- selects Frederiksberg and Copenhagen
Graph

Apparently, Frederiksberg is covered by Copenhagen. I now type:

geoframe generate plevel
geoplot (area DNK i.name if inlist(_ID,10,25))
Graph1

Problem solved. Note that I applied geoframe generate plevel to the whole dataset including all communes and it reports that there are some further nested polygons (possibly some communes have exclaves?). You could inspect _PLEVEL in frame DNK_shp to find out which polygons these are. Remark: geoframe generate plevel is computationally expensive (not a big issue though in a dataset as small as the one in this example) so that it is usually a good idea to store the data after generating the _PLEVEL variable (it is the shape frame that needs to be stored, i.e. DNK_shp in the example).

Thank you very much! That solved my problem :)

For some reason I can't get the code "geoplot (area DNK i.name if inlist(_ID,10,25))" to work. I get this error code:

. geoplot (area dktest i.name if inlist(_ID,10,25)) factor-variable and time-series operators not allowed (error in layer 1: area ...)

I get the same error if I try running "geoplot /// (area regions) /// (point capitals i.size [w=pop98], color(Set1, opacity(50)) mlcolor(%0)) /// (label capitals city if pop98>250000, color(black)) /// , legend compass sbar(length(300) units(km))" from your Italy examples.

Monefield commented 1 year ago

I hust found out that I needed to update the geoplot module... everything works now! :) Thank you very much :)

benjann commented 1 year ago

Maybe you need to update geoplot. Support for factor variable (i.varname) has been added in early July. Type

ssc install geoplot, replace

or

net install geoplot, replace from(https://raw.githubusercontent.com/benjann/geoplot/main/)

and try again.

benjann commented 1 year ago

It seems our messages crossed...

Monefield commented 1 year ago

Finally a request: Would it be possible to use x and y coordinates when placing a zoomed in area instead of degrees? I think that would be easier to work with and more precise if I have to allign two or more areas.

You mean something like

zoom(layers: scale x y) rather than

zoom(layers: scale offset angle) where x and y are the absolute coordinates of, say, the midpoint of the bounding box?

Yes, exactly :) It would be even nicer if it were possible to choose wether it was the midpoint or the left or right edge that was the reference. I succeded in making this map, but it would have been easier with the "x y"-posibility :) DK

benjann commented 1 year ago

Note that zoom() now has a position() option that allows you to place the selected objects an an absolute position. Syntax is position(x y [compassdir]) where x and y specify the coordinates of the position and optional compassdir specifies were the object be placed in relation to (x,y), e.g. sw for south-west.