diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index cdd067714bb..9efcca25773 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -520,7 +520,7 @@ exports.doModeBar = function(gd) { for(var i = 0; i < fullLayout._basePlotModules.length; i++) { var updateFx = fullLayout._basePlotModules[i].updateFx; - if(updateFx) updateFx(fullLayout); + if(updateFx) updateFx(gd); } return Plots.previousPromises(gd); diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index dad861716d4..06de3b99378 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -33,8 +33,8 @@ var setConvert = require('./set_convert'); */ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, options, layoutOut) { var letter = options.letter; - var id = containerOut._id; var font = options.font || {}; + var splomStash = options.splomStash || {}; var visible = coerce('visible', !options.cheateronly); @@ -66,7 +66,7 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, // template too. var dfltFontColor = (dfltColor !== layoutAttributes.color.dflt) ? dfltColor : font.color; // try to get default title from splom trace, fallback to graph-wide value - var dfltTitle = ((layoutOut._splomAxes || {})[letter] || {})[id] || layoutOut._dfltTitle[letter]; + var dfltTitle = splomStash.label || layoutOut._dfltTitle[letter]; coerce('title', dfltTitle); Lib.coerceFont(coerce, 'titlefont', { diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 2ed10621c1c..69dc718bf50 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -151,7 +151,7 @@ exports.initInteractions = function initInteractions(gd) { gd._fullLayout._lasthover.onmousedown(evt); }; - exports.updateFx(fullLayout); + exports.updateFx(gd); }; // Minimal set of update needed on 'modebar' edits. @@ -159,7 +159,8 @@ exports.initInteractions = function initInteractions(gd) { // // Note that changing the axis configuration and/or the fixedrange attribute // should trigger a full initInteractions. -exports.updateFx = function(fullLayout) { +exports.updateFx = function(gd) { + var fullLayout = gd._fullLayout; var cursor = fullLayout.dragmode === 'pan' ? 'move' : 'crosshair'; setCursor(fullLayout._draggers, cursor); }; diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index 6c2f824781e..63abbc2364b 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -157,7 +157,9 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { axLayoutOut._annIndices = []; axLayoutOut._shapeIndices = []; - handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, traces, axName); + // set up some private properties + axLayoutOut._name = axName; + var id = axLayoutOut._id = name2id(axName); var overlayableAxes = getOverlayableAxes(axLetter, axName); @@ -170,9 +172,11 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { bgColor: bgColor, calendar: layoutOut.calendar, automargin: true, - cheateronly: axLetter === 'x' && xaCheater[axName] && !xaNonCheater[axName] + cheateronly: axLetter === 'x' && xaCheater[axName] && !xaNonCheater[axName], + splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[id] }; + handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions); handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut); var spikecolor = coerce2('spikecolor'), diff --git a/src/plots/cartesian/type_defaults.js b/src/plots/cartesian/type_defaults.js index 5a9414054b6..1234f8a24a6 100644 --- a/src/plots/cartesian/type_defaults.js +++ b/src/plots/cartesian/type_defaults.js @@ -6,32 +6,24 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var Registry = require('../../registry'); var autoType = require('./axis_autotype'); -var name2id = require('./axis_ids').name2id; /* * data: the plot data to use in choosing auto type * name: axis object name (ie 'xaxis') if one should be stored */ -module.exports = function handleTypeDefaults(containerIn, containerOut, coerce, data, name) { - // set up some private properties - if(name) { - containerOut._name = name; - containerOut._id = name2id(name); - } +module.exports = function handleTypeDefaults(containerIn, containerOut, coerce, options) { + var axType = coerce('type', (options.splomStash || {}).type); - var axType = coerce('type'); if(axType === '-') { - setAutoType(containerOut, data); + setAutoType(containerOut, options.data); if(containerOut.type === '-') { containerOut.type = 'linear'; - } - else { + } else { // copy autoType back to input axis // note that if this object didn't exist // in the input layout, we have to put it in @@ -89,9 +81,10 @@ function setAutoType(ax, data) { } else if(d0.type === 'splom') { var dimensions = d0.dimensions; + var diag = d0._diag; for(i = 0; i < dimensions.length; i++) { var dim = dimensions[i]; - if(dim.visible) { + if(dim.visible && (diag[i][0] === id || diag[i][1] === id)) { ax.type = autoType(dim.values, calendar); break; } diff --git a/src/plots/geo/index.js b/src/plots/geo/index.js index d6468d7508d..a393f0b5b82 100644 --- a/src/plots/geo/index.js +++ b/src/plots/geo/index.js @@ -78,7 +78,8 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) } }; -exports.updateFx = function(fullLayout) { +exports.updateFx = function(gd) { + var fullLayout = gd._fullLayout; var subplotIds = fullLayout._subplots[GEO]; for(var i = 0; i < subplotIds.length; i++) { diff --git a/src/plots/gl2d/index.js b/src/plots/gl2d/index.js index 26bb7fdc06e..f56572b8bac 100644 --- a/src/plots/gl2d/index.js +++ b/src/plots/gl2d/index.js @@ -138,7 +138,8 @@ exports.toSVG = function(gd) { } }; -exports.updateFx = function(fullLayout) { +exports.updateFx = function(gd) { + var fullLayout = gd._fullLayout; var subplotIds = fullLayout._subplots.gl2d; for(var i = 0; i < subplotIds.length; i++) { diff --git a/src/plots/gl3d/index.js b/src/plots/gl3d/index.js index 4c094e6519a..3c4dccbfa2e 100644 --- a/src/plots/gl3d/index.js +++ b/src/plots/gl3d/index.js @@ -129,7 +129,8 @@ exports.cleanId = function cleanId(id) { return SCENE + sceneNum; }; -exports.updateFx = function(fullLayout) { +exports.updateFx = function(gd) { + var fullLayout = gd._fullLayout; var subplotIds = fullLayout._subplots[GL3D]; for(var i = 0; i < subplotIds.length; i++) { diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js index a3ac9c14285..e28b1a74de4 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -39,7 +39,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { containerOut._id = axName[0] + options.scene; containerOut._name = axName; - handleTypeDefaults(containerIn, containerOut, coerce, options.data); + handleTypeDefaults(containerIn, containerOut, coerce, options); handleAxisDefaults( containerIn, diff --git a/src/plots/mapbox/index.js b/src/plots/mapbox/index.js index dc19d1c5dca..cda8cd931cd 100644 --- a/src/plots/mapbox/index.js +++ b/src/plots/mapbox/index.js @@ -152,7 +152,8 @@ function findAccessToken(gd, mapboxIds) { throw new Error(constants.noAccessTokenErrorMsg); } -exports.updateFx = function(fullLayout) { +exports.updateFx = function(gd) { + var fullLayout = gd._fullLayout; var subplotIds = fullLayout._subplots[MAPBOX]; for(var i = 0; i < subplotIds.length; i++) { diff --git a/src/traces/splom/attributes.js b/src/traces/splom/attributes.js index 0c5dd6a9185..dbc9169bbe4 100644 --- a/src/traces/splom/attributes.js +++ b/src/traces/splom/attributes.js @@ -58,6 +58,23 @@ module.exports = { description: 'Sets the dimension values to be plotted.' }, + axis: { + type: { + valType: 'enumerated', + values: ['linear', 'log', 'date', 'category'], + role: 'info', + editType: 'calc+clearAxisTypes', + description: [ + 'Sets the axis type for this dimension\'s generated', + 'x and y axes.', + 'Note that the axis `type` values set in layout take', + 'precedence over this attribute.' + ].join(' ') + }, + + editType: 'calc+clearAxisTypes' + }, + // TODO should add an attribute to pin down x only vars and y only vars // like https://seaborn.pydata.org/generated/seaborn.pairplot.html // x_vars and y_vars diff --git a/src/traces/splom/base_plot.js b/src/traces/splom/base_plot.js index 80bef941fb1..a91f83b7a67 100644 --- a/src/traces/splom/base_plot.js +++ b/src/traces/splom/base_plot.js @@ -45,36 +45,34 @@ function drag(gd) { for(var i = 0; i < cd.length; i++) { var cd0 = cd[i][0]; var trace = cd0.trace; - var scene = cd0.t._scene; + var stash = cd0.t; + var scene = stash._scene; if(trace.type === 'splom' && scene && scene.matrix) { - dragOne(gd, trace, scene); + dragOne(gd, trace, stash, scene); } } } -function dragOne(gd, trace, scene) { - var dimensions = trace.dimensions; +function dragOne(gd, trace, stash, scene) { var visibleLength = scene.matrixOptions.data.length; + var visibleDims = stash.visibleDims; var ranges = new Array(visibleLength); - for(var i = 0, k = 0; i < dimensions.length; i++) { - if(dimensions[i].visible) { - var rng = ranges[k] = new Array(4); + for(var k = 0; k < visibleDims.length; k++) { + var i = visibleDims[k]; + var rng = ranges[k] = new Array(4); - var xa = AxisIDs.getFromId(gd, trace._diag[i][0]); - if(xa) { - rng[0] = xa.r2l(xa.range[0]); - rng[2] = xa.r2l(xa.range[1]); - } - - var ya = AxisIDs.getFromId(gd, trace._diag[i][1]); - if(ya) { - rng[1] = ya.r2l(ya.range[0]); - rng[3] = ya.r2l(ya.range[1]); - } + var xa = AxisIDs.getFromId(gd, trace._diag[i][0]); + if(xa) { + rng[0] = xa.r2l(xa.range[0]); + rng[2] = xa.r2l(xa.range[1]); + } - k++; + var ya = AxisIDs.getFromId(gd, trace._diag[i][1]); + if(ya) { + rng[1] = ya.r2l(ya.range[0]); + rng[3] = ya.r2l(ya.range[1]); } } @@ -229,6 +227,30 @@ function clean(newFullData, newFullLayout, oldFullData, oldFullLayout, oldCalcda Cartesian.clean(newFullData, newFullLayout, oldFullData, oldFullLayout); } +function updateFx(gd) { + Cartesian.updateFx(gd); + + var fullLayout = gd._fullLayout; + var dragmode = fullLayout.dragmode; + + // unset selection styles when coming out of a selection mode + if(dragmode === 'zoom' || dragmode === 'pan') { + var cd = gd.calcdata; + + for(var i = 0; i < cd.length; i++) { + var cd0 = cd[i][0]; + var trace = cd0.trace; + + if(trace.type === 'splom') { + var scene = cd0.t._scene; + if(scene.selectBatch === null) { + scene.matrix.update(scene.matrixOptions, null); + } + } + } + } +} + module.exports = { name: SPLOM, attr: Cartesian.attr, @@ -239,6 +261,6 @@ module.exports = { plot: plot, drag: drag, clean: clean, - updateFx: Cartesian.updateFx, + updateFx: updateFx, toSVG: Cartesian.toSVG }; diff --git a/src/traces/splom/defaults.js b/src/traces/splom/defaults.js index afcb002e8b3..4d0d9c8dc28 100644 --- a/src/traces/splom/defaults.js +++ b/src/traces/splom/defaults.js @@ -61,6 +61,8 @@ function dimensionDefaults(dimIn, dimOut) { if(!(values && values.length)) dimOut.visible = false; else coerce('visible'); + + coerce('axis.type'); } function handleAxisDefaults(traceIn, traceOut, layout, coerce) { @@ -113,14 +115,14 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { for(i = 0; i < dimLength; i++) { var dim = dimensions[i]; - var xa = xaxes[i + xShift]; - var ya = yaxes[i + yShift]; + var xaId = xaxes[i + xShift]; + var yaId = yaxes[i + yShift]; - fillAxisStash(layout, xa, dim); - fillAxisStash(layout, ya, dim); + fillAxisStash(layout, xaId, dim); + fillAxisStash(layout, yaId, dim); // note that some the entries here may be undefined - diag[i] = [xa, ya]; + diag[i] = [xaId, yaId]; } // when lower half is omitted, override grid default @@ -148,7 +150,13 @@ function fillAxisStash(layout, axId, dim) { var stash = layout._splomAxes[axLetter]; if(!(axId in stash)) { - stash[axId] = (dim || {}).label || ''; + var s = stash[axId] = {}; + if(dim) { + s.label = dim.label || ''; + if(dim.visible && dim.axis) { + s.type = dim.axis.type; + } + } } } diff --git a/src/traces/splom/index.js b/src/traces/splom/index.js index c5613e5844f..902646cb17a 100644 --- a/src/traces/splom/index.js +++ b/src/traces/splom/index.js @@ -36,20 +36,48 @@ function calc(gd, trace) { // only differ here for log axes, pass ldata to createMatrix as 'data' var cdata = opts.cdata = []; var ldata = opts.data = []; - var i, k, dim; + // keep track of visible dimensions + var visibleDims = stash.visibleDims = []; + var i, k, dim, xa, ya; + + function makeCalcdata(ax, dim) { + // call makeCalcdata with fake input + var ccol = ax.makeCalcdata({ + v: dim.values, + vcalendar: trace.calendar + }, 'v'); + + for(var j = 0; j < ccol.length; j++) { + ccol[j] = ccol[j] === BADNUM ? NaN : ccol[j]; + } + cdata.push(ccol); + ldata.push(ax.type === 'log' ? Lib.simpleMap(ccol, ax.c2l) : ccol); + } for(i = 0; i < dimensions.length; i++) { dim = dimensions[i]; if(dim.visible) { - var axId = trace._diag[i][0] || trace._diag[i][1]; - var ax = AxisIDs.getFromId(gd, axId); - if(ax) { - var ccol = makeCalcdata(ax, trace, dim); - var lcol = ax.type === 'log' ? Lib.simpleMap(ccol, ax.c2l) : ccol; - cdata.push(ccol); - ldata.push(lcol); + xa = AxisIDs.getFromId(gd, trace._diag[i][0]); + ya = AxisIDs.getFromId(gd, trace._diag[i][1]); + + // if corresponding x & y axes don't have matching types, skip dim + if(xa && ya && xa.type !== ya.type) { + Lib.log('Skipping splom dimension ' + i + ' with conflicting axis types'); + continue; + } + + if(xa) { + makeCalcdata(xa, dim); + if(ya && ya.type === 'category') { + ya._categories = xa._categories.slice(); + } + } else { + // should not make it here, if both xa and ya undefined + makeCalcdata(ya, dim); } + + visibleDims.push(i); } } @@ -59,26 +87,24 @@ function calc(gd, trace) { var visibleLength = cdata.length; var hasTooManyPoints = (visibleLength * commonLength) > TOO_MANY_POINTS; - for(i = 0, k = 0; i < dimensions.length; i++) { + for(k = 0; k < visibleDims.length; k++) { + i = visibleDims[k]; dim = dimensions[i]; - if(dim.visible) { - var xa = AxisIDs.getFromId(gd, trace._diag[i][0]) || {}; - var ya = AxisIDs.getFromId(gd, trace._diag[i][1]) || {}; - - // Reuse SVG scatter axis expansion routine. - // For graphs with very large number of points and array marker.size, - // use average marker size instead to speed things up. - var ppad; - if(hasTooManyPoints) { - ppad = 2 * (opts.sizeAvg || Math.max(opts.size, 3)); - } else { - ppad = calcMarkerSize(trace, commonLength); - } - - calcAxisExpansion(gd, trace, xa, ya, cdata[k], cdata[k], ppad); - k++; + xa = AxisIDs.getFromId(gd, trace._diag[i][0]) || {}; + ya = AxisIDs.getFromId(gd, trace._diag[i][1]) || {}; + + // Reuse SVG scatter axis expansion routine. + // For graphs with very large number of points and array marker.size, + // use average marker size instead to speed things up. + var ppad; + if(hasTooManyPoints) { + ppad = 2 * (opts.sizeAvg || Math.max(opts.size, 3)); + } else { + ppad = calcMarkerSize(trace, commonLength); } + + calcAxisExpansion(gd, trace, xa, ya, cdata[k], cdata[k], ppad); } var scene = stash._scene = sceneUpdate(gd, stash); @@ -91,20 +117,6 @@ function calc(gd, trace) { return [{x: false, y: false, t: stash, trace: trace}]; } -function makeCalcdata(ax, trace, dim) { - // call makeCalcdata with fake input - var ccol = ax.makeCalcdata({ - v: dim.values, - vcalendar: trace.calendar - }, 'v'); - - for(var i = 0; i < ccol.length; i++) { - ccol[i] = ccol[i] === BADNUM ? NaN : ccol[i]; - } - - return ccol; -} - function sceneUpdate(gd, stash) { var scene = stash._scene; @@ -126,9 +138,7 @@ function sceneUpdate(gd, stash) { // draw traces in selection mode if(scene.matrix && scene.selectBatch) { scene.matrix.draw(scene.unselectBatch, scene.selectBatch); - } - - else if(scene.matrix) { + } else if(scene.matrix) { scene.matrix.draw(); } @@ -184,34 +194,32 @@ function plotOne(gd, cd0) { matrixOpts.upper = trace.showlowerhalf; matrixOpts.diagonal = trace.diagonal.visible; - var dimensions = trace.dimensions; + var visibleDims = stash.visibleDims; var visibleLength = cdata.length; var viewOpts = {}; viewOpts.ranges = new Array(visibleLength); viewOpts.domains = new Array(visibleLength); - for(i = 0, k = 0; i < dimensions.length; i++) { - if(trace.dimensions[i].visible) { - var rng = viewOpts.ranges[k] = new Array(4); - var dmn = viewOpts.domains[k] = new Array(4); + for(k = 0; k < visibleDims.length; k++) { + i = visibleDims[k]; - xa = AxisIDs.getFromId(gd, trace._diag[i][0]); - if(xa) { - rng[0] = xa._rl[0]; - rng[2] = xa._rl[1]; - dmn[0] = xa.domain[0]; - dmn[2] = xa.domain[1]; - } + var rng = viewOpts.ranges[k] = new Array(4); + var dmn = viewOpts.domains[k] = new Array(4); - ya = AxisIDs.getFromId(gd, trace._diag[i][1]); - if(ya) { - rng[1] = ya._rl[0]; - rng[3] = ya._rl[1]; - dmn[1] = ya.domain[0]; - dmn[3] = ya.domain[1]; - } + xa = AxisIDs.getFromId(gd, trace._diag[i][0]); + if(xa) { + rng[0] = xa._rl[0]; + rng[2] = xa._rl[1]; + dmn[0] = xa.domain[0]; + dmn[2] = xa.domain[1]; + } - k++; + ya = AxisIDs.getFromId(gd, trace._diag[i][1]); + if(ya) { + rng[1] = ya._rl[0]; + rng[3] = ya._rl[1]; + dmn[1] = ya.domain[0]; + dmn[3] = ya.domain[1]; } } @@ -253,25 +261,23 @@ function plotOne(gd, cd0) { var xpx = stash.xpx = new Array(visibleLength); var ypx = stash.ypx = new Array(visibleLength); - for(i = 0, k = 0; i < dimensions.length; i++) { - if(trace.dimensions[i].visible) { - xa = AxisIDs.getFromId(gd, trace._diag[i][0]); - if(xa) { - xpx[k] = new Array(commonLength); - for(j = 0; j < commonLength; j++) { - xpx[k][j] = xa.c2p(cdata[k][j]); - } - } + for(k = 0; k < visibleDims.length; k++) { + i = visibleDims[k]; - ya = AxisIDs.getFromId(gd, trace._diag[i][1]); - if(ya) { - ypx[k] = new Array(commonLength); - for(j = 0; j < commonLength; j++) { - ypx[k][j] = ya.c2p(cdata[k][j]); - } + xa = AxisIDs.getFromId(gd, trace._diag[i][0]); + if(xa) { + xpx[k] = new Array(commonLength); + for(j = 0; j < commonLength; j++) { + xpx[k][j] = xa.c2p(cdata[k][j]); } + } - k++; + ya = AxisIDs.getFromId(gd, trace._diag[i][1]); + if(ya) { + ypx[k] = new Array(commonLength); + for(j = 0; j < commonLength; j++) { + ypx[k][j] = ya.c2p(cdata[k][j]); + } } } @@ -286,8 +292,8 @@ function plotOne(gd, cd0) { } } else { - scene.matrix.update(matrixOpts); - scene.matrix.update(viewOpts); + scene.matrix.update(matrixOpts, null); + scene.matrix.update(viewOpts, null); stash.xpx = stash.ypx = null; } @@ -306,8 +312,8 @@ function hoverPoints(pointData, xval, yval) { var ypx = ya.c2p(yval); var maxDistance = pointData.distance; - var xi = getDimIndex(trace, xa); - var yi = getDimIndex(trace, ya); + var xi = getDimIndex(trace, stash, xa); + var yi = getDimIndex(trace, stash, ya); if(xi === false || yi === false) return [pointData]; var x = cdata[xi]; @@ -356,8 +362,8 @@ function selectPoints(searchInfo, polygon) { var hasOnlyLines = (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace)); if(trace.visible !== true || hasOnlyLines) return selection; - var xi = getDimIndex(trace, xa); - var yi = getDimIndex(trace, ya); + var xi = getDimIndex(trace, stash, xa); + var yi = getDimIndex(trace, stash, ya); if(xi === false || yi === false) return selection; var xpx = stash.xpx[xi]; @@ -438,17 +444,15 @@ function style(gd, cds) { } } -function getDimIndex(trace, ax) { +function getDimIndex(trace, stash, ax) { var axId = ax._id; var axLetter = axId.charAt(0); var ind = {x: 0, y: 1}[axLetter]; - var dimensions = trace.dimensions; + var visibleDims = stash.visibleDims; - for(var i = 0, k = 0; i < dimensions.length; i++) { - if(dimensions[i].visible) { - if(trace._diag[i][ind] === axId) return k; - k++; - } + for(var k = 0; k < visibleDims.length; k++) { + var i = visibleDims[k]; + if(trace._diag[i][ind] === axId) return k; } return false; } diff --git a/test/image/baselines/splom_mismatched-axis-types.png b/test/image/baselines/splom_mismatched-axis-types.png new file mode 100644 index 00000000000..cb8d5793a81 Binary files /dev/null and b/test/image/baselines/splom_mismatched-axis-types.png differ diff --git a/test/image/baselines/splom_multi-axis-type.png b/test/image/baselines/splom_multi-axis-type.png new file mode 100644 index 00000000000..13c9aa9b176 Binary files /dev/null and b/test/image/baselines/splom_multi-axis-type.png differ diff --git a/test/image/mocks/splom_mismatched-axis-types.json b/test/image/mocks/splom_mismatched-axis-types.json new file mode 100644 index 00000000000..bddc0519b0a --- /dev/null +++ b/test/image/mocks/splom_mismatched-axis-types.json @@ -0,0 +1,52 @@ +{ + "data": [ + { + "type": "splom", + "showupperhalf": false, + "diagonal": {"visible": false }, + "dimensions": [ + { + "label": "numeric", + "values": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + }, + { + "label": "decimal", + "values": [-3.3, 2.2, -1.1, 0, 1.1, -2.2, 3.3, 4.4, -5, 6] + }, + { + "label": "bool", + "values": [false, true, true, true, false, true, false, false, false, true], + "axis": {"type": "category"} + }, + { + "label": "0/1", + "values": [0, 1, 1, 1, 1, 1, 0, 0, 0, 0], + "axis": {"type": "category"} + }, + { + "label": "blank", + "values": [] + }, + { + "label": "string", + "values": ["lyndon", "richard", "gerald", "jimmy", "ronald", "george", "bill", "georgeW", "barack", "donald"] + } + ] + } + ], + "layout": { + "hovermode": "closest", + "margin": {"b": 80, "l": 80, "r": 30, "t": 30}, + "xaxis3": {"type": "linear"}, + "annotations": [{ + "showarrow": false, + "xref": "paper", "yref": "paper", + "xanchor": "right", "yanchor": "top", + "x": 1, "y": 1, + "text": "Should not see points in the \"bool\" dimension
as it has conflicting axis types", + "borderwidth": 1, + "bordercolor": "black", + "borderpad": 5 + }] + } +} diff --git a/test/image/mocks/splom_multi-axis-type.json b/test/image/mocks/splom_multi-axis-type.json new file mode 100644 index 00000000000..35a817b0a40 --- /dev/null +++ b/test/image/mocks/splom_multi-axis-type.json @@ -0,0 +1,38 @@ +{ + "data": [ + { + "type": "splom", + "opacity": 0.9, + "showupperhalf": false, + "diagonal": {"visible": false }, + "dimensions": [ + { + "label": "numeric", + "values": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + }, + { + "label": "decimal", + "values": [-3.3, 2.2, -1.1, 0, 1.1, -2.2, 3.3, 4.4, -5, 6] + }, + { + "label": "bool", + "values": [false, true, true, true, false, true, false, false, false, true], + "axis": {"type": "category"} + }, + { + "label": "0/1", + "values": [0, 1, 1, 1, 1, 1, 0, 0, 0, 0], + "axis": {"type": "category"} + }, + { + "label": "string", + "values": ["lyndon", "richard", "gerald", "jimmy", "ronald", "george", "bill", "georgeW", "barack", "donald"] + } + ] + } + ], + "layout": { + "hovermode": "closest", + "margin": {"b": 80, "l": 80, "r": 30, "t": 30} + } +} diff --git a/test/jasmine/tests/splom_test.js b/test/jasmine/tests/splom_test.js index 43e37a3b3c3..0ec0a5de4e2 100644 --- a/test/jasmine/tests/splom_test.js +++ b/test/jasmine/tests/splom_test.js @@ -10,6 +10,7 @@ var destroyGraphDiv = require('../assets/destroy_graph_div'); var failTest = require('../assets/fail_test'); var mouseEvent = require('../assets/mouse_event'); var drag = require('../assets/drag'); +var doubleClick = require('../assets/double_click'); var customAssertions = require('../assets/custom_assertions'); var assertHoverLabelContent = customAssertions.assertHoverLabelContent; @@ -352,8 +353,81 @@ describe('Test splom trace defaults:', function() { }); var fullLayout = gd._fullLayout; - expect(fullLayout.xaxis.type).toBe('date'); - expect(fullLayout.yaxis.type).toBe('date'); + expect(fullLayout.xaxis.type).toBe('linear', 'fallbacks to linear for visible:false traces'); + expect(fullLayout.yaxis.type).toBe('linear', 'fallbacks to linear for visible:false traces'); + expect(fullLayout.xaxis2.type).toBe('date'); + expect(fullLayout.yaxis2.type).toBe('date'); + }); + + it('axis type in layout takes precedence over dimensions setting', function() { + _supply({ + dimensions: [ + {values: [1, 2, 1], axis: {type: 'category'}}, + {values: [2, 1, 3]} + ] + }, { + xaxis: {type: 'linear'}, + yaxis: {type: 'linear'}, + xaxis2: {type: 'category'}, + yaxis2: {type: 'category'} + }); + + var fullLayout = gd._fullLayout; + expect(fullLayout.xaxis.type).toBe('linear'); + expect(fullLayout.yaxis.type).toBe('linear'); + expect(fullLayout.xaxis2.type).toBe('category'); + expect(fullLayout.yaxis2.type).toBe('category'); + }); + + it('axis type setting should be skipped when dimension is not visible', function() { + _supply({ + dimensions: [ + {visible: false, values: [1, 2, 1], axis: {type: 'category'}}, + {values: [-1, 2, 3], axis: {type: 'category'}}, + ] + }, { + }); + + var fullLayout = gd._fullLayout; + expect(fullLayout.xaxis.type).toBe('linear'); + expect(fullLayout.yaxis.type).toBe('linear'); + expect(fullLayout.xaxis2.type).toBe('category'); + expect(fullLayout.yaxis2.type).toBe('category'); + }); +}); + +describe('Test splom trace calc step:', function() { + var gd; + + function _calc(opts, layout) { + gd = {}; + + gd.data = [Lib.extendFlat({type: 'splom'}, opts || {})]; + gd.layout = layout || {}; + supplyAllDefaults(gd); + Plots.doCalcdata(gd); + } + + it('should skip dimensions with conflicting axis types', function() { + spyOn(Lib, 'log').and.callThrough(); + + _calc({ + dimensions: [{ + values: [1, 2, 3] + }, { + values: [2, 1, 2] + }] + }, { + xaxis: {type: 'category'}, + yaxis: {type: 'linear'} + }); + + var cd = gd.calcdata[0][0]; + + expect(cd.t._scene.matrixOptions.data).toBeCloseTo2DArray([[2, 1, 2]]); + expect(cd.t.visibleDims).toEqual([1]); + expect(Lib.log).toHaveBeenCalledTimes(1); + expect(Lib.log).toHaveBeenCalledWith('Skipping splom dimension 0 with conflicting axis types'); }); }); @@ -849,7 +923,7 @@ describe('@gl Test splom select:', function() { } it('should emit correct event data and draw selection outlines', function(done) { - var fig = require('@mocks/splom_0.json'); + var fig = Lib.extendDeep({}, require('@mocks/splom_0.json')); fig.layout = { dragmode: 'select', width: 400, @@ -970,4 +1044,105 @@ describe('@gl Test splom select:', function() { .catch(failTest) .then(done); }); + + it('should behave correctly during select->dblclick->pan scenarios', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/splom_0.json')); + fig.layout = { + width: 400, + height: 400, + margin: {l: 0, t: 0, r: 0, b: 0}, + grid: {xgap: 0, ygap: 0} + }; + + var scene; + + function _assert(msg, exp) { + expect(scene.matrix.update).toHaveBeenCalledTimes(exp.updateCnt, 'update cnt'); + expect(scene.matrix.draw).toHaveBeenCalledTimes(exp.drawCnt, 'draw cnt'); + + expect(scene.matrix.traces.length).toBe(exp.matrixTraces, '# of regl-splom traces'); + expect(scene.selectBatch).toEqual(exp.selectBatch, 'selectBatch'); + expect(scene.unselectBatch).toEqual(exp.unselectBatch, 'unselectBatch'); + + scene.matrix.update.calls.reset(); + scene.matrix.draw.calls.reset(); + } + + Plotly.plot(gd, fig).then(function() { + scene = gd.calcdata[0][0].t._scene; + spyOn(scene.matrix, 'update').and.callThrough(); + spyOn(scene.matrix, 'draw').and.callThrough(); + }) + .then(function() { + _assert('base', { + updateCnt: 0, + drawCnt: 0, + matrixTraces: 1, + selectBatch: null, + unselectBatch: null + }); + }) + .then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); }) + .then(function() { + _assert('under dragmode:select', { + updateCnt: 3, // updates positions, viewport and style in 3 calls + drawCnt: 1, // results in a 'plot' edit + matrixTraces: 2, + selectBatch: [], + unselectBatch: [] + }); + }) + .then(function() { return _select([[5, 5], [100, 100]]); }) + .then(function() { + _assert('after selection', { + updateCnt: 0, + drawCnt: 1, + matrixTraces: 2, + selectBatch: [1], + unselectBatch: [0, 2] + }); + }) + .then(function() { return Plotly.relayout(gd, 'dragmode', 'pan'); }) + .then(function() { + _assert('under dragmode:pan with active selection', { + updateCnt: 0, + drawCnt: 0, // nothing here, this is a 'modebar' edit + matrixTraces: 2, + selectBatch: [1], + unselectBatch: [0, 2] + }); + }) + .then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); }) + .then(function() { + _assert('back dragmode:select', { + updateCnt: 3, + drawCnt: 1, // a 'plot' edit (again) + matrixTraces: 2, + selectBatch: [1], + unselectBatch: [0, 2] + }); + }) + .then(function() { return doubleClick(100, 100); }) + .then(function() { + _assert('after dblclick clearing selection', { + updateCnt: 0, + drawCnt: 1, + matrixTraces: 2, + selectBatch: null, + unselectBatch: [] + }); + }) + .then(function() { return Plotly.relayout(gd, 'dragmode', 'pan'); }) + .then(function() { + _assert('under dragmode:pan with NO active selection', { + updateCnt: 1, // to clear off 1 matrixTrace + drawCnt: 0, + matrixTraces: 1, // N.B. back to '1' here + selectBatch: null, + unselectBatch: [] + }); + }) + .catch(failTest) + .then(done); + }); });