diff --git a/README.md b/README.md index 8c426d6..5d4c46d 100644 --- a/README.md +++ b/README.md @@ -1,157 +1,43 @@ -# g2 # - -## A 2D graphics command queue ## - - -### Why ### - -Who might need *yet another 2D graphics library* ? Well, *(non-software)* engineers and scientists mostly need an easy way to create some prototypal - static or interactive - web based graphics. -They want a small and high performant library with a simple and intuitive API fully documented on a single page cheat sheet. - - -### Introduction ### - -**g2** is a tiny 2D graphics library based on the [command pattern](http://addyosmani.com/resources/essentialjsdesignpatterns/book/#commandpatternjavascript) principle. It helps to easily build a [command queue](http://en.wikipedia.org/wiki/Command_queue) of graphics commands for later addressing the concrete rendering context and executing the commands in a compact time frame. - -Here is an example for getting a better understanding of **g2**'s basic concepts. - -```javascript - +# g2 + +_g2_ is a 2D graphics javascript library based on the [command pattern](http://addyosmani.com/resources/essentialjsdesignpatterns/book/#commandpatternjavascript) +principle. Its main goal is to provide a simple API for users who want to generate 2D web graphics occasionally. +So the API is minimal and easy to understand. The library is tiny, fast and renderer agnostic. + +## Main features + +* Fast and lightweight graphics command queue builder. +* Adressing HTML canvas 2D context as the default renderer. +* Generating SVG output using an addon library. +* Method chaining. +* Optionally use cartesian coordinates. +* Setting viewport by pan and zoom transformation. +* Low level path commands with short names adopted from SVG. +* Higher level element commands. +* Maintaining a state stack for styling and transformations. +* Easy way to build custom symbol libraries. +* Tiny footprint by 5kB compressed (gzip). + +## Example + +```html + ``` -There are a few things to note: - -* Only two objects `g2` and `ctx` are involved. -* A couple of graphics commands are invoked via the `g2` object. -* Both objects are nearly completely independent from each other. Only the last code line establishes a loose connection between them. - - - -### How the queue works ### -Every invokation of a `g2` command method stores an equivalent graphics context or custom function pointer in `g2`'s command queue. Finally with the help of the `g2.exe` method the queue is applied to a graphics context instance for rendering. - -![img01] - -The command queue is implemented as an array holding objects containing a function pointer and an optional arguments array. So the command queue of the example above looks like this: - -```javascript -[ {c:CanvasRenderingContext2D.prototype.beginPath}, - {c:CanvasRenderingContext2D.prototype.moveTo, a:[100,50]}, - {c:CanvasRenderingContext2D.prototype.lineTo, a:[200,50]}, - {c:CanvasRenderingContext2D.prototype.lineTo, a:[200,150]}, - {c:CanvasRenderingContext2D.prototype.lineTo, a:[100,150]}, - {c:CanvasRenderingContext2D.prototype.closePath}, - {c:g2.prototype.drw.cmd} ] -``` -Applying this array of Function objects to a specific canvas context results in only very little additional runtime cost (performing the loop and possibly invoking wrapper functions) and moderate additional memory cost (the queue) compared to directly addressing the canvas context: - -```javascript -ctx.beginPath(); -ctx.moveTo(100,50); -ctx.lineTo(200,50); -ctx.lineTo(200,150); -ctx.lineTo(100,150); -ctx.closePath(); -ctx.fill(); ctx.stroke(); // g2.prototype.drw.cmd -``` - -Once you have successfully built a command queue, you can apply it repeatedly to one or multiple graphics contexts via its `exe`-method. - - -### Features ### - -**g2** is basically two things: a small javascript 2D graphics library **and** a lightweight javascript object holding the command queue. The function call `g2()` works as a constructor without requiring `new`. (There are [controversial](http://javascript.crockford.com/prototypal.html) [discussions](http://www.2ality.com/2013/07/defending-constructors.html) on the web about that). - -*g2* further supports - -* method chaining. -* low level path commands with short names adopted from SVG. - * `p,m,l,q,c,a,z` -* higher level element commands. - * `rec,cir,lin,arc,ply,img,txt` -* styling parallel and in extension to the canvas context state. - * `style` -* rendering commands. - * `stroke,fill,drw,clr,grid` -* state stack for style properties and transformations. - * `beg,end` -* managing the command queue. - * `cpy,del,dump` -* reuse other *g2* objects. - * `use` -* render the command queue to a graphics context. - * `exe` -* viewport initialization methods. - * `cartesian,pan,zoom,trf` - -A more detailed exploration of these features follows. - - -### Benefits ### - -*g2* is more than merely a thin wrapper around the canvas context. It helps in - -* collecting graphics commands in a queue for fast and compact rendering at a later time. -* decoupling the graphics commands from the renderer *instance* as well as abstracting away the renderer *api*. -* separating an applications *model* from its *view*. - -Let's elaborate these points a little more. - -##### Fast Rendering ##### -Graphics intense applications like simulations and games often work with *back buffers* for improving the visual experience. A back buffer is a temporarily invisible graphics context used to draw on during a certain time frame. On completion that graphics context is made visible then. Intermediate flicker effects are minimized by this technique. A *g2* object, while at first collecting graphics commands and finally rendering them, is acting similar to a back buffer. - -##### Decoupling ##### -A *g2* object is very loosely coupled with a graphics context. We can decide at the latest at rendering time, where to send the graphics commands stored in the queue to, or even send them to multiple different graphics contexts. Rendering the same graphics to a main window and in parallel to a small zoom-in window would be an example for this. - -In parallel implementations (libraries) the same graphics commands could be used to address *SVG* or *webGL*. - -*g2* supports the [*HTML5 canvas 2D context*](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) as the only renderer at current. - -##### Separating Model from View ##### -Assume a graphics application managing geometric shapes. The applications model will primarily consist of a container holding objects representing shapes. Discussing now the problem, how to render the shapes (the *view* in MVC speech) may lead to the demand, to strictly separate the model from the view. -```javascript -class Circle { - constructor(x,y,r) { ... } - render(g) {g.cir(this.x,this.y,this.r)} -} -class Rect { - constructor(x,y,w,h) { ... } - render(g) {g.rec(this.x,this.y,this.w,this.h)} -} - -let model = [new Circle(1,2,3),new Rect(4,5,6,7),...], - g = g2().grid().style(...), - ctx1 = getElementById('c1').getContext('2d'), // view 1 - ctx2 = getElementById('c2').getContext('2d'), // view 2 - -for (let i of model) // build command queue of ... - shapes[i].render(g); // ... model's shapes drawing commands. - -g.exe(ctx1); // render to view 1 -g.exe(ctx2); // render to view 2 - -``` -But then, who knows better how to draw a shape than the shape itself? One or multiple lightweight *g2* objects may act here as neutral mediators between the model's shapes and the graphics context, as in: "Show me how to draw yourself, I will hand this recipe over to a suitable renderer later!" - - -### Wiki ### -See the [Wiki](https://github.com/goessner/g2/wiki) to learn more about the usage of g2! - -[img01]: ./img/g2-concept.png "g2 command queue" +![first](./img/g2-first.png) + +## Documentation + * [Getting started](../../wiki/Getting-started) + * [Concepts](../../wiki/concepts) + * [Paths](../../wiki/paths) + * [Elements](../../wiki/elements) + * [Styles](../../wiki/styles) + * [State](../../wiki/state) + * [Viewport](../../wiki/viewport) + * [Reuse](../../wiki/reuse) + * [Renderers](../../wiki/renderers) diff --git a/api/README.md b/api/README.md index e542f4e..9b39eb6 100644 --- a/api/README.md +++ b/api/README.md @@ -1,90 +1,101 @@ ## g2([args]) -Maintains a queue of 2D graphics commands. +Create a queue of 2D graphics commands. **Kind**: global function -| Param | Type | Description | -| --- | --- | --- | -| [args] | object | Arguments object with one or more members. | -| [args.cartesian] | bool | Set cartesian coordinates. | -| [args.pan] | object | | -| [args.pan.dx] | float | Pan in x. | -| [args.pan.dy] | float | Pan in y. | -| [args.zoom] | object | | -| [args.zoom.x] | float | Zoom center x. | -| [args.zoom.y] | float | Zoom center y. | -| [args.zoom.scl] | float | Zoom factor. | -| [args.trf] | object | | -| [args.trf.x] | float | Transform in x. | -| [args.trf.y] | float | Transform in y. | -| [args.trf.scl] | float | Zoom factor. | +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| [args] | object | | Arguments object with one or more members. | +| [args.cartesian] | bool | | Use cartesian coordinate system. | +| [args.pan] | object | | | +| [args.pan.dx] | float | | Pan about dx in x direction. | +| [args.pan.dy] | float | | Pan about dy in y direction. | +| [args.zoom] | object | | | +| [args.zoom.x] | float | 0 | Zoom to point with x coordinate. | +| [args.zoom.y] | float | 0 | Zoom to point with x coordinate. | +| [args.zoom.scl] | float | | Zoom by factor 'scl'. | +| [args.trf] | object | | | +| [args.trf.x] | float | | Translate to x. | +| [args.trf.y] | float | | Translate to y. | +| [args.trf.scl] | float | | Scale by factor 'scl'. | **Example** ```js -// How to use g2() var ctx = document.getElementById("c").getContext("2d"); // Get your canvas context. g2() // The first call of g2() creates a g2 object. .lin(50,50,100,100) // Append commands. .lin(100,100,200,50) .exe(ctx); // Execute commands. +// How to use g2() +var ctx = document.getElementById("c").getContext("2d"); +g2() // Create 'g2' instance. + .lin(50,50,100,100) // Append 'line' command. + .lin(100,100,200,50) // Append more command ... + .exe(ctx); // Execute commands addressing canvas context. ``` * [g2([args])](#g2) - * _instance_ - * [.cartesian()](#g2+cartesian) ⇒ object - * [.pan(dx, dy)](#g2+pan) ⇒ object - * [.zoom(scl, [x], [y])](#g2+zoom) ⇒ object - * [.trf(x, y, scl)](#g2+trf) ⇒ object - * [.pntToUsr(x, y, h)](#g2+pntToUsr) ⇒ object - * [.vecToUsr(x, y)](#g2+vecToUsr) ⇒ object - * [.p()](#g2+p) ⇒ object - * [.m(x, y)](#g2+m) ⇒ object - * [.l(x, y)](#g2+l) ⇒ object - * [.q(x1, y1, x, y)](#g2+q) ⇒ object - * [.c(x1, y1, x2, y2, x, y)](#g2+c) ⇒ object - * [.a(dw, x, y)](#g2+a) ⇒ object - * [.z()](#g2+z) ⇒ object - * [.stroke([p])](#g2+stroke) ⇒ object - * [.fill([p])](#g2+fill) ⇒ object - * [.drw([p])](#g2+drw) ⇒ object - * [.txt(s, [x], [y], [maxWidth])](#g2+txt) ⇒ object - * [.img(uri, [x], [y], [b], [h], [xoff], [yoff], [dx], [dy])](#g2+img) ⇒ object - * [.lin(x1, y1, x2, y2)](#g2+lin) ⇒ object - * [.rec(x, y, b, h)](#g2+rec) ⇒ object - * [.cir(x, y, r)](#g2+cir) ⇒ object - * [.arc(x, y, r, [w], [dw])](#g2+arc) ⇒ object - * [.ply(parr, [closed], [opts])](#g2+ply) ⇒ object - * [.beg(x, y, w, scl)](#g2+beg) ⇒ object - * [.end()](#g2+end) ⇒ object - * [.clr()](#g2+clr) ⇒ object - * [.grid([color], [size])](#g2+grid) ⇒ object - * [.use(g, [x], [y], [w], [scl])](#g2+use) ⇒ object - * [.style(args)](#g2+style) ⇒ object - * [.exe(ctx, [g])](#g2+exe) ⇒ object - * [.cpy(g)](#g2+cpy) ⇒ object - * [.del()](#g2+del) ⇒ object - * [.dump([space])](#g2+dump) ⇒ string - * _static_ - * [.symbol](#g2.symbol) : object - * [.version](#g2.version) : string + * _instance_ + * [.cartesian()](#g2+cartesian) ⇒ object + * [.pan(dx, dy)](#g2+pan) ⇒ object + * [.zoom(scl, [x], [y])](#g2+zoom) ⇒ object + * [.trf(x, y, scl)](#g2+trf) ⇒ object + * [.p()](#g2+p) ⇒ object + * [.m(x, y)](#g2+m) ⇒ object + * [.l(x, y)](#g2+l) ⇒ object + * [.q(x1, y1, x, y)](#g2+q) ⇒ object + * [.c(x1, y1, x2, y2, x, y)](#g2+c) ⇒ object + * [.a(dw, x, y)](#g2+a) ⇒ object + * [.z()](#g2+z) ⇒ object + * [.stroke([d])](#g2+stroke) ⇒ object + * [.fill([d])](#g2+fill) ⇒ object + * [.drw([d])](#g2+drw) ⇒ object + * [.txt(s, [x], [y], [w], [args])](#g2+txt) ⇒ object + * [.img(uri, [x], [y], [b], [h], [xoff], [yoff], [dx], [dy])](#g2+img) ⇒ object + * [.lin(x1, y1, x2, y2)](#g2+lin) ⇒ object + * [.rec(x, y, b, h)](#g2+rec) ⇒ object + * [.cir(x, y, r)](#g2+cir) ⇒ object + * [.arc(x, y, r, [w], [dw])](#g2+arc) ⇒ object + * [.ply(parr, mode, opts)](#g2+ply) ⇒ object + * [.beg(args)](#g2+beg) ⇒ object + * [.end()](#g2+end) ⇒ object + * [.clr()](#g2+clr) ⇒ object + * [.grid([color], [size])](#g2+grid) ⇒ object + * [.use(g, args)](#g2+use) ⇒ object + * [.style(args)](#g2+style) ⇒ object + * [.exe(ctx, [g])](#g2+exe) ⇒ object + * [.cpy(g)](#g2+cpy) ⇒ object + * [.del()](#g2+del) ⇒ object + * [.dump([space])](#g2+dump) ⇒ string + * [.pntToUsr(x, y, h)](#g2+pntToUsr) ⇒ object + * [.vecToUsr(x, y)](#g2+vecToUsr) ⇒ object + * _static_ + * [.symbol](#g2.symbol) : object + * [.version](#g2.version) : string ### g2.cartesian() ⇒ object -Set cartesian coordinates mode within a viewport. +Set the viewports cartesian mode. +This is an explicite alternative to setting the cartesian flag in the +constructors arguments object. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 ### g2.pan(dx, dy) ⇒ object -Pan a distance within a viewport. +Pan the viewport about a vector. +This is an explicite alternative to setting the pan values in the +constructors arguments object. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 | Param | Type | Description | | --- | --- | --- | -| dx | float | x-component to pan. | -| dy | float | y-component to pan. | +| dx | float | x-value to pan. | +| dy | float | y-value to pan. | ### g2.zoom(scl, [x], [y]) ⇒ object -Zoom within a viewport. +Zoom the viewport by a scaling factor with respect to given center. +This is an explicite alternative to setting the zoom values in the +constructors arguments object. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -97,7 +108,9 @@ Zoom within a viewport. ### g2.trf(x, y, scl) ⇒ object -Set transform directly within a viewport. +Set the viewports transformation. +This is an explicite alternative to setting the zoom values in the +constructors arguments object. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -108,31 +121,6 @@ Set transform directly within a viewport. | y | float | y-translation. | | scl | float | Scaling factor. | - -### g2.pntToUsr(x, y, h) ⇒ object -Get user coordinates from canvas coordinates for point (with respect to initial transform). - -**Kind**: instance method of [g2](#g2) -**Returns**: object - User coordinates {x, y} - -| Param | Type | Description | -| --- | --- | --- | -| x | float | x-translation. | -| y | float | y-translation. | -| h | float | Viewport (canvas) height. Only needed in cartesian coordinate system. | - - -### g2.vecToUsr(x, y) ⇒ object -Get user coordinates from canvas coordinates for direction vector (with respect to initial transform). - -**Kind**: instance method of [g2](#g2) -**Returns**: object - User coordinates {x, y} - -| Param | Type | Description | -| --- | --- | --- | -| x | float | x-translation. | -| y | float | y-translation. | - ### g2.p() ⇒ object Begin new path. @@ -148,28 +136,34 @@ Move to point. | Param | Type | Description | | --- | --- | --- | -| x | float | array | object | Point x coordinate | -| y | float | undefined | Point y coordinate | +| x | float | Move to x coordinate | +| y | float | Move to y coordinate | ### g2.l(x, y) ⇒ object -Create line to point. +Create line segment to point. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 | Param | Type | Description | | --- | --- | --- | -| x | float | Point x coordinate. | -| y | float | Point y coordinate. | +| x | float | x coordinate of target point. | +| y | float | y coordinate of target point. | **Example** ```js -var g = g2(); // Create g2 object. g.p() // Begin path. .m(0,50) // Move to point. .l(300,0) // Create line to point. .l(400,100) // ... .stroke() // Stroke path. .exe(ctx); // Render to canvas context. +g2().p() // Begin path. + .m(0,50) // Move to point. + .l(300,0) // Line segment to point. + .l(400,100) // ... + .stroke() // Stroke path. + .exe(ctx); // Render to context. ``` ### g2.q(x1, y1, x, y) ⇒ object -Create quadratic bezier curve to point. ![Example](../img/q.png "Example") +Create quadratic bezier curve segment to point. +![Example](img/quadratic.png "Example") **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -178,16 +172,21 @@ Create quadratic bezier curve to point. ![Example](../img/q.png "Example") | --- | --- | --- | | x1 | float | x coordinate of control point. | | y1 | float | y coordinate of control point. | -| x | float | x coordinate of point. | -| y | float | y coordinate of point. | +| x | float | x coordinate of target point. | +| y | float | y coordinate of target point. | **Example** ```js -var g = g2(); // Create g2 object. g.p() // Begin path. .m(0,0) // Move to point. .q(200,200,400,0) // Create quadratic bezier curve. .stroke() // Stroke path. .exe(ctx); // Render to canvas context. +g2().p() // Begin path. + .m(0,0) // Move to point. + .q(200,200,400,0) // Quadratic bezier curve segment. + .stroke() // Stroke path. + .exe(ctx); // Render to context. ``` ### g2.c(x1, y1, x2, y2, x, y) ⇒ object -Create cubic bezier curve to point. ![Example](../img/c.png "Example") +Create cubic bezier curve to point. +![Example](img/curve.png "Example") **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -198,38 +197,48 @@ Create cubic bezier curve to point. ![Example](../img/c.png "Example") | y1 | float | y coordinate of first control point. | | x2 | float | x coordinate of second control point. | | y2 | float | y coordinate of second control point. | -| x | float | x coordinate of endpoint. | -| y | float | y coordinate of endpoint. | +| x | float | x coordinate of target point. | +| y | float | y coordinate of target point. | **Example** ```js -var g = g2(); // Create g2 object. g.p() // Begin path. .m(0,100) // Move to point. .c(100,200,200,0,400,100) // Create cubic bezier curve. .stroke() // Stroke path. .exe(ctx); // Render to canvas context. +g2().p() // Begin path. + .m(0,100) // Move to point. + .c(100,200,200,0,400,100) // Create cubic bezier curve. + .stroke() // Stroke path. + .exe(ctx); // Render to canvas context. ``` ### g2.a(dw, x, y) ⇒ object -Draw arc with angular range dw to point. ![Example](../img/a.png "Example") +Draw arc with angular range to target point. +![Example](img/a.png "Example") **Kind**: instance method of [g2](#g2) **Returns**: object - g2 | Param | Type | Description | | --- | --- | --- | -| dw | float | Angle in radians. Can be positive or negative. | -| x | float | x coordinate of endpoint. | -| y | float | y coordinate of endpoint. | +| dw | float | Angular range in radians. | +| x | float | x coordinate of target point. | +| y | float | y coordinate of target point. | **Example** ```js -var g = g2(); // Create g2 object. g.p() // Begin path. .m(50,50) // Move to point. .a(2,300,100) // Create cubic bezier curve. .stroke() // Stroke path. .exe(ctx); // Render to canvas context. +var g = g2(); // Create g2 object. +g2().p() // Begin path. + .m(50,50) // Move to point. + .a(2,300,100) // Create cubic bezier curve. + .stroke() // Stroke path. + .exe(ctx); // Render to canvas context. ``` ### g2.z() ⇒ object -Close current path. +Close current path by straigth line. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 -### g2.stroke([p]) ⇒ object +### g2.stroke([d]) ⇒ object Stroke the current path or path object. **Kind**: instance method of [g2](#g2) @@ -237,10 +246,10 @@ Stroke the current path or path object. | Param | Type | Description | | --- | --- | --- | -| [p] | object | Path2D object | +| [d] | string | SVG path definition string. | -### g2.fill([p]) ⇒ object +### g2.fill([d]) ⇒ object Fill the current path or path object. **Kind**: instance method of [g2](#g2) @@ -248,22 +257,23 @@ Fill the current path or path object. | Param | Type | Description | | --- | --- | --- | -| [p] | object | Path2D object | +| [d] | string | SVG path definition string. | -### g2.drw([p]) ⇒ object -Shortcut for stroke and fill the current path or path object. In case of shadow, only the path interior creates shadow, not the path contour additionally. +### g2.drw([d]) ⇒ object +Shortcut for stroke and fill the current path or path object. +In case of shadow, only the path interior creates shadow, not also the path contour. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 | Param | Type | Description | | --- | --- | --- | -| [p] | object | Path2D object | +| [d] | string | SVG path definition string. | -### g2.txt(s, [x], [y], [maxWidth]) ⇒ object -Draw text. Using ctx.fillText if 'fontColor' is not 'transparent', else ctx.strokeText. +### g2.txt(s, [x], [y], [w], [args]) ⇒ object +Draw text string at anchor point. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -271,32 +281,34 @@ Draw text. Using ctx.fillText if 'fontColor' is not 'transparent', else ctx.stro | Param | Type | Default | Description | | --- | --- | --- | --- | | s | string | | Drawing text | -| [x] | float | 0 | x coordinate of text position. | -| [y] | float | 0 | y coordinate of text position. | -| [maxWidth] | float | | Currently not used due to Chrome 36 (can't deal with 'undefined'). | +| [x] | float | 0 | x coordinate of text anchor position. | +| [y] | float | 0 | y coordinate of text anchor position. | +| [w] | float | 0 | w Rotation angle about anchor point with respect to positive x-axis. | +| [args] | object | | args Object with styling and/or transform values. | ### g2.img(uri, [x], [y], [b], [h], [xoff], [yoff], [dx], [dy]) ⇒ object -Draw image. It should be noted that the command queue will not be executed until all images have been completely loaded. This also applies to images of child objects. If an image can not be loaded, it will be replaced by a broken-image. +Draw image. The command queue will not be executed until all images have been completely loaded. +This also applies to images of reused g2 objects. If an image can not be loaded, it will be replaced by a broken-image symbol. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 | Param | Type | Default | Description | | --- | --- | --- | --- | -| uri | string | | Image uri or data:url. On error: Broken Image will be loaded. | +| uri | string | | Image uri or data:url. On error a broken image symboldwill be used. | | [x] | float | 0 | X-coordinate of image (upper left). | | [y] | float | 0 | Y-coordinate of image (upper left). | | [b] | float | | Width. | | [h] | float | | Height. | | [xoff] | float | | X-offset. | | [yoff] | float | | Y-offset. | -| [dx] | float | | X-delta. | -| [dy] | float | | Y-delta. | +| [dx] | float | | Region x. | +| [dy] | float | | Region y. | ### g2.lin(x1, y1, x2, y2) ⇒ object -Draw line. +Draw line by start point and end point. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -310,11 +322,12 @@ Draw line. **Example** ```js -var g = g2(); // Create g2 object. g.lin(10,10,190,10) // Draw line. .exe(ctx); // Render to canvas context. +g2().lin(10,10,190,10) // Draw line. + .exe(ctx); // Render to context. ``` ### g2.rec(x, y, b, h) ⇒ object -Draw rectangle. +Draw rectangle by anchor point and dimensions. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -328,11 +341,12 @@ Draw rectangle. **Example** ```js -var g = g2(); // Create g2 object. g.rec(100,80,40,30) // Draw rectangle. .exe(ctx); // Render to canvas context. +g2().rec(100,80,40,30) // Draw rectangle. + .exe(ctx); // Render to canvas context. ``` ### g2.cir(x, y, r) ⇒ object -Draw circle. +Draw circle by center and radius. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -345,11 +359,13 @@ Draw circle. **Example** ```js -var g = g2(); // Create g2 object. g.cir(100,80,20) // Draw circle. .exe(ctx); // Render to canvas context. +g2().cir(100,80,20) // Draw circle. + .exe(ctx); // Render to context. ``` ### g2.arc(x, y, r, [w], [dw]) ⇒ object -Draw arc. No fill applied. ![Example](../img/arc.png "Example") +Draw arc by center point, radius, start angle and angular range. +![Example](../img/arc.png "Example") **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -360,15 +376,19 @@ Draw arc. No fill applied. ![Example](../img/arc.png "Example") | y | float | | y-value center. | | r | float | | Radius. | | [w] | float | 0 | Start angle (in radian). | -| [dw] | float | 2*pi | Angular range in radian. In case of positive values it is running clockwise with left handed default coordinate system. | +| [dw] | float | 2*pi | Angular range in Radians. | **Example** ```js -var g = g2(); g.arc(300,400,390,-Math.PI/4,-Math.PI/2) .exe(ctx); +g2().arc(300,400,390,-Math.PI/4,-Math.PI/2) + .exe(ctx); ``` -### g2.ply(parr, [closed], [opts]) ⇒ object -Draw polygon. Using iterator function getting array and point index as parameters returning corresponding point object {x:,y:} [optional]. Default iterator expects sequence of x/y-coordinates as a flat array ([x0,y0,x1,y1,...]) +### g2.ply(parr, mode, opts) ⇒ object +Draw polygon by points. +Using iterator function for getting points from array by index. +It must return matching point object {x:,y:} or object {done:true}. +Default iterator expects sequence of x/y-coordinates as a flat array ([x0,y0,x1,y1,...]) **Kind**: instance method of [g2](#g2) **Returns**: object - this @@ -376,44 +396,52 @@ Draw polygon. Using iterator function getting array and point index as parameter | Param | Type | Default | Description | | --- | --- | --- | --- | | parr | array | | Array of points | -| [closed] | boolean | false | Draw closed polygon. | -| [opts] | object | | Options object. | -| [opts.fmt] | string | "x,y" | Predefined polygon point iterators: `"x,y"` (Flat Array of x,y-values sequence), `"[x,y]"` (Array of [x,y] arrays), `"{x,y}"` (Array of {x,y} objects) | -| [opts.itr] | function | | Iterator function getting array and point index as parameters: `function(arr,i)`. It has priority over 'fmt'. | +| mode | bool | 'split' | | = false: non-closed polygon mode = 'split': intermittend lines mode = not falsy: closed polygon | +| opts | object | | Options object. | +| [opts.fmt] | string | "\"x,y\"" | Points array format: "x,y" Flat Array of x,y-values [default] |"[x,y]" Array of [x,y] arrays |"{x,y}" Array of {x:} objects | +| [opts.itr] | function | | Iterator function getting array and point index as parameters: `function(arr,i)`. If provided it has priority over 'fmt'. | **Example** ```js -g2().ply([100,50,120,60,80,70]), .ply([150,60],[170,70],[130,80]],true,{fmt:"[x,y]"}), .ply({x:160,y:70},{x:180,y:80},{x:140,y:90}],true,{fmt:"{x,y}"}), .exe(ctx); +g2().ply([100,50,120,60,80,70]), + .ply([150,60],[170,70],[130,80]],true,{fmt:"[x,y]"}), + .ply({x:160,y:70},{x:180,y:80},{x:140,y:90}],true,{fmt:"{x,y}"}), + .exe(ctx); ``` -### g2.beg(x, y, w, scl) ⇒ object -Begin subcommands. Style state is saved. Optionally apply (similarity) transformation. +### g2.beg(args) ⇒ object +Begin subcommands. Current state is saved. +Optionally apply (similarity) transformation or style properties. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 | Param | Type | Description | | --- | --- | --- | -| x | float | Translation value x. | -| y | float | Translation value y. | -| w | float | Rotation angle (in radians). | -| scl | float | Scale factor. | +| args | object | Arguments object. | +| [args.x] | float | Translation value x. | +| [args.y] | float | Translation value y. | +| [args.w] | float | Rotation angle (in radians). | +| [args.scl] | float | Scale factor. | +| [args.matrix] | array | Matrix instead of single transform arguments (SVG-structure [a,b,c,d,x,y]). | +| [args.] | float | Style property. See 'g2.style' for details. | +| [args.] | float | ... | ### g2.end() ⇒ object -End subcommands. Previous style state is restored. +End subcommands. Previous state is restored. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 ### g2.clr() ⇒ object -Clear canvas. +Clear viewport. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 ### g2.grid([color], [size]) ⇒ object -Show grid. +Draw grid. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 @@ -421,116 +449,126 @@ Show grid. | Param | Type | Default | Description | | --- | --- | --- | --- | | [color] | string | "#ccc" | CSS grid color. | -| [size] | float | | Grid size (if g2 has a viewport object assigned, viewport's grid size is more relevant). | +| [size] | float | | Grid size. | -### g2.use(g, [x], [y], [w], [scl]) ⇒ object -Use g2 graphics commands from another g2 source object by reference. With this command you can reuse instances of grouped graphics commands while applying a similarity transformation on them. In fact you might want to build custom graphics libraries on top of that feature. +### g2.use(g, args) ⇒ object +Reference g2 graphics commands from another g2 object. +With this command you can reuse instances of grouped graphics commands +while applying a similarity transformation and style properties on them. +In fact you might want to build custom graphics libraries on top of that feature. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| g | object | string | | g2 source object or symbol name found in 'g2.symbol' namespace. | -| [x] | float | 0 | Drawing x position. | -| [y] | float | 0 | Drawing y position. | -| [w] | float | 0 | Rotation Angle (in radians). | -| [scl] | float | 1 | Scale Factor. | +| Param | Type | Description | +| --- | --- | --- | +| g | object | string | g2 source object or symbol name found in 'g2.symbol' namespace. | +| args | object | Arguments object. | +| [args.x] | float | Translation value x. | +| [args.y] | float | Translation value y. | +| [args.w] | float | Rotation angle (in radians). | +| [args.scl] | float | Scale factor. | +| [args.matrix] | array | Matrix instead of single transform arguments (SVG-structure [a,b,c,d,x,y]). | +| [args.] | float | Style property. See 'g2.style' for details. | +| [args.] | float | ... | **Example** ```js -var wheel = g2().style({fs:"#DDDDDD"}).cir(0,0,30).style({fs:"black"}).cir(0,0,5); var car = g2().p().m(-60,0).l(220,0).a(-Math.PI/2,160,-55) .l(150,-55).l(120,-100).l(20,-100).a(-Math.PI/2,-60,0).z().drw() .use(wheel,0,0) .use(wheel,170,0); g2().grid() .style({fs:"#4227F2",lw:3}) .use(car,80,120) .style({fs:"#F64385",lw:3}) .use(car,370,120,0,0.8) .exe(ctx); +g2.symbol.cross = g2().lin(5,5,-5,-5).lin(5,-5,-5,5); // Define symbol. +g2().use("cross",{x:100,y:100}) // Draw cross at position 100,100. + .exe(ctx); // Render to context. ``` ### g2.style(args) ⇒ object -Modifies the current graphics state. +Apply new style properties. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 | Param | Type | Description | | --- | --- | --- | -| args | object | Modifies graphics state by any number of properties. As an property name many of the known canvas names, as well as their short form are permitted. | -| args.fillStyle | string | Set fill color. | -| args.fs | string | See 'fillStyle'. | -| args.strokeStyle | string | Set stroke color. | -| args.ls | string | See 'strokeStyle'. | -| args.lineWidth | float | Set line width. | -| args.lw | float | See 'lineWidth'. | -| args.lineCap | string | Adds a cap to the line. Possible values: `butt`, `round` and `square` | -| args.lc | string | See 'lineCap'. | -| args.lineJoin | string | Set the way in which lines are joined. Possible values: `round`, `bevel` and `miter` | -| args.lj | string | See 'lineJoin'. | -| args.miterLimit | float | Set the mitering of the corner. | -| args.ml | float | See 'miterLimit'. | -| args.lineDash | array | Set the line dash. | -| args.ld | array | See 'lineDash'. | -| args.lineDashOffset | int | Set the offset of line dash. | -| args.lo | int | See 'lineDashOffset'. | -| args.lineMode | string | Set line mode. In _g2_'s basic form only `normal` supported. | -| args.lm | string | See 'lineMode'. | -| args.shadowOffsetX | float | Set the offset of the shadow in x. | -| args.shx | float | See 'shadowOffsetX'. | -| args.shadowOffsetY | float | Set the offset of the shadow in y. | -| args.shy | float | See 'shadowOffsetY'. | -| args.shadowBlur | float | Set the level of the blurring effect. | -| args.shb | float | See 'shadowBlur'. | -| args.shadowColor | string | Set the shadow color. | -| args.shc | string | See 'shadowColor'. | -| args.textAlign | string | Set holizontal text alignment. | -| args.thal | string | See 'textAlign'. | -| args.textBaseline | string | Set vertical text alignment. | -| args.tval | string | See 'textBaseline'. | -| args.fontFamily | string | Set font family. | -| args.fof | string | See 'fontFamily'. | -| args.fontSize | float | Set font size. | -| args.foz | float | See 'fontSize'. | -| args.fontColor | string | Set font color. | -| args.foc | string | See 'fontColor'. | -| args.fontWeight | string | Set font weight. | -| args.fow | string | See 'fontWeight'. | -| args.fontStyle | string | Set font style. | -| args.fos | string | See 'fontStyle'. | -| args.fontSizeNonScalable | bool | Prevent text scaling. | -| args.foznosc | bool | See 'fontSizeNonScalable'. | -| args.lineWidthNonScalable | bool | Prevent line scaling. | -| args.lwnosc | bool | See 'lineWidthNonScalable'. | +| args | object | Style properties object. | +| args.fs | string | Fill color (fillStyle). | +| args.ls | string | Line color (lineStroke). | +| args.lw | float | Line width. | +| args.lwnosc | bool | Line width nonscalable. | +| args.lc | string | Line cap [`butt`, `round`, `square`]. | +| args.lj | string | Line join [`round`, `bevel` and `miter`]. | +| args.ml | float | Miter limit'. | +| args.ld | array | Line dash array. | +| args.lo | int | Line dash offset. | +| args.shx | float | Shadow offset x-value. | +| args.shy | float | Shadow offset y-value. | +| args.shb | float | Shadow blur effect value. | +| args.shc | string | Shadow color. | +| args.thal | string | Text horizontal alignment. | +| args.tval | string | Text vertical alignment. | +| args.fof | string | Font family. | +| args.foz | float | Font size. | +| args.foc | string | Font color. | +| args.fow | string | Font weight ['normal','bold','lighter','bolder',100,200,...,900]. | +| args.fos | string | Font style ['normal','italic','oblique']. | +| args.foznosc | bool | Font size nonscalable. | **Example** ```js -g = g2(); g.style({ fillStyle:"#58dbfa", // Set fill style. lw:10, // Set line width. ls:"#313942", // Set line style. lj:"round" }) // Set line join. .rec(10,10,300,100) .style({ lw:20, // Set line again. fs:"transparent", // Set fill style. shx:10, // Set shadow x-translation. shc:"black", // Set shadow color shb:10, // Set shadow blur. ld:[0.05,0.25] }) // Set line dash. .p().m(40,40).c(150,150,200,0,280,50).drw(); g.exe(ctx); +g = g2(); +g2().style({ fs:"#58dbfa", // Set fill style. + lw:10, // Set line width. + ls:"#313942", // Set line style. + lj:"round" }) // Set line join. + .rec(10,10,300,100) + .style({ lw:20, // Set line width. + fs:"transparent", // Set fill style. + shx:10, // Set shadow x-translation. + shc:"black", // Set shadow color + shb:10, // Set shadow blur. + ld:[1,2] }) // Set line dash. + .p().m(40,40).c(150,150,200,0,280,50).drw() + .exe(ctx); ``` ### g2.exe(ctx, [g]) ⇒ object -Execute g2 commands. +Execute g2 commands. Do so recursively with 'use'ed commands. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 | Param | Type | Default | Description | | --- | --- | --- | --- | -| ctx | object | | Canvas Context. | +| ctx | object | | Context. | | [g] | object | this | g2 Object to execute. | ### g2.cpy(g) ⇒ object -Copy all g2 graphics commands from a g2 object. The copied commands are discrete and independent. +Copy all g2 graphics commands from a g2 object. +If the source object is 'this', nothing is done. **Kind**: instance method of [g2](#g2) **Returns**: object - g2 | Param | Type | Description | | --- | --- | --- | -| g | object | g2 object to copy commands from. | +| g | object | g2 source object to copy commands from. | **Example** ```js -var smiley = g2().cir(60,60,50).cir(40,40,10).cir(80,40,10).arc(60,60,30,0.8,2); g2().style({lw:8,fs:"yellow"}) .cpy(smiley) // Copy all commands from 'smiley'. .exe(ctx); +var smiley = g2().cir(60,60,50).cir(40,40,10).cir(80,40,10).arc(60,60,30,0.8,2); +g2().style({lw:8,fs:"yellow"}) + .cpy(smiley) // Copy all commands from 'smiley'. + .exe(ctx); ``` **Example** ```js -function smiley(g) { // maybe do some calculations to define a smiley return g.cir(60,60,50).cir(40,40,10).cir(80,40,10).arc(60,60,30,0.8,2) } var g = g2(); g.style({lw:8,fs:"yellow"}) .cpy(smiley(g)) .exe(ctx); +function smiley(g) { + // do some calculations ... + return g.cir(60,60,50).cir(40,40,10).cir(80,40,10).arc(60,60,30,0.8,2); +} +var g = g2(); +g.style({lw:8,fs:"yellow"}) + .cpy(smiley(g)) // invoke smiley here in method chain. + .exe(ctx); ``` ### g2.del() ⇒ object @@ -540,6 +578,7 @@ Delete all commands. **Returns**: object - g2 ### g2.dump([space]) ⇒ string +Debug helper method. Convert g2 command queue to JSON formatted string. **Kind**: instance method of [g2](#g2) @@ -549,6 +588,31 @@ Convert g2 command queue to JSON formatted string. | --- | --- | --- | | [space] | string | Number of spaces to use for indenting JSON output. | + +### g2.pntToUsr(x, y, h) ⇒ object +Get user coordinates from canvas coordinates for point (with respect to initial transform). + +**Kind**: instance method of [g2](#g2) +**Returns**: object - User coordinates {x, y} + +| Param | Type | Description | +| --- | --- | --- | +| x | float | x-translation. | +| y | float | y-translation. | +| h | float | Viewport (canvas) height. Only needed in cartesian coordinate system. | + + +### g2.vecToUsr(x, y) ⇒ object +Get user coordinates from canvas coordinates for direction vector (with respect to initial transform). + +**Kind**: instance method of [g2](#g2) +**Returns**: object - User coordinates {x, y} + +| Param | Type | Description | +| --- | --- | --- | +| x | float | x-translation. | +| y | float | y-translation. | + ### g2.symbol : object Namespace for symbol objects. A symbol can be used by `use("symbolname")`. @@ -556,10 +620,13 @@ Namespace for symbol objects. A symbol can be used by `use("symbolname")`. **Kind**: static property of [g2](#g2) **Example** ```js -g2.symbol.nodfix = g2().style({ls:"#333",fs:"#aeaeae"}) .p().m(-8,-12).l(0,0).l(8,-12).drw() .style({fs:"#dedede"}).cir(0,0,5); var g = g2(); // Create g2 object. g.grid() .use("nodfix",60,50,Math.PI,2) // Use 'nodfix' symbol with transformation. .use("nodfix",150,30,3*Math.PI/4,3); // ... g.exe(ctx); // Render graphics to 'ctx' canvas context. +g2.symbol.cross = g2().lin(5,5,-5,-5).lin(5,-5,-5,5); // Define symbol. +g2().use("cross",{x:100,y:100}) // Draw cross at position 100,100. + .exe(ctx); // Render to context. ``` ### g2.version : string -Current version. Using semantic versioning 'http://semver.org/'. +Current version. +Using semantic versioning 'http://semver.org/'. **Kind**: static constant of [g2](#g2) diff --git a/g2.js b/g2.js index 00b75d6..c2dec57 100644 --- a/g2.js +++ b/g2.js @@ -4,26 +4,19 @@ * MIT License */ /* jshint -W014 */ - /* jshint -W030 */ + // Used polyfills Math.hypot = Math.hypot || function(x,y) { return Math.sqrt(x*x+y*y); }; /** * Maintains a queue of 2D graphics commands. - * @param {object} [args] Arguments object with one or more members. - * @param {bool} [args.cartesian] Set cartesian coordinates. - * @param {object} [args.pan] - * @param {float} [args.pan.dx] Pan in x. - * @param {float} [args.pan.dy] Pan in y. - * @param {object} [args.zoom] - * @param {float} [args.zoom.x] Zoom center x. - * @param {float} [args.zoom.y] Zoom center y. - * @param {float} [args.zoom.scl] Zoom factor. - * @param {object} [args.trf] - * @param {float} [args.trf.x] Transform in x. - * @param {float} [args.trf.y] Transform in y. - * @param {float} [args.trf.scl] Zoom factor. + * @param {object} [args] Arguments object with one or more members of the following: + * { cartesian: , + * pan: { dx:, dy: }, + * zoom: { x:, y: }, scl: }, + * trf: { x:, y: }, scl: } + * } * @example * // How to use g2() * var ctx = document.getElementById("c").getContext("2d"); // Get your canvas context. @@ -32,6 +25,29 @@ Math.hypot = Math.hypot || function(x,y) { return Math.sqrt(x*x+y*y); }; * .lin(100,100,200,50) * .exe(ctx); // Execute commands. */ +/** + * Create a queue of 2D graphics commands. + * @param {object} [args] Arguments object with one or more members. + * @param {bool} [args.cartesian] Use cartesian coordinate system. + * @param {object} [args.pan] + * @param {float} [args.pan.dx] Pan about dx in x direction. + * @param {float} [args.pan.dy] Pan about dy in y direction. + * @param {object} [args.zoom] + * @param {float} [args.zoom.x = 0] Zoom to point with x coordinate. + * @param {float} [args.zoom.y = 0] Zoom to point with x coordinate. + * @param {float} [args.zoom.scl] Zoom by factor 'scl'. + * @param {object} [args.trf] + * @param {float} [args.trf.x] Translate to x. + * @param {float} [args.trf.y] Translate to y. + * @param {float} [args.trf.scl] Scale by factor 'scl'. + * @example + * // How to use g2() + * var ctx = document.getElementById("c").getContext("2d"); + * g2() // Create 'g2' instance. + * .lin(50,50,100,100) // Append 'line' command. + * .lin(100,100,200,50) // Append more command ... + * .exe(ctx); // Execute commands addressing canvas context. + */ function g2() { if (this instanceof g2) return this.constructor.apply(this,arguments); @@ -46,8 +62,17 @@ function g2() { * @type {string} * @const */ -g2.version = "1.0.0"; +g2.version = "2.0.0"; g2.transparent = "rgba(0, 0, 0, 0)"; +g2.exeStack = 0; +g2.proxy = Object.create(null); +g2.ifc = Object.create(null); +g2.ifcof = function(ctx) { + for (var ifc in g2.ifc) + if (g2.ifc[ifc](ctx)) + return ifc; + return false; +} /** * Constructor. @@ -57,51 +82,48 @@ g2.transparent = "rgba(0, 0, 0, 0)"; */ g2.prototype.constructor = function constructor(args) { if (args) { // only root g2's should have arguments ... - this.state = g2.State.create(); + this.state = g2.State.create(this); if (args.zoom) this.zoom(args.zoom.scl,args.zoom.x,args.zoom.y); if (args.pan) this.pan(args.pan.dx,args.pan.dy); if (args.trf) this.trf(args.trf.x,args.trf.y,args.trf.scl); if (args.cartesian) this.state.cartesian = args.cartesian; // static property ... } - this.cmds = [{c:constructor.cmd, a:[this]}]; + this.cmds = []; +// this.cmds = [{c:constructor, a:[this]}]; return this; }; -g2.prototype.constructor.cmd = function constructor_c(self) { - if (this.fillStyle === "#000000") { // root g2 found ... because parent g2's would have already modified 'fillStyle' to transparent. - var state = self.state || (self.state = g2.State.create()); - this.setTransform(1,0,0,state.cartesian?-1:1,0.5,(state.cartesian?this.canvas.height:0)+0.5); - state.reset(this) - .set("trf",state.trf0,this); - this.font = state.font; - } -}; /** - * Set cartesian coordinates mode within a viewport. + * Set the viewports cartesian mode. + * This is an explicite alternative to setting the cartesian flag in the + * constructors arguments object. * @method * @returns {object} g2 */ g2.prototype.cartesian = function cartesian() { - (this.state || (this.state = g2.State.create())).cartesian = true; + this.getState().cartesian = true; return this; }; /** - * Pan a distance within a viewport. + * Pan the viewport about a vector. + * This is an explicite alternative to setting the pan values in the + * constructors arguments object. * @method * @returns {object} g2 - * @param {float} dx x-component to pan. - * @param {float} dy y-component to pan. + * @param {float} dx x-value to pan. + * @param {float} dy y-value to pan. */ g2.prototype.pan = function pan(dx,dy) { - this.state = this.state || g2.State.create(); - this.state.trf0.x += dx; + this.getState().trf0.x += dx; this.state.trf0.y += dy; return this; }; /** - * Zoom within a viewport. + * Zoom the viewport by a scaling factor with respect to given center. + * This is an explicite alternative to setting the zoom values in the + * constructors arguments object. * @method * @returns {object} g2 * @param {float} scl Scaling factor. @@ -109,15 +131,16 @@ g2.prototype.pan = function pan(dx,dy) { * @param {float} [y=0] y-component of zoom center. */ g2.prototype.zoom = function zoom(scl,x,y) { - this.state = this.state || g2.State.create(); - this.state.trf0.x = (1-scl)*(x||0) + scl*this.state.trf0.x; + this.getState().trf0.x = (1-scl)*(x||0) + scl*this.state.trf0.x; this.state.trf0.y = (1-scl)*(y||0) + scl*this.state.trf0.y; this.state.trf0.scl *= scl; return this; }; /** - * Set transform directly within a viewport. + * Set the viewports transformation. + * This is an explicite alternative to setting the zoom values in the + * constructors arguments object. * @method * @returns {object} g2 * @param {float} x x-translation. @@ -125,45 +148,16 @@ g2.prototype.zoom = function zoom(scl,x,y) { * @param {float} scl Scaling factor. */ g2.prototype.trf = function trf(x,y,scl) { - this.state = this.state || g2.State.create(); - this.state.trf0.x = x; + this.getState().trf0.x = x; this.state.trf0.y = y; this.state.trf0.scl = scl; return this; }; -/** - * Get user coordinates from canvas coordinates for point (with respect to initial transform). - * @method - * @returns {object} User coordinates {x, y} - * @param {float} x x-translation. - * @param {float} y y-translation. - * @param {float} h Viewport (canvas) height. Only needed in cartesian coordinate system. - */ -g2.prototype.pntToUsr = function pntToUsr(x,y,h) { - var trf = this.state && this.state.trf0 || false; - return !trf ? {x:x,y:y} - : this.state.cartesian ? {x:(x - trf.x)/trf.scl, y:-(y - (h - trf.y))/trf.scl} - : {x:(x - trf.x)/trf.scl, y:(y - trf.y)/trf.scl}; -}; - -/** - * Get user coordinates from canvas coordinates for direction vector (with respect to initial transform). - * @method - * @returns {object} User coordinates {x, y} - * @param {float} x x-translation. - * @param {float} y y-translation. - */ -g2.prototype.vecToUsr = function vecToUsr(x,y) { - var trf = this.state && this.state.trf0 || false; - return !trf ? {x:x,y:y} - : this.state.cartesian ? {x:x/trf.scl, y:-y/trf.scl} - : {x:x/trf.scl, y:y/trf.scl}; -}; - // Path commands -// internal helper method .. +// internal helper methods .. +// ========================== // get current path point from previous command object g2.prototype._curPnt = function() { var lastcmd = this.cmds.length && this.cmds[this.cmds.length-1] || false; @@ -172,7 +166,7 @@ g2.prototype._curPnt = function() { // get index of command resolving 'callbk' to 'true'. // see 'Array.prototype.findIndex' g2.prototype.findCmdIdx = function(callbk) { - for (var i = this.cmds.length-1; i > 0; i--) + for (var i = this.cmds.length-1; i >= 0; i--) if (callbk(this.cmds[i],i,this.cmds)) return i; return 0; // command with index '0' signals 'failing' ... @@ -184,7 +178,7 @@ g2.prototype.findCmdIdx = function(callbk) { * @returns {object} g2 */ g2.prototype.p = function p() { - this.cmds.push({c:CanvasRenderingContext2D.prototype.beginPath}); + this.cmds.push({c:p}); return this; }; @@ -192,96 +186,93 @@ g2.prototype.p = function p() { * Move to point. * @method * @returns {object} g2 - * @param {float | array | object } x Point x coordinate - * @param {float | undefined} y Point y coordinate + * @param {float} x Move to x coordinate + * @param {float} y Move to y coordinate */ g2.prototype.m = function m(x,y) { - this.cmds.push({c:CanvasRenderingContext2D.prototype.moveTo,a:[x,y]}); + this.cmds.push({c:m,a:[x,y]}); return this; }; /** - * Create line to point. + * Create line segment to point. * @method * @returns {object} g2 - * @param {float} x Point x coordinate. - * @param {float} y Point y coordinate. + * @param {float} x x coordinate of target point. + * @param {float} y y coordinate of target point. * @example - * var g = g2(); // Create g2 object. - * g.p() // Begin path. - * .m(0,50) // Move to point. - * .l(300,0) // Create line to point. - * .l(400,100) // ... - * .stroke() // Stroke path. - * .exe(ctx); // Render to canvas context. + * g2().p() // Begin path. + * .m(0,50) // Move to point. + * .l(300,0) // Line segment to point. + * .l(400,100) // ... + * .stroke() // Stroke path. + * .exe(ctx); // Render to context. */ g2.prototype.l = function l(x,y) { - this.cmds.push({c:CanvasRenderingContext2D.prototype.lineTo,a:[x,y]}); + this.cmds.push({c:l,a:[x,y]}); return this; }; /** - * Create quadratic bezier curve to point. - * ![Example](../img/q.png "Example") + * Create quadratic bezier curve segment to point. + * ![Example](img/quadratic.png "Example") * @method * @returns {object} g2 * @param {float} x1 x coordinate of control point. * @param {float} y1 y coordinate of control point. - * @param {float} x x coordinate of point. - * @param {float} y y coordinate of point. + * @param {float} x x coordinate of target point. + * @param {float} y y coordinate of target point. * @example - * var g = g2(); // Create g2 object. - * g.p() // Begin path. - * .m(0,0) // Move to point. - * .q(200,200,400,0) // Create quadratic bezier curve. - * .stroke() // Stroke path. - * .exe(ctx); // Render to canvas context. + * g2().p() // Begin path. + * .m(0,0) // Move to point. + * .q(200,200,400,0) // Quadratic bezier curve segment. + * .stroke() // Stroke path. + * .exe(ctx); // Render to context. */ g2.prototype.q = function q(x1,y1,x,y) { - this.cmds.push({c:CanvasRenderingContext2D.prototype.quadraticCurveTo,a:[x1,y1,x,y],cp:[x,y]}); + this.cmds.push({c:q,a:[x1,y1,x,y],cp:[x,y]}); return this; }; /** * Create cubic bezier curve to point. - * ![Example](../img/c.png "Example") + * ![Example](img/curve.png "Example") * @method * @returns {object} g2 * @param {float} x1 x coordinate of first control point. * @param {float} y1 y coordinate of first control point. * @param {float} x2 x coordinate of second control point. * @param {float} y2 y coordinate of second control point. - * @param {float} x x coordinate of endpoint. - * @param {float} y y coordinate of endpoint. + * @param {float} x x coordinate of target point. + * @param {float} y y coordinate of target point. * @example - * var g = g2(); // Create g2 object. - * g.p() // Begin path. - * .m(0,100) // Move to point. - * .c(100,200,200,0,400,100) // Create cubic bezier curve. - * .stroke() // Stroke path. - * .exe(ctx); // Render to canvas context. + * g2().p() // Begin path. + * .m(0,100) // Move to point. + * .c(100,200,200,0,400,100) // Create cubic bezier curve. + * .stroke() // Stroke path. + * .exe(ctx); // Render to canvas context. */ g2.prototype.c = function c(x1,y1,x2,y2,x,y) { - this.cmds.push({c:CanvasRenderingContext2D.prototype.bezierCurveTo,a:[x1,y1,x2,y2,x,y],cp:[x,y]}); + this.cmds.push({c:c,a:[x1,y1,x2,y2,x,y],cp:[x,y]}); return this; }; -// arc path command /** - * Draw arc with angular range dw to point. - * ![Example](../img/a.png "Example") + * Draw arc with angular range to target point. + * ![Example](img/a.png "Example") * @method * @returns {object} g2 - * @param {float} dw Angle in radians. Can be positive or negative. - * @param {float} x x coordinate of endpoint. - * @param {float} y y coordinate of endpoint. + * @param {float} dw Angular range in radians. + * @param {float} x x coordinate of target point. + * @param {float} y y coordinate of target point. + * @example * var g = g2(); // Create g2 object. - * g.p() // Begin path. - * .m(50,50) // Move to point. - * .a(2,300,100) // Create cubic bezier curve. - * .stroke() // Stroke path. - * .exe(ctx); // Render to canvas context. + * g2().p() // Begin path. + * .m(50,50) // Move to point. + * .a(2,300,100) // Create cubic bezier curve. + * .stroke() // Stroke path. + * .exe(ctx); // Render to canvas context. */ g2.prototype.a = function a(dw,x,y) { var p1 = this._curPnt(); @@ -289,17 +280,18 @@ g2.prototype.a = function a(dw,x,y) { var dx = x-p1[0], dy = y-p1[1], tw2 = Math.tan(dw/2), rx = dx/2 - dy/tw2/2, ry = dy/2 + dx/tw2/2, w = Math.atan2(-ry,-rx); - this.cmds.push({c:CanvasRenderingContext2D.prototype.arc,a:[p1[0]+rx,p1[1]+ry,Math.hypot(rx,ry),w,w+dw,dw<0],cp:[x,y]}); + this.cmds.push({c:a,a:[p1[0]+rx,p1[1]+ry,Math.hypot(rx,ry),w,w+dw,dw<0],cp:[x,y]}); } return this; }; + /** - * Close current path. + * Close current path by straigth line. * @method * @returns {object} g2 */ g2.prototype.z = function z() { - this.cmds.push({c:CanvasRenderingContext2D.prototype.closePath}); + this.cmds.push({c:z}); return this; }; @@ -307,120 +299,81 @@ g2.prototype.z = function z() { /** * Stroke the current path or path object. * @method - * @param {object} [p] Path2D object + * @param {string} [d = undefined] SVG path definition string. * @returns {object} g2 */ -g2.prototype.stroke = function stroke(p) { - this.cmds.push(p ? {c:CanvasRenderingContext2D.prototype.stroke,a:[p]} - : {c:CanvasRenderingContext2D.prototype.stroke} ); +g2.prototype.stroke = function stroke(d) { + this.cmds.push(d ? {c:stroke,a:[d]} : {c:stroke}); return this; }; /** * Fill the current path or path object. * @method - * @param {object} [p] Path2D object + * @param {string} [d = undefined] SVG path definition string. * @returns {object} g2 */ -g2.prototype.fill = function fill(p) { - this.cmds.push(p ? {c:CanvasRenderingContext2D.prototype.fill,a:[p]} - : {c:CanvasRenderingContext2D.prototype.fill} ); +g2.prototype.fill = function fill(d) { + this.cmds.push(d ? {c:fill,a:[d]} : {c:fill}); return this; }; /** * Shortcut for stroke and fill the current path or path object. - * In case of shadow, only the path interior creates shadow, not the path contour additionally. + * In case of shadow, only the path interior creates shadow, not also the path contour. * @method - * @param {object} [p] Path2D object + * @param {string} [d = undefined] SVG path definition string. * @returns {object} g2 */ -g2.prototype.drw = function drw(p) { - this.cmds.push(p ? {c:drw.cmd,a:[p]} : {c:drw.cmd}); +g2.prototype.drw = function drw(d) { + this.cmds.push(d ? {c:drw,a:[d]} : {c:drw}); return this; }; -g2.prototype.drw.cmd = function drw_c(p) { - p ? this.fill(p) : this.fill(); - if (this.fillStyle !== g2.transparent && this.shadowColor !== g2.transparent) { - var tmp = this.shadowColor; // avoid stroke shadow when filled ... - this.shadowColor = "transparent"; // "rgba(0, 0, 0, 0)" - p ? this.stroke(p) : this.stroke(); - this.shadowColor = tmp; - } - else - p ? this.stroke(p) : this.stroke(); -}; // Graphics elements /** - * Draw text. - * Using ctx.fillText if 'fontColor' is not 'transparent', else ctx.strokeText. + * Draw text string at anchor point. * @method * @returns {object} g2 * @param {string} s Drawing text - * @param {float} [x=0] x coordinate of text position. - * @param {float} [y=0] y coordinate of text position. - * @param {float} [maxWidth] Currently not used due to Chrome 36 (can't deal with 'undefined'). + * @param {float} [x=0] x coordinate of text anchor position. + * @param {float} [y=0] y coordinate of text anchor position. + * @param {float} [w=0] w Rotation angle about anchor point with respect to positive x-axis. + * @param {object} [args=undefined] args Object with styling and/or transform values. */ -g2.prototype.txt = function txt(s,x,y,maxWidth) { - this.cmds.push({c:txt.cmd,a:[this,s,x||0,y||0,maxWidth]}); +g2.prototype.txt = function txt(s,x,y,w,args) { + this.cmds.push({c:txt,a:args?[this,s,x||0,y||0,w||0,args]:[this,s,x||0,y||0,w||0]}); return this; }; -g2.prototype.txt.cmd = function txt_c(self,s,x,y,maxWidth) { - var state = self.state, foc = state.get("foc"); - - if (state.cartesian || foc !== g2.transparent) this.save(); - if (state.cartesian) { this.scale(1,-1); y = -y; } - - if (foc !== g2.transparent) { - this.fillStyle = foc; - this.fillText(s,x,y); // maxWidth currently not used due to Chrome 42 (can#t deal with 'undefined') - } - else - this.strokeText(s,x,y); - - if (state.cartesian || foc !== g2.transparent) this.restore(); -}; /** - * Draw image. It should be noted that the command queue will not be executed until all images have been completely loaded. - * This also applies to images of child objects. If an image can not be loaded, it will be replaced by a broken-image. + * Draw image. The command queue will not be executed until all images have been completely loaded. + * This also applies to images of reused g2 objects. If an image can not be loaded, it will be replaced by a broken-image symbol. * @method * @returns {object} g2 - * @param {string} uri Image uri or data:url. On error: Broken Image will be loaded. + * @param {string} uri Image uri or data:url. On error a broken image symboldwill be used. * @param {float} [x=0] X-coordinate of image (upper left). * @param {float} [y=0] Y-coordinate of image (upper left). - * @param {float} [b] Width. - * @param {float} [h] Height. - * @param {float} [xoff] X-offset. - * @param {float} [yoff] Y-offset. - * @param {float} [dx] X-delta. - * @param {float} [dy] Y-delta. + * @param {float} [b = undefined] Width. + * @param {float} [h = undefined] Height. + * @param {float} [xoff = undefined] X-offset. + * @param {float} [yoff = undefined] Y-offset. + * @param {float} [dx = undefined] Region x. + * @param {float} [dy = undefined] Region y. */ g2.prototype.img = function img(uri,x,y,b,h,xoff,yoff,dx,dy) { - var image = new Image(), state = this.state ? this.state - : (this.state = g2.State.create()); + var image = new Image(), state = this.getState(); state.loading++; image.onload = function load() { state.loading--; }; image.onerror = function() { image.src = g2.prototype.img.broken; }; image.src = uri; - this.cmds.push({c:img.cmd,a:[this,image,x||0,y||0,b,h,xoff,yoff,dx,dy]}); + this.cmds.push({c:img,a:[this,image,x||0,y||0,b,h,xoff,yoff,dx,dy]}); return this; }; -g2.prototype.img.cmd = function img_c(self,img,x,y,b,h,xoff,yoff,dx,dy) { - b = b || img && img.width; - h = h || img && img.height; - if (self.state.cartesian) { this.save(); this.scale(1,-1); y = -y-h; } - if (xoff || yoff || dx || dy) // non-zero anyone .. ? - this.drawImage(img,xoff,yoff,dx,dy,x,y,b,h); - else - this.drawImage(img,x,y,b,h); - if (self.state.cartesian) { this.restore(); } -}; g2.prototype.img.broken = "data:image/gif;base64,R0lGODlhHgAeAKIAAAAAmWZmmZnM/////8zMzGZmZgAAAAAAACwAAAAAHgAeAEADimi63P5ryAmEqHfqPRWfRQF+nEeeqImum0oJQxUThGaQ7hSs95ezvB4Q+BvihBSAclk6fgKiAkE0kE6RNqwkUBtMa1OpVlI0lsbmFjrdWbMH5Tdcu6wbf7J8YM9H4y0YAE0+dHVKIV0Efm5VGiEpY1A0UVMSBYtPGl1eNZhnEBGEck6jZ6WfoKmgCQA7"; /** - * Draw line. + * Draw line by start point and end point. * @method * @returns {object} g2 * @param {float} x1 Start x coordinate. @@ -428,23 +381,16 @@ g2.prototype.img.broken = "data:image/gif;base64,R0lGODlhHgAeAKIAAAAAmWZmmZnM/// * @param {float} x2 End x coordinate. * @param {float} y2 End y coordinate. * @example - * var g = g2(); // Create g2 object. - * g.lin(10,10,190,10) // Draw line. - * .exe(ctx); // Render to canvas context. + * g2().lin(10,10,190,10) // Draw line. + * .exe(ctx); // Render to context. */ g2.prototype.lin = function lin(x1,y1,x2,y2) { - this.cmds.push({c:lin.cmd,a:[x1,y1,x2,y2]}); + this.cmds.push({c:lin,a:[x1,y1,x2,y2]}); return this; }; -g2.prototype.lin.cmd = function lin_c(x1,y1,x2,y2) { - this.beginPath(); - this.moveTo(x1,y1); - this.lineTo(x2,y2); - this.stroke(); -}; /** - * Draw rectangle. + * Draw rectangle by anchor point and dimensions. * @method * @returns {object} g2 * @param {float} x x-value upper left corner. @@ -452,44 +398,32 @@ g2.prototype.lin.cmd = function lin_c(x1,y1,x2,y2) { * @param {float} b Width. * @param {float} h Height. * @example - * var g = g2(); // Create g2 object. - * g.rec(100,80,40,30) // Draw rectangle. - * .exe(ctx); // Render to canvas context. + * g2().rec(100,80,40,30) // Draw rectangle. + * .exe(ctx); // Render to canvas context. */ g2.prototype.rec = function rec(x,y,b,h) { - this.cmds.push({c:rec.cmd,a:[x,y,b,h]}); + this.cmds.push({c:rec,a:[x,y,b,h]}); return this; }; -g2.prototype.rec.cmd = function rec_c(x,y,b,h) { - this.beginPath(); - this.rect(x,y,b,h); - g2.prototype.drw.cmd.call(this); -}; /** - * Draw circle. + * Draw circle by center and radius. * @method * @returns {object} g2 * @param {float} x x-value center. * @param {float} y y-value center. * @param {float} r Radius. * @example - * var g = g2(); // Create g2 object. - * g.cir(100,80,20) // Draw circle. - * .exe(ctx); // Render to canvas context. + * g2().cir(100,80,20) // Draw circle. + * .exe(ctx); // Render to context. */ g2.prototype.cir = function cir(x,y,r) { - this.cmds.push({c:cir.cmd,a:[x,y,r]}); + this.cmds.push({c:cir,a:[x,y,r]}); return this; }; -g2.prototype.cir.cmd = function cir_c(x,y,r) { - this.beginPath(); - this.arc(x,y,r,0,Math.PI*2,true); - g2.prototype.drw.cmd.call(this); -}; /** - * Draw arc. No fill applied. + * Draw arc by center point, radius, start angle and angular range. * ![Example](../img/arc.png "Example") * @method * @returns {object} g2 @@ -497,102 +431,84 @@ g2.prototype.cir.cmd = function cir_c(x,y,r) { * @param {float} y y-value center. * @param {float} r Radius. * @param {float} [w=0] Start angle (in radian). - * @param {float} [dw=2*pi] Angular range in radian. In case of positive values it is running clockwise with - * left handed default coordinate system. + * @param {float} [dw=2*pi] Angular range in Radians. * @example - * var g = g2(); - * g.arc(300,400,390,-Math.PI/4,-Math.PI/2) - * .exe(ctx); + * g2().arc(300,400,390,-Math.PI/4,-Math.PI/2) + * .exe(ctx); */ g2.prototype.arc = function arc(x,y,r,w,dw) { - this.cmds.push({c:arc.cmd,a:[x,y,r,w,dw]}); + this.cmds.push({c:arc,a:[x,y,r,w,dw]}); return this; }; -g2.prototype.arc.cmd = function arc_c(x,y,r,w,dw) { - this.beginPath(); - this.arc(x,y,r,w,w+dw,dw<0); - this.stroke(); -}; /** - * Draw polygon. - * Using iterator function getting array and point index as parameters - * returning corresponding point object {x:,y:} [optional]. + * Draw polygon by points. + * Using iterator function for getting points from array by index. + * It must return matching point object {x:,y:} or object {done:true}. * Default iterator expects sequence of x/y-coordinates as a flat array ([x0,y0,x1,y1,...]) * @method * @returns {object} this * @param {array} parr Array of points - * @param {boolean} [closed=false] Draw closed polygon. - * @param {object} [opts] Options object. - * @param {string} [opts.fmt=x,y] Predefined polygon point iterators: `"x,y"` (Flat Array of x,y-values sequence), `"[x,y]"` (Array of [x,y] arrays), `"{x,y}"` (Array of {x,y} objects) - * @param {function} [opts.itr] Iterator function getting array and point index as parameters: `function(arr,i)`. It has priority over 'fmt'. + * @param {bool | 'split'} mode = false: non-closed polygon + * mode = 'split': intermittend lines + * mode = not falsy: closed polygon + * @param {object} opts Options object. + * @param {string} [opts.fmt="x,y"] Points array format: + * "x,y" Flat Array of x,y-values [default] + * |"[x,y]" Array of [x,y] arrays + * |"{x,y}" Array of {x:} objects + * @param {function} [opts.itr=undefined] Iterator function getting array and point index as parameters: `function(arr,i)`. + * If provided it has priority over 'fmt'. * @example * g2().ply([100,50,120,60,80,70]), * .ply([150,60],[170,70],[130,80]],true,{fmt:"[x,y]"}), * .ply({x:160,y:70},{x:180,y:80},{x:140,y:90}],true,{fmt:"{x,y}"}), * .exe(ctx); */ -g2.prototype.ply = function ply(parr,closed,opts) { +g2.prototype.ply = function ply(parr,mode,opts) { var itr = opts && (opts.itr || opts.fmt && g2.prototype.ply.iterators[opts.fmt]) || false; - this.cmds.push({c:ply.cmd,a:[parr,closed,itr]}); + this.cmds.push({c:ply,a:[parr,mode,itr]}); return this; }; -g2.prototype.ply.cmd = function ply_c(parr,closed,itr) { - var p, i = 0; - itr = itr || g2.prototype.ply.itr; - p = itr(parr,i++); - if (!p.done) { // draw polygon .. - this.beginPath(); - this.moveTo(p.x,p.y); - while (!(p = itr(parr,i++)).done) - this.lineTo(p.x,p.y); - if (closed) - this.closePath(); - } - g2.prototype.drw.cmd.call(this); - return i-1; // number of points .. -}; // predefined polygon point iterators g2.prototype.ply.iterators = { - "x,y": function itr(arr,i) { return i < arr.length/2 ? {x:arr[2*i],y:arr[2*i+1]} : {done:true,count:arr.length/2}; }, - "[x,y]": function itr(arr,i) { return i < arr.length ? {x:arr[i][0],y:arr[i][1]} : {done:true,count:arr.length}; }, - "{x,y}": function itr(arr,i) { return i < arr.length ? arr[i] : {done:true,count:arr.length}; } + "x,y": function(arr,i) { return i < arr.length/2 ? {x:arr[2*i],y:arr[2*i+1]} : {done:true,count:arr.length/2}; }, + "[x,y]": function(arr,i) { return i < arr.length ? {x:arr[i][0],y:arr[i][1]} : {done:true,count:arr.length}; }, + "{x,y}": function(arr,i) { return i < arr.length ? arr[i] : {done:true,count:arr.length}; } }; // default polygon point iterator ... flat array g2.prototype.ply.itr = g2.prototype.ply.iterators["x,y"]; /** - * Begin subcommands. Style state is saved. Optionally apply (similarity) transformation. + * Begin subcommands. Current state is saved. + * Optionally apply (similarity) transformation or style properties. * @method * @returns {object} g2 - * @param {float} x Translation value x. - * @param {float} y Translation value y. - * @param {float} w Rotation angle (in radians). - * @param {float} scl Scale factor. + * @param {object} args Arguments object. + * @param {float} [args.x] Translation value x. + * @param {float} [args.y] Translation value y. + * @param {float} [args.w] Rotation angle (in radians). + * @param {float} [args.scl] Scale factor. + * @param {array} [args.matrix] Matrix instead of single transform arguments (SVG-structure [a,b,c,d,x,y]). + * @param {float} [args.] Style property. See 'g2.style' for details. + * @param {float} [args.] ... */ -g2.prototype.beg = function beg(x,y,w,scl) { - this.cmds.push({c:beg.cmd, a:(arguments.length ? [this,x||0,y||0,w||0,scl||1] : [this]), open:true}); +g2.prototype.beg = function beg(args) { + this.cmds.push({c:beg, a:(args ? [this,args] : [this]), open:true}); return this; }; -g2.prototype.beg.cmd = function beg_c(self,x,y,w,scl) { - self.state.save(this); - if (arguments.length > 1) - self.state.set("trf",{x:x,y:y,w:w,scl:scl},this); -}; + /** - * End subcommands. Previous style state is restored. + * End subcommands. Previous state is restored. * @method * @returns {object} g2 */ g2.prototype.end = function end() { - this.cmds.push({c:end.cmd,a:[this,this.findCmdIdx(end.isBeg)]}); + this.cmds.push({c:end,a:[this,this.findCmdIdx(end.matchBeg)]}); return this; }; -g2.prototype.end.cmd = function end_c(self,begidx) { - self.state.restore(this); -}; -g2.prototype.end.isBeg = function(cmd) { +g2.prototype.end.matchBeg = function(cmd) { if (cmd.c === g2.prototype.beg.cmd && cmd.open === true) { delete cmd.open; return true; @@ -601,54 +517,29 @@ g2.prototype.end.isBeg = function(cmd) { }; /** - * Clear canvas. + * Clear viewport. * @method * @returns {object} g2 */ g2.prototype.clr = function clr() { - this.cmds.push({c:clr.cmd}); + this.cmds.push({c:clr}); return this; }; -g2.prototype.clr.cmd = function clr_c() { - this.save(); - this.setTransform(1,0,0,1,0,0); - this.clearRect(0,0,this.canvas.width,this.canvas.height); - this.restore(); -}; // helper commands /** - * Show grid. + * Draw grid. * @method * @returns {object} g2 * @param {string} [color=#ccc] CSS grid color. - * @param {float} [size] Grid size (if g2 has a viewport object assigned, viewport's grid size is more relevant). + * @param {float} [size] Grid size. */ g2.prototype.grid = function grid(color,size) { - this.state = this.state || g2.State.create(); - this.state.gridBase = 2; + this.getState().gridBase = 2; this.state.gridExp = 1; - this.cmds.push({c:grid.cmd,a:[this,color,size]}); + this.cmds.push({c:grid,a:[this,color,size]}); return this; }; -g2.prototype.grid.cmd = function grid_c(self,color,size) { - var state = self.state, trf = state.get("trf"), - cartesian = state.cartesian, - b = this.canvas.width, h = this.canvas.height, - sz = size || g2.prototype.grid.getSize(state,trf ? trf.scl : 1), - xoff = trf.x ? trf.x%sz-sz : 0, yoff = trf.y ? trf.y%sz-sz : 0; - - this.save(); - if (cartesian) this.setTransform(1,0,0,-1,0.5,h+0.5); - else this.setTransform(1,0,0,1,0.5,0.5); - this.strokeStyle = color || "#ccc"; - this.lineWidth = 1; - this.beginPath(); - for (var x=xoff,nx=b+1; x] Style property. See 'g2.style' for details. + * @param {float} [args.] ... * @example - * var wheel = g2().style({fs:"#DDDDDD"}).cir(0,0,30).style({fs:"black"}).cir(0,0,5); - * - * var car = g2().p().m(-60,0).l(220,0).a(-Math.PI/2,160,-55) - * .l(150,-55).l(120,-100).l(20,-100).a(-Math.PI/2,-60,0).z().drw() - * .use(wheel,0,0) - * .use(wheel,170,0); - * - * g2().grid() - * .style({fs:"#4227F2",lw:3}) - * .use(car,80,120) - * .style({fs:"#F64385",lw:3}) - * .use(car,370,120,0,0.8) - * .exe(ctx); + * g2.symbol.cross = g2().lin(5,5,-5,-5).lin(5,-5,-5,5); // Define symbol. + * g2().use("cross",{x:100,y:100}) // Draw cross at position 100,100. + * .exe(ctx); // Render to context. */ -g2.prototype.use = function use(g,x,y,w,scl) { - if (typeof g === "string") // must be a member name of the 'g2.symbol' namespace +g2.prototype.use = function use(g,args) { + if (typeof g === "string") // should be a member name of the 'g2.symbol' namespace g = g2.symbol[g]; - if (g && g !== this) { // avoid self reference .. + if (g && g !== this) { // avoid self reference .. if (g.state && g.state.loading) { // referencing g2 object containing images ... - var state = this.state || (this.state = g2.State.create()); + var state = this.getState(); state.loading++; g.state.addListener("load",function() { state.loading--; }); } - this.cmds.push({c:use.cmd,a:arguments.length===1?[this,g]:[this,g,x,y,w,scl]}); + this.cmds.push({c:use,a:args?[this,g,args]:[this,g]}); } return this; }; -g2.prototype.use.cmd = function use_c(self,g,x,y,w,scl) { - var state = self.state; - state.save(this); - if (arguments.length > 2) - state.set("trf",{x:x||0,y:y||0,w:w||0,scl:scl||1},this); - self.exe(this,g); - state.restore(this); -}; -// style command .. /** - * Modifies the current graphics state. + * Apply new style properties. * @method * @returns {object} g2 - * @param {object} args Modifies graphics state by any number of properties. As an property name many of the known canvas names, as well as their short form are permitted. - * @param {string} args.fillStyle Set fill color. - * @param {string} args.fs See 'fillStyle'. - * @param {string} args.strokeStyle Set stroke color. - * @param {string} args.ls See 'strokeStyle'. - * @param {float} args.lineWidth Set line width. - * @param {float} args.lw See 'lineWidth'. - * @param {string} args.lineCap Adds a cap to the line. Possible values: `butt`, `round` and `square` - * @param {string} args.lc See 'lineCap'. - * @param {string} args.lineJoin Set the way in which lines are joined. Possible values: `round`, `bevel` and `miter` - * @param {string} args.lj See 'lineJoin'. - * @param {float} args.miterLimit Set the mitering of the corner. - * @param {float} args.ml See 'miterLimit'. - * @param {array} args.lineDash Set the line dash. - * @param {array} args.ld See 'lineDash'. - * @param {int} args.lineDashOffset Set the offset of line dash. - * @param {int} args.lo See 'lineDashOffset'. - * @param {string} args.lineMode Set line mode. In _g2_'s basic form only `normal` supported. - * @param {string} args.lm See 'lineMode'. - * @param {float} args.shadowOffsetX Set the offset of the shadow in x. - * @param {float} args.shx See 'shadowOffsetX'. - * @param {float} args.shadowOffsetY Set the offset of the shadow in y. - * @param {float} args.shy See 'shadowOffsetY'. - * @param {float} args.shadowBlur Set the level of the blurring effect. - * @param {float} args.shb See 'shadowBlur'. - * @param {string} args.shadowColor Set the shadow color. - * @param {string} args.shc See 'shadowColor'. - * @param {string} args.textAlign Set holizontal text alignment. - * @param {string} args.thal See 'textAlign'. - * @param {string} args.textBaseline Set vertical text alignment. - * @param {string} args.tval See 'textBaseline'. - * @param {string} args.fontFamily Set font family. - * @param {string} args.fof See 'fontFamily'. - * @param {float} args.fontSize Set font size. - * @param {float} args.foz See 'fontSize'. - * @param {string} args.fontColor Set font color. - * @param {string} args.foc See 'fontColor'. - * @param {string} args.fontWeight Set font weight. - * @param {string} args.fow See 'fontWeight'. - * @param {string} args.fontStyle Set font style. - * @param {string} args.fos See 'fontStyle'. - * @param {bool} args.fontSizeNonScalable Prevent text scaling. - * @param {bool} args.foznosc See 'fontSizeNonScalable'. - * @param {bool} args.lineWidthNonScalable Prevent line scaling. - * @param {bool} args.lwnosc See 'lineWidthNonScalable'. + * @param {object} args Style properties object. + * @param {string} args.fs Fill color (fillStyle). + * @param {string} args.ls Line color (lineStroke). + * @param {float} args.lw Line width. + * @param {bool} args.lwnosc Line width nonscalable. + * @param {string} args.lc Line cap [`butt`, `round`, `square`]. + * @param {string} args.lj Line join [`round`, `bevel` and `miter`]. + * @param {float} args.ml Miter limit'. + * @param {array} args.ld Line dash array. + * @param {int} args.lo Line dash offset. + * @param {float} args.shx Shadow offset x-value. + * @param {float} args.shy Shadow offset y-value. + * @param {float} args.shb Shadow blur effect value. + * @param {string} args.shc Shadow color. + * @param {string} args.thal Text horizontal alignment. + * @param {string} args.tval Text vertical alignment. + * @param {string} args.fof Font family. + * @param {float} args.foz Font size. + * @param {string} args.foc Font color. + * @param {string} args.fow Font weight ['normal','bold','lighter','bolder',100,200,...,900]. + * @param {string} args.fos Font style ['normal','italic','oblique']. + * @param {bool} args.foznosc Font size nonscalable. * @example * g = g2(); - * g.style({ fillStyle:"#58dbfa", // Set fill style. - * lw:10, // Set line width. - * ls:"#313942", // Set line style. - * lj:"round" }) // Set line join. - * .rec(10,10,300,100) - * .style({ lw:20, // Set line again. - * fs:"transparent", // Set fill style. - * shx:10, // Set shadow x-translation. - * shc:"black", // Set shadow color - * shb:10, // Set shadow blur. - * ld:[0.05,0.25] }) // Set line dash. - * .p().m(40,40).c(150,150,200,0,280,50).drw(); - * g.exe(ctx); + * g2().style({ fs:"#58dbfa", // Set fill style. + * lw:10, // Set line width. + * ls:"#313942", // Set line style. + * lj:"round" }) // Set line join. + * .rec(10,10,300,100) + * .style({ lw:20, // Set line width. + * fs:"transparent", // Set fill style. + * shx:10, // Set shadow x-translation. + * shc:"black", // Set shadow color + * shb:10, // Set shadow blur. + * ld:[1,2] }) // Set line dash. + * .p().m(40,40).c(150,150,200,0,280,50).drw() + * .exe(ctx); */ -g2.prototype.style = function style() { - if (arguments) { - if (arguments.length > 1) { // old style api with flat "name","value" pair list ... will be deprecated some time. - var styleObj = {}; - for (var i=0; i0;s--)if(t(this.cmds[s],s,this.cmds))return s;return 0},g2.prototype.p=function(){return this.cmds.push({c:CanvasRenderingContext2D.prototype.beginPath}),this},g2.prototype.m=function(t,s){return this.cmds.push({c:CanvasRenderingContext2D.prototype.moveTo,a:[t,s]}),this},g2.prototype.l=function(t,s){return this.cmds.push({c:CanvasRenderingContext2D.prototype.lineTo,a:[t,s]}),this},g2.prototype.q=function(t,s,e,i){return this.cmds.push({c:CanvasRenderingContext2D.prototype.quadraticCurveTo,a:[t,s,e,i],cp:[e,i]}),this},g2.prototype.c=function(t,s,e,i,n,r){return this.cmds.push({c:CanvasRenderingContext2D.prototype.bezierCurveTo,a:[t,s,e,i,n,r],cp:[n,r]}),this},g2.prototype.a=function(t,s,e){var i=this._curPnt();if(i){var n=s-i[0],r=e-i[1],o=Math.tan(t/2),a=n/2-r/o/2,h=r/2+n/o/2,c=Math.atan2(-h,-a);this.cmds.push({c:CanvasRenderingContext2D.prototype.arc,a:[i[0]+a,i[1]+h,Math.hypot(a,h),c,c+t,0>t],cp:[s,e]})}return this},g2.prototype.z=function(){return this.cmds.push({c:CanvasRenderingContext2D.prototype.closePath}),this},g2.prototype.stroke=function(t){return this.cmds.push(t?{c:CanvasRenderingContext2D.prototype.stroke,a:[t]}:{c:CanvasRenderingContext2D.prototype.stroke}),this},g2.prototype.fill=function(t){return this.cmds.push(t?{c:CanvasRenderingContext2D.prototype.fill,a:[t]}:{c:CanvasRenderingContext2D.prototype.fill}),this},g2.prototype.drw=function s(t){return this.cmds.push(t?{c:s.cmd,a:[t]}:{c:s.cmd}),this},g2.prototype.drw.cmd=function(t){if(t?this.fill(t):this.fill(),this.fillStyle!==g2.transparent&&this.shadowColor!==g2.transparent){var s=this.shadowColor;this.shadowColor="transparent",t?this.stroke(t):this.stroke(),this.shadowColor=s}else t?this.stroke(t):this.stroke()},g2.prototype.txt=function e(t,s,i,n){return this.cmds.push({c:e.cmd,a:[this,t,s||0,i||0,n]}),this},g2.prototype.txt.cmd=function(t,s,e,i,n){var r=t.state,o=r.get("foc");(r.cartesian||o!==g2.transparent)&&this.save(),r.cartesian&&(this.scale(1,-1),i=-i),o!==g2.transparent?(this.fillStyle=o,this.fillText(s,e,i)):this.strokeText(s,e,i),(r.cartesian||o!==g2.transparent)&&this.restore()},g2.prototype.img=function i(t,s,e,n,r,o,a,h,c){var f=new Image,l=this.state?this.state:this.state=g2.State.create();return l.loading++,f.onload=function(){l.loading--},f.onerror=function(){f.src=g2.prototype.img.broken},f.src=t,this.cmds.push({c:i.cmd,a:[this,f,s||0,e||0,n,r,o,a,h,c]}),this},g2.prototype.img.cmd=function(t,s,e,i,n,r,o,a,h,c){n=n||s&&s.width,r=r||s&&s.height,t.state.cartesian&&(this.save(),this.scale(1,-1),i=-i-r),o||a||h||c?this.drawImage(s,o,a,h,c,e,i,n,r):this.drawImage(s,e,i,n,r),t.state.cartesian&&this.restore()},g2.prototype.img.broken="data:image/gif;base64,R0lGODlhHgAeAKIAAAAAmWZmmZnM/////8zMzGZmZgAAAAAAACwAAAAAHgAeAEADimi63P5ryAmEqHfqPRWfRQF+nEeeqImum0oJQxUThGaQ7hSs95ezvB4Q+BvihBSAclk6fgKiAkE0kE6RNqwkUBtMa1OpVlI0lsbmFjrdWbMH5Tdcu6wbf7J8YM9H4y0YAE0+dHVKIV0Efm5VGiEpY1A0UVMSBYtPGl1eNZhnEBGEck6jZ6WfoKmgCQA7",g2.prototype.lin=function n(t,s,e,i){return this.cmds.push({c:n.cmd,a:[t,s,e,i]}),this},g2.prototype.lin.cmd=function(t,s,e,i){this.beginPath(),this.moveTo(t,s),this.lineTo(e,i),this.stroke()},g2.prototype.rec=function r(t,s,e,i){return this.cmds.push({c:r.cmd,a:[t,s,e,i]}),this},g2.prototype.rec.cmd=function(t,s,e,i){this.beginPath(),this.rect(t,s,e,i),g2.prototype.drw.cmd.call(this)},g2.prototype.cir=function o(t,s,e){return this.cmds.push({c:o.cmd,a:[t,s,e]}),this},g2.prototype.cir.cmd=function(t,s,e){this.beginPath(),this.arc(t,s,e,0,2*Math.PI,!0),g2.prototype.drw.cmd.call(this)},g2.prototype.arc=function a(t,s,e,i,n){return this.cmds.push({c:a.cmd,a:[t,s,e,i,n]}),this},g2.prototype.arc.cmd=function(t,s,e,i,n){this.beginPath(),this.arc(t,s,e,i,i+n,0>n),this.stroke()},g2.prototype.ply=function h(t,s,e){var i=e&&(e.itr||e.fmt&&g2.prototype.ply.iterators[e.fmt])||!1;return this.cmds.push({c:h.cmd,a:[t,s,i]}),this},g2.prototype.ply.cmd=function(t,s,e){var i,n=0;if(e=e||g2.prototype.ply.itr,i=e(t,n++),!i.done){for(this.beginPath(),this.moveTo(i.x,i.y);!(i=e(t,n++)).done;)this.lineTo(i.x,i.y);s&&this.closePath()}return g2.prototype.drw.cmd.call(this),n-1},g2.prototype.ply.iterators={"x,y":function(t,s){return s1&&t.state.set("trf",{x:s,y:e,w:i,scl:n},this)},g2.prototype.end=function f(){return this.cmds.push({c:f.cmd,a:[this,this.findCmdIdx(f.isBeg)]}),this},g2.prototype.end.cmd=function(t,s){t.state.restore(this)},g2.prototype.end.isBeg=function(t){return t.c===g2.prototype.beg.cmd&&t.open===!0?(delete t.open,!0):!1},g2.prototype.clr=function l(){return this.cmds.push({c:l.cmd}),this},g2.prototype.clr.cmd=function(){this.save(),this.setTransform(1,0,0,1,0,0),this.clearRect(0,0,this.canvas.width,this.canvas.height),this.restore()},g2.prototype.grid=function g(t,s){return this.state=this.state||g2.State.create(),this.state.gridBase=2,this.state.gridExp=1,this.cmds.push({c:g.cmd,a:[this,t,s]}),this},g2.prototype.grid.cmd=function(t,s,e){var i=t.state,n=i.get("trf"),r=i.cartesian,o=this.canvas.width,a=this.canvas.height,h=e||g2.prototype.grid.getSize(i,n?n.scl:1),c=n.x?n.x%h-h:0,f=n.y?n.y%h-h:0;this.save(),r?this.setTransform(1,0,0,-1,.5,a+.5):this.setTransform(1,0,0,1,.5,.5),this.strokeStyle=s||"#ccc",this.lineWidth=1,this.beginPath();for(var l=c,g=o+1;g>l;l+=h)this.moveTo(l,0),this.lineTo(l,a);for(var p=f,u=a+1;u>p;p+=h)this.moveTo(0,p),this.lineTo(o,p);this.stroke(),this.restore()},g2.prototype.grid.getSize=function(t,s){for(var e,i=t.gridBase||2,n=t.gridExp||1;(e=s*i*Math.pow(10,n))<14||e>35;)14>e?1==i?i=2:2==i?i=5:5==i&&(i=1,n++):1==i?(i=5,n--):2==i?i=1:5==i&&(i=2);return t.gridBase=i,t.gridExp=n,e},g2.prototype.use=function p(t,s,e,i,n){if("string"==typeof t&&(t=g2.symbol[t]),t&&t!==this){if(t.state&&t.state.loading){var r=this.state||(this.state=g2.State.create());r.loading++,t.state.addListener("load",function(){r.loading--})}this.cmds.push({c:p.cmd,a:1===arguments.length?[this,t]:[this,t,s,e,i,n]})}return this},g2.prototype.use.cmd=function(t,s,e,i,n,r){var o=t.state;o.save(this),arguments.length>2&&o.set("trf",{x:e||0,y:i||0,w:n||0,scl:r||1},this),t.exe(this,s),o.restore(this)},g2.prototype.style=function u(){if(arguments)if(arguments.length>1){for(var t={},s=0;sr;r++)(n=e[r]).c.apply(t,n.a);s?s.state=i:t.fillStyle="#000000"}return this},g2.prototype.cpy=function(t){return t!==this&&t.cmds.forEach(function(t,s){s&&this.cmds.push(t)},this),this},g2.prototype.del=function(){return this.cmds.length=1,this},g2.prototype.dump=function(t){function s(t){for(var e,i,n,r,o,a=[],h=0,c=t.cmds.length;c>h;h++){o=[],i=t.cmds[h],n=i.a;for(var f=0,l=n&&n.length||0;l>f;f++)"object"==typeof n[f]&&n[f]instanceof g2?n[f]!==t&&o.push(s(n[f])):void 0!==n[f]&&o.push(n[f]);r=/\W*function\s+([\w\$]+)\(/.exec(i.c.toString())[1],o.length?(e={},e[r]=o):e=r,a.push(e)}return a}return JSON.stringify(s(this),void 0,t)},g2.filterProperty=function(t){return{get:function(s){return s[t]},set:function(s,e){e[t]=s}}},g2.State={create:function(){var t=Object.create(this.prototype);return t.constructor.apply(t,arguments),t},prototype:{constructor:function(){this.stack=[{}],this.trf0={x:0,y:0,w:0,scl:1},this._loading=0,this.on={load:[]}},get loading(){return this._loading},set loading(t){0===(this._loading=t)&&this.invokeListeners("load")},hasListeners:function(t){return this.on[t]&&this.on[t].length},hasListener:function(t,s){if(this.on[t])for(var e=0;ei;i++)e[i]/=s;return e}return[]},set:function(t,s){if(s){for(var e=this.get("lwnosc")?this.currentScale:1,i=this.get("lw",s),n=[],r=0,o=t.length;o>r;r++)n.push(t[r]/e*i);s.setLineDash(n)}}},lo:{get:function(t){return t.lineDashOffset},set:function(t,s){s.lineDashOffset=t}},ml:g2.filterProperty("miterLimit"),shx:g2.filterProperty("shadowOffsetX"),shy:g2.filterProperty("shadowOffsetY"),shb:g2.filterProperty("shadowBlur"),shc:{get:function(t){return t.shadowColor},set:function(t,s){s.shadowColor="transparent"===t?g2.transparent:t}},thal:g2.filterProperty("textAlign"),tval:g2.filterProperty("textBaseline"),fos:{set:function(t,s){this.set("fos",t),s.font=this.font}},fow:{set:function(t,s){this.set("fow",t),s.font=this.font}},foz:{get:function(t){var s=this.get("foznosc")?this.currentScale:1;return this.get("foz")*s},set:function(t,s){var e=this.get("foznosc")?this.currentScale:1;this.set("foz",t/e),s.font=this.font}},fof:{set:function(t,s){this.set("fof",t),s&&(s.font=this.font)}},lwnosc:{set:function(t,s){if(t!==this.get("lwnosc")){var e=this.get("lw",s);this.set("lwnosc",t),this.set("lw",e,s)}}},foznosc:{set:function(t,s){if(t!==this.get("foznosc")){var e=this.get("foz");this.set("foznosc",t),this.set("foz",e,s)}}},trf:{set:function(t,s){var e,i,n,r=t.scl!==this.get("trf").scl;r&&(e=this.get("lw",s),i=this.get("ld",s),n=this.get("foz",s)),this.transform(t,s),r&&(this.set("lw",e,s),this.set("ld",i,s),this.set("foz",n,s))}}}},g2.symbol=Object.create(null); \ No newline at end of file +/** + * g2 (c) 2013-15 Stefan Goessner + * @license + * MIT License + */ +Math.hypot=Math.hypot||function(t,e){return Math.sqrt(t*t+e*e)};function g2(){if(this instanceof g2)return this.constructor.apply(this,arguments);else return g2.apply(Object.create(g2.prototype),arguments)}g2.version="2.0.0";g2.transparent="rgba(0, 0, 0, 0)";g2.exeStack=0;g2.proxy=Object.create(null);g2.ifc=Object.create(null);g2.ifcof=function(t){for(var e in g2.ifc)if(g2.ifc[e](t))return e;return false};g2.prototype.constructor=function t(e){if(e){this.state=g2.State.create(this);if(e.zoom)this.zoom(e.zoom.scl,e.zoom.x,e.zoom.y);if(e.pan)this.pan(e.pan.dx,e.pan.dy);if(e.trf)this.trf(e.trf.x,e.trf.y,e.trf.scl);if(e.cartesian)this.state.cartesian=e.cartesian}this.cmds=[];return this};g2.prototype.cartesian=function e(){this.getState().cartesian=true;return this};g2.prototype.pan=function s(t,e){this.getState().trf0.x+=t;this.state.trf0.y+=e;return this};g2.prototype.zoom=function i(t,e,s){this.getState().trf0.x=(1-t)*(e||0)+t*this.state.trf0.x;this.state.trf0.y=(1-t)*(s||0)+t*this.state.trf0.y;this.state.trf0.scl*=t;return this};g2.prototype.trf=function n(t,e,s){this.getState().trf0.x=t;this.state.trf0.y=e;this.state.trf0.scl=s;return this};g2.prototype._curPnt=function(){var t=this.cmds.length&&this.cmds[this.cmds.length-1]||false;return t&&(t.cp||t.a)};g2.prototype.findCmdIdx=function(t){for(var e=this.cmds.length-1;e>=0;e--)if(t(this.cmds[e],e,this.cmds))return e;return 0};g2.prototype.p=function r(){this.cmds.push({c:r});return this};g2.prototype.m=function o(t,e){this.cmds.push({c:o,a:[t,e]});return this};g2.prototype.l=function a(t,e){this.cmds.push({c:a,a:[t,e]});return this};g2.prototype.q=function c(t,e,s,i){this.cmds.push({c:c,a:[t,e,s,i],cp:[s,i]});return this};g2.prototype.c=function h(t,e,s,i,n,r){this.cmds.push({c:h,a:[t,e,s,i,n,r],cp:[n,r]});return this};g2.prototype.a=function f(t,e,s){var i=this._curPnt();if(i){var n=e-i[0],r=s-i[1],o=Math.tan(t/2),a=n/2-r/o/2,c=r/2+n/o/2,h=Math.atan2(-c,-a);this.cmds.push({c:f,a:[i[0]+a,i[1]+c,Math.hypot(a,c),h,h+t,t<0],cp:[e,s]})}return this};g2.prototype.z=function l(){this.cmds.push({c:l});return this};g2.prototype.stroke=function p(t){this.cmds.push(t?{c:p,a:[t]}:{c:p});return this};g2.prototype.fill=function u(t){this.cmds.push(t?{c:u,a:[t]}:{c:u});return this};g2.prototype.drw=function g(t){this.cmds.push(t?{c:g,a:[t]}:{c:g});return this};g2.prototype.txt=function d(t,e,s,i,n){this.cmds.push({c:d,a:n?[this,t,e||0,s||0,i||0,n]:[this,t,e||0,s||0,i||0]});return this};g2.prototype.img=function y(t,e,s,i,n,r,o,a,c){var h=new Image,f=this.getState();f.loading++;h.onload=function l(){f.loading--};h.onerror=function(){h.src=g2.prototype.img.broken};h.src=t;this.cmds.push({c:y,a:[this,h,e||0,s||0,i,n,r,o,a,c]});return this};g2.prototype.img.broken="data:image/gif;base64,R0lGODlhHgAeAKIAAAAAmWZmmZnM/////8zMzGZmZgAAAAAAACwAAAAAHgAeAEADimi63P5ryAmEqHfqPRWfRQF+nEeeqImum0oJQxUThGaQ7hSs95ezvB4Q+BvihBSAclk6fgKiAkE0kE6RNqwkUBtMa1OpVlI0lsbmFjrdWbMH5Tdcu6wbf7J8YM9H4y0YAE0+dHVKIV0Efm5VGiEpY1A0UVMSBYtPGl1eNZhnEBGEck6jZ6WfoKmgCQA7";g2.prototype.lin=function m(t,e,s,i){this.cmds.push({c:m,a:[t,e,s,i]});return this};g2.prototype.rec=function v(t,e,s,i){this.cmds.push({c:v,a:[t,e,s,i]});return this};g2.prototype.cir=function x(t,e,s){this.cmds.push({c:x,a:[t,e,s]});return this};g2.prototype.arc=function w(t,e,s,i,n){this.cmds.push({c:w,a:[t,e,s,i,n]});return this};g2.prototype.ply=function S(t,e,s){var i=s&&(s.itr||s.fmt&&g2.prototype.ply.iterators[s.fmt])||false;this.cmds.push({c:S,a:[t,e,i]});return this};g2.prototype.ply.iterators={"x,y":function(t,e){return e35){if(n<14){if(s==1)s=2;else if(s==2)s=5;else if(s==5){s=1;i++}}else{if(s==1){s=5;i--}else if(s==2)s=1;else if(s==5)s=2}}t.gridBase=s;t.gridExp=i;return n};g2.prototype.use=function z(t,e){if(typeof t==="string")t=g2.symbol[t];if(t&&t!==this){if(t.state&&t.state.loading){var s=this.getState();s.loading++;t.state.addListener("load",function(){s.loading--})}this.cmds.push({c:z,a:e?[this,t,e]:[this,t]})}return this};g2.prototype.style=function T(t){this.cmds.push({c:T,a:[this,t]});return this};g2.prototype.exe=function D(t,e){var s=g2.ifcof(t);if(s){var i=(e||this).cmds;if(this.state&&this.state.loading){requestAnimationFrame(D.bind(this,t,e))}else if(t&&i){var n=e&&e.state,r=g2.proxy[s](t);D[s].beg.call(r,this);if(e)e.state=this.state;for(var o=0,a=i.length,c;o ./api/readme.md", + "gzip": "7z -tgzip a g2.min.js.gz g2.min.js" + }, + "author": "Stefan Goessner ", + "repository": { + "type": "git", + "url": "https://github.com/goessner/g2.git" + }, + "license": "MIT", + "devDependencies": { + "concat": "^1.0.0", + "jsdoc-to-markdown": "^1.3.2", + "uglifyjs": "^2.4.10" + } +} diff --git a/src/g2.c2d.js b/src/g2.c2d.js new file mode 100644 index 0000000..7837d4c --- /dev/null +++ b/src/g2.c2d.js @@ -0,0 +1,344 @@ +/** + * @fileoverview g2.c2d.js + * @author Stefan Goessner (c) 2013-14 + * @license MIT License + */ +/* jshint -W014 */ + +g2.ifc.c2d = function(ctx) { return ctx instanceof CanvasRenderingContext2D; } +g2.proxy.c2d = function(ctx) { return ctx; } + +g2.prototype.exe.c2d = { + beg: function(self) { + if (g2.exeStack++ === 0) { // outermost g2 ... + var state = self.state || (self.state = g2.State.create(self)), t = state.trf0; + this.setTransform(t.scl,0,0,state.cartesian?-t.scl:t.scl,t.x+0.5,(state.cartesian?this.canvas.height-t.y:t.y)+0.5); + this.lineWidth = 1; + this.strokeStyle = "#000"; + this.setLineDash([]); + this.font = g2.State.c2d.getAttr.call(this,"font",state); + this.fillStyle = g2.transparent; + state.stack = [{}]; + } + }, + end: function(self) { + if (--g2.exeStack === 0) + this.fillStyle = "#000000"; + } +}; + +/** + * canvas 2d interface + */ +g2.prototype.p.c2d = CanvasRenderingContext2D.prototype.beginPath; + +g2.prototype.m.c2d = CanvasRenderingContext2D.prototype.moveTo; + +g2.prototype.l.c2d = CanvasRenderingContext2D.prototype.lineTo; + +g2.prototype.q.c2d = CanvasRenderingContext2D.prototype.quadraticCurveTo; + +g2.prototype.c.c2d = CanvasRenderingContext2D.prototype.bezierCurveTo; + +g2.prototype.a.c2d = CanvasRenderingContext2D.prototype.arc; + +g2.prototype.z.c2d = CanvasRenderingContext2D.prototype.closePath; + +g2.prototype.stroke.c2d = function stroke_c2d(d) { + if (d && typeof Path2D !== "undefined") + this.stroke(new Path2D(d)); + else + this.stroke(); +}; + +g2.prototype.fill.c2d = function fill_c2d(d) { + if (d && typeof Path2D !== "undefined") + this.fill(new Path2D(d)); + else + this.fill(); +}; + +g2.prototype.drw.c2d = function drw_c2d(d) { + var p2d = d && typeof Path2D !== "undefined" ? new Path2D(d) : false; + p2d ? this.fill(p2d) : this.fill(); + if (this.fillStyle !== g2.transparent && this.shadowColor !== g2.transparent) { + var tmp = this.shadowColor; // avoid stroke shadow when filled ... + this.shadowColor = "transparent"; // "rgba(0, 0, 0, 0)" + p2d ? this.stroke(p2d) : this.stroke(); + this.shadowColor = tmp; + } + else + p2d ? this.stroke(p2d) : this.stroke(); +}; + +g2.prototype.txt.c2d = function txt_c2d(self,s,x,y,w,args) { + var state = self.state, foc, saved; + + if (args) g2.prototype.style.c2d.call(this,self,args); + foc = state.getAttr("foc"); + if (saved = (state.cartesian || w || foc !== g2.transparent)) + this.save(); + if (w) { + var sw = Math.sin(w), cw = Math.cos(w); + this.transform(cw,sw,-sw,cw,(1-cw)*x+sw*y,-sw*x+(1-cw)*y); + } + if (state.cartesian) { this.scale(1,-1); y = -y; } + + if (foc !== g2.transparent) { + this.fillStyle = foc; + this.fillText(s,x,y); + } + else { + this.fillText(s,x,y); + this.strokeText(s,x,y); + } + + if (saved) this.restore(); +}; + +g2.prototype.img.c2d = function img_c2d(self,img,x,y,b,h,xoff,yoff,dx,dy) { + b = b || img && img.width; + h = h || img && img.height; + if (self.state.cartesian) { this.save(); this.scale(1,-1); y = -y-h; } + if (xoff || yoff || dx || dy) // non-zero anyone .. ? + this.drawImage(img,xoff,yoff,dx,dy,x,y,b,h); + else + this.drawImage(img,x,y,b,h); + if (self.state.cartesian) { this.restore(); } +}; + +g2.prototype.lin.c2d = function lin_c2d(x1,y1,x2,y2) { + this.beginPath(); + this.moveTo(x1,y1); + this.lineTo(x2,y2); + this.stroke(); +}; + +g2.prototype.rec.c2d = function rec_c2d(x,y,b,h) { + this.beginPath(); + this.rect(x,y,b,h); + g2.prototype.drw.c2d.call(this); +}; + +g2.prototype.cir.c2d = function cir_c2d(x,y,r) { + this.beginPath(); + this.arc(x,y,r,0,Math.PI*2,true); + g2.prototype.drw.c2d.call(this); +}; + +g2.prototype.arc.c2d = function arc_c2d(x,y,r,w,dw) { + this.beginPath(); + this.arc(x,y,r,w,w+dw,dw<0); + g2.prototype.drw.c2d.call(this); +}; + +g2.prototype.ply.c2d = function ply_c2d(parr,mode,itr) { + var p, i = 0, split = mode === "split"; + itr = itr || g2.prototype.ply.itr; + p = itr(parr,i++); + if (!p.done) { // draw polygon .. + this.beginPath(); + this.moveTo(p.x,p.y); + while (!(p = itr(parr,i++)).done) { + if (split && i%2) this.moveTo(p.x,p.y); + else this.lineTo(p.x,p.y); + } + if (mode && !split) // closed then .. + this.closePath(); + } + g2.prototype.drw.c2d.call(this); + return i-1; // number of points .. +}; + +g2.prototype.beg.c2d = function beg_c2d(self,args) { + var state = self.state; + state.save(); + this.save(); + if (args) { + if ("x" in args || "y" in args || "w" in args || "scl" in args) { + state.transform(args); + g2.State.c2d.set.trf.call(this,args,state); + } + else if ("matrix" in args) + this.transform.apply(this,args.matrix); + g2.prototype.style.c2d.call(this,self,args); + } +}; + +g2.prototype.end.c2d = function end_c2d(self,begidx) { + this.restore(); + self.state.restore(); +}; + +g2.prototype.clr.c2d = function clr_c2d() { + this.save(); + this.setTransform(1,0,0,1,0,0); + this.clearRect(0,0,this.canvas.width,this.canvas.height); + this.restore(); +}; + +g2.prototype.grid.c2d = function grid_c2d(self,color,size) { + var state = self.state, trf = state.getAttr("trf"), // no ctx required ... + cartesian = state.cartesian, + b = this.canvas.width, h = this.canvas.height, + sz = size || g2.prototype.grid.getSize(state,trf ? trf.scl : 1), + xoff = trf.x ? trf.x%sz-sz : 0, yoff = trf.y ? trf.y%sz-sz : 0; + + this.save(); + if (cartesian) this.setTransform(1,0,0,-1,0.5,h-0.5); + else this.setTransform(1,0,0,1,0.5,0.5); + this.strokeStyle = color || "#ccc"; + this.lineWidth = 1; + this.beginPath(); + for (var x=xoff,nx=b+1; x, + * pan: { dx:, dy: }, + * zoom: { x:, y: }, scl: }, + * trf: { x:, y: }, scl: } + * } + * @example + * // How to use g2() + * var ctx = document.getElementById("c").getContext("2d"); // Get your canvas context. + * g2() // The first call of g2() creates a g2 object. + * .lin(50,50,100,100) // Append commands. + * .lin(100,100,200,50) + * .exe(ctx); // Execute commands. + */ +/** + * Create a queue of 2D graphics commands. + * @param {object} [args] Arguments object with one or more members. + * @param {bool} [args.cartesian] Use cartesian coordinate system. + * @param {object} [args.pan] + * @param {float} [args.pan.dx] Pan about dx in x direction. + * @param {float} [args.pan.dy] Pan about dy in y direction. + * @param {object} [args.zoom] + * @param {float} [args.zoom.x = 0] Zoom to point with x coordinate. + * @param {float} [args.zoom.y = 0] Zoom to point with x coordinate. + * @param {float} [args.zoom.scl] Zoom by factor 'scl'. + * @param {object} [args.trf] + * @param {float} [args.trf.x] Translate to x. + * @param {float} [args.trf.y] Translate to y. + * @param {float} [args.trf.scl] Scale by factor 'scl'. + * @example + * // How to use g2() + * var ctx = document.getElementById("c").getContext("2d"); + * g2() // Create 'g2' instance. + * .lin(50,50,100,100) // Append 'line' command. + * .lin(100,100,200,50) // Append more command ... + * .exe(ctx); // Execute commands addressing canvas context. + */ +function g2() { + if (this instanceof g2) + return this.constructor.apply(this,arguments); + else + return g2.apply(Object.create(g2.prototype),arguments); +} + +// === g2 statics ==== +/** + * Current version. + * Using semantic versioning 'http://semver.org/'. + * @type {string} + * @const + */ +g2.version = "2.0.0"; +g2.transparent = "rgba(0, 0, 0, 0)"; +g2.exeStack = 0; +g2.proxy = Object.create(null); +g2.ifc = Object.create(null); +g2.ifcof = function(ctx) { + for (var ifc in g2.ifc) + if (g2.ifc[ifc](ctx)) + return ifc; + return false; +} + +/** + * Constructor. + * @method + * @returns {object} g2 + * @private + */ +g2.prototype.constructor = function constructor(args) { + if (args) { // only root g2's should have arguments ... + this.state = g2.State.create(this); + if (args.zoom) this.zoom(args.zoom.scl,args.zoom.x,args.zoom.y); + if (args.pan) this.pan(args.pan.dx,args.pan.dy); + if (args.trf) this.trf(args.trf.x,args.trf.y,args.trf.scl); + if (args.cartesian) this.state.cartesian = args.cartesian; // static property ... + } + this.cmds = []; +// this.cmds = [{c:constructor, a:[this]}]; + return this; +}; + +/** + * Set the viewports cartesian mode. + * This is an explicite alternative to setting the cartesian flag in the + * constructors arguments object. + * @method + * @returns {object} g2 + */ +g2.prototype.cartesian = function cartesian() { + this.getState().cartesian = true; + return this; +}; + +/** + * Pan the viewport about a vector. + * This is an explicite alternative to setting the pan values in the + * constructors arguments object. + * @method + * @returns {object} g2 + * @param {float} dx x-value to pan. + * @param {float} dy y-value to pan. + */ +g2.prototype.pan = function pan(dx,dy) { + this.getState().trf0.x += dx; + this.state.trf0.y += dy; + return this; +}; + +/** + * Zoom the viewport by a scaling factor with respect to given center. + * This is an explicite alternative to setting the zoom values in the + * constructors arguments object. + * @method + * @returns {object} g2 + * @param {float} scl Scaling factor. + * @param {float} [x=0] x-component of zoom center. + * @param {float} [y=0] y-component of zoom center. + */ +g2.prototype.zoom = function zoom(scl,x,y) { + this.getState().trf0.x = (1-scl)*(x||0) + scl*this.state.trf0.x; + this.state.trf0.y = (1-scl)*(y||0) + scl*this.state.trf0.y; + this.state.trf0.scl *= scl; + return this; +}; + +/** + * Set the viewports transformation. + * This is an explicite alternative to setting the zoom values in the + * constructors arguments object. + * @method + * @returns {object} g2 + * @param {float} x x-translation. + * @param {float} y y-translation. + * @param {float} scl Scaling factor. + */ +g2.prototype.trf = function trf(x,y,scl) { + this.getState().trf0.x = x; + this.state.trf0.y = y; + this.state.trf0.scl = scl; + return this; +}; + +// Path commands + +// internal helper methods .. +// ========================== +// get current path point from previous command object +g2.prototype._curPnt = function() { + var lastcmd = this.cmds.length && this.cmds[this.cmds.length-1] || false; + return lastcmd && (lastcmd.cp || lastcmd.a); +}; +// get index of command resolving 'callbk' to 'true'. +// see 'Array.prototype.findIndex' +g2.prototype.findCmdIdx = function(callbk) { + for (var i = this.cmds.length-1; i >= 0; i--) + if (callbk(this.cmds[i],i,this.cmds)) + return i; + return 0; // command with index '0' signals 'failing' ... +}; + +/** + * Begin new path. + * @method + * @returns {object} g2 + */ +g2.prototype.p = function p() { + this.cmds.push({c:p}); + return this; +}; + +/** + * Move to point. + * @method + * @returns {object} g2 + * @param {float} x Move to x coordinate + * @param {float} y Move to y coordinate + */ +g2.prototype.m = function m(x,y) { + this.cmds.push({c:m,a:[x,y]}); + return this; +}; + +/** + * Create line segment to point. + * @method + * @returns {object} g2 + * @param {float} x x coordinate of target point. + * @param {float} y y coordinate of target point. + * @example + * g2().p() // Begin path. + * .m(0,50) // Move to point. + * .l(300,0) // Line segment to point. + * .l(400,100) // ... + * .stroke() // Stroke path. + * .exe(ctx); // Render to context. + */ +g2.prototype.l = function l(x,y) { + this.cmds.push({c:l,a:[x,y]}); + return this; +}; + +/** + * Create quadratic bezier curve segment to point. + * ![Example](img/quadratic.png "Example") + * @method + * @returns {object} g2 + * @param {float} x1 x coordinate of control point. + * @param {float} y1 y coordinate of control point. + * @param {float} x x coordinate of target point. + * @param {float} y y coordinate of target point. + * @example + * g2().p() // Begin path. + * .m(0,0) // Move to point. + * .q(200,200,400,0) // Quadratic bezier curve segment. + * .stroke() // Stroke path. + * .exe(ctx); // Render to context. + */ +g2.prototype.q = function q(x1,y1,x,y) { + this.cmds.push({c:q,a:[x1,y1,x,y],cp:[x,y]}); + return this; +}; + +/** + * Create cubic bezier curve to point. + * ![Example](img/curve.png "Example") + * @method + * @returns {object} g2 + * @param {float} x1 x coordinate of first control point. + * @param {float} y1 y coordinate of first control point. + * @param {float} x2 x coordinate of second control point. + * @param {float} y2 y coordinate of second control point. + * @param {float} x x coordinate of target point. + * @param {float} y y coordinate of target point. + * @example + * g2().p() // Begin path. + * .m(0,100) // Move to point. + * .c(100,200,200,0,400,100) // Create cubic bezier curve. + * .stroke() // Stroke path. + * .exe(ctx); // Render to canvas context. + */ +g2.prototype.c = function c(x1,y1,x2,y2,x,y) { + this.cmds.push({c:c,a:[x1,y1,x2,y2,x,y],cp:[x,y]}); + return this; +}; + +/** + * Draw arc with angular range to target point. + * ![Example](img/a.png "Example") + * @method + * @returns {object} g2 + * @param {float} dw Angular range in radians. + * @param {float} x x coordinate of target point. + * @param {float} y y coordinate of target point. + + * @example + * var g = g2(); // Create g2 object. + * g2().p() // Begin path. + * .m(50,50) // Move to point. + * .a(2,300,100) // Create cubic bezier curve. + * .stroke() // Stroke path. + * .exe(ctx); // Render to canvas context. + */ +g2.prototype.a = function a(dw,x,y) { + var p1 = this._curPnt(); + if (p1) { + var dx = x-p1[0], dy = y-p1[1], tw2 = Math.tan(dw/2), + rx = dx/2 - dy/tw2/2, ry = dy/2 + dx/tw2/2, + w = Math.atan2(-ry,-rx); + this.cmds.push({c:a,a:[p1[0]+rx,p1[1]+ry,Math.hypot(rx,ry),w,w+dw,dw<0],cp:[x,y]}); + } + return this; +}; + +/** + * Close current path by straigth line. + * @method + * @returns {object} g2 + */ +g2.prototype.z = function z() { + this.cmds.push({c:z}); + return this; +}; + +// stroke, fill, draw +/** + * Stroke the current path or path object. + * @method + * @param {string} [d = undefined] SVG path definition string. + * @returns {object} g2 + */ +g2.prototype.stroke = function stroke(d) { + this.cmds.push(d ? {c:stroke,a:[d]} : {c:stroke}); + return this; +}; + +/** + * Fill the current path or path object. + * @method + * @param {string} [d = undefined] SVG path definition string. + * @returns {object} g2 + */ +g2.prototype.fill = function fill(d) { + this.cmds.push(d ? {c:fill,a:[d]} : {c:fill}); + return this; +}; + +/** + * Shortcut for stroke and fill the current path or path object. + * In case of shadow, only the path interior creates shadow, not also the path contour. + * @method + * @param {string} [d = undefined] SVG path definition string. + * @returns {object} g2 + */ +g2.prototype.drw = function drw(d) { + this.cmds.push(d ? {c:drw,a:[d]} : {c:drw}); + return this; +}; + + +// Graphics elements +/** + * Draw text string at anchor point. + * @method + * @returns {object} g2 + * @param {string} s Drawing text + * @param {float} [x=0] x coordinate of text anchor position. + * @param {float} [y=0] y coordinate of text anchor position. + * @param {float} [w=0] w Rotation angle about anchor point with respect to positive x-axis. + * @param {object} [args=undefined] args Object with styling and/or transform values. + */ +g2.prototype.txt = function txt(s,x,y,w,args) { + this.cmds.push({c:txt,a:args?[this,s,x||0,y||0,w||0,args]:[this,s,x||0,y||0,w||0]}); + return this; +}; + +/** + * Draw image. The command queue will not be executed until all images have been completely loaded. + * This also applies to images of reused g2 objects. If an image can not be loaded, it will be replaced by a broken-image symbol. + * @method + * @returns {object} g2 + * @param {string} uri Image uri or data:url. On error a broken image symboldwill be used. + * @param {float} [x=0] X-coordinate of image (upper left). + * @param {float} [y=0] Y-coordinate of image (upper left). + * @param {float} [b = undefined] Width. + * @param {float} [h = undefined] Height. + * @param {float} [xoff = undefined] X-offset. + * @param {float} [yoff = undefined] Y-offset. + * @param {float} [dx = undefined] Region x. + * @param {float} [dy = undefined] Region y. + */ +g2.prototype.img = function img(uri,x,y,b,h,xoff,yoff,dx,dy) { + var image = new Image(), state = this.getState(); + state.loading++; + image.onload = function load() { state.loading--; }; + image.onerror = function() { image.src = g2.prototype.img.broken; }; + image.src = uri; + this.cmds.push({c:img,a:[this,image,x||0,y||0,b,h,xoff,yoff,dx,dy]}); + return this; +}; +g2.prototype.img.broken = "data:image/gif;base64,R0lGODlhHgAeAKIAAAAAmWZmmZnM/////8zMzGZmZgAAAAAAACwAAAAAHgAeAEADimi63P5ryAmEqHfqPRWfRQF+nEeeqImum0oJQxUThGaQ7hSs95ezvB4Q+BvihBSAclk6fgKiAkE0kE6RNqwkUBtMa1OpVlI0lsbmFjrdWbMH5Tdcu6wbf7J8YM9H4y0YAE0+dHVKIV0Efm5VGiEpY1A0UVMSBYtPGl1eNZhnEBGEck6jZ6WfoKmgCQA7"; +/** + * Draw line by start point and end point. + * @method + * @returns {object} g2 + * @param {float} x1 Start x coordinate. + * @param {float} y1 Start y coordinate. + * @param {float} x2 End x coordinate. + * @param {float} y2 End y coordinate. + * @example + * g2().lin(10,10,190,10) // Draw line. + * .exe(ctx); // Render to context. + */ +g2.prototype.lin = function lin(x1,y1,x2,y2) { + this.cmds.push({c:lin,a:[x1,y1,x2,y2]}); + return this; +}; + +/** + * Draw rectangle by anchor point and dimensions. + * @method + * @returns {object} g2 + * @param {float} x x-value upper left corner. + * @param {float} y y-value upper left corner. + * @param {float} b Width. + * @param {float} h Height. + * @example + * g2().rec(100,80,40,30) // Draw rectangle. + * .exe(ctx); // Render to canvas context. + */ +g2.prototype.rec = function rec(x,y,b,h) { + this.cmds.push({c:rec,a:[x,y,b,h]}); + return this; +}; + +/** + * Draw circle by center and radius. + * @method + * @returns {object} g2 + * @param {float} x x-value center. + * @param {float} y y-value center. + * @param {float} r Radius. + * @example + * g2().cir(100,80,20) // Draw circle. + * .exe(ctx); // Render to context. + */ +g2.prototype.cir = function cir(x,y,r) { + this.cmds.push({c:cir,a:[x,y,r]}); + return this; +}; + +/** + * Draw arc by center point, radius, start angle and angular range. + * ![Example](../img/arc.png "Example") + * @method + * @returns {object} g2 + * @param {float} x x-value center. + * @param {float} y y-value center. + * @param {float} r Radius. + * @param {float} [w=0] Start angle (in radian). + * @param {float} [dw=2*pi] Angular range in Radians. + * @example + * g2().arc(300,400,390,-Math.PI/4,-Math.PI/2) + * .exe(ctx); + */ +g2.prototype.arc = function arc(x,y,r,w,dw) { + this.cmds.push({c:arc,a:[x,y,r,w,dw]}); + return this; +}; + +/** + * Draw polygon by points. + * Using iterator function for getting points from array by index. + * It must return matching point object {x:,y:} or object {done:true}. + * Default iterator expects sequence of x/y-coordinates as a flat array ([x0,y0,x1,y1,...]) + * @method + * @returns {object} this + * @param {array} parr Array of points + * @param {bool | 'split'} mode = false: non-closed polygon + * mode = 'split': intermittend lines + * mode = not falsy: closed polygon + * @param {object} opts Options object. + * @param {string} [opts.fmt="x,y"] Points array format: + * "x,y" Flat Array of x,y-values [default] + * |"[x,y]" Array of [x,y] arrays + * |"{x,y}" Array of {x:} objects + * @param {function} [opts.itr=undefined] Iterator function getting array and point index as parameters: `function(arr,i)`. + * If provided it has priority over 'fmt'. + * @example + * g2().ply([100,50,120,60,80,70]), + * .ply([150,60],[170,70],[130,80]],true,{fmt:"[x,y]"}), + * .ply({x:160,y:70},{x:180,y:80},{x:140,y:90}],true,{fmt:"{x,y}"}), + * .exe(ctx); + */ +g2.prototype.ply = function ply(parr,mode,opts) { + var itr = opts && (opts.itr || opts.fmt && g2.prototype.ply.iterators[opts.fmt]) || false; + this.cmds.push({c:ply,a:[parr,mode,itr]}); + return this; +}; + +// predefined polygon point iterators +g2.prototype.ply.iterators = { + "x,y": function(arr,i) { return i < arr.length/2 ? {x:arr[2*i],y:arr[2*i+1]} : {done:true,count:arr.length/2}; }, + "[x,y]": function(arr,i) { return i < arr.length ? {x:arr[i][0],y:arr[i][1]} : {done:true,count:arr.length}; }, + "{x,y}": function(arr,i) { return i < arr.length ? arr[i] : {done:true,count:arr.length}; } +}; +// default polygon point iterator ... flat array +g2.prototype.ply.itr = g2.prototype.ply.iterators["x,y"]; + +/** + * Begin subcommands. Current state is saved. + * Optionally apply (similarity) transformation or style properties. + * @method + * @returns {object} g2 + * @param {object} args Arguments object. + * @param {float} [args.x] Translation value x. + * @param {float} [args.y] Translation value y. + * @param {float} [args.w] Rotation angle (in radians). + * @param {float} [args.scl] Scale factor. + * @param {array} [args.matrix] Matrix instead of single transform arguments (SVG-structure [a,b,c,d,x,y]). + * @param {float} [args.] Style property. See 'g2.style' for details. + * @param {float} [args.] ... + */ +g2.prototype.beg = function beg(args) { + this.cmds.push({c:beg, a:(args ? [this,args] : [this]), open:true}); + return this; +}; + +/** + * End subcommands. Previous state is restored. + * @method + * @returns {object} g2 + */ +g2.prototype.end = function end() { + this.cmds.push({c:end,a:[this,this.findCmdIdx(end.matchBeg)]}); + return this; +}; +g2.prototype.end.matchBeg = function(cmd) { + if (cmd.c === g2.prototype.beg.cmd && cmd.open === true) { + delete cmd.open; + return true; + } + return false; +}; + +/** + * Clear viewport. + * @method + * @returns {object} g2 + */ +g2.prototype.clr = function clr() { + this.cmds.push({c:clr}); + return this; +}; + +// helper commands +/** + * Draw grid. + * @method + * @returns {object} g2 + * @param {string} [color=#ccc] CSS grid color. + * @param {float} [size] Grid size. + */ +g2.prototype.grid = function grid(color,size) { + this.getState().gridBase = 2; + this.state.gridExp = 1; + this.cmds.push({c:grid,a:[this,color,size]}); + return this; +}; +g2.prototype.grid.getSize = function(state,scl) { + var base = state.gridBase || 2, + exp = state.gridExp || 1, + sz; + while ((sz = scl*base*Math.pow(10,exp)) < 14 || sz > 35) { + if (sz < 14) { + if (base == 1) base = 2; + else if (base == 2) base = 5; + else if (base == 5) { base = 1; exp++; } + } + else { + if (base == 1) { base = 5; exp--; } + else if (base == 2) base = 1; + else if (base == 5) base = 2; + } + } + state.gridBase = base; + state.gridExp = exp; + return sz; +} + +/** + * Reference g2 graphics commands from another g2 object. + * With this command you can reuse instances of grouped graphics commands + * while applying a similarity transformation and style properties on them. + * In fact you might want to build custom graphics libraries on top of that feature. + * @method + * @returns {object} g2 + * @param {object | string} g g2 source object or symbol name found in 'g2.symbol' namespace. + * @param {object} args Arguments object. + * @param {float} [args.x] Translation value x. + * @param {float} [args.y] Translation value y. + * @param {float} [args.w] Rotation angle (in radians). + * @param {float} [args.scl] Scale factor. + * @param {array} [args.matrix] Matrix instead of single transform arguments (SVG-structure [a,b,c,d,x,y]). + * @param {float} [args.] Style property. See 'g2.style' for details. + * @param {float} [args.] ... + * @example + * g2.symbol.cross = g2().lin(5,5,-5,-5).lin(5,-5,-5,5); // Define symbol. + * g2().use("cross",{x:100,y:100}) // Draw cross at position 100,100. + * .exe(ctx); // Render to context. + */ +g2.prototype.use = function use(g,args) { + if (typeof g === "string") // should be a member name of the 'g2.symbol' namespace + g = g2.symbol[g]; + if (g && g !== this) { // avoid self reference .. + if (g.state && g.state.loading) { // referencing g2 object containing images ... + var state = this.getState(); + state.loading++; + g.state.addListener("load",function() { state.loading--; }); + } + this.cmds.push({c:use,a:args?[this,g,args]:[this,g]}); + } + return this; +}; + +/** + * Apply new style properties. + * @method + * @returns {object} g2 + * @param {object} args Style properties object. + * @param {string} args.fs Fill color (fillStyle). + * @param {string} args.ls Line color (lineStroke). + * @param {float} args.lw Line width. + * @param {bool} args.lwnosc Line width nonscalable. + * @param {string} args.lc Line cap [`butt`, `round`, `square`]. + * @param {string} args.lj Line join [`round`, `bevel` and `miter`]. + * @param {float} args.ml Miter limit'. + * @param {array} args.ld Line dash array. + * @param {int} args.lo Line dash offset. + * @param {float} args.shx Shadow offset x-value. + * @param {float} args.shy Shadow offset y-value. + * @param {float} args.shb Shadow blur effect value. + * @param {string} args.shc Shadow color. + * @param {string} args.thal Text horizontal alignment. + * @param {string} args.tval Text vertical alignment. + * @param {string} args.fof Font family. + * @param {float} args.foz Font size. + * @param {string} args.foc Font color. + * @param {string} args.fow Font weight ['normal','bold','lighter','bolder',100,200,...,900]. + * @param {string} args.fos Font style ['normal','italic','oblique']. + * @param {bool} args.foznosc Font size nonscalable. + * @example + * g = g2(); + * g2().style({ fs:"#58dbfa", // Set fill style. + * lw:10, // Set line width. + * ls:"#313942", // Set line style. + * lj:"round" }) // Set line join. + * .rec(10,10,300,100) + * .style({ lw:20, // Set line width. + * fs:"transparent", // Set fill style. + * shx:10, // Set shadow x-translation. + * shc:"black", // Set shadow color + * shb:10, // Set shadow blur. + * ld:[1,2] }) // Set line dash. + * .p().m(40,40).c(150,150,200,0,280,50).drw() + * .exe(ctx); + */ +g2.prototype.style = function style(args) { + this.cmds.push({c:style,a:[this,args]}); + return this; +}; + +// helper functions +/** + * Execute g2 commands. Do so recursively with 'use'ed commands. + * @method + * @returns {object} g2 + * @param {object} ctx Context. + * @param {object} [g=this] g2 Object to execute. + */ +g2.prototype.exe = function exe(ctx,g) { + var ifc = g2.ifcof(ctx); + if (ifc) { + var cmds = (g || this).cmds; + if (this.state && this.state.loading) { // give images a chance to complete loading .. + requestAnimationFrame(exe.bind(this,ctx,g)); // .. so wait a while .. + } + else if (ctx && cmds) { + var gstate = g && g.state, proxy = g2.proxy[ifc](ctx); + exe[ifc].beg.call(proxy,this); + if (g) // external g2 in use .. copy state + g.state = this.state; + for (var i=0,n=cmds.length,cmd; i