@@ -2847,10 +2847,196 @@ plots.doCalcdata = function(gd, traces) {
28472847
28482848 doCrossTraceCalc ( gd ) ;
28492849
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+
28502859 Registry . getComponentMethod ( 'fx' , 'calc' ) ( gd ) ;
28512860 Registry . getComponentMethod ( 'errorbars' , 'calc' ) ( gd ) ;
28522861} ;
28532862
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+
28543040function setupAxisCategories ( axList , fullData ) {
28553041 for ( var i = 0 ; i < axList . length ; i ++ ) {
28563042 var ax = axList [ i ] ;
0 commit comments