上一篇文章寫了如何根據數據表生成橫向直方圖,這次來寫一下豎向直方圖的生成和交互式提示框的加入.
與橫向相同,數據——比例尺——矩形——坐標軸.
不過在畫直方圖之前,先來考慮一下瀏覽器適配的問題.
動態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()