<commit_before>define([
	'jquery',
	'leaflet',
	'topojson',
	'colorbrewer',
    'hoverTract',
    'colors'
], function($, L, topojson, colorbrewer, hoverTract, colors){

	var CensusLayer = L.Class.extend({

	  initialize: function (iMap,iDataPath) {
           // save position of the layer or any options from the constructor

		   	this._datapath = iDataPath;

	    },

	    onAdd: function (map) {

	      this._map = map;

		   	this._layers_id = [];
		   	this._properties_data = {};

        this._zoom_level = 0;
        this._zoom_change = true;

		   	this._init();

	    },

	    onRemove: function (map) {
	        // remove layer's DOM elements and listeners
	        // map.off('viewreset', this._reset, this);
	    },

	    setProperty : function(iProperty, iColorBrewer) {
				this._currentProperty = iProperty;
				this._colorBrewerName = iColorBrewer;
				this._refreshStyles();
	    },

	    _init : function() {

				this._computZoomLevel();

				this._groupLayer = L.layerGroup([]);
				this._groupLayer.addTo(this._map);

				this._loadJson();

				var self = this;
				this._map.on('zoomend', function(e) {

				   if (self._computZoomLevel()) {
					 self._zoom_change = true;
				   }

				   self._loadJson();

				});

				this._map.on('moveend', function(e) {

			   console.log("moveend");
			   self._loadJson();

				});

				/*
				this._map.on('dragend', function(e) {

				   console.log("dragend");
				   self._loadJson();

				});
				*/

	    },

	    // Zoom management
	    _zoom_max : 18,
	    _zoom_area : [0,  11 , 13 , 15 , 17,  18],

	    _computZoomLevel : function(){

			 	var new_zoom = -1;

				var c_zoom = this._map.getZoom();

				for (var i=0; i<this._zoom_area.length-1; i++) {
					if ((c_zoom> this._zoom_area[i]) && (c_zoom<=this._zoom_area[i+1])) {
						new_zoom = i;
					}
				}

				if (new_zoom!=this._zoom_level) {
					this._zoom_level = new_zoom;
					return true;
				}

				return false;

			},

			// Json load part

			_map_cache : {},

			_loadJson : function (){

				if (this._zoom_change) {

					this._base_folder = this._datapath+"p"+(4-this._zoom_level)+"/";

					var self = this;
					if (!this._loadingProp) {
						this._loadingProp = true;

						var url = this._base_folder + "properties.json";
						$.getJSON( url, function( data_properties ) { // first loading properties
							self._loadingProp = false;
							self._properties_data = data_properties;
							self._loadMapBounds();
							self._computeLegend();

						}).fail(function(d) {
		          console.log("Error occured on loading data...");
							console.log(d);
		        });

					}

				} else {

					console.log('ComputBounds');
					this._computBounds();

				}

			},

			_loadMapBounds : function () {

				var self = this;

				$.getJSON( this._base_folder + "mapbounds.json", function( data ) { // here get bounding maps for
																	   // json MA grid

					self._mapbounds_data = data;

					self._parts = [];
					self._groupLayer.clearLayers();
					self._computBounds();

				});

			},

			_computBounds : function(){

				var bounds = this._map.getBounds();

				this._bounds_to_be_loaded = [];

				for (var i=0; i<this._mapbounds_data.length; i++){

					var southWest = L.latLng(this._mapbounds_data[i].bound.miny, this._mapbounds_data[i].bound.minx),
					    northEast = L.latLng(this._mapbounds_data[i].bound.maxy, this._mapbounds_data[i].bound.maxx);

					var aRecordBnd = L.latLngBounds( southWest, northEast );

					if (aRecordBnd.intersects(bounds)) {

						var key = this._mapbounds_data[i].file;
						this._bounds_to_be_loaded.push(key);

					}

				}

				this._todisplay_parts = [];
				this._geojsondata = {};

				this._loadBoundProcess();

			},

			_loadBoundProcess : function() {

				if (this._bounds_to_be_loaded.length>0) {

					var key = this._bounds_to_be_loaded[0];
					this._bounds_to_be_loaded = this._bounds_to_be_loaded.slice(1,this._bounds_to_be_loaded.length);
					var self = this;
					var url = this._base_folder+key;

					if (this._map_cache[url]) {

						//console.log("Rebuild file:"+url);
						this._computGeoJsonBound( this._map_cache[url] );

					} else {

						//console.log("Loading file:"+url);
						$.getJSON( url , (function(url) { return function( data ) {

							//console.log("Loaded ...");
							var aFeature = topojson.feature(data, data.objects.MA);
							self._map_cache[url] = aFeature;
							self._computGeoJsonBound( aFeature );

						};} ) (url));

					}

				} else {
					this._buildsGroupLayer();
				}

			},

			_computGeoJsonBound : function(data){

				var bounds = this._map.getBounds();

				for (var j=0; j<data.features.length; j++) {

					var aEntity = data.features[j]; // Entity, 1 sector

					if ($.inArray(aEntity.id, this._todisplay_parts)==-1) { // not in array

						var properties = aEntity.properties;

						var subSouthWest = L.latLng(properties.bound.miny, properties.bound.minx),
		    				    subNorthEast = L.latLng(properties.bound.maxy, properties.bound.maxx);

						var aEntityBnd = L.latLngBounds( subSouthWest, subNorthEast );

						if (aEntityBnd.intersects(bounds)) { // Bounds each entity
							this._geojsondata[aEntity.id] = aEntity;
							this._todisplay_parts.push(aEntity.id);
						}

					}

				}

				this._loadBoundProcess();

			},

			_buildsGroupLayer : function(){

				var remove = $(this._parts).not(this._todisplay_parts).get();
				var add =  $(this._todisplay_parts).not(this._parts).get();

				for (var i=0; i<remove.length; i++) {

					if (this._layers_id[remove[i]].length>0) {

						for (var j=0; j<this._layers_id[remove[i]].length; j++) {
							this._groupLayer.removeLayer(this._layers_id[remove[i]][j]);
						}
						this._layers_id[remove[i]] = [];

					}
				}

				for (var i=0; i<add.length; i++) {

					var geojsonLayer = L.geoJson(this._geojsondata[add[i]], {
					    style: $.proxy( this._styleFunction, this)
			   		});

					var self = this;
				    $.each(geojsonLayer._layers,function(idx,layer){
                                        var feature = layer.feature;
                                            layer.feature.map_mouse_over = false;
                                            hoverTract.watchForValue(feature.id,
                                                         function() {
                                                             layer.setStyle(self._styleFunction(feature, true));
                                                         },
                                                         function() {
                                                             layer.setStyle(self._styleFunction(feature));
                                                         });
					    layer.on("mouseover", function (e) {
                                                hoverTract.select(feature.id,
                                                                  feature,
                                                                  self.getPropertyData(),
                                                                  colorbrewer[self._colorBrewerName]);
 					              feature.map_mouse_over = true;
 					         });
					    layer.on("mouseout", function (e) {
						       feature.map_mouse_over = false;
                                                // TODO: Move this somewhere else
						       hoverTract.select(null);
					    });

					});

					if (this._layers_id[add[i]]) {
						this._layers_id[add[i]].push(geojsonLayer._leaflet_id);
					} else {
						this._layers_id[add[i]] = [geojsonLayer._leaflet_id];
					}

					this._groupLayer.addLayer(geojsonLayer);
				}

				this._parts = this._todisplay_parts.slice();
				this._zoom_change = false;

			},

			_styleFunction : function(feature,highlight) {

				 if (this._itemStyle){
					return this._itemStyle(feature);
				 }

				 if (this._currentProperty!="") {

					 var prop = this._properties_data[this._currentProperty];

					 var val = parseFloat(feature.properties[this._currentProperty]);

					 if (val) {
						if ((typeof(highlight)!=="undefined") || feature.map_mouse_over==true) {
					 		return {"fillColor": this._getColor(prop.serie, val) , "color" : "#404040" , "weight": 4 , "opacity" : 1.0,  "fillOpacity": 0.6};
						} else {
							return {"fillColor": this._getColor(prop.serie,val) ,  "weight": 0 , "opacity" : 0.0,  "fillOpacity": 0.6};
						}
					} else {
						return {color: "#7F7F7F", "weight": 0 , "fillOpacity": 0.6};
					}

				 }
				 return {color: "#7F7F7F", "weight": 0 , "fillOpacity": 0.6};

		  },

	    _getColor : function(geo,val){
                return colors.getColor(colorbrewer[this._colorBrewerName], geo, val);
	    },

			_refresh : function(){

				if (this._groupLayer){
					this._parts = [];
					this._groupLayer.clearLayers();
					this._loadJson();
					this._computeLegend();
				}

			},

			_refreshStyles : function(){
				if (this._map) {
					if (this._groupLayer){

						var self = this;
						this._groupLayer.eachLayer(function (geojsonlayer) {
							$.each(geojsonlayer._layers,function(idx,layer){
								layer.setStyle(self._styleFunction(layer.feature));
							});
						});

					}
				}
				this._computeLegend();
			},

            getPropertyData: function() {
                return this._properties_data[this._currentProperty];
            },

			_computeLegend : function() {
				var currentProp = this._currentProperty,
						properties,
						index,
						geo,
						min,
						max,
						legendData = [];

				if(currentProp){
					properties = this._properties_data[currentProp];

					if( properties ){

						geo = properties.serie;

						for(index = 0; index < geo.length - 1; index ++){
							min = geo[index];
							max = geo[index + 1];
							legendData.push({
								min: min.toFixed(0),
								max: max.toFixed(0),
								unit: properties.unit,
								color: this._getColor(geo, (max + min)/2.0 )
							});
						}

					$(document).trigger('legend:render', {
						title: properties.title,
						data: legendData
					});

					}
				}

			}

	});

	return CensusLayer;

});
<commit_msg>tweak<commit_after>define([
	'jquery',
	'leaflet',
	'topojson',
	'colorbrewer',
    'hoverTract',
    'colors'
], function($, L, topojson, colorbrewer, hoverTract, colors){

	var CensusLayer = L.Class.extend({

	  initialize: function (iMap,iDataPath) {
           // save position of the layer or any options from the constructor

		   	this._datapath = iDataPath;

	    },

	    onAdd: function (map) {

	      this._map = map;

		   	this._layers_id = [];
		   	this._properties_data = {};

        this._zoom_level = 0;
        this._zoom_change = true;

		   	this._init();

	    },

	    onRemove: function (map) {
	        // remove layer's DOM elements and listeners
	        // map.off('viewreset', this._reset, this);
	    },

	    setProperty : function(iProperty, iColorBrewer) {
				this._currentProperty = iProperty;
				this._colorBrewerName = iColorBrewer;
				this._refreshStyles();
	    },

	    _init : function() {

				this._computZoomLevel();

				this._groupLayer = L.layerGroup([]);
				this._groupLayer.addTo(this._map);

				this._loadJson();

				var self = this;
				this._map.on('zoomend', function(e) {

				   if (self._computZoomLevel()) {
					 self._zoom_change = true;
				   }

				   self._loadJson();

				});

				this._map.on('moveend', function(e) {

			   console.log("moveend");
			   self._loadJson();

				});

				/*
				this._map.on('dragend', function(e) {

				   console.log("dragend");
				   self._loadJson();

				});
				*/

	    },

	    // Zoom management
	    _zoom_max : 18,
	    _zoom_area : [0,  11 , 13 , 15 , 17,  18],

	    _computZoomLevel : function(){

			 	var new_zoom = -1;

				var c_zoom = this._map.getZoom();

				for (var i=0; i<this._zoom_area.length-1; i++) {
					if ((c_zoom> this._zoom_area[i]) && (c_zoom<=this._zoom_area[i+1])) {
						new_zoom = i;
					}
				}

				if (new_zoom!=this._zoom_level) {
					this._zoom_level = new_zoom;
					return true;
				}

				return false;

			},

			// Json load part

			_map_cache : {},

			_loadJson : function (){

				if (this._zoom_change) {

					this._base_folder = this._datapath+"p"+(4-this._zoom_level)+"/";

					var self = this;
					if (!this._loadingProp) {
						this._loadingProp = true;

						var url = this._base_folder + "properties.json";
						$.getJSON( url, function( data_properties ) { // first loading properties
							self._loadingProp = false;
							self._properties_data = data_properties;
							self._loadMapBounds();
							self._computeLegend();

						}).fail(function(d) {
		          console.log("Error occured on loading data...");
							console.log(d);
		        });

					}

				} else {

					console.log('ComputBounds');
					this._computBounds();

				}

			},

			_loadMapBounds : function () {

				var self = this;

				$.getJSON( this._base_folder + "mapbounds.json", function( data ) { // here get bounding maps for
																	   // json MA grid

					self._mapbounds_data = data;

					self._parts = [];
					self._groupLayer.clearLayers();
					self._computBounds();

				});

			},

			_computBounds : function(){

				var bounds = this._map.getBounds();

				this._bounds_to_be_loaded = [];

				for (var i=0; i<this._mapbounds_data.length; i++){

					var southWest = L.latLng(this._mapbounds_data[i].bound.miny, this._mapbounds_data[i].bound.minx),
					    northEast = L.latLng(this._mapbounds_data[i].bound.maxy, this._mapbounds_data[i].bound.maxx);

					var aRecordBnd = L.latLngBounds( southWest, northEast );

					if (aRecordBnd.intersects(bounds)) {

						var key = this._mapbounds_data[i].file;
						this._bounds_to_be_loaded.push(key);

					}

				}

				this._todisplay_parts = [];
				this._geojsondata = {};

				this._loadBoundProcess();

			},

			_loadBoundProcess : function() {

				if (this._bounds_to_be_loaded.length>0) {

					var key = this._bounds_to_be_loaded[0];
					this._bounds_to_be_loaded = this._bounds_to_be_loaded.slice(1,this._bounds_to_be_loaded.length);
					var self = this;
					var url = this._base_folder+key;

					if (this._map_cache[url]) {

						//console.log("Rebuild file:"+url);
						this._computGeoJsonBound( this._map_cache[url] );

					} else {

						//console.log("Loading file:"+url);
						$.getJSON( url , (function(url) { return function( data ) {

							//console.log("Loaded ...");
							var aFeature = topojson.feature(data, data.objects.MA);
							self._map_cache[url] = aFeature;
							self._computGeoJsonBound( aFeature );

						};} ) (url));

					}

				} else {
					this._buildsGroupLayer();
				}

			},

			_computGeoJsonBound : function(data){

				var bounds = this._map.getBounds();

				for (var j=0; j<data.features.length; j++) {

					var aEntity = data.features[j]; // Entity, 1 sector

					if ($.inArray(aEntity.id, this._todisplay_parts)==-1) { // not in array

						var properties = aEntity.properties;

						var subSouthWest = L.latLng(properties.bound.miny, properties.bound.minx),
		    				    subNorthEast = L.latLng(properties.bound.maxy, properties.bound.maxx);

						var aEntityBnd = L.latLngBounds( subSouthWest, subNorthEast );

						if (aEntityBnd.intersects(bounds)) { // Bounds each entity
							this._geojsondata[aEntity.id] = aEntity;
							this._todisplay_parts.push(aEntity.id);
						}

					}

				}

				this._loadBoundProcess();

			},

			_buildsGroupLayer : function(){

				var remove = $(this._parts).not(this._todisplay_parts).get();
				var add =  $(this._todisplay_parts).not(this._parts).get();

				for (var i=0; i<remove.length; i++) {

					if (this._layers_id[remove[i]].length>0) {

						for (var j=0; j<this._layers_id[remove[i]].length; j++) {
							this._groupLayer.removeLayer(this._layers_id[remove[i]][j]);
						}
						this._layers_id[remove[i]] = [];

					}
				}

				for (var i=0; i<add.length; i++) {

					var geojsonLayer = L.geoJson(this._geojsondata[add[i]], {
					    style: $.proxy( this._styleFunction, this)
			   		});

					var self = this;
				    $.each(geojsonLayer._layers,function(idx,layer){
                                        var feature = layer.feature;
                                            layer.feature.map_mouse_over = false;
                                            hoverTract.watchForValue(feature.id,
                                                         function() {
                                                             layer.setStyle(self._styleFunction(feature, true));
                                                         },
                                                         function() {
                                                             layer.setStyle(self._styleFunction(feature));
                                                         });
					    layer.on("mouseover", function (e) {
                                                hoverTract.select(feature.id,
                                                                  feature,
                                                                  self.getPropertyData(),
                                                                  colorbrewer[self._colorBrewerName]);
 					              feature.map_mouse_over = true;
 					              console.log(feature);
 					         });
					    layer.on("mouseout", function (e) {
						       feature.map_mouse_over = false;
                                                // TODO: Move this somewhere else
						       hoverTract.select(null);
					    });

					});

					if (this._layers_id[add[i]]) {
						this._layers_id[add[i]].push(geojsonLayer._leaflet_id);
					} else {
						this._layers_id[add[i]] = [geojsonLayer._leaflet_id];
					}

					this._groupLayer.addLayer(geojsonLayer);
				}

				this._parts = this._todisplay_parts.slice();
				this._zoom_change = false;

			},

			_styleFunction : function(feature,highlight) {

				 if (this._itemStyle){
					return this._itemStyle(feature);
				 }

				 if (this._currentProperty!="") {

					 var prop = this._properties_data[this._currentProperty];

					 var val = parseFloat(feature.properties[this._currentProperty]);

					 if (val) {
						if ((typeof(highlight)!=="undefined") || feature.map_mouse_over==true) {
					 		return {"fillColor": this._getColor(prop.serie, val) , "color" : "#404040" , "weight": 4 , "opacity" : 1.0,  "fillOpacity": 0.6};
						} else {
							return {"fillColor": this._getColor(prop.serie,val) ,  "weight": 0 , "opacity" : 0.0,  "fillOpacity": 0.6};
						}
					} else {
						return {color: "#7F7F7F", "weight": 0 , "fillOpacity": 0.6};
					}

				 }
				 return {color: "#7F7F7F", "weight": 0 , "fillOpacity": 0.6};

		  },

	    _getColor : function(geo,val){
                return colors.getColor(colorbrewer[this._colorBrewerName], geo, val);
	    },

			_refresh : function(){

				if (this._groupLayer){
					this._parts = [];
					this._groupLayer.clearLayers();
					this._loadJson();
					this._computeLegend();
				}

			},

			_refreshStyles : function(){
				if (this._map) {
					if (this._groupLayer){

						var self = this;
						this._groupLayer.eachLayer(function (geojsonlayer) {
							$.each(geojsonlayer._layers,function(idx,layer){
								layer.setStyle(self._styleFunction(layer.feature));
							});
						});

					}
				}
				this._computeLegend();
			},

            getPropertyData: function() {
                return this._properties_data[this._currentProperty];
            },

			_computeLegend : function() {
				var currentProp = this._currentProperty,
						properties,
						index,
						geo,
						min,
						max,
						legendData = [];

				if(currentProp){
					properties = this._properties_data[currentProp];

					if( properties ){

						geo = properties.serie;

						for(index = 0; index < geo.length - 1; index ++){
							min = geo[index];
							max = geo[index + 1];
							legendData.push({
								min: min.toFixed(0),
								max: max.toFixed(0),
								unit: properties.unit,
								color: this._getColor(geo, (max + min)/2.0 )
							});
						}

					$(document).trigger('legend:render', {
						title: properties.title,
						data: legendData
					});

					}
				}

			}

	});

	return CensusLayer;

});
<|endoftext|>


<commit_before>/**
 * echarts图表类：雷达图
 * Copyright 2013 Baidu Inc. All rights reserved.
 *
 * @desc echarts基于Canvas，纯Javascript图表库，提供直观，生动，可交互，可个性化定制的数据统计图表。
 * @author Neil (杨骥, yangji01@baidu.com)
 *
 */

 define(function(require) {
    /**
     * 构造函数
     * @param {Object} messageCenter echart消息中心
     * @param {ZRender} zr zrender实例
     * @param {Object} series 数据
     * @param {Object} component 组件
     */
    function Radar(messageCenter, zr, option, component) {
        // 基类装饰
        var ComponentBase = require('../component/base');
        ComponentBase.call(this, zr);
        // 可计算特性装饰
        var CalculableBase = require('./calculableBase');
        CalculableBase.call(this, zr, option);

        var ecConfig = require('../config');
        var ecData = require('../util/ecData');

        var zrColor = require('zrender/tool/color');

        var self = this;
        self.type = ecConfig.CHART_TYPE_RADAR;

        var series;                 // 共享数据源，不要修改跟自己无关的项
        var serie;

        var _zlevelBase = self.getZlevelBase();

        var _queryTarget;

        var _dropBoxList;

        var _symbol = [
              'circle', 'rectangle', 'triangle', 'diamond',
              'emptyCircle', 'emptyRectangle', 'emptyTriangle', 'emptyDiamond'
            ];
        var _radarDataCounter;

        /**
         * 绘制图形
         */
        function _buildShape() {
            self.selectedMap = {};
            _dropBoxList = [];
            _radarDataCounter = 0;
            for (var i = 0, l = series.length; i < l ; i ++) {
                if (series[i].type == ecConfig.CHART_TYPE_RADAR) {
                    serie = self.reformOption(series[i]);
                    _queryTarget = [serie, option];

                    // 添加可拖拽提示框，多系列共用一个极坐标，第一个优先
                    if (self.deepQuery(_queryTarget, 'calculable')) {
                        _addDropBox(i);
                    }
                    _buildSingleRadar(i);
                }
            }

            for (var i = 0, l = self.shapeList.length; i < l; i++) {
                self.shapeList[i].id = zr.newShapeId(self.type);
                zr.addShape(self.shapeList[i]);
            }

        }

        /**
         * 构建数据图形
         * @param {number} 序列的index
         */
        function _buildSingleRadar(index) {
            var legend = component.legend;
            var data = serie.data;
            var defaultColor;
            var name;
            var shape;
            var pointList;
            var calculable = self.deepQuery(_queryTarget, 'calculable');

            for (var i = 0; i < data.length; i ++) {
                name = data[i].name || '';

                // 图例开关
                self.selectedMap[name] = legend
                                         ? legend.isSelected(name)
                                         : true;
                if (!self.selectedMap[name]) {
                    continue;
                }

                 // 默认颜色策略
                if (legend) {
                    // 有图例则从图例中获取颜色定义
                    defaultColor = legend.getColor(name);
                }
                else {
                    // 全局颜色定义
                    defaultColor = zr.getColor(i);
                }

                pointList = _getPointList(serie.polarIndex, data[i]);
                // 添加拐点形状
                _addSymbol(pointList, defaultColor, data[i], index);
                // 添加数据形状
                _addDataShape(
                    pointList, defaultColor, data[i],
                    index, i, calculable
                );
                _radarDataCounter++;
            }

        }

        /**
         * 获取数据的点集
         * @param {number} polarIndex
         * @param {Array<Object>} 处理的数据
         * @return {Array<Array<number>>} 点集
         */
        function _getPointList(polarIndex, dataArr) {
            var pointList = [];
            var vector;
            var polar = component.polar;

            for (var i = 0, l = dataArr.value.length; i < l; i++) {
                vector = polar.getVector(polarIndex, i, dataArr.value[i]);
                if (vector) {
                    pointList.push(vector);
                }
            }
            return pointList;
        }

        /**
         * 生成折线图上的拐点图形
         */
        function _getSymbol(
            x, y, symbol, symbolSize, normalColor, emphasisColor, lineWidth
        ) {
            var itemShape = {
                shape : 'icon',
                zlevel : _zlevelBase + 1,
                style : {
                    iconType : symbol.replace('empty', '').toLowerCase(),
                    x : x - symbolSize,
                    y : y - symbolSize,
                    width : symbolSize * 2,
                    height : symbolSize * 2,
                    brushType : 'both',
                    color : symbol.match('empty') ? '#fff' : normalColor,
                    strokeColor : normalColor,
                    lineWidth: lineWidth * 2
                },
                hoverable: false
            };

            if (symbol.match('star')) {
                itemShape.style.iconType = 'star';
                itemShape.style.n =
                    (symbol.replace('empty', '').replace('star','') - 0) || 5;
            }

            itemShape._x = x;
            itemShape._y = y;

            return itemShape;
        }

        /**
         * 添加拐点
         * @param {Array<Array<number>>} pointList 点集
         * @param {string} defaultColor 默认填充颜色
         * @param {object} data 数据
         * @param {number} serieIndex
         */
        function _addSymbol(pointList, defaultColor, data, seriesIndex) {
            // 多级控制
            var queryTarget = [data, serie];
            var symbol = self.deepQuery(queryTarget,'symbol')
                         || _symbol[_radarDataCounter % _symbol.length]
                         || 'cricle';

            if (symbol != 'none') {
                var symbolSize = self.deepQuery(queryTarget,'symbolSize');
                var nColor = self.deepQuery(
                    queryTarget, 'itemStyle.normal.color'
                );
                var eColor = self.deepQuery(
                    queryTarget, 'itemStyle.emphasis.color'
                );
                var lineWidth = self.deepQuery(
                    queryTarget, 'itemStyle.normal.lineStyle.width'
                );

                for (var i = 0, l = pointList.length; i < l; i++) {
                    self.shapeList.push(_getSymbol(
                        pointList[i][0],    // x
                        pointList[i][1],    // y
                        symbol,
                        symbolSize,
                        nColor || defaultColor,
                        eColor || nColor || defaultColor,
                        lineWidth
                    ));
                }
            }
        }

        /**
         * 添加数据图形
         * @param {Array<Array<number>>} pointList 点集
         * @param {string} defaultColor 默认填充颜色
         * @param {object} data 数据
         * @param {number} serieIndex
         * @param {number} dataIndex
         * @param {boolean} calcalable
         */
        function _addDataShape(
            pointList, defaultColor, data,
            seriesIndex, dataIndex, calculable
        ) {
            // 多级控制
            var queryTarget = [data, serie];
            var nColor = self.deepQuery(
                queryTarget, 'itemStyle.normal.color'
            );
            var nLineWidth = self.deepQuery(
                queryTarget, 'itemStyle.normal.lineStyle.width'
            );
            var nLineType = self.deepQuery(
                queryTarget, 'itemStyle.normal.lineStyle.type'
            );
            var nAreaColor = self.deepQuery(
                queryTarget, 'itemStyle.normal.areaStyle.color'
            );
            var nIsAreaFill = self.deepQuery(
                queryTarget, 'itemStyle.normal.areaStyle'
            );
            var shape = {
                shape : 'polygon',
                zlevel : _zlevelBase,
                style : {
                    pointList   : pointList,
                    brushType   : nIsAreaFill ? 'both' : 'stroke',
                    color       : nAreaColor || nColor || defaultColor,
                    strokeColor : nColor || defaultColor,
                    lineWidth   : nLineWidth,
                    lineType    : nIsAreaFill ? 'solid' : nLineType
                },
                highlightStyle : {
                    brushType   : self.deepQuery(
                                      queryTarget,
                                      'itemStyle.emphasis.areaStyle'
                                  ) || nIsAreaFill
                                  ? 'both' : 'stroke',
                    color       : self.deepQuery(
                                      queryTarget,
                                      'itemStyle.emphasis.areaStyle.color'
                                  ) || nAreaColor || nColor || defaultColor,
                    strokeColor : self.deepQuery(
                                      queryTarget, 'itemStyle.emphasis.color'
                                  ) || nColor || defaultColor,
                    lineWidth   : self.deepQuery(
                                      queryTarget,
                                      'itemStyle.emphasis.lineStyle.width'
                                  ) || nLineWidth,
                    lineType    : nIsAreaFill
                                  ? 'solid'
                                  : self.deepQuery(
                                        queryTarget,
                                        'itemStyle.emphasis.lineStyle.type'
                                    ) || nLineType
                }
            };
            ecData.pack(
                shape,
                series[seriesIndex],    // 系列
                seriesIndex,            // 系列索引
                data,                   // 数据
                dataIndex,              // 数据索引
                data.name,              // 数据名称
                // 附加指标信息
                component.polar.getIndicator(series[seriesIndex].polarIndex)
            );
            if (calculable) {
                shape.draggable = true;
                self.setCalculable(shape);
            }
            self.shapeList.push(shape);
        }

        /**
         * 增加外围接受框
         * @param {number} serie的序列
         */
        function _addDropBox(index) {
            var polarIndex = self.deepQuery(
                _queryTarget, 'polarIndex'
            );
            if (!_dropBoxList[polarIndex]) {
                var shape = component.polar.getDropBox(polarIndex);
                shape.zlevel = _zlevelBase;
                self.setCalculable(shape);
                ecData.pack(shape, series, index, undefined, -1);
                self.shapeList.push(shape);
                _dropBoxList[polarIndex] = true;
            }
        }

        /**
         * 数据项被拖拽出去，重载基类方法
         */
        function ondragend(param, status) {
            if (!self.isDragend || !param.target) {
                // 没有在当前实例上发生拖拽行为则直接返回
                return;
            }

            var target = param.target;      // 被拖拽图形元素

            var seriesIndex = ecData.get(target, 'seriesIndex');
            var dataIndex = ecData.get(target, 'dataIndex');

            // 被拖拽的图形是饼图sector，删除被拖拽走的数据
            component.legend && component.legend.del(
                series[seriesIndex].data[dataIndex].name
            );

            series[seriesIndex].data.splice(dataIndex, 1);

            // 别status = {}赋值啊！！
            status.dragOut = true;
            status.needRefresh = true;

            // 处理完拖拽事件后复位
            self.isDragend = false;

            return;
        }

         /**
         * 数据项被拖拽进来， 重载基类方法
         */
        function ondrop(param, status) {
            if (!self.isDrop || !param.target) {
                // 没有在当前实例上发生拖拽行为则直接返回
                return;
            }

            var target = param.target;      // 拖拽安放目标
            var dragged = param.dragged;    // 当前被拖拽的图形对象

            var seriesIndex = ecData.get(target, 'seriesIndex');
            var dataIndex = ecData.get(target, 'dataIndex');

            var data;
            var legend = component.legend;
            var value;

            if (dataIndex == -1) {

                data = {
                    value : ecData.get(dragged, 'value'),
                    name : ecData.get(dragged, 'name')
                };

                series[seriesIndex].data.push(data);

                legend && legend.add(
                    data.name,
                    dragged.style.color || dragged.style.strokeColor
                );
            }
            else {
                data = series[seriesIndex].data[dataIndex];
                legend && legend.del(data.name);
                data.name += option.nameConnector
                             + ecData.get(dragged, 'name');
                value = ecData.get(dragged, 'value');
                for (var i = 0 ; i < value.length; i ++) {
                    data.value[i] += value[i];
                }

                legend && legend.add(
                    data.name,
                    dragged.style.color || dragged.style.strokeColor
                );
            }

            // 别status = {}赋值啊！！
            status.dragIn = status.dragIn || true;

            // 处理完拖拽事件后复位
            self.isDrop = false;

            return;
        }

        /**
         * 构造函数默认执行的初始化方法，也用于创建实例后动态修改
         * @param {Object} newZr
         * @param {Object} newSeries
         * @param {Object} newComponent
         */
        function init(newOption, newComponent) {
            component = newComponent;
            refresh(newOption);
        }

        /**
         * 刷新
         */
        function refresh(newOption) {
            if (newOption) {
                option = newOption;
                series = option.series;
            }
            self.clear();
            _buildShape();
        }

        function animation() {
            var duration = self.deepQuery([option], 'animationDuration');
            var easing = self.deepQuery([option], 'animationEasing');
            var dataIndex;
            var seriesIndex;
            var data;
            var serie;
            var polarIndex;
            var polar = component.polar;
            var center;
            var item;
            var x;
            var y;

            for (var i = 0, l = self.shapeList.length; i < l; i++) {
                if (self.shapeList[i].shape == 'polygon') {
                    item = self.shapeList[i];
                    seriesIndex = ecData.get(item, 'seriesIndex');
                    dataIndex = ecData.get(item, 'dataIndex');

                    serie = series[seriesIndex];
                    data = serie.data[dataIndex];

                    polarIndex = self.deepQuery(
                        [data, serie, option], 'polarIndex');
                    center = polar.getCenter(polarIndex);
                    x = center[0];
                    y = center[1];
                    zr.modShape(self.shapeList[i].id, {
                        scale : [0.1, 0.1, x, y]
                    });

                    zr.animate(item.id, '')
                        .when(
                            (self.deepQuery([serie],'animationDuration')
                            || duration)
                            + dataIndex * 100,

                            {scale : [1, 1, x, y]},

                            (self.deepQuery([serie], 'animationEasing')
                            || easing)
                        )
                        .start();
                }
                else {
                    x = self.shapeList[i]._x || 0;
                    y = self.shapeList[i]._y || 0;
                    zr.modShape(self.shapeList[i].id, {
                        scale : [0, 0, x, y]
                    });
                    zr.animate(self.shapeList[i].id, '')
                        .when(
                            duration,
                            {scale : [1, 1, x, y]},
                            'QuinticOut'
                        )
                        .start();
                }
            }

        }

        self.init = init;
        self.refresh = refresh;
        self.animation = animation;
        self.ondrop = ondrop;
        self.ondragend = ondragend;

        init(option, component);
    }

    // 图表注册
    require('../chart').define('radar', Radar);

    return Radar;
});<commit_msg>雷达图优化<commit_after>/**
 * echarts图表类：雷达图
 * Copyright 2013 Baidu Inc. All rights reserved.
 *
 * @desc echarts基于Canvas，纯Javascript图表库，提供直观，生动，可交互，可个性化定制的数据统计图表。
 * @author Neil (杨骥, yangji01@baidu.com)
 *
 */

 define(function(require) {
    /**
     * 构造函数
     * @param {Object} messageCenter echart消息中心
     * @param {ZRender} zr zrender实例
     * @param {Object} series 数据
     * @param {Object} component 组件
     */
    function Radar(messageCenter, zr, option, component) {
        // 基类装饰
        var ComponentBase = require('../component/base');
        ComponentBase.call(this, zr);
        // 可计算特性装饰
        var CalculableBase = require('./calculableBase');
        CalculableBase.call(this, zr, option);

        var ecConfig = require('../config');
        var ecData = require('../util/ecData');

        var zrColor = require('zrender/tool/color');

        var self = this;
        self.type = ecConfig.CHART_TYPE_RADAR;

        var series;                 // 共享数据源，不要修改跟自己无关的项
        var serie;

        var _zlevelBase = self.getZlevelBase();

        var _queryTarget;

        var _dropBoxList;

        var _symbol = [
              'circle', 'rectangle', 'triangle', 'diamond',
              'emptyCircle', 'emptyRectangle', 'emptyTriangle', 'emptyDiamond'
            ];
        var _radarDataCounter;

        /**
         * 绘制图形
         */
        function _buildShape() {
            self.selectedMap = {};
            _dropBoxList = [];
            _radarDataCounter = 0;
            for (var i = 0, l = series.length; i < l ; i ++) {
                if (series[i].type == ecConfig.CHART_TYPE_RADAR) {
                    serie = self.reformOption(series[i]);
                    _queryTarget = [serie, option];

                    // 添加可拖拽提示框，多系列共用一个极坐标，第一个优先
                    if (self.deepQuery(_queryTarget, 'calculable')) {
                        _addDropBox(i);
                    }
                    _buildSingleRadar(i);
                }
            }

            for (var i = 0, l = self.shapeList.length; i < l; i++) {
                self.shapeList[i].id = zr.newShapeId(self.type);
                zr.addShape(self.shapeList[i]);
            }
        }

        /**
         * 构建数据图形
         * @param {number} 序列的index
         */
        function _buildSingleRadar(index) {
            var legend = component.legend;
            var iconShape;
            var data = serie.data;
            var defaultColor;
            var name;
            var shape;
            var pointList;
            var calculable = self.deepQuery(_queryTarget, 'calculable');

            for (var i = 0; i < data.length; i ++) {
                name = data[i].name || '';

                // 图例开关
                self.selectedMap[name] = legend
                                         ? legend.isSelected(name)
                                         : true;
                if (!self.selectedMap[name]) {
                    continue;
                }

                 // 默认颜色策略
                if (legend) {
                    // 有图例则从图例中获取颜色定义
                    defaultColor = legend.getColor(name);
                    iconShape = legend.getItemShape(name);
                    if (iconShape) {
                        // 回调legend，换一个更形象的icon
                        iconShape.style.brushType = self.deepQuery(
                            [data[i], serie], 'itemStyle.normal.areaStyle'
                        ) ? 'both' : 'stroke';
                        legend.setItemShape(name, iconShape);
                    }
                }
                else {
                    // 全局颜色定义
                    defaultColor = zr.getColor(i);
                }

                pointList = _getPointList(serie.polarIndex, data[i]);
                // 添加拐点形状
                _addSymbol(pointList, defaultColor, data[i], index);
                // 添加数据形状
                _addDataShape(
                    pointList, defaultColor, data[i],
                    index, i, calculable
                );
                _radarDataCounter++;
            }

        }

        /**
         * 获取数据的点集
         * @param {number} polarIndex
         * @param {Array<Object>} 处理的数据
         * @return {Array<Array<number>>} 点集
         */
        function _getPointList(polarIndex, dataArr) {
            var pointList = [];
            var vector;
            var polar = component.polar;

            for (var i = 0, l = dataArr.value.length; i < l; i++) {
                vector = polar.getVector(polarIndex, i, dataArr.value[i]);
                if (vector) {
                    pointList.push(vector);
                }
            }
            return pointList;
        }

        /**
         * 生成折线图上的拐点图形
         */
        function _getSymbol(
            x, y, symbol, symbolSize, normalColor, emphasisColor, lineWidth
        ) {
            var itemShape = {
                shape : 'icon',
                zlevel : _zlevelBase + 1,
                style : {
                    iconType : symbol.replace('empty', '').toLowerCase(),
                    x : x - symbolSize,
                    y : y - symbolSize,
                    width : symbolSize * 2,
                    height : symbolSize * 2,
                    brushType : 'both',
                    color : symbol.match('empty') ? '#fff' : normalColor,
                    strokeColor : normalColor,
                    lineWidth: lineWidth * 2
                },
                hoverable: false
            };

            if (symbol.match('star')) {
                itemShape.style.iconType = 'star';
                itemShape.style.n =
                    (symbol.replace('empty', '').replace('star','') - 0) || 5;
            }

            itemShape._x = x;
            itemShape._y = y;

            return itemShape;
        }

        /**
         * 添加拐点
         * @param {Array<Array<number>>} pointList 点集
         * @param {string} defaultColor 默认填充颜色
         * @param {object} data 数据
         * @param {number} serieIndex
         */
        function _addSymbol(pointList, defaultColor, data, seriesIndex) {
            // 多级控制
            var queryTarget = [data, serie];
            var symbol = self.deepQuery(queryTarget,'symbol')
                         || _symbol[_radarDataCounter % _symbol.length]
                         || 'cricle';

            if (symbol != 'none') {
                var symbolSize = self.deepQuery(queryTarget,'symbolSize');
                var nColor = self.deepQuery(
                    queryTarget, 'itemStyle.normal.color'
                );
                var eColor = self.deepQuery(
                    queryTarget, 'itemStyle.emphasis.color'
                );
                var lineWidth = self.deepQuery(
                    queryTarget, 'itemStyle.normal.lineStyle.width'
                );

                for (var i = 0, l = pointList.length; i < l; i++) {
                    self.shapeList.push(_getSymbol(
                        pointList[i][0],    // x
                        pointList[i][1],    // y
                        symbol,
                        symbolSize,
                        nColor || defaultColor,
                        eColor || nColor || defaultColor,
                        lineWidth
                    ));
                }
            }
        }

        /**
         * 添加数据图形
         * @param {Array<Array<number>>} pointList 点集
         * @param {string} defaultColor 默认填充颜色
         * @param {object} data 数据
         * @param {number} serieIndex
         * @param {number} dataIndex
         * @param {boolean} calcalable
         */
        function _addDataShape(
            pointList, defaultColor, data,
            seriesIndex, dataIndex, calculable
        ) {
            // 多级控制
            var queryTarget = [data, serie];
            var nColor = self.deepQuery(
                queryTarget, 'itemStyle.normal.color'
            );
            var nLineWidth = self.deepQuery(
                queryTarget, 'itemStyle.normal.lineStyle.width'
            );
            var nLineType = self.deepQuery(
                queryTarget, 'itemStyle.normal.lineStyle.type'
            );
            var nAreaColor = self.deepQuery(
                queryTarget, 'itemStyle.normal.areaStyle.color'
            );
            var nIsAreaFill = self.deepQuery(
                queryTarget, 'itemStyle.normal.areaStyle'
            );
            var shape = {
                shape : 'polygon',
                zlevel : _zlevelBase,
                style : {
                    pointList   : pointList,
                    brushType   : nIsAreaFill ? 'both' : 'stroke',
                    color       : nAreaColor
                                  || nColor
                                  || zrColor.alpha(defaultColor,0.5),
                    strokeColor : nColor || defaultColor,
                    lineWidth   : nLineWidth,
                    lineType    : nLineType
                },
                highlightStyle : {
                    brushType   : self.deepQuery(
                                      queryTarget,
                                      'itemStyle.emphasis.areaStyle'
                                  ) || nIsAreaFill
                                  ? 'both' : 'stroke',
                    color       : self.deepQuery(
                                      queryTarget,
                                      'itemStyle.emphasis.areaStyle.color'
                                  )
                                  || nAreaColor
                                  || nColor
                                  || zrColor.alpha(defaultColor,0.5),
                    strokeColor : self.deepQuery(
                                      queryTarget, 'itemStyle.emphasis.color'
                                  ) || nColor || defaultColor,
                    lineWidth   : self.deepQuery(
                                      queryTarget,
                                      'itemStyle.emphasis.lineStyle.width'
                                  ) || nLineWidth,
                    lineType    : self.deepQuery(
                                      queryTarget,
                                      'itemStyle.emphasis.lineStyle.type'
                                  ) || nLineType
                }
            };
            ecData.pack(
                shape,
                series[seriesIndex],    // 系列
                seriesIndex,            // 系列索引
                data,                   // 数据
                dataIndex,              // 数据索引
                data.name,              // 数据名称
                // 附加指标信息
                component.polar.getIndicator(series[seriesIndex].polarIndex)
            );
            if (calculable) {
                shape.draggable = true;
                self.setCalculable(shape);
            }
            self.shapeList.push(shape);
        }

        /**
         * 增加外围接受框
         * @param {number} serie的序列
         */
        function _addDropBox(index) {
            var polarIndex = self.deepQuery(
                _queryTarget, 'polarIndex'
            );
            if (!_dropBoxList[polarIndex]) {
                var shape = component.polar.getDropBox(polarIndex);
                shape.zlevel = _zlevelBase;
                self.setCalculable(shape);
                ecData.pack(shape, series, index, undefined, -1);
                self.shapeList.push(shape);
                _dropBoxList[polarIndex] = true;
            }
        }

        /**
         * 数据项被拖拽出去，重载基类方法
         */
        function ondragend(param, status) {
            if (!self.isDragend || !param.target) {
                // 没有在当前实例上发生拖拽行为则直接返回
                return;
            }

            var target = param.target;      // 被拖拽图形元素

            var seriesIndex = ecData.get(target, 'seriesIndex');
            var dataIndex = ecData.get(target, 'dataIndex');

            // 被拖拽的图形是饼图sector，删除被拖拽走的数据
            component.legend && component.legend.del(
                series[seriesIndex].data[dataIndex].name
            );

            series[seriesIndex].data.splice(dataIndex, 1);

            // 别status = {}赋值啊！！
            status.dragOut = true;
            status.needRefresh = true;

            // 处理完拖拽事件后复位
            self.isDragend = false;

            return;
        }

         /**
         * 数据项被拖拽进来， 重载基类方法
         */
        function ondrop(param, status) {
            if (!self.isDrop || !param.target) {
                // 没有在当前实例上发生拖拽行为则直接返回
                return;
            }

            var target = param.target;      // 拖拽安放目标
            var dragged = param.dragged;    // 当前被拖拽的图形对象

            var seriesIndex = ecData.get(target, 'seriesIndex');
            var dataIndex = ecData.get(target, 'dataIndex');

            var data;
            var legend = component.legend;
            var value;

            if (dataIndex == -1) {

                data = {
                    value : ecData.get(dragged, 'value'),
                    name : ecData.get(dragged, 'name')
                };

                series[seriesIndex].data.push(data);

                legend && legend.add(
                    data.name,
                    dragged.style.color || dragged.style.strokeColor
                );
            }
            else {
                data = series[seriesIndex].data[dataIndex];
                legend && legend.del(data.name);
                data.name += option.nameConnector
                             + ecData.get(dragged, 'name');
                value = ecData.get(dragged, 'value');
                for (var i = 0 ; i < value.length; i ++) {
                    data.value[i] += value[i];
                }

                legend && legend.add(
                    data.name,
                    dragged.style.color || dragged.style.strokeColor
                );
            }

            // 别status = {}赋值啊！！
            status.dragIn = status.dragIn || true;

            // 处理完拖拽事件后复位
            self.isDrop = false;

            return;
        }

        /**
         * 构造函数默认执行的初始化方法，也用于创建实例后动态修改
         * @param {Object} newZr
         * @param {Object} newSeries
         * @param {Object} newComponent
         */
        function init(newOption, newComponent) {
            component = newComponent;
            refresh(newOption);
        }

        /**
         * 刷新
         */
        function refresh(newOption) {
            if (newOption) {
                option = newOption;
                series = option.series;
            }
            self.clear();
            _buildShape();
        }

        function animation() {
            var duration = self.deepQuery([option], 'animationDuration');
            var easing = self.deepQuery([option], 'animationEasing');
            var dataIndex;
            var seriesIndex;
            var data;
            var serie;
            var polarIndex;
            var polar = component.polar;
            var center;
            var item;
            var x;
            var y;

            for (var i = 0, l = self.shapeList.length; i < l; i++) {
                if (self.shapeList[i].shape == 'polygon') {
                    item = self.shapeList[i];
                    seriesIndex = ecData.get(item, 'seriesIndex');
                    dataIndex = ecData.get(item, 'dataIndex');

                    serie = series[seriesIndex];
                    data = serie.data[dataIndex];

                    polarIndex = self.deepQuery(
                        [data, serie, option], 'polarIndex');
                    center = polar.getCenter(polarIndex);
                    x = center[0];
                    y = center[1];
                    zr.modShape(self.shapeList[i].id, {
                        scale : [0.1, 0.1, x, y]
                    });

                    zr.animate(item.id, '')
                        .when(
                            (self.deepQuery([serie],'animationDuration')
                            || duration)
                            + dataIndex * 100,

                            {scale : [1, 1, x, y]},

                            (self.deepQuery([serie], 'animationEasing')
                            || easing)
                        )
                        .start();
                }
                else {
                    x = self.shapeList[i]._x || 0;
                    y = self.shapeList[i]._y || 0;
                    zr.modShape(self.shapeList[i].id, {
                        scale : [0, 0, x, y]
                    });
                    zr.animate(self.shapeList[i].id, '')
                        .when(
                            duration,
                            {scale : [1, 1, x, y]},
                            'QuinticOut'
                        )
                        .start();
                }
            }

        }

        self.init = init;
        self.refresh = refresh;
        self.animation = animation;
        self.ondrop = ondrop;
        self.ondragend = ondragend;

        init(option, component);
    }

    // 图表注册
    require('../chart').define('radar', Radar);

    return Radar;
});<|endoftext|>


<commit_before>charts.line = function(args) {
    'use strict';
    this.args = args;

    this.init = function(args) {
        raw_data_transformation(args);
        process_line(args);
        init(args);
        x_axis(args);
        y_axis(args);
        return this;
    }

    this.mainPlot = function() {
        var svg = d3.select($(args.target).find('svg').get(0));
        var g;
        var data_median = 0;

        //main area
        var area = d3.svg.area()
            .x(args.scalefns.xf)
            .y0(args.scales.Y.range()[0])
            .y1(args.scalefns.yf)
            .interpolate(args.interpolate);

        //confidence band
        var confidence_area;
        if(args.show_confidence_band) {
            var confidence_area = d3.svg.area()
                .x(args.scalefns.xf)
                .y0(function(d) {
                    var l = args.show_confidence_band[0];
                    return args.scales.Y(d[l]);
                })
                .y1(function(d) {
                    var u = args.show_confidence_band[1];
                    return args.scales.Y(d[u]);
                })
                .interpolate(args.interpolate);
        }

        //main line
        var line = d3.svg.line()
            .x(args.scalefns.xf)
            .y(args.scalefns.yf)
            .interpolate(args.interpolate);

        //for animating line on first load
        var flat_line = d3.svg.line()
            .x(args.scalefns.xf)
            .y(function() { return args.scales.Y(data_median); })
            .interpolate(args.interpolate);

        //for building the optional legend
        var legend = '';
        var this_data;

        for(var i=args.data.length-1; i>=0; i--) {
            this_data = args.data[i];

            //override increment if we have a custom increment series
            var line_id = i+1;
            if(args.custom_line_color_map.length > 0) {
                line_id = args.custom_line_color_map[i];
            }

            //add confidence band
            if(args.show_confidence_band) {
                svg.append('path')
                    .attr('class', 'confidence-band')
                    .attr('d', confidence_area(args.data[i]));
            }

            //add the area
            var $area = $(args.target).find('svg path.area' + (line_id) + '-color');
            if(args.area && !args.use_data_y_min && !args.y_axis_negative && args.data.length <= 1) {
                //if area already exists, transition it
                if($area.length > 0) {
                    d3.selectAll($(args.target).find('svg path.area' + (line_id) + '-color'))
                        .transition()
                            .duration(function() {
                                return (args.transition_on_update) ? 1000 : 0;
                            })
                            .attr('d', area(args.data[i]));
                }
                else { //otherwise, add the area
                    svg.append('path')
                        .attr('class', 'main-area ' + 'area' + (line_id) + '-color')
                        .attr('d', area(args.data[i]));
                }
            } else if ($area.length > 0) {
              $area.remove();
            }

            //add the line, if it already exists, transition the fine gentleman
            if($(args.target).find('svg path.line' + (line_id) + '-color').length > 0) {
                d3.selectAll($(args.target).find('svg path.line' + (line_id) + '-color'))
                    .transition()
                        .duration(function() {
                            return (args.transition_on_update) ? 1000 : 0;
                        })
                        .attr('d', line(args.data[i]));
            }
            else { //otherwise...
                //if we're animating on load, animate the line from its median value
                if(args.animate_on_load) {
                    data_median = d3.median(args.data[i], function(d) {
                        return d[args.y_accessor];
                    })

                    svg.append('path')
                        .attr('class', 'main-line ' + 'line' + (line_id) + '-color')
                        .attr('d', flat_line(args.data[i]))
                        .transition()
                            .duration(1000)
                            .attr('d', line(args.data[i]));
                }
                else { //or just add the line
                    svg.append('path')
                        .attr('class', 'main-line ' + 'line' + (line_id) + '-color')
                        .attr('d', line(args.data[i]));
                }
            }

            //build legend
            if(args.legend) {
                legend = "<span class='line" + line_id  + "-legend-color'>— "
                        + args.legend[i] + "  </span>" + legend;
            }
        }

        if(args.legend) {
            $(args.legend_target).html(legend);
        }

        return this;
    }

    this.markers = function() {
        markers(args);
        return this;
    };

    this.rollover = function() {
        var svg = d3.select($(args.target).find('svg').get(0));
        var $svg = $(svg);
        var g;

        //remove the old rollovers if they already exist
        $svg.find('.transparent-rollover-rect').remove();
        $svg.find('.voronoi').remove();

        //remove the old rollover text and circle if they already exist
        $svg.find('.active_datapoint').remove();
        $svg.find('.line_rollover_circle').remove();

        //rollover text
        svg.append('text')
            .attr('class', 'active_datapoint')
            .classed('active-datapoint-small', args.use_small_class)
            .attr('xml:space', 'preserve')
            .attr('x', args.width - args.right)
            .attr('y', args.top / 2)
            .attr('text-anchor', 'end');

        //append circle
        svg.append('circle')
            .classed('line_rollover_circle', true)
            .attr('cx', 0)
            .attr('cy', 0)
            .attr('r', 0);

        //update our data by setting a unique line id for each series
        //increment from 1... unless we have a custom increment series
        var line_id = 1;

        for(var i=0;i<args.data.length;i++) {
            for(var j=0;j<args.data[i].length;j++) {
                //if custom line-color map is set, use that instead of line_id
                if(args.custom_line_color_map.length > 0) {
                    args.data[i][j]['line_id'] = args.custom_line_color_map[i];
                }
                else {
                    args.data[i][j]['line_id'] = line_id;
                }
            }
            line_id++;
        }

        //for multi-line, use voronoi
        if(args.data.length > 1) {
            //main rollover
            var voronoi = d3.geom.voronoi()
                .x(function(d) { return args.scales.X(d[args.x_accessor]).toFixed(2); })
                .y(function(d) { return args.scales.Y(d[args.y_accessor]).toFixed(2); })
                .clipExtent([[args.buffer, args.buffer], [args.width - args.buffer, args.height - args.buffer]]);

            var g = svg.append('g')
                .attr('class', 'voronoi')

            //we'll be using these when constructing the voronoi rollovers
            var data_nested = d3.nest()
                .key(function(d) {
                    return args.scales.X(d[args.x_accessor]) + ","
                        + args.scales.Y(d[args.y_accessor]);
                })
                .rollup(function(v) { return v[0]; })
                .entries(d3.merge(args.data.map(function(d) { return d; })))
                .map(function(d) { return d.values; });

            //add the voronoi rollovers
            g.selectAll('path')
                .data(voronoi(data_nested))
                .enter()
                    .append('path')
                        .attr("d", function(d) { return "M" + d.join("L") + "Z"; })
                        .datum(function(d) { return d.point; }) //because of d3.nest, reassign d
                        .attr('class', function(d) {
                            if(args.linked) {
                                var v = d[args.x_accessor];
                                var formatter = d3.time.format('%Y-%m-%d');

                                return 'line' + d['line_id'] + '-color ' + 'roll_' + formatter(v);
                            }
                            else {
                                return 'line' + d['line_id'] + '-color';
                            }
                        })
                        .on('mouseover', this.rolloverOn(args))
                        .on('mouseout', this.rolloverOff(args));
        }
        //for single line, use rects
        else {
            //set to 1 unless we have a custom increment series
            var line_id = 1;
            if(args.custom_line_color_map.length > 0) {
                line_id = args.custom_line_color_map[0];
            }

            var g = svg.append('g')
                .attr('class', 'transparent-rollover-rect')

            var xf = args.data[0].map(args.scalefns.xf);

            g.selectAll('.rollover-rects')
                .data(args.data[0]).enter()
                    .append('rect')
                        .attr('class', function(d, i) {
                            if(args.linked) {
                                var v = d[args.x_accessor];
                                var formatter = d3.time.format('%Y-%m-%d');

                                //only format when y-axis is date
                                var id = (typeof v === 'number')
                                        ? i
                                        : formatter(v);

                                return 'line' + line_id + '-color ' + 'roll_' + id;
                            }
                            else {
                                return 'line' + line_id + '-color';
                            }
                        })
                        .attr('x', function(d, i) {
                            if (i == 0) {
                                return xf[i].toFixed(2);
                            } else {
                                return ((xf[i-1] + xf[i])/2).toFixed(2);
                            }
                        })
                        .attr('y', function(d, i) {
                            return (args.data.length > 1)
                                ? args.scalefns.yf(d) - 6 //multi-line chart sensitivity
                                : args.top;
                        })
                        .attr('width', function(d, i) {
                            if (i == 0) {
                                return ((xf[i+1] - xf[i]) / 2).toFixed(2);
                            }
                            else if (i == xf.length - 1) {
                                return ((xf[i] - xf[i-1]) / 2).toFixed(2);
                            }
                            else {
                                return ((xf[i+1] - xf[i-1]) / 2).toFixed(2);
                            }
                        })
                        .attr('height', function(d, i) {
                            return (args.data.length > 1)
                                ? 12 //multi-line chart sensitivity
                                : args.height - args.bottom - args.top - args.buffer;
                        })
                        .attr('opacity', 0)
                        .on('mouseover', this.rolloverOn(args))
                        .on('mouseout', this.rolloverOff(args));
        }

        return this;
    }

    this.rolloverOn = function(args) {
        var svg = d3.select($(args.target).find('svg').get(0));
        var x_formatter = d3.time.format('%Y-%m-%d');

        return function(d, i) {
            //show circle on mouse-overed rect
            svg.selectAll('circle.line_rollover_circle')
                .attr('class', "")
                .attr('class', 'area' + d['line_id'] + '-color')
                .classed('line_rollover_circle', true)
                .attr('cx', function() {
                    return args.scales.X(d[args.x_accessor]).toFixed(2);
                })
                .attr('cy', function() {
                    return args.scales.Y(d[args.y_accessor]).toFixed(2);
                })
                .attr('r', args.point_size)
                .style('opacity', 1);

            //trigger mouseover on all rects for this date in .linked charts
            if(args.linked && !globals.link) {
                globals.link = true;

                var v = d[args.x_accessor];
                var formatter = d3.time.format('%Y-%m-%d');

                //only format when y-axis is date
                var id = (typeof v === 'number')
                        ? i
                        : formatter(v);

                //trigger mouseover on matching line in .linked charts
                d3.selectAll('.line' + d['line_id'] + '-color.roll_' + id)
                    .each(function(d, i) {
                        d3.select(this).on('mouseover')(d,i);
                })
            }

            svg.selectAll('text')
                .filter(function(g, j) {
                    return d == g;
                })
                .attr('opacity', 0.3);

            var fmt = d3.time.format('%b %e, %Y');

            if (args.format == 'count') {
                var num = function(d_) {
                    var is_float = d_ % 1 != 0;
                    var n = d3.format("0,000");
                    d_ = is_float ? d3.round(d_, args.decimals) : d_;
                    return n(d_);
                }
            }
            else {
                var num = function(d_) {
                    var fmt_string = (args.decimals ? '.' + args.decimals : '' ) + '%';
                    var n = d3.format(fmt_string);
                    return n(d_);
                }
            }

            //update rollover text
            if (args.show_rollover_text) {
                svg.select('.active_datapoint')
                    .text(function() {
                        if(args.time_series) {
                            var dd = new Date(+d[args.x_accessor]);
                            dd.setDate(dd.getDate());

                            return fmt(dd) + '  ' + args.yax_units
                                + num(d[args.y_accessor]);
                        }
                        else {
                            return args.x_accessor + ': ' + d[args.x_accessor]
                                + ', ' + args.y_accessor + ': ' + args.yax_units
                                + num(d[args.y_accessor]);
                        }
                    });
            }

            if(args.rollover_callback) {
                args.rollover_callback(d, i);
            }
        }
    }

    this.rolloverOff = function(args) {
        var svg = d3.select($(args.target).find('svg').get(0));

        return function(d, i) {
            if(args.linked && globals.link) {
                globals.link = false;

                var v = d[args.x_accessor];
                var formatter = d3.time.format('%Y-%m-%d');

                //only format when y-axis is date
                var id = (typeof v === 'number')
                        ? i
                        : formatter(v);

                d3.selectAll('.roll_' + id)
                    .each(function(d, i) {
                        d3.select(this).on('mouseout')(d);
                });
            }

            //remove active datapoint text on mouse out
            svg.selectAll('circle.line_rollover_circle')
                .style('opacity', 0);

            svg.select('.active_datapoint')
                .text('');
        }
    }

    this.init(args);
    return this;
}
<commit_msg>Fix issue #228<commit_after>charts.line = function(args) {
    'use strict';
    this.args = args;

    this.init = function(args) {
        raw_data_transformation(args);
        process_line(args);
        init(args);
        x_axis(args);
        y_axis(args);
        return this;
    }

    this.mainPlot = function() {
        var svg = d3.select($(args.target).find('svg').get(0));
        var g;
        var data_median = 0;

        //main area
        var area = d3.svg.area()
            .x(args.scalefns.xf)
            .y0(args.scales.Y.range()[0])
            .y1(args.scalefns.yf)
            .interpolate(args.interpolate);

        //confidence band
        var confidence_area;
        if(args.show_confidence_band) {
            var confidence_area = d3.svg.area()
                .x(args.scalefns.xf)
                .y0(function(d) {
                    var l = args.show_confidence_band[0];
                    return args.scales.Y(d[l]);
                })
                .y1(function(d) {
                    var u = args.show_confidence_band[1];
                    return args.scales.Y(d[u]);
                })
                .interpolate(args.interpolate);
        }

        //main line
        var line = d3.svg.line()
            .x(args.scalefns.xf)
            .y(args.scalefns.yf)
            .interpolate(args.interpolate);

        //for animating line on first load
        var flat_line = d3.svg.line()
            .x(args.scalefns.xf)
            .y(function() { return args.scales.Y(data_median); })
            .interpolate(args.interpolate);

        //for building the optional legend
        var legend = '';
        var this_data;

        for(var i=args.data.length-1; i>=0; i--) {
            this_data = args.data[i];

            //override increment if we have a custom increment series
            var line_id = i+1;
            if(args.custom_line_color_map.length > 0) {
                line_id = args.custom_line_color_map[i];
            }

            //add confidence band
            if(args.show_confidence_band) {
                svg.append('path')
                    .attr('class', 'confidence-band')
                    .attr('d', confidence_area(args.data[i]));
            }

            //add the area
            var $area = $(args.target).find('svg path.area' + (line_id) + '-color');
            if(args.area && !args.use_data_y_min && !args.y_axis_negative && args.data.length <= 1) {
                //if area already exists, transition it
                if($area.length > 0) {
                    d3.selectAll($(args.target).find('svg path.area' + (line_id) + '-color'))
                        .transition()
                            .duration(function() {
                                return (args.transition_on_update) ? 1000 : 0;
                            })
                            .attr('d', area(args.data[i]));
                }
                else { //otherwise, add the area
                    svg.append('path')
                        .attr('class', 'main-area ' + 'area' + (line_id) + '-color')
                        .attr('d', area(args.data[i]));
                }
            } else if ($area.length > 0) {
              $area.remove();
            }

            //add the line, if it already exists, transition the fine gentleman
            if($(args.target).find('svg path.line' + (line_id) + '-color').length > 0) {
                d3.selectAll($(args.target).find('svg path.line' + (line_id) + '-color'))
                    .transition()
                        .duration(function() {
                            return (args.transition_on_update) ? 1000 : 0;
                        })
                        .attr('d', line(args.data[i]));
            }
            else { //otherwise...
                //if we're animating on load, animate the line from its median value
                if(args.animate_on_load) {
                    data_median = d3.median(args.data[i], function(d) {
                        return d[args.y_accessor];
                    })

                    svg.append('path')
                        .attr('class', 'main-line ' + 'line' + (line_id) + '-color')
                        .attr('d', flat_line(args.data[i]))
                        .transition()
                            .duration(1000)
                            .attr('d', line(args.data[i]));
                }
                else { //or just add the line
                    svg.append('path')
                        .attr('class', 'main-line ' + 'line' + (line_id) + '-color')
                        .attr('d', line(args.data[i]));
                }
            }

            //build legend
            if(args.legend) {
                legend = "<span class='line" + line_id  + "-legend-color'>— "
                        + args.legend[i] + "  </span>" + legend;
            }
        }

        if(args.legend) {
            $(args.legend_target).html(legend);
        }

        return this;
    }

    this.markers = function() {
        markers(args);
        return this;
    };

    this.rollover = function() {
        var svg = d3.select($(args.target).find('svg').get(0));
        var $svg = $(svg);
        var g;

        //remove the old rollovers if they already exist
        $svg.find('.transparent-rollover-rect').remove();
        $svg.find('.voronoi').remove();

        //remove the old rollover text and circle if they already exist
        $svg.find('.active_datapoint').remove();
        $svg.find('.line_rollover_circle').remove();

        //rollover text
        svg.append('text')
            .attr('class', 'active_datapoint')
            .classed('active-datapoint-small', args.use_small_class)
            .attr('xml:space', 'preserve')
            .attr('x', args.width - args.right)
            .attr('y', args.top / 2)
            .attr('text-anchor', 'end');

        //append circle
        svg.append('circle')
            .classed('line_rollover_circle', true)
            .attr('cx', 0)
            .attr('cy', 0)
            .attr('r', 0);

        //update our data by setting a unique line id for each series
        //increment from 1... unless we have a custom increment series
        var line_id = 1;

        for(var i=0;i<args.data.length;i++) {
            for(var j=0;j<args.data[i].length;j++) {
                //if custom line-color map is set, use that instead of line_id
                if(args.custom_line_color_map.length > 0) {
                    args.data[i][j]['line_id'] = args.custom_line_color_map[i];
                }
                else {
                    args.data[i][j]['line_id'] = line_id;
                }
            }
            line_id++;
        }

        //for multi-line, use voronoi
        if(args.data.length > 1) {
            //main rollover
            var voronoi = d3.geom.voronoi()
                .x(function(d) { return args.scales.X(d[args.x_accessor]).toFixed(2); })
                .y(function(d) { return args.scales.Y(d[args.y_accessor]).toFixed(2); })
                .clipExtent([[args.buffer, args.buffer], [args.width - args.buffer, args.height - args.buffer]]);

            var g = svg.append('g')
                .attr('class', 'voronoi')

            //we'll be using these when constructing the voronoi rollovers
            var data_nested = d3.nest()
                .key(function(d) {
                    return args.scales.X(d[args.x_accessor]) + ","
                        + args.scales.Y(d[args.y_accessor]);
                })
                .rollup(function(v) { return v[0]; })
                .entries(d3.merge(args.data.map(function(d) { return d; })))
                .map(function(d) { return d.values; });

            //add the voronoi rollovers
            g.selectAll('path')
                .data(voronoi(data_nested))
                .enter()
                    .append('path')
                        .attr("d", function(d) { return "M" + d.join("L") + "Z"; })
                        .datum(function(d) { return d.point; }) //because of d3.nest, reassign d
                        .attr('class', function(d) {
                            if(args.linked) {
                                var v = d[args.x_accessor];
                                var formatter = d3.time.format('%Y-%m-%d');

                                //only format when x-axis is date
                                var id = (typeof v === 'number')
                                        ? i
                                        : formatter(v);

                                return 'line' + d['line_id'] + '-color ' + 'roll_' + id;
                            }
                            else {
                                return 'line' + d['line_id'] + '-color';
                            }
                        })
                        .on('mouseover', this.rolloverOn(args))
                        .on('mouseout', this.rolloverOff(args));
        }
        //for single line, use rects
        else {
            //set to 1 unless we have a custom increment series
            var line_id = 1;
            if(args.custom_line_color_map.length > 0) {
                line_id = args.custom_line_color_map[0];
            }

            var g = svg.append('g')
                .attr('class', 'transparent-rollover-rect')

            var xf = args.data[0].map(args.scalefns.xf);

            g.selectAll('.rollover-rects')
                .data(args.data[0]).enter()
                    .append('rect')
                        .attr('class', function(d, i) {
                            if(args.linked) {
                                var v = d[args.x_accessor];
                                var formatter = d3.time.format('%Y-%m-%d');

                                //only format when x-axis is date
                                var id = (typeof v === 'number')
                                        ? i
                                        : formatter(v);

                                return 'line' + line_id + '-color ' + 'roll_' + id;
                            }
                            else {
                                return 'line' + line_id + '-color';
                            }
                        })
                        .attr('x', function(d, i) {
                            if (i == 0) {
                                return xf[i].toFixed(2);
                            } else {
                                return ((xf[i-1] + xf[i])/2).toFixed(2);
                            }
                        })
                        .attr('y', function(d, i) {
                            return (args.data.length > 1)
                                ? args.scalefns.yf(d) - 6 //multi-line chart sensitivity
                                : args.top;
                        })
                        .attr('width', function(d, i) {
                            if (i == 0) {
                                return ((xf[i+1] - xf[i]) / 2).toFixed(2);
                            }
                            else if (i == xf.length - 1) {
                                return ((xf[i] - xf[i-1]) / 2).toFixed(2);
                            }
                            else {
                                return ((xf[i+1] - xf[i-1]) / 2).toFixed(2);
                            }
                        })
                        .attr('height', function(d, i) {
                            return (args.data.length > 1)
                                ? 12 //multi-line chart sensitivity
                                : args.height - args.bottom - args.top - args.buffer;
                        })
                        .attr('opacity', 0)
                        .on('mouseover', this.rolloverOn(args))
                        .on('mouseout', this.rolloverOff(args));
        }

        return this;
    }

    this.rolloverOn = function(args) {
        var svg = d3.select($(args.target).find('svg').get(0));
        var x_formatter = d3.time.format('%Y-%m-%d');

        return function(d, i) {
            //show circle on mouse-overed rect
            svg.selectAll('circle.line_rollover_circle')
                .attr('class', "")
                .attr('class', 'area' + d['line_id'] + '-color')
                .classed('line_rollover_circle', true)
                .attr('cx', function() {
                    return args.scales.X(d[args.x_accessor]).toFixed(2);
                })
                .attr('cy', function() {
                    return args.scales.Y(d[args.y_accessor]).toFixed(2);
                })
                .attr('r', args.point_size)
                .style('opacity', 1);

            //trigger mouseover on all rects for this date in .linked charts
            if(args.linked && !globals.link) {
                globals.link = true;

                var v = d[args.x_accessor];
                var formatter = d3.time.format('%Y-%m-%d');

                //only format when y-axis is date
                var id = (typeof v === 'number')
                        ? i
                        : formatter(v);

                //trigger mouseover on matching line in .linked charts
                d3.selectAll('.line' + d['line_id'] + '-color.roll_' + id)
                    .each(function(d, i) {
                        d3.select(this).on('mouseover')(d,i);
                })
            }

            svg.selectAll('text')
                .filter(function(g, j) {
                    return d == g;
                })
                .attr('opacity', 0.3);

            var fmt = d3.time.format('%b %e, %Y');

            if (args.format == 'count') {
                var num = function(d_) {
                    var is_float = d_ % 1 != 0;
                    var n = d3.format("0,000");
                    d_ = is_float ? d3.round(d_, args.decimals) : d_;
                    return n(d_);
                }
            }
            else {
                var num = function(d_) {
                    var fmt_string = (args.decimals ? '.' + args.decimals : '' ) + '%';
                    var n = d3.format(fmt_string);
                    return n(d_);
                }
            }

            //update rollover text
            if (args.show_rollover_text) {
                svg.select('.active_datapoint')
                    .text(function() {
                        if(args.time_series) {
                            var dd = new Date(+d[args.x_accessor]);
                            dd.setDate(dd.getDate());

                            return fmt(dd) + '  ' + args.yax_units
                                + num(d[args.y_accessor]);
                        }
                        else {
                            return args.x_accessor + ': ' + d[args.x_accessor]
                                + ', ' + args.y_accessor + ': ' + args.yax_units
                                + num(d[args.y_accessor]);
                        }
                    });
            }

            if(args.rollover_callback) {
                args.rollover_callback(d, i);
            }
        }
    }

    this.rolloverOff = function(args) {
        var svg = d3.select($(args.target).find('svg').get(0));

        return function(d, i) {
            if(args.linked && globals.link) {
                globals.link = false;

                var v = d[args.x_accessor];
                var formatter = d3.time.format('%Y-%m-%d');

                //only format when y-axis is date
                var id = (typeof v === 'number')
                        ? i
                        : formatter(v);

                d3.selectAll('.roll_' + id)
                    .each(function(d, i) {
                        d3.select(this).on('mouseout')(d);
                });
            }

            //remove active datapoint text on mouse out
            svg.selectAll('circle.line_rollover_circle')
                .style('opacity', 0);

            svg.select('.active_datapoint')
                .text('');
        }
    }

    this.init(args);
    return this;
}
<|endoftext|>


<commit_before>const chordRegex = /((\\)?\b[A-G](?:(?:add|dim|aug|maj|mM|mMaj|sus|m|b|#|\d)?(?:\/[A-G0-9])?)*(?!\||—|-|\.|:)(?:\b|#)+)/g;

export const parseLine = line => {
    const result = [];

    let match;
    do {
        match = chordRegex.exec(line);
        if (match) {
            const info = { chord: match[0], position: match.index + 1 };
            result.push(info);
        }
    } while (match);

    return result;
};

export const parseInputText = text => {
    const lyrics = [];
    let chords = [];

    const inputLines = text.split('\n');

    for (let i = 0; i < inputLines.length; i += 2) {
        const chordLine = inputLines[i];
        const lyricLine = inputLines[i + 1].trim();

        lyrics.push(lyricLine);

        const parsedChords = parseLine(chordLine).map(info => ({
            chord: info.chord,
            line: i / 2 + 1,
            position: info.position,
        }));
        chords = chords.concat(parsedChords);
    }

    return { lyrics, chords };
};
<commit_msg>Don't crash when trailing line is missing<commit_after>const chordRegex = /((\\)?\b[A-G](?:(?:add|dim|aug|maj|mM|mMaj|sus|m|b|#|\d)?(?:\/[A-G0-9])?)*(?!\||—|-|\.|:)(?:\b|#)+)/g;

export const parseLine = line => {
    const result = [];

    let match;
    do {
        match = chordRegex.exec(line);
        if (match) {
            const info = { chord: match[0], position: match.index + 1 };
            result.push(info);
        }
    } while (match);

    return result;
};

export const parseInputText = text => {
    const lyrics = [];
    let chords = [];

    const inputLines = text.split('\n');

    for (let i = 0; i < inputLines.length; i += 2) {
        const chordLine = inputLines[i];
        const lyricLine = (inputLines[i + 1] || '').trim();

        lyrics.push(lyricLine);

        const parsedChords = parseLine(chordLine).map(info => ({
            chord: info.chord,
            line: i / 2 + 1,
            position: info.position,
        }));
        chords = chords.concat(parsedChords);
    }

    return { lyrics, chords };
};
<|endoftext|>