drawArc based on a percentage

Hi all,

another noob question...

I want to draw an arc based on a percentage 0-100%.

I made an arc based on seconds, and it was really simple:

var seconds = System.getClockTime().sec;  
var sec=myTop+(360-(seconds*6));
if(sec>360) {sec=sec-360;}
if(!hideCircle) { dc.drawArc(centerX,  centerY, centerX, cwise , 90, sec); }

Is there a way to make this for percentage?

I've read that I must transform percentage in rad, but I have no idea how to start...

Thank you all :)

  • It's actually fairly simple math.  Let's say you have an arc that's 60 degrees, and a value between 0 and 100

    so in a simple case 60/100=x/60, (60% of 60 degrees) would be 36. so if the arc you draw is 36 degrees in a space with room for a 60 degree are, that would be 60%

    if you wanted to have a full circle at 100%

    60/100=x/360, and x would be 216 degrees

    x=(60*360)/100

  • Sorry but I really don't understand...

    The arc is based on this:

        0 degrees: 3 o'clock position.
    
        90 degrees: 12 o'clock position.
    
        180 degrees: 9 o'clock position.
    
        270 degrees: 6 o'clock position.

    Or at least this is in Monkey C documentation.

    I start my arc on top of the screen, so the start angle is 90.

    On 25% my arc should end at 0 degrees, on 50% at 270 degree, on 75% at 180 degree and 100% again 90 right?

    But the entire circle is of 360 degrees, so how I can match a percentage with this degrees?

  • in your code you have : var sec=myTop+(360-(seconds*6));

    where 6 = 360 / 60 (60 for 60 seconds) so if you want % instead of seconds, it would be 360 / 100.0 so 3.6.

  • I start my arc on top of the screen, so the start angle is 90.

    On 25% my arc should end at 0 degrees, on 50% at 270 degree, on 75% at 180 degree and 100% again 90 right?

    But the entire circle is of 360 degrees, so how I can match a percentage with this degrees?

    Start with 90 (your start angle), then subtract 360 * percentage / 100.

    (I checked in the simulator, and it's fine if the degrees argument is greater than 359 or less than 0.)

    's answer is correct for your exact situation.

    Here's a generic example where the start angle and direction can be tweaked:

    import Toybox.Graphics;
    import Toybox.WatchUi;
    import Toybox.Lang;
    
    class TestAppView extends WatchUi.View {
        function initialize() {
            View.initialize();
        }
    
        const arcPenWidth = 10;
        function onUpdate(dc as Dc) as Void {
            dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
            dc.setPenWidth(penWidth);
            drawArcByRatio(dc, .75);
        }
    
        // Draw clockwise arc starting at the top of the screen, based on the given ratio
        // 0 = empty arc
        // 1 = full arc
        // e.g. .75 = arc which is 75% "full"
        const arcStart = 90; // 12 o'clock
        const arcDirection = Graphics.ARC_CLOCKWISE;
        function drawArcByRatio(dc as Dc, arcRatio as Float) as Void {
            var x = dc.getWidth() / 2;
            var y = dc.getHeight() / 2;
            var r = x < y ? x : y;
            var arcLength = 360 * arcRatio;
            var arcEnd = arcStart +
                (arcDirection == Graphics.ARC_CLOCKWISE ?
                -arcLength :
                arcLength);
    
            dc.drawArc(x, y, r - arcPenWidth / 2, arcDirection, arcStart, arcEnd);
        }
    }

    Example output for fr965 (75% arc):

    The above example can also be reused for drawing an arc based on seconds:

    function drawArcBySeconds(dc as Dc, seconds as Float) as Void {
      drawArcByRatio(dc, seconds / 60.0);
    }

  • in your code you have : var sec=myTop+(360-(seconds*6));

    where 6 = 360 / 60 (60 for 60 seconds) so if you want % instead of seconds, it would be 360 / 100.0 so 3.6.

    Ok, so this works, it's right.

    yes, this would be my next question: so does this code works even if my arc is not 360 degrees?

    For example a cirlce like this:

  • yes it will work, you just have to change the arcStart, the arcDirection and instead of 360, to put then right angle.

    so in your picture, start would be 240, the angle would be 330.

  • Yeah, what SHN said, except the angle (full arc length) should be 300 (see below).

    Here's another example with a fully customizable drawArcByRatio() function, where everything is an option to the function (instead of using constants.)

    There's a bunch of type warnings (sorry!) but it should compile at the default type check level (-l 1 / gradual)

    import Toybox.Graphics;
    import Toybox.WatchUi;
    import Toybox.Lang;
    
    class TestAppView extends WatchUi.View {
        function initialize() {
            View.initialize();
        }
    
        function onUpdate(dc as Dc) as Void {
            dc.setColor(Graphics.COLOR_GREEN, Graphics.COLOR_BLACK);
    
            //drawArcBySeconds(dc, 35);
    
            drawArcByRatio(dc, 0.5, {
                :arcStart => 240, // 270 - 30
                :fullArcLength => 300 // 360 - 30 - 30
            });
        }
    
       typedef CustomArcOptions as {
            :arcPenWidth as Numeric or Null, // pen width in pixels
            :fullArcLength as Numeric or Null, // full arc length in degrees (360 = circle)
            :arcStart as Numeric or Null, // same as drawArc()'s degreeStart (5th arg). (0: 3 o'clock, 90: 12 o'clock, 180: 9 o'clock, 270: 6 o'clock)
            :arcDirection as Graphics.ArcDirection or Null
       };
    
        const defaultCustomArcOptions = {
            :arcPenWidth => 10,
            :fullArcLength => 360, // full circle
            :arcStart => 90, // top
            :arcDirection => Graphics.ARC_CLOCKWISE
        };
    
        function mergeDictionaryValue(dict1 as Dictionary, dict2 as Dictionary, key as Symbol) as Void {
            if (dict1[key] == null) {
                dict1[key] = dict2[key];
            }
        }
    
        function drawArcByRatio(
            dc as Dc,
            arcRatio as Numeric,
            options as CustomArcOptions or Null) as Void {
                options = options != null ? options : defaultCustomArcOptions;
                mergeDictionaryValue(options, defaultCustomArcOptions, :arcPenWidth);
                mergeDictionaryValue(options, defaultCustomArcOptions, :fullArcLength);
                mergeDictionaryValue(options, defaultCustomArcOptions, :arcStart);
                mergeDictionaryValue(options, defaultCustomArcOptions, :arcDirection);
                
                if (arcRatio > 1) {
                    arcRatio = 1;
                }
                if (arcRatio < 0) {
                    arcRatio = 0;
                }
    
                var x = dc.getWidth() / 2;
                var y = dc.getHeight() / 2;
                var r = x < y ? x : y;
                var arcLength = options[:fullArcLength] * arcRatio;
                var arcEnd = options[:arcStart] +
                    (options[:arcDirection] == Graphics.ARC_CLOCKWISE ?
                    -arcLength :
                    arcLength);
    
                dc.setPenWidth(options[:arcPenWidth]);
                dc.drawArc(x, y, r - options[:arcPenWidth] / 2, options[:arcDirection], options[:arcStart], arcEnd);
        }
    
        function drawArcBySeconds(dc as Dc, seconds as Numeric) as Void {
            drawArcByRatio(dc, seconds / 60.0, null);
        }
    }

    EDIT: change code so that if ratio > 1, a value of 1 is used. Otherwise, due to the way drawArc works, you will get unexpected behavior. (e.g. 1.25 would actually be the same as 0.25, for an arc whose full length is 360). Similarly, if ratio is < 0, use 0.

    Call:

    drawArcByRatio(dc, 0.5, {
        :arcStart => 240, // 270 - 30
        :fullArcLength => 300 // 360 - 30 - 30
    });

    Output:

    Call:

    drawArcBySeconds(dc, 35);

    or

    drawArcByRatio(dc, 35 / 60.0, null);

    Output:

  • @FlowState works really good, amazing! Thank you!