From 9c09d8c9575ca8c25537d4bc5f2402cdfd9dde3d Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 24 Apr 2019 21:12:56 -0400 Subject: [PATCH 01/13] sort categorical Cartesian axes by value --- src/plot_api/plot_schema.js | 2 + src/plots/cartesian/layout_attributes.js | 14 +- src/plots/cartesian/set_convert.js | 31 +++ src/plots/plots.js | 166 ++++++++++++++ src/traces/bar/calc.js | 2 +- src/traces/box/calc.js | 4 + src/traces/ohlc/calc.js | 4 + .../hist_category_value_ascending.png | Bin 0 -> 19623 bytes .../scatter_category_value_descending.png | Bin 0 -> 13456 bytes .../baselines/sort_by_value_matching_axes.png | Bin 0 -> 18930 bytes .../mocks/hist_category_value_ascending.json | 39 ++++ .../scatter_category_value_descending.json | 26 +++ .../mocks/sort_by_value_matching_axes.json | 43 ++++ test/jasmine/tests/calcdata_test.js | 212 ++++++++++++++++++ 14 files changed, 537 insertions(+), 6 deletions(-) create mode 100644 test/image/baselines/hist_category_value_ascending.png create mode 100644 test/image/baselines/scatter_category_value_descending.png create mode 100644 test/image/baselines/sort_by_value_matching_axes.png create mode 100644 test/image/mocks/hist_category_value_ascending.json create mode 100644 test/image/mocks/scatter_category_value_descending.json create mode 100644 test/image/mocks/sort_by_value_matching_axes.json diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index 1b1551e0143..9bfcd6f604d 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -503,6 +503,8 @@ function getTraceAttributes(type) { var out = { meta: _module.meta || {}, + categories: _module.categories || {}, + type: type, attributes: formatAttributes(attributes), }; diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 8c920ecbac0..38d3a67f333 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -817,8 +817,11 @@ module.exports = { categoryorder: { valType: 'enumerated', values: [ - 'trace', 'category ascending', 'category descending', 'array' - /* , 'value ascending', 'value descending'*/ // value ascending / descending to be implemented later + 'trace', 'category ascending', 'category descending', 'array', + 'value ascending', 'value descending', + 'min ascending', 'min descending', + 'max ascending', 'max descending', + 'sum ascending', 'sum descending' ], dflt: 'trace', role: 'info', @@ -828,11 +831,12 @@ module.exports = { 'By default, plotly uses *trace*, which specifies the order that is present in the data supplied.', 'Set `categoryorder` to *category ascending* or *category descending* if order should be determined by', 'the alphanumerical order of the category names.', - /* 'Set `categoryorder` to *value ascending* or *value descending* if order should be determined by the', - 'numerical order of the values.',*/ // // value ascending / descending to be implemented later 'Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category', 'is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to', - 'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.' + 'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.', + 'Set `categoryorder` to *value ascending* or *value descending* if order should be determined by the', + 'numerical order of the values.', + 'Similarly, the order can be determined by the min, max or the sums of the values.' ].join(' ') }, categoryarray: { diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index a407b912e78..5781b4490ad 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -612,6 +612,37 @@ module.exports = function setConvert(ax, fullLayout) { } }; + // sort the axis (and all the matching ones) by _initialCategories + // returns the indices of the traces affected by the reordering + ax.sortByInitialCategories = function() { + var affectedTraces = []; + var emptyCategories = function() { + ax._categories = []; + ax._categoriesMap = {}; + }; + + emptyCategories(); + + if(ax._initialCategories) { + for(var j = 0; j < ax._initialCategories.length; j++) { + setCategoryIndex(ax._initialCategories[j]); + } + } + + affectedTraces = affectedTraces.concat(ax._traceIndices); + + // Propagate to matching axes + var group = ax._matchGroup; + for(var axId2 in group) { + if(axId === axId2) continue; + var ax2 = fullLayout[axisIds.id2name(axId2)]; + ax2._categories = ax._categories; + ax2._categoriesMap = ax._categoriesMap; + affectedTraces = affectedTraces.concat(ax2._traceIndices); + } + return affectedTraces; + }; + // Propagate localization into the axis so that // methods in Axes can use it w/o having to pass fullLayout // Default (non-d3) number formatting uses separators directly diff --git a/src/plots/plots.js b/src/plots/plots.js index 76db1aae239..dd7695d9577 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2843,10 +2843,176 @@ plots.doCalcdata = function(gd, traces) { doCrossTraceCalc(gd); + // Sort axis categories per value if specified + var sorted = sortAxisCategoriesByValue(axList, gd); + if(sorted.length) { + // If a sort operation was performed, run calc() again + for(i = 0; i < sorted.length; i++) calci(sorted[i], true); + for(i = 0; i < sorted.length; i++) calci(sorted[i], false); + doCrossTraceCalc(gd); + } + Registry.getComponentMethod('fx', 'calc')(gd); Registry.getComponentMethod('errorbars', 'calc')(gd); }; +var sortAxisCategoriesByValueRegex = /(value|sum|min|max) (ascending|descending)/; + +function sortAxisCategoriesByValue(axList, gd) { + var affectedTraces = []; + var i, j, k, l, o; + for(i = 0; i < axList.length; i++) { + var ax = axList[i]; + if(ax.type !== 'category') continue; + + // Order by value + var match = ax.categoryorder.match(sortAxisCategoriesByValueRegex); + if(match) { + // Store values associated with each category + var categoriesValue = []; + for(j = 0; j < ax._categories.length; j++) { + categoriesValue.push([ax._categories[j], []]); + } + + // Collect values across traces + for(j = 0; j < ax._traceIndices.length; j++) { + var traceIndex = ax._traceIndices[j]; + var fullData = gd._fullData[traceIndex]; + + // Skip over invisible traces + if(fullData.visible !== true) continue; + + var type = fullData.type; + if(type === 'histogram') delete fullData._autoBinFinished; + + var cd = gd.calcdata[traceIndex]; + for(k = 0; k < cd.length; k++) { + var cdi = cd[k]; + var cat, catIndex, value; + + // If `splom`, collect values across dimensions + if(type === 'splom') { + // Find which dimension the current axis is representing + var currentDimensionIndex = cdi.trace[ax._id.charAt(0) + 'axes'].indexOf(ax._id); + + // Apply logic to associated x axis + if(ax._id.charAt(0) === 'y') { + var associatedXAxis = ax._id.split(''); + associatedXAxis[0] = 'x'; + associatedXAxis = associatedXAxis.join(''); + ax = gd._fullLayout[axisIDs.id2name(associatedXAxis)]; + } + + var categories = cdi.trace.dimensions[currentDimensionIndex].values; + for(l = 0; l < categories.length; l++) { + cat = categories[l]; + catIndex = ax._categoriesMap[cat]; + + // Collect values over all other dimensions + for(o = 0; o < cdi.trace.dimensions.length; o++) { + if(o === currentDimensionIndex) continue; + var dimension = cdi.trace.dimensions[o]; + categoriesValue[catIndex][1].push(dimension.values[l]); + } + } + // If `scattergl`, collect all values stashed under cdi.t + } else if(type === 'scattergl') { + for(l = 0; l < cdi.t.x.length; l++) { + if(ax._id.charAt(0) === 'x') { + cat = cdi.t.x[l]; + catIndex = cat; + value = cdi.t.y[l]; + } + + if(ax._id.charAt(0) === 'y') { + cat = cdi.t.y[l]; + catIndex = cat; + value = cdi.t.x[l]; + } + categoriesValue[catIndex][1].push(value); + } + // must clear scene 'batches', so that 2nd + // _module.calc call starts from scratch + if(cdi.t && cdi.t._scene) { + delete cdi.t._scene.dirty; + } + // For all other 2d cartesian traces + } else { + if(ax._id.charAt(0) === 'x') { + cat = cdi.p + 1 ? cdi.p : cdi.x; + value = cdi.s || cdi.v || cdi.y; + } else if(ax._id.charAt(0) === 'y') { + cat = cdi.p + 1 ? cdi.p : cdi.y; + value = cdi.s || cdi.v || cdi.x; + } + + // If 2dMap, collect values in `z` + if(cdi.hasOwnProperty('z')) { + value = cdi.z; + + for(l = 0; l < value.length; l++) { + for(o = 0; o < value[l].length; o++) { + catIndex = ax._id.charAt(0) === 'y' ? l : o; + categoriesValue[catIndex][1].push(value[l][o]); + } + } + } else { + if(!Array.isArray(value)) value = [value]; + for(l = 0; l < value.length; l++) { + categoriesValue[cat][1].push(value[l]); + } + } + } + } + } + + // Aggregate values + var aggFn; + switch(match[1]) { + case 'min': + aggFn = Math.min; + break; + case 'max': + aggFn = Math.max; + break; + default: + aggFn = function(a, b) { return a + b;}; + } + + ax._categoriesValue = categoriesValue; + + var categoriesAggregatedValue = []; + for(j = 0; j < categoriesValue.length; j++) { + categoriesAggregatedValue.push([ + categoriesValue[j][0], + Lib.aggNums(aggFn, null, categoriesValue[j][1]) + ]); + } + + // Sort by aggregated value + categoriesAggregatedValue.sort(function(a, b) { + return a[1] - b[1]; + }); + + ax._categoriesAggregatedValue = categoriesAggregatedValue; + + // Set new category order + ax._initialCategories = categoriesAggregatedValue.map(function(c) { + return c[0]; + }); + + // Reverse if descending + if(match[2] === 'descending') { + ax._initialCategories.reverse(); + } + + // Sort all matching axes + affectedTraces = affectedTraces.concat(ax.sortByInitialCategories()); + } + } + return affectedTraces; +} + function setupAxisCategories(axList, fullData) { for(var i = 0; i < axList.length; i++) { var ax = axList[i]; diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index 41af79b7b1c..4faceb558da 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -33,7 +33,7 @@ module.exports = function calc(gd, trace) { // set position and size for(var i = 0; i < serieslen; i++) { - cd[i] = { p: pos[i], s: size[i] }; + cd[i] = { p: pos[i], s: size[i], v: size[i] }; if(trace.ids) { cd[i].id = String(trace.ids[i]); diff --git a/src/traces/box/calc.js b/src/traces/box/calc.js index 0e424c5223d..092d5795fdd 100644 --- a/src/traces/box/calc.js +++ b/src/traces/box/calc.js @@ -80,6 +80,10 @@ module.exports = function calc(gd, trace) { cdi.pos = posDistinct[i]; cdi.pts = pts; + // Sort categories by values + cdi[posLetter] = cdi.pos; + cdi[valLetter] = cdi.pts.map(function(pt) { return pt.v; }); + cdi.min = boxVals[0]; cdi.max = boxVals[bvLen - 1]; cdi.mean = Lib.mean(boxVals, bvLen); diff --git a/src/traces/ohlc/calc.js b/src/traces/ohlc/calc.js index f6ad3bfa74e..0121eb382b2 100644 --- a/src/traces/ohlc/calc.js +++ b/src/traces/ohlc/calc.js @@ -86,6 +86,10 @@ function calcCommon(gd, trace, x, ya, ptFunc) { pt.i = i; pt.dir = increasing ? 'increasing' : 'decreasing'; + // For categoryorder, store low and high + pt.x = pt.pos; + pt.y = [li, hi]; + if(hasTextArray) pt.tx = trace.text[i]; if(hasHovertextArray) pt.htx = trace.hovertext[i]; diff --git a/test/image/baselines/hist_category_value_ascending.png b/test/image/baselines/hist_category_value_ascending.png new file mode 100644 index 0000000000000000000000000000000000000000..4bc053c1405bba6276d29e7d9b37a7c4aa849d0e GIT binary patch literal 19623 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 literal 0 HcmV?d00001 diff --git a/test/image/baselines/scatter_category_value_descending.png b/test/image/baselines/scatter_category_value_descending.png new file mode 100644 index 0000000000000000000000000000000000000000..378e6393d895166055acae6d32dabbf0840e6d30 GIT binary patch literal 13456 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 U=UD>(mJ^k(rh!JG+WBk$3tU713;+NC literal 0 HcmV?d00001 diff --git a/test/image/mocks/hist_category_value_ascending.json b/test/image/mocks/hist_category_value_ascending.json new file mode 100644 index 00000000000..fe1379ecd20 --- /dev/null +++ b/test/image/mocks/hist_category_value_ascending.json @@ -0,0 +1,39 @@ +{ + "data": [{ + "x": ["a", "b", "c", "a", "b", "d", "b", "c", "b", "b"], + "type": "histogram" + }, + { + "x": ["d", "c", "a", "e", "a"], + "type": "histogram" + }, + { + "y": ["a", "b", "c", "a", "b", "d", "b", "c"], + "type": "histogram", + "xaxis": "x2", + "yaxis": "y2" + }, + { + "y": ["d", "c", "b", "a", "e", "a", "b"], + "type": "histogram", + "xaxis": "x2", + "yaxis": "y2" + }], + "layout": { + "title": "categoryorder: \"value ascending\"", + "height": 400, + "width": 600, + "barmode": "stack", + "xaxis": { + "domain": [0, 0.45], + "categoryorder": "value ascending" + }, + "xaxis2": { + "domain": [0.55, 1] + }, + "yaxis2": { + "anchor": "x2", + "categoryorder": "value ascending" + } + } +} diff --git a/test/image/mocks/scatter_category_value_descending.json b/test/image/mocks/scatter_category_value_descending.json new file mode 100644 index 00000000000..02c526b2be0 --- /dev/null +++ b/test/image/mocks/scatter_category_value_descending.json @@ -0,0 +1,26 @@ +{ + "data": [ + { + "x": ["a", "b", "c", "d"], + "y": [4, 2, 3, 1], + "type": "scatter", + "mode": "markers" + }, + { + "x": ["a", "b", "c", "d", "c", "c"], + "type": "histogram" + } + ], + "layout": { + "title": "xaxis.categoryorder: \"value descending\"", + "width": 400, + "height": 400, + "xaxis": { + "domain": [ + 0, + 1 + ], + "categoryorder": "value descending" + } + } +} diff --git a/test/image/mocks/sort_by_value_matching_axes.json b/test/image/mocks/sort_by_value_matching_axes.json new file mode 100644 index 00000000000..6d576bbc4a5 --- /dev/null +++ b/test/image/mocks/sort_by_value_matching_axes.json @@ -0,0 +1,43 @@ +{ + "data": [{ + "type": "scatter", + "orientation": "v", + "x": ["a", "b", "c"], + "y": [7, 2, 3], + + "z": [ + [7, 2, 3], + [0, 0, 0], + [0, 0, 0] + ], + "dimensions": [{ + "label": "DimensionA", + "values": ["a", "b", "c"] + }, { + "label": "DimensionB", + "values": [7, 2, 3] + }] + }, { + "type": "bar", + "x": ["a", "b", "c"], + "y": [10, 20, 30], + "yaxis": "y2", + "xaxis": "x2" + + }], + "layout": { + "xaxis": { + "type": "category", + "categoryorder": "value ascending" + }, + "xaxis2": { + "matches": "x" + }, + "yaxis": { + "domain": [0, 0.45] + }, + "yaxis2": { + "domain": [0.55, 1] + } + } +} diff --git a/test/jasmine/tests/calcdata_test.js b/test/jasmine/tests/calcdata_test.js index 430bfc5e7ca..7284f1af457 100644 --- a/test/jasmine/tests/calcdata_test.js +++ b/test/jasmine/tests/calcdata_test.js @@ -3,6 +3,8 @@ var Plotly = require('@lib/index'); var BADNUM = require('@src/constants/numerical').BADNUM; var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var failTest = require('../assets/fail_test'); +var Lib = require('@src/lib'); describe('calculated data and points', function() { var gd; @@ -869,6 +871,216 @@ describe('calculated data and points', function() { 'b': 1 }); }); + + describe('by value', function() { + var schema = Plotly.PlotSchema.get(); + var traces = Object.keys(schema.traces); + var tracesSchema = []; + var i, j, k; + for(i = 0; i < traces.length; i++) { + tracesSchema.push(schema.traces[traces[i]]); + } + var cartesianTraces = tracesSchema.filter(function(t) { + return t.categories.length && t.categories.indexOf('cartesian') !== -1; + }); + + // excludedTraces are traces that do not support sorting by value + var excludedTraces = [ 'carpet', 'contourcarpet', + // TODO: add support for the following + 'histogram2dcontour']; + + var supportedCartesianTraces = cartesianTraces.filter(function(t) { + if(excludedTraces.indexOf(t.type) === -1) return true; + }); + + var cat = ['a', 'b', 'c']; + + // oneOrientationTraces are traces for which swapping x/y is not supported + var oneOrientationTraces = ['ohlc', 'candlestick']; + + function makeData(type, a, b, axId) { + var input = [a, b]; + var cat = input[axId === 'yaxis' ? 1 : 0]; + var data = input[axId === 'yaxis' ? 0 : 1]; + + var measure = []; + for(j = 0; j < data.length; j++) { + measure.push('absolute'); + } + + var z = Lib.init2dArray(cat.length, data.length); + for(j = 0; j < z.length; j++) { + for(k = 0; k < z[j].length; k++) { + z[j][k] = 0; + } + } + if(axId === 'xaxis') { + for(j = 0; j < b.length; j++) { + z[0][j] = b[j]; + } + } + if(axId === 'yaxis') { + for(j = 0; j < b.length; j++) { + z[j][0] = b[j]; + } + } + + return Lib.extendDeep({}, { + orientation: axId === 'yaxis' ? 'h' : 'v', + type: type, + x: cat, + a: cat, + + b: data, + y: data, + z: z, + + // For OHLC + open: data, + close: data, + high: data, + low: data, + + // For waterfall + measure: measure, + + // For splom + dimensions: [ + { + label: 'DimensionA', + values: a + }, + { + label: 'DimensionB', + values: b + } + ] + }); + } + + supportedCartesianTraces.forEach(function(trace) { + ['xaxis', 'yaxis'].forEach(function(axId) { + if(axId === 'yaxis' && oneOrientationTraces.indexOf(trace.type) !== -1) return; + ['value ascending', 'value descending'].forEach(function(categoryorder) { + it('sorts ' + axId + ' by ' + categoryorder + 'for trace type ' + trace.type, function(done) { + var data = [7, 2, 3]; + var baseMock = { data: [makeData(trace.type, cat, data, axId)], layout: {}}; + var mock = Lib.extendDeep({}, baseMock); + mock.layout[axId] = { type: 'category', categoryorder: categoryorder}; + + // Set ordering + var finalOrder = ['b', 'c', 'a']; + if(categoryorder === 'value descending') finalOrder.reverse(); + + if(trace.type.match(/histogram/)) { + mock.data[0][axId === 'yaxis' ? 'y' : 'x'].push('a'); + mock.data[0][axId === 'yaxis' ? 'x' : 'y'].push(7); + } + + Plotly.newPlot(gd, mock) + .then(function(gd) { + expect(gd._fullLayout[trace.type === 'splom' ? 'xaxis' : axId]._categories).toEqual(finalOrder, 'for trace ' + trace.type); + }) + .catch(failTest) + .then(done); + }); + }); + + function checkAggregatedValue(baseMock, expectedAgg, done) { + var mock = Lib.extendDeep({}, baseMock); + + if(mock.data[0].type.match(/histogram/)) { + for(i = 0; i < mock.data.length; i++) { + mock.data[i][axId === 'yaxis' ? 'y' : 'x'].push('a'); + mock.data[i][axId === 'yaxis' ? 'x' : 'y'].push(7); + } + } + + Plotly.newPlot(gd, mock) + .then(function(gd) { + var agg = gd._fullLayout[mock.data[0].type === 'splom' ? 'xaxis' : axId]._categoriesAggregatedValue.sort(function(a, b) { + return a[0] > b[0]; + }); + expect(agg).toEqual(expectedAgg, 'wrong aggregation for ' + axId); + }) + .catch(failTest) + .then(done); + } + + it('retrieves values in trace type ' + trace.type, function(done) { + var data = [7, 2, 3]; + var baseMock = {data: [makeData(trace.type, cat, data, axId)], layout: {}}; + baseMock.layout[axId] = { type: 'category', categoryorder: 'value ascending'}; + + var expectedAgg = [['a', 7], ['b', 2], ['c', 3]]; + if(trace.type === 'ohlc' || trace.type === 'candlestick') expectedAgg = [['a', 14], ['b', 4], ['c', 6]]; + if(trace.type.match(/histogram/)) expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; + + checkAggregatedValue(baseMock, expectedAgg, done); + }); + + it('sum values across traces of type ' + trace.type, function(done) { + var type = trace.type; + var data = [7, 2, 3]; + var data2 = [5, 4, 2]; + var baseMock = { data: [makeData(type, cat, data, axId), makeData(type, cat, data2, axId)], layout: {}}; + baseMock.layout[axId] = { type: 'category', categoryorder: 'value ascending'}; + + var expectedAgg = [['a', data[0] + data2[0]], ['b', data[1] + data2[1]], ['c', data[2] + data2[2]]]; + if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', 2 * expectedAgg[0][1]], ['b', 2 * expectedAgg[1][1]], ['c', 2 * expectedAgg[2][1]]]; + if(type.match(/histogram/)) expectedAgg = [['a', 4], ['b', 2], ['c', 2]]; + + checkAggregatedValue(baseMock, expectedAgg, done); + }); + + it('ignores values from traces that are not visible ' + trace.type, function(done) { + var type = trace.type; + var data = [7, 2, 3]; + var data2 = [5, 4, 2]; + var baseMock = { data: [makeData(type, cat, data, axId), makeData(type, cat, data2, axId)], layout: {}}; + baseMock.layout[axId] = { type: 'category', categoryorder: 'value ascending'}; + + // Hide second trace + baseMock.data[1].visible = 'legendonly'; + var expectedAgg = [['a', data[0]], ['b', data[1]], ['c', data[2]]]; + if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', 2 * expectedAgg[0][1]], ['b', 2 * expectedAgg[1][1]], ['c', 2 * expectedAgg[2][1]]]; + if(type.match(/histogram/)) expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; + + checkAggregatedValue(baseMock, expectedAgg, done); + }); + + it('finds the minimum value per category across traces of type ' + trace.type, function(done) { + var type = trace.type; + var data = [7, 2, 3]; + var data2 = [5, 4, 2]; + var baseMock = { data: [makeData(type, cat, data, axId), makeData(type, cat, data2, axId)], layout: {}}; + baseMock.layout[axId] = { type: 'category', categoryorder: 'min ascending'}; + + var expectedAgg = [['a', Math.min(data[0], data2[0])], ['b', Math.min(data[1], data2[1])], ['c', Math.min(data[2], data2[2])]]; + // if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', expectedAgg[0][1]], ['b', expectedAgg[1][1]], ['c', expectedAgg[2][1]]]; + if(trace.categories.indexOf('2dMap') !== -1) expectedAgg = [['a', 0], ['b', 0], ['c', 0]]; + if(type === 'histogram') expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; + + checkAggregatedValue(baseMock, expectedAgg, done); + }); + + it('finds the maximum value per category across traces of type ' + trace.type, function(done) { + var type = trace.type; + var data = [7, 2, 3]; + var data2 = [5, 4, 2]; + var baseMock = { data: [makeData(type, cat, data, axId), makeData(type, cat, data2, axId)], layout: {}}; + baseMock.layout[axId] = { type: 'category', categoryorder: 'max ascending'}; + + var expectedAgg = [['a', Math.max(data[0], data2[0])], ['b', Math.max(data[1], data2[1])], ['c', Math.max(data[2], data2[2])]]; + if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', expectedAgg[0][1]], ['b', expectedAgg[1][1]], ['c', expectedAgg[2][1]]]; + // if(trace.categories.indexOf('2dMap') !== -1) expectedAgg = [['a', 0], ['b', 0], ['c', 0]]; + if(type.match(/histogram/)) expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; + + checkAggregatedValue(baseMock, expectedAgg, done); + }); + }); + }); + }); }); describe('customdata', function() { From 2d379f7647bec909b806f56276b6f6225578bc6b Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 15 May 2019 13:49:57 -0400 Subject: [PATCH 02/13] sort categories by values: improve code style --- src/plots/plots.js | 21 ++-- src/traces/bar/calc.js | 2 +- .../mocks/sort_by_value_matching_axes.json | 14 --- test/jasmine/tests/calcdata_test.js | 102 ++++++++---------- 4 files changed, 55 insertions(+), 84 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index dd7695d9577..0d9a70e15b2 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2877,13 +2877,14 @@ function sortAxisCategoriesByValue(axList, gd) { // Collect values across traces for(j = 0; j < ax._traceIndices.length; j++) { var traceIndex = ax._traceIndices[j]; - var fullData = gd._fullData[traceIndex]; + var fullTrace = gd._fullData[traceIndex]; + var axLetter = ax._id.charAt(0); // Skip over invisible traces - if(fullData.visible !== true) continue; + if(fullTrace.visible !== true) continue; - var type = fullData.type; - if(type === 'histogram') delete fullData._autoBinFinished; + var type = fullTrace.type; + if(Registry.traceIs(fullTrace, 'histogram')) delete fullTrace._autoBinFinished; var cd = gd.calcdata[traceIndex]; for(k = 0; k < cd.length; k++) { @@ -2893,10 +2894,10 @@ function sortAxisCategoriesByValue(axList, gd) { // If `splom`, collect values across dimensions if(type === 'splom') { // Find which dimension the current axis is representing - var currentDimensionIndex = cdi.trace[ax._id.charAt(0) + 'axes'].indexOf(ax._id); + var currentDimensionIndex = cdi.trace[axLetter + 'axes'].indexOf(ax._id); // Apply logic to associated x axis - if(ax._id.charAt(0) === 'y') { + if(axLetter === 'y') { var associatedXAxis = ax._id.split(''); associatedXAxis[0] = 'x'; associatedXAxis = associatedXAxis.join(''); @@ -2918,13 +2919,13 @@ function sortAxisCategoriesByValue(axList, gd) { // If `scattergl`, collect all values stashed under cdi.t } else if(type === 'scattergl') { for(l = 0; l < cdi.t.x.length; l++) { - if(ax._id.charAt(0) === 'x') { + if(axLetter === 'x') { cat = cdi.t.x[l]; catIndex = cat; value = cdi.t.y[l]; } - if(ax._id.charAt(0) === 'y') { + if(axLetter === 'y') { cat = cdi.t.y[l]; catIndex = cat; value = cdi.t.x[l]; @@ -2938,10 +2939,10 @@ function sortAxisCategoriesByValue(axList, gd) { } // For all other 2d cartesian traces } else { - if(ax._id.charAt(0) === 'x') { + if(axLetter === 'x') { cat = cdi.p + 1 ? cdi.p : cdi.x; value = cdi.s || cdi.v || cdi.y; - } else if(ax._id.charAt(0) === 'y') { + } else if(axLetter === 'y') { cat = cdi.p + 1 ? cdi.p : cdi.y; value = cdi.s || cdi.v || cdi.x; } diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index 4faceb558da..41af79b7b1c 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -33,7 +33,7 @@ module.exports = function calc(gd, trace) { // set position and size for(var i = 0; i < serieslen; i++) { - cd[i] = { p: pos[i], s: size[i], v: size[i] }; + cd[i] = { p: pos[i], s: size[i] }; if(trace.ids) { cd[i].id = String(trace.ids[i]); diff --git a/test/image/mocks/sort_by_value_matching_axes.json b/test/image/mocks/sort_by_value_matching_axes.json index 6d576bbc4a5..8defb75689f 100644 --- a/test/image/mocks/sort_by_value_matching_axes.json +++ b/test/image/mocks/sort_by_value_matching_axes.json @@ -4,26 +4,12 @@ "orientation": "v", "x": ["a", "b", "c"], "y": [7, 2, 3], - - "z": [ - [7, 2, 3], - [0, 0, 0], - [0, 0, 0] - ], - "dimensions": [{ - "label": "DimensionA", - "values": ["a", "b", "c"] - }, { - "label": "DimensionB", - "values": [7, 2, 3] - }] }, { "type": "bar", "x": ["a", "b", "c"], "y": [10, 20, 30], "yaxis": "y2", "xaxis": "x2" - }], "layout": { "xaxis": { diff --git a/test/jasmine/tests/calcdata_test.js b/test/jasmine/tests/calcdata_test.js index 7284f1af457..fdcd85aa067 100644 --- a/test/jasmine/tests/calcdata_test.js +++ b/test/jasmine/tests/calcdata_test.js @@ -898,10 +898,10 @@ describe('calculated data and points', function() { // oneOrientationTraces are traces for which swapping x/y is not supported var oneOrientationTraces = ['ohlc', 'candlestick']; - function makeData(type, a, b, axId) { + function makeData(type, a, b, axName) { var input = [a, b]; - var cat = input[axId === 'yaxis' ? 1 : 0]; - var data = input[axId === 'yaxis' ? 0 : 1]; + var cat = input[axName === 'yaxis' ? 1 : 0]; + var data = input[axName === 'yaxis' ? 0 : 1]; var measure = []; for(j = 0; j < data.length; j++) { @@ -914,19 +914,19 @@ describe('calculated data and points', function() { z[j][k] = 0; } } - if(axId === 'xaxis') { + if(axName === 'xaxis') { for(j = 0; j < b.length; j++) { z[0][j] = b[j]; } } - if(axId === 'yaxis') { + if(axName === 'yaxis') { for(j = 0; j < b.length; j++) { z[j][0] = b[j]; } } return Lib.extendDeep({}, { - orientation: axId === 'yaxis' ? 'h' : 'v', + orientation: axName === 'yaxis' ? 'h' : 'v', type: type, x: cat, a: cat, @@ -959,86 +959,72 @@ describe('calculated data and points', function() { } supportedCartesianTraces.forEach(function(trace) { - ['xaxis', 'yaxis'].forEach(function(axId) { - if(axId === 'yaxis' && oneOrientationTraces.indexOf(trace.type) !== -1) return; - ['value ascending', 'value descending'].forEach(function(categoryorder) { - it('sorts ' + axId + ' by ' + categoryorder + 'for trace type ' + trace.type, function(done) { - var data = [7, 2, 3]; - var baseMock = { data: [makeData(trace.type, cat, data, axId)], layout: {}}; - var mock = Lib.extendDeep({}, baseMock); - mock.layout[axId] = { type: 'category', categoryorder: categoryorder}; - - // Set ordering - var finalOrder = ['b', 'c', 'a']; - if(categoryorder === 'value descending') finalOrder.reverse(); - - if(trace.type.match(/histogram/)) { - mock.data[0][axId === 'yaxis' ? 'y' : 'x'].push('a'); - mock.data[0][axId === 'yaxis' ? 'x' : 'y'].push(7); - } - - Plotly.newPlot(gd, mock) - .then(function(gd) { - expect(gd._fullLayout[trace.type === 'splom' ? 'xaxis' : axId]._categories).toEqual(finalOrder, 'for trace ' + trace.type); - }) - .catch(failTest) - .then(done); - }); - }); + ['xaxis', 'yaxis'].forEach(function(axName) { + if(axName === 'yaxis' && oneOrientationTraces.indexOf(trace.type) !== -1) return; - function checkAggregatedValue(baseMock, expectedAgg, done) { + function checkAggregatedValue(baseMock, expectedAgg, finalOrder, done) { var mock = Lib.extendDeep({}, baseMock); if(mock.data[0].type.match(/histogram/)) { for(i = 0; i < mock.data.length; i++) { - mock.data[i][axId === 'yaxis' ? 'y' : 'x'].push('a'); - mock.data[i][axId === 'yaxis' ? 'x' : 'y'].push(7); + mock.data[i][axName === 'yaxis' ? 'y' : 'x'].push('a'); + mock.data[i][axName === 'yaxis' ? 'x' : 'y'].push(7); } } Plotly.newPlot(gd, mock) .then(function(gd) { - var agg = gd._fullLayout[mock.data[0].type === 'splom' ? 'xaxis' : axId]._categoriesAggregatedValue.sort(function(a, b) { + var agg = gd._fullLayout[trace.type === 'splom' ? 'xaxis' : axName]._categoriesAggregatedValue.sort(function(a, b) { return a[0] > b[0]; }); - expect(agg).toEqual(expectedAgg, 'wrong aggregation for ' + axId); + expect(agg).toEqual(expectedAgg, 'wrong aggregation for ' + axName); + + if(finalOrder) { + expect(gd._fullLayout[trace.type === 'splom' ? 'xaxis' : axName]._categories).toEqual(finalOrder, 'for trace ' + trace.type); + } }) .catch(failTest) .then(done); } - it('retrieves values in trace type ' + trace.type, function(done) { - var data = [7, 2, 3]; - var baseMock = {data: [makeData(trace.type, cat, data, axId)], layout: {}}; - baseMock.layout[axId] = { type: 'category', categoryorder: 'value ascending'}; + ['value ascending', 'value descending'].forEach(function(categoryorder) { + it('sorts ' + axName + ' by ' + categoryorder + ' for trace type ' + trace.type, function(done) { + var data = [7, 2, 3]; + var baseMock = {data: [makeData(trace.type, cat, data, axName)], layout: {}}; + baseMock.layout[axName] = { type: 'category', categoryorder: categoryorder}; - var expectedAgg = [['a', 7], ['b', 2], ['c', 3]]; - if(trace.type === 'ohlc' || trace.type === 'candlestick') expectedAgg = [['a', 14], ['b', 4], ['c', 6]]; - if(trace.type.match(/histogram/)) expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; + // Set expectations + var finalOrder = ['b', 'c', 'a']; + if(categoryorder === 'value descending') finalOrder.reverse(); + var expectedAgg = [['a', 7], ['b', 2], ['c', 3]]; - checkAggregatedValue(baseMock, expectedAgg, done); + if(trace.type === 'ohlc' || trace.type === 'candlestick') expectedAgg = [['a', 14], ['b', 4], ['c', 6]]; + if(trace.type.match(/histogram/)) expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; + + checkAggregatedValue(baseMock, expectedAgg, finalOrder, done); + }); }); it('sum values across traces of type ' + trace.type, function(done) { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; - var baseMock = { data: [makeData(type, cat, data, axId), makeData(type, cat, data2, axId)], layout: {}}; - baseMock.layout[axId] = { type: 'category', categoryorder: 'value ascending'}; + var baseMock = { data: [makeData(type, cat, data, axName), makeData(type, cat, data2, axName)], layout: {}}; + baseMock.layout[axName] = { type: 'category', categoryorder: 'value ascending'}; var expectedAgg = [['a', data[0] + data2[0]], ['b', data[1] + data2[1]], ['c', data[2] + data2[2]]]; if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', 2 * expectedAgg[0][1]], ['b', 2 * expectedAgg[1][1]], ['c', 2 * expectedAgg[2][1]]]; if(type.match(/histogram/)) expectedAgg = [['a', 4], ['b', 2], ['c', 2]]; - checkAggregatedValue(baseMock, expectedAgg, done); + checkAggregatedValue(baseMock, expectedAgg, false, done); }); it('ignores values from traces that are not visible ' + trace.type, function(done) { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; - var baseMock = { data: [makeData(type, cat, data, axId), makeData(type, cat, data2, axId)], layout: {}}; - baseMock.layout[axId] = { type: 'category', categoryorder: 'value ascending'}; + var baseMock = { data: [makeData(type, cat, data, axName), makeData(type, cat, data2, axName)], layout: {}}; + baseMock.layout[axName] = { type: 'category', categoryorder: 'value ascending'}; // Hide second trace baseMock.data[1].visible = 'legendonly'; @@ -1046,37 +1032,35 @@ describe('calculated data and points', function() { if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', 2 * expectedAgg[0][1]], ['b', 2 * expectedAgg[1][1]], ['c', 2 * expectedAgg[2][1]]]; if(type.match(/histogram/)) expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; - checkAggregatedValue(baseMock, expectedAgg, done); + checkAggregatedValue(baseMock, expectedAgg, false, done); }); it('finds the minimum value per category across traces of type ' + trace.type, function(done) { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; - var baseMock = { data: [makeData(type, cat, data, axId), makeData(type, cat, data2, axId)], layout: {}}; - baseMock.layout[axId] = { type: 'category', categoryorder: 'min ascending'}; + var baseMock = { data: [makeData(type, cat, data, axName), makeData(type, cat, data2, axName)], layout: {}}; + baseMock.layout[axName] = { type: 'category', categoryorder: 'min ascending'}; var expectedAgg = [['a', Math.min(data[0], data2[0])], ['b', Math.min(data[1], data2[1])], ['c', Math.min(data[2], data2[2])]]; - // if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', expectedAgg[0][1]], ['b', expectedAgg[1][1]], ['c', expectedAgg[2][1]]]; if(trace.categories.indexOf('2dMap') !== -1) expectedAgg = [['a', 0], ['b', 0], ['c', 0]]; if(type === 'histogram') expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; - checkAggregatedValue(baseMock, expectedAgg, done); + checkAggregatedValue(baseMock, expectedAgg, false, done); }); it('finds the maximum value per category across traces of type ' + trace.type, function(done) { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; - var baseMock = { data: [makeData(type, cat, data, axId), makeData(type, cat, data2, axId)], layout: {}}; - baseMock.layout[axId] = { type: 'category', categoryorder: 'max ascending'}; + var baseMock = { data: [makeData(type, cat, data, axName), makeData(type, cat, data2, axName)], layout: {}}; + baseMock.layout[axName] = { type: 'category', categoryorder: 'max ascending'}; var expectedAgg = [['a', Math.max(data[0], data2[0])], ['b', Math.max(data[1], data2[1])], ['c', Math.max(data[2], data2[2])]]; if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', expectedAgg[0][1]], ['b', expectedAgg[1][1]], ['c', expectedAgg[2][1]]]; - // if(trace.categories.indexOf('2dMap') !== -1) expectedAgg = [['a', 0], ['b', 0], ['c', 0]]; if(type.match(/histogram/)) expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; - checkAggregatedValue(baseMock, expectedAgg, done); + checkAggregatedValue(baseMock, expectedAgg, false, done); }); }); }); From e4e5eaa447cbbce14d550ddf0928b3d054e88e7d Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 15 May 2019 14:05:05 -0400 Subject: [PATCH 03/13] sort categories by values: fix mock syntax --- test/image/mocks/sort_by_value_matching_axes.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/image/mocks/sort_by_value_matching_axes.json b/test/image/mocks/sort_by_value_matching_axes.json index 8defb75689f..5855faf8e63 100644 --- a/test/image/mocks/sort_by_value_matching_axes.json +++ b/test/image/mocks/sort_by_value_matching_axes.json @@ -3,7 +3,7 @@ "type": "scatter", "orientation": "v", "x": ["a", "b", "c"], - "y": [7, 2, 3], + "y": [7, 2, 3] }, { "type": "bar", "x": ["a", "b", "c"], From 76683baa75d18ac65ae8d394d67821f7e77c1d8f Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 15 May 2019 23:22:26 -0400 Subject: [PATCH 04/13] sort categories by values: add support for histogram2dcontour --- src/plots/plots.js | 30 +++++++++++++++++++++++++++-- test/jasmine/tests/calcdata_test.js | 8 +++----- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 0d9a70e15b2..49fdfd13e77 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2861,6 +2861,31 @@ var sortAxisCategoriesByValueRegex = /(value|sum|min|max) (ascending|descending) function sortAxisCategoriesByValue(axList, gd) { var affectedTraces = []; var i, j, k, l, o; + + function zMapCategory(type, ax, value) { + var axLetter = ax._id.charAt(0); + if(type === 'histogram2dcontour') { + var counterAxLetter = ax._counterAxes[0]; + var counterAx = axisIDs.getFromId(gd, counterAxLetter); + + var xCategorical = axLetter === 'x' || (counterAxLetter === 'x' && counterAx.type === 'category'); + var yCategorical = axLetter === 'y' || (counterAxLetter === 'y' && counterAx.type === 'category'); + + return function(o, l) { + if(o === 0 || l === 0) return -1; // Skip first row and column + if(xCategorical && o === value[l].length - 1) return -1; + if(yCategorical && l === value.length - 1) return -1; + + var catIndex = axLetter === 'y' ? l : o; + return catIndex - 1; + }; + } else { + return function(o, l) { + return axLetter === 'y' ? l : o; + }; + } + } + for(i = 0; i < axList.length; i++) { var ax = axList[i]; if(ax.type !== 'category') continue; @@ -2950,11 +2975,12 @@ function sortAxisCategoriesByValue(axList, gd) { // If 2dMap, collect values in `z` if(cdi.hasOwnProperty('z')) { value = cdi.z; + var mapping = zMapCategory(fullTrace.type, ax, value); for(l = 0; l < value.length; l++) { for(o = 0; o < value[l].length; o++) { - catIndex = ax._id.charAt(0) === 'y' ? l : o; - categoriesValue[catIndex][1].push(value[l][o]); + catIndex = mapping(o, l); + if(catIndex + 1) categoriesValue[catIndex][1].push(value[l][o]); } } } else { diff --git a/test/jasmine/tests/calcdata_test.js b/test/jasmine/tests/calcdata_test.js index fdcd85aa067..eda58303c55 100644 --- a/test/jasmine/tests/calcdata_test.js +++ b/test/jasmine/tests/calcdata_test.js @@ -885,9 +885,7 @@ describe('calculated data and points', function() { }); // excludedTraces are traces that do not support sorting by value - var excludedTraces = [ 'carpet', 'contourcarpet', - // TODO: add support for the following - 'histogram2dcontour']; + var excludedTraces = [ 'carpet', 'contourcarpet']; var supportedCartesianTraces = cartesianTraces.filter(function(t) { if(excludedTraces.indexOf(t.type) === -1) return true; @@ -975,12 +973,12 @@ describe('calculated data and points', function() { Plotly.newPlot(gd, mock) .then(function(gd) { var agg = gd._fullLayout[trace.type === 'splom' ? 'xaxis' : axName]._categoriesAggregatedValue.sort(function(a, b) { - return a[0] > b[0]; + return a[0] > b[0] ? 1 : -1; }); expect(agg).toEqual(expectedAgg, 'wrong aggregation for ' + axName); if(finalOrder) { - expect(gd._fullLayout[trace.type === 'splom' ? 'xaxis' : axName]._categories).toEqual(finalOrder, 'for trace ' + trace.type); + expect(gd._fullLayout[trace.type === 'splom' ? 'xaxis' : axName]._categories).toEqual(finalOrder, 'wrong order'); } }) .catch(failTest) From fac239b7b8db0ac7d9f68850c75a9bca1c03a4c6 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 16 May 2019 13:28:32 -0400 Subject: [PATCH 05/13] sort categories by values: improve splom logic --- src/plots/cartesian/type_defaults.js | 10 ++-------- src/plots/plots.js | 10 ++++------ src/traces/splom/defaults.js | 3 +++ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/plots/cartesian/type_defaults.js b/src/plots/cartesian/type_defaults.js index 16237397bf8..5edf2a37365 100644 --- a/src/plots/cartesian/type_defaults.js +++ b/src/plots/cartesian/type_defaults.js @@ -80,14 +80,8 @@ function setAutoType(ax, data) { ax.type = autoType(boxPositions, calendar, opts); } 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 && (diag[i][0] === id || diag[i][1] === id)) { - ax.type = autoType(dim.values, calendar, opts); - break; - } - } + var dim = dimensions[d0._axesDim[id]]; + if(dim.visible) ax.type = autoType(dim.values, calendar, opts); } else { ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']], calendar, opts); } diff --git a/src/plots/plots.js b/src/plots/plots.js index 49fdfd13e77..12941978bf7 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2919,14 +2919,12 @@ function sortAxisCategoriesByValue(axList, gd) { // If `splom`, collect values across dimensions if(type === 'splom') { // Find which dimension the current axis is representing - var currentDimensionIndex = cdi.trace[axLetter + 'axes'].indexOf(ax._id); + var currentDimensionIndex = fullTrace._axesDim[ax._id]; // Apply logic to associated x axis if(axLetter === 'y') { - var associatedXAxis = ax._id.split(''); - associatedXAxis[0] = 'x'; - associatedXAxis = associatedXAxis.join(''); - ax = gd._fullLayout[axisIDs.id2name(associatedXAxis)]; + var associatedXAxisID = fullTrace._diag[currentDimensionIndex][0]; + ax = gd._fullLayout[axisIDs.id2name(associatedXAxisID)]; } var categories = cdi.trace.dimensions[currentDimensionIndex].values; @@ -2934,7 +2932,7 @@ function sortAxisCategoriesByValue(axList, gd) { cat = categories[l]; catIndex = ax._categoriesMap[cat]; - // Collect values over all other dimensions + // Collect associated values at index `l` over all other dimensions for(o = 0; o < cdi.trace.dimensions.length; o++) { if(o === currentDimensionIndex) continue; var dimension = cdi.trace.dimensions[o]; diff --git a/src/traces/splom/defaults.js b/src/traces/splom/defaults.js index 9e7846d99e7..aba52b88b75 100644 --- a/src/traces/splom/defaults.js +++ b/src/traces/splom/defaults.js @@ -127,6 +127,7 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { var mustShiftX = !showDiag && !showLower; var mustShiftY = !showDiag && !showUpper; + traceOut._axesDim = {}; for(i = 0; i < dimLength; i++) { var dim = dimensions[i]; var i0 = i === 0; @@ -143,6 +144,8 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { fillAxisStashes(xaId, yaId, dim, xList); fillAxisStashes(yaId, xaId, dim, yList); diag[i] = [xaId, yaId]; + traceOut._axesDim[xaId] = i; + traceOut._axesDim[yaId] = i; } // fill in splom subplot keys From 67cc14de291567ab282ca4e5b8c4e1608dbce4b2 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 16 May 2019 16:45:50 -0400 Subject: [PATCH 06/13] sort categories by values: implement mean and median --- src/plots/cartesian/layout_attributes.js | 6 ++- src/plots/plots.js | 34 ++++++++--------- test/jasmine/tests/calcdata_test.js | 48 ++++++++++++++++++++---- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 38d3a67f333..a48e2024f85 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -821,7 +821,9 @@ module.exports = { 'value ascending', 'value descending', 'min ascending', 'min descending', 'max ascending', 'max descending', - 'sum ascending', 'sum descending' + 'sum ascending', 'sum descending', + 'mean ascending', 'mean descending', + 'median ascending', 'median descending' ], dflt: 'trace', role: 'info', @@ -836,7 +838,7 @@ module.exports = { 'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.', 'Set `categoryorder` to *value ascending* or *value descending* if order should be determined by the', 'numerical order of the values.', - 'Similarly, the order can be determined by the min, max or the sums of the values.' + 'Similarly, the order can be determined by the min, max, sum, mean or media of all the values.' ].join(' ') }, categoryarray: { diff --git a/src/plots/plots.js b/src/plots/plots.js index 12941978bf7..51e943aa14f 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2856,7 +2856,7 @@ plots.doCalcdata = function(gd, traces) { Registry.getComponentMethod('errorbars', 'calc')(gd); }; -var sortAxisCategoriesByValueRegex = /(value|sum|min|max) (ascending|descending)/; +var sortAxisCategoriesByValueRegex = /(value|sum|min|max|mean|median) (ascending|descending)/; function sortAxisCategoriesByValue(axList, gd) { var affectedTraces = []; @@ -2876,8 +2876,7 @@ function sortAxisCategoriesByValue(axList, gd) { if(xCategorical && o === value[l].length - 1) return -1; if(yCategorical && l === value.length - 1) return -1; - var catIndex = axLetter === 'y' ? l : o; - return catIndex - 1; + return (axLetter === 'y' ? l : o) - 1; }; } else { return function(o, l) { @@ -2886,6 +2885,15 @@ function sortAxisCategoriesByValue(axList, gd) { } } + var aggFn = { + 'min': function(values) {return Lib.aggNums(Math.min, null, values);}, + 'max': function(values) {return Lib.aggNums(Math.max, null, values);}, + 'sum': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);}, + 'value': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);}, + 'mean': function(values) {return Lib.mean(values);}, + 'median': function(values) {values.sort(); var mid = Math.round((values.length - 1) / 2); return values[mid];} + }; + for(i = 0; i < axList.length; i++) { var ax = axList[i]; if(ax.type !== 'category') continue; @@ -2893,6 +2901,9 @@ function sortAxisCategoriesByValue(axList, gd) { // Order by value var match = ax.categoryorder.match(sortAxisCategoriesByValueRegex); if(match) { + var aggregator = match[1]; + var order = match[2]; + // Store values associated with each category var categoriesValue = []; for(j = 0; j < ax._categories.length; j++) { @@ -2991,26 +3002,13 @@ function sortAxisCategoriesByValue(axList, gd) { } } - // Aggregate values - var aggFn; - switch(match[1]) { - case 'min': - aggFn = Math.min; - break; - case 'max': - aggFn = Math.max; - break; - default: - aggFn = function(a, b) { return a + b;}; - } - ax._categoriesValue = categoriesValue; var categoriesAggregatedValue = []; for(j = 0; j < categoriesValue.length; j++) { categoriesAggregatedValue.push([ categoriesValue[j][0], - Lib.aggNums(aggFn, null, categoriesValue[j][1]) + aggFn[aggregator](categoriesValue[j][1]) ]); } @@ -3027,7 +3025,7 @@ function sortAxisCategoriesByValue(axList, gd) { }); // Reverse if descending - if(match[2] === 'descending') { + if(order === 'descending') { ax._initialCategories.reverse(); } diff --git a/test/jasmine/tests/calcdata_test.js b/test/jasmine/tests/calcdata_test.js index eda58303c55..4581af9daeb 100644 --- a/test/jasmine/tests/calcdata_test.js +++ b/test/jasmine/tests/calcdata_test.js @@ -896,7 +896,7 @@ describe('calculated data and points', function() { // oneOrientationTraces are traces for which swapping x/y is not supported var oneOrientationTraces = ['ohlc', 'candlestick']; - function makeData(type, a, b, axName) { + function makeData(type, axName, a, b) { var input = [a, b]; var cat = input[axName === 'yaxis' ? 1 : 0]; var data = input[axName === 'yaxis' ? 0 : 1]; @@ -939,6 +939,10 @@ describe('calculated data and points', function() { high: data, low: data, + // For histogram + nbinsx: cat.length, + nbinsy: data.length, + // For waterfall measure: measure, @@ -988,7 +992,7 @@ describe('calculated data and points', function() { ['value ascending', 'value descending'].forEach(function(categoryorder) { it('sorts ' + axName + ' by ' + categoryorder + ' for trace type ' + trace.type, function(done) { var data = [7, 2, 3]; - var baseMock = {data: [makeData(trace.type, cat, data, axName)], layout: {}}; + var baseMock = {data: [makeData(trace.type, axName, cat, data)], layout: {}}; baseMock.layout[axName] = { type: 'category', categoryorder: categoryorder}; // Set expectations @@ -1007,7 +1011,7 @@ describe('calculated data and points', function() { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; - var baseMock = { data: [makeData(type, cat, data, axName), makeData(type, cat, data2, axName)], layout: {}}; + var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2)], layout: {}}; baseMock.layout[axName] = { type: 'category', categoryorder: 'value ascending'}; var expectedAgg = [['a', data[0] + data2[0]], ['b', data[1] + data2[1]], ['c', data[2] + data2[2]]]; @@ -1021,7 +1025,7 @@ describe('calculated data and points', function() { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; - var baseMock = { data: [makeData(type, cat, data, axName), makeData(type, cat, data2, axName)], layout: {}}; + var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2)], layout: {}}; baseMock.layout[axName] = { type: 'category', categoryorder: 'value ascending'}; // Hide second trace @@ -1037,7 +1041,7 @@ describe('calculated data and points', function() { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; - var baseMock = { data: [makeData(type, cat, data, axName), makeData(type, cat, data2, axName)], layout: {}}; + var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2)], layout: {}}; baseMock.layout[axName] = { type: 'category', categoryorder: 'min ascending'}; var expectedAgg = [['a', Math.min(data[0], data2[0])], ['b', Math.min(data[1], data2[1])], ['c', Math.min(data[2], data2[2])]]; @@ -1051,15 +1055,45 @@ describe('calculated data and points', function() { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; - var baseMock = { data: [makeData(type, cat, data, axName), makeData(type, cat, data2, axName)], layout: {}}; + var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2)], layout: {}}; baseMock.layout[axName] = { type: 'category', categoryorder: 'max ascending'}; var expectedAgg = [['a', Math.max(data[0], data2[0])], ['b', Math.max(data[1], data2[1])], ['c', Math.max(data[2], data2[2])]]; - if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', expectedAgg[0][1]], ['b', expectedAgg[1][1]], ['c', expectedAgg[2][1]]]; if(type.match(/histogram/)) expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; checkAggregatedValue(baseMock, expectedAgg, false, done); }); + + it('take the mean of all values per category across traces of type ' + trace.type, function(done) { + var type = trace.type; + var data = [7, 2, 3]; + var data2 = [5, 4, 2]; + var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2)], layout: {}}; + baseMock.layout[axName] = { type: 'category', categoryorder: 'mean ascending'}; + + var expectedAgg = [['a', (data[0] + data2[0]) / 2 ], ['b', (data[1] + data2[1]) / 2], ['c', (data[2] + data2[2]) / 2]]; + if(type === 'histogram') expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; + if(type === 'histogram2d') expectedAgg = [['a', 2 / 3], ['b', 1 / 3], ['c', 1 / 3]]; + if(type === 'contour' || type === 'heatmap') expectedAgg = [['a', expectedAgg[0][1] / 3], ['b', expectedAgg[1][1] / 3], ['c', expectedAgg[2][1] / 3]]; + if(type === 'histogram2dcontour') expectedAgg = [['a', 2 / 4], ['b', 1 / 4], ['c', 1 / 4]]; // TODO: this result is inintuitive + + checkAggregatedValue(baseMock, expectedAgg, false, done); + }); + + it('take the median of all values per category across traces of type ' + trace.type, function(done) { + var type = trace.type; + var data = [7, 2, 3]; + var data2 = [5, 4, 2]; + var data3 = [6, 5, 7]; + var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2), makeData(type, axName, cat, data3)], layout: {}}; + baseMock.layout[axName] = { type: 'category', categoryorder: 'median ascending'}; + + var expectedAgg = [['a', 6], ['b', 4], ['c', 3]]; + if(type === 'histogram') expectedAgg = [['a', 2], ['b', 1], ['c', 1]]; + if(type === 'histogram2d') expectedAgg = [['a', 1], ['b', 0], ['c', 0]]; + if(type === 'histogram2dcontour' || type === 'contour' || type === 'heatmap') expectedAgg = [['a', 0], ['b', 0], ['c', 0]]; + checkAggregatedValue(baseMock, expectedAgg, false, done); + }); }); }); }); From c6e049b5f357771d12c776cdfbc3afd6f788199b Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 16 May 2019 18:26:17 -0400 Subject: [PATCH 07/13] implement Lib.median() --- src/lib/index.js | 1 + src/lib/stats.js | 16 ++++++++++++++++ test/jasmine/tests/lib_test.js | 14 ++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/src/lib/index.js b/src/lib/index.js index ed572a0cdcc..13cf3c86eb2 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -75,6 +75,7 @@ var statsModule = require('./stats'); lib.aggNums = statsModule.aggNums; lib.len = statsModule.len; lib.mean = statsModule.mean; +lib.median = statsModule.median; lib.midRange = statsModule.midRange; lib.variance = statsModule.variance; lib.stdev = statsModule.stdev; diff --git a/src/lib/stats.js b/src/lib/stats.js index 552b5bfd9b0..b697f559655 100644 --- a/src/lib/stats.js +++ b/src/lib/stats.js @@ -74,6 +74,22 @@ exports.stdev = function(data, len, mean) { return Math.sqrt(exports.variance(data, len, mean)); }; +/** + * median of a finite set of numbers + * reference page: https://en.wikipedia.org/wiki/Median#Finite_set_of_numbers +**/ +exports.median = function(data, len) { + if(!len) len = exports.len(data); + var b = data.slice().sort(); + if(len % 2 === 0) { + // If even + return (b[len / 2 - 1] + b[len / 2]) / 2; + } else { + // If odd + return b[(len - 1) / 2]; + } +}; + /** * interp() computes a percentile (quantile) for a given distribution. * We interpolate the distribution (to compute quantiles, we follow method #10 here: diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index cdfe16bf1e1..be73552baa1 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -176,6 +176,20 @@ describe('Test lib.js:', function() { }); }); + describe('median() should', function() { + it('return the middle value exactly for odd number of observations:', function() { + var input = [1, 3, 3, 6, 7, 8, 9]; + var res = Lib.median(input); + expect(res).toEqual(6); + }); + + it('return the mean of the two middle values for even number of observations', function() { + var input = [1, 2, 3, 4, 5, 6, 8, 9]; + var res = Lib.median(input); + expect(res).toEqual(4.5); + }); + }); + describe('stdev() should', function() { it('return 0 on input [2, 2, 2, 2, 2]:', function() { var input = [2, 2, 2, 2]; From 7c8d52b3f5da978f1795ecaff9ecb5dc611f64d4 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 16 May 2019 18:28:42 -0400 Subject: [PATCH 08/13] sort categories by values: use Lib.median() --- src/plots/plots.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 51e943aa14f..353a64b452d 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2891,7 +2891,7 @@ function sortAxisCategoriesByValue(axList, gd) { 'sum': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);}, 'value': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);}, 'mean': function(values) {return Lib.mean(values);}, - 'median': function(values) {values.sort(); var mid = Math.round((values.length - 1) / 2); return values[mid];} + 'median': function(values) {return Lib.median(values);} }; for(i = 0; i < axList.length; i++) { From 54b3ff93d95eef21660e66c49cde1f50b59b31a2 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Fri, 17 May 2019 11:15:07 -0400 Subject: [PATCH 09/13] Lib.median(): reuse existing Lib.interp() --- src/lib/stats.js | 11 ++--------- test/jasmine/tests/lib_test.js | 4 ++-- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/lib/stats.js b/src/lib/stats.js index b697f559655..a646aa0c1d5 100644 --- a/src/lib/stats.js +++ b/src/lib/stats.js @@ -78,16 +78,9 @@ exports.stdev = function(data, len, mean) { * median of a finite set of numbers * reference page: https://en.wikipedia.org/wiki/Median#Finite_set_of_numbers **/ -exports.median = function(data, len) { - if(!len) len = exports.len(data); +exports.median = function(data) { var b = data.slice().sort(); - if(len % 2 === 0) { - // If even - return (b[len / 2 - 1] + b[len / 2]) / 2; - } else { - // If odd - return b[(len - 1) / 2]; - } + return exports.interp(b, 0.5); }; /** diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index be73552baa1..38dc37927a0 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -178,13 +178,13 @@ describe('Test lib.js:', function() { describe('median() should', function() { it('return the middle value exactly for odd number of observations:', function() { - var input = [1, 3, 3, 6, 7, 8, 9]; + var input = [1, 8, 9, 2, 7, 6, 3]; var res = Lib.median(input); expect(res).toEqual(6); }); it('return the mean of the two middle values for even number of observations', function() { - var input = [1, 2, 3, 4, 5, 6, 8, 9]; + var input = [4, 3, 2, 1, 5, 6, 8, 9]; var res = Lib.median(input); expect(res).toEqual(4.5); }); From 9aaa10b59a62cd4595f665ec2860839384d4e047 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Fri, 17 May 2019 11:16:49 -0400 Subject: [PATCH 10/13] categoryorder: rename 'value' to 'total' --- src/plots/cartesian/layout_attributes.js | 6 +++--- src/plots/plots.js | 4 ++-- test/jasmine/tests/calcdata_test.js | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index a48e2024f85..04a44783209 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -818,7 +818,7 @@ module.exports = { valType: 'enumerated', values: [ 'trace', 'category ascending', 'category descending', 'array', - 'value ascending', 'value descending', + 'total ascending', 'total descending', 'min ascending', 'min descending', 'max ascending', 'max descending', 'sum ascending', 'sum descending', @@ -836,9 +836,9 @@ module.exports = { 'Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category', 'is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to', 'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.', - 'Set `categoryorder` to *value ascending* or *value descending* if order should be determined by the', + 'Set `categoryorder` to *total ascending* or *total descending* if order should be determined by the', 'numerical order of the values.', - 'Similarly, the order can be determined by the min, max, sum, mean or media of all the values.' + 'Similarly, the order can be determined by the min, max, sum, mean or median of all the values.' ].join(' ') }, categoryarray: { diff --git a/src/plots/plots.js b/src/plots/plots.js index 353a64b452d..da9b837943b 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2856,7 +2856,7 @@ plots.doCalcdata = function(gd, traces) { Registry.getComponentMethod('errorbars', 'calc')(gd); }; -var sortAxisCategoriesByValueRegex = /(value|sum|min|max|mean|median) (ascending|descending)/; +var sortAxisCategoriesByValueRegex = /(total|sum|min|max|mean|median) (ascending|descending)/; function sortAxisCategoriesByValue(axList, gd) { var affectedTraces = []; @@ -2889,7 +2889,7 @@ function sortAxisCategoriesByValue(axList, gd) { 'min': function(values) {return Lib.aggNums(Math.min, null, values);}, 'max': function(values) {return Lib.aggNums(Math.max, null, values);}, 'sum': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);}, - 'value': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);}, + 'total': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);}, 'mean': function(values) {return Lib.mean(values);}, 'median': function(values) {return Lib.median(values);} }; diff --git a/test/jasmine/tests/calcdata_test.js b/test/jasmine/tests/calcdata_test.js index 4581af9daeb..a6008a072a6 100644 --- a/test/jasmine/tests/calcdata_test.js +++ b/test/jasmine/tests/calcdata_test.js @@ -989,7 +989,7 @@ describe('calculated data and points', function() { .then(done); } - ['value ascending', 'value descending'].forEach(function(categoryorder) { + ['total ascending', 'total descending'].forEach(function(categoryorder) { it('sorts ' + axName + ' by ' + categoryorder + ' for trace type ' + trace.type, function(done) { var data = [7, 2, 3]; var baseMock = {data: [makeData(trace.type, axName, cat, data)], layout: {}}; @@ -997,7 +997,7 @@ describe('calculated data and points', function() { // Set expectations var finalOrder = ['b', 'c', 'a']; - if(categoryorder === 'value descending') finalOrder.reverse(); + if(categoryorder === 'total descending') finalOrder.reverse(); var expectedAgg = [['a', 7], ['b', 2], ['c', 3]]; if(trace.type === 'ohlc' || trace.type === 'candlestick') expectedAgg = [['a', 14], ['b', 4], ['c', 6]]; @@ -1012,7 +1012,7 @@ describe('calculated data and points', function() { var data = [7, 2, 3]; var data2 = [5, 4, 2]; var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2)], layout: {}}; - baseMock.layout[axName] = { type: 'category', categoryorder: 'value ascending'}; + baseMock.layout[axName] = { type: 'category', categoryorder: 'total ascending'}; var expectedAgg = [['a', data[0] + data2[0]], ['b', data[1] + data2[1]], ['c', data[2] + data2[2]]]; if(type === 'ohlc' || type === 'candlestick') expectedAgg = [['a', 2 * expectedAgg[0][1]], ['b', 2 * expectedAgg[1][1]], ['c', 2 * expectedAgg[2][1]]]; @@ -1026,7 +1026,7 @@ describe('calculated data and points', function() { var data = [7, 2, 3]; var data2 = [5, 4, 2]; var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2)], layout: {}}; - baseMock.layout[axName] = { type: 'category', categoryorder: 'value ascending'}; + baseMock.layout[axName] = { type: 'category', categoryorder: 'total ascending'}; // Hide second trace baseMock.data[1].visible = 'legendonly'; From 553e193c8ffc3ceef8023353dff43e2b5288980a Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Fri, 17 May 2019 11:38:23 -0400 Subject: [PATCH 11/13] sort categories by values: update mocks --- .../baselines/hist_category_total_ascending.png | Bin 0 -> 19793 bytes .../baselines/hist_category_value_ascending.png | Bin 19623 -> 0 bytes .../scatter_category_total_descending.png | Bin 0 -> 13378 bytes .../scatter_category_value_descending.png | Bin 13456 -> 0 bytes ...axes.png => sort_by_total_matching_axes.png} | Bin ....json => hist_category_total_ascending.json} | 6 +++--- ...n => scatter_category_total_descending.json} | 4 ++-- ...es.json => sort_by_total_matching_axes.json} | 2 +- 8 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 test/image/baselines/hist_category_total_ascending.png delete mode 100644 test/image/baselines/hist_category_value_ascending.png create mode 100644 test/image/baselines/scatter_category_total_descending.png delete mode 100644 test/image/baselines/scatter_category_value_descending.png rename test/image/baselines/{sort_by_value_matching_axes.png => sort_by_total_matching_axes.png} (100%) rename test/image/mocks/{hist_category_value_ascending.json => hist_category_total_ascending.json} (82%) rename test/image/mocks/{scatter_category_value_descending.json => scatter_category_total_descending.json} (80%) rename test/image/mocks/{sort_by_value_matching_axes.json => sort_by_total_matching_axes.json} (91%) diff --git a/test/image/baselines/hist_category_total_ascending.png b/test/image/baselines/hist_category_total_ascending.png new file mode 100644 index 0000000000000000000000000000000000000000..18fef9510d9ce34188bf1b525c88e2b479e6e6d5 GIT binary patch literal 19793 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 R;ZtcuYRZ?C3KcNe{|j+|!Lk4V literal 0 HcmV?d00001 diff --git a/test/image/baselines/hist_category_value_ascending.png b/test/image/baselines/hist_category_value_ascending.png deleted file mode 100644 index 4bc053c1405bba6276d29e7d9b37a7c4aa849d0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19623 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 diff --git a/test/image/baselines/scatter_category_total_descending.png b/test/image/baselines/scatter_category_total_descending.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0bf18d8e5536ccb4f71529f1b7205b8b02c61f GIT binary patch literal 13378 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 Ks`TlLz<&cTE$+Gi literal 0 HcmV?d00001 diff --git a/test/image/baselines/scatter_category_value_descending.png b/test/image/baselines/scatter_category_value_descending.png deleted file mode 100644 index 378e6393d895166055acae6d32dabbf0840e6d30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13456 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 Date: Fri, 17 May 2019 14:54:27 -0400 Subject: [PATCH 12/13] sort categories by values: handle asymmetric splom --- src/plots/plots.js | 4 ++-- test/jasmine/tests/calcdata_test.js | 27 ++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index da9b837943b..26a0d24d4c7 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2932,10 +2932,10 @@ function sortAxisCategoriesByValue(axList, gd) { // Find which dimension the current axis is representing var currentDimensionIndex = fullTrace._axesDim[ax._id]; - // Apply logic to associated x axis + // Apply logic to associated x axis if it's defined if(axLetter === 'y') { var associatedXAxisID = fullTrace._diag[currentDimensionIndex][0]; - ax = gd._fullLayout[axisIDs.id2name(associatedXAxisID)]; + if(associatedXAxisID) ax = gd._fullLayout[axisIDs.id2name(associatedXAxisID)]; } var categories = cdi.trace.dimensions[currentDimensionIndex].values; diff --git a/test/jasmine/tests/calcdata_test.js b/test/jasmine/tests/calcdata_test.js index a6008a072a6..f26b1481ce7 100644 --- a/test/jasmine/tests/calcdata_test.js +++ b/test/jasmine/tests/calcdata_test.js @@ -1007,7 +1007,7 @@ describe('calculated data and points', function() { }); }); - it('sum values across traces of type ' + trace.type, function(done) { + it('sums values across traces of type ' + trace.type, function(done) { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; @@ -1064,7 +1064,7 @@ describe('calculated data and points', function() { checkAggregatedValue(baseMock, expectedAgg, false, done); }); - it('take the mean of all values per category across traces of type ' + trace.type, function(done) { + it('takes the mean of all values per category across traces of type ' + trace.type, function(done) { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; @@ -1080,7 +1080,7 @@ describe('calculated data and points', function() { checkAggregatedValue(baseMock, expectedAgg, false, done); }); - it('take the median of all values per category across traces of type ' + trace.type, function(done) { + it('takes the median of all values per category across traces of type ' + trace.type, function(done) { var type = trace.type; var data = [7, 2, 3]; var data2 = [5, 4, 2]; @@ -1096,6 +1096,27 @@ describe('calculated data and points', function() { }); }); }); + + it('works on asymmetric splom', function(done) { + var mock = require('@mocks/splom_multi-axis-type'); + var mockCopy = Lib.extendDeep(mock, {}); + + var order = ['donald', 'georgeW', 'bill', 'ronald', 'richard', 'jimmy', 'george', 'barack', 'gerald', 'lyndon']; + + Plotly.newPlot(gd, mockCopy) + .then(function() { + return Plotly.relayout(gd, 'yaxis5.categoryorder', 'total descending'); + }) + .then(function() { + expect(gd._fullLayout.yaxis5._categories).toEqual(order, 'wrong order'); + return Plotly.relayout(gd, 'yaxis5.categoryorder', 'total ascending'); + }) + .then(function() { + expect(gd._fullLayout.yaxis5._categories).toEqual(order.reverse(), 'wrong order'); + }) + .catch(failTest) + .then(done); + }); }); }); From d13542e7a3a0c89040e026c9185df5ac0b4f5d4b Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Fri, 17 May 2019 16:08:52 -0400 Subject: [PATCH 13/13] sort categories by values: deal with 2dMap in its own conditional block --- src/plots/plots.js | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 26a0d24d4c7..67b397c8fd0 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2927,8 +2927,8 @@ function sortAxisCategoriesByValue(axList, gd) { var cdi = cd[k]; var cat, catIndex, value; - // If `splom`, collect values across dimensions if(type === 'splom') { + // If `splom`, collect values across dimensions // Find which dimension the current axis is representing var currentDimensionIndex = fullTrace._axesDim[ax._id]; @@ -2950,8 +2950,8 @@ function sortAxisCategoriesByValue(axList, gd) { categoriesValue[catIndex][1].push(dimension.values[l]); } } - // If `scattergl`, collect all values stashed under cdi.t } else if(type === 'scattergl') { + // If `scattergl`, collect all values stashed under cdi.t for(l = 0; l < cdi.t.x.length; l++) { if(axLetter === 'x') { cat = cdi.t.x[l]; @@ -2971,8 +2971,19 @@ function sortAxisCategoriesByValue(axList, gd) { if(cdi.t && cdi.t._scene) { delete cdi.t._scene.dirty; } - // For all other 2d cartesian traces + } else if(cdi.hasOwnProperty('z')) { + // If 2dMap, collect values in `z` + value = cdi.z; + var mapping = zMapCategory(fullTrace.type, ax, value); + + for(l = 0; l < value.length; l++) { + for(o = 0; o < value[l].length; o++) { + catIndex = mapping(o, l); + if(catIndex + 1) categoriesValue[catIndex][1].push(value[l][o]); + } + } } else { + // For all other 2d cartesian traces if(axLetter === 'x') { cat = cdi.p + 1 ? cdi.p : cdi.x; value = cdi.s || cdi.v || cdi.y; @@ -2980,23 +2991,9 @@ function sortAxisCategoriesByValue(axList, gd) { cat = cdi.p + 1 ? cdi.p : cdi.y; value = cdi.s || cdi.v || cdi.x; } - - // If 2dMap, collect values in `z` - if(cdi.hasOwnProperty('z')) { - value = cdi.z; - var mapping = zMapCategory(fullTrace.type, ax, value); - - for(l = 0; l < value.length; l++) { - for(o = 0; o < value[l].length; o++) { - catIndex = mapping(o, l); - if(catIndex + 1) categoriesValue[catIndex][1].push(value[l][o]); - } - } - } else { - if(!Array.isArray(value)) value = [value]; - for(l = 0; l < value.length; l++) { - categoriesValue[cat][1].push(value[l]); - } + if(!Array.isArray(value)) value = [value]; + for(l = 0; l < value.length; l++) { + categoriesValue[cat][1].push(value[l]); } } }