├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.sbt
├── project
└── assembly.sbt
└── src
├── main
└── scala
│ └── org
│ └── viz
│ └── lightning
│ ├── Lightning.scala
│ ├── Visualization.scala
│ ├── package.scala
│ └── types
│ ├── Base.scala
│ ├── Linked.scala
│ ├── Make.scala
│ ├── Plots.scala
│ ├── Settings.scala
│ ├── Style.scala
│ ├── Three.scala
│ └── Utils.scala
└── test
└── scala
└── org
└── viz
└── lightning
├── LightningImplicitsSuite.scala
├── LightningPlotsSuite.scala
├── LightningStreamingSuite.scala
├── LightningSuite.scala
└── LightningThreeSuite.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # sbt specific
5 | .cache/
6 | .history/
7 | .lib/
8 | dist/*
9 | target/
10 | lib_managed/
11 | src_managed/
12 | project/boot/
13 | project/plugins/project/
14 |
15 | # Scala-IDE specific
16 | .scala_dependencies
17 | .worksheet
18 | *.xml
19 | .idea
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 |
3 | scala:
4 | - 2.10.3
5 |
6 | script:
7 | - sbt ++$TRAVIS_SCALA_VERSION test
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Jeremy Freeman 2015
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lightning scala client
2 | Scala client for the Lightning data visualization server (WIP)
3 |
4 | [](https://travis-ci.org/lightning-viz/lightning-scala)
5 |
6 | ## installation
7 | Build the project using `sbt` and the [assembly](https://github.com/sbt/sbt-assembly) plugin
8 | ```
9 | sbt assembly
10 | ```
11 | To then use lightning in the scala REPL, just add the jar to your classpath (here we are assuming you launch from inside `lightning-scala`)
12 | ```
13 | scala -classpath target/scala-2.10/lightning-scala-assembly-0.1.0.jar
14 | ```
15 |
16 | ## usage
17 |
18 | ### creating a new session
19 | ```
20 | import org.viz.lightning._
21 |
22 | val lgn = Lightning(host="http://my-lightning-instance.herokuapp.com")
23 |
24 | lgn.createSession()
25 | lgn.createSession("provide an optional session name")
26 | ```
27 |
28 | ### creating a visualization
29 | Methods are available for the default visualization types included with Lightning
30 | ```
31 | lgn.line(Array(Array(1.0,1.0,2.0,3.0,9.0,20.0)))
32 | lgn.scatter(Array(1.0,2.0,3.0), Array(1.0,1.5,5.0))
33 | ```
34 |
35 | ### setting options
36 | Visualizations can be customized through optional parameters
37 | ```
38 | lgn.line(Array(Array(1.0,1.0,2.0),Array(3.0,9.0,20.0)), label=Array(1,2))
39 | lgn.scatter(Array(1.0,2.0,3.0), Array(1.0,1.5,5.0), label=Array(1,2,3))
40 | ```
41 |
42 | ### using a custom plot
43 | For any other plot type, just specify by name, and provide the data as a `Map`
44 | ```
45 | lgn.plot("line", Map("series" -> List(1,1,2,3,9,20)))
46 | ```
47 | This is especially useful when working with custom plot types
48 |
49 | ## tests
50 | Run the unit tests using `sbt` by calling
51 |
52 | ```
53 | sbt test
54 | ```
55 |
56 | You can specify a name to run a subset of the tests
57 |
58 | ```
59 | sbt "test-only *LightningPlotsSuite*"
60 | ```
61 |
62 | The tests require that a local lightning server is running on `http://localhost:3000`
63 |
64 | ## todo
65 | The following components need to be added
66 | - Add updating and appending
67 | - Add ability to post images
68 | - Add image-related visualizations (image, gallery, volume)
69 | - Add streaming visualizations (scatter, line)
70 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | name := "lightning-scala"
2 |
3 | version := "0.1.0-SNAPSHOT"
4 |
5 | scalaVersion := "2.10.3"
6 |
7 | homepage := Some(url("http://lightning-viz.org"))
8 |
9 | description := "Scala client for interactive data visualization with Lightning."
10 |
11 | organization := "org.lightning-viz"
12 |
13 | organizationName := "lightning-viz"
14 |
15 | organizationHomepage := Some(url("https://github.com/lightning-viz"))
16 |
17 | libraryDependencies += "org.scalaj" %% "scalaj-http" % "1.1.4"
18 |
19 | libraryDependencies += "org.json4s" %% "json4s-native" % "3.2.9"
20 |
21 | libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.1"
22 |
23 | publishMavenStyle := true
24 |
25 | publishTo := {
26 | val nexus = "https://oss.sonatype.org/"
27 | if (isSnapshot.value)
28 | Some("snapshots" at nexus + "content/repositories/snapshots")
29 | else
30 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
31 | }
32 |
33 | publishArtifact in Test := false
34 |
35 | pomIncludeRepository := { _ => false }
36 |
37 | pomExtra := (
38 | http://lightning-viz.org
39 |
40 |
41 | MIT
42 | http://opensource.org/licenses/MIT
43 | repo
44 |
45 |
46 |
47 | git@github.com:lightning-viz/lightning-scala.git
48 | scm:git:git@github.com:lightning-viz/lightning-scala.git
49 |
50 |
51 |
52 | freeman-lab
53 | Jeremy Freeman
54 | http://github.com/freeman-lab
55 |
56 |
57 | mathisonian
58 | Matthew Conlen
59 | http://github.com/mathisonian
60 |
61 | )
62 |
--------------------------------------------------------------------------------
/project/assembly.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/Lightning.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning
2 |
3 | import org.viz.lightning.types.{Three, Plots, Linked}
4 |
5 | import org.json4s._
6 | import org.json4s.native.JsonMethods._
7 | import org.json4s.native.Serialization
8 | import scalaj.http._
9 |
10 | class Lightning (var host: String) extends Plots with Three with Linked {
11 |
12 | var session: String = ""
13 | var auth: Option[(String, String)] = None
14 | var isNotebook: Boolean = false
15 |
16 | def this() = this("http://localhost:3000")
17 |
18 | def createSession(sessionName: String = "") {
19 |
20 | val url = host + "/sessions/"
21 |
22 | implicit val formats = DefaultFormats
23 |
24 | val payload = sessionName match {
25 | case "" => "{}"
26 | case _ => Serialization.write( Map("name" -> sessionName) )
27 | }
28 |
29 | val id = post(url, payload)
30 |
31 | session = id
32 |
33 | }
34 |
35 | def plot(name: String, data: Map[String, Any]): Visualization = {
36 |
37 | this.checkSession()
38 |
39 | val url = host + "/sessions/" + session + "/visualizations/"
40 |
41 | val id = postData(url, data, name)
42 |
43 | new Visualization(this, id, name)
44 |
45 | }
46 |
47 | def useSession(id: String): this.type = {
48 | this.session = id
49 | this
50 | }
51 |
52 | def useHost(host: String): this.type = {
53 | this.host = host
54 | this
55 | }
56 |
57 | def enableNotebook(): this.type = {
58 | this.isNotebook = true
59 | //implicit val HTMLViz = org.refptr.iscala.display.HTMLDisplay[Visualization] { viz =>
60 | // viz.getHTML
61 | //}
62 | this
63 | }
64 |
65 | def checkSession() {
66 | if (session == "") {
67 | this.createSession()
68 | }
69 | }
70 |
71 | def post(url: String, payload: String, method: String = "POST"): String = {
72 |
73 | var request = Http(url).postData(payload).method(method)
74 | .header("content-type", "application/json")
75 | .header("accept", "text/plain")
76 |
77 | if (auth.nonEmpty) {
78 | request = request.auth(auth.get._1, auth.get._2)
79 | }
80 |
81 | implicit val formats = DefaultFormats
82 |
83 | val response = request.asString
84 | response.body.toLowerCase match {
85 | case "unauthorized" => throw new Exception("Unauthorized. Check username and/or password.")
86 | case _ => {
87 | val json = parse(response.body)
88 | (json \ "id").extract[String]
89 | }
90 | }
91 | }
92 |
93 | def postData(url: String, data: Map[String, Any], name: String, method: String = "POST"): String = {
94 |
95 | implicit val formats = DefaultFormats
96 |
97 | val blob = Map("data" -> data, "type" -> name)
98 | val payload = Serialization.write(blob)
99 | post(url, payload, method)
100 | }
101 |
102 | }
103 |
104 | object Lightning {
105 |
106 | def apply(host: String = ""): Lightning = {
107 | host match {
108 | case "" => new Lightning()
109 | case _ => new Lightning(host)
110 | }
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/Visualization.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning
2 |
3 | import org.json4s.DefaultFormats
4 | import org.json4s.native.Serialization
5 | import scala.language.dynamics
6 | import scalaj.http._
7 |
8 | class Visualization(val lgn: Lightning, val id: String, val name: String) {
9 |
10 | if (lgn.isNotebook) {
11 | //implicit val HTMLViz = org.refptr.iscala.display.HTMLDisplay[Visualization] { viz =>
12 | // viz.getHTML
13 | //}
14 | //org.refptr.iscala.display.display_html(this)
15 | }
16 |
17 | def formatURL(url: String): String = {
18 | val out = url.last.toString match {
19 | case "/" => url
20 | case _ => url + "/"
21 | }
22 | out + "?host=" + lgn.host
23 | }
24 |
25 | def getPermalinkURL: String = {
26 | lgn.host + "/visualizations/" + id
27 | }
28 |
29 | def getEmbedLink: String = {
30 | formatURL(this.getPermalinkURL + "/embed")
31 | }
32 |
33 | def getIframeLink: String = {
34 | formatURL(this.getPermalinkURL + "/iframe")
35 | }
36 |
37 | def getPymLink: String = {
38 | formatURL(this.getPermalinkURL + "/pym")
39 | }
40 |
41 | def getDataLink: String = {
42 | formatURL(lgn.host + "/sessions/" + lgn.session + "/visualizations/" + id + "/data/")
43 | }
44 |
45 | def getHTML: String = {
46 | val url = getEmbedLink
47 | var request = Http(url).method("GET")
48 | if (lgn.auth.nonEmpty) {
49 | request = request.auth(lgn.auth.get._1, lgn.auth.get._2)
50 | }
51 | request.asString.body
52 | }
53 |
54 | def append(payload: Map[String, Any]) : Visualization = {
55 | val url = lgn.host + "/sessions/" + lgn.session + "/visualizations/" + this.id + "/data/"
56 | implicit val formats = DefaultFormats
57 | val blob = Map("data" -> payload)
58 | lgn.post(url, Serialization.write(blob))
59 | this
60 | }
61 |
62 | def getPublicLink: String = {
63 | this.getPermalinkURL + "/public/"
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/package.scala:
--------------------------------------------------------------------------------
1 | package org.viz
2 |
3 | package object lightning {
4 |
5 | implicit def SingletonInt(value: Int): Array[Double] =
6 | Array(value.toDouble)
7 |
8 | implicit def SingletonDouble(value: Double): Array[Double] =
9 | Array(value)
10 |
11 | implicit def IntToDouble(value: Array[Int]): Array[Double] =
12 | value.map(_.toDouble)
13 |
14 | implicit def NestedIntToDouble(value: Array[Array[Int]]): Array[Array[Double]] =
15 | value.map(_.map(_.toDouble))
16 |
17 | implicit def FlatToNestedInt(value : Array[Int]): Array[Array[Double]] =
18 | Array(value.map(_.toDouble))
19 |
20 | implicit def FlatToNestedDouble(value : Array[Double]): Array[Array[Double]] =
21 | Array(value)
22 |
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/types/Base.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning.types
2 |
3 | import org.viz.lightning.Visualization
4 |
5 | trait Base {
6 |
7 | def plot(name: String, payload: Map[String, Any]): Visualization
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/types/Linked.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning.types
2 |
3 | import org.viz.lightning.Visualization
4 |
5 | trait Linked extends Base {
6 |
7 | /**
8 | * Linked scatter and line plot
9 | */
10 | def scatterLine(x: Array[Double],
11 | y: Array[Double],
12 | series: Array[Array[Double]],
13 | label: Array[Int] = Array[Int](),
14 | size: Array[Double] = Array[Double](),
15 | alpha: Array[Double] = Array[Double]()): Visualization = {
16 |
17 | val points = Utils.getPoints(x, y)
18 | val data = Map("series" -> series.toList, "points" -> points.toList)
19 |
20 | val settings = new Settings()
21 | .append(List(Label(label), Size(size), Alpha(alpha)))
22 |
23 | plot("scatter-line", data ++ settings.toMap)
24 |
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/types/Make.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning.types
2 |
3 | import scala.util.Random
4 |
5 | object Make {
6 |
7 | def line(t: Int): Array[Double] = {
8 | Array.fill(t)(Random.nextDouble())
9 | }
10 |
11 | def lineInt(t: Int): Array[Int] = {
12 | Array.fill(t)(Random.nextInt(100))
13 | }
14 |
15 | def series(n: Int, t: Int): Array[Array[Double]] = {
16 | Array.fill(n)(Array.fill(t)(Random.nextDouble()))
17 | }
18 |
19 | def seriesInt(n: Int, t: Int): Array[Array[Int]] = {
20 | Array.fill(n)(Array.fill(t)(Random.nextInt(100)))
21 | }
22 |
23 | def labels(n: Int): Array[Int] = {
24 | Array.fill(n)(Random.nextInt(5))
25 | }
26 |
27 | def sizes(n: Int, scale: Double = 20, min: Double = 5): Array[Double] = {
28 | Array.fill(n)(Random.nextFloat() * scale + min)
29 | }
30 |
31 | def alphas(n: Int, min: Double = 0.3): Array[Double] = {
32 | Array.fill(n){
33 | val rnd = Random.nextDouble() + 0.3
34 | if (rnd > 1.0) {
35 | 1.0
36 | } else {
37 | rnd
38 | }
39 | }
40 | }
41 |
42 | def values(n: Int, scale: Double = 1): Array[Double] = {
43 | Array.fill(n)(Random.nextDouble() * scale)
44 | }
45 |
46 | def gaussian(n: Int, scale: Double = 1): Array[Double] = {
47 | Array.fill(n)(Random.nextGaussian() * scale)
48 | }
49 |
50 | def matrix(n: Int): Array[Array[Double]] = {
51 | Array.fill(n)(Array.fill(n)(Random.nextDouble()))
52 | }
53 |
54 | def sparseLinks(n: Int, threshold: Double = 0.9): Array[Array[Int]] = {
55 | val conn = Array.fill(n)(Array.fill(n){
56 | val rnd = Random.nextDouble()
57 | if (rnd > threshold) {
58 | rnd
59 | } else {
60 | 0.0
61 | }
62 | })
63 | conn.zipWithIndex
64 | .flatMap{case (row, i) => row.zipWithIndex
65 | .filter{case (x, j) => x != 0}.map{case (x, j) => Array(i, j, x).map(_.toInt)}}
66 | }
67 |
68 | def sparseMatrix(n: Int, threshold: Double = 0.9): Array[Array[Double]] = {
69 | Array.fill(n)(Array.fill(n){
70 | val rnd = Random.nextDouble()
71 | if (rnd > threshold) {
72 | rnd
73 | } else {
74 | 0.0
75 | }
76 | })
77 | }
78 |
79 | }
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/types/Plots.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning.types
2 |
3 | import org.viz.lightning.Visualization
4 |
5 | trait Plots extends Base {
6 |
7 | /**
8 | * One or more one-dimensional series data as lines.
9 | */
10 | def line(series: Array[Array[Double]],
11 | label: Array[Int] = Array[Int](),
12 | size: Array[Double] = Array[Double](),
13 | alpha: Array[Double] = Array[Double](),
14 | xaxis: String = "",
15 | yaxis: String = ""): Visualization = {
16 |
17 | val data = Map("series" -> series.toList)
18 |
19 | val settings = new Settings()
20 | .append(List(Label(label), Size(size), Alpha(alpha)))
21 | .append(List(Axis(xaxis, "xaxis"), Axis(yaxis, "yaxis")))
22 |
23 | plot("line", data ++ settings.toMap)
24 |
25 | }
26 |
27 | /**
28 | * One or more one-dimensional series data streamed as lines.
29 | */
30 | def lineStreaming(series: Array[Array[Double]],
31 | size: Array[Double] = Array[Double](),
32 | color: Array[Array[Double]] = Array[Array[Double]](),
33 | alpha: Array[Double] = Array[Double] (),
34 | label: Array[Int] = Array[Int](),
35 | xaxis: String = "",
36 | yaxis: String = "",
37 | viz: Visualization = null): Visualization = {
38 |
39 | val data = Map("series" -> series.toList, "color" -> color.toList)
40 |
41 | val settings = new Settings()
42 | .append(List(Label(label), Size(size), Alpha(alpha)))
43 | .append(List(Axis(xaxis, "xaxis"), Axis(yaxis, "yaxis")))
44 |
45 | val payload = data ++ settings.toMap
46 |
47 | if (viz == null) {
48 | plot("line-streaming", payload)
49 |
50 | } else {
51 | viz.append(payload)
52 | }
53 |
54 | }
55 |
56 | /**
57 | * Force-directed network from connectivity.
58 | */
59 | def force(conn: Array[Array[Double]],
60 | label: Array[Int] = Array[Int](),
61 | value: Array[Double] = Array[Double](),
62 | colormap: String = "",
63 | size: Array[Double] = Array[Double]()): Visualization = {
64 |
65 | val links = Utils.getLinks(conn)
66 | val nodes = Utils.getNodes(conn)
67 |
68 | val data = Map("links" -> links.toList, "nodes" -> nodes.toList)
69 |
70 | val settings = new Settings()
71 | .append(List(Label(label), Value(value), Colormap(colormap), Size(size)))
72 |
73 | plot("force", data ++ settings.toMap)
74 |
75 | }
76 |
77 | /**
78 | * Two-dimensional data as points.
79 | */
80 | def scatterStreaming(x: Array[Double],
81 | y: Array[Double],
82 | label: Array[Int] = Array[Int](),
83 | value: Array[Double] = Array[Double](),
84 | colormap: String = "",
85 | size: Array[Double] = Array[Double](),
86 | alpha: Array[Double] = Array[Double](),
87 | xaxis: String = "",
88 | yaxis: String = "",
89 | viz: Visualization = null): Visualization = {
90 |
91 | val points = Utils.getPoints(x, y)
92 | val data = Map("points" -> points.toList)
93 |
94 | val settings = new Settings()
95 | .append(List(Label(label), Value(value), Colormap(colormap), Size(size), Alpha(alpha)))
96 | .append(List(Axis(xaxis, "xaxis"), Axis(yaxis, "yaxis")))
97 |
98 | val payload = data ++ settings.toMap
99 |
100 | if (viz == null) {
101 | plot("scatter-streaming", payload)
102 | } else {
103 | viz.append(payload)
104 | }
105 |
106 | }
107 |
108 | /**
109 | * Streaming two-dimensional data as points.
110 | */
111 | def scatter(x: Array[Double],
112 | y: Array[Double],
113 | label: Array[Int] = Array[Int](),
114 | value: Array[Double] = Array[Double](),
115 | group: Array[Int] = Array[Int](),
116 | colormap: String = "",
117 | size: Array[Double] = Array[Double](),
118 | alpha: Array[Double] = Array[Double](),
119 | xaxis: String = "",
120 | yaxis: String = ""): Visualization = {
121 |
122 | val points = Utils.getPoints(x, y)
123 | val data = Map("points" -> points.toList)
124 |
125 | val settings = new Settings()
126 | .append(List(Label(label), Value(value), Colormap(colormap), Size(size), Alpha(alpha) , Group(group) ))
127 | .append(List(Axis(xaxis, "xaxis"), Axis(yaxis, "yaxis")))
128 |
129 | plot("scatter", data ++ settings.toMap)
130 | }
131 |
132 |
133 | /**
134 | * Dense matrix or a table as a heat map.
135 | */
136 | def matrix(matrix: Array[Array[Double]],
137 | colormap: String = "",
138 | rowLabels: Array[String] = Array[String](),
139 | colLabels: Array[String] = Array[String]()): Visualization = {
140 |
141 | val data = Map("matrix" -> matrix.toList, "rowLabels" -> rowLabels.toList,"columnLabels" -> colLabels.toList)
142 |
143 | val settings = new Settings()
144 | .append(Colormap(colormap))
145 |
146 | plot("matrix", data ++ settings.toMap)
147 | }
148 |
149 | /**
150 | * Sparse adjacency matrix with labels from connectivity.
151 | */
152 | def adjacency(conn: Array[Array[Double]],
153 | label: Array[Int] = Array[Int](),
154 | labels: Array[String] = Array[String]()): Visualization = {
155 |
156 | val links = Utils.getLinks(conn)
157 | val nodes = Utils.getNodes(conn)
158 |
159 | val data = Map("links" -> links.toList, "nodes" -> nodes.toList, "labels" -> labels.toList)
160 |
161 | val settings = new Settings()
162 | .append(Label(label))
163 |
164 | plot("adjacency", data ++ settings.toMap)
165 |
166 | }
167 |
168 | /**
169 | * Chloropleth map of the world or united states.
170 | */
171 | def map(regions: Array[String],
172 | values: Array[Double],
173 | colormap: String = ""): Visualization = {
174 |
175 | if (!(regions.forall(s => s.length == 2) | regions.forall(s => s.length == 3))) {
176 | throw new IllegalArgumentException("Region names must have 2 or 3 characters")
177 | }
178 |
179 | val data = Map("regions" -> regions.toList, "values" -> values.toList)
180 |
181 | val settings = new Settings()
182 | .append(Colormap(colormap))
183 |
184 | plot("map", data ++ settings.toMap)
185 |
186 | }
187 |
188 | /**
189 | * Node-link graph from spatial points and connectivity.
190 | */
191 | def graph(x: Array[Double],
192 | y: Array[Double],
193 | conn: Array[Array[Double]],
194 | label: Array[Int] = Array[Int](),
195 | value: Array[Double] = Array[Double](),
196 | colormap: String = "",
197 | size: Array[Double] = Array[Double]()): Visualization = {
198 |
199 | val links = Utils.getLinks(conn)
200 | val nodes = Utils.getPoints(x, y)
201 | val data = Map("links" -> links, "nodes" -> nodes.toList)
202 |
203 | val settings = new Settings()
204 | .append(List(Label(label), Value(value), Colormap(colormap), Size(size)))
205 |
206 | plot("graph", data ++ settings.toMap)
207 |
208 | }
209 |
210 | /**
211 | * Node-link graph with bundled edges from spatial points and connectivity.
212 | */
213 | def graphBundled(x: Array[Double],
214 | y: Array[Double],
215 | conn: Array[Array[Double]],
216 | label: Array[Int] = Array[Int](),
217 | value: Array[Double] = Array[Double](),
218 | colormap: String = "",
219 | size: Array[Double] = Array[Double]()): Visualization = {
220 |
221 | val links = Utils.getLinks(conn)
222 | val nodes = Utils.getPoints(x, y)
223 | val data = Map("links" -> links, "nodes" -> nodes.toList)
224 |
225 | val settings = new Settings()
226 | .append(List(Label(label), Value(value), Colormap(colormap), Size(size)))
227 |
228 | plot("graph-bundled", data ++ settings.toMap)
229 |
230 | }
231 |
232 | }
233 |
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/types/Settings.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning.types
2 |
3 | class Settings(var map: Map[String, Any]) {
4 |
5 | def this() = this(Map[String, Any]())
6 |
7 | def append(style: Style): this.type = {
8 | if (style.defined) {
9 | style.validate
10 | map += style.name -> style.contents
11 | }
12 | this
13 | }
14 |
15 | def append(styles: List[Style]): this.type = {
16 | styles.foreach{ style =>
17 | this.append(style)
18 | }
19 | this
20 | }
21 |
22 | def toMap: Map[String, Any] = {
23 | map
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/types/Style.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning.types
2 |
3 | trait Style {
4 |
5 | def name: String
6 | def defined: Boolean
7 | def validate = true: Boolean
8 | def contents: Any
9 |
10 | }
11 |
12 | case class Label(label: Array[Int]) extends Style {
13 |
14 | def name = "label"
15 | def defined = label.length > 0
16 | def contents = label.toList
17 |
18 | }
19 |
20 | case class Value(value: Array[Double]) extends Style {
21 |
22 | def name = "value"
23 | def defined = value.length > 0
24 | def contents = value.toList
25 |
26 | }
27 |
28 | case class Colormap(colormap: String) extends Style {
29 |
30 | def name = "colormap"
31 | def defined = colormap != ""
32 | override def validate = {
33 | val valid = List("BrBG", "PiYG", "PRGn", "PuOr", "RdBu", "RdGy", "RdYlBu", "RdYlGn",
34 | "Spectral", "Blues", "BuGn", "BuPu", "GnBu", "Greens", "Greys", "Oranges", "OrRd",
35 | "PuBu", "PuBuGn", "PuRd", "Purples", "RdPu", "Reds", "YlGn", "YlGnBu", "YlOrBr",
36 | "YlOrRd", "Accent", "Dark2", "Paired", "Pastel1", "Pastel2",
37 | "Set1", "Set2", "Set3", "Lightning")
38 | if (!valid.contains(colormap)) {
39 | throw new IllegalArgumentException("Colormap must be one of %s, got %s".format(valid, colormap))
40 | } else {
41 | true
42 | }
43 | }
44 | def contents = colormap
45 |
46 | }
47 |
48 | case class Axis(axis: String, name: String) extends Style {
49 |
50 | def defined = axis != ""
51 | def contents = axis
52 |
53 | }
54 |
55 | case class Alpha(alpha: Array[Double]) extends Style {
56 |
57 | def name = "alpha"
58 | def defined = alpha.length != 0
59 | def contents = alpha.toList
60 |
61 | }
62 |
63 | case class Size(size: Array[Double]) extends Style {
64 |
65 | def name = "size"
66 | def defined = size.length != 0
67 | def contents = size.toList
68 |
69 | }
70 |
71 | case class Group(group: Array[Int]) extends Style {
72 |
73 | def name = "group"
74 | def defined = group.length > 0
75 | def contents = group.toList
76 |
77 | }
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/types/Three.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning.types
2 |
3 | import org.viz.lightning.Visualization
4 |
5 | trait Three extends Base {
6 |
7 | /**
8 | * Three-dimensional data as spheres.
9 | */
10 | def scatter3(x: Array[Double],
11 | y: Array[Double],
12 | z: Array[Double],
13 | label: Array[Int] = Array[Int](),
14 | size: Array[Double] = Array[Double]()): Visualization = {
15 |
16 | val points = (x, y, z).zipped.map((x, y, z) => List(x, y, z)).toList
17 | val data = Map("points" -> points)
18 |
19 | val settings = new Settings()
20 | .append(List(Label(label), Size(size)))
21 |
22 | plot("scatter-3", data ++ settings.toMap)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/scala/org/viz/lightning/types/Utils.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning.types
2 |
3 | object Utils {
4 |
5 | def getLinks(conn: Array[Array[Double]]): Array[Array[Double]] = {
6 |
7 | if (conn.length == conn(0).length) {
8 |
9 | conn.zipWithIndex
10 | .flatMap{case (row, i) => row.zipWithIndex
11 | .filter{case (x, j) => x != 0}.map{case (x, j) => Array(i, j, x)}}
12 |
13 | } else {
14 |
15 | conn(0).length match {
16 | case 2 => conn
17 | case 3 => conn.map(l => Array(l(0), l(1), 1.0))
18 | case _ => throw new IllegalArgumentException("Elements per link must be 2 or 3")
19 | }
20 |
21 | }
22 | }
23 |
24 | def getNodes(conn: Array[Array[Double]]): Array[Int] = {
25 |
26 | if (conn.length == conn(0).length) {
27 | Range(0, conn.length).toArray
28 | } else {
29 | val n = conn.map(l => l.max).max.toInt + 1
30 | Range(0, n).toArray
31 | }
32 |
33 | }
34 |
35 | def getPoints(x: Array[Double], y: Array[Double]): Array[Array[Double]] = {
36 | (x, y).zipped.map((x, y) => Array(x, y))
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/scala/org/viz/lightning/LightningImplicitsSuite.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning
2 |
3 | import org.scalatest.{BeforeAndAfterAll, FunSuite}
4 |
5 | import org.viz.lightning.types.Make
6 |
7 | class LightningImplicitsSuite extends FunSuite with BeforeAndAfterAll {
8 |
9 | var lgn: Lightning = _
10 |
11 | override def beforeAll() {
12 | lgn = Lightning("http://public.lightning-viz.org")
13 | lgn.createSession("test-implicits")
14 | }
15 |
16 | test("line (flat)") {
17 | lgn.line(series = Make.line(t = 50))
18 | }
19 |
20 | test("line (flat int)") {
21 | lgn.line(series = Make.lineInt(t = 50))
22 | }
23 |
24 | test("line (nested int)") {
25 | lgn.line(series = Make.seriesInt(n = 5, t = 50))
26 | }
27 |
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/scala/org/viz/lightning/LightningPlotsSuite.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning
2 |
3 | import org.scalatest.{BeforeAndAfterAll, FunSuite}
4 | import org.viz.lightning.types.Make
5 |
6 | class LightningPlotsSuite extends FunSuite with BeforeAndAfterAll {
7 |
8 | var lgn: Lightning = _
9 |
10 | override def beforeAll() {
11 | lgn = Lightning("http://public.lightning-viz.org")
12 | lgn.createSession("test-plots")
13 | }
14 |
15 | test("line") {
16 | lgn.line(series = Make.series(n = 5, t = 20),
17 | label = Make.labels(n = 5),
18 | size = Make.sizes(n = 5, scale = 7, min = 3))
19 | }
20 |
21 | test("line (single)") {
22 | lgn.line(series = Make.line(t = 20))
23 | }
24 |
25 | test("force") {
26 | lgn.force(conn = Make.sparseMatrix(n = 30, threshold = 0.95),
27 | label = Make.labels(n = 30))
28 | }
29 |
30 | test("force (links)") {
31 | lgn.force(conn = Make.sparseLinks(n = 30, threshold = 0.95),
32 | label = Make.labels(n = 30))
33 | }
34 |
35 | test("force (links and value)") {
36 | lgn.force(conn = Make.sparseLinks(n = 30, threshold = 0.95),
37 | value = Make.values(n = 30), colormap="Purples")
38 | }
39 |
40 | test("matrix") {
41 | lgn.matrix(matrix = Make.matrix(n = 10))
42 | }
43 |
44 | test("adjacency") {
45 | lgn.adjacency(conn = Make.sparseMatrix(n = 10),
46 | label = Make.labels(n = 10))
47 | }
48 |
49 | test("map (states)") {
50 | lgn.map(regions = Array("NY", "CA", "VA"),
51 | values = Make.values(n = 3))
52 | }
53 |
54 | test("map (countries)") {
55 | lgn.map(regions = Array("USA", "ENG", "IND"),
56 | values = Make.values(n = 3))
57 | }
58 |
59 | test("scatter") {
60 | lgn.scatter(x = Make.gaussian(n = 50, scale = 5),
61 | y = Make.gaussian(n = 50, scale = 5),
62 | label = Make.labels(n = 50),
63 | size = Make.sizes(n = 50),
64 | alpha = Make.alphas(n = 50))
65 | }
66 |
67 | test("graph") {
68 | lgn.graph(x = Make.gaussian(n = 50),
69 | y = Make.gaussian(n = 50),
70 | conn = Make.sparseMatrix(n = 50),
71 | label = Make.labels(n = 50))
72 | }
73 |
74 | test("graph bundled") {
75 | lgn.graphBundled(x = Make.gaussian(n = 50),
76 | y = Make.gaussian(n = 50),
77 | conn = Make.sparseMatrix(n = 50),
78 | label = Make.labels(n = 50))
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/test/scala/org/viz/lightning/LightningStreamingSuite.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning
2 |
3 | import org.scalatest.{BeforeAndAfterAll, FunSuite}
4 | import org.viz.lightning.types.Make
5 |
6 | class LightningStreamingSuite extends FunSuite with BeforeAndAfterAll {
7 |
8 | var lgn: Lightning = _
9 |
10 | override def beforeAll() {
11 | lgn = Lightning("http://public.lightning-viz.org")
12 | lgn.createSession("test-streaming")
13 | }
14 |
15 | test("line streaming") {
16 | val viz = lgn.lineStreaming(series = Make.series(n = 5, t = 20))
17 | lgn.lineStreaming(series = Make.series(n = 5, t = 20), viz=viz)
18 | }
19 | test("scatter streaming") {
20 | val viz = lgn.scatterStreaming(x = Make.gaussian(n = 50, scale = 5),
21 | y = Make.gaussian(n = 50, scale = 5),
22 | label = Make.labels(n = 50),
23 | size = Make.sizes(n = 50),
24 | alpha = Make.alphas(n = 50))
25 |
26 | lgn.scatterStreaming(x = Make.gaussian(n = 50, scale = 5),
27 | y = Make.gaussian(n = 50, scale = 5), viz=viz)
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/scala/org/viz/lightning/LightningSuite.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning
2 |
3 | import org.scalatest.FunSuite
4 |
5 | class LightningSuite extends FunSuite {
6 |
7 | test("create session") {
8 | val lgn = Lightning("http://public.lightning-viz.org")
9 | lgn.createSession("test-session")
10 | assert(!lgn.session.isEmpty)
11 | }
12 |
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/test/scala/org/viz/lightning/LightningThreeSuite.scala:
--------------------------------------------------------------------------------
1 | package org.viz.lightning
2 |
3 | import org.scalatest.{BeforeAndAfterAll, FunSuite}
4 | import org.viz.lightning.types.Make
5 |
6 | class LightningThreeSuite extends FunSuite with BeforeAndAfterAll {
7 |
8 | var lgn: Lightning = _
9 |
10 | override def beforeAll() {
11 | lgn = Lightning("http://public.lightning-viz.org")
12 | lgn.createSession("test-three")
13 | }
14 |
15 | test("scatter3") {
16 | lgn.scatter3(x = Make.values(n = 20),
17 | y = Make.values(n = 20),
18 | z = Make.values(n = 20),
19 | label = Make.labels(n = 20),
20 | size = Make.sizes(n = 20))
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------