From a8aa1947c21c5833c20f1fe57f23b8030100f8d0 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 18 Apr 2016 13:41:32 +0100 Subject: [PATCH 1/4] Add `layout.shapes.layer` * Added functionality to show layer below traces. * The axis references of a 'below'-shape determine the subplots under which a shape is shown. * If both axis references of a 'below'-shape are set to 'paper', then the shape is shown below all the subplots. * Updated `test/image/mocks/shapes.json` to exercise the new functionality. * Updated `test/jasmine/tests/shapes_test.js` to account for the new shape layers. --- src/components/shapes/attributes.js | 8 ++ src/components/shapes/index.js | 87 +++++++++++++++---- src/plot_api/plot_api.js | 23 ++++- test/image/mocks/shapes.json | 8 +- test/jasmine/tests/shapes_test.js | 129 +++++++++++++++++++++++----- 5 files changed, 210 insertions(+), 45 deletions(-) diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index 1958f8d8efc..39ba21ba3a1 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -38,6 +38,14 @@ module.exports = { ].join(' ') }, + layer: { + valType: 'enumerated', + values: ['below', 'above'], + dflt: 'above', + role: 'info', + description: 'Specifies whether shapes are drawn below or above traces.' + }, + xref: extendFlat({}, annAttrs.xref, { description: [ 'Sets the shape\'s x coordinate axis.', diff --git a/src/components/shapes/index.js b/src/components/shapes/index.js index 0f02067b20f..f39de48e23e 100644 --- a/src/components/shapes/index.js +++ b/src/components/shapes/index.js @@ -38,6 +38,7 @@ function handleShapeDefaults(shapeIn, fullLayout) { return Lib.coerce(shapeIn, shapeOut, shapes.layoutAttributes, attr, dflt); } + coerce('layer'); coerce('opacity'); coerce('fillcolor'); coerce('line.color'); @@ -171,7 +172,8 @@ function updateAllShapes(gd, opt, value) { } function deleteShape(gd, index) { - gd._fullLayout._shapelayer.selectAll('[data-index="' + index + '"]') + getShapeLayer(gd, index) + .selectAll('[data-index="' + index + '"]') .remove(); gd._fullLayout.shapes.splice(index, 1); @@ -181,9 +183,9 @@ function deleteShape(gd, index) { for(var i = index; i < gd._fullLayout.shapes.length; i++) { // redraw all shapes past the removed one, // so they bind to the right events - gd._fullLayout._shapelayer - .selectAll('[data-index="' + (i+1) + '"]') - .attr('data-index', String(i)); + getShapeLayer(gd, i) + .selectAll('[data-index="' + (i + 1) + '"]') + .attr('data-index', i); shapes.draw(gd, i); } } @@ -201,10 +203,13 @@ function insertShape(gd, index, newShape) { gd.layout.shapes = [rule]; } + // there is no need to call shapes.draw(gd, index), + // because updateShape() is called from within shapes.draw() + for(var i = gd._fullLayout.shapes.length - 1; i > index; i--) { - gd._fullLayout._shapelayer + getShapeLayer(gd, i) .selectAll('[data-index="' + (i - 1) + '"]') - .attr('data-index', String(i)); + .attr('data-index', i); shapes.draw(gd, i); } } @@ -213,7 +218,8 @@ function updateShape(gd, index, opt, value) { var i; // remove the existing shape if there is one - gd._fullLayout._shapelayer.selectAll('[data-index="' + index + '"]') + getShapeLayer(gd, index) + .selectAll('[data-index="' + index + '"]') .remove(); // remember a few things about what was already there, @@ -288,7 +294,7 @@ function updateShape(gd, index, opt, value) { gd._fullLayout.shapes[index] = options; var attrs = { - 'data-index': String(index), + 'data-index': index, 'fill-rule': 'evenodd', d: shapePath(gd, options) }, @@ -296,15 +302,64 @@ function updateShape(gd, index, opt, value) { var lineColor = options.line.width ? options.line.color : 'rgba(0,0,0,0)'; - var path = gd._fullLayout._shapelayer.append('path') - .attr(attrs) - .style('opacity', options.opacity) - .call(Color.stroke, lineColor) - .call(Color.fill, options.fillcolor) - .call(Drawing.dashLine, options.line.dash, options.line.width); + if(options.layer !== 'below') { + drawShape(gd._fullLayout._shapeUpperLayer); + } + else if(options.xref === 'paper' && options.yref === 'paper') { + drawShape(gd._fullLayout._shapeLowerLayer); + } else { + forEachSubplot(gd, function(plotinfo) { + if(isShapeInSubplot(gd, options, plotinfo.id)) { + drawShape(plotinfo.shapelayer); + } + }); + } + + return; + + function drawShape(shapeLayer) { + var path = shapeLayer.append('path') + .attr(attrs) + .style('opacity', options.opacity) + .call(Color.stroke, lineColor) + .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); + } + } +} + +function getShapeLayer(gd, index) { + var shape = gd._fullLayout.shapes[index], + shapeLayer = gd._fullLayout._shapeUpperLayer; + + if(!shape) { + console.log('getShapeLayer: undefined shape: index', index); + } + else if(shape.layer === 'below') { + shapeLayer = (shape.xref === 'paper' && shape.yref === 'paper') ? + gd._fullLayout._shapeLowerLayer : + gd._fullLayout._subplotShapeLayer; + } + + return shapeLayer; +} + +function isShapeInSubplot(gd, shape, subplot) { + var xa = Plotly.Axes.getFromId(gd, subplot, 'x')._id, + ya = Plotly.Axes.getFromId(gd, subplot, 'y')._id; + return shape.layer === 'below' && (xa === shape.xref || ya === shape.yref); +} + +function forEachSubplot(gd, fn) { + var plots = gd._fullLayout._plots || {}, + subplots = Object.getOwnPropertyNames(plots); - if(clipAxes) { - path.call(Drawing.setClipUrl, 'clip' + gd._fullLayout._uid + clipAxes); + for(var i = 0, n = subplots.length; i < n; i++) { + fn(plots[subplots[i]]); } } diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index b283399f3ca..f97e613973e 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2615,6 +2615,11 @@ function makePlotFramework(gd) { fullLayout._draggers = fullLayout._paper.append('g') .classed('draglayer', true); + // lower shape layer + // (only for shapes to be drawn below the whole plot) + fullLayout._shapeLowerLayer = fullLayout._paper.append('g') + .classed('shapelayer shapelayer-below', true); + var subplots = Plotly.Axes.getSubplots(gd); if(subplots.join('') !== Object.keys(gd._fullLayout._plots || {}).join('')) { makeSubplots(gd, subplots); @@ -2622,9 +2627,15 @@ function makePlotFramework(gd) { if(fullLayout._hasCartesian) makeCartesianPlotFramwork(gd, subplots); - // single ternary, shape and pie layers for the whole plot + // single ternary layer for the whole plot fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true); - fullLayout._shapelayer = fullLayout._paper.append('g').classed('shapelayer', true); + + // upper shape layer + // (only for shapes to be drawn above the whole plot, including subplots) + fullLayout._shapeUpperLayer = fullLayout._paper.append('g') + .classed('shapelayer shapelayer-above', true); + + // single pie layer for the whole plot fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true); // fill in image server scrape-svg @@ -2752,6 +2763,10 @@ function makeCartesianPlotFramwork(gd, subplots) { // the plot and containers for overlays plotinfo.bg = plotgroup.append('rect') .style('stroke-width', 0); + // shape layer + // (only for shapes to be drawn below a subplot) + plotinfo.shapelayer = plotgroup.append('g') + .classed('shapelayer shapelayer-subplot', true); plotinfo.gridlayer = plotgroup.append('g'); plotinfo.overgrid = plotgroup.append('g'); plotinfo.zerolinelayer = plotgroup.append('g'); @@ -2800,6 +2815,10 @@ function makeCartesianPlotFramwork(gd, subplots) { .style('fill', 'none') .classed('crisp', true); }); + + // shape layers in subplots + fullLayout._subplotShapeLayer = fullLayout._paper + .selectAll('.shapelayer-subplot'); } // layoutStyles: styling for plot layout elements diff --git a/test/image/mocks/shapes.json b/test/image/mocks/shapes.json index 8ad07392f4c..86241c110d9 100644 --- a/test/image/mocks/shapes.json +++ b/test/image/mocks/shapes.json @@ -21,18 +21,18 @@ "margin": {"l":20,"r":20,"top":10,"bottom":10,"pad":0}, "showlegend":false, "shapes":[ - {"xref":"paper","yref":"paper","x0":0,"x1":0.1,"y0":0,"y1":0.1}, + {"layer":"below","xref":"paper","yref":"paper","x0":0,"x1":0.1,"y0":0,"y1":0.1}, {"xref":"paper","yref":"paper","path":"M0,0.2V0.3H0.05L0,0.4Q0.1,0.4 0.1,0.3T0.15,0.3C0.1,0.4 0.2,0.4 0.2,0.3S0.15,0.3 0.15,0.2Z","fillcolor":"#4c0"}, {"xref":"paper","yref":"paper","type":"circle","x0":0.23,"x1":0.3,"y0":0.2,"y1":0.4}, {"xref":"paper","yref":"paper","type":"line","x0":0.2,"x1":0.3,"y0":0,"y1":0.1}, - {"x0":0.1,"x1":0.4,"y0":1.5,"y1":20,"opacity":0.5,"fillcolor":"#f00","line":{"width":8,"color":"#008","dash":"dashdot"}}, + {"layer":"below","x0":0.1,"x1":0.4,"y0":1.5,"y1":20,"opacity":0.5,"fillcolor":"#f00","line":{"width":8,"color":"#008","dash":"dashdot"}}, {"path":"M0.5,3C0.5,9 0.9,9 0.9,3C0.9,1 0.5,1 0.5,3ZM0.6,4C0.6,5 0.66,5 0.66,4ZM0.74,4C0.74,5 0.8,5 0.8,4ZM0.6,3C0.63,2 0.77,2 0.8,3Z","fillcolor":"#fd2","line":{"width":1,"color":"black"}}, - {"xref":"x2","yref":"y2","type":"circle","x0":"2000-01-01 02","x1":"2000-01-01 08:30:33.456","y0":0.1,"y1":0.9,"fillcolor":"rgba(0,0,0,0.5)","line":{"color":"rgba(0,255,0,0.5)", "width":5}}, + {"layer":"below","xref":"x2","yref":"y2","type":"circle","x0":"2000-01-01 02","x1":"2000-01-01 08:30:33.456","y0":0.1,"y1":0.9,"fillcolor":"rgba(0,0,0,0.5)","line":{"color":"rgba(0,255,0,0.5)", "width":5}}, {"xref":"x2","yref":"y2","path":"M2000-01-01_11:20:45.6,0.2Q2000-01-01_10:00,0.85 2000-01-01_21,0.8Q2000-01-01_22:20,0.15 2000-01-01_11:20:45.6,0.2Z","fillcolor":"rgb(151,73,58)"}, {"yref":"paper","type":"line","x0":0.1,"x1":0.4,"y0":0,"y1":0.4,"line":{"color":"#009","dash":"dot","width":1}}, {"yref":"paper","path":"M0.5,0H1.1L0.8,0.4Z","line":{"width":0},"fillcolor":"#ccd3ff"}, {"xref":"paper","x0":0.1,"x1":0.2,"y0":-1,"y1":3,"fillcolor":"#ccc"}, - {"xref":"paper","path":"M0.05,4C0.4,12 -0.1,12 0.25,4Z","fillcolor":"#a66"} + {"layer":"above","xref":"paper","path":"M0.05,4C0.4,12 -0.1,12 0.25,4Z","fillcolor":"#a66"} ] } } diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index 05ab310d9bb..24da4ba8328 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -24,30 +24,114 @@ describe('Test shapes:', function() { afterEach(destroyGraphDiv); - function countShapeLayers() { - return d3.selectAll('.shapelayer').size(); + function countShapesInLowerLayer() { + return gd._fullLayout.shapes.filter(isShapeInLowerLayer).length; } - function countShapePaths() { - return d3.selectAll('.shapelayer > path').size(); + function countShapesInUpperLayer() { + return gd._fullLayout.shapes.filter(isShapeInUpperLayer).length; } - describe('DOM', function() { - it('has one *shapelayer* node', function() { - expect(countShapeLayers()).toEqual(1); + function countShapesInSubplots() { + return gd._fullLayout.shapes.filter(isShapeInSubplot).length; + } + + function isShapeInUpperLayer(shape) { + return shape.layer !== 'below'; + } + + function isShapeInLowerLayer(shape) { + return (shape.xref === 'paper' && shape.yref === 'paper') && + !isShapeInUpperLayer(shape); + } + + function isShapeInSubplot(shape) { + return !isShapeInUpperLayer(shape) && !isShapeInLowerLayer(shape); + } + + function countShapeLowerLayerNodes() { + return d3.selectAll('.shapelayer-below').size(); + } + + function countShapeUpperLayerNodes() { + return d3.selectAll('.shapelayer-above').size(); + } + + function countShapeLayerNodesInSubplots() { + return d3.selectAll('.shapelayer-subplot').size(); + } + + function countSubplots(gd) { + return Object.getOwnPropertyNames(gd._fullLayout._plots || {}).length; + } + + function countShapePathsInLowerLayer() { + return d3.selectAll('.shapelayer-below > path').size(); + } + + function countShapePathsInUpperLayer() { + return d3.selectAll('.shapelayer-above > path').size(); + } + + function countShapePathsInSubplots() { + return d3.selectAll('.shapelayer-subplot > path').size(); + } + + describe('*shapeLowerLayer*', function() { + it('has one node', function() { + expect(countShapeLowerLayerNodes()).toEqual(1); + }); + + it('has as many *path* nodes as shapes in the lower layer', function() { + expect(countShapePathsInLowerLayer()) + .toEqual(countShapesInLowerLayer()); + }); + + it('should be able to get relayout', function(done) { + Plotly.relayout(gd, {height: 200, width: 400}).then(function() { + expect(countShapeLowerLayerNodes()).toEqual(1); + expect(countShapePathsInLowerLayer()) + .toEqual(countShapesInLowerLayer()); + }).then(done); + }); + }); + + describe('*shapeUpperLayer*', function() { + it('has one node', function() { + expect(countShapeUpperLayerNodes()).toEqual(1); }); - it('has as many *path* nodes as there are shapes', function() { - expect(countShapePaths()).toEqual(mock.layout.shapes.length); + it('has as many *path* nodes as shapes in the upper layer', function() { + expect(countShapePathsInUpperLayer()) + .toEqual(countShapesInUpperLayer()); }); it('should be able to get relayout', function(done) { - expect(countShapeLayers()).toEqual(1); - expect(countShapePaths()).toEqual(mock.layout.shapes.length); + Plotly.relayout(gd, {height: 200, width: 400}).then(function() { + expect(countShapeUpperLayerNodes()).toEqual(1); + expect(countShapePathsInUpperLayer()) + .toEqual(countShapesInUpperLayer()); + }).then(done); + }); + }); + describe('each *subplot*', function() { + it('has one *shapelayer*', function() { + expect(countShapeLayerNodesInSubplots()) + .toEqual(countSubplots(gd)); + }); + + it('has as many *path* nodes as shapes in the subplot', function() { + expect(countShapePathsInSubplots()) + .toEqual(countShapesInSubplots()); + }); + + it('should be able to get relayout', function(done) { Plotly.relayout(gd, {height: 200, width: 400}).then(function() { - expect(countShapeLayers()).toEqual(1); - expect(countShapePaths()).toEqual(mock.layout.shapes.length); + expect(countShapeLayerNodesInSubplots()) + .toEqual(countSubplots(gd)); + expect(countShapePathsInSubplots()) + .toEqual(countShapesInSubplots()); }).then(done); }); }); @@ -75,33 +159,32 @@ describe('Test shapes:', function() { describe('Plotly.relayout', function() { it('should be able to add a shape', function(done) { - var pathCount = countShapePaths(); + var pathCount = countShapePathsInUpperLayer(); var index = countShapes(gd); var shape = getRandomShape(); - Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() { - expect(countShapeLayers()).toEqual(1); - expect(countShapePaths()).toEqual(pathCount + 1); + Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() + { + expect(countShapePathsInUpperLayer()).toEqual(pathCount + 1); expect(getLastShape(gd)).toEqual(shape); expect(countShapes(gd)).toEqual(index + 1); }).then(done); }); it('should be able to remove a shape', function(done) { - var pathCount = countShapePaths(); + var pathCount = countShapePathsInUpperLayer(); var index = countShapes(gd); var shape = getRandomShape(); - Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() { - expect(countShapeLayers()).toEqual(1); - expect(countShapePaths()).toEqual(pathCount + 1); + Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() + { + expect(countShapePathsInUpperLayer()).toEqual(pathCount + 1); expect(getLastShape(gd)).toEqual(shape); expect(countShapes(gd)).toEqual(index + 1); }).then(function() { Plotly.relayout(gd, 'shapes[' + index + ']', 'remove'); }).then(function() { - expect(countShapeLayers()).toEqual(1); - expect(countShapePaths()).toEqual(pathCount); + expect(countShapePathsInUpperLayer()).toEqual(pathCount); expect(countShapes(gd)).toEqual(index); }).then(done); }); From 7d85bfe0968b3b16c8d09326379a4e857ec95b20 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 19 Apr 2016 10:15:10 +0100 Subject: [PATCH 2/4] Ensure shapes use the right clip-path * Fixed bug that caused shapes drawn in a subplot to extend beyond the limits of the subplot. * Fixed issues with code styling. --- src/components/shapes/index.js | 31 +++++++++++++++---------------- test/jasmine/tests/shapes_test.js | 8 +++----- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/components/shapes/index.js b/src/components/shapes/index.js index f39de48e23e..0136143faf9 100644 --- a/src/components/shapes/index.js +++ b/src/components/shapes/index.js @@ -215,7 +215,7 @@ function insertShape(gd, index, newShape) { } function updateShape(gd, index, opt, value) { - var i; + var i, n; // remove the existing shape if there is one getShapeLayer(gd, index) @@ -298,25 +298,33 @@ function updateShape(gd, index, opt, value) { 'fill-rule': 'evenodd', d: shapePath(gd, options) }, - clipAxes = (options.xref + options.yref).replace(/paper/g, ''); + clipAxes; var lineColor = options.line.width ? options.line.color : 'rgba(0,0,0,0)'; 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 = ''; drawShape(gd._fullLayout._shapeLowerLayer); - } else { - forEachSubplot(gd, function(plotinfo) { + } + 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]; + if(isShapeInSubplot(gd, options, plotinfo.id)) { drawShape(plotinfo.shapelayer); } - }); + } } - return; - function drawShape(shapeLayer) { var path = shapeLayer.append('path') .attr(attrs) @@ -354,15 +362,6 @@ function isShapeInSubplot(gd, shape, subplot) { return shape.layer === 'below' && (xa === shape.xref || ya === shape.yref); } -function forEachSubplot(gd, fn) { - var plots = gd._fullLayout._plots || {}, - subplots = Object.getOwnPropertyNames(plots); - - for(var i = 0, n = subplots.length; i < n; i++) { - fn(plots[subplots[i]]); - } -} - function decodeDate(convertToPx) { return function(v) { return convertToPx(v.replace('_', ' ')); }; } diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index 24da4ba8328..b454bef0b81 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -62,7 +62,7 @@ describe('Test shapes:', function() { } function countSubplots(gd) { - return Object.getOwnPropertyNames(gd._fullLayout._plots || {}).length; + return Object.keys(gd._fullLayout._plots || {}).length; } function countShapePathsInLowerLayer() { @@ -163,8 +163,7 @@ describe('Test shapes:', function() { var index = countShapes(gd); var shape = getRandomShape(); - Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() - { + Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() { expect(countShapePathsInUpperLayer()).toEqual(pathCount + 1); expect(getLastShape(gd)).toEqual(shape); expect(countShapes(gd)).toEqual(index + 1); @@ -176,8 +175,7 @@ describe('Test shapes:', function() { var index = countShapes(gd); var shape = getRandomShape(); - Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() - { + Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() { expect(countShapePathsInUpperLayer()).toEqual(pathCount + 1); expect(getLastShape(gd)).toEqual(shape); expect(countShapes(gd)).toEqual(index + 1); From a0e6a5c93ac70e2e607b2208f8ef1cad18551e75 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 19 Apr 2016 12:23:31 +0100 Subject: [PATCH 3/4] Fix bug that prevented update of shape properties * Added test that updates the layer property of a shape. --- src/components/shapes/index.js | 2 +- test/jasmine/tests/shapes_test.js | 40 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/components/shapes/index.js b/src/components/shapes/index.js index 0136143faf9..b0097ac5665 100644 --- a/src/components/shapes/index.js +++ b/src/components/shapes/index.js @@ -238,7 +238,7 @@ function updateShape(gd, index, opt, value) { else if(Lib.isPlainObject(opt)) optionsEdit = opt; var optionKeys = Object.keys(optionsEdit); - for(i = 0; i < optionsEdit.length; i++) { + for(i = 0; i < optionKeys.length; i++) { var k = optionKeys[i]; Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]); } diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index b454bef0b81..13511fb3699 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -186,5 +186,45 @@ describe('Test shapes:', function() { expect(countShapes(gd)).toEqual(index); }).then(done); }); + + it('should be able to update a shape layer', function(done) { + var index = countShapes(gd), + astr = 'shapes[' + index + ']', + shape = getRandomShape(), + shapesInLowerLayer = countShapePathsInLowerLayer(), + shapesInUpperLayer = countShapePathsInUpperLayer(); + + shape.xref = 'paper'; + shape.yref = 'paper'; + + Plotly.relayout(gd, astr, shape).then(function() { + expect(countShapePathsInLowerLayer()) + .toEqual(shapesInLowerLayer); + expect(countShapePathsInUpperLayer()) + .toEqual(shapesInUpperLayer + 1); + expect(getLastShape(gd)).toEqual(shape); + expect(countShapes(gd)).toEqual(index + 1); + }).then(function() { + shape.layer = 'below'; + Plotly.relayout(gd, astr + '.layer', shape.layer); + }).then(function() { + expect(countShapePathsInLowerLayer()) + .toEqual(shapesInLowerLayer + 1); + expect(countShapePathsInUpperLayer()) + .toEqual(shapesInUpperLayer); + expect(getLastShape(gd)).toEqual(shape); + expect(countShapes(gd)).toEqual(index + 1); + }).then(function() { + shape.layer = 'above'; + Plotly.relayout(gd, astr + '.layer', shape.layer); + }).then(function() { + expect(countShapePathsInLowerLayer()) + .toEqual(shapesInLowerLayer); + expect(countShapePathsInUpperLayer()) + .toEqual(shapesInUpperLayer + 1); + expect(getLastShape(gd)).toEqual(shape); + expect(countShapes(gd)).toEqual(index + 1); + }).then(done); + }); }); }); From 81a44ce909b6865d835296931e137fb582d56f5d Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 19 Apr 2016 12:47:04 +0100 Subject: [PATCH 4/4] Add image test * Added a mock the places shapes in lower, upper and subplot shape layers. * The shapes extend over several subplots to test whether the appropriate clip paths have been set. --- test/image/baselines/shapes_below_traces.png | Bin 0 -> 37057 bytes test/image/mocks/shapes_below_traces.json | 156 +++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 test/image/baselines/shapes_below_traces.png create mode 100644 test/image/mocks/shapes_below_traces.json diff --git a/test/image/baselines/shapes_below_traces.png b/test/image/baselines/shapes_below_traces.png new file mode 100644 index 0000000000000000000000000000000000000000..77b62206fbf3a29a3a4d9e252e64096ca7e33b4f GIT binary patch literal 37057 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 DA%t$R literal 0 HcmV?d00001 diff --git a/test/image/mocks/shapes_below_traces.json b/test/image/mocks/shapes_below_traces.json new file mode 100644 index 00000000000..9758896af57 --- /dev/null +++ b/test/image/mocks/shapes_below_traces.json @@ -0,0 +1,156 @@ +{ + "data": [ + { + "line": { + "shape": "spline" + }, + "y": [ + 1, + 2, + 1, + 0, + -1, + 2, + 3, + 5 + ] + }, + { + "line": { + "shape": "spline" + }, + "xaxis": "x2", + "y": [ + 7.071067811865475, + 10, + 7.071067811865475, + 0, + -7.071067811865475, + 10, + 7.0710678118654755, + -7.071067811865475 + ] + }, + { + "line": { + "shape": "spline" + }, + "y": [ + 7.0710678118654755, + 6.123233995736766e-16, + 7.0710678118654755, + 10, + 7.0710678118654755, + 6.123233995736766e-16, + -7.071067811865475, + -7.071067811865477 + ], + "yaxis": "y2" + }, + { + "line": { + "shape": "spline" + }, + "xaxis": "x2", + "y": [ + 2, + 1.6666666666666667, + 2, + 2.5, + 3.3333333333333335, + 1.6666666666666667, + 1.4285714285714286, + 1.1111111111111112 + ], + "yaxis": "y2" + } + ], + "layout": { + "dragmode": "pan", + "shapes": [ + { + "fillcolor": "#c7eae5", + "layer": "below", + "type": "rect", + "x0": 3.5, + "x1": 4.5, + "xref": "x", + "y0": 0, + "y1": 1, + "yref": "paper" + }, + { + "fillcolor": "#c7eae5", + "layer": "above", + "opacity": 0.5, + "type": "rect", + "x0": 5.5, + "x1": 6.5, + "xref": "x2", + "y0": 0, + "y1": 1, + "yref": "paper" + }, + { + "fillcolor": "#f6e8c3", + "layer": "below", + "type": "rect", + "x0": 0, + "x1": 1, + "xref": "paper", + "y0": 0, + "y1": 3, + "yref": "y" + }, + { + "fillcolor": "#f6e8c3", + "layer": "above", + "opacity": 0.5, + "type": "rect", + "x0": 0, + "x1": 1, + "xref": "paper", + "y0": 1, + "y1": 4, + "yref": "y2" + }, + { + "fillcolor": "#d3d3d3", + "layer": "below", + "type": "rect", + "x0": 0.3, + "x1": 0.7, + "xref": "paper", + "y0": 0.3, + "y1": 0.7, + "yref": "paper" + } + ], + "showlegend": false, + "title": "shape shading a region", + "xaxis": { + "domain": [ + 0, + 0.45 + ] + }, + "xaxis2": { + "domain": [ + 0.55, + 1 + ] + }, + "yaxis": { + "domain": [ + 0, + 0.45 + ] + }, + "yaxis2": { + "domain": [ + 0.55, + 1 + ] + } + } +}