├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.gradle
├── docs
├── images
│ ├── fig1.png
│ ├── fig2.png
│ ├── fig3.png
│ ├── fig4.png
│ └── fig5.png
└── tutorial.md
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
├── java
│ └── com
│ │ └── github
│ │ └── sh0nk
│ │ └── matplotlib4j
│ │ ├── NumpyUtils.java
│ │ ├── Plot.java
│ │ ├── PlotImpl.java
│ │ ├── PyCommand.java
│ │ ├── PythonConfig.java
│ │ ├── PythonExecutionException.java
│ │ ├── TypeConversion.java
│ │ ├── builder
│ │ ├── ArgsBuilderImpl.java
│ │ ├── Builder.java
│ │ ├── CLabelBuilder.java
│ │ ├── CLabelBuilderImpl.java
│ │ ├── CompositeBuilder.java
│ │ ├── ContourBuilder.java
│ │ ├── ContourBuilderImpl.java
│ │ ├── HistBuilder.java
│ │ ├── HistBuilderImpl.java
│ │ ├── LabelBuilder.java
│ │ ├── LabelBuilderImpl.java
│ │ ├── LegendBuilder.java
│ │ ├── LegendBuilderImpl.java
│ │ ├── PColorBuilder.java
│ │ ├── PColorBuilderImpl.java
│ │ ├── PlotBuilder.java
│ │ ├── PlotBuilderImpl.java
│ │ ├── SaveFigBuilder.java
│ │ ├── SaveFigBuilderImpl.java
│ │ ├── ScaleBuilder.java
│ │ ├── ScaleBuilderImpl.java
│ │ ├── SubplotBuilder.java
│ │ ├── SubplotBuilderImpl.java
│ │ ├── TextBuilder.java
│ │ ├── TextBuilderImpl.java
│ │ ├── TicksBuilder.java
│ │ └── TicksBuilderImpl.java
│ │ └── kwargs
│ │ ├── KwargsBuilder.java
│ │ ├── Line2DBuilder.java
│ │ ├── Line2DBuilderImpl.java
│ │ ├── PatchBuilder.java
│ │ ├── PatchBuilderImpl.java
│ │ ├── TextArgsBuilder.java
│ │ └── TextArgsBuilderImpl.java
└── resources
│ └── log4j.properties
└── test
└── java
└── com
└── github
└── sh0nk
└── matplotlib4j
├── MainTest.java
├── NumpyUtilsTest.java
├── PythonConfigCommandTest.java
└── SaveFigTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.ear
17 | *.zip
18 | *.tar.gz
19 | *.rar
20 |
21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
22 | hs_err_pid*
23 |
24 | # gradle wrapper
25 | !gradle/wrapper/*
26 |
27 | # idea
28 | .idea
29 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - 2.7
4 | - 3.4
5 | jdk:
6 | - oraclejdk8
7 | sudo: required
8 | dist: trusty
9 |
10 | before_install:
11 | - jdk_switcher use oraclejdk8
12 |
13 | install:
14 | - pip install numpy matplotlib
15 |
16 | script: ./gradlew test --info
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Sho Nakamura
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # matplotlib4j
2 |
3 | [](https://maven-badges.herokuapp.com/maven-central/com.github.sh0nk/matplotlib4j)
4 | [](https://travis-ci.org/sh0nk/matplotlib4j)
5 | [](https://opensource.org/licenses/MIT)
6 |
7 |
8 | A simplest interface library to enable your java project to use matplotlib.
9 |
10 | Of course it is able to be imported to scala project as below. The API is designed as similar to the original matplotlib's.
11 |
12 |
13 |
14 | ## [Tutorial](/docs/tutorial.md)
15 |
16 | Now [tutorial](/docs/tutorial.md) is under preparation to walkthrough the features.
17 | If you want to skim only the idea of Matplotlib4j, skip that and go to the next section: *[How to use](#how-to-use)*
18 |
19 | ## How to use
20 |
21 | Here is an example. Find more examples on `MainTest.java`
22 |
23 | ```java
24 | Plot plt = Plot.create();
25 | plt.plot()
26 | .add(Arrays.asList(1.3, 2))
27 | .label("label")
28 | .linestyle("--");
29 | plt.xlabel("xlabel");
30 | plt.ylabel("ylabel");
31 | plt.text(0.5, 0.2, "text");
32 | plt.title("Title!");
33 | plt.legend();
34 | plt.show();
35 | ```
36 |
37 | Another example to draw **Contour**.
38 |
39 |
40 |
41 | ```java
42 | // Data generation
43 | List x = NumpyUtils.linspace(-1, 1, 100);
44 | List y = NumpyUtils.linspace(-1, 1, 100);
45 | NumpyUtils.Grid grid = NumpyUtils.meshgrid(x, y);
46 |
47 | List> zCalced = grid.calcZ((xi, yj) -> Math.sqrt(xi * xi + yj * yj));
48 |
49 | // Plotting
50 | Plot plt = Plot.create();
51 | ContourBuilder contour = plt.contour().add(x, y, zCalced);
52 | plt.clabel(contour)
53 | .inline(true)
54 | .fontsize(10);
55 | plt.title("contour");
56 | plt.legend().loc("upper right");
57 | plt.show();
58 | ```
59 |
60 | In addition to the interactive window opened by `.show()`, **`.savefig()`** is also supported.
61 | Only one thing to note is that `plt.executeSilently()` triggers to output figure files after calling `.savefig()`.
62 | This is by design as method chain coding style.
63 |
64 | ```java
65 | Random rand = new Random();
66 | List x = IntStream.range(0, 1000).mapToObj(i -> rand.nextGaussian())
67 | .collect(Collectors.toList());
68 |
69 | Plot plt = Plot.create();
70 | plt.hist().add(x).orientation(HistBuilder.Orientation.horizontal);
71 | plt.ylim(-5, 5);
72 | plt.title("histogram");
73 | plt.savefig("/tmp/histogram.png").dpi(200);
74 |
75 | // Don't miss this line to output the file!
76 | plt.executeSilently();
77 | ```
78 |
79 | This code generates the following picture at `/tmp/histogram.png`.
80 |
81 |
82 |
83 | ### Major supported functions
84 |
85 | - plot()
86 | - pcolor()
87 | - contour()
88 | - hist()
89 | - savefig()
90 | - subplot()
91 | - xlim(), ylim(), xscale(), yscale(), xlabel(), ylabel()
92 |
93 | ### Pyenv support
94 |
95 | It is possible to choose a python environment to run matplotlib with `pyenv` and `pyenv-virtualenv` support. Create `Plot` object by specifying existing names as follows.
96 |
97 | ```java
98 | // with pyenv name
99 | Plot plt = Plot.create(PythonConfig.pyenvConfig("anaconda3-4.4.0"));
100 | // with pyenv and virtualenv name
101 | Plot plt = Plot.create(PythonConfig.pyenvVirtualenvConfig("anaconda3-4.4.0", "env_plot"));
102 | ```
103 |
104 | ### Other way to specify your "python" environment
105 |
106 | Also direct path to python binary is also supported. For example this way can be used if your python runtime is installed
107 | by `poetry` and `venv`.
108 |
109 | ```java
110 | Plot plt = Plot.create(PythonConfig.pythonBinPathConfig("/Users/sh0nk/my_repos/.venv/bin/python"));
111 | ```
112 |
113 | ## Dependency
114 |
115 | * Java 8 or later
116 | * Python with Matplotlib installed
117 |
118 | It may work with almost all not too old `Python` and `Matplotlib` versions, but no guarantee. It has been tested
119 | on MacOS with
120 |
121 | * Python 2.7.10, 3.6.1
122 | * Matplotlib 1.3.1, 2.0.2
123 |
124 | If it does not work on your environment, please report that through [github issue](https://github.com/sh0nk/matplotlib4j/issues/new)
125 | with the error message and your environment (OS, python and matplotlib versions).
126 |
127 | ## Configure on your project
128 |
129 | This library is now found on [maven central repository](http://search.maven.org/#artifactdetails%7Ccom.github.sh0nk%7Cmatplotlib4j%7C0.5.0%7Cjar).
130 |
131 | Import to your projects as follows.
132 |
133 | **Maven**
134 |
135 | ```xml
136 |
137 | com.github.sh0nk
138 | matplotlib4j
139 | 0.5.0
140 |
141 | ```
142 |
143 | **Gradle**
144 |
145 | ```groovy
146 | compile 'com.github.sh0nk:matplotlib4j:0.5.0'
147 | ```
148 |
149 | # License
150 |
151 | MIT
152 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: "maven-publish"
3 | apply plugin: "maven"
4 | apply plugin: "signing"
5 |
6 | group 'com.github.sh0nk'
7 | version '0.5.1-SNAPSHOT'
8 | archivesBaseName = "matplotlib4j"
9 | description = "Matplotlib for java: A simple graph plot library for java with powerful python matplotlib"
10 |
11 | sourceCompatibility = 1.8
12 |
13 | task sourceJar(type: Jar) {
14 | from sourceSets.main.allJava
15 | }
16 |
17 | publishing {
18 | publications {
19 | mavenJava(MavenPublication) {
20 | from components.java
21 | artifact sourceJar {
22 | classifier "sources"
23 | }
24 | }
25 | }
26 | }
27 |
28 | repositories {
29 | mavenCentral()
30 | }
31 |
32 | dependencies {
33 | compile group: 'com.google.guava', name: 'guava', version: '15.0'
34 | compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.7'
35 | compile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.7'
36 | compile group: 'log4j', name: 'log4j', version: '1.2.17'
37 |
38 | testCompile group: 'junit', name: 'junit', version: '4.12'
39 | }
40 |
--------------------------------------------------------------------------------
/docs/images/fig1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sh0nk/matplotlib4j/51f07dbafb55b78e95789a7cfb522a11b587fcdf/docs/images/fig1.png
--------------------------------------------------------------------------------
/docs/images/fig2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sh0nk/matplotlib4j/51f07dbafb55b78e95789a7cfb522a11b587fcdf/docs/images/fig2.png
--------------------------------------------------------------------------------
/docs/images/fig3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sh0nk/matplotlib4j/51f07dbafb55b78e95789a7cfb522a11b587fcdf/docs/images/fig3.png
--------------------------------------------------------------------------------
/docs/images/fig4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sh0nk/matplotlib4j/51f07dbafb55b78e95789a7cfb522a11b587fcdf/docs/images/fig4.png
--------------------------------------------------------------------------------
/docs/images/fig5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sh0nk/matplotlib4j/51f07dbafb55b78e95789a7cfb522a11b587fcdf/docs/images/fig5.png
--------------------------------------------------------------------------------
/docs/tutorial.md:
--------------------------------------------------------------------------------
1 | # Tutorial
2 |
3 | This page is based on [Matplotlib: plotting](http://www.turbare.net/transl/scipy-lecture-notes/intro/matplotlib/matplotlib.html)
4 | page in [scipy lecture notes](http://www.turbare.net/transl/scipy-lecture-notes/)
5 |
6 | Matplotlib page on scipy lecture notes shows step by step codes by python Matplotlib.
7 | This page cites them, and add how to write in Matplotlib4j in **Java, Scala and Kotlin** for your understanding of Matplotlib4j.
8 |
9 | So, let's start!
10 |
11 | # Simple Plot
12 |
13 | *[Original doc](http://www.turbare.net/transl/scipy-lecture-notes/intro/matplotlib/matplotlib.html#simple-plot)*
14 |
15 | > In this section, we want to draw the cosine and sine functions on the same plot.
16 | > Starting from the default settings, we'll enrich the figure step by step to make it nicer.
17 | >
18 | > First step is to get the data for the sine and cosine functions:
19 |
20 | *Original in python:*
21 | ```python
22 | import numpy as np
23 |
24 | X = np.linspace(-np.pi, np.pi, 256)
25 | C, S = np.cos(X), np.sin(X)
26 | ```
27 |
28 | *Java:*
29 | ```java
30 | List x = NumpyUtils.linspace(-Math.PI, Math.PI, 256);
31 | List C = x.stream().map(xi -> Math.cos(xi)).collect(Collectors.toList());
32 | List S = x.stream().map(xi -> Math.sin(xi)).collect(Collectors.toList());
33 | ```
34 |
35 | *Scala:*
36 | ```scala
37 | import scala.jdk.CollectionConverters._
38 |
39 | val x = NumpyUtils.linspace(-Math.PI, Math.PI, 256)
40 | val C = x.asScala.map(xi => Math.cos(xi)).map(Double.box).asJava
41 | val S = x.asScala.map(xi => Math.sin(xi)).map(Double.box).asJava
42 | ```
43 |
44 | Note that in scala, converting java List to scala List is needed.
45 | In addition to that, boxing the elements are also required.
46 |
47 | *Kotlin:*
48 | ```kotlin
49 | val x = NumpyUtils.linspace(-Math.PI, Math.PI, 256)
50 | val C = x.map { xi -> Math.cos(xi!!) }.toList()
51 | val S = x.map { xi -> Math.sin(xi!!) }.toList()
52 | ```
53 |
54 | > `X` is now a numpy array with 256 values ranging from -π to +π (included).
55 | > `C` is the cosine (256 values) and S is the sine (256 values).
56 |
57 |
58 | ## Plotting with default settings
59 |
60 |
61 |
62 | > Matplotlib comes with a set of default settings that allow customizing all kinds of properties.
63 | > You can control the defaults of almost every property in matplotlib: figure size and dpi, line width,
64 | > color and style, axes, axis and grid properties, text and font properties and so on.
65 |
66 | You must see that all the languages here have almost the same form of writing to handle `plt` object as below.
67 |
68 | *Original in python:*
69 | ```python
70 | import numpy as np
71 | import matplotlib.pyplot as plt
72 |
73 | X = np.linspace(-np.pi, np.pi, 256, endpoint=True)
74 | C, S = np.cos(X), np.sin(X)
75 |
76 | plt.plot(X, C)
77 | plt.plot(X, S)
78 |
79 | plt.show()
80 | ```
81 |
82 | *Java:*
83 | ```java
84 | List x = NumpyUtils.linspace(-Math.PI, Math.PI, 256);
85 | List C = x.stream().map(xi -> Math.cos(xi)).collect(Collectors.toList());
86 | List S = x.stream().map(xi -> Math.sin(xi)).collect(Collectors.toList());
87 |
88 | Plot plt = Plot.create();
89 | plt.plot().add(x, C);
90 | plt.plot().add(x, S);
91 | plt.show();
92 | ```
93 |
94 | *Scala:*
95 | ```scala
96 | import scala.jdk.CollectionConverters._
97 |
98 | val x = NumpyUtils.linspace(-Math.PI, Math.PI, 256)
99 | val C = x.asScala.map(xi => Math.cos(xi)).map(Double.box).asJava
100 | val S = x.asScala.map(xi => Math.sin(xi)).map(Double.box).asJava
101 |
102 | val plt = Plot.create()
103 | plt.plot.add(x, C)
104 | plt.plot.add(x, S)
105 | plt.show()
106 | ```
107 |
108 | *Kotlin:*
109 | ```kotlin
110 | val x = NumpyUtils.linspace(-Math.PI, Math.PI, 256)
111 | val C = x.map { xi -> Math.cos(xi!!) }.toList()
112 | val S = x.map { xi -> Math.sin(xi!!) }.toList()
113 |
114 | val plt = Plot.create()
115 | plt.plot().add(x, C)
116 | plt.plot().add(x, S)
117 | plt.show()
118 | ```
119 |
120 |
121 | ## Changing colors and line widths
122 |
123 |
124 |
125 | > First step, we want to have the cosine in blue and the sine in red and a slighty thicker line
126 | > for both of them. We'll also slightly alter the figure size to make it more horizontal.
127 |
128 | *Original in python:*
129 | ```python
130 | ...
131 | plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-")
132 | plt.plot(X, S, color="red", linewidth=2.5, linestyle="-")
133 | ...
134 | ```
135 |
136 | *Java:*
137 | ```java
138 | ...
139 | plt.plot().add(x, C).color("blue").linewidth(2.5).linestyle("-");
140 | plt.plot().add(x, S).color("red").linewidth(2.5).linestyle("-");
141 | ...
142 | ```
143 |
144 | *Scala:*
145 | ```scala
146 | ...
147 | plt.plot.add(x, C).color("blue").linewidth(2.5).linestyle("-")
148 | plt.plot.add(x, S).color("red").linewidth(2.5).linestyle("-")
149 | ...
150 | ```
151 |
152 | *Kotlin:*
153 | ```kotlin
154 | ...
155 | plt.plot().add(x, C).color("blue").linewidth(2.5).linestyle("-")
156 | plt.plot().add(x, S).color("red").linewidth(2.5).linestyle("-")
157 | ...
158 | ```
159 |
160 |
161 | ## Setting limits
162 |
163 |
164 |
165 | > Current limits of the figure are a bit too tight and we want to make some space in order to clearly see all data points.
166 |
167 | *Original in python:*
168 | ```python
169 | ...
170 | plt.xlim(X.min() * 1.1, X.max() * 1.1)
171 | plt.ylim(C.min() * 1.1, C.max() * 1.1)
172 | ...
173 | ```
174 |
175 | *Java:*
176 | ```java
177 | ...
178 | plt.xlim(Collections.min(x) * 1.1, Collections.max(x) * 1.1);
179 | plt.ylim(Collections.min(C) * 1.1, Collections.max(C) * 1.1);
180 | ...
181 | ```
182 |
183 | *Scala:*
184 | ```scala
185 | ...
186 | plt.xlim(x.asScala.min * 1.1, x.asScala.max * 1.1)
187 | plt.ylim(C.asScala.min * 1.1, C.asScala.max * 1.1)
188 | ...
189 | ```
190 |
191 | *Kotlin:*
192 | ```kotlin
193 | ...
194 | plt.xlim(x.min()!! * 1.1, x.max()!! * 1.1)
195 | plt.ylim(C.min()!! * 1.1, C.max()!! * 1.1)
196 | ...
197 | ```
198 |
199 |
200 | ## Setting ticks
201 |
202 |
203 |
204 | > Current ticks are not ideal because they do not show the interesting values (+/-π,+/-π/2) for sine and cosine. We'll change them such that they show only these values.
205 |
206 | > :warning: `.xticks()` and `.yticks()` are supported in `>=0.6.0`
207 |
208 | *Original in python:*
209 | ```python
210 | ...
211 | plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi])
212 | plt.yticks([-1, 0, +1])
213 | ...
214 | ```
215 |
216 | *Java:*
217 | ```java
218 | ...
219 | plt.xticks(Arrays.asList(-Math.PI, -Math.PI / 2, 0, Math.PI / 2, Math.PI));
220 | plt.yticks(Arrays.asList(-1, 0, 1));
221 | ...
222 | ```
223 |
224 | *Scala:*
225 | ```scala
226 | ...
227 | plt.xticks(Seq(-Math.PI, -Math.PI / 2, 0.0, Math.PI / 2, Math.PI).map(Double.box).asJava)
228 | plt.yticks(Seq(-1.0, 0.0, 1.1).map(Double.box).asJava)
229 | ...
230 | ```
231 |
232 | *Kotlin:*
233 | ```kotlin
234 | ...
235 | plt.xticks(listOf(-Math.PI, -Math.PI / 2, 0, Math.PI / 2, Math.PI))
236 | plt.yticks(listOf(-1, 0, 1))
237 | ...
238 | ```
239 |
240 |
241 | ## Setting tick labels
242 |
243 |
244 |
245 | > Ticks are now properly placed but their label is not very explicit. We could guess that 3.142 is π but it would be better to make it explicit. When we set tick values, we can also provide a corresponding label in the second argument list. Note that we'll use latex to allow for nice rendering of the label.
246 |
247 | > :warning: `.xticks()` and `.yticks()` are supported in `>=0.6.0`
248 |
249 | *Original in python:*
250 | ```python
251 | ...
252 | plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
253 | [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
254 |
255 | plt.yticks([-1, 0, +1],
256 | [r'$-1$', r'$0$', r'$+1$'])
257 | ...
258 | ```
259 |
260 | *Java:*
261 | ```java
262 | ...
263 | plt.xticks(Arrays.asList(-Math.PI, -Math.PI / 2, 0, Math.PI / 2, Math.PI))
264 | .labels(Arrays.asList("$-\\pi$", "$-\\pi/2$", "$0$", "$+\\pi/2$", "$+\\pi$"));
265 | plt.yticks(Arrays.asList(-1, 0, 1))
266 | .labels(Arrays.asList("$-1$", "$0$", "$1$"));
267 | ...
268 | ```
269 |
270 | *Scala:*
271 | ```scala
272 | ...
273 | plt.xticks(Seq(-Math.PI, -Math.PI / 2, 0.0, Math.PI / 2, Math.PI).map(Double.box).asJava)
274 | .labels(Seq("$-\\pi$", "$-\\pi/2$", "$0$", "$+\\pi/2$", "$+\\pi$").asJava)
275 | plt.yticks(Seq(-1.0, 0.0, 1.1).map(Double.box).asJava)
276 | .labels(Seq("$-1$", "$0$", "$1$").asJava)
277 | ...
278 | ```
279 |
280 | *Kotlin:*
281 | ```kotlin
282 | ...
283 | plt.xticks(listOf(-Math.PI, -Math.PI / 2, 0, Math.PI / 2, Math.PI))
284 | .labels(listOf("$-\\pi$", "$-\\pi/2$", "$0$", "$+\\pi/2$", "$+\\pi$"))
285 | plt.yticks(listOf(-1, 0, 1))
286 | .labels(listOf("$-1$", "$0$", "$1$"))
287 | ...
288 | ```
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sh0nk/matplotlib4j/51f07dbafb55b78e95789a7cfb522a11b587fcdf/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Oct 23 10:40:47 UTC 2019
2 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-all.zip
3 | distributionBase=GRADLE_USER_HOME
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'matplotlib4j'
2 |
--------------------------------------------------------------------------------
/src/main/java/com/github/sh0nk/matplotlib4j/NumpyUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.sh0nk.matplotlib4j;
2 |
3 | import com.google.common.base.Preconditions;
4 | import com.google.common.collect.ContiguousSet;
5 | import com.google.common.collect.DiscreteDomain;
6 | import com.google.common.collect.Range;
7 |
8 | import java.util.List;
9 | import java.util.function.BiFunction;
10 | import java.util.stream.Collectors;
11 | import java.util.stream.IntStream;
12 |
13 | public class NumpyUtils {
14 |
15 | // TODO: more options from numpy
16 | public static List linspace(double start, double end, int num) {
17 | Preconditions.checkArgument(num >= 0);
18 | return ContiguousSet.create(Range.closedOpen(0, num), DiscreteDomain.integers())
19 | .stream().map(x -> (x * (end - start)) / (num - 1) + start).collect(Collectors.toList());
20 | }
21 |
22 | public static List arange(double start, double end, double step) {
23 | double scaledStart = start / step;
24 | double scaledEnd = end / step;
25 | double floorGap = scaledStart - (int) scaledStart;
26 | return ContiguousSet.create(Range.closed((int) scaledStart, (int) scaledEnd), DiscreteDomain.integers())
27 | .stream().map(x -> (x + floorGap) * step).collect(Collectors.toList());
28 | }
29 |
30 | public static Grid meshgrid(List x, List y) {
31 | Grid grid = new Grid<>();
32 | grid.x = IntStream.range(0, y.size()).mapToObj(i -> x).collect(Collectors.toList());
33 | grid.y = y.stream().map(t -> (IntStream.range(0, x.size()).mapToObj(i -> t).collect(Collectors.toList()))).collect(Collectors.toList());
34 | return grid;
35 | }
36 |
37 | public static class Grid {
38 | public List> x;
39 | public List> y;
40 |
41 | public List> calcZ(BiFunction biFunction) {
42 | return IntStream.range(0, x.size()).mapToObj(i ->
43 | IntStream.range(0, x.get(i).size()).mapToObj(j ->
44 | biFunction.apply(x.get(i).get(j), y.get(i).get(j))
45 | ).collect(Collectors.toList())
46 | ).collect(Collectors.toList());
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/github/sh0nk/matplotlib4j/Plot.java:
--------------------------------------------------------------------------------
1 | package com.github.sh0nk.matplotlib4j;
2 |
3 | import com.github.sh0nk.matplotlib4j.builder.*;
4 |
5 | import java.io.IOException;
6 | import java.util.List;
7 |
8 | public interface Plot {
9 |
10 | static Plot create() {
11 | return new PlotImpl(PythonConfig.systemDefaultPythonConfig(), false);
12 | }
13 |
14 | static Plot create(PythonConfig pythonConfig) {
15 | return new PlotImpl(pythonConfig, false);
16 | }
17 |
18 | LegendBuilder legend();
19 |
20 | /**
21 | * This renews a figure. Make sure that this has to be put before adding plots.
22 | * Unless that, a new figure window will be open.
23 | */
24 | void figure(String windowTitle);
25 |
26 | void title(String title);
27 |
28 | LabelBuilder xlabel(String label);
29 |
30 | LabelBuilder ylabel(String label);
31 |
32 | ScaleBuilder xscale(ScaleBuilder.Scale scale);
33 |
34 | ScaleBuilder yscale(ScaleBuilder.Scale scale);
35 |
36 | void xlim(Number xmin, Number xmax);
37 |
38 | void ylim(Number ymin, Number ymax);
39 |
40 | TicksBuilder xticks(List extends Number> ticks);
41 |
42 | TicksBuilder yticks(List extends Number> ticks);
43 |
44 | TextBuilder text(double x, double y, String s);
45 |
46 | PlotBuilder plot();
47 |
48 | ContourBuilder contour();
49 |
50 | PColorBuilder pcolor();
51 |
52 | HistBuilder hist();
53 |
54 | CLabelBuilder clabel(ContourBuilder contour);
55 |
56 | SaveFigBuilder savefig(String fname);
57 |
58 | SubplotBuilder subplot(int nrows, int ncols, int index);
59 |
60 | /**
61 | * Close a figure window.
62 | */
63 | void close();
64 |
65 | /**
66 | * Close a figure window with name label.
67 | */
68 | void close(String name);
69 |
70 | /**
71 | * Silently execute Python script until here by builders.
72 | * It is mostly useful to execute `plt.savefig()` without showing by window.
73 | */
74 | void executeSilently() throws IOException, PythonExecutionException;
75 |
76 | /**
77 | * matplotlib.pyplot.show(*args, **kw)
78 | */
79 | void show() throws IOException, PythonExecutionException;
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/github/sh0nk/matplotlib4j/PlotImpl.java:
--------------------------------------------------------------------------------
1 | package com.github.sh0nk.matplotlib4j;
2 |
3 | import com.github.sh0nk.matplotlib4j.builder.*;
4 | import com.google.common.annotations.VisibleForTesting;
5 | import com.google.common.base.Joiner;
6 |
7 | import java.io.IOException;
8 | import java.util.LinkedList;
9 | import java.util.List;
10 |
11 | public class PlotImpl implements Plot {
12 | @VisibleForTesting
13 | List registeredBuilders = new LinkedList<>();
14 | private List showBuilders = new LinkedList<>();
15 |
16 | private final boolean dryRun;
17 | private final PythonConfig pythonConfig;
18 |
19 | PlotImpl(PythonConfig pythonConfig, boolean dryRun) {
20 | this.pythonConfig = pythonConfig;
21 | this.dryRun = dryRun;
22 | }
23 |
24 | @VisibleForTesting
25 | PlotImpl(boolean dryRun) {
26 | this(PythonConfig.systemDefaultPythonConfig(), dryRun);
27 | }
28 |
29 | @Override
30 | public LegendBuilder legend() {
31 | LegendBuilder builder = new LegendBuilderImpl();
32 | registeredBuilders.add(builder);
33 | return builder;
34 | }
35 |
36 | @Override
37 | public void figure(String windowTitle) {
38 | registeredBuilders.add(new ArgsBuilderImpl("figure", windowTitle));
39 | }
40 |
41 | @Override
42 | public void title(String title) {
43 | registeredBuilders.add(new ArgsBuilderImpl("title", title));
44 | }
45 |
46 | @Override
47 | public LabelBuilder xlabel(String label) {
48 | LabelBuilder builder = LabelBuilderImpl.xLabelBuilder(label);
49 | registeredBuilders.add(builder);
50 | return builder;
51 | }
52 |
53 | @Override
54 | public LabelBuilder ylabel(String label) {
55 | LabelBuilder builder = LabelBuilderImpl.yLabelBuilder(label);
56 | registeredBuilders.add(builder);
57 | return builder;
58 | }
59 |
60 | @Override
61 | public ScaleBuilder xscale(ScaleBuilder.Scale scale) {
62 | ScaleBuilder builder = ScaleBuilderImpl.xScaleBuilder(scale);
63 | registeredBuilders.add(builder);
64 | return builder;
65 | }
66 |
67 | @Override
68 | public ScaleBuilder yscale(ScaleBuilder.Scale scale) {
69 | ScaleBuilder builder = ScaleBuilderImpl.yScaleBuilder(scale);
70 | registeredBuilders.add(builder);
71 | return builder;
72 | }
73 |
74 | @Override
75 | public void xlim(Number xmin, Number xmax) {
76 | registeredBuilders.add(new ArgsBuilderImpl("xlim", xmin, xmax));
77 | }
78 |
79 | @Override
80 | public void ylim(Number ymin, Number ymax) {
81 | registeredBuilders.add(new ArgsBuilderImpl("ylim", ymin, ymax));
82 | }
83 |
84 | @Override
85 | public TicksBuilder xticks(List extends Number> ticks) {
86 | TicksBuilder builder = TicksBuilderImpl.xTicksBuilder(ticks);
87 | registeredBuilders.add(builder);
88 | return builder;
89 | }
90 |
91 | @Override
92 | public TicksBuilder yticks(List extends Number> ticks) {
93 | TicksBuilder builder = TicksBuilderImpl.yTicksBuilder(ticks);
94 | registeredBuilders.add(builder);
95 | return builder;
96 | }
97 |
98 | @Override
99 | public TextBuilder text(double x, double y, String s) {
100 | TextBuilder builder = new TextBuilderImpl(x, y, s);
101 | registeredBuilders.add(builder);
102 | return builder;
103 | }
104 |
105 | @Override
106 | public PlotBuilder plot() {
107 | PlotBuilder builder = new PlotBuilderImpl();
108 | registeredBuilders.add(builder);
109 | return builder;
110 | }
111 |
112 | @Override
113 | public ContourBuilder contour() {
114 | ContourBuilder builder = new ContourBuilderImpl();
115 | registeredBuilders.add(builder);
116 | return builder;
117 | }
118 |
119 | @Override
120 | public PColorBuilder pcolor() {
121 | PColorBuilder builder = new PColorBuilderImpl();
122 | registeredBuilders.add(builder);
123 | return builder;
124 | }
125 |
126 | @Override
127 | public HistBuilder hist() {
128 | HistBuilder builder = new HistBuilderImpl();
129 | registeredBuilders.add(builder);
130 | return builder;
131 | }
132 |
133 | @Override
134 | public CLabelBuilder clabel(ContourBuilder contour) {
135 | CLabelBuilder builder = new CLabelBuilderImpl(contour);
136 | registeredBuilders.add(builder);
137 | return builder;
138 | }
139 |
140 | @Override
141 | public SaveFigBuilder savefig(String fname) {
142 | SaveFigBuilder builder = new SaveFigBuilderImpl(fname);
143 | registeredBuilders.add(builder);
144 | return builder;
145 | }
146 |
147 | @Override
148 | public SubplotBuilder subplot(int nrows, int ncols, int index) {
149 | SubplotBuilder builder = new SubplotBuilderImpl(nrows, ncols, index);
150 | registeredBuilders.add(builder);
151 | return builder;
152 | }
153 |
154 | @Override
155 | public void close() {
156 | registeredBuilders.add(new ArgsBuilderImpl("close"));
157 | }
158 |
159 | @Override
160 | public void close(String name) {
161 | registeredBuilders.add(new ArgsBuilderImpl("close", name));
162 | }
163 |
164 | @Override
165 | public void executeSilently() throws IOException, PythonExecutionException {
166 | List scriptLines = new LinkedList<>();
167 | scriptLines.add("import numpy as np");
168 | scriptLines.add("import matplotlib as mpl");
169 | scriptLines.add("mpl.use('Agg')");
170 | scriptLines.add("import matplotlib.pyplot as plt");
171 | registeredBuilders.forEach(b -> scriptLines.add(b.build()));
172 | showBuilders.forEach(b -> scriptLines.add(b.build()));
173 | PyCommand command = new PyCommand(pythonConfig);
174 | command.execute(Joiner.on('\n').join(scriptLines));
175 | }
176 |
177 | /**
178 | * matplotlib.pyplot.show(*args, **kw)
179 | */
180 | @Override
181 | public void show() throws IOException, PythonExecutionException {
182 | List scriptLines = new LinkedList<>();
183 | scriptLines.add("import numpy as np");
184 | if (dryRun) {
185 | // No need DISPLAY for test run
186 | scriptLines.add("import matplotlib as mpl");
187 | scriptLines.add("mpl.use('Agg')");
188 | }
189 | scriptLines.add("import matplotlib.pyplot as plt");
190 | registeredBuilders.forEach(b -> scriptLines.add(b.build()));
191 |
192 | // show
193 | if (!dryRun) {
194 | scriptLines.add("plt.show()");
195 | }
196 |
197 | PyCommand command = new PyCommand(pythonConfig);
198 | command.execute(Joiner.on('\n').join(scriptLines));
199 |
200 | // After showing, registered plot is cleared
201 | registeredBuilders.clear();
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/src/main/java/com/github/sh0nk/matplotlib4j/PyCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.sh0nk.matplotlib4j;
2 |
3 | import com.google.common.base.Strings;
4 | import com.google.common.collect.Lists;
5 | import com.google.common.io.Files;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import java.io.*;
10 | import java.nio.charset.StandardCharsets;
11 | import java.nio.file.Paths;
12 | import java.util.List;
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 |
16 | public class PyCommand {
17 | private final PythonConfig pythonConfig;
18 |
19 | public PyCommand(PythonConfig pythonConfig) {
20 | this.pythonConfig = pythonConfig;
21 | }
22 |
23 | private final static Logger LOGGER = LoggerFactory.getLogger(PyCommand.class);
24 |
25 | private final static Pattern ERROR_PAT = Pattern.compile("^.+Error:");
26 |
27 | private List buildCommandArgs(String scriptPath) {
28 | StringBuilder shell = new StringBuilder();
29 | if (!Strings.isNullOrEmpty(pythonConfig.getPyenv())) {
30 | shell.append("pyenv shell ").append(pythonConfig.getPyenv()).append("; ");
31 |
32 | if (!Strings.isNullOrEmpty(pythonConfig.getVirtualenv())) {
33 | shell.append("export PYENV_VIRTUALENV_DISABLE_PROMPT=1; ");
34 | shell.append("pyenv activate ").append(pythonConfig.getVirtualenv()).append("; ");
35 | }
36 | shell.append("python ").append(scriptPath);
37 | }
38 |
39 | List com;
40 | if (!Strings.isNullOrEmpty(pythonConfig.getPythonBinPath())) {
41 | com = Lists.newArrayList(pythonConfig.getPythonBinPath(), scriptPath);
42 | } else if (shell.length() != 0) {
43 | // -l: Use login shell
44 | com = Lists.newArrayList("bash", "-l", "-c", shell.toString());
45 | } else {
46 | // system's default
47 | com = Lists.newArrayList("python", scriptPath);
48 | }
49 |
50 | LOGGER.debug("Commands... : {}", com);
51 | return com;
52 | }
53 |
54 | private void command(List commands) throws IOException, PythonExecutionException {
55 | ProcessBuilder pb = new ProcessBuilder(commands);
56 | Process process = pb.start();
57 |
58 | // stdout
59 | BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
60 | String line = br.readLine();
61 | while (line != null) {
62 | System.out.println(line);
63 | line = br.readLine();
64 | }
65 |
66 | // stderr
67 | // TODO: have a common way with stdout
68 |
69 | br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
70 | StringBuilder sb = new StringBuilder();
71 | line = br.readLine();
72 | boolean hasError = false;
73 | while (line != null) {
74 | sb.append(line).append('\n');
75 | Matcher matcher = ERROR_PAT.matcher(line);
76 | if (matcher.find()) {
77 | hasError = true;
78 | }
79 | line = br.readLine();
80 | }
81 |
82 | String msg = sb.toString();
83 | if (hasError) {
84 | LOGGER.error(msg);
85 | throw new PythonExecutionException("Python execution error: " + msg);
86 | } else {
87 | LOGGER.warn(msg);
88 | }
89 | }
90 |
91 | private void writeFile(String pythonScript, File script) throws IOException {
92 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(script), StandardCharsets.UTF_8));
93 | bw.write(pythonScript);
94 | bw.close();
95 | }
96 |
97 | public void execute(String pythonScript) throws IOException, PythonExecutionException {
98 | File tmpDir = Files.createTempDir();
99 | tmpDir.deleteOnExit();
100 | File script = new File(tmpDir, "exec.py");
101 |
102 | writeFile(pythonScript, script);
103 |
104 | String scriptPath = Paths.get(script.toURI()).toAbsolutePath().toString();
105 | command(buildCommandArgs(scriptPath));
106 | tmpDir.delete();
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/com/github/sh0nk/matplotlib4j/PythonConfig.java:
--------------------------------------------------------------------------------
1 | package com.github.sh0nk.matplotlib4j;
2 |
3 | public class PythonConfig {
4 | private final String pyenv;
5 | private final String virtualenv;
6 | private final String pythonBinPath;
7 |
8 | private PythonConfig(String pyenv, String virtualenv, String pythonBinPath) {
9 | this.pyenv = pyenv;
10 | this.virtualenv = virtualenv;
11 | this.pythonBinPath = pythonBinPath;
12 | }
13 |
14 | public static PythonConfig systemDefaultPythonConfig() {
15 | return new PythonConfig(null, null, null);
16 | }
17 |
18 | public static PythonConfig pyenvConfig(String pyenv) {
19 | return new PythonConfig(pyenv, null, null);
20 | }
21 |
22 | public static PythonConfig pyenvVirtualenvConfig(String pyenv, String virtualenv) {
23 | return new PythonConfig(pyenv, virtualenv, null);
24 | }
25 |
26 | public static PythonConfig pythonBinPathConfig(String pythonBinPath) {
27 | return new PythonConfig(null, null, pythonBinPath);
28 | }
29 |
30 | public String getPyenv() {
31 | return pyenv;
32 | }
33 |
34 | public String getVirtualenv() {
35 | return virtualenv;
36 | }
37 |
38 | public String getPythonBinPath() {
39 | return pythonBinPath;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/github/sh0nk/matplotlib4j/PythonExecutionException.java:
--------------------------------------------------------------------------------
1 | package com.github.sh0nk.matplotlib4j;
2 |
3 | public class PythonExecutionException extends Exception {
4 |
5 | public PythonExecutionException(String msg) {
6 | super(msg);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/github/sh0nk/matplotlib4j/TypeConversion.java:
--------------------------------------------------------------------------------
1 | package com.github.sh0nk.matplotlib4j;
2 |
3 | import java.util.List;
4 | import java.util.stream.Collectors;
5 |
6 | public enum TypeConversion {
7 | INSTANCE;
8 |
9 | private final static String PYTHON_NONE = "None";
10 |
11 | public List