SVG paths for canvas
Summary: Canvas paths are verbose - this function can help you make them less so which will result in reduced download time for your code
- Introduction to the
path()
function - Path commands
- String based paths
- Get the code
- Example usage
- Changelog
- Changes available from RGraph 5.2
Introduction to the path() function
Canvas paths are verbose. Unnecessarily so in my opinion. The addition
of SVG paths to the forthcoming Path2D()
object
goes a long way to resolving this and providing a better solution for
creating and working with canvas paths.
But I'm rather impatient, and support for the object is still not widespread enough for it to be in common use. So there must be a better way to creating and using canvas paths...
Enter the RGraph path
function. This is a function that takes
the context and
the desired path as arguments and calls the
relevant functions for you. The path is an array of letters (and numbers) -
each
representing an action that should be taken. So it's similar to SVG paths,
but
there are a few more options that make it more versatile.
So what does it look like to use? Like the example below:
RGraph.path()
Here's an example of the code that you can download here that I've extracted:
context.path(['b', 'm', 5, 5, 'l', 45, 45, 'l', 5, 45, 'c', 'f', 'red']);
That would translate to the following sequence of function calls:
context.beginPath(); context.moveTo(5, 5); context.lineTo(45, 45); context.lineTo(5, 45); context.closePath()' context.fillStyle = 'red'; context.fill();
In terms of character size it's approximately 55% smaller. If you don't compress your code (you really should) that's very significant and larger paths will potentially give you bigger savings. Even when you take compression and minification into account the savings may well be worth it.
Note:
You can further reduce the size of your paths by using
string based paths.
Path commands
So here's a list of the available path commands and what they represent. The RGraph path function is constantly evolving so if you want to update in the future it shouldn't be too difficult to extract it. The function in the RGraph core common library is a function expression (NOT a statement), so the declaration may at first glance be unfamiliar, but I've tried to make the code that you can download here similar so taking it out of an updated RGraph library and replacing your code should be quite straightforward.
- b
context.beginPath()
No extra arguments - c
context.closePath()
No extra arguments - m
context.moveTo()
The next two numbers are used as the arguments - l
context.lineTo()
The next two numbers are used as the arguments - s
context.stroke()
The next string (which can also benull
) is used to first set thestrokeStyle
color - f
context.fill()
The next string (which can also benull
) is used to first set thefillStyle
color - qc
context.quadraticCurveTo()
The next four numbers are used as the arguments - bc
context.bezierCurveTo()
The next six numbers are used as the arguments - r
context.rect()
The next four numbers are used as the arguments - e
context.ellipse()
The next eight numbers are used as the arguments - a
context.arc()
The next six numbers are used as the arguments - at
context.arcTo()
The next five numbers are used as the arguments - lw
context.lineWidth
The next number is used as thelineWidth
to set - lj
context.lineJoin
The next string is used as thelineJoin
to set - lc
context.lineCap
The next string is used as thelineCap
to set - sc
context.shadowColor
The next string is used as theshadowColor
to set - sb
context.shadowBlur
The next number is used as theshadowBlur
to set - sx
context.shadowOffsetX
The next number is used as theshadowOffsetX
to set - sy
context.shadowOffsetY
The next number is used as theshadowOffsetY
to set - fs
context.fillStyle
The next string is set as thefillStyle
(thefill()
function is NOT called) - ss
context.strokeStyle
The next string is set as thestrokeStyle
(thestroke()
function is NOT called) - fr
context.fillRect()
TechnicallyfillRect()
is not a path function, but it's here regardless. The next four numbers are used as the arguments - sr
context.strokeRect()
TechnicallystrokeRect()
is not a path function, but it's here regardless. The next four numbers are used as the arguments - cl
context.clip()
No extra arguments - sa
context.save()
No extra arguments - rs
context.restore()
No extra arguments - tr
context.translate()
The next two numbers are used as the arguments - sc
context.scale()
The next two numbers are used as the arguments - ro
context.rotate()
The next number are used as the argument - tf
context.transform()
The next six numbers are used as the arguments - stf
context.settransform()
The next six numbers are used as the arguments - cr
context.clearRect()
The next four numbers are used as the arguments - ld
context.setLineDash()
The next array literal is used as the dash settings (example below) - ldo
context.lineDashOffset
The next number is used as the argument - fo
context.font
The next string is used as the argument - ft
context.fillText()
The next string and two numbers are used as the arguments - st
context.strokeText()
The next string and two numbers are used as the arguments - ta
context.textAlign
The next string is used as the argument - tbl
context.textBaseline
The next string is used as the argument - ga
context.globalAlpha
The next number is used as the argument - gco
context.globalCompositeOperation
The next string is used as the argument
String based paths
If you're not using animation with the path function you can use string based paths instead of array based paths. These are far easier to write and are significantly less verbose too. Obviously the strings need to be parsed so there's a tiny performance hit with them. An example of a string based path is:
context.path('b r 5 5 50 50 s black f red');
Note:
If you want to use the lineDashOffset
option - ldo
- you need to use
an array based path. But you could do this to set the lineDashOffset
before
you specify your entire path:
context.path(['ldo', [5,5]]); context.path('b r 5 5 56 56 s red');
Get the code
The code for the path function is here. You can include it in your page with a script tag:
<script src="rgraph.path.js"></script>
Example usage
A rectangle example
// Using the path function context.path(['b', 'r', 5, 5, 90, 90, 'f', 'red']); // Using the canvas API context.beginPath(); context.rect(5,5,90,90); context.fillStyle = 'red'; context.fill();
An arc()
example
// Using the path function context.path(['b', 'a', 50, 50, 50, 0, 3.14, false, 'f', 'red']); // Using the canvas API context.beginPath(); context.arc(50,50,50,0, Math.PI false); context.fillStyle = 'red'; context.fill();
A bezierCurveTo()
example
// Using the path function context.path(['b', 'm', 5, 100, 'bc', 5, 0, 100, 0, 100, 100, 's', 'red']); // Using the canvas API context.beginPath(); context.moveTo(5, 100); context.bezierCurveTo(5, 0, 100, 0, 100, 100); context.strokeStyle = 'red'; context.stroke();
An arcTo()
example
// Using the path function context.path(['b', 'm', 5, 100, 'at', 50, 0, 95, 100, 50, 's', 'red']); // Using the canvas API context.beginPath(); context.moveTo(5, 100); context.arcTo(50, 0, 95, 100, 50); context.strokeStyle = 'red'; context.stroke();
A clip()
example
// Using the path function context.path(['sa', 'b', 'r', 0, 0, 50, 50, 'cl', 'b', 'r', 5, 5, 590, 240, 'f', 'red', 'rs']); // Using the canvas API context.save(); context.beginPath(); context.rect(0, 0, 50, 50); context.clip(); context.beginPath(); context.rect(5, 5, 590, 240); context.fillStyle = 'red'; context.fill(); context.restore();
A setLineDash()
example
// Using the path function context.path(['ld', [2,6], 'ldo', 4, 'b', 'r', 5, 5, 590, 240, 's', 'red']); // Using the canvas API context.setLineDash([2,6]); context.lineDashOffset = 4; context.beginPath(); context.rect(5, 5, 590, 240); context.strokeStyle = 'red'; context.stroke();
A globalAlpha
example
// Using the path function context.path(['ga', 0.25, 'b', 'r', 5, 5, 590, 240, 'f', 'red']); // Using the canvas API context.globalAlpha = 0.25; context.beginPath(); context.rect(5, 5, 590, 240); context.fillStyle = 'red'; context.fill();
Changelog
10th November 21st 2015
- Added the ability to use string based paths back in albeit with a faster parser. Because they're just so convenient. If you using the path function in an animation though I would recommend you use array based paths as there's then no need to parse the string. Parsing the string would be fine if you're not using animation though.
10th November 20th 2015
- Removed the ability to use string based paths. Now your paths must be arrays - like the examples above.
15th September 2015
-
Added the ability to use % style substitution like this:
context.path( 'b r 0 0 % % f red', 50, 25 + 25 );
Similar to prepared SQL queries - but a basic substitution. The values are converted to strings - whatever they might end up as.
7th September 2015
- Removed the fu option
- Removed the ct option
-
Added these options:
- e
context.ellipse()
- tr
context.transform()
- sc
context.scale()
- ro
context.rotate()
- tf
context.transform()
- stf
context.setTransform()
- cr
context.clearRect()
- ld
context.setLineDash()
- ldo
context.lineDashOffset
- fo
context.font
- ft
context.fillText()
- st
context.strokeText()
- ta
context.textAlign
- tbl
context.textBaseline
- ga
context.globalAlpha
- gco
context.globalCompositeOperation
- e
Changes available from RGraph 5.2
path()
function
has been made to be a member function of the chart object. This means that you can do this:
obj.path({ path: 'b r % % % % f %', args: [10,10,100,100,'red'] });
And also this - an even shorter syntax:
obj.path('b r % % % % f %', 10, 10, 100, 100, 'red');