Make Your Own Analog Clock Widget

393
3
01-20-2022 10:38 AM
jcarlson
MVP Frequent Contributor
9 3 393

Someone recently posted an Idea for the development of a clock widget for Dashboards. As neat as that would be, I couldn't help wondering, "could we make our own using the tools already available?"

It turns out the answer to that is "yes!"

What does a clock need?

Obviously, the time. But more specifically, we need the hours and minutes separately. And for an analog clock, we'll need the degree that the hand is pointing at.

The first part of that is easy enough, using Arcade's date functions Now(), Hour(), and Minute(). And the degrees are just basic math.

1 hour = 30 degrees (360 / 12)

1 minute = 6 degrees (360 / 60)

In Arcade, that would all look like this:

 

var theHour = Hour(Now())
var theMinute = Minute(Now())

var hourDegrees = theHour * 30
var minuteDegrees = theMinute * 6

 

In-Between Times

If you implemented your clock with these expressions, though, you'd see your hour hand make a drastic jump when the hour changes, and that's just now how clocks work! We should see the hour hand gradually increment towards the next hour.

In the span of 1 hour on the clock (30 degrees), each minute will account for an additional 0.5 degrees (30 / 60). So to amend our expression, we can get the degrees for the hour hand with:

 

var hourDegrees = theHour * 30 + (theMinute * 0.5)

 

What about minutes? Don't they partially increment, too? I suppose they do. I don't know that anyone's going to be watching my clock widget that closely, but supposing they were, I want to reward their attention to detail with some details.

In the span of a single minute on the clock (6 degrees), each second will account for an additional 0.1 degrees (6 / 60). But we'll need to add the seconds to our expression for this.

Also, for the sake of being thorough: in the span of a single hour on the clock (30 degrees), each second accounts for an additional... 0.008333 degrees? Best leave this one as a fraction. It's 1/120 of a degree.

Aside: We're not adding a second hand to the clock, much as I'd like to. In an Arcade expression in a Dashboard, the most frequently we can refresh our expression is once every 0.1 minutes. That's frequent enough to make our minute hand look up-to-date, but a second hand would necessarily lag, and would probably distract viewers.

 

var theHour = Hour(Now())
var theMinute = Minute(Now())
var theSecond = Second(Now())

var hourDegrees = theHour * 30 + (theMinute * 0.5) + (theSecond / 120)
var minuteDegrees = theMinute * 6 + (theSecond * 0.1)

 

AM / PM?

For an analog clock, we don't really need to worry about AM vs PM. An hour value of 14 (2:00 PM) gives us a rotation of 420 degrees, which is equivalent to a rotation of 60 degrees as far as the circle of the clock is concerned.

Turns out an SVG does care about rotations being within a standard 0-360 range. The expressions have been amended to give us just the civilian hours.

Supposing we want a digital clock, too, or some text string based on the current time. You could use something like this:

 

 

var theHour = Iif(Hour(Now()) > 12, Hour(Now()) - 12, Hour(Now()))
var ampm = Iif(Hour(Now()) >= 12, 'PM', 'AM')

 

But for real: if the end result is to output a string, just let the Text function do the work.

 

var timeString = `${Text(Now(), 'hh:mm')} ${Iif(Hour(Now()) >= 12, 'PM', 'AM')}`

 

The Data Expression

To get this Arcade into something the Dashboard can work with, we need a data expression. Fortunately, the data's all just calculated from the current time, and there's only one set of values we need to reference, so we can bypass all that FeatureSetByPortalItem and iterating over our features array.

 

var theHour =    Iif(Hour(Now()) > 12, Hour(Now()) - 12, Hour(Now()))
var theMinute =  Minute(Now())
var theSecond =  Second(Now())
var timeString = `${Text(Now(), 'hh:mm')} ${Iif(Hour(Now()) >= 12, 'PM', 'AM')}`

var hourDegrees =   theHour * 30 + (theMinute * 0.5) + (theSecond / 120)
var minuteDegrees = theMinute * 6 + (theSecond * 0.1)

var outDict = {
    fields: [
        {name: 'theHour',       type: 'esriFieldTypeInteger'},
        {name: 'theMinute',     type: 'esriFieldTypeInteger'},
        {name: 'hourDegrees',   type: 'esriFieldTypeDouble'},
        {name: 'minuteDegrees', type: 'esriFieldTypeDouble'},
        {name: 'timeString',    type: 'esriFieldTypeString'}],
    geometryType: '',
    features: [
        {attributes: {
            theHour:       theHour,
            theMinute:     theMinute,
            hourDegrees:   hourDegrees,
            minuteDegrees: minuteDegrees,
            timeString:    timeString
        }}]
}

return FeatureSet(Text(outDict))

 

Which returns something like this:

theHourtheMinutehourDegreesminuteDegreestimeStringFID
88244.1416666649.708:08 AM0

 

Making the Clock

This is all well and good, but it's just a FeatureSet right now, and a far cry from a nice analog clock. How do we turn this into something visual? I had initially tried to misuse an existing widget, like a circular progress meter or a pie chart. Ultimately, these did not work well enough to pass as a standard clock, but did make for interesting widgets.

The real answer is spelled S-V-G. (That's Scalable Vector Graphics, if you don't know.)

SVGs are great for all kinds of reasons, but for our purposes here, we like them because they are code-based and totally possible in any element that supports HTML source editing, where we can pipe in our attributes into specific parts of the code.

Now, this post isn't meant to be a primer on SVGs, so I'm going to gloss over the particulars. Just know that we are making:

  1. A large circle
  2. Two paths
    1. Shorter, wider hour hand
    2. Longer, thinner minute hand

With the two paths, we are including the tag transform, within which we define a rotation value in the format (<degrees>, <origin x>, <origin y>). Here's the full HTML:

 

<p style="text-align: center">
  <svg height="300" width="300">
    <circle
      cx="150"
      cy="150"
      fill="white"
      r="130"
      stroke="black"
      stroke-width="5"
    ></circle>
    <path
      d="M150 150 L 150 40 Z"
      stroke="black"
      stroke-width="3"
      transform="rotate({minuteDegrees},150,150)"
    ></path>
    <path
      d="M150 150 L 150 60 Z"
      stroke="black"
      stroke-width="5"
      transform="rotate({hourDegrees},150,150)"
    ></path>
  </svg>
</p>

 

Without any attribute rotation, this HTML would look like this:

jcarlson_0-1642702965813.png

 

Which Widget?

Our single requirement is that we be able to access the HTML source editing and be able to pipe in attributes, which pretty much just leaves us with the List widget. Nothing else gives us both.

As silly as a single-item list is, we'll do it. Create a list, reference the data expression, and go to the Line Item Template editor and switch it to Source, then paste in the HTML above.

That's it! Your clock should now be referencing the calculated attributes and reflecting the current time. I changed my widget background to make it easier to see.

jcarlson_1-1642703080759.png

Application

I started this just to see if I could do it, and if the end result could actually look nice in addition to being functional. I'm pretty pleased with it. But maybe you don't want a clock in your dashboard, so why bother?

Before you shrug this off as something that doesn't apply to you, consider how else you might use SVG and calculated attributes. You could:

  • Have a list of symbols in which the shape, fill and stroke colors, stroke width, fill pattern, and many other attributes can be defined by your feature attributes
  • Animate your SVG? Possibly not recommended, but it's possible
  • Have a list of COGO lines in which the line item actually looks like the line itself
  • Use grouping in your Data Expression and create totally custom, non-standard charts

They sky's the limit, really, as long as you're willing to get creative!

Check out my clocks here!

 

EDIT:

That am/pm needed to be >= 12, since 12:00 is still PM.

SVG's won't actually rotate past 360, so that needed to be fixed.

3 Comments
About the Author
I'm a GIS Analyst for Kendall County, IL. When I'm not on the clock, you can usually find me contributing to OpenStreetMap, knitting, or nattering on to my family about any and all of the above.