From e7edc2a1b1e8994c90d22cc2602c575db86cf7d9 Mon Sep 17 00:00:00 2001 From: Dave Hadka Date: Sat, 23 Dec 2023 20:38:01 -0700 Subject: [PATCH] Update with latest Java / Gluon support --- .classpath | 19 ++- .project | 11 ++ README.md | 107 +++++++------ pom.xml | 142 ++++++++++++------ src/main/java/j3/ErrorUtils.java | 22 +++ src/main/java/j3/GUI.java | 102 +++++++------ src/main/java/j3/dataframe/MagicTyping.java | 20 +-- src/main/java/j3/io/impl/CSVReader.java | 2 +- .../j3/widget/impl/animate/AnimateWidget.java | 5 +- 9 files changed, 270 insertions(+), 160 deletions(-) create mode 100644 src/main/java/j3/ErrorUtils.java diff --git a/.classpath b/.classpath index dc28dbd..9f67578 100644 --- a/.classpath +++ b/.classpath @@ -23,10 +23,27 @@ - + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project index ca088f9..5fcf9e1 100644 --- a/.project +++ b/.project @@ -20,4 +20,15 @@ org.eclipse.m2e.core.maven2Nature org.eclipse.jdt.core.javanature + + + 1668977750143 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/README.md b/README.md index 5f7b824..03bce93 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,10 @@ -# J3 # +# J3 **A free desktop application for producing and sharing high-dimensional, interactive scientific visualizations.** ![Screenshot](http://i.imgur.com/W2zqCTT.jpg) -## What is J3 ## - -J3 is an open source, cross-platform application for producing and sharing high-dimensional, interactive scientific -visualizations. While there are high-quality JavaScript libraries for producing visualization on the web, they are -often plagued by performance issues when dealing with large data sets. J3, on the other hand, can fluidly support -thousands of data points by leveraging hardware accelerated graphics while simultaneously supporting animations and -interactivity. - -## Key Features ## +## Key Features 1. Interactive 2D and 3D plots (scatter, parallel axis, etc.) 2. Various annotations and data callouts @@ -20,43 +12,73 @@ interactivity. 4. Create scripted animations (experimental) 5. Extensible design allows adding new widgets -## Get It ## +## Compatibility + +The latest version of J3, `2.x`, supports Java 17 and newer. It requires installing the JavaFX runtime separately +and Gluon / GraalVM for native compilation. + +The older versions of J3, `1.x`, require Java 8. The `1.x` versions are no longer supported. + +## Compiling + +There are a few options for running J3 that have different requirements. These build steps are tested on Windows but +should be similar on other platforms. -Pre-packaged distributions are available for Windows, Linux, and Mac. All distributions include sample data files you can test. +### Prerequisites -#### Windows #### +1. Download and install [Maven](https://maven.apache.org/). Set the `MAVEN_HOME` environment variable to the + installation path. +2. Download and install the [JavaFX runtime](https://gluonhq.com/products/javafx/). Set the `JAVAFX_HOME` + environment variable to the installation path. + +If building native executables, the following dependencies are also required: + +1. Ensure all prerequisites for your target platform(s) are satisfied: https://docs.gluonhq.com/#_platforms +2. Download and install [GraalVM](https://www.graalvm.org/). Set the `GRAALVM_HOME` + environment variable to the installation path. + +### Option 1 - Eclipse -Download and extract [J3-Win.zip](https://github.com/MOEAFramework/J3/releases/download/1.0.1/J3-Win.zip) if you -already have Java 8 installed. Otherwise, download [J3-Win-JRE.zip](https://github.com/MOEAFramework/J3/releases/download/1.0.1/J3-Win-JRE.zip), -which is bundled with the Java 8 runtime environment. After extracting, run `J3.exe`. You can also load a data set -from the command line by running `J3.exe `. +J3 can be launched directly from Eclipse. First, create a new Eclipse project from the J3 source code. +Then, right-click on `src/main/java/j3/GUI.java` and run as a Java Application. + +Under VM arguments, add: + +``` +--module-path ${JAVAFX_HOME}\lib --add-modules javafx.controls +``` -You can also download and run [J3.exe](https://github.com/MOEAFramework/J3/releases/download/1.0.1/J3.exe) by -itself, although you will need to provide your own data files. +Apply the changes and run the application. -#### Linux (Debian, Ubuntu, etc.) #### +### Option 2 - Maven -A deb file is provided to assist installing J3 on Linux. This installation requires `openjdk-8-jre`. On -Ubuntu, we needed to add the following repository to satisfy this dependency: +On Windows, we use the Visual Studio Developer Command Prompt to build J3 as it requires a compiler/linker: ``` - sudo apt-add-repository ppa:openjdk-r/ppa - sudo apt-get update +"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + +set MAVEN_HOME=C:\apache-maven-3.8.6 +set JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-18.0.2.101-hotspot +set GRAALVM_HOME=C:\graalvm-svm-java17-windows-gluon-22.1.0.1-Final +set PATH=%MAVEN_HOME%\bin;%JAVA_HOME%\bin;%PATH% + +mvn clean +mvn gluonfx:build # Compile J3 +mvn gluonfx:run # Launch J3 ``` -On some versions of Linux, [JavaFX may not be bundled with OpenJDK](http://stackoverflow.com/questions/34243982/why-is-javafx-is-not-included-in-openjdk-8-on-ubuntu-wily-15-10). If this is the case, run `sudo apt-get install openjfx` to install JavaFX. +### Option 3 - Native Executable -Finally, download and install [J3-1.0-1.deb](https://github.com/MOEAFramework/J3/releases/download/1.0.0/J3_1.0-1.deb). -After installation, J3 will appear as a desktop application. You can also launch the program by running the command -`J3`. +Following the same steps above to build J3 using Maven, run: -#### Mac #### +``` +mvn gluonfx:nativerun # Run the native version of J3 +mvn gluonfx:package # Package J3 into an installer +``` -Download and extract [J3-Mac.zip](https://github.com/MOEAFramework/J3/releases/download/1.0.0/J3-Mac.zip) if you -already have Java 8 installed. Otherwise, download [J3-Mac-JRE.zip](https://github.com/MOEAFramework/J3/releases/download/1.0.0/J3-Mac-JRE.zip), -which is bundled with the Java 8 runtime environment. After extracting, run `J3.app`. +Note: Some functionality is not supported for native executables, including the Camera and Animation widgets. -## FAQ ## +## FAQ 1. **What does J3 stand for?** The name J3 is derived from the use of Java technologies and its design being influenced by the popular D3.js JavaScript library for "data driven documents." J3 shares many similarities with D3, such as @@ -70,7 +92,7 @@ which is bundled with the Java 8 runtime environment. After extracting, run `J3 3. **How can I contribute to J3?** J3 is designed to be extensible. Everything from themes, color maps, widgets, and supported file types is extensible. Clone this repository and give it a shot. If you have questions, please create - an issue on Github. + an issue on GitHub. 4. **How can I use J3 in my application?** J3 can be used by any program to view high-dimensional data sets. There are several options. If using Java, you can launch the GUI directly: @@ -89,19 +111,4 @@ which is bundled with the Java 8 runtime environment. After extracting, run `J3 os.environ['PATH'] += os.pathsep + r"C:\Users\J3Dev\Desktop\J3" os.system("J3.exe input.csv") ``` - - -## Building ## - -J3 uses Maven to manage dependencies. Use `mvn package` to compile the J3 JAR file. To create platform-specific -bundles, call the appropriate Ant task. For example: - -``` - mvn package - ant build-win - ant build-mac - - # Requires running on Linux (Debian, Ubuntu, etc.) with openjdk-8-jdk installed - export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-i386/ - ant build-deb -``` + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 23c5fba..a12a5a6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,29 +4,34 @@ org.j3 j3 - 1.1.0 - - - UTF-8 - 17 - 19 - 0.0.8 - 1.0.16 - 6.2.2 - 4.0.16 - j3.GUI - + 2.0.0 + + + UTF-8 + 17 + 21 + 0.0.8 + 1.0.22 + 6.2.2 + 4.0.16 + j3.GUI + org.openjfx javafx-controls - 19 + ${javafx.version} org.openjfx javafx-swing - 19 + ${javafx.version} + + + org.openjfx + javafx-graphics + ${javafx.version} org.apache.commons @@ -46,7 +51,7 @@ org.controlsfx controlsfx - 11.1.2 + 11.2.0 org.dom4j @@ -56,42 +61,83 @@ org.apache.groovy groovy - 4.0.6 + 4.0.17 - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - - org.openjfx - javafx-maven-plugin - ${javafx.plugin.version} - - ${main.class} - - - - com.gluonhq - gluonfx-maven-plugin - ${gluonfx.plugin.version} - - ${gluonfx.target} - ${main.class} - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.openjfx + javafx-maven-plugin + ${javafx.plugin.version} + + ${main.class} + + + + com.gluonhq + gluonfx-maven-plugin + ${gluonfx.plugin.version} + + ${gluonfx.target} + ${main.class} + --report-unsupported-elements-at-runtime + + com.sun.prism.shader.Solid_TextureRGB_AlphaTest_Loader + + + .*\\.properties$ + .*\\.cmap$ + + + Project-Platypus + + + + + + + + + desktop + + host + + + true + + + + android + + android + + + + ios + + ios + + + + web + + web + + + - - - gluon-releases - https://nexus.gluonhq.com/nexus/content/repositories/releases/ - - + + + gluon-releases + https://nexus.gluonhq.com/nexus/content/repositories/releases/ + + diff --git a/src/main/java/j3/ErrorUtils.java b/src/main/java/j3/ErrorUtils.java new file mode 100644 index 0000000..58ae959 --- /dev/null +++ b/src/main/java/j3/ErrorUtils.java @@ -0,0 +1,22 @@ +package j3; + +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.layout.Region; + +public class ErrorUtils { + + private ErrorUtils() { + super(); + } + + public static void showError(String header, Throwable e) { + Alert alert = new Alert(AlertType.ERROR); + alert.setTitle("J3 Error"); + alert.setHeaderText(header); + alert.setContentText(e.toString()); + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.showAndWait(); + } + +} diff --git a/src/main/java/j3/GUI.java b/src/main/java/j3/GUI.java index bf06733..5fd7fb2 100644 --- a/src/main/java/j3/GUI.java +++ b/src/main/java/j3/GUI.java @@ -120,8 +120,8 @@ public void start(Stage primaryStage) throws Exception { .get(fileChooser.getExtensionFilters().indexOf(fileChooser.getSelectedExtensionFilter())); selectedReader.load(selectedFile, canvas); - } catch (IOException e) { - e.printStackTrace(); + } catch (Exception e) { + ErrorUtils.showError("Failed to open file", e); } } }); @@ -144,60 +144,64 @@ public void start(Stage primaryStage) throws Exception { try { new J3Writer().save(selectedFile, canvas); } catch (IOException e) { - e.printStackTrace(); + ErrorUtils.showError("Failed to save file", e); } } }); camera.setOnAction(event -> { - double scale = 2.0; - WritableImage image = new WritableImage((int) Math.rint(scale * canvas.getWidth()), - (int) Math.rint(scale * canvas.getHeight())); - SnapshotParameters params = new SnapshotParameters(); - params.setCamera(new ParallelCamera()); - params.setTransform(Transform.scale(2.0, 2.0)); - - image = canvas.snapshot(params, image); - - FileChooser fileChooser = new FileChooser(); - - for (String suffix : ImageIO.getWriterFileSuffixes()) { - FileChooser.ExtensionFilter filter = new FileChooser.ExtensionFilter( - StringUtils.upperCase(suffix) + " image", "*." + suffix); - fileChooser.getExtensionFilters().add(filter); - - if (suffix.equalsIgnoreCase("png")) { - fileChooser.setSelectedExtensionFilter(filter); + try { + double scale = 2.0; + WritableImage image = new WritableImage((int) Math.rint(scale * canvas.getWidth()), + (int) Math.rint(scale * canvas.getHeight())); + SnapshotParameters params = new SnapshotParameters(); + params.setCamera(new ParallelCamera()); + params.setTransform(Transform.scale(2.0, 2.0)); + + image = canvas.snapshot(params, image); + + FileChooser fileChooser = new FileChooser(); + + for (String suffix : ImageIO.getWriterFileSuffixes()) { + FileChooser.ExtensionFilter filter = new FileChooser.ExtensionFilter( + StringUtils.upperCase(suffix) + " image", "*." + suffix); + fileChooser.getExtensionFilters().add(filter); + + if (suffix.equalsIgnoreCase("png")) { + fileChooser.setSelectedExtensionFilter(filter); + } } - } - - if (fileChooser.getSelectedExtensionFilter() == null) { - fileChooser.setSelectedExtensionFilter(fileChooser.getExtensionFilters().get(0)); - } - - LocalDateTime now = LocalDateTime.now(); - StringBuilder filename = new StringBuilder(); - filename.append("snapshot_"); - filename.append(String.format("%02d", now.getDayOfMonth())); - filename.append(String.format("%02d", now.getMonthValue())); - filename.append(String.format("%02d", now.getYear())); - filename.append("_"); - filename.append(String.format("%02d", now.getHour())); - filename.append(String.format("%02d", now.getMinute())); - filename.append(String.format("%02d", now.getSecond())); - filename.append(fileChooser.getSelectedExtensionFilter().getExtensions().get(0).substring(1)); - - fileChooser.setInitialFileName(filename.toString()); - - File selectedFile = fileChooser.showSaveDialog(primaryStage); - - if (selectedFile != null) { - try { - ImageIO.write(SwingFXUtils.fromFXImage(image, null), - fileChooser.getSelectedExtensionFilter().getExtensions().get(0).substring(2), selectedFile); - } catch (IOException e) { - e.printStackTrace(); + + if (fileChooser.getSelectedExtensionFilter() == null) { + fileChooser.setSelectedExtensionFilter(fileChooser.getExtensionFilters().get(0)); + } + + LocalDateTime now = LocalDateTime.now(); + StringBuilder filename = new StringBuilder(); + filename.append("snapshot_"); + filename.append(String.format("%02d", now.getDayOfMonth())); + filename.append(String.format("%02d", now.getMonthValue())); + filename.append(String.format("%02d", now.getYear())); + filename.append("_"); + filename.append(String.format("%02d", now.getHour())); + filename.append(String.format("%02d", now.getMinute())); + filename.append(String.format("%02d", now.getSecond())); + filename.append(fileChooser.getSelectedExtensionFilter().getExtensions().get(0).substring(1)); + + fileChooser.setInitialFileName(filename.toString()); + + File selectedFile = fileChooser.showSaveDialog(primaryStage); + + if (selectedFile != null) { + try { + ImageIO.write(SwingFXUtils.fromFXImage(image, null), + fileChooser.getSelectedExtensionFilter().getExtensions().get(0).substring(2), selectedFile); + } catch (IOException e) { + ErrorUtils.showError("Failed to save image", e); + } } + } catch (UnsatisfiedLinkError e) { + ErrorUtils.showError("Camera functionality disabled, missing AWT dependency", e); } }); diff --git a/src/main/java/j3/dataframe/MagicTyping.java b/src/main/java/j3/dataframe/MagicTyping.java index 7ee1397..8ab8e2a 100644 --- a/src/main/java/j3/dataframe/MagicTyping.java +++ b/src/main/java/j3/dataframe/MagicTyping.java @@ -1,22 +1,28 @@ package j3.dataframe; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; import java.util.function.Predicate; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.layout.Region; + import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; public class MagicTyping { - private List, Class>>> conversions; + private List, Function>>> conversions; public MagicTyping() { super(); conversions = new ArrayList<>(); - conversions.add(new ImmutablePair<>(isInteger, IntegerAttribute.class)); - conversions.add(new ImmutablePair<>(isDouble, DoubleAttribute.class)); + conversions.add(new ImmutablePair<>(isInteger, (name) -> new IntegerAttribute(name))); + conversions.add(new ImmutablePair<>(isDouble, (name) -> new DoubleAttribute(name))); } public void convert(DataFrame frame) { @@ -36,13 +42,9 @@ public void convert(DataFrame frame) { } private Attribute determineType(DataFrame frame, Attribute attribute) { - for (Pair, Class>> conversion : conversions) { + for (Pair, Function>> conversion : conversions) { if (frame.getInstances().stream().map(i -> (Object) i.get(attribute)).allMatch(conversion.getKey())) { - try { - return conversion.getValue().getDeclaredConstructor(String.class).newInstance(attribute.getName()); - } catch (Exception e) { - throw new RuntimeException("Unable to call constructor for " + conversion.getValue()); - } + return conversion.getValue().apply(attribute.getName()); } } diff --git a/src/main/java/j3/io/impl/CSVReader.java b/src/main/java/j3/io/impl/CSVReader.java index c306c4f..f54b3a9 100644 --- a/src/main/java/j3/io/impl/CSVReader.java +++ b/src/main/java/j3/io/impl/CSVReader.java @@ -31,7 +31,7 @@ public DataFrame load(InputStream is) throws IOException { DataFrame frame = new DataFrame(); // load CSV file into a data frame containing strings - try (CSVParser parser = new CSVParser(new InputStreamReader(is), CSVFormat.DEFAULT.withHeader())) { + try (CSVParser parser = new CSVParser(new InputStreamReader(is), CSVFormat.Builder.create().setHeader().build())) { int size = parser.getHeaderMap().size(); for (String column : parser.getHeaderMap().keySet()) { diff --git a/src/main/java/j3/widget/impl/animate/AnimateWidget.java b/src/main/java/j3/widget/impl/animate/AnimateWidget.java index 7e85041..12aea8c 100644 --- a/src/main/java/j3/widget/impl/animate/AnimateWidget.java +++ b/src/main/java/j3/widget/impl/animate/AnimateWidget.java @@ -3,6 +3,7 @@ import groovy.lang.Binding; import groovy.lang.GroovyShell; import j3.Canvas; +import j3.ErrorUtils; import j3.widget.SerializableWidget; import j3.widget.TitledWidget; @@ -86,8 +87,8 @@ public void initialize(Canvas canvas) { GroovyShell shell = new GroovyShell(bindings); shell.evaluate(script); - } catch (Exception ex) { - ex.printStackTrace(); + } catch (Throwable ex) { + ErrorUtils.showError("Failed to run animation script", ex); } } });