1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782 |
- /*!
- *
- * Jquery Mapael - Dynamic maps jQuery plugin (based on raphael.js)
- * Requires jQuery, raphael.js and jquery.mousewheel
- *
- * Version: 2.2.0
- *
- * Copyright (c) 2017 Vincent Brouté (https://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('jquery-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.2.0";
- /*
- * 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);
- // 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
- };
- self.currentViewBox = {
- x: 0, y: 0, w: 0, h: 0
- };
- // Panning: tell if panning action is in progress
- self.panning = false;
- // Animate view box
- self.zoomAnimID = null; // Interval handler (used to set and clear)
- self.zoomAnimStartTime = null; // Animation start time
- self.zoomAnimCVBTarget = null; // Current ViewBox target
- // Map subcontainer jQuery object
- self.$map = $("." + self.options.map.cssClass, self.container);
- // Save initial HTML content (used by destroy method)
- self.initialMapHTMLContent = self.$map.html();
- // 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 legends list
- self.legends = {};
- // The map configuration object (taken from map file)
- self.mapConf = {};
- // Holds all custom event handlers
- self.customEventHandlers = {};
- // 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 = {
- /* Filtering TimeOut value in ms
- * Used for mouseover trigger over elements */
- MouseOverFilteringTO: 120,
- /* Filtering TimeOut value in ms
- * Used for afterPanning trigger when panning */
- panningFilteringTO: 150,
- /* Filtering TimeOut value in ms
- * Used for mouseup/touchend trigger when panning */
- panningEndFilteringTO: 50,
- /* Filtering TimeOut value in ms
- * Used for afterZoom trigger when zooming */
- zoomFilteringTO: 150,
- /* Filtering TimeOut value in ms
- * Used for when resizing window */
- resizeFilteringTO: 150,
- /*
- * 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.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.setViewBox(0, 0, self.mapConf.width, self.mapConf.height);
- // 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));
- } else {
- // Responsive: handle resizing of the map
- self.initResponsiveSize();
- }
- // Draw map areas
- $.each(self.mapConf.elems, function (id) {
- // Init area object
- self.areas[id] = {};
- // Set area options
- self.areas[id].options = self.getElemOptions(
- self.options.map.defaultArea,
- (self.options.areas[id] ? self.options.areas[id] : {}),
- self.options.legend.area
- );
- // draw area
- self.areas[id].mapElem = self.paper.path(self.mapConf.elems[id]);
- });
- // 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
- // Allows text to be added after ALL areas and prevent them from being hidden
- $.each(self.mapConf.elems, function (id) {
- self.initElem(id, 'area', self.areas[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", self.options.map.zoom.init);
- }
- // Create the legends for areas
- self.createLegends("area", self.areas, 1);
- // Create the legends for plots taking into account the scale of the map
- self.createLegends("plot", self.plots, self.paper.width / self.mapConf.width);
- // 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);
- });
- // Attach delegated events
- self.initDelegatedMapEvents();
- // Attach delegated custom events
- self.initDelegatedCustomEvents();
- // 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 + " (https://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);
- self.$map.off("." + pluginName);
- // Detach the global resize event handler
- if (self.onResizeEvent) $(window).off("resize." + pluginName, self.onResizeEvent);
- // Empty the container (this will also detach all event listeners)
- self.$map.empty();
- // Replace initial HTML content
- self.$map.html(self.initialMapHTMLContent);
- // Empty legend containers and replace initial HTML content
- $.each(self.legends, function(legendType) {
- $.each(self.legends[legendType], function(legendIndex) {
- var legend = self.legends[legendType][legendIndex];
- legend.container.empty();
- legend.container.html(legend.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;
- self.customEventHandlers = undefined;
- },
- initResponsiveSize: function () {
- var self = this;
- var resizeTO = null;
- // Function that actually handle the resizing
- var handleResize = function(isInit) {
- 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
- // Do not do this on init (it will be done later)
- if (isInit !== true && self.options.legend.redrawOnResize) {
- self.createLegends("plot", self.plots, newScale);
- }
- }
- };
- self.onResizeEvent = function() {
- // Clear any previous setTimeout (avoid too much triggering)
- clearTimeout(resizeTO);
- // setTimeout to wait for the user to finish its resizing
- resizeTO = setTimeout(function () {
- handleResize();
- }, self.resizeFilteringTO);
- };
- // Attach resize handler
- $(window).on("resize." + pluginName, self.onResizeEvent);
- // Call once
- handleResize(true);
- },
- /*
- * 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(['area', 'plot'], function (key, 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 all delegated events for the whole map:
- * mouseover
- * mousemove
- * mouseout
- */
- initDelegatedMapEvents: function() {
- var self = this;
- // Mapping between data-type value and the corresponding elements array
- // Note: legend-elem and legend-label are not in this table because
- // they need a special processing
- var dataTypeToElementMapping = {
- 'area' : self.areas,
- 'area-text' : self.areas,
- 'plot' : self.plots,
- 'plot-text' : self.plots,
- 'link' : self.links,
- 'link-text' : self.links
- };
- /* Attach mouseover event delegation
- * Note: we filter the event with a timeout to reduce the firing when the mouse moves quickly
- */
- var mapMouseOverTimeoutID;
- self.$container.on("mouseover." + pluginName, "[data-id]", function () {
- var elem = this;
- clearTimeout(mapMouseOverTimeoutID);
- mapMouseOverTimeoutID = setTimeout(function() {
- var $elem = $(elem);
- var id = $elem.attr('data-id');
- var type = $elem.attr('data-type');
- if (dataTypeToElementMapping[type] !== undefined) {
- self.elemEnter(dataTypeToElementMapping[type][id]);
- } else if (type === 'legend-elem' || type === 'legend-label') {
- var legendIndex = $elem.attr('data-legend-id');
- var legendType = $elem.attr('data-legend-type');
- self.elemEnter(self.legends[legendType][legendIndex].elems[id]);
- }
- }, self.MouseOverFilteringTO);
- });
- /* Attach mousemove event delegation
- * Note: timeout filtering is small to update the Tooltip position fast
- */
- var mapMouseMoveTimeoutID;
- self.$container.on("mousemove." + pluginName, "[data-id]", function (event) {
- var elem = this;
- clearTimeout(mapMouseMoveTimeoutID);
- mapMouseMoveTimeoutID = setTimeout(function() {
- var $elem = $(elem);
- var id = $elem.attr('data-id');
- var type = $elem.attr('data-type');
- if (dataTypeToElementMapping[type] !== undefined) {
- self.elemHover(dataTypeToElementMapping[type][id], event);
- } else if (type === 'legend-elem' || type === 'legend-label') {
- /* Nothing to do */
- }
- }, 0);
- });
- /* Attach mouseout event delegation
- * Note: we don't perform any timeout filtering to clear & reset elem ASAP
- * Otherwise an element may be stuck in 'hover' state (which is NOT good)
- */
- self.$container.on("mouseout." + pluginName, "[data-id]", function () {
- var elem = this;
- // Clear any
- clearTimeout(mapMouseOverTimeoutID);
- clearTimeout(mapMouseMoveTimeoutID);
- var $elem = $(elem);
- var id = $elem.attr('data-id');
- var type = $elem.attr('data-type');
- if (dataTypeToElementMapping[type] !== undefined) {
- self.elemOut(dataTypeToElementMapping[type][id]);
- } else if (type === 'legend-elem' || type === 'legend-label') {
- var legendIndex = $elem.attr('data-legend-id');
- var legendType = $elem.attr('data-legend-type');
- self.elemOut(self.legends[legendType][legendIndex].elems[id]);
- }
- });
- /* Attach click event delegation
- * Note: we filter the event with a timeout to avoid double click
- */
- self.$container.on("click." + pluginName, "[data-id]", function (evt, opts) {
- var $elem = $(this);
- var id = $elem.attr('data-id');
- var type = $elem.attr('data-type');
- if (dataTypeToElementMapping[type] !== undefined) {
- self.elemClick(dataTypeToElementMapping[type][id]);
- } else if (type === 'legend-elem' || type === 'legend-label') {
- var legendIndex = $elem.attr('data-legend-id');
- var legendType = $elem.attr('data-legend-type');
- self.handleClickOnLegendElem(self.legends[legendType][legendIndex].elems[id], id, legendIndex, legendType, opts);
- }
- });
- },
- /*
- * Init all delegated custom events
- */
- initDelegatedCustomEvents: function() {
- var self = this;
- $.each(self.customEventHandlers, function(eventName) {
- // Namespace the custom event
- // This allow to easily unbound only custom events and not regular ones
- var fullEventName = eventName + '.' + pluginName + ".custom";
- self.$container.off(fullEventName).on(fullEventName, "[data-id]", function (e) {
- var $elem = $(this);
- var id = $elem.attr('data-id');
- var type = $elem.attr('data-type').replace('-text', '');
- if (!self.panning &&
- self.customEventHandlers[eventName][type] !== undefined &&
- self.customEventHandlers[eventName][type][id] !== undefined)
- {
- // Get back related elem
- var elem = self.customEventHandlers[eventName][type][id];
- // Run callback provided by user
- elem.options.eventHandlers[eventName](e, id, elem.mapElem, elem.textElem, elem.options);
- }
- });
- });
- },
- /*
- * Init the element "elem" on the map (drawing text, setting attributes, events, tooltip, ...)
- *
- * @param id the id of the element
- * @param type the type of the element (area, plot, link)
- * @param elem object the element object (with mapElem), it will be updated
- */
- initElem: function (id, type, elem) {
- var self = this;
- var $mapElem = $(elem.mapElem.node);
- // If an HTML link exists for this element, add cursor attributes
- if (elem.options.href) {
- elem.options.attrs.cursor = "pointer";
- if (elem.options.text) elem.options.text.attrs.cursor = "pointer";
- }
- // Set SVG attributes to map element
- elem.mapElem.attr(elem.options.attrs);
- // Set DOM attributes to map element
- $mapElem.attr({
- "data-id": id,
- "data-type": type
- });
- if (elem.options.cssClass !== undefined) {
- $mapElem.addClass(elem.options.cssClass);
- }
- // Init the label related to the element
- if (elem.options.text && elem.options.text.content !== undefined) {
- // Set a text label in the area
- var textPosition = self.getTextPosition(elem.mapElem.getBBox(), elem.options.text.position, elem.options.text.margin);
- elem.options.text.attrs.text = elem.options.text.content;
- elem.options.text.attrs.x = textPosition.x;
- elem.options.text.attrs.y = textPosition.y;
- elem.options.text.attrs['text-anchor'] = textPosition.textAnchor;
- // Draw text
- elem.textElem = self.paper.text(textPosition.x, textPosition.y, elem.options.text.content);
- // Apply SVG attributes to text element
- elem.textElem.attr(elem.options.text.attrs);
- // Apply DOM attributes
- $(elem.textElem.node).attr({
- "data-id": id,
- "data-type": type + '-text'
- });
- }
- // Set user event handlers
- if (elem.options.eventHandlers) self.setEventHandlers(id, type, elem);
- // Set hover option for mapElem
- self.setHoverOptions(elem.mapElem, elem.options.attrs, elem.options.attrsHover);
- // Set hover option for textElem
- if (elem.textElem) self.setHoverOptions(elem.textElem, elem.options.text.attrs, elem.options.text.attrsHover);
- },
- /*
- * 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", {"level": 0});
- },
- "in": function () {
- self.$container.trigger("zoom", {"level": "+1"});
- },
- "out": function () {
- self.$container.trigger("zoom", {"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", {
- "fixedCenter": true,
- "level": self.zoomData.zoomLevel + zoomLevel,
- "x": coord.x,
- "y": coord.y
- });
- e.preventDefault();
- });
- }
- // 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", {
- "fixedCenter": true,
- "level": self.zoomData.zoomLevel + zoomLevel,
- "x": coord.x,
- "y": coord.y
- });
- self.previousPinchDist = pinchDist;
- }
- return false;
- }
- });
- }
- // When the user drag the map, prevent to move the clicked element instead of dragging the map (behaviour seen with Firefox)
- self.$map.on("dragstart", function() {
- return false;
- });
- // Panning
- var panningMouseUpTO = null;
- var panningMouseMoveTO = null;
- $("body").on("mouseup." + pluginName + (zoomOptions.touch ? " touchend." + pluginName : ""), function () {
- mousedown = false;
- clearTimeout(panningMouseUpTO);
- clearTimeout(panningMouseMoveTO);
- panningMouseUpTO = setTimeout(function () {
- self.panning = false;
- }, self.panningEndFilteringTO);
- });
- self.$map.on("mousedown." + pluginName + (zoomOptions.touch ? " touchstart." + pluginName : ""), function (e) {
- clearTimeout(panningMouseUpTO);
- clearTimeout(panningMouseMoveTO);
- 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." + pluginName : ""), function (e) {
- var currentLevel = self.zoomData.zoomLevel;
- var pageX = 0;
- var pageY = 0;
- clearTimeout(panningMouseUpTO);
- clearTimeout(panningMouseMoveTO);
- 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.currentViewBox.x + offsetX), (mapWidth - self.currentViewBox.w));
- var panY = Math.min(Math.max(0, self.currentViewBox.y + offsetY), (mapHeight - self.currentViewBox.h));
- if (Math.abs(offsetX) > 5 || Math.abs(offsetY) > 5) {
- $.extend(self.zoomData, {
- panX: panX,
- panY: panY,
- zoomX: panX + self.currentViewBox.w / 2,
- zoomY: panY + self.currentViewBox.h / 2
- });
- self.setViewBox(panX, panY, self.currentViewBox.w, self.currentViewBox.h);
- panningMouseMoveTO = setTimeout(function () {
- self.$map.trigger("afterPanning", {
- x1: panX,
- y1: panY,
- x2: (panX + self.currentViewBox.w),
- y2: (panY + self.currentViewBox.h)
- });
- }, self.panningFilteringTO);
- 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
- *
- * zoomOptions.animDuration zoom duration
- *
- * zoomOptions.level level of the zoom between minLevel and maxLevel (absolute number, or relative string +1 or -1)
- * zoomOptions.fixedCenter set to true in order to preserve the position of x,y in the canvas when zoomed
- *
- * zoomOptions.x x coordinate of the point to focus on
- * zoomOptions.y y coordinate of the point to focus on
- * - OR -
- * zoomOptions.latitude latitude of the point to focus on
- * zoomOptions.longitude longitude of the point to focus on
- * - OR -
- * zoomOptions.plot plot ID to focus on
- * - OR -
- * zoomOptions.area area ID to focus on
- * zoomOptions.areaMargin margin (in pixels) around the area
- *
- * If an area ID is specified, the algorithm will override the zoom level to focus on the area
- * but it may be limited by the min/max zoom level limits set at initialization.
- *
- * If no coordinates are specified, the zoom will be focused on the center of the current view box
- *
- */
- onZoomEvent: function (e, zoomOptions) {
- var self = this;
- // new Top/Left corner coordinates
- var panX;
- var panY;
- // new Width/Height viewbox size
- var panWidth;
- var panHeight;
- // Zoom level in absolute scale (from 0 to max, by step of 1)
- var zoomLevel = self.zoomData.zoomLevel;
- // Relative zoom level (from 1 to max, by step of 0.25 (default))
- var previousRelativeZoomLevel = 1 + self.zoomData.zoomLevel * self.options.map.zoom.step;
- var relativeZoomLevel;
- var animDuration = (zoomOptions.animDuration !== undefined) ? zoomOptions.animDuration : self.options.map.zoom.animDuration;
- if (zoomOptions.area !== undefined) {
- /* An area is given
- * We will define x/y coordinate AND a new zoom level to fill the area
- */
- if (self.areas[zoomOptions.area] === undefined) throw new Error("Unknown area '" + zoomOptions.area + "'");
- var areaMargin = (zoomOptions.areaMargin !== undefined) ? zoomOptions.areaMargin : 10;
- var areaBBox = self.areas[zoomOptions.area].mapElem.getBBox();
- var areaFullWidth = areaBBox.width + 2 * areaMargin;
- var areaFullHeight = areaBBox.height + 2 * areaMargin;
- // Compute new x/y focus point (center of area)
- zoomOptions.x = areaBBox.cx;
- zoomOptions.y = areaBBox.cy;
- // Compute a new absolute zoomLevel value (inverse of relative -> absolute)
- // Take the min between zoomLevel on width vs. height to be able to see the whole area
- zoomLevel = Math.min(Math.floor((self.mapConf.width / areaFullWidth - 1) / self.options.map.zoom.step),
- Math.floor((self.mapConf.height / areaFullHeight - 1) / self.options.map.zoom.step));
- } else {
- // 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
- zoomLevel = self.zoomData.zoomLevel + parseInt(zoomOptions.level, 10);
- } else {
- // zoomLevel is absolute
- zoomLevel = parseInt(zoomOptions.level, 10);
- }
- } else {
- // level is integer
- if (zoomOptions.level < 0) {
- // zoomLevel is relative
- zoomLevel = self.zoomData.zoomLevel + zoomOptions.level;
- } else {
- // zoomLevel is absolute
- zoomLevel = zoomOptions.level;
- }
- }
- }
- if (zoomOptions.plot !== undefined) {
- if (self.plots[zoomOptions.plot] === undefined) throw new Error("Unknown plot '" + zoomOptions.plot + "'");
- zoomOptions.x = self.plots[zoomOptions.plot].coords.x;
- zoomOptions.y = self.plots[zoomOptions.plot].coords.y;
- } else {
- if (zoomOptions.latitude !== undefined && zoomOptions.longitude !== undefined) {
- var coords = self.mapConf.getCoords(zoomOptions.latitude, zoomOptions.longitude);
- zoomOptions.x = coords.x;
- zoomOptions.y = coords.y;
- }
- if (zoomOptions.x === undefined) {
- zoomOptions.x = self.currentViewBox.x + self.currentViewBox.w / 2;
- }
- if (zoomOptions.y === undefined) {
- zoomOptions.y = self.currentViewBox.y + self.currentViewBox.h / 2;
- }
- }
- }
- // Make sure we stay in the zoom level boundaries
- zoomLevel = Math.min(Math.max(zoomLevel, self.options.map.zoom.minLevel), self.options.map.zoom.maxLevel);
- // Compute relative zoom level
- relativeZoomLevel = 1 + zoomLevel * self.options.map.zoom.step;
- // Compute panWidth / panHeight
- panWidth = self.mapConf.width / relativeZoomLevel;
- panHeight = self.mapConf.height / relativeZoomLevel;
- if (zoomLevel === 0) {
- panX = 0;
- panY = 0;
- } else {
- if (zoomOptions.fixedCenter !== undefined && zoomOptions.fixedCenter === true) {
- panX = self.zoomData.panX + ((zoomOptions.x - self.zoomData.panX) * (relativeZoomLevel - previousRelativeZoomLevel)) / relativeZoomLevel;
- panY = self.zoomData.panY + ((zoomOptions.y - self.zoomData.panY) * (relativeZoomLevel - previousRelativeZoomLevel)) / relativeZoomLevel;
- } else {
- panX = zoomOptions.x - panWidth / 2;
- panY = zoomOptions.y - panHeight / 2;
- }
- // Make sure we stay in the map boundaries
- panX = Math.min(Math.max(0, panX), self.mapConf.width - panWidth);
- panY = Math.min(Math.max(0, panY), self.mapConf.height - panHeight);
- }
- // Update zoom level of the map
- if (relativeZoomLevel === previousRelativeZoomLevel && panX === self.zoomData.panX && panY === self.zoomData.panY) return;
- if (animDuration > 0) {
- self.animateViewBox(panX, panY, panWidth, panHeight, animDuration, self.options.map.zoom.animEasing);
- } else {
- self.setViewBox(panX, panY, panWidth, panHeight);
- clearTimeout(self.zoomTO);
- self.zoomTO = setTimeout(function () {
- self.$map.trigger("afterZoom", {
- x1: panX,
- y1: panY,
- x2: panX + panWidth,
- y2: panY + panHeight
- });
- }, self.zoomFilteringTO);
- }
- $.extend(self.zoomData, {
- zoomLevel: zoomLevel,
- panX: panX,
- panY: panY,
- zoomX: panX + panWidth / 2,
- zoomY: panY + panHeight / 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].options.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) {
- var self = this;
- // 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();
- }
- self.animate(elem.mapElem, {"opacity": opacity}, animDuration, function () {
- // If final attribute is 0, hide
- if (opacity === 0) elem.mapElem.hide();
- });
- self.animate(elem.textElem, {"opacity": opacity}, animDuration, function () {
- // If final attribute is 0, hide
- 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) {
- self.animate(elem.mapElem, {"opacity": 0}, animDuration, function () {
- elem.mapElem.remove();
- });
- self.animate(elem.textElem, {"opacity": 0}, animDuration, function () {
- 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='legend-elem']", self.$container).each(function (id, elem) {
- if ($(elem).attr('data-hidden') === "1") {
- // Toggle state of element by clicking
- $(elem).trigger("click", {hideOtherElems: false, animDuration: 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
- ) {
- self.areas[id].options = self.getElemOptions(
- self.options.map.defaultArea,
- (self.options.areas[id] ? self.options.areas[id] : {}),
- self.options.legend.area
- );
- self.updateElem(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
- ) {
- self.plots[id].options = self.getElemOptions(
- self.options.map.defaultPlot,
- (self.options.plots[id] ? self.options.plots[id] : {}),
- self.options.legend.plot
- );
- self.setPlotCoords(self.plots[id]);
- self.setPlotAttributes(self.plots[id]);
- self.updateElem(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
- ) {
- self.links[id].options = self.getElemOptions(
- self.options.map.defaultLink,
- (self.options.links[id] ? self.options.links[id] : {}),
- {}
- );
- self.updateElem(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='legend-elem']", self.$container).each(function (id, elem) {
- if ($(elem).attr('data-hidden') === "1") {
- $(elem).trigger("click", {hideOtherElems: false, animDuration: 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='legend-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
- $(elem).trigger("click", {hideOtherElems: false, animDuration: animDuration});
- }
- });
- }
- });
- } else {
- // setLegendElemsState is a string, or is undefined
- // Default : "show"
- var action = (opt.setLegendElemsState === "hide") ? "hide" : "show";
- $("[data-type='legend-elem']", self.$container).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
- $(elem).trigger("click", {hideOtherElems: false, animDuration: animDuration});
- }
- });
- }
- // Always rebind custom events on update
- self.initDelegatedCustomEvents();
- if (opt.afterUpdate) opt.afterUpdate(self.$container, self.paper, self.areas, self.plots, self.options, self.links);
- },
- /*
- * Set plot coordinates
- * @param plot object plot element
- */
- setPlotCoords: function(plot) {
- var self = this;
- if (plot.options.x !== undefined && plot.options.y !== undefined) {
- plot.coords = {
- x: plot.options.x,
- y: plot.options.y
- };
- } else if (plot.options.plotsOn !== undefined && self.areas[plot.options.plotsOn] !== undefined) {
- var areaBBox = self.areas[plot.options.plotsOn].mapElem.getBBox();
- plot.coords = {
- x: areaBBox.cx,
- y: areaBBox.cy
- };
- } else {
- plot.coords = self.mapConf.getCoords(plot.options.latitude, plot.options.longitude);
- }
- },
- /*
- * Set plot size attributes according to its type
- * Note: for SVG, plot.mapElem needs to exists beforehand
- * @param plot object plot element
- */
- setPlotAttributes: function(plot) {
- if (plot.options.type === "square") {
- plot.options.attrs.width = plot.options.size;
- plot.options.attrs.height = plot.options.size;
- plot.options.attrs.x = plot.coords.x - (plot.options.size / 2);
- plot.options.attrs.y = plot.coords.y - (plot.options.size / 2);
- } else if (plot.options.type === "image") {
- plot.options.attrs.src = plot.options.url;
- plot.options.attrs.width = plot.options.width;
- plot.options.attrs.height = plot.options.height;
- plot.options.attrs.x = plot.coords.x - (plot.options.width / 2);
- plot.options.attrs.y = plot.coords.y - (plot.options.height / 2);
- } else if (plot.options.type === "svg") {
- plot.options.attrs.path = plot.options.path;
- // Init transform string
- if (plot.options.attrs.transform === undefined) {
- plot.options.attrs.transform = "";
- }
- // Retrieve original boundary box if not defined
- if (plot.mapElem.originalBBox === undefined) {
- plot.mapElem.originalBBox = plot.mapElem.getBBox();
- }
- // The base transform will resize the SVG path to the one specified by width/height
- // and also move the path to the actual coordinates
- plot.mapElem.baseTransform = "m" + (plot.options.width / plot.mapElem.originalBBox.width) + ",0,0," +
- (plot.options.height / plot.mapElem.originalBBox.height) + "," +
- (plot.coords.x - plot.options.width / 2) + "," +
- (plot.coords.y - plot.options.height / 2);
- plot.options.attrs.transform = plot.mapElem.baseTransform + plot.options.attrs.transform;
- } else { // Default : circle
- plot.options.attrs.x = plot.coords.x;
- plot.options.attrs.y = plot.coords.y;
- plot.options.attrs.r = plot.options.size / 2;
- }
- },
- /*
- * Draw all links between plots on the paper
- */
- drawLinksCollection: function (linksCollection) {
- var self = this;
- var p1 = {};
- var p2 = {};
- var coordsP1 = {};
- var coordsP2 = {};
- var links = {};
- $.each(linksCollection, function (id) {
- var elemOptions = self.getElemOptions(self.options.map.defaultLink, linksCollection[id], {});
- if (typeof linksCollection[id].between[0] === 'string') {
- p1 = self.options.plots[linksCollection[id].between[0]];
- } else {
- p1 = linksCollection[id].between[0];
- }
- if (typeof linksCollection[id].between[1] === 'string') {
- p2 = self.options.plots[linksCollection[id].between[1]];
- } else {
- p2 = linksCollection[id].between[1];
- }
- if (p1.plotsOn !== undefined && self.areas[p1.plotsOn] !== undefined) {
- var p1BBox = self.areas[p1.plotsOn].mapElem.getBBox();
- coordsP1 = {
- x: p1BBox.cx,
- y: p1BBox.cy
- };
- }
- else if (p1.latitude !== undefined && p1.longitude !== undefined) {
- coordsP1 = self.mapConf.getCoords(p1.latitude, p1.longitude);
- } else {
- coordsP1.x = p1.x;
- coordsP1.y = p1.y;
- }
- if (p2.plotsOn !== undefined && self.areas[p2.plotsOn] !== undefined) {
- var p2BBox = self.areas[p2.plotsOn].mapElem.getBBox();
- coordsP2 = {
- x: p2BBox.cx,
- y: p2BBox.cy
- };
- }
- else if (p2.latitude !== undefined && p2.longitude !== undefined) {
- coordsP2 = self.mapConf.getCoords(p2.latitude, p2.longitude);
- } else {
- coordsP2.x = p2.x;
- coordsP2.y = p2.y;
- }
- links[id] = self.drawLink(id, coordsP1.x, coordsP1.y, coordsP2.x, coordsP2.y, elemOptions);
- });
- return links;
- },
- /*
- * Draw a curved link between two couples of coordinates a(xa,ya) and b(xb, yb) on the paper
- */
- drawLink: function (id, xa, ya, xb, yb, elemOptions) {
- var self = this;
- var link = {
- options: elemOptions
- };
- // Compute the "curveto" SVG point, d(x,y)
- // c(xc, yc) is the center of (xa,ya) and (xb, yb)
- var xc = (xa + xb) / 2;
- var yc = (ya + yb) / 2;
- // Equation for (cd) : y = acd * x + bcd (d is the cure point)
- var acd = -1 / ((yb - ya) / (xb - xa));
- var bcd = yc - acd * xc;
- // dist(c,d) = dist(a,b) (=abDist)
- var 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
- var a = 1 + acd * acd;
- var b = -2 * xc + 2 * acd * bcd - 2 * acd * yc;
- var c = xc * xc + bcd * bcd - bcd * yc - yc * bcd + yc * yc - ((elemOptions.factor * abDist) * (elemOptions.factor * abDist));
- var delta = b * b - 4 * a * c;
- var x = 0;
- var 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;
- }
- link.mapElem = self.paper.path("m " + xa + "," + ya + " C " + x + "," + y + " " + xb + "," + yb + " " + xb + "," + yb + "");
- self.initElem(id, 'link', link);
- return link;
- },
- /*
- * Check wether newAttrs object bring modifications to originalAttrs object
- */
- isAttrsChanged: function(originalAttrs, newAttrs) {
- for (var key in newAttrs) {
- if (newAttrs.hasOwnProperty(key) && typeof originalAttrs[key] === 'undefined' || newAttrs[key] !== originalAttrs[key]) {
- return true;
- }
- }
- return false;
- },
- /*
- * Update the element "elem" on the map with the new options
- */
- updateElem: function (elem, animDuration) {
- var self = this;
- var mapElemBBox;
- var plotOffsetX;
- var plotOffsetY;
- if (elem.options.toFront === true) {
- elem.mapElem.toFront();
- }
- // Set the cursor attribute related to the HTML link
- if (elem.options.href !== undefined) {
- elem.options.attrs.cursor = "pointer";
- if (elem.options.text) elem.options.text.attrs.cursor = "pointer";
- } else {
- // No HTML links, check if a cursor was defined to pointer
- if (elem.mapElem.attrs.cursor === 'pointer') {
- elem.options.attrs.cursor = "auto";
- if (elem.options.text) elem.options.text.attrs.cursor = "auto";
- }
- }
- // Update the label
- if (elem.textElem) {
- // Update text attr
- elem.options.text.attrs.text = elem.options.text.content;
- // Get mapElem size, and apply an offset to handle future width/height change
- mapElemBBox = elem.mapElem.getBBox();
- if (elem.options.size || (elem.options.width && elem.options.height)) {
- if (elem.options.type === "image" || elem.options.type === "svg") {
- plotOffsetX = (elem.options.width - mapElemBBox.width) / 2;
- plotOffsetY = (elem.options.height - mapElemBBox.height) / 2;
- } else {
- plotOffsetX = (elem.options.size - mapElemBBox.width) / 2;
- plotOffsetY = (elem.options.size - mapElemBBox.height) / 2;
- }
- mapElemBBox.x -= plotOffsetX;
- mapElemBBox.x2 += plotOffsetX;
- mapElemBBox.y -= plotOffsetY;
- mapElemBBox.y2 += plotOffsetY;
- }
- // Update position attr
- var textPosition = self.getTextPosition(mapElemBBox, elem.options.text.position, elem.options.text.margin);
- elem.options.text.attrs.x = textPosition.x;
- elem.options.text.attrs.y = textPosition.y;
- elem.options.text.attrs['text-anchor'] = textPosition.textAnchor;
- // Update text element attrs and attrsHover
- self.setHoverOptions(elem.textElem, elem.options.text.attrs, elem.options.text.attrsHover);
- if (self.isAttrsChanged(elem.textElem.attrs, elem.options.text.attrs)) {
- self.animate(elem.textElem, elem.options.text.attrs, animDuration);
- }
- }
- // Update elements attrs and attrsHover
- self.setHoverOptions(elem.mapElem, elem.options.attrs, elem.options.attrsHover);
- if (self.isAttrsChanged(elem.mapElem.attrs, elem.options.attrs)) {
- self.animate(elem.mapElem, elem.options.attrs, animDuration);
- }
- // Update the cssClass
- if (elem.options.cssClass !== undefined) {
- $(elem.mapElem.node).removeClass().addClass(elem.options.cssClass);
- }
- },
- /*
- * Draw the plot
- */
- drawPlot: function (id) {
- var self = this;
- var plot = {};
- // Get plot options and store it
- plot.options = self.getElemOptions(
- self.options.map.defaultPlot,
- (self.options.plots[id] ? self.options.plots[id] : {}),
- self.options.legend.plot
- );
- // Set plot coords
- self.setPlotCoords(plot);
- // Draw SVG before setPlotAttributes()
- if (plot.options.type === "svg") {
- plot.mapElem = self.paper.path(plot.options.path);
- }
- // Set plot size attrs
- self.setPlotAttributes(plot);
- // Draw other types of plots
- if (plot.options.type === "square") {
- plot.mapElem = self.paper.rect(
- plot.options.attrs.x,
- plot.options.attrs.y,
- plot.options.attrs.width,
- plot.options.attrs.height
- );
- } else if (plot.options.type === "image") {
- plot.mapElem = self.paper.image(
- plot.options.attrs.src,
- plot.options.attrs.x,
- plot.options.attrs.y,
- plot.options.attrs.width,
- plot.options.attrs.height
- );
- } else if (plot.options.type === "svg") {
- // Nothing to do
- } else {
- // Default = circle
- plot.mapElem = self.paper.circle(
- plot.options.attrs.x,
- plot.options.attrs.y,
- plot.options.attrs.r
- );
- }
- self.initElem(id, 'plot', plot);
- return plot;
- },
- /*
- * Set user defined handlers for events on areas and plots
- * @param id the id of the element
- * @param type the type of the element (area, plot, link)
- * @param elem the element object {mapElem, textElem, options, ...}
- */
- setEventHandlers: function (id, type, elem) {
- var self = this;
- $.each(elem.options.eventHandlers, function (event) {
- if (self.customEventHandlers[event] === undefined) self.customEventHandlers[event] = {};
- if (self.customEventHandlers[event][type] === undefined) self.customEventHandlers[event][type] = {};
- self.customEventHandlers[event][type][id] = elem;
- });
- },
- /*
- * Draw a legend for areas and / or plots
- * @param legendOptions options for the legend to draw
- * @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
- */
- drawLegend: function (legendOptions, legendType, elems, scale, legendIndex) {
- var self = this;
- var $legend = {};
- var legendPaper = {};
- var width = 0;
- var height = 0;
- var title = null;
- var titleBBox = null;
- var legendElems = {};
- var i = 0;
- var x = 0;
- var y = 0;
- var yCenter = 0;
- var sliceOptions = [];
- $legend = $("." + legendOptions.cssClass, self.$container);
- // Save content for later
- var initialHTMLContent = $legend.html();
- $legend.empty();
- legendPaper = new Raphael($legend.get(0));
- // Set some data to object
- $(legendPaper.canvas).attr({"data-legend-type": legendType, "data-legend-id": legendIndex});
- height = width = 0;
- // Set the title of the legend
- if (legendOptions.title && legendOptions.title !== "") {
- title = legendPaper.text(legendOptions.marginLeftTitle, 0, legendOptions.title).attr(legendOptions.titleAttrs);
- titleBBox = title.getBBox();
- title.attr({y: 0.5 * titleBBox.height});
- width = legendOptions.marginLeftTitle + titleBBox.width;
- height += legendOptions.marginBottomTitle + titleBBox.height;
- }
- // Calculate attrs (and width, height and r (radius)) for legend elements, and yCenter for horizontal legends
- for (i = 0; i < legendOptions.slices.length; ++i) {
- var yCenterCurrent = 0;
- sliceOptions[i] = $.extend(true, {}, (legendType === "plot") ? self.options.map.defaultPlot : self.options.map.defaultArea, legendOptions.slices[i]);
- if (legendOptions.slices[i].legendSpecificAttrs === undefined) {
- legendOptions.slices[i].legendSpecificAttrs = {};
- }
- $.extend(true, sliceOptions[i].attrs, legendOptions.slices[i].legendSpecificAttrs);
- if (legendType === "area") {
- if (sliceOptions[i].attrs.width === undefined)
- sliceOptions[i].attrs.width = 30;
- if (sliceOptions[i].attrs.height === undefined)
- sliceOptions[i].attrs.height = 20;
- } else if (sliceOptions[i].type === "square") {
- if (sliceOptions[i].attrs.width === undefined)
- sliceOptions[i].attrs.width = sliceOptions[i].size;
- if (sliceOptions[i].attrs.height === undefined)
- sliceOptions[i].attrs.height = sliceOptions[i].size;
- } else if (sliceOptions[i].type === "image" || sliceOptions[i].type === "svg") {
- if (sliceOptions[i].attrs.width === undefined)
- sliceOptions[i].attrs.width = sliceOptions[i].width;
- if (sliceOptions[i].attrs.height === undefined)
- sliceOptions[i].attrs.height = sliceOptions[i].height;
- } else {
- if (sliceOptions[i].attrs.r === undefined)
- sliceOptions[i].attrs.r = sliceOptions[i].size / 2;
- }
- // Compute yCenter for this legend slice
- yCenterCurrent = legendOptions.marginBottomTitle;
- // Add title height if it exists
- if (title) {
- yCenterCurrent += titleBBox.height;
- }
- if (legendType === "plot" && (sliceOptions[i].type === undefined || sliceOptions[i].type === "circle")) {
- yCenterCurrent += scale * sliceOptions[i].attrs.r;
- } else {
- yCenterCurrent += scale * sliceOptions[i].attrs.height / 2;
- }
- // Update yCenter if current larger
- yCenter = Math.max(yCenter, yCenterCurrent);
- }
- if (legendOptions.mode === "horizontal") {
- width = legendOptions.marginLeft;
- }
- // Draw legend elements (circle, square or image in vertical or horizontal mode)
- for (i = 0; i < sliceOptions.length; ++i) {
- var legendElem = {};
- var legendElemBBox = {};
- var legendLabel = {};
- if (sliceOptions[i].display === undefined || sliceOptions[i].display === true) {
- if (legendType === "area") {
- if (legendOptions.mode === "horizontal") {
- x = width + legendOptions.marginLeft;
- y = yCenter - (0.5 * scale * sliceOptions[i].attrs.height);
- } else {
- x = legendOptions.marginLeft;
- y = height;
- }
- legendElem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height));
- } else if (sliceOptions[i].type === "square") {
- if (legendOptions.mode === "horizontal") {
- x = width + legendOptions.marginLeft;
- y = yCenter - (0.5 * scale * sliceOptions[i].attrs.height);
- } else {
- x = legendOptions.marginLeft;
- y = height;
- }
- legendElem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height));
- } else if (sliceOptions[i].type === "image" || sliceOptions[i].type === "svg") {
- if (legendOptions.mode === "horizontal") {
- x = width + legendOptions.marginLeft;
- y = yCenter - (0.5 * scale * sliceOptions[i].attrs.height);
- } else {
- x = legendOptions.marginLeft;
- y = height;
- }
- if (sliceOptions[i].type === "image") {
- legendElem = legendPaper.image(
- sliceOptions[i].url, x, y, scale * sliceOptions[i].attrs.width, scale * sliceOptions[i].attrs.height);
- } else {
- legendElem = legendPaper.path(sliceOptions[i].path);
- if (sliceOptions[i].attrs.transform === undefined) {
- sliceOptions[i].attrs.transform = "";
- }
- legendElemBBox = legendElem.getBBox();
- sliceOptions[i].attrs.transform = "m" + ((scale * sliceOptions[i].width) / legendElemBBox.width) + ",0,0," + ((scale * sliceOptions[i].height) / legendElemBBox.height) + "," + x + "," + y + sliceOptions[i].attrs.transform;
- }
- } else {
- if (legendOptions.mode === "horizontal") {
- x = width + legendOptions.marginLeft + scale * (sliceOptions[i].attrs.r);
- y = yCenter;
- } else {
- x = legendOptions.marginLeft + scale * (sliceOptions[i].attrs.r);
- y = height + scale * (sliceOptions[i].attrs.r);
- }
- legendElem = legendPaper.circle(x, y, scale * (sliceOptions[i].attrs.r));
- }
- // Set attrs to the element drawn above
- delete sliceOptions[i].attrs.width;
- delete sliceOptions[i].attrs.height;
- delete sliceOptions[i].attrs.r;
- legendElem.attr(sliceOptions[i].attrs);
- legendElemBBox = legendElem.getBBox();
- // Draw the label associated with the element
- if (legendOptions.mode === "horizontal") {
- x = width + legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel;
- y = yCenter;
- } else {
- x = legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel;
- y = height + (legendElemBBox.height / 2);
- }
- legendLabel = legendPaper.text(x, y, sliceOptions[i].label).attr(legendOptions.labelAttrs);
- // Update the width and height for the paper
- if (legendOptions.mode === "horizontal") {
- var currentHeight = legendOptions.marginBottom + legendElemBBox.height;
- width += legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel + legendLabel.getBBox().width;
- if (sliceOptions[i].type !== "image" && legendType !== "area") {
- currentHeight += legendOptions.marginBottomTitle;
- }
- // Add title height if it exists
- if (title) {
- currentHeight += titleBBox.height;
- }
- height = Math.max(height, currentHeight);
- } else {
- width = Math.max(width, legendOptions.marginLeft + legendElemBBox.width + legendOptions.marginLeftLabel + legendLabel.getBBox().width);
- height += legendOptions.marginBottom + legendElemBBox.height;
- }
- // Set some data to elements
- $(legendElem.node).attr({
- "data-legend-id": legendIndex,
- "data-legend-type": legendType,
- "data-type": "legend-elem",
- "data-id": i,
- "data-hidden": 0
- });
- $(legendLabel.node).attr({
- "data-legend-id": legendIndex,
- "data-legend-type": legendType,
- "data-type": "legend-label",
- "data-id": i,
- "data-hidden": 0
- });
- // Set array content
- // We use similar names like map/plots/links
- legendElems[i] = {
- mapElem: legendElem,
- textElem: legendLabel
- };
- // 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
- legendLabel.attr({cursor: "pointer"});
- legendElem.attr({cursor: "pointer"});
- self.setHoverOptions(legendElem, sliceOptions[i].attrs, sliceOptions[i].attrs);
- self.setHoverOptions(legendLabel, legendOptions.labelAttrs, legendOptions.labelAttrsHover);
- if (sliceOptions[i].clicked !== undefined && sliceOptions[i].clicked === true) {
- self.handleClickOnLegendElem(legendElems[i], i, legendIndex, legendType, {hideOtherElems: false});
- }
- }
- }
- }
- // 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;
- legendPaper.setSize(width, height);
- return {
- container: $legend,
- initialHTMLContent: initialHTMLContent,
- elems: legendElems
- };
- },
- /*
- * Allow to hide elements of the map when the user clicks on a related legend item
- * @param elem legend element
- * @param id legend element ID
- * @param legendIndex corresponding legend index
- * @param legendType corresponding legend type (area or plot)
- * @param opts object additionnal options
- * hideOtherElems boolean, if other elems shall be hidden
- * animDuration duration of animation
- */
- handleClickOnLegendElem: function(elem, id, legendIndex, legendType, opts) {
- var self = this;
- var legendOptions;
- opts = opts || {};
- if (!$.isArray(self.options.legend[legendType])) {
- legendOptions = self.options.legend[legendType];
- } else {
- legendOptions = self.options.legend[legendType][legendIndex];
- }
- var legendElem = elem.mapElem;
- var legendLabel = elem.textElem;
- var $legendElem = $(legendElem.node);
- var $legendLabel = $(legendLabel.node);
- var sliceOptions = legendOptions.slices[id];
- var mapElems = legendType === 'area' ? self.areas : self.plots;
- // Check animDuration: if not set, this is a regular click, use the value specified in options
- var animDuration = opts.animDuration !== undefined ? opts.animDuration : legendOptions.hideElemsOnClick.animDuration ;
- var hidden = $legendElem.attr('data-hidden');
- var hiddenNewAttr = (hidden === '0') ? {"data-hidden": '1'} : {"data-hidden": '0'};
- if (hidden === '0') {
- self.animate(legendLabel, {"opacity": 0.5}, animDuration);
- } else {
- self.animate(legendLabel, {"opacity": 1}, animDuration);
- }
- $.each(mapElems, function (y) {
- var elemValue;
- // Retreive stored data of element
- // 'hidden-by' contains the list of legendIndex that is hiding this element
- var hiddenBy = mapElems[y].mapElem.data('hidden-by');
- // Set to empty object if undefined
- if (hiddenBy === undefined) hiddenBy = {};
- if ($.isArray(mapElems[y].options.value)) {
- elemValue = mapElems[y].options.value[legendIndex];
- } else {
- elemValue = mapElems[y].options.value;
- }
- // Hide elements whose value matches with the slice of the clicked legend item
- if (self.getLegendSlice(elemValue, legendOptions) === sliceOptions) {
- if (hidden === '0') { // we want to hide this element
- hiddenBy[legendIndex] = true; // add legendIndex to the data object for later use
- self.setElementOpacity(mapElems[y], legendOptions.hideElemsOnClick.opacity, animDuration);
- } else { // We want to show this element
- delete hiddenBy[legendIndex]; // Remove this legendIndex from object
- // Check if another legendIndex is defined
- // We will show this element only if no legend is no longer hiding it
- if ($.isEmptyObject(hiddenBy)) {
- self.setElementOpacity(
- mapElems[y],
- mapElems[y].mapElem.originalAttrs.opacity !== undefined ? mapElems[y].mapElem.originalAttrs.opacity : 1,
- animDuration
- );
- }
- }
- // Update elem data with new values
- mapElems[y].mapElem.data('hidden-by', hiddenBy);
- }
- });
- $legendElem.attr(hiddenNewAttr);
- $legendLabel.attr(hiddenNewAttr);
- if ((opts.hideOtherElems === undefined || opts.hideOtherElems === true) && legendOptions.exclusive === true ) {
- $("[data-type='legend-elem'][data-hidden=0]", self.$container).each(function () {
- var $elem = $(this);
- if ($elem.attr('data-id') !== id) {
- $elem.trigger("click", {hideOtherElems: false});
- }
- });
- }
- },
- /*
- * Create all legends for a specified type (area or plot)
- * @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
- */
- createLegends: function (legendType, elems, scale) {
- var self = this;
- var legendsOptions = self.options.legend[legendType];
- if (!$.isArray(self.options.legend[legendType])) {
- legendsOptions = [self.options.legend[legendType]];
- }
- self.legends[legendType] = {};
- for (var j = 0; j < legendsOptions.length; ++j) {
- if (legendsOptions[j].display === true && $.isArray(legendsOptions[j].slices) && legendsOptions[j].slices.length > 0 &&
- legendsOptions[j].cssClass !== "" && $("." + legendsOptions[j].cssClass, self.$container).length !== 0
- ) {
- self.legends[legendType][j] = self.drawLegend(legendsOptions[j], legendType, elems, scale, j);
- }
- }
- },
- /*
- * 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
- */
- 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 behaviour when mouse enters element ("mouseover" event)
- * It may be an area, a plot, a link or a legend element
- * @param elem the map element
- */
- elemEnter: function (elem) {
- var self = this;
- if (elem === undefined) return;
- /* Handle mapElem Hover attributes */
- if (elem.mapElem !== undefined) {
- self.animate(elem.mapElem, elem.mapElem.attrsHover, elem.mapElem.attrsHover.animDuration);
- }
- /* Handle textElem Hover attributes */
- if (elem.textElem !== undefined) {
- self.animate(elem.textElem, elem.textElem.attrsHover, elem.textElem.attrsHover.animDuration);
- }
- /* Handle tooltip init */
- if (elem.options && elem.options.tooltip !== undefined) {
- var content = '';
- // Reset classes
- self.$tooltip.removeClass().addClass(self.options.map.tooltip.cssClass);
- // Get content
- if (elem.options.tooltip.content !== undefined) {
- // if tooltip.content is function, call it. Otherwise, assign it directly.
- if (typeof elem.options.tooltip.content === "function") content = elem.options.tooltip.content(elem.mapElem);
- else content = elem.options.tooltip.content;
- }
- if (elem.options.tooltip.cssClass !== undefined) {
- self.$tooltip.addClass(elem.options.tooltip.cssClass);
- }
- self.$tooltip.html(content).css("display", "block");
- }
- // workaround for older version of Raphael
- if (elem.mapElem !== undefined || elem.textElem !== undefined) {
- if (self.paper.safari) self.paper.safari();
- }
- },
- /*
- * Set the behaviour when mouse moves in element ("mousemove" event)
- * @param elem the map element
- */
- elemHover: function (elem, event) {
- var self = this;
- if (elem === undefined) return;
- /* Handle tooltip position update */
- if (elem.options.tooltip !== undefined) {
- var mouseX = event.pageX;
- var mouseY = event.pageY;
- var offsetLeft = 10;
- var offsetTop = 20;
- if (typeof elem.options.tooltip.offset === "object") {
- if (typeof elem.options.tooltip.offset.left !== "undefined") {
- offsetLeft = elem.options.tooltip.offset.left;
- }
- if (typeof elem.options.tooltip.offset.top !== "undefined") {
- offsetTop = elem.options.tooltip.offset.top;
- }
- }
- var tooltipPosition = {
- "left": Math.min(self.$map.width() - self.$tooltip.outerWidth() - 5,
- mouseX - self.$map.offset().left + offsetLeft),
- "top": Math.min(self.$map.height() - self.$tooltip.outerHeight() - 5,
- mouseY - self.$map.offset().top + offsetTop)
- };
- if (typeof elem.options.tooltip.overflow === "object") {
- if (elem.options.tooltip.overflow.right === true) {
- tooltipPosition.left = mouseX - self.$map.offset().left + 10;
- }
- if (elem.options.tooltip.overflow.bottom === true) {
- tooltipPosition.top = mouseY - self.$map.offset().top + 20;
- }
- }
- self.$tooltip.css(tooltipPosition);
- }
- },
- /*
- * Set the behaviour when mouse leaves element ("mouseout" event)
- * It may be an area, a plot, a link or a legend element
- * @param elem the map element
- */
- elemOut: function (elem) {
- var self = this;
- if (elem === undefined) return;
- /* reset mapElem attributes */
- if (elem.mapElem !== undefined) {
- self.animate(elem.mapElem, elem.mapElem.originalAttrs, elem.mapElem.attrsHover.animDuration);
- }
- /* reset textElem attributes */
- if (elem.textElem !== undefined) {
- self.animate(elem.textElem, elem.textElem.originalAttrs, elem.textElem.attrsHover.animDuration);
- }
- /* reset tooltip */
- if (elem.options && elem.options.tooltip !== undefined) {
- self.$tooltip.css({
- 'display': 'none',
- 'top': -1000,
- 'left': -1000
- });
- }
- // workaround for older version of Raphael
- if (elem.mapElem !== undefined || elem.textElem !== undefined) {
- if (self.paper.safari) self.paper.safari();
- }
- },
- /*
- * Set the behaviour when mouse clicks element ("click" event)
- * It may be an area, a plot or a link (but not a legend element which has its own function)
- * @param elem the map element
- */
- elemClick: function (elem) {
- var self = this;
- if (elem === undefined) return;
- /* Handle click when href defined */
- if (!self.panning && elem.options.href !== undefined) {
- window.open(elem.options.href, elem.options.target);
- }
- },
- /*
- * Get element options by merging default options, element options and legend options
- * @param defaultOptions
- * @param elemOptions
- * @param legendOptions
- */
- getElemOptions: function (defaultOptions, elemOptions, legendOptions) {
- var self = this;
- var options = $.extend(true, {}, defaultOptions, elemOptions);
- if (options.value !== undefined) {
- if ($.isArray(legendOptions)) {
- for (var i = 0; i < legendOptions.length; ++i) {
- options = $.extend(true, {}, options, self.getLegendSlice(options.value[i], legendOptions[i]));
- }
- } else {
- options = $.extend(true, {}, options, self.getLegendSlice(options.value, legendOptions));
- }
- }
- return options;
- },
- /*
- * Get the coordinates of the text relative to a bbox and a position
- * @param bbox the boundary box of the element
- * @param textPosition the wanted text position (inner, right, left, top or bottom)
- * @param margin number or object {x: val, y:val} margin between the bbox and the text
- */
- getTextPosition: function (bbox, textPosition, margin) {
- var textX = 0;
- var textY = 0;
- var textAnchor = "";
- if (typeof margin === "number") {
- if (textPosition === "bottom" || textPosition === "top") {
- margin = {x: 0, y: margin};
- } else if (textPosition === "right" || textPosition === "left") {
- margin = {x: margin, y: 0};
- } else {
- margin = {x: 0, y: 0};
- }
- }
- switch (textPosition) {
- case "bottom" :
- textX = ((bbox.x + bbox.x2) / 2) + margin.x;
- textY = bbox.y2 + margin.y;
- textAnchor = "middle";
- break;
- case "top" :
- textX = ((bbox.x + bbox.x2) / 2) + margin.x;
- textY = bbox.y - margin.y;
- textAnchor = "middle";
- break;
- case "left" :
- textX = bbox.x - margin.x;
- textY = ((bbox.y + bbox.y2) / 2) + margin.y;
- textAnchor = "end";
- break;
- case "right" :
- textX = bbox.x2 + margin.x;
- textY = ((bbox.y + bbox.y2) / 2) + margin.y;
- textAnchor = "start";
- break;
- default : // "inner" position
- textX = ((bbox.x + bbox.x2) / 2) + margin.x;
- textY = ((bbox.y + bbox.y2) / 2) + margin.y;
- textAnchor = "middle";
- }
- return {"x": textX, "y": textY, "textAnchor": textAnchor};
- },
- /*
- * 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
- */
- getLegendSlice: function (value, legend) {
- for (var i = 0; i < legend.slices.length; ++i) {
- if ((legend.slices[i].sliceValue !== undefined && value === legend.slices[i].sliceValue) ||
- ((legend.slices[i].sliceValue === undefined) &&
- (legend.slices[i].min === undefined || value >= legend.slices[i].min) &&
- (legend.slices[i].max === undefined || value <= legend.slices[i].max))
- ) {
- return legend.slices[i];
- }
- }
- return {};
- },
- /*
- * Animated view box changes
- * As from http://code.voidblossom.com/animating-viewbox-easing-formulas/,
- * (from https://github.com/theshaun works on mapael)
- * @param x coordinate of the point to focus on
- * @param y coordinate of the point to focus on
- * @param w map defined width
- * @param h map defined height
- * @param duration defined length of time for animation
- * @param easingFunction defined Raphael supported easing_formula to use
- */
- animateViewBox: function (targetX, targetY, targetW, targetH, duration, easingFunction) {
- var self = this;
- var cx = self.currentViewBox.x;
- var dx = targetX - cx;
- var cy = self.currentViewBox.y;
- var dy = targetY - cy;
- var cw = self.currentViewBox.w;
- var dw = targetW - cw;
- var ch = self.currentViewBox.h;
- var dh = targetH - ch;
- // Init current ViewBox target if undefined
- if (!self.zoomAnimCVBTarget) {
- self.zoomAnimCVBTarget = {
- x: targetX, y: targetY, w: targetW, h: targetH
- };
- }
- // Determine zoom direction by comparig current vs. target width
- var zoomDir = (cw > targetW) ? 'in' : 'out';
- var easingFormula = Raphael.easing_formulas[easingFunction || "linear"];
- // To avoid another frame when elapsed time approach end (2%)
- var durationWithMargin = duration - (duration * 2 / 100);
- // Save current zoomAnimStartTime before assigning a new one
- var oldZoomAnimStartTime = self.zoomAnimStartTime;
- self.zoomAnimStartTime = (new Date()).getTime();
- /* Actual function to animate the ViewBox
- * Uses requestAnimationFrame to schedule itself again until animation is over
- */
- var computeNextStep = function () {
- // Cancel any remaining animationFrame
- // It means this new step will take precedence over the old one scheduled
- // This is the case when the user is triggering the zoom fast (e.g. with a big mousewheel run)
- // This actually does nothing when performing a single zoom action
- self.cancelAnimationFrame(self.zoomAnimID);
- // Compute elapsed time
- var elapsed = (new Date()).getTime() - self.zoomAnimStartTime;
- // Check if animation should finish
- if (elapsed < durationWithMargin) {
- // Hold the future ViewBox values
- var x, y, w, h;
- // There are two ways to compute the next ViewBox size
- // 1. If the target ViewBox has changed between steps (=> ADAPTATION step)
- // 2. Or if the target ViewBox is the same (=> NORMAL step)
- //
- // A change of ViewBox target between steps means the user is triggering
- // the zoom fast (like a big scroll with its mousewheel)
- //
- // The new animation step with the new target will always take precedence over the
- // last one and start from 0 (we overwrite zoomAnimStartTime and cancel the scheduled frame)
- //
- // So if we don't detect the change of target and adapt our computation,
- // the user will see a delay at beginning the ratio will stays at 0 for some frames
- //
- // Hence when detecting the change of target, we animate from the previous target.
- //
- // The next step will then take the lead and continue from there, achieving a nicer
- // experience for user.
- // Change of target IF: an old animation start value exists AND the target has actually changed
- if (oldZoomAnimStartTime && self.zoomAnimCVBTarget && self.zoomAnimCVBTarget.w !== targetW) {
- // Compute the real time elapsed with the last step
- var realElapsed = (new Date()).getTime() - oldZoomAnimStartTime;
- // Compute then the actual ratio we're at
- var realRatio = easingFormula(realElapsed / duration);
- // Compute new ViewBox values
- // The difference with the normal function is regarding the delta value used
- // We don't take the current (dx, dy, dw, dh) values yet because they are related to the new target
- // But we take the old target
- x = cx + (self.zoomAnimCVBTarget.x - cx) * realRatio;
- y = cy + (self.zoomAnimCVBTarget.y - cy) * realRatio;
- w = cw + (self.zoomAnimCVBTarget.w - cw) * realRatio;
- h = ch + (self.zoomAnimCVBTarget.h - ch) * realRatio;
- // Update cw, cy, cw and ch so the next step take animation from here
- cx = x;
- dx = targetX - cx;
- cy = y;
- dy = targetY - cy;
- cw = w;
- dw = targetW - cw;
- ch = h;
- dh = targetH - ch;
- // Update the current ViewBox target
- self.zoomAnimCVBTarget = {
- x: targetX, y: targetY, w: targetW, h: targetH
- };
- } else {
- // This is the classical approach when nothing come interrupting the zoom
- // Compute ratio according to elasped time and easing formula
- var ratio = easingFormula(elapsed / duration);
- // From the current value, we add a delta with a ratio that will leads us to the target
- x = cx + dx * ratio;
- y = cy + dy * ratio;
- w = cw + dw * ratio;
- h = ch + dh * ratio;
- }
- // Some checks before applying the new viewBox
- if (zoomDir === 'in' && (w > self.currentViewBox.w || w < targetW)) {
- // Zooming IN and the new ViewBox seems larger than the current value, or smaller than target value
- // We do NOT set the ViewBox with this value
- // Otherwise, the user would see the camera going back and forth
- } else if (zoomDir === 'out' && (w < self.currentViewBox.w || w > targetW)) {
- // Zooming OUT and the new ViewBox seems smaller than the current value, or larger than target value
- // We do NOT set the ViewBox with this value
- // Otherwise, the user would see the camera going back and forth
- } else {
- // New values look good, applying
- self.setViewBox(x, y, w, h);
- }
- // Schedule the next step
- self.zoomAnimID = self.requestAnimationFrame(computeNextStep);
- } else {
- /* Zoom animation done ! */
- // Perform some cleaning
- self.zoomAnimStartTime = null;
- self.zoomAnimCVBTarget = null;
- // Make sure the ViewBox hits the target!
- if (self.currentViewBox.w !== targetW) {
- self.setViewBox(targetX, targetY, targetW, targetH);
- }
- // Finally trigger afterZoom event
- self.$map.trigger("afterZoom", {
- x1: targetX, y1: targetY,
- x2: (targetX + targetW), y2: (targetY + targetH)
- });
- }
- };
- // Invoke the first step directly
- computeNextStep();
- },
- /*
- * requestAnimationFrame/cancelAnimationFrame polyfill
- * Based on https://gist.github.com/jlmakes/47eba84c54bc306186ac1ab2ffd336d4
- * and also https://gist.github.com/paulirish/1579671
- *
- * _requestAnimationFrameFn and _cancelAnimationFrameFn hold the current functions
- * But requestAnimationFrame and cancelAnimationFrame shall be called since
- * in order to be in window context
- */
- // The function to use for requestAnimationFrame
- requestAnimationFrame: function(callback) {
- return this._requestAnimationFrameFn.call(window, callback);
- },
- // The function to use for cancelAnimationFrame
- cancelAnimationFrame: function(id) {
- this._cancelAnimationFrameFn.call(window, id);
- },
- // The requestAnimationFrame polyfill'd function
- // Value set by self-invoking function, will be run only once
- _requestAnimationFrameFn: (function () {
- var polyfill = (function () {
- var clock = (new Date()).getTime();
- return function (callback) {
- var currentTime = (new Date()).getTime();
- // requestAnimationFrame strive to run @60FPS
- // (e.g. every 16 ms)
- if (currentTime - clock > 16) {
- clock = currentTime;
- callback(currentTime);
- } else {
- // Ask browser to schedule next callback when possible
- return setTimeout(function () {
- polyfill(callback);
- }, 0);
- }
- };
- })();
- return window.requestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.msRequestAnimationFrame ||
- window.oRequestAnimationFrame ||
- polyfill;
- })(),
- // The CancelAnimationFrame polyfill'd function
- // Value set by self-invoking function, will be run only once
- _cancelAnimationFrameFn: (function () {
- return window.cancelAnimationFrame ||
- window.webkitCancelAnimationFrame ||
- window.webkitCancelRequestAnimationFrame ||
- window.mozCancelAnimationFrame ||
- window.mozCancelRequestAnimationFrame ||
- window.msCancelAnimationFrame ||
- window.msCancelRequestAnimationFrame ||
- window.oCancelAnimationFrame ||
- window.oCancelRequestAnimationFrame ||
- clearTimeout;
- })(),
- /*
- * SetViewBox wrapper
- * Apply new viewbox values and keep track of them
- *
- * This avoid using the internal variable paper._viewBox which
- * may not be present in future version of Raphael
- */
- setViewBox: function(x, y, w, h) {
- var self = this;
- // Update current value
- self.currentViewBox.x = x;
- self.currentViewBox.y = y;
- self.currentViewBox.w = w;
- self.currentViewBox.h = h;
- // Perform set view box
- self.paper.setViewBox(x, y, w, h, false);
- },
- /*
- * Animate wrapper for Raphael element
- *
- * Perform an animation and ensure the non-animated attr are set.
- * This is needed for specific attributes like cursor who will not
- * be animated, and thus not set.
- *
- * If duration is set to 0 (or not set), no animation are performed
- * and attributes are directly set (and the callback directly called)
- */
- // List extracted from Raphael internal vars
- // Diff between Raphael.availableAttrs and Raphael._availableAnimAttrs
- _nonAnimatedAttrs: [
- "arrow-end", "arrow-start", "gradient",
- "class", "cursor", "text-anchor",
- "font", "font-family", "font-style", "font-weight", "letter-spacing",
- "src", "href", "target", "title",
- "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit"
- ],
- /*
- * @param element Raphael element
- * @param attrs Attributes object to animate
- * @param duration Animation duration in ms
- * @param callback Callback to eventually call after animation is done
- */
- animate: function(element, attrs, duration, callback) {
- var self = this;
- // Check element
- if (!element) return;
- if (duration > 0) {
- // Filter out non-animated attributes
- // Note: we don't need to delete from original attribute (they won't be set anyway)
- var attrsNonAnimated = {};
- for (var i=0 ; i < self._nonAnimatedAttrs.length ; i++) {
- var attrName = self._nonAnimatedAttrs[i];
- if (attrs[attrName] !== undefined) {
- attrsNonAnimated[attrName] = attrs[attrName];
- }
- }
- // Set non-animated attributes
- element.attr(attrsNonAnimated);
- // Start animation for all attributes
- element.animate(attrs, duration, 'linear', function() {
- if (callback) callback();
- });
- } else {
- // No animation: simply set all attributes...
- element.attr(attrs);
- // ... and call the callback if needed
- if (callback) callback();
- }
- },
- /*
- * Check for Raphael bug regarding drawing while beeing hidden (under display:none)
- * See https://github.com/neveldo/jQuery-Mapael/issues/135
- * @return true/false
- *
- * Wants to override this behavior? Use prototype overriding:
- * $.mapael.prototype.isRaphaelBBoxBugPresent = function() {return false;};
- */
- isRaphaelBBoxBugPresent: function() {
- var self = this;
- // Draw text, then get its boundaries
- var textElem = self.paper.text(-50, -50, "TEST");
- var textElemBBox = textElem.getBBox();
- // remove element
- textElem.remove();
- // If it has no height and width, then the paper is hidden
- return (textElemBBox.width === 0 && textElemBBox.height === 0);
- },
- // Default map options
- defaultOptions: {
- map: {
- cssClass: "map",
- tooltip: {
- cssClass: "mapTooltip"
- },
- 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",
- cssClass: "area"
- },
- 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",
- cssClass: "plot"
- },
- 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",
- cssClass: "link"
- },
- zoom: {
- enabled: false,
- minLevel: 0,
- maxLevel: 10,
- step: 0.25,
- mousewheel: true,
- touch: true,
- animDuration: 200,
- animEasing: "linear",
- buttons: {
- "reset": {
- cssClass: "zoomButton zoomReset",
- content: "•", // bullet sign
- title: "Reset zoom"
- },
- "in": {
- cssClass: "zoomButton zoomIn",
- content: "+",
- title: "Zoom in"
- },
- "out": {
- cssClass: "zoomButton zoomOut",
- content: "−", // minus sign
- title: "Zoom out"
- }
- }
- }
- },
- legend: {
- redrawOnResize: true,
- area: [],
- plot: []
- },
- areas: {},
- plots: {},
- links: {}
- },
- // Default legends option
- 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,
- animDuration: 300
- },
- 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,
- animDuration: 300
- },
- slices: [],
- mode: "vertical"
- }
- }
- };
- // Mapael version number
- // Accessible as $.mapael.version
- Mapael.version = version;
- // Extend jQuery with Mapael
- if ($[pluginName] === undefined) $[pluginName] = Mapael;
- // Add jQuery DOM function
- $.fn[pluginName] = function (options) {
- // Call Mapael on each element
- return this.each(function () {
- // Avoid leaking problem on multiple instanciation by removing an old mapael object on a container
- if ($.data(this, pluginName)) {
- $.data(this, pluginName).destroy();
- }
- // Create Mapael and save it as jQuery data
- // This allow external access to Mapael using $(".mapcontainer").data("mapael")
- $.data(this, pluginName, new Mapael(this, options));
- });
- };
- return Mapael;
- }));
|