equation-admin-template
Version:
Booststrap 4 admin template made by equation
1,109 lines (975 loc) • 101 kB
JavaScript
/*!
*
* Jquery Mapael - Dynamic maps jQuery plugin (based on raphael.js)
* Requires jQuery, raphael.js and jquery.mousewheel
*
* Version: 2.0.0
*
* Copyright (c) 2015 Vincent Brouté (http://www.vincentbroute.fr/mapael)
* Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php).
*
* Thanks to Indigo744
*
*/
(function (factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('jquery'), require('raphael'), require('mousewheel'));
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery', 'raphael', 'mousewheel'], factory);
} else {
// Browser globals
factory(jQuery, Raphael, jQuery.fn.mousewheel);
}
}(function ($, Raphael, mousewheel, undefined) {
"use strict";
// The plugin name (used on several places)
var pluginName = "mapael";
// Version number of jQuery Mapael. See http://semver.org/ for more information.
var version = "2.0.0-dev";
/*
* Mapael constructor
* Init instance vars and call init()
* @param container the DOM element on which to apply the plugin
* @param options the complete options to use
*/
var Mapael = function (container, options) {
var self = this;
// the global container (DOM element object)
self.container = container;
// the global container (jQuery object)
self.$container = $(container);
// the global options
self.options = self.extendDefaultOptions(options);
// Save initial HTML content (used by destroy method)
self.initialHTMLContent = self.$container.html();
// zoom TimeOut handler (used to set and clear)
self.zoomTO = 0;
// zoom center coordinate (set at touchstart)
self.zoomCenterX = 0;
self.zoomCenterY = 0;
// Zoom pinch (set at touchstart and touchmove)
self.previousPinchDist = 0;
// Zoom data
self.zoomData = {
zoomLevel: 0,
zoomX: 0,
zoomY: 0,
panX: 0,
panY: 0
};
// resize TimeOut handler (used to set and clear)
self.resizeTO = 0;
// Panning: tell if panning action is in progress
self.panning = false;
// Panning TimeOut handler (used to set and clear)
self.panningTO = 0;
// Animate view box Interval handler (used to set and clear)
self.animationIntervalID = null;
// Map subcontainer jQuery object
self.$map = {};
// The tooltip jQuery object
self.$tooltip = {};
// The paper Raphael object
self.paper = {};
// The areas object list
self.areas = {};
// The plots object list
self.plots = {};
// The links object list
self.links = {};
// The map configuration object (taken from map file)
self.mapConf = {};
// Let's start the initialization
self.init();
};
/*
* Mapael Prototype
* Defines all methods and properties needed by Mapael
* Each mapael object inherits their properties and methods from this prototype
*/
Mapael.prototype = {
/*
* Version number
*/
version: version,
/*
* Initialize the plugin
* Called by the constructor
*/
init: function () {
var self = this;
// Init check for class existence
if (self.options.map.cssClass === "" || $("." + self.options.map.cssClass, self.container).length === 0) {
throw new Error("The map class `" + self.options.map.cssClass + "` doesn't exists");
}
// Create the tooltip container
self.$tooltip = $("<div>").addClass(self.options.map.tooltip.cssClass).css("display", "none");
// Get the map container, empty it then append tooltip
self.$map = $("." + self.options.map.cssClass, self.container).empty().append(self.$tooltip);
// Get the map from $.mapael or $.fn.mapael (backward compatibility)
if ($[pluginName] && $[pluginName].maps && $[pluginName].maps[self.options.map.name]) {
// Mapael version >= 2.x
self.mapConf = $[pluginName].maps[self.options.map.name];
} else if ($.fn[pluginName] && $.fn[pluginName].maps && $.fn[pluginName].maps[self.options.map.name]) {
// Mapael version <= 1.x - DEPRECATED
self.mapConf = $.fn[pluginName].maps[self.options.map.name];
if (window.console && window.console.warn) {
window.console.warn("Extending $.fn.mapael is deprecated (map '" + self.options.map.name + "')");
}
} else {
throw new Error("Unknown map '" + self.options.map.name + "'");
}
// Create Raphael paper
self.paper = new Raphael(self.$map[0], self.mapConf.width, self.mapConf.height);
// issue #135: Check for Raphael bug on text element boundaries
if (self.isRaphaelBBoxBugPresent() === true) {
self.destroy();
throw new Error("Can't get boundary box for text (is your container hidden? See #135)");
}
// add plugin class name on element
self.$container.addClass(pluginName);
if (self.options.map.tooltip.css) self.$tooltip.css(self.options.map.tooltip.css);
self.paper.setViewBox(0, 0, self.mapConf.width, self.mapConf.height, false);
// Handle map size
if (self.options.map.width) {
// NOT responsive: map has a fixed width
self.paper.setSize(self.options.map.width, self.mapConf.height * (self.options.map.width / self.mapConf.width));
// Create the legends for plots taking into account the scale of the map
self.createLegends("plot", self.plots, (self.options.map.width / self.mapConf.width));
} else {
// Responsive: handle resizing of the map
self.handleMapResizing();
}
// Draw map areas
$.each(self.mapConf.elems, function (id) {
var elemOptions = self.getElemOptions(
self.options.map.defaultArea,
(self.options.areas[id] ? self.options.areas[id] : {}),
self.options.legend.area
);
self.areas[id] = {"mapElem": self.paper.path(self.mapConf.elems[id]).attr(elemOptions.attrs)};
});
// Hook that allows to add custom processing on the map
if (self.options.map.beforeInit) self.options.map.beforeInit(self.$container, self.paper, self.options);
// Init map areas in a second loop (prevent texts to be hidden by map elements)
$.each(self.mapConf.elems, function (id) {
var elemOptions = self.getElemOptions(
self.options.map.defaultArea,
(self.options.areas[id] ? self.options.areas[id] : {}),
self.options.legend.area
);
self.initElem(self.areas[id], elemOptions, id);
});
// Draw links
self.links = self.drawLinksCollection(self.options.links);
// Draw plots
$.each(self.options.plots, function (id) {
self.plots[id] = self.drawPlot(id);
});
// Attach zoom event
self.$container.on("zoom." + pluginName, function (e, zoomOptions) {
self.onZoomEvent(e, zoomOptions);
});
if (self.options.map.zoom.enabled) {
// Enable zoom
self.initZoom(self.mapConf.width, self.mapConf.height, self.options.map.zoom);
}
// Set initial zoom
if (self.options.map.zoom.init !== undefined) {
if (self.options.map.zoom.init.animDuration === undefined) {
self.options.map.zoom.init.animDuration = 0;
}
self.$container.trigger("zoom." + pluginName, self.options.map.zoom.init);
}
// Create the legends for areas
self.createLegends("area", self.areas, 1);
// Attach update event
self.$container.on("update." + pluginName, function (e, opt) {
self.onUpdateEvent(e, opt);
});
// Attach showElementsInRange event
self.$container.on("showElementsInRange." + pluginName, function (e, opt) {
self.onShowElementsInRange(e, opt);
});
// Hook that allows to add custom processing on the map
if (self.options.map.afterInit) self.options.map.afterInit(self.$container, self.paper, self.areas, self.plots, self.options);
$(self.paper.desc).append(" and Mapael " + self.version + " (http://www.vincentbroute.fr/mapael/)");
},
/*
* Destroy mapael
* This function effectively detach mapael from the container
* - Set the container back to the way it was before mapael instanciation
* - Remove all data associated to it (memory can then be free'ed by browser)
*
* This method can be call directly by user:
* $(".mapcontainer").data("mapael").destroy();
*
* This method is also automatically called if the user try to call mapael
* on a container already containing a mapael instance
*/
destroy: function () {
var self = this;
// Detach all event listeners attached to the container
self.$container.off("." + pluginName);
// Empty the container (this will also detach all event listeners)
self.$container.empty();
// Detach the global resize event handler
if (self.onResizeEvent) $(window).off("resize." + pluginName, self.onResizeEvent);
// Replace initial HTML content
self.$container.html(self.initialHTMLContent);
// Remove mapael class
self.$container.removeClass(pluginName);
// Remove the data
self.$container.removeData(pluginName);
// Remove all internal reference
self.container = undefined;
self.$container = undefined;
self.options = undefined;
self.paper = undefined;
self.$map = undefined;
self.$tooltip = undefined;
self.mapConf = undefined;
self.areas = undefined;
self.plots = undefined;
self.links = undefined;
},
handleMapResizing: function () {
var self = this;
// onResizeEvent: call when the window element trigger the resize event
// We create it inside this function (and not in the prototype) in order to have a closure
// Otherwise, in the prototype, 'this' when triggered is *not* the mapael object but the global window
self.onResizeEvent = function () {
// Clear any previous setTimeout (avoid too much triggering)
clearTimeout(self.resizeTO);
// setTimeout to wait for the user to finish its resizing
self.resizeTO = setTimeout(function () {
self.$map.trigger("resizeEnd." + pluginName);
}, 150);
};
// Attach resize handler
$(window).on("resize." + pluginName, self.onResizeEvent);
// Attach resize end handler, and call it once
self.$map.on("resizeEnd." + pluginName, function () {
var containerWidth = self.$map.width();
if (self.paper.width != containerWidth) {
var newScale = containerWidth / self.mapConf.width;
// Set new size
self.paper.setSize(containerWidth, self.mapConf.height * newScale);
// Create plots legend again to take into account the new scale
self.createLegends("plot", self.plots, newScale);
}
}).trigger("resizeEnd." + pluginName);
},
/*
* Extend the user option with the default one
* @param options the user options
* @return new options object
*/
extendDefaultOptions: function (options) {
// Extend default options with user options
options = $.extend(true, {}, Mapael.prototype.defaultOptions, options);
// Extend legend default options
$.each(options.legend, function (type) {
if ($.isArray(options.legend[type])) {
for (var i = 0; i < options.legend[type].length; ++i)
options.legend[type][i] = $.extend(true, {}, Mapael.prototype.legendDefaultOptions[type], options.legend[type][i]);
} else {
options.legend[type] = $.extend(true, {}, Mapael.prototype.legendDefaultOptions[type], options.legend[type]);
}
});
return options;
},
/*
* Init the element "elem" on the map (drawing, setting attributes, events, tooltip, ...)
*/
initElem: function (elem, elemOptions, id) {
var self = this;
var bbox = {};
var textPosition = {};
// Assign value attribute to element
if (elemOptions.value !== undefined){
elem.value = elemOptions.value;
}
// Init the label related to the element
if (elemOptions.text && elemOptions.text.content !== undefined) {
// Set a text label in the area
bbox = elem.mapElem.getBBox();
textPosition = self.getTextPosition(bbox, elemOptions.text.position, elemOptions.text.margin);
elemOptions.text.attrs["text-anchor"] = textPosition.textAnchor;
elem.textElem = self.paper.text(textPosition.x, textPosition.y, elemOptions.text.content).attr(elemOptions.text.attrs);
$(elem.textElem.node).attr("data-id", id);
}
// Set user event handlers
if (elemOptions.eventHandlers) self.setEventHandlers(id, elemOptions, elem.mapElem, elem.textElem);
// Set hover option for mapElem
self.setHoverOptions(elem.mapElem, elemOptions.attrs, elemOptions.attrsHover);
// Set hover option for textElem
if (elem.textElem) self.setHoverOptions(elem.textElem, elemOptions.text.attrs, elemOptions.text.attrsHover);
// Set hover behavior only if attrsHover is set for area or for text
if (($.isEmptyObject(elemOptions.attrsHover) === false) ||
(elem.textElem && $.isEmptyObject(elemOptions.text.attrsHover) === false)) {
// Set hover behavior
self.setHover(elem.mapElem, elem.textElem);
}
// Init the tooltip
if (elemOptions.tooltip) {
elem.mapElem.tooltip = elemOptions.tooltip;
self.setTooltip(elem.mapElem);
if (elemOptions.text && elemOptions.text.content !== undefined) {
elem.textElem.tooltip = elemOptions.tooltip;
self.setTooltip(elem.textElem);
}
}
// Init the link
if (elemOptions.href) {
elem.mapElem.href = elemOptions.href;
elem.mapElem.target = elemOptions.target;
self.setHref(elem.mapElem);
if (elemOptions.text && elemOptions.text.content !== undefined) {
elem.textElem.href = elemOptions.href;
elem.textElem.target = elemOptions.target;
self.setHref(elem.textElem);
}
}
$(elem.mapElem.node).attr("data-id", id);
},
/*
* Init zoom and panning for the map
* @param mapWidth
* @param mapHeight
* @param zoomOptions
*/
initZoom: function (mapWidth, mapHeight, zoomOptions) {
var self = this;
var mousedown = false;
var previousX = 0;
var previousY = 0;
var fnZoomButtons = {
"reset": function () {
self.$container.trigger("zoom." + pluginName, {"level": 0});
},
"in": function () {
self.$container.trigger("zoom." + pluginName, {"level": "+1"});
},
"out": function () {
self.$container.trigger("zoom." + pluginName, {"level": -1});
}
};
// init Zoom data
$.extend(self.zoomData, {
zoomLevel: 0,
panX: 0,
panY: 0
});
// init zoom buttons
$.each(zoomOptions.buttons, function(type, opt) {
if (fnZoomButtons[type] === undefined) throw new Error("Unknown zoom button '" + type + "'");
// Create div with classes, contents and title (for tooltip)
var $button = $("<div>").addClass(opt.cssClass)
.html(opt.content)
.attr("title", opt.title);
// Assign click event
$button.on("click." + pluginName, fnZoomButtons[type]);
// Append to map
self.$map.append($button);
});
// Update the zoom level of the map on mousewheel
if (self.options.map.zoom.mousewheel) {
self.$map.on("mousewheel." + pluginName, function (e) {
var zoomLevel = (e.deltaY > 0) ? 1 : -1;
var coord = self.mapPagePositionToXY(e.pageX, e.pageY);
self.$container.trigger("zoom." + pluginName, {
"fixedCenter": true,
"level": self.zoomData.zoomLevel + zoomLevel,
"x": coord.x,
"y": coord.y
});
return false;
});
}
// Update the zoom level of the map on touch pinch
if (self.options.map.zoom.touch) {
self.$map.on("touchstart." + pluginName, function (e) {
if (e.originalEvent.touches.length === 2) {
self.zoomCenterX = (e.originalEvent.touches[0].pageX + e.originalEvent.touches[1].pageX) / 2;
self.zoomCenterY = (e.originalEvent.touches[0].pageY + e.originalEvent.touches[1].pageY) / 2;
self.previousPinchDist = Math.sqrt(Math.pow((e.originalEvent.touches[1].pageX - e.originalEvent.touches[0].pageX), 2) + Math.pow((e.originalEvent.touches[1].pageY - e.originalEvent.touches[0].pageY), 2));
}
});
self.$map.on("touchmove." + pluginName, function (e) {
var pinchDist = 0;
var zoomLevel = 0;
if (e.originalEvent.touches.length === 2) {
pinchDist = Math.sqrt(Math.pow((e.originalEvent.touches[1].pageX - e.originalEvent.touches[0].pageX), 2) + Math.pow((e.originalEvent.touches[1].pageY - e.originalEvent.touches[0].pageY), 2));
if (Math.abs(pinchDist - self.previousPinchDist) > 15) {
var coord = self.mapPagePositionToXY(self.zoomCenterX, self.zoomCenterY);
zoomLevel = (pinchDist - self.previousPinchDist) / Math.abs(pinchDist - self.previousPinchDist);
self.$container.trigger("zoom." + pluginName, {
"fixedCenter": true,
"level": self.zoomData.zoomLevel + zoomLevel,
"x": coord.x,
"y": coord.y
});
self.previousPinchDist = pinchDist;
}
return false;
}
});
}
// Panning
$("body").on("mouseup." + pluginName + (zoomOptions.touch ? " touchend" : ""), function () {
mousedown = false;
setTimeout(function () {
self.panning = false;
}, 50);
});
self.$map.on("mousedown." + pluginName + (zoomOptions.touch ? " touchstart" : ""), function (e) {
if (e.pageX !== undefined) {
mousedown = true;
previousX = e.pageX;
previousY = e.pageY;
} else {
if (e.originalEvent.touches.length === 1) {
mousedown = true;
previousX = e.originalEvent.touches[0].pageX;
previousY = e.originalEvent.touches[0].pageY;
}
}
}).on("mousemove." + pluginName + (zoomOptions.touch ? " touchmove" : ""), function (e) {
var currentLevel = self.zoomData.zoomLevel;
var pageX = 0;
var pageY = 0;
if (e.pageX !== undefined) {
pageX = e.pageX;
pageY = e.pageY;
} else {
if (e.originalEvent.touches.length === 1) {
pageX = e.originalEvent.touches[0].pageX;
pageY = e.originalEvent.touches[0].pageY;
} else {
mousedown = false;
}
}
if (mousedown && currentLevel !== 0) {
var offsetX = (previousX - pageX) / (1 + (currentLevel * zoomOptions.step)) * (mapWidth / self.paper.width);
var offsetY = (previousY - pageY) / (1 + (currentLevel * zoomOptions.step)) * (mapHeight / self.paper.height);
var panX = Math.min(Math.max(0, self.paper._viewBox[0] + offsetX), (mapWidth - self.paper._viewBox[2]));
var panY = Math.min(Math.max(0, self.paper._viewBox[1] + offsetY), (mapHeight - self.paper._viewBox[3]));
if (Math.abs(offsetX) > 5 || Math.abs(offsetY) > 5) {
$.extend(self.zoomData, {
panX: panX,
panY: panY,
zoomX: panX + self.paper._viewBox[2] / 2,
zoomY: panY + self.paper._viewBox[3] / 2
});
self.paper.setViewBox(panX, panY, self.paper._viewBox[2], self.paper._viewBox[3]);
clearTimeout(self.panningTO);
self.panningTO = setTimeout(function () {
self.$map.trigger("afterPanning", {
x1: panX,
y1: panY,
x2: (panX + self.paper._viewBox[2]),
y2: (panY + self.paper._viewBox[3])
});
}, 150);
previousX = pageX;
previousY = pageY;
self.panning = true;
}
return false;
}
});
},
/*
* Map a mouse position to a map position
* Transformation principle:
* ** start with (pageX, pageY) absolute mouse coordinate
* - Apply translation: take into accounts the map offset in the page
* ** from this point, we have relative mouse coordinate
* - Apply homothetic transformation: take into accounts initial factor of map sizing (fullWidth / actualWidth)
* - Apply homothetic transformation: take into accounts the zoom factor
* ** from this point, we have relative map coordinate
* - Apply translation: take into accounts the current panning of the map
* ** from this point, we have absolute map coordinate
* @param pageX: mouse client coordinate on X
* @param pageY: mouse client coordinate on Y
* @return map coordinate {x, y}
*/
mapPagePositionToXY: function(pageX, pageY) {
var self = this;
var offset = self.$map.offset();
var initFactor = (self.options.map.width) ? (self.mapConf.width / self.options.map.width) : (self.mapConf.width / self.$map.width());
var zoomFactor = 1 / (1 + (self.zoomData.zoomLevel * self.options.map.zoom.step));
return {
x: (zoomFactor * initFactor * (pageX - offset.left)) + self.zoomData.panX,
y: (zoomFactor * initFactor * (pageY - offset.top)) + self.zoomData.panY
};
},
/*
* Zoom on the map at a specific level focused on specific coordinates
* If no coordinates are specified, the zoom will be focused on the center of the map
* options :
* "level" : level of the zoom between minLevel and maxLevel
* "x" or "latitude" : x coordinate or latitude of the point to focus on
* "y" or "longitude" : y coordinate or longitude of the point to focus on
* "fixedCenter" : set to true in order to preserve the position of x,y in the canvas when zoomed
* "animDuration" : zoom duration
*/
onZoomEvent: function (e, zoomOptions) {
var self = this;
var newLevel = self.zoomData.zoomLevel;
var panX = 0;
var panY = 0;
var previousZoomLevel = (1 + self.zoomData.zoomLevel * self.options.map.zoom.step);
var zoomLevel = 0;
var animDuration = (zoomOptions.animDuration !== undefined) ? zoomOptions.animDuration : self.options.map.zoom.animDuration;
var offsetX = 0;
var offsetY = 0;
var coords = {};
// Get user defined zoom level
if (zoomOptions.level !== undefined) {
if (typeof zoomOptions.level === "string") {
// level is a string, either "n", "+n" or "-n"
if ((zoomOptions.level.slice(0, 1) === '+') || (zoomOptions.level.slice(0, 1) === '-')) {
// zoomLevel is relative
newLevel = self.zoomData.zoomLevel + parseInt(zoomOptions.level);
} else {
// zoomLevel is absolute
newLevel = parseInt(zoomOptions.level);
}
} else {
// level is integer
if (zoomOptions.level < 0) {
// zoomLevel is relative
newLevel = self.zoomData.zoomLevel + zoomOptions.level;
} else {
// zoomLevel is absolute
newLevel = zoomOptions.level;
}
}
// Make sure we stay in the boundaries
newLevel = Math.min(Math.max(newLevel, self.options.map.zoom.minLevel), self.options.map.zoom.maxLevel);
}
zoomLevel = (1 + newLevel * self.options.map.zoom.step);
if (zoomOptions.latitude !== undefined && zoomOptions.longitude !== undefined) {
coords = self.mapConf.getCoords(zoomOptions.latitude, zoomOptions.longitude);
zoomOptions.x = coords.x;
zoomOptions.y = coords.y;
}
if (zoomOptions.x === undefined)
zoomOptions.x = self.paper._viewBox[0] + self.paper._viewBox[2] / 2;
if (zoomOptions.y === undefined)
zoomOptions.y = (self.paper._viewBox[1] + self.paper._viewBox[3] / 2);
if (newLevel === 0) {
panX = 0;
panY = 0;
} else if (zoomOptions.fixedCenter !== undefined && zoomOptions.fixedCenter === true) {
offsetX = self.zoomData.panX + ((zoomOptions.x - self.zoomData.panX) * (zoomLevel - previousZoomLevel)) / zoomLevel;
offsetY = self.zoomData.panY + ((zoomOptions.y - self.zoomData.panY) * (zoomLevel - previousZoomLevel)) / zoomLevel;
panX = Math.min(Math.max(0, offsetX), (self.mapConf.width - (self.mapConf.width / zoomLevel)));
panY = Math.min(Math.max(0, offsetY), (self.mapConf.height - (self.mapConf.height / zoomLevel)));
} else {
panX = Math.min(Math.max(0, zoomOptions.x - (self.mapConf.width / zoomLevel) / 2), (self.mapConf.width - (self.mapConf.width / zoomLevel)));
panY = Math.min(Math.max(0, zoomOptions.y - (self.mapConf.height / zoomLevel) / 2), (self.mapConf.height - (self.mapConf.height / zoomLevel)));
}
// Update zoom level of the map
if (zoomLevel == previousZoomLevel && panX == self.zoomData.panX && panY == self.zoomData.panY) return;
if (animDuration > 0) {
self.animateViewBox(panX, panY, self.mapConf.width / zoomLevel, self.mapConf.height / zoomLevel, animDuration, self.options.map.zoom.animEasing);
} else {
self.paper.setViewBox(panX, panY, self.mapConf.width / zoomLevel, self.mapConf.height / zoomLevel);
clearTimeout(self.zoomTO);
self.zoomTO = setTimeout(function () {
self.$map.trigger("afterZoom", {
x1: panX,
y1: panY,
x2: (panX + (self.mapConf.width / zoomLevel)),
y2: (panY + (self.mapConf.height / zoomLevel))
});
}, 150);
}
$.extend(self.zoomData, {
zoomLevel: newLevel,
panX: panX,
panY: panY,
zoomX: panX + self.paper._viewBox[2] / 2,
zoomY: panY + self.paper._viewBox[3] / 2
});
},
/*
* Show some element in range defined by user
* Triggered by user $(".mapcontainer").trigger("showElementsInRange", [opt]);
*
* @param opt the options
* opt.hiddenOpacity opacity for hidden element (default = 0.3)
* opt.animDuration animation duration in ms (default = 0)
* opt.afterShowRange callback
* opt.ranges the range to show:
* Example:
* opt.ranges = {
* 'plot' : {
* 0 : { // valueIndex
* 'min': 1000,
* 'max': 1200
* },
* 1 : { // valueIndex
* 'min': 10,
* 'max': 12
* }
* },
* 'area' : {
* {'min': 10, 'max': 20} // No valueIndex, only an object, use 0 as valueIndex (easy case)
* }
* }
*/
onShowElementsInRange: function(e, opt) {
var self = this;
// set animDuration to default if not defined
if (opt.animDuration === undefined) {
opt.animDuration = 0;
}
// set hiddenOpacity to default if not defined
if (opt.hiddenOpacity === undefined) {
opt.hiddenOpacity = 0.3;
}
// handle area
if (opt.ranges && opt.ranges.area) {
self.showElemByRange(opt.ranges.area, self.areas, opt.hiddenOpacity, opt.animDuration);
}
// handle plot
if (opt.ranges && opt.ranges.plot) {
self.showElemByRange(opt.ranges.plot, self.plots, opt.hiddenOpacity, opt.animDuration);
}
// handle link
if (opt.ranges && opt.ranges.link) {
self.showElemByRange(opt.ranges.link, self.links, opt.hiddenOpacity, opt.animDuration);
}
// Call user callback
if (opt.afterShowRange) opt.afterShowRange();
},
/*
* Show some element in range
* @param ranges: the ranges
* @param elems: list of element on which to check against previous range
* @hiddenOpacity: the opacity when hidden
* @animDuration: the animation duration
*/
showElemByRange: function(ranges, elems, hiddenOpacity, animDuration) {
var self = this;
// Hold the final opacity value for all elements consolidated after applying each ranges
// This allow to set the opacity only once for each elements
var elemsFinalOpacity = {};
// set object with one valueIndex to 0 if we have directly the min/max
if (ranges.min !== undefined || ranges.max !== undefined) {
ranges = {0: ranges};
}
// Loop through each valueIndex
$.each(ranges, function (valueIndex) {
var range = ranges[valueIndex];
// Check if user defined at least a min or max value
if (range.min === undefined && range.max === undefined) {
return true; // skip this iteration (each loop), goto next range
}
// Loop through each elements
$.each(elems, function (id) {
var elemValue = elems[id].value;
// set value with one valueIndex to 0 if not object
if (typeof elemValue !== "object") {
elemValue = [elemValue];
}
// Check existence of this value index
if (elemValue[valueIndex] === undefined) {
return true; // skip this iteration (each loop), goto next element
}
// Check if in range
if ((range.min !== undefined && elemValue[valueIndex] < range.min) ||
(range.max !== undefined && elemValue[valueIndex] > range.max)) {
// Element not in range
elemsFinalOpacity[id] = hiddenOpacity;
} else {
// Element in range
elemsFinalOpacity[id] = 1;
}
});
});
// Now that we looped through all ranges, we can really assign the final opacity
$.each(elemsFinalOpacity, function (id) {
self.setElementOpacity(elems[id], elemsFinalOpacity[id], animDuration);
});
},
/*
* Set element opacity
* Handle elem.mapElem and elem.textElem
* @param elem the element
* @param opacity the opacity to apply
* @param animDuration the animation duration to use
*/
setElementOpacity: function(elem, opacity, animDuration) {
// Ensure no animation is running
//elem.mapElem.stop();
//if (elem.textElem) elem.textElem.stop();
// If final opacity is not null, ensure element is shown before proceeding
if (opacity > 0) {
elem.mapElem.show();
if (elem.textElem) elem.textElem.show();
}
if (animDuration > 0) {
// Animate attribute
elem.mapElem.animate({"opacity": opacity}, animDuration, "linear", function () {
// If final attribute is 0, hide
if (opacity === 0) elem.mapElem.hide();
});
// Handle text element
if (elem.textElem) {
// Animate attribute
elem.textElem.animate({"opacity": opacity}, animDuration, "linear", function () {
// If final attribute is 0, hide
if (opacity === 0) elem.textElem.hide();
});
}
} else {
// Set attribute
elem.mapElem.attr({"opacity": opacity});
// For null opacity, hide it
if (opacity === 0) elem.mapElem.hide();
// Handle text elemen
if (elem.textElem) {
// Set attribute
elem.textElem.attr({"opacity": opacity});
// For null opacity, hide it
if (opacity === 0) elem.textElem.hide();
}
}
},
/*
*
* Update the current map
* Refresh attributes and tooltips for areas and plots
* @param opt option for the refresh :
* opt.mapOptions: options to update for plots and areas
* opt.replaceOptions: whether mapsOptions should entirely replace current map options, or just extend it
* opt.opt.newPlots new plots to add to the map
* opt.newLinks new links to add to the map
* opt.deletePlotKeys plots to delete from the map (array, or "all" to remove all plots)
* opt.deleteLinkKeys links to remove from the map (array, or "all" to remove all links)
* opt.setLegendElemsState the state of legend elements to be set : show (default) or hide
* opt.animDuration animation duration in ms (default = 0)
* opt.afterUpdate Hook that allows to add custom processing on the map
*/
onUpdateEvent: function (e, opt) {
var self = this;
// Abort if opt is undefined
if (typeof opt !== "object") return;
var i = 0;
var animDuration = (opt.animDuration) ? opt.animDuration : 0;
// This function remove an element using animation (or not, depending on animDuration)
// Used for deletePlotKeys and deleteLinkKeys
var fnRemoveElement = function (elem) {
// Unset all event handlers
self.unsetHover(elem.mapElem, elem.textElem);
if (animDuration > 0) {
elem.mapElem.animate({"opacity": 0}, animDuration, "linear", function () {
elem.mapElem.remove();
});
if (elem.textElem) {
elem.textElem.animate({"opacity": 0}, animDuration, "linear", function () {
elem.textElem.remove();
});
}
} else {
elem.mapElem.remove();
if (elem.textElem) {
elem.textElem.remove();
}
}
};
// This function show an element using animation
// Used for newPlots and newLinks
var fnShowElement = function (elem) {
// Starts with hidden elements
elem.mapElem.attr({opacity: 0});
if (elem.textElem) elem.textElem.attr({opacity: 0});
// Set final element opacity
self.setElementOpacity(
elem,
(elem.mapElem.originalAttrs.opacity !== undefined) ? elem.mapElem.originalAttrs.opacity : 1,
animDuration
);
};
if (typeof opt.mapOptions === "object") {
if (opt.replaceOptions === true) self.options = self.extendDefaultOptions(opt.mapOptions);
else $.extend(true, self.options, opt.mapOptions);
// IF we update areas, plots or legend, then reset all legend state to "show"
if (opt.mapOptions.areas !== undefined || opt.mapOptions.plots !== undefined || opt.mapOptions.legend !== undefined) {
$("[data-type='elem']", self.$container).each(function (id, elem) {
if ($(elem).attr('data-hidden') === "1") {
// Toggle state of element by clicking
$(elem).trigger("click." + pluginName, [false, animDuration]);
}
});
}
}
// Delete plots by name if deletePlotKeys is array
if (typeof opt.deletePlotKeys === "object") {
for (; i < opt.deletePlotKeys.length; i++) {
if (self.plots[opt.deletePlotKeys[i]] !== undefined) {
fnRemoveElement(self.plots[opt.deletePlotKeys[i]]);
delete self.plots[opt.deletePlotKeys[i]];
}
}
// Delete ALL plots if deletePlotKeys is set to "all"
} else if (opt.deletePlotKeys === "all") {
$.each(self.plots, function (id, elem) {
fnRemoveElement(elem);
});
// Empty plots object
self.plots = {};
}
// Delete links by name if deleteLinkKeys is array
if (typeof opt.deleteLinkKeys === "object") {
for (i = 0; i < opt.deleteLinkKeys.length; i++) {
if (self.links[opt.deleteLinkKeys[i]] !== undefined) {
fnRemoveElement(self.links[opt.deleteLinkKeys[i]]);
delete self.links[opt.deleteLinkKeys[i]];
}
}
// Delete ALL links if deleteLinkKeys is set to "all"
} else if (opt.deleteLinkKeys === "all") {
$.each(self.links, function (id, elem) {
fnRemoveElement(elem);
});
// Empty links object
self.links = {};
}
// New plots
if (typeof opt.newPlots === "object") {
$.each(opt.newPlots, function (id) {
if (self.plots[id] === undefined) {
self.options.plots[id] = opt.newPlots[id];
self.plots[id] = self.drawPlot(id);
if (animDuration > 0) {
fnShowElement(self.plots[id]);
}
}
});
}
// New links
if (typeof opt.newLinks === "object") {
var newLinks = self.drawLinksCollection(opt.newLinks);
$.extend(self.links, newLinks);
$.extend(self.options.links, opt.newLinks);
if (animDuration > 0) {
$.each(newLinks, function (id) {
fnShowElement(newLinks[id]);
});
}
}
// Update areas attributes and tooltips
$.each(self.areas, function (id) {
// Avoid updating unchanged elements
if ((typeof opt.mapOptions === "object" &&
(
(typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultArea === "object")
|| (typeof opt.mapOptions.areas === "object" && typeof opt.mapOptions.areas[id] === "object")
|| (typeof opt.mapOptions.legend === "object" && typeof opt.mapOptions.legend.area === "object")
))
|| opt.replaceOptions === true
) {
var elemOptions = self.getElemOptions(
self.options.map.defaultArea,
(self.options.areas[id] ? self.options.areas[id] : {}),
self.options.legend.area
);
self.updateElem(elemOptions, self.areas[id], animDuration);
}
});
// Update plots attributes and tooltips
$.each(self.plots, function (id) {
// Avoid updating unchanged elements
if ((typeof opt.mapOptions ==="object" &&
(
(typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultPlot === "object")
|| (typeof opt.mapOptions.plots === "object" && typeof opt.mapOptions.plots[id] === "object")
|| (typeof opt.mapOptions.legend === "object" && typeof opt.mapOptions.legend.plot === "object")
))
|| opt.replaceOptions === true
) {
var elemOptions = self.getElemOptions(
self.options.map.defaultPlot,
(self.options.plots[id] ? self.options.plots[id] : {}),
self.options.legend.plot
);
if (elemOptions.type == "square") {
elemOptions.attrs.width = elemOptions.size;
elemOptions.attrs.height = elemOptions.size;
elemOptions.attrs.x = self.plots[id].mapElem.attrs.x - (elemOptions.size - self.plots[id].mapElem.attrs.width) / 2;
elemOptions.attrs.y = self.plots[id].mapElem.attrs.y - (elemOptions.size - self.plots[id].mapElem.attrs.height) / 2;
} else if (elemOptions.type == "image") {
elemOptions.attrs.width = elemOptions.width;
elemOptions.attrs.height = elemOptions.height;
elemOptions.attrs.x = self.plots[id].mapElem.attrs.x - (elemOptions.width - self.plots[id].mapElem.attrs.width) / 2;
elemOptions.attrs.y = self.plots[id].mapElem.attrs.y - (elemOptions.height - self.plots[id].mapElem.attrs.height) / 2;
} else if (elemOptions.type == "svg") {
if (elemOptions.attrs.transform !== undefined) {
elemOptions.attrs.transform = self.plots[id].mapElem.baseTransform + elemOptions.attrs.transform;
}
}else { // Default : circle
elemOptions.attrs.r = elemOptions.size / 2;
}
self.updateElem(elemOptions, self.plots[id], animDuration);
}
});
// Update links attributes and tooltips
$.each(self.links, function (id) {
// Avoid updating unchanged elements
if ((typeof opt.mapOptions === "object" &&
(
(typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultLink === "object")
|| (typeof opt.mapOptions.links === "object" && typeof opt.mapOptions.links[id] === "object")
))
|| opt.replaceOptions === true
) {
var elemOptions = self.getElemOptions(
self.options.map.defaultLink,
(self.options.links[id] ? self.options.links[id] : {}),
{}
);
self.updateElem(elemOptions, self.links[id], animDuration);
}
});
// Update legends
if (opt.mapOptions && (
(typeof opt.mapOptions.legend === "object")
|| (typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultArea === "object")
|| (typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultPlot === "object")
)) {
// Show all elements on the map before updating the legends
$("[data-type='elem']", self.$container).each(function (id, elem) {
if ($(elem).attr('data-hidden') === "1") {
$(elem).trigger("click." + pluginName, [false, animDuration]);
}
});
self.createLegends("area", self.areas, 1);
if (self.options.map.width) {
self.createLegends("plot", self.plots, (self.options.map.width / self.mapConf.width));
} else {
self.createLegends("plot", self.plots, (self.$map.width() / self.mapConf.width));
}
}
// Hide/Show all elements based on showlegendElems
// Toggle (i.e. click) only if:
// - slice legend is shown AND we want to hide
// - slice legend is hidden AND we want to show
if (typeof opt.setLegendElemsState === "object") {
// setLegendElemsState is an object listing the legend we want to hide/show
$.each(opt.setLegendElemsState, function (legendCSSClass, action) {
// Search for the legend
var $legend = self.$container.find("." + legendCSSClass)[0];
if ($legend !== undefined) {
// Select all elem inside this legend
$("[data-type='elem']", $legend).each(function (id, elem) {
if (($(elem).attr('data-hidden') === "0" && action === "hide") ||
($(elem).attr('data-hidden') === "1" && action === "show")) {
// Toggle state of element by clicking