@@ -221,6 +221,7 @@ var SYMBOLDEFS = require('./symbol_defs');
221
221
222
222
drawing . symbolNames = [ ] ;
223
223
drawing . symbolFuncs = [ ] ;
224
+ drawing . symbolBackOffs = [ ] ;
224
225
drawing . symbolNeedLines = { } ;
225
226
drawing . symbolNoDot = { } ;
226
227
drawing . symbolNoFill = { } ;
@@ -240,6 +241,7 @@ Object.keys(SYMBOLDEFS).forEach(function(k) {
240
241
) ;
241
242
drawing . symbolNames [ n ] = k ;
242
243
drawing . symbolFuncs [ n ] = symDef . f ;
244
+ drawing . symbolBackOffs [ n ] = symDef . backoff || 0 ;
243
245
244
246
if ( symDef . needLine ) {
245
247
drawing . symbolNeedLines [ n ] = true ;
@@ -287,9 +289,9 @@ drawing.symbolNumber = function(v) {
287
289
0 : Math . floor ( Math . max ( v , 0 ) ) ;
288
290
} ;
289
291
290
- function makePointPath ( symbolNumber , r ) {
292
+ function makePointPath ( symbolNumber , r , t , s ) {
291
293
var base = symbolNumber % 100 ;
292
- return drawing . symbolFuncs [ base ] ( r ) + ( symbolNumber >= 200 ? DOTPATH : '' ) ;
294
+ return drawing . symbolFuncs [ base ] ( r , t , s ) + ( symbolNumber >= 200 ? DOTPATH : '' ) ;
293
295
}
294
296
295
297
var HORZGRADIENT = { x1 : 1 , x2 : 0 , y1 : 0 , y2 : 0 } ;
@@ -660,7 +662,10 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd) {
660
662
// because that impacts how to handle colors
661
663
d . om = x % 200 >= 100 ;
662
664
663
- sel . attr ( 'd' , makePointPath ( x , r ) ) ;
665
+ var angle = getMarkerAngle ( d , trace ) ;
666
+ var standoff = getMarkerStandoff ( d , trace ) ;
667
+
668
+ sel . attr ( 'd' , makePointPath ( x , r , angle , standoff ) ) ;
664
669
}
665
670
666
671
var perPointGradient = false ;
@@ -909,7 +914,7 @@ drawing.selectedPointStyle = function(s, trace) {
909
914
var mx = d . mx || marker . symbol || 0 ;
910
915
var mrc2 = fns . selectedSizeFn ( d ) ;
911
916
912
- pt . attr ( 'd' , makePointPath ( drawing . symbolNumber ( mx ) , mrc2 ) ) ;
917
+ pt . attr ( 'd' , makePointPath ( drawing . symbolNumber ( mx ) , mrc2 , getMarkerAngle ( d , trace ) , getMarkerStandoff ( d , trace ) ) ) ;
913
918
914
919
// save for Drawing.selectedTextStyle
915
920
d . mrc2 = mrc2 ;
@@ -1080,6 +1085,26 @@ drawing.smoothclosed = function(pts, smoothness) {
1080
1085
return path ;
1081
1086
} ;
1082
1087
1088
+ var lastDrawnX , lastDrawnY ;
1089
+
1090
+ function roundEnd ( pt , isY , isLastPoint ) {
1091
+ if ( isLastPoint ) pt = applyBackoff ( pt ) ;
1092
+
1093
+ return isY ? roundY ( pt [ 1 ] ) : roundX ( pt [ 0 ] ) ;
1094
+ }
1095
+
1096
+ function roundX ( p ) {
1097
+ var v = d3 . round ( p , 2 ) ;
1098
+ lastDrawnX = v ;
1099
+ return v ;
1100
+ }
1101
+
1102
+ function roundY ( p ) {
1103
+ var v = d3 . round ( p , 2 ) ;
1104
+ lastDrawnY = v ;
1105
+ return v ;
1106
+ }
1107
+
1083
1108
function makeTangent ( prevpt , thispt , nextpt , smoothness ) {
1084
1109
var d1x = prevpt [ 0 ] - thispt [ 0 ] ;
1085
1110
var d1y = prevpt [ 1 ] - thispt [ 1 ] ;
@@ -1093,47 +1118,111 @@ function makeTangent(prevpt, thispt, nextpt, smoothness) {
1093
1118
var denom2 = 3 * d1a * ( d1a + d2a ) ;
1094
1119
return [
1095
1120
[
1096
- d3 . round ( thispt [ 0 ] + ( denom1 && numx / denom1 ) , 2 ) ,
1097
- d3 . round ( thispt [ 1 ] + ( denom1 && numy / denom1 ) , 2 )
1121
+ roundX ( thispt [ 0 ] + ( denom1 && numx / denom1 ) ) ,
1122
+ roundY ( thispt [ 1 ] + ( denom1 && numy / denom1 ) )
1098
1123
] , [
1099
- d3 . round ( thispt [ 0 ] - ( denom2 && numx / denom2 ) , 2 ) ,
1100
- d3 . round ( thispt [ 1 ] - ( denom2 && numy / denom2 ) , 2 )
1124
+ roundX ( thispt [ 0 ] - ( denom2 && numx / denom2 ) ) ,
1125
+ roundY ( thispt [ 1 ] - ( denom2 && numy / denom2 ) )
1101
1126
]
1102
1127
] ;
1103
1128
}
1104
1129
1105
1130
// step paths - returns a generator function for paths
1106
1131
// with the given step shape
1107
1132
var STEPPATH = {
1108
- hv : function ( p0 , p1 ) {
1109
- return 'H' + d3 . round ( p1 [ 0 ] , 2 ) + 'V' + d3 . round ( p1 [ 1 ] , 2 ) ;
1133
+ hv : function ( p0 , p1 , isLastPoint ) {
1134
+ return 'H' +
1135
+ roundX ( p1 [ 0 ] ) + 'V' +
1136
+ roundEnd ( p1 , 1 , isLastPoint ) ;
1110
1137
} ,
1111
- vh : function ( p0 , p1 ) {
1112
- return 'V' + d3 . round ( p1 [ 1 ] , 2 ) + 'H' + d3 . round ( p1 [ 0 ] , 2 ) ;
1138
+ vh : function ( p0 , p1 , isLastPoint ) {
1139
+ return 'V' +
1140
+ roundY ( p1 [ 1 ] ) + 'H' +
1141
+ roundEnd ( p1 , 0 , isLastPoint ) ;
1113
1142
} ,
1114
- hvh : function ( p0 , p1 ) {
1115
- return 'H' + d3 . round ( ( p0 [ 0 ] + p1 [ 0 ] ) / 2 , 2 ) + 'V' +
1116
- d3 . round ( p1 [ 1 ] , 2 ) + 'H' + d3 . round ( p1 [ 0 ] , 2 ) ;
1143
+ hvh : function ( p0 , p1 , isLastPoint ) {
1144
+ return 'H' +
1145
+ roundX ( ( p0 [ 0 ] + p1 [ 0 ] ) / 2 ) + 'V' +
1146
+ roundY ( p1 [ 1 ] ) + 'H' +
1147
+ roundEnd ( p1 , 0 , isLastPoint ) ;
1117
1148
} ,
1118
- vhv : function ( p0 , p1 ) {
1119
- return 'V' + d3 . round ( ( p0 [ 1 ] + p1 [ 1 ] ) / 2 , 2 ) + 'H' +
1120
- d3 . round ( p1 [ 0 ] , 2 ) + 'V' + d3 . round ( p1 [ 1 ] , 2 ) ;
1149
+ vhv : function ( p0 , p1 , isLastPoint ) {
1150
+ return 'V' +
1151
+ roundY ( ( p0 [ 1 ] + p1 [ 1 ] ) / 2 ) + 'H' +
1152
+ roundX ( p1 [ 0 ] ) + 'V' +
1153
+ roundEnd ( p1 , 1 , isLastPoint ) ;
1121
1154
}
1122
1155
} ;
1123
- var STEPLINEAR = function ( p0 , p1 ) {
1124
- return 'L' + d3 . round ( p1 [ 0 ] , 2 ) + ',' + d3 . round ( p1 [ 1 ] , 2 ) ;
1156
+ var STEPLINEAR = function ( p0 , p1 , isLastPoint ) {
1157
+ return 'L' +
1158
+ roundEnd ( p1 , 0 , isLastPoint ) + ',' +
1159
+ roundEnd ( p1 , 1 , isLastPoint ) ;
1125
1160
} ;
1126
1161
drawing . steps = function ( shape ) {
1127
1162
var onestep = STEPPATH [ shape ] || STEPLINEAR ;
1128
1163
return function ( pts ) {
1129
- var path = 'M' + d3 . round ( pts [ 0 ] [ 0 ] , 2 ) + ',' + d3 . round ( pts [ 0 ] [ 1 ] , 2 ) ;
1130
- for ( var i = 1 ; i < pts . length ; i ++ ) {
1131
- path += onestep ( pts [ i - 1 ] , pts [ i ] ) ;
1164
+ var path = 'M' + roundX ( pts [ 0 ] [ 0 ] ) + ',' + roundY ( pts [ 0 ] [ 1 ] ) ;
1165
+ var len = pts . length ;
1166
+ for ( var i = 1 ; i < len ; i ++ ) {
1167
+ path += onestep ( pts [ i - 1 ] , pts [ i ] , i === len - 1 ) ;
1132
1168
}
1133
1169
return path ;
1134
1170
} ;
1135
1171
} ;
1136
1172
1173
+ function applyBackoff ( pt , start ) {
1174
+ var backoff = pt . backoff ;
1175
+ var trace = pt . trace ;
1176
+ var d = pt . d ;
1177
+ var i = pt . i ;
1178
+
1179
+ if ( backoff && trace &&
1180
+ trace . marker &&
1181
+ trace . marker . angle % 360 === 0 &&
1182
+ trace . line &&
1183
+ trace . line . shape !== 'spline'
1184
+ ) {
1185
+ var arrayBackoff = Lib . isArrayOrTypedArray ( backoff ) ;
1186
+ var end = pt ;
1187
+
1188
+ var x1 = start ? start [ 0 ] : lastDrawnX || 0 ;
1189
+ var y1 = start ? start [ 1 ] : lastDrawnY || 0 ;
1190
+
1191
+ var x2 = end [ 0 ] ;
1192
+ var y2 = end [ 1 ] ;
1193
+
1194
+ var dx = x2 - x1 ;
1195
+ var dy = y2 - y1 ;
1196
+
1197
+ var t = Math . atan2 ( dy , dx ) ;
1198
+
1199
+ var b = arrayBackoff ? backoff [ i ] : backoff ;
1200
+
1201
+ if ( b === 'auto' ) {
1202
+ var endI = end . i ;
1203
+ if ( trace . type === 'scatter' ) endI -- ; // Why we need this hack?
1204
+
1205
+ var endMarker = end . marker ;
1206
+ b = endMarker ? drawing . symbolBackOffs [ drawing . symbolNumber ( endMarker . symbol ) ] * endMarker . size : 0 ;
1207
+ b += drawing . getMarkerStandoff ( d [ endI ] , trace ) || 0 ;
1208
+ }
1209
+
1210
+ var x = x2 - b * Math . cos ( t ) ;
1211
+ var y = y2 - b * Math . sin ( t ) ;
1212
+
1213
+ if (
1214
+ ( ( x <= x2 && x >= x1 ) || ( x >= x2 && x <= x1 ) ) &&
1215
+ ( ( y <= y2 && y >= y1 ) || ( y >= y2 && y <= y1 ) )
1216
+ ) {
1217
+ pt = [ x , y ] ;
1218
+ }
1219
+ }
1220
+
1221
+ return pt ;
1222
+ }
1223
+
1224
+ drawing . applyBackoff = applyBackoff ;
1225
+
1137
1226
// off-screen svg render testing element, shared by the whole page
1138
1227
// uses the id 'js-plotly-tester' and stores it in drawing.tester
1139
1228
drawing . makeTester = function ( ) {
@@ -1458,3 +1547,168 @@ drawing.setTextPointsScale = function(selection, xScale, yScale) {
1458
1547
el . attr ( 'transform' , transforms . join ( '' ) ) ;
1459
1548
} ) ;
1460
1549
} ;
1550
+
1551
+ function getMarkerStandoff ( d , trace ) {
1552
+ var standoff ;
1553
+
1554
+ if ( d ) standoff = d . mf ;
1555
+
1556
+ if ( standoff === undefined ) {
1557
+ standoff = trace . marker ? trace . marker . standoff || 0 : 0 ;
1558
+ }
1559
+
1560
+ if ( ! trace . _geo && ! trace . _xA ) {
1561
+ // case of legends
1562
+ return - standoff ;
1563
+ }
1564
+
1565
+ return standoff ;
1566
+ }
1567
+
1568
+ drawing . getMarkerStandoff = getMarkerStandoff ;
1569
+
1570
+ var atan2 = Math . atan2 ;
1571
+ var cos = Math . cos ;
1572
+ var sin = Math . sin ;
1573
+
1574
+ function rotate ( t , xy ) {
1575
+ var x = xy [ 0 ] ;
1576
+ var y = xy [ 1 ] ;
1577
+ return [
1578
+ x * cos ( t ) - y * sin ( t ) ,
1579
+ x * sin ( t ) + y * cos ( t )
1580
+ ] ;
1581
+ }
1582
+
1583
+ var previousLon ;
1584
+ var previousLat ;
1585
+ var previousX ;
1586
+ var previousY ;
1587
+ var previousI ;
1588
+ var previousTraceUid ;
1589
+
1590
+ function getMarkerAngle ( d , trace ) {
1591
+ var angle = d . ma ;
1592
+
1593
+ if ( angle === undefined ) {
1594
+ angle = trace . marker . angle || 0 ;
1595
+ }
1596
+
1597
+ var x , y ;
1598
+ var ref = trace . marker . angleref ;
1599
+ if ( ref === 'previous' || ref === 'north' ) {
1600
+ if ( trace . _geo ) {
1601
+ var p = trace . _geo . project ( d . lonlat ) ;
1602
+ x = p [ 0 ] ;
1603
+ y = p [ 1 ] ;
1604
+ } else {
1605
+ var xa = trace . _xA ;
1606
+ var ya = trace . _yA ;
1607
+ if ( xa && ya ) {
1608
+ x = xa . c2p ( d . x ) ;
1609
+ y = ya . c2p ( d . y ) ;
1610
+ } else {
1611
+ // case of legends
1612
+ return 90 ;
1613
+ }
1614
+ }
1615
+
1616
+ if ( trace . _geo ) {
1617
+ var lon = d . lonlat [ 0 ] ;
1618
+ var lat = d . lonlat [ 1 ] ;
1619
+
1620
+ var north = trace . _geo . project ( [
1621
+ lon ,
1622
+ lat + 1e-5 // epsilon
1623
+ ] ) ;
1624
+
1625
+ var east = trace . _geo . project ( [
1626
+ lon + 1e-5 , // epsilon
1627
+ lat
1628
+ ] ) ;
1629
+
1630
+ var u = atan2 (
1631
+ east [ 1 ] - y ,
1632
+ east [ 0 ] - x
1633
+ ) ;
1634
+
1635
+ var v = atan2 (
1636
+ north [ 1 ] - y ,
1637
+ north [ 0 ] - x
1638
+ ) ;
1639
+
1640
+ var t ;
1641
+ if ( ref === 'north' ) {
1642
+ t = angle / 180 * Math . PI ;
1643
+ // To use counter-clockwise angles i.e.
1644
+ // East: 90, West: -90
1645
+ // to facilitate wind visualisations
1646
+ // in future we should use t = -t here.
1647
+ } else if ( ref === 'previous' ) {
1648
+ var lon1 = lon / 180 * Math . PI ;
1649
+ var lat1 = lat / 180 * Math . PI ;
1650
+ var lon2 = previousLon / 180 * Math . PI ;
1651
+ var lat2 = previousLat / 180 * Math . PI ;
1652
+
1653
+ var dLon = lon2 - lon1 ;
1654
+
1655
+ var deltaY = cos ( lat2 ) * sin ( dLon ) ;
1656
+ var deltaX = sin ( lat2 ) * cos ( lat1 ) - cos ( lat2 ) * sin ( lat1 ) * cos ( dLon ) ;
1657
+
1658
+ t = - atan2 (
1659
+ deltaY ,
1660
+ deltaX
1661
+ ) - Math . PI ;
1662
+
1663
+ previousLon = lon ;
1664
+ previousLat = lat ;
1665
+ }
1666
+
1667
+ var A = rotate ( u , [ cos ( t ) , 0 ] ) ;
1668
+ var B = rotate ( v , [ sin ( t ) , 0 ] ) ;
1669
+
1670
+ angle = atan2 (
1671
+ A [ 1 ] + B [ 1 ] ,
1672
+ A [ 0 ] + B [ 0 ]
1673
+ ) / Math . PI * 180 ;
1674
+
1675
+ if ( ref === 'previous' && ! (
1676
+ previousTraceUid === trace . uid &&
1677
+ d . i === previousI + 1
1678
+ ) ) {
1679
+ angle = null ;
1680
+ }
1681
+ }
1682
+
1683
+ if ( ref === 'previous' && ! trace . _geo ) {
1684
+ if (
1685
+ previousTraceUid === trace . uid &&
1686
+ d . i === previousI + 1 &&
1687
+ isNumeric ( x ) &&
1688
+ isNumeric ( y )
1689
+ ) {
1690
+ var dX = x - previousX ;
1691
+ var dY = y - previousY ;
1692
+
1693
+ var shape = trace . line ? trace . line . shape || '' : '' ;
1694
+
1695
+ var lastShapeChar = shape . slice ( shape . length - 1 ) ;
1696
+ if ( lastShapeChar === 'h' ) dY = 0 ;
1697
+ if ( lastShapeChar === 'v' ) dX = 0 ;
1698
+
1699
+ angle += atan2 ( dY , dX ) / Math . PI * 180 + 90 ;
1700
+ } else {
1701
+ angle = null ;
1702
+ }
1703
+ }
1704
+ }
1705
+
1706
+ previousX = x ;
1707
+ previousY = y ;
1708
+ previousI = d . i ;
1709
+ previousTraceUid = trace . uid ;
1710
+
1711
+ return angle ;
1712
+ }
1713
+
1714
+ drawing . getMarkerAngle = getMarkerAngle ;
0 commit comments