Log in to like this post! A Look Into The Future–Using the Ignite UI Geographic Proportional Symbol Series Map Jordan Tsankov / Sunday, December 16, 2012 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: <script src="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.