/**
* Creates map, draws paths, binds events.
* @constructor
* @param {Object} params Parameters to initialize map with.
* @param {String} params.map Name of the map in the format territory_proj_lang
where territory
is a unique code or name of the territory which the map represents (ISO 3166 alpha 2 standard is used where possible), proj
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 lang
is a code of the language, used for the names of regions.
* @param {String} params.backgroundColor Background color of the map in CSS format.
* @param {Boolean} params.zoomOnScroll When set to true map could be zoomed using mouse scroll. Default value is true
.
* @param {Number} params.zoomMax Indicates the maximum zoom ratio which could be reached zooming the map. Default value is 8
.
* @param {Number} params.zoomMin Indicates the minimum zoom ratio which could be reached zooming the map. Default value is 1
.
* @param {Number} params.zoomStep Indicates the multiplier used to zoom map with +/- buttons. Default value is 1.6
.
* @param {Boolean} params.regionsSelectable When set to true regions of the map could be selected. Default value is false
.
* @param {Boolean} params.regionsSelectableOne Allow only one region to be selected at the moment. Default value is false
.
* @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, stroke: 'none', "stroke-width": 0, "stroke-opacity": 1 }, hover: { "fill-opacity": 0.8 }, selected: { fill: 'yellow' }, selectedHover: { } }* @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', "fill-opacity": 1, "stroke-width": 1, "stroke-opacity": 1, r: 5 }, hover: { stroke: 'black', "stroke-width": 2 }, selected: { fill: 'blue' }, selectedHover: { } }* @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
latLng
(array of two numeric values), name
(string which will be show on marker's label) and any marker styles.
* @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. It could be expressed as a string representing region which should be in focus or an object representing coordinates and scale to set. For example to focus on the center of the map at the double scale you can provide the following value:
{ x: 0.5, y: 0.5, scale: 2 }* @param {Array|Object|String} params.selectedRegions Set initially selected regions. * @param {Array|Object|String} params.selectedMarkers Set initially selected markers. * @param {Function} params.onRegionLabelShow
(Event e, Object label, String code)
Will be called right before the region label is going to be shown.
* @param {Function} params.onRegionOver (Event e, String code)
Will be called on region mouse over event.
* @param {Function} params.onRegionOut (Event e, String code)
Will be called on region mouse out event.
* @param {Function} params.onRegionClick (Event e, String code)
Will be called on region click event.
* @param {Function} params.onRegionSelected (Event e, String code, Boolean isSelected, Array selectedRegions)
Will be called when region is (de)selected. isSelected
parameter of the callback indicates whether region is selected or not. selectedRegions
contains codes of all currently selected regions.
* @param {Function} params.onMarkerLabelShow (Event e, Object label, String code)
Will be called right before the marker label is going to be shown.
* @param {Function} params.onMarkerOver (Event e, String code)
Will be called on marker mouse over event.
* @param {Function} params.onMarkerOut (Event e, String code)
Will be called on marker mouse out event.
* @param {Function} params.onMarkerClick (Event e, String code)
Will be called on marker click event.
* @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.WorldMap = function(params) {
var map = this,
e;
this.params = jvm.$.extend(true, {}, jvm.WorldMap.defaultParams, params);
if (!jvm.WorldMap.maps[this.params.map]) {
throw new Error('Attempt to use map which was not loaded: '+this.params.map);
}
this.mapData = jvm.WorldMap.maps[this.params.map];
this.markers = {};
this.regions = {};
this.regionsColors = {};
this.regionsData = {};
this.container = jvm.$('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 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);
},
clearSelected: function(type){
var select = {},
selected = this.getSelected(type),
i;
for (i = 0; i < selected.length; i++) {
select[selected[i]] = false;
};
this.setSelected(type, select);
},
/**
* Remove the selected state from all the currently selected regions.
*/
clearSelectedRegions: function(){
this.clearSelected('regions');
},
/**
* Remove the selected state from all the currently selected markers.
*/
clearSelectedMarkers: function(){
this.clearSelected('markers');
},
/**
* Return the instance of WorldMap. Useful when instantiated as a jQuery plug-in.
* @returns {WorldMap}
*/
getMapObject: function(){
return this;
},
/**
* Return the name of the region by region code.
* @returns {String}
*/
getRegionName: function(code){
return this.mapData.paths[code].name;
},
createRegions: function(){
var key,
region,
map = this;
for (key in this.mapData.paths) {
region = this.canvas.addPath({
d: this.mapData.paths[key].path,
"data-code": key
}, jvm.$.extend(true, {}, this.params.regionStyle));
jvm.$(region.node).bind('selected', function(e, isSelected){
map.container.trigger('regionSelected.jvectormap', [jvm.$(this).attr('data-code'), isSelected, map.getSelectedRegions()]);
});
region.addClass('jvectormap-region jvectormap-element');
this.regions[key] = {
element: region,
config: this.mapData.paths[key]
};
}
},
createMarkers: function(markers) {
var i,
marker,
point,
markerConfig,
markersArray,
map = this;
this.markersGroup = this.markersGroup || this.canvas.addGroup();
if (jvm.$.isArray(markers)) {
markersArray = markers.slice();
markers = {};
for (i = 0; i < markersArray.length; i++) {
markers[i] = markersArray[i];
}
}
for (i in markers) {
markerConfig = markers[i] instanceof Array ? {latLng: markers[i]} : markers[i];
point = this.getMarkerPosition( markerConfig );
if (point !== false) {
marker = this.canvas.addCircle({
"data-index": i,
cx: point.x,
cy: point.y
}, jvm.$.extend(true, {}, this.params.markerStyle, {initial: markerConfig.style || {}}), this.markersGroup);
marker.addClass('jvectormap-marker jvectormap-element');
jvm.$(marker.node).bind('selected', function(e, isSelected){
map.container.trigger('markerSelected.jvectormap', [jvm.$(this).attr('data-index'), isSelected, map.getSelectedMarkers()]);
});
if (this.markers[i]) {
this.removeMarkers([i]);
}
this.markers[i] = {element: marker, config: markerConfig};
}
}
},
repositionMarkers: function() {
var i,
point;
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});
}
}
},
getMarkerPosition: function(markerConfig) {
if (jvm.WorldMap.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 = {};
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;
seriesData = seriesData || [];
this.createMarkers(markers);
for (i = 0; i < seriesData.length; i++) {
this.series.markers[i].setValues(seriesData[i] || {});
};
},
/**
* 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] ];
};
},
/**
* Remove all markers from the map.
*/
removeAllMarkers: function(){
var i,
markers = [];
for (i in this.markers) {
markers.push(i);
}
this.removeMarkers(markers)
},
/**
* 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.WorldMap.maps[this.params.map].projection,
centralMeridian = proj.centralMeridian,
width = this.width - this.baseTransX * 2 * this.baseScale,
height = this.height - this.baseTransY * 2 * this.baseScale,
inset,
bbox,
scaleFactor = this.scale / this.baseScale;
if (lng < (-180 + centralMeridian)) {
lng += 360;
}
point = jvm.Proj[proj.type](lat, lng, centralMeridian);
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;
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.WorldMap.maps[this.params.map].projection,
centralMeridian = proj.centralMeridian,
insets = jvm.WorldMap.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;
},
getInsetForPoint: function(x, y){
var insets = jvm.WorldMap.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];
}
}
},
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]
);
}
}
},
/**
* Gracefully remove the map and and all its accessories, unbind event handlers.
*/
remove: function(){
this.label.remove();
this.container.remove();
jvm.$(window).unbind('resize', this.onResize);
}
};
jvm.WorldMap.maps = {};
jvm.WorldMap.defaultParams = {
map: 'world_mill_en',
backgroundColor: '#505050',
zoomButtons: true,
zoomOnScroll: true,
zoomMax: 8,
zoomMin: 1,
zoomStep: 1.6,
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
},
selected: {
fill: 'yellow'
},
selectedHover: {
}
},
markerStyle: {
initial: {
fill: 'grey',
stroke: '#505050',
"fill-opacity": 1,
"stroke-width": 1,
"stroke-opacity": 1,
r: 5
},
hover: {
stroke: 'black',
"stroke-width": 2
},
selected: {
fill: 'blue'
},
selectedHover: {
}
}
};
jvm.WorldMap.apiEvents = {
onRegionLabelShow: 'regionLabelShow',
onRegionOver: 'regionOver',
onRegionOut: 'regionOut',
onRegionClick: 'regionClick',
onRegionSelected: 'regionSelected',
onMarkerLabelShow: 'markerLabelShow',
onMarkerOver: 'markerOver',
onMarkerOut: 'markerOut',
onMarkerClick: 'markerClick',
onMarkerSelected: 'markerSelected',
onViewportChange: 'viewportChange'
};