diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 228916d3de4..52d1e6456ca 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -16,16 +16,28 @@ var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); module.exports = function draw(gd) { var fullLayout = gd._fullLayout, imageDataAbove = [], - imageDataSubplot = [], - imageDataBelow = []; + imageDataSubplot = {}, + imageDataBelow = [], + subplot, + i; // Sort into top, subplot, and bottom layers - for(var i = 0; i < fullLayout.images.length; i++) { + for(i = 0; i < fullLayout.images.length; i++) { var img = fullLayout.images[i]; if(img.visible) { if(img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') { - imageDataSubplot.push(img); + subplot = img.xref + img.yref; + + var plotinfo = fullLayout._plots[subplot]; + if(plotinfo.mainplot) { + subplot = plotinfo.mainplot.id; + } + + if(!imageDataSubplot[subplot]) { + imageDataSubplot[subplot] = []; + } + imageDataSubplot[subplot].push(img); } else if(img.layer === 'above') { imageDataAbove.push(img); } else { @@ -143,36 +155,52 @@ module.exports = function draw(gd) { yId = ya ? ya._id : '', clipAxes = xId + yId; - if(clipAxes) { - thisImage.call(Drawing.setClipUrl, 'clip' + fullLayout._uid + clipAxes); - } + thisImage.call(Drawing.setClipUrl, clipAxes ? + ('clip' + fullLayout._uid + clipAxes) : + null + ); } var imagesBelow = fullLayout._imageLowerLayer.selectAll('image') .data(imageDataBelow), - imagesSubplot = fullLayout._imageSubplotLayer.selectAll('image') - .data(imageDataSubplot), imagesAbove = fullLayout._imageUpperLayer.selectAll('image') .data(imageDataAbove); imagesBelow.enter().append('image'); - imagesSubplot.enter().append('image'); imagesAbove.enter().append('image'); imagesBelow.exit().remove(); - imagesSubplot.exit().remove(); imagesAbove.exit().remove(); imagesBelow.each(function(d) { setImage.bind(this)(d); applyAttributes.bind(this)(d); }); - imagesSubplot.each(function(d) { - setImage.bind(this)(d); - applyAttributes.bind(this)(d); - }); imagesAbove.each(function(d) { setImage.bind(this)(d); applyAttributes.bind(this)(d); }); + + var allSubplots = Object.keys(fullLayout._plots); + for(i = 0; i < allSubplots.length; i++) { + subplot = allSubplots[i]; + var subplotObj = fullLayout._plots[subplot]; + + // filter out overlaid plots (which havd their images on the main plot) + // and gl2d plots (which don't support below images, at least not yet) + if(!subplotObj.imagelayer) continue; + + var imagesOnSubplot = subplotObj.imagelayer.selectAll('image') + // even if there are no images on this subplot, we need to run + // enter and exit in case there were previously + .data(imageDataSubplot[subplot] || []); + + imagesOnSubplot.enter().append('image'); + imagesOnSubplot.exit().remove(); + + imagesOnSubplot.each(function(d) { + setImage.bind(this)(d); + applyAttributes.bind(this)(d); + }); + } }; diff --git a/src/components/rangeslider/draw.js b/src/components/rangeslider/draw.js index 10e07a06992..fac1e9334ef 100644 --- a/src/components/rangeslider/draw.js +++ b/src/components/rangeslider/draw.js @@ -400,10 +400,6 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) { } Cartesian.rangePlot(gd, plotinfo, filterRangePlotCalcData(calcData, id)); - - // no need for the bg layer, - // drawBg handles coloring the background - if(isMainPlot) plotinfo.bg.remove(); }); } diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index fc019c7a1f8..86da0b6fc53 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -42,7 +42,7 @@ function draw(gd) { // Remove previous shapes before drawing new in shapes in fullLayout.shapes fullLayout._shapeUpperLayer.selectAll('path').remove(); fullLayout._shapeLowerLayer.selectAll('path').remove(); - fullLayout._shapeSubplotLayer.selectAll('path').remove(); + fullLayout._shapeSubplotLayers.selectAll('path').remove(); for(var i = 0; i < fullLayout.shapes.length; i++) { if(fullLayout.shapes[i].visible) { @@ -55,8 +55,6 @@ function draw(gd) { } function drawOne(gd, index) { - var i, n; - // remove the existing shape if there is one. // because indices can change, we need to look in all shape layers gd._fullLayout._paper @@ -70,28 +68,17 @@ function drawOne(gd, index) { // TODO: use d3 idioms instead of deleting and redrawing every time if(!optionsIn || options.visible === false) return; - var clipAxes; if(options.layer !== 'below') { - clipAxes = (options.xref + options.yref).replace(/paper/g, ''); drawShape(gd._fullLayout._shapeUpperLayer); } - else if(options.xref === 'paper' && options.yref === 'paper') { - clipAxes = ''; + else if(options.xref === 'paper' || options.yref === 'paper') { drawShape(gd._fullLayout._shapeLowerLayer); } else { - var plots = gd._fullLayout._plots || {}, - subplots = Object.keys(plots), - plotinfo; - - for(i = 0, n = subplots.length; i < n; i++) { - plotinfo = plots[subplots[i]]; - clipAxes = subplots[i]; + var plotinfo = gd._fullLayout._plots[options.xref + options.yref], + mainPlot = plotinfo.mainplot || plotinfo; - if(isShapeInSubplot(gd, options, plotinfo)) { - drawShape(plotinfo.shapelayer); - } - } + drawShape(mainPlot.shapelayer); } function drawShape(shapeLayer) { @@ -110,10 +97,15 @@ function drawOne(gd, index) { .call(Color.fill, options.fillcolor) .call(Drawing.dashLine, options.line.dash, options.line.width); - if(clipAxes) { - path.call(Drawing.setClipUrl, - 'clip' + gd._fullLayout._uid + clipAxes); - } + // note that for layer="below" the clipAxes can be different from the + // subplot we're drawing this in. This could cause problems if the shape + // spans two subplots. See https://github.com/plotly/plotly.js/issues/1452 + var clipAxes = (options.xref + options.yref).replace(/paper/g, ''); + + path.call(Drawing.setClipUrl, clipAxes ? + ('clip' + gd._fullLayout._uid + clipAxes) : + null + ); if(gd._context.editable) setupDragElement(gd, path, options, index); } @@ -271,15 +263,6 @@ function setupDragElement(gd, shapePath, shapeOptions, index) { } } -function isShapeInSubplot(gd, shape, plotinfo) { - var xa = Axes.getFromId(gd, plotinfo.id, 'x')._id, - ya = Axes.getFromId(gd, plotinfo.id, 'y')._id, - isBelow = shape.layer === 'below', - inSuplotAxis = (xa === shape.xref || ya === shape.yref), - isNotAnOverlaidSubplot = !!plotinfo.shapelayer; - return isBelow && inSuplotAxis && isNotAnOverlaidSubplot; -} - function getPathString(gd, options) { var type = options.type, xa = Axes.getFromId(gd, options.xref), diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index b8aad370b5a..53fe0fe56cf 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -308,8 +308,7 @@ Plotly.plot = function(gd, data, layout, config) { // keep reference to shape layers in subplots var layerSubplot = fullLayout._paper.selectAll('.layer-subplot'); - fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer'); - fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer'); + fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer'); // styling separate from drawing Plots.style(gd); @@ -2855,11 +2854,20 @@ function makePlotFramework(gd) { fullLayout._topdefs = fullLayout._toppaper.append('defs') .attr('id', 'topdefs-' + fullLayout._uid); + fullLayout._bgLayer = fullLayout._paper.append('g') + .classed('bglayer', true); + fullLayout._draggers = fullLayout._paper.append('g') .classed('draglayer', true); - // lower shape layer - // (only for shapes to be drawn below the whole plot) + // lower shape/image layer - note that this is behind + // all subplots data/grids but above the backgrounds + // except inset subplots, whose backgrounds are drawn + // inside their own group so that they appear above + // the data for the main subplot + // lower shapes and images which are fully referenced to + // a subplot still get drawn within the subplot's group + // so they will work correctly on insets var layerBelow = fullLayout._paper.append('g') .classed('layer-below', true); fullLayout._imageLowerLayer = layerBelow.append('g') diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 48b4962bd07..f0a81f76af8 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -9,6 +9,7 @@ 'use strict'; +var d3 = require('d3'); var Plotly = require('../plotly'); var Registry = require('../registry'); var Plots = require('../plots/plots'); @@ -24,6 +25,21 @@ exports.layoutStyles = function(gd) { return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd); }; +function overlappingDomain(xDomain, yDomain, domains) { + for(var i = 0; i < domains.length; i++) { + var existingX = domains[i][0], + existingY = domains[i][1]; + + if(existingX[0] >= xDomain[1] || existingX[1] <= xDomain[0]) { + continue; + } + if(existingY[0] < yDomain[1] && existingY[1] > yDomain[0]) { + return true; + } + } + return false; +} + exports.lsInner = function(gd) { var fullLayout = gd._fullLayout, gs = fullLayout._size, @@ -43,8 +59,73 @@ exports.lsInner = function(gd) { gd._context.setBackground(gd, fullLayout.paper_bgcolor); + var subplotSelection = fullLayout._paper.selectAll('g.subplot'); + + // figure out which backgrounds we need to draw, and in which layers + // to put them + var lowerBackgroundIDs = []; + var lowerDomains = []; + subplotSelection.each(function(subplot) { + var plotinfo = fullLayout._plots[subplot]; + + if(plotinfo.mainplot) { + // mainplot is a reference to the main plot this one is overlaid on + // so if it exists, this is an overlaid plot and we don't need to + // give it its own background + if(plotinfo.bg) { + plotinfo.bg.remove(); + } + plotinfo.bg = undefined; + return; + } + + var xa = Plotly.Axes.getFromId(gd, subplot, 'x'), + ya = Plotly.Axes.getFromId(gd, subplot, 'y'), + xDomain = xa.domain, + yDomain = ya.domain, + plotgroupBgData = []; + + if(overlappingDomain(xDomain, yDomain, lowerDomains)) { + plotgroupBgData = [0]; + } + else { + lowerBackgroundIDs.push(subplot); + lowerDomains.push([xDomain, yDomain]); + } + + // create the plot group backgrounds now, since + // they're all independent selections + var plotgroupBg = plotinfo.plotgroup.selectAll('.bg') + .data(plotgroupBgData); + + plotgroupBg.enter().append('rect') + .classed('bg', true); + + plotgroupBg.exit().remove(); + + plotgroupBg.each(function() { + plotinfo.bg = plotgroupBg; + var pgNode = plotinfo.plotgroup.node(); + pgNode.insertBefore(this, pgNode.childNodes[0]); + }); + }); + + // now create all the lower-layer backgrounds at once now that + // we have the list of subplots that need them + var lowerBackgrounds = fullLayout._bgLayer.selectAll('.bg') + .data(lowerBackgroundIDs); + + lowerBackgrounds.enter().append('rect') + .classed('bg', true); + + lowerBackgrounds.exit().remove(); + + lowerBackgrounds.each(function(subplot) { + fullLayout._plots[subplot].bg = d3.select(this); + }); + var freefinished = []; - fullLayout._paper.selectAll('g.subplot').each(function(subplot) { + subplotSelection.each(function(subplot) { var plotinfo = fullLayout._plots[subplot], xa = Plotly.Axes.getFromId(gd, subplot, 'x'), ya = Plotly.Axes.getFromId(gd, subplot, 'y'); @@ -58,7 +139,8 @@ exports.lsInner = function(gd) { .call(Drawing.setRect, xa._offset - gs.p, ya._offset - gs.p, xa._length + 2 * gs.p, ya._length + 2 * gs.p) - .call(Color.fill, fullLayout.plot_bgcolor); + .call(Color.fill, fullLayout.plot_bgcolor) + .style('stroke-width', 0); } // Clip so that data only shows up on the plot area. diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 4c91f281e10..41a626bd2b0 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -300,9 +300,6 @@ function makeSubplotLayer(plotinfo) { } if(!plotinfo.mainplot) { - plotinfo.bg = joinLayer(plotgroup, 'rect', 'bg'); - plotinfo.bg.style('stroke-width', 0); - var backLayer = joinLayer(plotgroup, 'g', 'layer-subplot'); plotinfo.shapelayer = joinLayer(backLayer, 'g', 'shapelayer'); plotinfo.imagelayer = joinLayer(backLayer, 'g', 'imagelayer'); diff --git a/test/image/baselines/dendrogram.png b/test/image/baselines/dendrogram.png index 934580604b5..92de5e5cbf4 100644 Binary files a/test/image/baselines/dendrogram.png and b/test/image/baselines/dendrogram.png differ diff --git a/test/image/baselines/shapes_below_traces.png b/test/image/baselines/shapes_below_traces.png index 77b62206fbf..bd8874feb65 100644 Binary files a/test/image/baselines/shapes_below_traces.png and b/test/image/baselines/shapes_below_traces.png differ diff --git a/test/image/mocks/layout_image.json b/test/image/mocks/layout_image.json index a4af1a99804..ab2307ebd60 100644 --- a/test/image/mocks/layout_image.json +++ b/test/image/mocks/layout_image.json @@ -11,6 +11,7 @@ } ], "layout": { + "plot_bgcolor": "rgba(0,0,0,0)", "xaxis2": { "anchor": "y2" }, @@ -19,7 +20,8 @@ }, "yaxis2": { "domain": [0.55, 1], - "type": "log" + "type": "log", + "anchor": "x2" }, "images": [ { diff --git a/test/jasmine/tests/cartesian_test.js b/test/jasmine/tests/cartesian_test.js index 236caa07eff..b634ffa3081 100644 --- a/test/jasmine/tests/cartesian_test.js +++ b/test/jasmine/tests/cartesian_test.js @@ -7,6 +7,7 @@ var Drawing = require('@src/components/drawing'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var mouseEvent = require('../assets/mouse_event'); +var failTest = require('../assets/fail_test'); describe('zoom box element', function() { @@ -111,7 +112,9 @@ describe('restyle', function() { }).then(function() { expect(d3.selectAll('g.trace.scatter').size()).toEqual(0); - }).then(done); + }) + .catch(failTest) + .then(done); }); it('reuses SVG lines', function(done) { @@ -149,7 +152,9 @@ describe('restyle', function() { // Second line was persisted: expect(firstLine2).toBe(secondLine2); - }).then(done); + }) + .catch(failTest) + .then(done); }); it('can change scatter mode', function(done) { @@ -194,6 +199,7 @@ describe('restyle', function() { .then(function() { assertScatterModeSizes(3, 9, 9); }) + .catch(failTest) .then(done); }); @@ -246,9 +252,9 @@ describe('relayout', function() { expect(gd._fullLayout.xaxis.categoryarray).toEqual(list); expect(gd._fullLayout.xaxis._initialCategories).toEqual(list); assertCategories(list); - - done(); - }); + }) + .catch(failTest) + .then(done); }); }); @@ -300,6 +306,7 @@ describe('relayout', function() { .then(function() { assertPointTranslate([-540, 135], [-540, 135]); }) + .catch(failTest) .then(done); }); @@ -308,6 +315,11 @@ describe('relayout', function() { }); describe('subplot creation / deletion:', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); @@ -322,13 +334,13 @@ describe('subplot creation / deletion:', function() { expect(d3.select('.ytitle').size()).toEqual(len); } - Plotly.plot(createGraphDiv(), [], { + Plotly.plot(gd, [], { xaxis: { title: 'X' }, yaxis: { title: 'Y' }, xaxis2: { title: 'X2', anchor: 'y2' }, yaxis2: { title: 'Y2', anchor: 'x2' } }) - .then(function(gd) { + .then(function() { assertCartesianSubplot(1); return Plotly.addTraces(gd, [{ @@ -340,6 +352,70 @@ describe('subplot creation / deletion:', function() { .then(function() { assertCartesianSubplot(0); }) + .catch(failTest) + .then(done); + }); + + it('puts plot backgrounds behind everything except if they overlap', function(done) { + function checkBGLayers(behindCount, x2y2Count) { + expect(gd.querySelectorAll('.bglayer rect.bg').length).toBe(behindCount); + expect(gd.querySelectorAll('.subplot.x2y2 rect.bg').length).toBe(x2y2Count); + + // xy is the first subplot, so it never gets put in front of others + expect(gd.querySelectorAll('.subplot.xy rect.bg').length).toBe(0); + + // xy3 is an overlay, so never gets its own bg + expect(gd.querySelectorAll('.subplot.xy3 rect.bg').length).toBe(0); + + // verify that these are *all* the subplots and backgrounds we have + expect(gd.querySelectorAll('.subplot').length).toBe(3); + ['xy', 'x2y2', 'xy3'].forEach(function(subplot) { + expect(gd.querySelectorAll('.subplot.' + subplot).length).toBe(1); + }); + expect(gd.querySelectorAll('.bg').length).toBe(behindCount + x2y2Count); + } + + Plotly.plot(gd, [ + {y: [1, 2, 3]}, + {y: [2, 3, 1], xaxis: 'x2', yaxis: 'y2'}, + {y: [3, 1, 2], yaxis: 'y3'} + ], { + xaxis: {domain: [0, 0.5]}, + xaxis2: {domain: [0.5, 1], anchor: 'y2'}, + yaxis: {domain: [0, 1]}, + yaxis2: {domain: [0.5, 1], anchor: 'x2'}, + yaxis3: {overlaying: 'y'}, + // legend makes its own .bg rect - delete so we can ignore that here + showlegend: false + }) + .then(function() { + // touching but not overlapping: all backgrounds are in back + checkBGLayers(2, 0); + + // now add a slight overlap: that's enough to put x2y2 in front + return Plotly.relayout(gd, {'xaxis2.domain': [0.49, 1]}); + }) + .then(function() { + checkBGLayers(1, 1); + + // x ranges overlap, but now y ranges are disjoint + return Plotly.relayout(gd, {'xaxis2.domain': [0, 1], 'yaxis.domain': [0, 0.5]}); + }) + .then(function() { + checkBGLayers(2, 0); + + // regular inset + return Plotly.relayout(gd, { + 'xaxis.domain': [0, 1], + 'yaxis.domain': [0, 1], + 'xaxis2.domain': [0.6, 0.9], + 'yaxis2.domain': [0.6, 0.9] + }); + }) + .then(function() { + checkBGLayers(1, 1); + }) + .catch(failTest) .then(done); }); }); diff --git a/test/jasmine/tests/layout_images_test.js b/test/jasmine/tests/layout_images_test.js index e38cab663f7..f571a4e969c 100644 --- a/test/jasmine/tests/layout_images_test.js +++ b/test/jasmine/tests/layout_images_test.js @@ -90,17 +90,28 @@ describe('Layout images', function() { afterEach(destroyGraphDiv); - it('should draw images on the right layers', function() { + function checkLayers(upper, lower, subplot) { + var upperLayer = gd._fullLayout._imageUpperLayer; + expect(upperLayer.size()).toBe(1); + expect(upperLayer.selectAll('image').size()).toBe(upper); + + var lowerLayer = gd._fullLayout._imageLowerLayer; + expect(lowerLayer.size()).toBe(1); + expect(lowerLayer.selectAll('image').size()).toBe(lower); + + var subplotLayer = gd._fullLayout._plots.xy.imagelayer; + expect(subplotLayer.size()).toBe(1); + expect(subplotLayer.selectAll('image').size()).toBe(subplot); + } - var layer; + it('should draw images on the right layers', function() { Plotly.plot(gd, data, { images: [{ source: 'imageabove', layer: 'above' }]}); - layer = gd._fullLayout._imageUpperLayer; - expect(layer.length).toBe(1); + checkLayers(1, 0, 0); destroyGraphDiv(); gd = createGraphDiv(); @@ -109,8 +120,7 @@ describe('Layout images', function() { layer: 'below' }]}); - layer = gd._fullLayout._imageLowerLayer; - expect(layer.length).toBe(1); + checkLayers(0, 1, 0); destroyGraphDiv(); gd = createGraphDiv(); @@ -121,8 +131,7 @@ describe('Layout images', function() { yref: 'y' }]}); - layer = gd._fullLayout._imageSubplotLayer; - expect(layer.length).toBe(1); + checkLayers(0, 0, 1); }); describe('with anchors and sizing', function() { @@ -307,7 +316,7 @@ describe('Layout images', function() { afterEach(destroyGraphDiv); - it('should properly add and removing image', function(done) { + it('should properly add and remove image', function(done) { var gd = createGraphDiv(), data = [{ x: [1, 2, 3], y: [1, 2, 3] }], layout = { width: 500, height: 400 }; @@ -425,23 +434,38 @@ describe('images log/linear axis changes', function() { // we don't try to figure out the position on a new axis / canvas // automatically when you change xref / yref, we leave it to the caller. + // initial clip path should end in 'xy' to match xref/yref + expect(d3.select('image').attr('clip-path') || '').toMatch(/xy\)$/); + // linear to log Plotly.relayout(gd, {'images[0].yref': 'y2'}) .then(function() { expect(gd.layout.images[0].y).toBe(1); + expect(d3.select('image').attr('clip-path') || '').toMatch(/xy2\)$/); + // log to paper return Plotly.relayout(gd, {'images[0].yref': 'paper'}); }) .then(function() { expect(gd.layout.images[0].y).toBe(1); + expect(d3.select('image').attr('clip-path') || '').toMatch(/x\)$/); + + // change to full paper-referenced, to make sure the clip path disappears + return Plotly.relayout(gd, {'images[0].xref': 'paper'}); + }) + .then(function() { + expect(d3.select('image').attr('clip-path')).toBe(null); + // paper to log return Plotly.relayout(gd, {'images[0].yref': 'y2'}); }) .then(function() { expect(gd.layout.images[0].y).toBe(1); + expect(d3.select('image').attr('clip-path') || '').toMatch(/^[^x]+y2\)$/); + // log to linear return Plotly.relayout(gd, {'images[0].yref': 'y'}); }) diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index 7b35e5c0a61..040ab964961 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -382,6 +382,71 @@ describe('Test shapes:', function() { }); }); +describe('shapes axis reference changes', function() { + 'use strict'; + + var gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + + Plotly.plot(gd, [ + {y: [1, 2, 3]}, + {y: [1, 2, 3], yaxis: 'y2'} + ], { + yaxis: {domain: [0, 0.4]}, + yaxis2: {domain: [0.6, 1]}, + shapes: [{ + xref: 'x', yref: 'paper', type: 'rect', + x0: 0.8, x1: 1.2, y0: 0, y1: 1, + fillcolor: '#eee', layer: 'below' + }] + }).then(done); + }); + + afterEach(destroyGraphDiv); + + function getShape(index) { + var s = d3.selectAll('path[data-index="' + index + '"]'); + expect(s.size()).toBe(1); + return s; + } + + it('draws the right number of objects and updates clip-path correctly', function(done) { + + expect(getShape(0).attr('clip-path') || '').toMatch(/x\)$/); + + Plotly.relayout(gd, { + 'shapes[0].xref': 'paper', + 'shapes[0].x0': 0.2, + 'shapes[0].x1': 0.6 + }) + .then(function() { + expect(getShape(0).attr('clip-path')).toBe(null); + + return Plotly.relayout(gd, { + 'shapes[0].yref': 'y2', + 'shapes[0].y0': 1.8, + 'shapes[0].y1': 2.2, + }); + }) + .then(function() { + expect(getShape(0).attr('clip-path') || '').toMatch(/^[^x]+y2\)$/); + + return Plotly.relayout(gd, { + 'shapes[0].xref': 'x', + 'shapes[0].x0': 1.5, + 'shapes[0].x1': 20 + }); + }) + .then(function() { + expect(getShape(0).attr('clip-path') || '').toMatch(/xy2\)$/); + }) + .catch(failTest) + .then(done); + }); +}); + describe('shapes autosize', function() { 'use strict';