@@ -430,6 +430,78 @@ TABS.pid_tuning.initialize = function (callback) {
430430 }
431431 }
432432
433+ function drawAxisLabel ( curveContext , axisLabel , x , y , align , color ) {
434+
435+ curveContext . fillStyle = color || '#000000' ;
436+ curveContext . textAlign = align || 'center' ;
437+ curveContext . fillText ( axisLabel , x , y ) ;
438+ }
439+
440+ function drawBalloonLabel ( curveContext , axisLabel , x , y , align , color , borderColor , textColor ) {
441+
442+ /**
443+ * curveContext is the canvas to draw on
444+ * axisLabel is the string to display in the center of the balloon
445+ * x, y are the coordinates of the point of the balloon
446+ * align is whether the balloon appears to the left (align 'right') or right (align left) of the x,y coordinates
447+ * color, borderColor and textColor are the fill color, border color and text color of the balloon
448+ */
449+
450+ const DEFAULT_OFFSET = 50 ; // in canvas scale; this is the horizontal length of the pointer
451+ const DEFAULT_RADIUS = 10 ; // in canvas scale, this is the radius around the balloon
452+
453+ const fontSize = parseInt ( curveContext . font ) ;
454+
455+ // calculate the width and height required for the balloon
456+ const width = ( curveContext . measureText ( axisLabel ) . width * 1.2 ) ;
457+ const height = fontSize * 1.5 ; // the balloon is bigger than the text height
458+ const pointerY = y ; // always point to the required Y coordinate, even if we move the balloon itself to keep it on the canvas
459+
460+ // setup balloon background
461+ curveContext . fillStyle = color || '#ffffff' ;
462+ curveContext . strokeStyle = borderColor || '#000000' ;
463+
464+ // adjust the coordinates for determine where the balloon background should be drawn
465+ x += ( ( align == 'right' ) ?- ( width + DEFAULT_OFFSET ) :0 ) + ( ( align == 'left' ) ?DEFAULT_OFFSET :0 ) ;
466+ y -= ( height / 2 ) ; if ( y < 0 ) y = 0 ; else if ( y > curveContext . height ) y = curveContext . height ; // prevent balloon from going out of canvas
467+
468+ var pointerLength = ( height - 2 * DEFAULT_RADIUS ) / 6 ;
469+
470+ curveContext . beginPath ( ) ;
471+ curveContext . moveTo ( x + DEFAULT_RADIUS , y ) ;
472+ curveContext . lineTo ( x + width - DEFAULT_RADIUS , y ) ;
473+ curveContext . quadraticCurveTo ( x + width , y , x + width , y + DEFAULT_RADIUS ) ;
474+
475+ if ( align == 'right' ) { // point is to the right
476+ curveContext . lineTo ( x + width , y + DEFAULT_RADIUS + pointerLength ) ;
477+ curveContext . lineTo ( x + width + DEFAULT_OFFSET , pointerY ) ; // point
478+ curveContext . lineTo ( x + width , y + height - DEFAULT_RADIUS - pointerLength ) ;
479+ }
480+ curveContext . lineTo ( x + width , y + height - DEFAULT_RADIUS ) ;
481+
482+ curveContext . quadraticCurveTo ( x + width , y + height , x + width - DEFAULT_RADIUS , y + height ) ;
483+ curveContext . lineTo ( x + DEFAULT_RADIUS , y + height ) ;
484+ curveContext . quadraticCurveTo ( x , y + height , x , y + height - DEFAULT_RADIUS ) ;
485+
486+ if ( align == 'left' ) { // point is to the left
487+ curveContext . lineTo ( x , y + height - DEFAULT_RADIUS - pointerLength ) ;
488+ curveContext . lineTo ( x - DEFAULT_OFFSET , pointerY ) ; // point
489+ curveContext . lineTo ( x , y + DEFAULT_RADIUS - pointerLength ) ;
490+ }
491+ curveContext . lineTo ( x , y + DEFAULT_RADIUS ) ;
492+
493+ curveContext . quadraticCurveTo ( x , y , x + DEFAULT_RADIUS , y ) ;
494+ curveContext . closePath ( ) ;
495+
496+ // fill in the balloon background
497+ curveContext . fill ( ) ;
498+ curveContext . stroke ( ) ;
499+
500+ // and add the label
501+ drawAxisLabel ( curveContext , axisLabel , x + ( width / 2 ) , y + ( height + fontSize ) / 2 - 4 , 'center' , textColor ) ;
502+
503+ }
504+
433505 function checkInput ( element ) {
434506 var value = parseFloat ( element . val ( ) ) ;
435507 if ( value < parseFloat ( element . prop ( 'min' ) )
@@ -713,6 +785,7 @@ TABS.pid_tuning.initialize = function (callback) {
713785 var curveWidth = rcCurveElement . width ;
714786
715787 curveContext . clearRect ( 0 , 0 , curveWidth , curveHeight ) ;
788+ curveContext . font = "24pt Verdana, Arial, sans-serif" ;
716789
717790 var maxAngularVel ;
718791 if ( ! useLegacyCurve ) {
@@ -721,7 +794,11 @@ TABS.pid_tuning.initialize = function (callback) {
721794 printMaxAngularVel ( self . currentRates . pitch_rate , self . currentRates . rc_rate , self . currentRates . rc_expo , self . currentRates . superexpo , self . currentRates . deadband , maxAngularVelPitchElement ) ,
722795 printMaxAngularVel ( self . currentRates . yaw_rate , self . currentRates . rc_rate_yaw , self . currentRates . rc_yaw_expo , self . currentRates . superexpo , self . currentRates . yawDeadband , maxAngularVelYawElement ) ) ;
723796
797+ // make maxAngularVel multiple of 200deg/s so that the auto-scale doesn't keep changing for small changes of the maximum curve
798+ maxAngularVel = Math . ceil ( maxAngularVel / 200 ) * 200 ;
799+
724800 drawAxes ( curveContext , curveWidth , curveHeight , ( curveHeight / 2 ) / maxAngularVel * 360 ) ;
801+ drawAxisLabel ( curveContext , maxAngularVel . toFixed ( 0 ) + ' deg/s' , ( curveWidth / 2 ) - 10 , parseInt ( curveContext . font ) * 1.2 , 'right' ) ;
725802 } else {
726803 maxAngularVel = 0 ;
727804 }
@@ -732,6 +809,17 @@ TABS.pid_tuning.initialize = function (callback) {
732809 drawCurve ( self . currentRates . pitch_rate , self . currentRates . rc_rate , self . currentRates . rc_expo , self . currentRates . superexpo , self . currentRates . deadband , maxAngularVel , '#00ff00' , - 4 , curveContext ) ;
733810 drawCurve ( self . currentRates . yaw_rate , self . currentRates . rc_rate_yaw , self . currentRates . rc_yaw_expo , self . currentRates . superexpo , self . currentRates . yawDeadband , maxAngularVel , '#0000ff' , 4 , curveContext ) ;
734811
812+ if ( ! useLegacyCurve && maxAngularVel ) {
813+ var maxAngularVelRoll = maxAngularVelRollElement . text ( ) + ' deg/s' ,
814+ maxAngularVelPitch = maxAngularVelPitchElement . text ( ) + ' deg/s' ,
815+ maxAngularVelYaw = maxAngularVelYawElement . text ( ) + ' deg/s' ,
816+ rateScaling = ( curveHeight / 2 ) / maxAngularVel ;
817+
818+ drawBalloonLabel ( curveContext , maxAngularVelRoll , curveWidth , rateScaling * ( maxAngularVel - parseInt ( maxAngularVelRoll ) ) , 'right' , '#FF8080' , '#FF8080' , '#000000' ) ;
819+ drawBalloonLabel ( curveContext , maxAngularVelPitch , curveWidth , rateScaling * ( maxAngularVel - parseInt ( maxAngularVelPitch ) ) , 'right' , '#80FF80' , '#80FF80' , '#000000' ) ;
820+ drawBalloonLabel ( curveContext , maxAngularVelYaw , curveWidth , rateScaling * ( maxAngularVel - parseInt ( maxAngularVelYaw ) ) , 'right' , '#8080FF' , '#8080FF' , '#000000' ) ;
821+ }
822+
735823 updateNeeded = false ;
736824 }
737825 } , 0 ) ;
0 commit comments