├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── build.sbt ├── docs └── img │ ├── gnuplot.png │ ├── jfreegraph.png │ ├── scatter.png │ └── test.png ├── pom.xml ├── project ├── build.properties ├── build.scala └── plugins.sbt └── src ├── main └── scala │ └── org │ └── sameersingh │ └── scalaplot │ ├── BarChart.scala │ ├── Chart.scala │ ├── Implicits.scala │ ├── Plotter.scala │ ├── Style.scala │ ├── XYChart.scala │ ├── XYData.scala │ ├── XYSeries.scala │ ├── gnuplot │ ├── Add.scala │ └── GnuplotPlotter.scala │ ├── jfreegraph │ └── JFGraphPlotter.scala │ ├── metrics │ ├── Histogram.scala │ ├── PrecRecallCurve.scala │ └── Stats.scala │ └── util │ └── CurveFitting.scala └── test └── scala └── org └── sameersingh └── scalaplot ├── BarPlotTest.scala ├── ExampleBarTest.scala ├── ExampleXYTest.scala ├── MetricsTest.scala └── XYPlotTest.scala /.gitignore: -------------------------------------------------------------------------------- 1 | # use glob syntax. 2 | syntax: glob 3 | *.ser 4 | *.class 5 | *~ 6 | *.bak 7 | #*.off 8 | *.old 9 | 10 | # eclipse conf file 11 | .settings 12 | .classpath 13 | .project 14 | .manager 15 | .scala_dependencies 16 | 17 | # idea 18 | .idea 19 | *.iml 20 | 21 | # building 22 | target 23 | build 24 | null 25 | tmp* 26 | temp* 27 | dist 28 | test-output 29 | build.log 30 | 31 | # other scm 32 | .svn 33 | .CVS 34 | .hg* 35 | 36 | # switch to regexp syntax. 37 | # syntax: regexp 38 | # ^\.pc/ 39 | 40 | #SHITTY output not in target directory 41 | build.log 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | before_install: 4 | - sudo apt-get update -qq 5 | - sudo apt-get install gnuplot 6 | 7 | install: mvn install -DskipTests=true -Dgpg.skip=true -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Sameer Singh 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/sameersingh/scalaplot.svg?branch=master)](https://travis-ci.org/sameersingh/scalaplot) 2 | 3 | scalaplot 4 | ========= 5 | 6 | This is a library for quick and easy plotting of simple plots (such as XY line plots, scatter plots) and supports outputs using different engines (currently Gnuplot and JFreeGraph). 7 | 8 | **Note:** The project is still in *beta*. If you just need a clean way to interface Java with gnuplot, see [gnujavaplot](http://gnujavaplot.sourceforge.net/JavaPlot/About.html). 9 | 10 | ## Requirements 11 | 12 | - maven 13 | - *for gnuplot*: [gnuplot 4.6](http://www.gnuplot.info/) with pdf support (on the mac+[homebrew](http://mxcl.github.com/homebrew/), `brew install pdflib-lite gnuplot`) 14 | 15 | ## Installation 16 | 17 | ### Maven dependency 18 | 19 | The easiest (and recommended) way to use scalaplot is as a maven dependency. Insert the following in your `pom` file: 20 | 21 | ```xml 22 | 23 | ... 24 | 25 | org.sameersingh.scalaplot 26 | scalaplot 27 | 0.0.4 28 | 29 | ... 30 | 31 | ``` 32 | ### SBT dependency 33 | Or to use scalaplot with SBT, add the following dependency to your `build.sbt` file: 34 | 35 | ``` 36 | libraryDependencies += "org.sameersingh.scalaplot" % "scalaplot" % "0.0.4" 37 | ``` 38 | 39 | ## Creating Charts 40 | 41 | Currently, the library supports line and point (scatter) charts. Let's start with a simple, complete example: 42 | 43 | ```scala 44 | import org.sameersingh.scalaplot.Implicits._ 45 | 46 | val x = 0.0 until 2.0 * math.Pi by 0.1 47 | output(PNG("docs/img/", "test"), xyChart(x ->(math.sin(_), math.cos(_)))) 48 | ``` 49 | 50 | which produces 51 | 52 | ![Example scalaplot](https://github.com/sameersingh/scalaplot/raw/master/docs/img/test.png) 53 | 54 | while 55 | 56 | ```scala 57 | output(ASCII, xyChart(x ->(math.sin(_), math.cos(_)))) 58 | ``` 59 | 60 | produces 61 | 62 | ``` 63 | 1 BBBB------+--AAAAAA-+---------+--------+---------+---------BBB------++ 64 | + BB +AA AAA + + + BB+ + 65 | 0.8 ++ BB AA AA BB ++ 66 | | AA A BB | 67 | 0.6 ++ A BB A B ++ 68 | | A B AA B | 69 | 0.4 ++ A B A B ++ 70 | | A B A B | 71 | 0.2 ++A B A B ++ 72 | 0 AA B A B ++ 73 | | B A B A | 74 | -0.2 ++ B A B A ++ 75 | | B A BB A | 76 | -0.4 ++ BB A B A ++ 77 | | B A B A | 78 | -0.6 ++ B AA B AA ++ 79 | | B AB A | 80 | -0.8 ++ BB B AA AA ++ 81 | + + + BBB + BB + AA +AA + + 82 | -1 ++--------+---------+--------BBBBBB----+---AAAAAAA---------+--------++ 83 | 0 1 2 3 4 5 6 7 84 | ``` 85 | 86 | As another example to introduce a bit of customization: 87 | 88 | ```scala 89 | import org.sameersingh.scalaplot.Implicits._ 90 | 91 | val x = 0.0 until 10.0 by 0.01 92 | val rnd = new scala.util.Random(0) 93 | 94 | output(PNG("docs/img/", "scatter"), xyChart( 95 | x -> Seq(Y(x, style = XYPlotStyle.Lines), 96 | Y(x.map(_ + rnd.nextDouble - 0.5), style = XYPlotStyle.Dots)))) 97 | ``` 98 | 99 | produces 100 | 101 | ![Example scatter](https://github.com/sameersingh/scalaplot/raw/master/docs/img/scatter.png) 102 | 103 | ### Output Formats 104 | 105 | The library, of course, supports different output formats. Most of these also produce an accompanying Gnuplot source file, allowing archival and further customization if needed. The current list of formats are: 106 | 107 | ```scala 108 | output(ASCII, xyChart(...)) // returns the string as above 109 | output(SVG, xyChart(...)) // returns the SVG text, which can be embedded in html or saved as a SVG file 110 | output(PDF(dir, name), xyChart(...)) // produces dir/name.gpl as the gnuplot source, and attempts dir/name.pdf 111 | output(PNG(dir, name), xyChart(...)) // produces dir/name.gpl as the gnuplot source, and attempts dir/name.png 112 | output(GUI, xyChart(...)) // opens a window with the plot, which can be modified/exported/resized/etc. 113 | ``` 114 | 115 | Note that scalaplot calls the `gnuplot` command to render the image in `dir/name.EXT`, but in case it fails, do the following: 116 | 117 | ```shell 118 | $ cd dir/ 119 | $ gnuplot name.gpl 120 | ``` 121 | 122 | which will create `name.EXT`, where `EXT` is one of `PDF` or `PNG`. 123 | 124 | ### XYChart 125 | 126 | The `xyChart` function is the main entry point for creating charts. The first argument of plot requires a `XYData` object, that we will describe in the next section. The rest of the arguments customize the aspects of the chart that are not data-specific. 127 | 128 | ```scala 129 | val d: XYData = ... 130 | xyChart(d) 131 | xyChart(d, "Chart Title!") 132 | xyChart(d, x = Axis(label = "Age"), y = Axis(log = true)) 133 | ``` 134 | 135 | Here are the relevant definitions and default parameters that you can override: 136 | 137 | ```scala 138 | def xyChart(data: XYData, title: String = "", 139 | x: NumericAxis = new NumericAxis, 140 | y: NumericAxis = new NumericAxis, 141 | pointSize: Option[Double] = None, 142 | legendPosX: LegendPosX.Type = LegendPosX.Right, 143 | legendPosY: LegendPosY.Type = LegendPosY.Center, 144 | showLegend: Boolean = false, 145 | monochrome: Boolean = false, 146 | size: Option[(Double, Double)] = None): XYChart 147 | def Axis(label: String = "", 148 | backward: Boolean = false, 149 | log: Boolean = false, 150 | range: Option[(Double, Double)] = None): NumericAxis 151 | ``` 152 | 153 | ### Data 154 | 155 | The data is the first argument of the plot function, and can be specified in many different ways, depending on the format your data is available in. Primarily, `XYData` consists of multiple sequences of `(Double,Double)` pairs, where each sequence forms a single series (line in line plots). Here are some ways of data can be specified. 156 | 157 | If you have a single `x` sequence and multiple `y` sequences, you can use: 158 | 159 | ```scala 160 | // data 161 | val x = (1 until 100).map(_.toDouble) 162 | val y1 = (1 until 100).map(j => math.pow(j, 1)) 163 | val y2 = (1 until 100).map(j => math.pow(j, 2)) 164 | val y3 = (1 until 100).map(j => math.pow(j, 3)) 165 | 166 | xyChart(x ->(y1, y2, y3)) 167 | xyChart(x ->(math.sin(_), math.cos(_))) // inline definition 168 | xyChart(x -> Seq(Y(y1, "1"), Y(y2, "2"), Y(y3, "3"))) // with labels and other possible customizations 169 | xyChart(x -> Seq(Yf(math.sin, "sin"), Yf(math.cos, color = Color.Blue), Yf(math.tan, lw = 3.0))) // Yf for functions 170 | ``` 171 | 172 | where each series can be fully customized using the following: 173 | 174 | ```scala 175 | def Y(yp: Seq[Double], 176 | label: String = "Label", 177 | style: XYPlotStyle.Type = XYPlotStyle.LinesPoints, 178 | color: Option[Color.Type] = None, 179 | ps: Option[Double] = None, 180 | pt: Option[PointType.Type] = None, 181 | lw: Option[Double] = None, 182 | lt: Option[LineType.Type] = None, 183 | every: Option[Int] = None) 184 | def Yf(f: Double => Double, 185 | label: String = "Label", 186 | style: XYPlotStyle.Type = XYPlotStyle.LinesPoints, 187 | color: Option[Color.Type] = None, 188 | ps: Option[Double] = None, 189 | pt: Option[PointType.Type] = None, 190 | lw: Option[Double] = None, 191 | lt: Option[LineType.Type] = None, 192 | every: Option[Int] = None) 193 | ``` 194 | 195 | If you have sequences of `(x,y)` pairs as your data, or if you want to use different `x` for each series: 196 | 197 | ```scala 198 | xyChart(List(x -> Y(y1), x -> Y(y2))) 199 | xyChart(List(x -> Y(y1, "1"), x -> Y(y2, color = Color.Blue))) 200 | 201 | val xy1 = x zip y1 202 | val xy2 = x zip y2 203 | xyChart(List(XY(xy1), XY(xy2))) 204 | xyChart(List(XY(xy1, "1"), XY(xy2, "2"))) 205 | ``` 206 | 207 | where the customization is similar to above: 208 | 209 | ```scala 210 | def XY(points: Seq[(Double, Double)], 211 | label: String = "Label", 212 | style: XYPlotStyle.Type = XYPlotStyle.LinesPoints, 213 | color: Option[Color.Type] = None, 214 | ps: Option[Double] = None, 215 | pt: Option[PointType.Type] = None, 216 | lw: Option[Double] = None, 217 | lt: Option[LineType.Type] = None, 218 | every: Option[Int] = None) 219 | ``` 220 | 221 | ### Other Implicits 222 | 223 | Scalaplot also supports a number of other implicits to make things easier to use. 224 | 225 | ```scala 226 | val d: XYData = x ->(y1, y2, y3) 227 | val c: XYChart = d // automatic conversion from data to chart 228 | 229 | // series 230 | val s1: XYSeries = x -> y1 231 | val s2: XYSeries = x zip y2 232 | val f1 = math.sin(_) 233 | val s1f: XYSeries = x -> f1 234 | val s2f: XYSeries = x -> Yf(math.sin) 235 | 236 | // series to data 237 | val d1: XYData = s1 238 | val d2: XYData = Seq(s1, s2) 239 | val d2l: XYData = s1 :: s2 :: List() 240 | ``` 241 | 242 | 243 | ## Explicit Data Structures 244 | 245 | For even further customization of the charts, you will need to dive into the API instead of relying on the above *implicits*. 246 | 247 | ### XY Line Charts 248 | 249 | First step is to get your data into `Seq[Double]`. 250 | 251 | ```scala 252 | val x = (1 until 100).map(_.toDouble) 253 | val y = x.map(i => i*i) 254 | ``` 255 | 256 | Create a dataset that represents these sequences. 257 | 258 | ```scala 259 | val series = new MemXYSeries(x, y, "Square") 260 | val data = new XYData(series) 261 | ``` 262 | 263 | You can add more series too. 264 | 265 | ```scala 266 | data += new MemXYSeries(x, x.map(i => i*i*i), "Cube") 267 | ``` 268 | 269 | Let's create the chart. 270 | 271 | ```scala 272 | val chart = new XYChart("Powers!", data) 273 | chart.showLegend = true 274 | ``` 275 | 276 | ## Rendering Charts 277 | 278 | Even though multiple backends are being supported to render the charts, gnuplot is the most actively developed and supported since it allows post plotting customizations (editing the script files), may possible output formats, and ease of use. 279 | 280 | ### Gnuplot 281 | 282 | Generates gnuplot scripts that will need to be run to actually generate the images. 283 | 284 | ```scala 285 | val plotter = new GnuplotPlotter(chart) 286 | plotter.writeToPdf("dir/", "name") 287 | ``` 288 | 289 | The output looks like 290 | 291 | ![Example gnuplot output](https://github.com/sameersingh/scalaplot/raw/master/docs/img/gnuplot.png) 292 | 293 | ### JFreegraph 294 | 295 | JFreegraph can also be called similarly to produce pdf plots (use `JFGraphPlotter`). 296 | However, it also supports a `gui()` option for when you just want to see the graph. 297 | 298 | ```scala 299 | val plotter = new JFGraphPlotter(chart) 300 | plotter.gui() 301 | ``` 302 | 303 | produces 304 | 305 | ![Example jfreegraph output](https://github.com/sameersingh/scalaplot/raw/master/docs/img/jfreegraph.png) 306 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | // This project uses the sbt-pom-reader plugin. 2 | // Most of the dependencies are specified in pom.xml 3 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" 4 | -------------------------------------------------------------------------------- /docs/img/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sameersingh/scalaplot/438a61e5b0b7276f869f73d36230fa5ac4c5897a/docs/img/gnuplot.png -------------------------------------------------------------------------------- /docs/img/jfreegraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sameersingh/scalaplot/438a61e5b0b7276f869f73d36230fa5ac4c5897a/docs/img/jfreegraph.png -------------------------------------------------------------------------------- /docs/img/scatter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sameersingh/scalaplot/438a61e5b0b7276f869f73d36230fa5ac4c5897a/docs/img/scatter.png -------------------------------------------------------------------------------- /docs/img/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sameersingh/scalaplot/438a61e5b0b7276f869f73d36230fa5ac4c5897a/docs/img/test.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | org.sameersingh.scalaplot 4 | scalaplot 5 | 0.2-SNAPSHOT 6 | ${project.artifactId} 7 | Library for plotting the results of experiments 8 | http://www.sameersingh.org/scalaplot 9 | 2012 10 | 11 | 12 | The New BSD License 13 | http://www.opensource.org/licenses/bsd-license.html 14 | 15 | 16 | 17 | 18 | Sameer Singh 19 | sameeersingh@gmail.com 20 | University of Washington 21 | http://www.sameersingh.org 22 | 23 | 24 | 25 | https://github.com/sameersingh/scalaplot 26 | scm:git:git://github.com/sameersingh/scalaplot.git 27 | scm:git:git@github.com:sameersingh/scalaplot.git 28 | HEAD 29 | 30 | 31 | 32 | 1.8 33 | 1.8 34 | UTF-8 35 | 2.11.7 36 | 37 | 38 | 39 | 40 | scala-tools.org 41 | Scala-Tools Maven2 Repository 42 | http://scala-tools.org/repo-releases 43 | 44 | 45 | 46 | 47 | 48 | scala-tools.org 49 | Scala-Tools Maven2 Repository 50 | http://scala-tools.org/repo-releases 51 | 52 | 53 | 54 | 55 | 56 | org.scala-lang 57 | scala-library 58 | ${scala.version} 59 | 60 | 61 | 62 | 63 | jfree 64 | jfreechart 65 | 1.0.13 66 | 67 | 68 | com.itextpdf 69 | itextpdf 70 | 5.1.2 71 | jar 72 | 73 | 74 | com.itextpdf.tool 75 | xmlworker 76 | 1.1.0 77 | jar 78 | 79 | 80 | 81 | 82 | junit 83 | junit 84 | 4.10 85 | test 86 | 87 | 88 | 89 | 90 | 91 | ossrh 92 | https://oss.sonatype.org/content/repositories/snapshots 93 | 94 | 95 | ossrh 96 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 97 | 98 | 99 | 100 | 101 | src/main/scala 102 | src/test/scala 103 | 104 | 105 | net.alchim31.maven 106 | scala-maven-plugin 107 | 3.1.6 108 | 109 | 111 | ${scala.version} 112 | 113 | 114 | 115 | attach-javadocs 116 | 117 | doc-jar 118 | 119 | 120 | 121 | 122 | compile 123 | testCompile 124 | 125 | 126 | 127 | 128 | -dependencyfile 129 | ${project.build.directory}/.scala_dependencies 130 | 131 | 132 | 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-surefire-plugin 138 | 2.10 139 | 140 | false 141 | true 142 | 143 | 144 | 145 | **/*Test.* 146 | **/*Suite.* 147 | 148 | 149 | 150 | 151 | org.apache.maven.plugins 152 | maven-release-plugin 153 | 2.5 154 | 155 | 156 | org.apache.maven.plugins 157 | maven-gpg-plugin 158 | 1.5 159 | 160 | 161 | sign-artifacts 162 | verify 163 | 164 | sign 165 | 166 | 167 | 168 | 169 | 170 | maven-source-plugin 171 | 2.3 172 | 173 | 174 | attach-sources 175 | 176 | jar 177 | 178 | 179 | 180 | 181 | 182 | maven-javadoc-plugin 183 | 2.9.1 184 | 185 | 186 | attach-javadocs 187 | 188 | jar 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.9 2 | -------------------------------------------------------------------------------- /project/build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | object MyBuild extends com.typesafe.sbt.pom.PomBuild 3 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-pom-reader" % "2.0.0") 2 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/BarChart.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import scala.collection.mutable.ArrayBuffer 4 | import java.io.PrintWriter 5 | import Style._ 6 | 7 | /** 8 | * @author sameer 9 | * @since 7/21/14. 10 | */ 11 | 12 | class DiscreteAxis { 13 | private var _label: String = "" 14 | 15 | def label = _label 16 | 17 | def label_=(l: String) = _label = l 18 | } 19 | 20 | abstract class BarSeries { 21 | def name: String 22 | 23 | def points: Seq[(Int, Double)] 24 | 25 | var color: Option[Color.Type] = None 26 | // fill style 27 | var fillStyle: Option[FillStyle.Type] = None 28 | var density: Option[Double] = None 29 | var pattern: Option[Int] = None 30 | // border config 31 | var border: Option[Boolean] = None 32 | var borderLineType: Option[LineType.Type] = None 33 | } 34 | 35 | class MemBarSeries(val ys: Seq[Double], val name: String = "Label") extends BarSeries { 36 | def isLarge: Boolean = ys.length > 1000 37 | 38 | def writeToFile(filename: String, names: Int => String) { 39 | val writer = new PrintWriter(filename) 40 | for (i <- 0 until xs.length) 41 | writer.println(xs(i).toString + "\t" + ys(i).toString + "\t" + names(i)) 42 | writer.flush() 43 | writer.close() 44 | } 45 | 46 | val xs = (0 until ys.length) 47 | 48 | def toStrings(names: Int => String): Seq[String] = (0 until xs.length).map(i => xs(i).toString + "\t" + ys(i).toString + "\t" + names(i)) 49 | 50 | def points = xs.zip(ys).seq 51 | } 52 | 53 | 54 | class BarData(val names: (Int) => String = _.toString, 55 | ss: Seq[BarSeries]) extends Data { 56 | def this(series: BarSeries*) = this(_.toString, series) 57 | 58 | val _serieses = new ArrayBuffer[BarSeries]() 59 | _serieses ++= ss 60 | 61 | def serieses: Seq[BarSeries] = _serieses 62 | 63 | def +=(s: BarSeries) = _serieses += s 64 | } 65 | 66 | 67 | class BarChart(chartTitle: Option[String], val data: BarData, 68 | val x: DiscreteAxis = new DiscreteAxis, val y: NumericAxis = new NumericAxis) extends Chart { 69 | def this(chartTitle: String, data: BarData) = this(Some(chartTitle), data) 70 | 71 | def this(data: BarData) = this(None, data) 72 | } 73 | 74 | trait BarSeriesImplicits { 75 | 76 | case class Bar(yp: Seq[Double], 77 | label: String = "Label", 78 | color: Option[Color.Type] = None, 79 | fillStyle: Option[FillStyle.Type] = None, 80 | density: Option[Double] = None, 81 | pattern: Option[Int] = None, 82 | border: Option[Boolean] = None, 83 | borderLineType: Option[LineType.Type] = None) 84 | 85 | def series(y: Bar): BarSeries = { 86 | val s = new MemBarSeries(y.yp, y.label) 87 | s.color = y.color 88 | s.fillStyle = y.fillStyle 89 | s.density = y.density 90 | s.pattern = y.pattern 91 | s.border = y.border 92 | s.borderLineType = y.borderLineType 93 | s 94 | } 95 | 96 | implicit def seqToBar(ys: Seq[Double]): Bar = Bar(ys.toSeq) 97 | 98 | implicit def seqToSeries(ys: Seq[Double]): BarSeries = new MemBarSeries(ys.toSeq) 99 | 100 | implicit def barToSeries(bar: Bar): BarSeries = series(bar) 101 | } 102 | 103 | object BarSeriesImplicits extends BarSeriesImplicits 104 | 105 | trait BarDataImplicits extends BarSeriesImplicits { 106 | def data(names: Int => String, ss: Iterable[BarSeries]): BarData = new BarData(names, ss.toSeq) 107 | 108 | implicit def seriesSeqToBarData(ss: Iterable[BarSeries]): BarData = new BarData(ss = ss.toSeq) 109 | 110 | implicit def barSeriesSeqToBarData(ss: Iterable[Bar]): BarData = new BarData(ss = ss.map(b => series(b)).toSeq) 111 | 112 | implicit def seriesToBarData(s: BarSeries): BarData = seriesSeqToBarData(Seq(s)) 113 | 114 | implicit def barSeriesSeqToSeqBarSeries(ss: Iterable[Bar]): Iterable[BarSeries] = ss.map(b => series(b)).toSeq 115 | 116 | implicit def seriesToSeqBarSeries(s: BarSeries): Iterable[BarSeries] = Seq(s) 117 | 118 | // seq of double 119 | implicit def seqYToData(y: Seq[Double]): BarData = seqToSeries(y) 120 | 121 | implicit def seqY2ToData(ys: Product2[Seq[Double], Seq[Double]]): BarData = seriesSeqToBarData(Seq(barToSeries(ys._1), barToSeries(ys._2))) 122 | 123 | implicit def seqY3ToData(ys: Product3[Seq[Double], Seq[Double], Seq[Double]]): BarData = seriesSeqToBarData(Seq(barToSeries(ys._1), barToSeries(ys._2), barToSeries(ys._3))) 124 | 125 | implicit def seqY4ToData(ys: Product4[Seq[Double], Seq[Double], Seq[Double], Seq[Double]]): BarData = seriesSeqToBarData(Seq(barToSeries(ys._1), barToSeries(ys._2), barToSeries(ys._3), barToSeries(ys._4))) 126 | 127 | implicit def seqY5ToData(ys: Product5[Seq[Double], Seq[Double], Seq[Double], Seq[Double], Seq[Double]]): BarData = seriesSeqToBarData(Seq(barToSeries(ys._1), barToSeries(ys._2), barToSeries(ys._3), barToSeries(ys._4), barToSeries(ys._5))) 128 | 129 | implicit def seqY6ToData(ys: Product6[Seq[Double], Seq[Double], Seq[Double], Seq[Double], Seq[Double], Seq[Double]]): BarData = seriesSeqToBarData(Seq(barToSeries(ys._1), barToSeries(ys._2), barToSeries(ys._3), barToSeries(ys._4), barToSeries(ys._5), barToSeries(ys._6))) 130 | 131 | implicit def seqYsToData(ys: Seq[Seq[Double]]): BarData = ys.map(y => series(y)) 132 | 133 | // names => seq of double 134 | implicit def namedSeqYToData(ny: Product2[Int => String, Seq[Double]]): BarData = data(ny._1, Seq(ny._2)) 135 | 136 | implicit def namedSeqY2ToData(nys: Product2[Int => String, Product2[Seq[Double], Seq[Double]]]): BarData = 137 | data(nys._1, Seq(barToSeries(nys._2._1), barToSeries(nys._2._2))) 138 | 139 | implicit def namedSeqY3ToData(nys: Product2[Int => String, Product3[Seq[Double], Seq[Double], Seq[Double]]]): BarData = 140 | data(nys._1, Seq(barToSeries(nys._2._1), barToSeries(nys._2._2), barToSeries(nys._2._3))) 141 | 142 | implicit def namedSeqY4ToData(nys: Product2[Int => String, Product4[Seq[Double], Seq[Double], Seq[Double], Seq[Double]]]): BarData = 143 | data(nys._1, Seq(barToSeries(nys._2._1), barToSeries(nys._2._2), barToSeries(nys._2._3), barToSeries(nys._2._4))) 144 | 145 | implicit def namedSeqY5ToData(nys: Product2[Int => String, Product5[Seq[Double], Seq[Double], Seq[Double], Seq[Double], Seq[Double]]]): BarData = 146 | data(nys._1, Seq(barToSeries(nys._2._1), barToSeries(nys._2._2), barToSeries(nys._2._3), barToSeries(nys._2._4), barToSeries(nys._2._5))) 147 | 148 | implicit def namedSeqY6ToData(nys: Product2[Int => String, Product6[Seq[Double], Seq[Double], Seq[Double], Seq[Double], Seq[Double], Seq[Double]]]): BarData = 149 | data(nys._1, Seq(barToSeries(nys._2._1), barToSeries(nys._2._2), barToSeries(nys._2._3), barToSeries(nys._2._4), barToSeries(nys._2._5), barToSeries(nys._2._6))) 150 | 151 | implicit def namedSeqYsToData(nys: Product2[Int => String, Seq[Seq[Double]]]): BarData = data(nys._1, nys._2.map(y => barToSeries(y))) 152 | 153 | // seq of Bars 154 | implicit def barToData(y: Bar): BarData = series(y) 155 | 156 | implicit def bar2ToData(ys: Product2[Bar, Bar]): BarData = Seq(ys._1, ys._2) 157 | 158 | implicit def bar3ToData(ys: Product3[Bar, Bar, Bar]): BarData = Seq(ys._1, ys._2, ys._3) 159 | 160 | implicit def bar4ToData(ys: Product4[Bar, Bar, Bar, Bar]): BarData = Seq(ys._1, ys._2, ys._3, ys._4) 161 | 162 | implicit def bar5ToData(ys: Product5[Bar, Bar, Bar, Bar, Bar]): BarData = Seq(ys._1, ys._2, ys._3, ys._4, ys._5) 163 | 164 | implicit def bar6ToData(ys: Product6[Bar, Bar, Bar, Bar, Bar, Bar]): BarData = Seq(ys._1, ys._2, ys._3, ys._4, ys._5, ys._6) 165 | 166 | // names => seq of Bars 167 | implicit def namedBarToData(ny: Product2[Int => String, Bar]): BarData = data(ny._1, Seq(ny._2)) 168 | 169 | implicit def namedBar2ToData(nys: Product2[Int => String, Product2[Bar, Bar]]): BarData = data(nys._1, Seq(nys._2._1, nys._2._2)) 170 | 171 | implicit def namedBar3ToData(nys: Product2[Int => String, Product3[Bar, Bar, Bar]]): BarData = data(nys._1, Seq(nys._2._1, nys._2._2, nys._2._3)) 172 | 173 | implicit def namedBar4ToData(nys: Product2[Int => String, Product4[Bar, Bar, Bar, Bar]]): BarData = data(nys._1, Seq(nys._2._1, nys._2._2, nys._2._3, nys._2._4)) 174 | 175 | implicit def namedBar5ToData(nys: Product2[Int => String, Product5[Bar, Bar, Bar, Bar, Bar]]): BarData = data(nys._1, Seq(nys._2._1, nys._2._2, nys._2._3, nys._2._4, nys._2._5)) 176 | 177 | implicit def namedBar6ToData(nys: Product2[Int => String, Product6[Bar, Bar, Bar, Bar, Bar, Bar]]): BarData = data(nys._1, Seq(nys._2._1, nys._2._2, nys._2._3, nys._2._4, nys._2._5, nys._2._6)) 178 | 179 | implicit def namedBarsToData(nys: Product2[Int => String, Seq[Bar]]): BarData = data(nys._1, nys._2.map(b => barToSeries(b))) 180 | } 181 | 182 | object BarDataImplicits extends BarDataImplicits 183 | 184 | trait BarChartImplicits extends BarDataImplicits { 185 | implicit def dataToChart(d: BarData): BarChart = new BarChart(d) 186 | 187 | def barChart(data: BarData, title: String = "", 188 | xLabel: String = "", 189 | y: NumericAxis = new NumericAxis, 190 | pointSize: Option[Double] = None, 191 | legendPosX: LegendPosX.Type = LegendPosX.Right, 192 | legendPosY: LegendPosY.Type = LegendPosY.Center, 193 | showLegend: Boolean = false, 194 | monochrome: Boolean = false, 195 | size: Option[(Double, Double)] = None 196 | ): BarChart = { 197 | val c = new BarChart(GlobalImplicits.stringToOptionString(title), data, { 198 | val d = new DiscreteAxis(); 199 | d.label = xLabel; 200 | d 201 | }, y) 202 | c.pointSize = pointSize 203 | c.legendPosX = legendPosX 204 | c.legendPosY = legendPosY 205 | c.showLegend = showLegend 206 | c.monochrome = monochrome 207 | c.size = size 208 | c 209 | } 210 | } 211 | 212 | object BarChartImplicits extends BarChartImplicits -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/Chart.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import collection.mutable.{ArrayBuffer, Buffer} 4 | 5 | /** 6 | * @author sameer 7 | * @date 10/9/12 8 | */ 9 | trait Data 10 | 11 | abstract class Chart { 12 | def title: Option[String] = None 13 | 14 | var pointSize: Option[Double] = None 15 | 16 | var legendPosX: LegendPosX.Type = LegendPosX.Right 17 | 18 | var legendPosY: LegendPosY.Type = LegendPosY.Center 19 | 20 | var showLegend: Boolean = false 21 | 22 | var monochrome: Boolean = false 23 | 24 | var size: Option[(Double, Double)] = None 25 | } 26 | 27 | object LegendPosX extends Enumeration { 28 | type Type = Value 29 | val Left, Center, Right = Value 30 | } 31 | 32 | object LegendPosY extends Enumeration { 33 | type Type = Value 34 | val Top, Center, Bottom = Value 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/Implicits.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | /** 4 | * @author sameer 5 | * @since 6/8/14. 6 | */ 7 | class GlobalImplicits { 8 | def Axis(label: String = "", backward: Boolean = false, log: Boolean = false, 9 | range: Option[(Double, Double)] = None): NumericAxis = { 10 | val a = new NumericAxis 11 | a.label = label 12 | if (backward) a.backward else a.forward 13 | if (log) a.log else a.linear 14 | range.foreach({ 15 | case (min, max) => a.range_=(min -> max) 16 | }) 17 | a 18 | } 19 | 20 | implicit def stringToOptionString(string: String): Option[String] = if (string.isEmpty) None else Some(string) 21 | 22 | implicit def anyToOptionAny[A](a: A): Option[A] = Some(a) 23 | 24 | } 25 | 26 | object GlobalImplicits extends GlobalImplicits 27 | 28 | object Implicits extends GlobalImplicits 29 | with XYDataImplicits with XYChartImplicits with XYSeriesImplicits 30 | with BarDataImplicits with BarChartImplicits with BarSeriesImplicits 31 | with PlotterImplicits -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/Plotter.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import org.sameersingh.scalaplot.gnuplot.GnuplotPlotter 4 | import org.sameersingh.scalaplot.jfreegraph.JFGraphPlotter 5 | 6 | /** 7 | * @author sameer 8 | * @date 10/25/12 9 | */ 10 | abstract class Plotter(val chart: Chart) { 11 | 12 | def pdf(directory: String, filenamePrefix: String): Unit 13 | 14 | def png(directory: String, filenamePrefix: String): Unit = throw new Error("png() not implemented") 15 | 16 | def svg(directory: String, filenamePrefix: String): String = throw new Error("svg() not implemented") 17 | 18 | def gui(): Unit = throw new Error("gui() not implemented") 19 | } 20 | 21 | trait PlotterImplicits { 22 | 23 | trait OutputSpecification 24 | 25 | case class PDF(dir: String, fnamePrefix: String) extends OutputSpecification 26 | 27 | case class PNG(dir: String, fnamePrefix: String) extends OutputSpecification 28 | 29 | object ASCII extends OutputSpecification 30 | 31 | object SVG extends OutputSpecification 32 | 33 | object GUI extends OutputSpecification 34 | 35 | private def defaultPlotter(c: Chart) = new GnuplotPlotter(c) 36 | 37 | def output(output: OutputSpecification, chart: Chart): String = output match { 38 | case PDF(dir, fnamePrefix) => { 39 | defaultPlotter(chart).pdf(dir, fnamePrefix) 40 | "" 41 | } 42 | case PNG(dir, fnamePrefix) => { 43 | defaultPlotter(chart).png(dir, fnamePrefix) 44 | "" 45 | } 46 | case GUI => { 47 | val gpl = new JFGraphPlotter(chart) 48 | gpl.gui() 49 | "" 50 | } 51 | case ASCII => { 52 | val file = java.io.File.createTempFile("scalaplot", System.currentTimeMillis().toString) 53 | file.delete() 54 | file.mkdir() 55 | defaultPlotter(chart).string(file.getCanonicalPath + "/", "ascii") 56 | } 57 | case SVG => { 58 | val file = java.io.File.createTempFile("scalaplot", System.currentTimeMillis().toString) 59 | file.delete() 60 | file.mkdir() 61 | defaultPlotter(chart).svg(file.getCanonicalPath + "/", "svg") 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/Style.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | /** 4 | * @author sameer 5 | * @since 8/2/14. 6 | */ 7 | object Style { 8 | object LineType extends Enumeration { 9 | type Type = Value 10 | val Solid = Value 11 | } 12 | 13 | object PointType extends Enumeration { 14 | type Type = Value 15 | val Dot, +, X, *, emptyBox, fullBox, emptyO, fullO, emptyTri, fullTri = Value 16 | } 17 | 18 | object Color extends Enumeration { 19 | type Type = Value 20 | val Black, Grey, Red, Green, Blue, Magenta, Cyan, Maroon, Mustard, RoyalBlue, Gold, DarkGreen, Purple, SteelBlue, Yellow = Value 21 | } 22 | 23 | object FillStyle extends Enumeration { 24 | type Type = Value 25 | val Empty, Solid, Pattern = Value 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/XYChart.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import collection.mutable.{ArrayBuffer, Buffer} 4 | 5 | /** 6 | * @author sameer 7 | * @date 10/9/12 8 | */ 9 | class NumericAxis { 10 | private var _label: String = "" 11 | private var _range: Pair[Option[Double], Option[Double]] = (None, None) 12 | protected var _reverse: Boolean = false 13 | private var _log: Boolean = false 14 | 15 | def label = _label 16 | 17 | def label_=(l: String) = _label = l 18 | 19 | def isBackward = _reverse 20 | 21 | def backward = _reverse = true 22 | 23 | def forward = _reverse = false 24 | 25 | def isLog = _log 26 | 27 | def log = _log = true 28 | 29 | def linear = _log = false 30 | 31 | def range_=(minMax: Pair[Double, Double]) = _range = (Some(minMax._1), Some(minMax._2)) 32 | 33 | def min = _range._1 34 | 35 | def max = _range._2 36 | 37 | def resetRange = _range = (None, None) 38 | } 39 | 40 | class XYChart(chartTitle: Option[String], val data: XYData, 41 | val x: NumericAxis = new NumericAxis, val y: NumericAxis = new NumericAxis) extends Chart { 42 | 43 | def this(chartTitle: String, data: XYData) = this(Some(chartTitle), data) 44 | 45 | def this(data: XYData) = this(None, data) 46 | 47 | override def title = chartTitle 48 | } 49 | 50 | trait XYChartImplicits extends XYDataImplicits { 51 | implicit def dataToChart(d: XYData): XYChart = new XYChart(d) 52 | 53 | def xyChart(data: XYData, title: String = "", 54 | x: NumericAxis = new NumericAxis, 55 | y: NumericAxis = new NumericAxis, 56 | pointSize: Option[Double] = None, 57 | legendPosX: LegendPosX.Type = LegendPosX.Right, 58 | legendPosY: LegendPosY.Type = LegendPosY.Center, 59 | showLegend: Boolean = false, 60 | monochrome: Boolean = false, 61 | size: Option[(Double, Double)] = None 62 | ): XYChart = { 63 | val c = new XYChart(GlobalImplicits.stringToOptionString(title), data, x, y) 64 | c.pointSize = pointSize 65 | c.legendPosX = legendPosX 66 | c.legendPosY = legendPosY 67 | c.showLegend = showLegend 68 | c.monochrome = monochrome 69 | c.size = size 70 | c 71 | } 72 | } 73 | 74 | object XYChartImplicits extends XYChartImplicits -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/XYData.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import collection.mutable.ArrayBuffer 4 | 5 | /** 6 | * @author sameer 7 | * @date 10/9/12 8 | */ 9 | class XYData(ss: XYSeries*) extends Data { 10 | //def this() = this(ss) 11 | 12 | val _serieses = new ArrayBuffer[XYSeries]() 13 | _serieses ++= ss 14 | 15 | def serieses: Seq[XYSeries] = _serieses 16 | 17 | def +=(s: XYSeries) = _serieses += s 18 | } 19 | 20 | trait XYDataImplicits extends XYSeriesImplicits { 21 | implicit def seriesSeqToData(ss: Iterable[XYSeries]): XYData = new XYData(ss.toSeq: _*) 22 | 23 | implicit def seriesToData(s: XYSeries): XYData = new XYData(s) 24 | 25 | // implicit def seriesStarToData(ss: XYSeries*): XYData = new XYData(ss: _*) 26 | 27 | // implicit def data(xys: Pair[Seq[Double], Y]): XYData = new XYData(series(xys._1, xys._2)) 28 | 29 | def Ys(ys: Iterable[Seq[Double]]): Seq[Y] = ys.map(y => Y(y)).toSeq 30 | 31 | def Ys(ys: Seq[Double]*): Seq[Y] = ys.map(y => Y(y)).toSeq 32 | 33 | // seq of double 34 | implicit def xSeqYToData(xy: Pair[Seq[Double], Seq[Double]]): XYData = xy._1 -> Ys(xy._2) 35 | 36 | implicit def xSeqY2ToData(xy: Pair[Seq[Double], Product2[Seq[Double], Seq[Double]]]): XYData = xy._1 -> Ys(xy._2._1, xy._2._2) 37 | 38 | implicit def xSeqY3ToData(xy: Pair[Seq[Double], Product3[Seq[Double], Seq[Double], Seq[Double]]]): XYData = xy._1 -> Ys(xy._2._1, xy._2._2, xy._2._3) 39 | 40 | implicit def xSeqY4ToData(xy: Pair[Seq[Double], Product4[Seq[Double], Seq[Double], Seq[Double], Seq[Double]]]): XYData = xy._1 -> Ys(xy._2._1, xy._2._2, xy._2._3, xy._2._4) 41 | 42 | implicit def xSeqY5ToData(xy: Pair[Seq[Double], Product5[Seq[Double], Seq[Double], Seq[Double], Seq[Double], Seq[Double]]]): XYData = xy._1 -> Ys(xy._2._1, xy._2._2, xy._2._3, xy._2._4, xy._2._5) 43 | 44 | implicit def xSeqY6ToData(xy: Pair[Seq[Double], Product6[Seq[Double], Seq[Double], Seq[Double], Seq[Double], Seq[Double], Seq[Double]]]): XYData = xy._1 -> Ys(xy._2._1, xy._2._2, xy._2._3, xy._2._4, xy._2._5, xy._2._6) 45 | 46 | // seq of funcs 47 | implicit def xFYToData(xy: Pair[Seq[Double], Double => Double]): XYData = xy._1 -> Yfs(xy._2) 48 | 49 | implicit def xFY2ToData(xy: Pair[Seq[Double], Product2[Double => Double, Double => Double]]): XYData = xy._1 -> Yfs(xy._2._1, xy._2._2) 50 | 51 | implicit def xFY3ToData(xy: Pair[Seq[Double], Product3[Double => Double, Double => Double, Double => Double]]): XYData = xy._1 -> Yfs(xy._2._1, xy._2._2, xy._2._3) 52 | 53 | implicit def xFY4ToData(xy: Pair[Seq[Double], Product4[Double => Double, Double => Double, Double => Double, Double => Double]]): XYData = xy._1 -> Yfs(xy._2._1, xy._2._2, xy._2._3, xy._2._4) 54 | 55 | implicit def xFY5ToData(xy: Pair[Seq[Double], Product5[Double => Double, Double => Double, Double => Double, Double => Double, Double => Double]]): XYData = xy._1 -> Yfs(xy._2._1, xy._2._2, xy._2._3, xy._2._4, xy._2._5) 56 | 57 | implicit def xFY6ToData(xy: Pair[Seq[Double], Product6[Double => Double, Double => Double, Double => Double, Double => Double, Double => Double, Double => Double]]): XYData = xy._1 -> Yfs(xy._2._1, xy._2._2, xy._2._3, xy._2._4, xy._2._5, xy._2._6) 58 | 59 | // seq of Ys 60 | implicit def xYToData(xy: Pair[Seq[Double], Y]): XYData = xy._1 -> Seq(xy._2) 61 | 62 | implicit def xY2ToData(xy: Pair[Seq[Double], Product2[Y, Y]]): XYData = xy._1 -> Seq(xy._2._1, xy._2._2) 63 | 64 | implicit def xY3ToData(xy: Pair[Seq[Double], Product3[Y, Y, Y]]): XYData = xy._1 -> Seq(xy._2._1, xy._2._2, xy._2._3) 65 | 66 | implicit def xY4ToData(xy: Pair[Seq[Double], Product4[Y, Y, Y, Y]]): XYData = xy._1 -> Seq(xy._2._1, xy._2._2, xy._2._3, xy._2._4) 67 | 68 | implicit def xY5ToData(xy: Pair[Seq[Double], Product5[Y, Y, Y, Y, Y]]): XYData = xy._1 -> Seq(xy._2._1, xy._2._2, xy._2._3, xy._2._4, xy._2._5) 69 | 70 | implicit def xY6ToData(xy: Pair[Seq[Double], Product6[Y, Y, Y, Y, Y, Y]]): XYData = xy._1 -> Seq(xy._2._1, xy._2._2, xy._2._3, xy._2._4, xy._2._5, xy._2._6) 71 | 72 | type Func = Double => Double 73 | 74 | def Yfs(fs: Iterable[Double => Double]): Seq[Y] = fs.map(f => Yf(f)).toSeq 75 | 76 | def Yfs(fs: Func*): Seq[Y] = fs.map(f => Yf(f)).toSeq 77 | 78 | implicit def xYsToData(xys: Pair[Seq[Double], Iterable[Y]]): XYData = new XYData(xys._2.map(y => series(xys._1, y)).toSeq: _*) 79 | 80 | implicit def xYSeqToData(xys: Iterable[Pair[Seq[Double], Y]]): XYData = new XYData(xys.map(xy => series(xy._1, xy._2)).toSeq: _*) 81 | 82 | implicit def data(xs: Seq[Double], ys: Y*): XYData = new XYData(ys.map(y => series(xs, y)).toSeq: _*) 83 | } 84 | 85 | object XYDataImplicits extends XYDataImplicits -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/XYSeries.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import Style._ 4 | import java.io.PrintWriter 5 | 6 | /** 7 | * @author sameer 8 | * @date 10/9/12 9 | */ 10 | abstract class XYSeries { 11 | def name: String 12 | 13 | var plotStyle: XYPlotStyle.Type = XYPlotStyle.LinesPoints 14 | var color: Option[Color.Type] = None 15 | // point config 16 | var pointSize: Option[Double] = None 17 | var pointType: Option[PointType.Type] = None 18 | // line config 19 | var lineWidth: Option[Double] = None 20 | var lineType: Option[LineType.Type] = None 21 | // how many points to skip between plots 22 | var every: Option[Int] = None 23 | 24 | def points: Iterable[(Double, Double)] 25 | } 26 | 27 | class FileXYSeries(val xcol: Int, val ycol: Int, val name: String, val dataFilename: String) extends XYSeries { 28 | def points = throw new Error("not implemented") 29 | } 30 | 31 | class MemXYSeries(val xs: Seq[Double], val ys: Seq[Double], val name: String = "Label") extends XYSeries { 32 | def this(points: Seq[(Double, Double)], name: String) = this(points.map(_._1), points.map(_._2), name) 33 | 34 | def this(points: Seq[(Double, Double)]) = this(points, "Label") 35 | 36 | assert(xs.length == ys.length) 37 | 38 | def isLarge: Boolean = xs.length > 1000 39 | 40 | def writeToFile(filename: String) { 41 | val writer = new PrintWriter(filename) 42 | for (i <- 0 until xs.length) 43 | writer.println(xs(i).toString + "\t" + ys(i).toString) 44 | writer.flush() 45 | writer.close() 46 | } 47 | 48 | def toStrings(): Seq[String] = (0 until xs.length).map(i => xs(i).toString + "\t" + ys(i).toString) 49 | 50 | def points = xs.zip(ys).seq 51 | } 52 | 53 | object XYPlotStyle extends Enumeration { 54 | type Type = Value 55 | val Lines, Points, LinesPoints, Dots, Impulses = Value 56 | } 57 | 58 | trait XYSeriesImplicits { 59 | 60 | trait YPoints { 61 | def xsToYs(xs: Seq[Double]): Seq[Double] 62 | } 63 | 64 | abstract class Y(val label: String = "Label", 65 | val style: XYPlotStyle.Type = XYPlotStyle.LinesPoints, 66 | val color: Option[Color.Type] = None, 67 | val ps: Option[Double] = None, 68 | val pt: Option[PointType.Type] = None, 69 | val lw: Option[Double] = None, 70 | val lt: Option[LineType.Type] = None, 71 | val every: Option[Int] = None) extends YPoints 72 | 73 | object Y { 74 | def apply(yp: Seq[Double], 75 | label: String = "Label", 76 | style: XYPlotStyle.Type = XYPlotStyle.LinesPoints, 77 | color: Option[Color.Type] = None, 78 | ps: Option[Double] = None, 79 | pt: Option[PointType.Type] = None, 80 | lw: Option[Double] = None, 81 | lt: Option[LineType.Type] = None, 82 | every: Option[Int] = None): Y = new Y(label, style, color, ps, pt, lw, lt, every) { 83 | override def xsToYs(xs: Seq[Double]): Seq[Double] = yp 84 | } 85 | } 86 | 87 | object Yf { 88 | def apply(f: Double => Double, 89 | label: String = "Label", 90 | style: XYPlotStyle.Type = XYPlotStyle.LinesPoints, 91 | color: Option[Color.Type] = None, 92 | ps: Option[Double] = None, 93 | pt: Option[PointType.Type] = None, 94 | lw: Option[Double] = None, 95 | lt: Option[LineType.Type] = None, 96 | every: Option[Int] = None): Y = new Y(label, style, color, ps, pt, lw, lt, every) { 97 | override def xsToYs(xs: Seq[Double]): Seq[Double] = xs.map(f) 98 | } 99 | } 100 | 101 | object XY { 102 | def apply(points: Seq[(Double, Double)], 103 | label: String = "Label", 104 | style: XYPlotStyle.Type = XYPlotStyle.LinesPoints, 105 | color: Option[Color.Type] = None, 106 | ps: Option[Double] = None, 107 | pt: Option[PointType.Type] = None, 108 | lw: Option[Double] = None, 109 | lt: Option[LineType.Type] = None, 110 | every: Option[Int] = None): XYSeries = { 111 | val s = new MemXYSeries(points, label) 112 | s.color = color 113 | s.plotStyle = style 114 | s.pointSize = ps 115 | s.pointType = pt 116 | s.lineWidth = lw 117 | s.lineType = lt 118 | s.every = every 119 | s 120 | } 121 | } 122 | 123 | def series(xs: Seq[Double], y: Y): XYSeries = { 124 | val s = new MemXYSeries(xs, y.xsToYs(xs), y.label) 125 | s.color = y.color 126 | s.plotStyle = y.style 127 | s.pointSize = y.ps 128 | s.pointType = y.pt 129 | s.lineWidth = y.lw 130 | s.lineType = y.lt 131 | s.every = y.every 132 | s 133 | } 134 | 135 | implicit def pairSeqToSeries(xys: Iterable[(Double, Double)]): XYSeries = new MemXYSeries(xys.toSeq) 136 | 137 | implicit def seqPairToSeries(xy: Pair[Iterable[Double], Iterable[Double]]): XYSeries = new MemXYSeries(xy._1.toSeq, xy._2.toSeq) 138 | 139 | implicit def seqFuncPairToSeries(xy: Pair[Iterable[Double], Double => Double]): XYSeries = new MemXYSeries(xy._1.toSeq, xy._1.map(xy._2).toSeq) 140 | 141 | implicit def xyspectoSeries(xy: Pair[Seq[Double], Y]): XYSeries = series(xy._1, xy._2) 142 | } 143 | 144 | object XYSeriesImplicits extends XYSeriesImplicits -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/gnuplot/Add.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot.gnuplot 2 | 3 | /** 4 | * @author sameer 5 | * @date 10/9/12 6 | */ 7 | object Add { 8 | 9 | val alpha = "{/Symbol a}" 10 | val Alpha = "{/Symbol A}" 11 | val beta = "{/Symbol b}" 12 | val Beta = "{/Symbol B}" 13 | val delta = "{/Symbol d}" 14 | val Delta = "{/Symbol D}" 15 | val phi = "{/Symbol f}" 16 | val Phi = "{/Symbol F}" 17 | val gamma = "{/Symbol g}" 18 | val Gamma = "{/Symbol G}" 19 | val lambda = "{/Symbol l}" 20 | val Lambda = "{/Symbol L}" 21 | val pi = "{/Symbol p}" 22 | val Pi = "{/Symbol P}" 23 | val theta = "{/Symbol q}" 24 | val Theta = "{/Symbol Q}" 25 | val tau = "{/Symbol t}" 26 | val Tau = "{/Symbol T}" 27 | 28 | def func(function: String): Seq[String] = { 29 | Seq("replot %s" format (function)) 30 | } 31 | 32 | def label(labelStr: String, xpos: Double, ypos: Double): Seq[String] = { 33 | Seq("set label \"%s\" at %f,%f" format(labelStr, xpos, ypos)) 34 | } 35 | 36 | def arrow(x1: Double, y1: Double, x2: Double, y2: Double): Seq[String] = { 37 | Seq("set arrow from %f,%f to %f,%f" format(x1, y1, x2, y2)) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/gnuplot/GnuplotPlotter.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot.gnuplot 2 | 3 | import org.sameersingh.scalaplot._ 4 | import collection.mutable.ArrayBuffer 5 | import java.io.{InputStreamReader, BufferedReader, File, PrintWriter} 6 | import Style._ 7 | 8 | /** 9 | * @author sameer 10 | * @date 10/25/12 11 | */ 12 | class GnuplotPlotter(chart: Chart) extends Plotter(chart) { 13 | 14 | var lines = new ArrayBuffer[String] 15 | var directory: String = "" 16 | var filename: String = "" 17 | var isFirst = false 18 | var isLast = false 19 | 20 | protected def getColorname(color: Color.Type): String = { 21 | import Color._ 22 | color match { 23 | case Black => "black" 24 | case Grey => "dark-grey" 25 | case Red => "red" 26 | case Green => "web-green" 27 | case Blue => "web-blue" 28 | case Magenta => "dark-magenta" 29 | case Cyan => "dark-cyan" 30 | case Maroon => "dark-orange" 31 | case Mustard => "dark-yellow" 32 | case RoyalBlue => "royalblue" 33 | case Gold => "goldenrod" 34 | case DarkGreen => "dark-spring-green" 35 | case Purple => "purple" 36 | case SteelBlue => "steelblue" 37 | case Yellow => "yellow" 38 | } 39 | } 40 | 41 | protected def getPointType(pt: PointType.Type): Int = { 42 | pt match { 43 | case PointType.Dot => 0 44 | case PointType.+ => 1 45 | case PointType.X => 2 46 | case PointType.* => 3 47 | case PointType.emptyBox => 4 48 | case PointType.fullBox => 5 49 | case PointType.emptyO => 6 50 | case PointType.fullO => 7 51 | case PointType.emptyTri => 8 52 | case PointType.fullTri => 9 53 | } 54 | } 55 | 56 | protected def getLineStyle(s: XYSeries): String = 57 | getLineStyle(s.plotStyle, s.color, s.pointSize, s.pointType, s.lineWidth, s.lineType) 58 | 59 | protected def getLineStyle(plotStyle: XYPlotStyle.Type, 60 | col: Option[Color.Type], 61 | pointSize: Option[Double], 62 | pointType: Option[PointType.Type], 63 | lineWidth: Option[Double], 64 | lineType: Option[LineType.Type]): String = { 65 | val sb = new StringBuffer() 66 | sb append "with " 67 | sb append (plotStyle match { 68 | case XYPlotStyle.Lines => "lines" 69 | case XYPlotStyle.LinesPoints => "linespoints" 70 | case XYPlotStyle.Points => "points" 71 | case XYPlotStyle.Dots => "dots" 72 | case XYPlotStyle.Impulses => "impulses" 73 | }) 74 | if (col.isDefined) 75 | sb.append(" linecolor rgbcolor \"%s\"" format (getColorname(col.get))) 76 | if (pointSize.isDefined) 77 | sb.append(" pointsize %f" format (pointSize.get)) 78 | if (pointType.isDefined) 79 | sb.append(" pointtype %d" format (getPointType(pointType.get))) 80 | if (lineWidth.isDefined) 81 | sb.append(" linewidth %f" format (lineWidth.get)) 82 | // TODO line type 83 | sb.toString 84 | } 85 | 86 | protected def getFillStyle(s: BarSeries): String = 87 | getFillStyle(s.color, s.border, s.borderLineType, s.fillStyle, s.density, s.pattern) 88 | 89 | protected def getFillStyle(col: Option[Color.Type], 90 | border: Option[Boolean], 91 | borderLineType: Option[LineType.Type], 92 | fillStyle: Option[FillStyle.Type], 93 | density: Option[Double], 94 | pattern: Option[Int]): String = { 95 | val sb = new StringBuffer() 96 | sb append "with histogram" 97 | if (col.isDefined) 98 | sb.append(" linecolor rgbcolor \"%s\"" format (getColorname(col.get))) 99 | if (border.isDefined || fillStyle.isDefined) { 100 | sb.append(" fill ") 101 | fillStyle.foreach({ 102 | case FillStyle.Empty => sb.append("empty ") 103 | case FillStyle.Solid => sb.append("solid "); density.foreach(d => sb.append(d + " ")) 104 | case FillStyle.Pattern => sb.append("pattern "); pattern.foreach(i => sb.append(i + " ")) 105 | }) 106 | border.foreach({ 107 | case false => sb.append("noborder ") 108 | // TODO line type 109 | case true => sb.append("border ") 110 | }) 111 | } 112 | sb.toString 113 | } 114 | 115 | def plotChart(chart: Chart, defaultTerminal: String = "dumb") { 116 | lines += "# Chart settings" 117 | chart.title.foreach(t => lines += "set title \"%s\"" format (t)) 118 | chart.pointSize.foreach(t => lines += "set pointSize %f" format (t)) 119 | // legend 120 | if (chart.showLegend) { 121 | lines += "set key %s %s" format(chart.legendPosX.toString.toLowerCase, chart.legendPosY.toString.toLowerCase) 122 | } else lines += "unset key" 123 | lines += "set terminal " + defaultTerminal 124 | lines += "" 125 | } 126 | 127 | def plotBarChart(chart: BarChart) { 128 | lines += "# BarChart settings" 129 | if (chart.y.isLog) lines += "set logscale y" 130 | else lines += "set nologscale" 131 | val yr1s = if (chart.y.min.isDefined) chart.y.min.get.toString else "*" 132 | val yr2s = if (chart.y.max.isDefined) chart.y.max.get.toString else "*" 133 | lines += "set yr [%s:%s] %sreverse" format(yr1s, yr2s, if (chart.y.isBackward) "" else "no") 134 | lines += "set xlabel \"%s\"" format (chart.x.label) 135 | lines += "set ylabel \"%s\"" format (chart.y.label) 136 | plotBarData(chart.data) 137 | } 138 | 139 | def plotBarData(data: BarData) { 140 | lines += "# BarData Plotting" 141 | lines += "set style data histogram" 142 | lines += "set style histogram cluster gap 1" 143 | lines += "set style fill solid border -1" 144 | lines += "plot \\" 145 | var index = 0 146 | for (series: BarSeries <- data.serieses) { 147 | isFirst = (series == data.serieses.head) 148 | isLast = (series == data.serieses.last) 149 | plotBarSeries(series, index) 150 | index += 1 151 | } 152 | // store the data if required 153 | for (series: BarSeries <- data.serieses) { 154 | isFirst = (series == data.serieses.head) 155 | isLast = (series == data.serieses.last) 156 | postPlotBarSeries(series, data.names) 157 | } 158 | lines += "" 159 | } 160 | 161 | def plotBarSeries(series: BarSeries, index: Int) { 162 | series match { 163 | case m: MemBarSeries => plotMemBarSeries(m, index) 164 | } 165 | } 166 | 167 | def postPlotBarSeries(series: BarSeries, names: Int => String) { 168 | series match { 169 | case m: MemBarSeries => postPlotMemBarSeries(m, names) 170 | } 171 | } 172 | 173 | def plotMemBarSeries(series: MemBarSeries, index: Int) { 174 | val suffix = if (isLast) "" else ", \\" 175 | val dataFilename = if (series.isLarge) filename + "-" + series.name + ".dat" else "-" 176 | val using = if (index == 0) "2:xtic(3)" else "2:xtic(3)" 177 | val fill = getFillStyle(series) 178 | lines += "'%s' using %s title \"%s\" %s%s" format(dataFilename, using, series.name, fill, suffix) 179 | } 180 | 181 | def postPlotMemBarSeries(series: MemBarSeries, names: Int => String) { 182 | if (series.isLarge) { 183 | // write to file, then refer to it in the script 184 | series.writeToFile(directory + filename + "-" + series.name + ".dat", names) 185 | } else { 186 | // write directly to the lines 187 | lines += "# %s" format (series.name) 188 | lines ++= series.toStrings(names) 189 | lines += "end" 190 | } 191 | } 192 | 193 | def plotXYChart(chart: XYChart) { 194 | lines += "# XYChart settings" 195 | if (chart.x.isLog && chart.y.isLog) lines += "set logscale" 196 | else if (chart.x.isLog) lines += "set logscale x" 197 | else if (chart.y.isLog) lines += "set logscale y" 198 | else lines += "set nologscale" 199 | val xr1s = if (chart.x.min.isDefined) chart.x.min.get.toString else "*" 200 | val xr2s = if (chart.x.max.isDefined) chart.x.max.get.toString else "*" 201 | val yr1s = if (chart.y.min.isDefined) chart.y.min.get.toString else "*" 202 | val yr2s = if (chart.y.max.isDefined) chart.y.max.get.toString else "*" 203 | lines += "set xr [%s:%s] %sreverse" format(xr1s, xr2s, if (chart.x.isBackward) "" else "no") 204 | lines += "set yr [%s:%s] %sreverse" format(yr1s, yr2s, if (chart.y.isBackward) "" else "no") 205 | lines += "set xlabel \"%s\"" format (chart.x.label) 206 | lines += "set ylabel \"%s\"" format (chart.y.label) 207 | plotXYData(chart.data) 208 | } 209 | 210 | def plotXYData(data: XYData) { 211 | lines += "# XYData Plotting" 212 | lines += "plot \\" 213 | for (series: XYSeries <- data.serieses) { 214 | isFirst = (series == data.serieses.head) 215 | isLast = (series == data.serieses.last) 216 | plotXYSeries(series) 217 | } 218 | // store the data if required 219 | for (series: XYSeries <- data.serieses) { 220 | isFirst = (series == data.serieses.head) 221 | isLast = (series == data.serieses.last) 222 | postPlotXYSeries(series) 223 | } 224 | lines += "" 225 | } 226 | 227 | def plotXYSeries(series: XYSeries) { 228 | series match { 229 | case m: MemXYSeries => plotMemXYSeries(m) 230 | case f: FileXYSeries => plotFileXYSeries(f) 231 | } 232 | } 233 | 234 | def postPlotXYSeries(series: XYSeries) { 235 | series match { 236 | case m: MemXYSeries => postPlotMemXYSeries(m) 237 | case f: FileXYSeries => postPlotFileXYSeries(f) 238 | } 239 | } 240 | 241 | def plotMemXYSeries(series: MemXYSeries) { 242 | val suffix = if (isLast) "" else ", \\" 243 | val dataFilename = if (series.isLarge) filename + "-" + series.name + ".dat" else "-" 244 | val everyString = if (!series.every.isDefined) "" else "every %d" format (series.every.get) 245 | lines += "'%s' %s using %d:%d title \"%s\" %s %s" format(dataFilename, everyString, 1, 2, series.name, getLineStyle(series), suffix) 246 | } 247 | 248 | def plotFileXYSeries(series: FileXYSeries) { 249 | val suffix = if (isLast) "" else ", \\" 250 | val everyString = if (!series.every.isDefined) "" else "every %d" format (series.every.get) 251 | lines += "'%s' %s using %d:%d title \"%s\" %s %s" format(series.dataFilename, everyString, series.xcol, series.ycol, series.name, getLineStyle(series), suffix) 252 | } 253 | 254 | def postPlotMemXYSeries(series: MemXYSeries) { 255 | if (series.isLarge) { 256 | // write to file, then refer to it in the script 257 | series.writeToFile(directory + filename + "-" + series.name + ".dat") 258 | } else { 259 | // write directly to the lines 260 | lines += "# %s" format (series.name) 261 | lines ++= series.toStrings() 262 | lines += "end" 263 | } 264 | } 265 | 266 | def reset = { 267 | lines.clear 268 | directory = "" 269 | filename = "" 270 | isFirst = false 271 | isLast = false 272 | } 273 | 274 | def postPlotFileXYSeries(series: FileXYSeries) {} 275 | 276 | def writeScriptFile(directory: String, filenamePrefix: String, terminal: String, 277 | filenameSuffix: String, stdout: Boolean = false, defaultTerminal: String = "dumb") { 278 | // write the description 279 | assert(new File(directory).isDirectory, directory + " should be a directory") 280 | assert(directory.endsWith("/"), directory + " should end with a /") 281 | reset 282 | this.directory = directory 283 | filename = filenamePrefix 284 | plotChart(chart, terminal) 285 | lines += "set terminal %s" format (terminal) 286 | if (stdout) lines += "set output" 287 | else lines += "set output \"%s\"" format (filename + "." + filenameSuffix) 288 | chart match { 289 | case xyc: XYChart => plotXYChart(xyc) 290 | case bc: BarChart => plotBarChart(bc) 291 | } 292 | lines += "unset output" 293 | lines += "# Wrapup" 294 | lines += "set terminal %s" format (defaultTerminal) 295 | lines += "refresh" 296 | 297 | val scriptFile = directory + filenamePrefix + ".gpl" 298 | val writer = new PrintWriter(scriptFile) 299 | for (line <- lines) { 300 | writer.println(line) 301 | } 302 | writer.close() 303 | } 304 | 305 | override def pdf(directory: String, filenamePrefix: String) { 306 | val monochromeString = if (chart.monochrome) "monochrome" else "" 307 | val sizeString = if (chart.size.isDefined) "size %f,%f" format(chart.size.get._1, chart.size.get._2) else "" 308 | val terminal = "pdf enhanced linewidth 3.0 %s %s" format(monochromeString, sizeString) 309 | writeScriptFile(directory, filenamePrefix, terminal, "pdf") 310 | runGnuplot(directory, filenamePrefix) 311 | } 312 | 313 | override def png(directory: String, filenamePrefix: String) { 314 | if (chart.monochrome) println("Warning: Monochrome ignored.") 315 | val sizeString = if (chart.size.isDefined) "size %f,%f" format(chart.size.get._1, chart.size.get._2) else "" 316 | val terminal = "png enhanced %s" format (sizeString) 317 | writeScriptFile(directory, filenamePrefix, terminal, "png") 318 | runGnuplot(directory, filenamePrefix) 319 | } 320 | 321 | override def svg(directory: String, filenamePrefix: String): String = { 322 | if (chart.monochrome) println("Warning: Monochrome ignored.") 323 | val sizeString = if (chart.size.isDefined) "size %f,%f" format(chart.size.get._1, chart.size.get._2) else "" 324 | val terminal = "svg enhanced linewidth 3.0 %s" format (sizeString) 325 | writeScriptFile(directory, filenamePrefix, terminal, "svg", true, "unknown") 326 | runGnuplot(directory, filenamePrefix) 327 | } 328 | 329 | def string(directory: String, filenamePrefix: String): String = { 330 | val terminal = "dumb enhanced" 331 | writeScriptFile(directory, filenamePrefix, terminal, "txt", true, "unknown") 332 | runGnuplot(directory, filenamePrefix) 333 | } 334 | 335 | def html(directory: String, filenamePrefix: String) { 336 | if (chart.monochrome) println("Warning: Monochrome ignored.") 337 | val sizeString = if (chart.size.isDefined) "size %f,%f" format(chart.size.get._1, chart.size.get._2) else "" 338 | val terminal = "canvas enhanced %s" format (sizeString) 339 | writeScriptFile(directory, filenamePrefix, terminal, "html") 340 | runGnuplot(directory, filenamePrefix) 341 | } 342 | 343 | def js(directory: String, filenamePrefix: String): String = { 344 | if (chart.monochrome) println("Warning: Monochrome ignored.") 345 | val sizeString = if (chart.size.isDefined) "size %f,%f" format(chart.size.get._1, chart.size.get._2) else "" 346 | val terminal = "canvas enhanced name\"%s\"" format (filenamePrefix) 347 | writeScriptFile(directory, filenamePrefix, terminal, "js") 348 | runGnuplot(directory, filenamePrefix) 349 | htmlWrap(directory, filenamePrefix) 350 | } 351 | 352 | def js2(directory: String, filenamePrefix: String): String = { 353 | // write the description 354 | assert(new File(directory).isDirectory, directory + " should be a directory") 355 | assert(directory.endsWith("/"), directory + " should end with a /") 356 | reset 357 | this.directory = directory 358 | filename = filenamePrefix 359 | plotChart(chart) 360 | chart match { 361 | case xyc: XYChart => plotXYChart(xyc) 362 | } 363 | lines += "# Wrapup" 364 | lines += "set terminal canvas enhanced name \"%s\"" format (filenamePrefix) 365 | lines += "set output \"%s\"" format (filename + ".js") 366 | lines += "refresh" 367 | lines += "unset output" 368 | val scriptFile = directory + filenamePrefix + ".gpl" 369 | val writer = new PrintWriter(scriptFile) 370 | for (line <- lines) { 371 | writer.println(line) 372 | } 373 | writer.close() 374 | val str = runGnuplot(directory, filenamePrefix) 375 | htmlWrap(directory, filenamePrefix) 376 | } 377 | 378 | private def htmlWrap(directory: String, filenamePrefix: String, jsDir: String = "/usr/local/Cellar/gnuplot/4.6.5/share/gnuplot/4.6/js") = { 379 | """ 380 | | 381 | | 382 | | 383 | | 384 | | 385 | | 386 | | 387 | | 388 | | 389 | |
No support for HTML 5 canvas element
390 | |
391 | | 392 | | 393 | """.stripMargin format(jsDir, jsDir, jsDir, filenamePrefix, filenamePrefix, filenamePrefix) 394 | } 395 | 396 | def runGnuplot(directory: String, filenamePrefix: String): String = { 397 | var line: String = "" 398 | var output = "" 399 | val cmdLine = "gnuplot " + filenamePrefix + ".gpl" 400 | 401 | try { 402 | val p = Runtime.getRuntime().exec(cmdLine, Array.empty[String], new File(directory)) 403 | val input = new BufferedReader(new InputStreamReader(p.getInputStream())) 404 | while (({ 405 | line = input.readLine(); 406 | line 407 | }) != null) { 408 | output += (line + '\n') 409 | } 410 | input.close() 411 | } 412 | catch { 413 | case ex: Exception => ex.printStackTrace() 414 | } 415 | output 416 | } 417 | } 418 | 419 | object GnuplotPlotter { 420 | def pdf(chart: Chart, directory: String, filePrefix: String): Unit = new GnuplotPlotter(chart).pdf(directory, filePrefix) 421 | 422 | def html(chart: Chart, directory: String, filePrefix: String): Unit = new GnuplotPlotter(chart).html(directory, filePrefix) 423 | 424 | def js(chart: Chart, directory: String, filePrefix: String): Unit = new GnuplotPlotter(chart).js(directory, filePrefix) 425 | 426 | def png(chart: Chart, directory: String, filePrefix: String): Unit = new GnuplotPlotter(chart).png(directory, filePrefix) 427 | } 428 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/jfreegraph/JFGraphPlotter.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot.jfreegraph 2 | 3 | import org.sameersingh.scalaplot._ 4 | import javax.swing.JFrame 5 | import org.jfree.chart.{JFreeChart => JChart, ChartPanel, ChartFactory, ChartUtilities} 6 | import org.jfree.data.xy.{XYSeries => XYS} 7 | import org.jfree.data.xy.XYSeriesCollection 8 | import com.itextpdf.text.pdf.DefaultFontMapper 9 | import java.io.{FileOutputStream, BufferedOutputStream} 10 | import com.itextpdf.text.Rectangle 11 | import com.itextpdf.text.Document 12 | import com.itextpdf.text.pdf.PdfWriter 13 | import java.awt.geom.Rectangle2D 14 | import com.itextpdf.text.DocumentException 15 | import org.jfree.chart.plot.PlotOrientation 16 | import org.jfree.chart.title.LegendTitle 17 | import org.jfree.chart.annotations.XYTitleAnnotation 18 | import org.jfree.chart.axis.LogarithmicAxis 19 | import java.awt.Color 20 | import org.jfree.ui.RectangleEdge 21 | 22 | /** 23 | * @author sameer 24 | * @date 11/5/12 25 | */ 26 | class JFGraphPlotter(chart: Chart) extends Plotter(chart) { 27 | 28 | val width = 500 29 | val height = 400 30 | lazy val jchart = jfreeChart(chart) 31 | 32 | def jfreeChart(chart: Chart): JChart = { 33 | val jchart = chart match { 34 | case xyc: XYChart => plotXYChart(xyc) 35 | } 36 | jchart 37 | } 38 | 39 | def plotXYChart(xyc: XYChart): JChart = { 40 | val data = plotXYData(xyc.data) 41 | val jchart = ChartFactory.createXYLineChart(chart.title.getOrElse(""), xyc.x.label, xyc.y.label, data, PlotOrientation.VERTICAL, false, false, false) 42 | val plot = jchart.getXYPlot() 43 | plot.setBackgroundPaint(Color.white) 44 | // add legend 45 | if (xyc.showLegend) { 46 | val legendTitle = new LegendTitle(plot) 47 | val legendPosX = xyc.legendPosX match { 48 | case LegendPosX.Left => 0.1 49 | case LegendPosX.Center => 0.5 50 | case LegendPosX.Right => 0.9 51 | } 52 | val legendPosY = xyc.legendPosY match { 53 | case LegendPosY.Bottom => 0.1 54 | case LegendPosY.Center => 0.5 55 | case LegendPosY.Top => 0.9 56 | } 57 | legendTitle.setPosition(RectangleEdge.RIGHT) 58 | val ta = new XYTitleAnnotation(legendPosX, legendPosY, legendTitle) 59 | ta.setMaxWidth(0.48) 60 | plot.addAnnotation(ta) 61 | } 62 | // log axis 63 | if (xyc.x.isLog) plot.setDomainAxis(new LogarithmicAxis(plot.getDomainAxis.getLabel)) 64 | if (xyc.y.isLog) plot.setRangeAxis(new LogarithmicAxis(plot.getRangeAxis.getLabel)) 65 | // axis ranges 66 | // TODO 67 | jchart 68 | } 69 | 70 | def plotXYData(xydata: XYData): XYSeriesCollection = JFGraphPlotter.xyCollection(xydata) 71 | 72 | override def pdf(directory: String, filenamePrefix: String) { 73 | val mapper = new DefaultFontMapper 74 | val filename = directory + filenamePrefix + ".pdf" 75 | val out = new BufferedOutputStream(new FileOutputStream(filename)) 76 | val pagesize = new Rectangle(width, height) 77 | val document = new Document(pagesize, 50, 50, 50, 50) 78 | try { 79 | val writer = PdfWriter.getInstance(document, out) 80 | document.addAuthor("Sameer Singh") 81 | document.addSubject("Plotting Using JFreeChart and ScalaPlot") 82 | document.open() 83 | val cb = writer.getDirectContent() 84 | val tp = cb.createTemplate(width, height) 85 | val g2 = tp.createGraphics(width, height, mapper) 86 | val r2D = new Rectangle2D.Double(0, 0, width, height) 87 | jchart.draw(g2, r2D, null) 88 | g2.dispose 89 | cb.addTemplate(tp, 0, 0) 90 | } catch { 91 | case de: DocumentException => System.err.println(de.getMessage) 92 | } 93 | document.close 94 | out.close 95 | } 96 | 97 | override def png(directory: String, filenamePrefix: String) { 98 | val filename = directory + filenamePrefix + ".png" 99 | ChartUtilities.saveChartAsPNG(new java.io.File(filename), jchart, 1280, 720) 100 | } 101 | 102 | override def gui() { JFGraphPlotter.gui(jchart) } 103 | } 104 | 105 | object JFGraphPlotter { 106 | 107 | def xySeries(series: XYSeries): XYS = { 108 | val result = new XYS(series.name) 109 | for (p <- series.points) { 110 | result.add(p._1, p._2) 111 | } 112 | result 113 | } 114 | 115 | def xyCollection(data: XYData): XYSeriesCollection = { 116 | val coll = new XYSeriesCollection() 117 | for (series <- data.serieses) { 118 | coll.addSeries(xySeries(series)) 119 | } 120 | coll 121 | } 122 | 123 | def gui(jchart: JChart): Unit = { 124 | val frame = new JFrame(jchart.getTitle.getText) 125 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) 126 | frame.setSize(640, 420) 127 | frame.add(new ChartPanel(jchart)) 128 | frame.pack() 129 | frame.setVisible(true) 130 | println("Done") 131 | } 132 | 133 | def main(args:Array[String]) { 134 | val series = new MemXYSeries((1 until 100).map(_.toDouble), (1 until 100).map(i => (i * i).toDouble), "Series") 135 | val data = new XYData(series) 136 | val chart = new XYChart("Chart", data) 137 | chart.x.log 138 | chart.y.log 139 | val plotter = new JFGraphPlotter(chart) 140 | plotter.gui() 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/metrics/Histogram.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot.metrics 2 | 3 | /** 4 | * @author sameer 5 | */ 6 | class Histogram(val numBins: Int) { 7 | assert(numBins > 0) 8 | 9 | def bin(points: Seq[Double]): Seq[(Double, Int)] = bin(points, points.min, points.max) 10 | 11 | def bin(points: Seq[Double], min: Double, max: Double): Seq[(Double, Int)] = { 12 | val step = (max - min) / numBins 13 | val bins = Array.fill(numBins)(0) 14 | for (p <- points) { 15 | val bin = ((p - min) / step).floor.toInt 16 | if (bin == numBins) { 17 | bins(bin - 1) += 1 18 | } else bins(bin) += 1 19 | } 20 | (0 until numBins).map(i => (min + step * i + step / 2.0, bins(i))) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/metrics/PrecRecallCurve.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot.metrics 2 | 3 | import scala.collection.mutable.ArrayBuffer 4 | import org.sameersingh.scalaplot.{MemXYSeries, XYData, XYChart} 5 | 6 | /** 7 | * Given a squence of predictions and true values (unsorted), return a list of points that 8 | * is used in plotting PR curve, ROC, finding maximum F1, etc. 9 | * 10 | * @author sameer 11 | */ 12 | 13 | case class PRPoint(thresh: Double, tp: Int, tn: Int, fp: Int, fn: Int) { 14 | def precNumerator: Double = tp 15 | 16 | def precDenominator: Double = tp + fp 17 | 18 | def recallNumerator: Double = tp 19 | 20 | def recallDenominator: Double = tp + fn 21 | 22 | def precision: Double = { 23 | if (precDenominator == 0.0) { 24 | 1.0 25 | } else { 26 | precNumerator / precDenominator 27 | } 28 | } 29 | 30 | def recall: Double = { 31 | if (recallDenominator == 0.0) { 32 | 1.0 33 | } else { 34 | recallNumerator / recallDenominator 35 | } 36 | } 37 | 38 | def f1: Double = { 39 | val r: Double = recall 40 | val p: Double = precision 41 | if (p + r == 0.0) 0.0 42 | else (2 * p * r) / (p + r) 43 | } 44 | 45 | def specifity: Double = if(tn + fp == 0) 1.0 else tn.toDouble / (fp + tn) 46 | 47 | def sensitivity = recall 48 | 49 | override def toString: String = { 50 | "%1.4f p:%6.3f r:%6.3f f1:%6.3f sp:%6.3f".format(thresh, precision * 100.0, recall * 100.0, f1 * 100.0, specifity*100.0) 51 | } 52 | } 53 | 54 | class PrecRecallCurve(data: Seq[(Double, Boolean)]) { 55 | 56 | lazy val curve: Seq[PRPoint] = { 57 | val points = new ArrayBuffer[PRPoint] 58 | var tp = 0 59 | var fp = 0 60 | var tn = data.count(!_._2) 61 | var fn = data.count(_._2) 62 | 63 | points += PRPoint(data.maxBy(_._1)._1, tp, tn, fp, fn) 64 | for (d <- data.sortBy(_._1)) { 65 | // i am turning d from negative to positive 66 | if (!d._2) { 67 | // mistake: it was tn, now fp 68 | tn -= 1 69 | fp += 1 70 | } else { 71 | // not a mistake: it was fn, now tp 72 | fn -= 1 73 | tp += 1 74 | } 75 | points += PRPoint(d._1, tp, tn, fp, fn) 76 | } 77 | points 78 | } 79 | 80 | private def createChart(f: PRPoint => (Double,Double), title: String, xname: String = "", yname: String = ""): XYChart = { 81 | val c = new XYChart(title, new XYData(new MemXYSeries(curve.map(d => f(d)), title))) 82 | c.x.label = xname 83 | c.y.label = yname 84 | c 85 | } 86 | 87 | def prThreshChart(title: String) : XYChart = { 88 | val xyData = new XYData() 89 | xyData += new MemXYSeries(curve.map(d => (d.thresh, d.precision)), "Precision") 90 | xyData += new MemXYSeries(curve.map(d => (d.thresh, d.recall)), "Recall") 91 | xyData += new MemXYSeries(curve.map(d => (d.thresh, d.f1)), "F1") 92 | val c = new XYChart(title, xyData) 93 | c.x.label = "Threshold" 94 | c.y.label = "Measure" 95 | c 96 | } 97 | 98 | def prChart(title: String): XYChart = createChart(d => (d.recall, d.precision), title, "Recall", "Precision") 99 | 100 | def rocChart(title: String): XYChart = createChart(d => (d.sensitivity, 1.0 - d.specifity), title, "Sensitivity", "1-Specificity") 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/metrics/Stats.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot.metrics 2 | 3 | /** 4 | * @author sameer 5 | */ 6 | object Stats { 7 | 8 | def mean(points: Seq[Double]): Double = points.sum / points.size 9 | 10 | def meanAndVariance(points: Seq[Double]): (Double, Double) = { 11 | var n = 0.0 12 | var mv = 0.0 13 | var m2 = 0.0 14 | for (x <- points) { 15 | n += 1.0 16 | // delta = x - mean 17 | val delta = x - mv 18 | // mean = mean + delta/n 19 | mv = mv + delta / n 20 | // M2 = M2 + delta*(x - mean) 21 | m2 = m2 + delta * (x - mv) 22 | } 23 | (mv, m2 / (n - 1)) 24 | } 25 | 26 | def variance(points: Seq[Double]): Double = meanAndVariance(points)._2 27 | 28 | def standardDev(points: Seq[Double]): Double = StrictMath.sqrt(variance(points)) 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/org/sameersingh/scalaplot/util/CurveFitting.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot.util 2 | 3 | import org.sameersingh.scalaplot.Chart 4 | 5 | /** 6 | * @author sameer 7 | * @date 10/9/12 8 | */ 9 | object CurveFitting { 10 | def addLinearRegression(chart: Chart): Seq[String] = Seq.empty 11 | } 12 | -------------------------------------------------------------------------------- /src/test/scala/org/sameersingh/scalaplot/BarPlotTest.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import gnuplot.GnuplotPlotter 4 | import org.junit._ 5 | 6 | /** 7 | * @author sameer 8 | * @date 10/6/12 9 | */ 10 | @Test 11 | class BarPlotTest { 12 | 13 | @Test 14 | def testGnuplotOneColOneFile(): Unit = { 15 | // TODO 16 | } 17 | 18 | @Test 19 | def testGnuplotTwoColOneFile(): Unit = { 20 | // TODO 21 | } 22 | 23 | @Test 24 | def testGnuplotOneSmallColMem(): Unit = { 25 | val series = new MemBarSeries((0 until 10).map(i => ((i+1) * (i+1)).toDouble), "Series") 26 | val data = new BarData((x:Int)=> "Label" + x, Seq(series)) 27 | val chart = new BarChart("Chart", data) 28 | val plotter = new GnuplotPlotter(chart) 29 | val tmpFile = java.io.File.createTempFile("bar", "1ser") 30 | println(tmpFile.getCanonicalPath) 31 | plotter.pdf(tmpFile.getParent + "/", tmpFile.getName) 32 | } 33 | 34 | @Test 35 | def testGnuplotTwoLargeColMem(): Unit = { 36 | val rand = new scala.util.Random(0) 37 | val series1 = new MemBarSeries((0 until 10).map(i => (rand.nextDouble())), "Series1") { 38 | override def isLarge = true 39 | } 40 | series1.color = Some(Style.Color.Purple) 41 | series1.fillStyle = Some(Style.FillStyle.Pattern) 42 | series1.pattern = Some(2) 43 | val series2 = new MemBarSeries((0 until 10).map(i => (rand.nextDouble())), "Series2") { 44 | override def isLarge = true 45 | } 46 | series2.fillStyle = Some(Style.FillStyle.Solid) 47 | series2.density = Some(0.2) 48 | val data = new BarData((x:Int)=> "Label" + x, Seq(series1, series2)) 49 | val chart = new BarChart("Chart", data) 50 | val plotter = new GnuplotPlotter(chart) 51 | val tmpFile = java.io.File.createTempFile("bar", "2serLmem") 52 | println(tmpFile.getCanonicalPath) 53 | plotter.pdf(tmpFile.getParent + "/", tmpFile.getName) 54 | } 55 | 56 | @Test 57 | def testGnuplotTwoSmallColMem(): Unit = { 58 | val rand = new scala.util.Random(0) 59 | val series1 = new MemBarSeries((0 until 10).map(i => (rand.nextDouble())), "Series1") { 60 | override def isLarge = false 61 | } 62 | val series2 = new MemBarSeries((0 until 10).map(i => (rand.nextDouble())), "Series2") { 63 | override def isLarge = false 64 | } 65 | val data = new BarData((x:Int)=> "Label" + x, Seq(series1, series2)) 66 | val chart = new BarChart("Chart", data) 67 | val plotter = new GnuplotPlotter(chart) 68 | val tmpFile = java.io.File.createTempFile("bar", "2serSmem") 69 | println(tmpFile.getCanonicalPath) 70 | plotter.pdf(tmpFile.getParent + "/", tmpFile.getName) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/scala/org/sameersingh/scalaplot/ExampleBarTest.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import org.junit.Test 4 | import org.sameersingh.scalaplot.gnuplot.GnuplotPlotter 5 | import org.sameersingh.scalaplot.Style.Color 6 | 7 | /** 8 | * Examples that demonstrate how to use the library. Not really tests. 9 | * @author sameer 10 | * @since 8/4/14. 11 | */ 12 | @Test 13 | class ExampleBarTest { 14 | 15 | @Test 16 | def testExplicit(): Unit = { 17 | // seqs 18 | val x = (0 until 10) 19 | val y = x.map(j => math.pow(j, 1.3)) 20 | 21 | // dataset 22 | val series = new MemBarSeries(y, "p=1.3") 23 | val data = new BarData(x.map("x" + _), Seq(series)) 24 | 25 | // add cube 26 | data += new MemBarSeries(x.map(i => math.pow(i, 1.5)), "p=1.5") 27 | 28 | // chart 29 | val chart = new BarChart("Powers!", data) 30 | chart.showLegend = true 31 | chart.legendPosX = LegendPosX.Left 32 | chart.legendPosY = LegendPosY.Top 33 | 34 | val file = java.io.File.createTempFile("example1", "pdf") 35 | file.delete() 36 | file.mkdir() 37 | println(file.getCanonicalPath) 38 | // new JFGraphPlotter(chart).writeToPdf(file) 39 | val gpl = new GnuplotPlotter(chart) 40 | println(gpl.string(file.getCanonicalPath + "/", "plot_string")) 41 | gpl.js(file.getCanonicalPath + "/", "plot_js") 42 | gpl.svg(file.getCanonicalPath + "/", "plot_svg") 43 | gpl.html(file.getCanonicalPath + "/", "plot_html") 44 | gpl.pdf(file.getCanonicalPath + "/", "plot_pdf") 45 | //gpl.png(file.getCanonicalPath + "/", "plot_png") 46 | } 47 | 48 | @Test 49 | def testDataImplicit(): Unit = { 50 | import org.sameersingh.scalaplot.Implicits._ 51 | // seqs 52 | val x = (1 until 100) 53 | val names = x.map(x => "Lab" + x) 54 | val y1 = x.map(j => math.pow(j, 1)) 55 | val y2 = x.map(j => math.pow(j, 0.75)) 56 | val y3 = x.map(j => math.pow(j, 0.5)) 57 | 58 | // series 59 | val s1: BarSeries = y1 60 | val s2: BarSeries = Bar(y2) 61 | 62 | // data using series 63 | val d1: BarData = s1 64 | val d2: BarData = Seq(s1, s2) 65 | val d2l: BarData = s1 :: s2 :: List() 66 | 67 | // data without series, without names 68 | val d3: BarData = (y1, y2, y3) // easiest, limited to 6 69 | val d4: BarData = Seq(y1, y2, y3) // unlimited 70 | val d5: BarData = (Bar(y1, "Y1"), Bar(y2, color = Color.Blue), Bar(y3, density = 0.5)) // <=6, arbitrary customization 71 | val d6: BarData = Seq(Bar(y1), Bar(y2), Bar(y3)) // unlimited, arbitrary customization 72 | // data without series, with names 73 | val d3n: BarData = names -> (y1, y2, y3) // easiest, limited to 6 74 | val d4n: BarData = names -> Seq(y1, y2, y3) // unlimited 75 | val d5n: BarData = names -> (Bar(y1, "Y1"), Bar(y2, color = Color.Blue), Bar(y3, density = 0.5)) // <=6, arbitrary customization 76 | val d6n: BarData = names -> Seq(Bar(y1), Bar(y2), Bar(y3)) // unlimited, arbitrary customization 77 | } 78 | 79 | @Test 80 | def testChartImplicit(): Unit = { 81 | import org.sameersingh.scalaplot.Implicits._ 82 | // seqs 83 | val x = (1 until 10) 84 | val names = x.map(x => "Lab" + x) 85 | val y1 = x.map(j => math.pow(j, 1)) 86 | val y2 = x.map(j => math.pow(j, 0.75)) 87 | val y3 = x.map(j => math.pow(j, 0.5)) 88 | // series 89 | val s1: BarSeries = y1 90 | val s2: BarSeries = Bar(y2) 91 | // data 92 | val d: BarData = Seq(Bar(y1, "1.0"), Bar(y2, "0.75"), Bar(y3, "0.5")) // unlimited, arbitrary customization 93 | 94 | // chart with data 95 | val c1: BarChart = d 96 | 97 | // chart with series 98 | val c2 = barChart(data = s1) 99 | val c3 = barChart(s1 :: s2 :: List()) 100 | 101 | // chart without series 102 | val c4 = barChart(names ->(y1, y2, y3)) 103 | val c5 = barChart((Bar(y1) -> Bar(y2))) 104 | val c8 = barChart(names ->(y1, y2, y3), xLabel = "X!", y = Axis(label = "Y!")) 105 | val c9 = barChart(Seq(Bar(y1, "1.0"), Bar(y2, "0.75"), Bar(y3, "0.5"))) 106 | val c10 = barChart(names -> (Bar(y1, "1.0"), Bar(y2, "0.75"), Bar(y3, "0.5"))) 107 | } 108 | 109 | @Test 110 | def testOutputImplicit(): Unit = { 111 | import org.sameersingh.scalaplot.Implicits._ 112 | // seqs 113 | val x = (1 until 5) 114 | val y1 = x.map(j => math.pow(j, 1)) 115 | val y2 = x.map(j => math.pow(j, 0.75)) 116 | val y3 = x.map(j => math.pow(j, 0.5)) 117 | // data 118 | val d: BarData = Seq(Bar(y1, "1"), Bar(y2, "2"), Bar(y3, "3")) 119 | // chart with data 120 | val c: BarChart = d 121 | 122 | val file = java.io.File.createTempFile("scalaplot.test", "example") 123 | file.delete() 124 | file.mkdir() 125 | println(file.getCanonicalPath) 126 | val dir = file.getCanonicalPath + "/" 127 | 128 | println(output(ASCII, c)) 129 | // println(output(SVG, c)) 130 | // output(GUI, c) // fails on X11-less nodes 131 | output(PDF(dir, "pdf"), c) 132 | //output(PNG(dir, "png"), c) 133 | } 134 | 135 | @Test 136 | def testExamples(): Unit = { 137 | import org.sameersingh.scalaplot.Implicits._ 138 | val x = 0 until 5 139 | println(output(ASCII, barChart(x.map("x" + _) ->(x.map(i => math.sin(i / 3.0) + 0.5), x.map(i => math.cos(i / 3.0) + 0.5))))) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/test/scala/org/sameersingh/scalaplot/ExampleXYTest.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import gnuplot.GnuplotPlotter 4 | import jfreegraph.JFGraphPlotter 5 | import org.junit._ 6 | import Style._ 7 | 8 | /** 9 | * Examples that demonstrate how to use the library. Not really tests. 10 | * @author sameer 11 | */ 12 | @Test 13 | class ExampleXYTest { 14 | 15 | @Test 16 | def testExplicit(): Unit = { 17 | // seqs 18 | val x = (1 until 100).map(_.toDouble) 19 | val y = (1 until 100).map(j => math.pow(j, 2)) 20 | 21 | // dataset 22 | val series = new MemXYSeries(x, y, "Square") 23 | val data = new XYData(series) 24 | 25 | // add cube 26 | data += new MemXYSeries(x, x.map(i => i * i * i), "Cube") 27 | 28 | // chart 29 | val chart = new XYChart("Powers!", data) 30 | chart.showLegend = true 31 | 32 | val file = java.io.File.createTempFile("example1", "pdf") 33 | file.delete() 34 | file.mkdir() 35 | println(file.getCanonicalPath) 36 | // new JFGraphPlotter(chart).writeToPdf(file) 37 | val gpl = new GnuplotPlotter(chart) 38 | println(gpl.string(file.getCanonicalPath + "/", "plot_string")) 39 | gpl.js(file.getCanonicalPath + "/", "plot_js") 40 | gpl.svg(file.getCanonicalPath + "/", "plot_svg") 41 | gpl.html(file.getCanonicalPath + "/", "plot_html") 42 | gpl.pdf(file.getCanonicalPath + "/", "plot_pdf") 43 | //gpl.png(file.getCanonicalPath + "/", "plot_png") 44 | } 45 | 46 | @Test 47 | def testDataImplicit(): Unit = { 48 | import org.sameersingh.scalaplot.Implicits._ 49 | // seqs 50 | val x = (1 until 100).map(_.toDouble) 51 | val y1 = (1 until 100).map(j => math.pow(j, 1)) 52 | val y2 = (1 until 100).map(j => math.pow(j, 2)) 53 | val y3 = (1 until 100).map(j => math.pow(j, 3)) 54 | val xy1 = x zip y1 55 | val xy2 = x zip y2 56 | 57 | // series 58 | val s1: XYSeries = x -> y1 59 | val s2: XYSeries = x zip y2 60 | val s3: XYSeries = x -> Y(y3) 61 | val s4: XYSeries = XY(xy1) 62 | val f1 = math.sin(_) 63 | val s1f: XYSeries = x -> f1 64 | val s2f: XYSeries = x -> Yf(math.sin) 65 | 66 | // data using series 67 | val d1: XYData = s1 68 | val d2: XYData = Seq(s1, s2) 69 | val d2l: XYData = s1 :: s2 :: List() 70 | 71 | // data without series 72 | val d3: XYData = x ->(y1, y2, y3) // easiest, limited to 6 73 | val d4: XYData = x -> Ys(y1, y2, y3) // unlimited 74 | val d5: XYData = x ->(Y(y1, "Y1"), Y(y2, color = Color.Blue), Y(y3, lw = 3.0)) // <=6, arbitrary customization 75 | val d6: XYData = x -> Seq(Y(y1), Y(y2), Y(y3)) // unlimited, arbitrary customization 76 | 77 | // same as above, but with functions instead 78 | val d3f: XYData = x ->(math.sin(_), math.cos(_)) 79 | val d4f: XYData = x -> Yfs(math.sin, math.cos) 80 | val d5f: XYData = x -> Seq(Yf(math.sin, "sin"), Yf(math.cos, color = Color.Blue), Yf(math.tan, lw = 3.0)) 81 | val d6f: XYData = x ->(Yf(math.sin), Yf(math.cos), Yf(math.tan)) 82 | } 83 | 84 | @Test 85 | def testChartImplicit(): Unit = { 86 | import org.sameersingh.scalaplot.Implicits._ 87 | // seqs 88 | val x = (1 until 100).map(_.toDouble) 89 | val y1 = (1 until 100).map(j => math.pow(j, 1)) 90 | val y2 = (1 until 100).map(j => math.pow(j, 2)) 91 | val y3 = (1 until 100).map(j => math.pow(j, 3)) 92 | val xy1 = x zip y1 93 | val xy2 = x zip y2 94 | // series 95 | val s1: XYSeries = x -> y1 96 | val s2: XYSeries = x zip y2 97 | // data 98 | val d: XYData = x -> Seq(Y(y1, "1"), Y(y2, "2"), Y(y3, "3")) 99 | 100 | // chart with data 101 | val c1: XYChart = d 102 | 103 | // chart with series 104 | val c2 = xyChart(data = s1) 105 | val c3 = xyChart(s1 :: s2 :: List()) 106 | 107 | // chart without series 108 | val c4 = xyChart(x ->(y1, y2, y3)) 109 | val c5 = xyChart(x -> Y(y1) :: x -> Y(y2) :: List()) 110 | val c6 = xyChart(XY(xy1) :: XY(xy2) :: List()) 111 | val c7 = xyChart(x ->(math.sin(_), math.cos(_))) 112 | val c8 = xyChart(x ->(y1, y2, y3), x = Axis(label = "X!", log = true), y = Axis(label = "Y!")) 113 | val c9 = xyChart(x -> Seq(Y(y1, "1"), Y(y2, "2"), Y(y3, "3"))) 114 | } 115 | 116 | @Test 117 | def testOutputImplicit(): Unit = { 118 | import org.sameersingh.scalaplot.Implicits._ 119 | // seqs 120 | val x = (1 until 5).map(_.toDouble) 121 | val y1 = (1 until 5).map(j => math.pow(j, 1)) 122 | val y2 = (1 until 5).map(j => math.pow(j, 2)) 123 | val y3 = (1 until 5).map(j => math.pow(j, 3)) 124 | // data 125 | val d: XYData = x -> Seq(Y(y1, "1"), Y(y2, "2"), Y(y3, "3")) 126 | // chart with data 127 | val c: XYChart = d 128 | 129 | val file = java.io.File.createTempFile("scalaplot.test", "example") 130 | file.delete() 131 | file.mkdir() 132 | println(file.getCanonicalPath) 133 | val dir = file.getCanonicalPath + "/" 134 | 135 | println(output(ASCII, c)) 136 | // println(output(SVG, c)) 137 | // output(GUI, c) // fails on X11-less nodes 138 | output(PDF(dir, "pdf"), c) 139 | //output(PNG(dir, "png"), c) 140 | } 141 | 142 | @Test 143 | def testLineExample(): Unit = { 144 | import org.sameersingh.scalaplot.Implicits._ 145 | val x = 0.0 until 2.0 * math.Pi by 0.1 146 | println(output(ASCII, xyChart(x ->(math.sin(_), math.cos(_))))) 147 | } 148 | 149 | @Test 150 | def testScatterExample(): Unit = { 151 | import org.sameersingh.scalaplot.Implicits._ 152 | val x = 0.0 until 10.0 by 0.01 153 | val rnd = new scala.util.Random(0) 154 | println(output(PNG("docs/img/", "scatter"), xyChart(x -> Seq(Y(x, style = XYPlotStyle.Lines), Y(x.map(_ + rnd.nextDouble - 0.5), style = XYPlotStyle.Dots))))) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/test/scala/org/sameersingh/scalaplot/MetricsTest.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import org.junit._ 4 | import Assert._ 5 | import scala.util.Random 6 | import org.sameersingh.scalaplot.metrics.{PrecRecallCurve, Stats, Histogram} 7 | 8 | /** 9 | * @author sameer 10 | */ 11 | class MetricsTest { 12 | 13 | val random = new Random(0) 14 | 15 | @Test 16 | def testSingleBin() { 17 | val points = (0 until 100).map(i => random.nextDouble()) 18 | val hist = new Histogram(1) 19 | val bins = hist.bin(points, 0.0, 1.0) 20 | assertEquals(1, bins.size) 21 | assertEquals(0.5, bins.head._1, 1e-8) 22 | assertEquals(100, bins.head._2) 23 | //println(bins) 24 | } 25 | 26 | @Test 27 | def testUniformBin() { 28 | val numPoints = 10000 29 | val numBins = 10 30 | val points = (0 until numPoints).map(i => random.nextDouble()) 31 | val hist = new Histogram(numBins) 32 | val bins = hist.bin(points, 0.0, 1.0) 33 | //println(bins) 34 | assertEquals(numBins, bins.size) 35 | assertEquals(numPoints, bins.map(_._2).sum) 36 | val stdDev = Stats.standardDev(bins.map(_._2.toDouble / (numPoints / numBins))) 37 | val mean = Stats.mean(bins.map(_._2.toDouble / (numPoints / numBins))) 38 | println(mean) 39 | assertEquals(1.0, mean, 1e-4) 40 | assertTrue(stdDev < 0.05) 41 | } 42 | 43 | @Test 44 | def testGaussianBin() { 45 | val numPoints = 10000 46 | val numBins = 10 47 | val points = (0 until numPoints).map(i => random.nextGaussian()) 48 | val hist = new Histogram(numBins) 49 | val bins = hist.bin(points) 50 | println(bins) 51 | assertEquals(numBins, bins.size) 52 | assertEquals(numPoints, bins.map(_._2).sum) 53 | val stdDev = Stats.standardDev(bins.map(_._2.toDouble / (numPoints / numBins))) 54 | println(stdDev) 55 | assertEquals(1.0, stdDev, 0.25) 56 | } 57 | 58 | @Test 59 | def testPRCurve() { 60 | val numPoints = 250 61 | val idealThresh = 0.5 62 | val probError = 1.0 63 | val data = for (i <- 0 until numPoints) yield { 64 | val pred = random.nextDouble() 65 | val effProbError = (0.5 - math.abs(pred - idealThresh)) / 0.5 * probError 66 | val truth = 67 | if (random.nextDouble() < effProbError) pred < idealThresh else pred > idealThresh 68 | (pred, truth) 69 | } 70 | //println(data.sortBy(_._1).mkString("\n")) 71 | val curve = new PrecRecallCurve(data).curve 72 | println(curve.mkString("\n")) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/scala/org/sameersingh/scalaplot/XYPlotTest.scala: -------------------------------------------------------------------------------- 1 | package org.sameersingh.scalaplot 2 | 3 | import gnuplot.GnuplotPlotter 4 | import org.junit._ 5 | import java.io.PrintWriter 6 | 7 | /** 8 | * @author sameer 9 | * @date 10/6/12 10 | */ 11 | @Test 12 | class XYPlotTest { 13 | 14 | @Test 15 | def testGnuplotOneColOneFile(): Unit = { 16 | // write data file 17 | val dataFile = java.io.File.createTempFile("test", "dat") 18 | println(dataFile.getCanonicalPath) 19 | val writer = new PrintWriter(dataFile) 20 | for (i <- 0 until 100) { 21 | writer.println(i + "\t" + (i * i)) 22 | } 23 | writer.flush() 24 | writer.close() 25 | 26 | val series = new FileXYSeries(1, 2, "Series", dataFile.getCanonicalPath) 27 | val data = new XYData(series) 28 | val chart = new XYChart("Chart", data) 29 | 30 | val plotter = new GnuplotPlotter(chart) 31 | plotter.pdf(dataFile.getParent + "/", dataFile.getName + "-onecol") 32 | } 33 | 34 | @Test 35 | def testGnuplotTwoColOneFile(): Unit = { 36 | // write data file 37 | val dataFile = java.io.File.createTempFile("test", "dat") 38 | println(dataFile.getCanonicalPath) 39 | val writer = new PrintWriter(dataFile) 40 | for (i <- 0 until 100) { 41 | writer.println(i + "\t" + (i * i) + "\t" + (50 * i)) 42 | } 43 | writer.flush() 44 | writer.close() 45 | 46 | val series1 = new FileXYSeries(1, 2, "Series1", dataFile.getCanonicalPath) 47 | val series2 = new FileXYSeries(1, 3, "Series2", dataFile.getCanonicalPath) 48 | val data = new XYData(series1, series2) 49 | val chart = new XYChart("Chart", data) 50 | 51 | val plotter = new GnuplotPlotter(chart) 52 | plotter.pdf(dataFile.getParent + "/", dataFile.getName + "-twocol") 53 | } 54 | 55 | @Test 56 | def testGnuplotOneSmallColMem(): Unit = { 57 | val series = new MemXYSeries((0 until 100).map(_.toDouble), (0 until 100).map(i => (i * i).toDouble), "Series") 58 | val data = new XYData(series) 59 | val chart = new XYChart("Chart", data) 60 | val plotter = new GnuplotPlotter(chart) 61 | val tmpFile = java.io.File.createTempFile("test", "dat") 62 | println(tmpFile.getCanonicalPath) 63 | //plotter.pdf(tmpFile.getCanonicalPath) 64 | } 65 | 66 | @Test 67 | def testGnuplotTwoLargeColMem(): Unit = { 68 | val series1 = new MemXYSeries((0 until 100).map(_.toDouble), (0 until 100).map(i => (i * i).toDouble), "Series1") { 69 | override def isLarge = true 70 | } 71 | val series2 = new MemXYSeries((0 until 100).map(_.toDouble), (0 until 100).map(i => (50 * i).toDouble), "Series2") { 72 | override def isLarge = true 73 | } 74 | val data = new XYData(series1, series2) 75 | val chart = new XYChart("Chart", data) 76 | val plotter = new GnuplotPlotter(chart) 77 | val tmpFile = java.io.File.createTempFile("test", "dat") 78 | println(tmpFile.getCanonicalPath) 79 | plotter.pdf(tmpFile.getParent + "/", tmpFile.getName) 80 | } 81 | 82 | } 83 | --------------------------------------------------------------------------------