Creating Beautiful Drawings Using Sencha Ext JS – Part 1

Many of you may already be familiar with the Sencha Charting package that comes with Sencha Ext JS. It allows you to quickly create great visualizations such as 3D Column Charts or 3D Pie Charts. Often, charts are not enough for your web applications, and you may need to create a flowchart, seat map, a schematic, or maybe an interactive animation in your app.

Of course, you can use the HTML5 Canvas or SVG directly, but those often lead to issues on platforms that are not supported. Dealing with cross-browser issues, differences across regular and retina displays, animations, etc. is not an easy task. You may be able to use third party libraries that support multiple renderers and provide some useful abstractions, but quickly you’ll find yourself spending time dealing with integrations across those libraries.

Ext JS Charts comes with a drawing package that allows you to create arbitrary graphics and animations without worrying about which technology a particular browser uses to render your drawings. It automatically selects the most appropriate renderer (Canvas, SVG or VML) depending on your browser. Under the hood, the draw package follows HTML5 Canvas as the underlying API model. Canvas API calls are automatically translated to SVG or VML, if those engines are required.

In this series of articles, we will go over several features of the draw package that comes with Sencha Charts, and how they have been implemented, so you don’t have to deal with cross-browser compatibility issues.

A Simple Sprite

A sprite is a basic primitive which represents a graphical object that can be drawn. You can create a desired image by combining multiple sprites. There are many different kinds of sprites available in the Draw package. Each sprite type has various attributes that define how a sprite should look. For example, this is a rect sprite:

{
    xtype: 'draw',
    width: 250,
    height: 250,
    sprites: [{
        type: 'rect',
        x: 50,
        y: 50,
        width: 100,
        height: 100,
        lineWidth: 4,
        strokeStyle: 'green',
        fillStyle: 'yellow'
    }]
}

Open in a Fiddle

Here the type: 'rect' corresponds to the sprite’s alias, and the rest of the config properties are sprite attributes. It’s important to note that sprite attributes are not configs. You’ll find out more about the differences between attributes and configs in subsequent parts of this series. For now, let’s just say they are processed and used differently.

The Draw Container

The draw xtype in our previous example corresponds to the Ext.draw.Container class. This is the container of the draw surfaces (instances of the Ext.draw.Surface) in which sprites are rendered.

Notice how we used the sprites config and not the items config of the draw container to add a rect sprite to it. This is because items of the draw container are its surfaces. And the sprites defined in the sprites config go into the default main surface. You can make a sprite go to a surface other than default, if you use sprite’s surface config (yes, this is not an attribute). For example:

{
    type: 'rect',
    surface: 'privateSurface',
    x: 50,
    y: 50,
    width: 100,
    height: 100,
    ...
}

The above will create a surface with ID privateSurface and the rect sprite will go into it, instead of the default ‘main’ surface. The surface config may also be an actual surface instance, which means you would add sprites via setSprites method after the draw container has been instantiated.

Please note that setSprites won’t remove sprites that were already added by the initial sprites config or by previous calls to setSprites. It will only add new sprites. This is because the sprites config is meant to be used declaratively. If you need to manipulate sprites, you can do this using surface methods.

Using Multiple Surfaces

The ability to have multiple surfaces is useful for performance (and battery life) reasons. Because changes to sprite attributes cause the whole surface (and all sprites in it) to re-render, it makes sense to group sprites by surface, so changes to one group of sprites will only trigger the surface they are in to re-render. The Sencha Chart package, which is built on top of the Draw package, heavily relies on this feature. If you have something like a cross zoom interaction in your chart, only the surface used to render the zoom rectangle repaints as you make a selection by dragging over the chart, while the series and axes surfaces are not repainted.

We can also rewrite the example above in an imperative way to better understand what’s going on:

var drawContainer = new Ext.draw.Container({
    renderTo: document.body,
    width: 250,
    height: 250
});
 
var mainSurface = drawContainer.getSurface(); // --- getSurface('main')
 
mainSurface.add({ // add sprite to the surface
    type: 'rect',
    x: 50,
    y: 50,
    width: 100,
    height: 100,
    lineWidth: 4,
    strokeStyle: 'green',
    fillStyle: 'yellow'
});
 
mainSurface.renderFrame(); // --- renders all the sprites in the surface

Open in a Fiddle

Modifying Sprite Attributes

Now, let’s have a look at how we can modify a sprite’s attributes. For example, we can make our rect sprite more rectangular than it is now by making it wider.

First, we need to get a reference to our sprite, and one way to do this would be to get the items config of the surface, which is an array of all the sprites that belong to the surface:

var items = mainSurface.getItems(),
    rectSprite = items[0];

Alternatively, we can use the surface’s get method:

var rectSprite = mainSurface.get(0);

Or, better yet, we can assign an ID to our sprite and use that to fetch it:

mainSurface.add({
    type: 'rect',
    id: 'myRect',
    ...
});
 
var rectSprite = mainSurface.get('myRect');

Now we can change the sprite’s width. This is how we do it:

rectSprite.setAttributes({
    width: 150
});
// --- Don't forget to repaint the surface after changing sprite's attributes
mainSurface.renderFrame();

You’ll notice that we can’t use rectSprite.setWidth(150); because width is not a config.

Open in a Fiddle

We can also set more than one attribute at once, which is the recommended and more efficient way to do it. Let’s now change the colors of both fill and the stroke:

rectSprite.setAttributes({
    fillStyle: 'rgba(255, 0, 0, .5)',
    strokeStyle: 'rgb(0, 0, 0)'
});

Here, instead of using named colors, we use CSS compliant rgb functions to specify our color values.

Open in a Fiddle

Try changing other attributes too and see how it affects the sprite. You can refer to the Ext JS docs to see which attributes are supported.

Conclusion

As you can see, using sprites is not much different than using components. The same principles of components are used with sprites. Instead of dealing with HTML directly, the component-like approach saves you time by not having to directly deal with SVG elements and Canvas API calls. You simply create sprites and configure the attributes, and the Draw package takes care of the rest.

In the next part of this series, you’ll learn how to animate, transform and interact with sprites, including the methods and approaches to create your own sprites. We will also cover special sprites features such as instancing and composites, which will help you to improve performance and reduce the complexity of your code.

In the meantime, we hope you have fun trying out different types of sprites such as circle, line, or text which are available in the drawing package.

Written by

Vitaly is a Graphics Engineer at Sencha. Over the past two years, he has transitioned Touch Charts to a single cross-toolkit package, adding numerous features to the API, and improving interactivity and 3D charts support. For 10 years before joining Sencha, Vitaly was an independent developer. He built desktop audio software called Spider Player with a user base of 2 million people, as well as apps for BlackBerry, WebOS, and Android.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *