spaced / scala-js-d3 Goto Github PK
View Code? Open in Web Editor NEWd3 facade types for Scala.js
License: BSD 3-Clause "New" or "Revised" License
d3 facade types for Scala.js
License: BSD 3-Clause "New" or "Revised" License
I had need recently to extend your library to use an additional D3 library however I couldn't 'Monkey Patch' your facade (as per https://www.scala-js.org/doc/interoperability/facade-types.html) as 'd3.scala' is an object and therefore not extendable.
Jquery (the example used in the ScalaJS documentaion) uses a trait which means it can be extended nicely.
Is there a reason d3 is an object?
PS; thanks for the awesome lib :)
Instead of using
js.Function2[T, Int, Double]
it should be possible to use functions without index. Example:
d3.svg.line[TestDatum]
.x( (d:TestDatum) => d.v )
In javascript axis
as-is can be used as a function.
In scala this is supported by the apply functions.
trait Axis extends js.Any {
def apply(selection: Selection[js.Any]): Unit = js.native
def apply(selection: Transition[js.Any]): Unit = js.native
However this makes it hard to pass an axis as argument to the selection.call() function,
as in e.g. from Simple d3.js Graph
svg
.append("g")
.attr("class", "x axis")
.call(xAxis)
I suggest the following:
trait Axis extends js.Function1[ Selection[js.Any] | Transition[js.Any], Unit]
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
I was looking for a better way to create Node instances, i.s.o. ( see example posted in Issue 6)
def apply(id: String): Node = {
val n = new js.Object().asInstanceOf[Node]
n.id = id
n
}
First, I changed trait forceModule.Node
into a class
package forceModule { . . .
@js.annotation.ScalaJSDefined
class Node extends js.Object {
var index: Double = 0 // or null.asInstanceOf[Double]
var x: Double = 0
var y: Double = 0
var px: Double = 0
var py: Double = 0
var fixed: Boolean = false // or null.asInstanceOf[Boolean]
var weight: Double = 0
}
. . . }
So I could change the object creation in the example into:
object ForceLayoutExample extends JSApp { . . .
@js.annotation.ScalaJSDefined
class Node(val id: String) extends forceModule.Node
object Node {
def apply(id:String): Node = new Node(id)
}
. . . }
While wondering whether it was a good idea to turn forceModule.Node
into a class, I realised that Node
is not defined as a class in d3.js; actually it is not defined at all. The API accepts or produces an object with the members x
, y
, etc, without explicitly defining a javascript class for it. This is interesting, because this means that there is not a strict reason to define the trait forceModule.Node
as @js.native
. This gives more flexibility form a scala perspective. As long as the objects that we pass to the d3.js API have the expected var members, it should be fine.
I'm relatively new into scala-js but I tried the following (complete other approach than above) and it works as well:
package forceModule { . . .
// no js.native!
trait Node {
var index: Double
var x: Double
var y: Double
var px: Double
var py: Double
var fixed: Boolean
var weight: Double
}
import js.annotation.JSExport
@JSExport
class DefaultNode extends Node { // alternatively init the vars to null
@JSExport var index: Double = 0
@JSExport var x: Double = 0
@JSExport var y: Double = 0
@JSExport var px: Double = 0
@JSExport var py: Double = 0
@JSExport var fixed: Boolean = false
@JSExport var weight: Double = 0
}
. . . }
object ForceLayoutExample extends JSApp { . . .
case class Node(id: String) extends forceModule.DefaultNode
. . . }
I could even define a case class
which would be not allowed extending from a js.native trait.
Some food for though ...
I re-implemented http://bl.ocks.org/d3noob/b3ff6ae1c120eea654b5
To do this I had to apply following changes in svg.scala
:
def line[Datum](): Line[Datum] = js.native
. . .
trait Line[T] extends js.Object {
. . .
def x[Datum >: T](x: js.Function1[Datum, Double]): Line[T] = js.native
def y[Datum >: T](y: js.Function1[Datum, Double]): Line[T] = js.native
. . .
}
The example
import scala.scalajs.js
import scala.scalajs.js._
import org.scalajs.dom
import org.singlespaced.d3js.Ops._
import org.singlespaced.d3js.d3
object LineGraphExample extends JSApp {
trait Data {
var date: Date
var close: Double
}
def main(): Unit = {
object margin {
val top = 30
val right = 20
val bottom = 30
val left = 50
}
val width = 600 - margin.left - margin.right
val height = 270 - margin.top - margin.bottom
val parseDate = d3.time.format("%d-%b-%y").parse(_)
val x = d3.time.scale().range(Array(0, width))
val y = d3.scale.linear().range(Array(height, 0))
val xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5)
val yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5)
val valueline = d3.svg.line[Data]()
.x { (d: Data) => x(d.date) }
.y { (d: Data) => y(d.close) }
val svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")")
d3.csv("data.csv", { (error: Any, rawdata: Array[Dictionary[String]]) =>
val data: Array[Data] = rawdata.map { record =>
object d extends Data {
var date = parseDate(record("date"))
var close = record("close").toDouble
}
d
}
val Tuple2(minx, maxx) = d3.extent(data.map(_.date))
x.domain(Array(minx, maxx))
val maxy = d3.max(data.map(_.close))
y.domain(Array(0, maxy))
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data))
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
()
})
}
}
In pure JS, I can use
var xScale = d3.time.scale()
.range([419, 1210])
.domain([new Date(2015, 10, 1), new Date(2015, 12, 30)]);
var xAxis = d3.svg
.axis()
.scale(xScale)
.orient("bottom");
var zoom = d3.behavior
.zoom()
.x(xScale)
.on("zoom", function() {
// Redraw the x-axis
...
});
But when moving to Scala with your facade, xScale
has type of timeModule.Scale[Double, Double]
, meanwhile d3.behavior.zoom().x()
requires a behavior.zoom.Scale
.
Can you help me?
Hi there - thanks for the great job, I enjoyed it a lot already. Any reason why partition got dropped in the moduled rewrite? Which other layout is the best example to mimic so I can add it?
I was trying to run an example of it in the workbench-example-app autowire skeleton. I couldn't run it, I got different errors. Would you please add example that runs with the lib?
Currently, I can't use the latest version of D3 (3.5.12 at the moment):
// built.sbt
jsDependencies += "org.webjars" % "d3js" % "3.5.12" / "3.5.12/d3.min.js"
The compilation tells me about the conflict that the current facade is compatible with D3 3.5.6 only.
To make the API useful
svg.selectAll[Node](".node").data(force.nodes(), (d: Node, i: Int) => d.id)
should compile, but (d: Node, i: Int) => d.id
is not lifted or recognised as a js.thisFunction2
I've read http://stackoverflow.com/questions/27577368/d3-js-key-function-running-twice-on-simple-selector-array-combo. The current definition might be technically spoken correct but of little use if you don't overload it as to accept the above. Instantiating from js.thisFunction2
may be useful to mimic this
binding from js in scala. But to pass a closure as argument, it just makes things complex. The point made on stackoverflow is that the passed function should work both on the selected data as well on the new data.
In case of
svg.selectAll[Node](".node").data[N1,N2](force.nodes(): js.Array[N1] , (d: N2, i: Int) => d.id)
The type relation should be: N2 >: N1
and N2 >: Node
or when N2 == Node
then N1 <: Node
as such I would propose both, next to the thisFunction based API if you like
def data[NewDatum <: Datum](data: js.Array[NewDatum], key: js.Function1[Datum, String]): Update[NewDatum] = js.native
def data[NewDatum <: Datum](data: js.Array[NewDatum], key: js.Function2[Datum, Int, String]): Update[NewDatum] = js.native
First and foremost: Thanks for you library!
When playing around with it, I ran into an issue with the update selection returned by selectAll(..).data(..)
. Given the following html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<!-- Include JavaScript dependencies -->
<script type="text/javascript" src="./target/scala-2.11/scala-js-playground-jsdeps.js"></script>
<!-- Include Scala.js compiled code -->
<script type="text/javascript" src="./target/scala-2.11/scala-js-playground-fastopt.js"></script>
<!-- Run JSApp -->
<script type="text/javascript" src="./target/scala-2.11/scala-js-playground-launcher.js"></script>
<svg>
<text y="50">Hello</text>
<text y="100">World</text>
</svg>
</body>
</html>
and the following Scala.js:
import org.singlespaced.d3js.d3
import scala.scalajs.js
object DemoApp extends js.JSApp {
def main(): Unit = {
val data = js.Array("0")
d3.select("body svg").selectAll("text").data(data).style("fill", "orange")
}
}
I would expect "Hello" to be rendered in orange (the array contains one datum, so there should be one match in the update selection). However, the selection is empty and both text elements appear in black.
The equivalent JS would be:
d3.select("body svg").selectAll("text").data(["0"]).style("fill", "orange")
If I manually enter it in the Chrome Console, everything works as expected.
The BaseSelection#data method's key argument has type js.ThisFunction2[Datum|NewDatum,js.UndefOr[NewDatum], Int, String]
But the 'this' argument of the key function would be a DOM node or an Array according to d3 documentation here:
https://github.com/mbostock/d3/wiki/Selections#data
Also, the documentation does not mention that the 'd' argument could be undefined.
Perhaps a plain js.Function2[Datum, Int, String] would be better here? It would certainly be easier to use. Supplying a function with the current signature is a pain.
I'm trying to implement this: http://bl.ocks.org/mbostock/3883195
val margin = Margin(top = 20, right = 20, bottom = 30, left = 50)
val width = 960 - margin.left - margin.right
val height = 500 - margin.top - margin.bottom
val parseDate = d3.time.format("%d-%b-%y").parse(_)
val x = d3.time.scale().range(js.Array(0, width))
val y = d3.scale.linear().range(js.Array(height, 0))
val xAxis = d3.svg.axis().scale(x).orient("bottom")
val yAxis = d3.svg.axis().scale(y).orient("left")
val xf: js.Function2[DataArray, Int, Double] = (d: DataArray, i: Int) => x(d(i).date)
val yf: js.Function2[DataArray, Int, Double] = (d: DataArray, i: Int) => x(d(i).close)
val area = d3.svg.area()
.x(xf)
.y0(height)
.y1(yf)
val svg: Selection[EventTarget] = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("apple.tsv", (error: Any, rawdata: Array[Dictionary[String]]) => {
val data: Array[Data] = rawdata.map { line =>
object d extends Data {
var date = parseDate(line("date"))
var close = line("close").toDouble
}
d
}
val Tuple2(minX, maxX) = d3.extent(data.map(_.date))
x.domain(Array(minX, maxX))
val maxY = d3.max(data.map(_.close))
y.domain(Array(0, maxY))
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area)
but last line does not compile since
overloaded method value attr with alternatives:
[error] (name: String,value: scala.scalajs.js.Function3[scala.scalajs.js.Array[Data],Int,scala.scalajs.js.UndefOr[Int],scala.scalajs.js.|[scala.scalajs.js.|[Double,String],Boolean]])org.singlespaced.d3js.selection.Update[scala.scalajs.js.Array[Data]] <and>
[error] (name: String,value: org.singlespaced.d3js.d3.Primitive)org.singlespaced.d3js.selection.Update[scala.scalajs.js.Array[Data]]
[error] cannot be applied to (String, org.singlespaced.d3js.svg.Area[DataArray])
[error] .attr("d", area)
[error] ^
As error said, there's no signature that allows to pass an Area
instance.
By the way, I'll do a pull request to put this into examples.
I followed the simple code taken from AxisTest.scala
val x = d3.time.scale().range(js.Array(0, 1000))
val xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(5)
d3.select("p").append("svg").call(xAxis)
but the compiler/IDE reports that call
expects a js.Function
, not Axis
.
In the .each function of d3, often code is executed as d3.select(this)
. However as far as my knowledge goes, in scala.js d3 it is not possible to currently perform this action.
One possibility would be using the ThisFunction from scala.js, this would allow the usage of this as an parameter.
Currently the library only supports scala-js 0.6 which is already in EOL. Would it be possible to upgrade to scala-js 1.7.1 (the latest).
Ideally with support for Scala 3.x.
I can help if necessary.
Hello!
I am trying to use the scala JS D3 façade within a project, and i am facing an issue, apparently coming from the scala-js-d3:compile build phase :
Ambiguous reference to a JS library: 3.5.17/d3.min.js
[error] Possible paths found on the classpath:
[error] - META-INF/resources/webjars/d3/3.5.17/d3.min.js
[error] - META-INF/resources/webjars/d3js/3.5.17/d3.min.js
[error] originating from: scala-js-d3:compile
My build.sbt part associated with scala js dependencies is given in the following :
enablePlugins(ScalaJSPlugin)
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.9.6"
scalaJSUseMainModuleInitializer := true
libraryDependencies += "org.singlespaced" %%% "scalajs-d3" % "0.3.4"
Should the façade build.sbt file contains the following way to load d3js, to avoid ambiguity ?
jsDependencies += "org.webjars" % "d3js" % "3.5.17" / "d3js/3.5.17/d3.min.js"
instead of
jsDependencies += "org.webjars" % "d3js" % "3.5.17" / "3.5.17/d3.min.js"
Thanks in advance!
I would like to add text values in the barchart similar to the following two examples:
https://alignedleft.com/tutorials/d3/making-a-bar-chart
https://stackoverflow.com/questions/18057917/adding-label-on-a-d3-bar-chart
here is my example source code:
val graphHeight = 450
//The width of each bar.
val barWidth = 80
//The distance between each bar.
val barSeparation = 10
//The maximum value of the data.
val maxData = 50
//The actual horizontal distance from drawing one bar rectangle to drawing the next.
val horizontalBarDistance = barWidth + barSeparation
//The value to multiply each bar's value by to get its height.
val barHeightMultiplier = graphHeight / maxData;
//Color for start
val color = d3.rgb("DarkSlateBlue")
val my_data = js.Array(8, 22, 31, 36, 48, 17, 25)
val rectXFun = (d: Int, i: Int) => i * horizontalBarDistance
val rectYFun = (d: Int) => graphHeight - d * barHeightMultiplier
val rectXFun2 = (d: Int, i: Int) => i * horizontalBarDistance +5
val rectYFun2 = (d: Int) => graphHeight - d * barHeightMultiplier +15
val rectHeightFun = (d: Int) => d * barHeightMultiplier
val rectColorFun = (d: Int, i: Int) => color.brighter(i * 0.5).toString
val svg = d3.select("#d3Container2").append("svg").attr("width", "100%").attr("height", "450px")
val sel = svg.selectAll("rect").data(my_data)
val sel2 = sel.enter()
.append("rect")
.attr("x", rectXFun)
.attr("y", rectYFun)
.attr("width", barWidth)
.attr("height", rectHeightFun)
.style("fill", rectColorFun)
sel2.append("text").
text((d: Int,i: Int) => d.toString)
.attr("x", rectXFun2)
.attr("y", rectYFun2)
.style("fill","black")
.style("text-anchor", "middle")
val svg_text =svg.selectAll("text")
.data(my_data)
.enter()
.append("text")
.text((d: Int,i: Int) => d.toString)
.attr("x", rectXFun2)
.attr("y", rectYFun2)
.style("fill","black")
.style("text-anchor", "middle")
Both adding text to the rectangle and adding text to the svg seem to fail.
Thanks in advance
The current implementation requires a Function3
as key function.
def data[NewDatum](data: js.Array[NewDatum], key:js.Function3[NewDatum, Int, Int, String]): Update[NewDatum] = js.native
This implementation gives execution errors when running with d3.js (master 20 May 2015 > tag 3.5.9)
complaining that the 3 argument is undefined iso Int.
The d3.js documentation only describes a function with one or two arguments: datum and index.
This can be corrected :
def data[NewDatum](data: js.Array[NewDatum], key: js.Function2[NewDatum, Int, String]): Update[NewDatum] = js.native
Thanks for the fine work. I'm curious as to what are the best examples for, or bases for, writing tests for the use of this facade with uTest. I see some tests in src/test. But I don't see anything other than a skeleton for the example.
Thanks so much.
Hi, could you please publish 0.3.4 for the other scala-versions to maven?
https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.singlespaced%22
Thank you!
Has this project been updated to support latest 4.x d3js release?
Hi,
I'm trying to make this code work but it does not compile
val line = d3.svg.line.radial().interpolate("basis").tension(0).radius(100)
Is something wrong in what I wrote or simply this function is not supported yet? I saw the a radial trait is present in code but it does not seem connected to the rest of the code.
Hello,
I'm trying to adapt an example to render a simple HTML-table from here.
Here is my code.
Code compiled successful, but when page is loading, I have the error:
Uncaught scala.scalajs.runtime.UndefinedBehaviorError: An undefined behavior was detected: undefined is not an instance of java.lang.Integer scalajsenv.js:192
May be I do something wrong. For example, I cannot implement a text()
method. Take a look, please.
Thanks
Is there a snapshot of this library on a sbt-accessible repository?
Hello,
I am trying to "port" this Scatterplot example (http://bl.ocks.org/weiglemc/6185069) to scala-js-d3
Unfortunately, I am having some trouble understanding the proper use of DatumFunction. Please see my code, which I just added to your example project:
https://gist.github.com/littler00t/943b1cd79c7922229dff
See lines 121ff : I understand the return type of DatumFunction should be String and the "Datum" type is also String.
Nevertheless, I get compilation error:
[error] ... scala-js-d3-example-app/src/main/scala/example/ScalaJSExample.scala:121: type mismatch;
[error] found : (String, Int, Int) => String
[error] required: legend.DatumFunction[org.singlespaced.d3js.d3.Primitive]
[error] (which expands to) scala.scalajs.js.Function3[String,Int,Int,scala.scalajs.js.|[scala.scalajs.js.|[Double,String],Boolean]]
[error] val transformLegend: legend.DatumFunction[d3.Primitive] = { (d: String, x: Int, y: Int) => "translate(0," + x * 20 + ")" }
[error] ^
[error] ... scala-js-d3-example-app/src/main/scala/example/ScalaJSExample.scala:124: type mismatch;
[error] found : (String, Int, Int) => String
[error] required: scala.scalajs.js.Function3[String,Int,Int,org.singlespaced.d3js.d3.Primitive]
[error] (which expands to) scala.scalajs.js.Function3[String,Int,Int,scala.scalajs.js.|[scala.scalajs.js.|[Double,String],Boolean]]
[error] val transformLegend2: scala.scalajs.js.Function3[String, Int, Int, d3.Primitive] = { (d: String, x: Int, y: Int) => "translate(0," + x * 20 + ")" }
[error] ^
[error] two errors found
[error] (compile:compileIncremental) Compilation failed
Would be great if you could help. It's probably just a minor issue coming from my lack of understanding so any help is greatly appreciated :-) If I finish the scatterplot, maybe it can serve as an example on how to handle this kind of stuff.
Thanks
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.