Codementor Events

Serving Clickable Scalable Vector Graphics (SVG) Part 4

Published Mar 24, 2018Last updated Sep 20, 2018

A Petri Net State Management Process (as part of the Express Server)

p1.png

This tutorial assumes some knowledge of JavaScript, HTML, and node.js

SVG Scalable Vector Graphics is a part of HTML5. It is a markup language as well.

SVG allows for rendering graphics as line drawings. You make drawings that look like an HTML page. Or, you can create artistic master pieces.

Continuing from the previous post, SVG Part 3

We have accomplished most of the tasks we need to make the web page go:

  • Create an SVG graphic to serve on a page.
  • Make a little JavaScript utility to generate some code for the animation tools.
  • Set up a simple node.js server using express to serve the page
    • Include a working child process that manages Petri State.

In the last part of this tutorial, SVG Part 3, We insterted our petri net state management system into our express stack. We accomplished that task by rewriting petriState.js.

So, in this part of the tutorial Part 4, we can finish the interface and use the results of the Petri net state keeper.

There are a couple of steps left to go through before we can get a live page up. Here is what is left to do:

  • Create JavaScript that will animate the SVG
  • Run the page in a browser.

So, this what we intended to do. We are going to show the state changes of the Petri net.

Updating the Server Side

To make our life a little easier, we can utilize the socket.io chat example. In fact, I went through some trials and tribulations yesterday when some errors cropped up as I change versions. So, I made changes to the express server and the web page. I will show the new code below.

One other decisions I made was to use the chat example to give my Petri net page an interface for user input. This is a fairly arbitrary way to get data into the net. But, it allows for some testing in the absence of real systems.

Here is the code for the server as it stands today. It is about the same as seen in the previous parts of this tutorial. But, now it is uses 'sendFile' from express for regular pages and JavaScript. But, it uses the homebewed loadPage for SVG. It turns out that juery failed to bind the result of sendFile to the target SVG containing element on the page.

Notice the socket.io part is added in. And, the elements needed to perform chat are included. You can see petri_io.emit('message', msg); is called on receipt of the message. But, also, if the user carefully types the right kind of message, it will be used as input to petriState.js.

const fs = require('fs');
const { spawn } = require('child_process');

// ---------
const appPort = 3000;

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

var petri_io = io.of('/petri');

petri_io.on('connection', function(socket){

    socket.on('disconnect', function() {
        console.log('user disconnected');
    });

    socket.on('message', function(msg) {
        console.log('message: ' + msg);
  
        petri_io.emit('message', msg);

        if ( typeof msg === "string" ) {
            try {
                msg = JSON.parse(msg);
            } catch (e) {
                // just to let random messages go through
            }
        }

        if ( msg.type !== undefined ) {
            HWProc.send({ "message" : msg.message,
                            "mtype" : msg.type,
                            "sourceNode" :  msg.sourceNode,
                            "value" : msg.value
                        });
        }
    });
});

const HWProc = spawn('node', ['./petriState.js'],
                     {
                         stdio: [process.stdin, process.stdout, process.stderr, 'ipc']
                     });


HWProc.on('close', (code) => {
              console.log(`child process exited with code ${code}`);
});

HWProc.dataEvents = petri_io;

// Receive message to be forwarded to socket.io clients.
HWProc.on('message', (message) => {
              HWProc.dataEvents.emit('datum', message);
          });

app.get('/', function(req, res){
    res.sendFile(__dirname + '/example.html');
})

app.get('/:file', function(req, res){
    console.log(req.params.file)
    res.sendFile(__dirname + '/' + req.params.file);
})

function loadPage(filename, res) {
    fs.readFile(filename,'utf8', (err,data) => {
                    if ( err ) {
                        res.send(JSON.stringify(err));
                    } else {
                        var page = data.toString();
                        res.send(page);
                    }
                })
}

app.get('/svg/:svFile',(req,res) => {  // look in a particular directory
            loadPage('./svg/' + req.params.svFile,res);
        });

http.listen(appPort, function(){
  console.log('listening on *:' + appPort);
});

Updating the Client Side

So, there are two parts to the client side, which is being used as a host for SVG. There is the HTML and there is the basic application JavaScript which makes calls to jQuery, and ultimately provides animation methods.

Web Page

Here is the HTML. You might recognize elements from pervious parts of this turorial. You might also recognize parts of page for chat. In fact, they have been fused together.

<html>
  <head>
    <meta http-equiv="Content-Type" content="charset=UTF-8" />
    <title>svg demo</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
      </style>
  </head>
  <body>
    <h1>Simple example that loads an SVG application</h1>
    <div>
      <ul id="messages"></ul>
      <form action="">
        <input id="m" autocomplete="off" /><button>Send</button>
      </form>
    </div>
    <div id="svgGoesHere-petri" >
      <!---  SVG GOES HERE -->
    </div>
    </body>
  
<script language="javascript">
  // This is part of the app for loading SVG.
  window.appVars = {
    runners : { }
  };
  window.fetchSections = "petri";
  window.targetDiv = "svgGoesHere-petri";
  window.fetchPage = "petri.svg"
  window.useFetchSVG = true;
</script>

<script src="/socket.io/socket.io.js"></script>
<script language="javascript" src="jquery-2.1.4.min.js"></script>
<script>
// here is code from the chat application on socket.io.
$(function () {
  var socket = io('/petri');
  $('form').submit(function(){
           	socket.emit('message', $('#m').val());
           	$('#m').val('');
           	return false;
        });
  socket.on('message', function(msg){
          $('#messages').append($('<li>').text(msg));
      });
  });
</script>
<!-- Call the client, which does the rest for us. -->
<script language="javascript" src="petriClientApp.js"></script>
</html>

Application JavaScript

The application is ready to send and receive messages. It needs to load the SVG as well.

The following code is petriClientApp.js. This is all there is to it:

$(document).ready(function() {
    secondFetch();
    // do other things here.
});

function secondFetch() {
    var section = window.fetchSections;
    // fetch the svg section from the server...
    if ( window.useFetchSVG ) {
        var page = window.fetchPage;
        var dataFetchPage = `/svg/${page}`;
        var parentEl =  "#" + window.targetDiv;
        $.get( dataFetchPage, function( data ) {
            $( parentEl ).html( data );
        });
    }
}

function showToken(tid,viz) {
    var svgElement = $('#' + tid);
    if ( viz ) svgElement.show();
    else svgElement.hide();
}
// blinker
var gViz = false;

setInterval(() => {
                gViz = !gViz;
                showToken('circle-token-A',gViz)
            }, 2000);

You may wonder what is going on at the end of the file, petriClientApp.js. I have put in a setInterval to flash the a single token. The routine just toggles a bit and the calls showToken, which is just above it. Notice that it uses jQuery on SVG elemens. Of course, SVG is just a standard for more HTML tags, and those tags are now part of HTML 5. They are also being supported by AMP. That is just to let you know. The page developed here is not an AMP page. But, maybe I can write another tutorial about doing a similar task here on AMP pages.

For now, we just want to exchange our bit of joy at seeing a circle flash on a page for seeing a Petri Net go through its steps.

Last But Not Least: Petri Net Animation

As a first step we might do something that will update the Petri net. Then, we have to get data from the sever that tells us how to draw the picture.

When it comes to redrawing the picture, this application needs to do something very much like setInterval call is doing. But, instead of being ruled by a clock, it is ruled by the data from the server. And, instead of updating one token, it updates all the ones we know of in our picture.

Send Data to the Petri Net State Manager

So, I left the chat box from the chat tutorial in the HTML page. And, if you type in the write message, the state of the petri net should change.

Let's see if it works.

So, I type the following into the chat entry line at the bottom of the page:

{ "message" : "hi", "type" : "petri", "sourceNode" :  "circle-input-A", "value" : 1 }

Then on my console, I will see the following code, with the state set. But, so far, no changes on the page. As such:

{ state: 'report',
  data: 
   { 'circle-input-C': [ 'circle-input-C', 0 ],
     'circle-input-A': [ 'circle-input-A', 1 ],
     'circle-input-D': [ 'circle-input-D', 0 ],
     'circle-input-E': [ 'circle-input-E', 0 ],
     'circle-input-B': [ 'circle-input-B', 0 ],
     'circle-output-Q': [ 'circle-output-Q', 0 ],
     'circle-output-P': [ 'circle-output-P', 0 ],
     'circle-state-F': [ 'circle-state-F', 0 ],
     'circle-state-G': [ 'circle-state-G', 0 ],
     'circle-state-H': [ 'circle-state-H', 0 ] } }

So, you can see the 1 next to 'circle-input-A'.

Of course, I put in a line for debugging:

HWProc.on('message', (message) => {
   console.log(message)  // this is that line
              HWProc.dataEvents.emit('datum', message);
          });

So, I will delete it. But, now I am fairly confident that data is going to the clients. So, let's work on the client.

Getting Data from the Petri Net State Manager

Our program can receive data right now. In fact, it might be receiving Petri net state data, but we would not know. That is because the express server is emitting datum messages in our namespace petri. It forwards messages from petriState.js with the following line:

  HWProc.dataEvents.emit('datum', message);

So, we have to add an event listener for this event. And, then it has to call the method that updates the state of the tokens. So, in the HTML pages, I have added the following line write after the line for recieving chat messages from the chat application:

  socket.on('datum', function(dataMsg){
        appUpdatePetri(dataMsg);
      });

Now, we can define appUpdatePetri in the file petriClientApp.js.

In order to define appUpdatePetri, it helps to look at the message that is being sent from the source of the Petri states. So, looking in petriState.js, we see the following lines:

setInterval(() => {
                pNet.step(); // step the petriNet every gInterval miliseconds
                process.send({ state: 'report', data: pNet.report() })
            },gInterval)

In fact the state is being sent once every somemany miliseconds, as defined by configuration. The message has the following form:

{ 
  state: 'report', 
  data: {
    	"circle-state-A" : 0,
        "circle-state-B" : 1,
        ... (etc)
  } 
}

The salient part, data contains a map of Petri node to the number of active markers for the node. So, our function, appUpdatePetri, just needs to read this map and make calls to showToken, passing false when the resource count is 0.

Here is the code that does that:

function appUpdatePetri(dataMessage) {
    if ( dataMessage.state === "report" ) {
        var stateMap = dataMessage.data;
        for ( var pnode in stateMap ) {
            var tokenId = 'circle-token-';
            var nid = pnode.split('-');
            nid = nid[nid.length-1];
            tokenId += nid;
            var state = ( stateMap[pnode][1] > 0 ) ? true : false;
             showToken(tokenId,state);
        }
    }
}

Now, with it all together, you can type inputs to the net all day and watch the states update.

Conclusion

So, I can see it work. I don't always get to see something that looks very logical. But, that has something to do with how quickly the updates are made to the state. Set the value longer, and tokens will staty on the page longer. Make it faster, it will be hard to see them at all.

This demo does not include animation of the output on the exit node. The run-petri.js module does not keep a state of the exit node. For the exit node, it just calls the callback. If you want to change the callback, you can change properties of the exit nodes.

The module, run-petri.js steps forward just like a petri net. It is not entirely asynchronous. But, with node.js, you can make an asynchronous token forwarding mechanism which allows for ways to deal with confusion and conflict. That will be the topic of another post elsewhere. Look for information about this through the following resources.

(coming soon - maybe even by the end of this day.)
You can try it out at copious.world. Or, get the code here: .

Thanks for taking your time here.

Discover and read more posts from Richard Leddy
get started