From bf054805514ea71dba7578d434a39f9531d51934 Mon Sep 17 00:00:00 2001 From: Yuval Adam Date: Sat, 6 Aug 2011 16:45:21 +0300 Subject: added linegraph test --- linegraph-test/analytics.js | 134 +++++++ linegraph-test/data.csv | 13 + linegraph-test/data.py | 8 + linegraph-test/index.html | 67 ++++ linegraph-test/lc.js | 856 ++++++++++++++++++++++++++++++++++++++++++ linegraph-test/popup.js | 121 ++++++ linegraph-test/raphael-min.js | 7 + linegraph-test/screen.css | 265 +++++++++++++ 8 files changed, 1471 insertions(+) create mode 100644 linegraph-test/analytics.js create mode 100644 linegraph-test/data.csv create mode 100644 linegraph-test/data.py create mode 100644 linegraph-test/index.html create mode 100644 linegraph-test/lc.js create mode 100644 linegraph-test/popup.js create mode 100644 linegraph-test/raphael-min.js create mode 100644 linegraph-test/screen.css diff --git a/linegraph-test/analytics.js b/linegraph-test/analytics.js new file mode 100644 index 0000000..9e712d3 --- /dev/null +++ b/linegraph-test/analytics.js @@ -0,0 +1,134 @@ +Raphael.fn.drawGrid = function (x, y, w, h, wv, hv, color) { + color = color || "#000"; + var path = ["M", Math.round(x) + .5, Math.round(y) + .5, "L", Math.round(x + w) + .5, Math.round(y) + .5, Math.round(x + w) + .5, Math.round(y + h) + .5, Math.round(x) + .5, Math.round(y + h) + .5, Math.round(x) + .5, Math.round(y) + .5], + rowHeight = h / hv, + columnWidth = w / wv; + for (var i = 1; i < hv; i++) { + path = path.concat(["M", Math.round(x) + .5, Math.round(y + i * rowHeight) + .5, "H", Math.round(x + w) + .5]); + } + for (i = 1; i < wv; i++) { + path = path.concat(["M", Math.round(x + i * columnWidth) + .5, Math.round(y) + .5, "V", Math.round(y + h) + .5]); + } + return this.path(path.join(",")).attr({stroke: color}); +}; + +$(function () { + $("#data").css({ + position: "absolute", + left: "-9999em", + top: "-9999em" + }); +}); + +window.onload = function () { + function getAnchors(p1x, p1y, p2x, p2y, p3x, p3y) { + var l1 = (p2x - p1x) / 2, + l2 = (p3x - p2x) / 2, + a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)), + b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)); + a = p1y < p2y ? Math.PI - a : a; + b = p3y < p2y ? Math.PI - b : b; + var alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2, + dx1 = l1 * Math.sin(alpha + a), + dy1 = l1 * Math.cos(alpha + a), + dx2 = l2 * Math.sin(alpha + b), + dy2 = l2 * Math.cos(alpha + b); + return { + x1: p2x - dx1, + y1: p2y + dy1, + x2: p2x + dx2, + y2: p2y + dy2 + }; + } + // Grab the data + var labels = [], + data = []; + $("#data tfoot th").each(function () { + labels.push($(this).html()); + }); + $("#data tbody td").each(function () { + data.push($(this).html()); + }); + + // Draw + var width = 800, + height = 250, + leftgutter = 30, + bottomgutter = 20, + topgutter = 20, + colorhue = .6 || Math.random(), + color = "hsb(" + [colorhue, .5, 1] + ")", + r = Raphael("holder", width, height), + txt = {font: '12px Helvetica, Arial', fill: "#fff"}, + txt1 = {font: '10px Helvetica, Arial', fill: "#fff"}, + txt2 = {font: '12px Helvetica, Arial', fill: "#000"}, + X = (width - leftgutter) / labels.length, + max = Math.max.apply(Math, data), + Y = (height - bottomgutter - topgutter) / max; + r.drawGrid(leftgutter + X * .5 + .5, topgutter + .5, width - leftgutter - X, height - topgutter - bottomgutter, 10, 10, "#333"); + var path = r.path().attr({stroke: color, "stroke-width": 4, "stroke-linejoin": "round"}), + bgp = r.path().attr({stroke: "none", opacity: .3, fill: color}), + label = r.set(), + is_label_visible = false, + leave_timer, + blanket = r.set(); + label.push(r.text(60, 12, "24 hits").attr(txt)); + label.push(r.text(60, 27, "22 September 2008").attr(txt1).attr({fill: color})); + label.hide(); + var frame = r.popup(100, 100, label, "right").attr({fill: "#000", stroke: "#666", "stroke-width": 2, "fill-opacity": .7}).hide(); + + var p, bgpp; + for (var i = 0, ii = labels.length; i < ii; i++) { + var y = Math.round(height - bottomgutter - Y * data[i]), + x = Math.round(leftgutter + X * (i + .5)), + t = r.text(x, height - 6, labels[i]).attr(txt).toBack(); + if (!i) { + p = ["M", x, y, "C", x, y]; + bgpp = ["M", leftgutter + X * .5, height - bottomgutter, "L", x, y, "C", x, y]; + } + if (i && i < ii - 1) { + var Y0 = Math.round(height - bottomgutter - Y * data[i - 1]), + X0 = Math.round(leftgutter + X * (i - .5)), + Y2 = Math.round(height - bottomgutter - Y * data[i + 1]), + X2 = Math.round(leftgutter + X * (i + 1.5)); + var a = getAnchors(X0, Y0, x, y, X2, Y2); + p = p.concat([a.x1, a.y1, x, y, a.x2, a.y2]); + bgpp = bgpp.concat([a.x1, a.y1, x, y, a.x2, a.y2]); + } + var dot = r.circle(x, y, 4).attr({fill: "#000", stroke: color, "stroke-width": 2}); + blanket.push(r.rect(leftgutter + X * i, 0, X, height - bottomgutter).attr({stroke: "none", fill: "#fff", opacity: 0})); + var rect = blanket[blanket.length - 1]; + (function (x, y, data, lbl, dot) { + var timer, i = 0; + rect.hover(function () { + clearTimeout(leave_timer); + var side = "right"; + if (x + frame.getBBox().width > width) { + side = "left"; + } + var ppp = r.popup(x, y, label, side, 1); + frame.show().stop().animate({path: ppp.path}, 200 * is_label_visible); + label[0].attr({text: data + " hit" + (data == 1 ? "" : "s")}).show().stop().animateWith(frame, {translation: [ppp.dx, ppp.dy]}, 200 * is_label_visible); + label[1].attr({text: lbl + " September 2008"}).show().stop().animateWith(frame, {translation: [ppp.dx, ppp.dy]}, 200 * is_label_visible); + dot.attr("r", 6); + is_label_visible = true; + }, function () { + dot.attr("r", 4); + leave_timer = setTimeout(function () { + frame.hide(); + label[0].hide(); + label[1].hide(); + is_label_visible = false; + }, 1); + }); + })(x, y, data[i], labels[i], dot); + } + p = p.concat([x, y, x, y]); + bgpp = bgpp.concat([x, y, x, y, "L", x, height - bottomgutter, "z"]); + path.attr({path: p}); + bgp.attr({path: bgpp}); + frame.toFront(); + label[0].toFront(); + label[1].toFront(); + blanket.toFront(); +}; \ No newline at end of file diff --git a/linegraph-test/data.csv b/linegraph-test/data.csv new file mode 100644 index 0000000..8b482dc --- /dev/null +++ b/linegraph-test/data.csv @@ -0,0 +1,13 @@ +Community and Social,Health and Welfare,Education,Public Admin,Business,Banking and Finance,Transport and Communication,Accomodation and Services,Wholesale and Retail,Electricity and Water,Manufacturing,Agriculture,Total +"5,380","6,893","6,476","14,038","8,849","15,383","9,585","3,972","7,427","7,353","19,540","11,286","5,575" +"5,341","6,800","6,264","12,890","8,991","14,360","9,459","3,796","7,149","7,417","19,748","11,370","5,437" +"5,375","6,783","6,224","12,144","9,163","17,339","10,552","4,044","7,523","7,657","21,707","12,724","5,739" +"5,252","6,667","6,297","12,212","8,680","15,623","9,604","4,034","7,091","7,237","19,646","11,683","5,458" +"5,275","6,676","6,369","12,211","8,390","17,119","8,946","3,922","7,070","7,312","20,459","11,305","5,526" +"5,884","7,834","8,499","16,317","8,687","14,627","9,416","3,873","7,305","7,404","20,496","11,525","5,783" +"5,730","7,247","7,259","13,055","8,863","15,763","9,301","3,968","7,437","7,572","26,815","11,753","5,787" +"5,827","7,697","6,563","13,205","8,821","14,258","9,320","4,089","7,264","7,542","23,335","11,735","5,678" +"5,721","7,436","6,578","13,777","8,789","14,111","9,108","3,968","7,064","7,369","21,224","11,118","5,573" +"5,413","6,846","6,660","12,345","8,802","13,442","8,867","4,038","7,038","7,353","20,253","11,222","5,626" +"5,422","7,173","7,120","12,857","8,859","14,555","8,969","4,046","7,106","7,466","21,028","11,153","5,622" +"5,651","6,814","6,599","13,039","9,644","15,646","9,817","4,284","7,949","8,081","19,970","12,405","6,022" diff --git a/linegraph-test/data.py b/linegraph-test/data.py new file mode 100644 index 0000000..9171e2a --- /dev/null +++ b/linegraph-test/data.py @@ -0,0 +1,8 @@ +import csv + +with open('data.csv', 'r') as f: + reader = csv.reader(f) + tags = reader.next() + data = zip(*[row for row in reader]) + + \ No newline at end of file diff --git a/linegraph-test/index.html b/linegraph-test/index.html new file mode 100644 index 0000000..35ef7d7 --- /dev/null +++ b/linegraph-test/index.html @@ -0,0 +1,67 @@ + + + + + + + + + Wage Graphs + + + + + + + + + + + + + + + + + + + + + +
+

Wage Graph

+
+
+ + + diff --git a/linegraph-test/lc.js b/linegraph-test/lc.js new file mode 100644 index 0000000..658562d --- /dev/null +++ b/linegraph-test/lc.js @@ -0,0 +1,856 @@ +/* + * Raphael Line Chart 1.2 - Raphael Line Charts plugin + * + * Copyright (c) 2011 Sagie Maoz (http://sagie.maoz.info) + * Dual-licensed under the GPL (http://www.opensource.org/licenses/gpl-3.0) + * and the MIT (http://www.opensource.org/licenses/mit-license) licenses. + */ +(function() { + +// modified version of Raphael.fn.drawGrid() from the Raphael Analytics example +Raphael.fn.drawGrid = function(x, y, width, height, x_step, x_size, y_count, y_size) { + var path, + rowHeight, + columnWidth; + + // frame border + path = ["M", Math.round(x) + 0.5, Math.round(y) + 0.5, + "L", Math.round(x + width) + 0.5, Math.round(y) + 0.5, + Math.round(x + width) + 0.5, Math.round(y + height) + 0.5, + Math.round(x) + 0.5, Math.round(y + height) + 0.5, + Math.round(x) + 0.5, Math.round(y) + 0.5]; + + // horizontal lines + rowHeight = Math.ceil(height / y_count); + for (var i = 0; i < y_count; i++) { + path = path.concat(["M", Math.round(x) + 0.5, Math.round(y + i * rowHeight) + 0.5, + "H", Math.round(x + width) + 0.5]); + } + + // vertical lines + if (!x_step) { + x_step = 1; + x_size = 10; + } + columnWidth = Math.ceil(width / x_size); + for (i = 0; i < x_size; i+= x_step) { + path = path.concat(["M", Math.round(x + i * (columnWidth + 1.1)) + 0.5, Math.round(y) + 0.5, + "V", Math.round(y + height) + 0.5]); + } + + return this.path(path.join(",")); +}; + +Raphael.fn.popup = function(X, Y, set, position, ret) { + var pos = String(position || "top-middle").split("-"), + pos_x = pos[1] || "middle", + tokenRegex = /\{([^\}]+)\}/g, + objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, + + replacer = function(all, key, obj) { + var res = obj; + key.replace(objNotationRegex, + function(all, name, quote, quotedName, isFunc) { + name = name || quotedName; + if (res) { + if (name in res) { + res = res[name]; + } + return (typeof res == "function") && isFunc && (res = res()); + } + }); + res = (res === null || res == obj ? all: res) + ""; + return res; + }, + + fill = function(str, obj) { + return String(str).replace(tokenRegex, + function(all, key) { + return replacer(all, key, obj); + }); + }, + + r = 5, + bb = set.getBBox(), + w = Math.round(bb.width), + h = Math.round(bb.height), + x = Math.round(bb.x) - r, + y = Math.round(bb.y) - r, + gap = Math.min(h / 2, w / 2, 10), + shapes = { + top: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z", + bottom: "M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z", + right: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z", + left: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z" + }, + offset = { + hx0: X - (x + r + w - gap * 2), + hx1: X - (x + r + w / 2 - gap), + hx2: X - (x + r + gap), + vhy: Y - (y + r + h + r + gap), + "^hy": Y - (y - gap) + }, + mask = [{ + x: x + r, + y: y, + w: w, + w4: w / 4, + h4: h / 4, + right: 0, + left: w - gap * 2, + bottom: 0, + top: h - gap * 2, + r: r, + h: h, + gap: gap + }, + { + x: x + r, + y: y, + w: w, + w4: w / 4, + h4: h / 4, + left: w / 2 - gap, + right: w / 2 - gap, + top: h / 2 - gap, + bottom: h / 2 - gap, + r: r, + h: h, + gap: gap + }, + { + x: x + r, + y: y, + w: w, + w4: w / 4, + h4: h / 4, + left: 0, + right: w - gap * 2, + top: 0, + bottom: h - gap * 2, + r: r, + h: h, + gap: gap + }][pos_x == "middle" ? 1: (pos_x == "top" || pos_x == "left") * 2], + dx = 0, + dy = 0, + out = this.path(fill(shapes[pos[0]], mask)).insertBefore(set); + + switch (pos[0]) { + case "top": + dx = X - (x + r + mask.left + gap); + dy = Y - (y + r + h + r + gap); + break; + case "bottom": + dx = X - (x + r + mask.left + gap); + dy = Y - (y - gap); + break; + case "left": + dx = X - (x + r + w + r + gap); + dy = Y - (y + r + mask.top + gap); + break; + case "right": + dx = X - (x - gap); + dy = Y - (y + r + mask.top + gap); + break; + } + out.translate(dx, dy); + if (ret) { + ret = out.attr("path"); + out.remove(); + return { + path: ret, + dx: dx, + dy: dy + }; + } + set.translate(dx, dy); + return out; +}; + +Raphael.fn.lineChart = function(method) { + + // public methods + var methods = { + + init: function(options) { + + this.lineChart.settings = helpers.extend(true, {}, this.lineChart.defaults, options); + this.customAttributes.lineChart = {}; + + //TODO either data or data_holder is a must + + // let's go + var element = this, + o = this.customAttributes.lineChart, + settings = this.lineChart.settings, + width = settings.width, + height = settings.height, + + table = helpers.getTable(element, o, settings.data, settings.data_holder), + size = table.labels.length, + + max = Math.max.apply(Math, table.data), + + YLabelWidth = element.text(-50, -50, max).attr(settings.text.axis_labels).getBBox().width, + gutter = { + top: settings.gutter.top, + right: settings.gutter.right, + bottom: settings.gutter.bottom, + left: settings.gutter.left + YLabelWidth + }, + blanket = element.set(), + + X = (width - gutter.left) / size, + Y, p, bgpp, x, y; + + // fix max. value + if (max > settings.y_labels_count*10) { + p = 25*settings.y_labels_count; + max = Math.ceil(max/p)*p; + } + Y = max ? ((height - gutter.bottom - gutter.top) / max) : 0; + + o.gutter = gutter; + o.labelGutter = settings.gutter.left; + + o.size = size; + o.dots = []; + o.rects = []; + o.info = []; + //TODO allow customizing + o.path = element.path().attr({ + stroke: settings.colors.master, + "stroke-width": 4, + "stroke-linejoin": "round" + }); + // chart area + //TODO allow customizing + if (settings.show_area) { + o.bgp = element.path().attr({ + stroke: "none", + opacity: 0.3, + fill: settings.colors.master + }); + } + else { + o.bgp = element.path().attr({ + stroke: "none", + opacity: 0, + fill: settings.colors.master + }).hide(); + } + + // draw background grid + if (!o.gridDrawn && settings.no_grid === false) { + var grid = element.drawGrid(gutter.left + X * 0.5 + 0.5, + gutter.top + 0.5, + width - gutter.left - X, + height - gutter.top - gutter.bottom, + settings.x_labels_step, + size, + settings.y_labels_count, + max) + .attr({ stroke: "#eaeaea" }); + + grid.toBack(); + } + o.gridDrawn = true; + + // draw x axis labels + o.XLabels = []; + if (settings.x_labels_step) { + helpers.drawXLabels(element, table.labels); + } + + // draw y axis labels + o.YLabels = []; + if (settings.y_labels_count) { + helpers.drawYLabels(element, max); + } + + // prepare popup + o.label = element.set(); + //TODO ?? + o.label.push(element.text(60, 12, "24 hits").attr(settings.text.popup_line1)); + o.label.push(element.text(60, 27, "22 September 2008").attr(settings.text.popup_line2).attr({ + fill: settings.colors.master + })); + o.label.hide(); + + //TODO allow customizing + o.frame = element.popup(100, 100, o.label, "right").attr({ + fill: "#ffffff", + stroke: "#666", + "stroke-width": 2, + "fill-opacity": 0.8 + }).hide(); + + for (var i = 0, ii = size; i < ii; i++) { + var dot, rect; + + // calculate current x, y + y = Math.round(height - gutter.bottom - Y * table.data[i]) || 0; + x = Math.round(gutter.left + X * (i + 0.5)); + + if (!i) { + p = ["M", x, y, "C", x, y]; + bgpp = ["M", gutter.left + X * 0.5, height - gutter.bottom, "L", x, y, "C", x, y]; + } + else if (i < ii - 1) { + var Y0 = Math.round(height - gutter.bottom - Y * table.data[i - 1]), + X0 = Math.round(gutter.left + X * (i - 0.5)), + Y2 = Math.round(height - gutter.bottom - Y * table.data[i + 1]), + X2 = Math.round(gutter.left + X * (i + 1.5)), + a = helpers.getAnchors(X0, Y0, x, y, X2, Y2); + p = p.concat([a.x1, a.y1, x, y, a.x2, a.y2]); + bgpp = bgpp.concat([a.x1, a.y1, x, y, a.x2, a.y2]); + } + + //TODO allow customizing all of these + dot = element.circle(x, y, 4).attr({ + fill: "#ffffff", + stroke: settings.colors.master, + "stroke-width": 2 + }); + if (y === 0) { + dot.attr({ + opacity: 0 + }); + } + + o.dots.push(dot); + + if (settings.mouse_coords == 'circle') { + blanket.push(element.circle(x, y, 14).attr({ + stroke: "none", + fill: "#fff", + opacity: 0 + })); + } else if (settings.mouse_coords == 'rect') { + blanket.push(element.rect(gutter.left + X * i, 0, X, height - gutter.bottom).attr({ + stroke: "none", + fill: "#fff", + opacity: 0 + })); + } + rect = blanket[blanket.length - 1]; + o.rects.push(rect); + + o.info.push({ + x: x, + y: y, + data: table.data[i], + label: table.labels[i], + line1: table.lines1[i], + line2: table.lines2[i] + }); + helpers.bindHoverEvent(this, i, dot, rect, o.frame, o.label); + } + + p = p.concat([x, y, x, y]); + bgpp = bgpp.concat([x, y, x, y, "L", x, height - gutter.bottom, "z"]); + o.path.attr({ + path: p + }); + o.bgp.attr({ + path: bgpp + }); + o.frame.toFront(); + o.label[0].toFront(); + o.label[1].toFront(); + blanket.toFront(); + }, + + setDataIndex: function(index) { + var o = this.customAttributes.lineChart; + + if (index < o.dataArray.data.length) { + var one = { + labels: o.dataArray.labels, + data: o.dataArray.data[index], + lines1: o.dataArray.lines1[index], + lines2: o.dataArray.lines2[index] + }; + return this.lineChart('setData', one); + } + else + { + return helpers.error('Data index out of range.'); + } + }, + + setDataHolder: function(holder) { + var table = helpers.loadTableData(this, holder); + if (table) { + return this.lineChart('setData', table); + } + else { + return helpers.error('No data holder element supplied.'); + } + }, + + // Caution: this would only work for the same number of records + setData: function(table) { + var element = this, + settings = this.lineChart.settings, + o = this.customAttributes.lineChart, + width = settings.width, + height = settings.height, + gutter = o.gutter, + + X = (width - gutter.left) / table.labels.length, + max = Math.max.apply(Math, table.data), + Y, p, bgpp; + + table = helpers.getTable(element, o, table); + + if (table.labels.length != o.size) { + return helpers.error('New data source has to be of same size'); + } + + // fix max. value + if (max > settings.y_labels_count*10) { + p = 25*settings.y_labels_count; + max = Math.ceil(max/p)*p; + } + Y = max ? ((height - gutter.bottom - gutter.top) / max) : 0; + + for (var i = 0, ii = table.labels.length; i < ii; i++) { + var dot = o.dots[i], + rect = o.rects[i]; + + // calculate current x, y + y = Math.round(height - gutter.bottom - Y * table.data[i]) || 0; + x = Math.round(gutter.left + X * (i + 0.5)); + + if (!i) { + p = ["M", x, y, "C", x, y]; + bgpp = ["M", gutter.left + X * 0.5, height - gutter.bottom, "L", x, y, "C", x, y]; + } + else if (i < ii - 1) { + var Y0 = Math.round(height - gutter.bottom - Y * table.data[i - 1]), + X0 = Math.round(gutter.left + X * (i - 0.5)), + Y2 = Math.round(height - gutter.bottom - Y * table.data[i + 1]), + X2 = Math.round(gutter.left + X * (i + 1.5)), + a = helpers.getAnchors(X0, Y0, x, y, X2, Y2); + + p = p.concat([a.x1, a.y1, x, y, a.x2, a.y2]); + bgpp = bgpp.concat([a.x1, a.y1, x, y, a.x2, a.y2]); + } + + dot.animate({cy: y}, + settings.animation.speed, + settings.animation.easing); + + // new popup data + o.info[i] = { + x: x, + y: y, + data: table.data[i], + label: table.labels[i], + line1: table.lines1[i], + line2: table.lines2[i] + }; + } + + // animate paths + p = p.concat([x, y, x, y]); + bgpp = bgpp.concat([x, y, x, y, "L", x, height - gutter.bottom, "z"]); + o.path.animate({path: p}, + settings.animation.speed, + settings.animation.easing); + o.bgp.animate({path: bgpp}, + settings.animation.speed, + settings.animation.easing); + + // update x axis labels + if (settings.x_labels_step) { + helpers.drawXLabels(element, table.labels); + } + + // draw y axis labels + if (settings.y_labels_count) { + helpers.drawYLabels(element, max); + } + } + + }; + + // private methods + var helpers = { + extend: function () { // copied from jQuery source + // copy reference to target object + var target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false, + options, name, src, copy; + + // Handle a deep copy situation + if (typeof target === "boolean") { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if (typeof target !== "object" && typeof target !== "function") { + target = {}; + } + + // extend myself if only one argument is passed + if (length === i) { + target = this; + --i; + } + + for (; i < length; i++) { + // Only deal with non-null/undefined values + if ((options = arguments[i]) !== null) { + // Extend the base object + for (name in options) { + if (options.hasOwnProperty(name)) + { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target === copy) { + continue; + } + + // Recurse if we're merging object literal values or arrays + if (deep && copy && (copy.constructor == Object || copy.constructor == Array)) { + var clone = src && (src.constructor == Object || src.constructor == Array) ? src : copy.constructor == Array ? [] : {}; + + // Never move original objects, clone them + target[name] = helpers.extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[name] = copy; + } + } + } + } + } + + // Return the modified object + return target; + }, + + getAnchors: function(p1x, p1y, p2x, p2y, p3x, p3y) { + var l1 = (p2x - p1x) / 2, + l2 = (p3x - p2x) / 2, + a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)), + b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)), + dx1 = dy1 = dx2 = dy2 = 0, + alpha; + + a = p1y < p2y ? Math.PI - a: a; + b = p3y < p2y ? Math.PI - b: b; + alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2; + dx1 = l1 * Math.sin(alpha + a); + dx2 = l2 * Math.sin(alpha + b); + if (p1y != p2y) { + dy1 = l1 * Math.cos(alpha + a); + } + if (p2y != p3y) { + dy2 = l2 * Math.cos(alpha + b); + } + + return { + x1: p2x - dx1, + y1: p2y + dy1, + x2: p2x + dx2, + y2: p2y + dy2 + }; + }, + + getTable: function(elm, o, obj, table_elm) { + if (obj) { + // handle multiple data rows + if (obj.data[0].constructor == Array) { + o.dataArray = obj; + var one = {}; + one.labels = obj.labels; + one.data = obj.data[0]; + one.lines1 = obj.lines1[0]; + one.lines2 = obj.lines2[0]; + return one; + } + else { + return obj; + } + } + else { + return helpers.loadTableData(elm, table_elm); + } + }, + + loadTableData: function(elm, table_elm) { + var settings = elm.lineChart.settings, + table = (table_elm && table_elm.constructor == String) ? document.getElementById(table_elm) : table_elm, + res = { + labels: [], + data: [], + lines1: [], + lines2: [] + }, + tds = {}, + curr, td, i, j; + + if (table) { + // find elements to collect + for (i=0; i < table.childNodes.length; i++, curr = null, td = 'td') { + if (table.childNodes[i].tagName == 'TFOOT') { + curr = 'labels'; + td = 'th'; + } + else if (table.childNodes[i].tagName == 'TBODY') { + if (table.childNodes[i].className == settings.table_classes.data) { + curr = 'data'; + } + else if (table.childNodes[i].className == settings.table_classes.line1) { + curr = 'lines1'; + } + else if (table.childNodes[i].className == settings.table_classes.line2) { + curr = 'lines2'; + } + } + + if (curr) { + tds[curr] = table.childNodes[i].getElementsByTagName(td); + } + } + + // populate res + if (tds.labels && tds.data && tds.lines1 && tds.lines2) { + for (j=0; j < tds.labels.length; j++) { + res.labels.push(tds.labels[j].innerHTML); + } + for (j=0; j < tds.data.length; j++) { + res.data.push(tds.data[j].innerHTML); + } + for (j=0; j < tds.lines1.length; j++) { + res.lines1.push(tds.lines1[j].innerHTML); + } + for (j=0; j < tds.lines2.length; j++) { + res.lines2.push(tds.lines2[j].innerHTML); + } + } + return res; + } else { + return false; + } + }, + + bindHoverEvent: function(elm, i, dot, rect, frame, label) { + var settings = elm.lineChart.settings, + o = elm.customAttributes.lineChart, + f_in = function() { + var side = "right", + info = o.info[i], + x = info.x, + y = info.y; + + window.clearTimeout(elm.leave_timer); + if (x + frame.getBBox().width > settings.width) { + side = "left"; + } + var ppp = elm.popup(x, y, label, side, 1); + if (settings.mouse_coords == 'circle') { + frame.attr({ + path: ppp.path, + width: '200px' + }).show(); + label[0].attr({ + text: info.line1, + fill: settings.colors.line1, + translation: [ppp.dx, ppp.dy] + }).show(); + label[1].attr({ + text: info.line2, + fill: settings.colors.line2, + translation: [ppp.dx, ppp.dy] + }).show(); + } else if (settings.mouse_coords == 'rect') { + frame.show().stop().animate({ + path: ppp.path + }, + 200 * o.is_label_visible); + label[0].attr({ + text: info.line1 + }).show().stop().animateWith(frame, { + translation: [ppp.dx, ppp.dy] + }, + 200 * o.is_label_visible); + label[1].attr({ + text: info.line2 + }).show().stop().animateWith(frame, { + translation: [ppp.dx, ppp.dy] + }, + 200 * o.is_label_visible); + } + frame.toFront(); + label[0].toFront(); + label[1].toFront(); + this.toFront(); + dot.attr("r", 6); + o.is_label_visible = true; + }, + f_out = function() { + dot.attr("r", 4); + + elm.leave_timer = window.setTimeout(function() { + frame.hide(); + label[0].hide(); + label[1].hide(); + o.is_label_visible = false; + }, + 1); + }; + + rect.hover(f_in, f_out); + }, + + drawXLabels: function(elm, labels) { + var settings = elm.lineChart.settings, + o = elm.customAttributes.lineChart, + x = (settings.width - o.gutter.left) / o.size, + y = settings.height - o.gutter.bottom + 18, + step = settings.x_labels_step, + style = settings.text.axis_labels, + i; + + // reset old labels + if (o.XLabels.length) { + for (i = 0; i < o.XLabels.length; i++) { + o.XLabels[i].remove(); + } + } + + o.XLabels = []; + for (i = 0; i < o.size; i++) { + var label_x = Math.round(o.gutter.left + x * (i + 0.5)); + + if (i % step === 0) { + var l = elm.text(label_x, y, labels[i]) + .attr(style).toBack(); + o.XLabels.push(l); + } + } + }, + + drawYLabels: function(elm, max) { + var settings = elm.lineChart.settings, + o = elm.customAttributes.lineChart, + x = o.labelGutter + 20, + y = o.gutter.bottom, + top = o.gutter.top, + height = settings.height, + count = settings.y_labels_count, + step = Math.round(max / count), + labelHeight = (height - top - y) / count; + + // reset old labels + if (o.YLabels.length) { + for (var i = 0; i < o.YLabels.length; i++) { + o.YLabels[i].remove(); + } + } + + + o.YLabels = []; + for (var j = 0; j <= count; j++) { + var txt = (j * (max/count)), + l; + + l = elm + .text(x, height - y - (j*labelHeight), + settings.y_labels_format(txt, max, count)) + .attr(settings.text.axis_labels); + o.YLabels.push(l); + + } + }, + + error: function(message) { + if (console && console.error) { + console.error('lineChart Error: ' + message); + } + return false; + } + }; + + // go go go! + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return methods.init.apply(this, arguments); + } else { + return helpers.error('Method "' + method + '" not found.'); + } +}; + +Raphael.fn.lineChart.defaults = { + data_holder: null, // table element holding the data to display + data: null, // alternatively, supply a data object + width: 500, // chart width + height: 250, // chart height + gutter: { // gutter dimensions + top: 20, + right: 0, + bottom: 50, + left: 30 + }, + show_area: false, // whether to fill the area below the line + mouse_coords: 'rect', // way to capture mouse events + no_grid: false, // whether to display background grid + x_labels_step: false, // X axis: either false or a step integer + y_labels_count: false, // Y axis: either false or a labels count + y_labels_format: function(txt, max, count) { // function to handle display of Y labels + if (max > count) { + txt = Math.floor(txt) + ''; + } + else { + txt = txt + ''; + } + txt = txt.replace(/\.(\d{3})\d*/, '.$1'); + txt = txt.replace(/(\d{1,3})(?=(?:\d{3})+$)/g,"$1,"); + return txt; + }, + animation: { // animation (on data source change) settings + speed: 600, + easing: "backOut" + }, + colors: { // color settings + master: '#01A8F0', + line1: '#000000', + line2: '#01A8F0' + }, + text: { // text style settings + axis_labels: { + font: '10px Helvetica, Arial', + fill: "#000000" + }, + popup_line1: { + font: 'bold 11px Helvetica, Arial', + fill: "#000000" + }, + popup_line2: { + font: 'bold 10px Helvetica, Arial', + fill: "#000000" + } + }, + table_classes: { + data: 'data', + line1: 'line1', + line2: 'line2' + } +}; + +})(); + diff --git a/linegraph-test/popup.js b/linegraph-test/popup.js new file mode 100644 index 0000000..5fd722a --- /dev/null +++ b/linegraph-test/popup.js @@ -0,0 +1,121 @@ +(function () { +var tokenRegex = /\{([^\}]+)\}/g, + objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties + replacer = function (all, key, obj) { + var res = obj; + key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) { + name = name || quotedName; + if (res) { + if (name in res) { + res = res[name]; + } + typeof res == "function" && isFunc && (res = res()); + } + }); + res = (res == null || res == obj ? all : res) + ""; + return res; + }, + fill = function (str, obj) { + return String(str).replace(tokenRegex, function (all, key) { + return replacer(all, key, obj); + }); + }; + Raphael.fn.popup = function (X, Y, set, pos, ret) { + pos = String(pos || "top-middle").split("-"); + pos[1] = pos[1] || "middle"; + var r = 5, + bb = set.getBBox(), + w = Math.round(bb.width), + h = Math.round(bb.height), + x = Math.round(bb.x) - r, + y = Math.round(bb.y) - r, + gap = Math.min(h / 2, w / 2, 10), + shapes = { + top: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z", + bottom: "M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z", + right: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z", + left: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z" + }, + offset = { + hx0: X - (x + r + w - gap * 2), + hx1: X - (x + r + w / 2 - gap), + hx2: X - (x + r + gap), + vhy: Y - (y + r + h + r + gap), + "^hy": Y - (y - gap) + + }, + mask = [{ + x: x + r, + y: y, + w: w, + w4: w / 4, + h4: h / 4, + right: 0, + left: w - gap * 2, + bottom: 0, + top: h - gap * 2, + r: r, + h: h, + gap: gap + }, { + x: x + r, + y: y, + w: w, + w4: w / 4, + h4: h / 4, + left: w / 2 - gap, + right: w / 2 - gap, + top: h / 2 - gap, + bottom: h / 2 - gap, + r: r, + h: h, + gap: gap + }, { + x: x + r, + y: y, + w: w, + w4: w / 4, + h4: h / 4, + left: 0, + right: w - gap * 2, + top: 0, + bottom: h - gap * 2, + r: r, + h: h, + gap: gap + }][pos[1] == "middle" ? 1 : (pos[1] == "top" || pos[1] == "left") * 2]; + var dx = 0, + dy = 0, + out = this.path(fill(shapes[pos[0]], mask)).insertBefore(set); + switch (pos[0]) { + case "top": + dx = X - (x + r + mask.left + gap); + dy = Y - (y + r + h + r + gap); + break; + case "bottom": + dx = X - (x + r + mask.left + gap); + dy = Y - (y - gap); + break; + case "left": + dx = X - (x + r + w + r + gap); + dy = Y - (y + r + mask.top + gap); + break; + case "right": + dx = X - (x - gap); + dy = Y - (y + r + mask.top + gap); + break; + } + out.translate(dx, dy); + if (ret) { + ret = out.attr("path"); + out.remove(); + return { + path: ret, + dx: dx, + dy: dy + }; + } + set.translate(dx, dy); + return out; + }; +})(); \ No newline at end of file diff --git a/linegraph-test/raphael-min.js b/linegraph-test/raphael-min.js new file mode 100644 index 0000000..e5e7126 --- /dev/null +++ b/linegraph-test/raphael-min.js @@ -0,0 +1,7 @@ +/* + * Raphael 1.5.2 - JavaScript Vector Library + * + * Copyright (c) 2010 Dmitry Baranovskiy (http://raphaeljs.com) + * Licensed under the MIT (http://raphaeljs.com/license.html) license. + */ +(function(){function a(){if(a.is(arguments[0],G)){var b=arguments[0],d=bV[m](a,b.splice(0,3+a.is(b[0],E))),e=d.set();for(var g=0,h=b[w];g";bg=bf.firstChild;bg.style.behavior="url(#default#VML)";if(!(bg&&typeof bg.adj=="object"))return a.type=null;bf=null}a.svg=!(a.vml=a.type=="VML");j[e]=a[e];k=j[e];a._id=0;a._oid=0;a.fn={};a.is=function(a,b){b=x.call(b);if(b=="finite")return!O[f](+a);return b=="null"&&a===null||b==typeof a||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||J.call(a).slice(8,-1).toLowerCase()==b};a.angle=function(b,c,d,e,f,g){{if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return((h<0)*180+y.atan(-i/-h)*180/D+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)}};a.rad=function(a){return a%360*D/180};a.deg=function(a){return a*180/D%360};a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,G)){var e=b.length;while(e--)if(B(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(fb-d)return c-f+b}return c};function bh(){var a=[],b=0;for(;b<32;b++)a[b]=(~(~(y.random()*16)))[H](16);a[12]=4;a[16]=(a[16]&3|8)[H](16);return"r-"+a[v]("")}a.setWindow=function(a){h=a;g=h.document};var bi=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write("");e.close();d=e.body}catch(a){d=createPopup().document.body}var f=d.createTextRange();bi=bm(function(a){try{d.style.color=r(a)[Y](c,p);var b=f.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b[H](16)).slice(-6)}catch(a){return"none"}})}else{var h=g.createElement("i");h.title="Raphaël Colour Picker";h.style.display="none";g.body[l](h);bi=bm(function(a){h.style.color=a;return g.defaultView.getComputedStyle(h,p).getPropertyValue("color")})}return bi(b)},bj=function(){return"hsb("+[this.h,this.s,this.b]+")"},bk=function(){return"hsl("+[this.h,this.s,this.l]+")"},bl=function(){return this.hex};a.hsb2rgb=function(b,c,d,e){if(a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b){d=b.b;c=b.s;b=b.h;e=b.o}return a.hsl2rgb(b,c,d/2,e)};a.hsl2rgb=function(b,c,d,e){if(a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b){d=b.l;c=b.s;b=b.h}if(b>1||c>1||d>1){b/=360;c/=100;d/=100}var f={},g=["r","g","b"],h,i,j,k,l,m;if(c){d<0.5?h=d*(1+c):h=d+c-d*c;i=2*d-h;for(var n=0;n<3;n++){j=b+1/3*-(n-1);j<0&&j++;j>1&&j--;j*6<1?f[g[n]]=i+(h-i)*6*j:j*2<1?f[g[n]]=h:j*3<2?f[g[n]]=i+(h-i)*(2/3-j)*6:f[g[n]]=i}}else f={r:d,g:d,b:d};f.r*=255;f.g*=255;f.b*=255;f.hex="#"+(16777216|f.b|f.g<<8|f.r<<16).toString(16).slice(1);a.is(e,"finite")&&(f.opacity=e);f.toString=bl;return f};a.rgb2hsb=function(b,c,d){if(c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b){d=b.b;c=b.g;b=b.r}if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r;c=e.g;d=e.b}if(b>1||c>1||d>1){b/=255;c/=255;d/=255}var f=z(b,c,d),g=A(b,c,d),h,i,j=f;{if(g==f)return{h:0,s:0,b:f,toString:bj};var k=f-g;i=k/f;b==f?h=(c-d)/k:c==f?h=2+(d-b)/k:h=4+(b-c)/k;h/=6;h<0&&h++;h>1&&h--}return{h:h,s:i,b:j,toString:bj}};a.rgb2hsl=function(b,c,d){if(c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b){d=b.b;c=b.g;b=b.r}if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r;c=e.g;d=e.b}if(b>1||c>1||d>1){b/=255;c/=255;d/=255}var f=z(b,c,d),g=A(b,c,d),h,i,j=(f+g)/2,k;if(g==f)k={h:0,s:0,l:j};else{var l=f-g;i=j<0.5?l/(f+g):l/(2-f-g);b==f?h=(c-d)/l:c==f?h=2+(d-b)/l:h=4+(b-c)/l;h/=6;h<0&&h++;h>1&&h--;k={h:h,s:i,l:j}}k.toString=bk;return k};a._path2string=function(){return this.join(",")[Y](ba,"$1")};function bm(a,b,c){function d(){var g=Array[e].slice.call(arguments,0),h=g[v]("►"),i=d.cache=d.cache||{},j=d.count=d.count||[];if(i[f](h))return c?c(i[h]):i[h];j[w]>=1000&&delete i[j.shift()];j[L](h);i[h]=a[m](b,g);return c?c(i[h]):i[h]}return d}a.getRGB=bm(function(b){if(!b||!(!((b=r(b)).indexOf("-")+1)))return{r:-1,g:-1,b:-1,hex:"none",error:1};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none"};!(_[f](b.toLowerCase().substring(0,2))||b.charAt()=="#")&&(b=bi(b));var c,d,e,g,h,i,j,k=b.match(N);if(k){if(k[2]){g=T(k[2].substring(5),16);e=T(k[2].substring(3,5),16);d=T(k[2].substring(1,3),16)}if(k[3]){g=T((i=k[3].charAt(3))+i,16);e=T((i=k[3].charAt(2))+i,16);d=T((i=k[3].charAt(1))+i,16)}if(k[4]){j=k[4][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);k[1].toLowerCase().slice(0,4)=="rgba"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100)}if(k[5]){j=k[5][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360);k[1].toLowerCase().slice(0,4)=="hsba"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,g,h)}if(k[6]){j=k[6][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360);k[1].toLowerCase().slice(0,4)=="hsla"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,g,h)}k={r:d,g:e,b:g};k.hex="#"+(16777216|g|e<<8|d<<16).toString(16).slice(1);a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1}},a);a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||0.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=0.075;if(b.h>1){b.h=0;b.s-=0.2;b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})}return c.hex};a.getColor.reset=function(){delete this.start};a.parsePathString=bm(function(b){if(!b)return null;var c={a:7,c:6,h:1,l:2,m:2,q:4,s:4,t:2,v:1,z:0},d=[];a.is(b,G)&&a.is(b[0],G)&&(d=bo(b));d[w]||r(b)[Y](bb,function(a,b,e){var f=[],g=x.call(b);e[Y](bc,function(a,b){b&&f[L](+b)});if(g=="m"&&f[w]>2){d[L]([b][n](f.splice(0,2)));g="l";b=b=="m"?"l":"L"}while(f[w]>=c[g]){d[L]([b][n](f.splice(0,c[g])));if(!c[g])break}});d[H]=a._path2string;return d});a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=C(j,3)*a+C(j,2)*3*i*c+j*3*i*i*e+C(i,3)*g,l=C(j,3)*b+C(j,2)*3*i*d+j*3*i*i*f+C(i,3)*h,m=a+2*i*(c-a)+i*i*(e-2*c+a),n=b+2*i*(d-b)+i*i*(f-2*d+b),o=c+2*i*(e-c)+i*i*(g-2*e+c),p=d+2*i*(f-d)+i*i*(h-2*f+d),q=(1-i)*a+i*c,r=(1-i)*b+i*d,s=(1-i)*e+i*g,t=(1-i)*f+i*h,u=90-y.atan((m-o)/(n-p))*180/D;(m>o||n1){x=y.sqrt(x);c=x*c;d=x*d}var z=c*c,A=d*d,C=(f==g?-1:1)*y.sqrt(B((z*A-z*u*u-A*t*t)/(z*u*u+A*t*t))),E=C*c*u/d+(a+h)/2,F=C*-d*t/c+(b+i)/2,G=y.asin(((b-F)/d).toFixed(9)),H=y.asin(((i-F)/d).toFixed(9));G=aH&&(G=G-D*2);!g&&H>G&&(H=H-D*2)}var I=H-G;if(B(I)>k){var J=H,K=h,L=i;H=G+k*(g&&H>G?1:-1);h=E+c*y.cos(H);i=F+d*y.sin(H);m=bt(h,i,c,d,e,0,g,K,L,[H,J,E,F])}I=H-G;var M=y.cos(G),N=y.sin(G),O=y.cos(H),P=y.sin(H),Q=y.tan(I/4),R=4/3*c*Q,S=4/3*d*Q,T=[a,b],U=[a+R*N,b-S*M],V=[h+R*P,i-S*O],W=[h,i];U[0]=2*T[0]-U[0];U[1]=2*T[1]-U[1];{if(j)return[U,V,W][n](m);m=[U,V,W][n](m)[v]()[s](",");var X=[];for(var Y=0,Z=m[w];Y"1e12"&&(l=0.5);B(n)>"1e12"&&(n=0.5);if(l>0&&l<1){q=bu(a,b,c,d,e,f,g,h,l);p[L](q.x);o[L](q.y)}if(n>0&&n<1){q=bu(a,b,c,d,e,f,g,h,n);p[L](q.x);o[L](q.y)}i=f-2*d+b-(h-2*f+d);j=2*(d-b)-2*(f-d);k=b-d;l=(-j+y.sqrt(j*j-4*i*k))/2/i;n=(-j-y.sqrt(j*j-4*i*k))/2/i;B(l)>"1e12"&&(l=0.5);B(n)>"1e12"&&(n=0.5);if(l>0&&l<1){q=bu(a,b,c,d,e,f,g,h,l);p[L](q.x);o[L](q.y)}if(n>0&&n<1){q=bu(a,b,c,d,e,f,g,h,n);p[L](q.x);o[L](q.y)}return{min:{x:A[m](0,p),y:A[m](0,o)},max:{x:z[m](0,p),y:z[m](0,o)}}}),bw=bm(function(a,b){var c=bq(a),d=b&&bq(b),e={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1];b.Y=a[2];break;case"A":a=["C"][n](bt[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x));d=b.y+(b.y-(b.by||b.y));a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x));b.qy=b.y+(b.y-(b.qy||b.y));a=["C"][n](bs(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1];b.qy=a[2];a=["C"][n](bs(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](br(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](br(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](br(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](br(b.x,b.y,b.X,b.Y));break}return a},h=function(a,b){if(a[b][w]>7){a[b].shift();var e=a[b];while(e[w])a.splice(b++,0,["C"][n](e.splice(0,6)));a.splice(b,1);k=z(c[w],d&&d[w]||0)}},i=function(a,b,e,f,g){if(a&&b&&a[g][0]=="M"&&b[g][0]!="M"){b.splice(g,0,["M",f.x,f.y]);e.bx=0;e.by=0;e.x=a[g][1];e.y=a[g][2];k=z(c[w],d&&d[w]||0)}};for(var j=0,k=z(c[w],d&&d[w]||0);j0.5)*2-1;C(e-0.5,2)+C(f-0.5,2)>0.25&&(f=y.sqrt(0.25-C(e-0.5,2))*g+0.5)&&f!=0.5&&(f=f.toFixed(5)-0.00001*g)}return p});b=b[s](/\s*\-\s*/);if(d=="linear"){var i=b.shift();i=-S(i);if(isNaN(i))return null;var j=[0,0,y.cos(i*D/180),y.sin(i*D/180)],k=1/(z(B(j[2]),B(j[3]))||1);j[2]*=k;j[3]*=k;if(j[2]<0){j[0]=-j[2];j[2]=0}if(j[3]<0){j[1]=-j[3];j[3]=0}}var m=bx(b);if(!m)return null;var n=a.getAttribute(I);n=n.match(/^url\(#(.*)\)$/);n&&c.defs.removeChild(g.getElementById(n[1]));var o=bG(d+"Gradient");o.id=bh();bG(o,d=="radial"?{fx:e,fy:f}:{x1:j[0],y1:j[1],x2:j[2],y2:j[3]});c.defs[l](o);for(var q=0,t=m[w];q1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(o);h[R](n,G.hex);n=="stroke"&&G[f]("opacity")&&bG(h,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity});break;case"gradient":(({circle:1,ellipse:1})[f](c.type)||r(o).charAt()!="r")&&bI(h,o,c.paper);break;case"opacity":i.gradient&&!i[f]("stroke-opacity")&&bG(h,{"stroke-opacity":o>1?o/100:o});case"fill-opacity":if(i.gradient){var H=g.getElementById(h.getAttribute(I)[Y](/^url\(#|\)$/g,p));if(H){var J=H.getElementsByTagName("stop");J[J[w]-1][R]("stop-opacity",o)}break}default:n=="font-size"&&(o=T(o,10)+"px");var K=n[Y](/(\-.)/g,function(a){return V.call(a.substring(1))});h.style[K]=o;h[R](n,o);break}}}bM(c,d);m?c.rotate(m.join(q)):S(j)&&c.rotate(j,true)},bL=1.2,bM=function(b,c){if(b.type!="text"||!(c[f]("text")||c[f]("font")||c[f]("font-size")||c[f]("x")||c[f]("y")))return;var d=b.attrs,e=b.node,h=e.firstChild?T(g.defaultView.getComputedStyle(e.firstChild,p).getPropertyValue("font-size"),10):10;if(c[f]("text")){d.text=c.text;while(e.firstChild)e.removeChild(e.firstChild);var i=r(c.text)[s]("\n");for(var j=0,k=i[w];jb.height&&(b.height=e.y+e.height-b.y);e.x+e.width-b.x>b.width&&(b.width=e.x+e.width-b.x)}}a&&this.hide();return b};bN[e].attr=function(b,c){if(this.removed)return this;if(b==null){var d={};for(var e in this.attrs)this.attrs[f](e)&&(d[e]=this.attrs[e]);this._.rt.deg&&(d.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(d.scale=this.scale());d.gradient&&d.fill=="none"&&(d.fill=d.gradient)&&delete d.gradient;return d}if(c==null&&a.is(b,F)){if(b=="translation")return cz.call(this);if(b=="rotation")return this.rotate();if(b=="scale")return this.scale();if(b==I&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;return this.attrs[b]}if(c==null&&a.is(b,G)){var g={};for(var h=0,i=b.length;h"));m.W=h.w=m.paper.span.offsetWidth;m.H=h.h=m.paper.span.offsetHeight;m.X=h.x;m.Y=h.y+Q(m.H/2);switch(h["text-anchor"]){case"start":m.node.style["v-text-align"]="left";m.bbx=Q(m.W/2);break;case"end":m.node.style["v-text-align"]="right";m.bbx=-Q(m.W/2);break;default:m.node.style["v-text-align"]="center";break}}};bI=function(a,b){a.attrs=a.attrs||{};var c=a.attrs,d,e="linear",f=".5 .5";a.attrs.gradient=b;b=r(b)[Y](bd,function(a,b,c){e="radial";if(b&&c){b=S(b);c=S(c);C(b-0.5,2)+C(c-0.5,2)>0.25&&(c=y.sqrt(0.25-C(b-0.5,2))*((c>0.5)*2-1)+0.5);f=b+q+c}return p});b=b[s](/\s*\-\s*/);if(e=="linear"){var g=b.shift();g=-S(g);if(isNaN(g))return null}var h=bx(b);if(!h)return null;a=a.shape||a.node;d=a.getElementsByTagName(I)[0]||cd(I);!d.parentNode&&a.appendChild(d);if(h[w]){d.on=true;d.method="none";d.color=h[0].color;d.color2=h[h[w]-1].color;var i=[];for(var j=0,k=h[w];j")}}catch(a){cd=function(a){return g.createElement("<"+a+" xmlns=\"urn:schemas-microsoft.com:vml\" class=\"rvml\">")}}bV=function(){var b=by[m](0,arguments),c=b.container,d=b.height,e,f=b.width,h=b.x,i=b.y;if(!c)throw new Error("VML container not found.");var k=new j,n=k.canvas=g.createElement("div"),o=n.style;h=h||0;i=i||0;f=f||512;d=d||342;f==+f&&(f+="px");d==+d&&(d+="px");k.width=1000;k.height=1000;k.coordsize=b_*1000+q+b_*1000;k.coordorigin="0 0";k.span=g.createElement("span");k.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";n[l](k.span);o.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d);if(c==1){g.body[l](n);o.left=h+"px";o.top=i+"px";o.position="absolute"}else c.firstChild?c.insertBefore(n,c.firstChild):c[l](n);bz.call(k,k,a.fn);return k};k.clear=function(){this.canvas.innerHTML=p;this.span=g.createElement("span");this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";this.canvas[l](this.span);this.bottom=this.top=null};k.remove=function(){this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]=bF(a);return true}}var ce=navigator.userAgent.match(/Version\\x2f(.*?)\s/);navigator.vendor=="Apple Computer, Inc."&&(ce&&ce[1]<4||navigator.platform.slice(0,2)=="iP")?k.safari=function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});h.setTimeout(function(){a.remove()})}:k.safari=function(){};var cf=function(){this.returnValue=false},cg=function(){return this.originalEvent.preventDefault()},ch=function(){this.cancelBubble=true},ci=function(){return this.originalEvent.stopPropagation()},cj=(function(){{if(g.addEventListener)return function(a,b,c,d){var e=o&&u[b]?u[b]:b,g=function(e){if(o&&u[f](b))for(var g=0,h=e.targetTouches&&e.targetTouches.length;g1&&(a=Array[e].splice.call(arguments,0,arguments[w]));return new cC(a)};k.setSize=bU;k.top=k.bottom=null;k.raphael=a;function co(){return this.x+q+this.y}bO.resetScale=function(){if(this.removed)return this;this._.sx=1;this._.sy=1;this.attrs.scale="1 1"};bO.scale=function(a,b,c,d){if(this.removed)return this;if(a==null&&b==null)return{x:this._.sx,y:this._.sy,toString:co};b=b||a;!(+b)&&(b=a);var e,f,g,h,i=this.attrs;if(a!=0){var j=this.getBBox(),k=j.x+j.width/2,l=j.y+j.height/2,m=B(a/this._.sx),o=B(b/this._.sy);c=+c||c==0?c:k;d=+d||d==0?d:l;var r=this._.sx>0,s=this._.sy>0,t=~(~(a/B(a))),u=~(~(b/B(b))),x=m*t,y=o*u,z=this.node.style,A=c+B(k-c)*x*(k>c==r?1:-1),C=d+B(l-d)*y*(l>d==s?1:-1),D=a*t>b*u?o:m;switch(this.type){case"rect":case"image":var E=i.width*m,F=i.height*o;this.attr({height:F,r:i.r*D,width:E,x:A-E/2,y:C-F/2});break;case"circle":case"ellipse":this.attr({rx:i.rx*m,ry:i.ry*o,r:i.r*D,cx:A,cy:C});break;case"text":this.attr({x:A,y:C});break;case"path":var G=bp(i.path),H=true,I=r?x:m,J=s?y:o;for(var K=0,L=G[w];Kr)p=n.data[r*l];else{p=a.findDotsAtSegment(b,c,d,e,f,g,h,i,r/l);n.data[r]=p}r&&(k+=C(C(o.x-p.x,2)+C(o.y-p.y,2),0.5));if(j!=null&&k>=j)return p;o=p}if(j==null)return k},cr=function(b,c){return function(d,e,f){d=bw(d);var g,h,i,j,k="",l={},m,n=0;for(var o=0,p=d.length;oe){if(c&&!l.start){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);k+=["C",m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k;k=["M",m.x,m.y+"C",m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]][v]();n+=j;g=+i[5];h=+i[6];continue}if(!b&&!c){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j;g=+i[5];h=+i[6]}k+=i}l.end=k;m=b?n:c?l:a.findDotsAtSegment(g,h,i[1],i[2],i[3],i[4],i[5],i[6],1);m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cs=cr(1),ct=cr(),cu=cr(0,1);bO.getTotalLength=function(){if(this.type!="path")return;if(this.node.getTotalLength)return this.node.getTotalLength();return cs(this.attrs.path)};bO.getPointAtLength=function(a){if(this.type!="path")return;return ct(this.attrs.path,a)};bO.getSubpath=function(a,b){if(this.type!="path")return;if(B(this.getTotalLength()-b)<"1e-6")return cu(this.attrs.path,a).end;var c=cu(this.attrs.path,b,1);return a?cu(c,a).end:c};a.easing_formulas={linear:function(a){return a},"<":function(a){return C(a,3)},">":function(a){return C(a-1,3)+1},"<>":function(a){a=a*2;if(a<1)return C(a,3)/2;a-=2;return(C(a,3)+2)/2},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==0||a==1)return a;var b=0.3,c=b/4;return C(2,-10*a)*y.sin((a-c)*(2*D)/b)+1},bounce:function(a){var b=7.5625,c=2.75,d;if(a<1/c)d=b*a*a;else if(a<2/c){a-=1.5/c;d=b*a*a+0.75}else if(a<2.5/c){a-=2.25/c;d=b*a*a+0.9375}else{a-=2.625/c;d=b*a*a+0.984375}return d}};var cv=[],cw=function(){var b=+(new Date);for(var c=0;cd)return d;while(cf?c=e:d=e;e=(d-c)/2+c}return e}return n(a,1/(200*f))}bO.onAnimation=function(a){this._run=a||0;return this};bO.animate=function(c,d,e,g){var h=this;h.timeouts=h.timeouts||[];if(a.is(e,"function")||!e)g=e||null;if(h.removed){g&&g.call(h);return h}var i={},j={},k=false,l={};for(var m in c)if(c[f](m)){if(X[f](m)||h.paper.customAttributes[f](m)){k=true;i[m]=h.attr(m);i[m]==null&&(i[m]=W[m]);j[m]=c[m];switch(X[m]){case"along":var n=cs(c[m]),o=ct(c[m],n*!(!c.back)),p=h.getBBox();l[m]=n/d;l.tx=p.x;l.ty=p.y;l.sx=o.x;l.sy=o.y;j.rot=c.rot;j.back=c.back;j.len=n;c.rot&&(l.r=S(h.rotate())||0);break;case E:l[m]=(j[m]-i[m])/d;break;case"colour":i[m]=a.getRGB(i[m]);var q=a.getRGB(j[m]);l[m]={r:(q.r-i[m].r)/d,g:(q.g-i[m].g)/d,b:(q.b-i[m].b)/d};break;case"path":var t=bw(i[m],j[m]);i[m]=t[0];var u=t[1];l[m]=[];for(var v=0,x=i[m][w];v