123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603 |
- define("dojox/geo/charting/Map", ["dojo/_base/lang","dojo/_base/array","dojo/_base/declare","dojo/_base/html","dojo/dom",
- "dojo/dom-geometry","dojo/dom-class", "dojo/_base/xhr","dojo/_base/connect","dojo/_base/window", "dojox/gfx",
- "dojox/geo/charting/_base","dojox/geo/charting/Feature","dojox/geo/charting/_Marker","dojo/number","dojo/_base/sniff"],
- function(lang, arr, declare, html, dom, domGeom, domClass, xhr, connect, win, gfx, base,
- Feature, Marker, number, has) {
- return declare("dojox.geo.charting.Map", null, {
- // summary:
- // Map widget interacted with charting.
- // description:
- // Support rendering Americas, AsiaPacific, ContinentalEurope, EuropeMiddleEastAfrica,
- // USStates, WorldCountries, and WorldCountriesMercator by default.
- // example:
- // | var usaMap = new dojox.geo.charting.Map(srcNode, "dojotoolkit/dojox/geo/charting/resources/data/USStates.json");
- // | <div id="map" style="width:600px;height:400px;"></div>
-
- // defaultColor: String
- // Default map feature color, e.g: "#B7B7B7"
- defaultColor:"#B7B7B7",
- // highlightColor: String
- // Map feature color when mouse over it, e.g: "#"
- highlightColor:"#D5D5D5",
- // series: Array
- // stack to data range, e.g: [{name:'label 1', min:20, max:70, color:'#DDDDDD'},{...},...]
- series:[],
- dataBindingAttribute:null,
- dataBindingValueFunction:null,
- dataStore:null,
- showTooltips: true,
- enableFeatureZoom: true,
- colorAnimationDuration:0,
- _idAttributes:null,
- _onSetListener:null,
- _onNewListener:null,
- _onDeleteListener:null,
- constructor: function(/*HTML Node*/container, /*String or Json object*/shapeData){
- // container:
- // map container html node/id
- // shapeData:
- // map shape data json object, or url to json file
-
- html.style(container, "display", "block");
-
- this.container = container;
- var containerBounds = this._getContainerBounds();
- // get map container coords
- this.surface = gfx.createSurface(container, containerBounds.w, containerBounds.h);
-
- this._createZoomingCursor();
-
- // add transparent background for event capture
- this.mapBackground = this.surface.createRect({x: 0, y: 0, width: containerBounds.w, height: containerBounds.w}).setFill("rgba(0,0,0,0)");
-
- this.mapObj = this.surface.createGroup();
- this.mapObj.features = {};
-
- if (typeof shapeData == "object") {
- this._init(shapeData);
- } else {
- // load map shape file
- if (typeof shapeData == "string" && shapeData.length > 0) {
- xhr.get({
- url: shapeData,
- handleAs: "json",
- sync: true,
- load: lang.hitch(this, "_init")
- });
- }
- }
- },
-
- _getContainerBounds: function() {
- // summary:
- // returns the bounds {x:, y:, w: ,h:} of the DOM node container in absolute coordinates
- // tags:
- // private
-
- var position = domGeom.position(this.container,true);
- var marginBox = domGeom.getMarginBox(this.container);
- // use contentBox for correct width and height - surface spans outside border otherwise
- var contentBox = domGeom.getContentBox(this.container);
- this._storedContainerBounds = {
- x: position.x,
- y: position.y,
- w: contentBox.w || 100,
- h: contentBox.h || 100
- };
- return this._storedContainerBounds;
- },
-
- resize: function(/**boolean**/ adjustMapCenter/**boolean**/,adjustMapScale,/**boolean**/ animate) {
- // summary:
- // resize the underlying GFX surface to accommodate to parent DOM Node size change
- // adjustMapCenter: boolean
- // keeps the center of the map when resizing the surface
- // adjustMapScale: boolean
- // adjusts the map scale to keep the visible portion of the map as much as possible
-
- var oldBounds = this._storedContainerBounds;
- var newBounds = this._getContainerBounds();
-
- if ((oldBounds.w == newBounds.w) && (oldBounds.h == newBounds.h)) {
- return;
- }
-
- // set surface dimensions, and background
- this.mapBackground.setShape({width:newBounds.w, height:newBounds.h});
- this.surface.setDimensions(newBounds.w,newBounds.h);
-
- this.mapObj.marker.hide();
- this.mapObj.marker._needTooltipRefresh = true;
-
- if (adjustMapCenter) {
-
- var mapScale = this.getMapScale();
- var newScale = mapScale;
-
- if (adjustMapScale) {
- var bbox = this.mapObj.boundBox;
- var widthFactor = newBounds.w / oldBounds.w;
- var heightFactor = newBounds.h / oldBounds.h;
- newScale = mapScale * Math.sqrt(widthFactor * heightFactor);
- }
-
- // current map center
- var invariantMapPoint = this.screenCoordsToMapCoords(oldBounds.w/2,oldBounds.h/2);
- // apply new parameters
- this.setMapCenterAndScale(invariantMapPoint.x,invariantMapPoint.y,newScale,animate);
- }
- },
-
- _isMobileDevice: function() {
- // summary:
- // tests whether the application is running on a mobile device (android or iOS)
- // tags:
- // private
- return (has("safari")
- && (navigator.userAgent.indexOf("iPhone") > -1 ||
- navigator.userAgent.indexOf("iPod") > -1 ||
- navigator.userAgent.indexOf("iPad") > -1
- )) || (navigator.userAgent.toLowerCase().indexOf("android") > -1);
- },
-
-
- setMarkerData: function(/*String*/ markerFile){
- // summary:
- // import markers from outside file, associate with map feature by feature id
- // which identified in map shape file, e.g: "NY":"New York"
- // markerFile:
- // outside marker data url, handled as json style.
- // data format: {"NY":"New York",.....}
- xhr.get({
- url: markerFile,
- handleAs: "json",
- handle: lang.hitch(this, "_appendMarker")
- });
- },
-
- setDataBindingAttribute: function(/*String*/prop) {
- // summary:
- // sets the property name of the dataStore items to use as value (see Feature.setValue function)
- // prop:
- // the property
- this.dataBindingAttribute = prop;
-
- // refresh data
- if (this.dataStore) {
- this._queryDataStore();
- }
- },
-
- setDataBindingValueFunction: function(/* function */valueFunction) {
- // summary:
- // sets the function that extracts values from dataStore items,to use as Feature values (see Feature.setValue function)
- // prop:
- // the function
- this.dataBindingValueFunction = valueFunction;
-
- // refresh data
- if (this.dataStore) {
- this._queryDataStore();
- }
- },
-
-
-
- _queryDataStore: function() {
- if (!this.dataBindingAttribute || (this.dataBindingAttribute.length == 0))
- return;
-
- var mapInstance = this;
- this.dataStore.fetch({
- scope: this,
- onComplete: function(items){
- this._idAttributes = mapInstance.dataStore.getIdentityAttributes({});
- arr.forEach(items, function(item) {
- var id = mapInstance.dataStore.getValue(item, this._idAttributes[0]);
- if(mapInstance.mapObj.features[id]){
- var val = null;
- var itemVal = mapInstance.dataStore.getValue(item, mapInstance.dataBindingAttribute);
- if (itemVal) {
- if (this.dataBindingValueFunction) {
- val = this.dataBindingValueFunction(itemVal);
- } else {
- if (isNaN(val)) {
- // regular parse
- val=number.parse(itemVal);
- } else {
- val = itemVal;
- }
- }
- }
- if (val)
- mapInstance.mapObj.features[id].setValue(val);
- }
- },this);
- }
- });
- },
-
- _onSet:function(item,attribute,oldValue,newValue){
- // look for matching feature
- var id = this.dataStore.getValue(item, this._idAttributes[0]);
- var feature = this.mapObj.features[id];
- if (feature && (attribute == this.dataBindingAttribute)) {
- if (newValue)
- feature.setValue(newValue);
- else
- feature.unsetValue();
- }
- },
- _onNew:function(newItem, parentItem){
- var id = this.dataStore.getValue(item, this._idAttributes[0]);
- var feature = this.mapObj.features[id];
- if (feature && (attribute == this.dataBindingAttribute)) {
- feature.setValue(newValue);
- }
- },
-
- _onDelete:function(item){
- var id = item[this._idAttributes[0]];
- var feature = this.mapObj.features[id];
- if (feature) {
- feature.unsetValue();
- }
- },
-
- setDataStore: function(/*ItemFileReadStore*/ dataStore, /*String*/ dataBindingProp){
- // summary:
- // populate data for each map feature from fetched data store
- // dataStore:
- // the dataStore to fetch the information from
- // dataBindingProp:
- // sets the property name of the dataStore items to use as value
- if (this.dataStore != dataStore) {
- // disconnect previous listener if any
- if (this._onSetListener) {
- connect.disconnect(this._onSetListener);
- connect.disconnect(this._onNewListener);
- connect.disconnect(this._onDeleteListener);
- }
-
- // set new dataStore
- this.dataStore = dataStore;
-
- // install listener on new dataStore
- if (dataStore) {
- _onSetListener = connect.connect(this.dataStore,"onSet",this,this._onSet);
- _onNewListener = connect.connect(this.dataStore,"onNew",this,this._onNew);
- _onDeleteListener = connect.connect(this.dataStore,"onDelete",this,this._onDelete);
- }
- }
- if (dataBindingProp)
- this.setDataBindingAttribute(dataBindingProp);
- },
-
-
-
- addSeries: function(/*url or Json Object*/ series){
- // summary:
- // sets ranges of data values (associated with label, color) to style map data values
- // series:
- // array of range objects such as : [{name:'label 1', min:20, max:70, color:'#DDDDDD'},{...},...]
-
- if (typeof series == "object") {
- this._addSeriesImpl(series);
- } else {
- // load series file
- if (typeof series == "string" && series.length > 0) {
- xhr.get({
- url: series,
- handleAs: "json",
- sync: true,
- load: lang.hitch(this, function(content){
- this._addSeriesImpl(content.series);
- })
- });
- }
- }
-
- },
-
- _addSeriesImpl: function(/*Json object*/series) {
-
- this.series = series;
-
- // refresh color scheme
- for (var item in this.mapObj.features) {
- var feature = this.mapObj.features[item];
- feature.setValue(feature.value);
- }
- },
-
- fitToMapArea: function(/*bbox: {x,y,w,h}*/mapArea,pixelMargin,animate,/* callback function */onAnimationEnd){
- // summary:
- // set this component's transformation so that the specified area fits in the component (centered)
- // mapArea:
- // the map area that needs to fill the component
- // pixelMargin: int
- // a margin (in pixels) from the borders of the Map component.
- // animate: boolean
- // true if the transform change should be animated
- // onAnimationEnd: function
- // a callback function to be executed when the animation completes (if animate set to true).
-
- if(!pixelMargin){
- pixelMargin = 0;
- }
- var width = mapArea.w,
- height = mapArea.h,
- containerBounds = this._getContainerBounds(),
- scale = Math.min((containerBounds.w - 2 * pixelMargin) / width,
- (containerBounds.h - 2 * pixelMargin) / height);
-
- this.setMapCenterAndScale(mapArea.x + mapArea.w / 2,mapArea.y + mapArea.h / 2,scale,animate,onAnimationEnd);
- },
-
- fitToMapContents: function(pixelMargin,animate,/* callback function */onAnimationEnd){
- // summary:
- // set this component's transformation so that the whole map data fits in the component (centered)
- // pixelMargin: int
- // a margin (in pixels) from the borders of the Map component.
- // animate: boolean
- // true if the transform change should be animated
- // onAnimationEnd: function
- // a callback function to be executed when the animation completes (if animate set to true).
-
- //transform map to fit container
- var bbox = this.mapObj.boundBox;
- this.fitToMapArea(bbox,pixelMargin,animate,onAnimationEnd);
- },
-
- setMapCenter: function(centerX,centerY,animate,/* callback function */onAnimationEnd) {
- // summary:
- // set this component's transformation so that the map is centered on the specified map coordinates
- // centerX: float
- // the X coordinate (in map coordinates) of the new center
- // centerY: float
- // the Y coordinate (in map coordinates) of the new center
- // animate: boolean
- // true if the transform change should be animated
- // onAnimationEnd: function
- // a callback function to be executed when the animation completes (if animate set to true).
-
- // call setMapCenterAndScale with current map scale
- var currentScale = this.getMapScale();
- this.setMapCenterAndScale(centerX,centerY,currentScale,animate,onAnimationEnd);
-
- },
-
- _createAnimation: function(onShape,fromTransform,toTransform,/* callback function */onAnimationEnd) {
- // summary:
- // creates a transform animation object (between two transforms) used internally
- // fromTransform: dojox.gfx.matrix.Matrix2D
- // the start transformation (when animation begins)
- // toTransform: dojox.gfx.matrix.Matrix2D
- // the end transormation (when animation ends)
- // onAnimationEnd: function
- // callback function to be executed when the animation completes.
- var fromDx = fromTransform.dx?fromTransform.dx:0;
- var fromDy = fromTransform.dy?fromTransform.dy:0;
- var toDx = toTransform.dx?toTransform.dx:0;
- var toDy = toTransform.dy?toTransform.dy:0;
- var fromScale = fromTransform.xx?fromTransform.xx:1.0;
- var toScale = toTransform.xx?toTransform.xx:1.0;
-
- var anim = gfx.fx.animateTransform({
- duration: 1000,
- shape: onShape,
- transform: [{
- name: "translate",
- start: [fromDx,fromDy],
- end: [toDx,toDy]
- },
- {
- name: "scale",
- start: [fromScale],
- end: [toScale]
- }
- ]
- });
- //install callback
- if (onAnimationEnd) {
- var listener = connect.connect(anim,"onEnd",this,function(event){
- onAnimationEnd(event);
- connect.disconnect(listener);
- });
- }
-
- return anim;
- },
-
- setMapCenterAndScale: function(centerX,centerY,scale, animate,/* callback function */onAnimationEnd) {
-
- // summary:
- // set this component's transformation so that the map is centered on the specified map coordinates
- // and scaled to the specified scale.
- // centerX: float
- // the X coordinate (in map coordinates) of the new center
- // centerY: float
- // the Y coordinate (in map coordinates) of the new center
- // scale: float
- // the scale of the map
- // animate: boolean
- // true if the transform change should be animated
- // onAnimationEnd: function
- // a callback function to be executed when the animation completes (if animate set to true).
-
-
- // compute matrix parameters
- var bbox = this.mapObj.boundBox;
- var containerBounds = this._getContainerBounds();
- var offsetX = containerBounds.w/2 - scale * (centerX - bbox.x);
- var offsetY = containerBounds.h/2 - scale * (centerY - bbox.y);
- var newTransform = new gfx.matrix.Matrix2D({xx: scale, yy: scale, dx:offsetX, dy:offsetY});
-
-
- var currentTransform = this.mapObj.getTransform();
-
- // can animate only if specified AND curentTransform exists
- if (!animate || !currentTransform) {
- this.mapObj.setTransform(newTransform);
- } else {
- var anim = this._createAnimation(this.mapObj,currentTransform,newTransform,onAnimationEnd);
- anim.play();
- }
- },
-
- getMapCenter: function() {
- // summary:
- // returns the map coordinates of the center of this Map component.
- // returns: {x:,y:}
- // the center in map coordinates
- var containerBounds = this._getContainerBounds();
- return this.screenCoordsToMapCoords(containerBounds.w/2,containerBounds.h/2);
- },
-
- setMapScale: function(scale,animate,/* callback function */onAnimationEnd) {
- // summary:
- // set this component's transformation so that the map is scaled to the specified scale.
- // animate: boolean
- // true if the transform change should be animated
- // onAnimationEnd: function
- // a callback function to be executed when the animation completes (if animate set to true).
-
-
- // default invariant is map center
- var containerBounds = this._getContainerBounds();
- var invariantMapPoint = this.screenCoordsToMapCoords(containerBounds.w/2,containerBounds.h/2);
- this.setMapScaleAt(scale,invariantMapPoint.x,invariantMapPoint.y,animate,onAnimationEnd);
- },
-
- setMapScaleAt: function(scale,fixedMapX,fixedMapY,animate,/* callback function */onAnimationEnd) {
- // summary:
- // set this component's transformation so that the map is scaled to the specified scale, and the specified
- // point (in map coordinates) stays fixed on this Map component
- // fixedMapX: float
- // the X coordinate (in map coordinates) of the fixed screen point
- // fixedMapY: float
- // the Y coordinate (in map coordinates) of the fixed screen point
- // animate: boolean
- // true if the transform change should be animated
- // onAnimationEnd: function
- // a callback function to be executed when the animation completes (if animate set to true).
-
-
- var invariantMapPoint = null;
- var invariantScreenPoint = null;
- invariantMapPoint = {x: fixedMapX, y: fixedMapY};
- invariantScreenPoint = this.mapCoordsToScreenCoords(invariantMapPoint.x,invariantMapPoint.y);
-
- // compute matrix parameters
- var bbox = this.mapObj.boundBox;
- var offsetX = invariantScreenPoint.x - scale * (invariantMapPoint.x - bbox.x);
- var offsetY = invariantScreenPoint.y - scale * (invariantMapPoint.y - bbox.y);
- var newTransform = new gfx.matrix.Matrix2D({xx: scale, yy: scale, dx:offsetX, dy:offsetY});
- var currentTransform = this.mapObj.getTransform();
- // can animate only if specified AND curentTransform exists
- if (!animate || !currentTransform) {
- this.mapObj.setTransform(newTransform);
- } else {
- var anim = this._createAnimation(this.mapObj,currentTransform,newTransform,onAnimationEnd);
- anim.play();
- }
- },
-
- getMapScale: function() {
- // summary:
- // returns the scale of this Map component.
- // returns: float
- // the scale
- var mat = this.mapObj.getTransform();
- var scale = mat?mat.xx:1.0;
- return scale;
- },
-
- mapCoordsToScreenCoords: function(mapX,mapY) {
- // summary:
- // converts map coordinates to screen coordinates given the current transform of this Map component
- // returns: {x:,y:}
- // the screen coordinates correspondig to the specified map coordinates.
- var matrix = this.mapObj.getTransform();
- var screenPoint = gfx.matrix.multiplyPoint(matrix, mapX, mapY);
- return screenPoint;
- },
-
- screenCoordsToMapCoords: function(screenX, screenY) {
- // summary:
- // converts screen coordinates to map coordinates given the current transform of this Map component
- // returns: {x:,y:}
- // the map coordinates corresponding to the specified screen coordinates.
- var invMatrix = gfx.matrix.invert(this.mapObj.getTransform());
- var mapPoint = gfx.matrix.multiplyPoint(invMatrix, screenX, screenY);
- return mapPoint;
- },
- deselectAll: function(){
- // summary:
- // deselect all features of map
- for(var name in this.mapObj.features){
- this.mapObj.features[name].select(false);
- }
- this.selectedFeature = null;
- this.focused = false;
- },
-
- _init: function(shapeData){
-
- // summary:
- // inits this Map component.
-
- //transform map to fit container
- this.mapObj.boundBox = {x: shapeData.layerExtent[0],
- y: shapeData.layerExtent[1],
- w: (shapeData.layerExtent[2] - shapeData.layerExtent[0]),
- h: shapeData.layerExtent[3] - shapeData.layerExtent[1]};
- this.fitToMapContents(3);
- // if there are "features", then implement them now.
- arr.forEach(shapeData.featureNames, function(item){
- var featureShape = shapeData.features[item];
- featureShape.bbox.x = featureShape.bbox[0];
- featureShape.bbox.y = featureShape.bbox[1];
- featureShape.bbox.w = featureShape.bbox[2];
- featureShape.bbox.h = featureShape.bbox[3];
- var feature = new Feature(this, item, featureShape);
- feature.init();
- this.mapObj.features[item] = feature;
- }, this);
-
- // set up a marker.
- this.mapObj.marker = new Marker({}, this);
- },
- _appendMarker: function(markerData){
- this.mapObj.marker = new Marker(markerData, this);
- },
- _createZoomingCursor: function(){
- if(!dom.byId("mapZoomCursor")){
- var mapZoomCursor = win.doc.createElement("div");
- html.attr(mapZoomCursor,"id","mapZoomCursor");
- domClass.add(mapZoomCursor,"mapZoomIn");
- html.style(mapZoomCursor,"display","none");
- win.body().appendChild(mapZoomCursor);
- }
- },
- onFeatureClick: function(feature){
- },
- onFeatureOver: function(feature){
- },
- onZoomEnd:function(feature){
- }
- });
- });
|