diff options
Diffstat (limited to 'src/modules/optional')
-rw-r--r-- | src/modules/optional/arrange.js | 98 | ||||
-rw-r--r-- | src/modules/optional/class.js | 44 | ||||
-rw-r--r-- | src/modules/optional/css.js | 71 | ||||
-rw-r--r-- | src/modules/optional/data.js | 26 | ||||
-rw-r--r-- | src/modules/optional/memory.js | 39 | ||||
-rw-r--r-- | src/modules/optional/sugar.js | 159 | ||||
-rw-r--r-- | src/modules/optional/transform.js | 72 |
7 files changed, 509 insertions, 0 deletions
diff --git a/src/modules/optional/arrange.js b/src/modules/optional/arrange.js new file mode 100644 index 0000000..ca0e074 --- /dev/null +++ b/src/modules/optional/arrange.js @@ -0,0 +1,98 @@ +import { registerMethods } from '../../utils/methods.js' + +// Get all siblings, including myself +export function siblings () { + return this.parent().children() +} + +// Get the curent position siblings +export function position () { + return this.parent().index(this) +} + +// Get the next element (will return null if there is none) +export function next () { + return this.siblings()[this.position() + 1] +} + +// Get the next element (will return null if there is none) +export function prev () { + return this.siblings()[this.position() - 1] +} + +// Send given element one step forward +export function forward () { + var i = this.position() + 1 + var p = this.parent() + + // move node one step forward + p.removeElement(this).add(this, i) + + // make sure defs node is always at the top + if (typeof p.isRoot === 'function' && p.isRoot()) { + p.node.appendChild(p.defs().node) + } + + return this +} + +// Send given element one step backward +export function backward () { + var i = this.position() + + if (i > 0) { + this.parent().removeElement(this).add(this, i - 1) + } + + return this +} + +// Send given element all the way to the front +export function front () { + var p = this.parent() + + // Move node forward + p.node.appendChild(this.node) + + // Make sure defs node is always at the top + if (typeof p.isRoot === 'function' && p.isRoot()) { + p.node.appendChild(p.defs().node) + } + + return this +} + +// Send given element all the way to the back +export function back () { + if (this.position() > 0) { + this.parent().removeElement(this).add(this, 0) + } + + return this +} + +// Inserts a given element before the targeted element +export function before (element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i) + + return this +} + +// Inserts a given element after the targeted element +export function after (element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i + 1) + + return this +} + +registerMethods('Dom', { + siblings, position, next, prev, forward, backward, front, back, before, after +}) diff --git a/src/modules/optional/class.js b/src/modules/optional/class.js new file mode 100644 index 0000000..1d28fd5 --- /dev/null +++ b/src/modules/optional/class.js @@ -0,0 +1,44 @@ +import { delimiter } from '../core/regex.js' +import { registerMethods } from '../../utils/methods.js' + +// Return array of classes on the node +function classes () { + var attr = this.attr('class') + return attr == null ? [] : attr.trim().split(delimiter) +} + +// Return true if class exists on the node, false otherwise +function hasClass (name) { + return this.classes().indexOf(name) !== -1 +} + +// Add class to the node +function addClass (name) { + if (!this.hasClass(name)) { + var array = this.classes() + array.push(name) + this.attr('class', array.join(' ')) + } + + return this +} + +// Remove class from the node +function removeClass (name) { + if (this.hasClass(name)) { + this.attr('class', this.classes().filter(function (c) { + return c !== name + }).join(' ')) + } + + return this +} + +// Toggle the presence of a class on the node +function toggleClass (name) { + return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) +} + +registerMethods('Dom', { + classes, hasClass, addClass, removeClass, toggleClass +}) diff --git a/src/modules/optional/css.js b/src/modules/optional/css.js new file mode 100644 index 0000000..924b13d --- /dev/null +++ b/src/modules/optional/css.js @@ -0,0 +1,71 @@ +// FIXME: We dont need exports +import { camelCase } from '../../utils/utils.js' +import { isBlank } from '../core/regex.js' +import { registerMethods } from '../../utils/methods.js' + +// Dynamic style generator +export function css (style, val) { + let ret = {} + if (arguments.length === 0) { + // get full style as object + this.node.style.cssText.split(/\s*;\s*/) + .filter(function (el) { return !!el.length }) + .forEach(function (el) { + let t = el.split(/\s*:\s*/) + ret[t[0]] = t[1] + }) + return ret + } + + if (arguments.length < 2) { + // get style properties in the array + if (Array.isArray(style)) { + for (let name of style) { + let cased = camelCase(name) + ret[cased] = this.node.style[cased] + } + return ret + } + + // get style for property + if (typeof style === 'string') { + return this.node.style[camelCase(style)] + } + + // set styles in object + if (typeof style === 'object') { + for (let name in style) { + // set empty string if null/undefined/'' was given + this.node.style[camelCase(name)] = + (style[name] == null || isBlank.test(style[name])) ? '' : style[name] + } + } + } + + // set style for property + if (arguments.length === 2) { + this.node.style[camelCase(style)] = + (val == null || isBlank.test(val)) ? '' : val + } + + return this +} + +// Show element +export function show () { + return this.css('display', '') +} + +// Hide element +export function hide () { + return this.css('display', 'none') +} + +// Is element visible? +export function visible () { + return this.css('display') !== 'none' +} + +registerMethods('Dom', { + css, show, hide, visible +}) diff --git a/src/modules/optional/data.js b/src/modules/optional/data.js new file mode 100644 index 0000000..341d129 --- /dev/null +++ b/src/modules/optional/data.js @@ -0,0 +1,26 @@ +import { registerMethods } from '../../utils/methods.js' + +// Store data values on svg nodes +export function data (a, v, r) { + if (typeof a === 'object') { + for (v in a) { + this.data(v, a[v]) + } + } else if (arguments.length < 2) { + try { + return JSON.parse(this.attr('data-' + a)) + } catch (e) { + return this.attr('data-' + a) + } + } else { + this.attr('data-' + a, + v === null ? null + : r === true || typeof v === 'string' || typeof v === 'number' ? v + : JSON.stringify(v) + ) + } + + return this +} + +registerMethods('Dom', { data }) diff --git a/src/modules/optional/memory.js b/src/modules/optional/memory.js new file mode 100644 index 0000000..d1bf7cf --- /dev/null +++ b/src/modules/optional/memory.js @@ -0,0 +1,39 @@ +import { registerMethods } from '../../utils/methods.js' +// FIXME: We need a constructor to set this up + +// Remember arbitrary data +export function remember (k, v) { + // remember every item in an object individually + if (typeof arguments[0] === 'object') { + for (var key in k) { + this.remember(key, k[key]) + } + } else if (arguments.length === 1) { + // retrieve memory + return this.memory()[k] + } else { + // store memory + this.memory()[k] = v + } + + return this +} + +// Erase a given memory +export function forget () { + if (arguments.length === 0) { + this._memory = {} + } else { + for (var i = arguments.length - 1; i >= 0; i--) { + delete this.memory()[arguments[i]] + } + } + return this +} + +// return local memory object +export function memory () { + return (this._memory = this._memory || {}) +} + +registerMethods('Dom', { remember, forget, memory }) diff --git a/src/modules/optional/sugar.js b/src/modules/optional/sugar.js new file mode 100644 index 0000000..904e353 --- /dev/null +++ b/src/modules/optional/sugar.js @@ -0,0 +1,159 @@ +import { registerMethods } from '../../utils/methods.js' +import Color from '../../types/Color.js' +import Element from '../../elements/Element.js' +import Matrix from '../../types/Matrix.js' +import Point from '../../types/Point.js' +import Runner from '../../animation/Runner.js' +import SVGNumber from '../../types/SVGNumber.js' + +// Define list of available attributes for stroke and fill +var sugar = { + stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'], + fill: ['color', 'opacity', 'rule'], + prefix: function (t, a) { + return a === 'color' ? t : t + '-' + a + } +} + +// Add sugar for fill and stroke +;['fill', 'stroke'].forEach(function (m) { + var extension = {} + var i + + extension[m] = function (o) { + if (typeof o === 'undefined') { + return this + } + if (typeof o === 'string' || Color.isRgb(o) || (o instanceof Element)) { + this.attr(m, o) + } else { + // set all attributes from sugar.fill and sugar.stroke list + for (i = sugar[m].length - 1; i >= 0; i--) { + if (o[sugar[m][i]] != null) { + this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) + } + } + } + + return this + } + + registerMethods(['Shape', 'Runner'], extension) +}) + +registerMethods(['Element', 'Runner'], { + // Let the user set the matrix directly + matrix: function (mat, b, c, d, e, f) { + // Act as a getter + if (mat == null) { + return new Matrix(this) + } + + // Act as a setter, the user can pass a matrix or a set of numbers + return this.attr('transform', new Matrix(mat, b, c, d, e, f)) + }, + + // Map rotation to transform + rotate: function (angle, cx, cy) { + return this.transform({ rotate: angle, ox: cx, oy: cy }, true) + }, + + // Map skew to transform + skew: function (x, y, cx, cy) { + return arguments.length === 1 || arguments.length === 3 + ? this.transform({ skew: x, ox: y, oy: cx }, true) + : this.transform({ skew: [x, y], ox: cx, oy: cy }, true) + }, + + shear: function (lam, cx, cy) { + return this.transform({ shear: lam, ox: cx, oy: cy }, true) + }, + + // Map scale to transform + scale: function (x, y, cx, cy) { + return arguments.length === 1 || arguments.length === 3 + ? this.transform({ scale: x, ox: y, oy: cx }, true) + : this.transform({ scale: [x, y], ox: cx, oy: cy }, true) + }, + + // Map translate to transform + translate: function (x, y) { + return this.transform({ translate: [x, y] }, true) + }, + + // Map relative translations to transform + relative: function (x, y) { + return this.transform({ relative: [x, y] }, true) + }, + + // Map flip to transform + flip: function (direction, around) { + var directionString = typeof direction === 'string' ? direction + : isFinite(direction) ? 'both' + : 'both' + var origin = (direction === 'both' && isFinite(around)) ? [around, around] + : (direction === 'x') ? [around, 0] + : (direction === 'y') ? [0, around] + : isFinite(direction) ? [direction, direction] + : [0, 0] + this.transform({ flip: directionString, origin: origin }, true) + }, + + // Opacity + opacity: function (value) { + return this.attr('opacity', value) + }, + + // Relative move over x axis + dx: function (x) { + return this.x(new SVGNumber(x).plus(this instanceof Runner ? 0 : this.x()), true) + }, + + // Relative move over y axis + dy: function (y) { + return this.y(new SVGNumber(y).plus(this instanceof Runner ? 0 : this.y()), true) + }, + + // Relative move over x and y axes + dmove: function (x, y) { + return this.dx(x).dy(y) + } +}) + +registerMethods('radius', { + // Add x and y radius + radius: function (x, y) { + var type = (this._element || this).type + return type === 'radialGradient' || type === 'radialGradient' + ? this.attr('r', new SVGNumber(x)) + : this.rx(x).ry(y == null ? x : y) + } +}) + +registerMethods('Path', { + // Get path length + length: function () { + return this.node.getTotalLength() + }, + // Get point at length + pointAt: function (length) { + return new Point(this.node.getPointAtLength(length)) + } +}) + +registerMethods(['Element', 'Runner'], { + // Set font + font: function (a, v) { + if (typeof a === 'object') { + for (v in a) this.font(v, a[v]) + } + + return a === 'leading' + ? this.leading(v) + : a === 'anchor' + ? this.attr('text-anchor', v) + : a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style' + ? this.attr('font-' + a, v) + : this.attr(a, v) + } +}) diff --git a/src/modules/optional/transform.js b/src/modules/optional/transform.js new file mode 100644 index 0000000..7535fdc --- /dev/null +++ b/src/modules/optional/transform.js @@ -0,0 +1,72 @@ +import { getOrigin } from '../../utils/utils.js' +import { delimiter, transforms } from '../core/regex.js' +import { registerMethods } from '../../utils/methods.js' +import Matrix from '../../types/Matrix.js' + +// Reset all transformations +export function untransform () { + return this.attr('transform', null) +} + +// merge the whole transformation chain into one matrix and returns it +export function matrixify () { + var matrix = (this.attr('transform') || '') + // split transformations + .split(transforms).slice(0, -1).map(function (str) { + // generate key => value pairs + var kv = str.trim().split('(') + return [kv[0], + kv[1].split(delimiter) + .map(function (str) { return parseFloat(str) }) + ] + }) + .reverse() + // merge every transformation into one matrix + .reduce(function (matrix, transform) { + if (transform[0] === 'matrix') { + return matrix.lmultiply(Matrix.fromArray(transform[1])) + } + return matrix[transform[0]].apply(matrix, transform[1]) + }, new Matrix()) + + return matrix +} + +// add an element to another parent without changing the visual representation on the screen +export function toParent (parent) { + if (this === parent) return this + var ctm = this.screenCTM() + var pCtm = parent.screenCTM().inverse() + + this.addTo(parent).untransform().transform(pCtm.multiply(ctm)) + + return this +} + +// same as above with parent equals root-svg +export function toDoc () { + return this.toParent(this.doc()) +} + +// Add transformations +export function transform (o, relative) { + // Act as a getter if no object was passed + if (o == null || typeof o === 'string') { + var decomposed = new Matrix(this).decompose() + return decomposed[o] || decomposed + } + + if (!Matrix.isMatrixLike(o)) { + // Set the origin according to the defined transform + o = { ...o, origin: getOrigin(o, this) } + } + + // The user can pass a boolean, an Element or an Matrix or nothing + var cleanRelative = relative === true ? this : (relative || false) + var result = new Matrix(cleanRelative).transform(o) + return this.attr('transform', result) +} + +registerMethods('Element', { + untransform, matrixify, toParent, toDoc, transform +}) |