Tuesday, March 23, 2010

SVG + Javascript drag and zoom

Recently I've been working on a project that uses SVG (Scalable Vector Graphics).

I have been using SVGWeb (http://code.google.com/p/svgweb/) so that the SVG will work in all the major browsers.

It is a fantastic library and I am so grateful to the people who work on it.

The things I found difficult were figuring out how to get zooming with the mouse wheel and dragging to work. I had it working in Firefox, using its native SVG renderer, however SVGWeb does things differently. It took me a while to work out how. I'm going to share what I found here. (Hooking the mouse wheel is actually explained on the SVGWeb mailing list: Mouse Wheel Events.)

With dragging, I knew I needed to store the old X and Y values of the position of the mouse and take the difference between them and the new mouse position. For some reason setting global variables for the old X and Y values didn't quite work - the delta was very small, approximatley 7.5 times too small.

With zooming, the SVGWeb library doesn't pick up the mouse wheel event. The way to get around this is to attach the mouse wheel event to the container tag (e.g. div) that is surrounding the object tag that is holding the SVG on the HTML page.

On to the code!

I did not come up with the Javascript - I took it from various places; mostly the SVGWeb mailing list entry above and the "photos" demo that comes with SVGWeb.

This is the main HTML and Javascript for the page that is holding the SVG:

toggle code

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
        <title>SVG Example</title>
        <meta name="svg.render.forceflash" content="true" />
        <link rel="SHORTCUT ICON" href="favicon.ico" />
    </head>
    <body onload="loaded()">
        <div id="svgContainer">
            <!--[if IE]>
            <object id="svgImage" src="example.svg" classid="image/svg+xml" width="100%" height="768px">
            <![endif]-->
            <!--[if !IE]>-->
            <object id="svgImage" data="example.svg" type="image/svg+xml" width="100%" height="768px">
            <!--<![endif]-->
            </object>
        </div>
        <script type="text/javascript" src="svg/src/svg.js" data-path="svg/src/" ></script>
        <script type="text/javascript">
            function loaded()
            {
                hookEvent("svgContainer", "mousewheel", onMouseWheel);
            }
            function hookEvent(element, eventName, callback)
            {
              if(typeof(element) == "string")
                element = document.getElementById(element);
              if(element == null)
                return;
              if(element.addEventListener)
              {
                if(eventName == 'mousewheel')
                  element.addEventListener('DOMMouseScroll', callback, false);
                element.addEventListener(eventName, callback, false);
              }
              else if(element.attachEvent)
                element.attachEvent("on" + eventName, callback);
            }
            function cancelEvent(e)
            {
                e = e ? e : window.event;
                if(e.stopPropagation)
                    e.stopPropagation();
                if(e.preventDefault)
                    e.preventDefault();
                e.cancelBubble = true;
                e.cancel = true;
                e.returnValue = false;
                return false;
            }
            function onMouseWheel(e)
            {
                var doc = document.getElementById("svgImage").contentDocument;  
                e = e ? e : window.event;
                doc.defaultView.onMouseWheel(e);
                return cancelEvent(e);
            }
        </script>
    </body>
</html>

This is the SVG and Javascript:

toggle code

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" onload="loaded()" id="svgMain" >
    <script type="text/javascript" language="javascript">
    <![CDATA[
        var isDragging = false;
        var mouseCoords = { x: 0, y: 0 };
        var gMain = 0;
       
        function loaded()
        {
            var onloadFunc = doload;

            if (top.svgweb)
            {
                top.svgweb.addOnLoad(onloadFunc, true, window);
            }
            else
            {
                onloadFunc();
            }
        }
       
        function doload()
        {
            hookEvent('mover', 'mousedown', onMouseDown);
            hookEvent('mover', 'mouseup', onMouseUp);
            hookEvent('mover', 'mousemove', onMouseMove);
            hookEvent('mover', 'mouseover', onMouseOver);
            gMain = document.getElementById('gMain');
            gMain.vScale = 1.0;
            gMover = document.getElementById('mover');
            gMover.vTranslate = [50,50];
            setupTransform();
        }
       
        function onMouseDown(e)
        {
            isDragging = true;
        }
       
        function onMouseUp(e)
        {
            isDragging = false;
        }
       
        function onMouseOver(e)
        {
            mouseCoords = {x: e.clientX, y: e.clientY};
        }
       
        function onMouseMove(e)
        {
            if(isDragging == true)
            {
                var g = e.currentTarget;
                var pos = g.vTranslate;
                var xd = (e.clientX - mouseCoords.x)/gMain.vScale;
                var yd = (e.clientY - mouseCoords.y)/gMain.vScale;
                g.vTranslate = [ pos[0] + xd, pos[1] + yd ];
                g.setAttribute("transform", "translate(" + g.vTranslate[0] + "," + g.vTranslate[1] + ")");
            }
           
            mouseCoords = {x: e.clientX, y: e.clientY};
           
            return cancelEvent(e);
        }
       
        function setupTransform()
        {
            gMain.setAttribute("transform", "scale(" + gMain.vScale + "," + gMain.vScale + ")");
        }
       
        function hookEvent(element, eventName, callback)
        {
            if(typeof(element) == "string")
                element = document.getElementById(element);
            if(element == null)
                return;
            if(eventName == 'mousewheel')
            {
                element.addEventListener('DOMMouseScroll', callback, false);
            }
            else
            {
                element.addEventListener(eventName, callback, false);
            }
        }
       
        function cancelEvent(e)
        {
            e = e ? e : window.event;
            if(e.stopPropagation)
                e.stopPropagation();
            if(e.preventDefault)
                e.preventDefault();
            e.cancelBubble = true;
            e.cancel = true;
            e.returnValue = false;
            return false;
        }
       
        function onMouseWheel(e)
        {
            e = e ? e : window.event;
            var wheelData = e.detail ? e.detail * -1 : e.wheelDelta / 40;
           
            if((gMain.vScale > 0.1) || (wheelData > 0))
            {
                gMain.vScale += (0.02 * wheelData);
            }
           
            setupTransform();
           
            return cancelEvent(e);
        }
    ]]>
    </script>
    <g id="gMain">
        <g transform="translate(50,50)" id="mover">
            <circle stroke-width="2" stroke="black" cx="0" cy="0"  r="20" fill="red"/>
            <text font-family="verdana" text-anchor="middle" transform="translate(0,40)" fill="black" stroke-width="1" font-size="12" >Drag me!</text>
        </g>
    </g>
</svg>
There is some overlap in the Javascript presented there, this is just to keep things simple if you're copy/pasting this to test for your self.

This Javascript in the main file passes the mouse wheel event info to the SVG document:
function onMouseWheel(e)
{
   var doc = document.getElementById("svgImage").contentDocument;   
   e = e ? e : window.event;
   doc.defaultView.onMouseWheel(e);
   return cancelEvent(e);
}
The rest of the important Javascript is in the SVG document.
To get dragging to work, first define a global object to hold position information:
var mouseCoords = { x: 0, y: 0 };
When the mouse moves over the desired element, update the object:
function onMouseOver(e)
{
    mouseCoords = {x: e.clientX, y: e.clientY};
}
There also needs to be a global boolean to switch dragging on and off. I called mine isDragging. Toggle dragging when the mouse is up or down on the element.
function onMouseDown(e)
{
    isDragging = true;
}
      
function onMouseUp(e)
{
    isDragging = false;
}
When moving the mouse with dragging on, change the position of the element and update the object. Notice that the delta is being divided by the scale. This prevents the movement from becoming erratic.
function onMouseMove(e)
{
    if(isDragging == true)
    {
        var g = e.currentTarget;
        var pos = g.vTranslate;
        var xd = (e.clientX - mouseCoords.x)/gMain.vScale;
        var yd = (e.clientY - mouseCoords.y)/gMain.vScale;
        g.vTranslate = [ pos[0] + xd, pos[1] + yd ];
        g.setAttribute("transform", "translate(" + g.vTranslate[0] + "," + g.vTranslate[1] + ")");
    }
  
    mouseCoords = {x: e.clientX, y: e.clientY};
  
    return cancelEvent(e);
}

And that's how it works.

Friday, March 05, 2010

Pomodoro!

I've been feverishly subscribing to blogs recently after I realised I'm only really reading channel9.

I've got so much reading to do it's unreal. I've got through about 50 .NET posts so far and I've got 50 more to go, before I'm caught up. I've also got about 50 PHP posts to read too.

In my .NET blogs I came across this entry: You say tomato i say pomodoro at the developing for .NET blog. The post outlines a simple way to help manage your time effectively. It has inspired me to create a little timer app and a todo list app.

The timer app is really simple: it's a picture of a tomato with a button on it that minimises the app to the notification area and sets a timeout period. Once the period is reached (the length is set in the config file) then the app pops back up and plays a sound at you. I've put the code over at GitHub: code for Pomodoro timer.

The todo list app is equally simple, just a list view and list item entry controls. On close it writes to a file. The source is also at GitHub: code for To Do List.

update

I've uploaded the binaries for each, so you don't have to compile them!

To Do List executable
Pomodoro executable

Tuesday, February 09, 2010

dec2int and foldl

So, I'm trying to learn Haskell (as well as about functional programming) and I have a book I'm using: Programming in Haskell by Graham Hutton.

At the end of each chapter there are exercises.

Chapter 7 is about higher-order functions and one of the exercises is to create a function dec2int which is of the type dec2int :: [Int] -> Int and takes a list of decimal numbers (i.e. numbers 0 to 9); so given the input [1,2,5,7] dec2int would output 1257. The other stipulation is that the function must use foldl.

Step 1 - define dec2int recursively

My first thought in solving this problem was that I wanted to have a working dec2int function.

dec2int [] = 0 dec2int (x:xs) = x*10^(length xs) + dec2int xs

Not very efficient, obviously, as it has to call length for each call to the function, but it works and gives a general idea as to how to write the function.

Step 2 - define dec2int to the letter but not the spirit of the problem

I spent quite some time trying to understand foldl as it's described in the book. foldl's type is foldl :: (a->b->a)->a->[b]->a. A recursive definition looks like this (taken from the book)

foldl f v [] = v foldl f v (x:xs) = foldl f(f v x) xs

So the function f that's passed into foldl has to take a default value, a current value, and return a value that can be used in the function in place of the default value.

My first successful attempt at this does not seem to me to be in keeping with the spirit of the problem:

xANDpos :: [a] -> [(a,Int)] xANDpos [] = [] xANDpos (x:xs) = (x,len):xNp len xs where len = length xs xNp _ [] = [] xNp (pos + 1) (x:xs) = (x,pos):xNp pos xs dec2int :: [Int] -> Int dec2int xs = foldl dec2int' 0 (xANDpos xs) where dec2int' n (x,pow) = n + x*10^pow

f in this instance is dec2int'. xANDpos is a function that takes a list of something and returns a list of tuples of something and its position in the list, if the list were reversed. Not the best name for the function.

I don't think this is in the spirit of the problem because, while it does accomplish the goal, it adds an extra section of recursion when recursion is meant to be being handled by foldl.

Step 3 - this time with spirit

It took some time, but I finally realised that my first function wasn't the only way to get to the answer. The final version is a lot simpler than the second, and therefore more beautiful.

dec2int :: [Integer] -> Integer dec2int = foldl d2i 0 where d2i n x = n*10 + x

This is also a lot quicker - it doesn't traverse the list multiple times. As you can see, I've changed the type of the function, slightly, to allow for bigger numbers to be produced.

Here is a worked example of how the function functions:

dec2int [3,7,0,4,2] = foldl d2i 0 [3,7,0,4,2] = foldl d2i(d2i 0 3 = 0*10 + 3) [7,0,4,2] = foldl d2i(d2i 3 7 = 3*10 + 7) [0,4,2] = foldl d2i(d2i 37 0 = 37*10 + 0) [4,2] = foldl d2i(d2i 370 4 = 370*10 + 4) [2] = foldl d2i(d2i 3704 2 = 3704*10 + 2) [] = foldl d2i 37042 [] = 37042

The function can also be written as a one liner, using lambda: dec2int = foldl(\n x -> n * 10 + x) 0 however I think the other version is more readable.

The problem I encountered here was that my first assumption was incorrect, i.e. the function I came up with in step 1 was not the only way to solve the problem using recursion.

Lesson learned!

Friday, November 06, 2009

Neural Network failure

I have deleted the posts regarding the neural network class as the class didn't work - I couldn't solve the XOR problem.

Tuesday, May 13, 2008

Adaptive Protection

One thing that has fascinated me for the last few years has been the thought that instead of these centralised, hub like antivirus (and anti-malware, anti-spyware, etc.) systems and firewalls that work on a per computer/network basis, we could have an adaptive peer to peer system.

At this point in time I think there are three major uses for the internet. Number one is business, number two is socialising and number three is sharing data. Although I suppose three encapsulates both one and two.

The only downsides to the internet are crime and government. I'm not about to try and fix governance of the internet except to say that peer to peer trumps centralised control, and I'll hopefully convince you of that in this blog, if not this post.

There are two types of crime on the internet; mostly on the web and in email, but they also feature in other applications. The first is digital crime like trojans, spyware, etc. The second type is confidence tricks, like when on ebay someone sells you a DVD allegedly signed by Ron Jeremy and it's just a blank DVD with a squiggle on it, and there's nothing to prove it is what it is. The latter type of crime is rife in a walks of life. Digital crime, however, can only happen on computers.

So now we get to the point of my post: adaptive protection from digital crime.

Our current model for protection is one where we install some software, be it a firewall, script blocker, antivirus programme or whatever and allow it to run. Every so often the software will call home, either automatically, or because we tell it too, and it will update how it works. This is what we do and it means that we are always one step behind the attackers.

Let me throw some crazy, metaphorical idealism at you.

The internet is like fertiliser. It's not a living thing its self, it doesn't change as such. Its purpose is always to be the place where ideas can grow and evolve. The applications of the internet: bit torrent, web, ftp, usenet, gopher, finger, etc., are all lifeforms that evolve, or become endangered or extinct (to over extend the metaphor). Essentially we (users) behave like seed carriers; we increase the population of an application by spreading it around, getting others to use it, a lot like corn or carrots.

The other applications, the ones that are less than favourable for most users, are also able to flourish in this fertiliser. Like weeds, I suppose, or maybe bacteria or fungi. So like sensible farmers we deploy pesticides to kill them off, which work fine until they evolve and we have to make a better pesticide, and so on and so on.

We have one advantage over bio-technologists, though. If we start doing mad scientist type experiments to try and create programmes that can behave in a way that is adaptive and can help wipe out the undesirable programmes, we can't cause ecological disaster, like DDT or engineering resistant plants might.

My basic premise is that the best way to beat the viruses et al. is through co-operation and on the fly adaptation, via implementing a new internet application. Rather than an application that considers the internet as a place to guard against, create an internet application that wants to defend its turf.

It would have to be an application that runs in the background of your computer, and is able to understand what should be happening and what shouldn't, and how to prevent these things. Once recognition of a problem takes place and action is taken then the programme would need to propagate that information to neighbours to help them identify similar problems.

You cannot define all the rules up front. You can, however, define how to define rules, and let evolution take its course. This is one area I think that evolutionary computing will excel in. It is perfectly suited to coming up with solutions that involve iterative design over many generations.

I haven't done enough reading around operating systems to be able to implement such a system, but the idea is most intriguing. I also haven't read around in the adaptive protection literature, so I don't know how far along this research is. It is definitely something I will be getting involved in.

Saturday, December 15, 2007

It seems I'm not very good at keeping on track.

I keep getting distracted by things and don't do enough work on my AI stuff.

It's not that I haven't done any work, but I've started a new project and I haven't progressed at all on the mini project. This is partly due to not having unpacked my Linux box after moving house, but also because my social life has improved after moving.

The new project isn't getting much work done on it either, but I don't want to discuss it yet, as it in the really really early stages. It's currently being developed on Windows XP, but it should be easy enough to port it to Linux when the time comes.

Merry Christmas to anyone reading this!

Wednesday, August 01, 2007

Minor Update

It's been a lot longer than I had planed since my last post.

I have made steps towards a neural network for solving sudoku, but I hit a snag when I came to generating test sudoku on the fly, then I got distracted with other AI research I'm doing and updating my website and stuff going on in my non-digital life.

I haven't got beyond my snag, so I have no real update, other than to say I have created a simple class for single layer neural networks

My other AI research has lead me to investigate Information Theory. I'm finding it very complicated so far, not having a degree in mathematics, but I'm sure I'll figure it out in time.

For now, you'll have to wait, while I decide if I should use canned sudoku grids or work to finish my generator.

Monday, April 09, 2007

New Mini Project

It's been a long while since I've been motivated to do any development.

I've finally come up with a small enough project that I should be able to do over a few weeks.

My main project: finishing the robot football AI, will be put on hold while I get back into the swing of C++ and Linux (slackware).

This mini project is a very simple neural network that will be limited to two layers (no need for complex learning algorithms). I will train the network to recognise sudoku puzzles, and hopefully I'll be able to get it to fill in novel puzzles.

The network code will be a lot simpler than the code I used for my Masters' thesis, with two one dimensional arrays of doubles for the input and output, and a single two dimensional array of doubles for the connections.

I'll post the code as bits get completed.

Saturday, October 21, 2006

3D thing source

OK! the source can be found at http://www.matthewellen.co.uk/index.php?page_name=portfolioitem&item=dynamicmotion

Unveiling

Ok! It's done.

It's not quite how I said it would be. There are no collisions, but everything else is there. All the nodes have the same mass, so there was no need to include that in the model.

To download the finished product goto http://www.geocities.com/griffle_the_waffle/ppi and click on the products link in the left hand menu. The first product is the one you want, although you might want to try out the others too.

I'll post links to the source in a bit.

Saturday, October 14, 2006

Warm up exercises

Right, today I'm going to start a simpler project:

I'm going to try to make an l-system style programme, but without any branches. Also each node will be of a certain type, that will be attracted to one type and repel its self from another. The attraction/repulsion will cause acceleration based on the mass of the nodes.

The nodes will be able to collide with eachother, and will also be enclosed within a cube, so they will bounce of the sides of that too.

The l-system part will be how I control the number of nodes - each type will beget two of two other types. You will be able to increase and decrease the number of nodes as whole layers, in a binary way i.e. 1, +2, +4, +8, etc.

I know I'm meant to be making AI software, but this idea excites me more than thoroughly designing a neural network designer.

The idea struck me after conversing with Baz of Elitebastards fame. He introduced me to an l-system that used repelling to form its shape. It looked pretty cool, so much so I wanted to do something like it.

Friday, September 22, 2006

Starting afresh

Post 1: the least important post.

This is the start of my blog. I'm creating a blog about developing AI software. If you have no interest in AI software, or me, turn back now. This will only make you turn blind.

Since this is the first post, I will tell you what I shall be developing first: An Artificial Neural Network Designer.

I have already taken the first steps, I have decided what it will be able to do:
  • Load a network
  • Save/save as
  • Create a new network
  • Add a neuron
  • Remove a neuron
  • Edit neuron properties
  • Add a neuron group
  • Remove a neuron group
  • Edit group properties
  • Add a neuron to a group
  • Remove a neuron from a group
  • Add connections: N->N, N->G, G->N, G->G
  • Removing connections
  • Create activation functions (AFs)
  • Edit AF properties
  • Remove AFs
  • Set network bias
And since I want to get things right this time, I'm going full force with UML.

Anyway, wish me luck.