Skip to content

Latest commit

 

History

History
1233 lines (975 loc) · 27.6 KB

d3.js-tutorial.md

File metadata and controls

1233 lines (975 loc) · 27.6 KB

1. Introduction

d3js.org

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

    </script>
</body>
</html>

Run Simple HTTP Server with Python3

$ python -m http.server 8000

2. Select and Append

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        d3.select("body").append("p").text("Hello World!");
    </script>
</body>
</html>

3. Basic SVG shapes

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var canvas = d3.select("body")
                    .append("svg")
                    .attr("width", 1000)
                    .attr("height", 1000);
        var circle = canvas.append("circle")
                    .attr("cx", 250)
                    .attr("cy", 250)
                    .attr("r", 50)
                    .attr("fill", "red");

        var rect = canvas.append("rect")
                    .attr("width", 100)
                    .attr("height", 50);

        var line = canvas.append("line")
                    .attr("x1", 0)
                    .attr("y1", 100)
                    .attr("x2", 400)
                    .attr("y2", 500)
                    .attr("stroke", "green")
                    .attr("stroke-width", 10);
    </script>
</body>
</html>

4. Visualizing data

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

        var dataArray = [20, 40, 70];

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

        var bars = canvas.selectAll("rect")
                    .data(dataArray)
                    .enter()
                        .append("rect")
                        .attr("width", function(d) { return d*10; })
                        .attr("height", 50)
                        .attr("y", function(d, i) { return i*100 });

    </script>
</body>
</html>

5. Scales

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

        var dataArray = [5, 30, 50, 80];
        var width = 500;
        var height = 500;

        var widthScale = d3.scale.linear()
                        .domain([0, 80])
                        .range([0, width]);

        var color = d3.scale.linear().domain([0, 80]).range(["red", "yellow"]);

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

        var bars = canvas.selectAll("rect")
                    .data(dataArray)
                    .enter()
                        .append("rect")
                        .attr("width", function(d) { return widthScale(d) })
                        .attr("height", 50)
                        .attr("fill", function(d) { return color(d) })
                        .attr("y", function(d, i) { return i*100 });

    </script>
</body>
</html>

6. Groups and axes

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

        var dataArray = [5, 30, 50, 80];
        var width = 500;
        var height = 500;

        var widthScale = d3.scale.linear()
                        .domain([0, 80])
                        .range([0, width]);

        var color = d3.scale.linear().domain([0, 80]).range(["red", "yellow"]);

        var axis = d3.svg.axis()
                    .ticks(20)
                    .scale(widthScale);

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

        var bars = canvas.selectAll("rect")
                    .data(dataArray)
                    .enter()
                        .append("rect")
                        .attr("width", function(d) { return widthScale(d) })
                        .attr("height", 50)
                        .attr("fill", function(d) { return color(d) })
                        .attr("y", function(d, i) { return i*100 });

        canvas.append("g")
            .attr("transform", "translate(0, 400)")
            .call(axis);

    </script>
</body>
</html>

7. Enter, Update, Exit

  • DOM elements < data elements => enter
  • DOM elements > data elements => exit
  • DOM elements = data elements => update
<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var data = [10, 20];

        var canvas = d3.select("body")
                    .append("svg")
                    .attr("width", 500)
                    .attr("height", 500);
                    
        var circle = canvas.append("circle")
                        .attr("cx", 50)
                        .attr("cy", 100)
                        .attr("r", 25);

        var circles = canvas.selectAll("circle")
                    .data(data)
                    .attr("fill", "red")    // update
                    .enter()                // enter
                        .append("circle")
                        .attr("cx", 50)
                        .attr("cy", 50)
                        .attr("fill", "green")
                        .attr("r", 25);
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var data = [10];

        var canvas = d3.select("body")
                    .append("svg")
                    .attr("width", 500)
                    .attr("height", 500);
                    
        var circle1 = canvas.append("circle")
                        .attr("cx", 50)
                        .attr("cy", 100)
                        .attr("r", 25);
        
        var circle2 = canvas.append("circle")
                        .attr("cx", 50)
                        .attr("cy", 200)
                        .attr("r", 25);

        var circles = canvas.selectAll("circle")
                    .data(data)
                    .attr("fill", "green")
                    .exit()
                        .attr("fill", "blue");
    </script>
</body>
</html>

8. Transition

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var data = [10];

        var canvas = d3.select("body")
                    .append("svg")
                    .attr("width", 500)
                    .attr("height", 500);
                    
        var circle = canvas.append("circle")
                        .attr("cx", 50)
                        .attr("cy", 50)
                        .attr("r", 25);

        circle.transition()
            .duration(1500)
            .delay(2000)
            .attr("cx", 150)
            .transition()
            .attr("cy", 200)
            .transition()
            .attr("cx", 50);

    </script>
</body>
</html>

9. Working with Arrays

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var data = [10, 20, 30, 40, 50];
    </script>
</body>
</html>
Open console in the browser
> data.sort(d3.descending);
[50, 40, 30, 20, 10]
> d3.min(data);
10
> d3.max(data);
50
> d3.extent(data);
[10, 50]
> d3.sum(data);
150
> d3.mean(data);
30
> d3.median(data);
30
> d3.shuffle(data);
[30, 50, 40, 20, 10]

10. Loading External Data

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        d3.csv("mydata.csv", function(data) {
        // d3.json("mydata.json", function(data)
            var canvas = d3.select("body").append("svg")
                .attr("width", 500)
                .attr("height", 500)

            canvas.selectAll("rect")
                .data(data)
                .enter()
                    .append("rect")
                    .attr("width", function(d){ return d.age*10; })
                    .attr("height", 48)
                    .attr("y", function(d, i){ return i*50; })
                    .attr("fill", "blue");

            canvas.selectAll("text")
                .data(data)
                .enter()
                    .append("text")
                    .attr("fill", "white")
                    .attr("y", function(d, i){ return i*50+24; })
                    .text(function(d){ return d.name; })
        })
    </script>
</body>
</html>
my data.json
[
    {"name": "Maria", "age": 30},
    {"name": "Fred", "age": 50},
    {"name": "Francis", "age": 12}
]
mydata.csv
"name","age"
"Maria",30
"Fred",50
"Francis",12

11. Paths

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var canvas = d3.select("body").append("svg")
                .attr("width", 500)
                .attr("height", 500);
        
        var data = [
            {x: 10, y: 20},
            {x: 40, y: 60},
            {x: 50, y: 70}
        ];

        var group = canvas.append("g")
            .attr("transform", "translate(100, 100)");

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

        group.selectAll("path")
            .data([data])
            .enter()
                .append("path")
                .attr("d", line)
                .attr("fill", "none")
                .attr("stroke", "#000")
                .attr("stroke-width", 5);
        
    </script>
</body>
</html>

12. Arcs

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var canvas = d3.select("body").append("svg")
                .attr("width", 500)
                .attr("height", 500);

        var group = canvas.append("g")
            .attr("transform", "translate(100, 100)");

        var r = 100;
        var p = Math.PI * 2;

        var arc = d3.svg.arc()
            .innerRadius(r-20)
            .outerRadius(r)
            .startAngle(0)
            .endAngle(p-1);

        group.append("path")
            .attr("d", arc);
        
    </script>
</body>
</html>

13. The Pie Layout

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

        var data = [10, 50, 80];
        var r = 300;

        var color = d3.scale.ordinal().range(["red", "blue", "green"]);

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

        var group = canvas.append("g")
            .attr("transform", "translate(300, 300)");

        var arc = d3.svg.arc()
            .innerRadius(r-100)
            .outerRadius(r);

        var pie = d3.layout.pie()
            .value(function(d) { return d; });

        var arcs = group.selectAll(".arc")
            .data(pie(data))
            .enter()
                .append("g")
                .attr("class", "arc");
        
        arcs.append("path")
            .attr("d", arc)
            .attr("fill", function(d){ return color(d.data); });

        arcs.append("text")
            .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"})
            .attr("text-anchor", "middle")
            .attr("font-size", "3em")
            .attr("fill", "white")
            .text(function(d) { return d.data })

    </script>
</body>
</html>

14. The Tree Layout (1/2) - Diagonal

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

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

        var diagonal = d3.svg.diagonal()
            .source({ x: 10, y: 10 })
            .target({ x: 300, y:300 });
        
        canvas.append("path")
            .attr("fill", "none")
            .attr("stroke", "black")
            .attr("d", diagonal);

    </script>
</body>
</html>

15. The Tree Layout (2/2)

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

        var canvas = d3.select("body").append("svg")
                .attr("width", 500)
                .attr("height", 500)
                .append("g")
                    .attr("transform", "translate(50, 50)");
                    
        var tree = d3.layout.tree()
                .size([400, 400]);

        d3.json("mydata.json", function(data){
            var nodes = tree.nodes(data);
            var links = tree.links(nodes);

            var node = canvas.selectAll(".node")
                .data(nodes)
                .enter()
                    .append("g")
                    .attr("class", "node")
                    .attr("transform", function(d){ return "translate(" + d.y + "," + d.x + ")"; })
            
            node.append("circle")
                .attr("r", 5)
                .attr("fill", "steelblue");

            node.append("text")
                .text(function(d){ return d.name; })

            var diagonal = d3.svg.diagonal()
                .projection(function(d){ return [d.y, d.x]; });

            canvas.selectAll(".link")
                .data(links)
                .enter()
                    .append("path")
                    .attr("class", "link")
                    .attr("fill", "none")
                    .attr("stroke", "#ADADAD")
                    .attr("d", diagonal);
        });

    </script>
</body>
</html>
mydata.json
{
    "name": "Max",
    "children": [
        {
            "name": "Sylvia",
            "children": [
                {"name": "Craig"},
                {"name": "Robin"},
                {"name": "Anna"}
            ]
        },
        {
            "name": "David",
            "children": [
                {"name": "Craig"},
                {"name": "Buffy"}
            ]
        }
    ]
}

16. Cluster, Pack & Bubble Layouts

Cluster

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

        var canvas = d3.select("body").append("svg")
                .attr("width", 500)
                .attr("height", 500)
                .append("g")
                    .attr("transform", "translate(50, 50)");
                    
        var cluster = d3.layout.cluster()
                .size([400, 400]);

        d3.json("mydata.json", function(data){
            var nodes = cluster.nodes(data);
            var links = cluster.links(nodes);

            var node = canvas.selectAll(".node")
                .data(nodes)
                .enter()
                    .append("g")
                    .attr("class", "node")
                    .attr("transform", function(d){ return "translate(" + d.y + "," + d.x + ")"; })
            
            node.append("circle")
                .attr("r", 5)
                .attr("fill", "steelblue");

            node.append("text")
                .text(function(d){ return d.name; })

            var diagonal = d3.svg.diagonal()
                .projection(function(d){ return [d.y, d.x]; });

            canvas.selectAll(".link")
                .data(links)
                .enter()
                    .append("path")
                    .attr("class", "link")
                    .attr("fill", "none")
                    .attr("stroke", "#ADADAD")
                    .attr("d", diagonal);
        });

    </script>
</body>
</html>

Pack

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var width = 800, 
            height = 600;

        var canvas = d3.select("body").append("svg")
                .attr("width", width)
                .attr("height", height)
                .append("g")
                    .attr("transform", "translate(50, 50)");
                    
        var pack = d3.layout.pack()
            .size([width, height - 50])
            .padding(10);

        d3.json("mydata.json", function(data) {
            
            var nodes = pack.nodes(data);
            
            var node = canvas.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", function(d){ return d.r; })
                .attr("fill", "steelblue")
                .attr("opacity", 0.25)
                .attr("stroke", "#ADADAD")
                .attr("stroke-width", 2);

            node.append("text")
                .text(function(d){ return d.children ? "" : d.name });
        });

    </script>
</body>
</html>

Bubble

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var width = 800, 
            height = 600;

        var canvas = d3.select("body").append("svg")
                .attr("width", width)
                .attr("height", height)
                .append("g")
                    .attr("transform", "translate(50, 50)");
                    
        var pack = d3.layout.pack()
            .size([width, height - 50])
            .padding(10);

        d3.json("mydata2.json", function(data) {
            
            var nodes = pack.nodes(data);
            
            var node = canvas.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", function(d){ return d.r; })
                .attr("fill", function(d){ return d.children ? "#fff" : "steelblue"; })
                .attr("opacity", 0.25)
                .attr("stroke", function(d){ return d.children ? "#fff" : "#ADADAD"; })
                .attr("stroke-width", 2);

            node.append("text")
                .text(function(d){ return d.children ? "" : d.name });
        });

    </script>
</body>
</html>
mydata.json
{
    "name": "Max",
    "value": 100,
    "children": [
        {
            "name": "Sylvia",
            "value": 75,
            "children": [
                {"name": "Craig", "value": 25},
                {"name": "Robin", "value": 25},
                {"name": "Anna", "value": 25}
            ]
        },
        {
            "name": "David",
            "value": 75,
            "children": [
                {"name": "Jeff", "value": 25},
                {"name": "Buffy", "value": 25}
            ]
        },
        {
            "name": "Mr X",
            "value": 75
        }
    ]
}
mydata2.json
{
    "name": "Max",
    "value": 100,
    "children": [
        { "name": "Max", "value": 100 },
        { "name": "Max", "value": 125 },
        { "name": "Max", "value": 50 },
        { "name": "Max", "value": 78 },
        { "name": "Max", "value": 24 },
        { "name": "Max", "value": 210 },
        { "name": "Max", "value": 140 },
        { "name": "Max", "value": 90 }
    ]
}

17. The Histogram Layout (1/2)

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        
        d3.csv("ages.csv", function (data) {
            
            var map = data.map(function(i){ return parseInt(i.age); })
            
            var histogram = d3.layout.histogram()
                .bins(5)
                (map)
            
            var canvas = d3.select("body").append("svg")
                .attr("width", 500)
                .attr("height", 500);

            var bars = canvas.selectAll(".bar")
                .data(histogram)
                .enter()
                    .append("g")

            bars.append("rect")
                .attr("x",function(d){ return d.x * 5; })
                .attr("y", 0)
                .attr("width", function(d){ return d.dx * 5 - 5; })
                .attr("height", function(d){ return d.y * 20; })
                .attr("fill", "steelblue");

        })
    </script>
</body>
</html>
ages.csv
"name","age"
"Hayden",20
"Hayden",30
"Hayden",40
"Hayden",50
"Hayden",60
"Hayden",70
"Hayden",80
"Hayden",10
"Hayden",14
"Hayden",27
"Hayden",29
"Hayden",28
"Hayden",32
"Hayden",34
"Hayden",42
"Hayden",53
"Hayden",67
"Hayden",78
"Hayden",99
"Hayden",34
"Hayden",25
"Hayden",22
"Hayden",39

18. The Histogram Layout (2/2)

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

        var width = 500,
            height = 500,
            padding = 50;
        
        d3.csv("ages.csv", function (data) {
            
            var map = data.map(function(i){ return parseInt(i.age); })
            
            var histogram = d3.layout.histogram()
                .bins(5)
                (map)

            var x = d3.scale.linear()
                .domain([0, d3.max(map)])
                .range([0, width]);

            var y = d3.scale.linear()
                .domain([0, d3.max(histogram.map(function(i){ return i.length; }))])
                .range([0, height]);

            var xAxis = d3.svg.axis()
                .scale(x)
                .orient("bottom");
            
            var canvas = d3.select("body").append("svg")
                .attr("width", width + padding)
                .attr("height", height + padding)
                .append("g")
                    .attr("transform", "translate(20, 0)");

            var group = canvas.append("g")
                .attr("transform", "translate(0," + height + ")")
                .call(xAxis);

            var bars = canvas.selectAll(".bar")
                .data(histogram)
                .enter()
                    .append("g")

            bars.append("rect")
                .attr("x",function(d){ return x(d.x); })
                .attr("y", function(d){ return 500 - y(d.y); })
                .attr("width", function(d){ return x(d.dx); })
                .attr("height", function(d){ return y(d.y); })
                .attr("fill", "steelblue");

            bars.append("text")
                .attr("x", function(d){ return x(d.x); })
                .attr("y", function(d){ return 500 - y(d.y); })
                .attr("dx", function(d){ return x(d.dx)/2; })
                .attr("dy", "20px")
                .attr("fill", "#fff")
                .attr("text-anchor", "middle")
                .text(function(d){ return d.y; })

        })
    </script>
</body>
</html>
ages.csv
"name","age"
"Hayden",20
"Hayden",30
"Hayden",40
"Hayden",50
"Hayden",60
"Hayden",70
"Hayden",80
"Hayden",10
"Hayden",14
"Hayden",27
"Hayden",29
"Hayden",28
"Hayden",32
"Hayden",34
"Hayden",42
"Hayden",53
"Hayden",67
"Hayden",78
"Hayden",99
"Hayden",34
"Hayden",25
"Hayden",22
"Hayden",39

19. The Treemap Layout

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>

        var color = d3.scale.category10();

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

        d3.json("mydata.json", function(data){
            
            var treemap = d3.layout.treemap()
                .size([500, 500])
                .nodes(data)
            
            var cells = canvas.selectAll("g")
                .data(treemap)
                .enter()
                .append("g")
                .attr("class", "cell")
        
            cells.append("rect")
                .attr("x", function(d){ return d.x; })
                .attr("y", function(d){ return d.y; })
                .attr("width", function(d){ return d.dx; })
                .attr("height", function(d){ return d.dy; })
                .attr("fill", function(d){ return d.children ? null: color(d.parent.name); })
                .attr("stroke", "#fff")

            cells.append("text")
                .attr("x", function(d){ return d.x + d.dx / 2; })
                .attr("y", function(d){ return d.y + d.dy / 2; })
                .attr("text-anchor", "middle")
                .attr("fill", "#fff")
                .text(function(d){ return d.name; })

        })
    </script>
</body>
</html>
mydata.json
{
    "name": "Max",
    "value": 100,
    "children": [
        {
            "name": "Sylvia",
            "value": 75,
            "children": [
                {"name": "Craig", "value": 25},
                {"name": "Robin", "value": 25},
                {"name": "Anna", "value": 25}
            ]
        },
        {
            "name": "David",
            "value": 75,
            "children": [
                {"name": "Jeff", "value": 25},
                {"name": "Buffy", "value": 25}
            ]
        },
        {
            "name": "Mr X",
            "value": 75
        }
    ]
}

20. Maps in D3

<!DOCTYPE html>
<html lang="en">
<head>
    <title>D3 Tutorial</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <script>
        var width = 600, 
            height = 800;
        var canvas = d3.select("body").append("svg")
            .attr("width", width)
            .attr("height", height);

        d3.json("skorea_provinces_geo.json", function(data){
            
            var group = canvas.selectAll("g")
                .data(data.features)
                .enter()
                .append("g")

            var projection = d3.geo.mercator().scale(4000).translate([-8620, 3080]);
            var path = d3.geo.path().projection(projection);

            var areas = group.append("path")
                .attr("d", path)
                .attr("class", "area")
                .attr("fill", "steelblue");

            group.append("text")
                .attr("x", function(d) { return path.centroid(d)[0]; })
                .attr("y", function(d) { return path.centroid(d)[1]; })
                .attr("text-anchor", "middle")
                .text(function(d){ return d.properties.name; })
        })
    </script>
</body>
</html>

I used skorea_provinces_geo.json which is the map of South Korea. You can download here.