/** * * Jquery Mapael - Dynamic maps jQuery plugin (based on raphael.js) * Requires jQuery and raphael.js * * Version: 1.0.1 * * Copyright (c) 2015 Vincent Brouté (http://www.vincentbroute.fr/mapael) * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php). * */ (function($) { "use strict"; $.fn.mapael = function(options) { // Extend legend default options with user options options = $.extend(true, {}, $.fn.mapael.defaultOptions, options); for (var type in options.legend) { if ($.isArray(options.legend[type])) { for (var i = 0; i < options.legend[type].length; ++i) options.legend[type][i] = $.extend(true, {}, $.fn.mapael.legendDefaultOptions[type], options.legend[type][i]); } else { options.legend[type] = $.extend(true, {}, $.fn.mapael.legendDefaultOptions[type], options.legend[type]); } } return this.each(function() { var $self = $(this) , $container = $("." + options.map.cssClass, this).empty() , $tooltip = $("
").addClass(options.map.tooltip.cssClass).css("display", "none").appendTo(options.map.tooltip.target || $container) , mapConf = $.fn.mapael.maps[options.map.name] , paper = new Raphael($container[0], mapConf.width, mapConf.height) , elemOptions = {} , resizeTO = 0 , areas = {} , plots = {} , legends = [] , id = 0; options.map.tooltip.css && $tooltip.css(options.map.tooltip.css); paper.setViewBox(0, 0, mapConf.width, mapConf.height, false); // Draw map areas for (id in mapConf.elems) { elemOptions = $.fn.mapael.getElemOptions( options.map.defaultArea , (options.areas[id] ? options.areas[id] : {}) , options.legend.area ); areas[id] = {"mapElem" : paper.path(mapConf.elems[id]).attr(elemOptions.attrs)}; } // Init map areas in a second loop (prevent texts to be hidden by map elements) for (id in mapConf.elems) { elemOptions = $.fn.mapael.getElemOptions( options.map.defaultArea , (options.areas[id] ? options.areas[id] : {}) , options.legend.area ); $.fn.mapael.initElem(paper, areas[id], elemOptions, $tooltip, id); } // Draw links $.fn.mapael.drawLinksCollection(paper, options, mapConf.getCoords, $tooltip); // Draw plots for (id in options.plots) { plots[id] = $.fn.mapael.drawPlot(id, options, mapConf, paper, $tooltip); } /** * 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 0 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 */ $self.on("zoom", function(e, zoomOptions) { var newLevel = Math.min(Math.max(zoomOptions.level, 0), options.map.zoom.maxLevel) , panX = 0 , panY = 0 , previousZoomLevel = (1 + $self.data("zoomLevel") * options.map.zoom.step) , zoomLevel = (1 + newLevel * options.map.zoom.step) , offsetX = 0 , offsetY = 0 , coords = {}; if (typeof zoomOptions.latitude != "undefined" && typeof zoomOptions.longitude != "undefined") { coords = mapConf.getCoords(zoomOptions.latitude, zoomOptions.longitude); zoomOptions.x = coords.x; zoomOptions.y = coords.y; } if (typeof zoomOptions.x == "undefined") zoomOptions.x = paper._viewBox[0] + paper._viewBox[2] / 2; if (typeof zoomOptions.y == "undefined") zoomOptions.y = (paper._viewBox[1] + paper._viewBox[3] / 2); // Update zoom level of the map if (newLevel == 0) { paper.setViewBox(panX, panY, mapConf.width, mapConf.height); } else { if (typeof zoomOptions.fixedCenter != 'undefined' && zoomOptions.fixedCenter == true) { if (zoomLevel == previousZoomLevel) return; offsetX = $self.data("panX") + ((zoomOptions.x - $self.data("panX")) * (zoomLevel - previousZoomLevel)) / zoomLevel; offsetY = $self.data("panY") + ((zoomOptions.y - $self.data("panY")) * (zoomLevel - previousZoomLevel)) / zoomLevel; panX = Math.min(Math.max(0, offsetX), (mapConf.width - (mapConf.width / zoomLevel))); panY = Math.min(Math.max(0, offsetY), (mapConf.height - (mapConf.height / zoomLevel))); } else { panX = Math.min(Math.max(0, zoomOptions.x - (mapConf.width / zoomLevel)/2), (mapConf.width - (mapConf.width / zoomLevel))); panY = Math.min(Math.max(0, zoomOptions.y - (mapConf.height / zoomLevel)/2), (mapConf.height - (mapConf.height / zoomLevel))); } paper.setViewBox(panX, panY, mapConf.width / zoomLevel, mapConf.height / zoomLevel); } $self.data({"zoomLevel" : newLevel, "panX" : panX, "panY" : panY, "zoomX" : zoomOptions.x, "zoomY" : zoomOptions.y}); }); /** * Update the zoom level of the map on mousewheel */ options.map.zoom.enabled && options.map.zoom.mousewheel && $self.on("mousewheel", function(e) { var offset = $container.offset(), initFactor = (options.map.width) ? ($.fn.mapael.maps[options.map.name].width / options.map.width) : ($.fn.mapael.maps[options.map.name].width / $container.width()) , zoomLevel = (e.deltaY > 0) ? 1 : -1 , zoomFactor = 1 / (1 + ($self.data("zoomLevel")) * options.map.zoom.step) , x = zoomFactor * initFactor * (e.clientX + $(window).scrollLeft() - offset.left) + $self.data("panX") , y = zoomFactor * initFactor * (e.clientY + $(window).scrollTop() - offset.top) + $self.data("panY"); $self.trigger("zoom", {fixedCenter : true, "level" : $self.data("zoomLevel") + zoomLevel, "x" : x, "y" : y}); return false; }); // Enable zoom if (options.map.zoom.enabled) $.fn.mapael.initZoom($container, paper, mapConf.width, mapConf.height, options.map.zoom); // Set initial zoom if (typeof options.map.zoom.init != "undefined") { $self.trigger("zoom", options.map.zoom.init); } // Create the legends for areas $.merge(legends, $.fn.mapael.createLegends($self, options, "area", areas, 1)); /** * * Update the current map * Refresh attributes and tooltips for areas and plots * @param updatedOptions options to update for plots and areas * @param newPlots new plots to add to the map * @param deletedPlotsplots to delete from the map * @param opt option for the refresh : * opt.animDuration animation duration in ms (default = 0) * opt.resetAreas true to reset previous areas options * opt.resetPlots true to reset previous plots options * opt.afterUpdate Hook that allows to add custom processing on the map */ $self.on("update", function(e, updatedOptions, newPlots, deletedPlots, opt) { var i = 0 , id = 0 , animDuration = 0 , elemOptions = {}; // Reset hidden map elements (when user click on legend elements) legends.forEach(function(el) { el.forEach && el.forEach(function(el) { if(typeof el.hidden != "undefined" && el.hidden == true) { $(el.node).trigger("click"); } }) }); if (typeof opt != "undefined") { (opt.resetAreas) && (options.areas = {}); (opt.resetPlots) && (options.plots = {}); (opt.animDuration) && (animDuration = opt.animDuration); } $.extend(true, options, updatedOptions); // Delete plots if (typeof deletedPlots == "object") { for (;i < deletedPlots.length; i++) { if (typeof plots[deletedPlots[i]] != "undefined") { if (animDuration > 0) { (function(plot) { plot.mapElem.animate({"opacity":0}, animDuration, "linear", function() {plot.mapElem.remove();}); if (plot.textElem) { plot.textElem.animate({"opacity":0}, animDuration, "linear", function() {plot.textElem.remove();}); } })(plots[deletedPlots[i]]); } else { plots[deletedPlots[i]].mapElem.remove(); if (plots[deletedPlots[i]].textElem) { plots[deletedPlots[i]].textElem.remove(); } } delete plots[deletedPlots[i]]; } } } // New plots if (typeof newPlots == "object") { for (id in newPlots) { if (typeof plots[id] == "undefined") { options.plots[id] = newPlots[id]; plots[id] = $.fn.mapael.drawPlot(id, options, mapConf, paper, $tooltip); if (animDuration > 0) { plots[id].mapElem.attr({opacity : 0}); plots[id].textElem.attr({opacity : 0}); plots[id].mapElem.animate({"opacity": (typeof plots[id].mapElem.originalAttrs.opacity != "undefined") ? plots[id].mapElem.originalAttrs.opacity : 1}, animDuration); plots[id].textElem.animate({"opacity": (typeof plots[id].textElem.originalAttrs.opacity != "undefined") ? plots[id].textElem.originalAttrs.opacity : 1}, animDuration); } } } } // Update areas attributes and tooltips for (id in areas) { elemOptions = $.fn.mapael.getElemOptions( options.map.defaultArea , (options.areas[id] ? options.areas[id] : {}) , options.legend.area ); $.fn.mapael.updateElem(elemOptions, areas[id], $tooltip, animDuration); } // Update plots attributes and tooltips for (id in plots) { elemOptions = $.fn.mapael.getElemOptions( options.map.defaultPlot , (options.plots[id] ? options.plots[id] : {}) , options.legend.plot ); if (elemOptions.type == "square") { elemOptions.attrs.width = elemOptions.size; elemOptions.attrs.height = elemOptions.size; elemOptions.attrs.x = plots[id].mapElem.attrs.x - (elemOptions.size - plots[id].mapElem.attrs.width) / 2; elemOptions.attrs.y = plots[id].mapElem.attrs.y - (elemOptions.size - plots[id].mapElem.attrs.height) / 2; } else if (elemOptions.type == "image") { elemOptions.attrs.x = plots[id].mapElem.attrs.x - (elemOptions.width - plots[id].mapElem.attrs.width) / 2; elemOptions.attrs.y = plots[id].mapElem.attrs.y - (elemOptions.height - plots[id].mapElem.attrs.height) / 2; } else { // Default : circle elemOptions.attrs.r = elemOptions.size / 2; } $.fn.mapael.updateElem(elemOptions, plots[id], $tooltip, animDuration); } if(typeof opt != "undefined") opt.afterUpdate && opt.afterUpdate($self, paper, areas, plots, options); }); // Handle resizing of the map if (options.map.width) { paper.setSize(options.map.width, mapConf.height * (options.map.width / mapConf.width)); // Create the legends for plots taking into account the scale of the map $.merge(legends, $.fn.mapael.createLegends($self, options, "plot", plots, (options.map.width / mapConf.width))); } else { $(window).on("resize", function() { clearTimeout(resizeTO); resizeTO = setTimeout(function(){$container.trigger("resizeEnd");}, 150); }); // Create the legends for plots taking into account the scale of the map var createPlotLegend = function() { $.merge(legends, $.fn.mapael.createLegends($self, options, "plot", plots, ($container.width() / mapConf.width))); $container.unbind("resizeEnd", createPlotLegend); }; $container.on("resizeEnd", function() { var containerWidth = $container.width(); if (paper.width != containerWidth) { paper.setSize(containerWidth, mapConf.height * (containerWidth / mapConf.width)); } }).on("resizeEnd", createPlotLegend).trigger("resizeEnd"); } // Hook that allows to add custom processing on the map options.map.afterInit && options.map.afterInit($self, paper, areas, plots, options); $(paper.desc).append(" and Mapael (http://www.vincentbroute.fr/mapael/)"); }); }; /** * Init the element "elem" on the map (drawing, setting attributes, events, tooltip, ...) */ $.fn.mapael.initElem = function(paper, elem, options, $tooltip, id) { var bbox = {}, textPosition = {}; if (typeof options.value != "undefined") elem.value = options.value; // Init attrsHover $.fn.mapael.setHoverOptions(elem.mapElem, options.attrs, options.attrsHover); // Init the label related to the element if (options.text && typeof options.text.content != "undefined") { // Set a text label in the area bbox = elem.mapElem.getBBox(); textPosition = $.fn.mapael.getTextPosition(bbox, options.text.position, options.text.margin); options.text.attrs["text-anchor"] = textPosition.textAnchor; elem.textElem = paper.text(textPosition.x, textPosition.y, options.text.content).attr(options.text.attrs); $.fn.mapael.setHoverOptions(elem.textElem, options.text.attrs, options.text.attrsHover); options.eventHandlers && $.fn.mapael.setEventHandlers(id, options, elem.mapElem, elem.textElem); $.fn.mapael.setHover(paper, elem.mapElem, elem.textElem); $(elem.textElem.node).attr("data-id", id); } else { options.eventHandlers && $.fn.mapael.setEventHandlers(id, options, elem.mapElem); $.fn.mapael.setHover(paper, elem.mapElem); } // Init the tooltip if (options.tooltip && options.tooltip.content) { elem.mapElem.tooltipContent = options.tooltip.content; $.fn.mapael.setTooltip(elem.mapElem, $tooltip); if (options.text && typeof options.text.content != "undefined") { elem.textElem.tooltipContent = options.tooltip.content; $.fn.mapael.setTooltip(elem.textElem, $tooltip); } } // Init the link if (options.href) { elem.mapElem.href = options.href; elem.mapElem.target = options.target; $.fn.mapael.setHref(elem.mapElem); if (options.text && typeof options.text.content != "undefined") { elem.textElem.href = options.href; elem.textElem.target = options.target; $.fn.mapael.setHref(elem.textElem); } } $(elem.mapElem.node).attr("data-id", id); }; /** * Draw all links between plots on the paper */ $.fn.mapael.drawLinksCollection = function(paper, options, getCoords, $tooltip) { var p1 = {} , p2 = {} , elemOptions = {} , coordsP1 = {} , coordsP2 ={}; for (var id in options.links) { elemOptions = $.fn.mapael.getElemOptions(options.map.defaultLink, options.links[id], {}); if (typeof options.links[id].between[0] == 'string') { p1 = options.plots[options.links[id].between[0]]; } else { p1 = options.links[id].between[0]; } if (typeof options.links[id].between[1] == 'string') { p2 = options.plots[options.links[id].between[1]]; } else { p2 = options.links[id].between[1]; } if (typeof p1.latitude != "undefined" && typeof p1.longitude != "undefined") { coordsP1 = getCoords(p1.latitude, p1.longitude); } else { coordsP1.x = p1.x; coordsP1.y = p1.y; } if (typeof p2.latitude != "undefined" && typeof p2.longitude != "undefined") { coordsP2 = getCoords(p2.latitude, p2.longitude); } else { coordsP2.x = p2.x; coordsP2.y = p2.y; } $.fn.mapael.drawLink(id, paper, coordsP1.x, coordsP1.y, coordsP2.x, coordsP2.y, elemOptions, $tooltip); } }; /** * Draw a curved link between two couples of coordinates a(xa,ya) and b(xb, yb) on the paper */ $.fn.mapael.drawLink = function(id, paper, xa, ya, xb, yb, elemOptions, $tooltip) { var elem = {} // Compute the "curveto" SVG point, d(x,y) // c(xc, yc) is the center of (xa,ya) and (xb, yb) , xc = (xa + xb) / 2 , yc = (ya + yb) / 2 // Equation for (cd) : y = acd * x + bcd (d is the cure point) , acd = - 1 / ((yb - ya) / (xb - xa)) , bcd = yc - acd * xc // dist(c,d) = dist(a,b) (=abDist) , abDist = Math.sqrt((xb-xa)*(xb-xa) + (yb-ya)*(yb-ya)) // Solution for equation dist(cd) = sqrt((xd - xc)² + (yd - yc)²) // dist(c,d)² = (xd - xc)² + (yd - yc)² // We assume that dist(c,d) = dist(a,b) // so : (xd - xc)² + (yd - yc)² - dist(a,b)² = 0 // With the factor : (xd - xc)² + (yd - yc)² - (factor*dist(a,b))² = 0 // (xd - xc)² + (acd*xd + bcd - yc)² - (factor*dist(a,b))² = 0 , a = 1 + acd*acd , b = -2 * xc + 2*acd*bcd - 2 * acd*yc , c = xc*xc + bcd*bcd - bcd*yc - yc*bcd + yc*yc - ((elemOptions.factor*abDist) * (elemOptions.factor*abDist)) , delta = b*b - 4*a*c , x = 0 , y = 0; // There are two solutions, we choose one or the other depending on the sign of the factor if (elemOptions.factor > 0) { x = (-b + Math.sqrt(delta)) / (2*a); y = acd * x + bcd; } else { x = (-b - Math.sqrt(delta)) / (2*a); y = acd * x + bcd; } elem.mapElem = paper.path("m "+xa+","+ya+" C "+x+","+y+" "+xb+","+yb+" "+xb+","+yb+"").attr(elemOptions.attrs); $.fn.mapael.initElem(paper, elem, elemOptions, $tooltip, id); return elem; }; /** * Update the element "elem" on the map with the new elemOptions options */ $.fn.mapael.updateElem = function(elemOptions, elem, $tooltip, animDuration) { var bbox, textPosition, plotOffset; if (typeof elemOptions.value != "undefined") elem.value = elemOptions.value; // Update the label if (elem.textElem) { if (typeof elemOptions.text != "undefined" && typeof elemOptions.text.content != "undefined" && elemOptions.text.content != elem.textElem.attrs.text) elem.textElem.attr({text : elemOptions.text.content}); bbox = elem.mapElem.getBBox(); if (elemOptions.size) { plotOffset = (elemOptions.size - bbox.height) / 2; bbox.x -= plotOffset; bbox.x2 += plotOffset; bbox.y -= plotOffset; bbox.y2 += plotOffset; } textPosition = $.fn.mapael.getTextPosition(bbox, elemOptions.text.position, elemOptions.text.margin); if (textPosition.x != elem.textElem.attrs.x || textPosition.y != elem.textElem.attrs.y) { if (animDuration > 0) { elem.textElem.attr({"text-anchor" : textPosition.textAnchor}); elem.textElem.animate({x : textPosition.x, y : textPosition.y}, animDuration); } else elem.textElem.attr({x : textPosition.x, y : textPosition.y, "text-anchor" : textPosition.textAnchor}); } $.fn.mapael.setHoverOptions(elem.textElem, elemOptions.text.attrs, elemOptions.text.attrsHover); if (animDuration > 0) elem.textElem.animate(elemOptions.text.attrs, animDuration); else elem.textElem.attr(elemOptions.text.attrs); } // Update elements attrs and attrsHover $.fn.mapael.setHoverOptions(elem.mapElem, elemOptions.attrs, elemOptions.attrsHover); if (animDuration > 0) elem.mapElem.animate(elemOptions.attrs, animDuration); else elem.mapElem.attr(elemOptions.attrs); // Update the tooltip if (elemOptions.tooltip && typeof elemOptions.tooltip.content != "undefined") { if (typeof elem.mapElem.tooltipContent == "undefined") { $.fn.mapael.setTooltip(elem.mapElem, $tooltip); (elem.textElem) && $.fn.mapael.setTooltip(elem.textElem, $tooltip); } elem.mapElem.tooltipContent = elemOptions.tooltip.content; (elem.textElem) && (elem.textElem.tooltipContent = elemOptions.tooltip.content); } // Update the link if (typeof elemOptions.href != "undefined") { if (typeof elem.mapElem.href == "undefined") { $.fn.mapael.setHref(elem.mapElem); (elem.textElem) && $.fn.mapael.setHref(elem.textElem); } elem.mapElem.href = elemOptions.href; elem.mapElem.target = elemOptions.target; if (elem.textElem) { elem.textElem.href = elemOptions.href; elem.textElem.target = elemOptions.target; } } }; /** * Draw the plot */ $.fn.mapael.drawPlot = function(id, options, mapConf, paper, $tooltip) { var plot = {} , coords = {} , elemOptions = $.fn.mapael.getElemOptions( options.map.defaultPlot , (options.plots[id] ? options.plots[id] : {}) , options.legend.plot ); if (typeof elemOptions.x != "undefined" && typeof elemOptions.y != "undefined") coords = {x : elemOptions.x, y : elemOptions.y}; else coords = mapConf.getCoords(elemOptions.latitude, elemOptions.longitude); if (elemOptions.type == "square") { plot = {"mapElem" : paper.rect( coords.x - (elemOptions.size / 2) , coords.y - (elemOptions.size / 2) , elemOptions.size , elemOptions.size ).attr(elemOptions.attrs)}; } else if (elemOptions.type == "image") { plot = { "mapElem" : paper.image( elemOptions.url , coords.x - elemOptions.width / 2 , coords.y - elemOptions.height / 2 , elemOptions.width , elemOptions.height ).attr(elemOptions.attrs) }; } else { // Default = circle plot = {"mapElem" : paper.circle(coords.x, coords.y, elemOptions.size / 2).attr(elemOptions.attrs)}; } $.fn.mapael.initElem(paper, plot, elemOptions, $tooltip, id); return plot; }; /** * Set target link on elem */ $.fn.mapael.setHref = function(elem) { elem.attr({cursor : "pointer"}); $(elem.node).bind("click", function() { if (!$.fn.mapael.panning && elem.href) window.open(elem.href, elem.target); }); }; /** * Set a tooltip for the areas and plots * @param elem area or plot element * @param $tooltip the tooltip container * @param content the content to set in the tooltip */ $.fn.mapael.setTooltip = function(elem, $tooltip) { var tooltipTO = 0 , $container = $tooltip.parent() , containerY2 = $container.offset().left + $container.width(); $(elem.node).on("mouseover", function(e) { tooltipTO = setTimeout( function() { elem.tooltipContent && $tooltip.html(elem.tooltipContent).css("display", "block"); $tooltip.css({"left" : Math.min(containerY2 - $tooltip.outerWidth() - 5, e.pageX + 10 - $(window).scrollLeft()), "top" : e.pageY + 20 - $(window).scrollTop()}); } , 120 ); }).on("mouseout", function(e) { clearTimeout(tooltipTO); $tooltip.css("display", "none"); }).on("mousemove", function(e) { $tooltip.css({"left" : Math.min(containerY2 - $tooltip.outerWidth() - 5, e.pageX + 10 - $(window).scrollLeft()), "top" : e.pageY + 20 - $(window).scrollTop()}); }); }; /** * Set user defined handlers for events on areas and plots * @param id the id of the element * @param elemOptions the element parameters * @param mapElem the map element to set callback on * @param textElem the optional text within the map element */ $.fn.mapael.setEventHandlers = function(id, elemOptions, mapElem, textElem) { for(var event in elemOptions.eventHandlers) { (function(event) { $(mapElem.node).on(event, function(e) {!$.fn.mapael.panning && elemOptions.eventHandlers[event](e, id, mapElem, textElem, elemOptions)}); textElem && $(textElem.node).on(event, function(e) {!$.fn.mapael.panning && elemOptions.eventHandlers[event](e, id, mapElem, textElem, elemOptions)}); })(event); } }; $.fn.mapael.panning = false; /** * Init zoom and panning for the map * @param $container * @param paper * @param mapWidth * @param mapHeight * @param options */ $.fn.mapael.initZoom = function($container, paper, mapWidth, mapHeight, options) { var $parentContainer = $container.parent() , $zoomIn = $("
").addClass(options.zoomInCssClass).html("+") , $zoomOut = $("
").addClass(options.zoomOutCssClass).html("−") , mousedown = false , previousX = 0 , previousY = 0; // Zoom $parentContainer.data("zoomLevel", 0).data({"panX" : 0, "panY" : 0}); $container.append($zoomIn).append($zoomOut); $zoomIn.on("click", function() {$parentContainer.trigger("zoom", {"level" : $parentContainer.data("zoomLevel") + 1});}); $zoomOut.on("click", function() {$parentContainer.trigger("zoom", {"level" : $parentContainer.data("zoomLevel") - 1});}); // Panning $("body").on("mouseup", function(e) { mousedown = false; setTimeout(function () {$.fn.mapael.panning = false;}, 50); }); $container.on("mousedown", function(e) { mousedown = true; previousX = e.pageX; previousY = e.pageY; return false; }).on("mousemove", function(e) { var currentLevel = $parentContainer.data("zoomLevel"); if (mousedown && currentLevel != 0) { var offsetX = (previousX - e.pageX) / (1 + (currentLevel * options.step)) * (mapWidth / paper.width) , offsetY = (previousY - e.pageY) / (1 + (currentLevel * options.step)) * (mapHeight / paper.height) , panX = Math.min(Math.max(0, paper._viewBox[0] + offsetX), (mapWidth - paper._viewBox[2])) , panY = Math.min(Math.max(0, paper._viewBox[1] + offsetY), (mapHeight - paper._viewBox[3])); if (Math.abs(offsetX) > 5 || Math.abs(offsetY) > 5) { $parentContainer.data({"panX" : panX, "panY" : panY}); paper.setViewBox(panX, panY, paper._viewBox[2], paper._viewBox[3]); previousX = e.pageX; previousY = e.pageY; $.fn.mapael.panning = true; } } return false; }); }; /** * Draw a legend for areas and / or plots * @param legendOptions options for the legend to draw * @param $container the map container * @param options map options object * @param legendType the type of the legend : "area" or "plot" * @param elems collection of plots or areas on the maps * @param legendIndex index of the legend in the conf array */ $.fn.mapael.drawLegend = function (legendOptions, $container, options, legendType, elems, scale, legendIndex) { var $legend = {} , paper = {} , width = 0 , height = 0 , title = {} , elem = {} , elemBBox = {} , label = {} , i = 0 , x = 0 , y = 0 , yCenter = 0 , sliceAttrs = [] , length = 0; if (!legendOptions.slices || !legendOptions.display) return; $legend = $("." + legendOptions.cssClass, $container).empty(); paper = new Raphael($legend.get(0)); height = width = 0; // Set the title of the legend if(legendOptions.title) { title = paper.text(legendOptions.marginLeftTitle, 0, legendOptions.title).attr(legendOptions.titleAttrs); title.attr({y : 0.5 * title.getBBox().height}); width = legendOptions.marginLeftTitle + title.getBBox().width; height += legendOptions.marginBottomTitle + title.getBBox().height; } // Calculate attrs (and width, height and r (radius)) for legend elements, and yCenter for horizontal legends for(i = 0, length = legendOptions.slices.length; i < length; ++i) { if (typeof legendOptions.slices[i].legendSpecificAttrs == "undefined") legendOptions.slices[i].legendSpecificAttrs = {}; sliceAttrs[i] = $.extend( {} , (legendType == "plot") ? options.map["defaultPlot"].attrs : options.map["defaultArea"].attrs , legendOptions.slices[i].attrs , legendOptions.slices[i].legendSpecificAttrs ); if (legendType == "area") { if (typeof sliceAttrs[i].width == "undefined") sliceAttrs[i].width = 30; if (typeof sliceAttrs[i].height == "undefined") sliceAttrs[i].height = 20; } else if (legendOptions.slices[i].type == "square") { if (typeof sliceAttrs[i].width == "undefined") sliceAttrs[i].width = legendOptions.slices[i].size; if (typeof sliceAttrs[i].height == "undefined") sliceAttrs[i].height = legendOptions.slices[i].size; } else if (legendOptions.slices[i].type == "image") { if (typeof sliceAttrs[i].width == "undefined") sliceAttrs[i].width = legendOptions.slices[i].width; if (typeof sliceAttrs[i].height == "undefined") sliceAttrs[i].height = legendOptions.slices[i].height; } else { if (typeof sliceAttrs[i].r == "undefined") sliceAttrs[i].r = legendOptions.slices[i].size / 2; } if(legendOptions.slices[i].type == "image" || legendType == "area") { yCenter = Math.max(yCenter, legendOptions.marginBottomTitle + title.getBBox().height + scale * sliceAttrs[i].height/2); } else { yCenter = Math.max(yCenter, legendOptions.marginBottomTitle + title.getBBox().height + scale * sliceAttrs[i].r); } } if (legendOptions.mode == "horizontal") { width = legendOptions.marginLeft; } // Draw legend elements (circle, square or image in vertical or horizontal mode) for(i = 0, length = legendOptions.slices.length; i < length; ++i) { if (typeof legendOptions.slices[i].display == "undefined" || legendOptions.slices[i].display == true) { if(legendType == "area") { if (legendOptions.mode == "horizontal") { x = width + legendOptions.marginLeft; y = yCenter - (0.5 * scale * sliceAttrs[i].height); } else { x = legendOptions.marginLeft; y = height; } elem = paper.rect(x, y, scale * (sliceAttrs[i].width), scale * (sliceAttrs[i].height)); } else if(legendOptions.slices[i].type == "square") { if (legendOptions.mode == "horizontal") { x = width + legendOptions.marginLeft; y = yCenter - (0.5 * scale * sliceAttrs[i].height); } else { x = legendOptions.marginLeft; y = height; } elem = paper.rect(x, y, scale * (sliceAttrs[i].width), scale * (sliceAttrs[i].height)); } else if(legendOptions.slices[i].type == "image") { if (legendOptions.mode == "horizontal") { x = width + legendOptions.marginLeft; y = yCenter - (0.5 * scale * sliceAttrs[i].height); } else { x = legendOptions.marginLeft; y = height; } elem = paper.image( legendOptions.slices[i].url, x, y, scale * sliceAttrs[i].width, scale * sliceAttrs[i].height); } else { if (legendOptions.mode == "horizontal") { x = width + legendOptions.marginLeft + scale * (sliceAttrs[i].r); y = yCenter; } else { x = legendOptions.marginLeft + scale * (sliceAttrs[i].r); y = height + scale * (sliceAttrs[i].r); } elem = paper.circle(x, y, scale * (sliceAttrs[i].r)); } // Set attrs to the element drawn above delete sliceAttrs[i].width; delete sliceAttrs[i].height; delete sliceAttrs[i].r; elem.attr(sliceAttrs[i]); elemBBox = elem.getBBox(); // Draw the label associated with the element if (legendOptions.mode == "horizontal") { x = width + legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel; y = yCenter; } else { x = legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel; y = height + (elemBBox.height / 2); } label = paper.text(x, y, legendOptions.slices[i].label).attr(legendOptions.labelAttrs); // Update the width and height for the paper if (legendOptions.mode == "horizontal") { width += legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel + label.getBBox().width; if(legendOptions.slices[i].type == "image" || legendType == "area") { height = Math.max(height, legendOptions.marginBottom + title.getBBox().height + elemBBox.height); } else { height = Math.max(height, legendOptions.marginBottomTitle + legendOptions.marginBottom + title.getBBox().height + elemBBox.height); } } else { width = Math.max(width, legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel + label.getBBox().width); height += legendOptions.marginBottom + elemBBox.height; } $(elem.node).attr({"data-type": "elem", "data-index": i, "data-hidden": 0}); $(label.node).attr({"data-type": "label", "data-index": i, "data-hidden": 0}); // Hide map elements when the user clicks on a legend item if (legendOptions.hideElemsOnClick.enabled) { // Hide/show elements when user clicks on a legend element label.attr({cursor:"pointer"}); elem.attr({cursor:"pointer"}); $.fn.mapael.setHoverOptions(elem, sliceAttrs[i], sliceAttrs[i]); $.fn.mapael.setHoverOptions(label, legendOptions.labelAttrs, legendOptions.labelAttrsHover); $.fn.mapael.setHover(paper, elem, label); $.fn.mapael.handleClickOnLegendElem($container, legendOptions, legendOptions.slices[i], label, elem, elems, legendIndex); } } } // VMLWidth option allows you to set static width for the legend // only for VML render because text.getBBox() returns wrong values on IE6/7 if (Raphael.type != "SVG" && legendOptions.VMLWidth) width = legendOptions.VMLWidth; paper.setSize(width, height); return paper; }; /** * Allow to hide elements of the map when the user clicks on a related legend item * @param $container the map container * @param legendOptions options for the legend to draw * @param sliceOptions options of the slice * @param label label of the legend item * @param elem element of the legend item * @param elems collection of plots or areas displayed on the map * @param legendIndex index of the legend in the conf array */ $.fn.mapael.handleClickOnLegendElem = function($container, legendOptions, sliceOptions, label, elem, elems, legendIndex) { var hideMapElems = function(e, hideOtherElems) { var elemValue = 0 , hidden = $(label.node).attr('data-hidden') , hiddenNewAttr = (hidden == 0) ? {"data-hidden": 1} : {"data-hidden": 0}; if (hidden == 0) { label.animate({"opacity":0.5}, 300); } else { label.animate({"opacity":1}, 300); } for (var id in elems) { if ($.isArray(elems[id].value)) { elemValue = elems[id].value[legendIndex]; } else { elemValue = elems[id].value; } if ((typeof sliceOptions.sliceValue != "undefined" && elemValue == sliceOptions.sliceValue) || ((typeof sliceOptions.sliceValue == "undefined") && (typeof sliceOptions.min == "undefined" || elemValue >= sliceOptions.min) && (typeof sliceOptions.max == "undefined" || elemValue < sliceOptions.max)) ) { (function(id) { if (hidden == 0) { elems[id].mapElem.animate({"opacity":legendOptions.hideElemsOnClick.opacity}, 300, "linear", function() {(legendOptions.hideElemsOnClick.opacity == 0) && elems[id].mapElem.hide();}); elems[id].textElem && elems[id].textElem.animate({"opacity":legendOptions.hideElemsOnClick.opacity}, 300, "linear", function() {(legendOptions.hideElemsOnClick.opacity == 0) && elems[id].textElem.hide();}); } else { if (legendOptions.hideElemsOnClick.opacity == 0) { elems[id].mapElem.show(); elems[id].textElem && elems[id].textElem.show(); } elems[id].mapElem.animate({"opacity":typeof elems[id].mapElem.originalAttrs.opacity != "undefined" ? elems[id].mapElem.originalAttrs.opacity : 1}, 300); elems[id].textElem && elems[id].textElem.animate({"opacity":typeof elems[id].textElem.originalAttrs.opacity != "undefined" ? elems[id].textElem.originalAttrs.opacity : 1}, 300); } })(id); } } $(elem.node).attr(hiddenNewAttr); $(label.node).attr(hiddenNewAttr); if ((typeof hideOtherElems === "undefined" || hideOtherElems === true) && typeof legendOptions.exclusive !== "undefined" && legendOptions.exclusive === true ) { $("[data-type='elem'][data-hidden=0]", $container).each(function() { if ($(this).attr('data-index') !== $(elem.node).attr('data-index')) { $(this).trigger('click', false); } }); } }; $(label.node).on("click", hideMapElems); $(elem.node).on("click", hideMapElems); if (typeof sliceOptions.hidden !== "undefined" && sliceOptions.hidden === true) { $(elem.node).trigger('click', false); } }; /** * Create all legends for a specified type (area or plot) * @param $container the map container * @param options map options * @param legendType the type of the legend : "area" or "plot" * @param elems collection of plots or areas displayed on the map * @param scale scale ratio of the map */ $.fn.mapael.createLegends = function ($container, options, legendType, elems, scale) { var legends = []; if ($.isArray(options.legend[legendType])) { for (var j = 0; j < options.legend[legendType].length; ++j) { legends.push($.fn.mapael.drawLegend(options.legend[legendType][j], $container, options, legendType, elems, scale, j)); } } else { legends.push($.fn.mapael.drawLegend(options.legend[legendType], $container, options, legendType, elems, scale)); } return legends; }; /** * Set the attributes on hover and the attributes to restore for a map element * @param elem the map element * @param originalAttrs the original attributes to restore on mouseout event * @param attrsHover the attributes to set on mouseover event */ $.fn.mapael.setHoverOptions = function (elem, originalAttrs, attrsHover) { // Disable transform option on hover for VML (IE<9) because of several bugs if (Raphael.type != "SVG") delete attrsHover.transform; elem.attrsHover = attrsHover; if (elem.attrsHover.transform) elem.originalAttrs = $.extend({transform : "s1"}, originalAttrs); else elem.originalAttrs = originalAttrs; }; /** * Set the hover behavior (mouseover & mouseout) for plots and areas * @param paper Raphael paper object * @param mapElem the map element * @param textElem the optional text element (within the map element) */ $.fn.mapael.setHover = function (paper, mapElem, textElem) { var $mapElem = {} , $textElem = {} , hoverTO = 0 , overBehaviour = function() {hoverTO = setTimeout(function () {$.fn.mapael.elemHover(paper, mapElem, textElem);}, 120);} , outBehaviour = function () {clearTimeout(hoverTO);$.fn.mapael.elemOut(paper, mapElem, textElem);}; $mapElem = $(mapElem.node); $mapElem.on("mouseover", overBehaviour); $mapElem.on("mouseout", outBehaviour); if (textElem) { $textElem = $(textElem.node); $textElem.on("mouseover", overBehaviour); $(textElem.node).on("mouseout", outBehaviour); } }; /** * Set he behaviour for "mouseover" event * @param paper paper Raphael paper object * @param mapElem mapElem the map element * @param textElem the optional text element (within the map element) */ $.fn.mapael.elemHover = function (paper, mapElem, textElem) { mapElem.animate(mapElem.attrsHover, mapElem.attrsHover.animDuration); textElem && textElem.animate(textElem.attrsHover, textElem.attrsHover.animDuration); paper.safari(); }; /** * Set he behaviour for "mouseout" event * @param paper Raphael paper object * @param mapElem the map element * @param textElem the optional text element (within the map element) */ $.fn.mapael.elemOut = function (paper, mapElem, textElem) { mapElem.animate(mapElem.originalAttrs, mapElem.attrsHover.animDuration); textElem && textElem.animate(textElem.originalAttrs, textElem.attrsHover.animDuration); paper.safari(); }; /** * Get element options by merging default options, element options and legend options * @param defaultOptions * @param elemOptions * @param legendOptions */ $.fn.mapael.getElemOptions = function(defaultOptions, elemOptions, legendOptions) { var options = $.extend(true, {}, defaultOptions, elemOptions); if (typeof options.value != "undefined") { if ($.isArray(legendOptions)) { for (var i = 0, length = legendOptions.length;i= legend.slices[i].min) && (typeof legend.slices[i].max == "undefined" || value < legend.slices[i].max)) ) { return legend.slices[i]; } } return {}; }; // Default map options $.fn.mapael.defaultOptions = { map : { cssClass : "map" , tooltip : { cssClass : "mapTooltip", target: null } , defaultArea : { attrs : { fill : "#343434" , stroke : "#5d5d5d" , "stroke-width" : 1 , "stroke-linejoin" : "round" } , attrsHover : { fill : "#f38a03" , animDuration : 300 } , text : { position : "inner" , margin : 10 , attrs : { "font-size" : 15 , fill : "#c7c7c7" } , attrsHover : { fill : "#eaeaea" , "animDuration" : 300 } } , target : "_self" } , defaultPlot : { type : "circle" , size : 15 , attrs : { fill : "#0088db" , stroke : "#fff" , "stroke-width" : 0 , "stroke-linejoin" : "round" } , attrsHover : { "stroke-width" : 3 , animDuration : 300 } , text : { position : "right" , margin : 10 , attrs : { "font-size" : 15 , fill : "#c7c7c7" } , attrsHover : { fill : "#eaeaea" , animDuration : 300 } } , target : "_self" } , defaultLink : { factor : 0.5 , attrs : { stroke : "#0088db" , "stroke-width" : 2 } , attrsHover : { animDuration : 300 } , text : { position : "inner" , margin : 10 , attrs : { "font-size" : 15 , fill : "#c7c7c7" } , attrsHover : { fill : "#eaeaea" , animDuration : 300 } } , target : "_self" } , zoom : { enabled : false , maxLevel : 5 , step : 0.25 , zoomInCssClass : "zoomIn" , zoomOutCssClass : "zoomOut" , mousewheel : true } } , legend : { area : [] , plot : [] } , areas : {} , plots : {} , links : {} }; $.fn.mapael.legendDefaultOptions = { area : { cssClass : "areaLegend" , display : true , marginLeft : 10 , marginLeftTitle : 5 , marginBottomTitle: 10 , marginLeftLabel : 10 , marginBottom : 10 , titleAttrs : { "font-size" : 16 , fill : "#343434" , "text-anchor" : "start" } , labelAttrs : { "font-size" : 12 , fill : "#343434" , "text-anchor" : "start" } , labelAttrsHover : { fill : "#787878" , animDuration : 300 } , hideElemsOnClick : { enabled : true , opacity : 0.2 } , slices : [] , mode : "vertical" } , plot : { cssClass : "plotLegend" , display : true , marginLeft : 10 , marginLeftTitle : 5 , marginBottomTitle: 10 , marginLeftLabel : 10 , marginBottom : 10 , titleAttrs : { "font-size" : 16 , fill : "#343434" , "text-anchor" : "start" } , labelAttrs : { "font-size" : 12 , fill : "#343434" , "text-anchor" : "start" } , labelAttrsHover : { fill : "#787878" , animDuration : 300 } , hideElemsOnClick : { enabled : true , opacity : 0.2 } , slices : [] , mode : "vertical" } }; })(jQuery);