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>";});

Thursday, October 29, 2015

Exploring Scala.rx

You can see more here.

package tutorial.webapp

import scala.scalajs.js.JSApp
import scala.scalajs.js.annotation.JSExport

/**
 * Created by IDEA on 29/10/15.
 */
import rx._

object TestRx extends JSApp {
  def main: Unit = {
    demo
    observe
    listInVar
    omg
    nest1
  }

  def nest1: Unit = {
    val a = Var(1)
    val b = Rx(
      (Rx(a() * 2), Rx(math.random))
    )
    val r = b()._2()
    println("random number: ", r)
    val r1 = b()._2()
    println("random number: ", r1)
    a() = 2
    println("First nested", b()._1())
    println("Second nested", b()._2())
  }

  def demo: Unit = {
    val a = Var(1)
    val b = Var(2)
    val c = Rx {
      a() + b()
    }
    println(c())
    a() = 4
    println(c())
  }

  def observe: Unit = {
    val x1: Var[Int] = Var(1)
    var count = 0
    val x1Obs = Obs(x1, skipInitial = true) {
      count = x1() + 1
      println("count: " + count)
    }
    x1() = 3
    x1Obs.kill()
    x1() = 5
  }


  def listInVar: Unit = {
    val x2 = Var(List(1, 2, 3))
    val x2Rx = Rx(x2().sum)
    val x2RxObs = Obs(x2Rx) {
      println("x2 sum: " + x2Rx())
    }
    x2() ++= List(4)
    x2() ++= List(5, 6)
  }

  def omg: Unit = {
    val a = Var(Seq(1, 2, 3))
    val b = Var(3)
    val c = Rx{ b() +: a() }
    val d = Rx{ c().map("omg" * _) }
    val e = Var("wtf")
    val f = Rx{ (d() :+ e()).mkString }
    println(f()) // "omgomgomgomgomgomgomgomgomgwtf"
    a() = Nil
    println(f()) // "omgomgomgwtf"
    e() = "wtfbbq"
    println(f()) // "omgomgomgwtfbbq"
  }
}

Tuesday, October 27, 2015

Replace View Bounds

Well the scala community has decided to deprecate view bounds in Scala. I suspect it might have to do with brevity. Full discussion can be viewed here

Well looks like most of the view bounds (<%) can be converted to context bounds. Lets see different strategies that can help us do the same. Suppose we have:

1
2
3
4
5
6
7
8
9
10
11
scala> def foo[T <% Int](x: T):Int = x
foo: [T](x: T)(implicit evidence$1: T => Int)Int
 
scala> implicit def a[T](n:T) = n match {
 | case x:String => x.toInt
 | }
warning: there were 1 feature warning(s); re-run with -feature for details
a: [T](n: T)Int
 
scala> foo("23")
res4: Int = 23

To convert it to `context bound` we need to implicitly have something that does T => Int. We could:

1
2
3
4
5
6
7
8
scala> type L[X] = X => Int
defined type alias L
 
scala> def goo[T : L](x: T):Int = x
goo: [T](x: T)(implicit evidence$1: T => Int)Int
 
scala> goo(23)
res2: Int = 23

We could combine the type declaration with the function definition as:

1
2
3
4
scala> def goo[T : ({type L[X] = X => Int})#L](x: T):Int = x
goo: [T](x: T)(implicit evidence$1: T => Int)Int
 
//quite cryptic though

A more readable version:

1
2
3
4
5
scala> def goo2[T](x: T)(implicit ev: T => Int):Int = x
goo2: [T](x: T)(implicit ev: T => Int)Int
 
scala> goo2("12")
res8: Int = 12

More generalized version:

1
2
3
4
5
6
7
8
9
10
11
scala> def goo[E,T : ({type L[X] = X => E})#L](x: T):E = x
goo: [E, T](x: T)(implicit evidence$1: T => E)E
 
scala> def goo2[T, E](x: T)(implicit ev: T => E):E = x
goo2: [T, E](x: T)(implicit ev: T => E)E
 
scala> goo("1000")
res10: String = 1000
 
scala> goo2("1000")
res11: String = 1000

Source