[BUG]: findEmpties throws "iterated with no new neighbors" for large NaN regions in contour (double-precision underflow)
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 2/5
- Geschätzter Aufwand
- 1-3 Stunden
- Anfängerfreundlichkeit
- 86/100
- Issue-Typ
- Bug
- Klarheit
- Klar beschrieben
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- javascript
- Bereich
- data-visualization
Rechercherichtung
Beginne in src/traces/heatmap/find_empties.js und konzentriere dich auf die Flood-Fill-Gewichtsberechnung und den im Issue beschriebenen Guard für den Fall ohne Nachbarn. Reproduziere den Fehler mit dem bereitgestellten 5 x 251-Streifen und füge dann neben den bestehenden Fällen in test/jasmine/tests/heatmap_test.js entsprechende Tests hinzu. Als abgeschlossen gilt die Arbeit, wenn verbundene große leere Regionen vollständig verarbeitet werden, ohne eine Exception auszulösen, während das bestehende Interpolationsverhalten weiterhin abgedeckt ist.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Description
findEmpties() (used by contour, heatmap with connectgaps, surface with connectgaps, and
contourcarpet) throws
findEmpties iterated with no new neighbors
for large empty (NaN) regions, even though every empty cell is connected to real data. The cause
is a double-precision underflow of the internal "fractional neighbour count" weight, which the code
then misreads as "this cell has no neighbours".
Inside the flood-fill loop the weight is recomputed once per layer as
neighborCount = ((neighborHash[[i - 1, j]] || blank)[2] +
(neighborHash[[i + 1, j]] || blank)[2] +
(neighborHash[[i, j - 1]] || blank)[2] +
(neighborHash[[i, j + 1]] || blank)[2]) / 20; // src/traces/heatmap/find_empties.js, L70-L73
if(neighborCount) { // L75: 0 is treated as "no neighbour"
newNeighborHash[thisPt] = [i, j, neighborCount];
noNeighborList.splice(p, 1);
foundNewNeighbors = true;
}
so the weight decays geometrically. With n already-filled neighbours per layer, after k layers
w ≈ w0 * (n / 20)^k. Once w falls below Number.MIN_VALUE (≈ 4.94e-324, the smallest positive
double) it becomes exactly 0, the if(neighborCount) guard fails, no cell is marked as filled in
that round, foundNewNeighbors stays false, and the function throws.
The number of layers until underflow is 324 / log10(20 / n):
| already-filled neighbours per layer | layers until the weight becomes 0 | typical geometry |
|---|---|---|
| 1 | 249 | a 1-cell-wide propagation channel |
| 2 | 324 | diagonal / corner of a 2-D front |
| 4 | 463 | interior of a 2-D island |
Numeric evidence: 20 ** -248 === 1.9763e-323 (still representable) but 20 ** -249 === 0
(exactly zero, verified in Node/Chrome).
So the real limit is the depth of contiguous empty cells along the propagation direction
(roughly 250-330), not the amount of missing data and not connectivity. Note also that a lot of
missing data is fine as long as it is shallow:
- a
5 x 251grid whose column 0 is data and columns 1-250 are NaN (only 1250 empty cells) throws; - a
200 x 200grid of data with a100 x 100NaN hole (10000 empty cells) renders fine.
Screenshots/Video
Console output for the reproducer below (plotly.js 3.7.0, unminified):
Uncaught findEmpties iterated with no new neighbors
Note that the exception is thrown synchronously out of Plotly.newPlot(), so it is not
delivered as a promise rejection and cannot be handled with .catch().
Steps to reproduce
Minimal, no browser required (the function body is copied verbatim from
src/traces/heatmap/find_empties.js; run with node repro.js):
// NOTE: plotly passes z through clean2dArray, whose cleanZvalue() maps any non-numeric
// value to undefined (`if(!isNumeric(v)) return undefined;`), so NaN reaches findEmpties
// as undefined - that is what "empty" means here.
var maxRowLength = function(z) { var m = 0; for (var k = 0; k < z.length; k++) if (z[k].length > m) m = z[k].length; return m; };
function findEmpties(z) { /* exact copy of src/traces/heatmap/find_empties.js, see file above */ }
// 5 rows, column 0 is data, columns 1..250 are empty -> THROWS
function strip(rows, nanCount) {
var z = [], r, i, j;
for(i = 0; i < rows; i++) { r = [1]; for(j = 0; j < nanCount; j++) r.push(undefined); z.push(r); }
return z;
}
findEmpties(strip(5, 250)); // Uncaught findEmpties iterated with no new neighbors
findEmpties(strip(5, 249)); // fine - one column less
End-to-end in the browser / CodePen (also verified through Plotly.newPlot with jsdom against the
real 3.7.0 bundle - the same exception is thrown synchronously):
<script src="https://cdn.plot.ly/plotly-3.7.0.js"></script>
<div id="plot" style="width:900px;height:400px"></div>
<script>
var z = [], i, j, row;
for(i = 0; i < 5; i++) { row = [1]; for(j = 0; j < 250; j++) row.push(NaN); z.push(row); }
// throws synchronously; try/catch is required, .catch() on the promise is not enough
try {
Plotly.newPlot('plot', [{ z: z, type: 'contour' }], {});
} catch(e) {
console.log(e); // findEmpties iterated with no new neighbors
}
</script>
CodePen: https://codepen.io/.../pen/... (see the attached example; button "Case 1" fails in < 1 s,
"Case 2" - a 325 x 325 grid whose only data are the centre 2 x 2 cells - fails after ~1 minute).
Suggested fix
The weight is only an ordering heuristic (see the file's own comment: "this is to give us an order
of points to evaluate for interpolation"), so it must not affect control flow. Testing the sum
instead of the quotient, and clamping the underflowed quotient to Number.MIN_VALUE, is enough:
--- a/src/traces/heatmap/find_empties.js
+++ b/src/traces/heatmap/find_empties.js
@@
- var neighborCount;
+ var neighborCount;
+ var neighborSum;
@@
- neighborCount = ((neighborHash[[i - 1, j]] || blank)[2] +
+ neighborSum = (neighborHash[[i - 1, j]] || blank)[2] +
(neighborHash[[i + 1, j]] || blank)[2] +
(neighborHash[[i, j - 1]] || blank)[2] +
- (neighborHash[[i, j + 1]] || blank)[2]) / 20;
+ (neighborHash[[i, j + 1]] || blank)[2];
+
+ neighborCount = neighborSum / 20;
- if(neighborCount) {
+ if(neighborSum) {
+ // neighborSum / 20 can underflow to 0 (once neighborSum <= ~1e-323),
+ // but this cell does have neighbours: keep a strictly positive weight
+ if(!neighborCount) neighborCount = Number.MIN_VALUE;
newNeighborHash[thisPt] = [i, j, neighborCount];
noNeighborList.splice(p, 1);
foundNewNeighbors = true;
Measured with this patch applied to the shipping implementation:
| input | unpatched | patched |
|---|---|---|
5 x 251 strip (1250 empty) |
throws | fills all 1250, ~0.6 s |
5 x 250 strip (1245 empty) |
fills 1245 | fills 1245 (unchanged) |
324 x 324 centre island |
fills 104972 | fills 104972 (unchanged) |
325 x 325 centre island |
throws after ~60 s | fills all 105621 |
512 x 512 centre island |
throws after ~134 s | fills all 262140 |
200 x 200 with a 60 x 60 hole |
fills 3600 | fills 3600 (unchanged) |
The returned list is still sorted by descending weight (verified monotonic non-increasing), and in
the 325 x 325 case only a single cell ends up clamped to Number.MIN_VALUE, so the interpolation
order is not distorted. A regression test could be added next to the existing heatmap tests in
test/jasmine/tests/heatmap_test.js.
Two further suggestions, happy to open PRs for either:
- Replace the weighted flood fill with a plain BFS over layer indices (
w = 1 / (1 + layer)).
This removes the underflow at the source and fixes the complexity: the current loop rescans
every remaining empty cell on every round and uses string-keyed hash lookups plus
Array#splicein the middle of a large array, so an input that fails can take minutes of
wasted work first (measured ~60 s for a 325 x 325 grid and ~134 s for 512 x 512 on a desktop CPU). throw 'findEmpties iterated with no new neighbors'throws a bare string, so it cannot be
identified withinstanceof Error. If a hard failure is really the intent (e.g. all-NaNz),
please throw anErrorwith a message that points at the missing data instead.
Notes
- Affected traces:
contour(findEmpties is called unconditionally),heatmapwith
connectgaps: true,surface(3D) withconnectgaps: true, andcontourcarpet. - There is no configuration that avoids this;
zsmooth,connectgaps: falseetc. only change
whether the code path is reached, not whether it can throw. - Every empty cell in all reproducers above is 4-connected to real data, so the error message
("iterated with no new neighbors") is misleading - it suggests disconnected data where there is none. findEmptiesonly seesundefinedcells becauseclean2dArrayconverts NaN toundefined
first; this is why a repro that calls the function directly with aFloat64Arrayfilled with
NaNsilently returns[].
Examples
CodePen Link:
https://codepen.io/Czhaowu/pen/myWBYEW
- Vorherrschende Sprache
- JavaScript
- Sterne
- 18.4k
- Forks
- 2k
- Ø Merge
- 2 T. 7 Std.
- Gemergte PRs (30 T.)
- 18
Entwicklungsumgebung
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus plotly/plotly.js
-
[BUG]: `hoverlabel.align` is ignored in `hovermode: "x unified"`Evtl. vergeben @MannXo hat das vor 1 Tag übernommen. Offenbug good first issue P2 size: 1
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
plotly/plotly.js#8100 · 3 Kommentare · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
chore P3 plotly-internal size: 3 task
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 77/100
plotly/plotly.js#8064 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
chore P1 plotly-internal size: 1 task
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 82/100
Maintainer antworten meist innerhalb von 1 Tag
-
chore P3 plotly-internal size: 1 task
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 65/100
Maintainer antworten meist innerhalb von 1 Tag
-
bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 65/100
plotly/plotly.js#7648 · 3 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in plotly/plotly.js
Ähnliche Issues
-
[quality] useFocusTrap's Shift+Tab wrap and non-Tab/non-Escape key arms are never driven end to endEvtl. vergeben @hivecommons-hive hat das heute übernommen. Offenagent/quality hive/covered-by-pr hive/hosted-available-lke648397-260827-5n31 quality testing
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 85/100
Maintainer antworten meist innerhalb von 1 Tag
-
[aw] Upgrade availableOffenagentic-workflows
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 85/100
githubnext/gh-aw-workshop#4220 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 92/100
JuliusBrussee/caveman#1189 ·
Maintainer antworten meist innerhalb von 1 Tag
-
priority:low ready-for-dev
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
OpenHands/extensions#738 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
invoiceninja/invoiceninja#13320 ·
Maintainer antworten meist innerhalb von 1 Tag