Skip to content

Commit c627f0e

Browse files
authored
Merge pull request #868 from CalvinFernandez/heatmap-padding
Heatmap Padding
2 parents 482fb09 + 94daf84 commit c627f0e

File tree

11 files changed

+299
-7
lines changed

11 files changed

+299
-7
lines changed

src/plot_api/plot_api.js

+1
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
16201620
// objects need to be made) but not a recalc
16211621
var replotAttrs = [
16221622
'zmin', 'zmax', 'zauto',
1623+
'xgap', 'ygap',
16231624
'marker.cmin', 'marker.cmax', 'marker.cauto',
16241625
'line.cmin', 'line.cmax',
16251626
'marker.line.cmin', 'marker.line.cmax',

src/traces/heatmap/attributes.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,20 @@ module.exports = extendFlat({},
7676
'in the `z` data are filled in.'
7777
].join(' ')
7878
},
79-
79+
xgap: {
80+
valType: 'number',
81+
dflt: 0,
82+
min: 0,
83+
role: 'style',
84+
description: 'Sets the horizontal gap (in pixels) between bricks.'
85+
},
86+
ygap: {
87+
valType: 'number',
88+
dflt: 0,
89+
min: 0,
90+
role: 'style',
91+
description: 'Sets the vertical gap (in pixels) between bricks.'
92+
},
8093
_nestedModules: {
8194
'colorbar': 'Colorbar'
8295
}

src/traces/heatmap/defaults.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
2929
}
3030

3131
coerce('text');
32-
coerce('zsmooth');
32+
33+
var zsmooth = coerce('zsmooth');
34+
if(zsmooth === false) {
35+
// ensure that xgap and ygap are coerced only when zsmooth allows them to have an effect.
36+
coerce('xgap');
37+
coerce('ygap');
38+
}
39+
3340
coerce('connectgaps', hasColumns(traceOut) && (traceOut.zsmooth !== false));
3441

3542
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});

src/traces/heatmap/plot.js

+57-1
Original file line numberDiff line numberDiff line change
@@ -238,13 +238,55 @@ function plotOne(gd, plotinfo, cd) {
238238
rcount = 0,
239239
gcount = 0,
240240
bcount = 0,
241+
brickWithPadding,
241242
xb,
242243
j,
243244
xi,
244245
v,
245246
row,
246247
c;
247248

249+
function applyBrickPadding(trace, x0, x1, y0, y1, xIndex, xLength, yIndex, yLength) {
250+
var padding = {
251+
x0: x0,
252+
x1: x1,
253+
y0: y0,
254+
y1: y1
255+
},
256+
xEdgeGap = trace.xgap * 2 / 3,
257+
yEdgeGap = trace.ygap * 2 / 3,
258+
xCenterGap = trace.xgap / 3,
259+
yCenterGap = trace.ygap / 3;
260+
261+
if(yIndex === yLength - 1) { // top edge brick
262+
padding.y1 = y1 - yEdgeGap;
263+
}
264+
265+
if(xIndex === xLength - 1) { // right edge brick
266+
padding.x0 = x0 + xEdgeGap;
267+
}
268+
269+
if(yIndex === 0) { // bottom edge brick
270+
padding.y0 = y0 + yEdgeGap;
271+
}
272+
273+
if(xIndex === 0) { // left edge brick
274+
padding.x1 = x1 - xEdgeGap;
275+
}
276+
277+
if(xIndex > 0 && xIndex < xLength - 1) { // brick in the center along x
278+
padding.x0 = x0 + xCenterGap;
279+
padding.x1 = x1 - xCenterGap;
280+
}
281+
282+
if(yIndex > 0 && yIndex < yLength - 1) { // brick in the center along y
283+
padding.y0 = y0 + yCenterGap;
284+
padding.y1 = y1 - yCenterGap;
285+
}
286+
287+
return padding;
288+
}
289+
248290
function setColor(v, pixsize) {
249291
if(v !== undefined) {
250292
var c = s((v - min) / (max - min));
@@ -364,7 +406,21 @@ function plotOne(gd, plotinfo, cd) {
364406
v = row[i];
365407
c = setColor(v, (xb[1] - xb[0]) * (yb[1] - yb[0]));
366408
context.fillStyle = 'rgba(' + c.join(',') + ')';
367-
context.fillRect(xb[0], yb[0], (xb[1] - xb[0]), (yb[1] - yb[0]));
409+
410+
brickWithPadding = applyBrickPadding(trace,
411+
xb[0],
412+
xb[1],
413+
yb[0],
414+
yb[1],
415+
i,
416+
n,
417+
j,
418+
m);
419+
420+
context.fillRect(brickWithPadding.x0,
421+
brickWithPadding.y0,
422+
(brickWithPadding.x1 - brickWithPadding.x0),
423+
(brickWithPadding.y1 - brickWithPadding.y0));
368424
}
369425
}
370426
}

src/traces/histogram2d/attributes.js

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ module.exports = extendFlat({},
3939
nbinsy: histogramAttrs.nbinsy,
4040
ybins: histogramAttrs.ybins,
4141

42+
xgap: heatmapAttrs.xgap,
43+
ygap: heatmapAttrs.ygap,
4244
zsmooth: heatmapAttrs.zsmooth,
4345

4446
_nestedModules: {

src/traces/histogram2d/defaults.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, layout) {
2323

2424
handleSampleDefaults(traceIn, traceOut, coerce);
2525

26-
coerce('zsmooth');
26+
var zsmooth = coerce('zsmooth');
27+
if(zsmooth === false) {
28+
// ensure that xgap and ygap are coerced only when zsmooth allows them to have an effect.
29+
coerce('xgap');
30+
coerce('ygap');
31+
}
2732

2833
colorscaleDefaults(
2934
traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}
23.6 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"data": [
3+
{
4+
"z": [
5+
[
6+
1,
7+
20,
8+
30
9+
],
10+
[
11+
20,
12+
1,
13+
60
14+
],
15+
[
16+
30,
17+
60,
18+
1
19+
]
20+
],
21+
"xgap": 9,
22+
"ygap": 6,
23+
"type": "heatmap"
24+
}
25+
]
26+
}

test/jasmine/tests/heatmap_test.js

+109
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,42 @@ describe('heatmap supplyDefaults', function() {
8989
expect(traceOut.visible).toBe(false);
9090
});
9191

92+
it('should set paddings to 0 when not defined', function() {
93+
traceIn = {
94+
type: 'heatmap',
95+
z: [[1, 2], [3, 4]]
96+
};
97+
98+
supplyDefaults(traceIn, traceOut, defaultColor, layout);
99+
expect(traceOut.xgap).toBe(0);
100+
expect(traceOut.ygap).toBe(0);
101+
});
102+
103+
it('should not step on defined paddings', function() {
104+
traceIn = {
105+
xgap: 10,
106+
type: 'heatmap',
107+
z: [[1, 2], [3, 4]]
108+
};
109+
110+
supplyDefaults(traceIn, traceOut, defaultColor, layout);
111+
expect(traceOut.xgap).toBe(10);
112+
expect(traceOut.ygap).toBe(0);
113+
});
114+
115+
it('should not coerce gap if zsmooth is set', function() {
116+
traceIn = {
117+
xgap: 10,
118+
zsmooth: 'best',
119+
type: 'heatmap',
120+
z: [[1, 2], [3, 4]]
121+
};
122+
123+
supplyDefaults(traceIn, traceOut, defaultColor, layout);
124+
expect(traceOut.xgap).toBe(undefined);
125+
expect(traceOut.ygap).toBe(undefined);
126+
});
127+
92128
});
93129

94130
describe('heatmap convertColumnXYZ', function() {
@@ -381,7 +417,80 @@ describe('heatmap plot', function() {
381417

382418
done();
383419
});
420+
});
421+
422+
it('draws canvas with correct margins', function(done) {
423+
var mockWithPadding = require('@mocks/heatmap_brick_padding.json'),
424+
mockWithoutPadding = Lib.extendDeep({}, mockWithPadding),
425+
gd = createGraphDiv(),
426+
getContextStub = {
427+
fillRect: jasmine.createSpy()
428+
},
429+
originalCreateElement = document.createElement;
430+
431+
mockWithoutPadding.data[0].xgap = 0;
432+
mockWithoutPadding.data[0].ygap = 0;
433+
434+
spyOn(document, 'createElement').and.callFake(function(elementType) {
435+
var element = originalCreateElement.call(document, elementType);
436+
if(elementType === 'canvas') {
437+
spyOn(element, 'getContext').and.returnValue(getContextStub);
438+
}
439+
return element;
440+
});
384441

442+
var argumentsWithoutPadding = [],
443+
argumentsWithPadding = [];
444+
Plotly.plot(gd, mockWithoutPadding.data, mockWithoutPadding.layout).then(function() {
445+
argumentsWithoutPadding = getContextStub.fillRect.calls.allArgs().slice(0);
446+
return Plotly.plot(gd, mockWithPadding.data, mockWithPadding.layout);
447+
}).then(function() {
448+
var centerXGap = mockWithPadding.data[0].xgap / 3;
449+
var centerYGap = mockWithPadding.data[0].ygap / 3;
450+
var edgeXGap = mockWithPadding.data[0].xgap * 2 / 3;
451+
var edgeYGap = mockWithPadding.data[0].ygap * 2 / 3;
452+
453+
argumentsWithPadding = getContextStub.fillRect.calls.allArgs().slice(getContextStub.fillRect.calls.allArgs().length - 9);
454+
expect(argumentsWithPadding).toEqual([
455+
[argumentsWithoutPadding[0][0],
456+
argumentsWithoutPadding[0][1] + edgeYGap,
457+
argumentsWithoutPadding[0][2] - edgeXGap,
458+
argumentsWithoutPadding[0][3] - edgeYGap],
459+
[argumentsWithoutPadding[1][0] + centerXGap,
460+
argumentsWithoutPadding[1][1] + edgeYGap,
461+
argumentsWithoutPadding[1][2] - edgeXGap,
462+
argumentsWithoutPadding[1][3] - edgeYGap],
463+
[argumentsWithoutPadding[2][0] + edgeXGap,
464+
argumentsWithoutPadding[2][1] + edgeYGap,
465+
argumentsWithoutPadding[2][2] - edgeXGap,
466+
argumentsWithoutPadding[2][3] - edgeYGap],
467+
[argumentsWithoutPadding[3][0],
468+
argumentsWithoutPadding[3][1] + centerYGap,
469+
argumentsWithoutPadding[3][2] - edgeXGap,
470+
argumentsWithoutPadding[3][3] - edgeYGap],
471+
[argumentsWithoutPadding[4][0] + centerXGap,
472+
argumentsWithoutPadding[4][1] + centerYGap,
473+
argumentsWithoutPadding[4][2] - edgeXGap,
474+
argumentsWithoutPadding[4][3] - edgeYGap],
475+
[argumentsWithoutPadding[5][0] + edgeXGap,
476+
argumentsWithoutPadding[5][1] + centerYGap,
477+
argumentsWithoutPadding[5][2] - edgeXGap,
478+
argumentsWithoutPadding[5][3] - edgeYGap],
479+
[argumentsWithoutPadding[6][0],
480+
argumentsWithoutPadding[6][1],
481+
argumentsWithoutPadding[6][2] - edgeXGap,
482+
argumentsWithoutPadding[6][3] - edgeYGap],
483+
[argumentsWithoutPadding[7][0] + centerXGap,
484+
argumentsWithoutPadding[7][1],
485+
argumentsWithoutPadding[7][2] - edgeXGap,
486+
argumentsWithoutPadding[7][3] - edgeYGap],
487+
[argumentsWithoutPadding[8][0] + edgeXGap,
488+
argumentsWithoutPadding[8][1],
489+
argumentsWithoutPadding[8][2] - edgeXGap,
490+
argumentsWithoutPadding[8][3] - edgeYGap
491+
]]);
492+
done();
493+
});
385494
});
386495
});
387496

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
var supplyDefaults = require('@src/traces/histogram2d/defaults');
2+
3+
4+
describe('Test histogram2d', function() {
5+
'use strict';
6+
7+
describe('supplyDefaults', function() {
8+
var traceIn,
9+
traceOut;
10+
11+
beforeEach(function() {
12+
traceOut = {};
13+
});
14+
15+
it('should set zsmooth to false when zsmooth is empty', function() {
16+
traceIn = {};
17+
supplyDefaults(traceIn, traceOut, {});
18+
expect(traceOut.zsmooth).toBe(false);
19+
});
20+
21+
it('doesnt step on zsmooth when zsmooth is set', function() {
22+
traceIn = {
23+
zsmooth: 'fast'
24+
};
25+
supplyDefaults(traceIn, traceOut, {});
26+
expect(traceOut.zsmooth).toBe('fast');
27+
});
28+
29+
it('should set xgap and ygap to 0 when xgap and ygap are empty', function() {
30+
traceIn = {};
31+
supplyDefaults(traceIn, traceOut, {});
32+
expect(traceOut.xgap).toBe(0);
33+
expect(traceOut.ygap).toBe(0);
34+
});
35+
36+
it('shouldnt step on xgap and ygap when xgap and ygap are set', function() {
37+
traceIn = {
38+
xgap: 10,
39+
ygap: 5
40+
};
41+
supplyDefaults(traceIn, traceOut, {});
42+
expect(traceOut.xgap).toBe(10);
43+
expect(traceOut.ygap).toBe(5);
44+
});
45+
46+
it('shouldnt coerce gap when zsmooth is set', function() {
47+
traceIn = {
48+
xgap: 10,
49+
ygap: 5,
50+
zsmooth: 'best'
51+
};
52+
supplyDefaults(traceIn, traceOut, {});
53+
expect(traceOut.xgap).toBe(undefined);
54+
expect(traceOut.ygap).toBe(undefined);
55+
});
56+
57+
});
58+
59+
});

0 commit comments

Comments
 (0)