diff --git a/src/line.js b/src/line.js new file mode 100644 index 00000000..b02acedf --- /dev/null +++ b/src/line.js @@ -0,0 +1,59 @@ +/** + * Created by fdominik + */ +jvm.Line = function(config){ + var text,name,points; + + this.config = config; + this.map = this.config.map; + + this.isImage = !!this.config.style.initial.image; + this.createShape(); + + text = this.getLabelText(config.index); + if (this.config.label && text) { + this.offsets = this.getLabelOffsets(config.index); + this.labelX = this.config.sx + this.offsets[0];// / this.map.scale - this.map.transX; + this.labelY = this.config.sy + this.offsets[1];// / this.map.scale - this.map.transY; + this.label = config.canvas.addText({ + text: text, + 'data-index': config.index, + dy: "0.6ex", + x: this.labelX, + y: this.labelY + }, config.labelStyle, config.labelsGroup); + + this.label.addClass('jvectormap-line jvectormap-element'); + } +}; + +jvm.inherits(jvm.Line, jvm.MapObject); + +jvm.Line.prototype.createShape = function(){ + var that = this; + + if (this.shape) { + this.shape.remove(); + } + this.shape = this.config.canvas.addPath({ + d: this.config.path, + 'data-index':this.config.index + }, this.config.style, this.config.group); + + this.shape.addClass('jvectormap-line jvectormap-element'); + + if (this.isImage) { + jvm.$(this.shape.node).on('imageloaded', function(){ + that.updateLabelPosition(); + }); + } +}; + +jvm.Line.prototype.updateLabelPosition = function(){ + if (this.label) { + this.label.set({ + x: this.labelX * this.map.scale + this.map.transX * this.map.scale, + y: this.labelY * this.map.scale + this.map.transY * this.map.scale + }); + } +}; \ No newline at end of file diff --git a/src/map.js b/src/map.js index a9f26ebd..1d67c324 100644 --- a/src/map.js +++ b/src/map.js @@ -16,7 +16,7 @@ * @param {Boolean} params.markersSelectable When set to true markers on the map could be selected. Default value is false. * @param {Boolean} params.markersSelectableOne Allow only one marker to be selected at the moment. Default value is false. * @param {Object} params.regionStyle Set the styles for the map's regions. Each region or marker has four states: initial (default state), hover (when the mouse cursor is over the region or marker), selected (when region or marker is selected), selectedHover (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: -
{
+ 
{
   initial: {
     fill: 'white',
     "fill-opacity": 1,
@@ -34,8 +34,8 @@
   selectedHover: {
   }
 }
-* @param {Object} params.regionLabelStyle Set the styles for the regions' labels. Each region or marker has four states: initial (default state), hover (when the mouse cursor is over the region or marker), selected (when region or marker is selected), selectedHover (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: -
{
+ * @param {Object} params.regionLabelStyle Set the styles for the regions' labels. Each region or marker has four states: initial (default state), hover (when the mouse cursor is over the region or marker), selected (when region or marker is selected), selectedHover (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:
+ 
{
   initial: {
     'font-family': 'Verdana',
     'font-size': '12',
@@ -48,7 +48,7 @@
   }
 }
* @param {Object} params.markerStyle Set the styles for the map's markers. Any parameter suitable for regionStyle could be used as well as numeric parameter r to set the marker's radius. Default value for that parameter is: -
{
+ 
{
   initial: {
     fill: 'grey',
     stroke: '#505050',
@@ -69,7 +69,7 @@
   }
 }
* @param {Object} params.markerLabelStyle Set the styles for the markers' labels. Default value for that parameter is: -
{
+ 
{
   initial: {
     'font-family': 'Verdana',
     'font-size': '12',
@@ -85,11 +85,11 @@
  * @param {Object} params.series Object with two keys: markers and regions. Each of which is an array of series configs to be applied to the respective map elements. See DataSeries description for a list of parameters available.
  * @param {Object|String} params.focusOn This parameter sets the initial position and scale of the map viewport. See setFocus docuemntation for possible parameters.
  * @param {Object} params.labels Defines parameters for rendering static labels. Object could contain two keys: regions and markers. Each key value defines configuration object with the following possible options:
-
    -
  • render {Function} - defines method for converting region code or marker index to actual label value.
  • -
  • offsets {Object|Function} - provides method or object which could be used to define label offset by region code or marker index.
  • -
-Plase note: static labels feature is not supported in Internet Explorer 8 and below. +
    +
  • render {Function} - defines method for converting region code or marker index to actual label value.
  • +
  • offsets {Object|Function} - provides method or object which could be used to define label offset by region code or marker index.
  • +
+ Plase note: static labels feature is not supported in Internet Explorer 8 and below. * @param {Array|Object|String} params.selectedRegions Set initially selected regions. * @param {Array|Object|String} params.selectedMarkers Set initially selected markers. * @param {Function} params.onRegionTipShow (Event e, Object tip, String code) Will be called right before the region tip is going to be shown. @@ -104,1083 +104,1280 @@ * @param {Function} params.onMarkerSelected (Event e, String code, Boolean isSelected, Array selectedMarkers) Will be called when marker is (de)selected. isSelected parameter of the callback indicates whether marker is selected or not. selectedMarkers contains codes of all currently selected markers. * @param {Function} params.onViewportChange (Event e, Number scale) Triggered when the map's viewport is changed (map was panned or zoomed). */ -jvm.Map = function(params) { - var map = this, - e; +jvm.Map = function (params) { + var map = this, + e; - this.params = jvm.$.extend(true, {}, jvm.Map.defaultParams, params); + this.params = jvm.$.extend(true, {}, jvm.Map.defaultParams, params); - if (!jvm.Map.maps[this.params.map]) { - throw new Error('Attempt to use map which was not loaded: '+this.params.map); - } + if (!jvm.Map.maps[this.params.map]) { + throw new Error('Attempt to use map which was not loaded: ' + this.params.map); + } - this.mapData = jvm.Map.maps[this.params.map]; - this.markers = {}; - this.regions = {}; - this.regionsColors = {}; - this.regionsData = {}; + this.mapData = jvm.Map.maps[this.params.map]; + this.markers = {}; + this.lines = {}; + this.regions = {}; + this.regionsColors = {}; + this.regionsData = {}; - this.container = jvm.$('
').addClass('jvectormap-container'); - if (this.params.container) { - this.params.container.append( this.container ); - } - this.container.data('mapObject', this); + this.container = jvm.$('
').addClass('jvectormap-container'); + if (this.params.container) { + this.params.container.append(this.container); + } + this.container.data('mapObject', this); - this.defaultWidth = this.mapData.width; - this.defaultHeight = this.mapData.height; + this.defaultWidth = this.mapData.width; + this.defaultHeight = this.mapData.height; - this.setBackgroundColor(this.params.backgroundColor); + this.setBackgroundColor(this.params.backgroundColor); - this.onResize = function(){ - map.updateSize(); - } - jvm.$(window).resize(this.onResize); + this.onResize = function () { + map.updateSize(); + } + jvm.$(window).resize(this.onResize); - for (e in jvm.Map.apiEvents) { - if (this.params[e]) { - this.container.bind(jvm.Map.apiEvents[e]+'.jvectormap', this.params[e]); + for (e in jvm.Map.apiEvents) { + if (this.params[e]) { + this.container.bind(jvm.Map.apiEvents[e] + '.jvectormap', this.params[e]); + } } - } - this.canvas = new jvm.VectorCanvas(this.container[0], this.width, this.height); + this.canvas = new jvm.VectorCanvas(this.container[0], this.width, this.height); - if (this.params.bindTouchEvents) { - if (('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch)) { - this.bindContainerTouchEvents(); - } else if (window.MSGesture) { - this.bindContainerPointerEvents(); + if (this.params.bindTouchEvents) { + if (('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch)) { + this.bindContainerTouchEvents(); + } else if (window.MSGesture) { + this.bindContainerPointerEvents(); + } + } + this.bindContainerEvents(); + this.bindElementEvents(); + this.createTip(); + if (this.params.zoomButtons) { + this.bindZoomButtons(); } - } - this.bindContainerEvents(); - this.bindElementEvents(); - this.createTip(); - if (this.params.zoomButtons) { - this.bindZoomButtons(); - } - this.createRegions(); - this.createMarkers(this.params.markers || {}); + this.createRegions(); + this.createMarkers(this.params.markers || {}); + this.createLines(this.params.lines || {}); - this.updateSize(); + this.updateSize(); - if (this.params.focusOn) { - if (typeof this.params.focusOn === 'string') { - this.params.focusOn = {region: this.params.focusOn}; - } else if (jvm.$.isArray(this.params.focusOn)) { - this.params.focusOn = {regions: this.params.focusOn}; + if (this.params.focusOn) { + if (typeof this.params.focusOn === 'string') { + this.params.focusOn = {region: this.params.focusOn}; + } else if (jvm.$.isArray(this.params.focusOn)) { + this.params.focusOn = {regions: this.params.focusOn}; + } + this.setFocus(this.params.focusOn); } - this.setFocus(this.params.focusOn); - } - if (this.params.selectedRegions) { - this.setSelectedRegions(this.params.selectedRegions); - } - if (this.params.selectedMarkers) { - this.setSelectedMarkers(this.params.selectedMarkers); - } + if (this.params.selectedRegions) { + this.setSelectedRegions(this.params.selectedRegions); + } + if (this.params.selectedMarkers) { + this.setSelectedMarkers(this.params.selectedMarkers); + } - this.legendCntHorizontal = jvm.$('
').addClass('jvectormap-legend-cnt jvectormap-legend-cnt-h'); - this.legendCntVertical = jvm.$('
').addClass('jvectormap-legend-cnt jvectormap-legend-cnt-v'); - this.container.append(this.legendCntHorizontal); - this.container.append(this.legendCntVertical); + if (this.params.selectedLines) { + this.setSelectedLines(this.params.selectedLines); + } - if (this.params.series) { - this.createSeries(); - } + this.legendCntHorizontal = jvm.$('
').addClass('jvectormap-legend-cnt jvectormap-legend-cnt-h'); + this.legendCntVertical = jvm.$('
').addClass('jvectormap-legend-cnt jvectormap-legend-cnt-v'); + this.container.append(this.legendCntHorizontal); + this.container.append(this.legendCntVertical); + + if (this.params.series) { + this.createSeries(); + } }; jvm.Map.prototype = { - transX: 0, - transY: 0, - scale: 1, - baseTransX: 0, - baseTransY: 0, - baseScale: 1, - - width: 0, - height: 0, - - /** - * Set background color of the map. - * @param {String} backgroundColor Background color in CSS format. - */ - setBackgroundColor: function(backgroundColor) { - this.container.css('background-color', backgroundColor); - }, - - resize: function() { - var curBaseScale = this.baseScale; - if (this.width / this.height > this.defaultWidth / this.defaultHeight) { - this.baseScale = this.height / this.defaultHeight; - this.baseTransX = Math.abs(this.width - this.defaultWidth * this.baseScale) / (2 * this.baseScale); - } else { - this.baseScale = this.width / this.defaultWidth; - this.baseTransY = Math.abs(this.height - this.defaultHeight * this.baseScale) / (2 * this.baseScale); - } - this.scale *= this.baseScale / curBaseScale; - this.transX *= this.baseScale / curBaseScale; - this.transY *= this.baseScale / curBaseScale; - }, + transX: 0, + transY: 0, + scale: 1, + baseTransX: 0, + baseTransY: 0, + baseScale: 1, + + width: 0, + height: 0, + + /** + * Set background color of the map. + * @param {String} backgroundColor Background color in CSS format. + */ + setBackgroundColor: function (backgroundColor) { + this.container.css('background-color', backgroundColor); + }, - /** - * 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. - */ - updateSize: function(){ - this.width = this.container.width(); - this.height = this.container.height(); - this.resize(); - this.canvas.setSize(this.width, this.height); - this.applyTransform(); - }, + resize: function () { + var curBaseScale = this.baseScale; + if (this.width / this.height > this.defaultWidth / this.defaultHeight) { + this.baseScale = this.height / this.defaultHeight; + this.baseTransX = Math.abs(this.width - this.defaultWidth * this.baseScale) / (2 * this.baseScale); + } else { + this.baseScale = this.width / this.defaultWidth; + this.baseTransY = Math.abs(this.height - this.defaultHeight * this.baseScale) / (2 * this.baseScale); + } + this.scale *= this.baseScale / curBaseScale; + this.transX *= this.baseScale / curBaseScale; + this.transY *= this.baseScale / curBaseScale; + }, - /** - * Reset all the series and show the map with the initial zoom. - */ - reset: function() { - var key, - i; - - for (key in this.series) { - for (i = 0; i < this.series[key].length; i++) { - this.series[key][i].clear(); - } - } - this.scale = this.baseScale; - this.transX = this.baseTransX; - this.transY = this.baseTransY; - this.applyTransform(); - }, + /** + * 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. + */ + updateSize: function () { + this.width = this.container.width(); + this.height = this.container.height(); + this.resize(); + this.canvas.setSize(this.width, this.height); + this.applyTransform(); + }, - applyTransform: function() { - var maxTransX, - maxTransY, - minTransX, - minTransY; - - if (this.defaultWidth * this.scale <= this.width) { - maxTransX = (this.width - this.defaultWidth * this.scale) / (2 * this.scale); - minTransX = (this.width - this.defaultWidth * this.scale) / (2 * this.scale); - } else { - maxTransX = 0; - minTransX = (this.width - this.defaultWidth * this.scale) / this.scale; - } + /** + * Reset all the series and show the map with the initial zoom. + */ + reset: function () { + var key, + i; - if (this.defaultHeight * this.scale <= this.height) { - maxTransY = (this.height - this.defaultHeight * this.scale) / (2 * this.scale); - minTransY = (this.height - this.defaultHeight * this.scale) / (2 * this.scale); - } else { - maxTransY = 0; - minTransY = (this.height - this.defaultHeight * this.scale) / this.scale; - } + for (key in this.series) { + for (i = 0; i < this.series[key].length; i++) { + this.series[key][i].clear(); + } + } + this.scale = this.baseScale; + this.transX = this.baseTransX; + this.transY = this.baseTransY; + this.applyTransform(); + }, - if (this.transY > maxTransY) { - this.transY = maxTransY; - } else if (this.transY < minTransY) { - this.transY = minTransY; - } - if (this.transX > maxTransX) { - this.transX = maxTransX; - } else if (this.transX < minTransX) { - this.transX = minTransX; - } + applyTransform: function () { + var maxTransX, + maxTransY, + minTransX, + minTransY; + + if (this.defaultWidth * this.scale <= this.width) { + maxTransX = (this.width - this.defaultWidth * this.scale) / (2 * this.scale); + minTransX = (this.width - this.defaultWidth * this.scale) / (2 * this.scale); + } else { + maxTransX = 0; + minTransX = (this.width - this.defaultWidth * this.scale) / this.scale; + } - this.canvas.applyTransformParams(this.scale, this.transX, this.transY); + if (this.defaultHeight * this.scale <= this.height) { + maxTransY = (this.height - this.defaultHeight * this.scale) / (2 * this.scale); + minTransY = (this.height - this.defaultHeight * this.scale) / (2 * this.scale); + } else { + maxTransY = 0; + minTransY = (this.height - this.defaultHeight * this.scale) / this.scale; + } - if (this.markers) { - this.repositionMarkers(); - } + if (this.transY > maxTransY) { + this.transY = maxTransY; + } else if (this.transY < minTransY) { + this.transY = minTransY; + } + if (this.transX > maxTransX) { + this.transX = maxTransX; + } else if (this.transX < minTransX) { + this.transX = minTransX; + } - this.repositionLabels(); + this.canvas.applyTransformParams(this.scale, this.transX, this.transY); - this.container.trigger('viewportChange', [this.scale/this.baseScale, this.transX, this.transY]); - }, + if (this.markers) { + this.repositionMarkers(); + } - bindContainerEvents: function(){ - var mouseDown = false, - oldPageX, - oldPageY, - map = this; + if (this.lines) { + this.repositionLines(); + } - if (this.params.panOnDrag) { - this.container.mousemove(function(e){ - if (mouseDown) { - map.transX -= (oldPageX - e.pageX) / map.scale; - map.transY -= (oldPageY - e.pageY) / map.scale; + this.repositionLabels(); - map.applyTransform(); + this.container.trigger('viewportChange', [this.scale / this.baseScale, this.transX, this.transY]); + }, - oldPageX = e.pageX; - oldPageY = e.pageY; + bindContainerEvents: function () { + var mouseDown = false, + oldPageX, + oldPageY, + map = this; + + if (this.params.panOnDrag) { + this.container.mousemove(function (e) { + if (mouseDown) { + map.transX -= (oldPageX - e.pageX) / map.scale; + map.transY -= (oldPageY - e.pageY) / map.scale; + + map.applyTransform(); + + oldPageX = e.pageX; + oldPageY = e.pageY; + } + return false; + }).mousedown(function (e) { + mouseDown = true; + oldPageX = e.pageX; + oldPageY = e.pageY; + return false; + }); + + this.onContainerMouseUp = function () { + mouseDown = false; + }; + jvm.$('body').mouseup(this.onContainerMouseUp); } - return false; - }).mousedown(function(e){ - mouseDown = true; - oldPageX = e.pageX; - oldPageY = e.pageY; - return false; - }); - this.onContainerMouseUp = function(){ - mouseDown = false; - }; - jvm.$('body').mouseup(this.onContainerMouseUp); - } + if (this.params.zoomOnScroll) { + this.container.mousewheel(function (event, delta, deltaX, deltaY) { + var offset = jvm.$(map.container).offset(), + centerX = event.pageX - offset.left, + centerY = event.pageY - offset.top, + zoomStep = Math.pow(1 + map.params.zoomOnScrollSpeed / 1000, event.deltaFactor * event.deltaY); - if (this.params.zoomOnScroll) { - this.container.mousewheel(function(event, delta, deltaX, deltaY) { - var offset = jvm.$(map.container).offset(), - centerX = event.pageX - offset.left, - centerY = event.pageY - offset.top, - zoomStep = Math.pow(1 + map.params.zoomOnScrollSpeed / 1000, event.deltaFactor * event.deltaY); + map.tip.hide(); - map.tip.hide(); + map.setScale(map.scale * zoomStep, centerX, centerY); + event.preventDefault(); + }); + } + }, - map.setScale(map.scale * zoomStep, centerX, centerY); - event.preventDefault(); - }); - } - }, + bindContainerTouchEvents: function () { + var touchStartScale, + touchStartDistance, + map = this, + touchX, + touchY, + centerTouchX, + centerTouchY, + lastTouchesLength, + handleTouchEvent = function (e) { + var touches = e.originalEvent.touches, + offset, + scale, + transXOld, + transYOld; + + if (e.type == 'touchstart') { + lastTouchesLength = 0; + } + + if (touches.length == 1) { + if (lastTouchesLength == 1) { + transXOld = map.transX; + transYOld = map.transY; + map.transX -= (touchX - touches[0].pageX) / map.scale; + map.transY -= (touchY - touches[0].pageY) / map.scale; + map.applyTransform(); + map.tip.hide(); + if (transXOld != map.transX || transYOld != map.transY) { + e.preventDefault(); + } + } + touchX = touches[0].pageX; + touchY = touches[0].pageY; + } else if (touches.length == 2) { + if (lastTouchesLength == 2) { + scale = Math.sqrt( + Math.pow(touches[0].pageX - touches[1].pageX, 2) + + Math.pow(touches[0].pageY - touches[1].pageY, 2) + ) / touchStartDistance; + map.setScale( + touchStartScale * scale, + centerTouchX, + centerTouchY + ) + map.tip.hide(); + e.preventDefault(); + } else { + offset = jvm.$(map.container).offset(); + if (touches[0].pageX > touches[1].pageX) { + centerTouchX = touches[1].pageX + (touches[0].pageX - touches[1].pageX) / 2; + } else { + centerTouchX = touches[0].pageX + (touches[1].pageX - touches[0].pageX) / 2; + } + if (touches[0].pageY > touches[1].pageY) { + centerTouchY = touches[1].pageY + (touches[0].pageY - touches[1].pageY) / 2; + } else { + centerTouchY = touches[0].pageY + (touches[1].pageY - touches[0].pageY) / 2; + } + centerTouchX -= offset.left; + centerTouchY -= offset.top; + touchStartScale = map.scale; + touchStartDistance = Math.sqrt( + Math.pow(touches[0].pageX - touches[1].pageX, 2) + + Math.pow(touches[0].pageY - touches[1].pageY, 2) + ); + } + } + + lastTouchesLength = touches.length; + }; + + jvm.$(this.container).bind('touchstart', handleTouchEvent); + jvm.$(this.container).bind('touchmove', handleTouchEvent); + }, - bindContainerTouchEvents: function(){ - var touchStartScale, - touchStartDistance, - map = this, - touchX, - touchY, - centerTouchX, - centerTouchY, - lastTouchesLength, - handleTouchEvent = function(e){ - var touches = e.originalEvent.touches, - offset, - scale, - transXOld, - transYOld; - - if (e.type == 'touchstart') { - lastTouchesLength = 0; - } - - if (touches.length == 1) { - if (lastTouchesLength == 1) { - transXOld = map.transX; - transYOld = map.transY; - map.transX -= (touchX - touches[0].pageX) / map.scale; - map.transY -= (touchY - touches[0].pageY) / map.scale; - map.applyTransform(); - map.tip.hide(); - if (transXOld != map.transX || transYOld != map.transY) { - e.preventDefault(); - } + bindContainerPointerEvents: function () { + var map = this, + gesture = new MSGesture(), + element = this.container[0], + handlePointerDownEvent = function (e) { + gesture.addPointer(e.pointerId); + }, + handleGestureEvent = function (e) { + var offset, + scale, + transXOld, + transYOld; + + if (e.translationX != 0 || e.translationY != 0) { + transXOld = map.transX; + transYOld = map.transY; + map.transX += e.translationX / map.scale; + map.transY += e.translationY / map.scale; + map.applyTransform(); + map.tip.hide(); + if (transXOld != map.transX || transYOld != map.transY) { + e.preventDefault(); + } + } + if (e.scale != 1) { + map.setScale( + map.scale * e.scale, + e.offsetX, + e.offsetY + ) + map.tip.hide(); + e.preventDefault(); + } + }; + + gesture.target = element; + element.addEventListener("MSGestureChange", handleGestureEvent, false); + element.addEventListener("pointerdown", handlePointerDownEvent, false); + }, + + bindElementEvents: function () { + var map = this, + pageX, + pageY, + mouseMoved; + + this.container.mousemove(function (e) { + if (Math.abs(pageX - e.pageX) + Math.abs(pageY - e.pageY) > 2) { + mouseMoved = true; } - touchX = touches[0].pageX; - touchY = touches[0].pageY; - } else if (touches.length == 2) { - if (lastTouchesLength == 2) { - scale = Math.sqrt( - Math.pow(touches[0].pageX - touches[1].pageX, 2) + - Math.pow(touches[0].pageY - touches[1].pageY, 2) - ) / touchStartDistance; - map.setScale( - touchStartScale * scale, - centerTouchX, - centerTouchY - ) - map.tip.hide(); - e.preventDefault(); + }); + + /* Can not use common class selectors here because of the bug in jQuery + SVG handling, use with caution. */ + this.container.delegate("[class~='jvectormap-element']", 'mouseover mouseout', function (e) { + var baseVal = jvm.$(this).attr('class').baseVal || jvm.$(this).attr('class'), + type = baseVal.indexOf('jvectormap-region') === -1 ? baseVal.indexOf('jvectormap-line') === -1 ? 'marker' : 'line' : 'region', + code = type == 'region' ? jvm.$(this).attr('data-code') : jvm.$(this).attr('data-index'), + element = type == 'region' ? map.regions[code].element : type == 'line' ? map.lines[code].element : map.markers[code].element, + tipText = type == 'region' ? map.mapData.paths[code].name : type == 'line' ? (map.lines[code].config.text || '') : (map.markers[code].config.name || ''), + tipShowEvent = jvm.$.Event(type + 'TipShow.jvectormap'), + overEvent = jvm.$.Event(type + 'Over.jvectormap'); + + if (e.type == 'mouseover') { + map.container.trigger(overEvent, [code]); + if (!overEvent.isDefaultPrevented()) { + element.setHovered(true); + } + + map.tip.text(tipText); + map.container.trigger(tipShowEvent, [map.tip, code]); + if (!tipShowEvent.isDefaultPrevented()) { + map.tip.show(); + map.tipWidth = map.tip.width(); + map.tipHeight = map.tip.height(); + } } else { - offset = jvm.$(map.container).offset(); - if (touches[0].pageX > touches[1].pageX) { - centerTouchX = touches[1].pageX + (touches[0].pageX - touches[1].pageX) / 2; - } else { - centerTouchX = touches[0].pageX + (touches[1].pageX - touches[0].pageX) / 2; - } - if (touches[0].pageY > touches[1].pageY) { - centerTouchY = touches[1].pageY + (touches[0].pageY - touches[1].pageY) / 2; - } else { - centerTouchY = touches[0].pageY + (touches[1].pageY - touches[0].pageY) / 2; - } - centerTouchX -= offset.left; - centerTouchY -= offset.top; - touchStartScale = map.scale; - touchStartDistance = Math.sqrt( - Math.pow(touches[0].pageX - touches[1].pageX, 2) + - Math.pow(touches[0].pageY - touches[1].pageY, 2) - ); + element.setHovered(false); + map.tip.hide(); + map.container.trigger(type + 'Out.jvectormap', [code]); } - } - - lastTouchesLength = touches.length; - }; + }); - jvm.$(this.container).bind('touchstart', handleTouchEvent); - jvm.$(this.container).bind('touchmove', handleTouchEvent); - }, + /* Can not use common class selectors here because of the bug in jQuery + SVG handling, use with caution. */ + this.container.delegate("[class~='jvectormap-element']", 'mousedown', function (e) { + pageX = e.pageX; + pageY = e.pageY; + mouseMoved = false; + }); - bindContainerPointerEvents: function(){ - var map = this, - gesture = new MSGesture(), - element = this.container[0], - handlePointerDownEvent = function(e){ - gesture.addPointer(e.pointerId); - }, - handleGestureEvent = function(e){ - var offset, - scale, - transXOld, - transYOld; - - if (e.translationX != 0 || e.translationY != 0) { - transXOld = map.transX; - transYOld = map.transY; - map.transX += e.translationX / map.scale; - map.transY += e.translationY / map.scale; - map.applyTransform(); - map.tip.hide(); - if (transXOld != map.transX || transYOld != map.transY) { - e.preventDefault(); + /* Can not use common class selectors here because of the bug in jQuery + SVG handling, use with caution. */ + this.container.delegate("[class~='jvectormap-element']", 'mouseup', function () { + var baseVal = jvm.$(this).attr('class').baseVal ? jvm.$(this).attr('class').baseVal : jvm.$(this).attr('class'), + type = baseVal.indexOf('jvectormap-region') === -1 ? baseVal.indexOf('jvectormap-line') === -1 ? 'marker' : 'line' : 'region', + code = type == 'region' ? jvm.$(this).attr('data-code') : jvm.$(this).attr('data-index'), + clickEvent = jvm.$.Event(type + 'Click.jvectormap'), + element = type == 'region' ? map.regions[code].element : type == 'line' ? map.lines[code].element : map.markers[code].element; + + if (!mouseMoved) { + map.container.trigger(clickEvent, [code]); + if ((type === 'region' && map.params.regionsSelectable) || (type === 'marker' && map.params.markersSelectable) || (type === 'line' && map.params.linesSelectable)) { + if (!clickEvent.isDefaultPrevented()) { + if (map.params[type + 'sSelectableOne']) { + map.clearSelected(type + 's'); + } + element.setSelected(!element.isSelected); + } + } } - } - if (e.scale != 1) { - map.setScale( - map.scale * e.scale, - e.offsetX, - e.offsetY - ) - map.tip.hide(); - e.preventDefault(); - } - }; + }); + }, - gesture.target = element; - element.addEventListener("MSGestureChange", handleGestureEvent, false); - element.addEventListener("pointerdown", handlePointerDownEvent, false); - }, + bindZoomButtons: function () { + var map = this; - bindElementEvents: function(){ - var map = this, - pageX, - pageY, - mouseMoved; + jvm.$('
').addClass('jvectormap-zoomin').text('+').appendTo(this.container); + jvm.$('
').addClass('jvectormap-zoomout').html('−').appendTo(this.container); - this.container.mousemove(function(e){ - if (Math.abs(pageX - e.pageX) + Math.abs(pageY - e.pageY) > 2) { - mouseMoved = true; - } - }); + this.container.find('.jvectormap-zoomin').click(function () { + map.setScale(map.scale * map.params.zoomStep, map.width / 2, map.height / 2, false, map.params.zoomAnimate); + }); + this.container.find('.jvectormap-zoomout').click(function () { + map.setScale(map.scale / map.params.zoomStep, map.width / 2, map.height / 2, false, map.params.zoomAnimate); + }); + }, - /* Can not use common class selectors here because of the bug in jQuery - SVG handling, use with caution. */ - this.container.delegate("[class~='jvectormap-element']", 'mouseover mouseout', function(e){ - var baseVal = jvm.$(this).attr('class').baseVal || jvm.$(this).attr('class'), - type = baseVal.indexOf('jvectormap-region') === -1 ? 'marker' : 'region', - code = type == 'region' ? jvm.$(this).attr('data-code') : jvm.$(this).attr('data-index'), - element = type == 'region' ? map.regions[code].element : map.markers[code].element, - tipText = type == 'region' ? map.mapData.paths[code].name : (map.markers[code].config.name || ''), - tipShowEvent = jvm.$.Event(type+'TipShow.jvectormap'), - overEvent = jvm.$.Event(type+'Over.jvectormap'); - - if (e.type == 'mouseover') { - map.container.trigger(overEvent, [code]); - if (!overEvent.isDefaultPrevented()) { - element.setHovered(true); - } + createTip: function () { + var map = this; - map.tip.text(tipText); - map.container.trigger(tipShowEvent, [map.tip, code]); - if (!tipShowEvent.isDefaultPrevented()) { - map.tip.show(); - map.tipWidth = map.tip.width(); - map.tipHeight = map.tip.height(); + this.tip = jvm.$('
').addClass('jvectormap-tip').appendTo(jvm.$('body')); + + this.container.mousemove(function (e) { + var left = e.pageX - 15 - map.tipWidth, + top = e.pageY - 15 - map.tipHeight; + + if (left < 5) { + left = e.pageX + 15; + } + if (top < 5) { + top = e.pageY + 15; + } + + map.tip.css({ + left: left, + top: top + }); + }); + }, + + setScale: function (scale, anchorX, anchorY, isCentered, animate) { + var viewportChangeEvent = jvm.$.Event('zoom.jvectormap'), + interval, + that = this, + i = 0, + count = Math.abs(Math.round((scale - this.scale) * 60 / Math.max(scale, this.scale))), + scaleStart, + scaleDiff, + transXStart, + transXDiff, + transYStart, + transYDiff, + transX, + transY, + deferred = new jvm.$.Deferred(); + + if (scale > this.params.zoomMax * this.baseScale) { + scale = this.params.zoomMax * this.baseScale; + } else if (scale < this.params.zoomMin * this.baseScale) { + scale = this.params.zoomMin * this.baseScale; } - } else { - element.setHovered(false); - map.tip.hide(); - map.container.trigger(type+'Out.jvectormap', [code]); - } - }); - - /* Can not use common class selectors here because of the bug in jQuery - SVG handling, use with caution. */ - this.container.delegate("[class~='jvectormap-element']", 'mousedown', function(e){ - pageX = e.pageX; - pageY = e.pageY; - mouseMoved = false; - }); - /* Can not use common class selectors here because of the bug in jQuery - SVG handling, use with caution. */ - this.container.delegate("[class~='jvectormap-element']", 'mouseup', function(){ - var baseVal = jvm.$(this).attr('class').baseVal ? jvm.$(this).attr('class').baseVal : jvm.$(this).attr('class'), - type = baseVal.indexOf('jvectormap-region') === -1 ? 'marker' : 'region', - code = type == 'region' ? jvm.$(this).attr('data-code') : jvm.$(this).attr('data-index'), - clickEvent = jvm.$.Event(type+'Click.jvectormap'), - element = type == 'region' ? map.regions[code].element : map.markers[code].element; - - if (!mouseMoved) { - map.container.trigger(clickEvent, [code]); - if ((type === 'region' && map.params.regionsSelectable) || (type === 'marker' && map.params.markersSelectable)) { - if (!clickEvent.isDefaultPrevented()) { - if (map.params[type+'sSelectableOne']) { - map.clearSelected(type+'s'); + if (typeof anchorX != 'undefined' && typeof anchorY != 'undefined') { + zoomStep = scale / this.scale; + if (isCentered) { + transX = anchorX + this.defaultWidth * (this.width / (this.defaultWidth * scale)) / 2; + transY = anchorY + this.defaultHeight * (this.height / (this.defaultHeight * scale)) / 2; + } else { + transX = this.transX - (zoomStep - 1) / scale * anchorX; + transY = this.transY - (zoomStep - 1) / scale * anchorY; } - element.setSelected(!element.isSelected); - } } - } - }); - }, - bindZoomButtons: function() { - var map = this; + if (animate && count > 0) { + scaleStart = this.scale; + scaleDiff = (scale - scaleStart) / count; + transXStart = this.transX * this.scale; + transYStart = this.transY * this.scale; + transXDiff = (transX * scale - transXStart) / count; + transYDiff = (transY * scale - transYStart) / count; + interval = setInterval(function () { + i += 1; + that.scale = scaleStart + scaleDiff * i; + that.transX = (transXStart + transXDiff * i) / that.scale; + that.transY = (transYStart + transYDiff * i) / that.scale; + that.applyTransform(); + if (i == count) { + clearInterval(interval); + that.container.trigger(viewportChangeEvent, [scale / that.baseScale]); + deferred.resolve(); + } + }, 10); + } else { + this.transX = transX; + this.transY = transY; + this.scale = scale; + this.applyTransform(); + this.container.trigger(viewportChangeEvent, [scale / this.baseScale]); + deferred.resolve(); + } - jvm.$('
').addClass('jvectormap-zoomin').text('+').appendTo(this.container); - jvm.$('
').addClass('jvectormap-zoomout').html('−').appendTo(this.container); + return deferred; + }, - this.container.find('.jvectormap-zoomin').click(function(){ - map.setScale(map.scale * map.params.zoomStep, map.width / 2, map.height / 2, false, map.params.zoomAnimate); - }); - this.container.find('.jvectormap-zoomout').click(function(){ - map.setScale(map.scale / map.params.zoomStep, map.width / 2, map.height / 2, false, map.params.zoomAnimate); - }); - }, + /** + * 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. + * @param This method takes a configuration object as the single argument. The options passed to it are the following: + * @param {Array} params.regions Array of region codes to zoom to. + * @param {String} params.region Region code to zoom to. + * @param {Number} params.scale Map scale to set. + * @param {Number} params.lat Latitude to set viewport to. + * @param {Number} params.lng Longitude to set viewport to. + * @param {Number} params.x Number from 0 to 1 specifying the horizontal coordinate of the central point of the viewport. + * @param {Number} params.y Number from 0 to 1 specifying the vertical coordinate of the central point of the viewport. + * @param {Boolean} params.animate Indicates whether or not to animate the scale change and transition. + */ + setFocus: function (config) { + var bbox, + itemBbox, + newBbox, + codes, + i, + point; + + config = config || {}; + + if (config.region) { + codes = [config.region]; + } else if (config.regions) { + codes = config.regions; + } - createTip: function(){ - var map = this; + if (codes) { + for (i = 0; i < codes.length; i++) { + if (this.regions[codes[i]]) { + itemBbox = this.regions[codes[i]].element.shape.getBBox(); + if (itemBbox) { + if (typeof bbox == 'undefined') { + bbox = itemBbox; + } else { + newBbox = { + x: Math.min(bbox.x, itemBbox.x), + y: Math.min(bbox.y, itemBbox.y), + width: Math.max(bbox.x + bbox.width, itemBbox.x + itemBbox.width) - Math.min(bbox.x, itemBbox.x), + height: Math.max(bbox.y + bbox.height, itemBbox.y + itemBbox.height) - Math.min(bbox.y, itemBbox.y) + } + bbox = newBbox; + } + } + } + } + return this.setScale( + Math.min(this.width / bbox.width, this.height / bbox.height), + -(bbox.x + bbox.width / 2), + -(bbox.y + bbox.height / 2), + true, + config.animate + ); + } else { + if (config.lat && config.lng) { + point = this.latLngToPoint(config.lat, config.lng); + config.x = this.transX - point.x / this.scale; + config.y = this.transY - point.y / this.scale; + } else if (config.x && config.y) { + config.x *= -this.defaultWidth; + config.y *= -this.defaultHeight; + } + return this.setScale(config.scale * this.baseScale, config.x, config.y, true, config.animate); + } + }, - this.tip = jvm.$('
').addClass('jvectormap-tip').appendTo(jvm.$('body')); + getSelected: function (type) { + var key, + selected = []; - this.container.mousemove(function(e){ - var left = e.pageX-15-map.tipWidth, - top = e.pageY-15-map.tipHeight; + for (key in this[type]) { + if (this[type][key].element.isSelected) { + selected.push(key); + } + } + return selected; + }, - if (left < 5) { - left = e.pageX + 15; - } - if (top < 5) { - top = e.pageY + 15; - } + /** + * Return the codes of currently selected regions. + * @returns {Array} + */ + getSelectedRegions: function () { + return this.getSelected('regions'); + }, - map.tip.css({ - left: left, - top: top - }); - }); - }, + /** + * Return the codes of currently selected markers. + * @returns {Array} + */ + getSelectedMarkers: function () { + return this.getSelected('markers'); + }, + getSelectedLines: function(){ + return this.getSelected('lines'); + }, - setScale: function(scale, anchorX, anchorY, isCentered, animate) { - var viewportChangeEvent = jvm.$.Event('zoom.jvectormap'), - interval, - that = this, - i = 0, - count = Math.abs(Math.round((scale - this.scale) * 60 / Math.max(scale, this.scale))), - scaleStart, - scaleDiff, - transXStart, - transXDiff, - transYStart, - transYDiff, - transX, - transY, - deferred = new jvm.$.Deferred(); - - if (scale > this.params.zoomMax * this.baseScale) { - scale = this.params.zoomMax * this.baseScale; - } else if (scale < this.params.zoomMin * this.baseScale) { - scale = this.params.zoomMin * this.baseScale; - } + setSelected: function (type, keys) { + var i; - if (typeof anchorX != 'undefined' && typeof anchorY != 'undefined') { - zoomStep = scale / this.scale; - if (isCentered) { - transX = anchorX + this.defaultWidth * (this.width / (this.defaultWidth * scale)) / 2; - transY = anchorY + this.defaultHeight * (this.height / (this.defaultHeight * scale)) / 2; - } else { - transX = this.transX - (zoomStep - 1) / scale * anchorX; - transY = this.transY - (zoomStep - 1) / scale * anchorY; - } - } + if (typeof keys != 'object') { + keys = [keys]; + } - if (animate && count > 0) { - scaleStart = this.scale; - scaleDiff = (scale - scaleStart) / count; - transXStart = this.transX * this.scale; - transYStart = this.transY * this.scale; - transXDiff = (transX * scale - transXStart) / count; - transYDiff = (transY * scale - transYStart) / count; - interval = setInterval(function(){ - i += 1; - that.scale = scaleStart + scaleDiff * i; - that.transX = (transXStart + transXDiff * i) / that.scale; - that.transY = (transYStart + transYDiff * i) / that.scale; - that.applyTransform(); - if (i == count) { - clearInterval(interval); - that.container.trigger(viewportChangeEvent, [scale/that.baseScale]); - deferred.resolve(); + if (jvm.$.isArray(keys)) { + for (i = 0; i < keys.length; i++) { + this[type][keys[i]].element.setSelected(true); + } + } else { + for (i in keys) { + this[type][i].element.setSelected(!!keys[i]); + } } - }, 10); - } else { - this.transX = transX; - this.transY = transY; - this.scale = scale; - this.applyTransform(); - this.container.trigger(viewportChangeEvent, [scale/this.baseScale]); - deferred.resolve(); - } + }, - return deferred; - }, + /** + * Set or remove selected state for the regions. + * @param {String|Array|Object} keys If String or Array the region(s) with the corresponding code(s) will be selected. If Object 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. + */ + setSelectedRegions: function (keys) { + this.setSelected('regions', keys); + }, - /** - * 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. - * @param This method takes a configuration object as the single argument. The options passed to it are the following: - * @param {Array} params.regions Array of region codes to zoom to. - * @param {String} params.region Region code to zoom to. - * @param {Number} params.scale Map scale to set. - * @param {Number} params.lat Latitude to set viewport to. - * @param {Number} params.lng Longitude to set viewport to. - * @param {Number} params.x Number from 0 to 1 specifying the horizontal coordinate of the central point of the viewport. - * @param {Number} params.y Number from 0 to 1 specifying the vertical coordinate of the central point of the viewport. - * @param {Boolean} params.animate Indicates whether or not to animate the scale change and transition. - */ - setFocus: function(config){ - var bbox, - itemBbox, - newBbox, - codes, - i, - point; - - config = config || {}; - - if (config.region) { - codes = [config.region]; - } else if (config.regions) { - codes = config.regions; - } + /** + * Set or remove selected state for the markers. + * @param {String|Array|Object} keys If String or Array the marker(s) with the corresponding code(s) will be selected. If Object 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. + */ + setSelectedMarkers: function (keys) { + this.setSelected('markers', keys); + }, - if (codes) { - for (i = 0; i < codes.length; i++) { - if (this.regions[codes[i]]) { - itemBbox = this.regions[codes[i]].element.shape.getBBox(); - if (itemBbox) { - if (typeof bbox == 'undefined') { - bbox = itemBbox; - } else { - newBbox = { - x: Math.min(bbox.x, itemBbox.x), - y: Math.min(bbox.y, itemBbox.y), - width: Math.max(bbox.x + bbox.width, itemBbox.x + itemBbox.width) - Math.min(bbox.x, itemBbox.x), - height: Math.max(bbox.y + bbox.height, itemBbox.y + itemBbox.height) - Math.min(bbox.y, itemBbox.y) - } - bbox = newBbox; - } - } + setSelectedLines: function (keys) { + this.setSelected('lines', keys); + }, + + clearSelected: function (type) { + var select = {}, + selected = this.getSelected(type), + i; + + for (i = 0; i < selected.length; i++) { + select[selected[i]] = false; } - } - return this.setScale( - Math.min(this.width / bbox.width, this.height / bbox.height), - - (bbox.x + bbox.width / 2), - - (bbox.y + bbox.height / 2), - true, - config.animate - ); - } else { - if (config.lat && config.lng) { - point = this.latLngToPoint(config.lat, config.lng); - config.x = this.transX - point.x / this.scale; - config.y = this.transY - point.y / this.scale; - } else if (config.x && config.y) { - config.x *= -this.defaultWidth; - config.y *= -this.defaultHeight; - } - return this.setScale(config.scale * this.baseScale, config.x, config.y, true, config.animate); - } - }, + ; - getSelected: function(type){ - var key, - selected = []; + this.setSelected(type, select); + }, - for (key in this[type]) { - if (this[type][key].element.isSelected) { - selected.push(key); - } - } - return selected; - }, + /** + * Remove the selected state from all the currently selected regions. + */ + clearSelectedRegions: function () { + this.clearSelected('regions'); + }, - /** - * Return the codes of currently selected regions. - * @returns {Array} - */ - getSelectedRegions: function(){ - return this.getSelected('regions'); - }, + /** + * Remove the selected state from all the currently selected markers. + */ + clearSelectedMarkers: function () { + this.clearSelected('markers'); + }, - /** - * Return the codes of currently selected markers. - * @returns {Array} - */ - getSelectedMarkers: function(){ - return this.getSelected('markers'); - }, + clearSelectedLines: function () { + this.clearSelected('lines'); + }, - setSelected: function(type, keys){ - var i; + /** + * Return the instance of Map. Useful when instantiated as a jQuery plug-in. + * @returns {Map} + */ + getMapObject: function () { + return this; + }, - if (typeof keys != 'object') { - keys = [keys]; - } + /** + * Return the name of the region by region code. + * @returns {String} + */ + getRegionName: function (code) { + return this.mapData.paths[code].name; + }, - if (jvm.$.isArray(keys)) { - for (i = 0; i < keys.length; i++) { - this[type][keys[i]].element.setSelected(true); - } - } else { - for (i in keys) { - this[type][i].element.setSelected(!!keys[i]); - } - } - }, + createRegions: function () { + var key, + region, + map = this; + + this.regionLabelsGroup = this.regionLabelsGroup || this.canvas.addGroup(); + + for (key in this.mapData.paths) { + region = new jvm.Region({ + map: this, + path: this.mapData.paths[key].path, + code: key, + style: jvm.$.extend(true, {}, this.params.regionStyle), + labelStyle: jvm.$.extend(true, {}, this.params.regionLabelStyle), + canvas: this.canvas, + labelsGroup: this.regionLabelsGroup, + label: this.canvas.mode != 'vml' ? (this.params.labels && this.params.labels.regions) : null + }); + + jvm.$(region.shape).bind('selected', function (e, isSelected) { + map.container.trigger('regionSelected.jvectormap', [jvm.$(this.node).attr('data-code'), isSelected, map.getSelectedRegions()]); + }); + this.regions[key] = { + element: region, + config: this.mapData.paths[key] + }; + } + }, - /** - * Set or remove selected state for the regions. - * @param {String|Array|Object} keys If String or Array the region(s) with the corresponding code(s) will be selected. If Object 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. - */ - setSelectedRegions: function(keys){ - this.setSelected('regions', keys); - }, + createMarkers: function (markers) { + var i, + marker, + point, + markerConfig, + markersArray, + map = this; + + this.markersGroup = this.markersGroup || this.canvas.addGroup(); + this.markerLabelsGroup = this.markerLabelsGroup || this.canvas.addGroup(); + + if (jvm.$.isArray(markers)) { + markersArray = markers.slice(); + markers = {}; + for (i = 0; i < markersArray.length; i++) { + markers[i] = markersArray[i]; + } + } - /** - * Set or remove selected state for the markers. - * @param {String|Array|Object} keys If String or Array the marker(s) with the corresponding code(s) will be selected. If Object 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. - */ - setSelectedMarkers: function(keys){ - this.setSelected('markers', keys); - }, + for (i in markers) { + markerConfig = markers[i] instanceof Array ? {latLng: markers[i]} : markers[i]; + point = this.getMarkerPosition(markerConfig); + + if (point !== false) { + marker = new jvm.Marker({ + map: this, + style: jvm.$.extend(true, {}, this.params.markerStyle, {initial: markerConfig.style || {}}), + labelStyle: jvm.$.extend(true, {}, this.params.markerLabelStyle), + index: i, + cx: point.x, + cy: point.y, + group: this.markersGroup, + canvas: this.canvas, + labelsGroup: this.markerLabelsGroup, + label: this.canvas.mode != 'vml' ? (this.params.labels && this.params.labels.markers) : null + }); + + jvm.$(marker.shape).bind('selected', function (e, isSelected) { + map.container.trigger('markerSelected.jvectormap', [jvm.$(this.node).attr('data-index'), isSelected, map.getSelectedMarkers()]); + }); + if (this.markers[i]) { + this.removeMarkers([i]); + } + this.markers[i] = {element: marker, config: markerConfig}; + } + } + }, - clearSelected: function(type){ - var select = {}, - selected = this.getSelected(type), - i; + createLines: function (lines) { + var i, + line, + path, + lineConfig, + linesArray, + map = this; + + this.linesGroup = this.linesGroup || this.canvas.addGroup(); + this.lineLabelsGroup = this.lineLabelsGroup || this.canvas.addGroup(); + + if (jvm.$.isArray(lines)) { + linesArray = lines.slice(); + lines = {}; + for (i = 0; i < linesArray.length; i++) { + lines[i] = linesArray[i]; + } + } - for (i = 0; i < selected.length; i++) { - select[selected[i]] = false; - }; + for (i in lines) { + lineConfig = lines[i] instanceof Array ? {points: lines[i]} : lines[i]; + path = this.getLinePath(lineConfig); - this.setSelected(type, select); - }, + if (lineConfig.points[0] != null) { + startPoint = this.latLngToPoint.apply(this, lineConfig.points[0]); + //startY = lineConfig.points[0][1]; + } - /** - * Remove the selected state from all the currently selected regions. - */ - clearSelectedRegions: function(){ - this.clearSelected('regions'); - }, + if (path !== false) { + line = new jvm.Line({ + map: this, + style: jvm.$.extend(true, {}, this.params.lineStyle, {initial: lineConfig.style || {}}), + labelStyle: jvm.$.extend(true, {}, this.params.lineLabelStyle), + index: i, + path: path, + sx: startPoint.x, + sy: startPoint.y, + group: this.linesGroup, + canvas: this.canvas, + labelsGroup: this.lineLabelsGroup, + label: this.canvas.mode != 'vml' ? (this.params.labels && this.params.labels.lines) : null + }); + } - /** - * Remove the selected state from all the currently selected markers. - */ - clearSelectedMarkers: function(){ - this.clearSelected('markers'); - }, + jvm.$(line.shape).bind('selected', function (e, isSelected) { + map.container.trigger('lineSelected.jvectormap', [jvm.$(this.node).attr('data-index'), isSelected, map.getSelectedLines()]); + }); + if (this.lines[i]) { + this.removeLines([i]); + } + this.lines[i] = {element: line, config: lineConfig}; - /** - * Return the instance of Map. Useful when instantiated as a jQuery plug-in. - * @returns {Map} - */ - getMapObject: function(){ - return this; - }, + } + }, - /** - * Return the name of the region by region code. - * @returns {String} - */ - getRegionName: function(code){ - return this.mapData.paths[code].name; - }, + repositionMarkers: function () { + var i, + point; - createRegions: function(){ - var key, - region, - map = this; - - this.regionLabelsGroup = this.regionLabelsGroup || this.canvas.addGroup(); - - for (key in this.mapData.paths) { - region = new jvm.Region({ - map: this, - path: this.mapData.paths[key].path, - code: key, - style: jvm.$.extend(true, {}, this.params.regionStyle), - labelStyle: jvm.$.extend(true, {}, this.params.regionLabelStyle), - canvas: this.canvas, - labelsGroup: this.regionLabelsGroup, - label: this.canvas.mode != 'vml' ? (this.params.labels && this.params.labels.regions) : null - }); - - jvm.$(region.shape).bind('selected', function(e, isSelected){ - map.container.trigger('regionSelected.jvectormap', [jvm.$(this.node).attr('data-code'), isSelected, map.getSelectedRegions()]); - }); - this.regions[key] = { - element: region, - config: this.mapData.paths[key] - }; - } - }, + for (i in this.markers) { + point = this.getMarkerPosition(this.markers[i].config); + if (point !== false) { + this.markers[i].element.setStyle({cx: point.x, cy: point.y}); + } + } + }, - createMarkers: function(markers) { - var i, - marker, - point, - markerConfig, - markersArray, - map = this; - - this.markersGroup = this.markersGroup || this.canvas.addGroup(); - this.markerLabelsGroup = this.markerLabelsGroup || this.canvas.addGroup(); - - if (jvm.$.isArray(markers)) { - markersArray = markers.slice(); - markers = {}; - for (i = 0; i < markersArray.length; i++) { - markers[i] = markersArray[i]; - } - } + repositionLabels: function () { + var key; - for (i in markers) { - markerConfig = markers[i] instanceof Array ? {latLng: markers[i]} : markers[i]; - point = this.getMarkerPosition( markerConfig ); - - if (point !== false) { - marker = new jvm.Marker({ - map: this, - style: jvm.$.extend(true, {}, this.params.markerStyle, {initial: markerConfig.style || {}}), - labelStyle: jvm.$.extend(true, {}, this.params.markerLabelStyle), - index: i, - cx: point.x, - cy: point.y, - group: this.markersGroup, - canvas: this.canvas, - labelsGroup: this.markerLabelsGroup, - label: this.canvas.mode != 'vml' ? (this.params.labels && this.params.labels.markers) : null - }); + for (key in this.regions) { + this.regions[key].element.updateLabelPosition(); + } - jvm.$(marker.shape).bind('selected', function(e, isSelected){ - map.container.trigger('markerSelected.jvectormap', [jvm.$(this.node).attr('data-index'), isSelected, map.getSelectedMarkers()]); - }); - if (this.markers[i]) { - this.removeMarkers([i]); + for (key in this.markers) { + this.markers[key].element.updateLabelPosition(); } - this.markers[i] = {element: marker, config: markerConfig}; - } - } - }, - repositionMarkers: function() { - var i, - point; + for (key in this.lines) { + this.lines[key].element.updateLabelPosition(); + } - for (i in this.markers) { - point = this.getMarkerPosition( this.markers[i].config ); - if (point !== false) { - this.markers[i].element.setStyle({cx: point.x, cy: point.y}); - } - } - }, + }, - repositionLabels: function() { - var key; + repositionLines: function () { + var i, point; + for (i in this.lines) { + path = this.getLinePath(this.lines[i].config); + if (path !== false) { + this.lines[i].element.setStyle({d: path}); + } + } + }, - for (key in this.regions) { - this.regions[key].element.updateLabelPosition(); - } + getMarkerPosition: function (markerConfig) { + if (jvm.Map.maps[this.params.map].projection) { + return this.latLngToPoint.apply(this, markerConfig.latLng || [0, 0]); + } else { + return { + x: markerConfig.coords[0] * this.scale + this.transX * this.scale, + y: markerConfig.coords[1] * this.scale + this.transY * this.scale + }; + } + }, - for (key in this.markers) { - this.markers[key].element.updateLabelPosition(); - } - }, + getLinePath: function (lineConfig) { + var i; + if (jvm.Map.maps[this.params.map].projection) { + return this.latLngToPath(lineConfig.points || [[0, 0], [0, 0]]); + } else { + var transferedPoints; + //TODO Which format should be here? Should it be SVG style (M10.1,5.1 L1.1,3.0) or just an Array? + for (i = 0; i < lineConfig.points.length; i++) { + transferedPoints[i][0] = lineConfig.points[i][0] * this.scale + this.transX * this.scale; + transferedPoints[i][1] = lineConfig.points[i][1] * this.scale + this.transY * this.scale; + } + return transferedPoints; + } + }, - getMarkerPosition: function(markerConfig) { - if (jvm.Map.maps[this.params.map].projection) { - return this.latLngToPoint.apply(this, markerConfig.latLng || [0, 0]); - } else { - return { - x: markerConfig.coords[0]*this.scale + this.transX*this.scale, - y: markerConfig.coords[1]*this.scale + this.transY*this.scale - }; - } - }, + /** + * Add one marker to the map. + * @param {String} key Marker unique code. + * @param {Object} marker Marker configuration parameters. + * @param {Array} seriesData Values to add to the data series. + */ + addMarker: function (key, marker, seriesData) { + var markers = {}, + data = [], + values, + i, + seriesData = seriesData || []; + + markers[key] = marker; + + for (i = 0; i < seriesData.length; i++) { + values = {}; + if (typeof seriesData[i] !== 'undefined') { + values[key] = seriesData[i]; + } + data.push(values); + } + this.addMarkers(markers, data); + }, + + /** + * Add set of marker to the map. + * @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. + * @param {Array} seriesData Values to add to the data series. + */ + addMarkers: function (markers, seriesData) { + var i; - /** - * Add one marker to the map. - * @param {String} key Marker unique code. - * @param {Object} marker Marker configuration parameters. - * @param {Array} seriesData Values to add to the data series. - */ - addMarker: function(key, marker, seriesData){ - var markers = {}, - data = [], - values, - i, seriesData = seriesData || []; - markers[key] = marker; + this.createMarkers(markers); + for (i = 0; i < seriesData.length; i++) { + this.series.markers[i].setValues(seriesData[i] || {}); + } + ; + }, - for (i = 0; i < seriesData.length; i++) { - values = {}; - if (typeof seriesData[i] !== 'undefined') { - values[key] = seriesData[i]; - } - data.push(values); - } - this.addMarkers(markers, data); - }, + /** + * Remove some markers from the map. + * @param {Array} markers Array of marker codes to be removed. + */ + removeMarkers: function (markers) { + var i; - /** - * Add set of marker to the map. - * @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. - * @param {Array} seriesData Values to add to the data series. - */ - addMarkers: function(markers, seriesData){ - var i; + for (i = 0; i < markers.length; i++) { + this.markers[markers[i]].element.remove(); + delete this.markers[markers[i]]; + } + ; + }, - seriesData = seriesData || []; + removeLines: function (lines) { + var i; + for (i = 0; i < lines.length; i++) { + this.lines[lines[i]].element.remove(); + delete this.lines[lines[i]]; + } + }, - this.createMarkers(markers); - for (i = 0; i < seriesData.length; i++) { - this.series.markers[i].setValues(seriesData[i] || {}); - }; - }, + /** + * Remove all markers from the map. + */ + removeAllMarkers: function () { + var i, + markers = []; - /** - * Remove some markers from the map. - * @param {Array} markers Array of marker codes to be removed. - */ - removeMarkers: function(markers){ - var i; - - for (i = 0; i < markers.length; i++) { - this.markers[ markers[i] ].element.remove(); - delete this.markers[ markers[i] ]; - }; - }, + for (i in this.markers) { + markers.push(i); + } + this.removeMarkers(markers) + }, - /** - * Remove all markers from the map. - */ - removeAllMarkers: function(){ - var i, - markers = []; + removeAllLines: function () { + var i, + lines = []; - for (i in this.markers) { - markers.push(i); - } - this.removeMarkers(markers) - }, + for (i in this.markelinesrs) { + lines.push(i); + } + this.removeLines(lines) + }, + /** + * Converts coordinates expressed as latitude and longitude to the coordinates in pixels on the map. + * @param {Number} lat Latitide of point in degrees. + * @param {Number} lng Longitude of point in degrees. + */ + latLngToPoint: function (lat, lng) { + var point, + proj = jvm.Map.maps[this.params.map].projection, + centralMeridian = proj.centralMeridian, + inset, + bbox; + + if (lng < (-180 + centralMeridian)) { + lng += 360; + } - /** - * Converts coordinates expressed as latitude and longitude to the coordinates in pixels on the map. - * @param {Number} lat Latitide of point in degrees. - * @param {Number} lng Longitude of point in degrees. - */ - latLngToPoint: function(lat, lng) { - var point, - proj = jvm.Map.maps[this.params.map].projection, - centralMeridian = proj.centralMeridian, - inset, - bbox; - - if (lng < (-180 + centralMeridian)) { - lng += 360; - } + point = jvm.Proj[proj.type](lat, lng, centralMeridian); - point = jvm.Proj[proj.type](lat, lng, centralMeridian); + inset = this.getInsetForPoint(point.x, point.y); + if (inset) { + bbox = inset.bbox; - inset = this.getInsetForPoint(point.x, point.y); - if (inset) { - bbox = inset.bbox; + point.x = (point.x - bbox[0].x) / (bbox[1].x - bbox[0].x) * inset.width * this.scale; + point.y = (point.y - bbox[0].y) / (bbox[1].y - bbox[0].y) * inset.height * this.scale; - point.x = (point.x - bbox[0].x) / (bbox[1].x - bbox[0].x) * inset.width * this.scale; - point.y = (point.y - bbox[0].y) / (bbox[1].y - bbox[0].y) * inset.height * this.scale; + return { + x: point.x + this.transX * this.scale + inset.left * this.scale, + y: point.y + this.transY * this.scale + inset.top * this.scale + }; + } else { + return false; + } + }, + /** + * Converts the array of Lat and Long coordinates to the coordinates in pixels of the map. The generated path is in + * SVG format (e.g.: M100.01,10.10L200.1,20.20). + * @param {Array} points lat and long of all points where path is curved. + * @returns {*} SVG format of the path converted in pixels already. + */ + latLngToPath: function (points) { + var i; + var transferedPoints = []; + if (points) { + if (points.length > 0) { + for (i = 0; i < points.length; i++) { + transferedPoints[i] = (this.latLngToPoint(points[i][0], points[i][1])); + } + var path; + path = "M"; + for (i = 0; i < transferedPoints.length; i++) { + if (i > 0) { + path += "L"; + } + path += transferedPoints[i].x + "," + transferedPoints[i].y; + + } + return path; + } else { + return null; + } + } else { + return null; + } + }, - return { - x: point.x + this.transX*this.scale + inset.left*this.scale, - y: point.y + this.transY*this.scale + inset.top*this.scale - }; - } else { - return false; - } - }, + /** + * Converts cartesian coordinates into coordinates expressed as latitude and longitude. + * @param {Number} x X-axis of point on map in pixels. + * @param {Number} y Y-axis of point on map in pixels. + */ + pointToLatLng: function (x, y) { + var proj = jvm.Map.maps[this.params.map].projection, + centralMeridian = proj.centralMeridian, + insets = jvm.Map.maps[this.params.map].insets, + i, + inset, + bbox, + nx, + ny; + + for (i = 0; i < insets.length; i++) { + inset = insets[i]; + bbox = inset.bbox; + + nx = x - (this.transX * this.scale + inset.left * this.scale); + ny = y - (this.transY * this.scale + inset.top * this.scale); + + nx = (nx / (inset.width * this.scale)) * (bbox[1].x - bbox[0].x) + bbox[0].x; + ny = (ny / (inset.height * this.scale)) * (bbox[1].y - bbox[0].y) + bbox[0].y; + + if (nx > bbox[0].x && nx < bbox[1].x && ny > bbox[0].y && ny < bbox[1].y) { + return jvm.Proj[proj.type + '_inv'](nx, -ny, centralMeridian); + } + } - /** - * Converts cartesian coordinates into coordinates expressed as latitude and longitude. - * @param {Number} x X-axis of point on map in pixels. - * @param {Number} y Y-axis of point on map in pixels. - */ - pointToLatLng: function(x, y) { - var proj = jvm.Map.maps[this.params.map].projection, - centralMeridian = proj.centralMeridian, - insets = jvm.Map.maps[this.params.map].insets, - i, - inset, - bbox, - nx, - ny; - - for (i = 0; i < insets.length; i++) { - inset = insets[i]; - bbox = inset.bbox; - - nx = x - (this.transX*this.scale + inset.left*this.scale); - ny = y - (this.transY*this.scale + inset.top*this.scale); - - nx = (nx / (inset.width * this.scale)) * (bbox[1].x - bbox[0].x) + bbox[0].x; - ny = (ny / (inset.height * this.scale)) * (bbox[1].y - bbox[0].y) + bbox[0].y; - - if (nx > bbox[0].x && nx < bbox[1].x && ny > bbox[0].y && ny < bbox[1].y) { - return jvm.Proj[proj.type + '_inv'](nx, -ny, centralMeridian); - } + return false; } + , - return false; - }, - - getInsetForPoint: function(x, y){ - var insets = jvm.Map.maps[this.params.map].insets, - i, - bbox; + getInsetForPoint: function (x, y) { + var insets = jvm.Map.maps[this.params.map].insets, + i, + bbox; - for (i = 0; i < insets.length; i++) { - bbox = insets[i].bbox; - if (x > bbox[0].x && x < bbox[1].x && y > bbox[0].y && y < bbox[1].y) { - return insets[i]; - } + for (i = 0; i < insets.length; i++) { + bbox = insets[i].bbox; + if (x > bbox[0].x && x < bbox[1].x && y > bbox[0].y && y < bbox[1].y) { + return insets[i]; + } + } } - }, + , - createSeries: function(){ - var i, - key; - - this.series = { - markers: [], - regions: [] - }; - - for (key in this.params.series) { - for (i = 0; i < this.params.series[key].length; i++) { - this.series[key][i] = new jvm.DataSeries( - this.params.series[key][i], - this[key], - this - ); - } - } - }, + createSeries: function () { + var i, + key; - /** - * Gracefully remove the map and and all its accessories, unbind event handlers. - */ - remove: function(){ - this.tip.remove(); - this.container.remove(); - jvm.$(window).unbind('resize', this.onResize); - jvm.$('body').unbind('mouseup', this.onContainerMouseUp); - } -}; + this.series = { + markers: [], + regions: [], + lines: [] + }; + + for (key in this.params.series) { + for (i = 0; i < this.params.series[key].length; i++) { + this.series[key][i] = new jvm.DataSeries( + this.params.series[key][i], + this[key], + this + ); + } + } + } + , + + /** + * Gracefully remove the map and and all its accessories, unbind event handlers. + */ + remove: function () { + this.tip.remove(); + this.container.remove(); + jvm.$(window).unbind('resize', this.onResize); + jvm.$('body').unbind('mouseup', this.onContainerMouseUp); + } +} +; jvm.Map.maps = {}; jvm.Map.defaultParams = { - map: 'world_mill_en', - backgroundColor: '#505050', - zoomButtons: true, - zoomOnScroll: true, - zoomOnScrollSpeed: 3, - panOnDrag: true, - zoomMax: 8, - zoomMin: 1, - zoomStep: 1.6, - zoomAnimate: true, - regionsSelectable: false, - markersSelectable: false, - bindTouchEvents: true, - regionStyle: { - initial: { - fill: 'white', - "fill-opacity": 1, - stroke: 'none', - "stroke-width": 0, - "stroke-opacity": 1 - }, - hover: { - "fill-opacity": 0.8, - cursor: 'pointer' - }, - selected: { - fill: 'yellow' - }, - selectedHover: { - } - }, - regionLabelStyle: { - initial: { - 'font-family': 'Verdana', - 'font-size': '12', - 'font-weight': 'bold', - cursor: 'default', - fill: 'black' + map: 'world_mill_en', + backgroundColor: '#505050', + zoomButtons: true, + zoomOnScroll: true, + zoomOnScrollSpeed: 3, + panOnDrag: true, + zoomMax: 8, + zoomMin: 1, + zoomStep: 1.6, + zoomAnimate: true, + regionsSelectable: false, + markersSelectable: false, + linesSelectable: false, + bindTouchEvents: true, + regionStyle: { + initial: { + fill: 'white', + "fill-opacity": 1, + stroke: 'none', + "stroke-width": 0, + "stroke-opacity": 1 + }, + hover: { + "fill-opacity": 0.8, + cursor: 'pointer' + }, + selected: { + fill: 'yellow' + }, + selectedHover: {} }, - hover: { - cursor: 'pointer' - } - }, - markerStyle: { - initial: { - fill: 'grey', - stroke: '#505050', - "fill-opacity": 1, - "stroke-width": 1, - "stroke-opacity": 1, - r: 5 + regionLabelStyle: { + initial: { + 'font-family': 'Verdana', + 'font-size': '12', + 'font-weight': 'bold', + cursor: 'default', + fill: 'black' + }, + hover: { + cursor: 'pointer' + } }, - hover: { - stroke: 'black', - "stroke-width": 2, - cursor: 'pointer' + markerStyle: { + initial: { + fill: 'grey', + stroke: '#505050', + "fill-opacity": 1, + "stroke-width": 1, + "stroke-opacity": 1, + r: 5 + }, + hover: { + stroke: 'black', + "stroke-width": 2, + cursor: 'pointer' + }, + selected: { + fill: 'blue' + }, + selectedHover: {} }, - selected: { - fill: 'blue' + markerLabelStyle: { + initial: { + 'font-family': 'Verdana', + 'font-size': '12', + 'font-weight': 'bold', + cursor: 'default', + fill: 'black' + }, + hover: { + cursor: 'pointer' + } }, - selectedHover: { - } - }, - markerLabelStyle: { - initial: { - 'font-family': 'Verdana', - 'font-size': '12', - 'font-weight': 'bold', - cursor: 'default', - fill: 'black' + lineStyle: { + initial: { + fill: 'none', + "fill-opacity": 0, + stroke: 'black', + "stroke-width": 3, + "stroke-opacity": 1 + }, + hover: { + "stroke-opacity": 0.8, + cursor: 'pointer' + }, + selected: { + stroke: 'yellow' + }, + selectedHover: {} + }, - hover: { - cursor: 'pointer' + lineLabelStyle: { + initial: { + 'font-family': 'Verdana', + 'font-size': '12', + 'font-weight': 'bold', + cursor: 'default', + fill: 'black' + }, + hover: { + cursor: 'pointer' + } + } - } }; jvm.Map.apiEvents = { - onRegionTipShow: 'regionTipShow', - onRegionOver: 'regionOver', - onRegionOut: 'regionOut', - onRegionClick: 'regionClick', - onRegionSelected: 'regionSelected', - onMarkerTipShow: 'markerTipShow', - onMarkerOver: 'markerOver', - onMarkerOut: 'markerOut', - onMarkerClick: 'markerClick', - onMarkerSelected: 'markerSelected', - onViewportChange: 'viewportChange' + onRegionTipShow: 'regionTipShow', + onRegionOver: 'regionOver', + onRegionOut: 'regionOut', + onRegionClick: 'regionClick', + onRegionSelected: 'regionSelected', + onMarkerTipShow: 'markerTipShow', + onMarkerOver: 'markerOver', + onMarkerOut: 'markerOut', + onMarkerClick: 'markerClick', + onMarkerSelected: 'markerSelected', + onLineTipShow: 'lineTipShow', + onLineOver: 'lineOver', + onLineOut: 'lineOut', + onLineClick: 'lineClick', + onLineSelected: 'lineSelected', + onViewportChange: 'viewportChange' }; \ No newline at end of file diff --git a/tests/lines.html b/tests/lines.html new file mode 100644 index 00000000..f66eae34 --- /dev/null +++ b/tests/lines.html @@ -0,0 +1,336 @@ + + + + + Maps + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
    +
  • +
  • +
  • +
  • +
  • +
  • +
+ + +    + + +    + +    + +    + + + \ No newline at end of file