Version 11, changed by owen 12/09/2006. Show version history
This is the major rewrite of the original proposal based mostly on feedback from Gavin Doughtie and Tom Trenka.
I skip Preamble. You can look it up in the original proposal.
After extensive consultations we strive to support two scenarios:
Our main target is SVG. VML is supported using a translation layer. Canvas can be targeted later using the proposed API definition.
I tried hard not to over-constrained the implementation --- a lot of things are defined informally or skipped. I expect them to be defined during the implementation process --- I eat my dog food and follow my own advice (see #2, #3, and #4).
I will use dojo.gfx as a package name mainly because it is short. Possibly the API will be split across several packages.
Several objects were identified as renderer-independent. They describe declaratively different aspects of the graphics: Fill, Stroke, Matrix, and Font. We need a Color object with alpha channel (opacity), which is already provided by dojo.graphics.color. We need a Coordinate object, which I propose to represent with a dictionary {x: 1, y: 2}.
All renderer-independent methods define trivial self-inspection methods, which were omitted for brevity.
dojo.math package deals with coordinates (point.js) and matrices (matrix.js). In my opinion it is too generic for our purposes, especially matrix operations. I think that array of arrays is an overkill for 6-value 2D matrix. Personally I like declarativeness of dictionaries and would prefer something like that:
c = {x: 1, y: 2};
m = {xx: 1, yy: 1, dx: -1, dy: 2}; // missing values are assumed to be 0
Fill object describes different fill styles. Two specializations of Fill are proposed at the moment: GradientFill, which implements a linear gradient fill, and PatternFill, which fills with specified image. Radial gradient fill is possible but its implementation in VML is bad and not consistent with SVG. Solid fill is proposed to represent with a Color object for now.
var lg = new dojo.gfx.LinearGradient(c1, c2) // more operations on lg ... rect.setFill(lg);
Returns a GradientFill object from c1 to c2 (coordinates), which can be used as a fill style. Colors are specified with color stops directly on LinearGradient object.
new dojo.gfx.Pattern(url, repetition, x, y, width, height)
new dojo.gfx.Pattern(url, repetition, c, width, height)
Returns a (tiled) image PatternFill object, which can be used as a fill style. Optional repetition argument can be one of "repeat" (default), or "no-repeat". Optional (x, y) pair or c coordinate provides an origin. Optional (width, height) pair specifies the image size in pixels.
addColorStop(offset, color)
Adds a color point with specified offset (0.0-1.0). Returns itself.
No methods. On the SVG side, it is feasible to create the pattern graphics on the fly.
Stroke object describes different stroke styles. One specialization is proposed at he moment: LineStroke, which implements a solid line of specified width with optional caps, and joins. Solid color line with default width can be specified using a Color object.
dojo.gfx.createStroke(color, width, cap, join)
Returns a stroke object. Cap can be one of "butt", "round", or "square". Join can be one of "round", "bevel", or a numeric miter value. Cap and join are optional arguments.
No methods.
This object defines a transformation matrix.
dojo.gfx.translate(dx, dy)
dojo.gfx.translate(c)
Returns a Matrix object, which represents a 2D translation defined either by (dx, dy) pair, or a coordinate c.
dojo.gfx.rotate(angle)
dojo.gfx.rotateg(degree)
Returns a Matrix object, which represents a 2D rotation in radians specified by the angle argument, or in degrees specified by degree the argument. The rotation is CCW (counterclockwise) around {x: 0, y: 0} (top-left corner) point.
dojo.gfx.scale(s)
dojo.gfx.scale(sx, sy)
dojo.gfx.scale(c)
Returns a Matrix object, which represents a 2D scaling specified by the s argument (for isotropic scaling), (sx, sy) pair or the coordinate c (for anisotropic scaling).
dojo.gfx.identity // {xx: 1, yy: 1}
dojo.gfx.flipX // {xx: -1, yy: 1}
dojo.gfx.flipY // {xx: 1, yy: -1}
dojo.gfx.flipXY // {xx: -1, yy: -1}
These are useful constant matrices, which can be used for combining transformations. Please note that flipX flips the X axis, and so on.
dojo.gfx.skewX(angle)
dojo.gfx.skewY(angle)
dojo.gfx.skewXg(degree)
dojo.gfx.skewYg(degree)
Returns a Matrix object, which represents a skew matrix specified in radians or degrees. Please note that skewX affects only X coordinate leaving Y unchanged, and so on.
dojo.gfx.multiply(a, b, . . .)
Returns a Matrix object, which represents a consequtive application of matrices. This method is listed here for logical completeness.
dojo.gfx.multiplyPoint(m, x, y)
dojo.gfx.multiplyPoint(m, c)
Returns a Coordinate object, which represents an application of m matrix to a coordinate. This method is listed here for logical completeness.
dojo.gfx.invert(a)
Returns an inverted matrix. This method is listed here for logical completeness.
Following member objects are available: xx, xy, yx, yy, dx, dy.
These are renderer-dependent objects: Path, Shape, Group, Surface.
This object is used to build a path string procedurally.
dojo.gfx.createPath()
Returns an empty Path object.
path.moveTo(x, y)
path.moveTo(c)
Moves the current point of the path starting a new subpath. Returns tself.
path.lineTo(x, y)
Adds the point to the path connecting the last point of the path with new point using a straight line. Returns itself.
path.hLineTo(x, y)
Adds the point horizentally to the path connecting the last point of the path with new point using a straight line. Returns itself.
path.vLineTo(x, y)
Adds the point vertically to the path connecting the last point of the path with new point using a straight line. Returns itself.
path.arcTo(x1, y1, x2, y2, radius)
Adds two points to the path. (x1, y1) is connected with the last point using a straight line, if they are different. (x2, y2) is connected with (x1, y1) with an arc. Returns itself.
path.curveTo(cp1x, cp1y, cp2x, cp2y, x, y)
Adds (x, y) connecting it to the last point with a cubic bezier curve, control points are (x1,y1), (x2,y2). Returns itself.
path.smoothCurveTo(x2, y2, x, y)
Adds (x, y) connecting it to the last point with a smooth cubic bezier curve, control points are (x2,y2). Returns itself.
path.closePath()
Closes the last subpath with a straight line. Returns itself.
Shape objects are used to represent geometric shapes. In fact they are proxy objects, which use underlying raw nodes.
dojo.gfx.attachShape(node)
Returns a shape object corresponding to specified raw node.
shape.setFill(fill)
Returns itself. This method sets a fill style, which can be either a GradientFill or PatternFill object, or a color, which represent a solid color fill.
shape.setStroke(stroke)
Returns itself. This method sets a stroke style, which can be either a LineStroke object, or a color, which represent a solid color stroke with default width.
shape.setPath(path)
Returns itself. This method sets a path, which can be a Path object or a path string in SVG notation. In order to be translatable to VML, it should use only pre-defined subset of path commands.
shape.setTranform(matrix)
Returns itself. This method sets a transformation matrix for the shape, which can be specified by a Matrix object, 6-item array, or a dictionary (xx, xy, yx, yy, dx, dy). It replaces the current transformation matrix.
shape.applyTransform(matrix)
Returns itself. This method apply a matrix to the shape. Logically it is equivalent to: shape.setTransform(dojo.gfx.multiply(shape.getTransform(), matrix));
shape.applyLeftTransform(matrix)
Returns itself. This method left-apply a matrix to the shape. Logically it is equivalent to: shape.setTransform(dojo.gfx.multiply(matrix, shape.getTransform()));
shape.getNode()
Returns a raw node used for this shape.
It is used to group several objects together. Essentially it is a shape object, wich supports setting a fill style, a stroke style, and a transformation. Additionally it defines one more method:
group.createShape()
Returns an empty shape object.
Surface objects are used to create shapes inside a predefined graphics area.
dojo.gfx.createSurface(parentNode, width, height, [renderer])
It creates a new surface for a picture. It corresponds to <svg> element of SVG, <v:g> element of VML, and <canvas> element of Canvas. Width and height are in pixels. Parent node specifies a parent for newly created surface (there are several ways to create an element, e.g., before or after given node --- we can address them later). Optional renderer is a text string, which identifies a renderer: "svg", "vml", or "canvas". It can be used for future extension. If it is unspecified, the preferred/default renderer is used. createSurface returns a surface object or null, if it cannot create a surface.
dojo.gfx.attachSurface(node)
It works just like createSurface but instead of creating new surface it attaches to existing element.
surface.createShape()
Returns an empty shape object.
surface.createGroup()
Returns an empty group object.
I didn't include SVG-to-VML conversion written in XSLT. I included SVG-path-to-VML-path conversion written in JavaScript (implicitly required by shape.setPath()).
I removed all Canvas-specific things. We can still create a Canvas-based renderer, but it is not going to be a simple mapping. It is not scheduled to be a part of the first release.
I didn't tie in the existing facilities. We can move parts of these API in more appropriate packages.
I didn't include text yet. This is a complex part and I want to spend more time on that. Anyway it can be added independently.
Graphics attributes can be mapped to regular HTML. For example, LineStroke and a color stroke can be used for borders of HTML elements, PatternFill and a color fill can be used for backgrounds of HTML elements. Upcoming Font object can be used for HTML text as well. The inverse is true too. Do we need this kind of generalization?
Matrices and coordinates can be merged with existing Dojo facilities. The reason I kept them different is a convenience. Existing stuff is too generic, and most of it is not going to be used anyway.
It is possible to move Path in renderer-independent objects assuming that it always produce valid SVG subset, which is converted on the fly to VML, if needed. The other possibility is to keep them renderer-dependent, but allow reading a path back as an SVG path. I didn't like the latter idea because it assumes we have SVG-to-VML and VML-to-SVG path converters, which can lead to bigger code footprint.
I didn't include introspection methods to reduce a size of this document. They are trivial for renderer-independent objects. But they can be quite complex for renderer-dependent ones. For example, we need to decide, if we going to support shape.getFill(), shape.getStroke(), shape.getPath(), and so on. We can omit them for now, and deal with it later because I don't think they are essential.
I assume that a group is a shape as well. It doesn't accept setPath() method, but it does accept a fill style, a stroke style, and a transformation.
The API can be used for simple "raw node"-based manipulations, when all you need is to change attributes, and transformations. The rest (surfaces, and shape creation) can be packaged separately to reduce the code base for small cases.
I tried to simplify an object creation: attributes, matrices, and coordinates can be represented by simple JavaScript objects (arrays, dictionaries), so we can reduce the code base even more.
Simple convenience methods will be added as well, e.g. "zoom around a point", and "rect-to-rect mapping".
Many methods return the object itself to facilitate chaining. My thanks go to Tom for pointing this enhancement out.
One possibility is to make graphical attributes (fill/stroke) a renderer-dependent objects. It may be more efficient, and it will simplify get/set methods. On the other hand it will lead to some code duplications, and will make it harder to use dictionary-style objects instead of real objects.
Gavin proposed to have a template shape factory. It allows registering creators for commonly used custom shapes. The same approach can be used for graphical attributes. We can add this facility later.
If somebody wants to change graphical attributes on per-component basis and see incremental changes, it makes sense to provide an API like that: shape.stroke.setWidth(2). It makes the attribute API friendlier and potentially faster, but it has to be implemented for all supported renderers, and it will make attributes renderer-dependent. We have to see, if we will have any performance problems first.
We may need a provision to go from a node to its associated object, if there is one. This problem is similar to widget-node and node-widget mapping.
For some applications we may need shape.containsPoint(c) method. I tried to avoid it because it is difficult to implement and the code size is relatively big. I suggest structuring an application, which needs this functionality, to use mouse events instead. In any case we can add it later.
Gavin proposed to implement
the Matrix object according to
SVG JavaScript mapping. I have 2 small reservations: it is quite big,
and not a
lot of people are familiar with it, so we cannot use it to ride a
familiarity
wave. I didn't put any methods in the Matrix object because I want to
preserve
the ability to specify matrices literally like that: {xx: 1, yy: 1}. We
can add
it later, if it is needed.
VML Notes are located on separate page.