Создание дерева D3 из JSON с помощью ScalaJS
Пожалуйста, обратите внимание, что это не простой вопрос JS. Мне очень нужна помощь Скалайса.
Я застрял, пытаясь нарисовать простой, связный древовидный график уже несколько дней. Он может быть вложен сколь угодно глубоко. Я читаю в этом файле:{
"name": "Animal",
"children": [
{
"name": "Vertebrates",
"children": [
{
"name": "Mammals"
},
{
"name": "Birds"
}
]
},
{
"name": "Invertebrates"
}
]
}
Когда я запускаю эту программу:
package example
import scala.scalajs.js
import org.singlespaced.d3js.{Link, Tree, d3}
@js.native
trait AnimalNode extends js.Object {
val name: String = js.native
val children: js.Array[AnimalNode] = js.native
}
object ScalaJSExample extends js.JSApp {
def main(): Unit =
d3.json("json-example.json", (error: js.Any, json: js.Any) => {
val jsonTypedFromFile = json.asInstanceOf[AnimalNode]
val width = 960.0
val height = 500.0
val tree: Tree[AnimalNode] = d3.layout.tree().size((width, height))
val nodes = tree.nodes(jsonTypedFromFile)
val links = tree.links(nodes)
val svg = d3.select("#tree").append("svg")
.attr("width", width).attr("height", height).append("g")
val diagonal = d3.svg.diagonal() //Want to draw Diagonals across all links.
svg.data(links)
.append("path")
.attr("class", "link")
.style("stroke-width", 5)
.attr("d", (myJson: Link[AnimalNode], x: Int, y: js.UndefOr[Int]) => {
??? // TODO: Draw Diagonal between source & target. Never reached.
})
println("Finished drawing paths.")
})
}
Я получаю эту ошибку в Firebug:
uncaught exception:
scala.scalajs.runtime.UndefinedBehaviorError:
An undefined behavior was detected:
[object Object] is not an instance of org.singlespaced.d3js.Link
Альтернативная сигнатура, которую я мог бы использовать для таргетинга:
.attr("d", (myJson: Link[Node], x: Int, y: js.UndefOr[Int]) => { ... }
Мой код является вилкой примера приложения ScalaJSD3 и доступен здесь: https://github.com/swoogles/scala-js-d3-example-app
Он был вдохновлен простым кодом Javascript здесь: http://bl.ocks.org/d3noob/8375092
1 ответ:
Скала.библиотека JS wrapper немного глючная и неполная, я боюсь. Его ошибка в
.attr("d", (myJson: Link[Node], x: Int, y: js.UndefOr[Int]) => { ... }
, потому что типы времени выполнения ссылок на самом деле не соответствуют сигнатурам оболочки, поскольку они создаются d3 через функциюjs.native
. AClassCastException
затем выбрасывается, потому что Scala.js не может привести простые объекты JS к ссылкам.Вы можете обойти это:
val untypedLinks: js.Array[_ <: Any] = animalNodeTree.links(animalNodes) val animalNodeLinks = untypedLinks.map(link => { val linkObj = link.asInstanceOf[js.Dynamic] SimpleLink(linkObj.source.asInstanceOf[AnimalNode], linkObj.target.asInstanceOf[AnimalNode]) })
Еще одна проблема с оболочкой заключается в том, что проекция реализуется только частично, вы не можете действительно создать проекция для ваших собственных ссылок прямо сейчас (см.
TODO
s: https://github.com/spaced/scala-js-d3/blob/master/src/main/scala/org/singlespaced/d3js/svg.scala).Может быть, строки достаточно хороши для вашего usecase, я адаптировал пример из http://www.d3noob.org/2014/01/tree-diagrams-in-d3js_11.html к вашему примеру:
package example import bill.d3.TreeData import scala.scalajs.js import scala.scalajs.js.Dynamic import org.singlespaced.d3js.{Link, Tree, d3, SimpleLink} import org.singlespaced.d3js.d3.Primitive import scala.util.Try import scala.collection.mutable import js.JSConverters._ @js.native trait AnimalNode extends js.Object { var id: js.UndefOr[Int] = js.native var x: js.UndefOr[Int] = js.native var y: js.UndefOr[Int] = js.native var depth: Int = js.native val parent: String = js.native val name: String = js.native val children: js.Array[AnimalNode] = js.native } object ScalaJSExample extends js.JSApp with TreeData { def main(): Unit = { println(Try { drawTree }) } def drawTree = { d3.json("json-example.json", (error: js.Any, json: js.Any) => { val jsonTypedFromFile = json.asInstanceOf[AnimalNode] val width = 960.0 val height = 650.0 val marginLeft = 0.0 val marginTop = 30.0 val svg = d3.select("#tree").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + marginLeft + "," + marginTop + ")") val tupledDimensions = (width, height) val animalNodeTree: Tree[AnimalNode] = d3.layout.tree().size(tupledDimensions) val animalNodes: js.Array[AnimalNode] = animalNodeTree.nodes(jsonTypedFromFile) val untypedLinks: js.Array[_ <: Any] = animalNodeTree.links(animalNodes) val animalNodeLinks = untypedLinks.map(link => { val linkObj = link.asInstanceOf[js.Dynamic] SimpleLink(linkObj.source.asInstanceOf[AnimalNode], linkObj.target.asInstanceOf[AnimalNode]) }) // Normalize for fixed-depth. animalNodes.foreach((node: AnimalNode) => { node.y = node.depth * 180 println(node.y) }) var nodeCount: Int = 0 val node: org.singlespaced.d3js.selection.Update[AnimalNode] = svg.selectAll("g.node").data(animalNodes, (node: AnimalNode, index: Int) => { nodeCount += 1 node.id = nodeCount node.id.toString }) val nodeEnter: org.singlespaced.d3js.selection.Enter[AnimalNode] = node.enter() val nodeWithPosition = nodeEnter.append("g") .attr("class", "node") .attr("transform", (animalNode: AnimalNode, x: Int, y: js.UndefOr[Int]) => { println(animalNode.id) "translate(" + animalNode.x + "," + animalNode.y + ")": Primitive }) nodeWithPosition.append("circle") .attr("r", 10) .style("fill", "#fff") nodeWithPosition.append("text") .attr("x", 13) .attr("dy", ".35em") .attr("text-anchor", "start") .text((node: AnimalNode, x: Int, y: js.UndefOr[Int]) => { node.name }) .style("fill-opacity", 1) val link = svg.selectAll("g.link") .data(animalNodeLinks, (link: SimpleLink[AnimalNode], index: Int) => { "" + link.target.id }) link.enter().insert("line", "g") .attr("class", "link") .attr("x1", (node: SimpleLink[AnimalNode], x: Int, y: js.UndefOr[Int]) => { "" + node.source.x.getOrElse(0.0) : Primitive }) .attr("y1", (node: SimpleLink[AnimalNode], x: Int, y: js.UndefOr[Int]) => { "" + node.source.y.getOrElse(0.0) : Primitive }) .attr("x2", (node: SimpleLink[AnimalNode], x: Int, y: js.UndefOr[Int]) => { "" + node.target.x.getOrElse(0.0) : Primitive }) .attr("y2", (node: SimpleLink[AnimalNode], x: Int, y: js.UndefOr[Int]) => { "" + node.target.y.getOrElse(0.0) : Primitive }) println("Done") } ) } }