diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index cf34be68e7e..f43ba1ed772 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -133,6 +133,18 @@ var attrs = module.exports = overrideAll(extendFlat({ }, hovertemplate: hovertemplateAttrs(), + connectgaps: { + valType: 'boolean', + dflt: false, + role: 'info', + editType: 'calc', + description: [ + 'Determines whether or not gaps', + '(i.e. {nan} or missing values)', + 'in the `z` data are filled in.' + ].join(' ') + }, + surfacecolor: { valType: 'data_array', description: [ diff --git a/src/traces/surface/convert.js b/src/traces/surface/convert.js index 488d55419ae..74d08837ce3 100644 --- a/src/traces/surface/convert.js +++ b/src/traces/surface/convert.js @@ -19,6 +19,9 @@ var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; var parseColorScale = require('../../lib/gl_format_color').parseColorScale; var str2RgbaArray = require('../../lib/str2rgbarray'); +var interp2d = require('../heatmap/interp2d'); +var findEmpties = require('../heatmap/find_empties'); + function SurfaceTrace(scene, surface, uid) { this.scene = scene; this.uid = uid; @@ -30,6 +33,7 @@ function SurfaceTrace(scene, surface, uid) { this.dataScaleX = 1.0; this.dataScaleY = 1.0; this.refineData = true; + this._interpolatedZ = false; } var proto = SurfaceTrace.prototype; @@ -59,9 +63,11 @@ proto.getYat = function(a, b, calendar, axis) { }; proto.getZat = function(a, b, calendar, axis) { - var v = ( - this.data.z[b][a] - ); + var v = this.data.z[b][a]; + + if(v === null && this.data.connectgaps && this.data._interpolatedZ) { + v = this.data._interpolatedZ[b][a]; + } return (calendar === undefined) ? v : axis.d2l(v, 0, calendar); }; @@ -404,6 +410,19 @@ proto.update = function(data) { } } + if(data.connectgaps) { + data._emptypoints = findEmpties(rawCoords[2]); + interp2d(rawCoords[2], data._emptypoints); + + data._interpolatedZ = []; + for(j = 0; j < xlen; j++) { + data._interpolatedZ[j] = []; + for(k = 0; k < ylen; k++) { + data._interpolatedZ[j][k] = rawCoords[2][j][k]; + } + } + } + // Note: log axes are not defined in surfaces yet. // but they could be defined here... diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index 0e83b883fce..5f883f8fd65 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -56,6 +56,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout 'lightposition.y', 'lightposition.z', 'hidesurface', + 'connectgaps', 'opacity' ].forEach(function(x) { coerce(x); }); diff --git a/test/image/baselines/gl3d_surface_connectgaps.png b/test/image/baselines/gl3d_surface_connectgaps.png new file mode 100644 index 00000000000..44135954afd Binary files /dev/null and b/test/image/baselines/gl3d_surface_connectgaps.png differ diff --git a/test/image/mocks/gl3d_surface_connectgaps.json b/test/image/mocks/gl3d_surface_connectgaps.json new file mode 100644 index 00000000000..4d9e46ee4f6 --- /dev/null +++ b/test/image/mocks/gl3d_surface_connectgaps.json @@ -0,0 +1,21 @@ +{ + "data": [ + { + "type": "surface", + "connectgaps": true, + "x": [0.1, 0.2, 0.3, 0.4], + "y": [0, 1, 2, 3, 4], + "z": [ + [1001, 1002, 1001, null, null], + [1002, 1001, null, 1002, null], + [1002, null, 1001, 1001], + [null, null, 1000, null] + ] + } + ], + "layout": { + "title": "Surface plot with interpolated gaps
(connectgaps: true)", + "width": 600, + "height": 400 + } +} diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 97e61a0998e..54596cfebd0 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -378,6 +378,37 @@ describe('Test gl3d plots', function() { .then(done); }); + it('@gl should display correct hover labels and emit correct event data (surface case with connectgaps enabled)', function(done) { + var surfaceConnectgaps = require('@mocks/gl3d_surface_connectgaps'); + var _mock = Lib.extendDeep({}, surfaceConnectgaps); + + function _hover() { + mouseEvent('mouseover', 300, 200); + return delay(20)(); + } + + Plotly.plot(gd, _mock) + .then(delay(20)) + .then(function() { + gd.on('plotly_hover', function(eventData) { + ptData = eventData.points[0]; + }); + }) + .then(_hover) + .then(function() { + assertHoverText('x: 0.2', 'y: 2', 'z: 1,001.25'); + assertEventData(0.2, 2, 1001.25, 0, [1, 2]); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(68, 68, 68)', + bordercolor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial', + fontColor: 'rgb(255, 255, 255)' + }, 'initial'); + }) + .then(done); + }); + it('@gl should display correct hover labels and emit correct event data (surface case)', function(done) { var _mock = Lib.extendDeep({}, mock3);