Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/8162_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Prevent double-precision underflow in `findEmpties` for deep empty (NaN) regions [[#8162](https://github.com/plotly/plotly.js/pull/8162)]
12 changes: 9 additions & 3 deletions src/traces/heatmap/find_empties.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = function findEmpties(z) {
var thisPt;
var p;
var neighborCount;
var neighborSum;
var newNeighborHash;
var foundNewNeighbors;

Expand Down Expand Up @@ -67,12 +68,17 @@ module.exports = function findEmpties(z) {
i = thisPt[0];
j = thisPt[1];

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];

if(neighborSum) {
neighborCount = neighborSum / 20;
// neighborSum / 20 can underflow to 0 in float64 when deep empty regions decay
// past ~249 layers, but this cell does have neighbours: keep a strictly positive weight
if(!neighborCount) neighborCount = Number.MIN_VALUE;

if(neighborCount) {
newNeighborHash[thisPt] = [i, j, neighborCount];
noNeighborList.splice(p, 1);
foundNewNeighbors = true;
Expand Down
27 changes: 27 additions & 0 deletions test/jasmine/tests/heatmap_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var Lib = require('../../../src/lib');
var setConvert = require('../../../src/plots/cartesian/set_convert');

var convertColumnXYZ = require('../../../src/traces/heatmap/convert_column_xyz');
var findEmpties = require('../../../src/traces/heatmap/find_empties');
var Heatmap = require('../../../src/traces/heatmap');

var d3Select = require('../../strict-d3').select;
Expand Down Expand Up @@ -1115,3 +1116,29 @@ describe('heatmap hover', function() {
});
});
});

describe('findEmpties', function() {
'use strict';

it('should fill deep empty regions without floating-point underflow (#8108)', function() {
// 5 rows, column 0 is defined, columns 1..250 are undefined (empty).
// Depth of 250 causes geometric weight decay (1/20)^249 to underflow double precision.
var z = [];
var r;
var i;
var j;
var nanCount = 250;
for(i = 0; i < 5; i++) {
r = [1];
for(j = 0; j < nanCount; j++) r.push(undefined);
z.push(r);
}

var empties;
expect(function() {
empties = findEmpties(z);
}).not.toThrow();

expect(empties.length).toBe(5 * nanCount);
});
});