SVG paths for canvas
Written by Richard Heyes, RGraph author, on 3rd September 2015- 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:
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 is 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 2015
- Added the ability to use string-based paths back in albeit with a faster parser. Because they're just so convenient. If you're 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 will be fine if you're not using animation though.
- 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');