• D3 入门笔记


    一、第一个程序

    选择集: 使用d3.select()或者 d3.selectAll()选择元素后返回的对象,就是选择集

    d3能够连续不断地调用函数,形如:d3.select().selctAll().text()这称为链式语法

    二、选择元素和绑定数据

    在D3中,用于选择元素的函数有两个:

    d3.select():是选择所有指定元素的第一个
    d3.selectAll():是选择指定元素的全部

    这两个函数返回的结果称为选择集,选择集合绑定数据通常是一起使用的


    D3 中通过以下两个函数来绑定数据:

    1)datum():绑定一个数据到选择集上
    2)data():绑定一个数组到选择集上,数组的各项值分别于选择集的各元素绑定

    var str="China";
    var body=d3.select('body');
    var p=body.selectAll("p");
    p.datum(str);
    p.text(function(d,i){
    return "第"+i+"个元素绑定的数据是"+d;
    });

    上面代码中,用到了一个无名函数 function(d,i),当选择集需要使用被绑定的数据时,
    常需要这么使用,其包含两个参数,其中:

    d 代表数据,也就是与某元素绑定的数据
    i 代表索引,代表数据的索引号,从0开始


    data()
    有一个数组,接下来要分别将数组的各元素绑定到三个段落元素上

    var dataset = ['I like dog','I like cat','I like snake'];
    var body = d3.select("body");
    var p = body.selectAll("p");
    p.data(dataset)
    .text(function(d,i){
    return d;
    });

    三个段落元素与数组dataset 的三个字符串是一一对应

    三、选择、插入、删除元素

    select 和selectAll的用法

    假设 body中有三个段落元素:

    <p>Apple</p>
    <p>Pear</p>
    <p>Banana</p>

    (1) 选择元素

    现在,要分别完成以下四种选择元素的任务

    1)选择第一个p元素

    使用select,参数传入p即可

    var p1=d3.select("p");
    p1.style("color","red");
    2)选择三个 p 元素

    var body=d3.select("body");
    var p= body.selectAll("p");
    p.style("color","green");

    3)选择第二个p 元素

    一种比较简单的是给第二个元素添加一个id号,然后使用select 选择元素,注意参数中
    id 名称前要加 # 号

    var p2= body.select("#myid");
    p2.style("color",'red');

    4)选择后两个p元素

    给后两个元素添加class

    var p=body.selectAll(".myclass");
    p.style("color","red");

    关于 select 和selectAll 的参数,其实是符合css 选择器的条件的,
    此外,对于已经绑定了数据的选择集,还有一种选择元素的方法,那就是灵活运用
    function(d,i),我们已经知道参数 i 是代表索引号的,于是便可以用条件判定语句来指定执行的元素

    (2)插入元素

    插入元素涉及两个函数:

    1)append():在选择集末尾插入元素
    2)insert():在选择集前面插入元素


    在body末尾添加一个p元素

    body.append("p")
    .text("append p element");

    在 body 中 id 为 myid 的元素前添加一个段落元素。

    body.insert("p","#myid")
    .text("insert a element");

    (3)删除元素

    删除一个元素时,对于选择的元素,使用remove即可,例如:

    var p=body.select("#myid");
    p.remove();


    四、做一个简单的图表

    柱形图是一种最简单的可视化表,主要有 矩形、文字标签、坐标轴组成。

    如何使用D3 在SVG画布中绘图

    1)画布

    要绘图,首要需要的是一块绘图的“画布”;
    HTML5 提供两种强有力的画布:SVG 和Canvas

    SVG 是什么:指可缩放矢量图形,用于描述二维矢量图形的一种图形格式。SVG使用XML
    格式来定义图形。绝大部分浏览器都支持SVG,可将SVG文本直接嵌入HTML中显示

    SVG 有如下特点:

    1)SVG绘制的是矢量图,因此对图象进行放大不会失真
    2)基于XMl,可以为每个元素添加javaScript 事件处理器
    3)每个图形均视为对象,更改对象的属性,图形也会改变
    4)不适合游戏应用


    D3虽然没有明文规定一定要在SVG上绘图,但是D3提供了众多的 SVG 图形的生成器,它们都是
    只支持SVG的,因此建议使用SVG 画布

    //  横向显示的矩形

    var width = 300;
    var height = 300;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);
    var dataset =[250 ,210 ,170 ,130,90];
    var rectHeight = 25;
    svg.selectAll("rect")
    .data(dataset)
    .enter() //这个不能少
    .append("rect") //添加rect
    .attr("x",20) //设置x
    .attr("y",function(d,i){ //设置y
    return i* rectHeight;
    })
    .attr("width",function(d,i){ //设置宽度
    return d;
    })
    .attr("height",rectHeight-2) //设置高度
    .attr("fill","steelblue");

    //纵向显示的矩形

    var width = 800;
    var height = 1200;
    var startY = 250;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);
    var dataset = [250,210,130,170,90];
    var rectHeight = 25;
    svg.selectAll("rect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("x",function(d,i){
    return (i+1)*rectHeight;
    })
    .attr("y",function(d,i){
    return startY-d;
    })
    .attr("width",rectHeight-2)
    .attr("height",function(d,i){
    return d;
    })
    .attr("fill","steelblue");


    这段代码添加了与dataset数组的长度相同数量的矩形,所使用的语句是

    svg.selectAll("rect") //选择svg内所有的矩形
    .data(dataset) //绑定数组
    .enter() //指定选择集的enter部分
    .append("rect") //添加足够数量的矩形元素

    这段代码以后会常常出现在D3 的代码中,请务必牢记。当:有数据,而没有足够图形元素的时候
    ,使用此方法可以添加足够的元素

    添加了元素后,就需要分别给各元素的属性赋值。咋这里用到了function(d,i),前面已经讲过,
    d代表与当前元素绑定的数据,i代表索引号。给属性赋值的时候,是需要用到被绑定的数据,已经索引号的


    五、比例尺的使用

    比例尺是D3中很重要的一个概念,上一章里曾经提到过直接用数值的大小来代表像素不是一种好办法
    本章正是要解决此问题。

    上面制作的柱形图,绘图时,直接使用数组中的值给矩形的宽度赋值,即矩形的宽度就是 250 个像素

    此方式非常有局限性,如果数值过大或过小就不合理了。

    于是,我们需要一种计算关系,能够 将某一区域的值映射到另一区域,其大小关系不变

    这就是比例尺,那么有哪些比例尺呢

    在数学中,x范围被称为定义域,y的范围被称为值域。

    D3中的比例尺,也有定义域和值域,分别被称为 domain 和range

    D3提供了多种比例尺,下面介绍最常用的两种

    线性比例尺:

    线性比例尺,能将一个连续的区间,映射到另一个区间。要解决柱形图宽度的问题,就需要线性比例尺。

    假设有以下数组:
    var dataset = [1.2,2.3,0.9,1.5,3.3];

    要求如下: 将dataset 中最小值,映射成0,将最大的值映射成300

    代码如下:

    var dataset = [1.2,2.3,0.9,1.5,3.3];

    var min = d3.min(dataset);
    var max = d3.max(dataset);

    var linear = d3.scale.linear()
    .domain([min,max])
    .range([0,300]);
    console.log(linear(0.9)); //0
    console.log( linear(2.3)); //175
    console.log(linear(3.3)); //300

    其中,d3.scale.linear()返回一个线性比例尺。domain()和range()分别设定比例尺的
    定义域和值域。在这里还用到了两个函数,它们经常与比例尺一起出现:
    d3.max()
    d3.min()

    这两个函数能够求数组的最大值和最小值,是D3提供的。
    d3.scale.linear()的返回值,是可以当做函数来使用的。因此才有这样的用法:linear(0.9)

    序数比例尺

    有时候,定义域和值域不一定是连续的。例如:

    var index = [0,1,2,3,4];
    var color = ["red","blue","green","yellow","black"];

    我们希望0 对应颜色 red ,1对应blue,但是这些值都是离散的,线性比例尺不合适
    ,需要用到序数比例尺
    var index = [0,1,2,3,4];
    var color = ["red","blue","green","yellow","black"];
    var ordinal = d3.scale.ordinal()
    .domain(index)
    .range(color);

    console.log(ordinal(0));
    console.log(ordinal(2));
    console.log(ordinal(4));

    用法和线性比例尺是类似的。

    给柱形图添加比例尺

    var width = 300;
    var height = 300;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);
    var dataset =[2.5,2.1 ,1.7 ,1.3,0.9];
    var linear = d3.scale.linear()
    .domain([0, d3.max(dataset)])
    .range([0, 250]);
    var rectHeight = 25;
    svg.selectAll("rect")
    .data(dataset)
    .enter() //这个不能少
    .append("rect") //添加rect
    .attr("x",20) //设置x
    .attr("y",function(d,i){ //设置y
    return i* rectHeight;
    })
    .attr("width",function(d,i){ //设置宽度
    return linear(d);
    })
    .attr("height",rectHeight-2) //设置高度
    .attr("fill","steelblue");


    六、 坐标轴

    坐标轴是可视化图表中经常出现的一种图形,由一些列线和刻度组成。坐标轴在SVG中
    是没有现成的图形元素的,需要用其他的元素组合构成。D3提供了坐标轴的组件。

    坐标轴由什么构成

    在SVG 画布的预定义元素里,有六种基本图形:

    1)矩形
    2)圆形
    3)椭圆
    4)线段
    5)折线
    6)多边形

    另外,还有一种比较特殊,也是功能最强的元素:

    路径
    画布中的所有图形,都是由以上七种元素组成的

    D3提供了一个组件:d3.svg.axis(),它为我们完成了坐标轴的工作

    var axis = d3.svg.axis()
    .scale(linear) //指定比例尺
    .orient("bottom") //指定刻度的方向
    .ticks(7); //指定刻度的数量

    定义了坐标轴之后,只需要在svg中添加一个分组元素,再将坐标轴的其他元素添加到
    这个分组元素中即可。代码如下:

    svg.append("g")
    .call(axis);

    上面有一个call()函数,其参数是前面定义的坐标轴axis

    在D3中,call()的参数是一个函数。调用之后,将当前的选择集作为参数传递给此函数。

    设定坐标轴的样式和位置

    提供了一个常见的样式:
    <style>
    .axis path,.axis line{
    fill:none;
    stroke:black;
    shape-rendering: crispEdges;
    }
    .axis text{
    font-family:sans-serif;
    font-size:11px;
    }
    </style>

    带刻度的柱状图

    var width = 300;
    var height = 300;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);
    var dataset =[2.5,2.1 ,1.7 ,1.3,0.9];
    var linear = d3.scale.linear()
    .domain([0, d3.max(dataset)])
    .range([0, 250]);
    var axis = d3.svg.axis()
    .scale(linear) //指定比例尺
    .orient("bottom") //指定刻度的方向
    .ticks(7); //指定刻度的数量
    svg.append("g")
    .attr("class","axis")
    .attr("transform","translate(20,130)")
    .call(axis);
    var rectHeight = 25;
    svg.selectAll("rect")
    .data(dataset)
    .enter() //这个不能少
    .append("rect") //添加rect
    .attr("x",20) //设置x
    .attr("y",function(d,i){ //设置y
    return i* rectHeight;
    })
    .attr("width",function(d,i){ //设置宽度
    return linear(d);
    })
    .attr("height",rectHeight-2) //设置高度
    .attr("fill","steelblue");

    //带刻度的竖型柱状图

    var width = 300;
    var height = 300;
    var startY = 250;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);
    var dataset =[250,210 ,170 ,130,90];
    var linear = d3.scale.linear()
    .domain([0, 2.5])
    .range([0, 250]);
    var axis = d3.svg.axis()
    .scale(linear) //指定比例尺
    .orient("bottom") //指定刻度的方向
    .ticks(5); //指定刻度的数量
    svg.append("g")
    .attr("class","axis")
    .attr("transform","translate(20,250)")
    .call(axis);
    var rectHeight = 25;
    svg.selectAll("rect")
    .data(dataset)
    .enter() //这个不能少
    .append("rect") //添加rect
    .attr("x",function(d,i){
    return (i+1)*rectHeight;
    })
    .attr("y",function(d,i){
    return startY-d;
    })
    .attr("width",rectHeight-2)
    .attr("height",function(d,i){
    return d;
    })
    .attr("fill","steelblue");


    七、完整的柱形图

    一个完整的柱形图包含三部分:矩形、文字、坐标轴。一下是完整柱形图:内容包括:选择集、数据绑定、比例尺、坐标轴等内容


    //添加SVG画布
    var width = 400;
    var height = 400;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);

    //画布周边的空白

    var padding = {left:30,right:30,top:20,bottom:20};

    //定义数据和比例尺
    var dataset = [10,20,30,40,33,24,12,5];
    //x轴的比例尺
    var xScale = d3.scale.ordinal()
    .domain(d3.range(dataset.length))
    .rangeRoundBands([0,width - padding.left -padding.right]);
    //y轴比例尺

    var yScale = d3.scale.linear()
    .domain([0,d3.max(dataset)])
    .range([height - padding.top - padding.bottom,0]) //因为y轴默认是越往下越大,所以反着写的

    //定义坐标轴
    //定义x轴
    var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom");
    //定义y轴
    var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left");
    //添加矩形和文字元素

    //矩形之间的空白
    var rectPadding = 4;
    //添加矩形元素
    var rects = svg.selectAll(".MyRect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("class","MyRect")
    .attr("transform","translate(" + padding.left + "," + padding.top + ")")
    .attr("x", function(d,i){
    return xScale(i) + rectPadding/2;
    } )
    .attr("y",function(d){
    return yScale(d);
    })
    .attr("width", xScale.rangeBand() - rectPadding )
    .attr("height", function(d){
    return height - padding.top - padding.bottom - yScale(d);
    })
    .attr("fill",'#e4393c');

    //添加文字元素
    var texts = svg.selectAll(".MyText")
    .data(dataset)
    .enter()
    .append("text")
    .attr("class","MyText")
    .attr("transform","translate(" + padding.left + "," + padding.top + ")")
    .attr("x", function(d,i){
    return xScale(i) + rectPadding/2;
    } )
    .attr("y",function(d){
    return yScale(d);
    })
    .attr("dx",function(){
    return (xScale.rangeBand() - rectPadding)/2;
    })
    .attr("dy",function(d){
    return 20;
    })
    .text(function(d){
    return d;
    });
    //添加坐标轴的元素 +padding.left+","+"+(height-padding.bottom)+"
    svg.append("g")
    .attr("class","axis")
    .attr("transform","translate("+padding.left+","+(height-padding.bottom)+")")
    .call(xAxis);

    //添加y轴
    svg.append("g")
    .attr("class","axis")
    .attr("transform","translate("+padding.left+","+padding.top+")")
    .call(yAxis);

    八、 让图表动起来

    D3中制作动态图表称为 过渡

    实现动态的方法:

    D3 提供了4个方法用于实现图形的过渡: 从状态A到状态 B

    1)transition()

    启动过渡效果

    其前后是图形变化前后的状态(形状、位置、颜色等等) 例如:

    .attr("fill","red") //初始颜色为红色
    .transition() //启动过渡
    .attr("fill","steelblue") //终止颜色为铁蓝色

    D3会自动对两种颜色(红色和铁蓝色)之间的颜色值进行插值计算,得到过渡用的颜色值。我们无需
    知道中间是怎么计算的,只需要享受结果即可

    duration()

    指定过渡的持续时间 ,单位为毫秒

    如 duration(2000)


    ease()

    指定过渡方式,常用的有:

    linear:普通的线性变化
    circle:慢慢地到达变换的最终状态
    elastic:带有弹跳的到达最终状态
    bounce:在最终状态处弹跳几次

    调用时,形如;ease("bounce")

    delay()

    指定延迟的时间,表示一定时间后才开始转变,单位同样为毫秒。此函数可以对整体指定延迟,
    也可以对个别指定延迟

    例如:对整体指定时:

    .transition()
    .duration(1000)
    .delay(500)

    又如,对一个个的图形进行指定:

    .transition()
    .duration(1000)
    .delay(function(d,i){return 200*i;})

    实现简单的动态效果

    var width = 400;
    var height = 400;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);
    var circle1 = svg.append("circle")
    .attr("cx", 100)
    .attr("cy", 100)
    .attr("r", 45)
    .style("fill","green");

    //在1秒(1000毫秒)内将圆心坐标由100变为300
    circle1.transition()
    .duration(1000)
    .attr("cx", 300);

    var circle2 = svg.append("circle")
    .attr("cx", 100)
    .attr("cy", 200)
    .attr("r", 45)
    .style("fill","green");//与第一个圆一样,省略部分代码

    //在1.5秒(1500毫秒)内将圆心坐标由100变为300,
    //将颜色从绿色变为红色
    circle2.transition()
    .duration(1500)
    .attr("cx", 300)
    .style("fill","red");

    var circle3 = svg.append("circle")
    .attr("cx", 100)
    .attr("cy", 300)
    .attr("r", 45)
    .style("fill","green");//与第一个圆一样,省略部分代码
    circle3.transition()
    .duration(2000)
    .ease("bounce")
    .attr("cx", 300)
    .style("fill","red")
    .attr("r", 25);

    动起来的柱状图

    <style>
    .axis path,.axis line{
    fill:none;
    stroke:black;
    shape-rendering: crispEdges;
    }
    .axis text{
    font-family:sans-serif;
    font-size:11px;
    }
    </style>

    <script>
    //添加SVG画布
    var width = 400;
    var height = 400;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);

    //画布周边的空白

    var padding = {left:30,right:30,top:20,bottom:20};

    //定义数据和比例尺
    var dataset = [10,20,30,40,33,24,12,5];
    //x轴的比例尺
    var xScale = d3.scale.ordinal()
    .domain(d3.range(dataset.length))
    .rangeRoundBands([0,width - padding.left -padding.right]);
    //y轴比例尺

    var yScale = d3.scale.linear()
    .domain([0,d3.max(dataset)])
    .range([height - padding.top - padding.bottom,0]) //因为y轴默认是越往下越大,所以反着写的

    //定义坐标轴
    //定义x轴
    var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom");
    //定义y轴
    var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left");
    //添加矩形和文字元素

    //矩形之间的空白
    var rectPadding = 4;
    //添加矩形元素
    //添加矩形元素
    var rects = svg.selectAll(".MyRect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("class","MyRect")
    .attr("transform","translate(" + padding.left + "," + padding.top + ")")
    .attr("x", function(d,i){
    return xScale(i) + rectPadding/2;
    } )
    .transition()
    .delay(function(d,i){
    return i*200;
    })
    .duration(2000)
    .ease("bounce")
    .attr("y",function(d){
    return yScale(d);
    })
    .attr("width", xScale.rangeBand() - rectPadding )
    .attr("height", function(d){
    return height - padding.top - padding.bottom - yScale(d);
    })
    .attr("fill",'#e4393c');

    //添加文字元素
    var texts = svg.selectAll(".MyText")
    .data(dataset)
    .enter()
    .append("text")
    .attr("class","MyText")
    .attr("transform","translate(" + padding.left + "," + padding.top + ")")
    .attr("x", function(d,i){
    return xScale(i) + rectPadding/2;
    } )
    .attr("y",function(d){
    var min = yScale.domain()[0];
    return yScale(min);
    })
    .transition()
    .delay(function(d,i){
    return i*200;
    })
    .duration(2000)
    .ease("bounce")
    .attr("y",function(d){return yScale(d)})
    .attr("dx",function(){
    return (xScale.rangeBand() - rectPadding)/2;
    })
    .attr("dy",function(d){
    return 20;
    })
    .style("stroke","#fff")
    .text(function(d){
    return d;
    });
    //添加坐标轴的元素 +padding.left+","+"+(height-padding.bottom)+"
    svg.append("g")
    .attr("class","axis")
    .attr("transform","translate("+padding.left+","+(height-padding.bottom)+")")
    .call(xAxis);

    //添加y轴
    svg.append("g")
    .attr("class","axis")
    .attr("transform","translate("+padding.left+","+padding.top+")")
    .call(yAxis);
    </script>


    九、理解 Update 、Enter 、Exit

    Update 、Enter 、Exit 是D3 中三个非常重要的概念,它处理的是当选择集和数据的数量关系
    不确定的情况。

    svg.selectAll("rect") //选择svg内所有的矩形
    .data(dataset) //绑定数组
    .enter() //指定选择集的enter部分
    .append("rect") //添加足够数量的矩形元素

    这段代码使用的情况是当以下情况出现的时候:

    有数据,而没有足够图形元素的时候,使用此方法可以添加足够的元素。


    假设,在 body 中有三个 p 元素,有一数组 [3, 6, 9],则可以将数组中的每一
    项分别与一个 p 元素绑定在一起。但是,有一个问题:当数组的长度与元素数量不
    一致(数组长度 > 元素数量 or 数组长度 < 元素数量)时呢?这时候就需要理解
    Update、Enter、Exit 的概念。

    如果数组为 [3, 6, 9, 12, 15],将此数组绑定到三个 p 元素的选择集上。可以想
    象,会有两个数据没有元素与之对应,这时候 D3 会建立两个空的元素与数据对应,
    这一部分就称为 Enter。而有元素与数据对应的部分称为 Update。如果数组为 [3],
    则会有两个元素没有数据绑定,那么没有数据绑定的部分被称为 Exit。示意图如下
    所示。

    此时 SVG 里没有 rect 元素,即元素数量为 0。有一数组 dataset,将数组与元素
    数量为 0 的选择集绑定后,选择其 Enter 部分(请仔细看上图),然后添加
    (append)元素,也就是添加足够的元素,使得每一个数据都有元素与之对应。

    Update 和 Enter 的使用
    当对应的元素不足时 ( 绑定数据数量 > 对应元素 ),需要添加元素(append)。

    现在 body 中有三个 p 元素,要绑定一个长度大于 3 的数组到 p 的选择集上,然后分别处理 update 和 enter 两部分。

    var dataset = [ 3 , 6 , 9 , 12 , 15 ];

    //选择body中的p元素
    var p = d3.select("body").selectAll("p");

    //获取update部分
    var update = p.data(dataset);

    //获取enter部分
    var enter = update.enter();

    //update部分的处理:更新属性值
    update.text(function(d){
    return "update " + d;
    });

    //enter部分的处理:添加元素后赋予属性值
    enter.append("p")
    .text(function(d){
    return "enter " + d;
    });
    结果如下图,update 部分和 enter 部分被绑定的数据很清晰地表示了出来。

    update和enter

    请大家记住:

    update 部分的处理办法一般是:更新属性值
    enter 部分的处理办法一般是:添加元素后,赋予属性值


    Update 和 Exit 的使用
    当对应的元素过多时 ( 绑定数据数量 < 对应元素 ),需要删掉多余的元素。

    现在 body 中有三个 p 元素,要绑定一个长度小于 3 的数组到 p 的选择集上,然后分别处理 update 和 exit 两部分。

    var dataset = [ 3 ];

    //选择body中的p元素
    var p = d3.select("body").selectAll("p");

    //获取update部分
    var update = p.data(dataset);

    //获取exit部分
    var exit = update.exit();

    //update部分的处理:更新属性值
    update.text(function(d){
    return "update " + d;
    });

    //exit部分的处理:修改p元素的属性
    exit.text(function(d){
    return "exit";
    });

    //exit部分的处理通常是删除元素
    // exit.remove();
    结果如下,请大家区分好 update 部分和 exit 部分。这里为了表明哪一部分是 exit,并没有删除掉多余的元素,但实际上 exit 部分的绝大部分操作是删除。

    update和exit

    请大家记住:

    exit 部分的处理办法一般是:删除元素(remove)

    十、交互式操作

    用户用于交互的工具一般有三种:鼠标、键盘、触屏。
    var circle = svg.append("circle");

    circle.on("click", function(){
    //在这里添加交互内容
    });
    这段代码在 SVG 中添加了一个圆,然后添加了一个监听器,是通过 on() 添加的。在 D3 中,每一个选择集都有 on() 函数,用于添加事件监听器。

    on() 的第一个参数是监听的事件,第二个参数是监听到事件后响应的内容,第二个参数是一个函数。

    鼠标常用的事件有:

    click:鼠标单击某元素时,相当于 mousedown 和 mouseup 组合在一起。
    mouseover:光标放在某元素上。
    mouseout:光标从某元素上移出来时。
    mousemove:鼠标被移动的时候。
    mousedown:鼠标按钮被按下。
    mouseup:鼠标按钮被松开。
    dblclick:鼠标双击。
    键盘常用的事件有三个:

    keydown:当用户按下任意键时触发,按住不放会重复触发此事件。该事件不会区分字母的大小写,例如“A”和“a”被视为一致。
    keypress:当用户按下字符键(大小写字母、数字、加号、等号、回车等)时触发,按住不放会重复触发此事件。该事件区分字母的大小写。
    keyup:当用户释放键时触发,不区分字母的大小写。 触屏常用的事件有三个:

    touchstart:当触摸点被放在触摸屏上时。
    touchmove:当触摸点在触摸屏上移动时。
    touchend:当触摸点从触摸屏上拿开时。 当某个事件被监听到时,D3 会把当前的事件存到 d3.event 对象,里面保存了当前事件的各种参数

    //添加SVG画布
    var width = 400;
    var height = 400;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);

    //画布周边的空白

    var padding = {left:30,right:30,top:20,bottom:20};

    //定义数据和比例尺
    var dataset = [10,20,30,40,33,24,12,5];
    //x轴的比例尺
    var xScale = d3.scale.ordinal()
    .domain(d3.range(dataset.length))
    .rangeRoundBands([0,width - padding.left -padding.right]);
    //y轴比例尺

    var yScale = d3.scale.linear()
    .domain([0,d3.max(dataset)])
    .range([height - padding.top - padding.bottom,0]) //因为y轴默认是越往下越大,所以反着写的

    //定义坐标轴
    //定义x轴
    var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom");
    //定义y轴
    var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left");
    //添加矩形和文字元素

    //矩形之间的空白
    var rectPadding = 4;
    //添加矩形元素
    //添加矩形元素
    var rects = svg.selectAll(".MyRect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("class","MyRect")
    .attr("transform","translate(" + padding.left + "," + padding.top + ")")
    .attr("x", function(d,i){
    return xScale(i) + rectPadding/2;
    } )
    .attr("y",function(d){
    return yScale(d);
    })
    .attr("width", xScale.rangeBand() - rectPadding )
    .attr("height", function(d){
    return height - padding.top - padding.bottom - yScale(d);
    })
    .attr("fill",'#e4393c')
    .on("mouseover",function(d,i){
    d3.select(this)
    .attr("fill","yellow");
    })
    .on("mouseout",function(d,i){
    d3.select(this)
    .transition()
    .duration(500)
    .attr("fill","#e4393c");
    });

    //添加文字元素
    var texts = svg.selectAll(".MyText")
    .data(dataset)
    .enter()
    .append("text")
    .attr("class","MyText")
    .attr("transform","translate(" + padding.left + "," + padding.top + ")")
    .attr("x", function(d,i){
    return xScale(i) + rectPadding/2;
    } )
    .attr("y",function(d){
    var min = yScale.domain()[0];
    return yScale(min);
    })
    .transition()
    .delay(function(d,i){
    return i*200;
    })
    .duration(2000)
    .ease("bounce")
    .attr("y",function(d){return yScale(d)})
    .attr("dx",function(){
    return (xScale.rangeBand() - rectPadding)/2;
    })
    .attr("dy",function(d){
    return 20;
    })
    .attr("stroke","#fff")
    .text(function(d){
    return d;
    });
    //添加坐标轴的元素 +padding.left+","+"+(height-padding.bottom)+"
    svg.append("g")
    .attr("class","axis")
    .attr("transform","translate("+padding.left+","+(height-padding.bottom)+")")
    .call(xAxis);

    //添加y轴
    svg.append("g")
    .attr("class","axis")
    .attr("transform","translate("+padding.left+","+padding.top+")")
    .call(yAxis);

    这段代码添加了鼠标移入(mouseover),鼠标移出(mouseout)两个事件的监听器。
    监听器函数中都使用了 d3.select(this),表示选择当前的元素,this 是当前的
    元素,要改变响应事件的元素时这么写就好。

    mouseover 监听器函数的内容为:将当前元素变为黄色

    mouseout 监听器函数的内容为:缓慢地将元素变为原来的颜色(蓝色)

    十一、布局

    我们可以据此定义什么时候选择 D3 比较好:

    选择 D3:如果希望开发脑海中任意想象到的图表。
    选择 Highcharts、Echarts 等:如果希望开发几种固定种类的、十分大众化的图表。
    如何理解布局
    从上面的图可以看到,布局的作用是:将不适合用于绘图的数据转换成了适合用于绘图的数据。

    因此,为了便于初学者理解,本站的教程叫布局的作用解释成:数据转换。


    布局有哪些
    D3 总共提供了 12 个布局:饼状图(Pie)、力导向图(Force)、弦图(Chord)、
    树状图(Tree)、集群图(Cluster)、捆图(Bundle)、打包图(Pack)、
    直方图(Histogram)、分区图(Partition)、堆栈图(Stack)、
    矩阵树图(Treemap)、层级图(Hierarchy)。

    1)饼图


    利用布局绘制饼图:

    var width = 400;
    var height = 400;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);
    //用D3进行数据可视化

    var dataset = [30,10,43,55,13];
    //定义一个布局
    var pie = d3.layout.pie();
    //返回值赋给变量pie,此时 pie 可以当做函数使用
    var piedata = pie(dataset);
    //将数组dataset作为pie()的参数,返回值给piedata,这样 piedata 就是转换后的数据
    // console.log(piedata); //转换后为一个包含5个对象的数组
    //布局不是要绘图而是为了得到绘图所需的数据
    //为了根据转换后的数据 piedata 来作图,还需要一样工具:生成器。
    //SVG 有一个元素,叫做路径 path,是 SVG 中功能最强的元素,
    // 它可以表示其它任意的图形。顾名思义,路径元素就是通过定义
    // 一个段“路径”,来绘制出各种图形。但是,路径是很难计算的,
    // 通过布局转换后的数据 piedat
    // a 仍然很难手动计算得到路径值。为我们完成这项任务的,就是生成器。
    //这里要用到的叫做弧生成器,能够生成弧的路径,因为饼图的每一部分都是一段弧。
    var outerRadius = 150;
    var innerRadius = 0;
    var arc = d3.svg.arc()
    .innerRadius(innerRadius)
    .outerRadius(outerRadius);
    //弧生成器返回的结果赋值给 arc。此时 arc可以当做一个函数使用,把piedata
    //作为参数传入,即可得到路径值
    //接下来,可以在 SVG 中添加图形元素了。先在 svg 里添加足够数量(5个)个分组元素(g),
    // 每一个分组用于存放一段弧的相关元素。
    var arcs = svg.selectAll("g")
    .data(piedata)
    .enter()
    .append("g")
    .attr("transform","translate("+(width/2)+","+(width/2)+")");
    //接下来对每个g元素,添加path
    //color是一个颜色比例尺,它能根据传入的索引号获取相应的颜色值
    var color = d3.scale.category10();
    var selectColor = "";
    arcs.append("path")
    .attr("fill",function(d,i){
    return color(i);
    })
    .attr("d",function(d){
    return arc(d); //调用弧生成器,得到路径值
    });
    //在每一个弧线中心添加文本

    arcs.append("text")
    .attr("transform",function(d){
    return "translate("+arc.centroid(d)+")";
    })
    .attr("text-anchor","middle")
    .text(function(d){
    return d.data;
    });

    //arc.centroid(d) 能算出弧线的中心。要注意,text() 里返回的是 d.data ,
    // 而不是 d 。因为被绑定的数据是对象,里面有 d.startAngle、d.endAngle、
    // d.data 等,其中 d.data 才是转换前的整数的值。

    2)力导向图


    力导向图(Force-Directed Graph),是绘图的一种算法。在二维或三维空间里配置
    节点,节点之间用线连接,称为连线。各连线的长度几乎相等,且尽可能不相交。
    节点和连线都被施加了力的作用,力是根据节点和连线的相对位置计算的。
    根据力的作用,来计算节点和连线的运动轨迹,并不断降低它们的能量,
    最终达到一种能量很低的安定状态。

    力导向图能表示节点之间的多对多的关系

    布局(数据转换) d3.layout.force()。

    var width = 800;
    var height = 400;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);
    //用D3进行数据可视化

    var nodes = [{name:"桂林"},{name:"广州"},{name:"厦门"},{name:"杭州"},{name:"上海"},{name:"青岛"},{name:"天津"}];
    var edges = [{source:0,target:1},{source:0,target:2},{source:0,target:3},
    {source:1,target:4},{source:1,target:5},{source:1,target:6},];
    //定义一个力导向图的布局如下

    var force = d3.layout.force()
    .nodes(nodes) //指定节点数组
    .links(edges) //指定连线数组
    .size([width,height]) //指定作用域范围
    .linkDistance(150) //指定连线长度
    .charge([-400]); //相互之间的作用力
    //使力学作用生效
    force.start(); //开始作用
    console.log(nodes);
    console.log(edges);
    //转换后,节点对象里多了一些变量。其意义如下:

    // index:节点的索引号
    //px, py:节点上一个时刻的坐标
    //x, y:节点的当前坐标
    //weight:节点的权重
    //有了转换后的数据就可以作图了。分别绘制三种图形元素
    //line 线段 ,表示连线
    //circle 圆,表示节点
    //text 文字,描述节点

    //添加连线
    var svg_edges = svg.selectAll("line")
    .data(edges)
    .enter()
    .append("line")
    .style("stroke","#ccc")
    .style("stroke-width",1);
    var color = d3.scale.category20();

    //添加节点
    var svg_nodes = svg.selectAll("circle")
    .data(nodes)
    .enter()
    .append("circle")
    .attr("r",20)
    .style("fill",function(d,i){
    return color(i);
    })
    .call(force.drag); //使节点能够拖动

    //添加描述节点的文字
    var svg_texts = svg.selectAll("text")
    .data(nodes)
    .enter()
    .append("text")
    .style("fill","black")
    .attr("dx",20)
    .attr("dy",8)
    .text(function(d){
    return d.name;
    });

    // 调用 call( force.drag ) 后节点可被拖动。force.drag() 是一个函数,将其作为 call() 的参数,相当于将当前选择的元素传到 force.drag() 函数中。

    //最后,还有一段最重要的代码。由于力导向图是不断运动的,每一时刻都在发生更新,因此,必须不断更新节点和连线的位置。
    //力导向图布局 force 有一个事件 tick,每进行到一个时刻,都要调用它,更新的内容就写在它的监听器里就好。

    force.on("tick", function(){ //对于每一个时间间隔
    //更新连线坐标
    svg_edges.attr("x1",function(d){ return d.source.x; })
    .attr("y1",function(d){ return d.source.y; })
    .attr("x2",function(d){ return d.target.x; })
    .attr("y2",function(d){ return d.target.y; });

    //更新节点坐标
    svg_nodes.attr("cx",function(d){ return d.x; })
    .attr("cy",function(d){ return d.y; });

    //更新文字坐标
    svg_texts.attr("x", function(d){ return d.x; })
    .attr("y", function(d){ return d.y; });
    });


    var width = 800;
    var height = 400;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height);
    //用D3进行数据可视化

    var nodes = [{name:"桂林"},{name:"广州"},{name:"厦门"},{name:"杭州"},{name:"上海"},{name:"青岛"},{name:"天津"}];
    var edges = [{source:0,target:1},{source:0,target:2},{source:0,target:3},
    {source:1,target:4},{source:1,target:5},{source:1,target:6},];
    //定义一个力导向图的布局如下

    var force = d3.layout.force()
    .nodes(nodes) //指定节点数组
    .links(edges) //指定连线数组
    .size([width,height]) //指定作用域范围
    .linkDistance(150) //指定连线长度
    .charge([-400]); //相互之间的作用力
    //使力学作用生效
    force.start(); //开始作用
    console.log(nodes);
    console.log(edges);
    //转换后,节点对象里多了一些变量。其意义如下:

    // index:节点的索引号
    //px, py:节点上一个时刻的坐标
    //x, y:节点的当前坐标
    //weight:节点的权重
    //有了转换后的数据就可以作图了。分别绘制三种图形元素
    //line 线段 ,表示连线
    //circle 圆,表示节点
    //text 文字,描述节点

    //添加连线
    var svg_edges = svg.selectAll("line")
    .data(edges)
    .enter()
    .append("line")
    .style("stroke","#ccc")
    .style("stroke-width",1);
    var color = d3.scale.category20();

    //添加节点
    var svg_nodes = svg.selectAll("circle")
    .data(nodes)
    .enter()
    .append("circle")
    .attr("r",20)
    .style("fill",function(d,i){
    return color(i);
    })
    .call(force.drag); //使节点能够拖动

    //添加描述节点的文字
    var svg_texts = svg.selectAll("text")
    .data(nodes)
    .enter()
    .append("text")
    .style("fill","black")
    .attr("dx",20)
    .attr("dy",8)
    .text(function(d){
    return d.name;
    });

    // 调用 call( force.drag ) 后节点可被拖动。force.drag() 是一个函数,将其作为 call() 的参数,相当于将当前选择的元素传到 force.drag() 函数中。

    //最后,还有一段最重要的代码。由于力导向图是不断运动的,每一时刻都在发生更新,因此,必须不断更新节点和连线的位置。
    //力导向图布局 force 有一个事件 tick,每进行到一个时刻,都要调用它,更新的内容就写在它的监听器里就好。

    force.on("tick", function(){ //对于每一个时间间隔
    //更新连线坐标
    svg_edges.attr("x1",function(d){ return d.source.x; })
    .attr("y1",function(d){ return d.source.y; })
    .attr("x2",function(d){ return d.target.x; })
    .attr("y2",function(d){ return d.target.y; });

    //更新节点坐标
    svg_nodes.attr("cx",function(d){ return d.x; })
    .attr("cy",function(d){ return d.y; });

    //更新文字坐标
    svg_texts.attr("x", function(d){ return d.x; })
    .attr("y", function(d){ return d.y; });
    });


    3)树状图

    树状图,可表示节点之间的包含与被包含关系

    数据

    初始数据先写在一个JSON文件中,再用D3来读取

    JSON 是一种轻量级的数据交换格式。

    {
    "name":"中国",
    "children":
    [
    {
    "name":"浙江" ,
    "children":
    [
    {"name":"杭州" },
    {"name":"宁波" },
    {"name":"温州" },
    {"name":"绍兴" }
    ]
    },

    {
    "name":"广西" ,
    "children":
    [
    {
    "name":"桂林",
    "children":
    [
    {"name":"秀峰区"},
    {"name":"叠彩区"},
    {"name":"象山区"},
    {"name":"七星区"}
    ]
    },
    {"name":"南宁"},
    {"name":"柳州"},
    {"name":"防城港"}
    ]
    },

    {
    "name":"黑龙江",
    "children":
    [
    {"name":"哈尔滨"},
    {"name":"齐齐哈尔"},
    {"name":"牡丹江"},
    {"name":"大庆"}
    ]
    },

    {
    "name":"新疆" ,
    "children":
    [
    {"name":"乌鲁木齐"},
    {"name":"克拉玛依"},
    {"name":"吐鲁番"},
    {"name":"哈密"}
    ]
    }
    ]
    }

    这段数据表示:“中国”“省份名”“城市名”的包含与被包含关系

    布局(数据转换)

    <style>
    .node circle{
    fill:#fff;
    stroke:steelblue;
    stroke- 1.5px;
    }
    .node{
    font: 12px sans-serif;
    }
    .link{
    fill:none;
    stroke:#ccc;
    stroke- 1.5px;
    }
    </style>
    var width = 800;
    var height = 800;
    var svg = d3.select("body")
    .append("svg")
    .attr("width",width)
    .attr("height",height)
    .style("padding-top","50px");
    var tree = d3.layout.tree()
    .size([width,height-500])
    .separation(function(a,b){return (a.parent == b.parent ?1:2);})
    //布局保存在变量tree中
    //size():设定尺寸,即转换后的各节点的坐标在哪一个范围内
    //separation():设定节点之间的间隔

    //转换数据
    d3.json("data/data.json",function(error,root){
    //root是读入的数据
    var nodes = tree.nodes(root);
    var links = tree.links(nodes);
    // console.log(nodes);
    //console.log(links);
    //画点
    var node = svg.selectAll(".node")
    .data(nodes)
    .enter()
    .append("g")
    .attr("class","node")
    .attr("transform",function(d){return "translate("+ d.x+","+ d.y+")"})
    //加圈圈
    node.append("circle")
    .attr("r",4.5)
    //加文字
    var text = node.append("text")
    .attr("dy",[15,15,15,15])
    .attr("dx",[-11,-11,-11,-11])
    .style("text-anchor", function (d) {return d.children?"end":"start"})
    .text(function(d){
    return d.name
    })

    var diagonal = d3.svg.diagonal()
    .projection(function(d){return [d.x, d.y];})
    //画线
    var line = svg.selectAll("link")
    .data(links)
    .enter()
    .append("path")
    .attr("class","link")
    .attr("d",diagonal)
    });
    // //d3.json() 是用来向服务器请求JSON文件的
    // //d3.json()函数后面跟一个无名函数function(error),参数root是读取的数据
    //
    // //后两行代码调用tree转换数据,保存到变量nodes和links中。然后输出转换后的数据
    // //nodes 中有各个节点的子节点(children)、深度(depth)、名称(name)、位置(x,y)信息,其中名称(name)是 json 文件中就有的属性。
    //
    // //links 中有连线两端( source , target )的节点信息。
    // //绘制
    // //D3已经基本上我们准备好了绘制的函数,d3.svg.diagonal()。这是一个对角线
    // //生成器,只需要输入两个顶点坐标,即可生成一条贝塞尔曲线
    //
    // //创建一个对角线生成器:
    // var diagonal = d3.svg.diagonal()
    // .projection(function(d){return [d.y, d.x];})
    // // projection()是一个点变换器,默认是[d.x , d.y],即保持原坐标不变,
    // //如果写成[d.y ,d.x] 即是说对任意输入的顶点,都交换 x 和y坐标
    // //绘制连线时,方法如下:
    // var link = svg.selectAll(".link")
    // .data(links)
    // .enter()
    // .append("path")
    // .attr("class","link")
    // .attr("d",diagonal); //使用对角线生成器

    十二、D3 的核心函数

     1、选择元素

     D3提供了两种高级方法来选择元素:select 和 selectAll 。这些方法接受选择器字符串,前者只返回第一个匹配的元素,后者选择在文档遍历次序中所有匹配的元素。这个方法也可以接受节点,这可以用来和第三方库,例如jQuery或者开发者工具整合。

      

    d3.select(selector)

    选中与指定选择器字符串匹配的第一个元素,返回单元素选择结果。如果当前文档中没有匹配的元素则返回空的选择。如果有多个元素被选中,只有第一个匹配的元素(在文档遍历次序中)被选中。

    # d3.select(node)

    选择指定的节点。如果你已经有一个节点的引用这将是很有用的。例如事件监听器中的d3.select(this) 或者一个全局对象例如document.body`。这个函数不会遍历DOM树。

    d3.selectAll(nodes)

    选择指定的元素数组。如果你已经有了一些元素的引用这将是很有用的,例如事件监听器中的d3.selectAll(this.childNodes),或者一个全局对象例如document.links。节点的参数不用恰好是数组;任何可以转化为一个数组的伪数组(例一个NodeList或者 arguments`)都可以,这个函数不会遍历DOM树。

    内容

    D3有一系列可以影响文档内容的操作。这些是你最常用来展示数据的。当操作用来设置文档内容的时候会返回当前选择,所以你就可以使用一个简单的声明将多个操作链接在一起

  • 相关阅读:
    如何将latex格式转换成word? Lei
    SEWM2012会议报告总结 Lei
    matlab图片到word的过程 Lei
    日记——有点郁闷的一天
    牛博国际开放了,等了一个月才能看牛博。
    做什么事都没有兴趣呢,怎么这么低调呢?
    读UML书
    firefox弹出窗口关闭时执行代码刷新父窗口
    终于关注了一下传说中的小强,firebug
    MOSS Export Site Column/Site Content Type
  • 原文地址:https://www.cnblogs.com/WEI-web/p/7209246.html
Copyright © 2020-2023  润新知