/* * This file is part of Toolkit. * * Toolkit is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * Toolkit is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General * Public License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ "use strict"; (function(w, TK) { function draw_lines(a, mode, last) { var labels = new Array(a.length); var coords = new Array(a.length); var i, label, obj; for (i = 0; i < a.length; i++) { obj = a[i]; if (obj.label) { label = TK.make_svg("text"); label.textContent = obj.label; label.style["dominant-baseline"] = "central"; TK.add_class(label, "toolkit-grid-label"); TK.add_class(label, mode ? "toolkit-horizontal" : "toolkit-vertical"); if (obj["class"]) TK.add_class(label, obj["class"]); this.element.appendChild(label); labels[i] = label; } } var w = this.range_x.options.basis; var h = this.range_y.options.basis; TK.S.add(function() { /* FORCE_RELAYOUT */ for (i = 0; i < a.length; i++) { obj = a[i]; label = labels[i]; if (!label) continue; var bb; try { bb = label.getBBox(); } catch(e) { // if we are hidden, this may throw // we should force redraw at some later point, but // its hard to do. the grid should really be deactivated // by an option. continue; } var tw = bb.width; var th = bb.height; var p = TK.get_style(label, "padding").split(" "); if (p.length < 2) p[1] = p[2] = p[3] = p[0]; if (p.length < 3) { p[2] = p[0]; p[3] = p[1]; } if (p.length < 4) p[3] = p[1]; var pt = parseInt(p[0]) || 0; var pr = parseInt(p[1]) || 0; var pb = parseInt(p[2]) || 0; var pl = parseInt(p[3]) || 0; var x, y; if (mode) { y = Math.max(th / 2, Math.min(h - th / 2 - pt, this.range_y.val2px(obj.pos))); if (y > last) continue; x = w - tw - pl; coords[i] = { x : x, y : y, m : tw + pl + pr, }; last = y - th; } else { x = Math.max(pl, Math.min(w - tw - pl, this.range_x.val2px(obj.pos) - tw / 2)); if (x < last) continue; y = h-th/2-pt; coords[i] = { x : x, y : y, m : th + pt + pb, }; last = x + tw; } } TK.S.add(function() { for (i = 0; i < a.length; i++) { label = labels[i]; if (label) { obj = coords[i]; if (obj) { label.setAttribute("x", obj.x); label.setAttribute("y", obj.y); } else { if (label.parentElement == this.element) this.element.removeChild(label); } } } for (i = 0; i < a.length; i++) { obj = a[i]; label = coords[i]; var m; if (label) m = label.m; else m = 0; if ((mode && obj.pos === this.range_y.options.min) || ( mode && obj.pos === this.range_y.options.max) || (!mode && obj.pos === this.range_x.options.min) || (!mode && obj.pos === this.range_x.options.max)) continue; var line = TK.make_svg("path"); TK.add_class(line, "toolkit-grid-line"); TK.add_class(line, mode ? "toolkit-horizontal" : "toolkit-vertical"); if (obj["class"]) TK.add_class(line, obj["class"]); if (obj.color) line.setAttribute("style", "stroke:" + obj.color); if (mode) { // line from left to right line.setAttribute("d", "M0 " + Math.round(this.range_y.val2px(obj.pos)) + ".5 L" + (this.range_x.options.basis - m) + " " + Math.round(this.range_y.val2px(obj.pos)) + ".5"); } else { // line from top to bottom line.setAttribute("d", "M" + Math.round(this.range_x.val2px(obj.pos)) + ".5 0 L" + Math.round(this.range_x.val2px(obj.pos)) + ".5 " + (this.range_y.options.basis - m)); } this.element.appendChild(line); } }.bind(this), 1); }.bind(this)); } TK.Grid = TK.class({ /** * TK.Grid creates a couple of lines and labels in a SVG * image on the x and y axis. It is used in e.g. {@link TK.Graph} and * {@link TK.FrequencyResponse} to draw markers and values. TK.Grid needs a * parent SVG image do draw into. The base element of a TK.Grid is a * SVG group containing all the labels and lines. * * @class TK.Grid * * @param {Object} [options={ }] - An object containing initial options. * * @property {Array} [options.grid_x=[]] - Array for vertical grid line definitions with the members: * @property {Number} [options.grid_x.pos] - The value where to draw grid line and correspon ding label. * @property {String} [options.grid_x.color] - A valid CSS color string to colorize the elements. * @property {String} [options.grid_x.class] - A class name for the elements. * @property {String} [options.grid_x.label] - A label string. * @property {Array} [options.grid_y=[]] - Array for horizontal grid lines with the members: * @property {Number} [options.grid_y.pos] - The value where to draw grid line and corresponding label. * @property {String} [options.grid_y.color] - A valid CSS color string to colorize the elements. * @property {String} [options.grid_y.class] - A class name for the elements. * @property {String} [options.grid_y.label] - A label string. * @property {Function|Object} [options.range_x={}] - A function returning * a {@link TK.Range} instance for vertical grid lines or an object * containing options. for a new {@link Range}. * @property {Function|Object} [options.range_y={}] - A function returning * a {@link TK.Range} instance for horizontal grid lines or an object * containing options. for a new {@link Range}. * @property {Number} [options.width=0] - Width of the grid. * @property {Number} [options.height=0] - Height of the grid. * * @extends TK.Widget * * @mixes TK.Ranges */ _class: "Grid", Extends: TK.Widget, Implements: TK.Ranges, _options: Object.assign(Object.create(TK.Widget.prototype._options), { grid_x: "array", grid_y: "array", range_x: "object", range_y: "object", width: "number", height: "number", }), options: { grid_x: [], grid_y: [], range_x: {}, range_y: {}, width: 0, height: 0 }, initialize: function (options) { TK.Widget.prototype.initialize.call(this, options); /** * @member {SVGGroup} TK.Grid#element - The main SVG group containing all grid elements. Has class toolkit-grid. */ this.element = this.widgetize( TK.make_svg("g", {"class": "toolkit-grid"}), true, true, true); /** * @member {TK.Range} TK.Grid#range_x - The range for the x axis. */ /** * @member {TK.Range} TK.Grid#range_y - The range for the y axis. */ this.add_range(this.options.range_x, "range_x"); this.add_range(this.options.range_y, "range_y"); if (this.options.width) this.set("width", this.options.width); if (this.options.height) this.set("height", this.options.width); this.invalidate_ranges = function (key, value) { this.invalid.range_x = true; this.invalid.range_y = true; this.trigger_draw(); }.bind(this); this.range_x.add_event("set", this.invalidate_ranges); this.range_y.add_event("set", this.invalidate_ranges); }, redraw: function () { var I = this.invalid, O = this.options; if (I.validate("grid_x", "grid_y", "range_x", "range_y")) { TK.empty(this.element); draw_lines.call(this, O.grid_x, false, 0); draw_lines.call(this, O.grid_y, true, this.range_y.options.basis); } TK.Widget.prototype.redraw.call(this); }, destroy: function () { this.range_x.remove_event("set", this.invalidate_ranges); this.range_y.remove_event("set", this.invalidate_ranges); TK.Widget.prototype.destroy.call(this); }, // GETTER & SETTER set: function (key, value) { this.options[key] = value; switch (key) { case "grid_x": case "grid_y": /** * Is fired when the grid has changed. * * @event TK.Grid#gridchanged * * @param {Array} grid_x - The grid elements for x axis. * @param {Array} grid_y - The grid elements for y axis. */ this.fire_event("gridchanged", this.options.grid_x, this.options.grid_y); break; case "width": this.range_x.set("basis", value); break; case "height": this.range_y.set("basis", value); break; } TK.Widget.prototype.set.call(this, key, value); } }); })(this, this.TK);