plot icon indicating copy to clipboard operation
plot copied to clipboard

A transform that uses @mapbox/polylabel to derive “pois”

Open Fil opened this issue 1 year ago • 8 comments

The poi/polylabel transform guarantees that the selected point belongs to the interior of the polygon or multipolygon. For line and point geometries, it defers to the classic centroid.

This PR applies the transform by default, as an almost always superior alternative to Plot.centroid.

For most geometries the change is almost imperceptible. Some geometries get a much better treatment:

  • USA: gets located somewhere near Nebraska — whereas its centroid might be in Canada, and its geoCentroid close to the Canadian border.
  • France: gets located around Paris — whereas its geoCentroid is in Spain, its centroid (in Mercator projection) in the Bay of Biscay.
  • Antarctica, and (to a lesser point) Russia — highly dependent on the projection, but generally better than the centroid, esp. with sphere clipping (and much better than the geoCentroid in most projections).
  • South Africa gets a point to the west of the country, further from Lesotho.
  • Chile might get an inferior treatment, as its poi sometimes gets located very far south where the country widens a little.

For the few specific projections that require clipping to sphere, make sure you use their versions from d3-geo-polygon rather than from d3-geo-projection.

TODO:

  • [ ] I absolutely want to find a better name than "poi", but I don't think "polylabel" is the correct choice because this is not only for labels, and also it's not the raw polylabel (it supports multipolygons, projections, and uses an ellipse rather than a circle).
  • [ ] documentation / API.md

cc: @mourner

demo: https://observablehq.com/@observablehq/poi-polylabel-transform

closes #1587

Fil avatar Jul 05 '24 15:07 Fil

How big is it?

The dependencies are:

  • https://github.com/mapbox/polylabel/blob/master/polylabel.js
  • https://github.com/mourner/tinyqueue/blob/master/index.js

If we copy and embed the code, the total size of grows by 1.8% (from 65715 to 66904 bytes, size of the gziped version of the minified script). There might be ways to reduce this a bit if we embed it, since we might not need all of it (though @mourner's code is usually quite minimalist).

Fil avatar Jul 05 '24 21:07 Fil

@Fil let me know if there's anything I can do on the packages side to make including them as a dependency more viable and reduce the bundle size. Looking at the code, there doesn't seem to be anything superfluous there. Only need to bump tinyqueue to export ESM properly (edit: did so).

mourner avatar Jul 06 '24 08:07 mourner

I was not clear in my remark. The cost of 1200 bytes is for the whole feature, including the two imports and my glue code. There is no difference between copying the source code or importing from the dependency, since it's part of the bundle (only d3 modules are external dependencies). We could cull a few bytes by copying the code and trimming it down to the absolute minimum (e.g. removing the debug log), and maybe there is a way to make Plot.centroid and this transform share more code.

Also, an alternative way of approaching this would be to make it part of d3-geo.

And I'll follow @mourner's suggestion to set the precision to 1 pixel.

Fil avatar Jul 06 '24 12:07 Fil

I also just released polylabel 2.0.1 that saves some bytes, worth upgrading.

mourner avatar Jul 06 '24 12:07 mourner

I think if it’s a bundled dependency it’s okay.

mbostock avatar Jul 06 '24 14:07 mbostock

Do you think peak could be a good name for this transform? (Instead of poi)

I've also considered wrapping this under the Plot.centroid transform, by adding a "derive" or "interpolate" option that would be similar to Plot.raster’s interpolate option. I have mixed feelings as this abuses the "centroid" word a little (not a center of mass anymore), but it could be seen as a generalization. The signature of that function might be (G, X, Y, projection) => void or (G, projection) => [X, Y] or (G, path) => [X, Y]

Also for more generality, the parameter describing the ellipse should be a vector (length = alpha ratio, angle = 0) — but this can be added later if requested (useful for people who would want to add labels at a 45° angle).

Fil avatar Jul 07 '24 10:07 Fil

Mathematicians might call it the Chebyshev center of the polygon, but not quite since we're scaling the polygon first.

Maybe center is the right name for this. Allows to be vague about it as "a suitable center for some applications". Another good name if we want to be less hand-wavy is incenter, as a generalization of the triangle incenter.

Fil avatar Nov 25 '24 12:11 Fil

“Poi” seems like a fine name to me. It’s still the pole of inaccessibility, no? Also, poi is a food, which is fun.

mbostock avatar Jul 04 '25 20:07 mbostock