The Ignite UI map is already a control that boasts a wide variety of features , proving useful in a number of different scenarios. I have been posting about the map’s current capabilities; this post and another one which will follow shortly will shed some light on some upcoming features.
Today we shall be looking into the Proportional Symbol Series – a nice touch on the standard symbol series. With this new type of series , you will be able to visually display some of the series’ item statistics by utilizing the size of the symbol as a visual cue.
Now without further ado – let’s get on with it !
Setup
As with all other jQuery-based controls , you will have to load up a set of required JavaScript scripts in order for the Ignite UI map to work and be rendered.
Here’s the set of includes you need on your webpage , as usual I’m using the CDN-hosted versions; you’re free to use locally-stored files if you wish.
1:<scriptsrc="http://www.modernizr.com/downloads/modernizr-latest.js"type="text/javascript"></script>1:
2:<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js" type="text/javascript">1:</script>
2:<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.17/jquery-ui.js" type="text/javascript">1:</script>
2:<script src="js/infragistics.loader.js">1:</script>
2:
3:<script src="MapHelper.js"></script>
The last file being loaded is a custom script with some useful functions for the Ignite UI map control – they can be used alongside the control’s default methods. Feel free to check the file out – it’s provided in the archived sample on the bottom of this post. You can also see the code here:
1:function MapHelper(options) {
2:var self = this;
3:// Initialize with the options supplied
4:this.mapSelector = options.mapSelector;
5:this.shapeDataSource = options.shapeDataSource;
6:
7:// Attaches an event handler which zooms the map so that all map shapes are visible
8:this.autoZoom = function() {
9: $(document).on("igmaprefreshcompleted", self.mapSelector, autoZoomHandler);
10: }
11:
12:// Finds the extent of all shapes in the map control and calculates a proper
13:// map window so that all shapes are visible.
14:// Detaches from the refreshCompleted event of the map control after
15:// themap rectangle is set to the map control
16:function autoZoomHandler() {
17:var shapes = self.getAllShapesExtent();
18:if (shapes.length > 0) {
19:var allShapesBounds = self.findShapeArrayBounds(shapes);
20:var mapWindow = self.calculateMapWindow(allShapesBounds);
21: $(self.mapSelector).igMap("option", "windowRect", mapWindow);
22: $(document).off("igmaprefreshcompleted", self.mapSelector, autoZoomHandler);
23: }
24: }
25:
26:this.mapShapes = function (mapShape) {
27:var shapeData = self.shapeDataSource;
28:var shapeEnumerator = shapeData.converter().getEnumerator();
29:var mappedShapes = [];
30:while (shapeEnumerator.moveNext()) {
31: mappedShapes.push(mapShape(shapeEnumerator.current()));
32: }
33:
34:return mappedShapes;
35: }
36:
37:this.getAllShapesExtent = function () {
38:var shapeData = self.shapeDataSource;
39:var shapeEnumerator = shapeData.converter().getEnumerator();
40:var shapesArray = [];
41:while (shapeEnumerator.moveNext()) {
42: shapesArray.push(shapeEnumerator.current());
43: }
44:return shapesArray;
45: }
46:
47:this.findShapeArrayBounds = function(shapeArray) {
48:// Store to improve performance and readability
49:var sCount = shapeArray.length;
50:
51:if (sCount > 0) {
52:var left, top, right, bottom;
53:
54:// Enumerate shapes
55:for (var s = 0; s < sCount; s++) {
56:var currentShapeBounds = self.findShapeBounds(shapeArray[s]);
57:
58:if (currentShapeBounds.left < left || !left) left = currentShapeBounds.left;
59:if (currentShapeBounds.right > right || !right) right = currentShapeBounds.right;
60:
61:if (currentShapeBounds.top > top || !top) top = currentShapeBounds.top;
62:if (currentShapeBounds.bottom < bottom || !bottom) bottom = currentShapeBounds.bottom;
63: }
64:
65:return {
66: left: left,
67: right: right,
68: top: top,
69: bottom: bottom
70: };
71: }
72: }
73:
74:this.findShapeBounds = function(shape) {
75:var left, top, right, bottom;
76:var points = shape.points.item(0);
77:var pCount = points.count();
78:// Enumerate shape points
79:if (pCount > 0) {
80:// Find bounds of the state
81:for (var i = 0; i < pCount; i++) {
82: currentPoint = points.item(i);
83:
84:if (currentPoint.__x < left || !left) left = currentPoint.__x;
85:if (currentPoint.__x > right || !right) right = currentPoint.__x;
86:
87:if (currentPoint.__y > top || !top) top = currentPoint.__y;
88:if (currentPoint.__y < bottom || !bottom) bottom = currentPoint.__y;
89: }
90:
91:return {
92: left: left,
93: right: right,
94: top: top,
95: bottom: bottom
96: };
97: }
98: }
99:
100:this.calculateMapWindow = function(minViewWindow, zoomRatio) {
101:if (!zoomRatio) {
102: zoomRatio = 1;
103: }
104:// Calculate central point and required radius
105:var width = minViewWindow.right - minViewWindow.left;
106:var height = minViewWindow.top - minViewWindow.bottom;
107:var centered = {
108: longitude: minViewWindow.right - width / 2,
109: latitude: minViewWindow.top - height / 2,
110: radius: (width > height) ? width / 2 * zoomRatio : height / 2 * zoomRatio
111: };
112:// Calculate map window in relative units
113:var zoomRect = $(self.mapSelector).igMap("getZoomFromGeographic", self.geographicFromCentered(centered));
114:return zoomRect;
115: }
116:
117:// Calculates the geographic coordinates of a square around a central point and radius
118:this.geographicFromCentered = function(centered) {
119:var geographic =
120: {
121: left: centered.longitude - centered.radius,
122: top: centered.latitude - centered.radius,
123: width: centered.radius * 2,
124: height: centered.radius * 2
125: };
126:return geographic;
127: }
128: }
Now , let’s try to understand how does the scaling work.
1: $.ig.loader({
2: scriptPath: "./js/",
3: cssPath: "./css/",
4: resources: "igMap"
5: });
6:
7: $.ig.loader("igMap", function () {
8:var worldCitiesSource = new $.ig.ShapeDataSource({
9: shapefileSource: 'world_cities.shp',
10: databaseSource: 'world_cities.dbf',
11: importCompleted: function (shapeSource) {
12:var helper = new MapHelper({
13: mapSelector: "#map",
14: shapeDataSource: shapeSource
15: });
16:var citiesData = helper.mapShapes(function (shape) {
17:return {
18: longitude: shape.points.item(0).item(0).__x,
19: latitude: shape.points.item(0).item(0).__y,
20: name: shape.fieldValues.NAME,
21:// 23620000 is the max number in the data
22:// 32 is scaling factor -> change to increase/decrease the size of the circles
23: population: shape.fieldValues.POPULATION / 23620000 * 32,
24: country: shape.fieldValues.COUNTRY,
25: isCapital: shape.fieldValues.CAPITAL === "Y"
26: };
27: });
28: createMap(citiesData);
29: }
30: });
31: worldCitiesSource.dataBind();
32: });
33:
34:function createMap(worldCities) {
35: $("#map").igMap({
36: width: "700px",
37: height: "700px",
38: verticalZoomable: true,
39: horizontalZoomable: true,
40: windowResponse: "immediate",
41: dataSource: worldCities,
42: series: [{
43: type: 'geographicProportionalSymbol',
44: name: 'worldCities',
45: latitudeMemberPath: 'latitude',
46: longitudeMemberPath: 'longitude',
47: radiusMemberPath: 'population',
48: markerType: 'automatic'// It is mandatory to set any kind of marker because the default is (?!) 'none'
49:// Whatever you set the control will always draw circles of varying size
50: }]
51: });
52: }
The first few lines are loading of the appropriate resources , this is present in all of the setups where we use the Infragistics Loader to load our control resource files.
What we do next is we create a shape data source , specifying the location of our shape files. Once the files have been loaded up , we use the shape files source as a parameter to pass to our Map Helper object , which we use to enumerate through the source’s shapes. The symbols’ size is each city’s population value , based on the largest population value in the data source. Once we’ve established a formula for calculating the size of each symbol , we pass the now “prepared” data source to the map initialization function , which we’ve described in the createMap function. It’s just the standard way of initializing the map control.
On line 47 , we’re binding the radius of the symbol shapes to the radius calculating function we defined on line 23. We also define the mandatory latitude and longitude properties , which are needed to pinpoint individual entries within the series.
To download the sample project , click on this link.
Check out my previous blogs on the Ignite UI Map here , here , here and here. And also here.