-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
sort categorical Cartesian axes by value #3864
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
9c09d8c
sort categorical Cartesian axes by value
antoinerg 2d379f7
sort categories by values: improve code style
antoinerg e4e5eaa
sort categories by values: fix mock syntax
antoinerg 76683ba
sort categories by values: add support for histogram2dcontour
antoinerg fac239b
sort categories by values: improve splom logic
antoinerg 67cc14d
sort categories by values: implement mean and median
antoinerg c6e049b
implement Lib.median()
antoinerg 7c8d52b
sort categories by values: use Lib.median()
antoinerg 54b3ff9
Lib.median(): reuse existing Lib.interp()
antoinerg 9aaa10b
categoryorder: rename 'value' to 'total'
antoinerg 553e193
sort categories by values: update mocks
antoinerg bf6bdf3
sort categories by values: handle asymmetric splom
antoinerg d13542e
sort categories by values: deal with 2dMap in its own conditional block
antoinerg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2843,10 +2843,199 @@ 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|mean|median) (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; | ||
|
||
return (axLetter === 'y' ? l : o) - 1; | ||
}; | ||
} else { | ||
return function(o, l) { | ||
return axLetter === 'y' ? l : o; | ||
}; | ||
} | ||
} | ||
|
||
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);}, | ||
etpinard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'mean': function(values) {return Lib.mean(values);}, | ||
'median': function(values) {values.sort(); var mid = Math.round((values.length - 1) / 2); return values[mid];} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm surprised our linter didn't complain here. Would you mind rewriting this as 'median': function(values) {
values.sort();
var mid = Math.round((values.length - 1) / 2);
return values[mid];
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's probably an eslint rule for this 🔬 |
||
}; | ||
|
||
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); | ||
etpinard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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++) { | ||
categoriesValue.push([ax._categories[j], []]); | ||
} | ||
|
||
// Collect values across traces | ||
for(j = 0; j < ax._traceIndices.length; j++) { | ||
var traceIndex = ax._traceIndices[j]; | ||
var fullTrace = gd._fullData[traceIndex]; | ||
var axLetter = ax._id.charAt(0); | ||
|
||
// Skip over invisible traces | ||
if(fullTrace.visible !== true) continue; | ||
|
||
var type = fullTrace.type; | ||
if(Registry.traceIs(fullTrace, 'histogram')) delete fullTrace._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 = fullTrace._axesDim[ax._id]; | ||
|
||
// Apply logic to associated x axis | ||
if(axLetter === 'y') { | ||
var associatedXAxisID = fullTrace._diag[currentDimensionIndex][0]; | ||
ax = gd._fullLayout[axisIDs.id2name(associatedXAxisID)]; | ||
} | ||
|
||
var categories = cdi.trace.dimensions[currentDimensionIndex].values; | ||
for(l = 0; l < categories.length; l++) { | ||
cat = categories[l]; | ||
catIndex = ax._categoriesMap[cat]; | ||
|
||
// 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]; | ||
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(axLetter === 'x') { | ||
cat = cdi.t.x[l]; | ||
catIndex = cat; | ||
value = cdi.t.y[l]; | ||
} | ||
|
||
if(axLetter === '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(axLetter === 'x') { | ||
cat = cdi.p + 1 ? cdi.p : cdi.x; | ||
value = cdi.s || cdi.v || cdi.y; | ||
} else if(axLetter === '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; | ||
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]); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
ax._categoriesValue = categoriesValue; | ||
|
||
var categoriesAggregatedValue = []; | ||
for(j = 0; j < categoriesValue.length; j++) { | ||
categoriesAggregatedValue.push([ | ||
categoriesValue[j][0], | ||
aggFn[aggregator](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(order === '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]; | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.