上一篇文章写了如何根据数据表生成横向直方图,这次来写一下竖向直方图的生成和交互式提示框的加入.
与横向相同,数据——比例尺——矩形——坐标轴.
不过在画直方图之前,先来考虑一下浏览器适配的问题.
动态SVG
如果想要SVG图形可以随浏览器改变大小,应注意以下设定:
var width = "100%"; //宽度与父元素保持一致
var height ="100%"; //高度与父元素保持一致
var country_svg = d3.select("#country_data_rank") //根据id选择父元素
.append("svg") //加入svg
.attr("width", width)
.attr("height", height)
.attr("viewBox","0 0 820 85") //viewBox 设定为(820,85)
.attr("preserveAspectRatio","xMaxYMax meet")
.attr("style", "border: 1px solid black");
具体细节可以参考这篇文章.
简单来说就是,默认情况下,SVG画出来的东西将会以原始大小(设定的像素)画出,当你缩放浏览器的时候,SVG在视觉上不会有任何变化,缩小浏览器比SVG的大小更小的时候浏览器会将SVG直接截断。
如果我们希望画出来的SVG可以永远完整的显示在一个大小不定的元素中,就必须用到视窗viewBox
的概念.
视窗就是从你的原始SVG中,按照你给出的坐标,截取一部分图形,将它拉伸或压缩到你SVG设定的长宽中.
在这里"viewBox","0 0 820 85"
,既等同于,将原SVG中(0,0)到(820,85)之间的图像拉伸到长度为"100%"
,宽度为"100%"
.
因为设定了SVG长宽是随着父元素变化的,我们SVG画布截取的这一部分图像自然也会随着变化,永远完整显示.
数据的传入
在这里,我要从mysql里面查询所有国家的数据量,排个序.
在python部的相应route下面直接执行一个新的sql查询:
mysql = MySQL()
# MySQL configurations
app.config['MYSQL_DATABASE_USER'] = 'root'
app.config['MYSQL_DATABASE_PASSWORD'] = 'Password'
app.config['MYSQL_DATABASE_DB'] = 'test'
app.config['MYSQL_DATABASE_HOST'] = 'localhost'
mysql.init_app(app)
@app.route("/iotemplate")
def iotemplate():
conn = mysql.connect() //连接数据库
cursor = conn.cursor() //设定cursor
sql_country_data_count = "select keyword, count(*) from wbdata group by keyword order by count(*) desc;"
cursor.execute(sql_country_data_count)
sql_country_data_count_result = cursor.fetchall()
def structure_result(myresult):
datalist = []
textlist = []
outputdata = []
for something in myresult:
datalist.append(int(something[1]))
textlist.append(something[0])
outputdata.append(datalist)
outputdata.append(textlist)
return outputdata
country_list = structure_result(sql_country_data_count_result)[0]
country_list_name = structure_result(sql_country_data_count_result)[1]
return render_template("iotemplate.html",datalist = datalist, textlist = mark_safe(textlist), country_list = country_list, country_list_name = mark_safe(country_list_name))
这一部分我看心情以后再单独写吧....注意这里必须要加mark_safe
,不然传输数据会出现解码错误,参照上一篇文章 Python到JS数据交互'解码错误.
JS动态生成直方图
第一步:接受数据
var country_list = {{country_list}};
var country_list_name ={{country_list_name}};
第二步:设定比例尺:
var max_height = 70;
var country_linear = d3.scaleLinear()
.domain([0, d3.max(country_list)])
.range([ max_height-5, 0]);
这里注意了,range不再是从0到max而是反过来的,这是因为一个竖直向的直方图以及竖着向上的坐标是和屏幕坐标相反的,对浏览器来说,y方向是竖着向下的而不是向上的,如果还是使用从0到max的值域,那最终的坐标轴就会变成0在最上面,大数往下排.
但是当值域颠倒以后也就意味着,原数据越大,算出来的像素值越小,和我们的实际想要效果是反的,因为在这种情况下,我们可以知道:
- 值域max是矩形最长的范围
- 我们想要的直方图每个矩形的长度其实应该是设定的值域最大长度减去比例尺算出来的那个反的像素值.
- 我们的直方图矩形起始y坐标将会不一样
所以,进行以下设置:
var country_rect_width = 3;
country_svg.selectAll("rect")
.data(country_list)
.enter()
.append("rect")
.attr("y", function(d){return country_linear(d)+6})//6 是为预留上边缘而给出的偏移量
.attr("x",function(d,i){
return i * (country_rect_width + 1)+22;
})
.attr("height",function(d){
return max_height - country_linear(d); //值域最大值减去比例尺算出来的那个像素值.
})
.attr("width", country_rect_width)
.attr("fill","steelblue")
交互式提示框
接下来就要加入提示框了,
理论很好理解,当鼠标滑过某个矩形的时候,一个div显现出来,鼠标划出,此div消失,鼠标移动,div出现的坐标跟着移动.
所以,先生成div,设定透明度为0
var tooltip = d3.select("body")
.append("div")
.attr("class","tooltip")
.style("opacity",0.0);
再接着刚才生成"rect"
的代码继续加入以下属性:
.attr("opacity",0.4) //矩形原始透明度为0.4
.on("mouseover",function(d,i){
d3.select(this)
.attr("opacity",1);//当鼠标在上,矩形变成全不透明
tooltip.html(i+1 +":"+ country_list_name[i])//且提示框内部html动态生成
.style("left", (d3.event.pageX+ 10) + "px")//x位置为当前鼠标X坐标向右10px
.style("top", (d3.event.pageY - 10) + "px")//y位置为当前鼠标Y坐标向上10px
.style("opacity",1.0);//不透明
})
.on("mouseout",function(d,i){//当鼠标移出矩形
d3.select(this)
.transition()
.duration(500)
.attr("opacity",0.4);//变回0.4的透明度
tooltip.style("opacity",0.0);//提示框直接变为全透明
})
.on("mousemove",function(d){//当鼠标移动
tooltip.style("left", (d3.event.pageX+ 10) + "px")//提示框跟着鼠标移动
.style("top", (d3.event.pageY - 10) + "px");//提示框跟着鼠标移动
})
再稍微设定一下样式:
.tooltip{
font-family: simsun;
font-size: 70%;
width: 120;
height: auto;
position: absolute;
text-align: center;
padding: 1px;
border: 1px solid darkgray;
background-color: white;
}
最后,再和之前文章里写的一样,
召唤坐标轴 XD
var country_axis = d3.axisLeft(country_linear)
.tickSize(780)
.ticks(4);
country_svg.append("g")
.attr("transform", "translate(800,10)")
.call(country_axis);
country_svg.selectAll("text")
.attr("font-size", "70%");
country_svg.selectAll("line")
.attr("stroke","grey")
.attr("stroke-dasharray","2.2");
country_svg.select(".domain").remove()
饼图的话,可以参考这篇教程哦~【 D3.js 高级系列 — 9.0 】 交互式提示框
如果要变换文本方向,记得参考CSS属性writing-mode.
到此,首图上的直方图和交互式提示框就应该可以实现啦~
:)
这里是V3版本
主要只有两个地方的不同,既axis的语法和scale的语法.
var width = "100%";
var height ="100%";
var country_svg = d3.select("#country_data_rank")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox","0 0 820 85")
.attr("preserveAspectRatio","xMaxYMax meet")
.attr("style", "border: 1px solid black");
var country_list = {{country_list}};
var country_list_name ={{country_list_name}};
var max_height = 70;
var country_linear = d3.scale.linear() //这里语法不同
.domain([0, d3.max(country_list)])
.range([ max_height-5, 0]);
var country_rect_width = 3;
country_svg.selectAll("rect")
.data(country_list)
.enter()
.append("rect")
.attr("y", function(d){return country_linear(d)+6})
.attr("x",function(d,i){
return i * (country_rect_width + 1)+22;
})
.attr("height",function(d){
return max_height - country_linear(d);
})
.attr("width", country_rect_width)
.attr("fill","steelblue")
.attr("opacity",0.4)
.on("mouseover",function(d,i){
d3.select(this)
.attr("opacity",1);
tooltip.html(i+1 +":"+ country_list_name[i])
.style("left", (d3.event.pageX+10) + "px")
.style("top", (d3.event.pageY - 10) + "px")
.style("opacity",1.0);
})
.on("mouseout",function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("opacity",0.4);
tooltip.style("opacity",0.0);
})
.on("mousemove",function(d){
tooltip.style("left", (d3.event.pageX+ 10) + "px")
.style("top", (d3.event.pageY - 10) + "px");
})
.on("click",function () {
});
var tooltip = d3.select("body")
.append("div")
.attr("class","tooltip")
.style("opacity",0.0);
var country_axis = d3.svg.axis() //这里语法不同
.scale(country_linear)
.orient("left")
.tickSize(780)
.ticks(4);
country_svg.append("g")
.attr("transform", "translate(800,10)")
.call(country_axis);
country_svg.selectAll("text")
.attr("font-size", "70%");
country_svg.selectAll("line")
.attr("stroke","grey")
.attr("stroke-dasharray","2.2");
country_svg.select(".domain").remove()