@@ -2847,10 +2847,196 @@ plots.doCalcdata = function(gd, traces) {
2847
2847
2848
2848
doCrossTraceCalc ( gd ) ;
2849
2849
2850
+ // Sort axis categories per value if specified
2851
+ var sorted = sortAxisCategoriesByValue ( axList , gd ) ;
2852
+ if ( sorted . length ) {
2853
+ // If a sort operation was performed, run calc() again
2854
+ for ( i = 0 ; i < sorted . length ; i ++ ) calci ( sorted [ i ] , true ) ;
2855
+ for ( i = 0 ; i < sorted . length ; i ++ ) calci ( sorted [ i ] , false ) ;
2856
+ doCrossTraceCalc ( gd ) ;
2857
+ }
2858
+
2850
2859
Registry . getComponentMethod ( 'fx' , 'calc' ) ( gd ) ;
2851
2860
Registry . getComponentMethod ( 'errorbars' , 'calc' ) ( gd ) ;
2852
2861
} ;
2853
2862
2863
+ var sortAxisCategoriesByValueRegex = / ( t o t a l | s u m | m i n | m a x | m e a n | m e d i a n ) ( a s c e n d i n g | d e s c e n d i n g ) / ;
2864
+
2865
+ function sortAxisCategoriesByValue ( axList , gd ) {
2866
+ var affectedTraces = [ ] ;
2867
+ var i , j , k , l , o ;
2868
+
2869
+ function zMapCategory ( type , ax , value ) {
2870
+ var axLetter = ax . _id . charAt ( 0 ) ;
2871
+ if ( type === 'histogram2dcontour' ) {
2872
+ var counterAxLetter = ax . _counterAxes [ 0 ] ;
2873
+ var counterAx = axisIDs . getFromId ( gd , counterAxLetter ) ;
2874
+
2875
+ var xCategorical = axLetter === 'x' || ( counterAxLetter === 'x' && counterAx . type === 'category' ) ;
2876
+ var yCategorical = axLetter === 'y' || ( counterAxLetter === 'y' && counterAx . type === 'category' ) ;
2877
+
2878
+ return function ( o , l ) {
2879
+ if ( o === 0 || l === 0 ) return - 1 ; // Skip first row and column
2880
+ if ( xCategorical && o === value [ l ] . length - 1 ) return - 1 ;
2881
+ if ( yCategorical && l === value . length - 1 ) return - 1 ;
2882
+
2883
+ return ( axLetter === 'y' ? l : o ) - 1 ;
2884
+ } ;
2885
+ } else {
2886
+ return function ( o , l ) {
2887
+ return axLetter === 'y' ? l : o ;
2888
+ } ;
2889
+ }
2890
+ }
2891
+
2892
+ var aggFn = {
2893
+ 'min' : function ( values ) { return Lib . aggNums ( Math . min , null , values ) ; } ,
2894
+ 'max' : function ( values ) { return Lib . aggNums ( Math . max , null , values ) ; } ,
2895
+ 'sum' : function ( values ) { return Lib . aggNums ( function ( a , b ) { return a + b ; } , null , values ) ; } ,
2896
+ 'total' : function ( values ) { return Lib . aggNums ( function ( a , b ) { return a + b ; } , null , values ) ; } ,
2897
+ 'mean' : function ( values ) { return Lib . mean ( values ) ; } ,
2898
+ 'median' : function ( values ) { return Lib . median ( values ) ; }
2899
+ } ;
2900
+
2901
+ for ( i = 0 ; i < axList . length ; i ++ ) {
2902
+ var ax = axList [ i ] ;
2903
+ if ( ax . type !== 'category' ) continue ;
2904
+
2905
+ // Order by value
2906
+ var match = ax . categoryorder . match ( sortAxisCategoriesByValueRegex ) ;
2907
+ if ( match ) {
2908
+ var aggregator = match [ 1 ] ;
2909
+ var order = match [ 2 ] ;
2910
+
2911
+ // Store values associated with each category
2912
+ var categoriesValue = [ ] ;
2913
+ for ( j = 0 ; j < ax . _categories . length ; j ++ ) {
2914
+ categoriesValue . push ( [ ax . _categories [ j ] , [ ] ] ) ;
2915
+ }
2916
+
2917
+ // Collect values across traces
2918
+ for ( j = 0 ; j < ax . _traceIndices . length ; j ++ ) {
2919
+ var traceIndex = ax . _traceIndices [ j ] ;
2920
+ var fullTrace = gd . _fullData [ traceIndex ] ;
2921
+ var axLetter = ax . _id . charAt ( 0 ) ;
2922
+
2923
+ // Skip over invisible traces
2924
+ if ( fullTrace . visible !== true ) continue ;
2925
+
2926
+ var type = fullTrace . type ;
2927
+ if ( Registry . traceIs ( fullTrace , 'histogram' ) ) delete fullTrace . _autoBinFinished ;
2928
+
2929
+ var cd = gd . calcdata [ traceIndex ] ;
2930
+ for ( k = 0 ; k < cd . length ; k ++ ) {
2931
+ var cdi = cd [ k ] ;
2932
+ var cat , catIndex , value ;
2933
+
2934
+ if ( type === 'splom' ) {
2935
+ // If `splom`, collect values across dimensions
2936
+ // Find which dimension the current axis is representing
2937
+ var currentDimensionIndex = fullTrace . _axesDim [ ax . _id ] ;
2938
+
2939
+ // Apply logic to associated x axis if it's defined
2940
+ if ( axLetter === 'y' ) {
2941
+ var associatedXAxisID = fullTrace . _diag [ currentDimensionIndex ] [ 0 ] ;
2942
+ if ( associatedXAxisID ) ax = gd . _fullLayout [ axisIDs . id2name ( associatedXAxisID ) ] ;
2943
+ }
2944
+
2945
+ var categories = cdi . trace . dimensions [ currentDimensionIndex ] . values ;
2946
+ for ( l = 0 ; l < categories . length ; l ++ ) {
2947
+ cat = categories [ l ] ;
2948
+ catIndex = ax . _categoriesMap [ cat ] ;
2949
+
2950
+ // Collect associated values at index `l` over all other dimensions
2951
+ for ( o = 0 ; o < cdi . trace . dimensions . length ; o ++ ) {
2952
+ if ( o === currentDimensionIndex ) continue ;
2953
+ var dimension = cdi . trace . dimensions [ o ] ;
2954
+ categoriesValue [ catIndex ] [ 1 ] . push ( dimension . values [ l ] ) ;
2955
+ }
2956
+ }
2957
+ } else if ( type === 'scattergl' ) {
2958
+ // If `scattergl`, collect all values stashed under cdi.t
2959
+ for ( l = 0 ; l < cdi . t . x . length ; l ++ ) {
2960
+ if ( axLetter === 'x' ) {
2961
+ cat = cdi . t . x [ l ] ;
2962
+ catIndex = cat ;
2963
+ value = cdi . t . y [ l ] ;
2964
+ }
2965
+
2966
+ if ( axLetter === 'y' ) {
2967
+ cat = cdi . t . y [ l ] ;
2968
+ catIndex = cat ;
2969
+ value = cdi . t . x [ l ] ;
2970
+ }
2971
+ categoriesValue [ catIndex ] [ 1 ] . push ( value ) ;
2972
+ }
2973
+ // must clear scene 'batches', so that 2nd
2974
+ // _module.calc call starts from scratch
2975
+ if ( cdi . t && cdi . t . _scene ) {
2976
+ delete cdi . t . _scene . dirty ;
2977
+ }
2978
+ } else if ( cdi . hasOwnProperty ( 'z' ) ) {
2979
+ // If 2dMap, collect values in `z`
2980
+ value = cdi . z ;
2981
+ var mapping = zMapCategory ( fullTrace . type , ax , value ) ;
2982
+
2983
+ for ( l = 0 ; l < value . length ; l ++ ) {
2984
+ for ( o = 0 ; o < value [ l ] . length ; o ++ ) {
2985
+ catIndex = mapping ( o , l ) ;
2986
+ if ( catIndex + 1 ) categoriesValue [ catIndex ] [ 1 ] . push ( value [ l ] [ o ] ) ;
2987
+ }
2988
+ }
2989
+ } else {
2990
+ // For all other 2d cartesian traces
2991
+ if ( axLetter === 'x' ) {
2992
+ cat = cdi . p + 1 ? cdi . p : cdi . x ;
2993
+ value = cdi . s || cdi . v || cdi . y ;
2994
+ } else if ( axLetter === 'y' ) {
2995
+ cat = cdi . p + 1 ? cdi . p : cdi . y ;
2996
+ value = cdi . s || cdi . v || cdi . x ;
2997
+ }
2998
+ if ( ! Array . isArray ( value ) ) value = [ value ] ;
2999
+ for ( l = 0 ; l < value . length ; l ++ ) {
3000
+ categoriesValue [ cat ] [ 1 ] . push ( value [ l ] ) ;
3001
+ }
3002
+ }
3003
+ }
3004
+ }
3005
+
3006
+ ax . _categoriesValue = categoriesValue ;
3007
+
3008
+ var categoriesAggregatedValue = [ ] ;
3009
+ for ( j = 0 ; j < categoriesValue . length ; j ++ ) {
3010
+ categoriesAggregatedValue . push ( [
3011
+ categoriesValue [ j ] [ 0 ] ,
3012
+ aggFn [ aggregator ] ( categoriesValue [ j ] [ 1 ] )
3013
+ ] ) ;
3014
+ }
3015
+
3016
+ // Sort by aggregated value
3017
+ categoriesAggregatedValue . sort ( function ( a , b ) {
3018
+ return a [ 1 ] - b [ 1 ] ;
3019
+ } ) ;
3020
+
3021
+ ax . _categoriesAggregatedValue = categoriesAggregatedValue ;
3022
+
3023
+ // Set new category order
3024
+ ax . _initialCategories = categoriesAggregatedValue . map ( function ( c ) {
3025
+ return c [ 0 ] ;
3026
+ } ) ;
3027
+
3028
+ // Reverse if descending
3029
+ if ( order === 'descending' ) {
3030
+ ax . _initialCategories . reverse ( ) ;
3031
+ }
3032
+
3033
+ // Sort all matching axes
3034
+ affectedTraces = affectedTraces . concat ( ax . sortByInitialCategories ( ) ) ;
3035
+ }
3036
+ }
3037
+ return affectedTraces ;
3038
+ }
3039
+
2854
3040
function setupAxisCategories ( axList , fullData ) {
2855
3041
for ( var i = 0 ; i < axList . length ; i ++ ) {
2856
3042
var ax = axList [ i ] ;
0 commit comments