SVG Web Animation

There are a number of good Web animation techniques that are available. Flash animation support is being discontinued in 2020, but luckily there are some good alternatives, such as HTML 5 Canvas, Scalar Vector Graphics (SVG),  and even CSS (Cascading Style Sheets) can be used .

HTML 5 Canvas draws 2D graphics, on the fly with a JavaScript. The canvas is rendered pixel by pixel. In canvas, once the graphic is drawn, it is forgotten by the browser. If its position should be changed, the entire scene needs to be redrawn, including any objects that might have been covered by the graphic.

SVG is a language for describing 2D graphics in XML, which means that every element is available within the SVG DOM. In SVG, each drawn shape is remembered as an object. If attributes of an SVG object are changed, the browser can automatically re-render the shape.

In this blog I wanted to document my SVG testing.

Using SVG

SVG can be used inline within an HTML document. Below is an example with a rectangle, circle and some text.

<!DOCTYPE html>
<html>
<body>

<svg width="400" height="400">
  <rect width="100" height="100" fill= "blue" />
  <circle cx="150" cy="50" r="40" fill="green" />
  <text x="10" y="120" fill="red">SOME SVG TEXT</text>
</svg>

</body>
</html>

This will generate a web like:

svg1

An SVG element has a width and height property and all the drawing elements are positioned within this area.

Dynamic SVG Bar

A bar can be created using a rectangle () element. Javascript is used to dynamically adjust the top and height of the rectangle.

<!DOCTYPE html>
<html>
<head>
<script type="text/JavaScript">

setTimeout(updatebar, 2000);

function updatebar() {
// 
  var newvalue = (Math.random() * 300); // get a random integer 0-100
  if (newvalue > 250) {
    document.getElementById("bar1").style.fill = "red";
  } else {
    document.getElementById("bar1").style.fill = "green";
  }
  newvalue = Math.round(newvalue);
  var newheight = newvalue.toString();
  var num = 300 - newvalue - 5;
  var newy = num.toString();  

  document.getElementById("bar1").setAttribute("height",newheight);
  document.getElementById("bar1").setAttribute("y",newy);
  document.getElementById("bartext").innerHTML = "value: " + newheight ;
  setTimeout(updatebar, 2000);
}
</script>
</head>
<body>
<h2> SVG Bar Example</h2>
<svg width="400" height="400" >
  <rect x="0" y="0" width="160" height="300" style="fill:white;stroke:black;stroke-width:4;" />
  <rect id="bar1" x="5" y="145" width="150" height="150" style="fill:green;stroke:black;stroke-width:2;" /> 
  <text id="bartext" x=30 y = 320 style="font-family:verdana;font-weight:bold">value: 50 </text>
</svg>

</body>
</html>

svg_bar

Animate Graphics

There some SVG drawing packages such as: https://www.drawsvg.org/drawsvg.html

There are also some free libraries, I used: https://www.opto22.com/support/resources-tools/demos/svg-image-library to animate a flow valve.

I needed to do a little bit of trial and error but I was able to add a text element to the bottom of the SVG that could be animated with Javascript. Below is the SVG file:

<svg x="0px" y="0px" viewBox="0 0 100 100" xml:space="preserve"><svg><g>
<g id="shape">
  <circle fill="#999999" cx="50.093" cy="32.75" r="32.75"></circle>
  <rect x="0.176" y="71.667" fill="#999999" width="99.833" height="26.833"></rect>
  <rect x="44.926" y="63" fill="#999999" width="10.333" height="10.5"></rect>
  <rect y="70" fill="#999999" width="4.5" height="30"></rect> 
  <rect x="95.509" y="70" fill="#999999" width="4.5" height="30"></rect>
</g>
<g id="linear">
  <linearGradient class="linear" id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="50.0926" y1="94.5113" x2="50.0926" y2="75.6554">
    <stop offset="0" style="stop-color:#E6E6E6"></stop>
    <stop offset="1" style="stop-color:#000000"></stop>
  </linearGradient>
  <polygon class="linear" fill="url(#SVGID_1_)" points="70.031,75.655 60.041,79.079 40.144,79.079 30.154,75.655 0.176,75.655 0.176,94.511 
    29.289,94.511 40.144,90.791 60.041,90.791 70.896,94.511 100.009,94.511 100.009,75.655   ">
</g>
<g id="light">
  <circle fill="#E6E6E6" cx="50.093" cy="32.75" r="29.074"></circle>
  <path fill="#E6E6E6" d="M44.833,71.5v-6.409c0.606,0.096,1.217,0.179,1.833,0.241V71.5H44.833z"></path>
</g>
<g id="hlight">
  <path fill="#FFFFFF" d="M18.343,33.75C18.343,15.663,33.005,1,51.093,1c8.789,0,16.763,3.469,22.646,9.104
    C67.777,3.881,59.391,0,50.093,0c-18.087,0-32.75,14.663-32.75,32.75c0,9.298,3.881,17.685,10.104,23.646
    C21.812,50.514,18.343,42.539,18.343,33.75z">
  <rect x="26.093" y="21.474" fill="#FFFFFF" width="48" height="23.053"></rect>
</g>
<g id="shadow">
  <path fill="#000000" d="M72.738,9.104c5.635,5.882,9.104,13.857,9.104,22.646c0,18.087-14.663,32.75-32.75,32.75
    c-8.789,0-16.763-3.469-22.646-9.104C32.408,61.619,40.795,65.5,50.093,65.5c18.087,0,32.75-14.663,32.75-32.75
    C82.843,23.452,78.961,15.066,72.738,9.104z">
  <path d="M55.259,71.5v-6.409c-0.606,0.096-1.217,0.179-1.833,0.241V71.5H55.259z"></path>
  <polygon points="27.235,22.589 74.093,22.589 74.093,21.474 26.093,21.474 26.093,44.526 27.235,44.526  "></polygon>
  <path d="M22.018,33.75c0-16.057,13.017-29.074,29.074-29.074c7.774,0,14.83,3.057,20.047,8.028
    c-5.296-5.558-12.764-9.028-21.047-9.028c-16.057,0-29.074,13.017-29.074,29.074c0,8.283,3.469,15.751,9.028,21.047
    C25.076,48.58,22.018,41.524,22.018,33.75z">
</g>
</g>
<text id="flowval" name="flowval" x = "40" y = "40" fill= "green" style="font-family:verdana;font-weight:bold"> 00 </text></svg>
</svg>

External SVG files can be used in a Web page by:

  •  <img – as an image file. (Only good for static or self-contained SVG files)
  • <frame – as a frame.
  • <object – as a object reference within an existing web document
  • <embed – as an embedded element

Unfortunately I found that Chrome only supports inline SVG with Javascript. Microsoft Edge and Firefox worked with both the object and embed tags.

Below is the code that I used to animate the text on the flow valve:

<!DOCTYPE html>
<html>
<head>
<script type="text/JavaScript">

setTimeout(updatebar, 2000);

function updatebar() {
// 
  var randvalue = (Math.random() * 100); // get a random integer 0-100
  var flowvol = Math.round(randvalue.toString());
  // set the text in the svg object   
  var svgobj = document.getElementById("flowmeter");
  var svgdoc =  svgobj.getSVGDocument();
  svgdoc.getElementById("flowval").textContent =  flowvol ;
  
  setTimeout(updatebar, 2000);
}
</script>
</head>
<body>
<h2> SVG Flow Meter</h2>
<embed id="flowmeter" height= '50%' width='100%' src="flowmeter.svg" >

</body>
</html>

flowmeter_1

Final Comments

I found that it can be a little challenging to tweek existing SVG library examples. The browser “development tools” can be used to find and test SVG elements. Below is an example where I found the solar panel element that I need to animate.

svg_devtools

For 100% cross-browser capability it would be recommended to put all the SVG code in one document.

The next step will be to use a Raspberry Pi Web app and some AJAX calls to animate the data.

Text Graphics

While I was working on curses text based graphical interfaces I came across two interesting Linux text applications:

  • cowsay – generates ASCII pictures of a cow (or other animals) with a message
  • jp2a – a small utility that converts JPG images to ASCII.

I this blog I wanted to document some of the things that I found and how these utilities could be used in a ncurses program.

Cowsay

Cowsay has been around in the Linux world since 2007. It is now available in Windows and Android. To install cowsay in Linux or a Rasberry Pi :

sudo apt-get install cowsay

Coway takes text messages that you pass it.

~$ cowsay "I'm not worried about mad cow...because I'm a helicopter"
 _________________________________________
/ I'm not worried about mad cow...because \
\ I'm a helicopter                       /
 -----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

If you install the Linux fortune app (sudo apt install fortune) you can pass random fortune messages:

~$ fortune | cowsay
 _______________________________________
/ You get along very well with everyone \
\ except animals and people.            /
 ---------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

There are a number of different images that can be used. To see the list of what is available:

~$ cowsay -l
Cow files in /usr/share/cowsay/cows:
apt bud-frogs bunny calvin cheese cock cower daemon default dragon
dragon-and-cow duck elephant elephant-in-snake eyes flaming-sheep
ghostbusters gnu hellokitty kiss koala kosh luke-koala mech-and-cow milk
moofasa moose pony pony-smaller ren sheep skeleton snowman stegosaurus
stimpy suse three-eyes turkey turtle tux unipony unipony-smaller vader
vader-koala www

To display all the images:

~$ for i in $(cowsay -l); do cowsay -f $i "$i"; done
 _____
< apt >
 -----
       \ (__)
         (oo)
   /------\/
  / |    ||
 *  /\---/\
    ~~   ~~
 ___________
< bud-frogs >
 -----------
     \
      \
          oO)-.                       .-(Oo
         /__  _\                     /_  __\
         \  \(  |     ()~()         |  )/  /
          \__|\ |    (-___-)        | /|__/
          '  '--'    ==`-'==        '--'  '
 _______
< bunny >
 -------
  \
   \   \
        \ /\
        ( )
      .( o ).

....and a bunch more

 Cowsay in Python

There is a native Python cowsay library:

~$ pip install cowsay --user

An example from the Python command line:

>>> import cowsay
>>> cowsay.cow("This is from Python")
  ___________________
< This is from Python >
  ===================
                        \
                         \
                           ^__^                             
                           (oo)\_______                   
                           (__)\       )\/\             
                               ||----w |           
                               ||     ||  

Cowsay in a Curses App

As an example I wanted to make a Raspberry Pi intrusion monitor. First I created a cowsay images with some eyes:

~$ cowsay -f eyes "Raspberry Pi - Intrusion Monitor"

eyes

Once I was happy with the presentation I saved the output to a file:

~$ cowsay -f eyes “Raspberry Pi – Intrusion Monitor” > eyes.txt

In my Python curses app I read the eyes.txt file and used the stdscr.addstr method to write it to the screen. (Note: For more info on writing Python/C curses or Lua curses)


# c_eyes.py - create a curses with a cowsay message
#
import curses , time, random

# create a curses object
stdscr = curses.initscr()
height, width = stdscr.getmaxyx() # get the window size

# define two color pairs
curses.start_color()
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_BLUE, curses.COLOR_BLACK)

# Read the cowsay output file and write it to the screen

f = open("eyes.txt", "r")
eyes = f.read()
stdscr.addstr(0, 0, eyes,curses.color_pair(3))

# Add a footer
stdscr.addstr(height-1, 0, " " * (width-1),curses.color_pair(1))
stdscr.addstr(height-1, 0, " Key Commands : q - to quit " ,curses.color_pair(1))

# Add intrusion code here....
stdscr.addstr(15, 5, "PIR1 input: no movement" ,curses.color_pair(2) )
stdscr.addstr(16, 5, "PIR2 input: no movement" ,curses.color_pair(2) )

curses.curs_set(0) # don't show the cursor
stdscr.refresh()

# Cycle to update text. Enter a 'q' to quit
k = 0
stdscr.nodelay(1)
while (k != ord('q')):
    k = stdscr.getch()

curses.endwin()

c_eyes

jp2a –  converts JPG images to ASCII

jp2a is a Linux utility that is installed by:

apt-get install jp2a

I found that you’ve got to be selective of the jpeg image that you are trying to convert an example of a castle:

 jp2a castle.jpg --colors --background=light 

Another example was to convert a flag. For this example I found the width option to be useful:

 jp2a can.jpg --colors --width=70