Building a Seven Segment Display Widget with YUI 3 and Raphaël

Electronics used to be my hobby while I was growing up, and one of the components that always fascinated me was the seven segment display. So when I wanted to get my feet wet on YUI 3 couple of weeks ago, I thought how cool would it be to develop a widget that renders a seven segment display. To make things interesting, I decided to bring Raphaël in the mix, and thus was born the YUI 3 seven segment display widget! In this article, I’ll explain how I went about developing this widget.

See the widget in action
(Note: Works on FF3 and IE8. Does not work on IE7)

Download source files


One of the nice things that YUI gives us is a rich Object Oriented Programming (OOP) framework. When we model an entity as an object, we essentially group together properties and methods that pertains to the object. This abstraction makes building large software systems easier. Nowadays, more and more applications are being built to run on the browser and the richness of these keep increasing. Hence, an OOP approach is required to manage the complexity. YUI 3 provides the OOP capability via the extend function.

For building the seven segment widget, we will also use the widget framework provided by YUI 3. For drawing the segments, we will use a fantastic JavaScript library called Raphaël. It allows us to draw vector graphics on the browser using SVG and VML, and is cross browser compatible.

To understand more on the YUI widget framework, view the excellent presentation by Satyen Desai.

Attributes

The logical model for a 7 segment display is rather simple. We will have three attributes “value” , “scale” and “colors”. The “value” attribute determines what digit is displayed. “scale” will determine the dimensions of the display. “colors” will determine the colors of the lighted up and inactive segments.


 function SevenSeg(config) {
     SevenSeg.superclass.constructor.apply(this, arguments);
 }
     
 SevenSeg.NAME = "sevenseg";
     
 SevenSeg.ATTRS = {
     /**
      * Value of the seven segment display. This can be set to change
      * the displayed digit. Default value is 8 (all segments light up)
      */
     value : {
         value: 8   
     },
         
     scale: {
         value: 50  // default scale is 50x.
     },

     colors: {
           value: { 
                 active: {
                    fill: "#000",
                    outline: "#eee"
                 },
                 inactive: {
                     fill: "#fff",
                     outline: "#eee"
                 }                 
           }
     }
 };

Segment Mapping

We have two static variables, SEGIDX, which is an array of segment indices, and DIGITMAP, which maps the digit to the segments which need to be lit. For instance, to display the digit 1, segments “b” & “c” need to be lit up.

  SevenSeg.SEGIDX = ['a','b','c','d','e','f','g'];
     
  // Map of digits to segments that are lit. 
  SevenSeg.DIGITMAP = {
      "0": "abcdef", "1": "bc", "2": "abged", "3": "abcdg",
      "4": "bcfg", "5": "afgcd", "6": "afedcg", "7": "abc",       
      "8": "abcdefg", "9": "abcdfg", "":""
  };

Drawing the Seven Segment Display

In order to draw the seven segment display, we will use the magic of Raphaël. First a Raphaël canvas is created. On that canvas, a unit length horizontal segment is drawn a prototype, and all other segments are created by scaling, rotating and translating the prototype segment. The prototype segment is drawn like below:


var seg = paper.path("M 0 0 L 0.15 0.1 L 0.85 0.1 L 1.0 0 L 0.85 -0.1 L 0.15 -0.1 Z");

In case if you are wondering what the “M 0 0 L 0.15″ stuff means, it is the path specification for the horizontal prototype segment. For more details, read the SVG specification here.

The above segment is cloned seven times, scaled and then the individual display segments are stored in an array. Once these display segments are constructed, the prototype segment is removed. After that each display segment is rotated and translated according to its position on the seven segment display.

         /**
          * Draw a 7 segment display using Raphael canvas.
          */
         _draw7seg: function() {
             var contentBox = this.get("contentBox");
             var scale = this.get("scale");
             var paper = Raphael(contentBox._node, scale*1.3, scale*2.3);
             var seg = paper.path("M 0 0 L 0.15 0.1 L 0.85 0.1 L 1.0 0 L 0.85 -0.1 L 0.15 -0.1 Z");
             
             var tx = 0.15*scale;
             var ty = 0.15*scale;

             this.segments = new Array();
             for (var i=0; i<7; i++) {
                var thisseg = this.segments[SevenSeg.SEGIDX[i]] = seg.clone();
                thisseg.scale(scale, scale);
             }

             seg.remove();             
             
             var segs = this.segments;
             segs['a'].translate(tx + scale/2, ty);

             segs['b'].rotate(90);
             segs['b'].translate(tx + scale, scale/2 + ty);

             segs['c'].rotate(90);
             segs['c'].translate(tx + scale, scale/2 + ty + scale);

             segs['d'].translate(tx + scale/2, 2*scale + ty);

             segs['e'].rotate(90);
             segs['e'].translate(tx + 0, scale/2 + ty + scale);

             segs['f'].rotate(90);
             segs['f'].translate(tx, scale/2 + ty);

             segs['g'].translate(tx + scale/2, ty + scale);             
         }

Methods

The methods to implement the widget are fairly straightforward. The _setDigit() method does the actual job of lighting up the appropriate segments for the given digit. The _clear() method clears all segments (filled with inactive color).

The bindUI() method hooks the “valueChange” event to _afterValueChange() handler, that takes the current value and sets the appropriate digit. Note that the clients should not call _setDigit() method to set the value. Instead, it would be done by just setting the “value” attribute and then the Attribute infrastructure takes care of the rest by firing the “valueChange” event!

         renderUI : function() {
             this._draw7seg();
         },
         
         bindUI : function() {
             this.after("valueChange", this._afterValueChange);
         },
         
         syncUI : function() {
             this._setDigit(this.get("value"));             
         },
         
         _afterValueChange : function(e) {
             this._setDigit(e.newVal);
         },
         
         _clear: function() {
             for (var i=0; i<7; i++) {
                 var thisseg = this.segments[SevenSeg.SEGIDX[i]];                 
                 thisseg.attr("fill", this.get("colors.inactive.fill"));
                 thisseg.attr("stroke", this.get("colors.inactive.outline"));
              }               
         },
         
         _setDigit: function(n) {
             this._clear();
             var chosenSegs = SevenSeg.DIGITMAP[n];
             for (var i=0; i<chosenSegs.length; i++) {
                var x = chosenSegs.charAt(i);
                var thisseg = this.segments[x];                                 
                thisseg.attr("fill", this.get("colors.active.fill"));
                thisseg.attr("stroke", this.get("colors.active.outline"));                
             }
          }

Demos

Click here to see the widget in action.

1 Comment Posted in Uncategorized

One Comment

  1. Awesome man. Thanks for sharing this.

Leave a Reply

Using Gravatars in the comments - get your own and be recognized!

XHTML: These are some of the tags you can use: <a href=""> <b> <blockquote> <code> <em> <i> <strike> <strong>