@@ -1284,7 +1284,6 @@ describe('Test gl2d interactions', function() {
1284
1284
} ) ;
1285
1285
1286
1286
it ( 'data-referenced annotations should update on drag' , function ( done ) {
1287
-
1288
1287
function drag ( start , end ) {
1289
1288
mouseEvent ( 'mousemove' , start [ 0 ] , start [ 1 ] ) ;
1290
1289
mouseEvent ( 'mousedown' , start [ 0 ] , start [ 1 ] , { buttons : 1 } ) ;
@@ -1329,3 +1328,336 @@ describe('Test gl2d interactions', function() {
1329
1328
. then ( done ) ;
1330
1329
} ) ;
1331
1330
} ) ;
1331
+
1332
+ describe ( 'Test gl3d annotations' , function ( ) {
1333
+ var gd ;
1334
+
1335
+ beforeAll ( function ( ) {
1336
+ jasmine . addMatchers ( customMatchers ) ;
1337
+ } ) ;
1338
+
1339
+ beforeEach ( function ( ) {
1340
+ gd = createGraphDiv ( ) ;
1341
+ } ) ;
1342
+
1343
+ afterEach ( function ( ) {
1344
+ Plotly . purge ( gd ) ;
1345
+ destroyGraphDiv ( ) ;
1346
+ } ) ;
1347
+
1348
+ function assertAnnotationText ( expectations , msg ) {
1349
+ var anns = d3 . selectAll ( 'g.annotation-text-g' ) ;
1350
+
1351
+ expect ( anns . size ( ) ) . toBe ( expectations . length , msg ) ;
1352
+
1353
+ anns . each ( function ( _ , i ) {
1354
+ var tx = d3 . select ( this ) . select ( 'text' ) . text ( ) ;
1355
+ expect ( tx ) . toEqual ( expectations [ i ] , msg + ' - ann ' + i ) ;
1356
+ } ) ;
1357
+ }
1358
+
1359
+ function assertAnnotationsXY ( expectations , msg ) {
1360
+ var TOL = 1.5 ;
1361
+ var anns = d3 . selectAll ( 'g.annotation-text-g' ) ;
1362
+
1363
+ expect ( anns . size ( ) ) . toBe ( expectations . length , msg ) ;
1364
+
1365
+ anns . each ( function ( _ , i ) {
1366
+ var ann = d3 . select ( this ) . select ( 'g' ) ;
1367
+ var translate = Drawing . getTranslate ( ann ) ;
1368
+
1369
+ expect ( translate . x ) . toBeWithin ( expectations [ i ] [ 0 ] , TOL , msg + ' - ann ' + i + ' x' ) ;
1370
+ expect ( translate . y ) . toBeWithin ( expectations [ i ] [ 1 ] , TOL , msg + ' - ann ' + i + ' y' ) ;
1371
+ } ) ;
1372
+ }
1373
+
1374
+ // more robust (especially on CI) than update camera via mouse events
1375
+ function updateCamera ( x , y , z ) {
1376
+ return new Promise ( function ( resolve ) {
1377
+ var scene = gd . _fullLayout . scene . _scene ;
1378
+ var camera = scene . getCamera ( ) ;
1379
+
1380
+ camera . eye = { x : x , y : y , z : z } ;
1381
+ scene . setCamera ( camera ) ;
1382
+
1383
+ setTimeout ( resolve , 100 ) ;
1384
+ } ) ;
1385
+ }
1386
+
1387
+ it ( 'should move with camera' , function ( done ) {
1388
+ Plotly . plot ( gd , [ {
1389
+ type : 'scatter3d' ,
1390
+ x : [ 1 , 2 , 3 ] ,
1391
+ y : [ 1 , 2 , 3 ] ,
1392
+ z : [ 1 , 2 , 1 ]
1393
+ } ] , {
1394
+ scene : {
1395
+ camera : { eye : { x : 2.1 , y : 0.1 , z : 0.9 } } ,
1396
+ annotations : [ {
1397
+ text : 'hello' ,
1398
+ x : 1 , y : 1 , z : 1
1399
+ } , {
1400
+ text : 'sup?' ,
1401
+ x : 1 , y : 1 , z : 2
1402
+ } , {
1403
+ text : 'look!' ,
1404
+ x : 2 , y : 2 , z : 1
1405
+ } ]
1406
+ }
1407
+ } )
1408
+ . then ( function ( ) {
1409
+ assertAnnotationsXY ( [ [ 262 , 199 ] , [ 257 , 135 ] , [ 325 , 233 ] ] , 'base 0' ) ;
1410
+
1411
+ return updateCamera ( 1.5 , 2.5 , 1.5 ) ;
1412
+ } )
1413
+ . then ( function ( ) {
1414
+ assertAnnotationsXY ( [ [ 340 , 187 ] , [ 341 , 142 ] , [ 325 , 221 ] ] , 'after camera update' ) ;
1415
+
1416
+ return updateCamera ( 2.1 , 0.1 , 0.9 ) ;
1417
+ } )
1418
+ . then ( function ( ) {
1419
+ assertAnnotationsXY ( [ [ 262 , 199 ] , [ 257 , 135 ] , [ 325 , 233 ] ] , 'base 0' ) ;
1420
+ } )
1421
+ . catch ( fail )
1422
+ . then ( done ) ;
1423
+ } ) ;
1424
+
1425
+ it ( 'should be removed when beyond the scene axis ranges' , function ( done ) {
1426
+ var mock = Lib . extendDeep ( { } , require ( '@mocks/gl3d_annotations' ) ) ;
1427
+
1428
+ // replace text with something easier to identify
1429
+ mock . layout . scene . annotations . forEach ( function ( ann , i ) { ann . text = String ( i ) ; } ) ;
1430
+
1431
+ Plotly . plot ( gd , mock ) . then ( function ( ) {
1432
+ assertAnnotationText ( [ '0' , '1' , '2' , '3' ] , 'base' ) ;
1433
+
1434
+ return Plotly . relayout ( gd , 'scene.yaxis.range' , [ 0.5 , 1.5 ] ) ;
1435
+ } )
1436
+ . then ( function ( ) {
1437
+ assertAnnotationText ( [ '1' ] , 'after yaxis range relayout' ) ;
1438
+
1439
+ return Plotly . relayout ( gd , 'scene.yaxis.range' , null ) ;
1440
+ } )
1441
+ . then ( function ( ) {
1442
+ assertAnnotationText ( [ '0' , '1' , '2' , '3' ] , 'back to base after yaxis range relayout' ) ;
1443
+
1444
+ return Plotly . relayout ( gd , 'scene.zaxis.range' , [ 0 , 3 ] ) ;
1445
+ } )
1446
+ . then ( function ( ) {
1447
+ assertAnnotationText ( [ '0' ] , 'after zaxis range relayout' ) ;
1448
+
1449
+ return Plotly . relayout ( gd , 'scene.zaxis.range' , null ) ;
1450
+ } )
1451
+ . then ( function ( ) {
1452
+ assertAnnotationText ( [ '0' , '1' , '2' , '3' ] , 'back to base after zaxis range relayout' ) ;
1453
+ } )
1454
+ . catch ( fail )
1455
+ . then ( done ) ;
1456
+ } ) ;
1457
+
1458
+ it ( 'should be able to add/remove and hide/unhide themselves via relayout' , function ( done ) {
1459
+ var mock = Lib . extendDeep ( { } , require ( '@mocks/gl3d_annotations' ) ) ;
1460
+
1461
+ // replace text with something easier to identify
1462
+ mock . layout . scene . annotations . forEach ( function ( ann , i ) { ann . text = String ( i ) ; } ) ;
1463
+
1464
+ var annNew = {
1465
+ x : '2017-03-01' ,
1466
+ y : 'C' ,
1467
+ z : 3 ,
1468
+ text : 'new!'
1469
+ } ;
1470
+
1471
+ Plotly . plot ( gd , mock ) . then ( function ( ) {
1472
+ assertAnnotationText ( [ '0' , '1' , '2' , '3' ] , 'base' ) ;
1473
+
1474
+ return Plotly . relayout ( gd , 'scene.annotations[1].visible' , false ) ;
1475
+ } )
1476
+ . then ( function ( ) {
1477
+ assertAnnotationText ( [ '0' , '2' , '3' ] , 'after [1].visible:false' ) ;
1478
+
1479
+ return Plotly . relayout ( gd , 'scene.annotations[1].visible' , true ) ;
1480
+ } )
1481
+ . then ( function ( ) {
1482
+ assertAnnotationText ( [ '0' , '1' , '2' , '3' ] , 'back to base (1)' ) ;
1483
+
1484
+ return Plotly . relayout ( gd , 'scene.annotations[0]' , null ) ;
1485
+ } )
1486
+ . then ( function ( ) {
1487
+ assertAnnotationText ( [ '1' , '2' , '3' ] , 'after [0] null' ) ;
1488
+
1489
+ return Plotly . relayout ( gd , 'scene.annotations[0]' , annNew ) ;
1490
+ } )
1491
+ . then ( function ( ) {
1492
+ assertAnnotationText ( [ 'new!' , '1' , '2' , '3' ] , 'after add new (1)' ) ;
1493
+
1494
+ return Plotly . relayout ( gd , 'scene.annotations' , null ) ;
1495
+ } )
1496
+ . then ( function ( ) {
1497
+ assertAnnotationText ( [ ] , 'after rm all' ) ;
1498
+
1499
+ return Plotly . relayout ( gd , 'scene.annotations[0]' , annNew ) ;
1500
+ } )
1501
+ . then ( function ( ) {
1502
+ assertAnnotationText ( [ 'new!' ] , 'after add new (2)' ) ;
1503
+ } )
1504
+ . catch ( fail )
1505
+ . then ( done ) ;
1506
+ } ) ;
1507
+
1508
+ it ( 'should work across multiple scenes' , function ( done ) {
1509
+ function assertAnnotationCntPerScene ( id , cnt ) {
1510
+ expect ( d3 . selectAll ( 'g.annotation-' + id ) . size ( ) ) . toEqual ( cnt ) ;
1511
+ }
1512
+
1513
+ Plotly . plot ( gd , [ {
1514
+ type : 'scatter3d' ,
1515
+ x : [ 1 , 2 , 3 ] ,
1516
+ y : [ 1 , 2 , 3 ] ,
1517
+ z : [ 1 , 2 , 1 ]
1518
+ } , {
1519
+ type : 'scatter3d' ,
1520
+ x : [ 1 , 2 , 3 ] ,
1521
+ y : [ 1 , 2 , 3 ] ,
1522
+ z : [ 2 , 1 , 2 ] ,
1523
+ scene : 'scene2'
1524
+ } ] , {
1525
+ scene : {
1526
+ annotations : [ {
1527
+ text : 'hello' ,
1528
+ x : 1 , y : 1 , z : 1
1529
+ } ]
1530
+ } ,
1531
+ scene2 : {
1532
+ annotations : [ {
1533
+ text : 'sup?' ,
1534
+ x : 1 , y : 1 , z : 2
1535
+ } , {
1536
+ text : 'look!' ,
1537
+ x : 2 , y : 2 , z : 1
1538
+ } ]
1539
+ }
1540
+ } )
1541
+ . then ( function ( ) {
1542
+ assertAnnotationCntPerScene ( 'scene' , 1 ) ;
1543
+ assertAnnotationCntPerScene ( 'scene2' , 2 ) ;
1544
+
1545
+ return Plotly . deleteTraces ( gd , [ 1 ] ) ;
1546
+ } )
1547
+ . then ( function ( ) {
1548
+ assertAnnotationCntPerScene ( 'scene' , 1 ) ;
1549
+ assertAnnotationCntPerScene ( 'scene2' , 0 ) ;
1550
+
1551
+ return Plotly . deleteTraces ( gd , [ 0 ] ) ;
1552
+ } )
1553
+ . then ( function ( ) {
1554
+ assertAnnotationCntPerScene ( 'scene' , 0 ) ;
1555
+ assertAnnotationCntPerScene ( 'scene2' , 0 ) ;
1556
+ } )
1557
+ . catch ( fail )
1558
+ . then ( done ) ;
1559
+ } ) ;
1560
+
1561
+ it ( 'should contribute to scene axis autorange' , function ( done ) {
1562
+ function assertSceneAxisRanges ( xRange , yRange , zRange ) {
1563
+ var sceneLayout = gd . _fullLayout . scene ;
1564
+
1565
+ expect ( sceneLayout . xaxis . range ) . toBeCloseToArray ( xRange , 1 , 'xaxis range' ) ;
1566
+ expect ( sceneLayout . yaxis . range ) . toBeCloseToArray ( yRange , 1 , 'yaxis range' ) ;
1567
+ expect ( sceneLayout . zaxis . range ) . toBeCloseToArray ( zRange , 1 , 'zaxis range' ) ;
1568
+ }
1569
+
1570
+ Plotly . plot ( gd , [ {
1571
+ type : 'scatter3d' ,
1572
+ x : [ 1 , 2 , 3 ] ,
1573
+ y : [ 1 , 2 , 3 ] ,
1574
+ z : [ 1 , 2 , 1 ]
1575
+ } ] , {
1576
+ scene : {
1577
+ annotations : [ {
1578
+ text : 'hello' ,
1579
+ x : 1 , y : 1 , z : 3
1580
+ } ]
1581
+ }
1582
+ } )
1583
+ . then ( function ( ) {
1584
+ assertSceneAxisRanges ( [ 0.9375 , 3.0625 ] , [ 0.9375 , 3.0625 ] , [ 0.9375 , 3.0625 ] ) ;
1585
+
1586
+ return Plotly . relayout ( gd , 'scene.annotations[0].z' , 10 ) ;
1587
+ } )
1588
+ . then ( function ( ) {
1589
+ assertSceneAxisRanges ( [ 0.9375 , 3.0625 ] , [ 0.9375 , 3.0625 ] , [ 0.7187 , 10.2813 ] ) ;
1590
+ } )
1591
+ . catch ( fail )
1592
+ . then ( done ) ;
1593
+ } ) ;
1594
+
1595
+ it ( 'should allow text and tail position edits under `editable: true`' , function ( done ) {
1596
+ function editText ( newText , expectation ) {
1597
+ return new Promise ( function ( resolve ) {
1598
+ gd . once ( 'plotly_relayout' , function ( eventData ) {
1599
+ expect ( eventData ) . toEqual ( expectation ) ;
1600
+ setTimeout ( resolve , 0 ) ;
1601
+ } ) ;
1602
+
1603
+ var clickNode = d3 . select ( 'g.annotation-text-g' ) . select ( 'g' ) . node ( ) ;
1604
+ clickNode . dispatchEvent ( new window . MouseEvent ( 'click' ) ) ;
1605
+
1606
+ var editNode = d3 . select ( '.plugin-editable.editable' ) . node ( ) ;
1607
+ editNode . dispatchEvent ( new window . FocusEvent ( 'focus' ) ) ;
1608
+
1609
+ editNode . textContent = newText ;
1610
+ editNode . dispatchEvent ( new window . FocusEvent ( 'focus' ) ) ;
1611
+ editNode . dispatchEvent ( new window . FocusEvent ( 'blur' ) ) ;
1612
+ } ) ;
1613
+ }
1614
+
1615
+ function moveArrowTail ( dx , dy , expectation ) {
1616
+ var px = 243 ;
1617
+ var py = 150 ;
1618
+
1619
+ return new Promise ( function ( resolve ) {
1620
+ gd . once ( 'plotly_relayout' , function ( eventData ) {
1621
+ expect ( eventData ) . toEqual ( expectation ) ;
1622
+ resolve ( ) ;
1623
+ } ) ;
1624
+
1625
+ mouseEvent ( 'mousemove' , px , py ) ;
1626
+ mouseEvent ( 'mousedown' , px , py ) ;
1627
+ mouseEvent ( 'mousemove' , px + dx , py + dy ) ;
1628
+ mouseEvent ( 'mouseup' , px + dx , py + dy ) ;
1629
+ } ) ;
1630
+ }
1631
+
1632
+ Plotly . plot ( gd , [ {
1633
+ type : 'scatter3d' ,
1634
+ x : [ 1 , 2 , 3 ] ,
1635
+ y : [ 1 , 2 , 3 ] ,
1636
+ z : [ 1 , 2 , 1 ]
1637
+ } ] , {
1638
+ scene : {
1639
+ annotations : [ {
1640
+ text : 'hello' ,
1641
+ x : 2 , y : 2 , z : 2 ,
1642
+ font : { size : 30 }
1643
+ } ]
1644
+ } ,
1645
+ margin : { l : 0 , t : 0 , r : 0 , b : 0 } ,
1646
+ width : 500 ,
1647
+ height : 500
1648
+ } , {
1649
+ editable : true
1650
+ } )
1651
+ . then ( function ( ) {
1652
+ return editText ( 'allo' , { 'scene.annotations[0].text' : 'allo' } ) ;
1653
+ } )
1654
+ . then ( function ( ) {
1655
+ return moveArrowTail ( - 100 , - 50 , {
1656
+ 'scene.annotations[0].ax' : - 110 ,
1657
+ 'scene.annotations[0].ay' : - 80
1658
+ } ) ;
1659
+ } )
1660
+ . catch ( fail )
1661
+ . then ( done ) ;
1662
+ } ) ;
1663
+ } ) ;
0 commit comments