Skip to content

Commit

Permalink
Merge pull request #5075 from plotly/fast-image
Browse files Browse the repository at this point in the history
Introduces attribute `source` to image traces
  • Loading branch information
antoinerg authored Aug 31, 2020
2 parents 2737bf2 + 66c58f6 commit f15af7f
Show file tree
Hide file tree
Showing 21 changed files with 361 additions and 61 deletions.
5 changes: 5 additions & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,11 @@ lib.isSafari = function() {
return IS_SAFARI_REGEX.test(window.navigator.userAgent);
};

var IS_IOS_REGEX = /iPad|iPhone|iPod/;
lib.isIOS = function() {
return IS_IOS_REGEX.test(window.navigator.userAgent);
};

/**
* Duck typing to recognize a d3 selection, mostly for IE9's benefit
* because it doesn't handle instanceof like modern browsers
Expand Down
23 changes: 18 additions & 5 deletions src/traces/image/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,25 @@ var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplat
var extendFlat = require('../../lib/extend').extendFlat;
var colormodel = require('./constants').colormodel;

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

module.exports = extendFlat({
source: {
valType: 'string',
role: 'info',
editType: 'calc',
description: [
'Specifies the data URI of the image to be visualized.',
'The URI consists of "data:image/[<media subtype>][;base64],<data>"'
].join(' ')
},
z: {
valType: 'data_array',
role: 'info',
Expand All @@ -33,10 +43,13 @@ module.exports = extendFlat({
colormodel: {
valType: 'enumerated',
values: cm,
dflt: 'rgb',
role: 'info',
editType: 'calc',
description: 'Color model used to map the numerical color components described in `z` into colors.'
description: [
'Color model used to map the numerical color components described in `z` into colors.',
'If `source` is specified, this attribute will be set to `rgba256`',
'otherwise it defaults to `rgb`.'
].join(' ')
},
zmin: {
valType: 'info_array',
Expand Down
27 changes: 23 additions & 4 deletions src/traces/image/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,27 @@ var constants = require('./constants');
var isNumeric = require('fast-isnumeric');
var Axes = require('../../plots/cartesian/axes');
var maxRowLength = require('../../lib').maxRowLength;
var sizeOf = require('image-size');
var dataUri = require('../../snapshot/helpers').IMAGE_URL_PREFIX;
var Buffer = require('buffer/').Buffer; // note: the trailing slash is important!

module.exports = function calc(gd, trace) {
var h;
var w;
if(trace._hasZ) {
h = trace.z.length;
w = maxRowLength(trace.z);
} else if(trace._hasSource) {
var size = getImageSize(trace.source);
h = size.height;
w = size.width;
}

var xa = Axes.getFromId(gd, trace.xaxis || 'x');
var ya = Axes.getFromId(gd, trace.yaxis || 'y');

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

// Set axis range
var i;
Expand Down Expand Up @@ -55,9 +67,9 @@ function constrain(min, max) {

// Generate a function to scale color components according to zmin/zmax and the colormodel
function makeScaler(trace) {
var colormodel = trace.colormodel;
var cr = constants.colormodel[trace.colormodel];
var colormodel = (cr.colormodel || trace.colormodel);
var n = colormodel.length;
var cr = constants.colormodel[colormodel];

trace._sArray = [];
// Loop over all color components
Expand All @@ -84,3 +96,10 @@ function makeScaler(trace) {
return c;
};
}

// Get image size
function getImageSize(src) {
var data = src.replace(dataUri, '');
var buff = new Buffer(data, 'base64');
return sizeOf(buff);
}
11 changes: 11 additions & 0 deletions src/traces/image/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

module.exports = {
colormodel: {
// min and max define the numerical range accepted in CSS
// If z(min|max)Dflt are not defined, z(min|max) will default to min/max
rgb: {
min: [0, 0, 0],
max: [255, 255, 255],
Expand All @@ -22,6 +24,15 @@ module.exports = {
fmt: function(c) {return c.slice(0, 4);},
suffix: ['', '', '', '']
},
rgba256: {
colormodel: 'rgba', // because rgba256 is not an accept colormodel in CSS
zminDflt: [0, 0, 0, 0],
zmaxDflt: [255, 255, 255, 255],
min: [0, 0, 0, 0],
max: [255, 255, 255, 1],
fmt: function(c) {return c.slice(0, 4);},
suffix: ['', '', '', '']
},
hsl: {
min: [0, 0, 0],
max: [360, 100, 100],
Expand Down
24 changes: 20 additions & 4 deletions src/traces/image/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@
var Lib = require('../../lib');
var attributes = require('./attributes');
var constants = require('./constants');
var dataUri = require('../../snapshot/helpers').IMAGE_URL_PREFIX;

module.exports = function supplyDefaults(traceIn, traceOut) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
coerce('source');
// sanitize source to only allow for data URI representing images
if(traceOut.source && !traceOut.source.match(dataUri)) delete traceOut.source;
traceOut._hasSource = !!traceOut.source;

var z = coerce('z');
if(z === undefined || !z.length || !z[0] || !z[0].length) {
traceOut._hasZ = !(z === undefined || !z.length || !z[0] || !z[0].length);
if(!traceOut._hasZ && !traceOut._hasSource) {
traceOut.visible = false;
return;
}
Expand All @@ -26,10 +33,19 @@ module.exports = function supplyDefaults(traceIn, traceOut) {
coerce('y0');
coerce('dx');
coerce('dy');
var colormodel = coerce('colormodel');

coerce('zmin', constants.colormodel[colormodel].min);
coerce('zmax', constants.colormodel[colormodel].max);
var cm;
if(traceOut._hasZ) {
coerce('colormodel', 'rgb');
cm = constants.colormodel[traceOut.colormodel];
coerce('zmin', (cm.zminDflt || cm.min));
coerce('zmax', (cm.zmaxDflt || cm.max));
} else if(traceOut._hasSource) {
traceOut.colormodel = 'rgba256';
cm = constants.colormodel[traceOut.colormodel];
traceOut.zmin = cm.zminDflt;
traceOut.zmax = cm.zmaxDflt;
}

coerce('text');
coerce('hovertext');
Expand Down
1 change: 1 addition & 0 deletions src/traces/image/event_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ module.exports = function eventData(out, pt) {
if(pt.ya) out.yaxis = pt.ya;
out.color = pt.color;
out.colormodel = pt.trace.colormodel;
if(!out.z) out.z = pt.color;
return out;
};
18 changes: 13 additions & 5 deletions src/traces/image/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,15 @@ module.exports = function hoverPoints(pointData, xval, yval) {
var nx = Math.floor((xval - cd0.x0) / trace.dx);
var ny = Math.floor(Math.abs(yval - cd0.y0) / trace.dy);

var pixel;
if(trace._hasZ) {
pixel = cd0.z[ny][nx];
} else if(trace._hasSource) {
pixel = trace._canvas.el.getContext('2d').getImageData(nx, ny, 1, 1).data;
}

// return early if pixel is undefined
if(!cd0.z[ny][nx]) return;
if(!pixel) return;

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

var colormodel = trace.colormodel;
var cr = constants.colormodel[trace.colormodel];
var colormodel = cr.colormodel || trace.colormodel;
var dims = colormodel.length;
var c = trace._scaler(cd0.z[ny][nx]);
var s = constants.colormodel[colormodel].suffix;
var c = trace._scaler(pixel);
var s = cr.suffix;

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

0 comments on commit f15af7f

Please sign in to comment.