Simple Canvas Drawing

Since version 3.1.3.9, PicoLisp comes with a library for drawing 2D images into HTML5 <canvas> elements. It was initially needed for a project displaying the results of real-time measurements on embedded systems.

This very simple library enables you to write commands in Lisp (in the application program, running on the server), which trigger corresponding JavaScript draw functions (in the browser-GUI on the client).

This article will only explain the underlying principles, and show some examples. General information about the <canvas> element is available at https://developer.mozilla.org/en-US/docs/HTML/Canvas.

Using the Library

To use it, just do the following 4 things:
  1. Include "@lib/canvas.l" in your application.
    
       (load "@lib/canvas.l")
    
    
    This will define a set of Lisp functions, of the form e.g.
    
       (csStrokeRect X Y DX DY)
    
    
    which correspond to JavaScript functions like
    
       strokeRect(x, y, width, height)
    
    


  2. Define a function named drawCanvas:
    
       (de drawCanvas (Id Dly)
          (make
             ... ) )
    
    
    This name "drawCanvas" is mandatory. This function will be used to draw all canvas contents. If there are several instances of <canvas> in the application, drawCanvas can tell them apart by the canvas ID it receives in its first argument.

    drawCanvas should return a list of commands, which is typically built with make.


  3. Create <canvas> element(s) on your pages, each with a unique ID, and a width and a height:
    
       (<canvas> "someId" 400 300)
    
    


  4. Let the canvas drawing loop start when the page is loaded. This can be done either with 'onload' in the page <body> tag:
    
       (html 0 "Page Title" "style.css" '((onload . "drawCanvas('$ID', -1)"))
    
    
    or by 'javascript' calls with 'onload' in the page body (used typically when there are dynamically varying canvas elements, as in "A Small Example" below):
    
       (javascript NIL "onload=drawCanvas('$ID', -1)")
    
    
    In both cases, the JavaScript 'drawCanvas' function (which is built-in into the JavaScript part of the library, and is separate from the Lisp-level 'drawCanvas' function described above) is called. This function accepts the canvas ID, and a numerical "delay" value. If it is negative, the canvas contents are drawn just once. Otherwise (zero or greater) the canvas will auto-draw repeatedly, sleeping that number of milliseconds between each draw.


A Minimal Example

To see the effect of the above four steps, put the following into a file called draw2Rects.l:

   (load "@lib/http.l" "@lib/xhtml.l" "@lib/form.l" "@lib/canvas.l")

   (de drawCanvas (Id Dly)
      (make
         (csStrokeStyle "blue")
         (csStrokeRect 0 0 400 300)
         (csFillStyle "red")
         (csFillRect 100 100 200 100) ) )

   (de canvasTest ()
      (action
         (html 0 "Canvas Test" "@lib.css" '((onload . "drawCanvas('$testID', -1)"))
            (form NIL
               (<canvas> "$testID" 400 300) ) ) ) )

   (de work ()
      (app)
      (redirect (baseHRef) *SesId "!canvasTest") )

   (server 8080 "!work")
Start it as

   $ pil draw2Rects.l +

Then point your browser to http://localhost:8080. You will see a large blue rectangle surrounding a smaller red rectangle.

Note: 'httpGate' must be running on your system for this to work, because XMLHttpRequests fail if the port changes (same-origin policy). Start it (as root) with:

   # /usr/lib/picolisp/bin/httpGate 80 8080



A Small Example

A second, more extensive, example I have installed on the server, so that it can be tried online: http://canvas.picolisp.com.

It shows a page with two tabs. The canvas on the first tab "Zappel" plots a graph of random numbers, at a rate of about 4 per second. Two buttons, "Faster" and "Slower", can be used to change the rate, by halving or doubling the delay.

The second tab "Single" stops the continuous process, and allows you to single-step the plot with the "Step" button.

The three "Pos" buttons at the bottom can be used to "calibrate" the graph vertically, by shifting it up or down similar to the "Y-Pos" knob of an oscilloscope.

I will not comment the code in detail here, but leave it as an exercise to the reader. Here is the complete file:

   # 16apr16abu
   # (c) Software Lab. Alexander Burger

   # *Value *Plot *Offset *Last *Frames *Hz

   (allowed ()
      "!zappel" "@lib.css" )

   (load "@lib/http.l" "@lib/xhtml.l" "@lib/form.l" "@lib/canvas.l")

   (de *DX . 600)
   (de *DY . 300)

   (setq
      *Delay 256
      *DX/10 (/ *DX 10)
      *DY/2 (/ *DY 2) )

   (de drawCanvas (Id Dly)
      (when (>= Dly -1)
         (setq *Plot
            (- *DY *DY/2 (setq *Value (rand -100 +100))) )
         (pop '*Plot) )
      (make
         (csClearRect 0 0 *DX *DY)
         (csFillText *Value 20 20)
         (let (U (usec)  D (- U (default *Last U)))
            (inc '*Frames)
            (when (>= D 1000000)
               (setq *Hz (*/ 100000000 *Frames D)  *Last U  *Frames 0) )
            (csFillText
               (pack (format *Hz 2) " Hz")
               (- *DX 60)
               20 ) )
         (csStrokeStyle "red")
         (csStrokeLine 0 *DY/2 *DX *DY/2)
         (csStrokeStyle "green")
         (csBeginPath)
         (let Y1 (pop '*Plot)
            (and Y1 (csMoveTo 0 (- @ *Offset)))
            (for X *DX/10
               (let Y2 (- (pop '*Plot) *Offset)
                  (if2 Y2 Y1
                     (csLineTo (* X 10) Y2)
                     (csMoveTo (* X 10) Y2) )
                  (setq Y1 Y2) ) ) )
         (csStroke) ) )

   (de zappel ()
      (app)
      (action
         (html 0 "Zappel" '("@lib.css" . "canvas {border: 1px solid}") NIL
            (form NIL
               (<h2> NIL "Zappel Demo")
               (<tab>
                  ("Zappel"
                     (<canvas> "$zappel" *DX *DY)
                     (javascript NIL "onload=drawCanvas('$zappel', " *Delay ")")
                     (gui '(+Able +Button) '(> *Delay 1) "Faster"
                        '(setq *Delay (>> 1 *Delay)) )
                     (gui '(+Button) "Slower"
                        '(setq *Delay (>> -1 *Delay)) ) )
                  ("Single"
                     (<canvas> "$single" *DX *DY)
                     (javascript NIL "onload=drawCanvas('$single', -2)")
                     (gui '(+OnClick +Button)
                        "return drawCanvas('$single', -1)"
                        "Step" ) ) )
               (----)
               (gui '(+Able +Button) '(n0 *Offset) "Pos = 0" '(zero *Offset))
               (gui '(+Button) "++ Pos" '(inc '*Offset 10))
               (gui '(+Button) "-- Pos" '(dec '*Offset 10)) ) ) ) )

   (de main ()
      (do (inc *DX/10)
         (fifo '*Plot NIL) )
      (zero *Offset) )

   (de go ()
      (retire 20)
      (server (or (format (sys "PORT")) 8080) "!zappel") )

   # vi:et:ts=3:sw=3

https://picolisp.com/wiki/?canvasdrawing

16may20   abu