@@ -2362,6 +2362,193 @@ exports._guiRestyle = guiEdit(restyle);
2362
2362
exports . _guiRelayout = guiEdit ( relayout ) ;
2363
2363
exports . _guiUpdate = guiEdit ( update ) ;
2364
2364
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
+
2365
2552
/**
2366
2553
* Plotly.react:
2367
2554
* 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) {
2424
2611
gd . layout = layout || { } ;
2425
2612
helpers . cleanLayout ( gd . layout ) ;
2426
2613
2614
+ applyUIRevisions ( gd . data , gd . layout , oldFullData , oldFullLayout ) ;
2615
+
2427
2616
// "true" skips updating calcdata and remapping arrays from calcTransforms,
2428
2617
// which supplyDefaults usually does at the end, but we may need to NOT do
2429
2618
// if the diff (which we haven't determined yet) says we'll recalc
0 commit comments