├── .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 | [](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 | 
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 | 
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 | 
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 | 
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 | |
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 |
--------------------------------------------------------------------------------