Skip to content
This repository has been archived by the owner on Apr 17, 2022. It is now read-only.

Use types for layout.force[...]() #6

Closed
daenenk opened this issue Nov 20, 2015 · 5 comments
Closed

Use types for layout.force[...]() #6

daenenk opened this issue Nov 20, 2015 · 5 comments
Assignees
Labels

Comments

@daenenk
Copy link

daenenk commented Nov 20, 2015

I had a look to the TODO's to support more typed functions, e.g. to created a custom typed layout.Force.
You may encounter issues with mutable typed classes, which you can't make co-variant, such as e.g. Link[Node].
The following approach may help:

@js.native
trait AbstractLink extends js.Object {
  type Node <: forceModule.Node
  var source: Node
  var target: Node
}

@js.native
trait Link[T <: forceModule.Node] extends forceModule.AbstractLink {
  type Node = T
}

@js.native
trait Force[Link <: forceModule.AbstractLink, Node <: forceModule.Node] extends js.Object 
@spaced
Copy link
Owner

spaced commented Nov 22, 2015

Tnx for your input. On the other side, are there any use cases where it make sense to use a typed force() function?
Or can you describe the expected behavior as a test case?

@spaced spaced self-assigned this Nov 26, 2015
@daenenk
Copy link
Author

daenenk commented Nov 27, 2015

I tried to re-impement the example: http://bl.ocks.org/mbostock/1095795

The arrays nodes and links are shared between the Force layout and the svg element in which they are inserted. In the example Node has a property id which is application specific.

! Updated code - 2015-11-30

import scala.scalajs.js
import scala.scalajs.js._
import scala.scalajs.js.timers.setTimeout
import org.scalajs.dom

import org.singlespaced.d3js._
import org.singlespaced.d3js.Ops._
import org.singlespaced.d3js.forceModule
import org.singlespaced.d3js.forceModule.Event

import scala.language.implicitConversions

object ForceLayoutExample extends JSApp {

  trait Node extends forceModule.Node {
    var id: String
  }

  object Node {
    def apply(id: String): Node = {
      val n = new js.Object().asInstanceOf[Node]
      n.id = id
      n
    }
  }

  type Link = forceModule.Link[Node]

  object Link {
    def apply(x: Node, y: Node): Link = {
      val l = new js.Object().asInstanceOf[Link]
      l.source = x
      l.target = y
      l
    }
  }
  def main(): Unit = {

    val width = 960
    val height = 500

    val color = d3.scale.category10()

    // node, link will be initialized in the start() function
    var node: selection.Update[Node] = null
    var link: selection.Update[Link] = null

    def tick(e: Event): Unit = {
      node.attr("cx", (d: Node) => d.x)
        .attr("cy", (d: Node) => d.y)

      link.attr("x1", (d: Link) => d.source.x)
        .attr("y1", (d: Link) => d.source.y)
        .attr("x2", (d: Link) => d.target.x)
        .attr("y2", (d: Link) => d.target.y)
    }

    val nodes = Array[Node]()
    val links = Array[Link]()

    val force = d3.layout.force[Node, Link]()
      .nodes(nodes)
      .links(links)
      .charge(-400)
      .linkDistance(120)
      .size(Tuple2(width, height))
      .on("tick", tick _)

    val svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height)

    // 1. Add three nodes and three links.
    setTimeout(0) {
      val a = Node("a")
      val b = Node("b")
      val c = Node("c")

      nodes.push(a, b, c);
      links.push(Link(a, b), Link(a, c), Link(b, c))
      start();
    }

    // 2. Remove node B and associated links.
    setTimeout(3000) {
      nodes.splice(1, 1); // remove b
      links.shift(); // remove a-b
      links.pop(); // remove b-c
      start();
    }

    // Add node B back
    setTimeout(6000) {
      val a = nodes(0)
      val b = Node("b")
      val c = nodes(1)
      nodes.push(b);
      links.push(Link(a, b), Link(b, c))
      start();
    }

    def start() = {
      link = svg.selectAll[Link](".link")
        .data(force.links(), (d: Link, i: Int) => d.source.id + "-" + d.target.id)

      link.enter().insert("line", ".node").attr("class", "link")
      link.exit().remove()

      node = svg.selectAll[Node](".node")
        .data(force.nodes(), (d: Node, i: Int) => d.id)

      node.enter().append("circle")
        .attr("class", (d: Node) => "node " + d.id)
        .attr("r", 8)

      node.exit().remove()

      force.start()
    }

  }

}

@daenenk daenenk closed this as completed Nov 27, 2015
@daenenk
Copy link
Author

daenenk commented Nov 27, 2015

Sorry, it was not my intention to close it; as it still not clear how to implement the above example. I pressed the wrong button.

@daenenk daenenk reopened this Nov 27, 2015
@spaced
Copy link
Owner

spaced commented Nov 30, 2015

with switching order of Nodes and Links works:

trait Force[Node <: forceModule.Node, Link <: forceModule.Link[Node] ]

because of scala from left to right rule see here http://pchiusano.blogspot.ch/2011/05/making-most-of-scalas-extremely-limited.html . scala is just awesome

Still not so happy with sub classing Nodes and Links because it does not allow to add additional code to it, because of In native JS types, all concrete definitions must have = js.native as body. I may think about . So this can change in future (see also LayoutTest). You can also send PR as proposals or tests for drill down issues.

@spaced spaced changed the title TODO layout.force[...]() Use types for layout.force[...]() Nov 30, 2015
@spaced spaced closed this as completed in 4eedaab Nov 30, 2015
@spaced
Copy link
Owner

spaced commented Nov 30, 2015

Im not sure if this fix catches all parts, but it should handle major part, otherwise reopen or create a new one ;-)

@daenenk daenenk mentioned this issue Dec 1, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants