Skip to content

Commit ba8d23f

Browse files
author
Calvin Fernandez
committed
closes #858
Add padding field to heatmap that allows users to define space between bricks.
1 parent 3a343e2 commit ba8d23f

File tree

8 files changed

+175
-5
lines changed

8 files changed

+175
-5
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

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

3131
coerce('text');
32+
coerce('xgap');
33+
coerce('ygap');
3234
coerce('zsmooth');
3335
coerce('connectgaps', hasColumns(traceOut) && (traceOut.zsmooth !== false));
3436

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
}
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

+58
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,28 @@ 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+
});
92114
});
93115

94116
describe('heatmap convertColumnXYZ', function() {
@@ -381,7 +403,43 @@ describe('heatmap plot', function() {
381403

382404
done();
383405
});
406+
});
384407

408+
it('draws canvas with correct margins', function(done) {
409+
var mockWithPadding = require('@mocks/heatmap_brick_padding.json'),
410+
mockWithoutPadding = Lib.extendDeep({}, mockWithPadding),
411+
gd = createGraphDiv(),
412+
getContextStub = {
413+
fillRect: jasmine.createSpy()
414+
},
415+
originalCreateElement = document.createElement;
416+
417+
mockWithoutPadding.data[0].xgap = 0;
418+
mockWithoutPadding.data[0].ygap = 0;
419+
420+
spyOn(document, 'createElement').and.callFake(function(elementType) {
421+
var element = originalCreateElement.call(document, elementType);
422+
if(elementType === 'canvas') {
423+
spyOn(element, 'getContext').and.returnValue(getContextStub);
424+
}
425+
return element;
426+
});
427+
428+
var argumentsWithoutPadding = [],
429+
argumentsWithPadding = [];
430+
Plotly.plot(gd, mockWithoutPadding.data, mockWithoutPadding.layout).then(function() {
431+
argumentsWithoutPadding = getContextStub.fillRect.calls.allArgs().slice(0);
432+
expect(argumentsWithoutPadding).toEqual([[0, 180, 177, 90], [177, 180, 177, 90], [354, 180, 177, 90],
433+
[0, 90, 177, 90], [177, 90, 177, 90], [354, 90, 177, 90],
434+
[0, 0, 177, 90], [177, 0, 177, 90], [354, 0, 177, 90]]);
435+
return Plotly.plot(gd, mockWithPadding.data, mockWithPadding.layout);
436+
}).then(function() {
437+
argumentsWithPadding = getContextStub.fillRect.calls.allArgs().slice(getContextStub.fillRect.calls.allArgs().length - 9);
438+
expect(argumentsWithPadding).toEqual([[0, 184, 171, 86], [180, 184, 171, 86], [360, 184, 171, 86],
439+
[0, 92, 171, 86], [180, 92, 171, 86], [360, 92, 171, 86],
440+
[0, 0, 171, 86], [180, 0, 171, 86], [360, 0, 171, 86]]);
441+
done();
442+
});
385443
});
386444
});
387445

test/jasmine/tests/plot_api_test.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ describe('Test plot api', function() {
8686

8787
describe('Plotly.restyle', function() {
8888
beforeEach(function() {
89-
spyOn(Plotly, 'plot');
89+
spyOn(PlotlyInternal, 'plot');
9090
spyOn(Plots, 'previousPromises');
9191
spyOn(Scatter, 'arraysToCalcdata');
9292
spyOn(Bar, 'arraysToCalcdata');
@@ -111,7 +111,7 @@ describe('Test plot api', function() {
111111
expect(Scatter.arraysToCalcdata).toHaveBeenCalled();
112112
expect(Bar.arraysToCalcdata).not.toHaveBeenCalled();
113113
expect(Plots.style).toHaveBeenCalled();
114-
expect(Plotly.plot).not.toHaveBeenCalled();
114+
expect(PlotlyInternal.plot).not.toHaveBeenCalled();
115115
// "docalc" deletes gd.calcdata - make sure this didn't happen
116116
expect(gd.calcdata).toBeDefined();
117117
});
@@ -126,10 +126,24 @@ describe('Test plot api', function() {
126126
expect(Scatter.arraysToCalcdata).not.toHaveBeenCalled();
127127
expect(Bar.arraysToCalcdata).toHaveBeenCalled();
128128
expect(Plots.style).toHaveBeenCalled();
129-
expect(Plotly.plot).not.toHaveBeenCalled();
129+
expect(PlotlyInternal.plot).not.toHaveBeenCalled();
130130
expect(gd.calcdata).toBeDefined();
131131
});
132132

133+
it('calls plot on xgap and ygap styling', function() {
134+
var gd = {
135+
data: [{z: [[1, 2, 3], [4, 5, 6], [7, 8, 9]], showscale: false, type: 'heatmap'}],
136+
layout: {}
137+
};
138+
139+
mockDefaultsAndCalc(gd);
140+
Plotly.restyle(gd, {'xgap': 2});
141+
expect(PlotlyInternal.plot).toHaveBeenCalled();
142+
143+
Plotly.restyle(gd, {'ygap': 2});
144+
expect(PlotlyInternal.plot.calls.count()).toEqual(2);
145+
});
146+
133147
it('ignores undefined values', function() {
134148
var gd = {
135149
data: [{x: [1, 2, 3], y: [1, 2, 3], type: 'scatter'}],

0 commit comments

Comments
 (0)