Create Cross Browser Vector Graphics

For years, web developers have asked the question, “How can I draw in the browser?”. This simple question has anything but a straightforward answer. While the image tag is great, CSS is amazing, and Flash gets the job done, it still is not enough. What I’ve wanted for almost a decade is the ability to draw and modify non-rectangular, browser-native shapes in my web sites. And I want to do this without having to use a graphics editing tool and reloading images from the server. I simply want the ability to manipulate, draw, and connect events to arbitrary shapes and styles, to create rich, sophisticated web user interfaces.

Landscape changes to web applications have been drastic with the widespread adoption of the XMLHttpRequest and other AJAX and Comet techniques. In my opinion, asynchronous, low-latency data transit (AJAX and Comet) paired with vector graphics opens up a world of new opportunities for web application development. I’m obviously not alone in this desire for browser-based drawing solutions, given the wide variety of image techniques and CSS hacks that have evolved over time.

Despite a history of W3C effort towards the SVG specification, there was little support among browser vendors until now. Apple, the WhatWG, Mozilla, and Opera have been working in parallel on Canvas, a simpler but less feature-rich procedural vector drawing API.

After years of frustration, we finally have enough support to create a simple, unified, cross-browser drawing API: dojo.gfx is the newest piece of the Dojo Toolkit and is an amazingly powerful yet simple way to accomplish these goals. Browser support currently includes Firefox 1.5 , Internet Explorer 6 , and Opera 9. Safari fans fear not – support is underway with the latest WebKit nightlies, and is expected to be functional with the next major release of Safari in 2007.

If you want to skip straight to the examples and source code, there is a collection of resources at the end of this article. Otherwise, sit back and enjoy the code!

What Does dojo.gfx Do?

In a nutshell, dojo.gfx lets you draw natively in the browser with an easy to follow JavaScript API. Let’s start by simply drawing a blue rectangle and a green circle:

Drawing of simple blue rectangle and green circle


var node = document.createElement("div");
document.body.appendChild(node);
var surfaceWidth = 120;
var surfaceHeight = 220;
var surface = dojo.gfx.createSurface(node,surfaceWidth, surfaceHeight);
var rect = { x: 100, y: 0, width: 100, height: 100 };
var circle = { cx: 150, cy: 160, r: 50 };
var group = surface.createGroup();
var blueRect = group.createRect(rect)
	.setFill([0, 0, 255, 0.5])
	.applyTransform(dojo.gfx.matrix.identity);
var greenCircle = group.createCircle(circle)
	.setFill([0, 255, 0, 1.0])
	.setStroke({color: "black", width: 4, cap: "butt", join: 4})
	.applyTransform(dojo.gfx.matrix.identity);

As you can see from this basic example, the first step is to create a gfx surface on which to add nodes that you will be drawing. It’s then possible to add shapes to the surface, and apply styles and transformations in a chainable manner. They are applied in the order defined and provide a concise yet flexible opportunity to style, fill, and transform your nodes. People familiar with vector graphics will immediately notice that we are using the same general concepts found in SVG. You will also notice that we have gone to great lengths to keep our syntax concise, using JavaScript literals for our property definitions.

What Time Is It?

A more complex and engaging example is the Dojo clock widget, developed by Dojo contributors Eugene Lazutkin and Tom Trenka:

Example of Dojo clock widget

The clock widget shows the flexibility of the Dojo widget system in non-HTML namespaces. The following code fragment demonstrates the straightforward method by which hour and minute hands are placed, including realistic shadows:


this.shadows.hour.shadow = this._initPoly(this.surface, hP)
	.setFill([0, 0, 0, 0.1]);
this.hands.hour = this._initPoly(this.surface, hP)
	.setStroke({color: this.handStroke, width:1 })
	.setFill({
		type:"linear",
		x1:0, y1:0, x2:0, y2:-27,
		colors:[{offset:0, color:"#fff"}, {offset:0.33, color:this.handColor}]
	});
this.shadows.minute.shadow = this._initPoly(this.surface, mP)
	.setFill([0, 0, 0, 0.1]);
this.hands.minute = this._initPoly(this.surface, mP)
	.setStroke({color: this.handStroke, width:1 })
	.setFill({
		type:"linear",
		x1:0, y1:0, x2:0, y2:-38,
		colors:[{offset:0, color:"#fff"}, {offset:0.33, color:this.handColor}]
	});
		

Circle Frenzy

An example showing the powerful use of the Dojo event model with a complex dojo.gfx layout is the draggable circles demo developed by Dojo contributor Gavin Doughtie:

Example of draggable circles demo

Two dojo.event.connect calls are used to handle mouse events, which then call some simple functions to detect which shape was selected, handle mousemove events for dragging a circle, and then canceling a mousemove event when the mouse button is released. This is in fact the single biggest advantage of dojo.gfx over Canvas… items drawn with SVG/VML are normal DOM nodes in your document, to which you can easily connect event handlers.


dojo.event.connect(this.domNode, 'onmousedown', this, "handleMouseDown");
dojo.event.connect(this.domNode, 'onmouseup', this, "handleMouseUp");
getShape: function(evt){
	var id = evt.target.getAttribute('shapeid');
	var s = null;
	if (id) {
		s = this.gShapes[id];
	}
	// dojo.debug('target: '   evt.target   ' id: '   id   ' shape: '   s);
	return s;
},
handleMouseDown: function(evt){
	dojo.debug("got an event");
	var shape = this.getShape(evt);
	dojo.debug(shape)
	if (shape) {
		this.gCurrentShape = shape;
		dojo.event.connect(this.domNode, 'onmousemove', this, "handleMouseMove");
		dojo.event.browser.stopEvent(evt);
	}
},
handleMouseMove: function(evt){
	dojo.debug("mouse move");
	dojo.debug(this.gCurrentShape);
	if (this.gCurrentShape) {
		var pos = dojo.html.getAbsolutePosition(this.domNode);
		var x = evt.pageX - pos.x;
		var y = evt.pageY - pos.y;
		dojo.debug(x   "/"   y);
		this.gCurrentShape.setShape({cx: x, cy: y});
		dojo.event.browser.stopEvent(evt);
	}
},
handleMouseUp: function(evt){
	this.gCurrentShape = null;
	dojo.event.disconnect(this.domNode, 'onmousemove', this, "handleMouseMove");
}
	

Which Features Are Supported?

Strokes

In HTML, we generally call this a border, but in vector graphics, it’s a stroke, because it can be a border of an element, or also the edge of anything, including a custom font. Strokes can be hidden, or set to various colors and widths. We also provide the ability to set join parameters, which define how lines come together at edges. You’ve probably noticed the latter, but had little control over it when using thick borders in HTML elements.

Fills

A fill is both background and foreground rolled into one. Transparent fills are supported though not particularly interesting in this discussion. Solid colors are nice and even more interesting are linear and radial gradients with various starting and stopping points. While you cannot create every imaginable gradient today (because SVG and VML do not yet support gradient fills beyond linear and radial), there are a number of clever techniques, using layers and opacity, to achieve most 2-dimensional gradient effects.

Shapes and Paths

dojo.gfx provides a number of commonly used shapes including rectangle, ellipse, line, and circle, as well as the ability to draw along any path. Shapes are simply a special case of a path, which supports simple point to point options, as well as complex cubic and bezier curves.

Opacity

Most browser implementations of this feature in HTML are limited to an opacity that is the same for background and foreground. With dojo.gfx, we can define full alpha opacity on the stroke and the fill, together or independently, providing much greater design flexibility.

Linear Transformations

Rotation, skew, move, resize, and more, are all possible with linear transformations and are based on standard matrix algebra. dojo.gfx exposes this API to make just about any transformation possible. And while dojo.gfx is a two-dimensional API, I have no doubt that people will become inspired to develop three-dimensional transformations using the two-dimensional transformation API.

Rounded Corners

What drawing API would be complete without the ability to draw rounded corners? dojo.gfx provides a simple API for doing just that. Say goodbye to background image hacks!

Charts

We recently announced that Dojo has APIs for live charting. These APIs are highly specialized and are not currently based on dojo.gfx. Charts were developed before dojo.gfx was ready for production. Nevertheless, they show how much is possible in today’s browsers with vector graphics. An example:

Example of Dojo chart API

You may have noticed that I mentioned live charts. We will very soon release examples that show animated, dynamic, real-time charting. Greenplum will soon be releasing the Greenplum Monitor, a database monitoring application built on top of this code that provides browser-based server performance information, much like that which you would find in the Apple Activity Monitor or Windows Task Manager.

So, Why Not Canvas?

The number one reason, in my perspective, is that because Canvas does not have a true DOM, you cannot easily attach DOM events to a specific node or layer in a drawing. There are other minor reasons, but this reason alone makes it much easier for JavaScript developers to work with HTML and vector graphical elements seamlessly and in the same web application interface.

Are We Reinventing The Wheel?

I hope not! dojo.gfx is not yet another vector markup language, but instead it is a solid subset of SVG features and nomenclature. Like Canvas, it is a procedural drawing API. dojo.gfx is not intended to solve every drawing need ever conceived – rather, it covers the baseline technology that is widely available across today’s browsers using SVG or VML within Internet Explorer. A major benefit of dojo.gfx is that it abstracts away the numerous inconsistencies across the browser vector graphics implementations. We all know that even the best, most standards-compliant implementations of HTML, CSS, and JavaScript are inconsistent. Vector graphics suffer in a similar manner.

One of the very first design decisions of which I was a proponent was that the Dojo Toolkit not be HTML-centric. After working on several SVG-based intranet applications, I was highly motivated to see Dojo work well with SVG and other namespaces that could become relevant in the future. This decision has helped significantly with the implementation of dojo.gfx, as very little of the Dojo codebase assumes an HTML DOM.

The Future

As you can see from the demos, we are just scratching the surface of what is possible with dojo.gfx. With this simple yet powerful set of options, we have the ability to create sophisticated native user interfaces in the browser. Based on our recent work at SitePen and the expressed interest of our clients, we’re well underway to building a new field of collaborative visualization web applications. I’m really excited about the possible application interfaces that we can build with native vector graphics support in today’s browsers.

Credits

Special thanks to Eugene Lazutkin, Gavin Doughtie, Kun Xi, Tom Trenka, Mozilla Foundation, Greenplum, SitePen, and the entire Dojo developer community for making this advancement possible. Without the strong community behind Dojo, ambitious projects like this would not be ready today.

Resources

Comments

Comments are closed.