d3.js竖向直方图与交互式提示框,V3与V4

效果图

上一篇文章写了如何根据数据表生成横向直方图,这次来写一下竖向直方图的生成和交互式提示框的加入.
与横向相同,数据——比例尺——矩形——坐标轴.
不过在画直方图之前,先来考虑一下浏览器适配的问题.

动态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数据交互&#39解码错误.

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在最上面,大数往下排.
但是当值域颠倒以后也就意味着,原数据越大,算出来的像素值越小,和我们的实际想要效果是反的,因为在这种情况下,我们可以知道:

  1. 值域max是矩形最长的范围
  2. 我们想要的直方图每个矩形的长度其实应该是设定的值域最大长度减去比例尺算出来的那个反的像素值.
  3. 我们的直方图矩形起始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()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 这里讲一下怎么样用d3.js,输入一个数据list,根据数据画一个带有坐标轴的简单直方图.以下是目标效果. 直方图...
    Kaidi_G阅读 4,791评论 1 3
  • 饼图的绘制稍微复杂一点点,主要是网上很多教程还是老版本d3.这里用的是 D3 4.0 API Reference....
    Kaidi_G阅读 3,467评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,079评论 25 707
  • 【正式测定:阜阳地震4.3级!】中国地震台网正式测定:3月14日14时13分在安徽阜阳市市辖区(北纬33.0度,东...
    刘汉皇阅读 694评论 4 1
  • 人心总是隔肚皮。不知道是自己特别zuo,还是怎么回事?最见不得那些自己亲近的人的欺骗,我既信了你,你又为何还欺骗我...
    拾麦穗的小女孩阅读 193评论 0 0