Skip to content

Commit 3366e95

Browse files
authored
Merge pull request #6565 from lvlte/heatmap_fast_zsmooth_fix
Fix heatmap rendering when zsmooth=fast
2 parents b21e3db + fde0f8d commit 3366e95

File tree

5 files changed

+149
-5
lines changed

5 files changed

+149
-5
lines changed

Diff for: draftlogs/6565_fix.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix heatmap rendering bug and improve performance when `zsmooth` is set to "fast" [[#6565](https://github.com/plotly/plotly.js/pull/6565)], with thanks to @lvlte for the contribution!

Diff for: src/traces/heatmap/plot.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,9 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) {
240240
var pixels;
241241

242242
try {
243-
pixels = new Uint8Array(imageWidth * imageHeight * 4);
243+
pixels = new Uint8Array(canvasW * canvasH * 4);
244244
} catch(e) {
245-
pixels = new Array(imageWidth * imageHeight * 4);
245+
pixels = new Array(canvasW * canvasH * 4);
246246
}
247247

248248
if(zsmooth === 'best') {
@@ -277,15 +277,15 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) {
277277
for(j = 0; j < m; j++) {
278278
row = z[j];
279279
yb = ypx(j);
280-
for(i = 0; i < imageWidth; i++) {
280+
for(i = 0; i < n; i++) {
281281
c = setColor(row[i], 1);
282-
pxIndex = (yb * imageWidth + xpx(i)) * 4;
282+
pxIndex = (yb * n + xpx(i)) * 4;
283283
putColor(pixels, pxIndex, c);
284284
}
285285
}
286286
}
287287

288-
var imageData = context.createImageData(imageWidth, imageHeight);
288+
var imageData = context.createImageData(canvasW, canvasH);
289289
try {
290290
imageData.data.set(pixels);
291291
} catch(e) {
7.38 KB
Loading

Diff for: test/image/mocks/zz-heatmap_small_layout_zsmooth_fast.json

+26
Large diffs are not rendered by default.

Diff for: test/jasmine/tests/heatmap_test.js

+117
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,123 @@ describe('heatmap plot', function() {
826826
})
827827
.then(done, done.fail);
828828
});
829+
830+
it('should set canvas dimensions according to z data shape if `zsmooth` is fast', function(done) {
831+
var mock1 = require('../../image/mocks/zsmooth_methods.json');
832+
var mock2 = require('../../image/mocks/zz-heatmap_small_layout_zsmooth_fast.json');
833+
834+
var canvasStub;
835+
var originalCreateElement = document.createElement;
836+
837+
spyOn(document, 'createElement').and.callFake(function(elementType) {
838+
var element = originalCreateElement.call(document, elementType);
839+
if(elementType === 'canvas') {
840+
canvasStub = {
841+
width: spyOnProperty(element, 'width', 'set').and.callThrough(),
842+
height: spyOnProperty(element, 'height', 'set').and.callThrough()
843+
};
844+
}
845+
return element;
846+
});
847+
848+
function assertCanvas(z) {
849+
expect(canvasStub.width.calls.count()).toBe(1);
850+
expect(canvasStub.height.calls.count()).toBe(1);
851+
var m = z.length;
852+
var n = Lib.maxRowLength(z);
853+
var canvasW = canvasStub.width.calls.argsFor(0)[0];
854+
var canvasH = canvasStub.height.calls.argsFor(0)[0];
855+
expect([canvasW, canvasH]).toEqual([n, m]);
856+
}
857+
858+
Plotly.newPlot(gd, [mock1.data[1]]).then(function() {
859+
assertCanvas(mock1.data[1].z);
860+
return Plotly.newPlot(gd, mock2.data, mock2.layout);
861+
}).then(function() {
862+
assertCanvas(mock2.data[0].z);
863+
}).then(done, done.fail);
864+
});
865+
866+
it('should create imageData that fits the canvas dimensions if zsmooth is set', function(done) {
867+
var mock1 = require('../../image/mocks/zsmooth_methods.json');
868+
var mock2 = require('../../image/mocks/zz-heatmap_small_layout_zsmooth_fast.json');
869+
870+
var imageDataStub = {
871+
data: {
872+
set: jasmine.createSpy()
873+
}
874+
};
875+
876+
var getContextStub = {
877+
createImageData: jasmine.createSpy().and.returnValue(imageDataStub),
878+
putImageData: function() {},
879+
fillRect: function() {},
880+
};
881+
882+
function checkPixels(pixels) {
883+
for(var j = 0, px, check; j < pixels.length; j += 4) {
884+
px = pixels.slice(j, j + 4);
885+
check = px.every(function(c) { return c === 0; });
886+
if(check) {
887+
return false;
888+
}
889+
}
890+
return true;
891+
}
892+
893+
var canvasStubs = [];
894+
var originalCreateElement = document.createElement;
895+
896+
spyOn(document, 'createElement').and.callFake(function(elementType) {
897+
var element = originalCreateElement.call(document, elementType);
898+
if(elementType === 'canvas') {
899+
spyOn(element, 'getContext').and.returnValue(getContextStub);
900+
canvasStubs.push({
901+
width: spyOnProperty(element, 'width', 'set').and.callThrough(),
902+
height: spyOnProperty(element, 'height', 'set').and.callThrough()
903+
});
904+
}
905+
return element;
906+
});
907+
908+
Plotly.newPlot(gd, mock1.data, mock1.layout).then(function() {
909+
expect(getContextStub.createImageData.calls.count()).toBe(2);
910+
expect(imageDataStub.data.set.calls.count()).toBe(2);
911+
912+
[0, 1].forEach(function(i) {
913+
var createImageDataArgs = getContextStub.createImageData.calls.argsFor(i);
914+
var setImageDataArgs = imageDataStub.data.set.calls.argsFor(i);
915+
916+
var canvasW = canvasStubs[i].width.calls.argsFor(0)[0];
917+
var canvasH = canvasStubs[i].height.calls.argsFor(0)[0];
918+
expect(createImageDataArgs).toEqual([canvasW, canvasH]);
919+
920+
var pixels = setImageDataArgs[0];
921+
expect(pixels.length).toBe(canvasW * canvasH * 4);
922+
expect(checkPixels(pixels)).toBe(true);
923+
});
924+
925+
getContextStub.createImageData.calls.reset();
926+
imageDataStub.data.set.calls.reset();
927+
canvasStubs = [];
928+
929+
return Plotly.newPlot(gd, mock2.data, mock2.layout);
930+
}).then(function() {
931+
expect(getContextStub.createImageData.calls.count()).toBe(1);
932+
expect(imageDataStub.data.set.calls.count()).toBe(1);
933+
934+
var canvasW = canvasStubs[0].width.calls.argsFor(0)[0];
935+
var canvasH = canvasStubs[0].height.calls.argsFor(0)[0];
936+
937+
var createImageDataArgs = getContextStub.createImageData.calls.argsFor(0);
938+
expect(createImageDataArgs).toEqual([canvasW, canvasH]);
939+
940+
var setImageDataArgs = imageDataStub.data.set.calls.argsFor(0);
941+
var pixels = setImageDataArgs[0];
942+
expect(pixels.length).toBe(canvasW * canvasH * 4);
943+
expect(checkPixels(pixels)).toBe(true);
944+
}).then(done, done.fail);
945+
});
829946
});
830947

831948
describe('heatmap hover', function() {

0 commit comments

Comments
 (0)