diff --git a/draftlogs/7834_remove.md b/draftlogs/7834_remove.md new file mode 100644 index 00000000000..e548a273cf9 --- /dev/null +++ b/draftlogs/7834_remove.md @@ -0,0 +1 @@ +- Remove internal `trace._fullInput` property and other dead code related to the removed `transforms` feature. No user-facing changes expected [[#7834](https://github.com/plotly/plotly.js/pull/7834)] \ No newline at end of file diff --git a/src/components/fx/helpers.js b/src/components/fx/helpers.js index 57c901313a8..d3d2530bccb 100644 --- a/src/components/fx/helpers.js +++ b/src/components/fx/helpers.js @@ -122,17 +122,7 @@ exports.makeEventData = function (pt, trace, cd) { pointNumber: pointNumber }; - if (trace._indexToPoints) { - var pointIndices = trace._indexToPoints[pointNumber]; - - if (pointIndices.length === 1) { - out.pointIndex = pointIndices[0]; - } else { - out.pointIndices = pointIndices; - } - } else { - out.pointIndex = pointNumber; - } + out.pointIndex = pointNumber; if (trace._module.eventData) { out = trace._module.eventData(out, pt, trace, cd, pointNumber); diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index cf6439f0cf8..707f2a77364 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -111,7 +111,6 @@ function drawOne(gd, opts) { var shapeLegend = { _isShape: true, - _fullInput: shape, index: shape._index, name: shape.name || shape.label.text || ('shape ' + shape._index), legend: shape.legend, @@ -599,12 +598,11 @@ function drawTexts(g, gd, legendObj) { this.text(ensureLength(newName, maxNameLength)) .call(textLayout, g, gd, legendObj); - var fullInput = legendItem.trace._fullInput || {}; var update = {}; update.name = newName; - if(fullInput._isShape) { + if(legendItem.trace._isShape) { return Registry.call('_guiRelayout', gd, 'shapes[' + trace.index + '].name', update.name); } else { return Registry.call('_guiRestyle', gd, update, trace.index); diff --git a/src/components/legend/handle_click.js b/src/components/legend/handle_click.js index be8d7c533b3..6170f03bf8b 100644 --- a/src/components/legend/handle_click.js +++ b/src/components/legend/handle_click.js @@ -50,7 +50,7 @@ exports.handleItemClick = function handleItemClick(g, gd, legendObj, mode) { var fullTrace = legendItem.trace; if (fullTrace._isShape) { - fullTrace = fullTrace._fullInput; + fullTrace = fullLayout.shapes[fullTrace.index]; } var legendgroup = fullTrace.legendgroup; @@ -92,15 +92,14 @@ exports.handleItemClick = function handleItemClick(g, gd, legendObj, mode) { function setVisibility(fullTrace, visibility) { if (legendItem.groupTitle && !toggleGroup) return; - var fullInput = fullTrace._fullInput || fullTrace; - var isShape = fullInput._isShape; - var index = fullInput.index; - if (index === undefined) index = fullInput._index; + var isShape = fullTrace._isShape; + var index = fullTrace.index; + if (index === undefined) index = fullTrace._index; // false -> false (not possible since will not be visible in legend) // true -> legendonly // legendonly -> true - var nextVisibility = fullInput.visible === false ? false : visibility; + var nextVisibility = fullTrace.visible === false ? false : visibility; if (isShape) { insertShapesUpdate(index, nextVisibility); @@ -111,10 +110,7 @@ exports.handleItemClick = function handleItemClick(g, gd, legendObj, mode) { var thisLegend = fullTrace.legend; - var fullInput = fullTrace._fullInput; - var isShape = fullInput && fullInput._isShape; - - if (!isShape && Registry.traceIs(fullTrace, 'pie-like')) { + if (!fullTrace._isShape && Registry.traceIs(fullTrace, 'pie-like')) { var thisLabel = legendItem.label; var thisLabelIndex = hiddenSlices.indexOf(thisLabel); diff --git a/src/components/selections/select.js b/src/components/selections/select.js index e44734665ad..01648f46da3 100644 --- a/src/components/selections/select.js +++ b/src/components/selections/select.js @@ -940,41 +940,33 @@ function isOnlyOnePointSelected(searchTraces) { function updateSelectedState(gd, searchTraces, eventData) { var i; + var trace; // before anything else, update preGUI if necessary for(i = 0; i < searchTraces.length; i++) { - var fullInputTrace = searchTraces[i].cd[0].trace._fullInput; - var tracePreGUI = gd._fullLayout._tracePreGUI[fullInputTrace.uid] || {}; + trace = searchTraces[i].cd[0].trace; + var tracePreGUI = gd._fullLayout._tracePreGUI[trace.uid] || {}; if(tracePreGUI.selectedpoints === undefined) { - tracePreGUI.selectedpoints = fullInputTrace._input.selectedpoints || null; + tracePreGUI.selectedpoints = trace._input.selectedpoints || null; } } - var trace; if(eventData) { var pts = eventData.points || []; for(i = 0; i < searchTraces.length; i++) { trace = searchTraces[i].cd[0].trace; - trace._input.selectedpoints = trace._fullInput.selectedpoints = []; - if(trace._fullInput !== trace) trace.selectedpoints = []; + trace._input.selectedpoints = trace.selectedpoints = []; } for(var k = 0; k < pts.length; k++) { var pt = pts[k]; var data = pt.data; - var fullData = pt.fullData; var pointIndex = pt.pointIndex; var pointIndices = pt.pointIndices; if(pointIndices) { [].push.apply(data.selectedpoints, pointIndices); - if(trace._fullInput !== trace) { - [].push.apply(fullData.selectedpoints, pointIndices); - } } else { data.selectedpoints.push(pointIndex); - if(trace._fullInput !== trace) { - fullData.selectedpoints.push(pointIndex); - } } } } else { @@ -982,9 +974,6 @@ function updateSelectedState(gd, searchTraces, eventData) { trace = searchTraces[i].cd[0].trace; delete trace.selectedpoints; delete trace._input.selectedpoints; - if(trace._fullInput !== trace) { - delete trace._fullInput.selectedpoints; - } } } diff --git a/src/lib/index.js b/src/lib/index.js index 8124c9ec9a5..7d5506d9503 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -585,39 +585,17 @@ lib.extractOption = function (calcPt, trace, calcKey, traceKey) { if (!Array.isArray(traceVal)) return traceVal; }; -function makePtIndex2PtNumber(indexToPoints) { - var ptIndex2ptNumber = {}; - for (var k in indexToPoints) { - var pts = indexToPoints[k]; - for (var j = 0; j < pts.length; j++) { - ptIndex2ptNumber[pts[j]] = +k; - } - } - return ptIndex2ptNumber; -} - /** Tag selected calcdata items - * - * N.B. note that point 'index' corresponds to input data array index - * whereas 'number' is its post-transform version. * * @param {array} calcTrace * @param {object} trace * - selectedpoints {array} - * - _indexToPoints {object} * @param {ptNumber2cdIndex} ptNumber2cdIndex (optional) * optional map object for trace types that do not have 1-to-1 point number to * calcdata item index correspondence (e.g. histogram) */ lib.tagSelected = function (calcTrace, trace, ptNumber2cdIndex) { var selectedpoints = trace.selectedpoints; - var indexToPoints = trace._indexToPoints; - var ptIndex2ptNumber; - - // make pt index-to-number map object, which takes care of transformed traces - if (indexToPoints) { - ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints); - } function isCdIndexValid(v) { return v !== undefined && v < calcTrace.length; @@ -630,7 +608,7 @@ lib.tagSelected = function (calcTrace, trace, ptNumber2cdIndex) { lib.isIndex(ptIndex) || (lib.isArrayOrTypedArray(ptIndex) && lib.isIndex(ptIndex[0]) && lib.isIndex(ptIndex[1])) ) { - var ptNumber = ptIndex2ptNumber ? ptIndex2ptNumber[ptIndex] : ptIndex; + var ptNumber = ptIndex; var cdIndex = ptNumber2cdIndex ? ptNumber2cdIndex[ptNumber] : ptNumber; if (isCdIndexValid(cdIndex)) { @@ -640,30 +618,6 @@ lib.tagSelected = function (calcTrace, trace, ptNumber2cdIndex) { } }; -lib.selIndices2selPoints = function (trace) { - var selectedpoints = trace.selectedpoints; - var indexToPoints = trace._indexToPoints; - - if (indexToPoints) { - var ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints); - var out = []; - - for (var i = 0; i < selectedpoints.length; i++) { - var ptIndex = selectedpoints[i]; - if (lib.isIndex(ptIndex)) { - var ptNumber = ptIndex2ptNumber[ptIndex]; - if (lib.isIndex(ptNumber)) { - out.push(ptNumber); - } - } - } - - return out; - } else { - return selectedpoints; - } -}; - /** Returns target as set by 'target' transform attribute * * @param {object} trace : full trace object @@ -956,8 +910,8 @@ lib.expandObjectPaths = function (data) { data[prop] = data[prop] || []; if (match[3] === '.') { - // This is the case where theere are subsequent properties into which - // we must recurse, e.g. transforms[0].value + // This is the case where there are subsequent properties into which + // we must recurse, e.g. annotations[0].text trailingPath = match[4]; dest = data[prop][idx] = data[prop][idx] || {}; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index c3cb8ad424c..0c0125c403b 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1408,16 +1408,6 @@ function _restyle(gd, aobj, traces) { return 'LAYOUT' + axName + '.range'; } - function getFullTrace(traceIndex) { - // usually fullData maps 1:1 onto data, but with groupby transforms - // the fullData index can be greater. Take the *first* matching trace. - for (var j = traceIndex; j < fullData.length; j++) { - if (fullData[j]._input === data[traceIndex]) return fullData[j]; - } - // should never get here - and if we *do* it should cause an error - // later on undefined fullTrace is passed to nestedProperty. - } - // for attrs that interact (like scales & autoscales), save the // old vals before making the change // val=undefined will not set a value, just record what the value was. @@ -1438,7 +1428,7 @@ function _restyle(gd, aobj, traces) { extraparam = layoutNP(gd.layout, attr.replace('LAYOUT', '')); } else { var tracei = traces[i]; - var preGUI = fullLayout._tracePreGUI[getFullTrace(tracei)._fullInput.uid]; + var preGUI = fullLayout._tracePreGUI[fullData[tracei].uid]; extraparam = makeNP(preGUI, guiEditFlag)(data[tracei], attr); } @@ -1509,8 +1499,8 @@ function _restyle(gd, aobj, traces) { undoit[ai] = a0(); for (i = 0; i < traces.length; i++) { cont = data[traces[i]]; - contFull = getFullTrace(traces[i]); - var preGUI = fullLayout._tracePreGUI[contFull._fullInput.uid]; + contFull = fullData[traces[i]]; + var preGUI = fullLayout._tracePreGUI[contFull.uid]; param = makeNP(preGUI, guiEditFlag)(cont, ai); oldVal = param.get(); newVal = Array.isArray(vi) ? vi[i % vi.length] : vi; @@ -2343,8 +2333,7 @@ var layoutUIControlPatterns = [ // or with no `attr` we use `trace.uirevision` var traceUIControlPatterns = [ { pattern: /^selectedpoints$/, attr: 'selectionrevision' }, - // "visible" includes trace.transforms[i].styles[j].value.visible - { pattern: /(^|value\.)visible$/, attr: 'legend.uirevision' }, + { pattern: /^visible$/, attr: 'legend.uirevision' }, { pattern: /^dimensions\[\d+\]\.constraintrange/ }, { pattern: /^node\.(x|y|groups)/ }, // for Sankey nodes { pattern: /^level$/ }, // for Sunburst, Treemap and Icicle traces @@ -2354,8 +2343,7 @@ var traceUIControlPatterns = [ // reasonable or should these be `editrevision`? // Also applies to axis titles up in the layout section - // "name" also includes transform.styles - { pattern: /(^|value\.)name$/ }, + { pattern: /^name$/ }, // including nested colorbar attributes (ie marker.colorbar) { pattern: /colorbar\.title\.text$/ }, { pattern: /colorbar\.(x|y)$/, attr: 'editrevision' } @@ -2392,7 +2380,7 @@ function getNewRev(revAttr, container) { function getFullTraceIndexFromUid(uid, fullData) { for (var i = 0; i < fullData.length; i++) { - if (fullData[i]._fullInput.uid === uid) return i; + if (fullData[i].uid === uid) return i; } return -1; } @@ -2499,7 +2487,7 @@ function applyUIRevisions(data, layout, oldFullData, oldFullLayout) { for (var uid in allTracePreGUI) { var tracePreGUI = allTracePreGUI[uid]; var newTrace = null; - var fullInput; + var fullTrace; for (key in tracePreGUI) { // wait until we know we have preGUI values to look for traces // but if we don't find both, stop looking at this uid @@ -2511,10 +2499,9 @@ function applyUIRevisions(data, layout, oldFullData, oldFullLayout) { delete allTracePreGUI[uid]; break; } - var fullTrace = oldFullData[fulli]; - fullInput = fullTrace._fullInput; + fullTrace = oldFullData[fulli]; - var newTracei = getTraceIndexFromUid(uid, data, fullInput.index); + var newTracei = getTraceIndexFromUid(uid, data, fullTrace.index); if (newTracei < 0) { // No match in new data delete allTracePreGUI[uid]; @@ -2529,7 +2516,7 @@ function applyUIRevisions(data, layout, oldFullData, oldFullLayout) { oldRev = nestedProperty(oldFullLayout, match.attr).get(); newRev = oldRev && getNewRev(match.attr, layout); } else { - oldRev = fullInput.uirevision; + oldRev = fullTrace.uirevision; // inheritance for trace.uirevision is simple, just layout.uirevision newRev = newTrace.uirevision; if (newRev === undefined) newRev = layout.uirevision; @@ -2541,7 +2528,7 @@ function applyUIRevisions(data, layout, oldFullData, oldFullLayout) { newNP = nestedProperty(newTrace, key); newVal = newNP.get(); if (valsMatch(newVal, preGUIVal)) { - newNP.set(undefinedToNull(nestedProperty(fullInput, key).get())); + newNP.set(undefinedToNull(nestedProperty(fullTrace, key).get())); continue; } } @@ -2630,9 +2617,9 @@ function react(gd, data, layout, config) { applyUIRevisions(gd.data, gd.layout, oldFullData, oldFullLayout); - // "true" skips updating calcdata and remapping arrays from calcTransforms, - // which supplyDefaults usually does at the end, but we may need to NOT do - // if the diff (which we haven't determined yet) says we'll recalc + // "true" skips updating calcdata, which supplyDefaults usually does at + // the end, but we may need to NOT do if the diff (which we haven't + // determined yet) says we'll recalc Plots.supplyDefaults(gd, { skipUpdateCalc: true }); var newFullData = gd._fullData; @@ -2667,7 +2654,7 @@ function react(gd, data, layout, config) { if (emptyCategories) emptyCategories(); } } - // otherwise do the calcdata updates and calcTransform array remaps that we skipped earlier + // otherwise do the calcdata updates that we skipped earlier } else { Plots.supplyDefaultsUpdateCalc(gd.calcdata, newFullData); } @@ -2783,11 +2770,11 @@ function diffData(gd, oldFullData, newFullData, immutable, transition, newDataRe for (i = 0; i < oldFullData.length; i++) { if (newFullData[i]) { - trace = newFullData[i]._fullInput; + trace = newFullData[i]; if (seenUIDs[trace.uid]) continue; seenUIDs[trace.uid] = 1; - getDiffFlags(oldFullData[i]._fullInput, trace, [], diffOpts); + getDiffFlags(oldFullData[i], trace, [], diffOpts); } } diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index f7aa6b7a9aa..ba0c7a490a1 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -218,7 +218,7 @@ exports.findArrayAttributes = function(trace) { * @param {object} trace * full trace object that contains a reference to `_module.attributes` * @param {object} parts - * an array of parts, like ['transforms', 1, 'value'] + * an array of parts, like ['dimensions', 1, 'values'] * typically from nestedProperty(...).parts * * @return {object|false} diff --git a/src/plot_api/template_api.js b/src/plot_api/template_api.js index cb80162222c..e6bbad4ded6 100644 --- a/src/plot_api/template_api.js +++ b/src/plot_api/template_api.js @@ -49,8 +49,6 @@ exports.makeTemplate = function(figure) { data.forEach(function(trace) { // TODO: What if no style info is extracted for this trace. We may // not want an empty object as the null value. - // TODO: allow transforms to contribute to templates? - // as it stands they are ignored, which may be for the best... var traceTemplate = {}; walkStyleKeys(trace, traceTemplate, getTraceInfo.bind(null, trace)); @@ -342,7 +340,7 @@ exports.validateTemplate = function(figureIn, template) { var fullTrace = fullData[i]; traceType = fullTrace.type; typeCount[traceType] = (typeCount[traceType] || 0) + 1; - if(!fullTrace._fullInput._template) { + if(!fullTrace._template) { // this takes care of the case of traceType in the data but not // the template errorList.push({ diff --git a/src/plots/cartesian/type_defaults.js b/src/plots/cartesian/type_defaults.js index 1d1b0e03217..71a3cd83040 100644 --- a/src/plots/cartesian/type_defaults.js +++ b/src/plots/cartesian/type_defaults.js @@ -118,11 +118,9 @@ function getBoxPosLetter(trace) { function isBoxWithoutPositionCoords(trace, axLetter) { var posLetter = getBoxPosLetter(trace); var isBox = traceIs(trace, 'box-violin'); - var isCandlestick = traceIs(trace._fullInput || {}, 'candlestick'); return ( isBox && - !isCandlestick && axLetter === posLetter && trace[posLetter] === undefined && trace[posLetter + '0'] === undefined diff --git a/src/plots/get_data.js b/src/plots/get_data.js index 7c0dcc2b31a..c32f9d897f9 100644 --- a/src/plots/get_data.js +++ b/src/plots/get_data.js @@ -65,7 +65,7 @@ exports.getModuleCalcData = function(calcdata, arg1, arg2) { var filterByZ = (trace.zorder !== undefined); // N.B. // - 'legendonly' traces do not make it past here - // - skip over 'visible' traces that got trimmed completely during calc transforms + // - skip over 'visible' traces with no data points if(trace.visible !== true || trace._length === 0) continue; // group calcdata trace not by 'module' (as the name of this function diff --git a/src/plots/plots.js b/src/plots/plots.js index 2378020e5de..6d4c6c7c9ab 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -183,9 +183,6 @@ var extraFormatKeys = [ * gd._fullLayout._basePlotModules * is a list of all the plot modules required to draw the plot. * - * gd._fullLayout._transformModules - * is a list of all the transform modules invoked. - * */ plots.supplyDefaults = function(gd, opts) { var skipUpdateCalc = opts && opts.skipUpdateCalc; @@ -412,7 +409,7 @@ plots.supplyDefaults = function(gd, opts) { var uid; for(uid in tracePreGUI) uids[uid] = 'old'; for(i = 0; i < newFullData.length; i++) { - uid = newFullData[i]._fullInput.uid; + uid = newFullData[i].uid; if(!uids[uid]) tracePreGUI[uid] = {}; uids[uid] = 'new'; } @@ -437,17 +434,6 @@ plots.supplyDefaultsUpdateCalc = function(oldCalcdata, newFullData) { var newTrace = newFullData[i]; var cd0 = (oldCalcdata[i] || [])[0]; if(cd0 && cd0.trace) { - var oldTrace = cd0.trace; - if(oldTrace._hasCalcTransform) { - var arrayAttrs = oldTrace._arrayAttrs; - var j, astr, oldArrayVal; - - for(j = 0; j < arrayAttrs.length; j++) { - astr = arrayAttrs[j]; - oldArrayVal = Lib.nestedProperty(oldTrace, astr).get().slice(); - Lib.nestedProperty(newTrace, astr).set(oldArrayVal); - } - } cd0.trace = newTrace; } } @@ -461,14 +447,8 @@ plots.supplyDefaultsUpdateCalc = function(oldCalcdata, newFullData) { */ function getTraceUids(oldFullData, newData) { var len = newData.length; - var oldFullInput = []; - var i, prevFullInput; - for(i = 0; i < oldFullData.length; i++) { - var thisFullInput = oldFullData[i]._fullInput; - if(thisFullInput !== prevFullInput) oldFullInput.push(thisFullInput); - prevFullInput = thisFullInput; - } - var oldLen = oldFullInput.length; + var i; + var oldLen = oldFullData.length; var out = new Array(len); var seenUids = {}; @@ -489,7 +469,7 @@ function getTraceUids(oldFullData, newData) { if(typeof newUid === 'number') newUid = String(newUid); if(tryUid(newUid, i)) continue; - if(i < oldLen && tryUid(oldFullInput[i].uid, i)) continue; + if(i < oldLen && tryUid(oldFullData[i].uid, i)) continue; setUid(Lib.randstr(seenUids), i); } @@ -908,51 +888,6 @@ function findMainSubplot(ax, fullLayout) { return mainSubplotID || nextBestMainSubplotID; } -// This function clears any trace attributes with valType: color and -// no set dflt filed in the plot schema. This is needed because groupby (which -// is the only transform for which this currently applies) supplies parent -// trace defaults, then expanded trace defaults. The result is that `null` -// colors are default-supplied and inherited as a color instead of a null. -// The result is that expanded trace default colors have no effect, with -// the final result that groups are indistinguishable. This function clears -// those colors so that individual groupby groups get unique colors. -plots.clearExpandedTraceDefaultColors = function(trace) { - var colorAttrs, path, i; - - // This uses weird closure state in order to satisfy the linter rule - // that we can't create functions in a loop. - function locateColorAttrs(attr, attrName, attrs, level) { - path[level] = attrName; - path.length = level + 1; - if(attr.valType === 'color' && attr.dflt === undefined) { - colorAttrs.push(path.join('.')); - } - } - - path = []; - - // Get the cached colorAttrs: - colorAttrs = trace._module._colorAttrs; - - // Or else compute and cache the colorAttrs on the module: - if(!colorAttrs) { - trace._module._colorAttrs = colorAttrs = []; - PlotSchema.crawl( - trace._module.attributes, - locateColorAttrs - ); - } - - for(i = 0; i < colorAttrs.length; i++) { - var origprop = Lib.nestedProperty(trace, '_input.' + colorAttrs[i]); - - if(!origprop.get()) { - Lib.nestedProperty(trace, colorAttrs[i]).set(null); - } - } -}; - - plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) { var modules = fullLayout._modules; var visibleModules = fullLayout._visibleModules; @@ -962,8 +897,6 @@ plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) { var i, fullTrace, trace; - fullLayout._transformModules = []; - function pushModule(fullTrace) { dataOut.push(fullTrace); @@ -1001,7 +934,6 @@ plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) { fullTrace.index = i; fullTrace._input = trace; - fullTrace._fullInput = fullTrace; pushModule(fullTrace); @@ -1488,16 +1420,6 @@ plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, trans } } - // transform module layout defaults - var transformModules = layoutOut._transformModules; - for(i = 0; i < transformModules.length; i++) { - _module = transformModules[i]; - - if(_module.supplyLayoutDefaults) { - _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData, transitionData); - } - } - for(component in componentsRegistry) { _module = componentsRegistry[component]; @@ -2303,14 +2225,14 @@ plots.extendObjectWithContainers = function(dest, src, containerPaths) { return dest; }; -plots.dataArrayContainers = ['transforms', 'dimensions']; +plots.dataArrayContainers = ['dimensions']; plots.layoutArrayContainers = Registry.layoutArrayContainers; /* * Extend a trace definition. This method: * * 1. directly transfers any array references - * 2. manually recurses into container arrays like transforms + * 2. manually recurses into container arrays like dimensions * * The result is the original object reference with the new contents merged in. */ @@ -2746,7 +2668,7 @@ plots.doCalcdata = function(gd, traces) { var fullData = gd._fullData; var fullLayout = gd._fullLayout; - var trace, _module, i, j; + var trace, _module, i; // XXX: Is this correct? Needs a closer look so that *some* traces can be recomputed without // *all* needing doCalcdata: @@ -2810,39 +2732,6 @@ plots.doCalcdata = function(gd, traces) { } } - var hasCalcTransform = false; - - function transformCalci(i) { - trace = fullData[i]; - _module = trace._module; - - if(trace.visible === true && trace.transforms) { - // we need one round of trace module calc before - // the calc transform to 'fill in' the categories list - // used for example in the data-to-coordinate method - if(_module && _module.calc) { - var cdi = _module.calc(gd, trace); - - // must clear scene 'batches', so that 2nd - // _module.calc call starts from scratch - if(cdi[0] && cdi[0].t && cdi[0].t._scene) { - delete cdi[0].t._scene.dirty; - } - } - - for(j = 0; j < trace.transforms.length; j++) { - var transform = trace.transforms[j]; - - _module = transformsRegistry[transform.type]; - if(_module && _module.calcTransform) { - trace._hasCalcTransform = true; - hasCalcTransform = true; - _module.calcTransform(gd, trace, transform); - } - } - } - } - function calci(i, isContainer) { trace = fullData[i]; _module = trace._module; @@ -2852,19 +2741,6 @@ plots.doCalcdata = function(gd, traces) { var cd = []; if(trace.visible === true && trace._length !== 0) { - // clear existing ref in case it got relinked - delete trace._indexToPoints; - // keep ref of index-to-points map object of the *last* enabled transform, - // this index-to-points map object is required to determine the calcdata indices - // that correspond to input indices (e.g. from 'selectedpoints') - var transforms = trace.transforms || []; - for(j = transforms.length - 1; j >= 0; j--) { - if(transforms[j].enabled) { - trace._indexToPoints = transforms[j]._indexToPoints; - break; - } - } - if(_module && _module.calc) { cd = _module.calc(gd, trace); } @@ -2889,15 +2765,7 @@ plots.doCalcdata = function(gd, traces) { setupAxisCategories(axList, fullData, fullLayout); - // 'transform' loop - must calc container traces first - // so that if their dependent traces can get transform properly - for(i = 0; i < fullData.length; i++) calci(i, true); - for(i = 0; i < fullData.length; i++) transformCalci(i); - - // clear stuff that should recomputed in 'regular' loop - if(hasCalcTransform) setupAxisCategories(axList, fullData, fullLayout); - - // 'regular' loop - make sure container traces (eg carpet) calc before + // make sure container traces (eg carpet) calc before // contained traces (eg contourcarpet) for(i = 0; i < fullData.length; i++) calci(i, true); for(i = 0; i < fullData.length; i++) calci(i, false); diff --git a/src/registry.js b/src/registry.js index 499abf2457b..fbd8e8aeed6 100644 --- a/src/registry.js +++ b/src/registry.js @@ -3,7 +3,6 @@ var Loggers = require('./lib/loggers'); var noop = require('./lib/noop'); var pushUnique = require('./lib/push_unique'); -var isPlainObject = require('./lib/is_plain_object'); var addStyleRule = require('./lib/dom').addStyleRule; var ExtendModule = require('./lib/extend'); @@ -48,13 +47,6 @@ exports.collectableSubplotTypes = null; * - format {object} : a `d3.locale` format specifier for this locale * any omitted keys we'll fall back on en-US. * - * A valid `moduleType: 'transform'` module has fields: - * - name {string} : transform name - * - transform {function} : default-level transform function - * - calcTransform {function} : calc-level transform function - * - attributes {object} : transform attributes declarations - * - supplyDefaults {function} : attributes default-supply function - * * A valid `moduleType: 'component'` module has fields: * - name {string} : the component name, used it with Register.getComponentMethod() * to employ component method. @@ -84,9 +76,6 @@ exports.register = function register(_modules) { case 'trace': registerTraceModule(newModule); break; - case 'transform': - registerTransformModule(newModule); - break; case 'component': registerComponentModule(newModule); break; @@ -294,33 +283,6 @@ function registerComponentModule(_module) { } } -function registerTransformModule(_module) { - if(typeof _module.name !== 'string') { - throw new Error('Transform module *name* must be a string.'); - } - - var prefix = 'Transform module ' + _module.name; - var hasTransform = typeof _module.transform === 'function'; - var hasCalcTransform = typeof _module.calcTransform === 'function'; - - if(!hasTransform && !hasCalcTransform) { - throw new Error(prefix + ' is missing a *transform* or *calcTransform* method.'); - } - if(hasTransform && hasCalcTransform) { - Loggers.log([ - prefix + ' has both a *transform* and *calcTransform* methods.', - 'Please note that all *transform* methods are executed', - 'before all *calcTransform* methods.' - ].join(' ')); - } - if(!isPlainObject(_module.attributes)) { - Loggers.log(prefix + ' registered without an *attributes* object.'); - } - if(typeof _module.supplyDefaults !== 'function') { - Loggers.log(prefix + ' registered without a *supplyDefaults* method.'); - } -} - function registerLocale(_module) { var locale = _module.name; var baseLocale = locale.split('-')[0]; diff --git a/src/traces/cone/defaults.js b/src/traces/cone/defaults.js index 459be0f43f9..53e25e8ea97 100644 --- a/src/traces/cone/defaults.js +++ b/src/traces/cone/defaults.js @@ -63,6 +63,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('yhoverformat'); coerce('zhoverformat'); - // disable 1D transforms (for now) + // Ensure _length is defined traceOut._length = null; }; diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index e28bab378a6..252d38cd978 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -206,7 +206,7 @@ module.exports = extendFlat( '*][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]`', 'Open vs. closed intervals make no difference to constraint display, but', - 'all versions are allowed for consistency with filter transforms.' + 'all versions are allowed.' ].join(' ') }, value: { diff --git a/src/traces/contour/constraint_mapping.js b/src/traces/contour/constraint_mapping.js index ba1560ccb7d..93c99258a65 100644 --- a/src/traces/contour/constraint_mapping.js +++ b/src/traces/contour/constraint_mapping.js @@ -3,8 +3,9 @@ var filterOps = require('../../constants/filter_ops'); var isNumeric = require('fast-isnumeric'); -// This syntax conforms to the existing filter transform syntax, but we don't care -// about open vs. closed intervals for simply drawing contours constraints: +// This syntax uses the comparison and interval operations defined in +// src/constants/filter_ops.js, but we don't care about open vs. closed intervals +// for the purpose of drawing contours constraints module.exports = { '[]': makeRangeSettings('[]'), '][': makeRangeSettings(']['), @@ -13,8 +14,7 @@ module.exports = { '=': makeInequalitySettings('=') }; -// This does not in any way shape or form support calendars. It's adapted from -// transforms/filter.js. +// This does not in any way shape or form support calendars function coerceValue(operation, value) { var hasArrayValue = Array.isArray(value); diff --git a/src/traces/histogram/event_data.js b/src/traces/histogram/event_data.js index db61fccc7db..fa77384d38e 100644 --- a/src/traces/histogram/event_data.js +++ b/src/traces/histogram/event_data.js @@ -22,17 +22,7 @@ module.exports = function eventData(out, pt, trace, cd, pointNumber) { delete out.pointNumber; delete out.pointIndex; - var pointIndices; - if(trace._indexToPoints) { - pointIndices = []; - for(var i = 0; i < pts.length; i++) { - pointIndices = pointIndices.concat(trace._indexToPoints[pts[i]]); - } - } else { - pointIndices = pts; - } - - out.pointIndices = pointIndices; + out.pointIndices = pts; } return out; diff --git a/src/traces/icicle/defaults.js b/src/traces/icicle/defaults.js index 4c4ad48332a..7827ef71495 100644 --- a/src/traces/icicle/defaults.js +++ b/src/traces/icicle/defaults.js @@ -95,6 +95,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleDomainDefaults(traceOut, layout, coerce); - // do not support transforms for now + // Ensure _length is defined traceOut._length = null; }; diff --git a/src/traces/indicator/defaults.js b/src/traces/indicator/defaults.js index 3da64f53514..98b47cb79ac 100644 --- a/src/traces/indicator/defaults.js +++ b/src/traces/indicator/defaults.js @@ -146,7 +146,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { traceOut._isAngular = traceOut._isBullet = false; } - // disable 1D transforms + // Ensure _length is defined traceOut._length = null; } diff --git a/src/traces/isosurface/defaults.js b/src/traces/isosurface/defaults.js index c0553d80897..13fb01c5b11 100644 --- a/src/traces/isosurface/defaults.js +++ b/src/traces/isosurface/defaults.js @@ -100,7 +100,7 @@ function supplyIsoDefaults(traceIn, traceOut, defaultColor, layout, coerce) { colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'}); - // disable 1D transforms (for now) + // Ensure _length is defined traceOut._length = null; } diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js index 26cf1b475b3..57bfccea924 100644 --- a/src/traces/mesh3d/defaults.js +++ b/src/traces/mesh3d/defaults.js @@ -92,8 +92,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('yhoverformat'); coerce('zhoverformat'); - // disable 1D transforms - // x/y/z should match lengths, and i/j/k should match as well, but - // the two sets have different lengths so transforms wouldn't work. + // Ensure _length is defined traceOut._length = null; }; diff --git a/src/traces/parcoords/plot.js b/src/traces/parcoords/plot.js index f2d2a0b0832..2857d918d64 100644 --- a/src/traces/parcoords/plot.js +++ b/src/traces/parcoords/plot.js @@ -57,7 +57,7 @@ var exports = module.exports = function plot(gd, cdModule) { // because it's an array of variable dimensionality. So store the whole // thing at once manually. var aStr = 'dimensions[' + initialDimIndex + '].constraintrange'; - var preGUI = fullLayout._tracePreGUI[gd._fullData[fullIndices[i]]._fullInput.uid]; + var preGUI = fullLayout._tracePreGUI[gd._fullData[fullIndices[i]].uid]; if(preGUI[aStr] === undefined) { var initialVal = dim.constraintrange; preGUI[aStr] = initialVal || null; @@ -116,7 +116,7 @@ var exports = module.exports = function plot(gd, cdModule) { // case to just store the order itself. // Registry.call('_storeDirectGUIEdit', // gd.data[inputIndices[i]], - // fullLayout._tracePreGUI[gd._fullData[fullIndices[i]]._fullInput.uid], + // fullLayout._tracePreGUI[gd._fullData[fullIndices[i]].uid], // {dimensions: currentDims[i]} // ); diff --git a/src/traces/sankey/defaults.js b/src/traces/sankey/defaults.js index d9ca15fea65..82d78efb1bb 100644 --- a/src/traces/sankey/defaults.js +++ b/src/traces/sankey/defaults.js @@ -108,8 +108,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout Lib.coerceFont(coerce, 'textfont', layout.font, { autoShadowDflt: true }); - // disable 1D transforms - arrays here are 1D but their lengths/meanings - // don't match, between nodes and links + // Ensure _length is defined traceOut._length = null; }; diff --git a/src/traces/scatter/stack_defaults.js b/src/traces/scatter/stack_defaults.js index 91c02c9c7d5..4f20b708662 100644 --- a/src/traces/scatter/stack_defaults.js +++ b/src/traces/scatter/stack_defaults.js @@ -33,10 +33,6 @@ module.exports = function handleStackDefaults(traceIn, traceOut, layout, coerce) }; firstTrace = true; } - // TODO: how is this going to work with groupby transforms? - // in principle it should be OK I guess, as long as explicit group styles - // don't override explicit base-trace styles? - var dflts = { orientation: (traceOut.x && !traceOut.y) ? 'h' : 'v' }; diff --git a/src/traces/scattergl/plot.js b/src/traces/scattergl/plot.js index bcc0af6d149..04e089e7b94 100644 --- a/src/traces/scattergl/plot.js +++ b/src/traces/scattergl/plot.js @@ -277,7 +277,7 @@ var exports = module.exports = function plot(gd, subplot, cdata) { // regenerate scene batch, if traces number changed during selection if(trace.selectedpoints) { - var selPts = scene.selectBatch[index] = Lib.selIndices2selPoints(trace); + var selPts = scene.selectBatch[index] = trace.selectedpoints; var selDict = {}; for(j = 0; j < selPts.length; j++) { diff --git a/src/traces/streamtube/defaults.js b/src/traces/streamtube/defaults.js index f49de256e21..43d42456793 100644 --- a/src/traces/streamtube/defaults.js +++ b/src/traces/streamtube/defaults.js @@ -65,8 +65,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('yhoverformat'); coerce('zhoverformat'); - // disable 1D transforms (for now) - // x/y/z and u/v/w have matching lengths, - // but they don't have to match with starts.(x|y|z) + // Ensure _length is defined traceOut._length = null; }; diff --git a/src/traces/sunburst/defaults.js b/src/traces/sunburst/defaults.js index 2439c817df6..828519633a6 100644 --- a/src/traces/sunburst/defaults.js +++ b/src/traces/sunburst/defaults.js @@ -72,6 +72,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleDomainDefaults(traceOut, layout, coerce); - // do not support transforms for now + // Ensure _length is defined traceOut._length = null; }; diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index 9da3b2e3d26..d86ba81d5e6 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -125,8 +125,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { opacityscaleDefaults(traceIn, traceOut, layout, coerce); - // disable 1D transforms - currently surface does NOT support column data like heatmap does - // you can use mesh3d for this use case, but not surface + // Ensure _length is defined traceOut._length = null; } diff --git a/src/traces/table/data_preparation_helper.js b/src/traces/table/data_preparation_helper.js index fb737366dd3..84035df22fe 100644 --- a/src/traces/table/data_preparation_helper.js +++ b/src/traces/table/data_preparation_helper.js @@ -38,7 +38,7 @@ module.exports = function calc(gd, trace) { var rowBlocks = makeRowBlock(anchorToRowBlock, headerRowBlocks); var uniqueKeys = {}; - var columnOrder = trace._fullInput.columnorder; + var columnOrder = trace.columnorder; if(isArrayOrTypedArray(columnOrder)) columnOrder = Array.from(columnOrder); columnOrder = columnOrder.concat(slicer(cellsValues.map(function(d, i) {return i;}))); diff --git a/src/traces/table/defaults.js b/src/traces/table/defaults.js index 45bfb8d74bb..bae59413204 100644 --- a/src/traces/table/defaults.js +++ b/src/traces/table/defaults.js @@ -50,6 +50,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('cells.fill.color'); Lib.coerceFont(coerce, 'cells.font', layout.font); - // disable 1D transforms + // Ensure _length is defined traceOut._length = null; }; diff --git a/src/traces/treemap/defaults.js b/src/traces/treemap/defaults.js index b877b34bedd..e6c71547fa5 100644 --- a/src/traces/treemap/defaults.js +++ b/src/traces/treemap/defaults.js @@ -108,6 +108,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleDomainDefaults(traceOut, layout, coerce); - // do not support transforms for now + // Ensure _length is defined traceOut._length = null; }; diff --git a/src/transforms/aggregate.js b/src/transforms/aggregate.js deleted file mode 100644 index eed6a937978..00000000000 --- a/src/transforms/aggregate.js +++ /dev/null @@ -1,450 +0,0 @@ -'use strict'; - -var Axes = require('../plots/cartesian/axes'); -var Lib = require('../lib'); -var PlotSchema = require('../plot_api/plot_schema'); -var pointsAccessorFunction = require('./helpers').pointsAccessorFunction; -var BADNUM = require('../constants/numerical').BADNUM; - -exports.moduleType = 'transform'; - -exports.name = 'aggregate'; - -var attrs = exports.attributes = { - enabled: { - valType: 'boolean', - dflt: true, - editType: 'calc', - description: [ - 'Determines whether this aggregate transform is enabled or disabled.' - ].join(' ') - }, - groups: { - // TODO: groupby should support string or array grouping this way too - // currently groupby only allows a grouping array - valType: 'string', - strict: true, - noBlank: true, - arrayOk: true, - dflt: 'x', - editType: 'calc', - description: [ - 'Sets the grouping target to which the aggregation is applied.', - 'Data points with matching group values will be coalesced into', - 'one point, using the supplied aggregation functions to reduce data', - 'in other data arrays.', - 'If a string, `groups` is assumed to be a reference to a data array', - 'in the parent trace object.', - 'To aggregate by nested variables, use *.* to access them.', - 'For example, set `groups` to *marker.color* to aggregate', - 'about the marker color array.', - 'If an array, `groups` is itself the data array by which we aggregate.' - ].join(' ') - }, - aggregations: { - _isLinkedToArray: 'aggregation', - target: { - valType: 'string', - editType: 'calc', - description: [ - 'A reference to the data array in the parent trace to aggregate.', - 'To aggregate by nested variables, use *.* to access them.', - 'For example, set `groups` to *marker.color* to aggregate', - 'over the marker color array.', - 'The referenced array must already exist, unless `func` is *count*,', - 'and each array may only be referenced once.' - ].join(' ') - }, - func: { - valType: 'enumerated', - values: ['count', 'sum', 'avg', 'median', 'mode', 'rms', 'stddev', 'min', 'max', 'first', 'last', 'change', 'range'], - dflt: 'first', - editType: 'calc', - description: [ - 'Sets the aggregation function.', - 'All values from the linked `target`, corresponding to the same value', - 'in the `groups` array, are collected and reduced by this function.', - '*count* is simply the number of values in the `groups` array, so does', - 'not even require the linked array to exist. *first* (*last*) is just', - 'the first (last) linked value.', - 'Invalid values are ignored, so for example in *avg* they do not', - 'contribute to either the numerator or the denominator.', - 'Any data type (numeric, date, category) may be aggregated with any', - 'function, even though in certain cases it is unlikely to make sense,', - 'for example a sum of dates or average of categories.', - '*median* will return the average of the two central values if there is', - 'an even count. *mode* will return the first value to reach the maximum', - 'count, in case of a tie.', - '*change* will return the difference between the first and last linked values.', - '*range* will return the difference between the min and max linked values.' - ].join(' ') - }, - funcmode: { - valType: 'enumerated', - values: ['sample', 'population'], - dflt: 'sample', - editType: 'calc', - description: [ - '*stddev* supports two formula variants: *sample* (normalize by N-1)', - 'and *population* (normalize by N).' - ].join(' ') - }, - enabled: { - valType: 'boolean', - dflt: true, - editType: 'calc', - description: [ - 'Determines whether this aggregation function is enabled or disabled.' - ].join(' ') - }, - editType: 'calc' - }, - editType: 'calc' -}; - -var aggAttrs = attrs.aggregations; - -/** - * Supply transform attributes defaults - * - * @param {object} transformIn - * object linked to trace.transforms[i] with 'func' set to exports.name - * @param {object} traceOut - * the _fullData trace this transform applies to - * @param {object} layout - * the plot's (not-so-full) layout - * @param {object} traceIn - * the input data trace this transform applies to - * - * @return {object} transformOut - * copy of transformIn that contains attribute defaults - */ -exports.supplyDefaults = function(transformIn, traceOut) { - var transformOut = {}; - var i; - - function coerce(attr, dflt) { - return Lib.coerce(transformIn, transformOut, attrs, attr, dflt); - } - - var enabled = coerce('enabled'); - - if(!enabled) return transformOut; - - /* - * Normally _arrayAttrs is calculated during doCalc, but that comes later. - * Anyway this can change due to *count* aggregations (see below) so it's not - * necessarily the same set. - * - * For performance we turn it into an object of truthy values - * we'll use 1 for arrays we haven't aggregated yet, 0 for finished arrays, - * as distinct from undefined which means this array isn't present in the input - * missing arrays can still be aggregate outputs for *count* aggregations. - */ - var arrayAttrArray = PlotSchema.findArrayAttributes(traceOut); - var arrayAttrs = {}; - for(i = 0; i < arrayAttrArray.length; i++) arrayAttrs[arrayAttrArray[i]] = 1; - - var groups = coerce('groups'); - - if(!Array.isArray(groups)) { - if(!arrayAttrs[groups]) { - transformOut.enabled = false; - return transformOut; - } - arrayAttrs[groups] = 0; - } - - var aggregationsIn = transformIn.aggregations || []; - var aggregationsOut = transformOut.aggregations = new Array(aggregationsIn.length); - var aggregationOut; - - function coercei(attr, dflt) { - return Lib.coerce(aggregationsIn[i], aggregationOut, aggAttrs, attr, dflt); - } - - for(i = 0; i < aggregationsIn.length; i++) { - aggregationOut = {_index: i}; - var target = coercei('target'); - var func = coercei('func'); - var enabledi = coercei('enabled'); - - // add this aggregation to the output only if it's the first instance - // of a valid target attribute - or an unused target attribute with "count" - if(enabledi && target && (arrayAttrs[target] || (func === 'count' && arrayAttrs[target] === undefined))) { - if(func === 'stddev') coercei('funcmode'); - - arrayAttrs[target] = 0; - aggregationsOut[i] = aggregationOut; - } else aggregationsOut[i] = {enabled: false, _index: i}; - } - - // any array attributes we haven't yet covered, fill them with the default aggregation - for(i = 0; i < arrayAttrArray.length; i++) { - if(arrayAttrs[arrayAttrArray[i]]) { - aggregationsOut.push({ - target: arrayAttrArray[i], - func: aggAttrs.func.dflt, - enabled: true, - _index: -1 - }); - } - } - - return transformOut; -}; - - -exports.calcTransform = function(gd, trace, opts) { - if(!opts.enabled) return; - - var groups = opts.groups; - - var groupArray = Lib.getTargetArray(trace, {target: groups}); - if(!groupArray) return; - - var i, vi, groupIndex, newGrouping; - - var groupIndices = {}; - var indexToPoints = {}; - var groupings = []; - - var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts); - - var len = groupArray.length; - if(trace._length) len = Math.min(len, trace._length); - - for(i = 0; i < len; i++) { - vi = groupArray[i]; - groupIndex = groupIndices[vi]; - if(groupIndex === undefined) { - groupIndices[vi] = groupings.length; - newGrouping = [i]; - groupings.push(newGrouping); - indexToPoints[groupIndices[vi]] = originalPointsAccessor(i); - } else { - groupings[groupIndex].push(i); - indexToPoints[groupIndices[vi]] = (indexToPoints[groupIndices[vi]] || []).concat(originalPointsAccessor(i)); - } - } - - opts._indexToPoints = indexToPoints; - - var aggregations = opts.aggregations; - - for(i = 0; i < aggregations.length; i++) { - aggregateOneArray(gd, trace, groupings, aggregations[i]); - } - - if(typeof groups === 'string') { - aggregateOneArray(gd, trace, groupings, { - target: groups, - func: 'first', - enabled: true - }); - } - - trace._length = groupings.length; -}; - -function aggregateOneArray(gd, trace, groupings, aggregation) { - if(!aggregation.enabled) return; - - var attr = aggregation.target; - var targetNP = Lib.nestedProperty(trace, attr); - var arrayIn = targetNP.get(); - var conversions = Axes.getDataConversions(gd, trace, attr, arrayIn); - var func = getAggregateFunction(aggregation, conversions); - - var arrayOut = new Array(groupings.length); - for(var i = 0; i < groupings.length; i++) { - arrayOut[i] = func(arrayIn, groupings[i]); - } - targetNP.set(arrayOut); - - if(aggregation.func === 'count') { - // count does not depend on an input array, so it's likely not part of _arrayAttrs yet - // but after this transform it most definitely *is* an array attribute. - Lib.pushUnique(trace._arrayAttrs, attr); - } -} - -function getAggregateFunction(opts, conversions) { - var func = opts.func; - var d2c = conversions.d2c; - var c2d = conversions.c2d; - - switch(func) { - // count, first, and last don't depend on anything about the data - // point back to pure functions for performance - case 'count': - return count; - case 'first': - return first; - case 'last': - return last; - - case 'sum': - // This will produce output in all cases even though it's nonsensical - // for date or category data. - return function(array, indices) { - var total = 0; - for(var i = 0; i < indices.length; i++) { - var vi = d2c(array[indices[i]]); - if(vi !== BADNUM) total += vi; - } - return c2d(total); - }; - - case 'avg': - // Generally meaningless for category data but it still does something. - return function(array, indices) { - var total = 0; - var cnt = 0; - for(var i = 0; i < indices.length; i++) { - var vi = d2c(array[indices[i]]); - if(vi !== BADNUM) { - total += vi; - cnt++; - } - } - return cnt ? c2d(total / cnt) : BADNUM; - }; - - case 'min': - return function(array, indices) { - var out = Infinity; - for(var i = 0; i < indices.length; i++) { - var vi = d2c(array[indices[i]]); - if(vi !== BADNUM) out = Math.min(out, vi); - } - return (out === Infinity) ? BADNUM : c2d(out); - }; - - case 'max': - return function(array, indices) { - var out = -Infinity; - for(var i = 0; i < indices.length; i++) { - var vi = d2c(array[indices[i]]); - if(vi !== BADNUM) out = Math.max(out, vi); - } - return (out === -Infinity) ? BADNUM : c2d(out); - }; - - case 'range': - return function(array, indices) { - var min = Infinity; - var max = -Infinity; - for(var i = 0; i < indices.length; i++) { - var vi = d2c(array[indices[i]]); - if(vi !== BADNUM) { - min = Math.min(min, vi); - max = Math.max(max, vi); - } - } - return (max === -Infinity || min === Infinity) ? BADNUM : c2d(max - min); - }; - - case 'change': - return function(array, indices) { - var first = d2c(array[indices[0]]); - var last = d2c(array[indices[indices.length - 1]]); - return (first === BADNUM || last === BADNUM) ? BADNUM : c2d(last - first); - }; - - case 'median': - return function(array, indices) { - var sortCalc = []; - for(var i = 0; i < indices.length; i++) { - var vi = d2c(array[indices[i]]); - if(vi !== BADNUM) sortCalc.push(vi); - } - if(!sortCalc.length) return BADNUM; - sortCalc.sort(Lib.sorterAsc); - var mid = (sortCalc.length - 1) / 2; - return c2d((sortCalc[Math.floor(mid)] + sortCalc[Math.ceil(mid)]) / 2); - }; - - case 'mode': - return function(array, indices) { - var counts = {}; - var maxCnt = 0; - var out = BADNUM; - for(var i = 0; i < indices.length; i++) { - var vi = d2c(array[indices[i]]); - if(vi !== BADNUM) { - var counti = counts[vi] = (counts[vi] || 0) + 1; - if(counti > maxCnt) { - maxCnt = counti; - out = vi; - } - } - } - return maxCnt ? c2d(out) : BADNUM; - }; - - case 'rms': - return function(array, indices) { - var total = 0; - var cnt = 0; - for(var i = 0; i < indices.length; i++) { - var vi = d2c(array[indices[i]]); - if(vi !== BADNUM) { - total += vi * vi; - cnt++; - } - } - return cnt ? c2d(Math.sqrt(total / cnt)) : BADNUM; - }; - - case 'stddev': - return function(array, indices) { - // balance numerical stability with performance: - // so that we call d2c once per element but don't need to - // store them, reference all to the first element - var total = 0; - var total2 = 0; - var cnt = 1; - var v0 = BADNUM; - var i; - for(i = 0; i < indices.length && v0 === BADNUM; i++) { - v0 = d2c(array[indices[i]]); - } - if(v0 === BADNUM) return BADNUM; - - for(; i < indices.length; i++) { - var vi = d2c(array[indices[i]]); - if(vi !== BADNUM) { - var dv = vi - v0; - total += dv; - total2 += dv * dv; - cnt++; - } - } - - // This is population std dev, if we want sample std dev - // we would need (...) / (cnt - 1) - // Also note there's no c2d here - that means for dates the result - // is a number of milliseconds, and for categories it's a number - // of category differences, which is not generically meaningful but - // as in other cases we don't forbid it. - var norm = (opts.funcmode === 'sample') ? (cnt - 1) : cnt; - // this is debatable: should a count of 1 return sample stddev of - // 0 or undefined? - if(!norm) return 0; - return Math.sqrt((total2 - (total * total / cnt)) / norm); - }; - } -} - -function count(array, indices) { - return indices.length; -} - -function first(array, indices) { - return array[indices[0]]; -} - -function last(array, indices) { - return array[indices[indices.length - 1]]; -} diff --git a/src/transforms/filter.js b/src/transforms/filter.js deleted file mode 100644 index 6a05527db34..00000000000 --- a/src/transforms/filter.js +++ /dev/null @@ -1,323 +0,0 @@ -'use strict'; - -var Lib = require('../lib'); -var Registry = require('../registry'); -var Axes = require('../plots/cartesian/axes'); -var pointsAccessorFunction = require('./helpers').pointsAccessorFunction; - -var filterOps = require('../constants/filter_ops'); -var COMPARISON_OPS = filterOps.COMPARISON_OPS; -var INTERVAL_OPS = filterOps.INTERVAL_OPS; -var SET_OPS = filterOps.SET_OPS; - -exports.moduleType = 'transform'; - -exports.name = 'filter'; - -exports.attributes = { - enabled: { - valType: 'boolean', - dflt: true, - editType: 'calc', - description: [ - 'Determines whether this filter transform is enabled or disabled.' - ].join(' ') - }, - target: { - valType: 'string', - strict: true, - noBlank: true, - arrayOk: true, - dflt: 'x', - editType: 'calc', - description: [ - 'Sets the filter target by which the filter is applied.', - - 'If a string, `target` is assumed to be a reference to a data array', - 'in the parent trace object.', - 'To filter about nested variables, use *.* to access them.', - 'For example, set `target` to *marker.color* to filter', - 'about the marker color array.', - - 'If an array, `target` is then the data array by which the filter is applied.' - ].join(' ') - }, - operation: { - valType: 'enumerated', - values: [] - .concat(COMPARISON_OPS) - .concat(INTERVAL_OPS) - .concat(SET_OPS), - dflt: '=', - editType: 'calc', - description: [ - 'Sets the filter operation.', - - '*=* keeps items equal to `value`', - '*!=* keeps items not equal to `value`', - - '*<* keeps items less than `value`', - '*<=* keeps items less than or equal to `value`', - - '*>* keeps items greater than `value`', - '*>=* keeps items greater than or equal to `value`', - - '*[]* keeps items inside `value[0]` to `value[1]` including both bounds', - '*()* keeps items inside `value[0]` to `value[1]` excluding both bounds', - '*[)* keeps items inside `value[0]` to `value[1]` including `value[0]` but excluding `value[1]', - '*(]* keeps items inside `value[0]` to `value[1]` excluding `value[0]` but including `value[1]', - - '*][* keeps items outside `value[0]` to `value[1]` and equal to both bounds', - '*)(* keeps items outside `value[0]` to `value[1]`', - '*](* keeps items outside `value[0]` to `value[1]` and equal to `value[0]`', - '*)[* keeps items outside `value[0]` to `value[1]` and equal to `value[1]`', - - '*{}* keeps items present in a set of values', - '*}{* keeps items not present in a set of values' - ].join(' ') - }, - value: { - valType: 'any', - dflt: 0, - editType: 'calc', - description: [ - 'Sets the value or values by which to filter.', - - 'Values are expected to be in the same type as the data linked', - 'to `target`.', - - 'When `operation` is set to one of', - 'the comparison values (' + COMPARISON_OPS + ')', - '`value` is expected to be a number or a string.', - - 'When `operation` is set to one of the interval values', - '(' + INTERVAL_OPS + ')', - '`value` is expected to be 2-item array where the first item', - 'is the lower bound and the second item is the upper bound.', - - 'When `operation`, is set to one of the set values', - '(' + SET_OPS + ')', - '`value` is expected to be an array with as many items as', - 'the desired set elements.' - ].join(' ') - }, - preservegaps: { - valType: 'boolean', - dflt: false, - editType: 'calc', - description: [ - 'Determines whether or not gaps in data arrays produced by the filter operation', - 'are preserved.', - 'Setting this to *true* might be useful when plotting a line chart', - 'with `connectgaps` set to *false*.' - ].join(' ') - }, - editType: 'calc' -}; - -exports.supplyDefaults = function(transformIn) { - var transformOut = {}; - - function coerce(attr, dflt) { - return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt); - } - - var enabled = coerce('enabled'); - - if(enabled) { - var target = coerce('target'); - - if(Lib.isArrayOrTypedArray(target) && target.length === 0) { - transformOut.enabled = false; - return transformOut; - } - - coerce('preservegaps'); - coerce('operation'); - coerce('value'); - - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); - handleCalendarDefaults(transformIn, transformOut, 'valuecalendar', null); - handleCalendarDefaults(transformIn, transformOut, 'targetcalendar', null); - } - - return transformOut; -}; - -exports.calcTransform = function(gd, trace, opts) { - if(!opts.enabled) return; - - var targetArray = Lib.getTargetArray(trace, opts); - if(!targetArray) return; - - var target = opts.target; - - var len = targetArray.length; - if(trace._length) len = Math.min(len, trace._length); - - var targetCalendar = opts.targetcalendar; - var arrayAttrs = trace._arrayAttrs; - var preservegaps = opts.preservegaps; - - // even if you provide targetcalendar, if target is a string and there - // is a calendar attribute matching target it will get used instead. - if(typeof target === 'string') { - var attrTargetCalendar = Lib.nestedProperty(trace, target + 'calendar').get(); - if(attrTargetCalendar) targetCalendar = attrTargetCalendar; - } - - var d2c = Axes.getDataToCoordFunc(gd, trace, target, targetArray); - var filterFunc = getFilterFunc(opts, d2c, targetCalendar); - var originalArrays = {}; - var indexToPoints = {}; - var index = 0; - - function forAllAttrs(fn, index) { - for(var j = 0; j < arrayAttrs.length; j++) { - var np = Lib.nestedProperty(trace, arrayAttrs[j]); - fn(np, index); - } - } - - var initFn; - var fillFn; - if(preservegaps) { - initFn = function(np) { - originalArrays[np.astr] = Lib.extendDeep([], np.get()); - np.set(new Array(len)); - }; - fillFn = function(np, index) { - var val = originalArrays[np.astr][index]; - np.get()[index] = val; - }; - } else { - initFn = function(np) { - originalArrays[np.astr] = Lib.extendDeep([], np.get()); - np.set([]); - }; - fillFn = function(np, index) { - var val = originalArrays[np.astr][index]; - np.get().push(val); - }; - } - - // copy all original array attribute values, and clear arrays in trace - forAllAttrs(initFn); - - var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts); - - // loop through filter array, fill trace arrays if passed - for(var i = 0; i < len; i++) { - var passed = filterFunc(targetArray[i]); - if(passed) { - forAllAttrs(fillFn, i); - indexToPoints[index++] = originalPointsAccessor(i); - } else if(preservegaps) index++; - } - - opts._indexToPoints = indexToPoints; - trace._length = index; -}; - -function getFilterFunc(opts, d2c, targetCalendar) { - var operation = opts.operation; - var value = opts.value; - var hasArrayValue = Lib.isArrayOrTypedArray(value); - - function isOperationIn(array) { - return array.indexOf(operation) !== -1; - } - - var d2cValue = function(v) { return d2c(v, 0, opts.valuecalendar); }; - var d2cTarget = function(v) { return d2c(v, 0, targetCalendar); }; - - var coercedValue; - - if(isOperationIn(COMPARISON_OPS)) { - coercedValue = hasArrayValue ? d2cValue(value[0]) : d2cValue(value); - } else if(isOperationIn(INTERVAL_OPS)) { - coercedValue = hasArrayValue ? - [d2cValue(value[0]), d2cValue(value[1])] : - [d2cValue(value), d2cValue(value)]; - } else if(isOperationIn(SET_OPS)) { - coercedValue = hasArrayValue ? value.map(d2cValue) : [d2cValue(value)]; - } - - switch(operation) { - case '=': - return function(v) { return d2cTarget(v) === coercedValue; }; - - case '!=': - return function(v) { return d2cTarget(v) !== coercedValue; }; - - case '<': - return function(v) { return d2cTarget(v) < coercedValue; }; - - case '<=': - return function(v) { return d2cTarget(v) <= coercedValue; }; - - case '>': - return function(v) { return d2cTarget(v) > coercedValue; }; - - case '>=': - return function(v) { return d2cTarget(v) >= coercedValue; }; - - case '[]': - return function(v) { - var cv = d2cTarget(v); - return cv >= coercedValue[0] && cv <= coercedValue[1]; - }; - - case '()': - return function(v) { - var cv = d2cTarget(v); - return cv > coercedValue[0] && cv < coercedValue[1]; - }; - - case '[)': - return function(v) { - var cv = d2cTarget(v); - return cv >= coercedValue[0] && cv < coercedValue[1]; - }; - - case '(]': - return function(v) { - var cv = d2cTarget(v); - return cv > coercedValue[0] && cv <= coercedValue[1]; - }; - - case '][': - return function(v) { - var cv = d2cTarget(v); - return cv <= coercedValue[0] || cv >= coercedValue[1]; - }; - - case ')(': - return function(v) { - var cv = d2cTarget(v); - return cv < coercedValue[0] || cv > coercedValue[1]; - }; - - case '](': - return function(v) { - var cv = d2cTarget(v); - return cv <= coercedValue[0] || cv > coercedValue[1]; - }; - - case ')[': - return function(v) { - var cv = d2cTarget(v); - return cv < coercedValue[0] || cv >= coercedValue[1]; - }; - - case '{}': - return function(v) { - return coercedValue.indexOf(d2cTarget(v)) !== -1; - }; - - case '}{': - return function(v) { - return coercedValue.indexOf(d2cTarget(v)) === -1; - }; - } -} diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js deleted file mode 100644 index 4f89c70d695..00000000000 --- a/src/transforms/groupby.js +++ /dev/null @@ -1,269 +0,0 @@ -'use strict'; - -var Lib = require('../lib'); -var PlotSchema = require('../plot_api/plot_schema'); -var Plots = require('../plots/plots'); -var pointsAccessorFunction = require('./helpers').pointsAccessorFunction; - -exports.moduleType = 'transform'; - -exports.name = 'groupby'; - -exports.attributes = { - enabled: { - valType: 'boolean', - dflt: true, - editType: 'calc', - description: [ - 'Determines whether this group-by transform is enabled or disabled.' - ].join(' ') - }, - groups: { - valType: 'data_array', - dflt: [], - editType: 'calc', - description: [ - 'Sets the groups in which the trace data will be split.', - 'For example, with `x` set to *[1, 2, 3, 4]* and', - '`groups` set to *[\'a\', \'b\', \'a\', \'b\']*,', - 'the groupby transform with split in one trace', - 'with `x` [1, 3] and one trace with `x` [2, 4].' - ].join(' ') - }, - nameformat: { - valType: 'string', - editType: 'calc', - description: [ - 'Pattern by which grouped traces are named. If only one trace is present,', - 'defaults to the group name (`"%{group}"`), otherwise defaults to the group name', - 'with trace name (`"%{group} (%{trace})"`). Available escape sequences are `%{group}`, which', - 'inserts the group name, and `%{trace}`, which inserts the trace name. If grouping', - 'GDP data by country when more than one trace is present, for example, the', - 'default "%{group} (%{trace})" would return "Monaco (GDP per capita)".' - ].join(' ') - }, - styles: { - _isLinkedToArray: 'style', - target: { - valType: 'string', - editType: 'calc', - description: [ - 'The group value which receives these styles.' - ].join(' ') - }, - value: { - valType: 'any', - dflt: {}, - editType: 'calc', - description: [ - 'Sets each group styles.', - 'For example, with `groups` set to *[\'a\', \'b\', \'a\', \'b\']*', - 'and `styles` set to *[{target: \'a\', value: { marker: { color: \'red\' } }}]', - 'marker points in group *\'a\'* will be drawn in red.' - ].join(' '), - _compareAsJSON: true - }, - editType: 'calc' - }, - editType: 'calc' -}; - -/** - * Supply transform attributes defaults - * - * @param {object} transformIn - * object linked to trace.transforms[i] with 'type' set to exports.name - * @param {object} traceOut - * the _fullData trace this transform applies to - * @param {object} layout - * the plot's (not-so-full) layout - * @param {object} traceIn - * the input data trace this transform applies to - * - * @return {object} transformOut - * copy of transformIn that contains attribute defaults - */ -exports.supplyDefaults = function(transformIn, traceOut, layout) { - var i; - var transformOut = {}; - - function coerce(attr, dflt) { - return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt); - } - - var enabled = coerce('enabled'); - - if(!enabled) return transformOut; - - coerce('groups'); - coerce('nameformat', layout._dataLength > 1 ? '%{group} (%{trace})' : '%{group}'); - - var styleIn = transformIn.styles; - var styleOut = transformOut.styles = []; - - if(styleIn) { - for(i = 0; i < styleIn.length; i++) { - var thisStyle = styleOut[i] = {}; - Lib.coerce(styleIn[i], styleOut[i], exports.attributes.styles, 'target'); - var value = Lib.coerce(styleIn[i], styleOut[i], exports.attributes.styles, 'value'); - - // so that you can edit value in place and have Plotly.react notice it, or - // rebuild it every time and have Plotly.react NOT think it changed: - // use _compareAsJSON to say we should diff the _JSON_value - if(Lib.isPlainObject(value)) thisStyle.value = Lib.extendDeep({}, value); - else if(value) delete thisStyle.value; - } - } - - return transformOut; -}; - - -/** - * Apply transform !!! - * - * @param {array} data - * array of transformed traces (is [fullTrace] upon first transform) - * - * @param {object} state - * state object which includes: - * - transform {object} full transform attributes - * - fullTrace {object} full trace object which is being transformed - * - fullData {array} full pre-transform(s) data array - * - layout {object} the plot's (not-so-full) layout - * - * @return {object} newData - * array of transformed traces - */ -exports.transform = function(data, state) { - var newTraces, i, j; - var newData = []; - - for(i = 0; i < data.length; i++) { - newTraces = transformOne(data[i], state); - - for(j = 0; j < newTraces.length; j++) { - newData.push(newTraces[j]); - } - } - - return newData; -}; - -function transformOne(trace, state) { - var i, j, k, attr, srcArray, groupName, newTrace, transforms, arrayLookup; - var groupNameObj; - - var opts = state.transform; - var transformIndex = state.transformIndex; - var groups = trace.transforms[transformIndex].groups; - var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts); - - if(!(Lib.isArrayOrTypedArray(groups)) || groups.length === 0) { - return [trace]; - } - - var groupNames = Lib.filterUnique(groups); - var newData = new Array(groupNames.length); - var len = groups.length; - - var arrayAttrs = PlotSchema.findArrayAttributes(trace); - - var styles = opts.styles || []; - var styleLookup = {}; - for(i = 0; i < styles.length; i++) { - styleLookup[styles[i].target] = styles[i].value; - } - - if(opts.styles) { - groupNameObj = Lib.keyedContainer(opts, 'styles', 'target', 'value.name'); - } - - // An index to map group name --> expanded trace index - var indexLookup = {}; - var indexCnts = {}; - - for(i = 0; i < groupNames.length; i++) { - groupName = groupNames[i]; - indexLookup[groupName] = i; - indexCnts[groupName] = 0; - - // Start with a deep extend that just copies array references. - newTrace = newData[i] = Lib.extendDeepNoArrays({}, trace); - newTrace._group = groupName; - newTrace.transforms[transformIndex]._indexToPoints = {}; - - var suppliedName = null; - if(groupNameObj) { - suppliedName = groupNameObj.get(groupName); - } - - if(suppliedName || suppliedName === '') { - newTrace.name = suppliedName; - } else { - newTrace.name = Lib.templateString(opts.nameformat, { - trace: trace.name, - group: groupName - }); - } - - // In order for groups to apply correctly to other transform data (e.g. - // a filter transform), we have to break the connection and clone the - // transforms so that each group writes grouped values into a different - // destination. This function does not break the array reference - // connection between the split transforms it creates. That's handled in - // initialize, which creates a new empty array for each arrayAttr. - transforms = newTrace.transforms; - newTrace.transforms = []; - for(j = 0; j < transforms.length; j++) { - newTrace.transforms[j] = Lib.extendDeepNoArrays({}, transforms[j]); - } - - // Initialize empty arrays for the arrayAttrs, to be split in the next step - for(j = 0; j < arrayAttrs.length; j++) { - Lib.nestedProperty(newTrace, arrayAttrs[j]).set([]); - } - } - - // For each array attribute including those nested inside this and other - // transforms (small note that we technically only need to do this for - // transforms that have not yet been applied): - for(k = 0; k < arrayAttrs.length; k++) { - attr = arrayAttrs[k]; - - // Cache all the arrays to which we'll push: - for(j = 0, arrayLookup = []; j < groupNames.length; j++) { - arrayLookup[j] = Lib.nestedProperty(newData[j], attr).get(); - } - - // Get the input data: - srcArray = Lib.nestedProperty(trace, attr).get(); - - // Send each data point to the appropriate expanded trace: - for(j = 0; j < len; j++) { - // Map group data --> trace index --> array and push data onto it - arrayLookup[indexLookup[groups[j]]].push(srcArray[j]); - } - } - - for(j = 0; j < len; j++) { - newTrace = newData[indexLookup[groups[j]]]; - - var indexToPoints = newTrace.transforms[transformIndex]._indexToPoints; - indexToPoints[indexCnts[groups[j]]] = originalPointsAccessor(j); - indexCnts[groups[j]]++; - } - - for(i = 0; i < groupNames.length; i++) { - groupName = groupNames[i]; - newTrace = newData[i]; - - Plots.clearExpandedTraceDefaultColors(newTrace); - - // there's no need to coerce styleLookup[groupName] here - // as another round of supplyDefaults is done on the transformed traces - newTrace = Lib.extendDeepNoArrays(newTrace, styleLookup[groupName] || {}); - } - - return newData; -} diff --git a/src/transforms/helpers.js b/src/transforms/helpers.js deleted file mode 100644 index 539889b962c..00000000000 --- a/src/transforms/helpers.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -exports.pointsAccessorFunction = function(transforms, opts) { - var tr; - var prevIndexToPoints; - for(var i = 0; i < transforms.length; i++) { - tr = transforms[i]; - if(tr === opts) break; - if(!tr._indexToPoints || tr.enabled === false) continue; - prevIndexToPoints = tr._indexToPoints; - } - var originalPointsAccessor = prevIndexToPoints ? - function(i) {return prevIndexToPoints[i];} : - function(i) {return [i];}; - return originalPointsAccessor; -}; diff --git a/src/transforms/sort.js b/src/transforms/sort.js deleted file mode 100644 index 083d3432328..00000000000 --- a/src/transforms/sort.js +++ /dev/null @@ -1,154 +0,0 @@ -'use strict'; - -var Lib = require('../lib'); -var Axes = require('../plots/cartesian/axes'); -var pointsAccessorFunction = require('./helpers').pointsAccessorFunction; - -var BADNUM = require('../constants/numerical').BADNUM; - -exports.moduleType = 'transform'; - -exports.name = 'sort'; - -exports.attributes = { - enabled: { - valType: 'boolean', - dflt: true, - editType: 'calc', - description: [ - 'Determines whether this sort transform is enabled or disabled.' - ].join(' ') - }, - target: { - valType: 'string', - strict: true, - noBlank: true, - arrayOk: true, - dflt: 'x', - editType: 'calc', - description: [ - 'Sets the target by which the sort transform is applied.', - - 'If a string, *target* is assumed to be a reference to a data array', - 'in the parent trace object.', - 'To sort about nested variables, use *.* to access them.', - 'For example, set `target` to *marker.size* to sort', - 'about the marker size array.', - - 'If an array, *target* is then the data array by which', - 'the sort transform is applied.' - ].join(' ') - }, - order: { - valType: 'enumerated', - values: ['ascending', 'descending'], - dflt: 'ascending', - editType: 'calc', - description: [ - 'Sets the sort transform order.' - ].join(' ') - }, - editType: 'calc' -}; - -exports.supplyDefaults = function(transformIn) { - var transformOut = {}; - - function coerce(attr, dflt) { - return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt); - } - - var enabled = coerce('enabled'); - - if(enabled) { - coerce('target'); - coerce('order'); - } - - return transformOut; -}; - -exports.calcTransform = function(gd, trace, opts) { - if(!opts.enabled) return; - - var targetArray = Lib.getTargetArray(trace, opts); - if(!targetArray) return; - - var target = opts.target; - - var len = targetArray.length; - if(trace._length) len = Math.min(len, trace._length); - - var arrayAttrs = trace._arrayAttrs; - var d2c = Axes.getDataToCoordFunc(gd, trace, target, targetArray); - var indices = getIndices(opts, targetArray, d2c, len); - var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts); - var indexToPoints = {}; - var i, j; - - for(i = 0; i < arrayAttrs.length; i++) { - var np = Lib.nestedProperty(trace, arrayAttrs[i]); - var arrayOld = np.get(); - var arrayNew = new Array(len); - - for(j = 0; j < len; j++) { - arrayNew[j] = arrayOld[indices[j]]; - } - - np.set(arrayNew); - } - - for(j = 0; j < len; j++) { - indexToPoints[j] = originalPointsAccessor(indices[j]); - } - - opts._indexToPoints = indexToPoints; - trace._length = len; -}; - -function getIndices(opts, targetArray, d2c, len) { - var sortedArray = new Array(len); - var indices = new Array(len); - var i; - - for(i = 0; i < len; i++) { - sortedArray[i] = {v: targetArray[i], i: i}; - } - - sortedArray.sort(getSortFunc(opts, d2c)); - - for(i = 0; i < len; i++) { - indices[i] = sortedArray[i].i; - } - - return indices; -} - -function getSortFunc(opts, d2c) { - switch(opts.order) { - case 'ascending': - return function(a, b) { - var ac = d2c(a.v); - var bc = d2c(b.v); - if(ac === BADNUM) { - return 1; - } - if(bc === BADNUM) { - return -1; - } - return ac - bc; - }; - case 'descending': - return function(a, b) { - var ac = d2c(a.v); - var bc = d2c(b.v); - if(ac === BADNUM) { - return 1; - } - if(bc === BADNUM) { - return -1; - } - return bc - ac; - }; - } -} diff --git a/src/types/core/data.internal.d.ts b/src/types/core/data.internal.d.ts index 60d70d751ae..10e9036c72f 100644 --- a/src/types/core/data.internal.d.ts +++ b/src/types/core/data.internal.d.ts @@ -37,18 +37,10 @@ export interface CalcData { * internal helpers should use `FullData` rather than this directly. */ interface FullDataInternals { - /** Names of attributes whose values were arrays (used by transform machinery). */ + /** Names of attributes whose values are data arrays (used for hover/event data). */ _arrayAttrs?: string[]; - /** Trace index after array attribute expansion. */ - _expandedIndex?: number; /** Cached extremes (per axis) for autorange computation. */ _extremes?: Record; - /** Snapshot of the trace before transforms were applied. */ - _fullInput?: Data; - /** True when the trace has a transform that affects `calcdata`. */ - _hasCalcTransform?: boolean; - /** Map from `_expandedIndex` back to original point indices. */ - _indexToPoints?: { [key: number]: number[] }; /** Original user-supplied trace object (pre-defaults). */ _input?: Data; /** Length of the trace's data arrays (after coercion). */ diff --git a/src/types/generated/schema.d.ts b/src/types/generated/schema.d.ts index 00bb93a5f8c..d8a20e31113 100644 --- a/src/types/generated/schema.d.ts +++ b/src/types/generated/schema.d.ts @@ -2941,7 +2941,7 @@ export interface ContourData { /** Sets the contour label formatting rule using d3 formatting mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. */ labelformat?: string; /** - * Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed for consistency with filter transforms. + * Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed. * @default '=' */ operation?: '=' | '<' | '>=' | '>' | '<=' | '[]' | '()' | '[)' | '(]' | '][' | ')(' | '](' | ')['; @@ -3282,7 +3282,7 @@ export interface ContourcarpetData { /** Sets the contour label formatting rule using d3 formatting mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. */ labelformat?: string; /** - * Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed for consistency with filter transforms. + * Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed. * @default '=' */ operation?: '=' | '<' | '>=' | '>' | '<=' | '[]' | '()' | '[)' | '(]' | '][' | ')(' | '](' | ')['; @@ -4947,7 +4947,7 @@ export interface Histogram2dcontourData { /** Sets the contour label formatting rule using d3 formatting mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. */ labelformat?: string; /** - * Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed for consistency with filter transforms. + * Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed. * @default '=' */ operation?: '=' | '<' | '>=' | '>' | '<=' | '[]' | '()' | '[)' | '(]' | '][' | ')(' | '](' | ')['; diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index ac158cf4c96..302a88e70a9 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -74,8 +74,8 @@ describe('finance charts defaults:', function () { assertDataLength(trace0, out._fullData[0], 5); assertDataLength(trace1, out._fullData[1], 4); - expect(out._fullData[0]._fullInput.x).toBeUndefined(); - expect(out._fullData[1]._fullInput.x).toBeDefined(); + expect(out._fullData[0].x).toBeUndefined(); + expect(out._fullData[1].x).toBeDefined(); }); it('should set visible to *false* when a component (other than x) is missing', function () { diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index fe1f6b71f0c..d4fabed33c8 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -3364,12 +3364,6 @@ describe('Queue', function () { expect(gd.undoQueue.queue[0].undo.args[0][1]['title.text']).toEqual(null); expect(gd.undoQueue.queue[0].redo.args[0][1]['title.text']).toEqual('A title'); - return Plotly.restyle(gd, 'transforms[0]', { type: 'filter' }); - }) - .then(function () { - expect(gd.undoQueue.queue[1].undo.args[0][1]).toEqual({ 'transforms[0]': null }); - expect(gd.undoQueue.queue[1].redo.args[0][1]).toEqual({ 'transforms[0]': { type: 'filter' } }); - return Plotly.relayout(gd, 'updatemenus[0]', { buttons: [] }); }) .then(function () { @@ -3381,12 +3375,6 @@ describe('Queue', function () { .then(function () { expect(gd.undoQueue.queue[1].undo.args[0][1]).toEqual({ 'updatemenus[0]': { buttons: [] } }); expect(gd.undoQueue.queue[1].redo.args[0][1]).toEqual({ 'updatemenus[0]': null }); - - return Plotly.restyle(gd, 'transforms[0]', null); - }) - .then(function () { - expect(gd.undoQueue.queue[1].undo.args[0][1]).toEqual({ 'transforms[0]': [{ type: 'filter' }] }); - expect(gd.undoQueue.queue[1].redo.args[0][1]).toEqual({ 'transforms[0]': null }); }) .then(done, done.fail); }); diff --git a/test/jasmine/tests/plot_api_react_test.js b/test/jasmine/tests/plot_api_react_test.js index 40c8e0a3f53..9dc48bc682d 100644 --- a/test/jasmine/tests/plot_api_react_test.js +++ b/test/jasmine/tests/plot_api_react_test.js @@ -626,7 +626,7 @@ describe('@noCIdep Plotly.react', function() { function fullJson() { var out = JSON.parse(Plotly.Plots.graphJson({ - data: gd._fullData.map(function(trace) { return trace._fullInput; }), + data: gd._fullData.map(function(trace) { return trace; }), layout: gd._fullLayout })); @@ -978,7 +978,7 @@ describe('Plotly.react and uirevision attributes', function() { var trace = gd.data[i]; var fullTrace = gd._fullData.filter(function(ft) { return ft.index === i; - })[0]._fullInput; + })[0]; for(var key in traceKeys) { var val = traceKeys[key]; diff --git a/test/jasmine/tests/plots_test.js b/test/jasmine/tests/plots_test.js index c7111a9d9ef..2adc7d26bd5 100644 --- a/test/jasmine/tests/plots_test.js +++ b/test/jasmine/tests/plots_test.js @@ -65,7 +65,6 @@ describe('Test Plots', function() { type: 'contour', _empties: [1, 2, 3] }]; - oldFullData.forEach(function(trace) { trace._fullInput = trace; }); var oldFullLayout = { _plots: { xy: { plot: {} } }, @@ -133,9 +132,6 @@ describe('Test Plots', function() { expect(gd._fullData[0]._input).toBe(trace0); expect(gd._fullData[1]._input).toBe(trace1); - - expect(gd._fullData[0]._fullInput).toBe(gd._fullData[0]); - expect(gd._fullData[1]._fullInput).toBe(gd._fullData[1]); }); function testSanitizeMarginsHasBeenCalledOnlyOnce(gd) { diff --git a/test/plot-schema.json b/test/plot-schema.json index 749fdb23592..4effd2f0c3c 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -28588,7 +28588,7 @@ "valType": "string" }, "operation": { - "description": "Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed for consistency with filter transforms.", + "description": "Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed.", "dflt": "=", "editType": "calc", "valType": "enumerated", @@ -30264,7 +30264,7 @@ "valType": "string" }, "operation": { - "description": "Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed for consistency with filter transforms.", + "description": "Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed.", "dflt": "=", "editType": "calc", "valType": "enumerated", @@ -41148,7 +41148,7 @@ "valType": "string" }, "operation": { - "description": "Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed for consistency with filter transforms.", + "description": "Sets the constraint operation. *=* keeps regions equal to `value` *<* and *<=* keep regions less than `value` *>* and *>=* keep regions greater than `value` *[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]` *][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]` Open vs. closed intervals make no difference to constraint display, but all versions are allowed.", "dflt": "=", "editType": "calc", "valType": "enumerated",