@@ -65,17 +65,6 @@ defaults._set('scale', {
65
65
}
66
66
} ) ;
67
67
68
- function labelsFromTicks ( ticks ) {
69
- var labels = [ ] ;
70
- var i , ilen ;
71
-
72
- for ( i = 0 , ilen = ticks . length ; i < ilen ; ++ i ) {
73
- labels . push ( ticks [ i ] . label ) ;
74
- }
75
-
76
- return labels ;
77
- }
78
-
79
68
function getPixelForGridLine ( scale , index , offsetGridLines ) {
80
69
var lineValue = scale . getPixelForTick ( index ) ;
81
70
@@ -93,10 +82,93 @@ function getPixelForGridLine(scale, index, offsetGridLines) {
93
82
return lineValue ;
94
83
}
95
84
96
- function computeTextSize ( context , tick , font ) {
97
- return helpers . isArray ( tick ) ?
98
- helpers . longestText ( context , font , tick ) :
99
- context . measureText ( tick ) . width ;
85
+ function garbageCollect ( caches , length ) {
86
+ helpers . each ( caches , function ( cache ) {
87
+ var gc = cache . gc ;
88
+ var gcLen = gc . length / 2 ;
89
+ var i ;
90
+ if ( gcLen > length ) {
91
+ for ( i = 0 ; i < gcLen ; ++ i ) {
92
+ delete cache . data [ gc [ i ] ] ;
93
+ }
94
+ gc . splice ( 0 , gcLen ) ;
95
+ }
96
+ } ) ;
97
+ }
98
+
99
+ /**
100
+ * Returns {width, height, offset} objects for the first, last, widest, highest tick
101
+ * labels where offset indicates the anchor point offset from the top in pixels.
102
+ */
103
+ function computeLabelSizes ( ctx , tickFonts , ticks , caches ) {
104
+ var length = ticks . length ;
105
+ var widths = [ ] ;
106
+ var heights = [ ] ;
107
+ var offsets = [ ] ;
108
+ var i , j , jlen , label , tickFont , fontString , cache , lineHeight , width , height , nestedLabel , widest , highest ;
109
+
110
+ for ( i = 0 ; i < length ; ++ i ) {
111
+ label = ticks [ i ] . label ;
112
+ tickFont = ticks [ i ] . major ? tickFonts . major : tickFonts . minor ;
113
+ ctx . font = fontString = tickFont . string ;
114
+ cache = caches [ fontString ] = caches [ fontString ] || { data : { } , gc : [ ] } ;
115
+ lineHeight = tickFont . lineHeight ;
116
+ width = height = 0 ;
117
+ // Undefined labels and arrays should not be measured
118
+ if ( ! helpers . isNullOrUndef ( label ) && ! helpers . isArray ( label ) ) {
119
+ width = helpers . measureText ( ctx , cache . data , cache . gc , width , label ) ;
120
+ height = lineHeight ;
121
+ } else if ( helpers . isArray ( label ) ) {
122
+ // if it is an array let's measure each element
123
+ for ( j = 0 , jlen = label . length ; j < jlen ; ++ j ) {
124
+ nestedLabel = label [ j ] ;
125
+ // Undefined labels and arrays should not be measured
126
+ if ( ! helpers . isNullOrUndef ( nestedLabel ) && ! helpers . isArray ( nestedLabel ) ) {
127
+ width = helpers . measureText ( ctx , cache . data , cache . gc , width , nestedLabel ) ;
128
+ height += lineHeight ;
129
+ }
130
+ }
131
+ }
132
+ widths . push ( width ) ;
133
+ heights . push ( height ) ;
134
+ offsets . push ( lineHeight / 2 ) ;
135
+ }
136
+ garbageCollect ( caches , length ) ;
137
+
138
+ widest = widths . indexOf ( Math . max . apply ( null , widths ) ) ;
139
+ highest = heights . indexOf ( Math . max . apply ( null , heights ) ) ;
140
+
141
+ function valueAt ( idx ) {
142
+ return {
143
+ width : widths [ idx ] || 0 ,
144
+ height : heights [ idx ] || 0 ,
145
+ offset : offsets [ idx ] || 0
146
+ } ;
147
+ }
148
+
149
+ return {
150
+ first : valueAt ( 0 ) ,
151
+ last : valueAt ( length - 1 ) ,
152
+ widest : valueAt ( widest ) ,
153
+ highest : valueAt ( highest )
154
+ } ;
155
+ }
156
+
157
+ function getTickMarkLength ( options ) {
158
+ return options . drawTicks ? options . tickMarkLength : 0 ;
159
+ }
160
+
161
+ function getScaleLabelHeight ( options ) {
162
+ var font , padding ;
163
+
164
+ if ( ! options . display ) {
165
+ return 0 ;
166
+ }
167
+
168
+ font = helpers . options . _parseFont ( options ) ;
169
+ padding = helpers . options . toPadding ( options . padding ) ;
170
+
171
+ return font . lineHeight + padding . height ;
100
172
}
101
173
102
174
function parseFontOptions ( options , nestedOpts ) {
@@ -330,39 +402,38 @@ module.exports = Element.extend({
330
402
} ,
331
403
calculateTickRotation : function ( ) {
332
404
var me = this ;
333
- var context = me . ctx ;
334
- var tickOpts = me . options . ticks ;
335
- var labels = labelsFromTicks ( me . _ticks ) ;
336
-
337
- // Get the width of each grid by calculating the difference
338
- // between x offsets between 0 and 1.
339
- var tickFont = helpers . options . _parseFont ( tickOpts ) ;
340
- context . font = tickFont . string ;
341
-
342
- var labelRotation = tickOpts . minRotation || 0 ;
343
-
344
- if ( labels . length && me . options . display && me . isHorizontal ( ) ) {
345
- var originalLabelWidth = helpers . longestText ( context , tickFont . string , labels , me . longestTextCache ) ;
346
- var labelWidth = originalLabelWidth ;
347
- var cosRotation , sinRotation ;
348
-
349
- // Allow 3 pixels x2 padding either side for label readability
350
- var tickWidth = me . getPixelForTick ( 1 ) - me . getPixelForTick ( 0 ) - 6 ;
351
-
352
- // Max label rotation can be set or default to 90 - also act as a loop counter
353
- while ( labelWidth > tickWidth && labelRotation < tickOpts . maxRotation ) {
354
- var angleRadians = helpers . toRadians ( labelRotation ) ;
355
- cosRotation = Math . cos ( angleRadians ) ;
356
- sinRotation = Math . sin ( angleRadians ) ;
357
-
358
- if ( sinRotation * originalLabelWidth > me . maxHeight ) {
359
- // go back one step
360
- labelRotation -- ;
361
- break ;
405
+ var options = me . options ;
406
+ var tickOpts = options . ticks ;
407
+ var ticks = me . getTicks ( ) ;
408
+ var minRotation = tickOpts . minRotation || 0 ;
409
+ var maxRotation = tickOpts . maxRotation ;
410
+ var labelRotation = minRotation ;
411
+ var labelSizes , maxLabelWidth , maxLabelHeight , maxWidth , tickWidth , maxHeight , maxLabelDiagonal ;
412
+
413
+ if ( me . _isVisible ( ) && tickOpts . display ) {
414
+ labelSizes = me . _labelSizes = computeLabelSizes ( me . ctx , parseTickFontOptions ( tickOpts ) , ticks , me . longestTextCache ) ;
415
+
416
+ if ( minRotation < maxRotation && ticks . length > 1 && me . isHorizontal ( ) ) {
417
+ maxLabelWidth = labelSizes . widest . width ;
418
+ maxLabelHeight = labelSizes . highest . height - labelSizes . highest . offset ;
419
+
420
+ // Estimate the width of each grid based on the canvas width, the maximum
421
+ // label width and the number of tick intervals
422
+ maxWidth = Math . min ( me . maxWidth , me . chart . width - maxLabelWidth ) ;
423
+ tickWidth = options . offset ? me . maxWidth / ticks . length : maxWidth / ( ticks . length - 1 ) ;
424
+
425
+ // Allow 3 pixels x2 padding either side for label readability
426
+ if ( maxLabelWidth + 6 > tickWidth ) {
427
+ tickWidth = maxWidth / ( ticks . length - ( options . offset ? 0.5 : 1 ) ) ;
428
+ maxHeight = me . maxHeight - getTickMarkLength ( options . gridLines )
429
+ - tickOpts . padding - getScaleLabelHeight ( options . scaleLabel ) ;
430
+ maxLabelDiagonal = Math . sqrt ( maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight ) ;
431
+ labelRotation = helpers . toDegrees ( Math . min (
432
+ Math . asin ( Math . min ( ( labelSizes . highest . height + 6 ) / tickWidth , 1 ) ) ,
433
+ Math . asin ( Math . min ( maxHeight / maxLabelDiagonal , 1 ) ) - Math . asin ( maxLabelHeight / maxLabelDiagonal )
434
+ ) ) ;
435
+ labelRotation = Math . max ( minRotation , Math . min ( maxRotation , labelRotation ) ) ;
362
436
}
363
-
364
- labelRotation ++ ;
365
- labelWidth = cosRotation * originalLabelWidth ;
366
437
}
367
438
}
368
439
@@ -385,8 +456,7 @@ module.exports = Element.extend({
385
456
height : 0
386
457
} ;
387
458
388
- var labels = labelsFromTicks ( me . _ticks ) ;
389
-
459
+ var ticks = me . getTicks ( ) ;
390
460
var opts = me . options ;
391
461
var tickOpts = opts . ticks ;
392
462
var scaleLabelOpts = opts . scaleLabel ;
@@ -395,94 +465,81 @@ module.exports = Element.extend({
395
465
var position = opts . position ;
396
466
var isHorizontal = me . isHorizontal ( ) ;
397
467
398
- var parseFont = helpers . options . _parseFont ;
399
- var tickFont = parseFont ( tickOpts ) ;
400
- var tickMarkLength = opts . gridLines . tickMarkLength ;
401
-
402
468
// Width
403
469
if ( isHorizontal ) {
404
470
// subtract the margins to line up with the chartArea if we are a full width scale
405
471
minSize . width = me . isFullWidth ( ) ? me . maxWidth - me . margins . left - me . margins . right : me . maxWidth ;
406
- } else {
407
- minSize . width = display && gridLineOpts . drawTicks ? tickMarkLength : 0 ;
472
+ } else if ( display ) {
473
+ minSize . width = getTickMarkLength ( gridLineOpts ) + getScaleLabelHeight ( scaleLabelOpts ) ;
408
474
}
409
475
410
476
// height
411
- if ( isHorizontal ) {
412
- minSize . height = display && gridLineOpts . drawTicks ? tickMarkLength : 0 ;
413
- } else {
477
+ if ( ! isHorizontal ) {
414
478
minSize . height = me . maxHeight ; // fill all the height
415
- }
416
-
417
- // Are we showing a title for the scale?
418
- if ( scaleLabelOpts . display && display ) {
419
- var scaleLabelFont = parseFont ( scaleLabelOpts ) ;
420
- var scaleLabelPadding = helpers . options . toPadding ( scaleLabelOpts . padding ) ;
421
- var deltaHeight = scaleLabelFont . lineHeight + scaleLabelPadding . height ;
422
-
423
- if ( isHorizontal ) {
424
- minSize . height += deltaHeight ;
425
- } else {
426
- minSize . width += deltaHeight ;
427
- }
479
+ } else if ( display ) {
480
+ minSize . height = getTickMarkLength ( gridLineOpts ) + getScaleLabelHeight ( scaleLabelOpts ) ;
428
481
}
429
482
430
483
// Don't bother fitting the ticks if we are not showing the labels
431
484
if ( tickOpts . display && display ) {
432
- var largestTextWidth = helpers . longestText ( me . ctx , tickFont . string , labels , me . longestTextCache ) ;
433
- var tallestLabelHeightInLines = helpers . numberOfLabelLines ( labels ) ;
434
- var lineSpace = tickFont . size * 0.5 ;
435
- var tickPadding = me . options . ticks . padding ;
436
-
437
- // Store max number of lines and widest label for _autoSkip
438
- me . _maxLabelLines = tallestLabelHeightInLines ;
439
- me . longestLabelWidth = largestTextWidth ;
485
+ var tickFonts = parseTickFontOptions ( tickOpts ) ;
486
+ var labelSizes = me . _labelSizes ;
487
+ var firstLabelSize = labelSizes . first ;
488
+ var lastLabelSize = labelSizes . last ;
489
+ var widestLabelSize = labelSizes . widest ;
490
+ var highestLabelSize = labelSizes . highest ;
491
+ var lineSpace = tickFonts . minor . lineHeight * 0.4 ;
492
+ var tickPadding = tickOpts . padding ;
440
493
441
494
if ( isHorizontal ) {
495
+ // A horizontal axis is more constrained by the height.
496
+ me . longestLabelWidth = widestLabelSize . width ;
497
+
498
+ var isRotated = me . labelRotation !== 0 ;
442
499
var angleRadians = helpers . toRadians ( me . labelRotation ) ;
443
500
var cosRotation = Math . cos ( angleRadians ) ;
444
501
var sinRotation = Math . sin ( angleRadians ) ;
445
502
446
- // TODO - improve this calculation
447
- var labelHeight = ( sinRotation * largestTextWidth )
448
- + ( tickFont . lineHeight * tallestLabelHeightInLines )
449
- + lineSpace ; // padding
503
+ var labelHeight = sinRotation * widestLabelSize . width
504
+ + cosRotation * ( highestLabelSize . height - ( isRotated ? highestLabelSize . offset : 0 ) )
505
+ + ( isRotated ? 0 : lineSpace ) ; // padding
450
506
451
507
minSize . height = Math . min ( me . maxHeight , minSize . height + labelHeight + tickPadding ) ;
452
508
453
- me . ctx . font = tickFont . string ;
454
- var firstLabelWidth = computeTextSize ( me . ctx , labels [ 0 ] , tickFont . string ) ;
455
- var lastLabelWidth = computeTextSize ( me . ctx , labels [ labels . length - 1 ] , tickFont . string ) ;
456
509
var offsetLeft = me . getPixelForTick ( 0 ) - me . left ;
457
- var offsetRight = me . right - me . getPixelForTick ( labels . length - 1 ) ;
510
+ var offsetRight = me . right - me . getPixelForTick ( ticks . length - 1 ) ;
458
511
var paddingLeft , paddingRight ;
459
512
460
513
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
461
514
// which means that the right padding is dominated by the font height
462
- if ( me . labelRotation !== 0 ) {
463
- paddingLeft = position === 'bottom' ? ( cosRotation * firstLabelWidth ) : ( cosRotation * lineSpace ) ;
464
- paddingRight = position === 'bottom' ? ( cosRotation * lineSpace ) : ( cosRotation * lastLabelWidth ) ;
515
+ if ( isRotated ) {
516
+ paddingLeft = position === 'bottom' ?
517
+ cosRotation * firstLabelSize . width + sinRotation * firstLabelSize . offset :
518
+ sinRotation * ( firstLabelSize . height - firstLabelSize . offset ) ;
519
+ paddingRight = position === 'bottom' ?
520
+ sinRotation * ( lastLabelSize . height - lastLabelSize . offset ) :
521
+ cosRotation * lastLabelSize . width + sinRotation * lastLabelSize . offset ;
465
522
} else {
466
- paddingLeft = firstLabelWidth / 2 ;
467
- paddingRight = lastLabelWidth / 2 ;
523
+ paddingLeft = firstLabelSize . width / 2 ;
524
+ paddingRight = lastLabelSize . width / 2 ;
468
525
}
469
- me . paddingLeft = Math . max ( paddingLeft - offsetLeft , 0 ) + 3 ; // add 3 px to move away from canvas edges
470
- me . paddingRight = Math . max ( paddingRight - offsetRight , 0 ) + 3 ;
526
+
527
+ // Adjust padding taking into account changes in offsets
528
+ // and add 3 px to move away from canvas edges
529
+ me . paddingLeft = Math . max ( ( paddingLeft - offsetLeft ) * me . width / ( me . width - offsetLeft ) , 0 ) + 3 ;
530
+ me . paddingRight = Math . max ( ( paddingRight - offsetRight ) * me . width / ( me . width - offsetRight ) , 0 ) + 3 ;
471
531
} else {
472
532
// A vertical axis is more constrained by the width. Labels are the
473
533
// dominant factor here, so get that length first and account for padding
474
- if ( tickOpts . mirror ) {
475
- largestTextWidth = 0 ;
476
- } else {
534
+ var labelWidth = tickOpts . mirror ? 0 :
477
535
// use lineSpace for consistency with horizontal axis
478
536
// tickPadding is not implemented for horizontal
479
- largestTextWidth += tickPadding + lineSpace ;
480
- }
537
+ widestLabelSize . width + tickPadding + lineSpace ;
481
538
482
- minSize . width = Math . min ( me . maxWidth , minSize . width + largestTextWidth ) ;
539
+ minSize . width = Math . min ( me . maxWidth , minSize . width + labelWidth ) ;
483
540
484
- me . paddingTop = tickFont . size / 2 ;
485
- me . paddingBottom = tickFont . size / 2 ;
541
+ me . paddingTop = firstLabelSize . height / 2 ;
542
+ me . paddingBottom = lastLabelSize . height / 2 ;
486
543
}
487
544
}
488
545
@@ -685,11 +742,10 @@ module.exports = Element.extend({
685
742
var cos = Math . abs ( Math . cos ( rot ) ) ;
686
743
var sin = Math . abs ( Math . sin ( rot ) ) ;
687
744
745
+ var labelSizes = me . _labelSizes ;
688
746
var padding = optionTicks . autoSkipPadding || 0 ;
689
- var w = ( me . longestLabelWidth + padding ) || 0 ;
690
-
691
- var tickFont = parseTickFontOptions ( optionTicks ) . minor ;
692
- var h = ( me . _maxLabelLines * tickFont . lineHeight + padding ) || 0 ;
747
+ var w = labelSizes ? labelSizes . widest . width + padding : 0 ;
748
+ var h = labelSizes ? labelSizes . highest . height + padding : 0 ;
693
749
694
750
// Calculate space needed for 1 tick in axis direction.
695
751
return isHorizontal
@@ -751,7 +807,7 @@ module.exports = Element.extend({
751
807
var tickPadding = optionTicks . padding ;
752
808
var labelOffset = optionTicks . labelOffset ;
753
809
754
- var tl = gridLines . drawTicks ? gridLines . tickMarkLength : 0 ;
810
+ var tl = getTickMarkLength ( gridLines ) ;
755
811
756
812
var scaleLabelFontColor = valueOrDefault ( scaleLabel . fontColor , defaults . global . defaultFontColor ) ;
757
813
var scaleLabelFont = helpers . options . _parseFont ( scaleLabel ) ;
0 commit comments