Skip to content

Commit f15af7f

Browse files
authored
Merge pull request #5075 from plotly/fast-image
Introduces attribute `source` to image traces
2 parents 2737bf2 + 66c58f6 commit f15af7f

21 files changed

+361
-61
lines changed

src/lib/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,11 @@ lib.isSafari = function() {
712712
return IS_SAFARI_REGEX.test(window.navigator.userAgent);
713713
};
714714

715+
var IS_IOS_REGEX = /iPad|iPhone|iPod/;
716+
lib.isIOS = function() {
717+
return IS_IOS_REGEX.test(window.navigator.userAgent);
718+
};
719+
715720
/**
716721
* Duck typing to recognize a d3 selection, mostly for IE9's benefit
717722
* because it doesn't handle instanceof like modern browsers

src/traces/image/attributes.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,25 @@ var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplat
1313
var extendFlat = require('../../lib/extend').extendFlat;
1414
var colormodel = require('./constants').colormodel;
1515

16-
var cm = ['rgb', 'rgba', 'hsl', 'hsla'];
16+
var cm = ['rgb', 'rgba', 'rgba256', 'hsl', 'hsla'];
1717
var zminDesc = [];
1818
var zmaxDesc = [];
1919
for(var i = 0; i < cm.length; i++) {
20-
zminDesc.push('For the `' + cm[i] + '` colormodel, it is [' + colormodel[cm[i]].min.join(', ') + '].');
21-
zmaxDesc.push('For the `' + cm[i] + '` colormodel, it is [' + colormodel[cm[i]].max.join(', ') + '].');
20+
var cr = colormodel[cm[i]];
21+
zminDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zminDflt || cr.min).join(', ') + '].');
22+
zmaxDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zmaxDflt || cr.max).join(', ') + '].');
2223
}
2324

2425
module.exports = extendFlat({
26+
source: {
27+
valType: 'string',
28+
role: 'info',
29+
editType: 'calc',
30+
description: [
31+
'Specifies the data URI of the image to be visualized.',
32+
'The URI consists of "data:image/[<media subtype>][;base64],<data>"'
33+
].join(' ')
34+
},
2535
z: {
2636
valType: 'data_array',
2737
role: 'info',
@@ -33,10 +43,13 @@ module.exports = extendFlat({
3343
colormodel: {
3444
valType: 'enumerated',
3545
values: cm,
36-
dflt: 'rgb',
3746
role: 'info',
3847
editType: 'calc',
39-
description: 'Color model used to map the numerical color components described in `z` into colors.'
48+
description: [
49+
'Color model used to map the numerical color components described in `z` into colors.',
50+
'If `source` is specified, this attribute will be set to `rgba256`',
51+
'otherwise it defaults to `rgb`.'
52+
].join(' ')
4053
},
4154
zmin: {
4255
valType: 'info_array',

src/traces/image/calc.js

+23-4
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,27 @@ var constants = require('./constants');
1313
var isNumeric = require('fast-isnumeric');
1414
var Axes = require('../../plots/cartesian/axes');
1515
var maxRowLength = require('../../lib').maxRowLength;
16+
var sizeOf = require('image-size');
17+
var dataUri = require('../../snapshot/helpers').IMAGE_URL_PREFIX;
18+
var Buffer = require('buffer/').Buffer; // note: the trailing slash is important!
1619

1720
module.exports = function calc(gd, trace) {
21+
var h;
22+
var w;
23+
if(trace._hasZ) {
24+
h = trace.z.length;
25+
w = maxRowLength(trace.z);
26+
} else if(trace._hasSource) {
27+
var size = getImageSize(trace.source);
28+
h = size.height;
29+
w = size.width;
30+
}
31+
1832
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
1933
var ya = Axes.getFromId(gd, trace.yaxis || 'y');
2034

2135
var x0 = xa.d2c(trace.x0) - trace.dx / 2;
2236
var y0 = ya.d2c(trace.y0) - trace.dy / 2;
23-
var h = trace.z.length;
24-
var w = maxRowLength(trace.z);
2537

2638
// Set axis range
2739
var i;
@@ -55,9 +67,9 @@ function constrain(min, max) {
5567

5668
// Generate a function to scale color components according to zmin/zmax and the colormodel
5769
function makeScaler(trace) {
58-
var colormodel = trace.colormodel;
70+
var cr = constants.colormodel[trace.colormodel];
71+
var colormodel = (cr.colormodel || trace.colormodel);
5972
var n = colormodel.length;
60-
var cr = constants.colormodel[colormodel];
6173

6274
trace._sArray = [];
6375
// Loop over all color components
@@ -84,3 +96,10 @@ function makeScaler(trace) {
8496
return c;
8597
};
8698
}
99+
100+
// Get image size
101+
function getImageSize(src) {
102+
var data = src.replace(dataUri, '');
103+
var buff = new Buffer(data, 'base64');
104+
return sizeOf(buff);
105+
}

src/traces/image/constants.js

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
module.exports = {
1212
colormodel: {
13+
// min and max define the numerical range accepted in CSS
14+
// If z(min|max)Dflt are not defined, z(min|max) will default to min/max
1315
rgb: {
1416
min: [0, 0, 0],
1517
max: [255, 255, 255],
@@ -22,6 +24,15 @@ module.exports = {
2224
fmt: function(c) {return c.slice(0, 4);},
2325
suffix: ['', '', '', '']
2426
},
27+
rgba256: {
28+
colormodel: 'rgba', // because rgba256 is not an accept colormodel in CSS
29+
zminDflt: [0, 0, 0, 0],
30+
zmaxDflt: [255, 255, 255, 255],
31+
min: [0, 0, 0, 0],
32+
max: [255, 255, 255, 1],
33+
fmt: function(c) {return c.slice(0, 4);},
34+
suffix: ['', '', '', '']
35+
},
2536
hsl: {
2637
min: [0, 0, 0],
2738
max: [360, 100, 100],

src/traces/image/defaults.js

+20-4
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@
1111
var Lib = require('../../lib');
1212
var attributes = require('./attributes');
1313
var constants = require('./constants');
14+
var dataUri = require('../../snapshot/helpers').IMAGE_URL_PREFIX;
1415

1516
module.exports = function supplyDefaults(traceIn, traceOut) {
1617
function coerce(attr, dflt) {
1718
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
1819
}
20+
coerce('source');
21+
// sanitize source to only allow for data URI representing images
22+
if(traceOut.source && !traceOut.source.match(dataUri)) delete traceOut.source;
23+
traceOut._hasSource = !!traceOut.source;
24+
1925
var z = coerce('z');
20-
if(z === undefined || !z.length || !z[0] || !z[0].length) {
26+
traceOut._hasZ = !(z === undefined || !z.length || !z[0] || !z[0].length);
27+
if(!traceOut._hasZ && !traceOut._hasSource) {
2128
traceOut.visible = false;
2229
return;
2330
}
@@ -26,10 +33,19 @@ module.exports = function supplyDefaults(traceIn, traceOut) {
2633
coerce('y0');
2734
coerce('dx');
2835
coerce('dy');
29-
var colormodel = coerce('colormodel');
3036

31-
coerce('zmin', constants.colormodel[colormodel].min);
32-
coerce('zmax', constants.colormodel[colormodel].max);
37+
var cm;
38+
if(traceOut._hasZ) {
39+
coerce('colormodel', 'rgb');
40+
cm = constants.colormodel[traceOut.colormodel];
41+
coerce('zmin', (cm.zminDflt || cm.min));
42+
coerce('zmax', (cm.zmaxDflt || cm.max));
43+
} else if(traceOut._hasSource) {
44+
traceOut.colormodel = 'rgba256';
45+
cm = constants.colormodel[traceOut.colormodel];
46+
traceOut.zmin = cm.zminDflt;
47+
traceOut.zmax = cm.zmaxDflt;
48+
}
3349

3450
coerce('text');
3551
coerce('hovertext');

src/traces/image/event_data.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ module.exports = function eventData(out, pt) {
1515
if(pt.ya) out.yaxis = pt.ya;
1616
out.color = pt.color;
1717
out.colormodel = pt.trace.colormodel;
18+
if(!out.z) out.z = pt.color;
1819
return out;
1920
};

src/traces/image/hover.js

+13-5
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,15 @@ module.exports = function hoverPoints(pointData, xval, yval) {
2828
var nx = Math.floor((xval - cd0.x0) / trace.dx);
2929
var ny = Math.floor(Math.abs(yval - cd0.y0) / trace.dy);
3030

31+
var pixel;
32+
if(trace._hasZ) {
33+
pixel = cd0.z[ny][nx];
34+
} else if(trace._hasSource) {
35+
pixel = trace._canvas.el.getContext('2d').getImageData(nx, ny, 1, 1).data;
36+
}
37+
3138
// return early if pixel is undefined
32-
if(!cd0.z[ny][nx]) return;
39+
if(!pixel) return;
3340

3441
var hoverinfo = cd0.hi || trace.hoverinfo;
3542
var fmtColor;
@@ -39,10 +46,11 @@ module.exports = function hoverPoints(pointData, xval, yval) {
3946
if(parts.indexOf('color') !== -1) fmtColor = true;
4047
}
4148

42-
var colormodel = trace.colormodel;
49+
var cr = constants.colormodel[trace.colormodel];
50+
var colormodel = cr.colormodel || trace.colormodel;
4351
var dims = colormodel.length;
44-
var c = trace._scaler(cd0.z[ny][nx]);
45-
var s = constants.colormodel[colormodel].suffix;
52+
var c = trace._scaler(pixel);
53+
var s = cr.suffix;
4654

4755
var colorstring = [];
4856
if(trace.hovertemplate || fmtColor) {
@@ -64,7 +72,7 @@ module.exports = function hoverPoints(pointData, xval, yval) {
6472
var py = ya.c2p(cd0.y0 + (ny + 0.5) * trace.dy);
6573
var xVal = cd0.x0 + (nx + 0.5) * trace.dx;
6674
var yVal = cd0.y0 + (ny + 0.5) * trace.dy;
67-
var zLabel = '[' + cd0.z[ny][nx].slice(0, trace.colormodel.length).join(', ') + ']';
75+
var zLabel = '[' + pixel.slice(0, trace.colormodel.length).join(', ') + ']';
6876
return [Lib.extendFlat(pointData, {
6977
index: [ny, nx],
7078
x0: xa.c2p(cd0.x0 + nx * trace.dx),

0 commit comments

Comments
 (0)