{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Scala API to Plot Widgets\n", "\n", "### Overview\n", "\n", "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](http://www.scalafx.org/docs/home/#fully-type-safe-apis).\n", "\n", "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`.\n", "\n", "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`.\n", "\n", "### Usage\n", "\n", "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:\n", "```scala\n", "val myLine = new Line()\n", "myLine.x = 1 to 3\n", "myLine.y = 5 to 15 by 5\n", "```\n", "you can use this:\n", "```scala\n", "val myLine = new Line {\n", " x = 1 to 3\n", " y = 5 to 15 by 5\n", "}\n", "```\n", "This allows setting the properties without creating a named value for the new `Line` in many cases:\n", "```scala\n", "val plot = new Plot\n", "plot.add(new Line { x = 1 to 3; y = (1 to 3) map (1.0 / _) })\n", "\n", "```\n", "\n", "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:\n", "```scala\n", "// THIS WON'T BEHAVE AS EXPECTED!\n", "val x = List(1, 2, 3)\n", "val myPoints = new Points {\n", " x = x\n", "}\n", "```\n", "Instead of using the outer value of `x`, this will just invoke the getter for `x`.\n", "\n", "#### Compatibility\n", "\n", "In general, the Scala plot classes extend the corresponding Java classes. This means that the `set` methods are still available on the Scala objects. This may be useful if you need to deal with data that is not statically typed." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val plot = new Plot()\n", "val y1 = Seq(1.5, 1, 6, 5, 2, 8)\n", "val cs = Seq(Color.black, Color.red, Color.gray, Color.green, Color.blue, Color.pink)\n", "val ss = Seq(StrokeType.SOLID, StrokeType.SOLID, StrokeType.DASH, StrokeType.DOT, StrokeType.DASHDOT, StrokeType.LONGDASH)\n", "plot.add(new Stems {\n", " y = y1\n", " color = cs\n", " style = ss\n", " width = 5\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val plot = new Plot { title = \"Elo\" }\n", "var cs = new Color(255, 0, 0, 128)// transparent bars\n", "//cs[3] = Color.red // set color of a single bar, solid colored bar\n", "plot.add(new Bars {\n", " x = Seq(1, 2, 3, 4)\n", " y = Seq(3, 5, 2, 3, 7)\n", " color = cs\n", " outlineColor = Color.black\n", " width = 0.3\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val plot = new Plot { title = \"Changing Point Size, Color, Shape\" }\n", "val y1 = Seq(6, 7, 12, 11, 8, 14)\n", "val y2 = y1.map(x => x - 1)\n", "val y3 = y1.map(x => x - 2)\n", "val y4 = y1.map(x => x - 3)\n", "plot.add(new Points { y = y1 })\n", "plot.add(new Points {\n", " y = y2\n", " shape = ShapeType.CIRCLE\n", "})\n", "plot.add(new Points {\n", " y = y3\n", " size = 8.0\n", " shape = ShapeType.DIAMOND\n", "})\n", "plot.add(new Points {\n", " y = y4\n", " size = 12.0\n", " color = Color.orange\n", " outlineColor = Color.red\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val plot = new Plot { title = \"Changing point properties with list\" }\n", "val cs = Seq(Color.black, Color.red, Color.orange, Color.green, Color.blue, Color.pink)\n", "val ss = Seq(6.0, 9.0, 12.0, 15.0, 18.0, 21.0)\n", "val fs = Seq(false, false, false, true, false, false)\n", "\n", "val list = List(\n", " new Points {\n", " y = Seq(5,5,5,5,5,5)\n", " size = 12.0\n", " color = cs\n", " },\n", " new Points {\n", " y = Seq(4,4,4,4,4,4)\n", " size = 12.0\n", " color = Color.gray\n", " outlineColor = cs\n", " },\n", " new Points {\n", " y = Seq(3,3,3,3,3,3)\n", " size = 12\n", " color = Color.red\n", " },\n", " new Points {\n", " y = Seq(2,2,2,2,2,2)\n", " size = 12.0\n", " color = Color.black\n", " fill = fs\n", " outlineColor = Color.black\n", " }\n", ")\n", "\n", "plot.add(list)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val plot = new Plot()\n", "val ys = Seq(3, 5, 2, 3)\n", "val x0 = Seq(0, 1, 2, 3)\n", "val x1 = Seq(3, 4, 5, 8)\n", "plot.add(new Area {\n", " x = x0\n", " y = ys\n", "})\n", "plot.add(new Area {\n", " x = x1\n", " y = ys\n", " color = new Color(128, 128, 128, 50)\n", " interpolation = 0\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val p = new Plot()\n", "p.add(new Line {\n", " y = Seq(3, 6, 12, 24)\n", " displayName = \"Median\"\n", "})\n", "p.add(new Area {\n", " base = Seq(4, 8, 16, 32)\n", " y = Seq(2, 4, 8, 16)\n", " color = new Color(255, 0, 0, 50)\n", " displayName = \"Q1 to Q3\"\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val y1 = Seq(1,5,3,2,3)\n", "val y2 = Seq(7,2,4,1,3)\n", "val p = new Plot { title = \"Plot with XYStacker\"; initHeight = 200 }\n", "val a1 = new Area { y = y1; displayName = \"y1\" }\n", "val a2 = new Area { y = y2; displayName = \"y2\" }\n", "p.add(XYStacker.stack(Seq(a1, a2)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val p = new Plot()\n", "p.add(new Line { y = Seq(-1, 1) })\n", "p.add(new ConstantLine {\n", " x = 0\n", " y = 0.65\n", " style = StrokeType.DOT\n", " color = Color.blue\n", "})\n", "p.add(new ConstantLine {\n", " x = 0\n", " y = 1\n", " style = StrokeType.DASHDOT\n", " color = Color.blue\n", "})\n", "p.add(new ConstantLine {\n", " x = 1\n", " y = 0.4\n", " color = Color.gray\n", " width = 5\n", " showLabel = true\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val constBand = new ConstantBand { x = Seq(1, 2); y = Seq(1, 3) }\n", "val lineVal = new Line { y = Seq(-3, 1, 3, 4, 5) }\n", "val plot = new Plot()\n", "plot.add(lineVal)\n", "plot.add(constBand)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val p = new Plot() \n", "p.add(new Line { x = Seq(-3, 1, 2, 4, 5); y = Seq(4, 2, 6, 1, 5) })\n", "p.add(new ConstantBand { x = Seq(Double.NegativeInfinity, 1); color = new Color(128, 128, 128, 50) })\n", "p.add(new ConstantBand { x = Seq(1, 2) })\n", "p.add(new ConstantBand { x = Seq(4, Double.PositiveInfinity) })" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val plot = new Plot()\n", "val xs = Seq(1,2,3,4,5,6,7,8,9,10)\n", "val ys = Seq(8.6, 6.1, 7.4, 2.5, 0.4, 0.0, 0.5, 1.7, 8.4, 1)\n", "\n", "plot.add(new Line {\n", " x = xs\n", " y = ys\n", "})\n", "\n", "def label(i: Int, ys: Seq[Double]): String = {\n", " val leftSign = Math.signum(ys(i) - ys(i - 1))\n", " val rightSign = Math.signum(ys(i + 1) - ys(i))\n", " (leftSign, rightSign) match {\n", " case (1, -1) => \"max\"\n", " case (-1, 1) => \"min\"\n", " case (1, _) => \"rising\"\n", " case (-1, _) => \"falling\"\n", " case _ => \"\"\n", " }\n", "}\n", "\n", "for (i <- 0 to xs.size) {\n", " if (i > 0 && i < xs.size - 1) {\n", " plot.add(new Text {\n", " x = xs(i)\n", " y = ys(i)\n", " text = label(i, ys)\n", " pointerAngle = -i/3.0\n", " })\n", " }\n", "}\n", "\n", "plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val ch = new Crosshair {\n", " color = new Color(255, 128, 5)\n", " width = 2\n", " style = StrokeType.DOT\n", "}\n", "val pp = new Plot {\n", " crosshair = ch\n", " omitCheckboxes = true\n", " legendLayout = LegendLayout.HORIZONTAL\n", " legendPosition = LegendPosition.TOP\n", "}\n", "def xs = Seq(1, 4, 6, 8, 10)\n", "def ys = Seq(3, 6, 4, 5, 9)\n", "pp.add(new Line {\n", " displayName = \"Line\"\n", " x = xs\n", " y = ys\n", " width = 3\n", "})\n", "pp.add(new Bars {\n", " displayName = \"Bar\"\n", " x = Seq(1,2,3,4,5,6,7,8,9,10)\n", " y = Seq(2, 2, 4, 4, 2, 2, 0, 2, 2, 4)\n", " width = 0.5\n", "})\n", "pp.add(new Points {\n", " x = xs\n", " y = ys\n", " size = 10\n", " // TODO: implement ToolTipBuilder\n", " toolTip = Seq(\"x = \",\"y = \")\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val rates = new CSV().readFile(\"../resources/data/interest-rates.csv\")\n", "\n", "new SimpleTimePlot {\n", " data = rates\n", " columns = Seq(\"y1\", \"y10\")\n", " yLabel = \"Price\"\n", " displayNames = Seq(\"All\", \"1 Year\", \"10 Year\")\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val points = 100\n", "val logBase = 10\n", "\n", "val xs = for (i <- 0 to points) yield i / 15.0\n", "val expys = for (x <- xs) yield Math.exp(x)\n", "\n", "val cplot = new CombinedPlot()\n", "val logYPlot = new Plot {\n", " title = \"Linear x, Log y\"\n", " xLabel = \"Log\"\n", " logY=true\n", " yLogBase = logBase\n", "}\n", "logYPlot.add(new Line {\n", " displayName = \"f(x) = exp(x)\"\n", " x = xs\n", " y = expys\n", " width = 3.0f\n", "})\n", "logYPlot.add(new Line {\n", " displayName = \"g(x) = x\"\n", " x = xs\n", " y = xs\n", " width = 3.0f\n", "})\n", "\n", "cplot.add(logYPlot, 3)\n", "\n", "val linearYPlot = new Plot {\n", " title = \"Linear x, Linear y\"\n", " xLabel = \"Linear\"\n", "}\n", "linearYPlot.add(new Line {\n", " displayName = \"f(x) = exp(x)\"\n", " x = xs\n", " y = expys\n", " width = 3.0f\n", "})\n", "linearYPlot.add(new Line {\n", " displayName = \"g(x) = x\"\n", " x = xs\n", " y = xs\n", " width = 3.0f\n", "})\n", "cplot.add(linearYPlot, 3)\n", "\n", "cplot" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val points = 100\n", "val logBase = 10\n", "val xs = for (i <- 0 to points) yield i / 15.0\n", "val expys = for (x <- xs) yield Math.exp(x)\n", "\n", "val plot = new Plot { \n", " title = \"Log x, Log y\"\n", " xLabel = \"Log\"\n", " yLabel = \"Log\"\n", " logX = true\n", " xLogBase = logBase\n", " logY = true\n", " yLogBase = logBase\n", "}\n", "\n", "plot.add(new Line {\n", " displayName = \"f(x) = exp(x)\"\n", " x = xs\n", " y = expys\n", " width = 3.0f\n", "})\n", "plot.add(new Line {\n", " displayName = \"g(x) = x\"\n", " x = xs\n", " y = xs\n", " width = 3.0f\n", "})\n", "plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import java.util.{Calendar, Date}\n", "import java.util.SimpleTimeZone\n", "\n", "val cal = Calendar.getInstance();\n", "cal.add(Calendar.HOUR, -1)\n", "\n", "val today = new Date()\n", "val millis = today.getTime()\n", "val hour = 1000 * 60 * 60;\n", "\n", "val plot = new TimePlot {\n", " timeZone = new SimpleTimeZone(10800000, \"America/New_York\")\n", "}\n", "\n", "//list of milliseconds\n", "plot.add(new Points {\n", " x = (0 to 10).map(x => millis + hour * x)\n", " y = (0 to 10)\n", " size = 10\n", " displayName = \"milliseconds\"\n", "})\n", "\n", "plot.add(new Points {\n", " x = (0 to 10).map(x => {cal.add(Calendar.HOUR, 1); cal.getTime()})\n", " y = (0 to 10)\n", " size = 5\n", " displayName = \"date objects\"\n", "})\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import java.util.Date\n", "\n", "val today = new Date()\n", "val millis = today.getTime()\n", "val nanos = millis * 1000 * 1000// g makes it arbitrary precision\n", "val np = new NanoPlot()\n", "\n", "np.add(new Points {\n", " x = (0 to 10).map(x => nanos + 7 * x)\n", " y = (0 to 10)\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import scala.util.Random\n", "\n", "val r = new Random()\n", "val p = new Plot {\n", " title = \"Advanced Plot Styling\"\n", " labelStyle = \"font-size:32px; font-weight: bold; font-family: courier; fill: green;\"\n", " gridLineStyle = \"stroke: purple; stroke-width: 3;\"\n", " titleStyle = \"color: green;\"\n", "}\n", "\n", "p.add(new Points {\n", " x = (1 to 1000).map(x => r.nextGaussian() * 10.0d)\n", " y = (1 to 1000).map(x => r.nextGaussian() * 20.0d)\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import java.nio.file.Files\n", "import java.io.File\n", "\n", "val picture: Array[Byte] = Files.readAllBytes(new File(\"../resources/img/widgetArch.png\").toPath())\n", "\n", "var p = new Plot();\n", "// x y width height are coordinates, opacity is a double in 0~1\n", "\n", "// image can be loaded via bytes, filepath, or url\n", "p.add(new Rasters {\n", " x = List(-10,3)\n", " y = List(3,1.5)\n", " height = List(6,5)\n", " width = List(10,8)\n", " opacity = List(1,0.5)\n", " dataString = picture\n", "})\n", "\n", "//p << new Rasters(x: -1, y: 4.5, width: 5, height: 8, opacity:0.5, filePath: \"../resources/img/widgetArch.png\");\n", "p.add(new Rasters {\n", " x = List(-4)\n", " y = List(10.5)\n", " height = List(2)\n", " width = List(10)\n", " opacity = List(1)\n", " fileUrl = \"https://www.twosigma.com/static/img/twosigma.png\"\n", "})\n", "\n", "// a list of images!\n", "def xs = List(-8, -5, -3, -2, -1, 1, 2, 4, 6, 8)\n", "def ys = List(4, 5, 1, 2, 0 ,3, 6, 4, 5, 9)\n", "def widths = List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)\n", "def opacities = List(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1)\n", "p.add(new Rasters {\n", " x = xs\n", " y = ys\n", " width = widths\n", " height = widths\n", " opacity = opacities\n", " fileUrl = \"http://icons.iconarchive.com/icons/paomedia/small-n-flat/1024/sign-check-icon.png\"\n", "})\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "val plot = new Plot { title = \"Setting 2nd Axis bounds\" }\n", "val ys = List(0, 2, 4, 6, 15, 10)\n", "val ys2 = List(-40, 50, 6, 4, 2, 0)\n", "val ys3 = List(3, 6, 3, 6, 70, 6)\n", "plot.add(new YAxis { label = \"Spread\" })\n", "plot.add(new Line { y = ys })\n", "plot.add(new Line { y = ys2; displayName = \"Spread\" })\n", "\n", "plot.yAxes(1).bound = (1, 5)\n", "\n", "plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "beakerx_kernel_parameters": {}, "kernelspec": { "display_name": "Scala", "language": "scala", "name": "scala" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "", "name": "Scala", "nbconverter_exporter": "", "version": "2.11.11" } }, "nbformat": 4, "nbformat_minor": 2 }