Skip to content

Commit 4b2ce92

Browse files
committed
Merge pull request #194 from plotly/svg-image-layer
Add svg image layer for heatmaps
2 parents fa2c61d + c080c03 commit 4b2ce92

File tree

6 files changed

+157
-35
lines changed

6 files changed

+157
-35
lines changed

devtools/test_dashboard/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
return graphDiv;
3737
}
3838
};
39+
40+
var d3 = Plotly.d3;
3941
</script>
4042

4143
</body>

src/plot_api/plot_api.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ Plotly.plot = function(gd, data, layout, config) {
251251
subplots = Plots.getSubplotIds(fullLayout, 'cartesian'),
252252
modules = gd._modules;
253253

254-
var i, j, cd, trace, uid, subplot, subplotInfo,
254+
var i, j, trace, subplot, subplotInfo,
255255
cdSubplot, cdError, cdModule, _module;
256256

257257
function getCdSubplot(calcdata, subplot) {
@@ -293,12 +293,21 @@ Plotly.plot = function(gd, data, layout, config) {
293293
// in case of traces that were heatmaps or contour maps
294294
// previously, remove them and their colorbars explicitly
295295
for (i = 0; i < calcdata.length; i++) {
296-
cd = calcdata[i];
297-
trace = cd[0].trace;
298-
if (trace.visible !== true || !trace._module.colorbar) {
296+
trace = calcdata[i][0].trace;
297+
298+
var isVisible = (trace.visible === true),
299299
uid = trace.uid;
300-
fullLayout._paper.selectAll('.hm'+uid+',.contour'+uid+',.cb'+uid+',#clip'+uid)
301-
.remove();
300+
301+
if(!isVisible || !Plots.traceIs(trace, '2dMap')) {
302+
fullLayout._paper.selectAll(
303+
'.hm' + uid +
304+
',.contour' + uid +
305+
',#clip' + uid
306+
).remove();
307+
}
308+
309+
if(!isVisible || !trace._module.colorbar) {
310+
fullLayout._infolayer.selectAll('.cb' + uid).remove();
302311
}
303312
}
304313

@@ -2654,6 +2663,7 @@ function makeCartesianPlotFramwork(gd, subplots) {
26542663
// 4. scatter
26552664
// 5. box plots
26562665
function plotLayers(svg) {
2666+
svg.append('g').classed('imagelayer', true);
26572667
svg.append('g').classed('maplayer', true);
26582668
svg.append('g').classed('barlayer', true);
26592669
svg.append('g').classed('errorlayer', true);

src/traces/contour/plot.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,24 @@ function plotOne(gd, plotinfo, cd) {
6464
pathinfo = emptyPathinfo(contours, plotinfo, cd[0]);
6565

6666
if(trace.visible !== true) {
67-
fullLayout._paper.selectAll('.'+id+',.cb'+uid+',.hm'+uid).remove();
67+
fullLayout._paper.selectAll('.' + id + ',.hm' + uid).remove();
68+
fullLayout._infolayer.selectAll('.cb' + uid).remove();
6869
return;
6970
}
7071

7172
// use a heatmap to fill - draw it behind the lines
72-
if(contours.coloring==='heatmap') {
73-
if(trace.zauto && trace.autocontour===false) {
73+
if(contours.coloring === 'heatmap') {
74+
if(trace.zauto && (trace.autocontour === false)) {
7475
trace._input.zmin = trace.zmin =
75-
contours.start - contours.size/2;
76+
contours.start - contours.size / 2;
7677
trace._input.zmax = trace.zmax =
7778
trace.zmin + pathinfo.length * contours.size;
7879
}
7980

8081
heatmapPlot(gd, plotinfo, [cd]);
8182
}
8283
// in case this used to be a heatmap (or have heatmap fill)
83-
else fullLayout._paper.selectAll('.hm'+uid).remove();
84+
else fullLayout._paper.selectAll('.hm' + uid).remove();
8485

8586
makeCrossings(pathinfo);
8687
findAllPaths(pathinfo);
@@ -471,12 +472,15 @@ function getInterpPx(pi, loc, step) {
471472

472473
function makeContourGroup(plotinfo, cd, id) {
473474
var plotgroup = plotinfo.plot.select('.maplayer')
474-
.selectAll('g.contour.'+id)
475+
.selectAll('g.contour.' + id)
475476
.data(cd);
477+
476478
plotgroup.enter().append('g')
477-
.classed('contour',true)
478-
.classed(id,true);
479+
.classed('contour', true)
480+
.classed(id, true);
481+
479482
plotgroup.exit().remove();
483+
480484
return plotgroup;
481485
}
482486

src/traces/heatmap/plot.js

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
2020
var maxRowLength = require('./max_row_length');
2121

2222

23-
// From http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/
2423
module.exports = function(gd, plotinfo, cdheatmaps) {
25-
cdheatmaps.forEach(function(cd) { plotOne(gd, plotinfo, cd); });
24+
for(var i = 0; i < cdheatmaps.length; i++) {
25+
plotOne(gd, plotinfo, cdheatmaps[i]);
26+
}
2627
};
2728

29+
// From http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/
2830
function plotOne(gd, plotinfo, cd) {
2931
Lib.markTime('in Heatmap.plot');
3032

@@ -33,14 +35,14 @@ function plotOne(gd, plotinfo, cd) {
3335
xa = plotinfo.x(),
3436
ya = plotinfo.y(),
3537
fullLayout = gd._fullLayout,
36-
id = 'hm' + uid,
37-
cbId = 'cb' + uid;
38+
id = 'hm' + uid;
3839

39-
fullLayout._paper.selectAll('.contour' + uid).remove(); // in case this used to be a contour map
40+
// in case this used to be a contour map
41+
fullLayout._paper.selectAll('.contour' + uid).remove();
4042

4143
if(trace.visible !== true) {
4244
fullLayout._paper.selectAll('.' + id).remove();
43-
fullLayout._paper.selectAll('.' + cbId).remove();
45+
fullLayout._infolayer.selectAll('.cb' + uid).remove();
4446
return;
4547
}
4648

@@ -367,20 +369,28 @@ function plotOne(gd, plotinfo, cd) {
367369
gd._hmpixcount = (gd._hmpixcount||0) + pixcount;
368370
gd._hmlumcount = (gd._hmlumcount||0) + pixcount * avgColor.getLuminance();
369371

370-
// put this right before making the new image, to minimize flicker
371-
fullLayout._paper.selectAll('.'+id).remove();
372-
plotinfo.plot.select('.maplayer').append('svg:image')
373-
.classed(id, true)
374-
.datum(cd[0])
375-
.attr({
376-
xmlns: xmlnsNamespaces.svg,
377-
'xlink:href': canvas.toDataURL('image/png'),
378-
height: imageHeight,
379-
width: imageWidth,
380-
x: left,
381-
y: top,
382-
preserveAspectRatio: 'none'
383-
});
372+
var plotgroup = plotinfo.plot.select('.imagelayer')
373+
.selectAll('g.hm.' + id)
374+
.data([0]);
375+
plotgroup.enter().append('g')
376+
.classed('hm', true)
377+
.classed(id, true);
378+
plotgroup.exit().remove();
379+
380+
var image3 = plotgroup.selectAll('image')
381+
.data(cd);
382+
image3.enter().append('svg:image');
383+
image3.exit().remove();
384+
385+
image3.attr({
386+
xmlns: xmlnsNamespaces.svg,
387+
'xlink:href': canvas.toDataURL('image/png'),
388+
height: imageHeight,
389+
width: imageWidth,
390+
x: left,
391+
y: top,
392+
preserveAspectRatio: 'none'
393+
});
384394

385395
Lib.markTime('done showing png');
386396
}

test/jasmine/tests/plot_interact_test.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var d3 = require('d3');
22

33
var Plotly = require('@lib/index');
4+
var Lib = require('@src/lib');
45

56
var createGraphDiv = require('../assets/create_graph_div');
67
var destroyGraphDiv = require('../assets/destroy_graph_div');
@@ -62,6 +63,101 @@ describe('Test plot structure', function() {
6263
});
6364
});
6465

66+
describe('contour/heatmap traces', function() {
67+
var mock = require('@mocks/connectgaps_2d.json');
68+
69+
function extendMock() {
70+
var mockCopy = Lib.extendDeep(mock);
71+
72+
// add a colorbar for testing
73+
mockCopy.data[0].showscale = true;
74+
75+
return mockCopy;
76+
}
77+
78+
describe('initial structure', function() {
79+
beforeEach(function(done) {
80+
var mockCopy = extendMock();
81+
82+
Plotly.plot(createGraphDiv(), mockCopy.data, mockCopy.layout)
83+
.then(done);
84+
});
85+
86+
it('has four *subplot* nodes', function() {
87+
var nodes = d3.selectAll('g.subplot');
88+
expect(nodes.size()).toEqual(4);
89+
});
90+
91+
// N.B. the contour traces both have a heatmap fill
92+
it('has four heatmap image nodes', function() {
93+
var hmNodes = d3.selectAll('g.hm');
94+
expect(hmNodes.size()).toEqual(4);
95+
96+
var imageNodes = d3.selectAll('image');
97+
expect(imageNodes.size()).toEqual(4);
98+
});
99+
100+
it('has two contour nodes', function() {
101+
var nodes = d3.selectAll('g.contour');
102+
expect(nodes.size()).toEqual(2);
103+
});
104+
105+
it('has one colorbar nodes', function() {
106+
var nodes = d3.selectAll('rect.cbbg');
107+
expect(nodes.size()).toEqual(1);
108+
});
109+
});
110+
111+
describe('structure after restyle', function() {
112+
beforeEach(function(done) {
113+
var mockCopy = extendMock();
114+
var gd = createGraphDiv();
115+
116+
Plotly.plot(gd, mockCopy.data, mockCopy.layout);
117+
118+
Plotly.restyle(gd, {
119+
type: 'scatter',
120+
x: [[1, 2, 3]],
121+
y: [[2, 1, 2]],
122+
z: null
123+
}, 0);
124+
125+
Plotly.restyle(gd, 'type', 'contour', 1);
126+
127+
Plotly.restyle(gd, 'type', 'heatmap', 2)
128+
.then(done);
129+
});
130+
131+
it('has four *subplot* nodes', function() {
132+
var nodes = d3.selectAll('g.subplot');
133+
expect(nodes.size()).toEqual(4);
134+
});
135+
136+
it('has two heatmap image nodes', function() {
137+
var hmNodes = d3.selectAll('g.hm');
138+
expect(hmNodes.size()).toEqual(2);
139+
140+
var imageNodes = d3.selectAll('image');
141+
expect(imageNodes.size()).toEqual(2);
142+
});
143+
144+
it('has two contour nodes', function() {
145+
var nodes = d3.selectAll('g.contour');
146+
expect(nodes.size()).toEqual(2);
147+
});
148+
149+
it('has one scatter node', function() {
150+
var nodes = d3.selectAll('g.trace.scatter');
151+
expect(nodes.size()).toEqual(1);
152+
});
153+
154+
it('has no colorbar node', function() {
155+
var nodes = d3.selectAll('rect.cbbg');
156+
expect(nodes.size()).toEqual(0);
157+
});
158+
});
159+
});
160+
65161
describe('pie traces', function() {
66162
var mock = require('@mocks/pie_simple.json');
67163

test/jasmine/tests/plot_promise_test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ describe('Plotly.___ methods', function() {
282282

283283
Plotly.plot(initialDiv, data, {});
284284

285-
promise = Plotly.restyle(initialDiv, 'title', 'Promise test!');
285+
promise = Plotly.relayout(initialDiv, 'title', 'Promise test!');
286286

287287
promise.then(function(gd){
288288
promiseGd = gd;

0 commit comments

Comments
 (0)