Subtitel

A blog by Juri Urbainczyk | Juri on Google+ | Juri on Twitter | Juri on Xing

Showing posts with label GUI design. Show all posts
Showing posts with label GUI design. Show all posts

Tuesday, June 25, 2013

An HTML5 canvas component for ExtJS 4

As I described in another post, ExtJS 4 features components for graphics ("Ext.draw.Component") which are based on the standard HTML SVG tag. But what if we liked to use a <canvas> tag? ExtJS does not bring one of those - thus we have to create one.

We also need a nice little showcase. For that, I implemented a small application, which simulates a solar system, with a configurable number of planets, moons and stars. The application calculates the gravity which affects the objects and then determines their new positions. This is done repeatedly, leading to a simulated continuous motion. The positions (and trajectories) of all objects are displayed in the canvas element. Picture 1 shows the solar system simulator in action.
Picture 1: Solar system simulator written with the canvas component
I put the canvas component in a separate file (canvasPanelClass.js), thus it should be easily reusable. The component utilizes the powerful object-oriented features of ExtJS. It extends Ext.Panel and creates the canvas element in the constructor method. In the afterrender listener (when the component already is complete) it stores a reference to the canvas in a private variable (this.canvas = this.items.items[0].el.dom;).

After that, the canvas is ready to be used. The component already brings some important methods, e.g. drawCircle( 50, 50, 10, "red" ) which draws a red circle with a radius of 10 pixels around the point 50, 50.

And now, the most interesting part of the source code:

        Ext.define('CanvasPanelClass', {
            extend: 'Ext.Panel',
           
            gridColor: '',
            ctx: null,        // is set when rendered
            canvas: null,    // is set when rendered                       
            bodyStyle: { background: '#000' },
            frame: false,
            margin: '2 2 2 2',
           
            listeners: {
                afterrender: {
                    fn: function(){                       
                        this.canvas = this.items.items[0].el.dom;           
                        this.ctx = this.canvas.getContext("2d");                                       this.clear();                       
                    }
                }       
            },           
           
            constructor: function(config) {
               
                //define this here because we can set width and height
                this.items = {
                    xtype: 'box',
                    autoEl:{
                        tag: 'canvas',
                        height: config.height,
                        width: config.width
                    }
                };
               
                this.tempCanvas = document.createElement("canvas");
               
                CanvasPanelClass.superclass.constructor.call(this, config);
            },
                    
            drawRect: function( x, y, width, height, lineWidth, color ) {
                this.ctx.strokeStyle = color;
                this.ctx.lineWidth = lineWidth;
                this.ctx.strokeRect(x,y,width,height);               
            },
           
            fillRect: function( x, y, width, height, color ) {
                this.ctx.fillStyle = color;
                this.ctx.fillRect(x,y,width,height);               
            },       
           
            drawCircle : function( x, y, radius, color ) {
                this.ctx.beginPath();
                this.ctx.strokeStyle = color;
                this.ctx.fillStyle = color;
                this.ctx.arc( x, y, radius, 0, Math.PI*2, true );
                this.ctx.closePath();
                this.ctx.fill();       
            },           
           
            putPixel: function( x, y, size, color ) {
                this.ctx.fillStyle = color;
                this.ctx.fillRect(x,y,size,size);           
            },

                  
            clear: function() {
           
                // Store the current transformation matrix
                this.ctx.save();

                // Use the identity matrix while clearing the canvas
                this.ctx.setTransform(1, 0, 0, 1, 0, 0);
                this.ctx.clearRect ( 0, 0, this.getWidth(), this.getHeight() );

                // Restore the transform
                this.ctx.restore();                                      
            }


        }); // define CanvasPanelClass

Then, the canvas component can be used like in the following code snippet. (There is an array of canvasPanels because you might want to create more than one canvas.) The mousedown handler shows how an application can react on mouse clicks into the canvas.

      canvasPanel[0] = new CanvasPanelClass({
            height: 500,
            width: 500
      });              

      canvasPanel[0].on({
            mousedown: function(DOMevent) {

                             var canvasPanelX = this.getPosition()[0];
                             var canvasPanelY = this.getPosition()[1]
                             var point = DOMevent.getPoint();

                             var eventX = point.x-canvasPanelX;
                             var eventY = point.y-canvasPanelY;

                              // do something …

                             canvasPanel[0].clear();
                             canvasPanel[0].renderGrid(25*50/ZOOM,GRIDCOLOR);
            },
            element: 'body',
            scope: canvasPanel[i] //Ensure "this" is correct during handler execution
      });

      tablePanel.add(canvasPanel[0]); 
  
      canvasPanel[0].clear();    

      canvasPanel[0].putPixel( 50, 50, 1, "green" );

Saturday, December 22, 2012

ExtJS Game of life – testing the Ext.draw library


I always wanted to check out the ExtJS graphics library (Ext.draw) and thus I decided to implement the Game of Life in ExtJS as a kind of prototype. The following screenshot shows the results, a running game of life on a 40x40 grid.

A running 40x40 game of life in ExtJS
 
Details about the Game of life can be found here: http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life

How can it be implemented in ExtJS? Since I wanted to test the ExtJS drawing functions, I chose an Ext.draw.Component as the basis. All grid cells are painted inside this component by creating a “sprite” for each grid. When a grid is alive I simply change the color of the sprite to black, otherwise I change it to white.

Now, that’s how the Ext.draw.Component is initialized. As you can see, it is created with one item (sprite) which just paints the blue border around the whole component.

var drawComponent = Ext.create('Ext.draw.Component', {
     viewBox: false,
     margin: '10 10 10 10',
     height: (DIMSIZE*(SPRITEWIDTH+SPRITEBORDER))+8,     
     width: (DIMSIZE*(SPRITEWIDTH+SPRITEBORDER))+8,
     items: [{
         type: 'rect',
         width: (DIMSIZE*(SPRITEWIDTH+SPRITEBORDER))+4,
         height: (DIMSIZE*(SPRITEWIDTH+SPRITEBORDER))+4,
         x: 0,  y: 0,
         stroke: 'blue',    
         'stroke-width': 2                    
     }]
}); 

In order to add new sprites to the Ext.draw.Component, you can just call the add() method. I noticed, that it was necessary to change one of the sprite’s attributes in order to make it appear on screen, so I just change the sprite type to ‘rect’ (as it was before). Of course, we have to implement a loop in order to create all necessary sprites.

myRect = drawComponent.surface.add({
     type: 'rect',
     width: SPRITEWIDTH,
     height: SPRITEWIDTH,
     fill: Ext.draw.Color.create(0, 0, 0, 1),
     x: 3+(x*(SPRITEWIDTH+SPRITEBORDER)),
     y: 3+(y*(SPRITEWIDTH+SPRITEBORDER)),
     xpos : x,
     ypos : y
});          

myRect.setAttributes({
     type: 'rect'
}, true);

The next picture shows which ExtJS classes are used to build the GUI:

ExtJS components in the game of life GUI

Furthermore, we need a timer, because we have to calculate the next generation and paint it again an again. This is achived with an ‘task’ object as seen in this piec of code:

var task = {
     run: function(){

         newGeneration();
         paintGeneration();              
     },
     interval: 500
}   

As you can see, the task interval (the time between to calls of the loop) is quite big: half a second. As a matter of fact, I learned that the interval could not become any smaller on my machine (and with Firefox or Chrome). Otherwise, the next run of the cycle would be called before the former generation was completed, botching the whole game. With a 40x40 grid, we already need 1600 sprites which seem to quite a lot, since the creation of the sprites takesca. 5 seconds. When I increase the grid size the time needed to instantiate the game grows heavily and the program becomes unusesable. 

As you can see, the ExtJS draw functions are not very fast and not well suited to graphics programming like this (I assume it is rather meant to be used for charting).

Thus, I decided to port the whole program to HTML5 canvas (or rather the grid part), which I will explain in a later blog post, so stay tuned.

Thursday, August 30, 2012

"100 Things Every Designer Needs to Know About People" - a Review

I discovered this very worthwhile book by Susan M. Weinschenk some weeks ago in a tweet about design nd presentation literature.

I simply love this book. It's about the psychology of design and covers many topics (like "How people remember", "How people decide" and "How people focus attention") which are very, very relevant for everybody who only sometimes has to design a user interface, a web site or any other tool which is used by humans.

I found "100 things" to be one of the most interesting and fascinating popular science books I've ever read. That's mainly because of two reasons:
1) There is lots of well founded and well presented information - most of it surprising.
2) It is well structured and thus fun to read.

The book contains 100 insights (like "people remember only four items at once") which are presented on 2 to 3 pages each. Every insight is introduced in well written language and also structured in a very user-friendly way which for the reader is easy to digest. E.g., each contains a "Takeaways" box, which sums up the most important facts and keeps the reader on track. Especially important texts are highlighted in colored boxes, making it easy to find them later on. So, the author seems apply her own findings to the design of her own book and that really shows.

Personally, I am working in the field of software design and development and the contents of this book are very much applicable for me. Many ideas came to my mind how to apply these findings in my own work while I was reading. Therefore, I very much recommend this book. Go buy it, read it and you'll certainly read it (or parts of it) again.

If you're are interested in similar books I can also recommend:
  • Dont' make me think - Steve Krug
  • Made to Stick: Why Some Ideas Survive and Others Die - Chip & Dan Heath
  • Presentation Zen: Simple Ideas on Presentation Design and Delivery - Garr Reynolds