CHAPTER II.

SVG

var svg = RabbitEar.svg()

SVGs are natively 2D, vector-based, and great for interoperability with pen plotters and laser cutters.

Viewbox

SVG viewboxes allow us to zoom and translate the view space. This allows us to continue working in a unit-square-sized space and visualize full-screen, without having to multiply everything by 100.

svg.setViewBox(-1, -1, 2, 2)
// (top left corner) x, y, width, height
svg.mouseMoved = function (mouse) { }
{ x: ,y: }

Because of the computer standard, the SVG y-axis increases downwards.

Y-Axis

It's very easy to invert the y-axis, but that decision is up to you.

svg.setAttribute("transform", "matrix(1 0 0 -1 0 0)");

Introduction

This command creates an SVG element; and if run on a browser will automatically append the SVG to the document.

var svg = RabbitEar.svg()

Drawing a shape is as simple as:

var svg = RabbitEar.svg();
svg.circle(0, 1, 2);

My peferred initialization waits for DOM loading, and wraps code following Javascript's function-scoping.

RabbitEar.svg((svg) => {
  svg.circle(0, 1, 2);
});

This optional function parameter will be called after the page has finished loading.

Style

Style is applied functionally, each function name relates to an SVG attribute.

svg.ellipse(40, 30, 20, 10)
  .fill("crimson")
  .stroke("#fcd")
  .strokeDasharray("5 10")
  .strokeWidth(5);

kebab-case converts to camelCase.

Interactivity

draw on this canvas:

var points = [];
var shape = svg.polygon()

svg.mouseMoved = function (mouse) {
  points.push(mouse);
  if (points.length > 100) { points.shift(); }
  shape.setPoints(points);
};

This library makes it as easy as possible to make your SVG interactive.

There are three touch handlers

Each time the event handler fires you get the location of the touch. The coordinates of the touch are in the viewbox space.

event = {
  position: {x: 0.000, y: 0.000}
  previous: {x: 0.000, y: 0.000}
  isPressed: false
  pressed: {x: 0.000, y: 0.000}
  drag: {x: 0.000, y: 0.000}
}

Mice and trackpads allow movement without being pressed. Touchscreens will always be pressed when moved.

Drawing

Primitives

These relate to primitives in the SVG specification, anyone familiar with the spec should immediately recognize these.

svg.line(x1, y1, x2, y2)

> creates one <line> element


svg.circle(x, y, radius)

> creates one <circle> element

the x, y is the circle's center.


svg.ellipse(x, y, rx, ry)

> creates one <ellipse> element

an ellipse has 2 radii, along the X and Y axes.


svg.rect(x, y, width, height)

> creates one <rect> element

the x, y is top left corner.


svg.polygon(pointsArray)

> creates one <polygon> element

A polygon is a closed shape defined by a series of points.

Accepts points as arrays or {x:, y:} objects.

  • [[0.5, 1.0], [3.5, 2.5]]
  • {x:0.5, y:1.0}, {x:3.5, y:2.5}
  • 0.5, 1.0, 3.5, 2.5, 10, 10

svg.polyline(pointsArray)

> creates one <polyline> element

just like the polygon but the beginning and end aren't connected.


svg.text(textString, x, y)

> creates one <text> element

the x and y describe the location of the left most point along the baseline. the text sits above the point in the y direction. if defined at (0,0) the text will sit above the top of the frame.

Special Primitives

svg.regularPolygon(cX, cY, radius, sides)

> creates one <polygon> element

creates regular polygons of n number of sides (3 = eq. triangle), centered at point (cX, cY), with a circumscribed radius.


svg.arc(x, y, radius, angleA, angleB)

> creates one <path> element

an arc is the curve along the boundary of a circle.


svg.wedge(x, y, radius, angleA, angleB)

> creates one <path> element

a wedge is a filled arc, like a slice of a pie that includes the center of the circle.


svg.bezier(fromX, fromY, c1X, c1Y, c2X, c2Y, toX, toY)

> creates one <path> element

a bezier curve is defined between two points (fromX, fromY) and (toX, toY) with two control points (c1X, c1Y) and (c2X, c2Y). The arguments are arranged in order of appearance when tracing the line from start to finish.


svg.parabola(x, y, width, height)

> creates one <polyline> element

This creates a polyline approximation of the curve y=x², centered on the y-axis. The parameters define the rectangle that encloses the curve.

Layering and Clipping

group()

> creates one <group> element

like a layer in Photoshop, a group is a container. it's useful for z-ordering. it doesn't show up visually, but if you style it, the style will apply to all its children.


clipPath()

> creates one <clip-path> element

A clip path sits in the header, like style. Append closed-geometry elements to it and they will become a clipping mask. Clip another element by styling its clip-path attribute:

  • const clip = clipPath();
    shape.clipPath(clip);

which will automatically perform a call like this, matching the url id name parameter.

  • shape.setAttribute("clip-path", "url(#id_name)");

mask()

> creates one <mask> element

A mask is similar to a clip path but instead of a path, a mask is a black and white image. Fill the mask layer with black and white shapes (or gray for transparency). Clip another element by setting its mask attribute:

  • const m = mask();
    shape.mask(m);

which will automatically perform a call like this, matching the url id name parameter.

  • shape.setAttribute("mask", "url(#id_name)");

stylesheet()

> creates one <style> element

A style section contain CSS text (on property .innerHTML). CSS is helpful for selecting multiple objects and styling them with one definition.

This is not advised. Even if a stylesheet is inside one SVG, it applies to all SVGs on the same HTML page.

But be careful! Style sections affect the global space. Multiple SVG images on the same HTML document all inherit each other's style sections.


defs()

> creates one <defs> element

It's typical that everything mentioned in this section is a child in the defs() element: style, clip paths, and masks (though, it appears to not be required). Any drawing primitives appended to the defs will not be drawn.

Special

Paths

The Path object is like a pen tool, each command is a function, stacking one after another.

path().moveTo(50, 50)
  .lineTo(100, 150)
  .curveTo(200, 200, 300, 0)

Arrows

Arrows are like special lines, with a functional set of modifiers. The head and tail define the options (and existence of) the arrow's pointy bits.

Controls

Sometimes it's important to establish an anchor point that persists beyond the life of the handler. These are called controls.

svg.controls(4)
    .svg(function () { return RabbitEar.svg.circle(0, 0, svg.getWidth() * 0.05).fill("#e53"); })
    .position(function () { return [random(svg.getWidth()), random(svg.getHeight())]; })
    .parent(back)
    .onChange(function () {
      l1.setPoints(this[0], this[1]);
      l2.setPoints(this[3], this[2]);
      curve.clear().moveTo(this[0]).curveTo(this[1], this[2], this[3]);
    }, true);

This library is essentially a wrapper for creating SVG elements.

mySVG.rect(10, 10, 280, 130)

Calling the line above is the same as writing

var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect")
rect.setAttribute("x", 10)
rect.setAttribute("y", 10)
rect.setAttribute("width", 280)
rect.setAttribute("height", 130)
mySVG.appendChild(rect)

`RabbitEar.svg` is the global namespace. It's also a constructor. It makes an <svg> element.

var mySVG = RabbitEar.svg()

This library does a few things well:

  1. Wrap W3C methods in a more convenient way to create and manipulate svgs, drawing primitives, groups, masks, etc..
  2. Nice event handling (touches, animation).

Appendix

container and header types

group()
defs()
clipPath()
mask()
createElement(tagName) // create any element under the svg namespace

geometry primitives

line(x1, y1, x2, y2)
circle(x, y, radius)
ellipse(x, y, radiusX, radiusY)
rect(x, y, width, height)
polygon(pointsArray)
polyline(pointsArray)
bezier(fromX, fromY, c1X, c1Y, c2X, c2Y, toX, toY)
text(textString, x, y)
arc(x, y, radius, startAngle, endAngle)
wedge(x, y, radius, startAngle, endAngle)
arcEllipse(x, y, radiusX, radiusY, startAngle, endAngle)
wedgeEllipse(x, y, radiusX, radiusY, startAngle, endAngle)
parabola(x, y, width, height)
regularPolygon(cX, cY, radius, sides)
roundRect(x, y, width, height, cornerRadius)
straightArrow(start, end, options)
arcArrow(start, end, options)
svg: optional initializers * 2 numbers: width *then* height * DOM object, this will be the parent to attach the SVG ```javascript let mySVG = SVG(640, 480, parent_element); ```

Methods on an SVG


save(svg, filename = "image.svg")
load(filename, callback)

removeChildren()
appendTo(parent)
setAttributes(attributeObject)
addClass(className)
removeClass(className)
setClass(className)
setID(idName)

translate(tx, ty)
rotate(degrees)
scale(sx, sy)

getViewBox()
setViewBox(x, y, w, h)
convertToViewBox(x, y)
translateViewBox(dx, dy)
scaleViewBox(scale, origin_x = 0, origin_y = 0)

getWidth()
getHeight()
setWidth(w)
setHeight(h)
background(color)
size(width, height)
size(x, y, width, height)

Methods for interaction


// a property of an SVG (not a method), the current location of the pointer
mouse

// event handlers
mouseMoved = function (event) { }
mousePressed = function (event) { }
mouseReleased = function (event) { }
scroll = function (event) { }
animate = function (event) { }

// clear all event handlers
clear()
the mouse event is ```javascript { isPressed: false, // is the mouse button pressed (y/n) position: [0,0], // the current position of the mouse [x,y] pressed: [0,0], // the last location the mouse was pressed drag: [0,0], // vector, displacement from start to now prev: [0,0], // on mouseMoved, the previous location x: 0, // y: 0 // -- x and y, these are the same as position } ``` the animate event is ```javascript { time: 0.0, // in seconds frame: 0 // integer } ```