840 lines
29 KiB
JavaScript
840 lines
29 KiB
JavaScript
|
/*
|
||
|
* 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 calculate_overlap(X, Y) {
|
||
|
/* no overlap, return 0 */
|
||
|
if (X[2] < Y[0] || Y[2] < X[0] || X[3] < Y[1] || Y[3] < X[1]) return 0;
|
||
|
|
||
|
return (Math.min(X[2], Y[2]) - Math.max(X[0], Y[0])) *
|
||
|
(Math.min(X[3], Y[3]) - Math.max(X[1], Y[1]));
|
||
|
}
|
||
|
|
||
|
function show_handles() {
|
||
|
var handles = this.handles;
|
||
|
|
||
|
for (var i = 0; i < handles.length; i++) {
|
||
|
this.add_child(handles[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function hide_handles() {
|
||
|
var handles = this.handles;
|
||
|
|
||
|
for (var i = 0; i < handles.length; i++) {
|
||
|
this.remove_child(handles[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var STOP = function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
return false;
|
||
|
}
|
||
|
function draw_key() {
|
||
|
var __key, bb;
|
||
|
|
||
|
var _key = this._key;
|
||
|
var _key_bg = this._key_background;
|
||
|
|
||
|
if (!_key || !_key_bg) return;
|
||
|
|
||
|
while (_key.firstChild !== _key.lastChild)
|
||
|
_key.removeChild(_key.lastChild);
|
||
|
|
||
|
TK.empty(_key.firstChild);
|
||
|
|
||
|
var O = this.options;
|
||
|
|
||
|
var disp = "none";
|
||
|
var gpad = TK.css_space(_key, "padding");
|
||
|
var gmarg = TK.css_space(_key, "margin");
|
||
|
var c = 0;
|
||
|
var w = 0;
|
||
|
var top = 0;
|
||
|
var lines = [];
|
||
|
for (var i = 0; i < this.graphs.length; i++) {
|
||
|
if (this.graphs[i].get("key") !== false) {
|
||
|
var t = TK.make_svg("tspan", {"class": "toolkit-label",
|
||
|
style: "dominant-baseline: central;"
|
||
|
});
|
||
|
t.textContent = this.graphs[i].get("key");
|
||
|
t.setAttribute("x", gpad.left);
|
||
|
_key.firstChild.appendChild(t);
|
||
|
|
||
|
if (!bb) bb = _key.getBoundingClientRect();
|
||
|
top += c ? parseInt(TK.get_style(t, "line-height")) : gpad.top;
|
||
|
t.setAttribute("y", top + bb.height / 2);
|
||
|
|
||
|
lines.push({
|
||
|
x: (parseInt(TK.get_style(t, "margin-right")) || 0),
|
||
|
y: Math.round(top),
|
||
|
width: Math.round(bb.width),
|
||
|
height: Math.round(bb.height),
|
||
|
"class": this.graphs[i].element.getAttribute("class"),
|
||
|
color: (this.graphs[i].element.getAttribute("color") || ""),
|
||
|
style: this.graphs[i].element.getAttribute("style")
|
||
|
})
|
||
|
w = Math.max(w, t.getComputedTextLength());
|
||
|
disp = "block";
|
||
|
c++;
|
||
|
}
|
||
|
}
|
||
|
for (var i = 0; i < lines.length; i++) {
|
||
|
var b = TK.make_svg("rect", {
|
||
|
"class": lines[i]["class"] + " toolkit-rect",
|
||
|
color: lines[i].color,
|
||
|
style: lines[i].style,
|
||
|
x: lines[i].x + 0.5 + w + gpad.left,
|
||
|
y: lines[i].y + 0.5 + parseInt(lines[i].height / 2 - O.key_size.y / 2),
|
||
|
height: O.key_size.y,
|
||
|
width: O.key_size.x
|
||
|
});
|
||
|
_key.appendChild(b);
|
||
|
}
|
||
|
_key_bg.style.display = disp;
|
||
|
_key.style.display = disp;
|
||
|
|
||
|
bb = _key.getBoundingClientRect();
|
||
|
var width = this.range_x.options.basis;
|
||
|
var height = this.range_y.options.basis;
|
||
|
|
||
|
switch (O.key) {
|
||
|
case "top-left":
|
||
|
__key = {
|
||
|
x1: gmarg.left,
|
||
|
y1: gmarg.top,
|
||
|
x2: gmarg.left + parseInt(bb.width) + gpad.left + gpad.right,
|
||
|
y2: gmarg.top + parseInt(bb.height) + gpad.top + gpad.bottom
|
||
|
}
|
||
|
break;
|
||
|
case "top-right":
|
||
|
__key = {
|
||
|
x1: width - gmarg.right - parseInt(bb.width) - gpad.left - gpad.right,
|
||
|
y1: gmarg.top,
|
||
|
x2: width - gmarg.right,
|
||
|
y2: gmarg.top + parseInt(bb.height) + gpad.top + gpad.bottom
|
||
|
}
|
||
|
break;
|
||
|
case "bottom-left":
|
||
|
__key = {
|
||
|
x1: gmarg.left,
|
||
|
y1: height - gmarg.bottom - parseInt(bb.height) - gpad.top - gpad.bottom,
|
||
|
x2: gmarg.left + parseInt(bb.width) + gpad.left + gpad.right,
|
||
|
y2: height - gmarg.bottom
|
||
|
}
|
||
|
break;
|
||
|
case "bottom-right":
|
||
|
__key = {
|
||
|
x1: width - gmarg.right - parseInt(bb.width) - gpad.left - gpad.right,
|
||
|
y1: height -gmarg.bottom - parseInt(bb.height) - gpad.top - gpad.bottom,
|
||
|
x2: width - gmarg.right,
|
||
|
y2: height - gmarg.bottom
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
TK.warn("Unsupported key", O.key);
|
||
|
}
|
||
|
_key.setAttribute("transform", "translate(" + __key.x1 + "," + __key.y1 + ")");
|
||
|
_key_bg.setAttribute("x", __key.x1);
|
||
|
_key_bg.setAttribute("y", __key.y1);
|
||
|
_key_bg.setAttribute("width", __key.x2 - __key.x1);
|
||
|
_key_bg.setAttribute("height", __key.y2 - __key.y1);
|
||
|
}
|
||
|
function draw_title() {
|
||
|
var _title = this._title;
|
||
|
if (!_title) return;
|
||
|
|
||
|
_title.textContent = this.options.title;
|
||
|
|
||
|
/* FORCE_RELAYOUT */
|
||
|
TK.S.add(function() {
|
||
|
var mtop = parseInt(TK.get_style(_title, "margin-top") || 0);
|
||
|
var mleft = parseInt(TK.get_style(_title, "margin-left") || 0);
|
||
|
var mbottom = parseInt(TK.get_style(_title, "margin-bottom") || 0);
|
||
|
var mright = parseInt(TK.get_style(_title, "margin-right") || 0);
|
||
|
var bb = _title.getBoundingClientRect();
|
||
|
var x,y,anchor, range_x = this.range_x, range_y = this.range_y;
|
||
|
switch (this.options.title_position) {
|
||
|
case "top-left":
|
||
|
anchor = "start";
|
||
|
x = mleft;
|
||
|
y = mtop + bb.height / 2;
|
||
|
break;
|
||
|
case "top":
|
||
|
anchor = "middle";
|
||
|
x = range_x.options.basis / 2;
|
||
|
y = mtop + bb.height / 2;
|
||
|
break;
|
||
|
case "top-right":
|
||
|
anchor = "end";
|
||
|
x = range_x.options.basis - mright;
|
||
|
y = mtop + bb.height / 2;
|
||
|
break;
|
||
|
case "left":
|
||
|
anchor = "start";
|
||
|
x = mleft;
|
||
|
y = range_y.options.basis / 2;
|
||
|
break;
|
||
|
case "center":
|
||
|
anchor = "middle";
|
||
|
x = range_x.options.basis / 2;
|
||
|
y = range_y.options.basis / 2;
|
||
|
break;
|
||
|
case "right":
|
||
|
anchor = "end";
|
||
|
x = range_x.options.basis - mright;
|
||
|
y = range_y.options.basis / 2;
|
||
|
break;
|
||
|
case "bottom-left":
|
||
|
anchor = "start";
|
||
|
x = mleft;
|
||
|
y = range_y.options.basis - mtop - bb.height / 2;
|
||
|
break;
|
||
|
case "bottom":
|
||
|
anchor = "middle";
|
||
|
x = range_x.options.basis / 2;
|
||
|
y = range_y.options.basis - mtop - bb.height / 2;
|
||
|
break;
|
||
|
case "bottom-right":
|
||
|
anchor = "end";
|
||
|
x = range_x.options.basis - mright;
|
||
|
y = range_y.options.basis - mtop - bb.height / 2;
|
||
|
break;
|
||
|
default:
|
||
|
TK.warn("Unsupported title_position", this.options.title_position);
|
||
|
}
|
||
|
TK.S.add(function() {
|
||
|
_title.setAttribute("text-anchor", anchor);
|
||
|
_title.setAttribute("x", x);
|
||
|
_title.setAttribute("y", y);
|
||
|
}, 1);
|
||
|
}.bind(this));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* TK.Chart is an SVG image containing one or more Graphs. TK.Chart
|
||
|
* extends {@link TK.Widget} and contains a {@link TK.Grid} and two
|
||
|
* {@link TK.Range}s.
|
||
|
*
|
||
|
* @param {Object} [options={ }] - An object containing initial options.
|
||
|
*
|
||
|
* @property {String|Boolean} [options.title=""] - A title for the Chart.
|
||
|
* Set to `false` to remove the title from the DOM.
|
||
|
* @property {String} [options.title_position="top-right"] - Position of the
|
||
|
* title inside of the chart. Possible values are
|
||
|
* <code>"top-left"</code>, <code>"top"</code>, <code>"top-right"</code>,
|
||
|
* <code>"left"</code>, <code>"center"</code>, <code>"right"</code>,
|
||
|
* <code>"bottom-left"</code>, <code>"bottom"</code> and
|
||
|
* <code>"bottom-right"</code>.
|
||
|
* @property {Boolean|String} [options.key=false] - If set to a string
|
||
|
* a key is rendered into the chart at the given position. The key
|
||
|
* will detail names and colors of the graphs inside of this chart.
|
||
|
* Possible values are <code>"top-left"</code>, <code>"top-right"</code>,
|
||
|
* <code>"bottom-left"</code> and <code>"bottom-right"</code>. Set to `false`
|
||
|
* to remove the key from the DOM.
|
||
|
* @property {Object} [options.key_size={x:20,y:10}] - Size of the colored
|
||
|
* rectangles inside of the key describing individual graphs.
|
||
|
* @property {Array<Object>} [options.grid_x=[]] - An array containing
|
||
|
* objects with the following optional members to draw the grid:
|
||
|
* @property {Number} [options.grid_x.pos] - The value where to draw grid line and corresponding 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<Object>} [options.grid_y=[]] - An array containing
|
||
|
* objects with the following optional members to draw the grid:
|
||
|
* @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 {Boolean} [options.show_grid=true] - Set to <code>false</code> to
|
||
|
* hide the grid.
|
||
|
* @property {Function|Object} [options.range_x={}] - Either a function
|
||
|
* returning a {@link TK.Range} or an object containing options for a
|
||
|
* new {@link TK.Range}.
|
||
|
* @property {Function|Object} [options.range_y={}] - Either a function
|
||
|
* returning a {@link TK.Range} or an object containing options for a
|
||
|
* new {@link TK.Range}.
|
||
|
* @property {Object|Function} [options.range_z={ scale: "linear", min: 0, max: 1 }] -
|
||
|
* Either a function returning a {@link TK.Range} or an object
|
||
|
* containing options for a new {@link TK.Range}.
|
||
|
* @property {Number} [options.importance_label=4] - Multiplicator of
|
||
|
* square pixels on hit testing labels to gain importance.
|
||
|
* @property {Number} [options.importance_handle=1] - Multiplicator of
|
||
|
* square pixels on hit testing handles to gain importance.
|
||
|
* @property {Number} [options.importance_border=50] - Multiplicator of
|
||
|
* square pixels on hit testing borders to gain importance.
|
||
|
* @property {Array<Object>} [options.handles=[]] - An array of options for
|
||
|
* creating {@link TK.ResponseHandle} on init.
|
||
|
* @property {Boolean} [options.show_handles=true] - Show or hide all
|
||
|
* handles.
|
||
|
*
|
||
|
* @class TK.Chart
|
||
|
*
|
||
|
* @extends TK.Widget
|
||
|
*/
|
||
|
function geom_set(value, key) {
|
||
|
this.set_style(key, value+"px");
|
||
|
TK.error("using deprecated '"+key+"' options");
|
||
|
}
|
||
|
TK.Chart = TK.class({
|
||
|
_class: "Chart",
|
||
|
Extends: TK.Widget,
|
||
|
Implements: TK.Ranges,
|
||
|
_options: Object.assign(Object.create(TK.Widget.prototype._options), {
|
||
|
width: "int",
|
||
|
height: "int",
|
||
|
_width: "int",
|
||
|
_height: "int",
|
||
|
range_x: "object",
|
||
|
range_y: "object",
|
||
|
range_z: "object",
|
||
|
key: "string",
|
||
|
key_size: "object",
|
||
|
title: "string",
|
||
|
title_position: "string",
|
||
|
resized: "boolean",
|
||
|
|
||
|
importance_label: "number",
|
||
|
importance_handle: "number",
|
||
|
importance_border: "number",
|
||
|
handles: "array",
|
||
|
show_handles: "boolean",
|
||
|
depth: "number",
|
||
|
}),
|
||
|
options: {
|
||
|
grid_x: [],
|
||
|
grid_y: [],
|
||
|
range_x: {}, // an object with options for a range for the x axis
|
||
|
// or a function returning a TK.Range instance (only on init)
|
||
|
range_y: {}, // an object with options for a range for the y axis
|
||
|
// or a function returning a TK.Range instance (only on init)
|
||
|
range_z: { scale: "linear", min: 0, max: 1 }, // TK.Range z options
|
||
|
key: false, // key draws a description for the graphs at the given
|
||
|
// position, use false for no key
|
||
|
key_size: {x:20, y:10}, // size of the key rects
|
||
|
title: "", // a title for the chart
|
||
|
title_position: "top-right", // the position of the title
|
||
|
resized: false,
|
||
|
|
||
|
importance_label: 4, // multiplicator of square pixels on hit testing
|
||
|
// labels to gain importance
|
||
|
importance_handle: 1, // multiplicator of square pixels on hit testing
|
||
|
// handles to gain importance
|
||
|
importance_border: 50, // multiplicator of square pixels on hit testing
|
||
|
// borders to gain importance
|
||
|
handles: [], // list of bands to create on init
|
||
|
show_handles: true,
|
||
|
},
|
||
|
static_events: {
|
||
|
set_width: geom_set,
|
||
|
set_height: geom_set,
|
||
|
|
||
|
mousewheel: STOP,
|
||
|
DOMMouseScroll: STOP,
|
||
|
set_depth: function(value) {
|
||
|
this.range_z.set("basis", value);
|
||
|
},
|
||
|
set_show_handles: function(value) {
|
||
|
(value ? show_handles : hide_handles).call(this);
|
||
|
},
|
||
|
},
|
||
|
initialize: function (options) {
|
||
|
var E, S;
|
||
|
/**
|
||
|
* @member {Array} TK.Chart#graphs - An array containing all SVG paths acting as graphs.
|
||
|
*/
|
||
|
this.graphs = [];
|
||
|
/**
|
||
|
* @member {Array} TK.Chart#handles - An array containing all {@link TK.ResponseHandle} instances.
|
||
|
*/
|
||
|
this.handles = [];
|
||
|
TK.Widget.prototype.initialize.call(this, options);
|
||
|
|
||
|
/**
|
||
|
* @member {TK.Range} TK.Chart#range_x - The {@link TK.Range} for the x axis.
|
||
|
*/
|
||
|
/**
|
||
|
* @member {TK.Range} TK.Chart#range_y - The {@link TK.Range} for the y axis.
|
||
|
*/
|
||
|
this.add_range(this.options.range_x, "range_x");
|
||
|
this.add_range(this.options.range_y, "range_y");
|
||
|
this.add_range(this.options.range_z, "range_z");
|
||
|
this.range_y.set("reverse", true, true, true);
|
||
|
|
||
|
/**
|
||
|
* @member {HTMLDivElement} TK.Chart#element - The main DIV container.
|
||
|
* Has class <code>toolkit-chart</code>.
|
||
|
*/
|
||
|
if (!(E = this.element)) this.element = E = TK.element("div");
|
||
|
TK.add_class(E, "toolkit-chart");
|
||
|
this.widgetize(E, true, true, true);
|
||
|
|
||
|
this.svg = S = TK.make_svg("svg");
|
||
|
|
||
|
if (!this.options.width)
|
||
|
this.options.width = this.range_x.options.basis;
|
||
|
if (!this.options.height)
|
||
|
this.options.height = this.range_y.options.basis;
|
||
|
|
||
|
/**
|
||
|
* @member {SVGGroup} TK.Chart#_graphs - The SVG group containing all graphs.
|
||
|
* Has class <code>toolkit-graphs</code>.
|
||
|
*/
|
||
|
this._graphs = TK.make_svg("g", {"class": "toolkit-graphs"});
|
||
|
S.appendChild(this._graphs);
|
||
|
E.appendChild(S);
|
||
|
|
||
|
if (this.options.width) this.set("width", this.options.width);
|
||
|
if (this.options.height) this.set("height", this.options.height);
|
||
|
|
||
|
/**
|
||
|
* @member {SVGGroup} TK.Chart#_handles - The SVG group containing all handles.
|
||
|
* Has class <code>toolkit-handles</code>.
|
||
|
*/
|
||
|
this._handles = TK.make_svg("g", {"class": "toolkit-handles"});
|
||
|
this.svg.appendChild(this._handles);
|
||
|
this.svg.onselectstart = function () { return false; };
|
||
|
this.add_handles(this.options.handles);
|
||
|
},
|
||
|
resize: function () {
|
||
|
var E = this.element;
|
||
|
var O = this.options;
|
||
|
var S = this.svg;
|
||
|
|
||
|
TK.Widget.prototype.resize.call(this);
|
||
|
|
||
|
var tmp = TK.css_space(S, "border", "padding");
|
||
|
var w = TK.inner_width(E) - tmp.left - tmp.right;
|
||
|
var h = TK.inner_height(E) - tmp.top - tmp.bottom;
|
||
|
|
||
|
if (w > 0 && O._width !== w) {
|
||
|
this.set("_width", w);
|
||
|
this.range_x.set("basis", w);
|
||
|
this.invalid._width = true;
|
||
|
this.trigger_draw();
|
||
|
}
|
||
|
if (h > 0 && O._height !== h) {
|
||
|
this.set("_height", h);
|
||
|
this.range_y.set("basis", h);
|
||
|
this.invalid._height = true;
|
||
|
this.trigger_draw();
|
||
|
}
|
||
|
},
|
||
|
redraw: function () {
|
||
|
var I = this.invalid;
|
||
|
var E = this.svg;
|
||
|
var O = this.options;
|
||
|
|
||
|
TK.Widget.prototype.redraw.call(this);
|
||
|
|
||
|
if (I.validate("ranges", "_width", "_height", "range_x", "range_y")) {
|
||
|
/* we need to redraw both key and title, because
|
||
|
* they do depend on the size */
|
||
|
I.title = true;
|
||
|
I.key = true;
|
||
|
var w = O._width;
|
||
|
var h = O._height;
|
||
|
if (w && h) {
|
||
|
E.setAttribute("width", w + "px");
|
||
|
E.setAttribute("height", h + "px");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (I.graphs) {
|
||
|
for (var i = 0; i < this.graphs.length; i++) {
|
||
|
this.graphs[i].redraw();
|
||
|
}
|
||
|
}
|
||
|
if (I.validate("title", "title_position")) {
|
||
|
draw_title.call(this);
|
||
|
}
|
||
|
if (I.validate("key", "key_size", "graphs")) {
|
||
|
draw_key.call(this);
|
||
|
}
|
||
|
if (I.show_handles) {
|
||
|
I.show_handles = false;
|
||
|
if (O.show_handles) {
|
||
|
this._handles.style.removeProperty("display");
|
||
|
} else {
|
||
|
this._handles.style.display = "none";
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
destroy: function () {
|
||
|
for (var i = 0; i < this._graphs.length; i++) {
|
||
|
this._graphs[i].destroy();
|
||
|
}
|
||
|
this._graphs.remove();
|
||
|
this._handles.remove();
|
||
|
TK.Widget.prototype.destroy.call(this);
|
||
|
},
|
||
|
add_child: function(child) {
|
||
|
if (child instanceof TK.Graph) {
|
||
|
this.add_graph(child);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
TK.Widget.prototype.add_child.call(this, child);
|
||
|
},
|
||
|
remove_child: function(child) {
|
||
|
if (child instanceof TK.Graph) {
|
||
|
this.remove_graph(child);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
TK.Widget.prototype.remove_child.call(this, child);
|
||
|
},
|
||
|
/**
|
||
|
* Add a graph to the chart.
|
||
|
*
|
||
|
* @method TK.Chart#add_graph
|
||
|
*
|
||
|
* @param {Object} graph - The graph to add. This can be either an
|
||
|
* instance of {@link TK.Graph} or an object of options to
|
||
|
* {@link TK.Graph}.
|
||
|
*
|
||
|
* @returns {Object} The instance of {@link TK.Graph}.
|
||
|
*
|
||
|
* @emits TK.Chart#graphadded
|
||
|
*/
|
||
|
add_graph: function (options) {
|
||
|
var g;
|
||
|
|
||
|
if (TK.Graph.prototype.isPrototypeOf(options)) {
|
||
|
g = options;
|
||
|
} else {
|
||
|
g = new TK.Graph(options);
|
||
|
}
|
||
|
|
||
|
g.set("container", this._graphs);
|
||
|
if (!g.options.range_x) g.set("range_x", this.range_x);
|
||
|
if (!g.options.range_y) g.set("range_y", this.range_y);
|
||
|
|
||
|
this.graphs.push(g);
|
||
|
g.add_event("set", function (key, value, obj) {
|
||
|
if (key === "color" || key === "class" || key === "key") {
|
||
|
this.invalid.graphs = true;
|
||
|
this.trigger_draw();
|
||
|
}
|
||
|
}.bind(this));
|
||
|
/**
|
||
|
* Is fired when a graph was added. Arguments are the graph
|
||
|
* and its position in the array.
|
||
|
*
|
||
|
* @event TK.Chart#graphadded
|
||
|
*
|
||
|
* @param {TK.Graph} graph - The {@link TK.Graph} which was added.
|
||
|
* @param {int} id - The ID of the added {@link TK.Graph}.
|
||
|
*/
|
||
|
this.fire_event("graphadded", g, this.graphs.length - 1);
|
||
|
|
||
|
this.invalid.graphs = true;
|
||
|
this.trigger_draw();
|
||
|
TK.Widget.prototype.add_child.call(this, g);
|
||
|
return g;
|
||
|
},
|
||
|
/**
|
||
|
* Remove a graph from the chart.
|
||
|
*
|
||
|
* @method TK.Chart#remove_graph
|
||
|
*
|
||
|
* @param {TK.Graph} graph - The {@link TK.Graph} to remove.
|
||
|
*
|
||
|
* @emits TK.Chart#graphremoved
|
||
|
*/
|
||
|
remove_graph: function (g) {
|
||
|
var i;
|
||
|
if ((i = this.graphs.indexOf(g)) !== -1) {
|
||
|
/**
|
||
|
* Is fired when a graph was removed. Arguments are the graph
|
||
|
* and its position in the array.
|
||
|
*
|
||
|
* @event TK.Chart#graphremoved
|
||
|
*
|
||
|
* @param {TK.Graph} graph - The {@link TK.Graph} which was removed.
|
||
|
* @param {int} id - The ID of the removed {@link TK.Graph}.
|
||
|
*/
|
||
|
this.fire_event("graphremoved", g, i);
|
||
|
g.destroy();
|
||
|
this.graphs.splice(i, 1);
|
||
|
TK.Widget.prototype.remove_child.call(this, g);
|
||
|
this.invalid.graphs = true;
|
||
|
this.trigger_draw();
|
||
|
}
|
||
|
},
|
||
|
/**
|
||
|
* Remove all graphs from the chart.
|
||
|
*
|
||
|
* @method TK.Chart#empty
|
||
|
*
|
||
|
* @emits TK.Chart#emptied
|
||
|
*/
|
||
|
empty: function () {
|
||
|
this.graphs.map(this.remove_graph, this);
|
||
|
/**
|
||
|
* Is fired when all graphs are removed from the chart.
|
||
|
*
|
||
|
* @event TK.Chart#emptied
|
||
|
*/
|
||
|
this.fire_event("emptied");
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Add a new handle to the widget. Options is an object containing
|
||
|
* options for the {@link TK.ResponseHandle}.
|
||
|
*
|
||
|
* @method TK.Chart#add_handle
|
||
|
*
|
||
|
* @param {Object} [options={ }] - An object containing initial options. - The options for the {@link TK.ResponseHandle}.
|
||
|
* @param {Object} [type=TK.ResponseHandle] - A widget class to be used as the new handle.
|
||
|
*
|
||
|
* @emits TK.Chart#handleadded
|
||
|
*/
|
||
|
add_handle: function (options, type) {
|
||
|
type = type || TK.ResponseHandle;
|
||
|
options.container = this._handles;
|
||
|
if (options.range_x === void(0))
|
||
|
options.range_x = function () { return this.range_x; }.bind(this);
|
||
|
if (options.range_y === void(0))
|
||
|
options.range_y = function () { return this.range_y; }.bind(this);
|
||
|
if (options.range_z === void(0))
|
||
|
options.range_z = function () { return this.range_z; }.bind(this);
|
||
|
|
||
|
options.intersect = this.intersect.bind(this);
|
||
|
|
||
|
var h = new type(options);
|
||
|
this.handles.push(h);
|
||
|
if (this.options.show_handles)
|
||
|
this.add_child(h);
|
||
|
/**
|
||
|
* Is fired when a new handle was added.
|
||
|
*
|
||
|
* @param {TK.ResponseHandle} handle - The {@link TK.ResponseHandle} which was added.
|
||
|
*
|
||
|
* @event TK.Chart#handleadded
|
||
|
*/
|
||
|
this.fire_event("handleadded", h);
|
||
|
return h;
|
||
|
},
|
||
|
/**
|
||
|
* Add multiple new {@link TK.ResponseHandle} to the widget. Options is an array
|
||
|
* of objects containing options for the new instances of {@link TK.ResponseHandle}.
|
||
|
*
|
||
|
* @method TK.Chart#add_handles
|
||
|
*
|
||
|
* @param {Array<Object>} options - An array of options objects for the {@link TK.ResponseHandle}.
|
||
|
* @param {Object} [type=TK.ResponseHandle] - A widget class to be used for the new handles.
|
||
|
*/
|
||
|
add_handles: function (handles, type) {
|
||
|
for (var i = 0; i < handles.length; i++)
|
||
|
this.add_handle(handles[i], type);
|
||
|
},
|
||
|
/**
|
||
|
* Remove a handle from the widget.
|
||
|
*
|
||
|
* @method TK.Chart#remove_handle
|
||
|
*
|
||
|
* @param {TK.ResponseHandle} handle - The {@link TK.ResponseHandle} to remove.
|
||
|
*
|
||
|
* @emits TK.Chart#handleremoved
|
||
|
*/
|
||
|
remove_handle: function (handle) {
|
||
|
// remove a handle from the widget.
|
||
|
for (var i = 0; i < this.handles.length; i++) {
|
||
|
if (this.handles[i] === handle) {
|
||
|
if (this.options.show_handles)
|
||
|
this.remove_child(handle);
|
||
|
this.handles[i].destroy();
|
||
|
this.handles.splice(i, 1);
|
||
|
/**
|
||
|
* Is fired when a handle was removed.
|
||
|
*
|
||
|
* @event TK.Chart#handleremoved
|
||
|
*/
|
||
|
this.fire_event("handleremoved");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
/**
|
||
|
* Remove multiple or all {@link TK.ResponseHandle} from the widget.
|
||
|
*
|
||
|
* @method TK.Chart#remove_handles
|
||
|
*
|
||
|
* @param {Array<TK.ResponseHandle>} handles - An array of
|
||
|
* {@link TK.ResponseHandle} instances. If the argument reveals to
|
||
|
* `false`, all handles are removed from the widget.
|
||
|
*/
|
||
|
remove_handles: function (handles) {
|
||
|
var H = handles || this.handles.slice();
|
||
|
for (var i = 0; i < H.length; i++) {
|
||
|
this.remove_handle(H[i]);
|
||
|
}
|
||
|
if (!handles) {
|
||
|
this.handles = [];
|
||
|
/**
|
||
|
* Is fired when all handles are removed.
|
||
|
*
|
||
|
* @event TK.Chart#emptied
|
||
|
*/
|
||
|
this.fire_event("emptied");
|
||
|
}
|
||
|
},
|
||
|
|
||
|
intersect: function (X, handle) {
|
||
|
// this function walks over all known handles and asks for the coords
|
||
|
// of the label and the handle. Calculates intersecting square pixels
|
||
|
// according to the importance set in options. Returns an object
|
||
|
// containing intersect (the amount of intersecting square pixels) and
|
||
|
// count (the amount of overlapping elements)
|
||
|
var c = 0;
|
||
|
var a = 0, _a;
|
||
|
var O = this.options;
|
||
|
var importance_handle = O.importance_handle
|
||
|
var importance_label = O.importance_label
|
||
|
|
||
|
for (var i = 0; i < this.handles.length; i++) {
|
||
|
var h = this.handles[i];
|
||
|
if (h === handle || !h.get("active") || !h.get("show_handle")) continue;
|
||
|
_a = calculate_overlap(X, h.handle);
|
||
|
|
||
|
if (_a) {
|
||
|
c ++;
|
||
|
a += _a * importance_handle;
|
||
|
}
|
||
|
|
||
|
_a = calculate_overlap(X, h.label);
|
||
|
|
||
|
if (_a) {
|
||
|
c ++;
|
||
|
a += _a * importance_label;
|
||
|
}
|
||
|
}
|
||
|
if (this.bands && this.bands.length) {
|
||
|
for (var i = 0; i < this.bands.length; i++) {
|
||
|
var b = this.bands[i];
|
||
|
if (b === handle || !b.get("active") || !b.get("show_handle")) continue;
|
||
|
_a = calculate_overlap(X, b.handle);
|
||
|
|
||
|
if (_a > 0) {
|
||
|
c ++;
|
||
|
a += _a * importance_handle;
|
||
|
}
|
||
|
|
||
|
_a = calculate_overlap(X, b.label);
|
||
|
if (_a > 0) {
|
||
|
c ++;
|
||
|
a += _a * importance_label;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/* calculate intersection with border */
|
||
|
_a = calculate_overlap(X, [ 0, 0, this.range_x.options.basis, this.range_y.options.basis ]);
|
||
|
a += ((X[2] - X[0]) * (X[3] - X[1]) - _a) * O.importance_border;
|
||
|
return {intersect: a, count: c};
|
||
|
},
|
||
|
});
|
||
|
/**
|
||
|
* @member {TK.Grid} TK.Chart#grid - The grid element of the chart.
|
||
|
* Has class <code>toolkit-grid</code>.
|
||
|
*/
|
||
|
TK.ChildWidget(TK.Chart, "grid", {
|
||
|
create: TK.Grid,
|
||
|
show: true,
|
||
|
append: function() {
|
||
|
this.svg.insertBefore(this.grid.element, this.svg.firstChild);
|
||
|
},
|
||
|
map_options: {
|
||
|
grid_x: "grid_x",
|
||
|
grid_y: "grid_y",
|
||
|
},
|
||
|
default_options: function () {
|
||
|
return {
|
||
|
range_x: this.range_x,
|
||
|
range_y: this.range_y,
|
||
|
};
|
||
|
},
|
||
|
});
|
||
|
function key_hover_cb(ev) {
|
||
|
var b = ev.type === "mouseenter";
|
||
|
TK.toggle_class(this, "toolkit-hover", b);
|
||
|
/* this.nextSibling is the key */
|
||
|
TK.toggle_class(this.nextSibling, "toolkit-hover", b);
|
||
|
}
|
||
|
/**
|
||
|
* @member {SVGRect} TK.Chart#_key_background - The SVG rectangle of the key.
|
||
|
* Has class <code>toolkit-background</code>.
|
||
|
*/
|
||
|
TK.ChildElement(TK.Chart, "key_background", {
|
||
|
option: "key",
|
||
|
display_check: function(v) {
|
||
|
return !!v;
|
||
|
},
|
||
|
create: function() {
|
||
|
var k = TK.make_svg("rect", {"class": "toolkit-background"});
|
||
|
k.addEventListener("mouseenter", key_hover_cb);
|
||
|
k.addEventListener("mouseleave", key_hover_cb);
|
||
|
return k;
|
||
|
},
|
||
|
append: function() {
|
||
|
this.svg.appendChild(this._key_background);
|
||
|
},
|
||
|
});
|
||
|
/**
|
||
|
* @member {SVGGroup} TK.Chart#_key - The SVG group containing all descriptions.
|
||
|
* Has class <code>toolkit-key</code>.
|
||
|
*/
|
||
|
TK.ChildElement(TK.Chart, "key", {
|
||
|
option: "key",
|
||
|
display_check: function(v) {
|
||
|
return !!v;
|
||
|
},
|
||
|
create: function() {
|
||
|
var key = TK.make_svg("g", {"class": "toolkit-key"});
|
||
|
key.appendChild(TK.make_svg("text", {"class": "toolkit-key-text"}));
|
||
|
return key;
|
||
|
},
|
||
|
append: function() {
|
||
|
this.svg.appendChild(this._key);
|
||
|
},
|
||
|
});
|
||
|
/**
|
||
|
* @member {SVGText} TK.Chart#_title - The title of the chart.
|
||
|
* Has class <code>toolkit-title</code>.
|
||
|
*/
|
||
|
TK.ChildElement(TK.Chart, "title", {
|
||
|
option: "title",
|
||
|
display_check: function(v) {
|
||
|
return typeof(v) === "string" && v.length;
|
||
|
},
|
||
|
create: function() {
|
||
|
return TK.make_svg("text", {
|
||
|
"class": "toolkit-title",
|
||
|
style: "dominant-baseline: central;"
|
||
|
});
|
||
|
},
|
||
|
append: function() {
|
||
|
var svg = this.svg;
|
||
|
svg.insertBefore(this._title, svg.firstChild);
|
||
|
},
|
||
|
});
|
||
|
})(this, this.TK);
|