olihawkins/d3-hexjson

Positioning when using odd-r hexjson

Closed this issue ยท 10 comments

I feel a little hesitant asking this, but is d3-hexjson's implementation of hex positioning in line with ODI Leeds's spec?

Per their spec (or, specifically, the documentation on hex-based coordinates that they reference) odd-r hexjson shoves odd rows right, in relation to even rows.

But it looks like d3-hexjson is implementing both even-r and odd-r hexjson as even-r hexjson - i.e. with even rows shoved right.

This example is odd-r, so based on ODI Leeds's spec I think Q0R1 should be where Q1R1 is, and so on:
image

Comparing the outputs that d3-hexjson and the ODI's hex map builder generate when fed with an odd-r hexjson file also shows the discrepancy in implementations.

Hi Philip. This goes back to an issue I ran into when first developing the plugin, which is mentioned in this bit of the readme:

"Note that while the absolute row numbers of each hex are represented internally in the rc property from top to bottom, the r property of the HexJSON data represents row numbers from bottom to top. This follows the row numbering convention used in the example HexJSON implementation provided by the ODI in their hexmap of UK Parliamentary constituencies."

The ODI use row coordinates that go from bottom to top while the documentation they reference at Red Blob Games uses row coordinates that go from top to bottom. The ODI spec does not address this point; it can only be inferred from using their hexmaps. Given that the coordinate system the ODI uses in practice differs from the one set out in the documentation they reference I think the spec is ambiguous.

The plugin was developed to render the hexes using the coordinate system described at Red Blob Games. It was only when I tried to use it to render ODI hexmaps that I ran into this issue. As one of the first uses of the plugin was to render the ODI hexmap of Parliamentary constituencies to show general election results, I switched around the row order to start from the bottom, but I left the row layouts to offset from the top. In retrospect, I think this was probably a mistake but I didn't spot it at the time.

Using the example.hexjson file to test this, I get the inverse row offset pattern using d3-hexjson to the one that I get using the ODI's hexmap builder, but I don't get the same result using odd-r and even-r layouts. In other words, I can get exactly the same result using both d3-hexjson and the ODI hexmap builder if I set the layout to odd-r in one and even-r in the other (and vice versa).

Changing d3-hexjson's behaviour at this point is difficult as it would affect the layout of any hexmaps using it. It would need to be done as a new version with a new major version number to flag breaking changes to the API. That might also be an opportunity to update the plugin to use ES2015 for consistency with D3 version 6. But to be honest I am unlikely to have the time to do this in the near future.

Thanks for the detailed response, Oli - and, yes, I nearly mentioned the fact that the ODI's row numbering goes from bottom to top unlike in the linked Red Blob examples, so I agree the spec is in practice somewhat ambiguous.

I've gone back to my example to check, and in the light of the day today I agree that even-r and odd-r are generating different things, so I misspoke there - sorry.

Given it's a fairly easy thing to work around (even without appreciating the even-r/odd-r switch that fixes it, I did it with two lines of code) I'd agree that it doesn't seem a huge priority to fix. It's a great plugin either way.

I think the difficulty I am facing with this package is that it really needs an overhaul. It was developed almost four years ago for D3 v.4, and while it continues to work, it really needs updating. But if I was going to invest that time in it, I would be very tempted to start again and implement top-down row numbering.

There are lots of reasons to prefer it. First, it would be good to be able to just point at the Red Blob games documentation, which in unambiguous, and say "it does that". Second, in SVG and most other graphics systems vertical coordinates are top-down, so it's easier to reason about the layout if the hex coordinates are top-down too.

If I did that, the package wouldn't really be a hexjson renderer at that point (I'd call it something else). It might even be desirable to decouple it from D3: the code just creates data that D3 can use, but the same data could be used in non-D3 based applications too.

This is just me thinking off the top of my head, but given the continuing popularity of the package, it leaves me with a bit of a dilemma.

I would be in favor of applying the Red Blob games documentation as it is pretty much the "default way" of doing this due to their excellent documentation. But for backwards compatibility, you would have to implement the other numbering as default and apply top down numbering ("strict mode") as a setting that you could choose on initialization. I understand your hesitation.

As for decoupling from D3: I would be for that as well, and it would make things a lot easier:

  • You could have the underlying library do all the hex manipulation, it could have variable numbering schemes (top down/bottom up, start with indentation on the top row or not, etc.) and a given default setting that is Red Blob and SVG compliant
  • You could keep this D3 library and just have the default set to the way things are now, with the additional option of applying other numbering schemes.

This would have the advantage that this library would be a lot smaller, and the other one as well. It would have the disadvantage of now being dependent on a second library that needs to remain in sync. But I think that is a small disadvantage.

Thanks Ronald. These are all interesting ideas. I like the idea of having rendering options that subsume how d3-hexjson and the ODI's hexmapper currently work, with more sensible defaults.

Just ran into this issue using the library (which works brilliantly, by the way, @olihawkins). FYI, I'm using it on a piece of work for ODI Leeds.

I can fix very quickly in my own codebase by switching from using hex.rc to hex.r to work out if I'm on an even or odd row - in that way the hexjson file provides the definitive statement about odd- or even-ness. I think this would be consistent with the intent of the spec that Stuart originally published. I agree that there's some vagueness in the spec given the Red Blob Games examples. I will discuss it with him tomorrow and see if we can't clarify.

I will probably publish an alternative implementation which honours the 'r as entered in the hexjson file' rather than calculated absolute row - this is a fairly small change, but as you rightly state, would potentially invalidate existing users of the codebase. (NB, this only affects hexmaps with an even number of rows between top and bottom.)

For now, there's a version available by including

<script src="https://cdn.jsdelivr.net/gh/opnprd/d3-hexjson@absolute-r-offset/build/d3-hexjson.min.js"></script>

or

<script src="https://cdn.jsdelivr.net/gh/opnprd/d3-hexjson@absolute-r-offset/build/d3-hexjson.js"></script>

This works per the ODI Leeds hexmap implementation.

Thanks Giles, that's really helpful. Sadly I don't have capacity to work on this repo right now. If/when I do, I would seriously consider doing a new major version that supports multiple rendering modes. That seems to be the best way to support old and new behaviour simultaneously. But to be perfectly honest I don't know when it will happen.

Completely understood, @olihawkins.

I've generalised my solution a bit, so that you can optionally specify a rendering mode when calling any of the three main functions, defaulting to what I'm terming 'relative' rendering - i.e. first calculated row (rc) in the hexes presented is row 0. Compared with 'absolute' rendering - i.e. the r in the HexJSON file defines if a row is odd or even. I'll submit a PR for consideration.

FYI, Stuart has also updated our HexJSON spec page to make our assumptions about numbering clear! We couldn't remember why we went for bottom-up rather than top-down numbering, aside from bottom-left being the origin in graphing.

slowe commented

I've just stumbled across this discussion. The HexJSON format was defined back in 2017 because I thought there should be a simple way to share hex layouts - particularly around the 2017 General Election. In defining it I used Red Blob Games as inspiration but also GeoJSON. The y-direction was set to match the y-direction (latitude) in GeoJSON. Only very recently has someone mentioned the difference in direction to me. They've suggested adding an origin key set to one of topLeft, bottomLeft, topRight, bottomRight. That seems fairly sensible although it obviously makes renderers need to be more complicated.

In practice, I think we (Open Innovations, formerly ODI Leeds) are almost the only ones to actually share layouts using HexJSON. The only exception I know of is the old MSOA layout by the House of Commons Library (they haven't provided their 2021 MSOA layout as HexJSON although I have).