实现如下效果:
一、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)');*/
}
}
以上就是散点图的全部实现,如果有不清楚的地方可以留言,欢迎交流