Found by the 2026-08 layouts/viz amplification probe. Repros on master @ 42b4f7f7d, numpy 2.2.6 / pandas 2.3.3 / py3.12.
mercator_layout() applies the Mercator formula with no domain validation and writes whatever falls out — -inf, +23810, or NaN — straight into the bound x/y columns.
graphistry/layout/mercator.py:107-111:
107: lat_rad = np.radians(lat_deg)
108: lon_rad = np.radians(lon_deg)
110: x_vals = R * lon_rad
111: y_vals = R * np.log(np.tan(np.pi / 4 + lat_rad / 2))
Mercator is undefined at the poles and outside [-90, 90]; there is no clamp, no range check, and no post-assert (contrast circle.py:362-363, which does assert non-NaN positions).
Repro
import pandas as pd, graphistry
e = pd.DataFrame({'s':[0,1,2], 'd':[1,2,0]})
def run(label, lat, lon):
n = pd.DataFrame({'id':[0,1,2], 'lat':lat, 'lon':lon})
out = (graphistry.edges(e,'s','d').nodes(n,'id')
.bind(point_latitude='lat', point_longitude='lon')
.mercator_layout())
print(label, out._nodes['y'].tolist())
run('poles ', [90.0, -90.0, 0.0], [0.,0.,0.])
run('lat=200 ', [200.0, 0., 0.], [0.,0.,0.])
run('lat=NaN ', [37.7, float('nan'), 51.5], [-122.4, -74.0, -0.1])
Observed
poles [23810.77, -inf, -0.0]
lat=200 [nan, -0.0, -0.0]
lat=NaN [453.71, nan, 671.02]
Expected: a typed decline naming the out-of-domain latitude, or the standard Web-Mercator clamp to ±85.051129°, which is what every mapping library does. Whatever the choice, -inf should never reach a position column.
Note the pole case is also asymmetric: lat=+90 gives a large finite 23810.77 (floating-point tan(π/2) is finite), lat=-90 gives -inf. The two poles are not mirror images, which is a numerical artifact rather than a design decision.
Why inf/NaN in a position column is not benign
Out-of-range latitude is not exotic — it is what you get from a lat/lon column swap, from projected coordinates mistaken for degrees, or from a sentinel value like 999.
Secondary (bare-crash): non-numeric lat/lon dies deep in numpy
n = pd.DataFrame({'id':[0,1,2], 'lat':['37.7','40.7','51.5'], 'lon':['-122.4','-74.0','-0.1']})
graphistry.edges(e,'s','d').nodes(n,'id').bind(point_latitude='lat', point_longitude='lon').mercator_layout()
Observed: TypeError: loop of ufunc does not support argument 0 of type str which has no callable radians method
Expected: a typed error at the binding check (mercator.py:70-71 already validates that the columns exist; it does not validate that they are numeric).
Secondary (dead fallback): the advertised cupy→numpy fallback cannot work
mercator.py:82-87 warns "cuDF DataFrame detected but cupy is not available. Falling back to NumPy (CPU)", then the fallback at :103-104 does g._nodes[lat_col].values — which on a cuDF frame returns a cupy array, and np.radians on a cupy array raises TypeError: Implicit conversion to a NumPy array is not allowed. The documented fallback path is unreachable-as-intended. INDICATIVE — stated from inspection; not executed, since cupy in this environment cannot load libnvrtc.so.12 and the cuDF-without-cupy combination could not be constructed.
(The polars misrouting at mercator.py:73 is filed separately in the layout engine-parity issue.)
Category
silent-wrong (non-finite and NaN positions accepted and emitted, no warning) + bare-crash on non-numeric input.
Found by the 2026-08 layouts/viz amplification probe. Repros on
master@42b4f7f7d, numpy 2.2.6 / pandas 2.3.3 / py3.12.mercator_layout()applies the Mercator formula with no domain validation and writes whatever falls out —-inf,+23810, orNaN— straight into the boundx/ycolumns.graphistry/layout/mercator.py:107-111:Mercator is undefined at the poles and outside
[-90, 90]; there is no clamp, no range check, and no post-assert (contrastcircle.py:362-363, which does assert non-NaN positions).Repro
Observed
Expected: a typed decline naming the out-of-domain latitude, or the standard Web-Mercator clamp to ±85.051129°, which is what every mapping library does. Whatever the choice,
-infshould never reach a position column.Note the pole case is also asymmetric:
lat=+90gives a large finite23810.77(floating-pointtan(π/2)is finite),lat=-90gives-inf. The two poles are not mirror images, which is a numerical artifact rather than a design decision.Why
inf/NaNin a position column is not benign-infinx/ydestroys zoom-to-fit and any bounds computation over the node frame.requests.InvalidJSONErrorout of the remote path.mercator_layout()is a supported way to produce exactly that from data that looks entirely valid.|lat| > 90producingNaNwith only a numpy RuntimeWarning is a silent-wrong: bad input is accepted and turned into an unpositioned node rather than rejected.Out-of-range latitude is not exotic — it is what you get from a lat/lon column swap, from projected coordinates mistaken for degrees, or from a sentinel value like
999.Secondary (bare-crash): non-numeric lat/lon dies deep in numpy
Observed:
TypeError: loop of ufunc does not support argument 0 of type str which has no callable radians methodExpected: a typed error at the binding check (
mercator.py:70-71already validates that the columns exist; it does not validate that they are numeric).Secondary (dead fallback): the advertised cupy→numpy fallback cannot work
mercator.py:82-87warns "cuDF DataFrame detected but cupy is not available. Falling back to NumPy (CPU)", then the fallback at:103-104doesg._nodes[lat_col].values— which on a cuDF frame returns a cupy array, andnp.radianson a cupy array raisesTypeError: Implicit conversion to a NumPy array is not allowed. The documented fallback path is unreachable-as-intended. INDICATIVE — stated from inspection; not executed, sincecupyin this environment cannot loadlibnvrtc.so.12and the cuDF-without-cupy combination could not be constructed.(The polars misrouting at
mercator.py:73is filed separately in the layout engine-parity issue.)Category
silent-wrong (non-finite and NaN positions accepted and emitted, no warning) + bare-crash on non-numeric input.