d3.js绘制散点图+置信区间+箭头

实现如下效果:

散点图+置信区间+箭头
一、json数据:
 

{
    "data":[{"name":"A_1", "value":[-6.89292010667, -46.3134198876]},{"name":"A_2", "value":[-38.4093008453, 72.8325103196],"color":"#ff0000", "symbol":"square","color_category_name":"A", "symbol_category_name":"B"},{"name":"A_3","value":[-9.86094028781, 2.30333750613],"color":"#388E3C", "symbol":"circle","color_category_name":"A", "symbol_category_name":"B"},{"name":"B_1","value":[91.2493989001, 16.4003414111],"color":"#388E3C", "symbol":"circle","color_category_name":"B", "symbol_category_name":"B"},{"name":"B_2", "value":[-14.1303963457, 1.46942794051],"color":"#388E3C", "symbol":"circle","color_category_name":"B", "symbol_category_name":"A"},{"name":"B_3", "value":[-21.9558413147, -46.6921972897],"color":"#388E3C", "symbol":"circle","color_category_name":"A", "symbol_category_name":"A"}],

    "marker":[
        {"name":"vector", "color":"#ff0000", "data":[["P", -1157.2167, -539.4148],["C", -108.123, -768.7028],["N", -1644.1453, 231.7858],["C_N", 587.9731, -1202.0454]]},
        {"name":"vector1", "color":"#ccc", "data":[["P", -1057.2167, -339.4148],["C", -98.123, -668.7028],["N", -1444.1453, 131.7858],["C_N", 487.9731, -1002.0454]]}
    ],
	"ellipse": [
		{"color": "#388E3C","name": "type1","c22": 87.859328134468,"c21": -31.536237516401,"cy": 189.26165885308,"cx": 151.22500150983,"c12": 22.719880658555,"c11": 121.95278143051}
	],
    "params":{
        "title":"PCA Analysis",
        "x_label":"PC1(21.88%)",
        "y_label":"PC2(20.39)",
		"show_label":true,
		"show_grid":true,
		"show_legend":true,
		"show_marker":true,
		"show_ellipse":true
    },
	"size":{
		"width":800,
		"height":600
	}
}

参数说明:

  • 用途:散点图+环境因子(箭头)+置信区间(椭圆)(不需要指定的属性可以去掉)
  • *data[name:标签,value[x轴,y轴], color:散点颜色, symbol:散点形状, color_category_name:颜色对应的图例名称,symbol_category_name:形状对应的图例名称]
  • marker:环境因子,二维数组
  • ellipse:置信区间(画椭圆)
  • show_label:显示散点的标签名
  • title:标题
  • x_label:x轴标签
  • y_label:y轴标签
  • show_grid:true|false 是否显示网格
  • show_legend:true|false, 是否显示图例
  • show_marker:true|false, 是否显示环境因子
  • show_ellipse:true|false, 是否显示置信区间

以下为toolip的样式:

<style>
.scatter_tooltip{  
	font-family:simsun;  
	font-size:16px;  
	width:120;  
	height:auto;  
	position:absolute;   
	text-align:center;  
	border-style:solid;  
	border-width:1px;  
	background-color:white;  
	border-radius:5px;
	text-align:left;
}  
</style>


二、用到的js插件
#jquery插件

<script src="jquery-1.8.3.min.js"></script>
#d3 v3版本插件

<script src="d3-3.min.js"></script>
#图的实现文件
<script src="scatter_meta.js"></script>

三、图形的调用
var content = $.parseJSON($('textarea').val()); //从textarea里面取出图的json数据并转化为object
graph.scatter("container", content);   //把生成的图放在#container里面

四、以下为图的代码实现(D3.js实现)

var graph = {

	/**
	 * 散点图
	 *
	 *
	 *
	 **/
	scatter:function(container, content)
	{
		d3.select('#'+container).select('svg').remove();
		d3.select('body').select('.scatter_tooltip').remove();

		var colors = ["#388E3C", "#F44336", "#0288D1", "#FF9800", "#727272", "#E91E63", "#673AB7", "#8BC34A", "#2196F3", "#D32F2F", "#FFC107", "#BDBDBD", "#F8BBD0", "#3F51B5", "#CDDC39", "#009688", "#C2185B", "#FFEB3B", "#212121", "#FFCCBC", "#BBDEFB", "#0099CC", "#FFcc99"];
		
		var width  = typeof(content.size.width) != 'undefined' ? content.size.width : 700;
		var height = typeof(content.size.height) != 'undefined' ? content.size.height : 500;

		var margin = {top:70, right:150, bottom:80, left:60};

		var grid_width  = width - margin.left - margin.right;
		var grid_height = height - margin.top - margin.bottom;

		var min_x_value = max_x_value = 0;
		var min_y_value = max_y_value = 0;

		var legend_colors  = {};
		var legend_symbols = {};
		var default_color = '#ccc';
		var default_symbol = 'circle';

		// 找到最小值、最大值
		for (var i in content.data) {
			min_x_value = min_x_value > content.data[i].value[0] ? content.data[i].value[0] : min_x_value;
			max_x_value = max_x_value < content.data[i].value[0] ? content.data[i].value[0] : max_x_value;

			min_y_value = min_y_value > content.data[i].value[1] ? content.data[i].value[1] : min_y_value;
			max_y_value = max_y_value < content.data[i].value[1] ? content.data[i].value[1] : max_y_value;

			if (typeof(content.data[i].color_category_name) != 'undefined'){
				if (typeof(legend_colors[content.data[i].color_category_name]) == 'undefined'){
					legend_colors[content.data[i].color_category_name] = {};
				}
				legend_colors[content.data[i].color_category_name] = {
					color  : typeof(content.data[i].color) != 'undefined' ? content.data[i].color : default_color,
					symbol : default_symbol,
					name: content.data[i].color_category_name
				};
			}
			if (typeof(content.data[i].symbol_category_name) != 'undefined'){
				if (typeof(legend_symbols[content.data[i].symbol_category_name]) == 'undefined'){
					legend_symbols[content.data[i].symbol_category_name] = {};
				}

				legend_symbols[content.data[i].symbol_category_name] = {
					color  : '#388E3C',
					symbol : typeof(content.data[i].symbol) != 'undefined' ? content.data[i].symbol : default_symbol,
					name: content.data[i].symbol_category_name
				}
			}
		}

		if (typeof(content.marker) != 'undefined') {
			for (var i in content.marker) {
				for (var j in content.marker[i]['data']) {
					min_x_value = min_x_value > content.marker[i]['data'][j][1] ? content.marker[i]['data'][j][1] : min_x_value;
					max_x_value = max_x_value < content.marker[i]['data'][j][1] ? content.marker[i]['data'][j][1] : max_x_value;

					min_y_value = min_y_value > content.marker[i]['data'][j][2] ? content.marker[i]['data'][j][2] : min_y_value;
					max_y_value = max_y_value < content.marker[i]['data'][j][2] ? content.marker[i]['data'][j][2] : max_y_value;
				}
			}
		}

		
		if (min_x_value >=0) {
			min_x_value = 0;
		} else {
			min_x_value += min_x_value /5;
		}

		if (min_y_value >=0) {
			min_y_value = 0;
		} else {
			min_y_value += min_y_value /5;
		}

		if (max_x_value >= 0){
			max_x_value += max_x_value/5;
		} else {
			max_x_value -= max_x_value/5;
		}

		if (max_y_value >= 0){
			max_y_value += max_y_value/5;
		} else {
			max_y_value -= max_y_value/5;
		}



		console.log(min_x_value, max_x_value);
		console.log(min_y_value, max_y_value);


		// 定义比例尺
		var x_scale = d3.scale.linear().domain([min_x_value, max_x_value]).range([0, grid_width]);
		var y_scale = d3.scale.linear().domain([max_y_value, min_y_value]).range([0, grid_height]);

		// 定义坐标轴
		var xaxis = d3.svg.axis()
			.scale(x_scale)
			.orient('bottom')
			.ticks(6) //设置刻度数量
			.outerTickSize(0);

		var yaxis = d3.svg.axis()
			.scale(y_scale)
			.orient('left')
			.ticks(6) // 设置刻度数量
			.outerTickSize(0);

		// 绘制svg
		var svg = d3.select('#'+container)
			.append('svg')
			.attr('version', '1.1')
			.attr('style', 'font-family:arial')
			.attr('xmlns', 'http://www.w3.org/2000/svg')
			.attr('width', width)
			.attr('height', height);


		// 标题
		var title = typeof(content.params.title) != 'undefined' ? content.params.title : '';
		var title_width = 8;
		var title_x = width/2 - title.length*title_width/2;

		if (title != ''){
			svg.append('text').text(title).attr('transform', 'translate('+title_x+','+margin.top/2+')');
		}

		var main = svg.append('g')
			.attr('class', 'main')
			.attr('transform', 'translate('+margin.left+','+margin.top+')');

		var xaxis_y = grid_height;
		//绘制x比例尺
		var xaxis_obj = main.append('g')
			.attr('class', 'xaxis')
			.call(xaxis)
			.attr('text-ahchor', 'start')
			.attr('transform', 'translate(0,'+xaxis_y+')');

		var yaxis_obj = main.append('g')
			.attr('class', 'yaxis')
			.call(yaxis)
			.attr('text-anchor', 'start');

		// 绘制辅助线

		if (typeof(content.params.show_grid) != 'undefined' && content.params.show_grid == true) {
			var main_help = main.append('g')
				.attr('class', 'main_help');
			d3.select('.yaxis').selectAll('text').each(function() {
				var value = parseFloat(d3.select(this).text().replace(/,/, ''));
				console.log(value);
				main_help.append('line')
					.attr('x1', x_scale(min_x_value))
					.attr('y1', y_scale(value))
					.attr('x2', x_scale(max_x_value))
					.attr('y2', y_scale(value))
					.attr('stroke-dasharray', '5,5')
					.attr('stroke', '#ccc');
			});
		}

		// 显示0位置的辅助线
		if (typeof(content.params.show_zero_grid) != 'undefined' && content.params.show_zero_grid == true) {
			var main_zero_grid = main.append('g')
				.attr('class', 'zero_grid');
			if (0 > min_x_value) {
				main_zero_grid.append('line')
					.attr('class', 'zero_line')
					.attr('x1', x_scale(0))
					.attr('y1', y_scale(min_y_value))
					.attr('x2', x_scale(0))
					.attr('y2', y_scale(max_y_value))
					.attr('stroke-dasharray', '5,5')
					.attr('stroke', '#ccc');
			}
			if (0 > min_y_value) {
				main_zero_grid.append('line')
					.attr('class', 'zero_line')
					.attr('x1', x_scale(min_x_value))
					.attr('y1', y_scale(0))
					.attr('x2', x_scale(max_x_value))
					.attr('y2', y_scale(0))
					.attr('stroke-dasharray', '5,5')
					.attr('stroke', '#ccc');
			}
		}

		// 是否显示边框

		if (typeof(content.params.show_border) != 'undefined' && content.params.show_border == true) {
			main.append('g')
				.attr('class', 'background')
				.append('rect')
				.attr('width', grid_width)
				.attr('height', grid_height)
				.attr('fill', 'none')
				.attr('stroke', '#000');
		}
		

		var main_scatter = main.append('g')
			.attr('class', 'symbol_group');

		// 符号生成器
		var shape = graph.drawSymbol();

		
		var word_width = 6;
		var text_y     = -10;

		var tooltip = d3.select("body").append("div")  
			.attr("class","scatter_tooltip") //用于css设置类样式  
			.attr("opacity",0.0);

		// 绘制对应的椭圆

		if (typeof(content.ellipse) != 'undefined' && typeof(content.params.show_ellipse) != 'undefined' && content.params.show_ellipse == true) {

			// 设置生成器
			var ellipse_line = d3.svg.line()
				.x(function(d) {
					return x_scale(d[0]);
				})
				.y(function(d) {
					return y_scale(d[1]);
				});
			var main_ellipse = main.append('g')
				.attr('class', 'main_ellpse');
			
			main_ellipse.selectAll('.ellipses')
				.data(content.ellipse)
				.enter()
				.append('g')
				.attr('class', 'ellipses')
				.each(function(d) {
					var self = d3.select(this);
					self.append('path')
						.attr('d', ellipse_line(graph.generateEllipseData(d)))
						.attr('stroke', d.color)
						.attr('fill',d.color)
						.attr('fill-opacity',0.2);
				});
		}

		// 画对应的散点
		main_scatter.selectAll('.symbols')
			.data(content.data)
			.enter()
			.append('g')
			.attr('class', 'symbols')
			.attr('transform', function(d,i) {
				return 'translate('+x_scale(d.value[0])+','+y_scale(d.value[1])+')';
			})
			.each(function(d) {
				var self = d3.select(this);
				self.append('path')
					.attr('d', shape.type(d.symbol))
					.attr('fill', typeof(d.color) != 'undefined' ? d.color : default_color)
					.on('mouseover', function(d) {
						var page_x     = d3.event.pageX;
						var page_y     = d3.event.pageY+20;
						tooltip.html('<b>'+d.name+"</b><br/> x:"+d.value[0]+"<br/>y:"+d.value[1])  
							.style("left",page_x+"px")  
							.style("top",page_y+"px")  
							.style("opacity",0.9)
							.style('padding', '5px');
					})
					.on('mouseout', function() {
						tooltip.style('opacity', 0);
					})
					.attr('color_category_name', d.color_category_name)
					.attr('symbol_category_name', d.symbol_category_name);

				if(typeof(content.params.show_label) != 'undefined' && content.params.show_label == true) {
					self.append('text')
						.text(function(d) {
							return d.name;
						})
						.attr('style','font-size:11px')
						.attr('x', -(word_width)*d.name.length/2)
						.attr('y', text_y);
				}
			});

		



		//绘制对应的marker数据

		if (typeof(content.marker) != 'undefined' && typeof(content.params.show_marker) != 'undefined' && content.params.show_marker == true) {

			var marker_colors = {};
			for (var i in content.marker){
				marker_colors[content.marker[i].color] = content.marker[i].color;
			}
			//调用箭头生成器
			graph.drawArrow(svg, {colors:marker_colors});

			
			var main_marker = main.append('g')
				.attr('class', 'marker_group');

			var main_marker_symbol = main.append('g')
					.attr('class', 'main_marker_symbol');

			for (var i in content.marker) {
				var color = typeof(content.marker[i].color) != 'undefined' ? content.marker[i].color : '#ccc';
				var data  = content.marker[i].data;
				main_marker.selectAll('.mark_'+i)
					.data(data)
					.enter()
					.append('line')
					.attr('class', 'mark_'+i)
					.attr('x1', x_scale(0))
					.attr('y1', y_scale(0))
					.attr('x2', function(d) {
						return x_scale(d[1]);
					})
					.attr('y2', function(d) {
						return y_scale(d[2]);
					})
					.attr('stroke', color)
					.attr('marker-end', 'url('+color+')'); //匹配对应颜色的箭头

				// 画marker对应的圆点
				main_marker_symbol.selectAll('.marker_symbols_'+i)
					.data(data)
					.enter()
					.append('g')
					.attr('class', 'marker_symbols_'+i)
					.attr('transform', function(d) {
						return 'translate('+x_scale(d[1])+','+y_scale(d[2])+')';
					}).each(function(d) {
						var self = d3.select(this);
						self.append('path')
							.attr('d', shape.type('circle'))
							.attr('fill', color)
							.attr('fill-opacity', 0.1)
							.on('mouseover', function() {
								d3.select(this).attr('fill-opacity',0.9);
								var page_x     = d3.event.pageX;
								var page_y     = d3.event.pageY+20;
								tooltip.html('<b>'+d[0]+"</b><br/> x:"+d[1]+"<br/>y:"+d[2])  
									.style("left",page_x+"px")  
									.style("top",page_y+"px")  
									.style("opacity",0.9)
									.style('padding', '5px')
									.style('z-index',10);
							})
							.on('mouseout', function() {
								d3.select(this).attr('fill-opacity',0.1);
								tooltip.style('opacity',0.0)
									.style('z-index', -10);

							});
					});
			}
		}

		// 定义比例尺css样式
		d3.select('.xaxis').selectAll('line').attr('stroke', '#000');
		d3.select('.xaxis').selectAll('path').attr('stroke', '#000');

		d3.select('.yaxis').selectAll('line').attr('stroke', '#000');
		d3.select('.yaxis').selectAll('path').attr('stroke', '#000');

		var x_label = typeof(content.params.x_label) != 'undefined' ? content.params.x_label : '';
		var tick_label_width = 10;
		if (x_label != '') {
			x_label_x = width/2 - x_label.length *tick_label_width/2;
			svg.append('text').text(x_label)
				.attr('transform', 'translate('+x_label_x+','+(height-margin.bottom/3)+')')
				.attr('style', 'font-size:12px');
		}

		var y_label = typeof(content.params.y_label) != 'undefined' ? content.params.y_label : '';

		if (y_label != '') {
			y_label_x = height/2 - x_label.length *tick_label_width/4;
			svg.append('text').text(y_label)
				.attr('transform', 'translate('+margin.left/4+', '+y_label_x+')rotate(-90)')
				.attr('style', 'font-size:12px');
		}


		// 显示图例
		if (typeof(content.params.show_legend) != 'undefined' && content.params.show_legend == true) {
			var legend_line_height = 20;
			var legend_obj = svg.append('g')
				.attr('class', 'legend_group')
				.attr('transform', 'translate('+(grid_width+margin.right/2)+','+margin.top/2+')');
			var k = 0;
			for (var i in legend_colors){
				var value = legend_colors[i];
				var legend = legend_obj
					.append('g')
					.attr('class', 'legend_colors_'+i)
					.attr('transform', 'translate(0, '+(k* legend_line_height)+')')
					.attr('select_id', value.name)
					.attr('select', 1)
					.attr('style', 'cursor:pointer')
					.on('click', function() {
						var self = d3.select(this);
						var value = self.attr('select_id');
						var select = self.attr('select');

						if (select == '1') {
							self.attr('select', 0);
							self.attr('fill-opacity',0.2);

							// 找到对应的散点

							d3.select('.symbol_group').selectAll('.symbols')
								.each(function() {
									var self = d3.select(this);
									if (self.select('path').attr('color_category_name') == value) {
										self.attr('opacity', 0.0);
									}
								});
						} else {
							self.attr('select', 1);
							self.attr('fill-opacity', 1.0);

							// 找到对应的散点

							d3.select('.symbol_group').selectAll('.symbols')
								.each(function() {
									var self = d3.select(this);
									if (self.select('path').attr('color_category_name') == value) {
										self.attr('opacity', 1.0);
									}
								});
						}

					});
				legend.append('path')
					.attr('d', shape.type(value.symbol))
					.attr('fill', value.color);
				legend.append('text')
					.text(value.name)
					.attr('style', 'font-size:11px')
					.attr('x', 8)
					.attr('y', 4)
					.attr('select',1)
				k ++;
			}

			var legend = legend_obj
					.append('g')
					.attr('transform', 'translate(0, '+(k* legend_line_height)+')');

			legend.append('text')
				.text('----------')
				.attr('fill', '#000')
				.attr('stroke', 'none');
			k++;

			for (var i in legend_symbols){
				var value = legend_symbols[i];
				var legend = legend_obj
					.append('g')
					.attr('transform', 'translate(0, '+(k* legend_line_height)+')')
					.attr('select_id', value.name)
					.attr('select', 1)
					.attr('style', 'cursor:pointer')
					.on('click', function() {
						var self = d3.select(this);
						var value = self.attr('select_id');
						var select = self.attr('select');

						if (select == '1') {
							self.attr('select', 0);
							self.attr('fill-opacity',0.2);

							// 找到对应的散点

							d3.select('.symbol_group').selectAll('.symbols')
								.each(function() {
									var self = d3.select(this);
									if (self.select('path').attr('symbol_category_name') == value) {
										self.attr('opacity', 0.0);
									}
								});
						} else {
							self.attr('select', 1);
							self.attr('fill-opacity', 1.0);

							// 找到对应的散点

							d3.select('.symbol_group').selectAll('.symbols')
								.each(function() {
									var self = d3.select(this);
									if (self.select('path').attr('symbol_category_name') == value) {
										self.attr('opacity', 1.0);
									}
								});
						}

					});
				;
				legend.append('path')
					.attr('d', shape.type(value.symbol))
					.attr('fill', value.color);
				legend.append('text')
					.text(value.name)
					.attr('style', 'font-size:11px')
					.attr('x', 8)
					.attr('y', 4)
				k ++;
			}
		}
	},
	/**
	 *
	 * 生成椭圆
	 *
	 **/
	generateEllipseData:function(d, count)
	{
		if (typeof(count) == 'undefined') {
			count = 200;
		}

		var rad = 4 * Math.PI / count;

		var n = d3.range(1, count);
		return n.map(function(j, i){
			return [
				d.cx + d.c11 * Math.cos(rad * i) + d.c12 * Math.sin(rad * i),
				d.cy + d.c21 * Math.cos(rad * i) + d.c22 * Math.sin(rad * i)
			];
		});
	},
	/**
	 * 生成箭头
	 *
	 **/
	drawArrow:function(svg, params)
	{
		var defs = svg.append("defs");
		if (typeof(params) != 'undefined' && typeof(params.colors) != 'undefined') {
			for (var i in params.colors) {
				var arrowMarker = defs.append("marker")  
				.attr("id",params.colors[i].replace(/#/,''))  
				.attr("markerUnits","strokeWidth")  
				.attr("markerWidth","12")  
				.attr("markerHeight","12")  
				.attr("viewBox","0 0 12 12")   
				.attr("refX","6")  
				.attr("refY","6")  
				.attr("orient","auto");
			  
				var arrow_path = "M2,2 L10,6 L2,10 L6,6 L2,2";  
										  
				arrowMarker.append("path")  
					.attr("d",arrow_path)  
					.attr("fill",params.colors[i]);
			}
		} else {
			var arrowMarker = defs.append("marker")  
				.attr("id","arrow")  
				.attr("markerUnits","strokeWidth")  
				.attr("markerWidth","12")  
				.attr("markerHeight","12")  
				.attr("viewBox","0 0 12 12")   
				.attr("refX","6")  
				.attr("refY","6")  
				.attr("orient","auto");
			  
			var arrow_path = "M2,2 L10,6 L2,10 L6,6 L2,2";  
									  
			arrowMarker.append("path")  
				.attr("d",arrow_path)  
				.attr("fill","#000");
		}
		//使用方式,在线条上加箭头

		//.attr('marker-end', 'url(#arrow)');
	},
	/**
	 * 生成图标(symbol)
	 * 
	 *
	 **/
	drawSymbol:function(params)
	{
		var symbols = ["circle", "triangle-up", "triangle-down", "square", "diamond", "cross"];
		var shape_size = 60;
		if (typeof(params) != 'undefined' && typeof(params.shape_size) != 'undefined') {
			shape_size = params.shape_size;
		}

		// 符号生成器
		var shape = d3.svg.symbol().size(shape_size);

		return shape;
		
		// 画对应的图标
		/*svg.append('path')
			.attr('class', 'test-symbol')
			.attr('d', shape.type('cross'))
			.attr('transform', 'translate(20,20)');*/
	}
}

以上就是散点图的全部实现,如果有不清楚的地方可以留言,欢迎交流

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值