Saturday, October 31, 2015

Learning D3.js

The basics

d3.select("body").append("svg")
    .attr("width", 50)
    .attr("height", 50)
    .append("circle")
    .attr("cx", 25)
    .attr("cy", 25)
    .attr("r", 25)
    .style("fill", "purple")

var theData = [ 1, 2, 3 ]
var p = d3.select("body").selectAll("p")
  .data(theData)
  .enter()
  .append("p")
    .text("Hello");
    //text(function () {
    //    return "hello world!";
    //});
    //.text(function (d) {
    //    return d * 2
    //});
    //.text(function (d, i) {
    //    return "Index: " + i + ", Val: " + d;
    //});

console.log(p)

Binding shape properties to data

circleRadii = [40, 20, 10]

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

var circles = svgContainer.selectAll("circle")
    .data(circleRadii)
    .enter()
    .append("circle")

var circleAttributes = circles
    .attr("cx", 50)
    .attr("cy", 50)
    .attr("r", function (d) { return d; })
    .style("fill", function(d) {
        var returnColor;
        if (d === 40) { returnColor = "green";
        } else if (d === 20) { returnColor = "purple";
        } else if (d === 10) { returnColor = "red"; }
        return returnColor;
    });

Binding styles and coordinates to data

var spaceCircles = [30, 70, 110];

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

var circles = svgContainer.selectAll("circle")
    .data(spaceCircles)
    .enter()
    .append("circle");

var circleAttributes = circles
    .attr("cx", function (d) { return d; })
    .attr("cy", function (d) { return d; })
    .attr("r", 20 )
    .style("fill", function(d) {
        var returnColor;
        if (d === 30) { returnColor = "green";
        } else if (d === 70) { returnColor = "purple";
        } else if (d === 110) { returnColor = "red"; }
        return returnColor;
    });

Using json object as data

var jsonCircles = [
    {
        "x_axis": 30,
        "y_axis": 30,
        "radius": 20,
        "color" : "green"
    }, {
        "x_axis": 70,
        "y_axis": 70,
        "radius": 20,
        "color" : "purple"
    }, {
        "x_axis": 110,
        "y_axis": 100,
        "radius": 20,
        "color" : "red"
    }];

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


var circles = svgContainer.selectAll("circle")
    .data(jsonCircles)
    .enter()
    .append("circle");

var circleAttributes = circles
    .attr("cx", function (d) { return d.x_axis; })
    .attr("cy", function (d) { return d.y_axis; })
    .attr("r", function (d) { return d.radius; })
    .style("fill", function(d) { return d.color; });

Polylines

//The data for our line
var lineData = [ { "x": 1,   "y": 5},  { "x": 20,  "y": 20},
    { "x": 40,  "y": 10}, { "x": 60,  "y": 40},
    { "x": 80,  "y": 5},  { "x": 100, "y": 60}];

//This is the accessor function we talked about above
var lineFunction = d3.svg.line()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .interpolate("linear");

//The SVG Container
var svgContainer = d3.select("body").append("svg")
    .attr("width", 200)
    .attr("height", 200);

//The line SVG Path we draw
var lineGraph = svgContainer.append("path")
    .attr("d", lineFunction(lineData))
    .attr("stroke", "blue")
    .attr("stroke-width", 2)
    .attr("fill", "none");

Dynamically determine size of svg

var jsonRectangles = [
    { "x_axis": 10, "y_axis": 10, "height": 20, "width":20, "color" : "green" },
    { "x_axis": 160, "y_axis": 40, "height": 20, "width":20, "color" : "purple" },
    { "x_axis": 70, "y_axis": 70, "height": 20, "width":20, "color" : "red" }];

var max_x = 0;
var max_y = 0;

for (var i = 0; i < jsonRectangles.length; i++) {
    var temp_x, temp_y;
    var temp_x = jsonRectangles[i].x_axis + jsonRectangles[i].width;
    var temp_y = jsonRectangles[i].y_axis + jsonRectangles[i].height;

    if ( temp_x >= max_x ) { max_x = temp_x; }

    if ( temp_y >= max_y ) { max_y = temp_y; }
}

var svgContainer = d3.select("body").append("svg")
    .attr("width", max_x)
    .attr("height", max_y)

var rectangles = svgContainer.selectAll("rect")
    .data(jsonRectangles)
    .enter()
    .append("rect");

var rectangleAttributes = rectangles
    .attr("x", function (d) { return d.x_axis; })
    .attr("y", function (d) { return d.y_axis; })
    .attr("height", function (d) { return d.height; })
    .attr("width", function (d) { return d.width; })
    .style("fill", function(d) { return d.color; });

Linear scale

var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];

var newScaledData = [];
var minDataPoint = d3.min(initialScaleData);
var maxDataPoint = d3.max(initialScaleData);

var linearScale = d3.scale.linear()
                           .domain([minDataPoint,maxDataPoint])
                           .range([0,100]);

for (var i = 0; i < initialScaleData.length; i++) {
  newScaledData[i] = linearScale(initialScaleData[i]);
}

newScaledData;
//[0, 10, 30, 20, 50, 40, 70, 60, 90, 80, 100]

Transformation

var circleData = [
    { "cx": 20, "cy": 20, "radius": 20, "color" : "green" },
    { "cx": 70, "cy": 70, "radius": 20, "color" : "purple" }];


var rectangleData = [
    { "rx": 110, "ry": 110, "height": 30, "width": 30, "color" : "blue" },
    { "rx": 160, "ry": 160, "height": 30, "width": 30, "color" : "red" }];

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

var circleGroup = svgContainer.append("g")
    .attr("transform", "translate(80,0)")

var circles = circleGroup.selectAll("circle")
    .data(circleData)
    .enter()
    .append("circle");

var circleAttributes = circles
    .attr("cx", function (d) { return d.cx; })
    .attr("cy", function (d) { return d.cy; })
    .attr("r", function (d) { return d.radius; })
    .style("fill", function (d) { return d.color; });

var rectangles = svgContainer.selectAll("rect")
    .data(rectangleData)
    .enter()
    .append("rect");

var rectangleAttributes = rectangles
    .attr("x", function (d) { return d.rx; })
    .attr("y", function (d) { return d.ry; })
    .attr("height", function (d) { return d.height; })
    .attr("width", function (d) { return d.width; })
    .style("fill", function(d) { return d.color; });

Adding text

//Circle Data Set
var circleData = [
    { "cx": 20, "cy": 20, "radius": 20, "color" : "green" },
    { "cx": 70, "cy": 70, "radius": 20, "color" : "purple" }];

//Create the SVG Viewport
var svgContainer = d3.select("#svgContainer")
    .attr("width",200)
    .attr("height",200);

//Add the SVG Text Element to the svgContainer
var text = svgContainer.selectAll("text")
    .data(circleData)
    .enter()
    .append("text");

var circles = svgContainer.selectAll("circle")
    .data(circleData)
    .enter()
    .append("circle")
    .attr("cx", function(d) {return d.cx})
    .attr("cy", function(d) {return d.cy})
    .attr("r", function(d) {return d.radius})
    .attr("fill", function(d) {return d.color})

//Add SVG Text Element Attributes
var textLabels = text
    .attr("x", function(d) { return d.cx; })
    .attr("y", function(d) { return d.cy; })
    .text( function (d) { return "( " + d.cx + ", " + d.cy +" )"; })
    .attr("font-family", "sans-serif")
    .attr("font-size", "20px")
    .attr("fill", "red");

Thinking in data joins: update, enter, exit


var width = 960,
    height = 500;

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(32," + (height / 2) + ")");

function update(data) {

    // DATA JOIN
    // Join new data with old elements, if any.
    var text = svg.selectAll("text")
        .data(data);

    // UPDATE
    // Update old elements as needed.
    // Initially this part is empty
    text.attr("class", "update").attr("fill", "blue");

    // ENTER
    // Create new elements as needed.
    text.enter().append("text")
        .attr("class", "enter")
        .attr("fill", "green")
        .attr("x", function(d, i) { return i * 32; })
        .attr("dy", ".35em");

    // ENTER + UPDATE
    // Appending to the enter selection expands the update selection to include
    // entering elements; so, operations on the update selection after appending to
    // the enter selection will apply to both entering and updating nodes.
    text.text(function(d) { return d; });

    // EXIT
    // Remove old elements as needed.
    text.exit().remove();
}

In the javascript console:

update([1, 2])

enter image description here

update([1, 2, 3, 4])

enter image description here

update([1, 2, 3, 4, 5, 6])

enter image description here

Now with a touch of animation (transition in coordinates):

var width = 960,
    height = 500;

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(32," + (height / 2) + ")");

function update(data) {
    var text = svg.selectAll("text")
        .data(data);
    text.attr("class", "update").attr("fill", "blue");
    text.enter().append("text")
        .attr("class", "enter")
        .attr("fill", "green")
        .attr("x", function(d, i) { return i * 32; })
        .attr("y", -50)
        .transition()
        .attr("y", 0)
        .attr("dy", ".35em");
    text.text(function(d) { return d; });
    text.exit()
        .attr("y", 0)
        .transition()
        .attr("y", -50)
        .remove();
}

Nested Selection

var tableBody = d3.select("body").append("table").append("tbody");
//var cells = tableBody.selectAll("tr").selectAll("td")
//    .style("color", function(d, i, j) {return i === j ? "green" : "lightblue"})

var matrix = [
    [ 0,  1,  2,  3],
    [ 4,  5,  6,  7],
    [ 8,  9, 10, 11],
    [12, 13, 14, 15],
];
var cells = tableBody.selectAll("tr")
    .data(matrix)
    .enter()
    .append("tr")
    .selectAll("td")
    .data(function(d, i) {return d})
    .enter()
    .append("td")
    .html(function(d) {return "<b>" + d + "</b>";});

0 comments: