D3.js

d3.js ( data-driven document )

1
2
3
4
5
6
7
8
9
10
11
12
// Update…
var p = d3.select("body")
.selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.text(function(d) { return d; });

// Enter…
p.enter().append("p")
.text(function(d) { return d; });

// Exit…
p.exit().remove();

如果我们用函数式风格创建对象,并且该对象所有的方法都没有用this,(并且这个对象不包含外部可见的成员数据),那么这个对象就是持久的(durable)。持久对象就是许多功能行为的集合了。
— Crockfort D.

数据可视化的步骤

1.获取 Acquire
2.分析 Parse
3.过滤 Filter
4.挖掘 Mine
5.表现 Represent
6.改善 Refine
7.交互 Interact

视觉要素

1.坐标
2.大小
3.色彩
4.标签
5.关联

图标种类

1.柱形图
2.散点图
3.折线图
4.饼状图
5.弦图
6.力导向图
7.树状图
8.打包图
9.分区图  

var i = document.querySelectAll("p").iterator();
var e;
while( e = i.next() ) {
    //对每个选中的元素操作
    console.log(e);
}

选取单个元素

d3.select("p").attr("foo","goo");
d3.select("p").attr("foo");

// 检测P元素是否有名为goo的class
d3.select("p").classed("goo");
// 为p元素添加 goo class
d3.select("p").classed("goo",true);
// 移除p元素上的goo class
d3.select("p").classed("goo",function(){return false;});

d3.select("p").style("font-size");
d3.select("p").style("font-size","10px");
d3.select("p").style("font-size",function(){return normalFontSize + 10;});

d3.select(".d3").text();
d3.select(".d3").text("Hello World");
d3.select(".d3").text(function(){
    var model = retrieveModel();
    return model.message;
});

d3.select(".d3").html();
d3.select(".d3").html("<b>Hello</b>");
d3.select(".d3").html(function(){
    var template = compileTemplate();
    return template;
});

选取多个元素

d3.selectAll("p").attr("class","red");

迭代选集中元素

d3.selectAll("div")
  .attr("class", "red box")
  .each(function(d, i){
      d3.select(this).append("h1").text(i);
  });

子选择器

d3.select("#section1 > div").attr("class","blue box");
d3.select("#section2").select("div").attr("class","red box");

函数级联调用

var body = d3.select("body");
body.append("section")
     .attr("id","section1")
     .append("div")
     .attr("class","blue box")
     .append("p")
     .attr("class","dynamic blue box");

原始选集

<table class="table">
    <thead>
    <tr>
        <th>Time</th>
        <th>Type</th>
        <th>Amount</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>10:22</td>
        <td>Purchase</td>
        <td>$10.00</td>
    </tr>
    <tr>
        <td>12:12</td>
        <td>Purchase</td>
        <td>$12.50</td>
    </tr>
    <tr>
        <td>14:11</td>
        <td>Expense</td>
        <td>$9.70</td>
    </tr>
    </tbody>
</table>

var rows = d3.selectAll("tr"); ∂
var headerElement = rows._groups[0][0];
d3.select(headerElement).attr("class", "table-header"); 
d3.select(rows._groups[0][1]).attr("class", "table-row-odd"); 
d3.select(rows._groups[0][2]).attr("class", "table-row-even"); 
d3.select(rows._groups[0][3]).attr("class", "table-row-odd");

进入-更新-退出模式(enter-update-exit)

将数组绑定为数据
var data = [10,15,30,50,80,65,55,30,20,10,9];
function render(data) {
    //进入 enter
    d3.select("body").selectAll("div.h-bar")
        .data(data)
        .enter()
        .append("div")
            .attr("class","h-bar")
                .append("span");
    //更新 update
    d3.select("body").selectAll("div.h-bar")
    .data(data)
    .style("width",function(d){
        return d*3 + "px";
    })
    .select("span")
        .text(function(d){
            return d;
        });
    //退出 exit
    d3.select("body").selectAll("div.h-bar")
        .data(data)
        .exit()
            .remove();
}
setInterval(function(){
    data.shift();
    data.push(Math.round(Math.random()*100));
    render(data);
},1500);
render(data);
将对象字面量绑定为数据
var data2 = [
    {width:10,color:23},
    {width:15,color:33},
    {width:30,color:40},
    {width:50,color:60},
    {width:80,color:22},
    {width:65,color:10},
    {width:55,color:5},
    {width:30,color:30},
    {width:20,color:60},
    {width:10,color:90},
    {width:8,color:10},
];

var colorScale = d3.scaleLinear().domain([10,100]).range(['#add8e6',"blue"]);
function render(data) {
    //进入 enter
    d3.select("body").selectAll("div.h-bar")
        .data(data)
        .enter()
        .append("div")
            .attr("class","h-bar")
                .append("span");
    //更新 update
    d3.select("body").selectAll("div.h-bar")
    .data(data)
    .style("width",function(d){
        return d.width*3 + "px";
    })
    .style("background-color",function(d){
        return colorScale(d.color);
    })
    .select("span")
        .text(function(d){
            return d.width;
        });
    //退出 exit
    d3.select("body").selectAll("div.h-bar")
        .data(data)
        .exit()
            .remove();
}
function randomValue() {
    return Math.round(Math.random() * 100);
}
setInterval(function(){
    data2.shift();
    data2.push({width:randomValue(), color:randomValue()});
    render(data2);
},1500);
render(data2);
将函数绑定为数据
    d3.select("body").append("div").attr("id","container3");
var data3 = [];
var next = function(x) {
    return 15+x*x;
};
var newData = function() {
    if(data3.length > 8) data3.shift();
    data3.push(next);
    return data3;
}
function render3() {
    var selection = d3.select("#container3").selectAll("div").data(newData);
    selection.enter().append("div").append("span");
    selection.exit().remove();
    selection.attr("class","v-bar")
                     .style("height",function(d,i){
                             return d(i) + "px";
                     })
                     .select("span")
                         .text(function(d,i){
                             return d(i);
                         });
}
setInterval(function(){
    render3();
},1500);
render3();

作为数据的函数一般情况下需要时幂等的。幂等函数是指同一个函数,对于相同输入的多次运算结果和一次的运算结果是相同的。

处理数组

var array = [3,2,11,7,6,4,10,7,15];
d3.select("#min").text(d3.min(array));
d3.select("#max").text(d3.max(array));
d3.select("#extent").text(d3.extent(array));
d3.select("#sum").text(d3.sum(array));
d3.select("#median").text(d3.median(array));
d3.select("#mean").text(d3.mean(array));
d3.select("#asc").text(array.sort(d3.ascending));
d3.select("#desc").text(array.sort(d3.descending));
//分位数
d3.select("#quantile").text(
    d3.quantile(array.sort(d3.ascending), 0.25)
);
//返回插入点,插入点左边的元素都小于等于指定的元素,插入点右边的元素大于指定的元素
d3.select("#bisect").text(
    d3.bisect(array.sort(d3.ascending), 6)
);

var records = [
    {quantity: 2, total: 190, tip: 100, type: "tab"},
    {quantity: 2, total: 190, tip: 100, type: "tab"},
    {quantity: 1, total: 300, tip: 200, type: "visa"},
    {quantity: 2, total: 90, tip: 0, type: "tab"},
    {quantity: 2, total: 90, tip: 0, type: "tab"},
    {quantity: 2, total: 90, tip: 0, type: "tab"},
    {quantity: 1, total: 100, tip: 0, type: "cash"},
    {quantity: 2, total: 90, tip: 0, type: "tab"},
    {quantity: 2, total: 90, tip: 0, type: "tab"},
    {quantity: 2, total: 90, tip: 0, type: "tab"},
    {quantity: 2, total: 200, tip: 0, type: "cash"},
    {quantity: 1, total: 200, tip: 100, type: "visa"}
];
//将一维数据结构的数据转换为树状嵌套结构的数据
var nest = d3.nest()
        .key(function (d) { // <- A
            return d.type;
        })
        .key(function (d) { // <- B
            return d.tip;
        })
        .entries(records); // <- C

d3.select("body").append("div").html(printNest(nest, ""));

function printNest(nest, out, i) {
    if(i === undefined) i = 0;

    var tab = ""; for(var j = 0; j < i; ++j) tab += "&nbsp;";

    nest.forEach(function (e) {
        if (e.key)
            out += tab + e.key + "<br>";
        else
            out += tab + printObject(e) + "<br>";

        if (e.values)
            out = printNest(e.values, out, ++i);
        else
            return out;
    });

    return out;
}

function printObject(obj) {
    var s = "{";
    for (var f in obj) {
        s += f + ": " + obj[f] + ", ";
    }
    s += "}";
    return s;
}

#
d3.ascending = function(a,b){return ab ? 1 : 0;}
d3.descending = function(a,b){return a>b ? -1 : a<b ? 1 : 0;}

数据的过滤

var data = [ // <-A
    {expense: 10, category: "Retail"},
    {expense: 15, category: "Gas"},
    {expense: 30, category: "Retail"},
    {expense: 50, category: "Dining"},
    {expense: 80, category: "Gas"},
    {expense: 65, category: "Retail"},
    {expense: 55, category: "Gas"},
    {expense: 30, category: "Dining"},
    {expense: 20, category: "Retail"},
    {expense: 10, category: "Dining"},
    {expense: 8, category: "Gas"}
];

function render(data, category) {
    d3.select("body").selectAll("div.h-bar") // <-B
            .data(data)
    .enter()
        .append("div")
            .attr("class", "h-bar")
    .append("span");

    d3.select("body").selectAll("div.h-bar") // <-C
            .data(data)
        .exit().remove();

    d3.select("body").selectAll("div.h-bar") // <-D
            .data(data)
        .attr("class", "h-bar")
        .style("width", function (d) {
            return (d.expense * 5) + "px";}
        )
        .select("span")
            .text(function (d) {
                return d.category;
            });

    d3.select("body").selectAll("div.h-bar")
            .filter(function (d, i) { // <-E
                return d.category == category;
            })
            .classed("selected", true);
}

render(data);

function select(category) {
    render(data, category);
}

<div class="control-group">
    <button onclick="select('Retail')">
        Retail
    </button>
    <button onclick="select('Gas')">
        Gas
    </button>
    <button onclick="select('Dining')">
        Dining
    </button>
    <button onclick="select()">
        Clear
    </button>
</div>

基于数据的图形排序

var data = [ // <-A
    {expense: 10, category: "Retail"},
    {expense: 15, category: "Gas"},
    {expense: 30, category: "Retail"},
    {expense: 50, category: "Dining"},
    {expense: 80, category: "Gas"},
    {expense: 65, category: "Retail"},
    {expense: 55, category: "Gas"},
    {expense: 30, category: "Dining"},
    {expense: 20, category: "Retail"},
    {expense: 10, category: "Dining"},
    {expense: 8, category: "Gas"}
];

function render(data, comparator) {
    d3.select("body").selectAll("div.h-bar") // <-B
            .data(data)
        .enter().append("div")
            .attr("class", "h-bar")
            .append("span");

    d3.select("body").selectAll("div.h-bar") // <-C
            .data(data)
        .exit().remove();

    d3.select("body").selectAll("div.h-bar") // <-D
            .data(data)
        .attr("class", "h-bar")
        .style("width", function (d) {
            return (d.expense * 5) + "px";
        })
        .select("span")
            .text(function (d) {
                return d.category;
            });

    if(comparator)
        d3.select("body")
            .selectAll("div.h-bar") 
            .sort(comparator); // <-E
}

var compareByExpense = function (a, b) {  // <-F
    return a.expense < b.expense?-1:1;
};
var compareByCategory = function (a, b) {  // <-G
    return a.category < b.category?-1:1;
};

render(data);

function sort(comparator) {
    render(data, comparator);
}

<div class="control-group">
    <button onclick="sort(compareByExpense)">
        Sort by Width
    </button>
    <button onclick="sort(compareByCategory)">
        Sort by Category
    </button>
    <button onclick="sort()">
        Clear
    </button>
</div>

从服务器加载数据

function load(){
    d3.json("data.json", function(error, json){
        data = data.concat(json);  
        render(data);
    });
}

json,csv,tsv,txt,html,xml
d3.xhr

假设A和B是两个非空集合,函数f是A到B的一个映射,使得B中的每个元素与A中的元素一一对应。当b中每个元素都可以通过函数f映射到a中的相应元素时,记做f(a)=b

数值尺度
<div id="linear" class="clear"><span>n</span></div>
<div id="linear-capped" class="clear">
    <span>1 &lt;= a*n + b &lt;= 20</span>
</div>
<div id="pow" class="clear"><span>n^2</span></div>
<div id="pow-capped" class="clear">
    <span>1 &lt;= a*n^2 + b &lt;= 10</span>
</div>
<div id="log" class="clear"><span>log(n)</span></div>
<div id="log-capped" class="clear">
    <span>1 &lt;= a*log(n) + b &lt;= 10</span>
</div>

var max = 11, data = [];
for (var i = 1; i < max; ++i) data.push(i);

// 线性尺度 f(n) = a*n + b
var linear = d3.scaleLinear() // <-A
    .domain([1, 10]) // <-B
    .range([1, 10]); // <-C        
var linearCapped = d3.scaleLinear()
    .domain([1, 10])        
    .range([1, 20]); // <-D

// 幂级尺度 f(n) = a*n^2 + b
var pow = d3.scalePow().exponent(2); // <-E
var powCapped = d3.scalePow() // <-F
    .exponent(2)
    .domain([1, 10])
    .rangeRound([1, 10]); // <-G

// 对数尺度 f(n) = a*log(n) + b
var log = d3.scaleLog(); // <-H
var logCapped = d3.scaleLog() // <-I
    .domain([1, 10])
    .rangeRound([1, 10]);


function render(data, scale, selector) {
    d3.select(selector).selectAll("div.cell")
            .data(data)
            .enter().append("div").classed("cell", true);

    d3.select(selector).selectAll("div.cell")
            .data(data)
            .exit().remove();

    d3.select(selector).selectAll("div.cell")
            .data(data)
            .style("display", "inline-block")
            .text(function (d) {
                return Math.round(scale(d) * 100 ) / 100;
            });
}

render(data, linear, "#linear");
render(data, linearCapped, "#linear-capped");
render(data, pow, "#pow");
render(data, powCapped, "#pow-capped");
render(data, log, "#log");
render(data, logCapped, "#log-capped");

除了上述尺度外,D3还提供多种数值尺度,例如 quantize(一种线性离散尺度),threshold(阈值尺度),quantile(分位尺度),identity(等价尺度)

时间尺度
var start = new Date(2013, 0, 1), 
    end = new Date(2013, 11, 31),
    range = [0, 1200],
    time = d3.scaleTime().domain([start, end]) 
        .rangeRound(range), 
    max = 12,
    data = [];

for (var i = 0; i < max; ++i){ 
    var date = new Date(start.getTime());
    date.setMonth(start.getMonth() + i);
    data.push(date);
}

function render(data, scale, selector) { 
    d3.select(selector).selectAll("div.fixed-cell")
                .data(data)
            .enter()
                .append("div").classed("fixed-cell", true);

    d3.select(selector).selectAll("div.fixed-cell")
                .data(data)
            .exit().remove();

    d3.select(selector).selectAll("div.fixed-cell")
                .data(data)
            .style("margin-left", function(d){ 
                return scale(d) + "px";
            })
            .html(function (d) { 
                var format = d3.timeFormat("%x"); 
                return format(d) + "<br>" + scale(d) + "px";
            });
}

render(data, time, "#time");
有序尺度
var max = 10, data = [];

for (var i = 0; i < max; ++i) data.push(i); 

var alphabet = d3.scaleOrdinal()
    .domain(data)
    .range(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]);

function render(data, scale, selector) { 
    d3.select(selector).selectAll("div.cell")
                .data(data)
            .enter().append("div").classed("cell", true);

    d3.select(selector).selectAll("div.cell")
                .data(data)
            .exit().remove();

    d3.select(selector).selectAll("div.cell")
                .data(data)
            .style("display", "inline-block")
            .style("background-color", function(d){ 
                return scale(d).indexOf("#")>=0?scale(d):"white";
            })
            .text(function (d) { 
                return scale(d);
            });
}

render(data, alphabet, "#alphabet");
render(data, d3.scaleOrdinal(d3.schemeCategory10), "#category10");
插值器
var interpolate = d3.interpolateNumber(0,100);
interpolate(0.1);//10
interpolate(0.99);//99

function interpolate(t){
    return a*(1-t)+b*t;
}
字符串插值
d3.select("body").append("div").attr("id","font");
var max = 11, data = [];

var sizeScale = d3.scaleLinear() 
    .domain([0, max])
    .range([  
        "italic bold 12px/30px Georgia, serif", 
        "italic bold 120px/180px Georgia, serif"
    ]);

for (var i = 0; i < max; ++i) data.push(i);

function render(data, scale, selector) { 
    d3.select(selector).selectAll("div.cell")
            .data(data)
        .enter().append("div").classed("cell", true)
            .append("span");

    d3.select(selector).selectAll("div.cell")
            .data(data)
        .exit().remove();

    d3.select(selector).selectAll("div.cell")
            .data(data)
        .style("display", "inline-block")
        .select("span")
            .style("font", function(d,i){ 
                return scale(d); 
            })
            .text(function(d,i){return i;}); 
}

render(data, sizeScale, "#font");
颜色插值
    d3.select("body").append("div").attr("id","color").attr("class","clear");
    d3.select("body").append("div").attr("id","color-diverge").attr("class","clear");
    var max = 21, data = [];

var colorScale = d3.scaleLinear() 
    .domain([0, max])
    .range(["white", "#4169e1"]);

//分段线性尺度
function divergingScale(pivot) { 
    var divergingColorScale = d3.scaleLinear()
        .domain([0, pivot, max]) 
        .range(["white", "#4169e1", "white"]);
    return divergingColorScale;
}

for (var i = 0; i < max; ++i) data.push(i);

function render(data, scale, selector) { 
    d3.select(selector).selectAll("div.cell")
            .data(data)
        .enter()
            .append("div")
                .classed("cell", true)
            .append("span");

    d3.select(selector).selectAll("div.cell")
            .data(data)
        .exit().remove();

    d3.select(selector).selectAll("div.cell")
            .data(data)
        .style("display", "inline-block")
        .style("background-color", function(d){
            return scale(d);
        })
        .select("span")
            .text(function(d,i){return i;});
}

render(data, colorScale, "#color");
render(data, divergingScale(5), "#color-diverge");
复合对象插值
d3.select("body").append("div").attr("id","compound");
    var max = 21, data = [];

var compoundScale = d3.scalePow()
        .exponent(2)
        .domain([0, max])
        .range([
            {color:"#add8e6", height:"15px"}, 
            {color:"#4169e1", height:"150px"} 
        ]);

for (var i = 0; i <= max; ++i) data.push(i);

function render(data, scale, selector) { 
    d3.select(selector).selectAll("div.v-bar")
            .data(data)
            .enter().append("div").classed("v-bar", true)
            .append("span");

    d3.select(selector).selectAll("div.v-bar")
            .data(data)
            .exit().remove();

    d3.select(selector).selectAll("div.v-bar")
            .data(data)
            .classed("v-bar", true)
            .style("height", function(d){ 
                return scale(d).height;
            }) 
            .style("background-color", function(d){ 
                return scale(d).color;
            })
            .select("span")
            .text(function(d,i){return i;});
}

render(data, compoundScale, "#compound");
自定义插值器
d3.select("body").append("div").attr("id","dollar");
d3.select("body").append("div").attr("id","alphabet");

var dollarScale = d3.scaleLinear()
        .domain([0, 13])
        .range(["$0", "$300"])
        .interpolate(
                    function(a, b) {
                        var re = /^\$([0-9,.]+)$/;
                        var ma, mb;
                        var f = d3.format(",.02f");

                        if ((ma = re.exec(a)) && (mb = re.exec(b))) {
                            a = parseFloat(ma[1]);
                            b = parseFloat(mb[1]) - a;
                            return function(t) {
                                return "$" + f(a + b * t);
                            }
                        }
                    }
                );

var alphabetScale = d3.scaleLinear()
        .domain([0, 27])
        .range(["a", "z"])
        .interpolate(
            function(a, b) { 
                      var re = /^([a-z])$/, ma, mb; 
                      if ((ma = re.exec(a)) && (mb = re.exec(b))) { 
                        a = a.charCodeAt(0);
                        var delta = a - b.charCodeAt(0); 
                        return function(t) { 
                          return String.fromCharCode(Math.ceil(a - delta * t));
                        };
                      }
                    }
        );

function render(scale, selector) {        
    var data = [];
    var max = scale.domain()[1];

    for (var i = 0; i <= max; ++i) data.push(i);      

    d3.select(selector).selectAll("div.cell")
                .data(data)
            .enter()
                .append("div")
                    .classed("cell", true)
                .append("span");

    d3.select(selector).selectAll("div.cell")
                .data(data)
            .exit().remove();

    d3.select(selector).selectAll("div.cell")
            .data(data)
            .style("display", "inline-block")
            .select("span")
                .text(function(d,i){return scale(d);}); 
}

render(dollarScale, "#dollar");
render(alphabetScale, "#alphabet");
坐标轴基础
<div class="control-group">
    <button onclick="renderAll('bottom')">
        horizontal bottom
    </button>
    <button onclick="renderAll('top')">
        horizontal top
    </button>
    <button onclick="renderAll('left')">
        vertical left
    </button>
    <button onclick="renderAll('right')">
        vertical right
    </button>
</div>
String.prototype.firstUpperCase = function(){  
     return this.replace(/\b(\w)/g, function($1){  
         return $1.toUpperCase();  
     });  
    } 

var height = 500, 
    width = 500, 
    margin = 25,
    offset = 50,
    axisWidth = width - 2 * margin,
    svg;

function createSvg(){
     svg = d3.select("body").insert("svg",".control-group") 
        .attr("class", "axis") 
        .attr("width", width)
        .attr("height", height);

}

function renderAxis(scale, i, orient){
    var axis = d3['axis'+orient.firstUpperCase()]()
        .scale(scale) 
        .ticks(5); 

    svg.append("g")        
        .attr("transform", function(){ 
            if(["top", "bottom"].indexOf(orient) >= 0)
                return "translate(" + margin + "," + i * offset + ")";
            else
                return "translate(" + i * offset + ", " + margin + ")";
        })
        .call(axis); 
}

function renderAll(orient){
    if(svg) svg.remove();

    createSvg();

    renderAxis(d3.scaleLinear()
                .domain([0, 1000])
                .range([0, axisWidth]), 1, orient);
    renderAxis(d3.scalePow()
                .exponent(2)
                .domain([0, 1000])
                .range([0, axisWidth]), 2, orient);
    renderAxis(d3.scaleTime()
                .domain([new Date(2012, 0, 1), new Date()])
                .range([0, axisWidth]), 3, orient);
}
window.renderAll = renderAll;
自定义刻度
var height = 500, 
    width = 500, 
    margin = 25,
    axisWidth = width - 2 * margin;

var svg = d3.select("body").append("svg")
        .attr("class", "axis")
        .attr("width", width)
        .attr("height", height);

var scale = d3.scaleLinear().domain([0, 100]).range([0, axisWidth]);

var axis = d3.axisBottom()
        .scale(scale)
        .ticks(5)
        //.tickSubdivide(5) 
        .tickPadding(10) 
        .tickFormat(function(v){ 
            return v + "%";
        });

svg.append("g")        
    .attr("transform", function(){
        return "translate(" + margin + "," + margin + ")";
    })
    .call(axis);

#####绘制表格线

var height = 500, 
    width = 500, 
    margin = 25;       

var svg = d3.select("body").append("svg")
        .attr("class", "axis")
        .attr("width", width)
        .attr("height", height);

function renderXAxis(){
    var axisLength = width - 2 * margin;

    var scale = d3.scaleLinear()
                    .domain([0, 100])
                    .range([0, axisLength]);

    var xAxis = d3.axisBottom()
            .scale(scale);

    svg.append("g")       
        .attr("class", "x-axis")
        .attr("transform", function(){ 
            return "translate(" + margin + "," + (height - margin) + ")";
        })
        .call(xAxis);

    d3.selectAll("g.x-axis g.tick") 
        .append("line") 
            .classed("grid-line", true)
            .attr("x1", 0) 
            .attr("y1", 0)
            .attr("x2", 0)
            .attr("y2", - (height - 2 * margin)); 
}

function renderYAxis(){        
    var axisLength = height - 2 * margin;

    var scale = d3.scaleLinear()
                    .domain([100, 0])
                    .range([0, axisLength]);

    var yAxis = d3.axisLeft()
            .scale(scale);

    svg.append("g")       
        .attr("class", "y-axis")
        .attr("transform", function(){
            return "translate(" + margin + "," + margin + ")";
        })
        .call(yAxis);

    d3.selectAll("g.y-axis g.tick")
        .append("line")
            .classed("grid-line", true)
            .attr("x1", 0)
            .attr("y1", 0)
            .attr("x2", axisLength) 
            .attr("y2", 0);
}   

renderYAxis();
renderXAxis();
动态调节坐标轴尺度
<div class="control-group">
    <button onclick="rescale()">ReScale</button>
</div>
var height = 500, 
    width = 500, 
    margin = 25,
    xAxis, yAxis, xAxisLength, yAxisLength;

var svg = d3.select("body").insert("svg",".control-group")     
        .attr("class", "axis")    
        .attr("width", width)
        .attr("height", height);

function renderXAxis(){
    xAxisLength = width - 2 * margin;

    var scale = d3.scaleLinear()
                    .domain([0, 100])
                    .range([0, xAxisLength]);

    xAxis = d3.axisBottom()
            .scale(scale);

    svg.append("g")       
        .attr("class", "x-axis")
        .attr("transform", function(){ 
            return "translate(" + margin + "," + (height - margin) + ")";
        })
        .call(xAxis);
}

function renderYAxis(){        
    yAxisLength = height - 2 * margin;

    var scale = d3.scaleLinear()
                    .domain([100, 0])
                    .range([0, yAxisLength]);

    yAxis = d3.axisLeft()
            .scale(scale);

    svg.append("g")       
        .attr("class", "y-axis")
        .attr("transform", function(){
            return "translate(" + margin + "," + margin + ")";
        })
        .call(yAxis);
}   

function rescale(){ 
    var max = Math.round(Math.random() * 100);

    xAxis.scale().domain([0, max]); 
    svg.select("g.x-axis")
        .transition()
        .call(xAxis); 

    yAxis.scale().domain([max, 0]);
    svg.select("g.y-axis")
        .transition()
        .call(yAxis);

    renderXGridlines();
    renderYGridlines();
}       

function renderXGridlines(){
    var lines = d3.selectAll("g.x-axis g.tick")
            .select("line.grid-line")
            .remove(); 

    lines = d3.selectAll("g.x-axis g.tick")
            .append("line") 
            .classed("grid-line", true)

    lines.attr("x1", 0) 
            .attr("y1", 0)
            .attr("x2", 0)
            .attr("y2", - yAxisLength); 
}

function renderYGridlines(){
    var lines = d3.selectAll("g.y-axis g.tick")
            .select("line.grid-line").remove(); 

    lines = d3.selectAll("g.y-axis g.tick")
            .append("line") 
            .classed("grid-line", true)

    lines.attr("x1", 0)
        .attr("y1", 0)
        .attr("x2", xAxisLength)
        .attr("y2", 0);
}

renderYAxis();
renderXAxis();
renderXGridlines();
renderYGridlines();
window.rescale = rescale;
单元素动画
var body = d3.select("body"), 
    duration = 5000;

body.append("div") 
        .classed("box", true)
        .style("background-color", "#e9967a") 
    .transition()
    .duration(duration) 
        .style("background-color", "#add8e6") 
        .style("margin-left", "600px") 
        .style("width", "100px")
        .style("height", "100px");
多元素动画
var id= 0, 
    data = [], 
    duration = 500, 
    chartHeight = 100, 
    chartWidth = 680;

for(var i = 0; i < 20; i++) push(data);   

function render(data) {
    var selection = d3.select("body")
            .selectAll("div.v-bar")
            .data(data, function(d){return d.id;}); 

    // enter
    selection.enter()
            .append("div")
                .attr("class", "v-bar")
                .style("position", "fixed")
                .style("top", chartHeight + "px")
                .style("left", function(d, i){
                    return barLeft(i+1) + "px"; 
                })
                .style("height", "0px") 
            .append("span");

    // update
    selection
        .transition().duration(duration) 
            .style("top", function (d) { 
                return chartHeight - barHeight(d) + "px"; 
            })
            .style("left", function(d, i){
                return barLeft(i) + "px";
            })
            .style("height", function (d) { 
                return barHeight(d) + "px"; 
            })
            .select("span")
                .text(function (d) {return d.value;});

    // exit
    selection.exit()
            .transition().duration(duration) 
            .style("left", function(d, i){
                return barLeft(-1) + "px"; 
            })
            .remove(); 
}

function push(data) {
    data.push({
        id: ++id, 
        value: Math.round(Math.random() * chartHeight)
    });
}

function barLeft(i) {
    return i * (30 + 2);
}

function barHeight(d) {
    return d.value;
}

setInterval(function () {
    data.shift();
    push(data);
    render(data);
}, 2000);

render(data);

d3.select("body")
   .append("div")
       .attr("class", "baseline")
       .style("position", "fixed")
       .style("top", chartHeight + "px")
       .style("left", "0px")
       .style("width", chartWidth + "px");
使用缓动函数
var data = [ 
        "easeLinear", "easeCubic", "easeCubicInOut", 
        "easeSin", "easeSinOut", "easeExp", "easeCircle", "easeBack", 
        "easeBounce",
        function(t){ 
            return t * t;
        }
    ],
  colors = d3.scaleLinear().domain([0,10]).range(['pink','blue']);

d3.select("body").selectAll("div")
        .data(data) 
    .enter()
    .append("div")
        .attr("class", "fixed-cell")
        .style("top", function (d, i) {
            return i * 40 + "px";
        })
        .style("background-color", function (d, i) {
            return colors(i);
        })
        .style("color", "white")
        .style("left", "500px")
        .text(function (d) {
            if(typeof d === 'function') return "custom";
            return d;
        });

setTimeout(function(){
        d3.selectAll("div.fixed-cell").each(function(d){
            if(typeof d === 'function') {
                    d3.select(this)
                            .transition()
                            .ease(d)
                            .duration(1500)
                            .style("left", "10px");
            } else {
                    d3.select(this)
                            .transition()
                            .ease(d3[d])
                            .duration(1500)
                            .style("left", "10px");
            }
        });
},3000);
使用中间帧计算
var body = d3.select("body"), duration = 5000;

body.append("div").append("input")
    .attr("type", "button")
    .attr("class", "countdown")
    .attr("value", "0")
    .style("width", "150px")
    .transition().duration(duration).ease(d3.easeLinear)
        .style("width", "400px")
        .attr("value", "9");

body.append("div").append("input")
    .attr("type", "button")
    .attr("class", "countdown")
    .attr("value", "0")
    .transition().duration(duration).ease(d3.easeLinear)
        .styleTween("width", widthTween) 
        .attrTween("value", valueTween);


​ function widthTween(a){
​ var interpolate = d3.scaleQuantize()
​ .domain([0, 1])
​ .range([150, 200, 250, 350, 400]);

return function(t){
return interpolate(t) + “px”;
};
}

function valueTween(){
    var interpolate = d3.scaleQuantize() 
        .domain([0, 1])
        .range([1, 2, 3, 4, 5, 6, 7, 8, 9]);

    return function(t){ 
        return interpolate(t);
    };
}        
级联过渡
var body = d3.select("body");

function teleport(s){
    s.transition().duration(300) 
        .style("width", "200px")
        .style("height", "1px")
    .transition().duration(100) 
        .style("left", "600px")
    .transition().duration(300) 
        .style("left", "800px")
        .style("height", "80px")
        .style("width", "80px");
}

body.append("div")    
        .style("position", "fixed")
        .style("background-color", "steelblue")
        .style("left", "10px")
        .style("width", "80px")
        .style("height", "80px")
        .call(teleport); 
选择性过渡
var data = ["Cat", "Dog", "Cat", "Dog", "Cat", "Dog", "Cat", "Dog"],
    duration = 1500;

d3.select("body").selectAll("div")
        .data(data)
    .enter()
    .append("div")
        .attr("class", "fixed-cell")
        .style("top", function (d, i) {
            return i * 40 + "px";
        })
        .style("background-color", "steelblue")
        .style("color", "white")
        .style("left", "500px")
        .text(function (d) {
            return d;
        })
        .transition() 
            .duration(duration)
                .style("left", "10px")
        .filter(function(d){return d == "Cat";}) 
            .transition() 
            .duration(duration)
                .style("left", "500px");
监听过渡事件
var body = d3.select("body"), duration = 3000;

var div = body.append("div")
        .classed("box", true)
        .style("background-color", "steelblue")
        .style("color", "white")
        .text("waiting") 
    .transition().duration(duration) 
            .delay(1000) 
            .on("start", function(){ 
                console.log(arguments);
                d3.select(this).text(function (d, i) {
                    return "transitioning";
                });
            })
            .on("end", function(){ 
                d3.select(this).text(function (d, i) {
                    return "done";
                });
            })
        .style("margin-left", "600px");

#####自定义插值器

function customInterpolator(a, b) { 
  var re = /^([a-z])$/, ma, mb;
  if ((ma = re.exec(a)) && (mb = re.exec(b))) {
    a = a.charCodeAt(0);
    var delta = a - b.charCodeAt(0);
    return function(t) {
      return String.fromCharCode(Math.ceil(a - delta * t));
    };
  }
}
var customScale = d3.scaleLinear()
        .domain([0, 1])
        .range(["a", "z"])
        .interpolate(customInterpolator);

var body = d3.select("body");

var countdown = body.append("div").append("input");

countdown.attr("type", "button")
    .attr("class", "countdown")
    .attr("value", "a") 
    .transition().ease(d3.easeLinear) 
    .duration(4000).delay(300)
    .attrTween("value",function(d) {
        return function(t) {
            return customScale(t);
        }
    }); 
定时器
<div class="control-group">
    <button onclick="countup(100)">
        Start
    </button>
    <button onclick="reset()">
        Clear
    </button>
</div>
var body = d3.select("body");

var countdown = body.append("div").append("input");

countdown.attr("type", "button")
    .attr("class", "countdown")
    .attr("value", "0");

function countup(target){ 
    d3.timer(function(){ 
        var value = countdown.attr("value");
        if(value == target) return true;  
        countdown.attr("value", ++value);          
    });
}
window.countup = countup;

function reset(){
    countdown.attr("value", 0);
}
window.reset = reset;
svg简单图形
<style>
  svg line{
      stroke: grey;
      stroke-width: 2;
  }

  svg circle{
      stroke: red;
      fill: none;
      stroke-width: 2;
  }

  svg rect{
      stroke: steelblue;
      fill: none;
      stroke-width: 2;
  }

  svg polygon{
      stroke: green;
      fill: none;
      stroke-width: 2;
  }
  </style>
  var width = 600,
    height = 500;

var svg = d3.select("body").append("svg");

svg.attr("height", height)
    .attr("width", width);    

svg.append("line") 
    .attr("x1", 0)
    .attr("y1", 200)
    .attr("x2", 100)
    .attr("y2", 100);

svg.append("circle") 
    .attr("cx", 200)
    .attr("cy", 150)
    .attr("r", 50);

svg.append("rect")
    .attr("x", 300) 
    .attr("y", 100)
    .attr("width", 100) 
    .attr("height", 100)
    .attr("rx", 5); 

svg.append("polygon")
    .attr("points", "450,200 500,100 550,200"); 
线条生成器
var width = 500,
    height = 500,
    margin = 50,
    x = d3.scaleLinear() 
        .domain([0, 10])
        .range([margin, width - margin]),
    y = d3.scaleLinear()
        .domain([0, 10])
        .range([height - margin, margin]);

var data = [ 
    [
        {x: 0, y: 5},{x: 1, y: 9},{x: 2, y: 7},
        {x: 3, y: 5},{x: 4, y: 3},{x: 6, y: 4},
        {x: 7, y: 2},{x: 8, y: 3},{x: 9, y: 2}
    ],

    d3.range(10).map(function(i){
        return {x: i, y: Math.sin(i) + 5};
    })
];

var line = d3.line() 
        .x(function(d){return x(d.x);})
        .y(function(d){return y(d.y);});

var svg = d3.select("body").append("svg");

svg.attr("height", height)
    .attr("width", width);

 svg.selectAll("path")
        .data(data)
    .enter()
        .append("path") 
        .attr("class", "line")            
        .attr("d", function(d){return line(d);});

renderAxes(svg);

function renderAxes(svg){ 
    var xAxis = d3.axisBottom()
            .scale(x.range([0, quadrantWidth()]));            

    var yAxis = d3.axisLeft()
            .scale(y.range([quadrantHeight(), 0]));

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() 
                + "," + yStart() + ")";
        })
        .call(xAxis);

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() 
                + "," + yEnd() + ")";
        })
        .call(yAxis);
}

function xStart(){
    return margin;
}        

function yStart(){
    return height - margin;
}

function xEnd(){
    return width - margin;
}

function yEnd(){
    return margin;
}

function quadrantWidth(){
    return width - 2 * margin;
}

function quadrantHeight(){
    return height - 2 * margin;
}  
线条插值
<h4>Interpolation Mode:</h4>
<div class="control-group">
    <button onclick="render(d3.curveLinear)">d3.curveLinear</button>
    <button onclick="render(d3.curveLinearClosed)">d3.curveLinearClosed</button>
    <button onclick="render(d3.curveStepBefore)">d3.curveStepBefore</button>
    <button onclick="render(d3.curveStepAfter)">d3.curveStepAfter</button>
    <button onclick="render(d3.curveBasis)">d3.curveBasis</button>
    <button onclick="render(d3.curveBasisOpen)">d3.curveBasisOpen</button>
</div>
<div class="control-group">
    <button onclick="render(d3.curveBasisClosed)">d3.curveBasisClosed</button>
    <button onclick="render(d3.curveBundle)">d3.curveBundle</button>
    <button onclick="render(d3.curveCardinal)">d3.curveCardinal</button>
    <button onclick="render(d3.curveCardinalOpen)">d3.curveCardinalOpen</button>
    <button onclick="render(d3.curveCardinalClosed)">d3.curveCardinalClosed</button>
    <button onclick="render(d3.curveMonotoneX)">d3.curveMonotoneX</button>    
</div>

var width = 500,
    height = 500,
    margin = 30,
    x = d3.scaleLinear()
        .domain([0, 10])
        .range([margin, width - margin]),
    y = d3.scaleLinear()
        .domain([0, 10])
        .range([height - margin, margin]);

var data = [
    [
        {x: 0, y: 5},{x: 1, y: 9},{x: 2, y: 7},
        {x: 3, y: 5},{x: 4, y: 3},{x: 6, y: 4},
        {x: 7, y: 2},{x: 8, y: 3},{x: 9, y: 2}
    ],  
    d3.range(10).map(function(i){
        return {x: i, y: Math.sin(i) + 5};
    })
];

var svg = d3.select("body").append("svg");

svg.attr("height", height)
    .attr("width", width);        

renderAxes(svg);

render(d3.curveLinear);    

renderDots(svg);

function render(mode){
    var line = d3.line()
            .curve(mode)
            .x(function(d){return x(d.x);})
            .y(function(d){return y(d.y);});

    svg.selectAll("path.line")
            .data(data)
        .enter()
            .append("path")
            .attr("class", "line");                

    svg.selectAll("path.line")
            .data(data)       
        .attr("d", function(d){return line(d);});        
}
window.render = render;
window.d3 = d3;

function renderDots(svg){ 
    data.forEach(function(list){
         svg.append("g").selectAll("circle")
            .data(list)
          .enter().append("circle") 
            .attr("class", "dot")
            .attr("cx", function(d) { return x(d.x); })
            .attr("cy", function(d) { return y(d.y); })
            .attr("r", 4.5);
    });
}

function renderAxes(svg){            
    var xAxis = d3.axisBottom()
            .scale(d3.scaleLinear().range([0, quadrantWidth()]));            

    var yAxis = d3.axisLeft()
            .scale(d3.scaleLinear().range([quadrantHeight(), 0]));

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() + "," + yStart() + ")";
        })
        .call(xAxis);

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() + "," + yEnd() + ")";
        })
        .call(yAxis);
}

function xStart(){
    return margin;
}        

function yStart(){
    return height - margin;
}

function xEnd(){
    return width - margin;
}

function yEnd(){
    return margin;
}

function quadrantWidth(){
    return width - 2 * margin;
}

function quadrantHeight(){
    return height - 2 * margin;
}
更改线条张力

只有d3.curveCardinal, d3.curveCardinalOpen, d3.curveCardinalClosed可以设置张力

var width = 500,
    height = 500,
    margin = 30,
    duration = 500,    
    x = d3.scaleLinear()
        .domain([0, 10])
        .range([margin, width - margin]),
    y = d3.scaleLinear()
        .domain([0, 1])
        .range([height - margin, margin]);

var data = d3.range(10).map(function(i){
        return {x: i, y: (Math.sin(i * 3) + 1) / 2};
    });

var svg = d3.select("body").append("svg");

svg.attr("height", height)
    .attr("width", width);

renderAxes(svg);

render([1]);    

function render(tension){
    var line = d3.line()
            .curve(d3.curveCardinal.tension(tension[0]))
            .x(function(d){return x(d.x);})
            .y(function(d){return y(d.y);});

    svg.selectAll("path.line")
            .data(tension)
        .enter()
            .append("path")
            .attr("class", "line");            

    svg.selectAll("path.line")
            .data(tension)                
        .transition().duration(duration).ease(d3.easeLinear)
        .attr("d", function(d){
                // attention : line接受数组
            return line(data);
        });

    svg.selectAll("circle")
        .data(data)
      .enter().append("circle")
        .attr("class", "dot")
        .attr("cx", function(d) { return x(d.x); })
        .attr("cy", function(d) { return y(d.y); })
        .attr("r", 4.5);
}
window.render = render;

function renderAxes(svg){            
    var xAxis = d3.axisBottom()                
            .scale(d3.scaleLinear().domain([0, 10]).range([0, quadrantWidth()]));            

    var yAxis = d3.axisLeft()
            .scale(d3.scaleLinear().domain([0,1]).range([quadrantHeight(), 0]));

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() + "," + yStart() + ")";
        })
        .call(xAxis);

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() + "," + yEnd() + ")";
        })
        .call(yAxis);
}

function xStart(){
    return margin;
}        

function yStart(){
    return height - margin;
}

function xEnd(){
    return width - margin;
}

function yEnd(){
    return margin;
}

function quadrantWidth(){
    return width - 2 * margin;
}

function quadrantHeight(){
    return height - 2 * margin;
}
区域生成器
    var width = 500,
    height = 500,
    margin = 30,
    duration = 500,
    x = d3.scaleLinear()
        .domain([0, 10])
        .range([margin, width - margin]),
    y = d3.scaleLinear()
        .domain([0, 10])
        .range([height - margin, margin]);

var data = d3.range(11).map(function(i){
        return {x: i, y: Math.sin(i)*3 + 5};
    });

var svg = d3.select("body").append("svg");

svg.attr("height", height)
    .attr("width", width);        

renderAxes(svg);

render();    

renderDots(svg);

function render(){
    var line = d3.line()
            .x(function(d){return x(d.x);})
            .y(function(d){return y(d.y);});

    svg.selectAll("path.line")
            .data([data])
        .enter()
            .append("path")
            .attr("class", "line");                

    svg.selectAll("path.line")
            .data([data])       
        .attr("d", function(d){return line(d);});        

    var area = d3.area() 
        .x(function(d) { return x(d.x); }) 
        .y0(y(0)) 
        .y1(function(d) { return y(d.y); }); 

    svg.selectAll("path.area") 
            .data([data])
        .enter()
            .append("path")
            .attr("class", "area")
            .attr("d", function(d){return area(d);}); 
}

function renderDots(svg){        
     svg.append("g").selectAll("circle")
        .data(data)
      .enter().append("circle")
        .attr("class", "dot")
        .attr("cx", function(d) { return x(d.x); })
        .attr("cy", function(d) { return y(d.y); })
        .attr("r", 4.5);
}

function renderAxes(svg){            
    var xAxis = d3.axisBottom()
            .scale(d3.scaleLinear().range([0, quadrantWidth()]));            

    var yAxis = d3.axisLeft()
            .scale(d3.scaleLinear().range([quadrantHeight(), 0]));

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() + "," + yStart() + ")";
        })
        .call(xAxis);

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() + "," + yEnd() + ")";
        })
        .call(yAxis);
}

function xStart(){
    return margin;
}        

function yStart(){
    return height - margin;
}

function xEnd(){
    return width - margin;
}

function yEnd(){
    return margin;
}

function quadrantWidth(){
    return width - 2 * margin;
}

function quadrantHeight(){
    return height - 2 * margin;
}
区域插值
<h4>Interpolation Mode:</h4>
<div class="control-group">
    <button onclick="render(d3.curveLinear)">d3.curveLinear</button>
    <button onclick="render(d3.curveLinearClosed)">d3.curveLinearClosed</button>
    <button onclick="render(d3.curveStepBefore)">d3.curveStepBefore</button>
    <button onclick="render(d3.curveStepAfter)">d3.curveStepAfter</button>
    <button onclick="render(d3.curveBasis)">d3.curveBasis</button>
    <button onclick="render(d3.curveBasisOpen)">d3.curveBasisOpen</button>
</div>
<div class="control-group">
    <button onclick="render(d3.curveBasisClosed)">d3.curveBasisClosed</button>
    <button onclick="render(d3.curveCardinal)">d3.curveCardinal</button>
    <button onclick="render(d3.curveCardinalOpen)">d3.curveCardinalOpen</button>
    <button onclick="render(d3.curveCardinalClosed)">d3.curveCardinalClosed</button>
    <button onclick="render(d3.curveMonotoneX)">d3.curveMonotoneX</button>    
</div>
var width = 500,
    height = 500,
    margin = 30,
    x = d3.scaleLinear()
        .domain([0, 10])
        .range([margin, width - margin]),
    y = d3.scaleLinear()
        .domain([0, 10])
        .range([height - margin, margin]);

var data = d3.range(11).map(function(i){
    return {x: i, y: Math.sin(i)*3 + 5};
});

var svg = d3.select("body").append("svg");

svg.attr("height", height)
    .attr("width", width);        

renderAxes(svg);

render(d3.curveLinear);    

renderDots(svg);

function render(mode){
    var line = d3.line()
            .curve(mode)
            .x(function(d){return x(d.x);})
            .y(function(d){return y(d.y);});

    svg.selectAll("path.line")
            .data([data])
        .enter()
            .append("path")
            .attr("class", "line");                

    svg.selectAll("path.line")
            .data([data])       
        .attr("d", function(d){return line(d);});        

    var area = d3.area()
        .curve(mode) 
        .x(function(d) { return x(d.x); })
        .y0(y(0))
        .y1(function(d) { return y(d.y); });

    svg.selectAll("path.area")
            .data([data])
        .enter()
            .append("path")
            .attr("class", "area")

    svg.selectAll("path.area")
        .data([data])
        .attr("d", function(d){return area(d);});        
}
window.render = render;
window.d3 = d3;

function renderDots(svg){        
     svg.append("g").selectAll("circle")
        .data(data)
      .enter().append("circle")
        .attr("class", "dot")
        .attr("cx", function(d) { return x(d.x); })
        .attr("cy", function(d) { return y(d.y); })
        .attr("r", 4.5);
}

function renderAxes(svg){            
    var xAxis = d3.axisBottom()
            .scale(d3.scaleLinear().range([0, quadrantWidth()]));            

    var yAxis = d3.axisLeft()
            .scale(d3.scaleLinear().range([quadrantHeight(), 0]));

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() + "," + yStart() + ")";
        })
        .call(xAxis);

    svg.append("g")        
        .attr("class", "axis")
        .attr("transform", function(){
            return "translate(" + xStart() + "," + yEnd() + ")";
        })
        .call(yAxis);
}

function xStart(){
    return margin;
}        

function yStart(){
    return height - margin;
}

function xEnd(){
    return width - margin;
}

function yEnd(){
    return margin;
}

function quadrantWidth(){
    return width - 2 * margin;
}

function quadrantHeight(){
    return height - 2 * margin;
}

#####圆弧生成器

<div class="control-group">
    <button onclick="render(0)">Circle</button>
    <button onclick="render(100)">Annulus(Donut)</button>
    <button onclick="render(0, Math.PI)">Circular Sector</button>
    <button onclick="render(100, Math.PI)">Annulus Sector</button>
</div>
var width = 400,
    height = 400,
    fullAngle = 2 * Math.PI, 
    colors = d3.scaleLinear().domain([0,9]).range(["pink","blue"]);

    var svg = d3.select("body").append("svg")
                .attr("class", "pie")
                .attr("height", height)
                .attr("width", width);    

    function render(innerRadius, endAngle){
        if(!endAngle) endAngle = fullAngle;

        var data = [ 
            {startAngle: 0, endAngle: 0.1 * endAngle},
            {startAngle: 0.1 * endAngle, endAngle: 0.2 * endAngle},
            {startAngle: 0.2 * endAngle, endAngle: 0.4 * endAngle},
            {startAngle: 0.4 * endAngle, endAngle: 0.6 * endAngle},        
            {startAngle: 0.6 * endAngle, endAngle: 0.7 * endAngle},        
            {startAngle: 0.7 * endAngle, endAngle: 0.9 * endAngle},        
            {startAngle: 0.9 * endAngle, endAngle: endAngle}
        ];

        var arc = d3.arc().outerRadius(200) 
                        .innerRadius(innerRadius);

        svg.select("g").remove();

        svg.append("g")
                .attr("transform", "translate(200,200)")
        .selectAll("path.arc")
                .data(data)
            .enter()
                .append("path")
                    .attr("class", "arc")
                    .attr("fill", function(d, i){return colors(i);})
                    .attr("d", function(d, i){
                        return arc(d, i); 
                    });
    }
    window.render = render;

    render(0);

#####圆弧过渡器

        var width = 400,
        height = 400,
        endAngle = 2 * Math.PI,
        colors = d3.scaleLinear().domain([0,9]).range(["pink","blue"]);

var svg = d3.select("body").append("svg")
        .attr("class", "pie")
        .attr("height", height)
        .attr("width", width);

function render(innerRadius) {

    var data = [
        {startAngle: 0, endAngle: 0.1 * endAngle},
        {startAngle: 0.1 * endAngle, endAngle: 0.2 * endAngle},
        {startAngle: 0.2 * endAngle, endAngle: 0.4 * endAngle},
        {startAngle: 0.4 * endAngle, endAngle: 0.6 * endAngle},
        {startAngle: 0.6 * endAngle, endAngle: 0.7 * endAngle},
        {startAngle: 0.7 * endAngle, endAngle: 0.9 * endAngle},
        {startAngle: 0.9 * endAngle, endAngle: endAngle}
    ];

    var arc = d3.arc().outerRadius(200).innerRadius(innerRadius);

    svg.select("g").remove();

    svg.append("g")
        .attr("transform", "translate(200,200)")
        .selectAll("path.arc")
            .data(data)
        .enter()
            .append("path")
            .attr("class", "arc")
            .attr("fill", function (d, i) {
                return colors(i);
            })
            .transition().duration(2000)
            .attrTween("d", function (d) { 
                var start = {startAngle: 0, endAngle: 0}; 
                var interpolate = d3.interpolate(start, d); 
                return function (t) {
                    return arc(interpolate(t)); 
                };
            });
}

render(100);

#####线图

function lineChart() { 
        var _chart = {};

        var _width = 600, _height = 300, 
                _margins = {top: 30, left: 30, right: 30, bottom: 30},
                _x, _y,
                _data = [],
                _colors = d3.scaleLinear().domain([0,9]).range(["pink","blue"]),
                _svg,
                _bodyG,
                _line;

        _chart.render = function () { 
            if (!_svg) {
                _svg = d3.select("body").append("svg") 
                        .attr("height", _height)
                        .attr("width", _width);

                renderAxes(_svg);

                defineBodyClip(_svg);
            }

            renderBody(_svg);
        };

        function renderAxes(svg) {
            var axesG = svg.append("g")
                    .attr("class", "axes");

            renderXAxis(axesG);

            renderYAxis(axesG);
        }

        function renderXAxis(axesG){
            var xAxis = d3.axisBottom()
                    .scale(_x.range([0, quadrantWidth()]));        

            axesG.append("g")
                    .attr("class", "x axis")
                    .attr("transform", function () {
                        return "translate(" + xStart() + "," + yStart() + ")";
                    })
                    .call(xAxis);

            d3.selectAll("g.x g.tick")
                .append("line")
                    .classed("grid-line", true)
                    .attr("x1", 0)
                    .attr("y1", 0)
                    .attr("x2", 0)
                    .attr("y2", - quadrantHeight());
        }

        function renderYAxis(axesG){
            var yAxis = d3.axisLeft()
                    .scale(_y.range([quadrantHeight(), 0]));

            axesG.append("g")
                    .attr("class", "y axis")
                    .attr("transform", function () {
                        return "translate(" + xStart() + "," + yEnd() + ")";
                    })
                    .call(yAxis);

             d3.selectAll("g.y g.tick")
                .append("line")
                    .classed("grid-line", true)
                    .attr("x1", 0)
                    .attr("y1", 0)
                    .attr("x2", quadrantWidth())
                    .attr("y2", 0);
        }

        function defineBodyClip(svg) { 
            var padding = 5;

            svg.append("defs")
                    .append("clipPath")
                    .attr("id", "body-clip")
                    .append("rect")
                    .attr("x", 0 - padding)
                    .attr("y", 0)
                    .attr("width", quadrantWidth() + 2 * padding)
                    .attr("height", quadrantHeight());
        }

        function renderBody(svg) { 
            if (!_bodyG)
                _bodyG = svg.append("g")
                        .attr("class", "body")
                        .attr("transform", "translate(" 
                            + xStart() + "," 
                            + yEnd() + ")") 
                        .attr("clip-path", "url(#body-clip)");        

            renderLines();

            renderDots();
        }

        function renderLines() {
            _line = d3.line() 
                  .x(function (d) { return _x(d.x); })
                  .y(function (d) { return _y(d.y); });

            _bodyG.selectAll("path.line")
                        .data(_data)
                    .enter() 
                    .append("path")                
                    .style("stroke", function (d, i) { 
                        return _colors(i); 
                    })
                    .attr("class", "line");

            _bodyG.selectAll("path.line")
                    .data(_data)
                    .transition() 
                    .attr("d", function (d) { return _line(d); });
        }

        function renderDots() {
            _data.forEach(function (list, i) {
                _bodyG.selectAll("circle._" + i) 
                            .data(list)
                        .enter()
                        .append("circle")
                        .attr("class", "dot _" + i);

                _bodyG.selectAll("circle._" + i)
                        .data(list)                    
                        .style("stroke", function (d) { 
                            return _colors(i); 
                        })
                        .transition() 
                        .attr("cx", function (d) { return _x(d.x); })
                        .attr("cy", function (d) { return _y(d.y); })
                        .attr("r", 4.5);
            });
        }

        function xStart() {
            return _margins.left;
        }

        function yStart() {
            return _height - _margins.bottom;
        }

        function xEnd() {
            return _width - _margins.right;
        }

        function yEnd() {
            return _margins.top;
        }

        function quadrantWidth() {
            return _width - _margins.left - _margins.right;
        }

        function quadrantHeight() {
            return _height - _margins.top - _margins.bottom;
        }

        _chart.width = function (w) {
            if (!arguments.length) return _width;
            _width = w;
            return _chart;
        };

        _chart.height = function (h) { 
            if (!arguments.length) return _height;
            _height = h;
            return _chart;
        };

        _chart.margins = function (m) {
            if (!arguments.length) return _margins;
            _margins = m;
            return _chart;
        };

        _chart.colors = function (c) {
            if (!arguments.length) return _colors;
            _colors = c;
            return _chart;
        };

        _chart.x = function (x) {
            if (!arguments.length) return _x;
            _x = x;
            return _chart;
        };

        _chart.y = function (y) {
            if (!arguments.length) return _y;
            _y = y;
            return _chart;
        };

        _chart.addSeries = function (series) { 
            _data.push(series);
            return _chart;
        };

        return _chart; 
    }

    function randomData() {
        return Math.random() * 9;
    }

    function update() {
        for (var i = 0; i < data.length; ++i) {
            var series = data[i];
            series.length = 0;
            for (var j = 0; j < numberOfDataPoint; ++j)
                series.push({x: j, y: randomData()});
        }

        chart.render();
    }
    window.update = update;

    var numberOfSeries = 2,
        numberOfDataPoint = 11,
        data = [];

    for (var i = 0; i < numberOfSeries; ++i)
        data.push(d3.range(numberOfDataPoint).map(function (i) {
            return {x: i, y: randomData()};
        }));

    var chart = lineChart()
            .x(d3.scaleLinear().domain([0, 10]))
            .y(d3.scaleLinear().domain([0, 10]));

    data.forEach(function (series) {
        chart.addSeries(series);
    });

    chart.render();

#####面积图

function renderAreas() {
            var area = d3.area()
                                     .x(function(d){ return _x(d.x)})
                                     .y0(yStart())
                                     .y1(function(d){ return _y(d.y)});
            _bodyG.selectAll("path.area")
                        .data(_data)
                        .enter()
                            .append("path")
                            .style("fill",function(d,i){
                                return _colors(i);
                            })
                            .attr("class","area");

            _bodyG.selectAll("path.area")
                        .data(_data)
                        .transition()
                        .attr("d",function(d){
                            return area(d);
                        });
        }

#####散点图

function renderSymbols() { 
    _data.forEach(function (list, i) {
        _bodyG.selectAll("path._" + i)
                    .data(list)
                .enter()
                .append("path")
                .attr("class", "symbol _" + i);

        _bodyG.selectAll("path._" + i)
                .data(list)
                    .classed(_symbolTypes(i), true)
                .transition() 
                    .attr("transform", function(d){
                        return "translate(" 
                                + _x(d.x) 
                                + "," 
                                + _y(d.y) 
                                + ")";
                    })
                    .attr("d", 
                        d3.symbol() 
                            .type(d3[_symbolTypes(i)])
                    ); 
    });
}

#####气泡图

function renderBubbles() {
    _r.range([0,50]);

    _data.forEach(function(list,i){
        _bodyG.selectAll("circle._"+i)
            .data(list)
            .enter()
                .append("circle")
                .attr("class","bubble _"+i);

        _bodyG.selectAll("circle._"+i)
            .data(list)
            .style("stroke",function(d,j) {
                return _colors(j);
            })
            .style("fill",function(d,j) {
                return _colors(j);
            })
            .transition()
            .attr("cx",function(d) {
                return _x(d.x);
            })
            .attr("cy",function(d) {
                return _y(d.y);
            })
            .attr("r",function(d) {
                return _r(d.r);
            })
    });
}

#####条形图

function lineChart() { 
        var _chart = {};

        var _width = 600, _height = 300, 
                _margins = {top: 30, left: 30, right: 30, bottom: 30},
                _x, _y, _r,
                _data = [],
                _colors = d3.scaleLinear().domain([0,9]).range(["pink","blue"]),
                _svg,
                _bodyG,
                _line;

        var _symbolTypes = d3.scaleOrdinal()
                                                    .range(["symbolCircle","symbolCross","symbolDiamond","symbolSquare","symbolStar","symbolTriangle","symbolWye"]);

        _chart.render = function () { 
            if (!_svg) {
                _svg = d3.select("body").append("svg") 
                        .attr("height", _height)
                        .attr("width", _width);

                renderAxes(_svg);

                defineBodyClip(_svg);
            }

            renderBody(_svg);
        };

        function renderAxes(svg) {
            var axesG = svg.append("g")
                    .attr("class", "axes");

            renderXAxis(axesG);

            renderYAxis(axesG);
        }

        function renderXAxis(axesG){
            var xAxis = d3.axisBottom()
                    .scale(_x.range([0, quadrantWidth()]));        

            axesG.append("g")
                    .attr("class", "x axis")
                    .attr("transform", function () {
                        return "translate(" + xStart() + "," + yStart() + ")";
                    })
                    .call(xAxis);

            d3.selectAll("g.x g.tick")
                .append("line")
                    .classed("grid-line", true)
                    .attr("x1", 0)
                    .attr("y1", 0)
                    .attr("x2", 0)
                    .attr("y2", - quadrantHeight());
        }

        function renderYAxis(axesG){
            var yAxis = d3.axisLeft()
                    .scale(_y.range([quadrantHeight(), 0]));

            axesG.append("g")
                    .attr("class", "y axis")
                    .attr("transform", function () {
                        return "translate(" + xStart() + "," + yEnd() + ")";
                    })
                    .call(yAxis);

             d3.selectAll("g.y g.tick")
                .append("line")
                    .classed("grid-line", true)
                    .attr("x1", 0)
                    .attr("y1", 0)
                    .attr("x2", quadrantWidth())
                    .attr("y2", 0);
        }

        function defineBodyClip(svg) { 
            var padding = 5;

            svg.append("defs")
                    .append("clipPath")
                    .attr("id", "body-clip")
                    .append("rect")
                    .attr("x", 0 - padding)
                    .attr("y", 0)
                    .attr("width", quadrantWidth() + 2 * padding)
                    .attr("height", quadrantHeight());
        }

        function renderBody(svg) { 
            if (!_bodyG)
                _bodyG = svg.append("g")
                        .attr("class", "body")
                        .attr("transform", "translate(" 
                            + xStart() + "," 
                            + yEnd() + ")") 
                        .attr("clip-path", "url(#body-clip)");        

            // renderLines();

            // renderAreas();

            // renderDots();

            // renderSymbols();

            // renderBubbles();

            renderBars();
        }

        function renderLines() {
            _line = d3.line() 
                  .x(function (d) { return _x(d.x); })
                  .y(function (d) { return _y(d.y); });

            _bodyG.selectAll("path.line")
                        .data(_data)
                    .enter() 
                    .append("path")                
                    .style("stroke", function (d, i) { 
                        return _colors(i); 
                    })
                    .attr("class", "line");

            _bodyG.selectAll("path.line")
                    .data(_data)
                    .transition() 
                    .attr("d", function (d) { return _line(d); });
        }

        function renderDots() {
            _data.forEach(function (list, i) {
                _bodyG.selectAll("circle._" + i) 
                            .data(list)
                        .enter()
                        .append("circle")
                        .attr("class", "dot _" + i);

                _bodyG.selectAll("circle._" + i)
                        .data(list)                    
                        .style("stroke", function (d) { 
                            return _colors(i); 
                        })
                        .transition() 
                        .attr("cx", function (d) { return _x(d.x); })
                        .attr("cy", function (d) { return _y(d.y); })
                        .attr("r", 4.5);
            });
        }

        function renderAreas() {
            var area = d3.area()
                                     .x(function(d){ return _x(d.x)})
                                     .y0(yStart())
                                     .y1(function(d){ return _y(d.y)});
            _bodyG.selectAll("path.area")
                        .data(_data)
                        .enter()
                            .append("path")
                            .style("fill",function(d,i){
                                return _colors(i);
                            })
                            .attr("class","area");

            _bodyG.selectAll("path.area")
                        .data(_data)
                        .transition()
                        .attr("d",function(d){
                            return area(d);
                        });
        }

        function renderSymbols() { 
            _data.forEach(function (list, i) {
                _bodyG.selectAll("path._" + i)
                            .data(list)
                        .enter()
                        .append("path")
                        .attr("class", "symbol _" + i);

                _bodyG.selectAll("path._" + i)
                        .data(list)
                            .classed(_symbolTypes(i), true)
                        .transition() 
                            .attr("transform", function(d){
                                return "translate(" 
                                        + _x(d.x) 
                                        + "," 
                                        + _y(d.y) 
                                        + ")";
                            })
                            .attr("d", 
                                d3.symbol() 
                                    .type(d3[_symbolTypes(i)])
                            ); 
            });
        }

        function renderBubbles() {
            _r.range([0,50]);

            _data.forEach(function(list,i){
                _bodyG.selectAll("circle._"+i)
                    .data(list)
                    .enter()
                        .append("circle")
                        .attr("class","bubble _"+i);

                _bodyG.selectAll("circle._"+i)
                    .data(list)
                    .style("stroke",function(d,j) {
                        return _colors(j);
                    })
                    .style("fill",function(d,j) {
                        return _colors(j);
                    })
                    .transition()
                    .attr("cx",function(d) {
                        return _x(d.x);
                    })
                    .attr("cy",function(d) {
                        return _y(d.y);
                    })
                    .attr("r",function(d) {
                        return _r(d.r);
                    })
            });
        }

        function renderBars() {
            var padding = 2;

            _bodyG.selectAll("rect.bar")
                .data(_data)
                .enter()
                    .append("rect")
                    .attr("class","bar");

            _bodyG.selectAll("rect.bar")
                .data(_data)
                .transition()
                .attr("x",function(d){
                    return _x(d.x);
                })
                .attr("y",function(d){
                    return _y(d.y);
                })
                .attr("height",function(d){
                    return yStart() - _y(d.y);
                })
                .attr("width",function(d){
                    return Math.floor(quadrantWidth() / _data.length) - padding;
                });
        }

        function xStart() {
            return _margins.left;
        }

        function yStart() {
            return _height - _margins.bottom;
        }

        function xEnd() {
            return _width - _margins.right;
        }

        function yEnd() {
            return _margins.top;
        }

        function quadrantWidth() {
            return _width - _margins.left - _margins.right;
        }

        function quadrantHeight() {
            return _height - _margins.top - _margins.bottom;
        }

        _chart.width = function (w) {
            if (!arguments.length) return _width;
            _width = w;
            return _chart;
        };

        _chart.height = function (h) { 
            if (!arguments.length) return _height;
            _height = h;
            return _chart;
        };

        _chart.margins = function (m) {
            if (!arguments.length) return _margins;
            _margins = m;
            return _chart;
        };

        _chart.colors = function (c) {
            if (!arguments.length) return _colors;
            _colors = c;
            return _chart;
        };

        _chart.x = function (x) {
            if (!arguments.length) return _x;
            _x = x;
            return _chart;
        };

        _chart.y = function (y) {
            if (!arguments.length) return _y;
            _y = y;
            return _chart;
        };

        _chart.r = function (r) {
            if (!arguments.length) return _r;
            _r = r;
            return _chart;
        };

        _chart.addSeries = function (series) { 
            _data.push(series);
            return _chart;
        };

        _chart.setSeries = function (series) {
            _data = series;
            return _chart;
        };

        return _chart; 
    }

    function randomData() {
        return Math.random() * 9;
    }

    // function update() {
    //     for (var i = 0; i < data.length; ++i) {
    //         var series = data[i];
    //         series.length = 0;
    //         for (var j = 0; j < numberOfDataPoint; ++j)
    //             series.push({x: j, y: randomData() , r:randomData()});
    //     }

    //     chart.render();
    // }
    function update() {
            data.length = 0;
    for (var j = 0; j < numberOfDataPoint; ++j)
        data.push({x: j, y: randomData()});
        chart.render();
    }
    window.update = update;

    var numberOfSeries = 2,
        numberOfDataPoint = 31,
        data = [];

    // for (var i = 0; i < numberOfSeries; ++i)
    //     data.push(d3.range(numberOfDataPoint).map(function (i) {
    //         return {x: i, y: randomData() , r:randomData()};
    //     }));
    data = d3.range(numberOfDataPoint).map(function(i) {
        return {x: i , y :randomData()};
    });

    var chart = lineChart()
            .x(d3.scaleLinear().domain([0, 33]))
            .y(d3.scaleLinear().domain([0, 10]))
            .r(d3.scalePow().exponent(2).domain([0, 10]));

    // data.forEach(function (series) {
    //     chart.addSeries(series);
    // });
    chart.setSeries(data);

    chart.render();

#####饼图

function pieChart() {
    var _chart = {};

    var _width = 500, _height = 500,
            _data = [],
            _colors = d3.scaleLinear().domain([0,9]).range(["pink","blue"]),
            _svg,
            _bodyG,
            _pieG,
            _radius = 200,
            _innerRadius = 100;

    _chart.render = function () {
        if (!_svg) {
            _svg = d3.select("body").append("svg")
                    .attr("height", _height)
                    .attr("width", _width);
        }

        renderBody(_svg);
    };

    function renderBody(svg) {
        if (!_bodyG)
            _bodyG = svg.append("g")
                    .attr("class", "body");

        renderPie();
    }

    function renderPie() {
        var pie = d3.pie() 
                .sort(function (d) {
                    return d.id;
                })
                .value(function (d) {
                    return d.value;
                });

        var arc = d3.arc()
                .outerRadius(_radius)
                .innerRadius(_innerRadius);

        if (!_pieG)
            _pieG = _bodyG.append("g")
                    .attr("class", "pie")
                    .attr("transform", "translate(" 
                        + _radius 
                        + "," 
                        + _radius + ")");

        renderSlices(pie, arc);

        renderLabels(pie, arc);
    }

    function renderSlices(pie, arc) {
        var slices = _pieG.selectAll("path.arc")
                .data(pie(_data)); 

        slices.enter()
                .append("path")
                .attr("class", "arc")
                .attr("fill", function (d, i) {
                    return _colors(i);
                });

        slices.transition()
                .attrTween("d", function (d) {
                    var currentArc = this.__current__; 

                    if (!currentArc)
                        currentArc = {startAngle: 0, 
                                        endAngle: 0};

                    var interpolate = d3.interpolate(
                                        currentArc, d);

                    this.__current__ = interpolate(1);

                    return function (t) {
                        return arc(interpolate(t));
                    };
                });
    }

    function renderLabels(pie, arc) {
        var labels = _pieG.selectAll("text.label")
                .data(pie(_data)); 

        labels.enter()
                .append("text")
                .attr("class", "label");

        labels.transition()
                .attr("transform", function (d) {
                    return "translate(" 
                        + arc.centroid(d) + ")"; 
                })
                .attr("dy", ".35em")
                .attr("text-anchor", "middle")
                .text(function (d) {
                    return d.data.id;
                });
    }

    _chart.width = function (w) {
        if (!arguments.length) return _width;
        _width = w;
        return _chart;
    };

    _chart.height = function (h) {
        if (!arguments.length) return _height;
        _height = h;
        return _chart;
    };

    _chart.colors = function (c) {
        if (!arguments.length) return _colors;
        _colors = c;
        return _chart;
    };

    _chart.radius = function (r) {
        if (!arguments.length) return _radius;
        _radius = r;
        return _chart;
    };

    _chart.innerRadius = function (r) {
        if (!arguments.length) return _innerRadius;
        _innerRadius = r;
        return _chart;
    };

    _chart.data = function (d) {
        if (!arguments.length) return _data;
        _data = d;
        return _chart;
    };

    return _chart;
}

function randomData() {
    return Math.random() * 9 + 1;
}

function update() {
    for (var j = 0; j < data.length; ++j)
        data[j].value = randomData();

    chart.render();
}
window.update = update;

var numberOfDataPoint = 6,
        data = [];

data = d3.range(numberOfDataPoint).map(function (i) {
    return {id: i, value: randomData()};
});

var chart = pieChart()
        .radius(200)
        .innerRadius(100)
        .data(data);

chart.render();

#####堆叠面积图

#####矩形式树状结构图

#####鼠标事件

var r = 400;

var svg = d3.select("body")
        .append("svg");

var positionLabel = svg.append("text")
        .attr("x", 10)
        .attr("y", 30);

svg.on("mousemove", function () {
    printPosition();
});

function printPosition() { 
    var position = d3.mouse(svg.node()); 
    positionLabel.text(position);
}  

svg.on("click", function () { 
    for (var i = 1; i < 5; ++i) {
        var position = d3.mouse(svg.node());

        var circle = svg.append("circle")
                .attr("cx", position[0])
                .attr("cy", position[1])
                .attr("r", 0)
                .style("stroke-width", 5 / (i))
                .transition()
                    .delay(Math.pow(i, 2.5) * 50)
                    .duration(2000)
                    .ease(d3.easeQuadIn)
                .attr("r", r)
                .style("stroke-opacity", 0)
                .on("end", function () {
                    d3.select(this).remove();
                });
    }
});

#####多点触摸设备交互

var initR = 100, 
    r = 400, 
    thickness = 20;

var svg = d3.select("body")
        .append("svg");

d3.select("body")
        .on("touchstart", touch)
        .on("touchend", touch);

function touch() {
    d3.event.preventDefault();

    var arc = d3.arc()
            .outerRadius(initR)
            .innerRadius(initR - thickness);

    var g = svg.selectAll("g.touch")
            .data(d3.touches(svg.node()));

    g.enter()
        .append("g")
        .attr("class", "touch")
        .attr("transform", function (d) {
            return "translate(" + d[0] + "," + d[1] + ")";
        })
        .append("path")
            .attr("class", "arc")
            .transition().duration(2000).ease(d3.easeLinear)
            .attrTween("d", function (d) {
                var interpolate = d3.interpolate(
                        {startAngle: 0, endAngle: 0},
                        {startAngle: 0, endAngle: 2 * Math.PI}
                    );
                return function (t) {
                    return arc(interpolate(t));
                };
            })
            .on("end", function (d) {
                if (complete(d))
                    ripples(d);
                g.remove();
            });

    g.exit().remove().each(function (d) {
        d.__stopped__ = true;
    });
}

function complete(d) {
    return d.__stopped__ != true;
}

function ripples(position) {
    for (var i = 1; i < 5; ++i) {
        var circle = svg.append("circle")
                .attr("cx", position[0])
                .attr("cy", position[1])
                .attr("r", initR - (thickness / 2))
                .style("stroke-width", thickness / (i))
            .transition()
                .delay(Math.pow(i, 2.5) * 50)
                .duration(2000).ease(d3.easeQuadIn)
                .attr("r", r)
                .style("stroke-opacity", 0)
                .on("end", function () {
                    d3.select(this).remove();
                });
    }
}

#####实现缩放和平移

var width = 600, height = 350, r = 50;

var data = [
    [width / 2 - r, height / 2 - r],
    [width / 2 - r, height / 2 + r],
    [width / 2 + r, height / 2 - r],
    [width / 2 + r, height / 2 + r]
];

var svg = d3.select("body").append("svg")
        .attr("style", "1px solid black")
        .attr("width", width)
        .attr("height", height)
        .call( 
            d3.zoom() 
                .scaleExtent([1, 10]) 
                .on("zoom", zoom) 
        )
        .append("g");

svg.selectAll("circle")
        .data(data)
        .enter().append("circle")
        .attr("r", r)
        .attr("transform", function (d) {
            return "translate(" + d + ")";
        });

function zoom() {
    svg.attr("transform", "translate(" 
        + d3.event.transform.x+","+d3.event.transform.y 
        + ")scale(" + d3.event.transform.k + ")");
}

#####拖曳

var width = 960, height = 500, r = 50;

var data = [
    [width / 2 - r, height / 2 - r],
    [width / 2 - r, height / 2 + r],
    [width / 2 + r, height / 2 - r],
    [width / 2 + r, height / 2 + r]
];

var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height)
        .append("g");

var drag = d3.drag() 
        .on("drag", move);

svg.selectAll("circle")
        .data(data)
        .enter().append("circle")
        .attr("r", r)
        .attr("transform", function (d) {
            return "translate(" + d + ")";
        })
        .call(drag); 

function move(d) {
        console.log(d3.event)
    var x = d3.event.x, 
        y = d3.event.y;

    if(inBoundaries(x, y))
        d3.select(this) 
            .attr("transform", function (d) { 
                return "translate(" + x + ", " + y + ")";
            });
}

function inBoundaries(x, y){
    return (x >= (0 + r) && x <= (width - r)) 
        && (y >= (0 + r) && y <= (height - r));
}

#####使用引力和相互作用力