/** * * Jquery Mapael - Dynamic maps jQuery plugin (based on raphael.js) * Requires jQuery and raphael.js * * Version: 0.2.0 (06-30-2013) * * Copyright (c) 2013 Vincent Brouté (http://www.neveldo.fr/mapael) * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php). * */ (function($) { "use strict"; $.fn.mapael = function(options) { options = $.extend(true, {}, $.fn.mapael.defaultOptions, options); return this.each(function() { var $tooltip = $("
").addClass(options.map.tooltip.cssClass).css("display", "none") , $container = $(this).empty().append($tooltip) , mapConf = $.fn.mapael.maps[options.map.name] , containerWidth = $container.width() , paper = new Raphael(this, mapConf.width, mapConf.height) , areaParams = {} , legend = {} , mapElem = {} , bbox = {} , plotParams = {} , textElem = {} , coords = {} , resizeTO = 0; if (options.map.width) { paper.setSize(options.map.width, mapConf.height * (options.map.width / mapConf.width)); } else { // Handle resizing of the container $(window).bind('resize', function(){ clearTimeout(resizeTO); resizeTO = setTimeout(function(){$container.trigger('resizeEnd');}, 150); }); $(document).bind('ready', function(){$container.trigger('resizeEnd');}); $container.bind('resizeEnd', function(e) { var containerWidth = $container.width(); if (paper.width != containerWidth) { paper.setSize(containerWidth, mapConf.height * (containerWidth / mapConf.width)); } }); } options.map.tooltip.css && $tooltip.css(options.map.tooltip.css); paper.setViewBox(0, 0, mapConf.width, mapConf.height, false); // Draw map areas for (var id in mapConf.elems) { areaParams = $.extend( true , {} , options.map.defaultArea , (options.areas[id] ? options.areas[id] : {}) ); if (options.legend.area && areaParams.value) { legend = $.fn.mapael.getLegendEl(areaParams.value, options.legend.area); legend && $.extend(true, areaParams, legend); } mapElem = paper.path(mapConf.elems[id]).attr(areaParams.attrs); mapElem.elemType = 'area'; areaParams.tooltip && areaParams.tooltip.content && $.fn.mapael.setTooltip(mapElem, $tooltip, areaParams.tooltip.content); $.fn.mapael.paramHover(mapElem, areaParams.attrs, areaParams.attrsHover); // Set a text label in the area if (areaParams.text) { bbox = mapElem.getBBox(); textElem = paper.text( (bbox.x + bbox.x2) / 2 , (bbox.y + bbox.y2) / 2 , areaParams.text ).attr(areaParams.textAttrs); areaParams.tooltip && areaParams.tooltip.content && $.fn.mapael.setTooltip(textElem, $tooltip, areaParams.tooltip.content); areaParams.attrs.href && (textElem.attr({href: areaParams.attrs.href})); $.fn.mapael.paramHover(textElem, areaParams.textAttrs, areaParams.textAttrsHover); $.fn.mapael.setHover(paper, mapElem, textElem); $.fn.mapael.setCallbacks(areaParams, mapElem, textElem); } else { $.fn.mapael.setHover(paper, mapElem); $.fn.mapael.setCallbacks(areaParams, mapElem); } } // Draw plots for (var i = 0, length = options.plots.length; i < length; ++i) { plotParams = $.extend( true, {} , options.map.defaultPlot , (options.plots[i] ? options.plots[i] : {}) ); if (plotParams.x && plotParams.y) { coords = {x : plotParams.x, y : plotParams.y}; } else { coords = mapConf.getCoords(plotParams.latitude, plotParams.longitude); } if (options.legend.plot && plotParams.value) { legend = $.fn.mapael.getLegendEl(plotParams.value, options.legend.plot); legend && $.extend(true, plotParams, legend); } if ("square" == plotParams.type) { mapElem = paper.rect( coords.x - (plotParams.size / 2) , coords.y - (plotParams.size / 2) , plotParams.size , plotParams.size ); } else if ("circle" == plotParams.type) { mapElem = paper.circle(coords.x, coords.y, plotParams.size / 2); } else { throw "Unknown plot type '" + plotParams.type + "'"; } mapElem.elemType = 'plot'; mapElem.attr(plotParams.attrs); plotParams.tooltip && plotParams.tooltip.content && $.fn.mapael.setTooltip(mapElem, $tooltip, plotParams.tooltip.content); $.fn.mapael.paramHover(mapElem, plotParams.attrs, plotParams.attrsHover); // Set a text label next to the plot if (plotParams.text) { textElem = (mapElem.type == "circle") ? paper.text(coords.x + (plotParams.size / 2) + 10, coords.y, plotParams.text) : paper.text(coords.x + plotParams.size + 10, coords.y, plotParams.text); textElem.attr(plotParams.textAttrs); plotParams.tooltip && plotParams.tooltip.content && $.fn.mapael.setTooltip(textElem, $tooltip, plotParams.tooltip.content); plotParams.attrs.href && (textElem.attr({"href": plotParams.attrs.href})); $.fn.mapael.paramHover(textElem, plotParams.textAttrs, plotParams.textAttrsHover); $.fn.mapael.setHover(paper, mapElem, textElem); $.fn.mapael.setCallbacks(plotParams, mapElem, textElem); } else { $.fn.mapael.setHover(paper, mapElem); $.fn.mapael.setCallbacks(plotParams, mapElem); } } // Create the legends for areas and plots if (options.legend.area.slices && options.legend.area.display) { $.fn.mapael.createLegend($container, options, 'area'); } if (options.legend.plot.slices && options.legend.plot.display) { $.fn.mapael.createLegend($container, options, 'plot'); } $(paper.desc).append(" and Mapael (http://neveldo.fr/mapael)"); }); }; /** * Set user defined callbacks on areas and plots * @param elemParams the element parameters * @param mapElem the map element to set callback on * @param textElem the optional text within the map element */ $.fn.mapael.setCallbacks = function(elemParams, mapElem, textElem) { var callbacks = []; if (elemParams.onclick) { callbacks.push({ event : 'click' , callback : function() {elemParams.onclick(elemParams, mapElem, textElem)} }); } if (elemParams.onmouseover) { callbacks.push({ event : 'mouseover' , callback : function() {elemParams.onmouseover(elemParams, mapElem, textElem)} }); } if (elemParams.onmouseout) { callbacks.push({ event : 'mouseout' , callback : function() {elemParams.onmouseout(elemParams, mapElem, textElem)} }); } for(var i = 0, length = callbacks.length; i < length; ++i) { $(mapElem.node).bind( callbacks[i].event , callbacks[i].callback ); textElem && $(textElem.node).bind( callbacks[i].event , callbacks[i].callback ); } } /** * Get the legend conf matching with the value * @param value the value to match with a slice in the legend * @param legend the legend params object * @return the legend slice matching with the value */ $.fn.mapael.getLegendEl = function (value, legend) { for(var i = 0, length = legend.slices.length; i < length; ++i) { if ((!legend.slices[i].min || value >= legend.slices[i].min) && (!legend.slices[i].max || value < legend.slices[i].max) ) { return legend.slices[i]; } } }; /** * Join a tooltip to 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, content) { var tooltipTO = 0; $(elem.node).bind("mouseover", function() { tooltipTO = setTimeout(function() {$tooltip.html(content).css("display", "block");}, 120); }).bind("mouseout", function() { clearTimeout(tooltipTO); $tooltip.css("display", "none"); }).bind("mousemove", function(e) { $tooltip.css("left", e.pageX + 15).css("top", e.pageY + 15 - $(window).scrollTop()); }); }; /** * Draw a legend for areas and / or plots * @param $container the legend container * @param options map options * @param legendType the type of the legend : 'area' or 'plot' */ $.fn.mapael.createLegend = function ($container, options, legendType) { var legendParams = options.legend[legendType] , $legend = $('
').addClass(legendParams["cssClass"]) , paper = new Raphael($legend.get(0)) , width = 5 , height = 5 , marginLeft = legendParams.marginLeft , marginLeftTitle = legendParams.marginLeftTitle , marginLeftLabel = legendParams.marginLeftLabel , marginBottom = legendParams.marginBottom , title = {} , attrParamName = '' , elem = {} , label = {} , lineWidth = {}; $container.append($legend); if(legendParams.title) { title = paper.text(marginLeftTitle, marginBottom, legendParams.title) .attr(legendParams.titleAttrs); width = marginLeftTitle + title.getBBox().width; height += marginBottom + title.getBBox().height; } for(var i = 0, length = legendParams.slices.length; i < length; ++i) { attrParamName = (legendType == 'plot') ? 'defaultPlot' : 'defaultArea'; legendParams.slices[i].attrs = $.extend( {} , options.map[attrParamName].attrs , legendParams.slices[i].attrs ); legendParams.slices[i].attrsHover = $.extend( {} , options.map[attrParamName].attrsHover , legendParams.slices[i].attrsHover ); if (legendParams.slices[i].type == "circle") { elem = paper.circle( marginLeft + legendParams.slices[i].size / 2 , height + legendParams.slices[i].size / 2 , legendParams.slices[i].size / 2 ).attr(legendParams.slices[i].attrs); } else { // Draw a square for squared plots AND areas !legendParams.slices[i].size && (legendParams.slices[i].size = 20); elem = paper.rect( marginLeft , height , legendParams.slices[i].size , legendParams.slices[i].size ).attr(legendParams.slices[i].attrs); } elem.elemType = 'plot'; label = paper.text( marginLeft + legendParams.slices[i].size + marginLeftLabel , height + legendParams.slices[i].size / 2 , legendParams.slices[i].label ).attr(legendParams.labelAttrs); height += marginBottom + legendParams.slices[i].size; lineWidth = marginLeft + legendParams.slices[i].size + marginBottom + label.getBBox().width; width = (width < lineWidth) ? lineWidth : width; $.fn.mapael.paramHover(elem, legendParams.slices[i].attrs, legendParams.slices[i].attrsHover); $.fn.mapael.paramHover(label, legendParams.labelAttrs, legendParams.labelAttrs); $.fn.mapael.setHover(paper, elem, label); } paper.setSize(width, height); } // Fix IE bug when toFront() is called // https://github.com/DmitryBaranovskiy/raphael/issues/225 $.fn.mapael.mouseHovered = false; $.fn.mapael.elemsHovered = []; $.fn.mapael.oldEvents = typeof document.documentElement.onmouseenter !== 'undefined'; /** * 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.hoverIn = function (paper, mapElem, textElem) { if (!$.fn.mapael.mouseHovered) { $.fn.mapael.mouseHovered = true; $.fn.mapael.elemsHovered.push(mapElem); if (mapElem) { mapElem.animate( mapElem.attrsHover , mapElem.attrsHover.animDuration ); mapElem.elemType == 'area' && mapElem.attrsHover.transform && mapElem.toFront(); } if (textElem) { textElem.animate( textElem.attrsHover , textElem.attrsHover.animDuration ); mapElem.elemType == 'area' && mapElem.attrsHover.transform && textElem.toFront(); } paper.safari(); } else { // IE fix for(var i = 0, length = $.fn.mapael.elemsHovered.length; i < length; ++i) { if($.fn.mapael.elemsHovered[i] != mapElem) { $($.fn.mapael.elemsHovered[i].node).trigger("mouseout"); } } $.fn.mapael.elemsHovered = []; } } /** * 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.hoverOut = function (paper, mapElem, textElem) { textElem && textElem.animate( textElem.originalAttrs , textElem.attrsHover.animDuration ); mapElem && mapElem.animate( mapElem.originalAttrs, mapElem.attrsHover.animDuration ); paper.safari(); $.fn.mapael.mouseHovered = false; }; /** * 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; if (mapElem) { $mapElem = $(mapElem.node); $mapElem.bind("mouseover", function() { hoverTO = setTimeout(function () {$.fn.mapael.hoverIn(paper, mapElem, textElem);}, 120); } ); $mapElem.bind("mouseout", function () {clearTimeout(hoverTO);$.fn.mapael.hoverOut(paper, mapElem, textElem);} ); } if (textElem) { $textElem = $(textElem.node); $textElem.bind("mouseover", function () {$.fn.mapael.hoverIn(paper, mapElem, textElem);} ); $textElem && $(textElem.node).bind("mouseout", function () {$.fn.mapael.hoverOut(paper, mapElem, textElem);} ); } }; /** * 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.paramHover = function (elem, originalAttrs, attrsHover) { // Don't use transform option on hover for VML (IE<9) because of several bugs if (Raphael.type != 'SVG') { delete attrsHover.transform; } elem.attrsHover = {}; $.extend(elem.attrsHover, attrsHover); if (elem.attrsHover.transform) { elem.originalAttrs = {transform : "s1"}; } else { elem.originalAttrs = {}; } $.extend(elem.originalAttrs, originalAttrs); }; // Default map options $.fn.mapael.defaultOptions = { map: { tooltip: { cssClass: "mapTooltip" } , defaultArea: { attrs: { fill: "#343434" , stroke: "#5d5d5d" , "stroke-width": 1 , "stroke-linejoin": "round" } , attrsHover: { fill: "#f38a03" , animDuration : 300 } , textAttrs: { "font-size": 15 , fill:"#c7c7c7" , "text-anchor": "center" } , textAttrsHover: { fill:"#eaeaea" , "animDuration" : 300 } } , defaultPlot: { type: "circle" , size: 15 , attrs: { fill: "#0088db" , stroke: "#fff" , "stroke-width": 0 , "stroke-linejoin": "round" } , attrsHover: { "stroke-width": 3 , animDuration : 300 } , textAttrs: { "font-size": 15 , fill:"#c7c7c7" , "text-anchor": "start" }, textAttrsHover: { fill:"#eaeaea" , animDuration : 300 } } } , legend: { area: { cssClass: "mapLegend" , display: false , marginLeft: 15 , marginLeftTitle: 5 , marginLeftLabel: 10 , marginBottom: 15 , titleAttrs: { "font-size" : 18 , fill : "#343434" , "text-anchor" : "start" } , labelAttrs: { "font-size" : 15 , fill : "#343434" , "text-anchor" : "start" } , slices : [] }, plot: { cssClass: "mapLegend" , display: false , marginLeft: 15 , marginLeftTitle: 5 , marginLeftLabel: 10 , marginBottom: 15 , titleAttrs: { "font-size" : 18 , fill : "#343434" , "text-anchor" : "start" } , labelAttrs: { "font-size" : 15 , fill : "#343434" , "text-anchor" : "start" } , slices : [] } } , areas: {} , plots: {} }; })(jQuery);