diff --git a/devtools/test_dashboard/index.html b/devtools/test_dashboard/index.html index d024ee9c6a0..5103afbd22b 100644 --- a/devtools/test_dashboard/index.html +++ b/devtools/test_dashboard/index.html @@ -36,6 +36,8 @@ return graphDiv; } }; + + var d3 = Plotly.d3; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 8c708da5d7d..38f11655f6c 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -251,7 +251,7 @@ Plotly.plot = function(gd, data, layout, config) { subplots = Plots.getSubplotIds(fullLayout, 'cartesian'), modules = gd._modules; - var i, j, cd, trace, uid, subplot, subplotInfo, + var i, j, trace, subplot, subplotInfo, cdSubplot, cdError, cdModule, _module; function getCdSubplot(calcdata, subplot) { @@ -293,12 +293,21 @@ Plotly.plot = function(gd, data, layout, config) { // in case of traces that were heatmaps or contour maps // previously, remove them and their colorbars explicitly for (i = 0; i < calcdata.length; i++) { - cd = calcdata[i]; - trace = cd[0].trace; - if (trace.visible !== true || !trace._module.colorbar) { + trace = calcdata[i][0].trace; + + var isVisible = (trace.visible === true), uid = trace.uid; - fullLayout._paper.selectAll('.hm'+uid+',.contour'+uid+',.cb'+uid+',#clip'+uid) - .remove(); + + if(!isVisible || !Plots.traceIs(trace, '2dMap')) { + fullLayout._paper.selectAll( + '.hm' + uid + + ',.contour' + uid + + ',#clip' + uid + ).remove(); + } + + if(!isVisible || !trace._module.colorbar) { + fullLayout._infolayer.selectAll('.cb' + uid).remove(); } } @@ -2654,6 +2663,7 @@ function makeCartesianPlotFramwork(gd, subplots) { // 4. scatter // 5. box plots function plotLayers(svg) { + svg.append('g').classed('imagelayer', true); svg.append('g').classed('maplayer', true); svg.append('g').classed('barlayer', true); svg.append('g').classed('errorlayer', true); diff --git a/src/traces/contour/plot.js b/src/traces/contour/plot.js index 4511320b51b..f9f0ccafb4f 100644 --- a/src/traces/contour/plot.js +++ b/src/traces/contour/plot.js @@ -64,15 +64,16 @@ function plotOne(gd, plotinfo, cd) { pathinfo = emptyPathinfo(contours, plotinfo, cd[0]); if(trace.visible !== true) { - fullLayout._paper.selectAll('.'+id+',.cb'+uid+',.hm'+uid).remove(); + fullLayout._paper.selectAll('.' + id + ',.hm' + uid).remove(); + fullLayout._infolayer.selectAll('.cb' + uid).remove(); return; } // use a heatmap to fill - draw it behind the lines - if(contours.coloring==='heatmap') { - if(trace.zauto && trace.autocontour===false) { + if(contours.coloring === 'heatmap') { + if(trace.zauto && (trace.autocontour === false)) { trace._input.zmin = trace.zmin = - contours.start - contours.size/2; + contours.start - contours.size / 2; trace._input.zmax = trace.zmax = trace.zmin + pathinfo.length * contours.size; } @@ -80,7 +81,7 @@ function plotOne(gd, plotinfo, cd) { heatmapPlot(gd, plotinfo, [cd]); } // in case this used to be a heatmap (or have heatmap fill) - else fullLayout._paper.selectAll('.hm'+uid).remove(); + else fullLayout._paper.selectAll('.hm' + uid).remove(); makeCrossings(pathinfo); findAllPaths(pathinfo); @@ -471,12 +472,15 @@ function getInterpPx(pi, loc, step) { function makeContourGroup(plotinfo, cd, id) { var plotgroup = plotinfo.plot.select('.maplayer') - .selectAll('g.contour.'+id) + .selectAll('g.contour.' + id) .data(cd); + plotgroup.enter().append('g') - .classed('contour',true) - .classed(id,true); + .classed('contour', true) + .classed(id, true); + plotgroup.exit().remove(); + return plotgroup; } diff --git a/src/traces/heatmap/plot.js b/src/traces/heatmap/plot.js index 3f0f87d0597..236fad50b74 100644 --- a/src/traces/heatmap/plot.js +++ b/src/traces/heatmap/plot.js @@ -20,11 +20,13 @@ var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); var maxRowLength = require('./max_row_length'); -// From http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/ module.exports = function(gd, plotinfo, cdheatmaps) { - cdheatmaps.forEach(function(cd) { plotOne(gd, plotinfo, cd); }); + for(var i = 0; i < cdheatmaps.length; i++) { + plotOne(gd, plotinfo, cdheatmaps[i]); + } }; +// From http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/ function plotOne(gd, plotinfo, cd) { Lib.markTime('in Heatmap.plot'); @@ -33,14 +35,14 @@ function plotOne(gd, plotinfo, cd) { xa = plotinfo.x(), ya = plotinfo.y(), fullLayout = gd._fullLayout, - id = 'hm' + uid, - cbId = 'cb' + uid; + id = 'hm' + uid; - fullLayout._paper.selectAll('.contour' + uid).remove(); // in case this used to be a contour map + // in case this used to be a contour map + fullLayout._paper.selectAll('.contour' + uid).remove(); if(trace.visible !== true) { fullLayout._paper.selectAll('.' + id).remove(); - fullLayout._paper.selectAll('.' + cbId).remove(); + fullLayout._infolayer.selectAll('.cb' + uid).remove(); return; } @@ -367,20 +369,28 @@ function plotOne(gd, plotinfo, cd) { gd._hmpixcount = (gd._hmpixcount||0) + pixcount; gd._hmlumcount = (gd._hmlumcount||0) + pixcount * avgColor.getLuminance(); - // put this right before making the new image, to minimize flicker - fullLayout._paper.selectAll('.'+id).remove(); - plotinfo.plot.select('.maplayer').append('svg:image') - .classed(id, true) - .datum(cd[0]) - .attr({ - xmlns: xmlnsNamespaces.svg, - 'xlink:href': canvas.toDataURL('image/png'), - height: imageHeight, - width: imageWidth, - x: left, - y: top, - preserveAspectRatio: 'none' - }); + var plotgroup = plotinfo.plot.select('.imagelayer') + .selectAll('g.hm.' + id) + .data([0]); + plotgroup.enter().append('g') + .classed('hm', true) + .classed(id, true); + plotgroup.exit().remove(); + + var image3 = plotgroup.selectAll('image') + .data(cd); + image3.enter().append('svg:image'); + image3.exit().remove(); + + image3.attr({ + xmlns: xmlnsNamespaces.svg, + 'xlink:href': canvas.toDataURL('image/png'), + height: imageHeight, + width: imageWidth, + x: left, + y: top, + preserveAspectRatio: 'none' + }); Lib.markTime('done showing png'); } diff --git a/test/jasmine/tests/plot_interact_test.js b/test/jasmine/tests/plot_interact_test.js index fc9822e70aa..eae10037d55 100644 --- a/test/jasmine/tests/plot_interact_test.js +++ b/test/jasmine/tests/plot_interact_test.js @@ -1,6 +1,7 @@ var d3 = require('d3'); var Plotly = require('@lib/index'); +var Lib = require('@src/lib'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); @@ -62,6 +63,101 @@ describe('Test plot structure', function() { }); }); + describe('contour/heatmap traces', function() { + var mock = require('@mocks/connectgaps_2d.json'); + + function extendMock() { + var mockCopy = Lib.extendDeep(mock); + + // add a colorbar for testing + mockCopy.data[0].showscale = true; + + return mockCopy; + } + + describe('initial structure', function() { + beforeEach(function(done) { + var mockCopy = extendMock(); + + Plotly.plot(createGraphDiv(), mockCopy.data, mockCopy.layout) + .then(done); + }); + + it('has four *subplot* nodes', function() { + var nodes = d3.selectAll('g.subplot'); + expect(nodes.size()).toEqual(4); + }); + + // N.B. the contour traces both have a heatmap fill + it('has four heatmap image nodes', function() { + var hmNodes = d3.selectAll('g.hm'); + expect(hmNodes.size()).toEqual(4); + + var imageNodes = d3.selectAll('image'); + expect(imageNodes.size()).toEqual(4); + }); + + it('has two contour nodes', function() { + var nodes = d3.selectAll('g.contour'); + expect(nodes.size()).toEqual(2); + }); + + it('has one colorbar nodes', function() { + var nodes = d3.selectAll('rect.cbbg'); + expect(nodes.size()).toEqual(1); + }); + }); + + describe('structure after restyle', function() { + beforeEach(function(done) { + var mockCopy = extendMock(); + var gd = createGraphDiv(); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout); + + Plotly.restyle(gd, { + type: 'scatter', + x: [[1, 2, 3]], + y: [[2, 1, 2]], + z: null + }, 0); + + Plotly.restyle(gd, 'type', 'contour', 1); + + Plotly.restyle(gd, 'type', 'heatmap', 2) + .then(done); + }); + + it('has four *subplot* nodes', function() { + var nodes = d3.selectAll('g.subplot'); + expect(nodes.size()).toEqual(4); + }); + + it('has two heatmap image nodes', function() { + var hmNodes = d3.selectAll('g.hm'); + expect(hmNodes.size()).toEqual(2); + + var imageNodes = d3.selectAll('image'); + expect(imageNodes.size()).toEqual(2); + }); + + it('has two contour nodes', function() { + var nodes = d3.selectAll('g.contour'); + expect(nodes.size()).toEqual(2); + }); + + it('has one scatter node', function() { + var nodes = d3.selectAll('g.trace.scatter'); + expect(nodes.size()).toEqual(1); + }); + + it('has no colorbar node', function() { + var nodes = d3.selectAll('rect.cbbg'); + expect(nodes.size()).toEqual(0); + }); + }); + }); + describe('pie traces', function() { var mock = require('@mocks/pie_simple.json'); diff --git a/test/jasmine/tests/plot_promise_test.js b/test/jasmine/tests/plot_promise_test.js index b42af7044db..981d5c30d8d 100644 --- a/test/jasmine/tests/plot_promise_test.js +++ b/test/jasmine/tests/plot_promise_test.js @@ -282,7 +282,7 @@ describe('Plotly.___ methods', function() { Plotly.plot(initialDiv, data, {}); - promise = Plotly.restyle(initialDiv, 'title', 'Promise test!'); + promise = Plotly.relayout(initialDiv, 'title', 'Promise test!'); promise.then(function(gd){ promiseGd = gd;