The Scala API for plot widgets maps the widgets' Java bean-style properties to Scala properties. It provides both type safety and a script-friendly syntax with the same techniques as ScalaFX.
For example, the operations getDisplayName
and setDisplayName
turn into a displayName
getter and setter. In general, methods that expect a Java (or Groovy) List
will accept a Scala Seq
. Arrays should also work where a Seq
is expected, although Array
is not a subtype of Seq
.
The Java/Groovy API frequently allows setting some property to be either a single value or a list of values. In those cases, there are typically two getters, one with a singular name (like the setter) and one with a plural name (e.g., getFill
and getFills
). The Scala API follows this pattern for consistency. If the single value might be undefined, the getter will be an Option
type. If the list value is undefined, the API will map it to an empty Seq
.
Groovy allows setting an arbitrary set of properties when constructing an object. To get similar terseness in creating Scala plot widgets, you can use the anonymous subclass initialization idiom. Instead of this:
val myLine = new Line()
myLine.x = 1 to 3
myLine.y = 5 to 15 by 5
you can use this:
val myLine = new Line {
x = 1 to 3
y = 5 to 15 by 5
}
This allows setting the properties without creating a named value for the new Line
in many cases:
val plot = new Plot
plot.add(new Line { x = 1 to 3; y = (1 to 3) map (1.0 / _) })
Note that this syntax is not using named parameters. There is one important caveat about using this style: inside the body of the initializer, the names of the getters will shadow any identifiers in the outer scope. So, don't try this:
// THIS WON'T BEHAVE AS EXPECTED!
val x = List(1, 2, 3)
val myPoints = new Points {
x = x
}
Instead of using the outer value of x
, this will just invoke the getter for x
.
In general, the Scala plot classes extend the corresponding Java classes. This means that the set<Property>
methods are still available on the Scala objects. This may be useful if you need to deal with data that is not statically typed.
In [ ]:
val plot = new Plot()
val y1 = Seq(1.5, 1, 6, 5, 2, 8)
val cs = Seq(Color.black, Color.red, Color.gray, Color.green, Color.blue, Color.pink)
val ss = Seq(StrokeType.SOLID, StrokeType.SOLID, StrokeType.DASH, StrokeType.DOT, StrokeType.DASHDOT, StrokeType.LONGDASH)
plot.add(new Stems {
y = y1
color = cs
style = ss
width = 5
})
In [ ]:
val plot = new Plot { title = "Elo" }
var cs = new Color(255, 0, 0, 128)// transparent bars
//cs[3] = Color.red // set color of a single bar, solid colored bar
plot.add(new Bars {
x = Seq(1, 2, 3, 4)
y = Seq(3, 5, 2, 3, 7)
color = cs
outlineColor = Color.black
width = 0.3
})
In [ ]:
val plot = new Plot { title = "Changing Point Size, Color, Shape" }
val y1 = Seq(6, 7, 12, 11, 8, 14)
val y2 = y1.map(x => x - 1)
val y3 = y1.map(x => x - 2)
val y4 = y1.map(x => x - 3)
plot.add(new Points { y = y1 })
plot.add(new Points {
y = y2
shape = ShapeType.CIRCLE
})
plot.add(new Points {
y = y3
size = 8.0
shape = ShapeType.DIAMOND
})
plot.add(new Points {
y = y4
size = 12.0
color = Color.orange
outlineColor = Color.red
})
In [ ]:
val plot = new Plot { title = "Changing point properties with list" }
val cs = Seq(Color.black, Color.red, Color.orange, Color.green, Color.blue, Color.pink)
val ss = Seq(6.0, 9.0, 12.0, 15.0, 18.0, 21.0)
val fs = Seq(false, false, false, true, false, false)
val list = List(
new Points {
y = Seq(5,5,5,5,5,5)
size = 12.0
color = cs
},
new Points {
y = Seq(4,4,4,4,4,4)
size = 12.0
color = Color.gray
outlineColor = cs
},
new Points {
y = Seq(3,3,3,3,3,3)
size = 12
color = Color.red
},
new Points {
y = Seq(2,2,2,2,2,2)
size = 12.0
color = Color.black
fill = fs
outlineColor = Color.black
}
)
plot.add(list)
In [ ]:
val plot = new Plot()
val ys = Seq(3, 5, 2, 3)
val x0 = Seq(0, 1, 2, 3)
val x1 = Seq(3, 4, 5, 8)
plot.add(new Area {
x = x0
y = ys
})
plot.add(new Area {
x = x1
y = ys
color = new Color(128, 128, 128, 50)
interpolation = 0
})
In [ ]:
val p = new Plot()
p.add(new Line {
y = Seq(3, 6, 12, 24)
displayName = "Median"
})
p.add(new Area {
base = Seq(4, 8, 16, 32)
y = Seq(2, 4, 8, 16)
color = new Color(255, 0, 0, 50)
displayName = "Q1 to Q3"
})
In [ ]:
val y1 = Seq(1,5,3,2,3)
val y2 = Seq(7,2,4,1,3)
val p = new Plot { title = "Plot with XYStacker"; initHeight = 200 }
val a1 = new Area { y = y1; displayName = "y1" }
val a2 = new Area { y = y2; displayName = "y2" }
p.add(XYStacker.stack(Seq(a1, a2)))
In [ ]:
val p = new Plot()
p.add(new Line { y = Seq(-1, 1) })
p.add(new ConstantLine {
x = 0
y = 0.65
style = StrokeType.DOT
color = Color.blue
})
p.add(new ConstantLine {
x = 0
y = 1
style = StrokeType.DASHDOT
color = Color.blue
})
p.add(new ConstantLine {
x = 1
y = 0.4
color = Color.gray
width = 5
showLabel = true
})
In [ ]:
val constBand = new ConstantBand { x = Seq(1, 2); y = Seq(1, 3) }
val lineVal = new Line { y = Seq(-3, 1, 3, 4, 5) }
val plot = new Plot()
plot.add(lineVal)
plot.add(constBand)
In [ ]:
val p = new Plot()
p.add(new Line { x = Seq(-3, 1, 2, 4, 5); y = Seq(4, 2, 6, 1, 5) })
p.add(new ConstantBand { x = Seq(Double.NegativeInfinity, 1); color = new Color(128, 128, 128, 50) })
p.add(new ConstantBand { x = Seq(1, 2) })
p.add(new ConstantBand { x = Seq(4, Double.PositiveInfinity) })
In [ ]:
val plot = new Plot()
val xs = Seq(1,2,3,4,5,6,7,8,9,10)
val ys = Seq(8.6, 6.1, 7.4, 2.5, 0.4, 0.0, 0.5, 1.7, 8.4, 1)
plot.add(new Line {
x = xs
y = ys
})
def label(i: Int, ys: Seq[Double]): String = {
val leftSign = Math.signum(ys(i) - ys(i - 1))
val rightSign = Math.signum(ys(i + 1) - ys(i))
(leftSign, rightSign) match {
case (1, -1) => "max"
case (-1, 1) => "min"
case (1, _) => "rising"
case (-1, _) => "falling"
case _ => ""
}
}
for (i <- 0 to xs.size) {
if (i > 0 && i < xs.size - 1) {
plot.add(new Text {
x = xs(i)
y = ys(i)
text = label(i, ys)
pointerAngle = -i/3.0
})
}
}
plot
In [ ]:
val ch = new Crosshair {
color = new Color(255, 128, 5)
width = 2
style = StrokeType.DOT
}
val pp = new Plot {
crosshair = ch
omitCheckboxes = true
legendLayout = LegendLayout.HORIZONTAL
legendPosition = LegendPosition.TOP
}
def xs = Seq(1, 4, 6, 8, 10)
def ys = Seq(3, 6, 4, 5, 9)
pp.add(new Line {
displayName = "Line"
x = xs
y = ys
width = 3
})
pp.add(new Bars {
displayName = "Bar"
x = Seq(1,2,3,4,5,6,7,8,9,10)
y = Seq(2, 2, 4, 4, 2, 2, 0, 2, 2, 4)
width = 0.5
})
pp.add(new Points {
x = xs
y = ys
size = 10
// TODO: implement ToolTipBuilder
toolTip = Seq("x = ","y = ")
})
In [ ]:
val rates = new CSV().readFile("../resources/data/interest-rates.csv")
new SimpleTimePlot {
data = rates
columns = Seq("y1", "y10")
yLabel = "Price"
displayNames = Seq("All", "1 Year", "10 Year")
}
In [ ]:
val points = 100
val logBase = 10
val xs = for (i <- 0 to points) yield i / 15.0
val expys = for (x <- xs) yield Math.exp(x)
val cplot = new CombinedPlot()
val logYPlot = new Plot {
title = "Linear x, Log y"
xLabel = "Log"
logY=true
yLogBase = logBase
}
logYPlot.add(new Line {
displayName = "f(x) = exp(x)"
x = xs
y = expys
width = 3.0f
})
logYPlot.add(new Line {
displayName = "g(x) = x"
x = xs
y = xs
width = 3.0f
})
cplot.add(logYPlot, 3)
val linearYPlot = new Plot {
title = "Linear x, Linear y"
xLabel = "Linear"
}
linearYPlot.add(new Line {
displayName = "f(x) = exp(x)"
x = xs
y = expys
width = 3.0f
})
linearYPlot.add(new Line {
displayName = "g(x) = x"
x = xs
y = xs
width = 3.0f
})
cplot.add(linearYPlot, 3)
cplot
In [ ]:
val points = 100
val logBase = 10
val xs = for (i <- 0 to points) yield i / 15.0
val expys = for (x <- xs) yield Math.exp(x)
val plot = new Plot {
title = "Log x, Log y"
xLabel = "Log"
yLabel = "Log"
logX = true
xLogBase = logBase
logY = true
yLogBase = logBase
}
plot.add(new Line {
displayName = "f(x) = exp(x)"
x = xs
y = expys
width = 3.0f
})
plot.add(new Line {
displayName = "g(x) = x"
x = xs
y = xs
width = 3.0f
})
plot
In [ ]:
import java.util.{Calendar, Date}
import java.util.SimpleTimeZone
val cal = Calendar.getInstance();
cal.add(Calendar.HOUR, -1)
val today = new Date()
val millis = today.getTime()
val hour = 1000 * 60 * 60;
val plot = new TimePlot {
timeZone = new SimpleTimeZone(10800000, "America/New_York")
}
//list of milliseconds
plot.add(new Points {
x = (0 to 10).map(x => millis + hour * x)
y = (0 to 10)
size = 10
displayName = "milliseconds"
})
plot.add(new Points {
x = (0 to 10).map(x => {cal.add(Calendar.HOUR, 1); cal.getTime()})
y = (0 to 10)
size = 5
displayName = "date objects"
})
In [ ]:
import java.util.Date
val today = new Date()
val millis = today.getTime()
val nanos = millis * 1000 * 1000// g makes it arbitrary precision
val np = new NanoPlot()
np.add(new Points {
x = (0 to 10).map(x => nanos + 7 * x)
y = (0 to 10)
})
In [ ]:
import scala.util.Random
val r = new Random()
val p = new Plot {
title = "Advanced Plot Styling"
labelStyle = "font-size:32px; font-weight: bold; font-family: courier; fill: green;"
gridLineStyle = "stroke: purple; stroke-width: 3;"
titleStyle = "color: green;"
}
p.add(new Points {
x = (1 to 1000).map(x => r.nextGaussian() * 10.0d)
y = (1 to 1000).map(x => r.nextGaussian() * 20.0d)
})
In [ ]:
import java.nio.file.Files
import java.io.File
val picture: Array[Byte] = Files.readAllBytes(new File("../resources/img/widgetArch.png").toPath())
var p = new Plot();
// x y width height are coordinates, opacity is a double in 0~1
// image can be loaded via bytes, filepath, or url
p.add(new Rasters {
x = List(-10,3)
y = List(3,1.5)
height = List(6,5)
width = List(10,8)
opacity = List(1,0.5)
dataString = picture
})
//p << new Rasters(x: -1, y: 4.5, width: 5, height: 8, opacity:0.5, filePath: "../resources/img/widgetArch.png");
p.add(new Rasters {
x = List(-4)
y = List(10.5)
height = List(2)
width = List(10)
opacity = List(1)
fileUrl = "https://www.twosigma.com/static/img/twosigma.png"
})
// a list of images!
def xs = List(-8, -5, -3, -2, -1, 1, 2, 4, 6, 8)
def ys = List(4, 5, 1, 2, 0 ,3, 6, 4, 5, 9)
def widths = List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
def opacities = List(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1)
p.add(new Rasters {
x = xs
y = ys
width = widths
height = widths
opacity = opacities
fileUrl = "http://icons.iconarchive.com/icons/paomedia/small-n-flat/1024/sign-check-icon.png"
})
In [ ]:
val plot = new Plot { title = "Setting 2nd Axis bounds" }
val ys = List(0, 2, 4, 6, 15, 10)
val ys2 = List(-40, 50, 6, 4, 2, 0)
val ys3 = List(3, 6, 3, 6, 70, 6)
plot.add(new YAxis { label = "Spread" })
plot.add(new Line { y = ys })
plot.add(new Line { y = ys2; displayName = "Spread" })
plot.yAxes(1).bound = (1, 5)
plot
In [ ]: