1/** 2 * Creates map, draws paths, binds events. 3 * @constructor 4 * @param {Object} params Parameters to initialize map with. 5 * @param {String} params.map Name of the map in the format <code>territory_proj_lang</code> where <code>territory</code> is a unique code or name of the territory which the map represents (ISO 3166 standard is used where possible), <code>proj</code> is a name of projection used to generate representation of the map on the plane (projections are named according to the conventions of proj4 utility) and <code>lang</code> is a code of the language, used for the names of regions. 6 * @param {String} params.backgroundColor Background color of the map in CSS format. 7 * @param {Boolean} params.zoomOnScroll When set to true map could be zoomed using mouse scroll. Default value is <code>true</code>. 8 * @param {Number} params.zoomOnScrollSpeed Mouse scroll speed. Number from 1 to 10. Default value is <code>3</code>. 9 * @param {Boolean} params.panOnDrag When set to true, the map pans when being dragged. Default value is <code>true</code>. 10 * @param {Number} params.zoomMax Indicates the maximum zoom ratio which could be reached zooming the map. Default value is <code>8</code>. 11 * @param {Number} params.zoomMin Indicates the minimum zoom ratio which could be reached zooming the map. Default value is <code>1</code>. 12 * @param {Number} params.zoomStep Indicates the multiplier used to zoom map with +/- buttons. Default value is <code>1.6</code>. 13 * @param {Boolean} params.zoomAnimate Indicates whether or not to animate changing of map zoom with zoom buttons. 14 * @param {Boolean} params.regionsSelectable When set to true regions of the map could be selected. Default value is <code>false</code>. 15 * @param {Boolean} params.regionsSelectableOne Allow only one region to be selected at the moment. Default value is <code>false</code>. 16 * @param {Boolean} params.markersSelectable When set to true markers on the map could be selected. Default value is <code>false</code>. 17 * @param {Boolean} params.markersSelectableOne Allow only one marker to be selected at the moment. Default value is <code>false</code>. 18 * @param {Object} params.regionStyle Set the styles for the map's regions. Each region or marker has four states: <code>initial</code> (default state), <code>hover</code>
18 (when the mouse cursor is over the region or marker), <code>selected</code> (when region or marker is selected), <code>selectedHover</code> (when the mouse cursor is over the region or marker and it's selected simultaneously). Styles could be set for each of this states. Default value for that parameter is: 19<pre>{ 20 initial: { 21 fill: 'white', 22 "fill-opacity": 1, 23 stroke: 'none', 24 "stroke-width": 0, 25 "stroke-opacity": 1 26 }, 27 hover: { 28 "fill-opacity": 0.8, 29 cursor: 'pointer' 30 }, 31 selected: { 32 fill: 'yellow' 33 }, 34 selectedHover: { 35 } 36}</pre> 37* @param {Object} params.regionLabelStyle Set the styles for the regions' labels. Each region or marker has four states: <code>initial</code> (default state), <code>hover</code> (when the mouse cursor is over the region or marker), <code>selected</code> (when region or marker is selected), <code>selectedHover</code> (when the mouse cursor is over the region or marker and it's selected simultaneously). Styles could be set for each of this states. Default value for that parameter is: 38<pre>{ 39 initial: { 40 'font-family': 'Verdana', 41 'font-size': '12', 42 'font-weight': 'bold', 43 cursor: 'default', 44 fill: 'black' 45 }, 46 hover: { 47 cursor: 'pointer' 48 } 49}</pre> 50 * @param {Object} params.markerStyle Set the styles for the map's markers. Any parameter suitable for <code>regionStyle</code> could be used as well as numeric parameter <code>r</code> to set the marker's radius. Default value for that parameter is: 51<pre>{ 52 initial: { 53 fill: 'grey', 54 stroke: '#505050', 55 "fill-opacity": 1, 56 "stroke-width": 1, 57 "stroke-opacity": 1, 58 r: 5 59 }, 60 hover: { 61 stroke: 'black', 62 "stroke-width": 2, 63 cursor: 'pointer' 64 }, 65 selected: { 66 fill: 'blue' 67 }, 68 selectedHover: { 69 } 70}</pre> 71You can also use <code>image</code> style attribute for markers. By default marker images are centered with the target point on map. To supply a custom offset please use the following format: 72<pre>{ 73 url: 'image/url', 74 offset: [-10, 5] 75}</pre> 76 * @param {Object} params.markerLabelStyle Set the styles for the markers' labels. Default value for that parameter is: 77<pre>{ 78 initial: { 79 'font-family': 'Verdana', 80 'font-size': '12', 81 'font-weight': 'bold', 82 cursor: 'default', 83 fill: 'black' 84 }, 85 hover: { 86 cursor: 'pointer' 87 } 88}</pre> 89 * @param {Object|Array} params.markers Set of markers to add to the map during initialization. In case of array is provided, codes of markers will be set as string representations of array indexes. Each marker is represented by <code>latLng</code> (array of two numeric values), <code>name</code> (string which will be show on marker's tip) and any marker styles. 90 * @param {Object} params.series Object with two keys: <code>markers</code> and <code>regions</code>. Each of which is an array of series configs to be applied to the respective map elements. See <a href="jvm.DataSeries.html">DataSeries</a> description for a list of parameters available. 91 * @param {Object|String} params.focusOn This parameter sets the initial position and scale of the map viewport. See <code>setFocus</code> docuemntation for possible parameters. 92 * @param {Object} params.labels Defines parameters for rendering static labels. Object could contain two keys: <code>regions</code> and <code>markers</code>. Each key value defines configuration object with the following possible options: 93<ul> 94 <li><code>render {Function}</code> - defines method for converting region code or marker index to actual label value.</li> 95 <li><code>offsets {Object|Function}</code> - provides method or object which could be used to define label offset by region code or marker index.</li> 96</ul> 97<b>Plase note: static labels feature is not supported in Internet Explorer 8 and below.</b> 98 * @param {Array|Object|String} params.selectedRegions Set initially selected regions. 99 * @param {Array|Object|String} params.selectedMarkers Set initially selected markers. 100 * @param {Function} params.onRegionTipShow <code>(Event e, Object tip, String code)</code> Will be called right before the region tip is going to be shown. 101 * @param {Function} params.onRegionOver <code>(Event e, String code)</code> Will be called on region mouse over event. 102 * @param {Function} params.onRegionOut <code>(Event e, String code)</code> Will be called on region mouse out event. 103 * @param {Function} params.onRegionClick <code>(Event e, String code)</code> Will be called on region click event. 104 * @param {Function} params.onRegionSelected <code>(Event e, String code, Boolean isSelected, Array selectedRegions)</code> Will be called when region is (de)selected. <code>isSelected</code> parameter of the callback indicates whether region is selected or not. <code>selectedRegions</code>
104 contains codes of all currently selected regions. 105 * @param {Function} params.onMarkerTipShow <code>(Event e, Object tip, String code)</code> Will be called right before the marker tip is going to be shown. 106 * @param {Function} params.onMarkerOver <code>(Event e, String code)</code> Will be called on marker mouse over event. 107 * @param {Function} params.onMarkerOut <code>(Event e, String code)</code> Will be called on marker mouse out event. 108 * @param {Function} params.onMarkerClick <code>(Event e, String code)</code> Will be called on marker click event. 109 * @param {Function} params.onMarkerSelected <code>(Event e, String code, Boolean isSelected, Array selectedMarkers)</code> Will be called when marker is (de)selected. <code>isSelected</code> parameter of the callback indicates whether marker is selected or not. <code>selectedMarkers</code> contains codes of all currently selected markers. 110 * @param {Function} params.onViewportChange <code>(Event e, Number scale)</code> Triggered when the map's viewport is changed (map was panned or zoomed). 111 */ 112jvm.Map = function(params) { 113 var map = this, 114 e; 115 116 this.params = jvm.$.extend(true, {}, jvm.Map.defaultParams, params); 117 118 if (!jvm.Map.maps[this.params.map]) { 119 throw new Error('Attempt to use map which was not loaded: '+this.params.map); 120 } 121 122 this.mapData = jvm.Map.maps[this.params.map]; 123 this.markers = {}; 124 this.regions = {}; 125 this.regionsColors = {}; 126 this.regionsData = {}; 127 128 this.container = jvm.$('<div>').addClass('jvectormap-container'); 129 if (this.params.container) { 130 this.params.container.append( this.container ); 131 } 132 this.container.data('mapObject', this); 133 134 this.defaultWidth = this.mapData.width; 135 this.defaultHeight = this.mapData.height; 136 137 this.setBackgroundColor(this.params.backgroundColor); 138 139 this.onResize = function(){ 140 map.updateSize(); 141 } 142 jvm.$(window).resize(this.onResize); 143 144 for (e in jvm.Map.apiEvents) { 145 if (this.params[e]) { 146 this.container.bind(jvm.Map.apiEvents[e]+'.jvectormap', this.params[e]); 147 } 148 } 149 150 this.canvas = new jvm.VectorCanvas(this.container[0], this.width, this.height); 151 152 if (this.params.bindTouchEvents) { 153 if (('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch)) { 154 this.bindContainerTouchEvents(); 155 } else if (window.MSGesture) { 156 this.bindContainerPointerEvents(); 157 } 158 } 159 this.bindContainerEvents(); 160 this.bindElementEvents(); 161 this.createTip(); 162 if (this.params.zoomButtons) { 163 this.bindZoomButtons(); 164 } 165 166 this.createRegions(); 167 this.createMarkers(this.params.markers || {}); 168 169 this.updateSize(); 170 171 if (this.params.focusOn) { 172 if (typeof this.params.focusOn === 'string') { 173 this.params.focusOn = {region: this.params.focusOn}; 174 } else if (jvm.$.isArray(this.params.focusOn)) { 175 this.params.focusOn = {regions: this.params.focusOn}; 176 } 177 this.setFocus(this.params.focusOn); 178 } 179 180 if (this.params.selectedRegions) { 181 this.setSelectedRegions(this.params.selectedRegions); 182 } 183 if (this.params.selectedMarkers) { 184 this.setSelectedMarkers(this.params.selectedMarkers); 185 } 186 187 this.legendCntHorizontal = jvm.$('<div/>').addClass('jvectormap-legend-cnt jvectormap-legend-cnt-h'); 188 this.legendCntVertical = jvm.$('<div/>').addClass('jvectormap-legend-cnt jvectormap-legend-cnt-v'); 189 this.container.append(this.legendCntHorizontal); 190 this.container.append(this.legendCntVertical); 191 192 if (this.params.series) { 193 this.createSeries(); 194 } 195}; 196 197jvm.Map.prototype = { 198 transX: 0, 199 transY: 0, 200 scale: 1, 201 baseTransX: 0, 202 baseTransY: 0, 203 baseScale: 1, 204 205 width: 0, 206 height: 0, 207 208 /** 209 * Set background color of the map. 210 * @param {String}
210 backgroundColor Background color in CSS format. 211 */ 212 setBackgroundColor: function(backgroundColor) { 213 this.container.css('background-color', backgroundColor); 214 }, 215 216 resize: function() { 217 var curBaseScale = this.baseScale; 218 if (this.width / this.height > this.defaultWidth / this.defaultHeight) { 219 this.baseScale = this.height / this.defaultHeight; 220 this.baseTransX = Math.abs(this.width - this.defaultWidth * this.baseScale) / (2 * this.baseScale); 221 } else { 222 this.baseScale = this.width / this.defaultWidth; 223 this.baseTransY = Math.abs(this.height - this.defaultHeight * this.baseScale) / (2 * this.baseScale); 224 } 225 this.scale *= this.baseScale / curBaseScale; 226 this.transX *= this.baseScale / curBaseScale; 227 this.transY *= this.baseScale / curBaseScale; 228 }, 229 230 /** 231 * Synchronize the size of the map with the size of the container. Suitable in situations where the size of the container is changed programmatically or container is shown after it became visible. 232 */ 233 updateSize: function(){ 234 this.width = this.container.width(); 235 this.height = this.container.height(); 236 this.resize(); 237 this.canvas.setSize(this.width, this.height); 238 this.applyTransform(); 239 }, 240 241 /** 242 * Reset all the series and show the map with the initial zoom. 243 */ 244 reset: function() { 245 var key, 246 i; 247 248 for (key in this.series) { 249 for (i = 0; i < this.series[key].length; i++) { 250 this.series[key][i].clear(); 251 } 252 } 253 this.scale = this.baseScale; 254 this.transX = this.baseTransX; 255 this.transY = this.baseTransY; 256 this.applyTransform(); 257 }, 258 259 applyTransform: function() { 260 var maxTransX, 261 maxTransY, 262 minTransX, 263 minTransY; 264 265 if (this.defaultWidth * this.scale <= this.width) { 266 maxTransX = (this.width - this.defaultWidth * this.scale) / (2 * this.scale); 267 minTransX = (this.width - this.defaultWidth * this.scale) / (2 * this.scale); 268 } else { 269 maxTransX = 0; 270 minTransX = (this.width - this.defaultWidth * this.scale) / this.scale; 271 } 272 273 if (this.defaultHeight * this.scale <= this.height) { 274 maxTransY = (this.height - this.defaultHeight * this.scale) / (2 * this.scale); 275 minTransY = (this.height - this.defaultHeight * this.scale) / (2 * this.scale); 276 } else { 277 maxTransY = 0; 278 minTransY = (this.height - this.defaultHeight * this.scale) / this.scale; 279 } 280 281 if (this.transY > maxTransY) { 282 this.transY = maxTransY; 283 } else if (this.transY < minTransY) { 284 this.transY = minTransY; 285 } 286 if (this.transX > maxTransX) { 287 this.transX = maxTransX; 288 } else if (this.transX < minTransX) { 289 this.transX = minTransX; 290 } 291 292 this.canvas.applyTransformParams(this.scale, this.transX, this.transY); 293 294 if (this.markers) { 295 this.repositionMarkers(); 296 } 297 298 this.repositionLabels(); 299 300 this.container.trigger('viewportChange', [this.scale/this.baseScale, this.transX, this.transY]); 301 }, 302 303 bindContainerEvents: function(){ 304 var mouseDown = false, 305 oldPageX, 306 oldPageY, 307 map = this; 308 309 if (this.params.panOnDrag) { 310 this.container.mousemove(function(e){ 311 if (mouseDown) { 312 map.transX -= (oldPageX - e.pageX) / map.scale; 313 map.transY -= (oldPageY - e.pageY) / map.scale; 314 315 map.applyTransform(); 316 317 oldPageX = e.pageX; 318 oldPageY = e.pageY; 319 } 320 return false; 321 }).mousedown(function(e){ 322 mouseDown = true; 323 oldPageX = e.pageX; 324 oldPageY = e.pageY; 325 return false; 326 }); 327 328 this.onContainerMouseUp = function(){ 329 mouseDown = false; 330 }; 331 jvm.$('body').mouseup(this.onContainerMouseUp); 332 } 333 334 // if (this.params.zoomOnScroll) { 335 // this.container.mousewheel(function(event, delta, deltaX, deltaY) { 336 // var offset = jvm.$(map.container).offset(), 337 // centerX = event.pageX - offset.left, 338 // centerY = event.pageY - offset.top, 339 // zoomStep = Math.pow(1 + map.params.zoomOnScrollSpeed / 1000, event.deltaFactor * event.deltaY); 340 // 341 // map.tip.hide(); 342 // 343 // map.setScale(map.scale * zoomStep, centerX, centerY); 344 // event.preventDefault(); 345 // }); 346 // } 347 }, 348 349 bindContainerTouchEvents: function(){ 350 var touchStartScale, 351 touchStartDistance, 352 map = this, 353 touchX, 354 touchY, 355 centerTouchX, 356 centerTouchY, 357 lastTouchesLength,
358 handleTouchEvent = function(e){ 359 var touches = e.originalEvent.touches, 360 offset, 361 scale, 362 transXOld, 363 transYOld; 364 365 if (e.type == 'touchstart') { 366 lastTouchesLength = 0; 367 } 368 369 if (touches.length == 1) { 370 if (lastTouchesLength == 1) { 371 transXOld = map.transX; 372 transYOld = map.transY; 373 map.transX -= (touchX - touches[0].pageX) / map.scale; 374 map.transY -= (touchY - touches[0].pageY) / map.scale; 375 map.applyTransform(); 376 map.tip.hide(); 377 if (transXOld != map.transX || transYOld != map.transY) { 378 e.preventDefault(); 379 } 380 } 381 touchX = touches[0].pageX; 382 touchY = touches[0].pageY; 383 } else if (touches.length == 2) { 384 if (lastTouchesLength == 2) { 385 scale = Math.sqrt( 386 Math.pow(touches[0].pageX - touches[1].pageX, 2) + 387 Math.pow(touches[0].pageY - touches[1].pageY, 2) 388 ) / touchStartDistance; 389 map.setScale( 390 touchStartScale * scale, 391 centerTouchX, 392 centerTouchY 393 ) 394 map.tip.hide(); 395 e.preventDefault(); 396 } else { 397 offset = jvm.$(map.container).offset(); 398 if (touches[0].pageX > touches[1].pageX) { 399 centerTouchX = touches[1].pageX + (touches[0].pageX - touches[1].pageX) / 2; 400 } else { 401 centerTouchX = touches[0].pageX + (touches[1].pageX - touches[0].pageX) / 2; 402 } 403 if (touches[0].pageY > touches[1].pageY) { 404 centerTouchY = touches[1].pageY + (touches[0].pageY - touches[1].pageY) / 2; 405 } else { 406 centerTouchY = touches[0].pageY + (touches[1].pageY - touches[0].pageY) / 2; 407 } 408 centerTouchX -= offset.left; 409 centerTouchY -= offset.top; 410 touchStartScale = map.scale; 411 touchStartDistance = Math.sqrt( 412 Math.pow(touches[0].pageX - touches[1].pageX, 2) + 413 Math.pow(touches[0].pageY - touches[1].pageY, 2) 414 ); 415 } 416 } 417 418 lastTouchesLength = touches.length; 419 }; 420 421 jvm.$(this.container).bind('touchstart', handleTouchEvent); 422 jvm.$(this.container).bind('touchmove', handleTouchEvent); 423 }, 424 425 bindContainerPointerEvents: function(){ 426 var map = this, 427 gesture = new MSGesture(), 428 element = this.container[0], 429 handlePointerDownEvent = function(e){ 430 gesture.addPointer(e.pointerId); 431 }, 432 handleGestureEvent = function(e){ 433 var offset, 434 scale, 435 transXOld, 436 transYOld; 437 438 if (e.translationX != 0 || e.translationY != 0) { 439 transXOld = map.transX; 440 transYOld = map.transY; 441 map.transX += e.translationX / map.scale; 442 map.transY += e.translationY / map.scale; 443 map.applyTransform(); 444 map.tip.hide(); 445 if (transXOld != map.transX || transYOld != map.transY) { 446 e.preventDefault(); 447 } 448 } 449 if (e.scale != 1) { 450 map.setScale( 451 map.scale * e.scale, 452 e.offsetX, 453 e.offsetY 454 ) 455 map.tip.hide(); 456 e.preventDefault(); 457 } 458 }; 459 460 gesture.target = element; 461 element.addEventListener("MSGestureChange", handleGestureEvent, false); 462 element.addEventListener("pointerdown", handlePointerDownEvent, false); 463 }, 464 465 bindElementEvents: function(){ 466 var map = this, 467 pageX, 468 pageY, 469 mouseMoved; 470 471 this.container.mousemove(function(e){ 472 if (Math.abs(pageX - e.pageX) + Math.abs(pageY - e.pageY) > 2) { 473 mouseMoved = true; 474 } 475 }); 476 477 /* Can not use common class selectors here because of the bug in jQuery 478 SVG handling, use with caution. */ 479 this.container.delegate("[class~='jvectormap-element']", 'mouseover mouseout', function(e){ 480 var baseVal = jvm.$(this).attr('class').baseVal || jvm.$(this).attr('class'), 481 type = baseVal.indexOf('jvectormap-region') === -1 ? 'marker' : 'region', 482 code = type == 'region' ? jvm.$(this).attr('data-code') : jvm.$(this).attr('data-index'),
483 element = type == 'region' ? map.regions[code].element : map.markers[code].element, 484 tipText = type == 'region' ? map.mapData.paths[code].name : (map.markers[code].config.name || ''), 485 tipShowEvent = jvm.$.Event(type+'TipShow.jvectormap'), 486 overEvent = jvm.$.Event(type+'Over.jvectormap'); 487 488 if (e.type == 'mouseover') { 489 map.container.trigger(overEvent, [code]); 490 if (!overEvent.isDefaultPrevented()) { 491 element.setHovered(true); 492 } 493 494 map.tip.text(tipText); 495 map.container.trigger(tipShowEvent, [map.tip, code]); 496 if (!tipShowEvent.isDefaultPrevented()) { 497 map.tip.show(); 498 map.tipWidth = map.tip.width(); 499 map.tipHeight = map.tip.height(); 500 } 501 } else { 502 element.setHovered(false); 503 map.tip.hide(); 504 map.container.trigger(type+'Out.jvectormap', [code]); 505 } 506 }); 507 508 /* Can not use common class selectors here because of the bug in jQuery 509 SVG handling, use with caution. */ 510 this.container.delegate("[class~='jvectormap-element']", 'mousedown', function(e){ 511 pageX = e.pageX; 512 pageY = e.pageY; 513 mouseMoved = false; 514 }); 515 516 /* Can not use common class selectors here because of the bug in jQuery 517 SVG handling, use with caution. */ 518 this.container.delegate("[class~='jvectormap-element']", 'mouseup', function(){ 519 var baseVal = jvm.$(this).attr('class').baseVal ? jvm.$(this).attr('class').baseVal : jvm.$(this).attr('class'), 520 type = baseVal.indexOf('jvectormap-region') === -1 ? 'marker' : 'region', 521 code = type == 'region' ? jvm.$(this).attr('data-code') : jvm.$(this).attr('data-index'), 522 clickEvent = jvm.$.Event(type+'Click.jvectormap'), 523 element = type == 'region' ? map.regions[code].element : map.markers[code].element; 524 525 if (!mouseMoved) { 526 map.container.trigger(clickEvent, [code]); 527 if ((type === 'region' && map.params.regionsSelectable) || (type === 'marker' && map.params.markersSelectable)) { 528 if (!clickEvent.isDefaultPrevented()) { 529 if (map.params[type+'sSelectableOne']) { 530 map.clearSelected(type+'s'); 531 } 532 element.setSelected(!element.isSelected); 533 } 534 } 535 } 536 }); 537 }, 538 539 bindZoomButtons: function() { 540 var map = this; 541 542 jvm.$('<div/>').addClass('jvectormap-zoomin').text('+').appendTo(this.container); 543 jvm.$('<div/>').addClass('jvectormap-zoomout').html('−').appendTo(this.container); 544 545 this.container.find('.jvectormap-zoomin').click(function(){ 546 map.setScale(map.scale * map.params.zoomStep, map.width / 2, map.height / 2, false, map.params.zoomAnimate); 547 }); 548 this.container.find('.jvectormap-zoomout').click(function(){ 549 map.setScale(map.scale / map.params.zoomStep, map.width / 2, map.height / 2, false, map.params.zoomAnimate); 550 }); 551 }, 552 553 createTip: function(){ 554 var map = this; 555 556 this.tip = jvm.$('<div/>').addClass('jvectormap-tip').appendTo(jvm.$('body')); 557 558 this.container.mousemove(function(e){ 559 var left = e.pageX-15-map.tipWidth, 560 top = e.pageY-15-map.tipHeight; 561 562 if (left < 5) { 563 left = e.pageX + 15; 564 } 565 if (top < 5) { 566 top = e.pageY + 15; 567 } 568 569 map.tip.css({ 570 left: left, 571 top: top 572 }); 573 }); 574 }, 575 576 setScale: function(scale, anchorX, anchorY, isCentered, animate) { 577 var viewportChangeEvent = jvm.$.Event('zoom.jvectormap'), 578 interval, 579 that = this, 580 i = 0, 581 count = Math.abs(Math.round((scale - this.scale) * 60 / Math.max(scale, this.scale))), 582 scaleStart, 583 scaleDiff, 584 transXStart, 585 transXDiff, 586 transYStart, 587 transYDiff, 588 transX, 589 transY, 590 deferred = new jvm.$.Deferred(); 591 592 if (scale > this.params.zoomMax * this.baseScale) { 593 scale = this.params.zoomMax * this.baseScale; 594 } else if (scale < this.params.zoomMin * this.baseScale) { 595 scale = this.params.zoomMin * this.baseScale; 596 } 597 598 if (typeof anchorX != 'undefined' && typeof anchorY != 'undefined') {
599 zoomStep = scale / this.scale; 600 if (isCentered) { 601 transX = anchorX + this.defaultWidth * (this.width / (this.defaultWidth * scale)) / 2; 602 transY = anchorY + this.defaultHeight * (this.height / (this.defaultHeight * scale)) / 2; 603 } else { 604 transX = this.transX - (zoomStep - 1) / scale * anchorX; 605 transY = this.transY - (zoomStep - 1) / scale * anchorY; 606 } 607 } 608 609 if (animate && count > 0) { 610 scaleStart = this.scale; 611 scaleDiff = (scale - scaleStart) / count; 612 transXStart = this.transX * this.scale; 613 transYStart = this.transY * this.scale; 614 transXDiff = (transX * scale - transXStart) / count; 615 transYDiff = (transY * scale - transYStart) / count; 616 interval = setInterval(function(){ 617 i += 1; 618 that.scale = scaleStart + scaleDiff * i; 619 that.transX = (transXStart + transXDiff * i) / that.scale; 620 that.transY = (transYStart + transYDiff * i) / that.scale; 621 that.applyTransform(); 622 if (i == count) { 623 clearInterval(interval); 624 that.container.trigger(viewportChangeEvent, [scale/that.baseScale]); 625 deferred.resolve(); 626 } 627 }, 10); 628 } else { 629 this.transX = transX; 630 this.transY = transY; 631 this.scale = scale; 632 this.applyTransform(); 633 this.container.trigger(viewportChangeEvent, [scale/this.baseScale]); 634 deferred.resolve(); 635 } 636 637 return deferred; 638 }, 639 640 /** 641 * Set the map's viewport to the specific point and set zoom of the map to the specific level. Point and zoom level could be defined in two ways: using the code of some region to focus on or a central point and zoom level as numbers. 642 * @param This method takes a configuration object as the single argument. The options passed to it are the following: 643 * @param {Array} params.regions Array of region codes to zoom to. 644 * @param {String} params.region Region code to zoom to. 645 * @param {Number} params.scale Map scale to set. 646 * @param {Number} params.lat Latitude to set viewport to. 647 * @param {Number} params.lng Longitude to set viewport to. 648 * @param {Number} params.x Number from 0 to 1 specifying the horizontal coordinate of the central point of the viewport. 649 * @param {Number} params.y Number from 0 to 1 specifying the vertical coordinate of the central point of the viewport. 650 * @param {Boolean} params.animate Indicates whether or not to animate the scale change and transition. 651 */ 652 setFocus: function(config){ 653 var bbox, 654 itemBbox, 655 newBbox, 656 codes, 657 i, 658 point; 659 660 config = config || {}; 661 662 if (config.region) { 663 codes = [config.region]; 664 } else if (config.regions) { 665 codes = config.regions; 666 } 667 668 if (codes) { 669 for (i = 0; i < codes.length; i++) { 670 if (this.regions[codes[i]]) { 671 itemBbox = this.regions[codes[i]].element.shape.getBBox(); 672 if (itemBbox) { 673 if (typeof bbox == 'undefined') { 674 bbox = itemBbox; 675 } else { 676 newBbox = { 677 x: Math.min(bbox.x, itemBbox.x), 678 y: Math.min(bbox.y, itemBbox.y), 679 width: Math.max(bbox.x + bbox.width, itemBbox.x + itemBbox.width) - Math.min(bbox.x, itemBbox.x), 680 height: Math.max(bbox.y + bbox.height, itemBbox.y + itemBbox.height) - Math.min(bbox.y, itemBbox.y) 681 } 682 bbox = newBbox; 683 } 684 } 685 } 686 } 687 return this.setScale( 688 Math.min(this.width / bbox.width, this.height / bbox.height), 689 - (bbox.x + bbox.width / 2), 690 - (bbox.y + bbox.height / 2), 691 true, 692 config.animate 693 ); 694 } else { 695 if (config.lat !== undefined && config.lng !== undefined) { 696 point = this.latLngToPoint(config.lat, config.lng); 697 config.x = this.transX - point.x / this.scale; 698 config.y = this.transY - point.y / this.scale; 699 } else if (config.x && config.y) { 700 config.x *= -this.defaultWidth; 701 config.y *= -this.defaultHeight; 702 } 703 return this.setScale(config.scale * this.baseScale, config.x, config.y, true, config.animate); 704 } 705 }, 706 707 getSelected: function(type){ 708 var key, 709 selected = []; 710 711 for (key in this[type]) { 712 if (this[type][key].element.isSelected) { 713 selected.push(key); 714 } 715 } 716 return selected; 717 }, 718 719 /**
720 * Return the codes of currently selected regions. 721 * @returns {Array} 722 */ 723 getSelectedRegions: function(){ 724 return this.getSelected('regions'); 725 }, 726 727 /** 728 * Return the codes of currently selected markers. 729 * @returns {Array} 730 */ 731 getSelectedMarkers: function(){ 732 return this.getSelected('markers'); 733 }, 734 735 setSelected: function(type, keys){ 736 var i; 737 738 if (typeof keys != 'object') { 739 keys = [keys]; 740 } 741 742 if (jvm.$.isArray(keys)) { 743 for (i = 0; i < keys.length; i++) { 744 this[type][keys[i]].element.setSelected(true); 745 } 746 } else { 747 for (i in keys) { 748 this[type][i].element.setSelected(!!keys[i]); 749 } 750 } 751 }, 752 753 /** 754 * Set or remove selected state for the regions. 755 * @param {String|Array|Object} keys If <code>String</code> or <code>Array</code> the region(s) with the corresponding code(s) will be selected. If <code>Object</code> was provided its keys are codes of regions, state of which should be changed. Selected state will be set if value is true, removed otherwise. 756 */ 757 setSelectedRegions: function(keys){ 758 this.setSelected('regions', keys); 759 }, 760 761 /** 762 * Set or remove selected state for the markers. 763 * @param {String|Array|Object} keys If <code>String</code> or <code>Array</code> the marker(s) with the corresponding code(s) will be selected. If <code>Object</code> was provided its keys are codes of markers, state of which should be changed. Selected state will be set if value is true, removed otherwise. 764 */ 765 setSelectedMarkers: function(keys){ 766 this.setSelected('markers', keys); 767 }, 768 769 clearSelected: function(type){ 770 var select = {}, 771 selected = this.getSelected(type), 772 i; 773 774 for (i = 0; i < selected.length; i++) { 775 select[selected[i]] = false; 776 }; 777 778 this.setSelected(type, select); 779 }, 780 781 /** 782 * Remove the selected state from all the currently selected regions. 783 */ 784 clearSelectedRegions: function(){ 785 this.clearSelected('regions'); 786 }, 787 788 /** 789 * Remove the selected state from all the currently selected markers. 790 */ 791 clearSelectedMarkers: function(){ 792 this.clearSelected('markers'); 793 }, 794 795 /** 796 * Return the instance of Map. Useful when instantiated as a jQuery plug-in. 797 * @returns {Map} 798 */ 799 getMapObject: function(){ 800 return this; 801 }, 802 803 /** 804 * Return the name of the region by region code. 805 * @returns {String} 806 */ 807 getRegionName: function(code){ 808 return this.mapData.paths[code].name; 809 }, 810 811 createRegions: function(){ 812 var key, 813 region, 814 map = this; 815 816 this.regionLabelsGroup = this.regionLabelsGroup || this.canvas.addGroup(); 817 818 for (key in this.mapData.paths) { 819 region = new jvm.Region({ 820 map: this, 821 path: this.mapData.paths[key].path, 822 code: key, 823 style: jvm.$.extend(true, {}, this.params.regionStyle), 824 labelStyle: jvm.$.extend(true, {}, this.params.regionLabelStyle), 825 canvas: this.canvas, 826 labelsGroup: this.regionLabelsGroup, 827 label: this.canvas.mode != 'vml' ? (this.params.labels && this.params.labels.regions) : null 828 }); 829 830 jvm.$(region.shape).bind('selected', function(e, isSelected){ 831 map.container.trigger('regionSelected.jvectormap', [jvm.$(this.node).attr('data-code'), isSelected, map.getSelectedRegions()]); 832 }); 833 this.regions[key] = { 834 element: region, 835 config: this.mapData.paths[key] 836 }; 837 } 838 }, 839 840 createMarkers: function(markers) { 841 var i, 842 marker, 843 point, 844 markerConfig, 845 markersArray, 846 map = this; 847 848 this.markersGroup = this.markersGroup || this.canvas.addGroup(); 849 this.markerLabelsGroup = this.markerLabelsGroup || this.canvas.addGroup(); 850 851 if (jvm.$.isArray(markers)) { 852 markersArray = markers.slice(); 853 markers = {}; 854 for (i = 0; i < markersArray.length; i++) { 855 markers[i] = markersArray[i]; 856 } 857 } 858 859 for (i in markers) { 860 markerConfig = markers[i] instanceof Array ? {latLng: markers[i]} : markers[i]; 861 point = this.getMarkerPosition( markerConfig ); 862 if (point !== false) { 863 //console.log(markers[i]) 864 this.params.markerStyle.initial.r = markers[i].realRadius; 865 marker = new jvm.Marker({ 866 map: this, 867 style: jvm.$.extend(true, {}, this.params.markerStyle, {initial: markerConfig.style || {}}), 868 labelStyle: jvm.$.extend(true, {}, this.params.markerLabelStyle), 869 index: i, 870 cx: point.x, 871 cy: point.y, 872 regionCode: markers[i].regionCode, 873 group: this.markersGroup, 874 canvas: this.canvas, 875 labelsGroup: this.markerLabelsGroup, 876 label: this.canvas.mode != 'vml' ? (this.params.labels && this.params.labels.markers) : null 877 }); 878 879 880 881 jvm.$(marker.shape).bind('selected', function(e, isSelected){ 882 map.container.trigger('markerSelected.jvectormap', [jvm.$(this.node).attr('data-index'), isSelected, map.getSelectedMarkers()]); 883 }); 884 if (this.markers[i]) { 885 this.removeMarkers([i]); 886 } 887 this.markers[i] = {element: marker, config: markerConfig}; 888 } 889 890 } 891 }, 892 893 repositionMarkers: function() { 894 var i, 895 point; 896 897 for (i in this.markers) { 898 point = this.getMarkerPosition( this.markers[i].config ); 899 if (point !== false) { 900 this.markers[i].element.setStyle({cx: point.x, cy: point.y}); 901 } 902 } 903 }, 904 905 repositionLabels: function() { 906 var key; 907 908 for (key in this.regions) { 909 this.regions[key].element.updateLabelPosition(); 910 } 911 912 for (key in this.markers) { 913 this.markers[key].element.updateLabelPosition(); 914 } 915 }, 916 917 getMarkerPosition: function(markerConfig) { 918 if (jvm.Map.maps[this.params.map].projection) { 919 return this.latLngToPoint.apply(this, markerConfig.latLng || [0, 0]); 920 } else { 921 return { 922 x: markerConfig.coords[0]*this.scale + this.transX*this.scale, 923 y: markerConfig.coords[1]*this.scale + this.transY*this.scale, 924 }; 925 } 926 }, 927 928 /** 929 * Add one marker to the map. 930 * @param {String}
930 key Marker unique code. 931 * @param {Object} marker Marker configuration parameters. 932 * @param {Array} seriesData Values to add to the data series. 933 */ 934 addMarker: function(key, marker, seriesData){ 935 var markers = {}, 936 data = [], 937 values, 938 i, 939 seriesData = seriesData || []; 940 941 markers[key] = marker; 942 943 for (i = 0; i < seriesData.length; i++) { 944 values = {}; 945 if (typeof seriesData[i] !== 'undefined') { 946 values[key] = seriesData[i]; 947 } 948 data.push(values); 949 } 950 this.addMarkers(markers, data); 951 }, 952 953 /** 954 * Add set of marker to the map. 955 * @param {Object|Array} markers Markers to add to the map. In case of array is provided, codes of markers will be set as string representations of array indexes. 956 * @param {Array} seriesData Values to add to the data series. 957 */ 958 addMarkers: function(markers, seriesData){ 959 var i; 960 961 seriesData = seriesData || []; 962 963 this.createMarkers(markers); 964 for (i = 0; i < seriesData.length; i++) { 965 this.series.markers[i].setValues(seriesD
965ata[i] || {}); 966 }; 967 }, 968 969 /** 970 * Remove some markers from the map. 971 * @param {Array} markers Array of marker codes to be removed. 972 */ 973 removeMarkers: function(markers){ 974 var i; 975 976 for (i = 0; i < markers.length; i++) { 977 this.markers[ markers[i] ].element.remove(); 978 delete this.markers[ markers[i] ]; 979 }; 980 }, 981 982 /** 983 * Remove all markers from the map. 984 */ 985 removeAllMarkers: function(){ 986 var i, 987 markers = []; 988 989 for (i in this.markers) { 990 markers.push(i); 991 } 992 this.removeMarkers(markers) 993 }, 994 995 /** 996 * Converts coordinates expressed as latitude and longitude to the coordinates in pixels on the map. 997 * @param {Number} lat Latitide of point in degrees. 998 * @param {Number} lng Longitude of point in degrees. 999 */ 1000 latLngToPoint: function(lat, lng) { 1001 var point, 1002 proj = jvm.Map.maps[this.params.map].projection, 1003 centralMeridian = proj.centralMeridian, 1004 inset, 1005 bbox; 1006 1007 if (lng < (-180 + centralMeridian)) { 1008 lng += 360; 1009 } 1010 1011 point = jvm.Proj[proj.type](lat, lng, centralMeridian); 1012 1013 inset = this.getInsetForPoint(point.x, point.y); 1014 if (inset) { 1015 bbox = inset.bbox; 1016 1017 point.x = (point.x - bbox[0].x) / (bbox[1].x - bbox[0].x) * inset.width * this.scale; 1018 point.y = (point.y - bbox[0].y) / (bbox[1].y - bbox[0].y) * inset.height * this.scale; 1019 1020 return { 1021 x: point.x + this.transX*this.scale + inset.left*this.scale, 1022 y: point.y + this.transY*this.scale + inset.top*this.scale 1023 }; 1024 } else { 1025 return false; 1026 } 1027 }, 1028 1029 /** 1030 * Converts cartesian coordinates into coordinates expressed as latitude and longitude. 1031 * @param {Number} x X-axis of point on map in pixels. 1032 * @param {Number} y Y-axis of point on map in pixels. 1033 */ 1034 pointToLatLng: function(x, y) { 1035 var proj = jvm.Map.maps[this.params.map].projection, 1036 centralMeridian = proj.centralMeridian, 1037 insets = jvm.Map.maps[this.params.map].insets, 1038 i, 1039 inset, 1040 bbox, 1041 nx, 1042 ny; 1043 1044 for (i = 0; i < insets.length; i++) { 1045 inset = insets[i]; 1046 bbox = inset.bbox; 1047 1048 nx = x - (this.transX*this.scale + inset.left*this.scale); 1049 ny = y - (this.transY*this.scale + inset.top*this.scale); 1050 1051 nx = (nx / (inset.width * this.scale)) * (bbox[1].x - bbox[0].x) + bbox[0].x; 1052 ny = (ny / (inset.height * this.scale)) * (bbox[1].y - bbox[0].y) + bbox[0].y; 1053 1054 if (nx > bbox[0].x && nx < bbox[1].x && ny > bbox[0].y && ny < bbox[1].y) { 1055 return jvm.Proj[proj.type + '_inv'](nx, -ny, centralMeridian); 1056 } 1057 } 1058 1059 return false; 1060 }, 1061 1062 getInsetForPoint: function(x, y){ 1063 var insets = jvm.Map.maps[this.params.map].insets, 1064 i, 1065 bbox; 1066 1067 for (i = 0; i < insets.length; i++) { 1068 bbox = insets[i].bbox; 1069 if (x > bbox[0].x && x < bbox[1].x && y > bbox[0].y && y < bbox[1].y) { 1070 return insets[i]; 1071 } 1072 } 1073 }, 1074 1075 createSeries: function(){ 1076 var i, 1077 key; 1078 1079 this.series = { 1080 markers: [], 1081 regions: [] 1082 }; 1083 1084 for (key in this.params.series) { 1085 for (i = 0; i < this.params.series[key].length; i++) { 1086 this.series[key][i] = new jvm.DataSeries( 1087 this.params.series[key][i], 1088 this[key], 1089 this 1090 ); 1091 } 1092 } 1093 }, 1094 1095 /** 1096 * Gracefully remove the map and and all its accessories, unbind event handlers. 1097 */ 1098 remove: function(){ 1099 this.tip.remove(); 1100 this.container.remove(); 1101 jvm.$(window).unbind('resize', this.onResize); 1102 jvm.$('body').unbind('mouseup', this.onContainerMouseUp); 1103 } 1104}; 1105 1106jvm.Map.maps = {}; 1107jvm.Map.defaultParams = { 1108 map: 'world_mill_en', 1109 backgroundColor: '#505050', 1110 zoomButtons: true, 1111 zoomOnScroll: true, 1112 zoomOnScrollSpeed: 3, 1113 panOnDrag: true, 1114 zoomMax: 8, 1115 zoomMin: 1, 1116 zoomStep: 1.6, 1117 zoomAnimate: true, 1118 regionsSelectable: false, 1119 markersSelectable: false, 1120 bindTouchEvents: true, 1121 regionStyle: { 1122 initial: { 1123 fill: 'white', 1124 "fill-opacity": 1, 1125 stroke: 'none', 1126 "stroke-width": 0, 1127 "stroke-opacity": 1 1128 }, 1129 hover: { 1130 "fill-opacity": 0.8, 1131 cursor: 'pointer' 1132 }, 1133 selected: { 1134 fill: 'yellow' 1135 }, 1136 selectedHover: { 1137 } 1138 }, 1139 regionLabelStyle: { 1140 initial: { 1141 'font-family': 'Verdana', 1142 'font-size': '12', 1143 'font-weight': 'bold', 1144 cursor: 'default', 1145 fill: 'black' 1146 }, 1147 hover: { 1148 cursor: 'pointer' 1149 } 1150 }, 1151 markerStyle: { 1152 initial: { 1153 fill: 'grey', 1154 stroke: '#505050', 1155 "fill-opacity": 1, 1156 "stroke-width": 1, 1157 "stroke-opacity": 1, 1158 r: 5 1159 }, 1160 hover: { 1161 stroke: 'black', 1162 "stroke-width": 2, 1163 cursor: 'pointer' 1164 }, 1165 selected: { 1166 fill: 'blue' 1167 }, 1168 selectedHover: { 1169 } 1170 }, 1171 markerLabelStyle: { 1172 initial: { 1173 'font-family': 'Verdana', 1174 'font-size': '12', 1175 'font-weight': 'bold', 1176 cursor: 'default', 1177 fill: 'black' 1178 }, 1179 hover: { 1180 cursor: 'pointer' 1181 } 1182 } 1183}; 1184jvm.Map.apiEvents = {
1185 onRegionTipShow: 'regionTipShow', 1186 onRegionOver: 'regionOver', 1187 onRegionOut: 'regionOut', 1188 onRegionClick: 'regionClick', 1189 onRegionSelected: 'regionSelected', 1190 onMarkerTipShow: 'markerTipShow', 1191 onMarkerOver: 'markerOver', 1192 onMarkerOut: 'markerOut', 1193 onMarkerClick: 'markerClick', 1194 onMarkerSelected: 'markerSelected', 1195 onViewportChange: 'viewportChange' 1196};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.