Some tests and extensions of SVG.
This section is dedicated to investigations I've made into SVG. They are not of production quality, for the most part. Some of them are calls for extending the feature-set of SVG; others are tests of current or imminent features in the Spec.
Behavior and Appearance Markup
I have made a number of suggestions regarding extensions to SVG, with the common theme of replacing CSS and adding more declarative functionality. Read more about this in my BAM Proposal.
Variable Stroke Width
This is the first failing I noticed in SVG. I would like the stroke (or outline) of SVG to be a variable thickness, to simulate pen strokes. I have hope that this may be resolved with the new Vector Effects from SVG 1.2.
I've been writing an SVG graphics application for doing art, and I have noticed what I think is a deficit in the W3C Specification: variable-width strokes. In drawing a line with a pencil or brush (or with an increasingly inexpensive graphics tablet), an artist can make great utility of tapering a line. I realize that this can be simulated by the path element, but that seems an unnecessarily complex solution.
I would like to have stroke-widths (that is to say, the width of the line that defines the outside of a shape) of variable size on a single shape.
Since stroke-width is a style characteristic, we'll need to keep within the style attribute for consistency; also for consistency, we should reuse any format conventions as much as possible. Therefore, I have modelled this on “stroke-dasharray”. Currently, “stroke-width” is a single number; I propose extending this to a list. Each entry in a list is the linewidth for a control point (details below).
I would expect the following behaviors for the different basic shapes:
<polyline> and <polygon>
The control points for the <polyline> and <polygon> elements are the coordinates pairs defined in the points attribute. If the number of arguments for “stroke-width” is greater than the number of control points, any additional arguments are ignored; vice versa, and the last argument applies to all remaining control points.
The control points for a <rect> element are the coordinates of the four Floaters. In the case of rounded rectangles, the control point would be the midpoint of the curve. The number of arguments for “stroke-width” is handled as for <polyline> and <polygon>.
The control points for a <path> element are the vertices (i.e. those places at which the path changes); these are the locations on the path where <marker> elements are instantiated, so this builds on existing parameters (already implemented in most viewers). The number of arguments for “stroke-width” is handled as for <polyline> and <polygon>.
The primary control points for a <line> element are the coordinates of the endpoints. However, additional control points --specified by more than two arguments for stroke-width-- would be evenly placed along the length of the line. For example, for a horizontal line with a length of 30 and “stroke-width='2,10,5”, the line would start at “stroke-width” of 2, expand to 30 at its midpoint, and taper off to 5 at its terminus (x2,y2).
<circle> and <ellipse>
The control points for the <circle> and <ellipse> elements would be evenly distributed around their circumferences, as with a <line> element. The start point would be at the top --the coordinates (x=cx, y=cy-r). A circle with “stroke-width='2,10'” would be thin at the top and thick at the bottom; “stroke-width='2,10,2,10'” would show a circle with thin top and bottom and thick sides (like the “o” in a Times New Roman font).
In addition to being more intuitive for many people, this could significantly decrease file sizes for certain types of SVGs, and provide some effects that might be difficult to do with the current syntax, as with the simple examples above.
Obviously, these are meager offerings, and I don't purport to be an artist. But when I look at a lovely line-drawing, and admire the sweep of a curve or the subtlety of a tapered line, I've wondered to myself, “How could someone do that in SVG?” I know many artists, too, and when I've discussed SVG with them, many of them have asked about this feature, which has been sorely lacking in previous vector graphics formats. I'd like to think maybe this is a workable solution.
I have some other ideas, too, that complicate the idea. Various style properties could modify the stroke-width array, so as to allow the stroke to be different widths on either side of the definition line. But I'll not muddy the waters with that.
One thing I will mention is that I think this may have great potential if used with SVGFonts. I've been playing with them a bit lately, and intend to create a free SVGFont repository. The variable-width stroke seems a natural fit with character representation. I have recently seen an interesting paper online that suggests the same thing (in 1993... nothing new under the sun ;), using splines: “Variable width splines: a possible font representation?”.
Kevin Lindsey, SVG luminary, has run with his idea in a very promising and exciting way. He has made code modules to simulate different approaches to how this might be accomplished, and created a custom namespace for it. He has a put up a example page with illustrations at KevLinDev.
SVG should support definable stroke styles. Possible examples of defined styles are wave strokes, strokes with multiple lines and the brushes that are supported by many illustration packages. [SVG 2.0]
We also discussed another interesting idea, peripherally related: Creature House's Skeletal Strokes. Rather than define only a particular set of instructions for stroke-width, one could use a shape as a framework for more complex shapes, aiding in animation and other effects. This would be similar to SVG's <textpath> and possibly <use> elements.
As I mentioned before, though, there may be some technical reason that this is more complex than I thought, despite Kevin's encouraging results. Still to be done is experimentation with polygons other than <rect>s, and with <line>s and <path>s. To facilitate this, I intend --with Kevin's generous permission-- to incorporate this into my soon-to-be-released online graphical SVG editor (SVG-Whiz!), so the SVG community and I can kick the tires. I'll welcome any feedback or reactions to this idea.
New Basic Shapes: 'regularPoly' and 'triangle' Elements
SVG has seven basic shape primatives (not including text):
- 'circle': a circle, defined by the attributes cx (centerpoint on the x-axis), cy (centerpoint on the y-axis), and r (radius)
- 'ellipse': an ellipse, defined by the attributes cx (centerpoint on the x-axis), cy (centerpoint on the y-axis), rx (radius on the x-axis), and ry (radius on the y-axis)
- 'rect': a rectangle, defined by the attributes x (origin point on the x-axis), y (origin point on the y-axis), width (length from the x-origin), and height (length from the y-origin); it also has the optional corner-rounding attributes cx (offset from each corner on the x-axis) and cy (offset from each corner on the y-axis)
- 'line': a line, defined by the attributes x1 (origin point on the x-axis), y1 (origin point on the y-axis), x2 (end point on the x-axis), y2 (end point on the y-axis)
- 'polyline': a complex line, defined by the single attribute points (a parameterized list of start and end coordinate pairs, in the pattern 'x,y x,y x,y')
- 'polygon': a regular or irregular polygon, defined by the single attribute points (a parameterized list of start and end coordinate pairs, in the pattern 'x,y x,y x,y'); identical to 'polyline', but the shape is automatically closed, which carries some semantic value
- 'path': a complex shape, defined by the single attribute d (for path-data, a parameterized list of segment commands and their x, y, or coordinate pair arguments); the segment commands are case-sensitve, with the uppercase commands being absolute position on the shape's coordinate system, and the lowercase commands being a relative movement from the last set of coordinates; they consist of various kinds of straight lines and bezier curves, and an arc segment.
But while almost any shape can be drawn with these, especially with the versatile 'path' element, there are reasons to desire more basic shapes, either for semantics or ease of authoring.
Jerrold Maddox, a creative artist who does very interesting experiments in SVG, recently asked about the absence of a 'square' element in SVG, defined by the centerpoint and the length of the sides, which he posited as useful for relative layouts. My initial thought was that there wasn't a strong enough use case for squares to merit their explicit inclusion, separate from the 'rect' element. But on further reflection, I realized that with a bit of an extension, an attribute to indicate the number of sides in addition to the length of each side, it would be an element that generates regular polygons, which are actually quite tricky to code by hand, and which are very useful in general.
With some help from Wolfram's MathWorld and poke with a clue-by-four from my pal Kevin Lindsey, I whipped up a sample that turns this:
<svg> <regularPoly id='bigon' cx='100' cy='100' sides='2' edge='100' orient='x' /> <regularPoly id='trigon-equilateral_triangle' cx='190' cy='100' sides='3' edge='90' orient='x' /> <regularPoly id='tetragon-square' cx='340' cy='100' sides='4' edge='80' orient='x' /> <regularPoly id='pentagon' cx='500' cy='100' sides='5' edge='70' orient='x' /> <regularPoly id='hexagon' cx='700' cy='100' sides='6' edge='60' orient='x' /> <regularPoly id='heptagon' cx='100' cy='250' sides='7' edge='50' orient='x' /> <regularPoly id='octagon' cx='300' cy='250' sides='8' edge='40' orient='x' /> <regularPoly id='nonagon' cx='500' cy='250' sides='9' edge='30' orient='x' /> <regularPoly id='decagon' cx='700' cy='250' sides='10' edge='30' orient='x' /> <regularPoly id='undecagon' cx='100' cy='400' sides='11' edge='30' orient='x' /> <regularPoly id='dodecagon' cx='300' cy='400' sides='12' edge='30' orient='x' /> <regularPoly id='triskaidecagon' cx='500' cy='400' sides='13' edge='30' orient='x' /> <regularPoly id='tetrakaidecagon' cx='700' cy='400' sides='14' edge='30' orient='x' /> </svg>
You may have noticed the 'orient' attribute; I haven't yet implemented it, but it's intended to have values 'x' or 'y'; 'x' would mean that the top edge would be parallel to the x-axis, 'y' would mean that one of the corners would be pointing up instead (that is, a line between the centerpoint the topmost vertex would be parallel to the y-axis). There should be an accompanying DOM API to find the coordinate points of each of the vertices, and optionally, markers could be used with this meta-shape at each point.
And, yes, I know there's no such thing as a bigon... that was my little joke. But let's let bigons be bygones, shall we? I'm halfway serious when I suggest that there is a use case for setting sides='1' to draw a circle with a circumference equal to the edge length, but that may be pushing it, since you can already draw a circle a number of ways.
Update, 23 August 2005: I decided that the most useful way to approach regular polygons is as a radius, rather than as a side length (although if I had my druthers, we'd have both); I think most people would want to have a regular polygon of a fixed size. This would also give us the latitude to introduce stellation into the agenda. Given a circumaradius, you could have a second radius, the inradius, that would define the inner set of points from which lines are drawn to form regular stars. This gives us the 'star' primative, one intuitive to understand and to use. You can still make a regular polygon by defining only the number of points and the circumradius, not the inradius. Here is an example of a regular polygon defined as a star:
<svg> <star id='trigon-equilateral_triangle' cx='100' cy='100' r='40' points='3' orient='edge'/> <star id='tetragon-square' cx='200' cy='100' r='40' points='4' orient='edge'/> <star id='pentagon' cx='300' cy='100' r='40' points='5' orient='edge'/> <star id='hexagon' cx='400' cy='100' r='40' points='6' orient='edge'/> <star id='heptagon' cx='100' cy='200' r='40' points='7' orient='edge'/> <star id='octagon' cx='200' cy='200' r='40' points='8' orient='edge'/> <star id='nonagon' cx='300' cy='200' r='40' points='9' orient='edge'/> <star id='decagon' cx='400' cy='200' r='40' points='10' orient='edge'/> <star id='undecagon' cx='100' cy='300' r='40' points='11' orient='edge'/> <star id='dodecagon' cx='200' cy='300' r='40' points='12' orient='edge'/> <star id='triskaidecagon' cx='300' cy='300' r='40' points='13' orient='edge'/> <star id='tetrakaidecagon' cx='400' cy='300' r='40' points='14' orient='edge'/> <star id='duodecagon' cx='250' cy='450' r='100' points='100' orient='edge'/> </svg>
This shape has very similar syntax to that of a circle; in fact, the size of the shape is such that it would be circumscribed by a circle of the same 'cx', 'cy', and 'r' attribute values. The 'points' attribute, unlike in a polygon or polyline, indicates the number of points the shape will have. Note that the 'orient' attribute has a value of 'edge'; this means that the top of the shape will be parallel to the x axis. In this case, the 'star' element is simply a regular polygon; in order to make it into a traditional star shape, with concave rather than flat edges, you have to define an inner radius (an inradius, as opposed to the circumradius), which will serve as the terminus of the concavity. Here is an example of syntax for such as set of stars:
<svg> <star id='trigram-equilateral_triangle' cx='100' cy='100' r='40' r2='10' points='3' orient='point'/> <star id='tetragram-square' cx='200' cy='100' r='40' r2='20' points='4' orient='point'/> <star id='pentagram' cx='300' cy='100' r='40' r2='20' points='5' orient='point'/> <star id='hexagram' cx='400' cy='100' r='40' r2='20' points='6' orient='point'/> <star id='heptagram' cx='100' cy='200' r='40' r2='20' points='7' orient='point'/> <star id='octagram' cx='200' cy='200' r='40' r2='20' points='8' orient='point'/> <star id='nonagram' cx='300' cy='200' r='40' r2='20' points='9' orient='point'/> <star id='decagram' cx='400' cy='200' r='40' r2='20' points='10' orient='point'/> <star id='undecagram' cx='100' cy='300' r='40' r2='20' points='11' orient='point'/> <star id='dodecagram' cx='200' cy='300' r='40' r2='20' points='12' orient='point'/> <star id='triskaidecagram' cx='300' cy='300' r='40' r2='20' points='13' orient='point'/> <star id='tetrakaidecagram' cx='400' cy='300' r='40' r2='20' points='14' orient='point'/> <star id='duodecagram' cx='250' cy='450' r='100' r2='50' points='100' orient='point'/> </svg>
Note that the 'orient' attribute now has a value of 'point'; this means that the top of the shape will be a point, with a bifurcating line parallel to the y axis. More importantly, each element now has an 'r2' attribute, which indicates how acute the inner angle of the star will be, and how deep or shallow the concavity; if the 'r2' attribute is omitted, the shape will be a simple regular polygon. If the 'r2' attribute has a value greater than that of the 'r' attribute, 'r' effectively acts as the inradius; if the 'r2' attribute number is negative, the crossing lines will produce a kaleidoscopic effect. Here are 2 examples of this syntax:
<svg> <star id='tetragram-square_negative' cx='100' cy='400' r='40' r2='-20' points='4' orient='edge'/> <star id='decagram_negative' cx='400' cy='400' r='40' r2='-20' points='10' orient='point'/> </svg>
These variables can produce a wide variety of useful and attractive regular shapes. Finally, however, I am proposing another attribute, the 'radial-shift' attribute, which takes a floating point number which rotates the anchorpoints of the inner radius to produce spiraling shapes. This is another kaleidoscopic effect which introduces radial asymmetry into the shape. Example syntax can be seen here:
<svg> <star id='trigram-equilateral_triangle' cx='100' cy='500' r='40' r2='10' points='3' orient='point' radial-shift='-1'/> <star id='pentagram' cx='400' cy='500' r='40' r2='20' orient='point' points='5' radial-shift='2'/> </svg>
Here are all of the above examples rendered, with the stars in blue superimposed over the matching regular polygons in green, the negative inradius stars in red and gold, and the radially-shifted stars in purple:
Why is this important? Why not just do this in script, since it's obvious it's not that hard? Why not define it in sXBL? Intuition, efficency, animation, and semantics.
People know what stars are. Even as the element for regular polygons, it makes sense to have a star... regular polygons are a simple kind of star. People understand radii, and would quickly grasp the syntax where they would never grasp the formulae. Stars and regular polygons are a very common use case, but are surprisingly hard to calculate by hand, even something as simple as a hexagon. Authoring tools could standardize on a single way to make these primatives, if they were defined.
A partial solution such as this simply doesn't scale to large numbers of elements. It also requires the use of script, which should be avoided whenever possible; mobile devices do not currently require scripting, so it would not work there at all. And with custom extensions like this, few people would know about the element, and fewer still would use it, since content would not be portable.
As difficult as it is, to define the geometry of a star, it is much harder to try to animate that. SMIL animation would be the perfect way to grow or shrink a star, to make it pulse in time, to change the ratio of the radii, or to spin it. Without a star primative, you are stuck with a rather unweildy bit of scripting. Even to find the centerpoint, for rotation, requires far too much work.
A polygon with 5 points is not necessarily a star. But screenreaders, humans, and indexing bots could grasp the idea of a star with a given number of points. Semantics has gotten a bad rap, but I think it is important to lay the groundwork now.
Where To Go From Here?
In order to get such a scheme accepted, it is best to present to the SVG Working Group both a use case and an implementation. I think I've done both here, and shown how this element has potential for extension as well. One such experiment i've already done is to change the 'orient' attribute to 'rotate' and allow the values 'point' (default), 'edge', or any number to represent the angle of rotation in radians (as a shortut for the 'rotate' transform. Another, more complicated, extension would be to allow an attribute to introduce curvature of the edges, which would allow for rounded stars or even spirals. Yet another would be a new attribute, 'line-rule', which would control the rendering of lines segments that cross over the fill area. This would also be appropriate for the 'path' and 'poly-' elements.
On the subject of duplicate ways to draw shapes, you will notice that there is an equilateral triangle in the polygons above. I would like to see the triangle treated as a first-class citizen, since triangles are both common and mathematically significant.
I think that the ability to define a triangle by the angles of its vertices would be really useful for a variety of real-world use cases, such as pie-chart wedges, for which there could be a special rounding parameter, like there is for rectangles, that would draw an arc between 2 of the designated corners, completing a circle when several triangles are abutted. Calculating angles from percentages would then be a trivial task. So, the adding a triangle primative would thus pull double-duty, satisfying both the frequently-requested pie-slice primative and the utilitarian triangle.
I'm not completely certain what the syntax should look like... perhaps tx, ty, a1, a2? I'll put together another script example when I've settled on syntax. More to come...
Rendering Custom Content
These are tests I made while investigating the non-defunct RCC proposal of SVG 1.2. RCC has been replaced by the more generic sXBL. More to come...