Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 5940dd1

Browse filesBrowse files
committed
Merge pull request plotly#525 from plotly/plot-images
Feature: Plot layout images 🎉
2 parents 1903061 + 1f409e4 commit 5940dd1
Copy full SHA for 5940dd1

File tree

20 files changed

+888
-18
lines changed
Filter options

20 files changed

+888
-18
lines changed

‎src/components/images/attributes.js

Copy file name to clipboard
+158Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var cartesianConstants = require('../../plots/cartesian/constants');
12+
13+
14+
module.exports = {
15+
_isLinkedToArray: true,
16+
17+
source: {
18+
valType: 'string',
19+
role: 'info',
20+
description: [
21+
'Specifies the URL of the image to be used.',
22+
'The URL must be accessible from the domain where the',
23+
'plot code is run, and can be either relative or absolute.'
24+
25+
].join(' ')
26+
},
27+
28+
layer: {
29+
valType: 'enumerated',
30+
values: ['below', 'above'],
31+
dflt: 'above',
32+
role: 'info',
33+
description: [
34+
'Specifies whether images are drawn below or above traces.',
35+
'When `xref` and `yref` are both set to `paper`,',
36+
'image is drawn below the entire plot area.'
37+
].join(' ')
38+
},
39+
40+
sizex: {
41+
valType: 'number',
42+
role: 'info',
43+
dflt: 0,
44+
description: [
45+
'Sets the image container size horizontally.',
46+
'The image will be sized based on the `position` value.',
47+
'When `xref` is set to `paper`, units are sized relative',
48+
'to the plot width.'
49+
].join(' ')
50+
},
51+
52+
sizey: {
53+
valType: 'number',
54+
role: 'info',
55+
dflt: 0,
56+
description: [
57+
'Sets the image container size vertically.',
58+
'The image will be sized based on the `position` value.',
59+
'When `yref` is set to `paper`, units are sized relative',
60+
'to the plot height.'
61+
].join(' ')
62+
},
63+
64+
sizing: {
65+
valType: 'enumerated',
66+
values: ['fill', 'contain', 'stretch'],
67+
dflt: 'contain',
68+
role: 'info',
69+
description: [
70+
'Specifies which dimension of the image to constrain.'
71+
].join(' ')
72+
},
73+
74+
opacity: {
75+
valType: 'number',
76+
role: 'info',
77+
min: 0,
78+
max: 1,
79+
dflt: 1,
80+
description: 'Sets the opacity of the image.'
81+
},
82+
83+
x: {
84+
valType: 'number',
85+
role: 'info',
86+
dflt: 0,
87+
description: [
88+
'Sets the image\'s x position.',
89+
'When `xref` is set to `paper`, units are sized relative',
90+
'to the plot height.',
91+
'See `xref` for more info'
92+
].join(' ')
93+
},
94+
95+
y: {
96+
valType: 'number',
97+
role: 'info',
98+
dflt: 0,
99+
description: [
100+
'Sets the image\'s y position.',
101+
'When `yref` is set to `paper`, units are sized relative',
102+
'to the plot height.',
103+
'See `yref` for more info'
104+
].join(' ')
105+
},
106+
107+
xanchor: {
108+
valType: 'enumerated',
109+
values: ['left', 'center', 'right'],
110+
dflt: 'left',
111+
role: 'info',
112+
description: 'Sets the anchor for the x position'
113+
},
114+
115+
yanchor: {
116+
valType: 'enumerated',
117+
values: ['top', 'middle', 'bottom'],
118+
dflt: 'top',
119+
role: 'info',
120+
description: 'Sets the anchor for the y position.'
121+
},
122+
123+
xref: {
124+
valType: 'enumerated',
125+
values: [
126+
'paper',
127+
cartesianConstants.idRegex.x.toString()
128+
],
129+
dflt: 'paper',
130+
role: 'info',
131+
description: [
132+
'Sets the images\'s x coordinate axis.',
133+
'If set to a x axis id (e.g. *x* or *x2*), the `x` position',
134+
'refers to an x data coordinate',
135+
'If set to *paper*, the `x` position refers to the distance from',
136+
'the left of plot in normalized coordinates',
137+
'where *0* (*1*) corresponds to the left (right).'
138+
].join(' ')
139+
},
140+
141+
yref: {
142+
valType: 'enumerated',
143+
values: [
144+
'paper',
145+
cartesianConstants.idRegex.y.toString()
146+
],
147+
dflt: 'paper',
148+
role: 'info',
149+
description: [
150+
'Sets the images\'s y coordinate axis.',
151+
'If set to a y axis id (e.g. *y* or *y2*), the `y` position',
152+
'refers to a y data coordinate.',
153+
'If set to *paper*, the `y` position refers to the distance from',
154+
'the bottom of the plot in normalized coordinates',
155+
'where *0* (*1*) corresponds to the bottom (top).'
156+
].join(' ')
157+
}
158+
};

‎src/components/images/defaults.js

Copy file name to clipboard
+64Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Axes = require('../../plots/cartesian/axes');
12+
var Lib = require('../../lib');
13+
var attributes = require('./attributes');
14+
15+
16+
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
17+
18+
if(!layoutIn.images || !Array.isArray(layoutIn.images)) return;
19+
20+
21+
var containerIn = layoutIn.images,
22+
containerOut = layoutOut.images = [];
23+
24+
25+
for(var i = 0; i < containerIn.length; i++) {
26+
var image = containerIn[i];
27+
28+
if(!image.source) continue;
29+
30+
var defaulted = imageDefaults(containerIn[i] || {}, containerOut[i] || {}, layoutOut);
31+
containerOut.push(defaulted);
32+
}
33+
};
34+
35+
36+
function imageDefaults(imageIn, imageOut, fullLayout) {
37+
38+
imageOut = imageOut || {};
39+
40+
function coerce(attr, dflt) {
41+
return Lib.coerce(imageIn, imageOut, attributes, attr, dflt);
42+
}
43+
44+
coerce('source');
45+
coerce('layer');
46+
coerce('x');
47+
coerce('y');
48+
coerce('xanchor');
49+
coerce('yanchor');
50+
coerce('sizex');
51+
coerce('sizey');
52+
coerce('sizing');
53+
coerce('opacity');
54+
55+
for(var i = 0; i < 2; i++) {
56+
var tdMock = { _fullLayout: fullLayout },
57+
axLetter = ['x', 'y'][i];
58+
59+
// 'paper' is the fallback axref
60+
Axes.coerceRef(imageIn, imageOut, tdMock, axLetter, 'paper');
61+
}
62+
63+
return imageOut;
64+
}

‎src/components/images/draw.js

Copy file name to clipboard
+171Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var d3 = require('d3');
12+
var Drawing = require('../drawing');
13+
var Axes = require('../../plots/cartesian/axes');
14+
15+
module.exports = function draw(gd) {
16+
17+
var fullLayout = gd._fullLayout,
18+
imageDataAbove = [],
19+
imageDataSubplot = [],
20+
imageDataBelow = [];
21+
22+
if(!fullLayout.images) return;
23+
24+
25+
// Sort into top, subplot, and bottom layers
26+
for(var i = 0; i < fullLayout.images.length; i++) {
27+
var img = fullLayout.images[i];
28+
29+
if(img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') {
30+
imageDataSubplot.push(img);
31+
} else if(img.layer === 'above') {
32+
imageDataAbove.push(img);
33+
} else {
34+
imageDataBelow.push(img);
35+
}
36+
}
37+
38+
39+
var anchors = {
40+
x: {
41+
left: { sizing: 'xMin', offset: 0 },
42+
center: { sizing: 'xMid', offset: -1 / 2 },
43+
right: { sizing: 'xMax', offset: -1 }
44+
},
45+
y: {
46+
top: { sizing: 'YMin', offset: 0 },
47+
middle: { sizing: 'YMid', offset: -1 / 2 },
48+
bottom: { sizing: 'YMax', offset: -1 }
49+
}
50+
};
51+
52+
53+
// Images must be converted to dataURL's for exporting.
54+
function setImage(d) {
55+
56+
var thisImage = d3.select(this);
57+
58+
var imagePromise = new Promise(function(resolve) {
59+
60+
var img = new Image();
61+
62+
// If not set, a `tainted canvas` error is thrown
63+
img.setAttribute('crossOrigin', 'anonymous');
64+
img.onerror = errorHandler;
65+
img.onload = function() {
66+
67+
var canvas = document.createElement('canvas');
68+
canvas.width = this.width;
69+
canvas.height = this.height;
70+
71+
var ctx = canvas.getContext('2d');
72+
ctx.drawImage(this, 0, 0);
73+
74+
var dataURL = canvas.toDataURL('image/png');
75+
76+
thisImage.attr('xlink:href', dataURL);
77+
};
78+
79+
80+
thisImage.on('error', errorHandler);
81+
thisImage.on('load', resolve);
82+
83+
img.src = d.source;
84+
85+
function errorHandler() {
86+
thisImage.remove();
87+
resolve();
88+
}
89+
});
90+
91+
gd._promises.push(imagePromise);
92+
}
93+
94+
function applyAttributes(d) {
95+
96+
var thisImage = d3.select(this);
97+
98+
// Axes if specified
99+
var xref = Axes.getFromId(gd, d.xref),
100+
yref = Axes.getFromId(gd, d.yref);
101+
102+
var size = fullLayout._size,
103+
width = xref ? Math.abs(xref.l2p(d.sizex) - xref.l2p(0)) : d.sizex * size.w,
104+
height = yref ? Math.abs(yref.l2p(d.sizey) - yref.l2p(0)) : d.sizey * size.h;
105+
106+
// Offsets for anchor positioning
107+
var xOffset = width * anchors.x[d.xanchor].offset + size.l,
108+
yOffset = height * anchors.y[d.yanchor].offset + size.t;
109+
110+
var sizing = anchors.x[d.xanchor].sizing + anchors.y[d.yanchor].sizing;
111+
112+
// Final positions
113+
var xPos = (xref ? xref.l2p(d.x) : d.x * size.w) + xOffset,
114+
yPos = (yref ? yref.l2p(d.y) : size.h - d.y * size.h) + yOffset;
115+
116+
117+
// Construct the proper aspectRatio attribute
118+
switch(d.sizing) {
119+
case 'fill':
120+
sizing += ' slice';
121+
break;
122+
123+
case 'stretch':
124+
sizing = 'none';
125+
break;
126+
}
127+
128+
thisImage.attr({
129+
x: xPos,
130+
y: yPos,
131+
width: width,
132+
height: height,
133+
preserveAspectRatio: sizing,
134+
opacity: d.opacity
135+
});
136+
137+
138+
// Set proper clipping on images
139+
var xId = xref ? xref._id : '',
140+
yId = yref ? yref._id : '',
141+
clipAxes = xId + yId;
142+
143+
thisImage.call(Drawing.setClipUrl, 'clip' + fullLayout._uid + clipAxes);
144+
}
145+
146+
147+
// Required for updating images
148+
function keyFunction(d) {
149+
return d.source;
150+
}
151+
152+
153+
var imagesBelow = fullLayout._imageLowerLayer.selectAll('image')
154+
.data(imageDataBelow, keyFunction),
155+
imagesSubplot = fullLayout._imageSubplotLayer.selectAll('image')
156+
.data(imageDataSubplot, keyFunction),
157+
imagesAbove = fullLayout._imageUpperLayer.selectAll('image')
158+
.data(imageDataAbove, keyFunction);
159+
160+
imagesBelow.enter().append('image').each(setImage);
161+
imagesSubplot.enter().append('image').each(setImage);
162+
imagesAbove.enter().append('image').each(setImage);
163+
164+
imagesBelow.exit().remove();
165+
imagesSubplot.exit().remove();
166+
imagesAbove.exit().remove();
167+
168+
imagesBelow.each(applyAttributes);
169+
imagesSubplot.each(applyAttributes);
170+
imagesAbove.each(applyAttributes);
171+
};

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.