Datafield with simple boxes

Doing my first steps now with connect iq and try to create a data field which calcuates the pace for the last kilometers and shows a box colored chart to see the speed differences...

I thought, a simple version could be done by using unicode chars for block, but that would have been to simple :rolleyes:

So "all" I need to get the size of the drawing area and how to draw a filled rectangle - I found some code snippets here but was not able to get them to run... Does anyone have a short source code which draws a rectangle within a data field?
  • dc.getWidth() and dc.getHeight() will get you the dimensions

    dc.fillRectangle() will draw a filled rectangle. dc.drawRectangle(), just the border of a rectangle

    You want a complex DF and not a simple DF for this so you can do the draws in onUpdate().
    var w=dc.getWidth();
    var h=dc.getHeight();
    dc.setColor(Gfx.COLOR_BLUE,Gfx.COLOR_BLUE);
    dc.fillRectangle(20,10,w-40,h-20);

    should draw a blue, filled rectangle, with a border in the background color, centered inside the DF for you. For round/semi round, you'll probably want to look at the obscurity flags due to the round edges.
  • Thanks Jim,
    please give me another push to get into the right direction, I don't know, where to put the onUpdate function - all my efforts are leading to compiler errors...

    function onUpdate()
    {
    var w=dc.getWidth();
    var h=dc.getHeight();
    dc.setColor(Gfx.COLOR_BLUE,Gfx.COLOR_BLUE);
    dc.fillRectangle(20,10,width-40,height-20);
    }


    I have this base code:
    using Toybox.Application as App;

    class MyProgram extends App.AppBase {

    function initialize() {
    AppBase.initialize();
    }

    function onStart(state) {
    return false;
    }

    function getInitialView() {
    return [new DataField()];
    }

    function onStop(state) {
    return false;
    }

    }


    ...and this simple (!) datafield class:
    using Toybox.WatchUi as Ui;
    using Toybox.System as Sys;
    using Toybox.Time as Time;
    using Toybox.Math as Math;

    class DataField extends Ui.SimpleDataField
    {
    const METERS_TO_MILES = 0.000621371;
    const MILLISECONDS_TO_SECONDS = 0.001;

    var counter;
    var display;
    var dist;
    var time;
    var pace;
    var pmin;
    var psec;
    var ascii;

    function initialize()
    {
    SimpleDataField.initialize();
    label= "Birds";
    counter=0;
    display="*";
    dist= 0;
    time= 0;
    }

    function PaceStr(pace)
    {
    var psec;
    var pmin;

    pmin=pace / 60;
    psec=pace-pmin*60;

    if (psec<10)
    {
    return pmin+":0"+psec;
    }
    else
    {
    return pmin+":"+psec;
    }
    }

    function compute(info)
    {
    dist=info.elapsedDistance;
    time=info.elapsedTime*MILLISECONDS_TO_SECONDS;
    time=time.toLong();

    if (dist==null)
    {
    dist=0l;
    pace=0;
    }

    dist=dist.toLong();
    if (dist==0)
    {
    pace=0;
    }
    else
    {
    pace=1000*time/dist;
    }

    if (time%10==0)
    {

    }

    display=dist + "m|" + PaceStr(time) + "|"+PaceStr(pace);

    counter += 1;

    return display;

    }
    }


    I have changed 'class DataField extends Ui.SimpleDataField' to 'class MyDataField extends Ui.DataField' and 'SimpleDataField.initialize();' to 'DataField.initialize();' and tried to adapt the main file as well, but couldn't solve the puzzle :(
  • The onUpdate() function goes into your view class. The function takes an object, usually named dc, as a parameter (you're omitting the parameter).

    If you're stuck, I suggest you start with one of the sample programs that come with the SDK or generate a new project from the template. To create a project from the template...

    • click File.
    • click New.
    • click ConnectIQ Project.
    • enter a project name in the Project name field.
    • click Next.
    • select Data Field in the Choose the Project Type dropdown.
    • select the Minimum SDK Version you want to support.
    • select the Target Platforms you want to support.
    • click Finish.


    Compile and run that project. Now that you have something that works, tweak it to your liking.

    Travis
  • Thanks, I don't have the SDK fully installed, just using the command line (which isn't a big problem, posted already a batch file here which does compiling and testing by just a simple call...)

    As suggested, I played around with the sample codes of the SDK and so I have a first running piece of code now (with a lot of unneeded lines still, but I am happy with it). My target is to get an optical impression of the pace during the last kilometers of my run.

    But before finishing this, I might have new questions - actually I'd like to retrieve not only the height and width of a datafield, but also it's location. On round displays, I need to shift my graphics upwards when the datafield is displayed at the bottom of the screen.


    using Toybox.WatchUi as Ui;
    using Toybox.Application as App;
    using Toybox.System as Sys;
    using Toybox.Time as Time;
    using Toybox.Activity as Act;
    using Toybox.Graphics as Gfx;
    using Toybox.Math as Math;

    const BORDER_PAD = 4;
    const UNITS_SPACING = 2;

    const BarCount=6;
    const BarHeight=32;
    const BarDistance=1000;
    const BarRed=330;
    const BarOrange=300;
    const BarYellow=285;


    var fonts = [Gfx.FONT_XTINY,Gfx.FONT_TINY,Gfx.FONT_SMALL,Gfx.FONT_MEDIUM,Gfx.FONT_LARGE,
    Gfx.FONT_NUMBER_MILD,Gfx.FONT_NUMBER_MEDIUM,Gfx.FONT_NUMBER_HOT,Gfx.FONT_NUMBER_THAI_HOT];

    var counter;

    class BirdField extends Ui.DataField {

    var sx;
    var sy;
    var bw;
    var t=new[BarCount];

    // Label Variables
    hidden var mLabelString = "Birdie";
    hidden var mLabelFont = Gfx.FONT_SMALL;
    hidden var mLabelX;
    hidden var mLabelY = 10; // Does not change

    // Hemoglobin Percentage variables
    hidden var mHPUnitsString = "%";
    hidden var mHPUnitsWidth;
    hidden var mHPX;
    hidden var mHPY;
    hidden var mHPMaxWidth;

    // Fit Contributor
    hidden var mFitContributor;

    // Font values
    hidden var mDataFont;
    hidden var mDataFontAscent;
    hidden var mUnitsFont = Gfx.FONT_TINY;

    // field separator line
    hidden var separator;

    hidden var mSensor;
    hidden var xCenter;
    hidden var yCenter;

    function initialize()
    {
    var i;
    DataField.initialize();

    for (i=0; i<BarCount; i+=1)
    {
    t=Math.rand() & 63+270;
    }

    }

    function compute(info)
    {
    counter=counter+1;
    }

    function onLayout(dc)
    {
    sx=dc.getWidth();
    sy=dc.getHeight();
    bw=sx/(BarCount+2);

    var width = dc.getWidth();
    var height = dc.getHeight();
    var top = mLabelY + Gfx.getFontAscent(mLabelFont) + BORDER_PAD;

    var vLayoutWidth;
    var vLayoutHeight;
    var vLayoutFontIdx;

    var hLayoutWidth;
    var hLayoutHeight;
    var hLayoutFontIdx;

    // Units width does not change, compute only once
    if (mHPUnitsWidth == null) {
    mHPUnitsWidth = dc.getTextWidthInPixels(mHPUnitsString, mUnitsFont) + UNITS_SPACING;
    }

    // Center the field label
    mLabelX = width / 2;

    // Compute data width/height for both layouts
    hLayoutWidth = (width - (4 * BORDER_PAD) ) / 2;
    hLayoutHeight = height - (2 * BORDER_PAD) - top;
    hLayoutFontIdx = selectFont(dc, (hLayoutWidth - mHPUnitsWidth), hLayoutHeight);

    vLayoutWidth = width - (2 * BORDER_PAD);
    vLayoutHeight = (height - top - (3 * BORDER_PAD)) / 2;
    vLayoutFontIdx = selectFont(dc, (vLayoutWidth - mHPUnitsWidth), vLayoutHeight);

    //Use the horizontal layout if it supports a larger font
    if (hLayoutFontIdx > vLayoutFontIdx) {
    mDataFont = fonts[hLayoutFontIdx];
    mDataFontAscent = Gfx.getFontAscent(mDataFont);

    //Compute the center of the Hemo Percentage data
    mHPX = (2 * BORDER_PAD) + hLayoutWidth + (hLayoutWidth / 2) - (mHPUnitsWidth / 2);
    mHPY = 20;

    //Use a separator line for horizontal layout
    separator = [(width / 2), top + BORDER_PAD, (width / 2), height - BORDER_PAD];
    } else {
    // otherwise, use the veritical layout
    mDataFont = fonts[vLayoutFontIdx];
    mDataFontAscent = Gfx.getFontAscent(mDataFont);

    mHPX = BORDER_PAD + (vLayoutWidth / 2) - (mHPUnitsWidth / 2);
    // mHPY = mHCY + BORDER_PAD + vLayoutHeight;

    // Do not use a separator line for vertical layout
    separator = null;
    }

    xCenter = dc.getWidth() / 2;
    yCenter = dc.getHeight() / 2;
    }

    function selectFont(dc, width, height) {
    var testString = "88.88"; //Dummy string to test data width
    var fontIdx;
    var dimensions;

    //Search through fonts from biggest to smallest
    for (fontIdx = (fonts.size() - 1); fontIdx > 0; fontIdx--) {
    dimensions = dc.getTextDimensions(testString, fonts[fontIdx]);
    if ((dimensions[0] <= width) && (dimensions[1] <= height)) {
    //If this font fits, it is the biggest one that does
    break;
    }
    }

    return fontIdx;
    }


    function onUpdate(dc)
    {

    var bgColor = getBackgroundColor();
    var fgColor = Gfx.COLOR_WHITE;
    var i,n,x,y,z;

    for (i=0; i<BarCount-1; i+=1)
    {
    t=t[i+1];
    }
    t[BarCount-1]=Math.rand() & 63+270;

    if (bgColor == Gfx.COLOR_WHITE)
    {
    fgColor = Gfx.COLOR_BLACK;
    }

    dc.setColor(fgColor, bgColor);
    dc.clear();

    dc.setColor(fgColor, Gfx.COLOR_TRANSPARENT);

    //Draw the field label
    if (sy>100)
    {
    dc.drawText(mLabelX, mLabelY, mLabelFont, mLabelString, Gfx.TEXT_JUSTIFY_CENTER);
    }

    // Update status
    //dc.drawText(xCenter, yCenter, Gfx.FONT_MEDIUM, "A", Gfx.TEXT_JUSTIFY_CENTER);
    dc.drawText(xCenter, yCenter, Gfx.FONT_XTINY, sx+" / "+sy, Gfx.TEXT_JUSTIFY_CENTER);
    dc.drawText(20,20,Gfx.FONT_TINY,counter,Gfx.TEXT_JUSTIFY_CENTER);

    x=0;
    y=sy-22;
    for (i=0; i<BarCount; i+=1)
    {
    n=t*BarHeight/BarRed;
    x+=bw;
    if (t>=BarRed)
    {
    z=Gfx.COLOR_RED;
    }
    else if (t>=BarOrange)
    {
    z=Gfx.COLOR_ORANGE;
    }
    else if (t>=BarYellow)
    {
    z=Gfx.COLOR_YELLOW;
    }
    else
    {
    z=Gfx.COLOR_GREEN;
    }

    dc.setColor(z,bgColor);
    dc.fillRoundedRectangle(x,y-n,bw-2,n,2);
    dc.setColor(fgColor,bgColor);
    dc.drawRoundedRectangle(x,y-n,bw-2,n,2);
    dc.drawText(x+bw/2,y,Gfx.FONT_XTINY,i+1,Gfx.TEXT_JUSTIFY_CENTER);
    }
    dc.setColor(fgColor, fgColor);
    y-=1;
    dc.drawLine(bw/2,y,x+bw/2+bw,y);
    }

    function onTimerStart()
    {
    }

    function onTimerStop()
    {
    }

    function onTimerPause()
    {
    }

    function onTimerResume()
    {
    }

    function onTimerLap()
    {
    }

    function onTimerReset()
    {
    }

    }

    class MoxyField extends App.AppBase
    {

    function getInitialView()
    {
    return [new BirdField()];
    }

    function initialize()
    {
    AppBase.initialize();
    counter=900;
    }

    function onStart(state)
    {
    }

    function onStop(state)
    {
    return false;
    }
    }
    [/CODE]
  • Former Member
    Former Member over 7 years ago
    Thanks Jim,
    please give me another push to get into the right direction, I don't know, where to put the onUpdate function - all my efforts are leading to compiler errors...

    function onUpdate()
    {
    var w=dc.getWidth();
    var h=dc.getHeight();
    dc.setColor(Gfx.COLOR_BLUE,Gfx.COLOR_BLUE);
    dc.fillRectangle(20,10,width-40,height-20);
    }


    Shouldn't the last line be:
    dc.fillRectangle(20,10,w-40,h-20);
  • Shouldn't the last line be:


    Yes.. Typo on my part (untested code - sorry). I use width and height in my own code most of the time and added that line as an after thought. I fixed the original post.
  • Former Member
    Former Member over 7 years ago
    (imagine a 'thumbs up' icon here)
  • But before finishing this, I might have new questions - actually I'd like to retrieve not only the height and width of a datafield, but also it's location. On round displays, I need to shift my graphics upwards when the datafield is displayed at the bottom of the screen.


    Look into using the obscurity flags. ( getObsurityFlags() )

    Returns mask of OBSCURE_X values giving the sides of the display that that are obscured by a round screen. Valid during onUpdate.
  • Thanks to all, meantime I did already the first steps to create a nice datafield, here's the code...



    Manifest.xml
    <iq:manifest xmlns:iq="www.garmin.com/.../connectiq" version="1">
    <iq:application entry="Birdie" id="FC4D9C18A1CC4AE5BA7EC3449F1C8500" launcherIcon="@Drawables.LauncherIcon" minSdkVersion="1.2.0" name="@Strings.AppName" type="datafield">
    <iq:products>
    <iq:product id="fenix5"/>
    <iq:product id="fenix5x"/>
    <iq:product id="fr935"/>
    </iq:products>
    <iq:permissions>
    </iq:permissions>
    <iq:languages>
    <iq:language>eng</iq:language>
    </iq:languages>
    </iq:application>
    </iq:manifest>


    Strings.xml
    <strings>
    <string id="AppName">Birdie</string>
    </strings>


    Bitmaps.xml
    <drawables>
    <bitmap id="LauncherIcon" filename="images/birdie.png" />
    </drawables>


    MainCode.mc
    using Toybox.WatchUi as Ui;
    using Toybox.Application as App;
    using Toybox.System as Sys;
    using Toybox.Time as Time;
    using Toybox.Activity as Act;
    using Toybox.Graphics as Gfx;
    using Toybox.Math as Math;


    var fonts = [Gfx.FONT_XTINY,Gfx.FONT_TINY,Gfx.FONT_SMALL,Gfx.FONT_MEDIUM,Gfx.FONT_LARGE,
    Gfx.FONT_NUMBER_MILD,Gfx.FONT_NUMBER_MEDIUM,Gfx.FONT_NUMBER_HOT,Gfx.FONT_NUMBER_THAI_HOT];

    class BirdField extends Ui.DataField {

    const LabelText = "Birdie";
    const LabelFont = Gfx.FONT_SMALL;
    const LabelY=8; // Position von oben

    const BarCount=6;
    const BarHeight=32;
    const BarDistance=1000;

    const BarTarget=300;
    const BarMinMax=120;
    const BarLines=60;

    const BarColBlk=360;
    const BarColBlu=330;
    const BarColRed=BarTarget;
    const BarColYel=285;
    const BarColGrn=BarTarget-BarMinMax;

    var time;
    var dist;
    var laptime;
    var distcount;
    var barindex;

    var update;

    var sx,sy;
    var mx,my;
    var bx,by,bw,bh;
    var t=new[BarCount+1];

    function initialize()
    {
    var i;

    update=true;

    time=0; // aktuelle Zeit
    dist=0; // aktuelle Distanz
    laptime=0; // Zeit beim letzten Kilometer
    distcount=0; // aktueller Kilometer
    barindex=0; // aktueller Balken

    for (i=0; i<=BarCount; i+=1)
    {
    t=0;
    }

    DataField.initialize();
    }

    function compute(info)
    {
    var i,d;

    time=time+30; // Sekunden-Sprung
    dist=dist+55+Math.rand()%75; // und ein paar Meter weiter

    if (dist>=distcount*BarDistance)
    {
    if (barindex<BarCount)
    {
    barindex=barindex+1;
    }
    else
    {
    t[0]+=t[1];
    for (i=1;i<BarCount;i+=1)
    {
    t=t[i+1];
    }
    }

    t[barindex]=(time-laptime);
    laptime=time;
    distcount+=1;
    }
    else
    {
    t[barindex]=(time-laptime);
    }

    }

    function redraw(dc)
    {
    var bgColor = getBackgroundColor();
    var fgColor = Gfx.COLOR_WHITE;

    var width;
    var tick;
    var i,n,x,y,z;

    if (bgColor == Gfx.COLOR_WHITE)
    {
    fgColor = Gfx.COLOR_BLACK;
    }

    dc.setColor(fgColor, bgColor);
    dc.clear();

    dc.setColor(fgColor, Gfx.COLOR_TRANSPARENT);

    if (sy>100)
    {
    dc.drawText(mx,LabelY,LabelFont,LabelText,Gfx.TEXT_JUSTIFY_CENTER);
    }


    x=0;
    y=by;

    dc.setColor(Gfx.COLOR_LT_GRAY,Gfx.COLOR_TRANSPARENT);
    dc.drawLine(bw/2,y-bh,bw*BarCount+bw/2+bw,y-bh);
    dc.drawLine(bw/2,y-bh/2,bw*BarCount+bw/2+bw,y-bh/2);
    dc.setColor(fgColor, bgColor);

    for (i=1; i<=BarCount and i<=barindex; i+=1)
    {
    z=i+distcount-barindex;
    width=bw-2;
    if (i<barindex)
    {
    if (i==1 and barindex<distcount)
    {
    tick=(t[0]+t[1])/(distcount-barindex+1);
    dc.drawText(100,200,Gfx.FONT_XTINY,tick,Gfx.TEXT_JUSTIFY_CENTER);
    z=-z;
    }
    else
    {
    tick=t;
    }
    }
    else
    {
    width=(dist%BarDistance);
    if (width==0)
    {
    tick=0;
    width=0;
    }
    else
    {
    tick=t[barindex]*BarDistance/width;
    width=(bw-2)*width/BarDistance;
    }
    }


    n=tick*bh/BarColRed;
    x+=bw;
    dc.drawText(x+bw/2,y,Gfx.FONT_XTINY,z,Gfx.TEXT_JUSTIFY_CENTER);

    if (tick>=BarColRed)
    {
    if (tick>=BarColBlk) { z=Gfx.COLOR_BLACK; }
    else if (tick>=BarColBlu) { z=Gfx.COLOR_BLUE; }
    else { z=Gfx.COLOR_RED; }
    }
    else
    {
    if (tick>=BarColYel) { z=Gfx.COLOR_YELLOW; }
    else { z=Gfx.COLOR_GREEN; }
    }

    dc.setColor(z,bgColor);
    dc.fillRoundedRectangle(x+1,y-n,width,n,2);
    dc.setColor(fgColor,bgColor);
    dc.drawRoundedRectangle(x+1,y-n,width,n,2);

    dc.drawText(x+bw/2,y-25,Gfx.FONT_XTINY,t,Gfx.TEXT_JUSTIFY_CENTER);

    }

    dc.setColor(fgColor, fgColor);
    y-=1;
    dc.drawLine(bw/2,y,bw*BarCount+bw/2+bw,y);

    update=false;
    }

    function onLayout(dc)
    {
    sx=dc.getWidth();
    sy=dc.getHeight();
    mx=sx/2;
    my=sy/2;

    bw=sx/(BarCount+2);

    if (sy>180)
    {
    bh=sy-110;
    by=sy-50;
    }
    else if (sy>100)
    {
    bh=sy-80;
    by=sy-25;
    }
    else
    {
    bh=sy-35;
    by=sy-20;
    }
    }


    function onUpdate(dc)
    {
    update=true;
    if (update)
    {
    redraw(dc);
    }
    }

    function onTimerStart()
    {
    }

    function onTimerStop()
    {
    }

    function onTimerPause()
    {
    }

    function onTimerResume()
    {
    }

    function onTimerLap()
    {
    }

    function onTimerReset()
    {
    }

    }

    class Birdie extends App.AppBase
    {

    function getInitialView()
    {
    return [new BirdField()];
    }

    function initialize()
    {
    AppBase.initialize();
    }

    function onStart(state)
    {
    }

    function onStop(state)
    {
    return false;
    }
    }
    [/CODE]