Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces attribute source to image traces #5075

Merged
merged 26 commits into from
Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1c893e2
Introduces attribute "source" to image traces featuring fast rendering
antoinerg Jun 26, 2020
d45496d
Ignores image trace's source attribute that are not data URIs
antoinerg Aug 13, 2020
f03c7ae
Adds image_labuda_droplets_source to mock_test.js
antoinerg Aug 14, 2020
2a687f2
Add "z" variable to hovertemplate in image defined by source attr
antoinerg Aug 14, 2020
aaf7cbc
Append promise of image loading to the plot api so it can wait for it
antoinerg Aug 14, 2020
e693446
In image traces, delete source attribute when it is invalid
antoinerg Aug 14, 2020
130593e
Fixes image_test to reflect that invalid source are deleted
antoinerg Aug 17, 2020
09eb1d0
Improve code style in the plot routine of image traces
antoinerg Aug 17, 2020
9b8a843
Add a workaround to image's onload event not firing when image is cached
antoinerg Aug 17, 2020
2ba0998
Introduces Lib.isIOS() to detect Apple mobile devices
antoinerg Aug 17, 2020
a2ea331
Fall back to legacy rendering of image traces for iOS devices
antoinerg Aug 17, 2020
4f807c5
Add missing semicolon
antoinerg Aug 18, 2020
7dd3b68
Fall back to legacy rendering of images traces for IE
antoinerg Aug 18, 2020
f2caed8
Loads images into an <img> instead of an <image> for image traces
antoinerg Aug 18, 2020
d91e0c1
Reintroduce image traces with source attribute in the `test-image` suite
antoinerg Aug 18, 2020
fe1d208
Improves code readability for image traces
antoinerg Aug 18, 2020
2960e55
Removes broken test following changes in how image traces check userA…
antoinerg Aug 18, 2020
3717cbe
Rename _isFrom(Z|Source) to _has(Z|Source)
antoinerg Aug 19, 2020
b586a86
Replace Labuda's photography of droplets by one from public domain
antoinerg Aug 19, 2020
d73f4c4
Add image_astronaut_source to mock_test.js
antoinerg Aug 19, 2020
18133c9
Update description of attribute `source` in images traces
antoinerg Aug 20, 2020
19ce21d
Introduce colormodel "rgba256" to image traces
antoinerg Aug 21, 2020
74574b6
Update description of attribute colormodel in image traces
antoinerg Aug 31, 2020
8c736f7
Update description of attribute source in image traces
antoinerg Aug 31, 2020
3b75921
Prefer z data over source in image traces and improve code readability
antoinerg Aug 31, 2020
66c58f6
:hocho: unreachable code in image trace defaults routine
antoinerg Aug 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
archmoj marked this conversation as resolved.
Show resolved Hide resolved
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 || cm.min);
traceOut.zmax = (cm.zmaxDflt || cm.max);
archmoj marked this conversation as resolved.
Show resolved Hide resolved
}

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