@@ -2362,6 +2362,193 @@ exports._guiRestyle = guiEdit(restyle);
23622362exports . _guiRelayout = guiEdit ( relayout ) ;
23632363exports . _guiUpdate = guiEdit ( update ) ;
23642364
2365+ // For connecting edited layout attributes to uirevision attrs
2366+ // If no `attr` we use `match[1] + '.uirevision'`
2367+ // Ordered by most common edits first, to minimize our search time
2368+ var layoutUIControlPatterns = [
2369+ { pattern : / ^ h i d d e n l a b e l s / , attr : 'legend.uirevision' } ,
2370+ { pattern : / ^ ( ( x | y ) a x i s \d * ) \. ( ( a u t o ) ? r a n g e | t i t l e ) / , autofill : true } ,
2371+
2372+ // showspikes and modes include those nested inside scenes
2373+ { pattern : / a x i s \d * \. s h o w s p i k e s $ / , attr : 'modebar.uirevision' } ,
2374+ { pattern : / ( h o v e r | d r a g ) m o d e $ / , attr : 'modebar.uirevision' } ,
2375+
2376+ { pattern : / ^ ( s c e n e \d * ) \. c a m e r a / } ,
2377+ { pattern : / ^ ( g e o \d * ) \. ( p r o j e c t i o n | c e n t e r ) / } ,
2378+ { pattern : / ^ ( t e r n a r y \d * \. [ a b c ] a x i s ) \. ( m i n | t i t l e ) $ / } ,
2379+ { pattern : / ^ ( p o l a r \d * \. ( r a d i a l | a n g u l a r ) a x i s ) \. / } ,
2380+ { pattern : / ^ ( m a p b o x \d * ) \. ( c e n t e r | z o o m | b e a r i n g | p i t c h ) / } ,
2381+
2382+ { pattern : / ^ l e g e n d \. ( x | y ) $ / , attr : 'editrevision' } ,
2383+ { pattern : / ^ ( s h a p e s | a n n o t a t i o n s ) / , attr : 'editrevision' } ,
2384+ { pattern : / ^ t i t l e $ / , attr : 'editrevision' }
2385+ ] ;
2386+
2387+ // same for trace attributes: if `attr` is given it's in layout,
2388+ // or with no `attr` we use `trace.uirevision`
2389+ var traceUIControlPatterns = [
2390+ // "visible" includes trace.transforms[i].styles[j].value.visible
2391+ { pattern : / ( ^ | v a l u e \. ) v i s i b l e $ / , attr : 'legend.uirevision' } ,
2392+ { pattern : / ^ d i m e n s i o n s \[ \d + \] \. c o n s t r a i n t r a n g e / } ,
2393+
2394+ // below this you must be in editable: true mode
2395+ // TODO: I still put name and title with `trace.uirevision`
2396+ // reasonable or should these be `editrevision`?
2397+ // Also applies to axis titles up in the layout section
2398+
2399+ // "name" also includes transform.styles
2400+ { pattern : / ( ^ | v a l u e \. ) n a m e $ / } ,
2401+ // including nested colorbar attributes (ie marker.colorbar)
2402+ { pattern : / c o l o r b a r \. t i t l e $ / } ,
2403+ { pattern : / c o l o r b a r \. ( x | y ) $ / , attr : 'editrevision' }
2404+ ] ;
2405+
2406+ function findUIPattern ( key , patternSpecs ) {
2407+ for ( var i = 0 ; i < patternSpecs . length ; i ++ ) {
2408+ var spec = patternSpecs [ i ] ;
2409+ var match = key . match ( spec . pattern ) ;
2410+ if ( match ) {
2411+ return { head : match [ 1 ] , attr : spec . attr , autofill : spec . autofill } ;
2412+ }
2413+ }
2414+ }
2415+
2416+ // We're finding the new uirevision before supplyDefaults, so do the
2417+ // inheritance manually. Note that only `undefined` inherits - other
2418+ // falsy values are returned.
2419+ function getNewRev ( revAttr , container ) {
2420+ var newRev = nestedProperty ( container , revAttr ) . get ( ) ;
2421+ if ( newRev !== undefined ) return newRev ;
2422+
2423+ var parts = revAttr . split ( '.' ) ;
2424+ parts . pop ( ) ;
2425+ while ( parts . length > 1 ) {
2426+ parts . pop ( ) ;
2427+ newRev = nestedProperty ( container , parts . join ( '.' ) + '.uirevision' ) . get ( ) ;
2428+ if ( newRev !== undefined ) return newRev ;
2429+ }
2430+
2431+ return container . uirevision ;
2432+ }
2433+
2434+ function getFullTraceIndexFromUid ( uid , fullData ) {
2435+ for ( var i = 0 ; i < fullData . length ; i ++ ) {
2436+ if ( fullData [ i ] . _fullInput . uid === uid ) return i ;
2437+ }
2438+ return - 1 ;
2439+ }
2440+
2441+ function getTraceIndexFromUid ( uid , data , tracei ) {
2442+ for ( var i = 0 ; i < data . length ; i ++ ) {
2443+ if ( data [ i ] . uid === uid ) return i ;
2444+ }
2445+ // fall back on trace order, but only if user didn't provide a uid for that trace
2446+ return data [ tracei ] . uid ? - 1 : tracei ;
2447+ }
2448+
2449+ function applyUIRevisions ( data , layout , oldFullData , oldFullLayout ) {
2450+ var layoutPreGUI = oldFullLayout . _preGUI ;
2451+ var key , revAttr , oldRev , newRev , match , preGUIVal , newNP , newVal ;
2452+ for ( key in layoutPreGUI ) {
2453+ match = findUIPattern ( key , layoutUIControlPatterns ) ;
2454+ if ( match ) {
2455+ revAttr = match . attr || ( match . head + '.uirevision' ) ;
2456+ oldRev = nestedProperty ( oldFullLayout , revAttr ) . get ( ) ;
2457+ newRev = oldRev && getNewRev ( revAttr , layout ) ;
2458+ if ( newRev && ( newRev === oldRev ) ) {
2459+ preGUIVal = layoutPreGUI [ key ] ;
2460+ if ( preGUIVal === null ) preGUIVal = undefined ;
2461+ newNP = nestedProperty ( layout , key ) ;
2462+ newVal = newNP . get ( ) ;
2463+ // TODO: This test for undefined is to account for the case where
2464+ // the value was filled in automatically in gd.layout,
2465+ // like axis.range/autorange. In principle though, if the initial
2466+ // plot had a value and the new plot removed that value, we would
2467+ // want the removal to override the GUI edit and generate a new
2468+ // auto value. But that would require figuring out what value was
2469+ // in gd.layout *before* the auto values were filled in, and
2470+ // storing *that* in preGUI... oh well, for now at least I limit
2471+ // this to attributes that get autofilled, which AFAICT among
2472+ // the GUI-editable attributes is just axis.range/autorange.
2473+ if ( newVal === preGUIVal || ( match . autofill && newVal === undefined ) ) {
2474+ newNP . set ( nestedProperty ( oldFullLayout , key ) . get ( ) ) ;
2475+ continue ;
2476+ }
2477+ }
2478+ }
2479+ else {
2480+ Lib . warn ( 'unrecognized GUI edit: ' + key ) ;
2481+ }
2482+ // if we got this far, the new value was accepted as the new starting
2483+ // point (either because it changed or revision changed)
2484+ // so remove it from _preGUI for next time.
2485+ delete layoutPreGUI [ key ] ;
2486+ }
2487+
2488+ // Now traces - try to match them up by uid (in case we added/deleted in
2489+ // the middle), then fall back on index.
2490+ // var tracei = -1;
2491+ // for(var fulli = 0; fulli < oldFullData.length; fulli++) {
2492+ var allTracePreGUI = oldFullLayout . _tracePreGUI ;
2493+ for ( var uid in allTracePreGUI ) {
2494+ var tracePreGUI = allTracePreGUI [ uid ] ;
2495+ var newTrace = null ;
2496+ var fullInput ;
2497+ for ( key in tracePreGUI ) {
2498+ // wait until we know we have preGUI values to look for traces
2499+ // but if we don't find both, stop looking at this uid
2500+ if ( ! newTrace ) {
2501+ var fulli = getFullTraceIndexFromUid ( uid , oldFullData ) ;
2502+ if ( fulli < 0 ) {
2503+ // Somehow we didn't even have this trace in oldFullData...
2504+ // I guess this could happen with `deleteTraces` or something
2505+ delete allTracePreGUI [ uid ] ;
2506+ break ;
2507+ }
2508+ var fullTrace = oldFullData [ fulli ] ;
2509+ fullInput = fullTrace . _fullInput ;
2510+
2511+ var newTracei = getTraceIndexFromUid ( uid , data , fullInput . index ) ;
2512+ if ( newTracei < 0 ) {
2513+ // No match in new data
2514+ delete allTracePreGUI [ uid ] ;
2515+ break ;
2516+ }
2517+ newTrace = data [ newTracei ] ;
2518+ }
2519+
2520+ match = findUIPattern ( key , traceUIControlPatterns ) ;
2521+ if ( match ) {
2522+ if ( match . attr ) {
2523+ oldRev = nestedProperty ( oldFullLayout , match . attr ) . get ( ) ;
2524+ newRev = oldRev && getNewRev ( match . attr , layout ) ;
2525+ }
2526+ else {
2527+ oldRev = fullInput . uirevision ;
2528+ // inheritance for trace.uirevision is simple, just layout.uirevision
2529+ newRev = newTrace . uirevision ;
2530+ if ( newRev === undefined ) newRev = layout . uirevision ;
2531+ }
2532+
2533+ if ( newRev && newRev === oldRev ) {
2534+ preGUIVal = tracePreGUI [ key ] ;
2535+ if ( preGUIVal === null ) preGUIVal = undefined ;
2536+ newNP = nestedProperty ( newTrace , key ) ;
2537+ newVal = newNP . get ( ) ;
2538+ if ( newVal === preGUIVal || ( match . autofill && newVal === undefined ) ) {
2539+ newNP . set ( nestedProperty ( fullInput , key ) . get ( ) ) ;
2540+ continue ;
2541+ }
2542+ }
2543+ }
2544+ else {
2545+ Lib . warn ( 'unrecognized GUI edit: ' + key + ' in trace uid ' + uid ) ;
2546+ }
2547+ delete tracePreGUI [ key ] ;
2548+ }
2549+ }
2550+ }
2551+
23652552/**
23662553 * Plotly.react:
23672554 * A plot/update method that takes the full plot state (same API as plot/newPlot)
@@ -2424,6 +2611,8 @@ exports.react = function(gd, data, layout, config) {
24242611 gd . layout = layout || { } ;
24252612 helpers . cleanLayout ( gd . layout ) ;
24262613
2614+ applyUIRevisions ( gd . data , gd . layout , oldFullData , oldFullLayout ) ;
2615+
24272616 // "true" skips updating calcdata and remapping arrays from calcTransforms,
24282617 // which supplyDefaults usually does at the end, but we may need to NOT do
24292618 // if the diff (which we haven't determined yet) says we'll recalc
0 commit comments