Two-dimensional plots

Time-series plot example

axle.visualize.Plot

Imports

import org.joda.time.DateTime

import scala.collection.immutable.TreeMap
import scala.math.sin
import scala.util.Random.nextDouble

import cats.implicits._
import cats.Order.catsKernelOrderingForOrder

import axle._
import axle.visualize._
import axle.joda.dateTimeOrder

import axle.visualize.Color._

Generate the time-series to plot

val now = new DateTime()
// now: org.joda.time.DateTime = 2017-06-04T16:48:27.577-07:00

val colors = Vector(red, blue, green, yellow, orange)
// colors: scala.collection.immutable.Vector[axle.visualize.Color] = Vector(Color(255,0,0), Color(0,0,255), Color(0,255,0), Color(255,255,0), Color(255,200,0))

def randomTimeSeries(i: Int) = {

  val φ = nextDouble
  val A = nextDouble
  val ω = 0.1 / nextDouble
  val color = colors.random

  val data = new TreeMap[DateTime, Double]() ++ (0 to 100).map(t => (now.plusMinutes(2 * t) -> A * sin(ω*t + φ))).toMap

  val label = "%1.2f %1.2f %1.2f".format(φ, A, ω)

  (color, label) -> data
}
// randomTimeSeries: (i: Int)((axle.visualize.Color, String), scala.collection.immutable.TreeMap[org.joda.time.DateTime,Double])

val waves = (0 until 20).map(randomTimeSeries)
// waves: scala.collection.immutable.IndexedSeq[((axle.visualize.Color, String), scala.collection.immutable.TreeMap[org.joda.time.DateTime,Double])] = Vector(((Color(0,0,255),0.30 0.15 7.79),Map(2017-06-04T16:48:27.577-07:00 -> 0.04533121539421923, 2017-06-04T16:50:27.577-07:00 -> 0.15041757908345593, 2017-06-04T16:52:27.577-07:00 -> -0.025872983179808323, 2017-06-04T16:54:27.577-07:00 -> -0.1537645450289252, 2017-06-04T16:56:27.577-07:00 -> 0.005981782684845084, 2017-06-04T16:58:27.577-07:00 -> 0.1545383569546794, 2017-06-04T17:00:27.577-07:00 -> 0.014009519222688924, 2017-06-04T17:02:27.577-07:00 -> -0.152726065599451, 2017-06-04T17:04:27.577-07:00 -> -0.03376638053993261, 2017-06-04T17:06:27.577-07:00 -> 0.14835799853182652, 2017-06-04T17:08:27.577-07:00 -> 0.0529581824814857, 2017-06-0...

import axle.joda.dateTimeZero
// import axle.joda.dateTimeZero

implicit val zeroDT = dateTimeZero(now)
// zeroDT: axle.algebra.Zero[org.joda.time.DateTime] = axle.joda.package$$anon$4@91229da

Imports

import cats.Show
import spire.implicits.DoubleAlgebra
import axle.visualize.Plot
import axle.algebra.Plottable.doublePlottable
import axle.joda.dateTimeOrder
import axle.joda.dateTimePlottable
import axle.joda.dateTimeTics
import axle.joda.dateTimeDurationLengthSpace

Define the visualization

implicit val showCL: Show[(Color, String)] = new Show[(Color, String)] { def show(cl: (Color, String)): String = cl._2 }
// showCL: cats.Show[(axle.visualize.Color, String)] = $anon$1@3816da89

val plot = Plot(
  () => waves,
  colorOf = (cl: (Color, String)) => cl._1,
  title = Some("Random Waves"),
  xAxis = Some(0d),
  xAxisLabel = Some("time (t)"),
  yAxisLabel = Some("A sin(ωt + φ)"))
// plot: axle.visualize.Plot[(axle.visualize.Color, String),org.joda.time.DateTime,Double,scala.collection.immutable.TreeMap[org.joda.time.DateTime,Double]] = Plot(<function0>,true,true,700,600,50,4,20,50,80,Courier New,12,false,Palatino,20,<function1>,Some(Random Waves),None,Some(0.0),Some(time (t)),None,Some(A sin(ωt + φ)))

Create the SVG

import axle.web._
// import axle.web._

svg(plot, "waves.svg")

waves

Animation

This example traces two “saw” functions vs time:

Imports

import org.joda.time.DateTime
import edu.uci.ics.jung.graph.DirectedSparseGraph
import collection.immutable.TreeMap
import cats.implicits._
import monix.reactive._
import monix.execution.Scheduler.Implicits.global
import spire.implicits.DoubleAlgebra
import axle.joda._
import axle.jung._
import axle.quanta.Time
import axle.visualize._
import axle.reactive.intervalScan
import axle.reactive.CurrentValueSubscriber
import axle.awt.play

Define stream of data updates refreshing every 500 milliseconds

val initialData = List(
  ("saw 1", new TreeMap[DateTime, Double]()),
  ("saw 2", new TreeMap[DateTime, Double]())
)
// initialData: List[(String, scala.collection.immutable.TreeMap[org.joda.time.DateTime,Double])] = List((saw 1,Map()), (saw 2,Map()))

// Note: uses zeroDT defined above

val saw1 = (t: Long) => (t % 10000) / 10000d
// saw1: Long => Double = <function1>

val saw2 = (t: Long) => (t % 100000) / 50000d
// saw2: Long => Double = <function1>

val fs = List(saw1, saw2)
// fs: List[Long => Double] = List(<function1>, <function1>)

val refreshFn = (previous: List[(String, TreeMap[DateTime, Double])]) => {
  val now = new DateTime()
  previous.zip(fs).map({ case (old, f) => (old._1, old._2 ++ Vector(now -> f(now.getMillis))) })
}
// refreshFn: List[(String, scala.collection.immutable.TreeMap[org.joda.time.DateTime,Double])] => List[(String, scala.collection.immutable.TreeMap[org.joda.time.DateTime,Double])] = <function1>

implicit val timeConverter = {
  import axle.algebra.modules.doubleRationalModule
  Time.converterGraphK2[Double, DirectedSparseGraph]
}
// timeConverter: axle.quanta.UnitConverterGraph[axle.quanta.Time,Double,edu.uci.ics.jung.graph.DirectedSparseGraph[axle.quanta.UnitOfMeasurement[axle.quanta.Time],Double => Double]] with axle.quanta.TimeConverter[Double] = axle.quanta.Time$$anon$1@79ed7627

import timeConverter.millisecond
// import timeConverter.millisecond

val dataUpdates: Observable[Seq[(String, TreeMap[DateTime, Double])]] =
  intervalScan(initialData, refreshFn, 500d *: millisecond)
// dataUpdates: monix.reactive.Observable[Seq[(String, scala.collection.immutable.TreeMap[org.joda.time.DateTime,Double])]] = monix.reactive.internal.operators.ScanObservable@24eb959d

Create CurrentValueSubscriber, which will be used by the Plot to get the latest values

val cvSub = new CurrentValueSubscriber[Seq[(String, TreeMap[DateTime, Double])]]()
val cvCancellable = dataUpdates.subscribe(cvSub)

// [DateTime, Double, TreeMap[DateTime, Double]]

val plot = Plot(
  () => cvSub.currentValue.getOrElse(initialData),
  connect = true,
  colorOf = (label: String) => Color.black,
  title = Some("Saws"),
  xAxis = Some(0d),
  xAxisLabel = Some("time (t)"),
  yAxisLabel = Some("y")
)

Animate

val paintCancellable = play(plot, dataUpdates)

Tear down resources

paintCancellable.cancel()
cvCancellable.cancel()