DEV Community

Ampersanda
Ampersanda

Posted on • Originally published at Medium on

4 2

Quil — Making a Clock with Clojurescript

Hello guys, finally, we create a thing, yay! Maybe code below is not the best code to get the best performances but I try my best :D,

sorry…

I am not good at explain thing. I open for suggestion and criticism :).

*Please read comments inside the code for hint.

Requirements

  1. JDK v8 (because the newer version get trouble with Clojure)
  2. Leiningen

Oh yeah, time to create a new things!! (I mean project).

lein new quil-cljs clock
Enter fullscreen mode Exit fullscreen mode

Or you just can do that online on http://quil.info/sketches/create

Attention!!!

To make sure things, in this project, we’re just gonna focus on one file called core.cljs , open it :D

Running empty project (It’s not actually empty)

To run the current project, just execute command below inside the project folder, as soon as the figwheel done, Wait for a while until you see Successfully compiled “resources/public/js/main.js” , open http://localhost:3449 and minimize the terminal instead of closing it.


lein figwheel
Enter fullscreen mode Exit fullscreen mode

The Clock

So, the clock we are going to make is like this

I know that’s it’s not kinda beautiful :’( , but we are going to learn some of the basics such as change the background, create an Arc, line etc.

As seen on the screenshot above, the circle around is kinda jaggy, like no smooth at all, there’s an issue about it and you can follow [link].

Setup

Change the setup function and the defsketch with the below format (please read the comments and below is just one core.cljs file).

and also delete the update function because we just re-draw the elements over and over.

(defn ^:export run-sketch []
(q/defsketch clock
:host "clock"
;; setting size of the canvas
:size [500 500]
;; setup function called only once, during sketch initialization.
:setup setup
:draw draw-state
;; This sketch uses functional-mode middleware.
;; Check quil wiki for more info about middlewares and particularly
;; fun-mode.
:middleware [m/fun-mode]))
(defn setup []
;; set antialiasing on
(q/smooth)
;; set the frame-rate 30fps
(q/frame-rate 30)
;; set the color mode to rgb
(q/color-mode :rgb))
view raw core-setup.cljs hosted with ❤ by GitHub

Draw! 🎨

Here’s the sample of on draw function, which is only draw the background color gray

(defn draw-state [state]
; Clear the sketch by filling it with light-grey color.
(q/background 150))

Drawing Line

(q/line x-from y-from x-to y-to)
Enter fullscreen mode Exit fullscreen mode

x-from : horizontal / x position where the line should start

y-from : vertical / y position where the line should start

x-to : horizontal / x position where the line should end

y-to : vertical / y position where the line should end

Making Arc

Yes, it’s called arc, it’s a pie not a circle

(q/arc pos-x pos-y width height value)
Enter fullscreen mode Exit fullscreen mode

pos-x : horizontal / x position where the center point of arc should start

pos-y : vertical / y position where the center point of arc should start

width : horizontal / x diameter of the pie

height : variable / y diameter of the pie

value : how much pie part do still remains?

Now, we talk about the value, arc has value from 0 to 2π which means 0° = 0 and 360° = 2π, so how about 45°, 77°, and so on?.

Well, math will solve our problems. 🌈

Mapping Values

Mapping values is like how the step of the increment as per value

Manual Way

Actually, there’s a formula

deg° -\> deg\*π/180
Enter fullscreen mode Exit fullscreen mode

and replace the deg with degree you want to calculate

90° -\> 90\*π/180 = 0.5π
360° —\> 360\*π/180 = 2π
Enter fullscreen mode Exit fullscreen mode

and how we apply it, to the clock.

Clock has 0–59 seconds (right?!), so the circle is not fully 360° because 360° mapped in 60 seconds

First, We can convert 59 seconds to degree

60/59 = 360°/x
 x = 360°\*59/60
 x = 354°
Enter fullscreen mode Exit fullscreen mode

Map-Range Way

(q/map-range value min-val1 max-val1 min-val2 max-val2)
Enter fullscreen mode Exit fullscreen mode

value : value to calculated

min-val1 : minimum value of the range, like in seconds has 0 seconds

max-val1 : maximum value of the range, like in seconds has 59 seconds

min-val2 : minimum value of the second range (0°)

max-val2 : maximum value of the second range (354°)

That’s all we need to mapping the values.

Getting Hours, Minutes, and Seconds

(q/hour) ;; getting hour
(q/minute) ;; getting minute
(q/seconds) ;; getting seconds
Enter fullscreen mode Exit fullscreen mode

You can see the result using console.log ,

(js/console.log (q/hour))
Enter fullscreen mode Exit fullscreen mode

Let’s Draw

For the first we are going to draw the arcs first, then the lines

Declaring all stuffs

(defn draw-state [state]
(q/background 51)
(let
[max-scale-h (- 1 (/ 1 12)) ;; get percentage of current hour, range value from 0-1
max-scale-m (- 1 (/ 1 60)) ;; get percentage of current minute, range value from 0-1
max-scale-s (- 1 (/ 1 60)) ;; get percentage of current seconds, range value from 0-1
;; above values are half of the actual values, so we need to multiply it with 2π
;; mappings
h (q/map-range
(if (> (q/hour) 12) (- (q/hour) 12) (q/hour)) ;; if hour more than 12, subtract with 12
0 11
0 max-scale-h)
m (q/map-range
(q/minute)
0 59
0 max-scale-m)
s (q/map-range
(q/seconds)
0 59
0 max-scale-s)
;; multiply values above with 2π
h-a (* q/TWO-PI h)
m-a (* q/TWO-PI m)
s-a (* q/TWO-PI s)]))

Drawing values (into arcs)

(defn draw-state [state]
(q/background 51)
(let
[max-scale-h (- 1 (/ 1 12)) ;; get percentage of current hour, range value from 0-1
max-scale-m (- 1 (/ 1 60)) ;; get percentage of current minute, range value from 0-1
max-scale-s (- 1 (/ 1 60)) ;; get percentage of current seconds, range value from 0-1
;; above values are half of the actual values, so we need to multiply it with 2π
;; mappings
h (q/map-range
(if (> (q/hour) 12) (- (q/hour) 12) (q/hour)) ;; if hour more than 12, subtract with 12
0 11
0 max-scale-h)
m (q/map-range
(q/minute)
0 59
0 max-scale-m)
s (q/map-range
(q/seconds)
0 59
0 max-scale-s)
;; multiply values above with 2π
h-a (* q/TWO-PI h)
m-a (* q/TWO-PI m)
s-a (* q/TWO-PI s)]
;; setting the stroke thickness
(q/stroke-weight 8)
;; setting the arc with not fill inside
(q/no-fill)
;; drawing the arcs
; drawing hours
(q/stroke 255 100 150)
(q/arc 0 0 400 400 0 h-a)
; drawing minutes
(q/stroke 150 100 255)
(q/arc 0 0 360 360 0 m-a)
; drawing hours
(q/stroke 150 255 100)
(q/arc 0 0 320 320 0 s-a)))

see this part,

;; setting the stroke thickness
(q/stroke-weight 8)

;; setting the arc with not fill inside
(q/no-fill)

;; drawing the arcs
; drawing hours
(q/stroke 255 100 150)
(q/arc 0 0 400 400 0 h-a)

; drawing minutes
(q/stroke 150 100 255)
(q/arc 0 0 360 360 0 m-a)

; drawing seconds
(q/stroke 150 255 100)
(q/arc 0 0 320 320 0 s-a)
Enter fullscreen mode Exit fullscreen mode

The clock is drawn but we got wrong position, so CENTER IT!!!, with with-translation

(defn draw-state [state]
(q/background 51)
(let
[max-scale-h (- 1 (/ 1 12)) ;; get percentage of current hour, range value from 0-1
max-scale-m (- 1 (/ 1 60)) ;; get percentage of current minute, range value from 0-1
max-scale-s (- 1 (/ 1 60)) ;; get percentage of current seconds, range value from 0-1
;; above values are half of the actual values, so we need to multiply it with 2π
;; mappings
h (q/map-range
(if (> (q/hour) 12) (- (q/hour) 12) (q/hour)) ;; if hour more than 12, subtract with 12
0 11
0 max-scale-h)
m (q/map-range
(q/minute)
0 59
0 max-scale-m)
s (q/map-range
(q/seconds)
0 59
0 max-scale-s)
;; multiply values above with 2π
h-a (* q/TWO-PI h)
m-a (* q/TWO-PI m)
s-a (* q/TWO-PI s)]
;; setting the stroke thickness
(q/stroke-weight 8)
;; setting the arc with not fill inside
(q/no-fill)
;; setting the stroke color
(let [halfw (/ (q/width) 2)
halfh (/ (q/height) 2)]
;; halfw -> is the half of the current canvas width
;; halfh -> is the half of the current canvas height
(q/with-translation [halfw halfh]
;; drawing the arcs
; drawing hours
(q/stroke 255 100 150)
(q/arc 0 0 400 400 0 h-a)
; drawing minutes
(q/stroke 150 100 255)
(q/arc 0 0 360 360 0 m-a)
; drawing hours
(q/stroke 150 255 100)
(q/arc 0 0 320 320 0 s-a)))))

It does success drawing the arcs actually, but it seems like we got wrong start position, because circles/arcs start in 0° and clock start 90° back from the 0° or -90°, use with-rotation to rotate specific (block of) drawings.

(defn draw-state [state]
(q/background 51)
(let
[max-scale-h (- 1 (/ 1 12)) ;; get percentage of current hour, range value from 0-1
max-scale-m (- 1 (/ 1 60)) ;; get percentage of current minute, range value from 0-1
max-scale-s (- 1 (/ 1 60)) ;; get percentage of current seconds, range value from 0-1
;; above values are half of the actual values, so we need to multiply it with 2π
;; mappings
h (q/map-range
(if (> (q/hour) 12) (- (q/hour) 12) (q/hour)) ;; if hour more than 12, subtract with 12
0 11
0 max-scale-h)
m (q/map-range
(q/minute)
0 59
0 max-scale-m)
s (q/map-range
(q/seconds)
0 59
0 max-scale-s)
;; multiply values above with 2π
h-a (* q/TWO-PI h)
m-a (* q/TWO-PI m)
s-a (* q/TWO-PI s)]
;; setting the stroke thickness
(q/stroke-weight 8)
;; setting the arc with not fill inside
(q/no-fill)
;; setting the stroke color
(let [halfw (/ (q/width) 2)
halfh (/ (q/height) 2)]
;; move to the center
(q/with-translation [halfw halfh]
;; rotate to -90deg
(q/with-rotation [(* -1 q/HALF_PI)]
;; drawing the arcs
; drawing hours
(q/stroke 255 100 150)
(q/arc 0 0 400 400 0 h-a)
; drawing minutes
(q/stroke 150 100 255)
(q/arc 0 0 360 360 0 m-a)
; drawing hours
(q/stroke 150 255 100)
(q/arc 0 0 320 320 0 s-a))))))

Done!!!

We forgot the lines,

(defn draw-state [state]
(q/background 51)
(let
[max-scale-h (- 1 (/ 1 12)) ;; get percentage of current hour, range value from 0-1
max-scale-m (- 1 (/ 1 60)) ;; get percentage of current minute, range value from 0-1
max-scale-s (- 1 (/ 1 60)) ;; get percentage of current seconds, range value from 0-1
;; above values are half of the actual values, so we need to multiply it with 2π
;; mappings
h (q/map-range
(if (> (q/hour) 12) (- (q/hour) 12) (q/hour)) ;; if hour more than 12, subtract with 12
0 11
0 max-scale-h)
m (q/map-range
(q/minute)
0 59
0 max-scale-m)
s (q/map-range
(q/seconds)
0 59
0 max-scale-s)
;; multiply values above with 2π
h-a (* q/TWO-PI h)
m-a (* q/TWO-PI m)
s-a (* q/TWO-PI s)]
;; setting the stroke thickness
(q/stroke-weight 8)
;; setting the arc with not fill inside
(q/no-fill)
;; setting the stroke color
(let [halfw (/ (q/width) 2)
halfh (/ (q/height) 2)]
(q/with-translation [halfw halfh]
(q/with-rotation [(* -1 q/HALF_PI)]
;; drawing the arcs
; drawing hours
(q/stroke 255 100 150)
(q/with-rotation [h-a]
(q/line 0 0 60 0))
(q/arc 0 0 400 400 0 h-a)
; drawing minutes
(q/stroke 150 100 255)
(q/with-rotation [m-a]
(q/line 0 0 100 0))
(q/arc 0 0 360 360 0 m-a)
; drawing hours
(q/stroke 150 255 100)
(q/with-rotation [s-a]
(q/line 0 0 160 0))
(q/arc 0 0 320 320 0 s-a))))))

the demo page can be accessed here and you can grab it here (Github)

ampersanda/quil-clock

and as always, thank you for taking time to read this article.

Happy Coding :)


Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs