alexarchambault / plotly-scala

Scala bindings for plotly.js
http://plotly-scala.org
222 stars 48 forks source link

plotly-scala

Scala bindings for plotly.js

Build Status Join the chat at https://gitter.im/alexarchambault/plotly-scala Maven Central ScalaDoc

Demo

plotly-scala is a Scala library able to output JSON that can be passed to plotly.js. Its classes closely follow the API of plotly.js, so that one can use plotly-scala by following the documentation of plotly.js. These classes can be converted to JSON, that can be fed directly to plotly.js.

It can be used from almond, from scala-js, or from a Scala REPL like Ammonite, to plot things straightaway in the browser.

It runs demos of the plotly.js documentation during its tests, to ensure that it is fine with all their features. That allows it to reliably cover a wide range of the plotly.js features - namely, all the examples of the supported sections of the plotly.js documentation are guaranteed to be fine.

It is published for both scala 2.12 and 2.13.

Table of content

  1. Quick start
  2. Rationale
  3. Internals
  4. Supported features

Quick start

From almond

Add the org.plotly-scala::plotly-almond:0.8.1 dependency to the notebook. (Latest version: Maven Central) Then initialize plotly-scala, and use it, like

import $ivy.`org.plotly-scala::plotly-almond:0.8.1`

import plotly._
import plotly.element._
import plotly.layout._
import plotly.Almond._

val (x, y) = Seq(
  "Banana" -> 10,
  "Apple" -> 8,
  "Grapefruit" -> 5
).unzip

Bar(x, y).plot()

JupyterLab

If you're using JupyterLab, you have to install jupyterlab-plotly to enable support for rendering Plotly charts:

jupyter labextension install jupyterlab-plotly

From scala-js

Add the corresponding dependency to your project, like

libraryDependencies += "org.plotly-scala" %%% "plotly-render" % "0.8.1"

(Latest version: Maven Central)

From your code, add some imports for plotly,

import plotly._, element._, layout._, Plotly._

Then define plots like

val x = (0 to 100).map(_ * 0.1)
val y1 = x.map(d => 2.0 * d + util.Random.nextGaussian())
val y2 = x.map(math.exp)

val plot = Seq(
  Scatter(x, y1).withName("Approx twice"),
  Scatter(x, y2).withName("Exp")
)

and plot them with

val lay = Layout().withTitle("Curves")
plot.plot("plot", lay)  // attaches to div element with id 'plot'

From Ammonite

Load the corresponding dependency, and some imports, like

import $ivy.`org.plotly-scala::plotly-render:0.8.1`
import plotly._, element._, layout._, Plotly._

Then plot things like

val labels = Seq("Banana", "Banano", "Grapefruit")
val valuesA = labels.map(_ => util.Random.nextGaussian())
val valuesB = labels.map(_ => 0.5 + util.Random.nextGaussian())

Seq(
  Bar(labels, valuesA, name = "A"),
  Bar(labels, valuesB, name = "B")
).plot(
  title = "Level"
)

Rationale

Most high-level Javascript libraries for plotting have well designed APIs, enforcing immutability and almost relying on typed objects, although not explicitly. Yet, the few existing Scala libraries for plotting still try to mimick matplotlib or Matlab, and have APIs requiring users to mutate things, in order to do plots. They also tend to lack a lot of features, especially compared to the current high-end Javascript plotting libraries. plotly-scala aims at filling this gap, by providing a reliable bridge from Scala towards the renowned plotly.js.

Internals

plotly-scala consists in a bunch of definitions, mostly case classes and sealed class hierarchies, closely following the API of plotly.js. It also contains JSON codecs for those, allowing to convert them to JSON that can be passed straightaway to plotly.js.

Having the ability to convert these classes to JSON, the codecs can also go the other way around: from plotly.js-compatible JSON to plotly-scala Scala classes. This way of going is used by the tests of plotly-scala, to ensure that the examples of the plotly.js documentation, illustrating a wide range of the features of plotly.js, can be represented via the classes of plotly-scala. Namely, the Javascript examples of the documentation of plotly.js are run inside a Rhino VM, with mocks of the plotly API. These mocks allow to keep the Javascript objects passed to the plotly.js API, and convert them to JSON. These JSON objects are then validated against the codecs of plotly-scala, to ensure that all their fields can be decoded by them. If these are fine, this gives a proof that all the features of the examples have a counterpart in plotly-scala.

Internally, plotly-scala uses circe (along with custom codec derivation mechanisms) to convert things to JSON, then render them. The circe objects don't appear in the plotly-scala API - circe is only used internally. The plotly-scala API only returns JSON strings, that can be passed to plotly.js. In subsequent versions, plotly-scala will likely try to shade circe and its dependencies, or switch to a more lightweight JSON library.

Supported features

plotly-scala supports the features illustrated in the following sections of the plotly.js documentation:

Some of these are illustrated in the demo page.

Adding support for extra plotly.js features

The following workflow can be followed to add support for extra sections of the plotly.js documentation:

About

Battlefield tested since early 2016 at Teads.tv

Released under the LGPL v3 license, copyright 2016-2019 Alexandre Archambault and contributors.

Parts based on the original plotly.js API, which is copyright 2016 Plotly, Inc.