Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Monday, August 26, 2013

Web-based Javascript Interactive map makes easy with SVG

Recently I was thinking what else can i come up with, after seeing the successful story of the Waze, by using the world map. With the advancement of the html5, it can be done easily with canvas and SVG.

Of course, there's a lot of ready-made powerful tile-based detailed map API, eg.
1) Google Maps JavaScript API v3.
2) jquerygeo
3) leafletjs

However, the subtleness of them is beyond comparable with svg path-based map, with the aid of GPS and millions of dollars pumps by them.

Few of the prominent sites in Javascript svg path-based interactive map are:
1) jvectormap
2) amMap

So sad that my country of origin, Malaysia, is too small to be included by them. "Hey, why don't I create my own Interactive map", struck my mind.

Let's get started.
1) I need a map of my country. With the help of Google Image Search, there's plenty of them. Any image format will do, the important thing is the plain background. Beware of the infringement of the propriety though.

2) Once you find the proper image, you can using all sorts of image converting software to turn it into a .svg file. One option is Adobe Illustrator, if you are a designer. If you are just some independent programmer, online tool like Vector Magic, might be a good friend of you. Choose high-resolution when you do conversion.



3) Verify the newly generated .svg file if all the details are there. If not, you can use SVG-edit to add/remove the detail accordingly. Click on the particular svg-path and give a meaningful id to it. Click  twice will allow you to twist the svg-path, that something similar to spline.


4) Embed the svg element in the html page. This can be done easily.
*You might need to set/clip the width and height on the svg element, due to browser compatibility. See more here.

5) However, there's no element of interactive there. So we're gonna add it now. What we need are:

  • display the name of the region when mouse hovers
  • able to zoom
  • able to pan

6) To display the name of the region when mouse hovers, we use jquery and can be achieved easily through following code:

$(function() {
  $(".icon svg path").bind("mouseover", function( evt ) {
    var currentMap = evt.target.getAttributeNS(null,'id');
    if(currentMap != "background" 
        && currentMap != "KualaLumpur_border" 
        && currentMap != "Putrajaya_border"){
   //$(this).css('fill','blue');                            
   //$("#" + currentMap).removeAttr("fill");
   $("#" + currentMap).css('fill','blue');
   //evt.target from viewbox; text.text1 from diff cooor; need to offset
   // more complicated at: http://msdn.microsoft.com/en-us/library/ie/hh535760(v=vs.85).aspx
   var targetedPath = evt.target.getBBox();
   $(".icon svg text.text1").css('display','block')
                            .text(currentMap)
                            .attr( "x", targetedPath.x + targetedPath.width / 2)
                            .attr( "y", targetedPath.y + targetedPath.height / 2);
    }
  })
  .bind("mouseleave", function( evt ) {
    //IE9 & FF not supported
    //var currentMap = evt.target.getAttributeNS(null,'id');
    //console.log("mouseleave:" + currentMap);
  })
  .bind("mouseout", function( evt ) {
    var currentMap = evt.target.getAttributeNS(null,'id');
    if(currentMap != "background" &&
        currentMap != "KualaLumpur_border" &&
        currentMap != "Putrajaya_border"){
      //$(this).css('fill','c9c9c9');//IE9 & FF not supported
      //$("#" + currentMap).css('fill','c9c9c9');//IE9 & FF not supported
      var theCurrentMap = document.getElementById(currentMap);
      theCurrentMap.setAttribute('style', '');
      $(".icon svg text").css('display','none');
    }
  });
 });

*FireFox & IE9 does not fire 'mouseleave' event in the event bubling, so have to bind at 'mouseout' event. See more here.

7) What it does is loop through all the svg-paths, and bind the mouseover & mouseleave events. When mouse hovers, change the current element's css, then display the id of the region using a svg text on cursor's tip, with the help of  the current box selection, getBBox(), . What mouse leaves does is restoring whatever you've done on mouseover event. You might need to append this svg text programmatically though.


8) To enable the zooming capability, we use the following codes when DOM body's loads:
// Must be greater than 1. Increase this value for faster zooming (i.e., less granularity).
var zoomRate         = 1.1; 

function zoom(zoomType)
    {
  var theSvgElement = document.getElementById('svgElement');
  var viewBox = theSvgElement.getAttribute('viewBox'); // Grab the object representing the SVG element's viewBox attribute.
  var viewBoxValues = viewBox.split(' ');    // Create an array and insert each individual view box attribute value (assume they're seperated by a single whitespace character).

  viewBoxValues[2] = parseFloat(viewBoxValues[2]);  // Convert string "numeric" values to actual numeric values.
  viewBoxValues[3] = parseFloat(viewBoxValues[3]);
      
  if (zoomType == 'zoomIn')
  {
   viewBoxValues[2] /= zoomRate; // Decrease the width and height attributes of the viewBox attribute to zoom in.
   viewBoxValues[3] /= zoomRate; 
  }
  else if (zoomType == 'zoomOut')
  {
   viewBoxValues[2] *= zoomRate; // Increase the width and height attributes of the viewBox attribute to zoom out.
   viewBoxValues[3] *= zoomRate; 
  }
  else
   alert("function zoom(zoomType) given invalid zoomType parameter.");
      
  // Convert the viewBoxValues array into a string with a white space character between the given values.
  theSvgElement.setAttribute('viewBox', viewBoxValues.join(' '));
    }
        
    function zoomViaMouseWheel(mouseWheelEvent)
    {      
      //check for detail first so Opera uses that instead of wheelDelta 
      var delta=mouseWheelEvent.detail? mouseWheelEvent.detail*(-120) : mouseWheelEvent.wheelDelta 
      //if (mouseWheelEvent.wheelDelta > 0)
        zoom('zoomIn');
      else
        zoom('zoomOut');
        
      /* When the mouse is over the webpage, don't let the mouse wheel scroll the entire webpage: */
      mouseWheelEvent.cancelBubble = true; 
      return false;       
    }

 function initialize()
    {        
      /* Add event listeners: */
      // Don't let the mousewheel event bubble up to stop native browser window scrolling.
      //window.addEventListener('mousewheel', zoomViaMouseWheel, false);
      
      //FF doesn't recognize mousewheel as of FF3.x
      var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel" 
 
      if (document.attachEvent) //if IE (and Opera depending on user setting)
        document.attachEvent("on"+mousewheelevt, zoomViaMouseWheel)
      else if (document.addEventListener) //WC3 browsers
        document.addEventListener(mousewheelevt, zoomViaMouseWheel, false)
    }

9) What it does is hooking-up the mousewheel event, and change the svg viewbox properties.
*Due to FireFox & Safari handles mousewheel event differently, we have to cater as well. See more here.

10) To enable panning, it needs a little more treatment.

// Number of pixels to pan per key press.  
var panRate          = 10;   

 var isDragging = false;
 var mouseCoords = { x: 0, y: 0 };

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 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 xd = (e.clientX - mouseCoords.x);
           var yd = (e.clientY - mouseCoords.y);
           pan(xd, yd)
        }
            
        mouseCoords = {x: e.clientX, y: e.clientY};
            
        return cancelEvent(e);
   }
  
  function pan(x, y)
  {
   var theSvgElement = document.getElementById('svgElement');
   var viewBox = theSvgElement.getAttribute('viewBox'); // Grab the object representing the SVG element's viewBox attribute.
   var viewBoxValues = viewBox.split(' ');    // Create an array and insert each individual view box attribute value (assume they're seperated by a single whitespace character).

   viewBoxValues[0] = parseFloat(viewBoxValues[0]);  // Convert string "numeric" values to actual numeric values.
   viewBoxValues[1] = parseFloat(viewBoxValues[1]);
    
   viewBoxValues[0] -= x;//panRate; // Increase the x-coordinate value of the viewBox attribute to pan right.
   viewBoxValues[1] -= y;//panRate; // Increase the y-coordinate value of the viewBox attribute to pan down.
    
   theSvgElement.setAttribute('viewBox', viewBoxValues.join(' ')); // Convert the viewBoxValues array into a string with a white space character between the given values.
  }

 
  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 initialize()
    {        
        hookEvent('svgElement', 'mousedown', onMouseDown);
        hookEvent('svgElement', 'mouseup', onMouseUp);
        hookEvent('svgElement', 'mousemove', onMouseMove);
    }

11) What it does is quite similar to mousewheel event. The different is now it uses global variable to check the mouse-down and the distance of mouse move, and eventually apply that to svg viewbox properties.

12) That's it. Now a javascript  interactive map is created. Full codes here.


Thursday, July 11, 2013

Create, Load & Export Module in Node.js

Nowadays, javascript development is getting broader in web development.
Either on front-end (JQueryKnockoutJSTypeScript, etc) or back-end (Node.js, ect), more libraries are created with more compact, more light-weight.

Today, i'll show the basic setup of Node.js.
From wikipedia, Node.js is a server-side software system designed for writing scalable Internet applications, notably web servers. It's akin to IIS in Windows, or Apache Tomcat in world of Java.

1) Download and install from official website.

2) Upon successful of installation, you should able to see several shortcut created in the "Start" menu.


3) Launch the "Node.js" . This is the place where you can issue all the Node.js related commands.

4) To find out the current execution path, we can execute this "Path" command:

process.env.PATH.split(path.delimiter)

5) Two additional environment PATHs are created:


6) For "Loading a File Module", create a "circle.js" (see code below) from official user manual, and place in Node.js environment PATH:

var PI = Math.PI;

exports.area = function (r) {
  return PI * r * r;
};

exports.circumference = function (r) {
  return 2 * PI * r;
};


7) Execute this in "Node.js":

var circle = require('./circle.js');circle.area(4);

8) and you should get your result:


9)  For "Loading a Folder Module" or so called user-defined library, it needs few more steps. Create a folder called "a" in Node.js environment PATH.


10) Create a file called "package.json" (see code below):

{
 "name" : "some-library",
 "main" : "./lib/b.js"
}

11) Create a folder called "lib" in folder "a" and another file called "b.js" (this is the file mentioned above in "package.json"; see code below) in folder "lib":

console.log('module b initializing...');

var PI = Math.PI;

exports.area = function (r) {
  return PI * r * r;
};

exports.circumference = function (r) {
  return 2 * PI * r;
};

console.log('b initialized.');

12) Execute the following command:

var circle = require('./a');circle.area(4);


13) That's it!

Thursday, April 11, 2013

JavaScript search() != indexOf() Method

Quite frequently we need to find a specific word from a large chunk of string. With more focus on web development and the advancement of client-side scripting capability, more efforts have to focus on javascript nowadays.

I came across with this and bump into these 2 methods, both methods look identical (almost) to me to achieve what I was looking for.
  • search()
  • indexOf()
I thought they can achieve the same result, but eventually they aren't.

First, let's use the search() to perform some testing.
Test 1: Using the w3schools's Tryit Editor with default function to search "W3Schools":

function myFunction()
{
var str="Visit W3Schools!"; 
var n=str.search("W3Schools");
document.getElementById("demo").innerHTML=n;
}

You'll get result:
6 (which is correct)

Test 2: then use another method indexOf() on the same function to search same string [MUST click "Submit Code" after change in "Tryit Editor"]:

function myFunction()
{
var str="Visit W3Schools!"; 
var n=str.indexOf("W3Schools");
document.getElementById("demo").innerHTML=n;
}

You'll get result:
6 (which is correct too)

Nothing fancy, nothing wrong.
OK, what about change the string to search from "W3Schools" to "W3|Schools" (adding a "|" character, since it's frequently used as a delimiter)

Test 3: if using search() method [MUST click "Submit Code" after change in "Tryit Editor"]:

function myFunction()
{
var str="Visit W3Schools!"; 
var n=str.search("W3|Schools");
document.getElementById("demo").innerHTML=n;
}

You'll get result:
6 (which is wrong)

Test 4: if using indexOf() method [MUST click "Submit Code" after change in "Tryit Editor"]:

function myFunction()
{
var str="Visit W3Schools!"; 
var n=str.indexOf("W3|Schools");
document.getElementById("demo").innerHTML=n;
}

You'll get result:
-1 (which is still correct)

From Test 3 & Test 4, you get different result if the string your search contains special character "|". Looking back, I was overlooked by its definition (see fonts in read).

search() Method:
The search() method searches a string for a specified value, or regular expression, and returns the position of the match.
This method returns -1 if no match is found.

indexOf() Method:
The indexOf() method returns the position of the first occurrence of a specified value in a string.
This method returns -1 if the value to search for never occurs.

Looks like special character "|" has caused some effect on the regular expression and need special treatment on it. Lesson learnt.