From fa0e49bd3f49a24531a29dee6b2d3696bc868acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Fri, 6 May 2016 16:28:03 -0400 Subject: [PATCH 01/10] Shapes/Images: Reorganize above/below/subplot layers --- src/components/shapes/index.js | 4 ++-- src/plot_api/plot_api.js | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/shapes/index.js b/src/components/shapes/index.js index a3eebd52ff9..0e493e7a287 100644 --- a/src/components/shapes/index.js +++ b/src/components/shapes/index.js @@ -98,7 +98,7 @@ shapes.drawAll = function(gd) { // Remove previous shapes before drawing new in shapes in fullLayout.shapes fullLayout._shapeUpperLayer.selectAll('path').remove(); fullLayout._shapeLowerLayer.selectAll('path').remove(); - fullLayout._subplotShapeLayer.selectAll('path').remove(); + fullLayout._shapeSubplotLayer.selectAll('path').remove(); for(var i = 0; i < fullLayout.shapes.length; i++) { shapes.draw(gd, i); @@ -356,7 +356,7 @@ function getShapeLayer(gd, index) { else if(shape.layer === 'below') { shapeLayer = (shape.xref === 'paper' && shape.yref === 'paper') ? gd._fullLayout._shapeLowerLayer : - gd._fullLayout._subplotShapeLayer; + gd._fullLayout._shapeSubplotLayer; } return shapeLayer; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 74e751b1030..1e91ef9229c 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2652,6 +2652,8 @@ function makePlotFramework(gd) { .classed('layer-below', true); fullLayout._shapeLowerLayer = layerBelow.append('g') .classed('shapelayer', true); + fullLayout._imageLowerLayer = layerBelow.append('g') + .classed('imagelayer', true); var subplots = Plotly.Axes.getSubplots(gd); if(subplots.join('') !== Object.keys(gd._fullLayout._plots || {}).join('')) { @@ -2664,8 +2666,9 @@ function makePlotFramework(gd) { fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true); // shape layers in subplots - fullLayout._subplotShapeLayer = fullLayout._paper - .selectAll('.shapelayer-subplot'); + var layerSubplot = fullLayout._paper.selectAll('.layer-subplot'); + fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer'); + fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer'); // upper shape layer // (only for shapes to be drawn above the whole plot, including subplots) @@ -2673,6 +2676,8 @@ function makePlotFramework(gd) { .classed('layer-above', true); fullLayout._shapeUpperLayer = layerAbove.append('g') .classed('shapelayer', true); + fullLayout._imageUpperLayer = layerAbove.append('g') + .classed('imagelayer', true); // single pie layer for the whole plot fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true); @@ -2803,10 +2808,16 @@ 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); + + // back layer for shapes and images to + // be drawn below a subplot + var backlayer = plotgroup.append('g') + .classed('layer-subplot', true); + + plotinfo.shapelayer = backlayer.append('g') + .classed('shapelayer', true); + plotinfo.imagelayer = backlayer.append('g') + .classed('imagelayer', true); plotinfo.gridlayer = plotgroup.append('g'); plotinfo.overgrid = plotgroup.append('g'); plotinfo.zerolinelayer = plotgroup.append('g'); From 33193875ac57983f7822203d6885b0e5ab3c01a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Fri, 6 May 2016 16:30:10 -0400 Subject: [PATCH 02/10] Images: Create image attributes and defaults As well as creating the attributes and defaults, a default argument has been added to `axes.coerceRef`. --- src/components/images/attributes.js | 158 +++++++++++++++++++++++++ src/components/images/defaults.js | 65 ++++++++++ src/components/images/index.js | 17 +++ src/components/rangeslider/defaults.js | 3 +- src/plots/cartesian/axes.js | 4 +- 5 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 src/components/images/attributes.js create mode 100644 src/components/images/defaults.js create mode 100644 src/components/images/index.js diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js new file mode 100644 index 00000000000..647333b4b91 --- /dev/null +++ b/src/components/images/attributes.js @@ -0,0 +1,158 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var cartesianConstants = require('../../plots/cartesian/constants'); + + +module.exports = { + _isLinkedToArray: true, + + source: { + valType: 'string', + role: 'info', + description: [ + 'Specifies the URL of the image to be used.', + 'The URL must be accessible from the domain where the', + 'plot code is run, and can be either relative or absolute.' + + ].join(' ') + }, + + layer: { + valType: 'enumerated', + values: ['below', 'above'], + dflt: 'above', + role: 'info', + description: [ + 'Specifies whether images are drawn below or above traces.', + 'When `xref` and `yref` are both set to `paper`,', + 'image is drawn below the entire plot area.' + ].join(' ') + }, + + width: { + valType: 'number', + role: 'info', + dflt: 0, + description: [ + 'Sets the image container width.', + 'The image will be sized based on the `position` value.', + 'When `xref` is set to `paper`, units are sized relative', + 'to the plot width.' + ].join(' ') + }, + + height: { + valType: 'number', + role: 'info', + dflt: 0, + description: [ + 'Sets the image container height.', + 'The image will be sized based on the `position` value.', + 'When `yref` is set to `paper`, units are sized relative', + 'to the plot height.' + ].join(' ') + }, + + sizing: { + valType: 'enumerated', + values: ['fill', 'contain', 'stretch'], + dflt: 'contain', + role: 'info', + description: [ + 'Specifies which dimension of the image to constrain.' + ].join(' ') + }, + + opacity: { + valType: 'number', + role: 'info', + min: 0, + max: 1, + dflt: 1, + description: 'Sets the opacity of the image.' + }, + + x: { + valType: 'number', + role: 'info', + dflt: 0, + description: [ + 'Sets the image\'s x position.', + 'When `xref` is set to `paper`, units are sized relative', + 'to the plot height.', + 'See `xref` for more info' + ].join(' ') + }, + + y: { + valType: 'number', + role: 'info', + dflt: 0, + description: [ + 'Sets the image\'s y position.', + 'When `yref` is set to `paper`, units are sized relative', + 'to the plot height.', + 'See `yref` for more info' + ].join(' ') + }, + + xanchor: { + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'left', + role: 'info', + description: 'Sets the anchor for the x position' + }, + + yanchor: { + valType: 'enumerated', + values: ['top', 'middle', 'bottom'], + dflt: 'top', + role: 'info', + description: 'Sets the anchor for the y position.' + }, + + xref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.x.toString() + ], + dflt: 'paper', + role: 'info', + description: [ + 'Sets the images\'s x coordinate axis.', + 'If set to a x axis id (e.g. *x* or *x2*), the `x` position', + 'refers to an x data coordinate', + 'If set to *paper*, the `x` position refers to the distance from', + 'the left of plot in normalized coordinates', + 'where *0* (*1*) corresponds to the left (right).' + ].join(' ') + }, + + yref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.y.toString() + ], + dflt: 'paper', + role: 'info', + description: [ + 'Sets the images\'s y coordinate axis.', + 'If set to a y axis id (e.g. *y* or *y2*), the `y` position', + 'refers to a y data coordinate.', + 'If set to *paper*, the `y` position refers to the distance from', + 'the bottom of the plot in normalized coordinates', + 'where *0* (*1*) corresponds to the bottom (top).' + ].join(' ') + } +}; diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js new file mode 100644 index 00000000000..8e858f178ed --- /dev/null +++ b/src/components/images/defaults.js @@ -0,0 +1,65 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = require('../../plots/cartesian/axes'); +var Lib = require('../../lib'); +var attributes = require('./attributes'); + + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { + + if(!layoutIn.images) return; + + + var containerIn = Array.isArray(layoutIn.images) ? + layoutIn.images : [layoutIn.images], + containerOut = layoutOut.images = []; + + + for(var i = 0; i < containerIn.length; i++) { + var image = containerIn[i]; + + if(!image.source) continue; + + var defaulted = imageDefaults(containerIn[i] || {}, containerOut[i] || {}, layoutOut); + containerOut.push(defaulted); + } +}; + + +function imageDefaults(imageIn, imageOut, fullLayout) { + + imageOut = imageOut || {}; + + function coerce(attr, dflt) { + return Lib.coerce(imageIn, imageOut, attributes, attr, dflt); + } + + coerce('source'); + coerce('layer'); + coerce('x'); + coerce('y'); + coerce('xanchor'); + coerce('yanchor'); + coerce('width'); + coerce('height'); + coerce('sizing'); + coerce('opacity'); + + for(var i = 0; i < 2; i++) { + var tdMock = { _fullLayout: fullLayout }, + axLetter = ['x', 'y'][i]; + + // 'paper' is the fallback axref + Axes.coerceRef(imageIn, imageOut, tdMock, axLetter, 'paper'); + } + + return imageOut; +} diff --git a/src/components/images/index.js b/src/components/images/index.js new file mode 100644 index 00000000000..7e750313b9e --- /dev/null +++ b/src/components/images/index.js @@ -0,0 +1,17 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + + +var supplyLayoutDefaults = require('./defaults'); + + +module.exports = { + supplyLayoutDefaults: supplyLayoutDefaults +}; diff --git a/src/components/rangeslider/defaults.js b/src/components/rangeslider/defaults.js index f0d0efeebd7..0095c7b243e 100644 --- a/src/components/rangeslider/defaults.js +++ b/src/components/rangeslider/defaults.js @@ -21,8 +21,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, axName, coun containerOut = layoutOut[axName].rangeslider = {}; function coerce(attr, dflt) { - return Lib.coerce(containerIn, containerOut, - attributes, attr, dflt); + return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); } coerce('bgcolor'); diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 4440e2b54d6..7278caf2d95 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -38,7 +38,7 @@ axes.getFromTrace = axisIds.getFromTrace; // find the list of possible axes to reference with an xref or yref attribute // and coerce it to that list -axes.coerceRef = function(containerIn, containerOut, gd, axLetter) { +axes.coerceRef = function(containerIn, containerOut, gd, axLetter, dflt) { var axlist = gd._fullLayout._hasGL2D ? [] : axes.listIds(gd, axLetter), refAttr = axLetter + 'ref', attrDef = {}; @@ -48,7 +48,7 @@ axes.coerceRef = function(containerIn, containerOut, gd, axLetter) { attrDef[refAttr] = { valType: 'enumerated', values: axlist.concat(['paper']), - dflt: axlist[0] || 'paper' + dflt: dflt || axlist[0] || 'paper' }; // xref, yref From dd15bfdab248984b5ee743233a78aa362b5d188a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Fri, 6 May 2016 16:30:50 -0400 Subject: [PATCH 03/10] Images: Add draw method --- src/components/images/draw.js | 125 +++++++++++++++++++++++++++++++++ src/components/images/index.js | 2 + 2 files changed, 127 insertions(+) create mode 100644 src/components/images/draw.js diff --git a/src/components/images/draw.js b/src/components/images/draw.js new file mode 100644 index 00000000000..af59cbd8828 --- /dev/null +++ b/src/components/images/draw.js @@ -0,0 +1,125 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var d3 = require('d3'); +var Drawing = require('../drawing'); +var Axes = require('../../plots/cartesian/axes'); + +module.exports = function draw(gd) { + + var fullLayout = gd._fullLayout, + imageDataAbove = [], + imageDataSubplot = [], + imageDataBelow = []; + + if(!fullLayout.images) return; + + + // Sort into top, subplot, and bottom layers + for(var i = 0; i < fullLayout.images.length; i++) { + var img = fullLayout.images[i]; + + if(img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') { + imageDataSubplot.push(img); + } else if(img.layer === 'above') { + imageDataAbove.push(img); + } else { + imageDataBelow.push(img); + } + } + + + var anchors = { + x: { + left: { sizing: 'xMin', offset: 0 }, + center: { sizing: 'xMid', offset: -1 / 2 }, + right: { sizing: 'xMax', offset: -1 } + }, + y: { + top: { sizing: 'YMin', offset: 0 }, + middle: { sizing: 'YMid', offset: -1 / 2 }, + bottom: { sizing: 'YMax', offset: -1 } + } + }; + + + function applyAttributes(d) { + + var thisImage = d3.select(this); + + // Axes if specified + var xref = Axes.getFromId(gd, d.xref), + yref = Axes.getFromId(gd, d.yref); + + var size = fullLayout._size, + width = xref ? Math.abs(xref.l2p(d.width) - xref.l2p(0)) : d.width * size.w, + height = yref ? Math.abs(yref.l2p(d.height) - yref.l2p(0)) : d.width * size.h; + + // Offsets for anchor positioning + var xOffset = width * anchors.x[d.xanchor].offset + size.l, + yOffset = height * anchors.y[d.yanchor].offset + size.t; + + var sizing = anchors.x[d.xanchor].sizing + anchors.y[d.yanchor].sizing; + + // Final positions + var xPos = (xref ? xref.l2p(d.x) : d.x * width) + xOffset, + yPos = (yref ? yref.l2p(d.y) : -d.y * width) + yOffset; + + + // Construct the proper aspectRatio attribute + switch(d.sizing) { + case 'fill': + sizing += ' slice'; + break; + + case 'stretch': + sizing = 'none'; + break; + } + + thisImage.attr({ + href: d.source, + x: xPos, + y: yPos, + width: width, + height: height, + preserveAspectRatio: sizing, + opacity: d.opacity + }); + + + // Set proper clipping on images + var xId = xref ? xref._id : '', + yId = yref ? yref._id : '', + clipAxes = xId + yId; + + thisImage.call(Drawing.setClipUrl, 'clip' + fullLayout._uid + clipAxes); + } + + + 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(applyAttributes); + imagesSubplot.each(applyAttributes); + imagesAbove.each(applyAttributes); +}; diff --git a/src/components/images/index.js b/src/components/images/index.js index 7e750313b9e..9ea6816a08a 100644 --- a/src/components/images/index.js +++ b/src/components/images/index.js @@ -9,9 +9,11 @@ 'use strict'; +var draw = require('./draw'); var supplyLayoutDefaults = require('./defaults'); module.exports = { + draw: draw, supplyLayoutDefaults: supplyLayoutDefaults }; From b94065faf7b34d4a426ae411d471111291a6236b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Fri, 6 May 2016 16:31:34 -0400 Subject: [PATCH 04/10] Images: Call Images.draw in plot step and while dragging --- src/plot_api/plot_api.js | 2 ++ src/plotly.js | 1 + src/plots/cartesian/dragbox.js | 1 + src/plots/plots.js | 2 +- src/traces/heatmap/style.js | 2 +- 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 1e91ef9229c..c9a8f1234cf 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -24,6 +24,7 @@ var Fx = require('../plots/cartesian/graph_interact'); var Color = require('../components/color'); var Drawing = require('../components/drawing'); var ErrorBars = require('../components/errorbars'); +var Images = require('../components/images'); var Legend = require('../components/legend'); var RangeSlider = require('../components/rangeslider'); var RangeSelector = require('../components/rangeselector'); @@ -307,6 +308,7 @@ Plotly.plot = function(gd, data, layout, config) { // be set to false before these will work properly. function finalDraw() { Shapes.drawAll(gd); + Images.draw(gd); Plotly.Annotations.drawAll(gd); Legend.draw(gd); RangeSlider.draw(gd); diff --git a/src/plotly.js b/src/plotly.js index 3ae37158882..5ceb2019839 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -49,6 +49,7 @@ exports.ErrorBars = require('./components/errorbars'); exports.Annotations = require('./components/annotations'); exports.Shapes = require('./components/shapes'); exports.Legend = require('./components/legend'); +exports.Images = require('./components/images'); exports.ModeBar = require('./components/modebar'); exports.register = function register(_modules) { diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index a539e3d7e1b..6dc13c6ad01 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -520,6 +520,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { redrawObjs(fullLayout.annotations || [], Plotly.Annotations); redrawObjs(fullLayout.shapes || [], Plotly.Shapes); + redrawObjs(fullLayout.images || [], Plotly.Images); } function doubleClick() { diff --git a/src/plots/plots.js b/src/plots/plots.js index 271c18374cf..4c355b98617 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -750,7 +750,7 @@ plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData) { // TODO register these // Legend must come after traces (e.g. it depends on 'barmode') - var moduleLayoutDefaults = ['Fx', 'Annotations', 'Shapes', 'Legend']; + var moduleLayoutDefaults = ['Fx', 'Annotations', 'Shapes', 'Legend', 'Images']; for(i = 0; i < moduleLayoutDefaults.length; i++) { _module = moduleLayoutDefaults[i]; diff --git a/src/traces/heatmap/style.js b/src/traces/heatmap/style.js index 2a466860377..2d1fdb3f431 100644 --- a/src/traces/heatmap/style.js +++ b/src/traces/heatmap/style.js @@ -12,7 +12,7 @@ var d3 = require('d3'); module.exports = function style(gd) { - d3.select(gd).selectAll('image') + d3.select(gd).selectAll('.hm image') .style('opacity', function(d) { return d.trace.opacity; }); From 00742e81efa4b66ccf658997a7964a725bc56f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Fri, 6 May 2016 17:21:24 -0400 Subject: [PATCH 05/10] Images: Add mock --- test/image/mocks/layout_image.json | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 test/image/mocks/layout_image.json diff --git a/test/image/mocks/layout_image.json b/test/image/mocks/layout_image.json new file mode 100644 index 00000000000..ee6ada23d03 --- /dev/null +++ b/test/image/mocks/layout_image.json @@ -0,0 +1,60 @@ +{ + "data": [ + { + "x": [1,2,3], + "y": [1,2,3] + } + ], + "layout": { + "images": [ + { + "source": "http://www.placekitten.com/100", + "xref": "paper", + "yref": "paper", + "x": 0, + "y": 0, + "width": 0.2, + "height": 0.2, + "xanchor": "right", + "yanchor": "bottom" + }, + { + "source": "http://www.placekitten.com/300", + "xref": "x", + "yref": "y", + "x": 2.5, + "y": 2, + "width": 1, + "height": 1, + "xanchor": "center", + "yanchor": "middle", + "layer": "below" + }, + { + "source": "http://www.placekitten.com/400", + "xref": "x", + "yref": "y", + "x": 1, + "y": 3, + "width": 2, + "height": 2, + "sizing": "stretch", + "opacity": 0.4 + }, + { + "source": "http://www.placekitten.com/300", + "xref": "x", + "yref": "paper", + "x": 2.75, + "y": 0, + "width": 0.5, + "height": 1, + "opacity": 1, + "xanchor": "left", + "yanchor": "top" + } + ], + "width": 800, + "height": 500 + } +} From cc742faf74d2a4504ccaea34e44c8084d3bb0698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Mon, 9 May 2016 12:52:17 -0400 Subject: [PATCH 06/10] Images: Add jasmine tests --- test/jasmine/tests/layout_images_test.js | 217 +++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 test/jasmine/tests/layout_images_test.js diff --git a/test/jasmine/tests/layout_images_test.js b/test/jasmine/tests/layout_images_test.js new file mode 100644 index 00000000000..bbb5fb05d9f --- /dev/null +++ b/test/jasmine/tests/layout_images_test.js @@ -0,0 +1,217 @@ +var Plotly = require('@lib/index'); +var Images = require('@src/components/images'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var mouseEvent = require('../assets/mouse_event'); + +describe('Layout images', function() { + + describe('supplyLayoutDefaults', function() { + + var layoutIn, + layoutOut; + + beforeEach(function() { + layoutIn = { images: [] }; + layoutOut = {}; + }); + + it('should reject when there is no `source`', function() { + layoutIn.images[0] = { opacity: 0.5, width: 0.2, height: 0.2 }; + + Images.supplyLayoutDefaults(layoutIn, layoutOut); + + expect(layoutOut.images.length).toEqual(0); + }); + + it('should coerce the correct defaults', function() { + layoutIn.images[0] = { source: 'http://www.someimagesource.com' }; + + var expected = { + source: 'http://www.someimagesource.com', + layer: 'above', + x: 0, + y: 0, + xanchor: 'left', + yanchor: 'top', + width: 0, + height: 0, + sizing: 'contain', + opacity: 1, + xref: 'paper', + yref: 'paper' + }; + + Images.supplyLayoutDefaults(layoutIn, layoutOut); + + expect(layoutOut.images[0]).toEqual(expected); + }); + + }); + + describe('drawing', function() { + + var gd, + data = [{ x: [1,2,3], y: [1,2,3] }]; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + it('should draw images on the right layers', function() { + + var layer; + + Plotly.plot(gd, data, { images: [{ + source: 'imageabove', + layer: 'above' + }]}); + + layer = gd._fullLayout._imageUpperLayer; + expect(layer.length).toBe(1); + + destroyGraphDiv(); + gd = createGraphDiv(); + Plotly.plot(gd, data, { images: [{ + source: 'imagebelow', + layer: 'below' + }]}); + + layer = gd._fullLayout._imageLowerLayer; + expect(layer.length).toBe(1); + + destroyGraphDiv(); + gd = createGraphDiv(); + Plotly.plot(gd, data, { images: [{ + source: 'imagesubplot', + layer: 'below', + xref: 'x', + yref: 'y' + }]}); + + layer = gd._fullLayout._imageSubplotLayer; + expect(layer.length).toBe(1); + }); + + describe('with anchors and sizing', function() { + + function testAspectRatio(xAnchor, yAnchor, sizing, expected) { + var anchorName = xAnchor + yAnchor; + Plotly.plot(gd, data, { images: [{ + source: anchorName, + xanchor: xAnchor, + yanchor: yAnchor, + sizing: sizing + }]}); + + var image = Plotly.d3.select('[href="' + anchorName + '"]'), + parValue = image.attr('preserveAspectRatio'); + + expect(parValue).toBe(expected); + } + + it('should work for center middle', function() { + testAspectRatio('center', 'middle', undefined, 'xMidYMid'); + }); + + it('should work for left top', function() { + testAspectRatio('left', 'top', undefined, 'xMinYMin'); + }); + + it('should work for right bottom', function() { + testAspectRatio('right', 'bottom', undefined, 'xMaxYMax'); + }); + + it('should work for stretch sizing', function() { + testAspectRatio('middle', 'center', 'stretch', 'none'); + }); + + it('should work for fill sizing', function() { + testAspectRatio('invalid', 'invalid', 'fill', 'xMinYMin slice'); + }); + + }); + + }); + + describe('when the plot is dragged', function() { + var gd, + data = [{ x: [1,2,3], y: [1,2,3] }]; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + it('should not move when referencing the paper', function(done) { + var source = 'http://www.placekitten.com/200', + image = { + source: source, + xref: 'paper', + yref: 'paper', + x: 0, + y: 0, + width: 0.1, + height: 0.1 + }; + + Plotly.plot(gd, data, { + images: [image], + dragmode: 'pan', + width: 600, + height: 400 + }).then(function() { + var img = Plotly.d3.select('[href="' + source + '"]').node(), + oldPos = img.getBoundingClientRect(); + + mouseEvent('mousedown', 250, 200); + mouseEvent('mousemove', 300, 250); + + var newPos = img.getBoundingClientRect(); + + expect(newPos.left).toBe(oldPos.left); + expect(newPos.top).toBe(oldPos.top); + + mouseEvent('mouseup', 300, 250); + }).then(done); + }); + + it('should move when referencing axes', function(done) { + var source = 'http://www.placekitten.com/200', + image = { + source: source, + xref: 'x', + yref: 'y', + x: 2, + y: 2, + width: 1, + height: 1 + }; + + Plotly.plot(gd, data, { + images: [image], + dragmode: 'pan', + width: 600, + height: 400 + }).then(function() { + var img = Plotly.d3.select('[href="' + source + '"]').node(), + oldPos = img.getBoundingClientRect(); + + mouseEvent('mousedown', 250, 200); + mouseEvent('mousemove', 300, 250); + + var newPos = img.getBoundingClientRect(); + + expect(newPos.left).toBe(oldPos.left + 50); + expect(newPos.top).toBe(oldPos.top + 50); + + mouseEvent('mouseup', 300, 250); + }).then(done); + }); + + }); + +}); From 7b2859d56a0953226c4f2ffd4deca6513b6722af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Mon, 9 May 2016 17:13:46 +0000 Subject: [PATCH 07/10] Images: Add baseline test images --- test/image/baselines/layout_image.png | Bin 0 -> 25135 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/image/baselines/layout_image.png diff --git a/test/image/baselines/layout_image.png b/test/image/baselines/layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..6f9bf42d0f671b80512db6ae2074fa7a78ef7343 GIT binary patch literal 25135 zcmeFZc|6qX`^Vo*Q-+EeiR^DnC_=~*F|ubVvXnL1_v||(vSugQk1U1k*|&)76xp}3 zMwaYr{O&oOPIXS__x<w3Ok*Bx+ELFyC%1;LRcM^4E|iz^*D zauhmp1h$Sm4*sVRBja@B2y{e7TvXXfd$AfnN=qfS^iWkPmUT@?O$CmRkgg}WmUR=R zXikArL@7g=M51wzbqD=<&~tc9Y3K5<{+^ML?%3jYQOkWwi=*ttoHh z6a{IrT#y-cT@u(^_?k4E+c@8Av*|IuwPSIKXg0z11O+7}-M{xf< zfgnJkPDGuD{rsQEvv4F55BB{#e||k*@(8TJH}2YB$Nlva@OtuZzw`YkU!XCV7bzL2 zUrvL?-ar29Y0VHQl-cw1h+obMK?%Qn#=N3{AsQ%52}J+CT-<4pNTXjqqyBek-=F@U z5BYyj?SEtK#}oM94E-fo{4aR?5@P=sa{oWDP(A&0(tNDy&J~9RmWK}?24>ugD#JG) ztxy>%v0$aXV&`8;vd2oFZ!ye!o`r?;u>IpvFLV5bk@8y(i$mwMD(qSu@cTtE4$ET~ zhfA$uo<5~FW}(AkS@5~e9)&5AWHc(GP(k@;qOV~{9-3H_AR6e+CDKtS30)5%0{Byw zTL^K*C!|uM7`A#4T8by+Vw#Ig`X3PQW};{QBu=m`>5AbLmV6hd4l zV+5ucb^f2n^-QoJ{UrthuhT(+^;3lYHJoU0zF&^xWx9qjzuBjD6kOtYM+}KUAUNMI z$GzkyfZs!xJXOM7V)@ah9CEnx{e0Xnx7JFHl8B`T=lkWjUv6#b|2L1MIGeYn`*M7m z`8p(VE8Ud@F?D1I&-0Jk*(BGgMlu~9wNo@gw{xpjudjJ(?L!cI#)QWWr2igy`2Zb! z;@*X&Egx)Ia=00_$Kk=9)q#cdjGQrv#pySBH0(MQ+^>px=a{A7$YvNeN2^jb<3=(& zbFy2Z-KV#rG|p4#SOMFN{ry>yE_6|18yY2;P^=<~d3ny%hz8L>Xk?*+LRkd75$vMX z&W)x_cFDUFj=46r-No@n}KV3ASiOOqZ+ z0s;a}lK^2M2$c~3p&eo5M}#Om_xA1EyX?~=`m&Ec!{K%1Cg$c*B^JYQ1U!fMiQ5)c z?48=&Mtesce(g7>=KAJ$`8ilFTnMyv-Ed`q?nqYYzCOOwVdcS)o}O+q<~(bldW=J| zd=>v7SbuMGaR;xG)yt{7DTG?EotA#M*lbp&>E#^U>%$0Qf?}Wh&(ov!tqi}Yfv*&4 z)!UzE%=h@rUOpM^Qoh)@L*KoslO^oAfzS2A=$Uv62`MR)`uNraQkoS%_jbWG_R0k= ze8kify^wIT+;YhZ$K@scC7}n?`fQzijSROtrKa?~fBqO{>}^)M5Y;+=WoMjB#B@Hj zKyixuhJC^%%diobrET%OG^YauDvXW%_~F}=+LE2~cr z>I@2lu{pUiV%rrxey~~E{p?0EmD(Fm0XVUUUho+dDme3v$mt&+e3MOf-YnXT1Rt6g z)ald;Du!H~i{9zlp$_}~1N;PV>gdjwAG zOJRlzvrG1pFe|;jBhT>?`{k2Cr2fcTn!ShK*iV9dAhgJ#K|ocfItD91YABxn5mFtV zD}Rm4S>4eoCJAji%#jN@+r3M+2SJ1n$rV|V(&CWTz;T#0-LXR#3}(Nr{b2|iMW*y6 z4-e5$+rBzi=Fm$jurfoYVZJg|8`JVcG$*KO>>*NG_2%W92a|i#TrPK)L?HkF<7yTDcwaRgKh;+2Q7$oU zlq)1kxHNv*P6{b)Y}d&yj!6h++IoWu8;LErhJbw}@d2xhPs$q#g!nAEljES|PHRqY zhp*uBfG$^hmAK^k#a35i1f!Ktrv(PHiD5Ws?*#D%YRzpAQ*ynxaB4ZkqmrWP1#ee}Kns2ZBk#)qi$_>S zU*X+)r1ZL6Pd)=Y!Z#{484U|aGNEt~$L!1P6z|%vZjAGhg$Aw?$115_I9loW$ejhj z=+Mx20)qO&o77OSfY6+oL^P^n?hPL$oS0UlLy!UTNEb}C%t^4_TQ`*V-11RcvvID? zT>k4l`$QwmAi#khvLB9DwB~^=Dp$pd`ff?RccWp4Y;&M75c`@U??& z)1q3}xmhV`i8}%7?DKcsk%)#Xs{<$Kp#p{yHBhiOsoy$>5Wf|-X()rH7UbCd8zW{zT`?`x)R9+DyWBK3K-c?pszIG0USp znnE7y(PH{GcLGRC&P!)xQf1MDV6i%-glLHTqtN6uti_kO-|g`cczrBV>!5xb)ohKEdv>~j`=e$|7-|CAGN;=qMjG(sVNK=5U5|V zDT?Wic2w@6hZMu*`h}6wRn8(pW&xTTF@qg}`%{lzPYIs(YtrDK8AXRZfidS_g-0A& zFS||;S%>#lbHJYpj+bSKVJ7IaU3CzQNp7@G?&13%>I6D?4+qtVV%C^X20i;@S__S8 zdv!5N48w*gQGvq>B-GWY!6Z&9$q>buhxe)A!2)=@_Keu0XodG3isU^#`p~N#dVZ1` zEFVWvVLnIwQ-t8eN8hKFh+`t?mmReb4a@rx^E5D-z^ZY3beQpKrO_LB`BJ!JoP|Tr zpj0zoNZ&acS3GgdQvp8q#Ru53D9NO12y%wY&DkIr)9Yphc;OMuucFwcFsmmHx_Zc( zH$pFyg~rzktJXNY58LN;aJvkjaKg|HiD9l`Z0+&k6Q*32TMw~iiJ{q}bWkqSq=ODp zy3B3KEnVAVe|w~T;`MRInDYkbR(n^xUt4?{yntvZdR1sl25W&waKu1R4db+GGSXke zdAl3TP+29*jR5S_ETV6gwLumKuLIIp_^cZ_X>q;{rsevkR}tUljiP6AF4sn?lp=3%4`cg2P(ICqz&W~_a~Ou zxX7rlRy~tD&k=o#i1O~Gb6X&DUNVTV0s*`RL9HH&4$YfMR;GNhX{QqL}iiwcys~-_u`J}zb?^IQn|%^xYl)}VbaHY?>Y#Eh{Kq!1AOr8 z6!89|#_48(K$^reVLg0;)<7E1@yY{1nz}?H;hfVLjJna(@EN4PRRHxLIzQI(zssX; zMkt$aISiL!eU)9~_VIQUeBdbb!13y~{*5EBmZWG7HF_{gq9u}%(xhQ!X~#e&A%^}d zljw#ZR$fa!N1=l+HWo}@J`BHjU{y>|Ga7hf31np>vE?;znDx~Zs$DUR4Yo0z9AsmD zbbn=I#-w2elqIo(G+u5CUlFzA==~6>FRnCh_28Q8i)`)B;l?)I?lmyb^#UM>>H#PC zNzV7TQx!3!dBcV^3R@v28?jh#9kyh!T21=LgX!CV?4vJm zzbiM&gp{7A57obmU?hH&IV|pteV;Q%AKhDQ)Ai_K?0(G^k9_Cy{rtMoTcD`)LW81m zu@8e;SPdzrfD^N77soR|rm4a9oCrqMDpS6#I~Za0^1-Tc>+yE*cFm+tU8X}X%pAGbFjf97u(XA#1yzc0qBU~OGq(67v z9jq-+m*xIrIyFkbVvFxr?_3#Wo?&YXD>GIqK!?&@6}*!smiZNs`SnClZvVDiFJpxA zIBqkm^cJsfIX*T*B9kB?4~e;Ro=wU{x=-_2s^I^rx;H$byOSI7+0S4m!~9HGPJZ&h{_6OAAzh5D6eQ zNS`%*A)|!J=+e>CX7^mGW?C`sFL@<=0_53EWF zgsX$DG)H}K6l#@C7t^H->QTJF4xX&0e&jG2OeVyADjE&avy6KnB|dXWN|+H&EYPiN zel{KZBaqZR(hG(Ff=ZDxZB)%Y{6q2 zvpz>3$sFBW%1pSGboA&Hkh)reV~D`YEu0o*5DMzzKgwQ^gD8IVdlWB3ht<~9G>@z} zdG0VBH5ad?Ukhp4IgJW^nFImAwE7<}TObTO!pDsYRo^k5Htcy<_qCE@g@0>6msBt< zDhLd5vRf^A%m_vi4}}~_OhVx6ctaG*>ZwhZBBCLazskf=2ab!PN_Vta?Xydt2uKnw z7o_yygdu!~+FmaM;g1R#iQwEr?M zZ)>t48?WcZ;rwbDq2rE`CqgJMJPVnNvE-KZYv9kaJuI6biqgH(dlJE%KI{lZwAfQyYhyYD)ernuWI8PK}Wvk z_a^pWL@{Uc@dY#$oU3l94($qEI1j+G;w5IW0lHTVw;lM0UqqO~2J>~j!$UvhzA3Yw zES&J@6qs$cC>>>T-(7E+cZmnsf!E}7=0ohKmhKrwq`&;o!TX^S+fZ~Uh)I2xgC-Or zxi3q>BG}yGw;p5ef!C01GWY&_kWgZU>S+7&syEf~$5x*3Ti@$z09S8bzSb$2{`@)1 z=gTDkKzyq&-bFvlF+rg;(!M?~GH9~Qj8o+R%T}T8FniB>TDUt%PMAXurl^S!4WU9> zpNV1C7sL1WWWb#8{7|t}@tH9!Kqivua%!2|j?LP0mHTS?9dVau^1O+JZ$BXWMC0@U z=Db+k-(GsJd|MQwDcATl35D9?wkZ%pG&CwNY`wG|W{f%P(($W!|pz^D3YaM*?l~k;1Q9@hd z-@O32QIwWtqVV%3<2BV(E}v3t=F(%X=V@0b2;n3BBMbP)N#GN1;tIU}Sbp-L;*36v z(eo<3_A}PmIfC&u=*6|vU62z_<1HSd4G<0L;=maIWliwBPE*3HHxBTi;ZH9QI?Z0g zOiX9nJ0ir*3h|Q6I%cSq?%ZO&Wj1fnw8XT+KNp4(x<0*)uGVFO!NMlzyBjx*EgTq%ru&)|S9-nErE0S9@%nAh)CE@!Ob1KLwB zvNA6_3Qvt{66hdEfI-j8AB2_=*zaQhjIiXLLd$sPf_+>rTf4{M zN5sPsLo2}MxH3?Q4}ZEh(r}p?PE0m9wJCv#(4%!WKrq^*We%WVG7|0KL|`U{ybm}< zmOE-%bA2m!?gZD@{m%oUfc((w`E52=O5uoA=ExD)J2JuFoj$`Kq_g)tguX1PiJ(Kd zn}~SFP3^ZoEQOy6!ixKHsZi2EyAzWCPVBDKC)Ao2fy{R-_5}A)_r+#9kl%^nIRv>$ zf4{zWWZeHgK_Tp0-0eA}-I%Coj2R`UiZ)F!#i=UG%_mQ}yrVrlE^- z_TsQHSRLy4NR5v3Z#_u?R8E<2UX5h$CQ^FAUxgH;gN;Bh`DbRaPhh*$eF|KipV7q@ z9*;2}T^trmcdxunc^o)E)K%yZ;wlM-XaAiOOh`SkCAH$Lom>H8K&)@=RqHaa!cL4O z<`4n4A(i_SfT2%d#6{PXsX`xXwthGst~{zjAf+p zia7;A+HV|QW&EZL*U;^Jp<&iAIal5$uV+EfAY^#PEpZ8JNu6i{C@T{|9)&2TDv&kX z5FPe{bH^GDpO8|>wGY6SJ@%*l3PF}r>!ynonJ;o9XU8;jDK406+|_0yqA8vqF82PL zPgNNsVJq|*331{WoBHX^MsQC%Wkb~8VFY7**UM1VctM)3E)5fkxtjxTMpmA)aD;1X z4KaU*RFigkNdKM{A5%gY{=<$A38a5~dD++*_=L+f%S}H3sbn|&vO)BY5x#9e@oA(c zr+gUQEEU?h7-4e3FAFJ6m|w;NnAAJRv%i_tqntW!p3$Ii0B|tAUV@sSV>ARK1ajGz z!*Vpes4$@q=}thQ<8b@;Yrn_b?pagX?cc<@*8k+es9>>K#U#kvwTAW>Xk1O`AcxN2 zk_$-x67e0nYbpWsAT+(?Jqf>esyOQ!KvdSkdrJhMz=|d>Hj_{JP#uyXm2*8!ud-x? zG}PJ=!6;c4SSpEGf7teKWlma1_h2${dS-gHe?Gn;^@buI19jb`>f%sIz_aI#091XK zJ$F`slK4+X&p5=H?B*J)tkT~Sf1*D&c+c@fL+adTyW!&^^C?r_KtSzYJ&W`=_(0l9 z2a!U<7rd|o^f}pt@TaUdGrqihta3vQbR zfLb_2Z+;XOfHLgXpo3(XKNx|Wt7{};i2~Mg(F;$Ts3BE#=Hj?M>0^-go-sIQb71}2 zcDQ*BoU`kw+;evXBi)xvj{)lOF>^Homv2qIfkIIW3?;!~Eg{=q^DahY&8zR+pEfMI zs}J(t=e>NP)Q?Axw}J{f>X@ks6{3OIILH7eI#05LL|^{)>;clhQ*$zj04Bis(bb5J zr*=}Y(ln_2KB&XNGq9q%%ClsgFh&crzCvRZFNV_@z)%OG!_q}@%t%{mI&BGo9D1E5%cQyBbf-v2vDNZ)z_*2?x}CpN9xj{QTh zR)n38ygs#V?M;$SJ<5WVwqxsgc^XU*i+@fK<+x*MDT?iU>c>LWFOHXurnyXgNx~Ib z2@Z$}>p%2BakuM-{if1i1S&n7SBWABwBMz1UZ(4**ab^tAn2)sXPqT4cydNB(6wc% z@Sea%z87!4tIko_ig3O7aSVpIj>b$82TmOLqySRsQ#u8E7m+q90DzUC1K-&>STgr14T$QJpFvayf`>K`Jg5t*!Pz z6xaQdFUSx!Sfy90tP1kWjK#`R?^nHd;~a%*I4hE`HWRSYK?GvOoW$^Zu)=9#+FSUa9;-cY-YffZc%iY zwDpw&a`=S!$#*x`*9b&b>y`Sdjr-pZ5aRfgqc;WOZ zPKQgN?jpSQ4MJGviDE`NnV&78!V>OVuQcKK1o#sLLVsf>apqF?2kUGtSBjIq95h2Z zOgAos2Qaw!)z!zTf=Y-g{2Oc=(e?&t`Gx{O%K{%K?jd}*!|-6q$Mko67rE@!d#FRN z)`hzGH^;m=$;lCOgGE$mm7g?+T>_&n(Qs@J!UBbk^hd0EDEOy~VmcKR>8fMvIjZ)t zj5Rjf7$7#$OPyNIWTAEh&W(|dH`ARcR7?+kKaiM0;v+#QVAiv)IsdKHddm#E^U@(( zE>V@kQEpFMNP$vN`aA#BAqv*ne zW$F}3Wc0mnOU~#wVgX8WKFHP@!j>+D>kc0USc#uj7d!#o=jGKv%oI#cr~vG;zOj)O zU`c)gU1S*>1gr+Ky)RwLXM2yuzU#bVim8_FOQ=&pMMsY1-(!vT+)d`(grTY1D&Sjn@-cQ2!xv9ovHWK z@QBmw><3T4pA?Z|ua3c-9p@z1>???B3i!F}olpCcubh2XD*L41_GxaQksH3cUMhuI zKNj@QMC!T{W=y+K6g;&S90BzA0Mh1+Mo!=!M8i(YjcAy*8(BOBv*wG>Po#r%TLj7o zVb+`^MC#WtLdcdn(nwTTDQONLM~sLtNN37EfPBbiCto{}I!#Ao=#!)RygfQ*QCnT>CXfS{pj96hpfcAz?yK5mZu7)<6ypQ1t_ zR$QhhxEh7xv+++m@s0K(U@}@$;U}q{VcVZ;F+^XsUtOqVmCJi*K@iuFS|z?~zlf4K zG>^dHZ1SZVH3^K{jeLW{ALIR-exAvXn2O%XPS2`PI1sQ(r|YfB4=x|ZA+_qDA=alc zV~VJ-b;IPYDWHU}k7x#A%VK4p=c2-H4k*ChK4H$Iz~_tQrw%RZkf4J& zs%x)9b-kP;^2>&m8r4!fP7H4X?Ltu@q4hF5!j?csZ&D+(EYuQmQN%^U$wuZN)`&tAw3^njk_*audespXiK{4 znpReJJGW-nbZ)pbFOnJd6JIJn?zz9Mx%2sRP@4NNug%PMolQd+4Va8GAd7Oru|p8f z>Q)*%Hx$HLzzd3)UN*BwH1tnxj`P87uyTABLyVq#!{bx!++9(fiGppTj^h{Y?d@&8 zZuAQ}6aO4+O*z9t1$8pVjczo$S3KDgtg#=t|I8(W+z~wczjf)qBnR8x{hhVxjCjk4 zVXNA!MW(<>B(Q*5POlRgIC0_xpjS02OxoQQ4&%jUF(f1;Pl{gS!E@03t8d5*K6hU2 zU^mma2_4!GvFyAF_g2^F9KRvp^}*x;KvpLGA+&LFJBrOF`@sH0++E|om(bVfZ2e3j zabiUK>li2n&>{!hlakkfxvRB3>w|!?nlRFzRc58>ol)9Z zIlY!vP~f~slQ`6}e#lZfjyhA#fWzbR+#c0w`z6ox-C&3l-g;1Jn#62$%yPb5`?d$l)_39nf?1yHharAn} z?IX_{q;naq>>6^UFwm|T{9$2)5Z8F>FasOB{*vVp?@JS{uxpeALoDHKICwA_Bdcc!XOQANva z$%$?F?fNvur0-@JaES;k`akA~VSxG=loU}gMRe*09SZfiS&zdXOLb%=@`mvF(lHma zAS=9z>Bk!S>!02g_(ht5Q>)$6qy_Q5+nsQehbL}J5WaJ}+{Dkz8W@|vWKkoeEz{vg zN>_Br56q#%@~67134tS0IVm&5Rm}% znEx9+p4p^8G%UK=%oD;|@X5$PqITt|hQq?awqo{`!7}%lSoiK`9CZTun2n*=kJs_r zl?@%HoZyAw#i_KeG+sW4WROJy+e@aKrSkaOlc=z_pOmTr^2yye`C_<2Hp|pB*bNb?bu76nbRN7N1UzCHh@LTbukfB`%GAtX(?JC(D%F?ZPp^!9gF^8G zZ;;aWKBaOz!isnCoD!smJTj{Ezw+8P>CL~aXg_iYGDrp*DZ*v)A1Fxq4aK()Ib@)L zhaof%`e9b8yuYXl(*0budG6q3Vat3V(y)O7DF5#&-am^!=ae`0wwOf@3KaJC`wvlJ z+vH|1fQu*G^y~b^nQXgd4|~y!k9i@qY(UcR4vfM+l1nnzPLH>n}T z)xGC*pl%AFy7FA(n_X$Ef)hSr?-p=56lc^bsYgni8!uWt!VVbarqF}+J&^XrfO*3t zN8ZI&p6%dNzgAE*kza)bRtAPo!Y5|Rt*coP4I4fq3q&|Gom<5<%xcQ)fI3bJ7Xnha zHS2TdHa-~x`y8R|HxqJt<3bKkz+%LPx3N4gSA_KER&?q`!Y5QTJ9&a|CN=$Fpn#{y zc`zUtcTT!39D!Lg6qn2L0ey?sW9{?`{~^c#r&qgcBrJJ$GTBi-3(Yu!6;VYAEA4*17hNycvHcLJGfe$gPdzAhB+xaX<) zcP*CO&dPvdU}GlIkm@*M!gYgT)jF0>ea@NZ}@>c%rN3p0_fJ*cobdiH`RnTy8#&&;2Uz3z`SIp z>T<)WhLIycHH`3RC`O0*du8YgA{dAM&^fpptt_7%88fD1z`N94^Hy34q6M@osO0to zU_QXF-zwvQPgpzQmjLqtQY51rDJ^$5a)1;j^PuAJGH^C=x#^Bw$4LiSBA|-Rr9RQ2 zrwVWtU~Q@AC*HDuH{?tx-X<YR9U@f`4kyzF!-aXyFr&oEdEM-dJ&0*Tw;z_dsa zpuImOZlGAlkIxY1Nn)r7n?Cm8lyd8`%ZP@L_pg=sVto*^6I?j$Vq_T~h9C2e+cpMQ zip45czA=BBE$fPdR-uFsE$S`i-vO&rYCe_e8JIQ8$xwAsj1U29n<_etO?2lzsI0}V zFKze(t5eJP_#Iq|s9M{{wg4b!cnC(AV3=g8B(_P70iyAXx%(L@{ou=0_F$|N)tHC6 z)K55E+95yrNzwbN$(5N0_J(x;vWj?=S1Bt^QIp?BN;}N@X2;>GM)ZFcl!R_1G|AQP z!Jr1l&-fGGI;?p}4@~|G#$F?k`+pDrvK!X`7vKN6vKZuBkTSd&4%n5m(VV1h9he8e zq(nxeV}6O+Q5HC*3@2@jkp69RWy6&42}@pGK`~5ygzbI6Xz#Mny!65H5Y46Vk;;PA zv#QiQa)+|9y7GxsPL2D>eWLD{{q*pAp~V^3k^UJxiyaTJ19}$OfN2E1e0T-vf2^56 z7J}qc4uA7@Khrm?#nBF$b>^IZg} zTr>ZpsKuxsn@ajC6$3Jc^dp+Lz5Cb?{h>7VgUt^>etnFQ-2|uq-u!Jyc&3aw;He%H zGA+Kls4Dj1t>`Ea+jInpSDL45Ns!X)FUuT&v&EqtpS3_bSK03a}WUQ<1U-|CHCMyPTV znC$F(QdR#HNiQ{4w+qq$(F)@t7NQG0P{*I29{{#y(dJ8!{ILy3E`J0LIpB0G1on_~ z9qS9}qL`B9p|ut2&?|Q;4W8j!T{GcW13w`2($fVrnwd;Fc;GQ zRO_zkHXEeju*5rVd}d^2Am1Vee-_Aez!8*WCoSTxD**JpiJNC>iXUK|NFIv;zaHU3 z^gq1iUjRb07`GT(M$(iH0Tkz@?QxTqfhL)@x4#x8v~U)-!pVcD4#Eer?lmb2MI>gH zZtw$RPDEl~0P5!t3@&=0@bK-PC0t@XGks3U0|m@M14#J2$dV+G0wu9oD1d>VX7P*^ ze$THc8ZiGe?el@#5LD+%Ezc3t-x@M{ipl%5)D|}&UlfB(xeX!eM5w2H%^4(7N z0jD)3$1@Pl5<;2=XEaH}RN@_;R8mKS_vd;eRo7{i?rR5i=;U=*!8V>#!E}&`w7@Po zI_y(yhOQKt^{qce|dfe zw_FsOpMkXyM-j!)0k_PQb|^1Ge{5BLhB_2a`%qk{Q^4>!c>Es@v_`$JN#%g*`a9^n z&-U9qUxW$u3|m!co(i-mzcI~(kM3gyuiH~q8Fop-y>LJ_^tEihJ%Ukmn0U*B1{4kf-aD!gI?;0*}MLnu0 z#`%M~YAWe_<{WRZT;_<``Wy(}Jx75Ot}3Wze}r|S*7>d9|49(Gipw*&d~&60OVv=a zc72TjC?24(;13ucx4sN;MG=$4Tn4rOeds^lU05(y*|Tg2tjMkbIud zo9j+_o4sSqO9RYsLYl?Pfmq`k{7E~gFsCx6a$Mz5-WsjM*xzqnpwa8?m=CQWXqJyx$aDNjaj1< zQCD!Wt}iaDT*L6Bn^=(|8feTkqJhJONO=1XxnyCj6031`H7)yIk7H94K^%{DzRs(4 zSGziD#QNlVf3p{QVTBIsD0X@tHF6bMTKF~+-JrH;NOjkZS6!Eqqy z9tXc&WYqQelHMEX`+k#!ZS2E&*aXKhXPd68Tlz~_w@oDCkkfwpbh9$>GNoB)9LN{5 z#+HpWiOl3iW%kVLZ%zcqs^;x<*V)uH=qe3l%;nP3A{T;Qjd^YsTXn^t?q4fpM(vB* zbR}KwPB}Ur7jyM;?#a&fT&>D9Jt@bUnMBXcX-D>3tdMJgmUZJ9o1N*HWi`y7b}&HW zcHZTLGf1`Kc+Sz|C!24M7aGWV;ER`C0k$lN)|Hkl=)PHrlH>>4<=lu$Z7oLI$8Oh! z6v)qy3p;SmO5N$R4nDo_1#N%f5SpNWVLJ{87yiKowka*HMEoXh6+OLFv|3E3dVC7 z<%J<%U2x4lW28<_eZ)W`u#K_g_Mg0W|AtO82Y=OKJ0 zA5mcgZ<$Ii!6QoWpW&p4iDLsL98U!rcz#Pw<0=DxA%cfH^B4m1|>T`i%2gO={9Ud)P z-I^bf&=+jg=8)NpMuFy^t(*~ZH@3PoS#CY+ZNp>bjXe%5&A?<*e^b{S`qCe%T zf@s)6M9$FyL|wV8f<}#>HXjs$uad)Jxs5_bfRAa0y{R>tb=sHGG7NM5ft4Wt`dhK- zZds0K?P?c+Z&1i0(q{Y6s>n0J{3%mwJCsR=k`Jc`(qX&BW04y=P`}7#fD;v0emrPPXzmZ8=$@DSjeS@Yn45vS z^qaZ4h-$i1ZGO5bw5vnYec;0qj*&GHb=u&4Jh%Qr#`@zkz_Jqj{&L6Tdiaih&lyU% zTE91PWG~ z(x&q8@W$nJe;vHFY;dr&t$P)Oq@IwoXI-5BwfN$g)jP7kL0vM1!B*1oHI<6m?Y*jY zPnUI|6od6g;i@@iObq0<406-QkQ4px{3jk9X)9An>MTWvE(u%y_qBPD`!x zhN>sJN!v{nINBn_o5~BW9)sVDFAH%&`iD?i+yGt(CK}JH2u6=LeHVc8d$UQ&7`Wkp zrHr-KW9fDKfdRwblbE(z&MY0a%ii*6FyOPs&cYO_t$yNF9NORBSF*0>9v15CWiqt4JLnsI`;m{UoVwS_I=9V*f=bb~{$1TMI~; z-~Aed?}1$ewC6YEaFYFK-gxui@8*sB8Fvs3u`E@l2-wO6_HTW3_1TgrRNh2u7JwhD zavqwXLp!|Z!DWqLr+t&%2XbQ1LaFt&*=YqEr~w0`f=|3DU&#TjAEgZ=pU42N!W*x^ zVBBP1Yi%;iyEZUE^OaBiIIQ6>b+7u8*B6VMt{ zulw(mrhszhr|yl|`XAjJ4)HCi6#pvxWLK0x`oH~cDhuk?l!7EuH5=EsasEf+Mq!V* zj|wUbsheTUieQw`mEykuC#GvBu>i)^6O?~e0K`^LaISKiVi*0s2)Psxb|Zo4e15Ly z+}OmWr4VcbQf;LV=|6E_kLuKS`sDuc+nAidU(0Fy48THYZ86WS51`>LKCg#*tkPCU z;^I=^?>9(Tn*>`l;D*;bUjeg^E?^}AVKS(irgSt&hKFx_{_}>zjO*Rp>#pGP_S)Jap(5Qc9#UU&RFArTLWfoZM*J=z z{v~LiOS`yYJR^a5`teDL7N~gwzU9uVCu|Kz)p0H?qnX@hbGsG%bdZQ0j9!^3Eq@Js zL4N}!DodbQ^Hce0hym8btZ#;tmcGx<4nRpdm-_()kf?2!-9g&tX6XZrzS7B02|w~~ z7z2yicc%4g|Cu=GKO;s9KNclPH z`p{vBk4?%HumF5>;$eDdP|*`KaI7YR&Qz`aWoByqhE&&Q4ucj?KV1N`?|0i<{BYU` za88qr>6|AUT~RLz%H`jIGFe!opN2f-rMD?O4xhla*#js^8yhGQzvr!GbZD{NSfkYm~=eEulK<0H&G93)hwd7_^;k z5268w^PjNh?LP*$k3iy@|QTab_dGQaTU_uu5fvoLt zaf~lMrDloMuQ_U!*yPsDy9{s+Gn_&4*OcO$cT#lf6yVwn@|Le(5;RX*U5xguwNmB& z0;IxfPtScIp9)OI}&wG60@u{P;RRgOT{Oaw^jQmbG~INjNdSBp~Xh?~Y{0bb~uJJ&DxqszH;c?@S5Gd&&VYRDG#nZ~*D&b8Vrd&r&L zcVcJKAMXQq*edz<01Q5%XWFs_n#z;CLa8B0IK$yX(w_&S!$4~tbht4f*bz}aonUR# zw_O+5ZDRyBYe{5tn+Ngz{6$9>3Cu`K{WAp=D!IEgR}H}^sGzMmU5Oo8xn8~R8QNv> zX1C9_sf~Q{xO3fjKj>rAiIgp|LNMMrs8N-|xXH5*)}X`mcbXIdG50pT=DN!Q1qTNM zPg3>%dVpiR;@+aOR4eE@mGjrHXax;`t%6x5=&J#zA#UxA|&A+r#A@sTLqahVh-?ZN@chTxHGO_W^<3#wYgVIJ@;64y3aN+1Wfv48AV4A}TUvl2xk@Qm26ljrDq6Er_6pT(&8C-%}TM^{ZD`ygV3KW(E=e+M`TO1hN$ZH2yZ$uXF;#W1HINCE$J6$GzGL2>M*I6qR{-V>NzO#9VT->lTyr zj;K4yyFPBP`wp{gd?fVGwh0GQ;F;yu$c#wC0-09qg4z6(BS)td@8{~nS$+WzaVyrf zTZ~iynNpR$%)5r^_JG%SgkpUrA8)FF)1JoR03LkUopO^(oVRt6qo0>yCuUh83jbU+ z$-7=ba9X}$e&%01cD~pK8NS$RFh0tflzKC;Y{U6lKq*ZxtjHZ?Bhs{~qPjK0{di}9 z=Ql;P>G&BiaX4`khZ0_>Kgx?+Mg%i@`53moY=y)y_|ee_(*Omt>3zg9IZQ_QD6JsK zG2|}pBmwlo?O$;e!D#vve~IiFmQAx#MWJl&A!*N5he1Yi!Ix4GZr!?~18S!lL{B+r z5jR@eQ3%FP$#N!s#$|!HwJTT*Gp}kD&ar>UQS!%nXxwC?9ZfQ+jWZXC{*mEle!CSe68 zI)F_SMD`bu(oPO0+F%SHm`bl?Ib9yft3%T~rPW|B_5y*0>)d*LR1pHl) z+&}&gzCtF60cI8Ga8=~;#d#smjoI{_Q`_&6YP(I+ z3y6EYz&~Sxoems;jvTki&AE#iZW~g}EkGGMBj3fM3y#ulNp}Udyruou3SgX{i&?S< z<9r{;Qe26b^n6F0CWCF-E|xa?qZtZs>mEjkRB5tubZXEYb+wDKq7l?|;Nxj6_jD?j zV93*QEL>k4s`Kzxwgbi6&t@;2fclfc-sIRYXb58Id3Gaaak#WiMNMlu4eR#TK1UtQ zCgvpPS(^8ath5g$=6ciJpNrMgkAgQby7T7G=94?4d-PDnTCmv_1S5N4%VH4rWxWSz z#N#q-KU+Q%XEp^8jTs#TV9a&)tLQsrmhlO8vTftbC=-%!xbxx=hcwXkh!?{rEKd&< z==tF`H5w|OOejl?(ilB9%d~Cx=@qp~X@qrU{0E|lIvr}kRb>9KOnL7p@If(#^(!Qf zI2%PDcC;@)f}lIAl@o!iQ>$$pR&;>GlgW4(a*VNzSJb-e}w z93v{~6^_D{wiq!n@A4?y$cvY9PaqMq83hH3U^}uCD5SnT#LrzfXgxPVa%P30;||!` z-~@OTbBi}Hg_<;<4WvfDyZ~XV=uR-L`!uS2+o${X=fZMAxX^=i2C3m&UyzS2Ca%n{ zARpt(NWf!C0-AJ)U@Z{^pzC>kWO_AKYOGVb8LVP0IkO>MHeBqof*{l_GD9Q#NNIjc z6P<_Hw66%Ccv={KQ^&Lla30M8f2pg~^Vm}5;~Pu3MioPWGclr=U$&_EIi!cNt*wY+ z3U|(}vo%c@k^m~KMB?C+O8;9mTAv&^M9#6WuZ4i|qNT6E^9ak2_I8xVbwbaGzef6> zz9c?`fD@nZ)aRQd1cp3!V948!(=2ocdzY+k6$6>x%%)`%^q{nfgv=wQsgzF-orcLE zt7Z*;vfC%cNc>=*ICOL532)Ff){b)`2<->pVDctkPb}~bVTbEr50ZRmuFD2(slei)P2;Nx- zSJ9}iBt3vf;CA-~aB8it29W+DWVgWV8{P3sq=DhfvX6cSdpOO~IsjtFCg2GA(3cD& zZHTfO>3^bn_}?M=n*kS%>cr3h(EY7nCSu~t+F5E?%h9A% zS&*fAr(8t-*m@Uf3>uY<<)UMMY}Sc@KCl37=-pl86F+ahO96r)#VrG-pAY804*VrN zXNMmAxsC6)zhEW7$`zp;!*+)rwBA-HI1HKC^xZK!9~*e-DPvd%c{$kYYkH}2`7zk} zEUp{3m)Yl>Qpv&NuFeV{UK5E#i5#qi;`(-}1s$UDJimUn*`0glDtu;9N(LGR#m}9jZnjSmTSnpTD_*GvwhYR(Z z2nFV!r|JVtJ(GMHkJ>IQ!F8z|wUz+pt}|A2Z)0O)X8i2cjrX^@i_O#!;-VRfp<#>U zJA+*wh6ff7z@H0+o$@+7SU!AwOF=+_Nj@&BV_Gxv8hlTX;fBzP&kLPe?V61otMo7e zkk)+Jo(zqz%o{&E*m})o6f6dA)33g{H2MRt!PXMkV#iJNg7)|j`A*m2{*5~?jl*~N z1n#p=5FrR+vUN< z&HY&8=sYoCvJ^`^9=mF*eax}g`G4=KPk1f4^W!n;)I%+tMVCC)ciPLyMyYh?c9pLuhyF>^3{&iSOuZC=eM&&vOQzdv2IdR{c`ye za2NPVUh_K#wqLZE6Fg~ctEKY6&wmeYm#ckWecyXiX_mE*!Vk0K@_%2rySe=M;R`%l z!Fqq%-RHmByQb0>gX@YT7+^`?N%3~ajprvf+;!n@@`d=blQKf9kP z3Qh{n4<5G5PkXT5!LI4M-LZc8{D0*uSFGUhng^U+vf-cbO%dsw@CU-cQfc{?7oy0e z(x^gMSw0#(@JeVjQBasD`h!nTVPIgq1iB*t+AtUmQwqa$Foffe`nnSbt}{PL+XB1@ Og2B_(&t;ucLK6U{6>;wX literal 0 HcmV?d00001 From 8e759052f4283d862208e02508b1aba1f48689d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Mon, 9 May 2016 14:52:57 -0400 Subject: [PATCH 08/10] Shapes: Update shapes tests for reorganized layers --- test/jasmine/tests/shapes_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index 74993cf628f..c553b55a396 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -58,7 +58,7 @@ describe('Test shapes:', function() { } function countShapeLayerNodesInSubplots() { - return d3.selectAll('.shapelayer-subplot').size(); + return d3.selectAll('.layer-subplot').size(); } function countSubplots(gd) { @@ -74,7 +74,7 @@ describe('Test shapes:', function() { } function countShapePathsInSubplots() { - return d3.selectAll('.shapelayer-subplot > path').size(); + return d3.selectAll('.layer-subplot > .shapelayer > path').size(); } describe('*shapeLowerLayer*', function() { From 83592c11ad1ab74252ba360e59e8e74f96b0eb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Mon, 9 May 2016 19:02:08 -0400 Subject: [PATCH 09/10] Images: Return promise for image loading --- src/components/images/draw.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/images/draw.js b/src/components/images/draw.js index af59cbd8828..d90275658f6 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -85,7 +85,6 @@ module.exports = function draw(gd) { } thisImage.attr({ - href: d.source, x: xPos, y: yPos, width: width, @@ -95,6 +94,22 @@ module.exports = function draw(gd) { }); + // Images load async so we must add the promise to the list + var imagePromise = new Promise(function(resolve) { + + thisImage.on('load', resolve); + thisImage.on('error', function() { + thisImage.remove(); + console.log('Image with source ' + d.source + ' could not be loaded.'); + resolve(); + }); + + thisImage.attr('href', d.source); + }); + + gd._promises.push(imagePromise); + + // Set proper clipping on images var xId = xref ? xref._id : '', yId = yref ? yref._id : '', From 466a7706472b349e58c35d0850470d02365ec78e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Mon, 9 May 2016 19:09:13 -0400 Subject: [PATCH 10/10] Images: Reject non-array images attribute --- src/components/images/defaults.js | 5 ++--- test/jasmine/tests/layout_images_test.js | 13 +++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 8e858f178ed..61b70b8fb6d 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -15,11 +15,10 @@ var attributes = require('./attributes'); module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { - if(!layoutIn.images) return; + if(!layoutIn.images || !Array.isArray(layoutIn.images)) return; - var containerIn = Array.isArray(layoutIn.images) ? - layoutIn.images : [layoutIn.images], + var containerIn = layoutIn.images, containerOut = layoutOut.images = []; diff --git a/test/jasmine/tests/layout_images_test.js b/test/jasmine/tests/layout_images_test.js index bbb5fb05d9f..33a67b439f5 100644 --- a/test/jasmine/tests/layout_images_test.js +++ b/test/jasmine/tests/layout_images_test.js @@ -24,6 +24,19 @@ describe('Layout images', function() { expect(layoutOut.images.length).toEqual(0); }); + it('should reject when not an array', function() { + layoutIn.images = { + source: 'http://www.someimagesource.com', + opacity: 0.5, + width: 0.2, + height: 0.2 + }; + + Images.supplyLayoutDefaults(layoutIn, layoutOut); + + expect(layoutOut.images).not.toBeDefined(); + }); + it('should coerce the correct defaults', function() { layoutIn.images[0] = { source: 'http://www.someimagesource.com' };