From 2270a96f123103946c47e348148fd151d5150370 Mon Sep 17 00:00:00 2001 From: Dmitry Baranovskiy Date: Thu, 11 Jul 2013 13:23:37 +1000 Subject: [PATCH] Added gradients. Changed plugin architecture. --- bg.png | Bin 0 -> 5795 bytes demojs.html | 48 +++++++----- savage.set.js | 13 ++-- svg.js | 192 ++++++++++++++++++++++++++++++++++++++-------- test/test.html | 202 ++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 387 insertions(+), 68 deletions(-) create mode 100644 bg.png diff --git a/bg.png b/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..70b685013033a5dd0861cf036f6b3aae51cb1c4f GIT binary patch literal 5795 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 literal 0 HcmV?d00001 diff --git a/demojs.html b/demojs.html index d74a573..65dee12 100644 --- a/demojs.html +++ b/demojs.html @@ -3,6 +3,11 @@ Savage + @@ -25,31 +30,31 @@ diff --git a/savage.set.js b/savage.set.js index 02b13b6..bac18a2 100644 --- a/savage.set.js +++ b/savage.set.js @@ -1,7 +1,6 @@ -(function () { +Savage.plugin(function (Savage, Element, Paper, glob) { var mmax = Math.max, - mmin = Math.min, - g = eve("savage.globals")[0]; + mmin = Math.min; // Set var Set = function (items) { @@ -71,9 +70,9 @@ } return this; }; - setproto.attr = function (name, value) { + setproto.attr = function (value) { for (var i = 0, ii = this.items.length; i < ii; i++) { - this.items[i].attr(name, value); + this.items[i].attr(value); } return this; }; @@ -190,11 +189,11 @@ }; setproto.type = "set"; // export - g.savage.set = function () { + Savage.set = function () { var set = new Set; if (arguments.length) { set.push.apply(set, Array.prototype.slice.call(arguments, 0)); } return set; }; -})(); \ No newline at end of file +}); \ No newline at end of file diff --git a/svg.js b/svg.js index 5bd6fb9..045aba3 100644 --- a/svg.js +++ b/svg.js @@ -11,6 +11,8 @@ var Savage = function (w, h) { return new Element(glob.doc.querySelector(w)); } } + w = w == null ? "100%" : w; + h = h == null ? "100%" : h; return new Paper(w, h); }; var glob = { @@ -38,13 +40,12 @@ var has = "hasOwnProperty", spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029", separator = new RegExp("[," + spaces + "]+"), whitespace = new RegExp("[" + spaces + "]", "g"), - commaSpaces = new RegExp("[" + spaces + "]*,[" + spaces + "*]"), + commaSpaces = new RegExp("[" + spaces + "]*,[" + spaces + "]*"), hsrg = {hs: 1, rg: 1}, p2s = /,?([a-z]),?/gi, pathCommand = new RegExp("([a-z])[" + spaces + ",]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[" + spaces + "]*,?[" + spaces + "]*)+)", "ig"), tCommand = new RegExp("([rstm])[" + spaces + ",]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[" + spaces + "]*,?[" + spaces + "]*)+)", "ig"), pathValues = new RegExp("(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[" + spaces + "]*,?[" + spaces + "]*", "ig"), - radial_gradient = new RegExp("^r(?:\\(([^,]+?)[" + spaces + "]*,[" + spaces + "]*([^\\)]+?)\\))?"), idgen = 0, idprefix = "S" + (+new Date).toString(36), ID = function () { @@ -109,6 +110,24 @@ function is(o, type) { (type == "array" && Array.isArray && Array.isArray(o)) || objectToString.call(o).slice(8, -1).toLowerCase() == type; } +var preload = (function () { + function onerror() { + this.parentNode.removeChild(this); + } + return function (src, f) { + var img = glob.doc.createElement("img"), + body = glob.doc.body; + img.style.cssText = "position:absolute;left:-9999em;top:-9999em"; + img.onload = function () { + f.call(img); + img.onload = img.onerror = null; + body.removeChild(img); + }; + img.onerror = onerror; + body.appendChild(img); + img.src = src; + }; +}()); function clone(obj) { if (typeof obj == "function" || Object(obj) !== obj) { return obj; @@ -425,7 +444,7 @@ Savage.Matrix = Matrix; #
  • Colour name (“red”, “green”, “cornflowerblue”, etc)
  • #
  • #••• — shortened HTML colour: (“#000”, “#fc0”, etc)
  • #
  • #•••••• — full length HTML colour: (“#000000”, “#bd2300”)
  • - #
  • rgb(•••, •••, •••) — red, green and blue channels’ values: (“rgb(200, 100, 0)”)
  • + #
  • rgb(•••, •••, •••) — red, green and blue channels values: (“rgb(200, 100, 0)”)
  • #
  • rgb(•••%, •••%, •••%) — same as above, but in %: (“rgb(100%, 175%, 0%)”)
  • #
  • hsb(•••, •••, •••) — hue, saturation and brightness values: (“hsb(0.5, 0.25, 1)”)
  • #
  • hsb(•••%, •••%, •••%) — same as above, but in %
  • @@ -438,7 +457,7 @@ Savage.Matrix = Matrix; o g (number) green, o b (number) blue o hex (string) color in HTML/CSS format: #••••••, - o error (boolean) true if string can’t be parsed + o error (boolean) true if string cant be parsed o } \*/ Savage.getRGB = cacher(function (colour) { @@ -571,7 +590,9 @@ hsltoString = function () { return "hsl(" + [this.h, this.s, this.l] + ")"; }, rgbtoString = function () { - return this.hex; + return this.opacity == 1 || this.opacity == null ? + this.hex : + "rgba(" + [this.r, this.g, this.b, this.opacity] + ")"; }, prepareRGB = function (r, g, b) { if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) { @@ -594,9 +615,9 @@ prepareRGB = function (r, g, b) { return [r, g, b]; }, packageRGB = function (r, g, b, o) { - r *= 255; - g *= 255; - b *= 255; + r = math.round(r * 255); + g = math.round(g * 255); + b = math.round(b * 255); var rgb = { r: r, g: g, @@ -621,7 +642,7 @@ packageRGB = function (r, g, b, o) { o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #••••••, - o error (boolean) `true` if string can’t be parsed, + o error (boolean) `true` if string cant be parsed, o h (number) hue, o s (number) saturation, o v (number) value (brightness), @@ -1696,11 +1717,20 @@ function Element(el) { el.savage = id; hub[id] = this; } +function arrayFirstValue(arr) { + var res; + for (var i = 0, ii = arr.length; i < ii; i++) { + res = res || arr[i]; + if (res) { + return res; + } + } +} (function (elproto) { elproto.attr = function (params) { var node = this.node; if (is(params, "string")) { - return eve("savage.util.getattr." + params, this)[0]; + return arrayFirstValue(eve("savage.util.getattr." + params, this)); } for (var att in params) { if (params[has](att)) { @@ -1883,6 +1913,31 @@ function Element(el) { p.node.appendChild(this.node); return p; }; + elproto.marker = function (x, y, width, height, refX, refY) { + var p = make("marker", this.paper.defs); + if (x == null) { + x = this.getBBox(); + } + if (x && "x" in x) { + y = x.y; + width = x.width; + height = x.height; + refX = x.refX; + refY = x.refY; + x = x.x; + } + $(p.node, { + viewBox: [x, y, width, height].join(S), + markerWidth: width, + markerHeight: height, + orient: "auto", + refX: refX || 0, + refY: refY || 0, + id: p.id + }); + p.node.appendChild(this.node); + return p; + }; // animation function applyAttr(el, key) { var at = {}; @@ -2026,6 +2081,38 @@ function wrap(dom) { } return el; }; + proto.image = function (src, x, y, width, height) { + var el = make("image", this.node); + if (is(src, "object") && "src" in src) { + el.attr(src); + } else if (src != null) { + var set = { + "xlink:href": src, + preserveAspectRatio: "none" + }; + if (x != null && y != null) { + set.x = x; + set.y = y; + } + if (width != null && height != null) { + set.width = width; + set.height = height; + } else { + preload(src, function () { + $(el.node, { + width: this.offsetWidth, + height: this.offsetHeight + }); + }); + } + $(el.node, set); + } + return el; + }; + proto.custom = function (name) { + var el = make(name, this.node); + return el; + }; proto.ellipse = function (cx, cy, rx, ry) { var el = make("ellipse", this.node); if (is(cx, "object") && "cx" in cx) { @@ -2139,7 +2226,7 @@ function wrap(dom) { // gradients (function () { proto.gradient = function (str) { - var grad = eve("savage.util.grad.parse", null, str)[0], + var grad = arrayFirstValue(eve("savage.util.grad.parse", null, str)), el; if (grad.type.toLowerCase() == "l") { el = this.gradientLinear.apply(this, grad.params); @@ -2147,7 +2234,7 @@ function wrap(dom) { el = this.gradientRadial.apply(this, grad.params); } if (grad.type != grad.type.toLowerCase()) { - el.attr({ + $(el.node, { gradientUnits: "userSpaceOnUse" }); } @@ -2195,9 +2282,9 @@ function wrap(dom) { y2 = $(this.node, "y2") || 0; return box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1)); } else { - var cx = this.node.cx, - cy = this.node.cy, - r = this.node.r; + var cx = this.node.cx || .5, + cy = this.node.cy || .5, + r = this.node.r || 0; return box(cx - r, cy - r, r * 2, r * 2); } } @@ -2216,6 +2303,26 @@ function wrap(dom) { } return el; }; + proto.gradientRadial = function (cx, cy, r, fx, fy) { + var el = make("radialGradient", this.node); + el.stops = stops; + el.addStop = addStop; + el.getBBox = getBBox; + if (cx != null) { + $(el.node, { + cx: cx, + cy: cy, + r: r + }); + } + if (fx != null && fy != null) { + $(el.node, { + fx: fx, + fy: fy + }); + } + return el; + }; }()); }(Paper.prototype)); // Attributes event handlers @@ -2351,6 +2458,15 @@ eve.on("savage.util.attr.transform", function (value) { this.transform(value); eve.stop(); }); +eve.on("savage.util.attr.r", function (value) { + if (this.type == "rect") { + eve.stop(); + $(this.node, { + rx: value, + ry: value + }); + } +}); eve.on("savage.util.attr.text", function (value) { if (this.type == "text") { var i = 0, @@ -2376,15 +2492,24 @@ eve.on("savage.util.attr.text", function (value) { }); // default var availableAttributes = { - rect: {x: 0, y: 0, width: 0, height: 0, rx: 0, ry: 0}, - circle: {cx: 0, cy: 0, r: 0}, - ellipse: {cx: 0, cy: 0, rx: 0, ry: 0}, - line: {x1: 0, y1: 0, x2: 0, y2: 0}, - polyline: {points: ""}, - polygon: {points: ""}, - text: {x: 0, y: 0, dx: 0, dy: 0, rotate: 0, textLength: 0}, - tspan: {x: 0, y: 0, dx: 0, dy: 0, rotate: 0, textLength: 0}, - path: {d: ""} + rect: {x: 0, y: 0, width: 0, height: 0, rx: 0, ry: 0, "class": 0}, + circle: {cx: 0, cy: 0, r: 0, "class": 0}, + ellipse: {cx: 0, cy: 0, rx: 0, ry: 0, "class": 0}, + line: {x1: 0, y1: 0, x2: 0, y2: 0, "class": 0}, + polyline: {points: "", "class": 0}, + polygon: {points: "", "class": 0}, + text: {x: 0, y: 0, dx: 0, dy: 0, rotate: 0, textLength: 0, lengthAdjust: 0, "class": 0}, + tspan: {x: 0, y: 0, dx: 0, dy: 0, rotate: 0, textLength: 0, lengthAdjust: 0, "class": 0}, + textPath: {"xlink:href": 0, startOffset: 0, method: 0, spacing: 0, "class": 0}, + marker: {viewBox: 0, preserveAspectRatio: 0, refX: 0, refY: 0, markerUnits: 0, markerWidth: 0, markerHeight: 0, orient: 0, "class": 0}, + linearGradient: {x1: 0, y1: 0, x2: 0, y2: 0, gradientUnits: 0, gradientTransform: 0, spreadMethod: 0, "xlink:href": 0, "class": 0}, + radialGradient: {cx: 0, cy: 0, r: 0, fx: 0, fy: 0, gradientUnits: 0, gradientTransform: 0, spreadMethod: 0, "xlink:href": 0, "class": 0}, + stop: {offset: 0, "class": 0}, + pattern: {viewBox: 0, preserveAspectRatio: 0, x: 0, y: 0, width: 0, height: 0, patternUnits: 0, patternContentUnits: 0, patternTransform: 0, "xlink:href": 0, "class": 0}, + clipPath: {transform: 0, clipPathUnits: 0, "class": 0}, + mask: {x: 0, y: 0, width: 0, height: 0, maskUnits: 0, maskContentUnits: 0, "class": 0}, + image: {preserveAspectRatio: 0, transform: 0, x: 0, y: 0, width: 0, height: 0, "xlink:href": 0, "class": 0}, + path: {d: "", "class": 0} }; eve.on("savage.util.attr", function (value) { var att = eve.nt(); @@ -2403,6 +2528,12 @@ eve.on("savage.util.getattr.transform", function () { eve.stop(); return this.transform(); })(-1); +eve.on("savage.util.getattr.r", function () { + if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) { + eve.stop(); + return $(this.node, "rx"); + } +})(-1); eve.on("savage.util.getattr.viewBox", function () { eve.stop(); var vb = $(this.node, "viewBox").split(separator); @@ -2424,16 +2555,11 @@ eve.on("savage.util.getattr", function () { if (availableAttributes[has](this.type) && availableAttributes[this.type][has](att)) { return this.node.getAttribute(att); } else { - return document.defaultView.getComputedStyle(this.node, null).getPropertyValue(style); + return glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue(style); } }); -eve.on("savage.globals", function () { - return { - savage: Savage, - element: Element, - paper: Paper, - glob: glob - }; -}); +Savage.plugin = function (f) { + f(Savage, Element, Paper, glob); +}; return Savage; }()); \ No newline at end of file diff --git a/test/test.html b/test/test.html index 410f9ad..7fb828e 100644 --- a/test/test.html +++ b/test/test.html @@ -3,19 +3,25 @@ Savage Tests - - - - - - + + + + + + +
    - - - - + + +