基础部分
坐标系
D3通过SVG画图,SVG的坐标系同HTML中的坐标系类似,都是以左上角作为坐标原点,向下向右为正延伸。
坐标轴
坐标轴的实现有多种方法,主要可以分为以下几种:
- 连续性输入连续性输出:线性比例尺scalelinear,通过把domain中给定的范围通过线性关系映射到SVG中由range规定的指定范围
- 连续性输入离散性输出:scaleQuantile将连续性的domain映射为离散的range块
- 离散性输入离散性输出:scaleordinal将离散的domain映射为离散的range块
- 离散性输入连续性输出:通过划分n个一定宽度的band,把domain中的离散的数据,每个数据作为一个band,映射到range中指定的区域
坐标轴的绘制通过上述的各种映射,然后根据映射设置样式,如d3中的axisBottom()绘制在底部的坐标轴,还可以设置刻度的长度、数量等等,然后通过添加g元素,通过call回调来画出给定格式的坐标轴,轴通过path绘制,刻度通过d3中提供的line(“path generator“之一)进行绘制,刻度之下的标注通过text标签,各项的位置可以通过attr添加transform属性来添加translate进行微调。
主要的图表元素
例如矩形图中的rect、散点图中的circle都是SVG中的元素,在d3中通过selectAll("rect").data(dataset).enter().append("rect")
这种方式来给各项创建的矩形绑定数据。
.data
会给所选择的元素分配数据,如果所给元素少于可分配数据的话,就会把数据存于placeholder,.enter返回代表需要增加的那些元素(已经绑定了placeholder数据)的selection,所以在后面接的append()创建元素一经创建都绑定了所给的数据。矩形元素的定位依靠x,y,其中x,y的数据需要与坐标轴进行绑定,就是通过上述的各种映射,计算出相应的在SVG图上的位置。
在SVG中可以通过path路径来进行产生任意的graph,d3提供了path generator可以根据points数组为我们自动创建path所需的d属性字符串,两者结合,可以很轻松的创建复杂的图形,对于想饼图这种需要startangle、endangle,输入的数据只是一个数组,故先把数组转换为对应的[startangle,endangle]数组,然后通过弧生成器来绘制最终的饼图。
Brush and Zoom
刷子选定选区,zoom进行缩放,缩放的原理是将选中的选区映射到整个画布之中,相当于一种对画布的重新绘制。
条形图、面积图和折线图的zoom采用的机制都比较类似。都是先调用zoom的各项API创建一个zoom behavior,
zoom.extent()
设置平移显示的视口范围,
zoom.scaleExtent()
设置缩放的倍数,
zoom.translateExtent()
设置可以平移的元素范围,
zoom.on()
声明监听器,处理鼠标手势发生时的进行的操作,
在监听器处理程序之中,最主要的一步就是
xScale.range([2*padding,width-2*padding].map(d => d3.event.transform.applyX(d)));
这是对x轴的比例尺的range进行改写,
d3.event.transform.applyX(d)
返回的是转换之后的x轴坐标.对比例尺进行改写之后再重新绘制各个元素的位置达到缩放的效果。
demo
完整代码见 github
-
bar chart
屏幕快照 2018-11-19 下午12.43.30.png
//部分关键代码
var svg = d3.select("#chart")
.append("svg")
.attr("id","column")
.attr("width",width)
.attr("height",height)
.call(zoom);
var xScale = d3.scaleBand()
.domain(tumourid)
.rangeRound([2*padding,width-2*padding]);
var yScale = d3.scaleLinear()
.domain([0,1.1*yAxisWidth])
.range([height-3*padding,3*padding]);
var xAxis = d3.axisBottom(xScale)
.tickSize(0)
.tickPadding(6)
.tickValues(xScale.domain().filter(function(d,i){return !(i%10)}));
var yAxis = d3.axisLeft(yScale).tickSize(0);
//缩放操作
function zoom(svg){
const extent = [[2*padding,3*padding],[width-2*padding,height-3*padding]];
svg.call(d3.zoom()
.scaleExtent([1,20])
.translateExtent(extent)
.extent(extent)
.on("zoom",zoomed));
function zoomed(){
xScale.range([2*padding,width-2*padding].map(d => d3.event.transform.applyX(d)));
svg.select("#svgrects")
.selectAll("rect")
.attr("x",function(d){
return xScale(d["ID "])+xScale.bandwidth()/2;
})
.attr("width",xScale.bandwidth());
svg.select(".axis--x").call(xAxis);
}
}
//缩放裁剪
svg.append("defs").append("clipPath")
.attr("id","myclip")
.append("rect")
.attr("x","40")
.attr("y","0")
.attr("width","600")
.attr("height","500");
function make_y_axis(){
return d3.axisLeft(yScale);
}
svg.append("g")
.attr("stroke","lightgray")
.attr("stroke-opacity","0.1")
.attr("shape-rendering","crispEdges")
.call(make_y_axis()
.tickSize(-width+4*padding,0,0)
.tickFormat(""))
.attr("transform","translate("+(2*padding)+",0)");
svg.append("g")
.attr("class","axis axis--x")
.attr("clip-path","url(#myclip)")
.attr("transform","translate(0,"+(height-3*padding)+")")
.call(xAxis)
.selectAll("text")
.attr("transform","rotate(90)"+"translate("+(1.4*padding)+",0)")
svg.append("text")
.attr("fill","black")
.attr("text-anchor","end")
.attr("font-size",10)
.attr("x",width-padding)
.attr("y",height-2*padding)
.text("肿瘤ID");
svg.append("g")
.attr("class","axis axis--y")
.attr("transform","translate("+(2*padding)+",0)")
.call(yAxis)
.append("text")
.attr("text-anchor","middle")
.attr("font-size",10)
.attr("fill","black")
.attr("x",0)
.attr("y",3*padding)
.text(s);
svg.selectAll("text")
.attr("fill","black");
var rects = svg.append("g")
.attr("id","svgrects")
.attr("clip-path","url(#myclip)")
.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("fill",document.getElementById("variablescolor").value)
.attr("x",function(d){
return xScale(d["ID "])+xScale.bandwidth()/2;
})
.attr("y",function(d){
return yScale(d[s]);
})
.attr("width",xScale.bandwidth())
.attr("height",function(d){
return height-yScale(d[s])-3*padding;
}) ;
-
scatter diagram
屏幕快照 2018-11-19 下午1.12.32.png
//部分关键代码
var svg = d3.select("#chart")
.append("svg")
.attr("id","solid")
.attr("width",width)
.attr("height",height)
var xScale = d3.scaleLinear()
.domain([0,1.1*xAxisWidth])
.range([2*padding,width-padding*2]);
var yScale = d3.scaleLinear()
.domain([0,1.1*yAxisWidth])
.range([height-padding,padding]);
var circle =svg.append("g")
.attr("id","svgcircles")
.attr("clip-path","url(#myclip)")
.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("fill",document.getElementById("variablescolor").value)
.attr("cx",function(d){
return xScale(d[xstring]);
})
.attr("cy",function(d){
return yScale(d[ystring]);
})
.attr("r",document.getElementById("variablessize").value);
var xAxis = d3.axisBottom(xScale).tickSize(0,0,0);
var yAxis = d3.axisLeft(yScale).tickSize(0,0,0);
function make_x_axis(){
return d3.axisBottom(xScale);
}
function make_y_axis(){
return d3.axisLeft(yScale);
}
svg.append("g")
.attr("calss","grid grid--x")
.attr("stroke","lightgray")
.attr("stroke-opacity","0.1")
.attr("shape-rendering","crispEdges")
.call(make_x_axis()
.tickSize(height-2*padding,0,0)
.tickFormat("")
)
.attr("transform","translate(0,"+padding+")");
svg.append("g")
.attr("class","grid grid--y")
.attr("stroke","lightgray")
.attr("stroke-opacity","0.1")
.attr("shape-rendering","crispEdges")
.call(make_y_axis()
.tickSize(-width+4*padding,0,0)
.tickFormat("")
)
.attr("transform","translate("+(2*padding)+",0)");
svg.append("g")
.attr("class","axis axis--x")
.attr("transform","translate(0,"+(height-padding)+")")
.call(xAxis)
.append("text")
.attr("fill","black")
.attr("text-anchor","end")
.attr("font-size",10)
.attr("x",width)
.attr("y",0)
.text(xstring);
svg.append("g")
.attr("class","axis axis--y")
.attr("transform","translate("+(2*padding)+",0)")
.call(yAxis)
.append("text")
.attr("fill","black")
.attr("text-anchor","middle")
.attr("font-size",10)
.attr("x",0)
.attr("y",padding)
.text(ystring);
svg.selectAll("text")
.attr("fill","black");
svg.append("defs").append("clipPath")
.attr("id","myclip")
.append("rect")
.attr("x","40")
.attr("y","20")
.attr("width","420")
.attr("height","460");
var brush = d3.brush().on("end",brushended),
idleTimeout,
idleDelay = 350;
svg.append("g")
.attr("id","brush")
.call(brush);
d3.select(".overlay").attr("pointer-events","none");
d3.select("svg")
.on("mousedown",function(){
d3.select(".overlay").attr("pointer-events","all");
});
function brushended(){
var s = d3.event.selection;
if(!s){
if(!idleTimeout){
return idleTimeout = setTimeout(idled,idleDelay);
}
xScale.domain([0,1.1*xAxisWidth]);
yScale.domain([0,1.1*yAxisWidth]);
}else{
xScale.domain([s[0][0],s[1][0]].map(xScale.invert,xScale));
yScale.domain([s[1][1],s[0][1]].map(yScale.invert,yScale));
svg.select("#brush").call(brush.move,null);
}
d3.select(".overlay").attr("pointer-events","none");
zoom();
}
function idled(){
idleTimeout = null;
}
function zoom(){
var t = svg.transition().duration(750);
svg.select(".axis--x").transition(t).call(xAxis);
svg.select(".axis--y").transition(t).call(yAxis);
svg.select(".grid--x").transition(t).call(make_x_axis()
.tickSize(height-2*padding,0,0)
.tickFormat(""));
svg.select(".grid--y").transition(t).call(make_y_axis()
.tickSize(-width+4*padding,0,0)
.tickFormat(""));
d3.select("#svgcircles").selectAll("circle").transition(t)
.attr("cx",function(d){return xScale(d[xstring]);})
.attr("cy",function(d){return yScale(d[ystring]);});
}