上一篇文章寫了如何根據(jù)數(shù)據(jù)表生成橫向直方圖溉苛,這次來寫一下豎向直方圖的生成和交互式提示框的加入.
與橫向相同镜廉,數(shù)據(jù)——比例尺——矩形——坐標(biāo)軸.
不過在畫直方圖之前,先來考慮一下瀏覽器適配的問題.
動(dòng)態(tài)SVG
如果想要SVG圖形可以隨瀏覽器改變大小愚战,應(yīng)注意以下設(shè)定:
var width = "100%"; //寬度與父元素保持一致
var height ="100%"; //高度與父元素保持一致
var country_svg = d3.select("#country_data_rank") //根據(jù)id選擇父元素
.append("svg") //加入svg
.attr("width", width)
.attr("height", height)
.attr("viewBox","0 0 820 85") //viewBox 設(shè)定為(820娇唯,85)
.attr("preserveAspectRatio","xMaxYMax meet")
.attr("style", "border: 1px solid black");
具體細(xì)節(jié)可以參考這篇文章.
簡單來說就是,默認(rèn)情況下寂玲,SVG畫出來的東西將會(huì)以原始大兴濉(設(shè)定的像素)畫出,當(dāng)你縮放瀏覽器的時(shí)候拓哟,SVG在視覺上不會(huì)有任何變化想许,縮小瀏覽器比SVG的大小更小的時(shí)候?yàn)g覽器會(huì)將SVG直接截?cái)唷?br>
如果我們希望畫出來的SVG可以永遠(yuǎn)完整的顯示在一個(gè)大小不定的元素中,就必須用到視窗viewBox
的概念.
視窗就是從你的原始SVG中,按照你給出的坐標(biāo)流纹,截取一部分圖形糜烹,將它拉伸或壓縮到你SVG設(shè)定的長寬中.
在這里"viewBox","0 0 820 85"
,既等同于漱凝,將原SVG中(0疮蹦,0)到(820,85)之間的圖像拉伸到長度為"100%"
茸炒,寬度為"100%"
.
因?yàn)樵O(shè)定了SVG長寬是隨著父元素變化的愕乎,我們SVG畫布截取的這一部分圖像自然也會(huì)隨著變化,永遠(yuǎn)完整顯示.
數(shù)據(jù)的傳入
在這里壁公,我要從mysql里面查詢所有國家的數(shù)據(jù)量感论,排個(gè)序.
在python部的相應(yīng)route下面直接執(zhí)行一個(gè)新的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() //連接數(shù)據(jù)庫
cursor = conn.cursor() //設(shè)定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))
這一部分我看心情以后再單獨(dú)寫吧....注意這里必須要加mark_safe
,不然傳輸數(shù)據(jù)會(huì)出現(xiàn)解碼錯(cuò)誤紊册,參照上一篇文章 Python到JS數(shù)據(jù)交互'解碼錯(cuò)誤.
JS動(dòng)態(tài)生成直方圖
第一步:接受數(shù)據(jù)
var country_list = {{country_list}};
var country_list_name ={{country_list_name}};
第二步:設(shè)定比例尺:
var max_height = 70;
var country_linear = d3.scaleLinear()
.domain([0, d3.max(country_list)])
.range([ max_height-5, 0]);
這里注意了比肄,range不再是從0到max而是反過來的,這是因?yàn)?strong>一個(gè)豎直向的直方圖以及豎著向上的坐標(biāo)是和屏幕坐標(biāo)相反的湿硝,對(duì)瀏覽器來說薪前,y方向是豎著向下的而不是向上的,如果還是使用從0到max的值域关斜,那最終的坐標(biāo)軸就會(huì)變成0在最上面示括,大數(shù)往下排.
但是當(dāng)值域顛倒以后也就意味著,原數(shù)據(jù)越大痢畜,算出來的像素值越小垛膝,和我們的實(shí)際想要效果是反的,因?yàn)樵谶@種情況下丁稀,我們可以知道:
- 值域max是矩形最長的范圍
- 我們想要的直方圖每個(gè)矩形的長度其實(shí)應(yīng)該是設(shè)定的值域最大長度減去比例尺算出來的那個(gè)反的像素值.
- 我們的直方圖矩形起始y坐標(biāo)將會(huì)不一樣
所以吼拥,進(jìn)行以下設(shè)置:
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 是為預(yù)留上邊緣而給出的偏移量
.attr("x",function(d,i){
return i * (country_rect_width + 1)+22;
})
.attr("height",function(d){
return max_height - country_linear(d); //值域最大值減去比例尺算出來的那個(gè)像素值.
})
.attr("width", country_rect_width)
.attr("fill","steelblue")
交互式提示框
接下來就要加入提示框了,
理論很好理解线衫,當(dāng)鼠標(biāo)滑過某個(gè)矩形的時(shí)候凿可,一個(gè)div顯現(xiàn)出來,鼠標(biāo)劃出授账,此div消失枯跑,鼠標(biāo)移動(dòng),div出現(xiàn)的坐標(biāo)跟著移動(dòng).
所以白热,先生成div敛助,設(shè)定透明度為0
var tooltip = d3.select("body")
.append("div")
.attr("class","tooltip")
.style("opacity",0.0);
再接著剛才生成"rect"
的代碼繼續(xù)加入以下屬性:
.attr("opacity",0.4) //矩形原始透明度為0.4
.on("mouseover",function(d,i){
d3.select(this)
.attr("opacity",1);//當(dāng)鼠標(biāo)在上,矩形變成全不透明
tooltip.html(i+1 +":"+ country_list_name[i])//且提示框內(nèi)部html動(dòng)態(tài)生成
.style("left", (d3.event.pageX+ 10) + "px")//x位置為當(dāng)前鼠標(biāo)X坐標(biāo)向右10px
.style("top", (d3.event.pageY - 10) + "px")//y位置為當(dāng)前鼠標(biāo)Y坐標(biāo)向上10px
.style("opacity",1.0);//不透明
})
.on("mouseout",function(d,i){//當(dāng)鼠標(biāo)移出矩形
d3.select(this)
.transition()
.duration(500)
.attr("opacity",0.4);//變回0.4的透明度
tooltip.style("opacity",0.0);//提示框直接變?yōu)槿该? })
.on("mousemove",function(d){//當(dāng)鼠標(biāo)移動(dòng)
tooltip.style("left", (d3.event.pageX+ 10) + "px")//提示框跟著鼠標(biāo)移動(dòng)
.style("top", (d3.event.pageY - 10) + "px");//提示框跟著鼠標(biāo)移動(dòng)
})
再稍微設(shè)定一下樣式:
.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;
}
最后屋确,再和之前文章里寫的一樣纳击,
召喚坐標(biāo)軸 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 高級(jí)系列 — 9.0 】 交互式提示框
如果要變換文本方向,記得參考CSS屬性writing-mode.
到此焕数,首圖上的直方圖和交互式提示框就應(yīng)該可以實(shí)現(xiàn)啦~
:)
這里是V3版本
主要只有兩個(gè)地方的不同纱昧,既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()