You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1204 lines
29 KiB

/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program 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 2 of the License, or
** (at your option) any later version.
**
** This program 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 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.
**/
/**
* SVGMap class.
*
* Implements vector map rendering functionality.
*/
function SVGMap(options) {
var container,
layers;
this.layers = {};
this.options = options;
this.elements = {};
this.shapes = {};
this.links = {};
this.background = null;
this.container = null;
this.imageUrl = 'imgstore.php?iconid=';
this.imageCache = new ImageCache();
this.canvas = new SVGCanvas(options.canvas, true);
if (typeof this.options.show_timestamp === 'undefined') {
this.options.show_timestamp = true;
}
// Extra group for font styles.
container = this.canvas.add('g', {
class: 'map-container',
'font-family': SVGMap.FONTS[9],
'font-size': '10px'
});
var layers_to_add = [
// Background.
{
type: 'g',
attributes: {
class: 'map-background',
fill: '#' + options.theme.backgroundcolor
}
},
// Grid.
{
type: 'g',
attributes: {
class: 'map-grid',
stroke: '#' + options.theme.gridcolor,
fill: '#' + options.theme.gridcolor,
'stroke-width': '1',
'stroke-dasharray': '4,4',
'shape-rendering': 'crispEdges'
}
},
// Custom shapes.
{
type: 'g',
attributes: {
class: 'map-shapes'
}
},
// Highlights of elements.
{
type: 'g',
attributes: {
class: 'map-highlights'
}
},
// Links.
{
type: 'g',
attributes: {
class: 'map-links'
}
},
// Elements.
{
type: 'g',
attributes: {
class: 'map-elements'
}
}
];
// Marks (timestamp and homepage).
if (options.show_timestamp) {
layers_to_add.push({
type: 'g',
attributes: {
class: 'map-marks',
fill: 'rgba(150, 150, 150, 0.75)',
'font-size': '8px',
'shape-rendering': 'crispEdges'
},
content: [
{
type: 'text',
attributes: {
class: 'map-timestamp',
'text-anchor': 'end',
x: options.canvas.width - 6,
y: options.canvas.height - 6
}
}
]
});
}
layers = container.add(layers_to_add);
['background', 'grid', 'shapes', 'highlights', 'links', 'elements', 'marks'].forEach(function (attribute, index) {
this.layers[attribute] = layers[index];
}, this);
this.layers.background.add('rect', {
x: 0,
y: 0,
width: this.options.canvas.width,
height: this.options.canvas.height
});
// Render goes first as it is needed for getBBox to work.
if (this.options.container) {
this.render(this.options.container);
}
if (options.show_timestamp) {
var elements = this.canvas.getElementsByAttributes({class: 'map-timestamp'});
if (elements.length == 0) {
throw 'timestamp element is missing';
}
else {
this['timestamp'] = elements[0];
}
}
this.update(this.options);
}
// Predefined list of fonts for maps.
SVGMap.FONTS = [
'Georgia, serif',
'"Palatino Linotype", "Book Antiqua", Palatino, serif',
'"Times New Roman", Times, serif',
'Arial, Helvetica, sans-serif',
'"Arial Black", Gadget, sans-serif',
'"Comic Sans MS", cursive, sans-serif',
'Impact, Charcoal, sans-serif',
'"Lucida Sans Unicode", "Lucida Grande", sans-serif',
'Tahoma, Geneva, sans-serif',
'"Trebuchet MS", Helvetica, sans-serif',
'Verdana, Geneva, sans-serif',
'"Courier New", Courier, monospace',
'"Lucida Console", Monaco, monospace'
];
// Predefined border types (@see dash-array of SVG) for maps.
SVGMap.BORDER_TYPES = {
'0': '',
'1': 'none',
'2': '1,2',
'3': '4,4'
};
/**
* Convert array of objects to hashmap (object).
*
* @param {array} array Array of objects.
* @param {string} key Object field used to identify object.
*
* @return {object} Hashmap.
*/
SVGMap.toHashmap = function (array, key) {
var hashmap = {};
array.forEach(function (item) {
if (typeof item !== 'object' || typeof item[key] === 'undefined') {
// Skip elements that are not objects.
return;
}
hashmap[item[key]] = item;
});
return hashmap;
};
/**
* Get image url.
*
* @param {number|string} id Image id.
*
* @return {string} Image url.
*/
SVGMap.prototype.getImageUrl = function (id) {
return this.imageUrl + id;
};
/**
* Get image from image cache.
*
* @param {number|string} id Image id.
*
* @return {object} Image object or null if image object is not present in cache.
*/
SVGMap.prototype.getImage = function (id) {
if (typeof id !== 'undefined' && typeof this.imageCache.images[id] !== 'undefined') {
return this.imageCache.images[id];
}
return null;
};
/**
* Update background image.
*
* @param {string} background Background image id.
*/
SVGMap.prototype.updateBackground = function (background) {
var element = null;
if (background && background !== '0') {
if (this.background !== null && background === this.options.background) {
// Background was not changed.
return;
}
var image = this.getImage(background);
element = this.layers.background.add('image', {
x: 0,
y: 0,
width: image.naturalWidth,
height: image.naturalHeight,
'xlink:href': this.getImageUrl(background)
});
}
if (this.background !== null) {
this.background.remove();
}
this.background = element;
};
/**
* Set grid size.
*
* @param {number} size Grid size. Setting grid size to 0 turns of the grid.
*/
SVGMap.prototype.setGrid = function (size) {
this.layers.grid.clear();
if (size === 0) {
return;
}
for (var x = size; x < this.options.canvas.width; x += size) {
this.layers.grid.add('line', {
'x1': x,
'y1': 0,
'x2': x,
'y2': this.options.canvas.height
});
this.layers.grid.add('text', {
'x': x + 3,
'y': 9 + 3,
'stroke-width': 0
}, x);
}
for (var y = size; y < this.options.canvas.height; y += size) {
this.layers.grid.add('line', {
'x1': 0,
'y1': y,
'x2': this.options.canvas.width,
'y2': y
});
this.layers.grid.add('text', {
'x': 3,
'y': y + 12,
'stroke-width': 0
}, y);
}
this.layers.grid.add('text', {
'x': 2,
'y': 12,
'stroke-width': 0
}, 'Y X:');
};
/**
* Compare objects. * Used to compare map object attributes to determine if attributes were changed.
*
* @param {object} source Object to be compared.
* @param {object} target Object to be compared with.
*
* @return {boolean} True if objects attributes are different, false if object attributes are the same.
*/
SVGMap.isChanged = function (source, target) {
if (typeof source !== 'object' || source === null) {
return true;
}
var keys = Object.keys(target);
for (var i = 0; i < keys.length; i++) {
if (typeof target[keys[i]] === 'object') {
if (SVGMap.isChanged(source[keys[i]], target[keys[i]])) {
return true;
}
}
else {
if (target[keys[i]] !== source[keys[i]]) {
return true;
}
}
}
return false;
};
/**
* Update map objects. Iterate through map objects of specified type and update object attributes.
*
* @param {string} type Object type (name of SVGMap class attribute).
* @param {string} className Class name used to create instance of a new object.
* @param {object} items Hashmap of map objects.
* @param {boolean} incremental Update method. If set to true, items are added to the existing set of map objects.
*/
SVGMap.prototype.updateItems = function (type, className, items, incremental) {
var keys = Object.keys(items);
if (incremental !== true) {
Object.keys(this[type]).forEach(function (key) {
if (keys.indexOf(key) === -1) {
this[type][key].remove();
}
}, this);
}
keys.forEach(function (key) {
if (typeof this[type][key] !== 'object') {
this[type][key] = new window[className](this, {});
}
this[type][key].update(items[key]);
}, this);
};
/**
* Update ordered map objects.
*
* @param {string} type Object type (name of SVGMap class attribute).
* @param {string} idField Field used to identify objects.
* @param {string} className Class name used to create instance of a new object.
* @param {object} items Array of map objects.
* @param {boolean} incremental Update method. If set to true, items are added to the existing set of map objects.
*/
SVGMap.prototype.updateOrderedItems = function (type, idField, className, items, incremental) {
if (incremental !== true) {
Object.keys(this[type]).forEach(function (key) {
if (items.filter(function (item) {
return item[idField] == key;
}).length === 0) {
this[type][key].remove();
}
}, this);
}
items.forEach(function (item) {
if (typeof this[type][item[idField]] !== 'object') {
this[type][item[idField]] = new window[className](this, {});
}
this[type][item[idField]].update(item);
}, this);
};
/**
* Update map objects based on specified options.
*
* @param {object} options Map options.
* @param {boolean} incremental Update method. If set to true, items are added to the existing set of map objects.
*/
SVGMap.prototype.update = function (options, incremental) {
var images = {},
rules = [
{
name: 'elements',
field: 'selementid'
},
{
name: 'links',
field: 'linkid'
}
];
// elements and links are converted into hashmap as order is not important.
rules.forEach(function (rule) {
if (typeof options[rule.name] !== 'undefined') {
options[rule.name] = SVGMap.toHashmap(options[rule.name], rule.field);
}
else {
options[rule.name] = {};
}
});
// Performs ordering of shapes based on zindex value.
if (typeof options.shapes === 'undefined') {
options.shapes = [];
}
else {
options.shapes = options.shapes.sort(function (a,b) {
return a.zindex - b.zindex;
});
}
this.options.label_location = options.label_location;
// Collect the list of images.
Object.keys(options.elements).forEach(function (key) {
var element = options.elements[key];
if (typeof element.icon !== 'undefined') {
images[element.icon] = this.getImageUrl(element.icon);
}
}, this);
if (options.background && options.background !== '0') {
images[options.background] = this.getImageUrl(options.background);
}
// Resize the canvas and move marks
if (typeof options.canvas !== 'undefined' && typeof options.canvas.width !== 'undefined'
&& typeof options.canvas.height !== 'undefined'
&& this.canvas.resize(options.canvas.width, options.canvas.height)) {
this.options.canvas = options.canvas;
if (this.container !== null) {
this.container.style.width = options.canvas.width + 'px';
this.container.style.height = options.canvas.height + 'px';
}
if (options.show_timestamp) {
this.timestamp.update({
x: options.canvas.width,
y: options.canvas.height - 6
});
}
}
// Images are preloaded before update.
this.imageCache.preload(images, function () {
// Update is performed after preloading all of the images.
this.updateItems('elements', 'SVGMapElement', options.elements, incremental);
this.updateOrderedItems('shapes', 'sysmap_shapeid', 'SVGMapShape', options.shapes, incremental);
this.updateItems('links', 'SVGMapLink', options.links, incremental);
this.updateBackground(options.background, incremental);
this.options = SVGElement.mergeAttributes(this.options, options);
}, this);
// Timestamp (date on map) is updated.
if (options.show_timestamp && typeof options.timestamp !== 'undefined') {
this.timestamp.element.textContent = options.timestamp;
}
};
/**
* Invalidate items based on type.
*
* @param {string} type Object type (name of SVGMap class attribute).
*/
SVGMap.prototype.invalidate = function (type) {
Object.keys(this[type]).forEach(function (key) {
this[type][key].options = {};
this[type][key].element.invalidate();
}, this);
};
/**
* Render map within container.
*
* @param {mixed} container DOM element or jQuery selector.
*/
SVGMap.prototype.render = function (container) {
if (typeof container === 'string') {
container = jQuery(container)[0];
}
this.canvas.render(container);
this.container = container;
};
/*
* SVGMapElement class. Implements rendering of map elements (selements).
*
* @param {object} map Parent map.
* @param {object} options Element attributes (match field names in data source).
*/
function SVGMapElement(map, options) {
this.map = map;
this.options = options;
this.highlight = null;
this.image = null;
this.label = null;
this.markers = null;
}
// Predefined label positions.
SVGMapElement.LABEL_POSITION_NONE = null;
SVGMapElement.LABEL_POSITION_DEFAULT = -1;
SVGMapElement.LABEL_POSITION_BOTTOM = 0;
SVGMapElement.LABEL_POSITION_LEFT = 1;
SVGMapElement.LABEL_POSITION_RIGHT = 2;
SVGMapElement.LABEL_POSITION_TOP = 3;
/**
* Remove part (item) of an element.
*
* @param {string} item Item to be removed.
*/
SVGMapElement.prototype.removeItem = function (item) {
if (this[item] !== null) {
this[item].remove();
this[item] = null;
}
};
/**
* Remove element.
*/
SVGMapElement.prototype.remove = function () {
['highlight', 'image', 'label', 'markers'].forEach(function (name) {
this.removeItem(name);
}, this);
delete this.map.elements[this.options.selementid];
};
/**
* Update element highlight (shape and markers placed on the background of element).
*/
SVGMapElement.prototype.updateHighlight = function() {
var type = null,
options = null;
if (this.options.latelyChanged) {
var radius = Math.floor(this.width / 2) + 12,
markers = [];
if (this.options.label_location !== SVGMapElement.LABEL_POSITION_BOTTOM) {
markers.push({
type: 'path',
attributes: {
d: 'M11, 2.91 L5.87, 8 L11, 13.09 L8.07, 16 L0, 8 L8.07, 0, L11, 2.91',
transform: 'rotate(90 ' + (this.center.x+8) + ',' + (this.center.y+radius) + ') translate(' +
(this.center.x+8) + ',' + (this.center.y+radius) + ')'
}
});
}
if (this.options.label_location !== SVGMapElement.LABEL_POSITION_LEFT) {
markers.push({
type: 'path',
attributes: {
d: 'M11, 2.91 L5.87, 8 L11, 13.09 L8.07, 16 L0, 8 L8.07, 0, L11, 2.91',
transform: 'rotate(180 ' + (this.center.x-radius) + ',' + (this.center.y+8) + ') translate(' +
(this.center.x-radius) + ',' + (this.center.y+8) + ')'
}
});
}
if (this.options.label_location !== SVGMapElement.LABEL_POSITION_RIGHT) {
markers.push({
type: 'path',
attributes: {
d: 'M11, 2.91 L5.87, 8 L11, 13.09 L8.07, 16 L0, 8 L8.07, 0, L11, 2.91',
transform: 'translate(' + (this.center.x+radius) + ',' + (this.center.y-8) + ')'
}
});
}
if (this.options.label_location !== SVGMapElement.LABEL_POSITION_TOP) {
markers.push({
type: 'path',
attributes: {
d: 'M11, 2.91 L5.87, 8 L11, 13.09 L8.07, 16 L0, 8 L8.07, 0, L11, 2.91',
transform: 'rotate(270 ' + (this.center.x-8) + ',' + (this.center.y-radius) + ') translate(' +
(this.center.x-8) + ',' + (this.center.y-radius) + ')'
}
});
}
var element = this.map.layers.highlights.add('g', {
fill: '#F44336',
stroke: '#B71C1C'
}, markers);
this.removeItem('markers');
this.markers = element;
}
else {
this.removeItem('markers');
}
if (typeof this.options.highlight === 'object' && this.options.highlight !== null) {
if (this.options.highlight.st !== null) {
type = 'rect';
options = {
x: this.x - 2,
y: this.y - 2,
width: this.width + 4,
height: this.height + 4,
fill: '#' + this.options.highlight.st,
'fill-opacity': 0.5
};
}
if (this.options.highlight.hl !== null) {
type = 'ellipse';
options = {
cx: this.center.x,
cy: this.center.y,
rx: Math.floor(this.width / 2) + 10,
ry: Math.floor(this.width / 2) + 10,
fill: '#' + this.options.highlight.hl
};
if (this.options.highlight.ack === true) {
options.stroke = '#329632';
options['stroke-width'] = '4px';
}
else {
options['stroke-width'] = '0';
}
}
}
if (type !== null) {
if (this.highlight === null || type !== this.highlight.type) {
var element = this.map.layers.highlights.add(type, options);
this.removeItem('highlight');
this.highlight = element;
}
else {
this.highlight.update(options);
}
}
else {
this.removeItem('highlight');
}
};
/**
* Update element image. Image should be pre-loaded and placed in cache before calling this method.
*/
SVGMapElement.prototype.updateImage = function() {
var image,
options = {
x: this.x,
y: this.y,
width: this.width,
height: this.height
};
if (this.options.actions !== null && this.options.actions !== 'null'
&& typeof this.options.actions !== 'undefined') {
var actions = JSON.parse(this.options.actions);
// 4 - SYSMAP_ELEMENT_TYPE_IMAGE. Don't draw context menu and hand cursor for image elements with no links.
if (actions.data.elementtype != 4 || actions.data.urls.length != 0) {
options['data-menu-popup'] = this.options.actions;
options['style'] = 'cursor: pointer';
}
}
if (typeof this.options.icon !== 'undefined') {
var href = this.map.getImageUrl(this.options.icon);
// 2 - PERM_READ
if (2 > this.options.permission) {
href += '&unavailable=1';
}
if (this.image === null || this.image.attributes['xlink:href'] !== href) {
options['xlink:href'] = href;
var image = this.map.layers.elements.add('image', options);
this.removeItem('image');
this.image = image;
}
else {
this.image.update(options);
}
}
else {
this.removeItem('image');
}
};
/**
* Update element label.
*/
SVGMapElement.prototype.updateLabel = function() {
var x = this.center.x,
y = this.center.y,
anchor = {
horizontal: 'left',
vertical: 'top'
};
switch (this.options.label_location) {
case SVGMapElement.LABEL_POSITION_BOTTOM:
y = this.y + this.height + this.map.canvas.textPadding;
anchor.horizontal = 'center';
break;
case SVGMapElement.LABEL_POSITION_LEFT:
x = this.x - this.map.canvas.textPadding;
anchor.horizontal = 'right';
anchor.vertical = 'middle';
break;
case SVGMapElement.LABEL_POSITION_RIGHT:
x = this.x + this.width + this.map.canvas.textPadding;
anchor.vertical = 'middle';
break;
case SVGMapElement.LABEL_POSITION_TOP:
y = this.y - this.map.canvas.textPadding;
anchor.horizontal = 'center';
anchor.vertical = 'bottom';
break;
}
if (this.options.label !== null) {
var element = this.map.layers.elements.add('textarea', {
'x': x,
'y': y,
fill: '#' + this.map.options.theme.textcolor,
'anchor': anchor,
background: {
fill: '#' + this.map.options.theme.backgroundcolor,
opacity: 0.7
}
}, this.options.label);
this.removeItem('label');
this.label = element;
}
else {
this.removeItem('label');
}
};
/**
* Update element (highlight, image and label).
*
* @param {object} options Element attributes.
*/
SVGMapElement.prototype.update = function(options) {
var image = this.map.getImage(options.icon);
if (image === null) {
throw "Invalid element configuration!";
}
// Data type normalization.
['x', 'y', 'width', 'height', 'label_location'].forEach(function(name) {
if (typeof options[name] !== 'undefined') {
options[name] = parseInt(options[name]);
}
});
// Inherit label location from map options.
if (options.label_location === SVGMapElement.LABEL_POSITION_DEFAULT) {
options.label_location = parseInt(this.map.options.label_location);
}
if (typeof options.width !== 'undefined' && typeof options.height !== 'undefined') {
options.x += Math.floor(options.width / 2) - Math.floor(image.naturalWidth / 2);
options.y += Math.floor(options.height / 2) - Math.floor(image.naturalHeight / 2);
}
options.width = image.naturalWidth;
options.height = image.naturalHeight;
if (options.label === null) {
options.label_location = SVGMapElement.LABEL_POSITION_NONE;
}
if (SVGMap.isChanged(this.options, options) === false) {
// No need to update.
return;
}
this.options = options;
if (this.x !== options.x || this.y !== options.y || this.width !== options.width
|| this.height !== options.height) {
['x', 'y', 'width', 'height'].forEach(function(name) {
this[name] = options[name];
}, this);
this.center = {
x: this.x + Math.floor(this.width / 2),
y: this.y + Math.floor(this.height / 2)
};
}
this.updateHighlight();
this.updateImage();
this.updateLabel();
};
/**
* SVGMapLink class. Implements rendering of map links.
*
* @param {object} map Parent map.
* @param {object} options Link attributes.
*/
function SVGMapLink(map, options) {
this.map = map;
this.options = options;
this.element = null;
}
// Predefined set of line styles
SVGMapLink.LINE_STYLE_DEFAULT = 0;
SVGMapLink.LINE_STYLE_BOLD = 2;
SVGMapLink.LINE_STYLE_DOTTED = 3;
SVGMapLink.LINE_STYLE_DASHED = 4;
/**
* Update link.
*
* @param {object} options Link attributes (match field names in data source).
*/
SVGMapLink.prototype.update = function(options) {
// Data type normalization.
options.drawtype = parseInt(options.drawtype);
options.elements = [this.map.elements[options.selementid1], this.map.elements[options.selementid2]];
if (typeof options.elements[0] === 'undefined' || typeof options.elements[1] === 'undefined') {
var remove = true;
if (options.elements[0] === options.elements[1]) {
// Check if link is from hostgroup to hostgroup.
options.elements = [
this.map.shapes['e-' + options.selementid1],
this.map.shapes['e-' + options.selementid2]
];
remove = (typeof options.elements[0] === 'undefined' || typeof options.elements[1] === 'undefined');
}
if (remove) {
// Invalid link configuration.
this.remove();
return;
}
}
options.elements[0] = options.elements[0].center;
options.elements[1] = options.elements[1].center;
options.center = {
x: options.elements[0].x + Math.floor((options.elements[1].x - options.elements[0].x)/2),
y: options.elements[0].y + Math.floor((options.elements[1].y - options.elements[0].y)/2)
};
if (SVGMap.isChanged(this.options, options) === false) {
// No need to update.
return;
}
this.options = options;
this.remove();
var attributes = {
stroke: '#' + options.color,
'stroke-width': 1,
fill: '#' + this.map.options.theme.backgroundcolor
};
switch (options.drawtype) {
case SVGMapLink.LINE_STYLE_BOLD:
attributes['stroke-width'] = 2;
break;
case SVGMapLink.LINE_STYLE_DOTTED:
attributes['stroke-dasharray'] = '1,2';
break;
case SVGMapLink.LINE_STYLE_DASHED:
attributes['stroke-dasharray'] = '4,4';
break;
}
this.element = this.map.layers.links.add('g', attributes, [
{
type: 'line',
attributes: {
x1: options.elements[0].x,
y1: options.elements[0].y,
x2: options.elements[1].x,
y2: options.elements[1].y
}
}
]);
this.element.add('textarea', {
x: options.center.x,
y: options.center.y,
fill: '#' + this.map.options.theme.textcolor,
'font-size': '10px',
'stroke-width': 0,
anchor: {
horizontal: 'center',
vertical: 'middle'
},
background: {
}
}, options.label
);
};
/**
* Remove link.
*/
SVGMapLink.prototype.remove = function () {
if (this.element !== null) {
this.element.remove();
this.element = null;
}
};
/**
* SVGMapShape class. Implements rendering of map shapes.
*
* @param {object} map Parent map.
* @param {object} options Shape attributes.
*/
function SVGMapShape(map, options) {
this.map = map;
this.options = options;
this.element = null;
}
// Predefined set of map shape types.
SVGMapShape.TYPE_RECTANGLE = 0;
SVGMapShape.TYPE_ELLIPSE = 1;
SVGMapShape.TYPE_LINE = 2;
// Predefined label horizontal alignments.
SVGMapShape.LABEL_HALIGN_CENTER = 0;
SVGMapShape.LABEL_HALIGN_LEFT = 1;
SVGMapShape.LABEL_HALIGN_RIGHT = 2;
// Predefined label vertical alignments.
SVGMapShape.LABEL_VALIGN_MIDDLE = 0;
SVGMapShape.LABEL_VALIGN_TOP = 1;
SVGMapShape.LABEL_VALIGN_BOTTOM = 2;
/**
* Update shape.
*
* @param {object} options Shape attributes (match field names in data source).
*/
SVGMapShape.prototype.update = function(options) {
if (SVGMap.isChanged(this.options, options) === false) {
// No need to update.
return;
}
this.options = options;
['x', 'y', 'width', 'height'].forEach(function(name) {
this[name] = parseInt(options[name]);
}, this);
this.rx = Math.floor(this.width / 2);
this.ry = Math.floor(this.height / 2);
this.center = {
x: this.x + this.rx,
y: this.y + this.ry
};
var type,
element,
clip = {},
attributes = {},
mapping = [
{
key: 'background_color',
value: 'fill'
},
{
key: 'border_color',
value: 'stroke'
}
];
mapping.forEach(function(map) {
if (typeof options[map.key] !== 'undefined' && /[0-9A-F]{6}/g.test(options[map.key].trim())) {
attributes[map.value] = '#' + options[map.key];
}
else {
attributes[map.value] = 'none';
}
}, this);
if (typeof options['border_width'] !== 'undefined') {
attributes['stroke-width'] = parseInt(options['border_width']);
}
if (typeof options['border_type'] !== 'undefined') {
var border_type = SVGMap.BORDER_TYPES[parseInt(options['border_type'])];
if (border_type !== '' && border_type !== 'none' && attributes['stroke-width'] > 1) {
var parts = border_type.split(',').map(function (value) {
return parseInt(value);
});
// Make dots round.
if (parts[0] === 1 && attributes['stroke-width'] > 2) {
attributes['stroke-linecap'] = 'round';
}
border_type = parts.map(function (part) {
if (part === 1 && attributes['stroke-width'] > 2) {
return 1;
}
return part * attributes['stroke-width'];
}).join(',');
}
if (border_type !== '') {
attributes['stroke-dasharray'] = border_type;
}
else {
attributes['stroke-width'] = 0;
}
}
switch (parseInt(options.type)) {
case SVGMapShape.TYPE_RECTANGLE:
type = 'rect';
attributes = SVGElement.mergeAttributes(attributes, {
x: this.x,
y: this.y,
width: this.width,
height: this.height
});
clip = {
x: this.x,
y: this.y,
width: this.width,
height: this.height
};
break;
case SVGMapShape.TYPE_ELLIPSE:
type = 'ellipse';
attributes = SVGElement.mergeAttributes(attributes, {
cx: this.center.x,
cy: this.center.y,
rx: this.rx,
ry: this.ry
});
clip = {
cx: this.center.x,
cy: this.center.y,
rx: this.rx,
ry: this.ry
};
break;
case SVGMapShape.TYPE_LINE:
type = 'line';
delete attributes['fill'];
delete options['text'];
attributes = SVGElement.mergeAttributes(attributes, {
x1: this.x,
y1: this.y,
x2: this.width,
y2: this.height
});
break;
default:
throw "Invalid shape configuration!";
}
if (typeof options.text === 'undefined' || options.text.trim() === '') {
element = this.map.layers.shapes.add(type, attributes);
}
else {
element = this.map.layers.shapes.add('g', null, [{
'type': type,
'attributes': attributes
}]);
var x = this.center.x,
y = this.center.y,
anchor = {
horizontal: 'center',
vertical: 'middle'
};
switch (parseInt(options['text_halign'])) {
case SVGMapShape.LABEL_HALIGN_LEFT:
x = this.x + this.map.canvas.textPadding;
anchor.horizontal = 'left';
break;
case SVGMapShape.LABEL_HALIGN_RIGHT:
x = this.x + this.width - this.map.canvas.textPadding;
anchor.horizontal = 'right';
break;
}
switch (parseInt(options['text_valign'])) {
case SVGMapShape.LABEL_VALIGN_TOP:
y = this.y + this.map.canvas.textPadding;
anchor.vertical = 'top';
break;
case SVGMapShape.LABEL_VALIGN_BOTTOM:
y = this.y + this.height - this.map.canvas.textPadding;
anchor.vertical = 'bottom';
break;
}
element.add('textarea', {
'x': x,
'y': y,
fill: '#' + (/[0-9A-F]{6}/g.test(options['font_color'].trim()) ? options['font_color'] : '000000'),
'font-family': SVGMap.FONTS[parseInt(options.font)],
'font-size': parseInt(options['font_size']) + 'px',
'anchor': anchor,
clip: {
'type': type,
'attributes': clip
},
'parse-links': true
}, options.text);
}
this.replace(element);
};
/**
* Replace shape.
*
* @see SVGElement.prototype.replace
*
* @param {object} element New shape element.
*/
SVGMapShape.prototype.replace = function (element) {
if (this.element !== null) {
this.element.replace(element);
}
else {
this.element = element;
}
};
/**
* Remove shape.
*/
SVGMapShape.prototype.remove = function () {
if (this.element !== null) {
delete this.map.shapes[this.options.sysmap_shapeid];
this.element.remove();
this.element = null;
}
};