diff --git a/devtools/test_dashboard/test_gl3d.js b/devtools/test_dashboard/test_gl3d.js index dce47915ce0..a6321c50160 100644 --- a/devtools/test_dashboard/test_gl3d.js +++ b/devtools/test_dashboard/test_gl3d.js @@ -35,6 +35,7 @@ plots['marker-arrays'] = require('@mocks/gl3d_marker-arrays.json'); plots['scatter3d-colorscale'] = require('@mocks/gl3d_scatter3d-colorscale.json'); plots['autocolorscale'] = require('@mocks/gl3d_autocolorscale.json'); plots['nan-holes'] = require('@mocks/gl3d_nan-holes.json'); -plots['tetrahedra'] = require('@mocks/gl3d_tet.json'); +plots['tet'] = require('@mocks/gl3d_tet.json'); +plots['surface_intensity'] = require('@mocks/gl3d_surface_intensity.json'); plotButtons(plots, figDir); diff --git a/package.json b/package.json index 8fd5469086b..94714c3f85b 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "gl-scatter3d": "^1.0.4", "gl-select-box": "^1.0.1", "gl-spikes2d": "^1.0.1", - "gl-surface3d": "^1.1.1", + "gl-surface3d": "^1.2.3", "mouse-change": "^1.1.1", "mouse-wheel": "^1.0.2", "ndarray": "^1.0.16", diff --git a/src/traces/choropleth/calc.js b/src/traces/choropleth/calc.js new file mode 100644 index 00000000000..107d4db5384 --- /dev/null +++ b/src/traces/choropleth/calc.js @@ -0,0 +1,17 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var colorscaleCalc = require('../../components/colorscale/calc'); + + +module.exports = function calc(gd, trace) { + colorscaleCalc(trace, trace.z, '', 'z'); +}; diff --git a/src/traces/choropleth/index.js b/src/traces/choropleth/index.js index 41e45e3c84f..e782d6d0144 100644 --- a/src/traces/choropleth/index.js +++ b/src/traces/choropleth/index.js @@ -14,7 +14,7 @@ var Choropleth = {}; Choropleth.attributes = require('./attributes'); Choropleth.supplyDefaults = require('./defaults'); Choropleth.colorbar = require('../heatmap/colorbar'); -Choropleth.calc = require('../surface/calc'); +Choropleth.calc = require('./calc'); Choropleth.plot = require('./plot').plot; Choropleth.moduleType = 'trace'; diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index b8af834d412..57270b2799b 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -95,9 +95,16 @@ module.exports = { valType: 'data_array', description: 'Sets the text elements associated with each z value.' }, - zauto: colorscaleAttrs.zauto, - zmin: colorscaleAttrs.zmin, - zmax: colorscaleAttrs.zmax, + surfacecolor: { + valType: 'data_array', + description: [ + 'Sets the surface color values,', + 'used for setting a color scale independent of `z`.' + ].join(' ') + }, + cauto: colorscaleAttrs.zauto, + cmin: colorscaleAttrs.zmin, + cmax: colorscaleAttrs.zmax, colorscale: colorscaleAttrs.colorscale, autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}), @@ -161,5 +168,17 @@ module.exports = { _nestedModules: { // nested module coupling 'colorbar': 'Colorbar' + }, + + _deprecated: { + zauto: extendFlat({}, colorscaleAttrs.zauto, { + description: 'Obsolete. Use `cauto` instead.' + }), + zmin: extendFlat({}, colorscaleAttrs.zmin, { + description: 'Obsolete. Use `cmin` instead.' + }), + zmax: extendFlat({}, colorscaleAttrs.zmax, { + description: 'Obsolete. Use `cmax` instead.' + }) } }; diff --git a/src/traces/surface/calc.js b/src/traces/surface/calc.js index d6137f6d5fb..4ac8bc03d2d 100644 --- a/src/traces/surface/calc.js +++ b/src/traces/surface/calc.js @@ -14,5 +14,9 @@ var colorscaleCalc = require('../../components/colorscale/calc'); // Compute auto-z and autocolorscale if applicable module.exports = function calc(gd, trace) { - colorscaleCalc(trace, trace.z, '', 'z'); + if(trace.surfacecolor) { + colorscaleCalc(trace, trace.surfacecolor, '', 'c'); + } else { + colorscaleCalc(trace, trace.z, '', 'c'); + } }; diff --git a/src/traces/surface/colorbar.js b/src/traces/surface/colorbar.js new file mode 100644 index 00000000000..26d1fd475f5 --- /dev/null +++ b/src/traces/surface/colorbar.js @@ -0,0 +1,47 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var d3 = require('d3'); +var isNumeric = require('fast-isnumeric'); + +var Lib = require('../../lib'); +var Plots = require('../../plots/plots'); +var getColorscale = require('../../components/colorscale/get_scale'); +var drawColorbar = require('../../components/colorbar/draw'); + + +module.exports = function colorbar(gd, cd) { + var trace = cd[0].trace, + cbId = 'cb' + trace.uid, + scl = getColorscale(trace.colorscale), + zmin = trace.cmin, + zmax = trace.cmax, + vals = trace.surfacecolor || trace.z; + + if(!isNumeric(zmin)) zmin = Lib.aggNums(Math.min, null, vals); + if(!isNumeric(zmax)) zmax = Lib.aggNums(Math.max, null, vals); + + gd._fullLayout._infolayer.selectAll('.' + cbId).remove(); + + if(!trace.showscale) { + Plots.autoMargin(gd, cbId); + return; + } + + var cb = cd[0].t.cb = drawColorbar(gd, cbId); + cb.fillcolor(d3.scale.linear() + .domain(scl.map(function(v) { return zmin + v[0]*(zmax-zmin); })) + .range(scl.map(function(v) { return v[1]; }))) + .filllevels({start: zmin, end: zmax, size: (zmax-zmin)/254}) + .options(trace.colorbar)(); + + Lib.markTime('done colorbar'); +}; diff --git a/src/traces/surface/convert.js b/src/traces/surface/convert.js index 3097561dc2b..0cb3f1d4671 100644 --- a/src/traces/surface/convert.js +++ b/src/traces/surface/convert.js @@ -20,7 +20,6 @@ var str2RgbaArray = require('../../lib/str2rgbarray'); var MIN_RESOLUTION = 128; - function SurfaceTrace(scene, surface, uid) { this.scene = scene; this.uid = uid; @@ -136,7 +135,7 @@ function refine(coords) { Math.floor((coords[0].shape[1]) * scaleF+1)|0 ]; var nsize = nshape[0] * nshape[1]; - for(var i = 0; i < 3; ++i) { + for(var i = 0; i < coords.length; ++i) { var padImg = padField(coords[i]); var scaledImg = ndarray(new Float32Array(nsize), nshape); homography(scaledImg, padImg, [scaleF, 0, 0, @@ -230,9 +229,6 @@ proto.update = function(data) { }); } - //Refine if necessary - this.dataScale = refine(coords); - var params = { colormap: colormap, levels: [[], [], []], @@ -249,10 +245,33 @@ proto.update = function(data) { dynamicColor: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], dynamicWidth: [1, 1, 1], dynamicTint: [1, 1, 1], - opacity: 1, - colorBounds: [data.zmin * scaleFactor[2], data.zmax * scaleFactor[2]] + opacity: 1 }; + params.intensityBounds = [data.cmin, data.cmax]; + + //Refine if necessary + if(data.surfacecolor) { + var intensity = ndarray(new Float32Array(xlen * ylen), [xlen, ylen]); + + fill(intensity, function(row, col) { + return data.surfacecolor[col][row]; + }); + + coords.push(intensity); + } + else { + // when 'z' is used as 'intensity', + // we must scale its value + params.intensityBounds[0] *= scaleFactor[2]; + params.intensityBounds[1] *= scaleFactor[2]; + } + + this.dataScale = refine(coords); + + if(data.surfacecolor) { + params.intensity = coords.pop(); + } if('opacity' in data) { if(data.opacity < 1) { @@ -300,6 +319,7 @@ proto.update = function(data) { } params.coords = coords; + surface.update(params); surface.highlightEnable = highlightEnable; diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index 653b9df5638..0fa8c456401 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -58,6 +58,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hidesurface'); coerce('opacity'); + var surfaceColor = coerce('surfacecolor'); + coerce('colorscale'); var dims = ['x', 'y', 'z']; @@ -85,7 +87,20 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } } + // backward compatibility block + if(!surfaceColor) { + mapLegacy(traceIn, 'zmin', 'cmin'); + mapLegacy(traceIn, 'zmax', 'cmax'); + mapLegacy(traceIn, 'zauto', 'cauto'); + } + colorscaleDefaults( - traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} + traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'} ); }; + +function mapLegacy(traceIn, oldAttr, newAttr) { + if(oldAttr in traceIn && !(newAttr in traceIn)) { + traceIn[newAttr] = traceIn[oldAttr]; + } +} diff --git a/src/traces/surface/index.js b/src/traces/surface/index.js index 9a3c5795f16..910d1af9046 100644 --- a/src/traces/surface/index.js +++ b/src/traces/surface/index.js @@ -13,7 +13,7 @@ var Surface = {}; Surface.attributes = require('./attributes'); Surface.supplyDefaults = require('./defaults'); -Surface.colorbar = require('../heatmap/colorbar'); +Surface.colorbar = require('./colorbar'); Surface.calc = require('./calc'); Surface.plot = require('./convert'); @@ -30,7 +30,11 @@ Surface.meta = { 'or {2D arrays} (e.g. to graph parametric surfaces).', 'If not provided in `x` and `y`, the x and y coordinates are assumed', - 'to be linear starting at 0 with a unit step.' + 'to be linear starting at 0 with a unit step.', + + 'The color scale corresponds to the `z` values by default.', + 'For custom color scales, use `surfacecolor` which should be a {2D array},', + 'where its bounds can be controlled using `cmin` and `cmax`.' ].join(' ') }; diff --git a/test/image/baselines/gl3d_autocolorscale.png b/test/image/baselines/gl3d_autocolorscale.png index 9156bc49bae..4bec636020d 100644 Binary files a/test/image/baselines/gl3d_autocolorscale.png and b/test/image/baselines/gl3d_autocolorscale.png differ diff --git a/test/image/baselines/gl3d_contour-lines.png b/test/image/baselines/gl3d_contour-lines.png index 791afb6099c..fd75d20ff94 100644 Binary files a/test/image/baselines/gl3d_contour-lines.png and b/test/image/baselines/gl3d_contour-lines.png differ diff --git a/test/image/baselines/gl3d_surface_intensity.png b/test/image/baselines/gl3d_surface_intensity.png new file mode 100644 index 00000000000..53327bb75d9 Binary files /dev/null and b/test/image/baselines/gl3d_surface_intensity.png differ diff --git a/test/image/mocks/gl3d_surface_intensity.json b/test/image/mocks/gl3d_surface_intensity.json new file mode 100644 index 00000000000..068e8a037ae --- /dev/null +++ b/test/image/mocks/gl3d_surface_intensity.json @@ -0,0 +1,256 @@ +{ + "data": [ + { + "z": [ + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 0.955336489125606, + 0.955336489125606, + 0.955336489125606, + 0.955336489125606, + 0.955336489125606, + 0.955336489125606, + 0.955336489125606, + 0.955336489125606, + 0.955336489125606, + 0.955336489125606 + ], + [ + 0.8253356149096783, + 0.8253356149096783, + 0.8253356149096783, + 0.8253356149096783, + 0.8253356149096783, + 0.8253356149096783, + 0.8253356149096783, + 0.8253356149096783, + 0.8253356149096783, + 0.8253356149096783 + ], + [ + 0.6216099682706645, + 0.6216099682706645, + 0.6216099682706645, + 0.6216099682706645, + 0.6216099682706645, + 0.6216099682706645, + 0.6216099682706645, + 0.6216099682706645, + 0.6216099682706645, + 0.6216099682706645 + ], + [ + 0.3623577544766736, + 0.3623577544766736, + 0.3623577544766736, + 0.3623577544766736, + 0.3623577544766736, + 0.3623577544766736, + 0.3623577544766736, + 0.3623577544766736, + 0.3623577544766736, + 0.3623577544766736 + ], + [ + 0.0707372016677029, + 0.0707372016677029, + 0.0707372016677029, + 0.0707372016677029, + 0.0707372016677029, + 0.0707372016677029, + 0.0707372016677029, + 0.0707372016677029, + 0.0707372016677029, + 0.0707372016677029 + ], + [ + -0.2272020946930869, + -0.2272020946930869, + -0.2272020946930869, + -0.2272020946930869, + -0.2272020946930869, + -0.2272020946930869, + -0.2272020946930869, + -0.2272020946930869, + -0.2272020946930869, + -0.2272020946930869 + ], + [ + -0.5048461045998576, + -0.5048461045998576, + -0.5048461045998576, + -0.5048461045998576, + -0.5048461045998576, + -0.5048461045998576, + -0.5048461045998576, + -0.5048461045998576, + -0.5048461045998576, + -0.5048461045998576 + ], + [ + -0.7373937155412454, + -0.7373937155412454, + -0.7373937155412454, + -0.7373937155412454, + -0.7373937155412454, + -0.7373937155412454, + -0.7373937155412454, + -0.7373937155412454, + -0.7373937155412454, + -0.7373937155412454 + ], + [ + -0.904072142017061, + -0.904072142017061, + -0.904072142017061, + -0.904072142017061, + -0.904072142017061, + -0.904072142017061, + -0.904072142017061, + -0.904072142017061, + -0.904072142017061, + -0.904072142017061 + ] + ], + "surfacecolor": [ + [ + 10, + 9, + 8, + 7, + 6, + 5, + 6, + 7, + 8, + 9 + ], + [ + 9, + 8, + 7, + 6, + 5, + 4, + 5, + 6, + 7, + 8 + ], + [ + 8, + 7, + 6, + 5, + 4, + 3, + 4, + 5, + 6, + 7 + ], + [ + 7, + 6, + 5, + 4, + 3, + 2, + 3, + 4, + 5, + 6 + ], + [ + 6, + 5, + 4, + 3, + 2, + 1, + 2, + 3, + 4, + 5 + ], + [ + 5, + 4, + 3, + 2, + 1, + 0, + 1, + 2, + 3, + 4 + ], + [ + 6, + 5, + 4, + 3, + 2, + 1, + 2, + 3, + 4, + 5 + ], + [ + 7, + 6, + 5, + 4, + 3, + 2, + 3, + 4, + 5, + 6 + ], + [ + 8, + 7, + 6, + 5, + 4, + 3, + 4, + 5, + 6, + 7 + ], + [ + 9, + 8, + 7, + 6, + 5, + 4, + 5, + 6, + 7, + 8 + ] + ], + "type": "surface", + "cmin": 0, + "cmax": 5 + } + ], + "layout": { + "title": "Surface intensity test" + } +} diff --git a/test/jasmine/tests/surface_test.js b/test/jasmine/tests/surface_test.js new file mode 100644 index 00000000000..1936de871b1 --- /dev/null +++ b/test/jasmine/tests/surface_test.js @@ -0,0 +1,137 @@ +var Surface = require('@src/traces/surface'); + + +describe('Test surface', function() { + 'use strict'; + + describe('supplyDefaults', function() { + var supplyDefaults = Surface.supplyDefaults; + + var defaultColor = '#444', + layout = {}; + + var traceIn, traceOut; + + beforeEach(function() { + traceOut = {}; + }); + + it('should set \'visible\' to false if \'z\' isn\'t provided', function() { + traceIn = {}; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); + }); + + it('should fill \'x\' and \'y\' if not provided', function() { + traceIn = { + z: [[1,2,3], [2,1,2]] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.x).toEqual([0,1,2]); + expect(traceOut.y).toEqual([0,1]); + }); + + it('should coerce \'project\' if contours or highlight lines are enabled', function() { + traceIn = { + z: [[1,2,3], [2,1,2]], + contours: { + x: { show: true } + } + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.contours.x.project).toEqual({ x: false, y: false, z: false }); + expect(traceOut.contours.y).toEqual({ show: false, highlight: false }); + expect(traceOut.contours.z).toEqual({ show: false, highlight: false }); + }); + + it('should coerce contour style attributes if contours lines are enabled', function() { + traceIn = { + z: [[1,2,3], [2,1,2]], + contours: { + x: { show: true } + } + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.contours.x.color).toEqual('#000'); + expect(traceOut.contours.x.width).toEqual(2); + expect(traceOut.contours.x.usecolormap).toEqual(false); + + ['y', 'z'].forEach(function(ax) { + expect(traceOut.contours[ax].color).toBeUndefined(); + expect(traceOut.contours[ax].width).toBeUndefined(); + expect(traceOut.contours[ax].usecolormap).toBeUndefined(); + }); + }); + + it('should coerce colorscale and colorbar attributes', function() { + traceIn = { + z: [[1,2,3], [2,1,2]] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.cauto).toBe(true); + expect(traceOut.cmin).toBeUndefined(); + expect(traceOut.cmax).toBeUndefined(); + expect(traceOut.colorscale).toEqual([ + [0, 'rgb(5,10,172)'], + [0.35, 'rgb(106,137,247)'], + [0.5, 'rgb(190,190,190)'], + [0.6, 'rgb(220,170,132)'], + [0.7, 'rgb(230,145,90)'], + [1, 'rgb(178,10,28)'] + ]); + expect(traceOut.showscale).toBe(true); + expect(traceOut.colorbar).toBeDefined(); + }); + + it('should coerce \'c\' attributes with \'z\' if \'c\' isn\'t present', function() { + traceIn = { + z: [[1,2,3], [2,1,2]], + zauto: false, + zmin: 0, + zmax: 10 + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.cauto).toEqual(false); + expect(traceOut.cmin).toEqual(0); + expect(traceOut.cmax).toEqual(10); + }); + + it('should coerce \'c\' attributes with \'c\' values regardless of `\'z\' if \'c\' is present', function() { + traceIn = { + z: [[1,2,3], [2,1,2]], + zauto: false, + zmin: 0, + zmax: 10, + cauto: true, + cmin: -10, + cmax: 20 + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.cauto).toEqual(true); + expect(traceOut.cmin).toEqual(-10); + expect(traceOut.cmax).toEqual(20); + }); + + it('should default \'c\' attributes with if \'surfacecolor\' is present', function() { + traceIn = { + z: [[1,2,3], [2,1,2]], + surfacecolor: [[2,1,2], [1,2,3]], + zauto: false, + zmin: 0, + zmax: 10 + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.cauto).toEqual(true); + expect(traceOut.cmin).toBeUndefined(); + expect(traceOut.cmax).toBeUndefined(); + }); + }); +});