Skip to content

Commit 0ba044a

Browse files
committed
sort categories by value: support different aggregation functions
1 parent df7afb6 commit 0ba044a

File tree

4 files changed

+74
-25
lines changed

4 files changed

+74
-25
lines changed

src/plots/cartesian/layout_attributes.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -817,8 +817,11 @@ module.exports = {
817817
categoryorder: {
818818
valType: 'enumerated',
819819
values: [
820-
'trace', 'category ascending', 'category descending', 'array', 'value ascending', 'value descending'
821-
/* , 'value ascending', 'value descending'*/ // value ascending / descending to be implemented later
820+
'trace', 'category ascending', 'category descending', 'array',
821+
'value ascending', 'value descending',
822+
'min ascending', 'min descending',
823+
'max ascending', 'max descending',
824+
'sum ascending', 'sum descending'
822825
],
823826
dflt: 'trace',
824827
role: 'info',

src/plots/plots.js

+44-18
Original file line numberDiff line numberDiff line change
@@ -2822,59 +2822,85 @@ plots.doCalcdata = function(gd, traces) {
28222822
doCrossTraceCalc(gd);
28232823

28242824
// Sort axis categories per value if specified
2825-
if(sortAxisCategoriesByValue(axList, gd)) {
2825+
var sorted = sortAxisCategoriesByValue(axList, gd);
2826+
if(sorted.length) {
28262827
// If a sort operation was performed, run calc() again
2827-
for(i = 0; i < fullData.length; i++) calci(i, true);
2828-
for(i = 0; i < fullData.length; i++) calci(i, false);
2828+
for(i = 0; i < sorted.length; i++) calci(sorted[i], true);
2829+
for(i = 0; i < sorted.length; i++) calci(sorted[i], false);
28292830
doCrossTraceCalc(gd);
28302831
}
28312832

28322833
Registry.getComponentMethod('fx', 'calc')(gd);
28332834
Registry.getComponentMethod('errorbars', 'calc')(gd);
28342835
};
28352836

2837+
var sortAxisCategoriesByValueRegex = /(value|sum|min|max) (ascending|descending)/;
2838+
28362839
function sortAxisCategoriesByValue(axList, gd) {
2837-
var sortByValue = false;
2840+
var sortByValue = [];
28382841
var i, j, k;
28392842
for(i = 0; i < axList.length; i++) {
28402843
var ax = axList[i];
28412844
if(ax.type !== 'category') continue;
28422845

28432846
// Order by value
2844-
if(ax.categoryorder === 'value ascending' ||
2845-
ax.categoryorder === 'value descending') {
2846-
sortByValue = true;
2847-
2848-
// Store value associated with each category
2847+
var m = ax.categoryorder.match(sortAxisCategoriesByValueRegex);
2848+
if(m) {
2849+
// Store values associated with each category
28492850
var categoriesValue = [];
28502851
for(j = 0; j < ax._categories.length; j++) {
2851-
categoriesValue.push([ax._categories[j], 0]);
2852+
categoriesValue.push([ax._categories[j], []]);
28522853
}
28532854

2854-
// Aggregate values across traces
2855+
// Collect values across traces
28552856
for(j = 0; j < ax._traceIndices.length; j++) {
2857+
// Keep track of traces we sort!
28562858
var traceIndex = ax._traceIndices[j];
2859+
sortByValue.push(traceIndex);
2860+
28572861
var fullData = gd._fullData[traceIndex];
28582862
if(fullData.visible !== true) continue;
2859-
var cd = gd.calcdata[traceIndex];
2860-
var type = fullData.type;
28612863

2864+
var type = fullData.type;
28622865
if(type === 'histogram') delete fullData._autoBinFinished;
28632866

2867+
var cd = gd.calcdata[traceIndex];
28642868
for(k = 0; k < cd.length; k++) {
2869+
var cat, value;
28652870
if(type === 'scatter') {
28662871
if(ax._id[0] === 'x') {
2867-
categoriesValue[cd[k].x][1] += cd[k].y;
2872+
cat = cd[k].x;
2873+
value = cd[k].y;
28682874
} else if(ax._id[0] === 'y') {
2869-
categoriesValue[cd[k].y][1] += cd[k].x;
2875+
cat = cd[k].y;
2876+
value = cd[k].x;
28702877
}
28712878
} else if(type === 'histogram') {
2872-
categoriesValue[cd[k].p][1] += cd[k].s;
2879+
cat = cd[k].p;
2880+
value = cd[k].s;
28732881
}
2882+
categoriesValue[cat][1].push(value);
28742883
}
28752884
}
28762885

2877-
// Sort by value
2886+
// Aggregate values
2887+
var aggFn;
2888+
switch(m[1]) {
2889+
case 'min':
2890+
aggFn = Math.min;
2891+
break;
2892+
case 'max':
2893+
aggFn = Math.max;
2894+
break;
2895+
default:
2896+
aggFn = function(a, b) { return a + b;};
2897+
}
2898+
2899+
for(j = 0; j < categoriesValue.length; j++) {
2900+
categoriesValue[j][1] = Lib.aggNums(aggFn, null, categoriesValue[j][1]);
2901+
}
2902+
2903+
// Sort by aggregated value
28782904
categoriesValue.sort(function(a, b) {
28792905
return a[1] - b[1];
28802906
});
@@ -2885,7 +2911,7 @@ function sortAxisCategoriesByValue(axList, gd) {
28852911
});
28862912

28872913
// Reverse if descending
2888-
if(ax.categoryorder === 'value descending') {
2914+
if(m[2] === 'descending') {
28892915
ax._initialCategories.reverse();
28902916
}
28912917

Loading

test/image/mocks/hist_category_value_ascending.json

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
11
{
22
"data": [{
3-
"x": ["a", "b", "c", "a", "b", "d", "b", "c"],
3+
"x": ["a", "b", "c", "a", "b", "d", "b", "c", "b", "b"],
44
"type": "histogram"
55
},
66
{
7-
"x": ["d", "c", "b", "a", "e", "a", "b"],
7+
"x": ["d", "c", "a", "e", "a"],
88
"type": "histogram"
9+
},
10+
{
11+
"y": ["a", "b", "c", "a", "b", "d", "b", "c"],
12+
"type": "histogram",
13+
"xaxis": "x2",
14+
"yaxis": "y2"
15+
},
16+
{
17+
"y": ["d", "c", "b", "a", "e", "a", "b"],
18+
"type": "histogram",
19+
"xaxis": "x2",
20+
"yaxis": "y2"
921
}],
1022
"layout": {
11-
"title": "xaxis.categoryorder: \"value ascending\"",
12-
"height": 300,
13-
"width": 400,
23+
"title": "categoryorder: \"value ascending\"",
24+
"height": 400,
25+
"width": 600,
1426
"barmode": "stack",
1527
"xaxis": {
28+
"domain": [0, 0.45],
29+
"categoryorder": "value ascending"
30+
},
31+
"xaxis2": {
32+
"domain": [0.55, 1]
33+
},
34+
"yaxis2": {
35+
"anchor": "x2",
1636
"categoryorder": "value ascending"
1737
}
1838
}

0 commit comments

Comments
 (0)