From: Edward Date: Fri, 18 Jan 2019 07:12:17 +0000 (+0800) Subject: Fix #5648 - New design for post release report X-Git-Url: http://git.roojs.org/?p=g.raphael;a=commitdiff_plain;h=c7319c9a3fb8497aa8fcf2c307969eb9b1066d7c Fix #5648 - New design for post release report --- diff --git a/g.bar.0.51.js b/g.bar.0.51.js index 796ef66..726c8a1 100644 --- a/g.bar.0.51.js +++ b/g.bar.0.51.js @@ -227,7 +227,6 @@ if (typeof(Raphael) == 'undefined') { } opts.axis = opts.axis || ""; - var allx = Array.prototype.concat.apply([], opts.xvalues), ally = Array.prototype.concat.apply([], opts.yvalues), diff --git a/g.bar.overlay.js b/g.bar.overlay.js new file mode 100644 index 0000000..fae7ede --- /dev/null +++ b/g.bar.overlay.js @@ -0,0 +1,241 @@ +/*! + * g.Raphael 0.51 - Charting library, based on Raphaël + * + * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com) + * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. + */ + +if (typeof(Raphael) == 'undefined') { + // support for seed/simple browser usage + importz = imports['seed/importz.js'].importz; + + Raphael = importz('Raphael'); + Roo = importz('Roo'); +} + +(function () { + + function clearAr(ar) { + var ret = new Array(); + //Roo.log(JSON.stringify(ar)); + for (var i = 0; i < ar.length; i++) { + //print(typeof(ar[i])); + if (Raphael.is(ar[i], "object")) { + ret.push(clearAr(ar[i])); + continue; + } + ret.push(parseInt(ar[i])); + } + return ret; + } + + +/*\ + * Paper.vbarchart + [ method ] + ** + * Creates a vertical bar chart + ** + > Parameters + ** + - x (number) x coordinate of the chart + - y (number) y coordinate of the chart + - width (number) width of the chart (respected by all elements in the set) + - height (number) height of the chart (respected by all elements in the set) + - values (array) values + - opts (object) options for the chart + o { + o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'. + o gutter (number)(string) default '20%' (WHAT DOES IT DO?) + o vgutter (number) + o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color. + o stacked (boolean) whether or not to tread values as in a stacked bar chart + o to + o stretch (boolean) + o } + ** + = (object) path element of the popup + > Usage + | r.vbarchart(0, 0, 620, 260, [[76, 70], [67, 71]], {}) + \*/ + + + function MVBarchart(paper, x, y, width, height, values, opts) { + opts = opts || {}; + values = clearAr(values); + + var chartinst = this, + chart = paper.set(), + bars = paper.set(), + covers = paper.set(), + barsteps = opts.barsteps || 20, + colors = opts.colors || chartinst.colors, + max = 3000; + + opts.axis = opts.axis || ""; + opts.baroffset = opts.baroffset || 50; + opts.asixxheight = opts.asixxheight || 100; + opts.asixywidth = opts.asixywidth || 100; + opts.axisxlabels = opts.axisxlabels || []; + + // background + paper.rect(0, 0, width, height).attr({ stroke: "none", fill: (opts.background || "#F0F4F7") }); +// background.toBack(); + + // bar border + var barwidth = Math.floor((width - x - opts.asixywidth - opts.baroffset) / barsteps); + var barheight = height - y - opts.asixxheight || 100; + var path = ["M", x + 1, y, "l", 0, barheight] + + for (var i = 0; i < barsteps; i++) { + path = path.concat(["M", x + (i * barwidth), y + 1, "l", barwidth, 0, "l", 0, barheight]); + } + + paper.path(path).attr({ stroke: "#fff", "stroke-width": 2 }); + + //bars + var unit = barheight / max; + var indicator = []; + values.forEach((value,i) => { + if (!Raphael.is(values[0], "array")) { + value = [value]; + } + + value.forEach((v,k) => { + paper.rect(x + 1 + (k * barwidth) , y + (max - v) * unit, barwidth - 2, v * unit).attr({ stroke: "none", fill: colors[i%colors.length] }); + paper.rect(x + 1 + (k * barwidth) + barwidth / 4 , y + (max - v) * unit - 42, barwidth / 2, 30, 5).attr({ stroke: "none", fill: "#0C014D" }); + indicator = indicator.concat(["M", x + 1 + (k * barwidth) + barwidth / 4 + 10, y + (max - v) * unit - 12, "l", 5, 10, "l", 5, -10]); + paper.text(x + 1 + (k * barwidth) + barwidth / 2, y + (max - v) * unit - 28, Roo.util.Format.number(v, 0)).attr({ + "font-size": "16", + "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + "font-weight": "bold", + fill : "#fff" + }); + }); + + }); + + paper.path(indicator).attr({ stroke: "none", fill: "#0C014D" }); + + var ax = (opts.axis + "").split(/[,\s]+/), + axis = paper.set(); + + // right axis + +ax[1] && axis.push( + chartinst.rightAxis( + x + width - opts.asixywidth , + y + height - opts.asixxheight, + height - y - opts.asixxheight, + max, + opts.axisystep || 10, + opts.asixywidth, + paper + ) + ); + + // bottom axis + +ax[2] && axis.push( + chartinst.bottomAxis( + x , + height - opts.asixxheight, + width, + opts.axisxlabels.length, + opts.axisxlabels, + barwidth, + opts.asixxheight, + paper + ) + ); + + chart.push(bars, covers); + chart.bars = bars; + chart.covers = covers; + return chart; + }; + + //inheritance + var F = function() {}; + F.prototype = Raphael.g; + MVBarchart.prototype = new F; //prototype reused by hbarchart + + Raphael.fn.mbarchart = function(x, y, width, height, values, opts) { + return new MVBarchart(this, x, y, width, height, values, opts); + }; + + MVBarchart.prototype.rightAxis = function (x, y, length, max, steps, ewidth, paper) + { +// Roo.log('Right Axis'); +// Roo.log([x, y, length, max, steps, ewidth]); + + var path = [], + color = "#bababa", + text = paper.set(), + d = Math.ceil(max / steps); + + var label = 0, + dx = length / steps; + + var Y = y; + + for(var i = 0; i <= steps; i++) { + + if(i != 0) { + paper.text(x + (ewidth / 2), Y + 15, label).attr({ + "font-size": "20", + "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + "font-weight": "bold", + "text-anchor": "end", + fill : color + }); + path = path.concat(["M", x, Y, "l", (ewidth / 2), 0]); + + } + + label += d; + Y -= dx; + } + + var res = paper.path(path).attr({ stroke: color, "stroke-width": 2 }); + + res.text = text; + res.all = paper.set([res, text]); + res.remove = function () { + this.text.remove(); + this.constructor.prototype.remove.call(this); + }; + + return res; + } + + MVBarchart.prototype.bottomAxis = function (x, y, length, steps, labels, barwidth, eheight, paper) + { +// Roo.log('Bottom Axis'); +// Roo.log([x, y, length, steps, labels, barwidth]); + + var path = ["M", x, y, "l", length, 0], + color = "#bababa", + text = paper.set(), + offset = Math.round(barwidth / 2); + + labels.forEach((v,k) => { + paper.text(x + (k * barwidth) + offset, y + (eheight / 2), v).attr({ + "font-size": "20", + "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + "font-weight": "bold", + fill : color + }); + }); + + var res = paper.path(path).attr({ stroke: color, "stroke-width": 2 }); + + res.text = text; + res.all = paper.set([res, text]); + res.remove = function () { + this.text.remove(); + this.constructor.prototype.remove.call(this); + }; + + return res; + } + +})(); \ No newline at end of file diff --git a/g.bar.split.js b/g.bar.split.js new file mode 100644 index 0000000..7c2c56e --- /dev/null +++ b/g.bar.split.js @@ -0,0 +1,88 @@ +/* + * g.Raphael 0.5 - Charting library, based on Raphaël + * + * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com) + * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. + */ +Raphael = typeof(Raphael) != 'undefined' ? Raphael : (imports ? imports.seed.Raphael.Raphael : {}); +Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); +//chartinst = typeof(chartinst) != 'undefined' ? chartinst: (imports ? imports.chartinst.chartinst : {}); + + + +(function () { + + /** + * @param {Raphael} paper to draw on + * @param {int} cx - centre X + * @param {int} cy - centre Y + * @param {int} r - radius + * @param {Array} values + * @param {Object} opts options + * cut : after this meany items - do not show a pie element? + * + * + * + */ + + function Barsplitchart(paper, width, height, values, opts) { + + opts = opts || {}; + + var chartinst = this, + chart = paper.set(); + + opts.top = opts.top || 50; + opts.bottom = opts.bottom || 50; + opts.left = opts.left || 20; + opts.right = opts.right || 20; + opts.barwidth = opts.barwidth || 100; + + paper.rect(0, 0, width, height).attr({ stroke: "none", fill: (opts.background || "#F0F4F7") }); + + var cw = width - opts.left - opts.right, + ch = height - opts.top - opts.bottom; + + paper.rect(opts.left, opts.top, opts.barwidth, ch).attr({ stroke: "#CCCCCC", "stroke-dasharray": "--" }); + paper.rect(opts.left + cw / 2, opts.top, opts.barwidth, ch).attr({ stroke: "#CCCCCC", "stroke-dasharray": "--" }); + + values.forEach((v,k) => { + v = Math.min(100, v); + var bh = v * ch / 100, + bx = (k == 0) ? opts.left : opts.left + cw / 2, + by = opts.top + ch - bh; + + paper.rect(bx, by, opts.barwidth, bh).attr({ stroke: "none", fill: "#0A2BC4" }); + + paper.rect(bx + opts.barwidth / 4 , by - 42, opts.barwidth / 2, 30, 5).attr({ stroke: "none", fill: "#0C014D" }); + paper.path(["M", bx + opts.barwidth / 4 + 10, by - 12, "l", 5, 10, "l", 5, -10]).attr({ stroke: "none", fill: "#0C014D" }); + paper.text(bx + opts.barwidth / 4 + 25, by - 28, Roo.util.Format.number(v, 0) + '%').attr({ + "font-size": "16", + "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + "font-weight": "bold", + fill : "#fff" + }); + + paper.text(bx + opts.barwidth + 25, opts.top + 50, opts.legend[k]).attr({ + "font-size": "22", + "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + "font-weight": "bold", + "text-anchor": "start", + fill : "#0C024B" + }); + }); + + return chart; + }; + + //inheritance + var F = function() {}; + F.prototype = Raphael.g; + Barsplitchart.prototype = new F; + + //public + Raphael.fn.barsplitchart = function(width, height, values, opts) { + return new Barsplitchart(this, width, height, values, opts); + } + +})(); diff --git a/g.pie.circular.js b/g.pie.circular.js new file mode 100644 index 0000000..f90083c --- /dev/null +++ b/g.pie.circular.js @@ -0,0 +1,103 @@ +/* + * g.Raphael 0.5 - Charting library, based on Raphaël + * + * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com) + * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. + */ +Raphael = typeof(Raphael) != 'undefined' ? Raphael : (imports ? imports.seed.Raphael.Raphael : {}); +Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); +//chartinst = typeof(chartinst) != 'undefined' ? chartinst: (imports ? imports.chartinst.chartinst : {}); + + + +(function () { + + /** + * @param {Raphael} paper to draw on + * @param {int} cx - centre X + * @param {int} cy - centre Y + * @param {int} r - radius + * @param {Array} values + * @param {Object} opts options + * cut : after this meany items - do not show a pie element? + * + * + * + */ + + function Piecircularchart(paper, width, height, cx, cy, r, values, opts) { + + opts = opts || {}; + + var chartinst = this, + chart = paper.set(), + len = values.length; + + opts.linewidth = opts.linewidth || 200; + + paper.rect(0, 0, width, height).attr({ stroke: "none", fill: (opts.background || "#F0F4F7") }); + + for (var i = 0; i < len; i++) { + values[i] = { + value: values[i], + origin: i, + valueOf: function () { return this.value; } + }; + } + + values.sort(function (a, b) { + return b.value - a.value; + }); + + var dx = cx, + dy = cy, + dr = r; + + for (i = 0; i < len; i++) { + + paper.circle(dx, dy, dr).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#3E66BC", stroke: "#fff", "stroke-width": 2 }); + + var nx = dx, + ny = dy - dr + 20; + + if(i != 0) { + ny = dy - dr - 20; + } + + paper.text(nx, ny, Roo.util.Format.number(values[i], 0)).attr({ + "font-size": "18", + "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + "font-weight": "bold", + fill : "#fff" + }); + + paper.path(["M", dx, dy - dr, "l", opts.linewidth, 0]).attr({ stroke: "#fff", "stroke-width": 2 }); + paper.text(dx + opts.linewidth + 10, dy - dr, opts.legend[values[i].origin]).attr({ + "font-size": "18", + "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + "text-anchor": "start", + fill : "#0E1587" + }); + + dy = dy + dr / 4 *3; + dr = Math.round(dr / 4); + + } + + chart.cx = cx; + chart.cy = cy; + chart.r = r; + return chart; + }; + + //inheritance + var F = function() {}; + F.prototype = Raphael.g; + Piecircularchart.prototype = new F; + + //public + Raphael.fn.piecircularchart = function(width, height, cx, cy, r, values, opts) { + return new Piecircularchart(this, width, height, cx, cy, r, values, opts); + } + +})(); diff --git a/g.pie.sector.js b/g.pie.sector.js new file mode 100644 index 0000000..c0bb4d9 --- /dev/null +++ b/g.pie.sector.js @@ -0,0 +1,146 @@ +/* + * g.Raphael 0.5 - Charting library, based on Raphaël + * + * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com) + * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. + */ +Raphael = typeof(Raphael) != 'undefined' ? Raphael : (imports ? imports.seed.Raphael.Raphael : {}); +Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); +//chartinst = typeof(chartinst) != 'undefined' ? chartinst: (imports ? imports.chartinst.chartinst : {}); + + + +(function () { + + /** + * @param {Raphael} paper to draw on + * @param {int} cx - centre X + * @param {int} cy - centre Y + * @param {int} r - radius + * @param {Array} values + * @param {Object} opts options + * cut : after this meany items - do not show a pie element? + * + * + * + */ + + function Piesectorchart(paper, width, height, cx, cy, r, values, opts) { + + opts = opts || {}; + + var chartinst = this, + chart = paper.set(), + len = values.length, + angle = opts.start_angle || 90, + total = 0, + others = 0, + cut = opts.cut || 9, + defcut = true; + + opts.barwidth = opts.barwidth || 80; + + paper.rect(0, 0, width, height).attr({ stroke: "none", fill: (opts.background || "#F0F4F7") }); + + paper.customAttributes.sector = function (cx, cy, startAngle, endAngle, color, R) { + + var rad = Math.PI / 180, + x1 = cx + r * Math.cos(-startAngle * rad), + x2 = cx + r * Math.cos(-endAngle * rad), + y1 = cy + r * Math.sin(-startAngle * rad), + y2 = cy + r * Math.sin(-endAngle * rad), + path; + + path = [["M", x1, y1], ["A", R, R, 0, +(Math.abs(endAngle - startAngle) > 180), 1, x2, y2]]; + + return {path: path, stroke: color}; + }; + + if (len == 1) { + total = values[0]; + paper.circle(cx, cy, r + opts.barwidth / 2).attr({ fill: opts.colors && opts.colors[0] || chartinst.colors[0] || "#3E66BC" }); + paper.circle(cx, cy, r - opts.barwidth / 2).attr({ fill: opts.background || "#F0F4F7" }); + + } else { + + for (var i = 0; i < len; i++) { + total += values[i] * 1; + values[i] = { + value: values[i], + valueOf: function () { return this.value; } + }; + } + + if (!opts.no_sort) { + values.sort(function (a, b) { + return b.value - a.value; + }); + } + + for (i = 0; i < len; i++) { + if (defcut && values[i] * 360 / total <= 1.5) { + cut = i; + defcut = false; + } + + if (i > cut) { + defcut = false; + values[cut].value += values[i]; + values[cut].others = true; + } + } + + len = Math.min(cut + 1, values.length); + + for (i = 0; i < len; i++) { + + var p = paper.path().attr({ + "stroke": "#fff", + "stroke-width": opts.barwidth + }).attr({sector: [cx, cy, angle, angle -= 360 * values[i] / total, opts.colors && opts.colors[i] || chartinst.colors[i], r]}); + + } + + } + + var ix = cx + r + opts.barwidth / 2 + 30, + iy = cy - r - 30; + + for (var i = 0; i < len; i++) { + + paper.circle(ix, iy, 6).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#3E66BC" }); + + var text = (values[i].others) ? opts.others : opts.legend[i] || values[i]; + + if(text.indexOf('#%#') !== -1) { + text = text.replace('#%#', Math.round(values[i] / total * 100) + '%'); + } + + paper.text(ix + 20, iy, text).attr({ + "font-size": "18", + "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + "text-anchor": "start", + fill : "#0C014F" + }); + + iy += 30; + + } + + chart.cx = cx; + chart.cy = cy; + chart.r = r; + return chart; + }; + + //inheritance + var F = function() {}; + F.prototype = Raphael.g; + Piesectorchart.prototype = new F; + + //public + Raphael.fn.piesectorchart = function(width, height, cx, cy, r, values, opts) { + return new Piesectorchart(this, width, height, cx, cy, r, values, opts); + } + +})(); diff --git a/seed/setFillAndStroke.js b/seed/setFillAndStroke.js index d64e06a..3f1f4dc 100644 --- a/seed/setFillAndStroke.js +++ b/seed/setFillAndStroke.js @@ -15,8 +15,6 @@ var has = "hasOwnProperty"; FakeDom(o.pattern, {patternTransform: o.matrix.invert() + " translate(" + bbox.x + "," + bbox.y + ")"}); } - - function tuneText (el, params) { if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) { return; @@ -63,6 +61,91 @@ function tuneText (el, params) { dif && R.is(dif, "finite") && FakeDom(tspans[0], {dy: dif}); } +function addDashes(o, value, params) { + + var dasharray = { + // In Firefox 37.0.1 the value of "stroke-dasharray" attribute `0` make the stroke/border invisible. + // The actual issue is setting `none` as the value of `stroke-dasharray` attribute + // redraphael internally changes the "none" value to "0", thus the stroke/border becomes invisible + // To fix this issue now instead of setting the value as `0` for `stroke-dasharray` attribute + // now using `none` string as none is a w3c standard value for stroke-dasharray + "": ["none"], + "none": ["none"], + "-": [3, 1], + ".": [1, 1], + "-.": [3, 1, 1, 1], + "-..": [3, 1, 1, 1, 1, 1], + ". ": [1, 3], + "- ": [4, 3], + "--": [8, 3], + "- .": [4, 3, 1, 3], + "--.": [8, 3, 1, 3], + "--..": [8, 3, 1, 3, 1, 3] + }; + + var $ = R._createNode = function(el, attr) { + if (attr) { + if (typeof el == "string") { + el = $(el); + } + for (var key in attr) + if (attr.hasOwnProperty(key)) { + if (key.substring(0, 6) == "xlink:") { + // setAttributeNS setAttribute won't works.. +// el.setAttributeNS("http://www.w3.org/1999/xlink", key.substring(6), String(attr[key])); + el.attributes[key.substring(6)] = String(attr[key]); + } else { +// el.setAttribute(key, String(attr[key])); + el.attributes[key] = String(attr[key]); + } + } + } else { + el = R._g.doc.createElementNS("http://www.w3.org/2000/svg", el); + } + return el; + } + + var predefValue = dasharray[String(value).toLowerCase()], + calculatedValues, + width, + butt, + i, + l, + widthFactor; + + value = predefValue || ((value !== undefined) && [].concat(value)); + if (value) { + + width = o.attrs["stroke-width"] || 1; + butt = { + round: width, + square: width, + butt: 0 + }[params["stroke-linecap"] || o.attrs["stroke-linecap"]] || 0; + l = i = value.length; + widthFactor = predefValue ? width : 1; + + if (value[0] == 'none') { + calculatedValues = value; + } else { + calculatedValues = []; + while (i--) { + calculatedValues[i] = (value[i] * widthFactor + ((i % 2) ? 1 : -1) * butt); + calculatedValues[i] <= 0 && (calculatedValues[i] = 0.01 + (width <= 1 ? butt : 0)); + if (isNaN(calculatedValues[i])) { + calculatedValues[i] = 0; + } + } + } + + if (R.is(value, 'array')) { + $(o.node, { + "stroke-dasharray": calculatedValues.join(",") + }); + } + } +} + setFillAndStroke = function (o, params) { //Roo.log(JSON.stringify(params)); diff --git a/seed/toSVG.js b/seed/toSVG.js index 0816c6a..facc7c5 100644 --- a/seed/toSVG.js +++ b/seed/toSVG.js @@ -9,241 +9,250 @@ importz = imports['seed/importz.js'].importz; Raphael = importz('Raphael'); Roo = importz('Roo'); - - /** - * Escapes string for XML interpolation - * @param value string or number value to escape - * @returns string escaped - */ - function escapeXML(s) { - if ( typeof s === 'number' ) return s.toString(); - - var replace = { - '&': 'amp', - '<': 'lt', - '>': 'gt', - '"': 'quot', - '\'': 'apos' - }; - - for ( var entity in replace ) { - s = s.replace(new RegExp(entity, 'g'), '&' + replace[entity] + ';'); - } - - return s; - } - - /** - * Generic map function - * @param iterable the array or object to be mapped - * @param callback the callback function(element, key) - * @returns array - */ - function map(iterable, callback) { - var mapped = new Array; - - for ( var i in iterable ) { - if ( iterable.hasOwnProperty(i) ) { - var value = callback.call(this, iterable[i], i); - - if ( value !== null ) mapped.push(value); - } - } - - return mapped; - } - - /** - * Generic reduce function - * @param iterable array or object to be reduced - * @param callback the callback function(initial, element, i) - * @param initial the initial value - * @return the reduced value - */ - function reduce(iterable, callback, initial) { - for ( var i in iterable ) { - if ( iterable.hasOwnProperty(i) ) { - initial = callback.call(this, initial, iterable[i], i); - } - } - - return initial; - } - - /** - * Utility method for creating a tag - * @param name the tag name, e.g., 'text' - * @param attrs the attribute string, e.g., name1="val1" name2="val2" - * or attribute map, e.g., { name1 : 'val1', name2 : 'val2' } - * @param content the content string inside the tag - * @returns string of the tag - */ - function tag(name, attrs, matrix, content) { - if ( typeof content === 'undefined' || content === null ) { - content = ''; - } - - if ( typeof attrs === 'object' ) { - attrs = map(attrs, function(element, name) { - if ( name === 'transform') return; - - return name + '="' + escapeXML(element) + '"'; - }).join(' '); - } - - return '<' + name + ( matrix ? ' transform="matrix(' + matrix.toString().replace(/^matrix\(|\)$/g, '') + ')" ' : ' ' ) + attrs + '>' + - content + + +/** + * Escapes string for XML interpolation + * @param value string or number value to escape + * @returns string escaped + */ +function escapeXML(s) { + if (typeof s === 'number') + return s.toString(); + + var replace = { + '&': 'amp', + '<': 'lt', + '>': 'gt', + '"': 'quot', + '\'': 'apos' + }; + + for (var entity in replace) { + s = s.replace(new RegExp(entity, 'g'), '&' + replace[entity] + ';'); + } + + return s; +} + +/** + * Generic map function + * @param iterable the array or object to be mapped + * @param callback the callback function(element, key) + * @returns array + */ +function map(iterable, callback) { + var mapped = new Array; + + for (var i in iterable) { + if (iterable.hasOwnProperty(i)) { + var value = callback.call(this, iterable[i], i); + + if (value !== null) + mapped.push(value); + } + } + + return mapped; +} + +/** + * Generic reduce function + * @param iterable array or object to be reduced + * @param callback the callback function(initial, element, i) + * @param initial the initial value + * @return the reduced value + */ +function reduce(iterable, callback, initial) { + for (var i in iterable) { + if (iterable.hasOwnProperty(i)) { + initial = callback.call(this, initial, iterable[i], i); + } + } + + return initial; +} + +/** + * Utility method for creating a tag + * @param name the tag name, e.g., 'text' + * @param attrs the attribute string, e.g., name1="val1" name2="val2" + * or attribute map, e.g., { name1 : 'val1', name2 : 'val2' } + * @param content the content string inside the tag + * @returns string of the tag + */ +function tag(name, attrs, matrix, content) { + if (typeof content === 'undefined' || content === null) { + content = ''; + } + + if (typeof attrs === 'object') { + attrs = map(attrs, function (element, name) { + if (name === 'transform') + return; + + return name + '="' + escapeXML(element) + '"'; + }).join(' '); + } + + return '<' + name + (matrix ? ' transform="matrix(' + matrix.toString().replace(/^matrix\(|\)$/g, '') + ')" ' : ' ') + attrs + '>' + + content + '' + "\n"; - } - - /** - * @return object the style object - */ - function extractStyle(node) { - //Roo.log(JSON.stringify(style)); - return { - font: { - family: typeof node.attrs['font-family'] === 'undefined' ? null :node.attrs['font-family'], - size: typeof node.attrs['font-size'] === 'undefined' ? null : (node.attrs['font-size']), - anchor : typeof node.attrs['text-anchor'] === 'undefined' ? null : node.attrs['text-anchor'], - } - }; - } - - /** - * @param style object from style() - * @return string - */ - function styleToString(style) { - // TODO figure out what is 'normal' - //Roo.log(JSON.stringify(style)); - var r = [ - 'font-family:' + style.font.family, - 'font-weight:normal', - 'font-style:normal', - 'font-stretch:normal', - 'font-variant:normal' - ]; - if (style.font.size !== null ) { - r.push('font-size: ' + style.font.size + 'px') +} + +/** + * @return object the style object + */ +function extractStyle(node) { + //Roo.log(JSON.stringify(style)); + return { + font: { + family: typeof node.attrs['font-family'] === 'undefined' ? null : node.attrs['font-family'], + size: typeof node.attrs['font-size'] === 'undefined' ? null : (node.attrs['font-size']), + anchor: typeof node.attrs['text-anchor'] === 'undefined' ? null : node.attrs['text-anchor'], } - - return r.join(';') - - } - - /** - * Computes tspan dy using font size. This formula was empircally determined - * using a best-fit line. Works well in both VML and SVG browsers. - * @param fontSize number - * @return number - */ - function computeTSpanDy(fontSize, line, lines) { - if ( fontSize === null ) fontSize = 10; - - //return fontSize * 4.5 / 13 - return fontSize * 4.5 / 13 * ( line - .2 - lines / 2 ) * 3.5; - } - - var serializer = { - 'text': function(node) { - style = extractStyle(node); - //Roo.log(JSON.stringify(node, null,4)); - var tags = new Array; - - map(node.attrs['text'].split('\n'), function(text, iterable, line) { - line = line || 0; - tags.push(tag( - 'text', - reduce( - node.attrs, - function(initial, value, name) { - if ( name !== 'text' && name !== 'w' && name !== 'h' ) { - if ( name === 'font-size') value = value + 'px'; - - initial[name] = escapeXML(value.toString()); - } - - return initial; - }, - { - style: 'text-anchor: ' + (style.font.anchor ? (style.font.anchor +';' ): 'middle;') + - styleToString(style) + ';' - } - ), - node.matrix, - tag('tspan', - { - dy: computeTSpanDy(style.font.size, line + 1, node.attrs['text'].split('\n').length) - }, - null, - escapeXML(text) - ) - )); - }); - - return tags; - }, - 'path' : function(node) { - var initial = ( node.matrix.a === 1 && node.matrix.d === 1 ) ? {} : { 'transform' : node.matrix.toString() }; - - - - return tag( - 'path', - reduce( - node.attrs, - function(initial, value, name) { - if ( name === 'path' ) { - name = 'd'; - } + }; +} + +/** + * @param style object from style() + * @return string + */ +function styleToString(style) { + // TODO figure out what is 'normal' + //Roo.log(JSON.stringify(style)); + var r = [ + 'font-family:' + style.font.family, + 'font-weight:normal', + 'font-style:normal', + 'font-stretch:normal', + 'font-variant:normal' + ]; + if (style.font.size !== null) { + r.push('font-size: ' + style.font.size + 'px') + } + + return r.join(';') + +} - initial[name] = (typeof(value) == 'undefined') ? '' : value.toString(); +/** + * Computes tspan dy using font size. This formula was empircally determined + * using a best-fit line. Works well in both VML and SVG browsers. + * @param fontSize number + * @return number + */ +function computeTSpanDy(fontSize, line, lines) { + if (fontSize === null) + fontSize = 10; - return initial; - }, - { - style: 'fill:' + Raphael.color(node.attrs.fill).hex + ';' + //return fontSize * 4.5 / 13 + return fontSize * 4.5 / 13 * (line - .2 - lines / 2) * 4.5; +} + +var serializer = { + 'text': function (node) { + var style = extractStyle(node); + + var tags = new Array; + + var content = []; + + var textArray = node.attrs['text'].split('\n'); + + textArray.forEach((v,k) => { + content.push(tag('tspan', + { + x: node.attrs.x, + dy: computeTSpanDy(style.font.size, k + 1, textArray.length) + }, + null, + escapeXML(v) + )) + + }); + + tags.push(tag( + 'text', + reduce( + node.attrs, + function (initial, value, name) { + if (name !== 'text' && name !== 'w' && name !== 'h') { + if (name === 'font-size') + value = value + 'px'; + + initial[name] = escapeXML(value.toString()); } - ), - node.matrix - ); - } - // Other serializers should go here - }; + + return initial; + }, + { + style: 'text-anchor: ' + (style.font.anchor ? (style.font.anchor + ';') : 'middle;') + + styleToString(style) + ';' + } + ), + node.matrix, + content.join("") + )); + + return tags; + }, + 'path': function (node) { + var initial = (node.matrix.a === 1 && node.matrix.d === 1) ? {} : {'transform': node.matrix.toString()}; + + + + return tag( + 'path', + reduce( + node.attrs, + function (initial, value, name) { + if (name === 'path') { + name = 'd'; + } + + initial[name] = (typeof (value) == 'undefined') ? '' : value.toString(); + + return initial; + }, + { + style: 'fill:' + Raphael.color(node.attrs.fill).hex + ';' + } + ), + node.matrix + ); + } + // Other serializers should go here +}; function toSVG() { - var - paper = this, - - svg = '' - ; - - - for ( var node = paper.bottom; node != null; node = node.next ) { + var paper = this, + svg = ''; + + + for (var node = paper.bottom; node != null; node = node.next) { //if ( node.node.style.display === 'none' ) continue; var attrs = ''; - + // Use serializer - if ( typeof serializer[node.type] === 'function' ) { + if (typeof serializer[node.type] === 'function') { svg += serializer[node.type](node); continue; } - switch ( node.type ) { + switch (node.type) { case 'image': attrs += ' preserveAspectRatio="none"'; break; } //Roo.log(JSON.stringify(node, null,4)); - - for ( i in node.attrs ) { + + for (i in node.attrs) { var name = i; - + var val = node.attrs[i].toString(); - switch ( i ) { + switch (i) { case 'src': name = 'xlink:href'; @@ -251,27 +260,27 @@ function toSVG() { case 'transform': name = ''; break; - + case 'stroke': case 'fill': //s(JSON.stringify(node, null,4)); - val = typeof(node.node.attributes[i]) == 'undefined' ? val : node.node.attributes[i]; - + val = typeof (node.node.attributes[i]) == 'undefined' ? val : node.node.attributes[i]; + val = Raphael.color.getRGB(val).hex; //Roo.log("fill: " + val); break; } - if ( name ) { + if (name) { attrs += ' ' + name + '="' + escapeXML(val) + '"'; } } - svg += '<' + node.type + ' transform="matrix(' + node.matrix.toString().replace(/^matrix\(|\)$/g, '') + ')"' + attrs + '>' + "\n"; + svg += '<' + node.type + ' transform="matrix(' + node.matrix.toString().replace(/^matrix\(|\)$/g, '') + ')"' + attrs + '>' + "\n"; } svg += ''; - + return svg; }; \ No newline at end of file