100,000 Persons 65 and over
Source: U.S. Bureau of the Census
The above map attempts to reproduce an example from Judy Olson's 1976 article Noncontiguous Area Cartograms using the OpenLayers JavaScript mapping framework. See this blog post for more information.
Just 'view source' to see what all's going on here. Feel free to re-use any code you find there. Below's the main method that applies the transformation. It could be a lot shorter but I allow for classed as well as more typical unclassed cartograms.
OpenLayersThematic.applyNoncontiguousCartogramScalesToVectorLayer = function( layer, propertyName, classificationMethod, numClasses, breaks, maxArea, classAreas ) { if ( classificationMethod == null ) classificationMethod = OpenLayersThematic.ClassificationMethods.UNCLASSED; if ( maxArea == null ) maxArea = 12000; if ( classificationMethod != OpenLayersThematic.ClassificationMethods.UNCLASSED ) { if ( numClasses == null ) numClasses = 5; if ( breaks == null ) { var values = []; for ( var i in layer.features ) values.push( Number( layer.features[ i ].attributes[ propertyName ] ) ); breaks = OpenLayersThematic.calculateClassBreaks( values, numClasses, classificationMethod ); } if ( classAreas == null || classAreas.length != numClasses ) { classAreas = []; for ( var i = 1; i <= numClasses; i++ ) classAreas.push( i/numClasses * maxArea ); } } // determine the maximum attribute value for all these features var maxValue = -999999; for ( var i in layer.features ) if ( Number(layer.features[ i ].attributes[ propertyName ]) > maxValue ) maxValue = Number(layer.features[ i ].attributes[ propertyName ]); // now we'll loop thru the features and scale them var originalArea, desiredArea, value, desiredScale; for ( var i in layer.features ) { value = Number( layer.features[ i ].attributes[ propertyName ] ); originalArea = layer.features[ i ].geometry.getArea() / Math.pow( layer.map.getResolution(), 2 ); if ( classificationMethod != OpenLayersThematic.ClassificationMethods.UNCLASSED ) { for ( var ii=1; ii<breaks.length; ii++ ) if ( value < breaks[ ii ] || ( ( ii == breaks.length - 1 ) && value == breaks[ ii] ) ) break; desiredArea = classAreas[ ii - 1 ]; } else desiredArea = ( value / maxValue ) * maxArea; var centroid = layer.features[ i ].geometry.getCentroid(); desiredScale = Math.sqrt( desiredArea / originalArea ); layer.features[ i ].geometry.resize( desiredScale, centroid ); } };
By Zachary Forest Johnson of indiemaps.com