Skip to content

Commit 1eff83d

Browse files
committed
fix: ignore non-finite values in scatter/plot auto bounding box
scatter(x, y) and the Tables.jl plot(table, :x, :y) extension both computed boundingbox via extrema(x), which propagates NaN when the data array contains any NaN/Inf. The classic JSXGraph scatter-via- curve idiom feeds [x, x, NaN, ...] arrays, which made the auto-axis path produce an unrenderable boundingbox of (NaN, NaN, NaN, NaN). Filter non-finite values before extrema and fall back to (-1.0, 1.0) when no finite values remain. Tests cover both call sites.
1 parent 7173943 commit 1eff83d

6 files changed

Lines changed: 40 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.5.1] - 2026-05-13
11+
12+
### Fixed
13+
14+
- `scatter(x, y)` and `plot(table, :x, :y)` (Tables.jl extension) no longer produce an invalid `(NaN, NaN, NaN, NaN)` bounding box when the data contains `NaN` or `Inf`. Non-finite values are filtered before computing the auto-padded axis range, with a safe `(-1.0, 1.0)` fallback when no finite values remain. This unblocks the classic JSXGraph scatter-via-curve idiom (`[x, x, NaN, ...]`) when using the auto-axis path.
15+
16+
## [0.5.0] - 2026-05-13
17+
1018
### Added
1119

1220
- Pluto `@bind` integration via `bindable=true` keyword on `board(...)`. The bound state exposes draggable points (2D + 3D) and sliders by name; helpers `points_xy` and `points_xyz` project to parallel coordinate vectors for spline / regression / array workflows. New optional weak dependency on `AbstractPlutoDingetjes` (loaded automatically by Pluto) routes seed coordinates through Pluto's `published_to_js` channel for efficiency; outside Pluto a JSON-inline fallback keeps the feature working with zero new dependencies. Duplicate element names on a bindable board raise `ArgumentError` at construction. Docs include a runnable Pluto example notebook (`docs/notebooks/pluto_bind_demo.jl`) downloadable from the new **Pluto Integration** page; the page can also embed the rendered notebook via `PlutoStaticHTML.jl` when the docs are built with `BUILD_PLUTO_NOTEBOOKS=true`. Fixes #12.

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "JSXGraph"
22
uuid = "97e4c2e4-f0cb-461e-a268-312dec255b76"
3-
version = "0.5.0"
3+
version = "0.5.1"
44
authors = ["JuliaJSXGraph maintainers"]
55

66
[deps]

ext/JSXGraphTablesExt.jl

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,8 @@ function JSXGraph.plot(table, xcol::Symbol, ycol::Symbol;
117117
throw(ArgumentError("Table columns must not be empty"))
118118
end
119119

120-
# Auto-compute axis limits with 10% padding
121-
if xlim === nothing
122-
xmin, xmax = extrema(x)
123-
pad = xmin == xmax ? 1.0 : 0.1 * (xmax - xmin)
124-
xlim = (xmin - pad, xmax + pad)
125-
end
126-
if ylim === nothing
127-
ymin, ymax = extrema(y)
128-
pad = ymin == ymax ? 1.0 : 0.1 * (ymax - ymin)
129-
ylim = (ymin - pad, ymax + pad)
130-
end
120+
xlim = xlim === nothing ? JSXGraph._padded_extrema(x) : xlim
121+
ylim = ylim === nothing ? JSXGraph._padded_extrema(y) : ylim
131122

132123
b = Board(""; xlim=xlim, ylim=ylim)
133124
# Use curve element with data arrays for a connected line plot

src/composition.jl

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ end
134134

135135
# --- Convenience functions (REQ-API-002) ---
136136

137+
# Compute padded (min, max) ignoring non-finite values (NaN, Inf).
138+
# Returns (-1.0, 1.0) when no finite values remain.
139+
function _padded_extrema(v::AbstractVector{<:Real})
140+
finite = filter(isfinite, v)
141+
isempty(finite) && return (-1.0, 1.0)
142+
vmin, vmax = extrema(finite)
143+
pad = vmin == vmax ? 1.0 : 0.1 * (vmax - vmin)
144+
return (vmin - pad, vmax + pad)
145+
end
146+
137147
"""
138148
$(SIGNATURES)
139149
@@ -163,17 +173,8 @@ function scatter(x::AbstractVector{<:Real}, y::AbstractVector{<:Real};
163173
throw(ArgumentError("x and y must not be empty"))
164174
end
165175

166-
# Auto-compute axis limits with 10% padding
167-
if xlim === nothing
168-
xmin, xmax = extrema(x)
169-
pad = xmin == xmax ? 1.0 : 0.1 * (xmax - xmin)
170-
xlim = (xmin - pad, xmax + pad)
171-
end
172-
if ylim === nothing
173-
ymin, ymax = extrema(y)
174-
pad = ymin == ymax ? 1.0 : 0.1 * (ymax - ymin)
175-
ylim = (ymin - pad, ymax + pad)
176-
end
176+
xlim = xlim === nothing ? _padded_extrema(x) : xlim
177+
ylim = ylim === nothing ? _padded_extrema(y) : ylim
177178

178179
board = Board(""; xlim=xlim, ylim=ylim)
179180
for (xi, yi) in zip(x, y)

test/test_composition.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ end
112112
# HTML output contains points
113113
html = html_string(scatter([0, 1], [0, 1]))
114114
@test occursin("create('point'", html)
115+
116+
# NaN values in data must not poison the auto-computed boundingbox
117+
b5 = scatter([1.0, NaN, 3.0], [2.0, NaN, 4.0])
118+
bb5 = b5.options["boundingbox"]
119+
@test all(isfinite, bb5)
120+
121+
# All-NaN falls back to a safe default range (not NaN)
122+
b6 = scatter([NaN, NaN], [NaN, NaN])
123+
bb6 = b6.options["boundingbox"]
124+
@test all(isfinite, bb6)
115125
end
116126

117127
@testset "parametric" begin

test/test_tables.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ using Tables
7474
@test occursin("create('curve'", html)
7575
# Data arrays appear in JS
7676
@test occursin("[1.0,2.0,3.0,4.0]", html)
77+
78+
# NaN-separated data (e.g. JSXGraph scatter trick using a curve)
79+
# must not poison the auto-computed boundingbox.
80+
nan_data = (x=[1.0, 1.0, NaN, 2.0, 2.0, NaN, 3.0, 3.0, NaN],
81+
y=[1.0, 1.0, NaN, 2.0, 2.0, NaN, 3.0, 3.0, NaN])
82+
b6 = plot(nan_data, :x, :y; strokeWidth=3)
83+
@test all(isfinite, b6.options["boundingbox"])
7784
end
7885

7986
@testset "table edge cases" begin

0 commit comments

Comments
 (0)