diff --git a/src/traces/image/attributes.js b/src/traces/image/attributes.js index c41d7aa4e0c..ac193e559a0 100644 --- a/src/traces/image/attributes.js +++ b/src/traces/image/attributes.js @@ -35,19 +35,19 @@ module.exports = extendFlat({ values: cm, dflt: 'rgb', role: 'info', - editType: 'plot', + editType: 'calc', description: 'Color model used to map the numerical color components described in `z` into colors.' }, zmin: { valType: 'info_array', items: [ - {valType: 'number', editType: 'plot'}, - {valType: 'number', editType: 'plot'}, - {valType: 'number', editType: 'plot'}, - {valType: 'number', editType: 'plot'} + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'} ], role: 'info', - editType: 'plot', + editType: 'calc', description: [ 'Array defining the lower bound for each color component.', 'Note that the default value will depend on the colormodel.', @@ -57,13 +57,13 @@ module.exports = extendFlat({ zmax: { valType: 'info_array', items: [ - {valType: 'number', editType: 'plot'}, - {valType: 'number', editType: 'plot'}, - {valType: 'number', editType: 'plot'}, - {valType: 'number', editType: 'plot'} + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'} ], role: 'info', - editType: 'plot', + editType: 'calc', description: [ 'Array defining the higher bound for each color component.', 'Note that the default value will depend on the colormodel.', diff --git a/src/traces/image/calc.js b/src/traces/image/calc.js index e826c3c2a5a..db3df881b78 100644 --- a/src/traces/image/calc.js +++ b/src/traces/image/calc.js @@ -8,6 +8,9 @@ 'use strict'; +var Lib = require('../../lib'); +var constants = require('./constants'); +var isNumeric = require('fast-isnumeric'); var Axes = require('../../plots/cartesian/axes'); var maxRowLength = require('../../lib').maxRowLength; @@ -28,6 +31,7 @@ module.exports = function calc(gd, trace) { if(ya && ya.type === 'log') for(i = 0; i < h; i++) yrange.push(y0 + i * trace.dy); trace._extremes[xa._id] = Axes.findExtremes(xa, xrange); trace._extremes[ya._id] = Axes.findExtremes(ya, yrange); + trace._scaler = makeScaler(trace); var cd0 = { x0: x0, @@ -38,3 +42,45 @@ module.exports = function calc(gd, trace) { }; return [cd0]; }; + +function scale(zero, ratio, min, max) { + return function(c) { + return Lib.constrain((c - zero) * ratio, min, max); + }; +} + +function constrain(min, max) { + return function(c) { return Lib.constrain(c, min, max);}; +} + +// Generate a function to scale color components according to zmin/zmax and the colormodel +function makeScaler(trace) { + var colormodel = trace.colormodel; + var n = colormodel.length; + var cr = constants.colormodel[colormodel]; + + trace._sArray = []; + // Loop over all color components + for(var k = 0; k < n; k++) { + if(cr.min[k] !== trace.zmin[k] || cr.max[k] !== trace.zmax[k]) { + trace._sArray.push(scale( + trace.zmin[k], + (cr.max[k] - cr.min[k]) / (trace.zmax[k] - trace.zmin[k]), + cr.min[k], + cr.max[k] + )); + } else { + trace._sArray.push(constrain(cr.min[k], cr.max[k])); + } + } + + return function(pixel) { + var c = pixel.slice(0, n); + for(var k = 0; k < n; k++) { + var ck = c[k]; + if(!isNumeric(ck)) return false; + c[k] = trace._sArray[k](ck); + } + return c; + }; +} diff --git a/src/traces/image/constants.js b/src/traces/image/constants.js index 865c340ff40..1a4570c3f1c 100644 --- a/src/traces/image/constants.js +++ b/src/traces/image/constants.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; module.exports = { diff --git a/src/traces/image/defaults.js b/src/traces/image/defaults.js index ee55ee8859f..c2ebd3c3310 100644 --- a/src/traces/image/defaults.js +++ b/src/traces/image/defaults.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var Lib = require('../../lib'); diff --git a/src/traces/image/index.js b/src/traces/image/index.js index 69c58b5fed9..9ae0c9542be 100644 --- a/src/traces/image/index.js +++ b/src/traces/image/index.js @@ -12,7 +12,7 @@ module.exports = { attributes: require('./attributes'), supplyDefaults: require('./defaults'), calc: require('./calc'), - plot: require('./plot').plot, + plot: require('./plot'), style: require('./style'), hoverPoints: require('./hover'), eventData: require('./event_data'), diff --git a/src/traces/image/plot.js b/src/traces/image/plot.js index e0f6087f8a4..851c1772c11 100644 --- a/src/traces/image/plot.js +++ b/src/traces/image/plot.js @@ -7,55 +7,13 @@ */ 'use strict'; + var d3 = require('d3'); var Lib = require('../../lib'); var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); var constants = require('./constants'); -module.exports = {}; - -// Generate a function to scale color components according to zmin/zmax and the colormodel -var scaler = function(trace) { - var colormodel = trace.colormodel; - var n = colormodel.length; - var cr = constants.colormodel[colormodel]; - - function scale(zero, factor, min, max) { - return function(c) { - c = (c - zero) * factor; - c = Lib.constrain(c, min, max); - return c; - }; - } - - function constrain(min, max) { - return function(c) { return Lib.constrain(c, min, max);}; - } - - var s = []; - // Loop over all color components - for(var k = 0; k < n; k++) { - if(cr.min[k] !== trace.zmin[k] || cr.max[k] !== trace.zmax[k]) { - s.push(scale( - trace.zmin[k], - (cr.max[k] - cr.min[k]) / (trace.zmax[k] - trace.zmin[k]), - cr.min[k], - cr.max[k] - )); - } else { - s.push(constrain(cr.min[k], cr.max[k])); - } - } - - return function(pixel) { - var c = pixel.slice(0, n); - for(var k = 0; k < n; k++) { - c[k] = s[k](c[k]); - } - return c; - }; -}; -module.exports.plot = function(gd, plotinfo, cdimage, imageLayer) { +module.exports = function plot(gd, plotinfo, cdimage, imageLayer) { var xa = plotinfo.xaxis; var ya = plotinfo.yaxis; @@ -129,10 +87,10 @@ module.exports.plot = function(gd, plotinfo, cdimage, imageLayer) { canvas.width = imageWidth; canvas.height = imageHeight; var context = canvas.getContext('2d'); + var ipx = function(i) {return Lib.constrain(Math.round(xa.c2p(x0 + i * dx) - left), 0, imageWidth);}; var jpx = function(j) {return Lib.constrain(Math.round(ya.c2p(y0 + j * dy) - top), 0, imageHeight);}; - trace._scaler = scaler(trace); var fmt = constants.colormodel[trace.colormodel].fmt; var c; for(i = 0; i < cd0.w; i++) { @@ -142,7 +100,12 @@ module.exports.plot = function(gd, plotinfo, cdimage, imageLayer) { var jpx0 = jpx(j); var jpx1 = jpx(j + 1); if(jpx1 === jpx0 || isNaN(jpx1) || isNaN(jpx0) || !z[j][i]) continue; c = trace._scaler(z[j][i]); - context.fillStyle = trace.colormodel + '(' + fmt(c).join(',') + ')'; + if(c) { + context.fillStyle = trace.colormodel + '(' + fmt(c).join(',') + ')'; + } else { + // Return a transparent pixel + context.fillStyle = 'rgba(0,0,0,0)'; + } context.fillRect(ipx0, jpx0, ipx1 - ipx0, jpx1 - jpx0); } } diff --git a/src/traces/image/style.js b/src/traces/image/style.js index 9f98c4f4bd2..efb79761b32 100644 --- a/src/traces/image/style.js +++ b/src/traces/image/style.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var d3 = require('d3'); diff --git a/test/image/baselines/image_non_numeric.png b/test/image/baselines/image_non_numeric.png new file mode 100644 index 00000000000..78da6e369b1 Binary files /dev/null and b/test/image/baselines/image_non_numeric.png differ diff --git a/test/image/mocks/image_non_numeric.json b/test/image/mocks/image_non_numeric.json new file mode 100644 index 00000000000..b5ca58be16c --- /dev/null +++ b/test/image/mocks/image_non_numeric.json @@ -0,0 +1,94 @@ +{ + "data": [{ + "type": "image", + "hoverinfo": "all", + "z": [ + [ + [255, 0, 0], + [191, 0, 0], + [127, 0, 0] + ], + [ + [0, 255, 0], + [0, 191, 0], + [0, 127, 0] + ], + [ + [0, 0, 255], + [0, 0, 191], + [0, 0, 127] + ] + ] + }, { + "yaxis": "y2", + "xaxis": "x2", + "type": "image", + "hoverinfo": "all", + "z": [ + [ + [255, 0, 0], + [191, 0, null], + [127, 0, 0] + ], + [ + [0, 255, 0], + [false, 191, 0], + [0, 127, 0] + ], + [ + [0, 0, 255], + [0, true, 191], + [0, 0, 127] + ] + ] + }, { + "yaxis": "y3", + "xaxis": "x3", + "type": "image", + "hoverinfo": "all", + "z": [ + [ + [255, 0, 0], + [191, 0, 0], + [-10000, 0, 0] + ], + [ + [0, 255, 0], + [0, "", 0], + [0, 127, 0] + ], + [ + [0, 0, 10000], + [0, 0, 191], + [0, 0, 127] + ] + ] + }, { + "yaxis": "y4", + "xaxis": "x4", + "type": "image", + "hoverinfo": "all", + "z": [ + [ + [255, 0], + [191, 0, null], + [127, 0, ""] + ], + [ + [true, 255, 0], + [false, 191, 0], + ["text", 127, 0] + ], + [] + ] + }], + "layout": { + "width": 600, + "height": 600, + "grid": { + "rows": 2, + "columns": 2, + "pattern": "independent" + } + } +}