/*
* 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 interpret_label(x) {
if (typeof x === "object") return x;
if (typeof x === "number") return { pos: x };
TK.error("Unsupported label type ", x);
}
var __rad = Math.PI / 180;
function _get_coords(deg, inner, outer, pos) {
deg = +deg;
inner = +inner;
outer = +outer;
pos = +pos;
deg = deg * __rad;
return {
x1: Math.cos(deg) * outer + pos,
y1: Math.sin(deg) * outer + pos,
x2: Math.cos(deg) * inner + pos,
y2: Math.sin(deg) * inner + pos
}
}
function _get_coords_single(deg, inner, pos) {
deg = +deg;
inner = +inner;
pos = +pos;
deg = deg * __rad;
return {
x: Math.cos(deg) * inner + pos,
y: Math.sin(deg) * inner + pos
}
}
var format_path = TK.FORMAT("M %f,%f " +
"A %f,%f 0 %d,%d %f,%f " +
"L %f,%f " +
"A %f,%f 0 %d,%d %f,%f z");
var format_translate = TK.FORMAT("translate(%f, %f)");
var format_translate_rotate = TK.FORMAT("translate(%f %f) rotate(%f %f %f)");
var format_rotate = TK.FORMAT("rotate(%f %f %f)");
function draw_dots() {
// depends on dots, dot, min, max, size
var _dots = this._dots;
var O = this.options;
var dots = O.dots;
var dot = O.dot;
var angle = O.angle;
TK.empty(_dots);
for (var i = 0; i < dots.length; i++) {
var m = dots[i];
var r = TK.make_svg("rect", {"class": "toolkit-dot"});
var length = m.length === void(0)
? dot.length : m.length;
var width = m.width === void(0)
? dot.width : m.width;
var margin = m.margin === void(0)
? dot.margin : m.margin;
var pos = Math.min(O.max, Math.max(O.min, m.pos));
// TODO: consider adding them all at once
_dots.appendChild(r);
if (m["class"]) TK.add_class(r, m["class"]);
if (m.color) r.style.fill = m.color;
r.setAttribute("x", O.size - length - margin);
r.setAttribute("y", O.size / 2 - width / 2);
r.setAttribute("width", length);
r.setAttribute("height", width);
r.setAttribute("transform", "rotate("
+ (this.val2coef(this.snap(pos)) * angle) + " "
+ (O.size / 2) + " " + (this.options.size / 2) + ")");
}
/**
* Is fired when dots are (re)drawn.
* @event TK.Circular#dotsdrawn
*/
this.fire_event("dotsdrawn");
}
function draw_markers() {
// depends on size, markers, marker, min, max
var I = this.invalid;
var O = this.options;
var markers = O.markers;
var marker = O.marker;
TK.empty(this._markers);
var stroke = this._get_stroke();
var outer = O.size / 2;
var angle = O.angle;
for (var i = 0; i < markers.length; i++) {
var m = markers[i];
var thick = m.thickness === void(0)
? marker.thickness : m.thickness;
var margin = m.margin === void(0)
? marker.margin : m.margin;
var inner = outer - thick;
var outer_p = outer - margin - stroke / 2;
var inner_p = inner - margin - stroke / 2;
var from, to;
if (m.from === void(0))
from = O.min;
else
from = Math.min(O.max, Math.max(O.min, m.from));
if (m.to === void(0))
to = O.max;
else
to = Math.min(O.max, Math.max(O.min, m.to));
var s = TK.make_svg("path", {"class": "toolkit-marker"});
this._markers.appendChild(s);
if (m["class"]) TK.add_class(s, m["class"]);
if (m.color) s.style.fill = m.color;
if (!m.nosnap) {
from = this.snap(from);
to = this.snap(to);
}
from = this.val2coef(from) * angle;
to = this.val2coef(to) * angle;
draw_slice.call(this, from, to, inner_p, outer_p, outer, s);
}
/**
* Is fired when markers are (re)drawn.
* @event TK.Circular#markersdrawn
*/
this.fire_event("markersdrawn");
}
function draw_labels() {
// depends on size, labels, label, min, max, start
var _labels = this._labels;
var O = this.options;
var labels = O.labels;
TK.empty(this._labels);
if (!labels.length) return;
var outer = O.size / 2;
var a = new Array(labels.length);
var i;
var l, p, positions = new Array(labels.length);
for (i = 0; i < labels.length; i++) {
l = labels[i];
p = TK.make_svg("text", {"class": "toolkit-label",
style: "dominant-baseline: central;"
});
if (l["class"]) TK.add_class(p, l["class"]);
if (l.color) p.style.fill = l.color;
if (l.label !== void(0))
p.textContent = l.label;
else
p.textContent = O.label.format(l.pos);
p.setAttribute("text-anchor", "middle");
_labels.appendChild(p);
a[i] = p;
}
/* FORCE_RELAYOUT */
TK.S.add(function() {
var i, p;
for (i = 0; i < labels.length; i++) {
l = labels[i];
p = a[i];
var margin = l.margin !== void(0) ? l.margin : O.label.margin;
var align = (l.align !== void(0) ? l.align : O.label.align) === "inner";
var pos = Math.min(O.max, Math.max(O.min, l.pos));
var bb = p.getBBox();
var angle = (this.val2coef(this.snap(pos)) * O.angle + O.start) % 360;
var outer_p = outer - margin;
var coords = _get_coords_single(angle, outer_p, outer);
var mx = ((coords.x - outer) / outer_p) * (bb.width + bb.height / 2.5) / (align ? -2 : 2);
var my = ((coords.y - outer) / outer_p) * bb.height / (align ? -2 : 2);
positions[i] = format_translate(coords.x + mx, coords.y + my);
}
TK.S.add(function() {
for (i = 0; i < labels.length; i++) {
p = a[i];
p.setAttribute("transform", positions[i]);
}
/**
* Is fired when labels are (re)drawn.
* @event TK.Circular#labelsdrawn
*/
this.fire_event("labelsdrawn");
}.bind(this), 1);
}.bind(this));
}
function draw_slice(a_from, a_to, r_inner, r_outer, pos, slice) {
a_from = +a_from;
a_to = +a_to;
r_inner = +r_inner;
r_outer = +r_outer;
pos = +pos;
// ensure from !== to
if(a_from % 360 === a_to % 360) a_from += 0.001;
// ensure from and to in bounds
while (a_from < 0) a_from += 360;
while (a_to < 0) a_to += 360;
if (a_from > 360) a_from %= 360;
if (a_to > 360) a_to %= 360;
// get drawing direction (sweep = clock-wise)
if (this.options.reverse && a_to <= a_from
|| !this.options.reverse && a_to > a_from)
var sweep = 1;
else
var sweep = 0;
// get large flag
if (Math.abs(a_from - a_to) >= 180)
var large = 1;
else
var large = 0;
// draw this slice
var from = _get_coords(a_from, r_inner, r_outer, pos);
var to = _get_coords(a_to, r_inner, r_outer, pos);
var path = format_path(from.x1, from.y1,
r_outer, r_outer, large, sweep, to.x1, to.y1,
to.x2, to.y2,
r_inner, r_inner, large, !sweep, from.x2, from.y2);
slice.setAttribute("d", path);
}
TK.Circular = TK.class({
/**
* TK.Circular is a SVG group element containing two paths for displaying
* numerical values in a circular manner. TK.Circular is able to draw labels,
* dots and markers and can show a hand. TK.Circular e.g. is implemented by
* {@link TK.Clock} to draw hours, minutes and seconds.
*
* @class TK.Circular
*
* @param {Object} [options={ }] - An object containing initial options.
*
* @property {Number} [options.value=0] - Sets the value on the hand and on the
* ring at the same time.
* @property {Number} [options.value_hand=0] - Sets the value on the hand.
* @property {Number} [options.value_ring=0] - Sets the value on the ring.
* @property {Number} [options.size=100] - The diameter of the circle. This
* is the base value for all following layout-related parameters. Keeping
* it set to 100 offers percentual lenghts. Set the final size of the widget
* via CSS.
* @property {Number} [options.thickness=3] - The thickness of the circle.
* @property {Number} [options.margin=0] - The margin between base and value circles.
* @property {Boolean} [options.show_hand=true] - Draw the hand.
* @property {Object} [options.hand] - Dimensions of the hand.
* @property {Number} [options.hand.width=2] - Width of the hand.
* @property {Number} [options.hand.length=30] - Length of the hand.
* @property {Number} [options.hand.margin=10] - Margin of the hand.
* @property {Number} [options.start=135] - The starting point in degrees.
* @property {Number} [options.angle=270] - The maximum degree of the rotation when
* options.value === options.max.
* @property {Number|Boolean} [options.base=false] - If a base value is set in degrees,
* circular starts drawing elements from this position.
* @property {Boolean} [options.show_base=true] - Draw the base ring.
* @property {Boolean} [options.show_value=true] - Draw the value ring.
* @property {Number} [options.x=0] - Horizontal displacement of the circle.
* @property {Number} [options.y=0] - Vertical displacement of the circle.
* @property {Boolean} [options.show_dots=true] - Show/hide all dots.
* @property {Object} [options.dot] - This option acts as default values for the individual dots
* specified in options.dots.
* @property {Number} [options.dot.width=2] - Width of the dots.
* @property {Number} [options.dot.length=2] - Length of the dots.
* @property {Number} [options.dot.margin=5] - Margin of the dots.
* @property {Array