Customizing the look and feel of markers

This will be our last recipe for social mapping. In this recipe, we will revisit our marker itself and give it a facelift. As our marker represents Twitter messages in a clicked area, we will update our marker to look like a Twitter bird (hand made). We will not stop there; after updating our graphic, we will add another graphical layer to shadow our Twitter marker. It will be a shadow, and its opacity will range from zero to full, depending on the number of tweets (a maximum of hundred tweets).

The best way to understand our goal is by checking out the following screenshot:

Customizing the look and feel of markers

Note how some tweets have no visible circle outline, while others have a very dark one (that is based on how many tweets are there).

Getting ready

To complete this task you need to first complete all the previous recipes in this chapter.

How to do it...

We will jump right into the JavaScript file and continue from where we left off in the previous recipe.

  1. Update the showTweet function.
    function showTweet(a,latLng){
          if(!a) a = [{text:'No tweet found in this area for this topic'}];
          //console.log(obj);	
    
          var marker = new TwitterMarker({
                map: map,
                position: latLng,
                tweet: a,
                title:a[0].text,
                icon:"img/bird.png"    });			
    
      }
  2. Create an instance of the MarkerCounter object in the TweeterMarker constructor.
    function TwitterMarker(opt){
      this.count = opt.tweet.length;
      this.mc = new MarkerCounter(opt);
      this.crnt = 0;
    ...
  3. Create the MarkerCounter constructor.
    function MarkerCounter(opt) {
        this.radius = 15;
        this.opacity = (opt.tweet.length) /100;
        this.opt = opt;
        this.setMap(opt.map);
      }
  4. Create subclass, MarkerCounter, for the google.maps.OverlayView object.
    MarkerCounter.prototype = new google.maps.OverlayView();
  5. Create an onAdd method. It will be called automatically when an element is added into the map. In this method, we will finish up all the preparatory work for the drawing but won't draw the elements.
    MarkerCounter.prototype.onAdd = function() {
      var div = document.createElement('div'),
        div.style.border = "none";
        div.style.borderWidth = "0px";
        div.style.position = "absolute";
    
      
      this.canvas = document.createElement("CANVAS");
        this.canvas.width = this.radius*2;
      this.canvas.height = this.radius*2;
    
        this.context = this.canvas.getContext("2d");
      div.appendChild(this.canvas);
      this.div_ = div;
    
    
        var panes = this.getPanes();
      	panes.overlayLayer.appendChild(div);
    
     }
  6. Last but not least, it's time to override the draw method and draw into the new canvas element created in the previous step and position the div element.
     MarkerCounter.prototype.draw = function() {
        var radius = this.radius;
        var context = this.context;
      context.clearRect(0,0,radius*2,radius*2);
    
      context.fillStyle = "rgba(73,154,219,"+this.opacity+")";
      context.beginPath();
        context.arc(radius,radius, radius, 0, Math.PI*2, true);
      context.closePath();
      context.fill();
      var projection = this.getProjection();
      var point = projection.fromLatLngToDivPixel(this.opt.position);
        
        this.div_.style.left = (point.x - radius) + 'px';
        this.div_.style.top = (point.y - radius) + 'px';
        
      };

When you run the application, you will find that now our markers look like those of Twitter and the larger the number of tweets that originate from a location, the more opaque the egg under our Twitter bird will be.

How it works...

The first step is to swap the graphic that is the default graphic for the marker. As we are extending the regular marker, we have all of its default features and behaviors. One of these features is the ability to swap the icon. To do that, we pass in one of our object parameters as the icon and its path.

var marker = new TwitterMarker({
            map: map,
            position: latLng,
            tweet: a,
            title:a[0].text,
            icon:"img/bird.png"    });

You might be wondering how this actually works, as we are not actually doing anything to the icon parameter in our code. It's very simple. If you take a deeper look at the TwitterMaker constructor, you will find the following line:

this.setValues(opt);

Passing the setValues method to the opt object is our way of letting the marker continue and rendering our marker with the information we just got into our constructor. All the things that can be done in a regular marker can be done in ours as well.

At this stage we have our Twitter bird as our graphic interface for our marker. Unfortunately, this is as far as we can go with customizing our marker; next, we will need to add another visual layer. As we want to create a visual layer that behaves like a marker just visually (as it will be part of the marker), we will need to create a subclass for the google.maps.OverlayView object.

Similar to the marker logic, when we are ready to render our element, we want to call the method setMap (for the marker it was a different method but the same idea).

function MarkerCounter(opt) {
    this.radius = 15;
    this.opacity = (opt.tweet.length) /100;
    this.opt = opt;
    this.setMap(opt.map);
  }

  MarkerCounter.prototype = new google.maps.OverlayView();

In our constructor, we are only storing very basic global information, such as our target opacity, radius, and the options object. We can store any information we want here. The most important element of information that we will need is the position (latitude and longitude). We will send that information into our marker, and it will be inside our opt object.

The google.maps.OverlayView object has an onAdd method. It's just like a listener, but in addition, we will override this method and add our processing/preparation work when the element is added into the map.

MarkerCounter.prototype.onAdd = function() {
  var div = document.createElement('div'),
    div.style.border = "none";
    div.style.borderWidth = "0px";
    div.style.position = "absolute";


  this.canvas = document.createElement("CANVAS");
    this.canvas.width = this.radius*2;
  this.canvas.height = this.radius*2;

    this.context = this.canvas.getContext("2d");
  div.appendChild(this.canvas);
  this.div_ = div;
    var panes = this.getPanes();
    panes.overlayLayer.appendChild(div);
  }

Most of the logic here should look familiar. We start by creating a new div element. We set its CSS attributes to make absolute the position of the div element so we can move it around easily. We follow this with creating a canvas element and setting its width and height to be two times the radius of our circle. We add the canvas into our div element. Last but not least it's time for us to add our div element into the map. We will do that by accessing the getPanes method. This method will return all the visual layers this element can contain. In our case, we will go right to our overlay layer and add our div element to it. We do this inside the onAdd method rather than doing it earlier because the overlay will not be rendered and we will not have access to the last two lines in the previous code.

Just as we overrode the onAdd method, we do the same for the draw method. This is our last critical step. For the most part, all the work in this method will be very familiar as we have played a lot with canvas in this book. So, let's explore the new steps to find where we want to position our overlay.

var projection = this.getProjection();
	var point = projection.fromLatLngToDivPixel(this.opt.position);
    
    this.div_.style.left = (point.x - radius) + 'px';
    this.div_.style.top = (point.y - radius) + 'px';

In the first line in the preceding code, we get the projection. The projection is the relative point of view of our overlay. Through this projection, we can extract the actual point in pixels. We call the projection.fromLatLngToDivPixel method, send to it a latitude/longitude object, and get back a point (x, y values). All that is left is to update the style of our div element and position it according to this information (not forgetting to subtract our radius size so our element is exactly in the middle of the actual point that was clicked).

Until now, we have treated our TwitterMarker constructor as if there are always tweets somewhere in the world, but the reality is that sometimes there will not be anything, and right now we are creating both a visualization that won't work and a marker that won't visualize it. Let's override our behaviors and put up an alternative marker if there is no result and skip all of our customizations.

How it works...

Let's sort it out. We start by removing our original error logic from the showTweet method. Instead, we will just update the text attribute but will not create a new array.

function showTweet(a,latLng){
      var marker = new TwitterMarker({
            map: map,
            position: latLng,
            tweet: a,
            title:a.length? a[0].text : 'No tweet found in this area for this topic' ,
            icon:"img/bird.png"    });			

  }

In case you are not familiar with the ternary operator, it's a very condensed way of creating an if...else statement within code. The core logic of it is as follows:

condition?true outcome:false outcome;

The outcome is then sent back, and we can capture it right into our variable as we are doing in this case.

The next area we want to change is the TwitterMarker constructor.

function TwitterMarker(opt){
  if(!opt.tweet || !opt.tweet.length){
    opt.icon = "img/x.png";	
  }else{

    this.count = opt.tweet.length;
    this.mc = new MarkerCounter(opt);
    this.crnt = 0;
    this.id = TwitterMarker.aMarkers.push(this);
    this.aTweets = opt.tweet;
    var strTweet = this.buildTwitterHTML(opt.tweet[0])
    this.infoWindow = new google.maps.InfoWindow({
        maxWidth:300,
        content:strTweet
    });

    this.infoWindow.open(this.map,this);
    google.maps.event.addListener(this, 'click', this.onMarkerClick);

  }
  this.setValues(opt);
}

The main changes here are that we start our application by first checking if there are any tweets. If no tweets are around, we update the icon graphic to a new X icon. If we do have a result, all remains the same. We extracted the setValues method to be called out of the if...else conditions as we need to call it in any case.

There you go! We've completed our social map. There is much more you can do with this project. A couple of examples could be making it easier to change the search term, and comparing between two search results (that could be very interesting and easy). I would be interested to see around the world the number of times Flash versus HTML5 are mentioned, so if you get to it send me an e-mail.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.143.31