@@ -765,6 +765,10 @@ axes.calcTicks = function calcTicks(ax) {
765
765
minPx = ax . _id . charAt ( 0 ) === 'y' ? 40 : 80 ;
766
766
nt = Lib . constrain ( ax . _length / minPx , 4 , 9 ) + 1 ;
767
767
}
768
+
769
+ // radial axes span half their domain,
770
+ // multiply nticks value by two to get correct number of auto ticks.
771
+ if ( ax . _name === 'radialaxis' ) nt *= 2 ;
768
772
}
769
773
770
774
// add a couple of extra digits for filling in ticks when we
@@ -819,6 +823,13 @@ axes.calcTicks = function calcTicks(ax) {
819
823
vals . push ( x ) ;
820
824
}
821
825
826
+ // If same angle over a full circle, the last tick vals is a duplicate.
827
+ //
828
+ // TODO must do something similar for angular date axes.
829
+ if ( ax . _id === 'angular' && Math . abs ( rng [ 1 ] - rng [ 0 ] ) === 360 ) {
830
+ vals . pop ( ) ;
831
+ }
832
+
822
833
// save the last tick as well as first, so we can
823
834
// show the exponent only on the last one
824
835
ax . _tmax = vals [ vals . length - 1 ] ;
@@ -886,7 +897,11 @@ var roundBase10 = [2, 5, 10],
886
897
// approx. tick positions for log axes, showing all (1) and just 1, 2, 5 (2)
887
898
// these don't have to be exact, just close enough to round to the right value
888
899
roundLog1 = [ - 0.046 , 0 , 0.301 , 0.477 , 0.602 , 0.699 , 0.778 , 0.845 , 0.903 , 0.954 , 1 ] ,
889
- roundLog2 = [ - 0.301 , 0 , 0.301 , 0.699 , 1 ] ;
900
+ roundLog2 = [ - 0.301 , 0 , 0.301 , 0.699 , 1 ] ,
901
+ // TODO
902
+ // maybe [1, 2, 5, 10, 15, 30, 45, 90, 180] would give better results
903
+ // on thin polar sectors?
904
+ roundAngles = [ 15 , 30 , 45 , 90 , 180 ] ;
890
905
891
906
function roundDTick ( roughDTick , base , roundingSet ) {
892
907
return base * Lib . roundUp ( roughDTick / base , roundingSet ) ;
@@ -911,6 +926,10 @@ function roundDTick(roughDTick, base, roundingSet) {
911
926
axes . autoTicks = function ( ax , roughDTick ) {
912
927
var base ;
913
928
929
+ function getBase ( v ) {
930
+ return Math . pow ( v , Math . floor ( Math . log ( roughDTick ) / Math . LN10 ) ) ;
931
+ }
932
+
914
933
if ( ax . type === 'date' ) {
915
934
ax . tick0 = Lib . dateTick0 ( ax . calendar ) ;
916
935
// the criteria below are all based on the rough spacing we calculate
@@ -919,7 +938,7 @@ axes.autoTicks = function(ax, roughDTick) {
919
938
920
939
if ( roughX2 > ONEAVGYEAR ) {
921
940
roughDTick /= ONEAVGYEAR ;
922
- base = Math . pow ( 10 , Math . floor ( Math . log ( roughDTick ) / Math . LN10 ) ) ;
941
+ base = getBase ( 10 ) ;
923
942
ax . dtick = 'M' + ( 12 * roundDTick ( roughDTick , base , roundBase10 ) ) ;
924
943
}
925
944
else if ( roughX2 > ONEAVGMONTH ) {
@@ -944,7 +963,7 @@ axes.autoTicks = function(ax, roughDTick) {
944
963
}
945
964
else {
946
965
// milliseconds
947
- base = Math . pow ( 10 , Math . floor ( Math . log ( roughDTick ) / Math . LN10 ) ) ;
966
+ base = getBase ( 10 ) ;
948
967
ax . dtick = roundDTick ( roughDTick , base , roundBase10 ) ;
949
968
}
950
969
}
@@ -963,7 +982,7 @@ axes.autoTicks = function(ax, roughDTick) {
963
982
// ticks on a linear scale, labeled fully
964
983
roughDTick = Math . abs ( Math . pow ( 10 , rng [ 1 ] ) -
965
984
Math . pow ( 10 , rng [ 0 ] ) ) / nt ;
966
- base = Math . pow ( 10 , Math . floor ( Math . log ( roughDTick ) / Math . LN10 ) ) ;
985
+ base = getBase ( 10 ) ;
967
986
ax . dtick = 'L' + roundDTick ( roughDTick , base , roundBase10 ) ;
968
987
}
969
988
else {
@@ -977,10 +996,15 @@ axes.autoTicks = function(ax, roughDTick) {
977
996
ax . tick0 = 0 ;
978
997
ax . dtick = Math . ceil ( Math . max ( roughDTick , 1 ) ) ;
979
998
}
999
+ else if ( ax . _id === 'angular' ) {
1000
+ ax . tick0 = 0 ;
1001
+ base = getBase ( 1 ) ;
1002
+ ax . dtick = roundDTick ( roughDTick , base , roundAngles ) ;
1003
+ }
980
1004
else {
981
1005
// auto ticks always start at 0
982
1006
ax . tick0 = 0 ;
983
- base = Math . pow ( 10 , Math . floor ( Math . log ( roughDTick ) / Math . LN10 ) ) ;
1007
+ base = getBase ( 10 ) ;
984
1008
ax . dtick = roundDTick ( roughDTick , base , roundBase10 ) ;
985
1009
}
986
1010
@@ -1208,6 +1232,7 @@ axes.tickText = function(ax, x, hover) {
1208
1232
if ( ax . type === 'date' ) formatDate ( ax , out , hover , extraPrecision ) ;
1209
1233
else if ( ax . type === 'log' ) formatLog ( ax , out , hover , extraPrecision , hideexp ) ;
1210
1234
else if ( ax . type === 'category' ) formatCategory ( ax , out ) ;
1235
+ else if ( ax . _id === 'angular' ) formatAngle ( ax , out , hover , extraPrecision , hideexp ) ;
1211
1236
else formatLinear ( ax , out , hover , extraPrecision , hideexp ) ;
1212
1237
1213
1238
// add prefix and suffix
@@ -1402,6 +1427,66 @@ function formatLinear(ax, out, hover, extraPrecision, hideexp) {
1402
1427
out . text = numFormat ( out . x , ax , hideexp , extraPrecision ) ;
1403
1428
}
1404
1429
1430
+ function formatAngle ( ax , out , hover , extraPrecision , hideexp ) {
1431
+ if ( ax . thetaunit === 'radians' && ! hover ) {
1432
+ var isNeg = out . x < 0 ;
1433
+ var num = out . x / 180 ;
1434
+
1435
+ if ( num === 0 ) {
1436
+ out . text = '0' ;
1437
+ } else {
1438
+ var frac = num2frac ( num ) ;
1439
+
1440
+ if ( frac [ 1 ] === 1 ) {
1441
+ if ( frac [ 0 ] === 1 ) out . text = 'π' ;
1442
+ else out . text = frac [ 0 ] + 'π' ;
1443
+ } else {
1444
+ out . text = [
1445
+ '<sup>' , frac [ 0 ] , '</sup>' ,
1446
+ '⁄' ,
1447
+ '<sub>' , frac [ 1 ] , '</sub>' ,
1448
+ 'π'
1449
+ ] . join ( '' ) ;
1450
+ }
1451
+ }
1452
+
1453
+ if ( isNeg ) out . text = MINUS_SIGN + out . text ;
1454
+ } else {
1455
+ out . text = numFormat ( out . x , ax , hideexp , extraPrecision ) ;
1456
+ }
1457
+ }
1458
+
1459
+ // inspired by
1460
+ // https://github.com/yisibl/num2fraction/blob/master/index.js
1461
+ function num2frac ( num ) {
1462
+ function almostEq ( a , b ) {
1463
+ return Math . abs ( a - b ) <= 1e-6 ;
1464
+ }
1465
+
1466
+ function findGCD ( a , b ) {
1467
+ return almostEq ( b , 0 ) ? a : findGCD ( b , a % b ) ;
1468
+ }
1469
+
1470
+ function findPrecision ( n ) {
1471
+ var e = 1 ;
1472
+ while ( ! almostEq ( Math . round ( n * e ) / e , n ) ) {
1473
+ e *= 10 ;
1474
+ }
1475
+ return e ;
1476
+ }
1477
+
1478
+ var precision = findPrecision ( num ) ;
1479
+ var number = num * precision ;
1480
+ var gcd = Math . abs ( findGCD ( number , precision ) ) ;
1481
+
1482
+ return [
1483
+ // numerator
1484
+ Math . round ( number / gcd ) ,
1485
+ // denominator
1486
+ Math . round ( precision / gcd )
1487
+ ] ;
1488
+ }
1489
+
1405
1490
// format a number (tick value) according to the axis settings
1406
1491
// new, more reliable procedure than d3.round or similar:
1407
1492
// add half the rounding increment, then stringify and truncate
@@ -1846,7 +1931,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
1846
1931
// positioning arguments for x vs y axes
1847
1932
if ( axLetter === 'x' ) {
1848
1933
sides = [ 'bottom' , 'top' ] ;
1849
- transfn = function ( d ) {
1934
+ transfn = ax . _transfn || function ( d ) {
1850
1935
return 'translate(' + ax . l2p ( d . x ) + ',0)' ;
1851
1936
} ;
1852
1937
tickpathfn = function ( shift , len ) {
@@ -1859,7 +1944,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
1859
1944
}
1860
1945
else if ( axLetter === 'y' ) {
1861
1946
sides = [ 'left' , 'right' ] ;
1862
- transfn = function ( d ) {
1947
+ transfn = ax . _transfn || function ( d ) {
1863
1948
return 'translate(0,' + ax . l2p ( d . x ) + ')' ;
1864
1949
} ;
1865
1950
tickpathfn = function ( shift , len ) {
@@ -1870,6 +1955,13 @@ axes.doTicks = function(gd, axid, skipTitle) {
1870
1955
else return 'M' + shift + ',0h' + len ;
1871
1956
} ;
1872
1957
}
1958
+ else if ( axid === 'angular' ) {
1959
+ sides = [ 'left' , 'right' ] ;
1960
+ transfn = ax . _transfn ;
1961
+ tickpathfn = function ( shift , len ) {
1962
+ return 'M' + shift + ',0h' + len ;
1963
+ } ;
1964
+ }
1873
1965
else {
1874
1966
Lib . warn ( 'Unrecognized doTicks axis:' , axid ) ;
1875
1967
return ;
@@ -1893,6 +1985,11 @@ axes.doTicks = function(gd, axid, skipTitle) {
1893
1985
}
1894
1986
var valsClipped = vals . filter ( clipEnds ) ;
1895
1987
1988
+ // don't clip angular values
1989
+ if ( ax . _id === 'angular' ) {
1990
+ valsClipped = vals ;
1991
+ }
1992
+
1896
1993
function drawTicks ( container , tickpath ) {
1897
1994
var ticks = container . selectAll ( 'path.' + tcls )
1898
1995
. data ( ax . ticks === 'inside' ? valsClipped : vals , datafn ) ;
@@ -1941,7 +2038,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
1941
2038
return ( angle * flipit < 0 ) ? 'end' : 'start' ;
1942
2039
} ;
1943
2040
}
1944
- else {
2041
+ else if ( axLetter === 'y' ) {
1945
2042
flipit = ( axside === 'right' ) ? 1 : - 1 ;
1946
2043
labely = function ( d ) {
1947
2044
return d . dy + d . fontSize * MID_SHIFT - labelShift * flipit ;
@@ -1957,6 +2054,16 @@ axes.doTicks = function(gd, axid, skipTitle) {
1957
2054
return axside === 'right' ? 'start' : 'end' ;
1958
2055
} ;
1959
2056
}
2057
+ else if ( axid === 'angular' ) {
2058
+ ax . _labelShift = labelShift ;
2059
+ ax . _labelStandoff = labelStandoff ;
2060
+ ax . _pad = pad ;
2061
+
2062
+ labelx = ax . _labelx ;
2063
+ labely = ax . _labely ;
2064
+ labelanchor = ax . _labelanchor ;
2065
+ }
2066
+
1960
2067
var maxFontSize = 0 ,
1961
2068
autoangle = 0 ,
1962
2069
labelsReady = [ ] ;
@@ -1996,7 +2103,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
1996
2103
1997
2104
function positionLabels ( s , angle ) {
1998
2105
s . each ( function ( d ) {
1999
- var anchor = labelanchor ( angle ) ;
2106
+ var anchor = labelanchor ( angle , d ) ;
2000
2107
var thisLabel = d3 . select ( this ) ,
2001
2108
mathjaxGroup = thisLabel . select ( '.text-math-group' ) ,
2002
2109
transform = transfn ( d ) +
0 commit comments