I would like to create and embed the .pngs of technical plots (based on JavaScript and plotly) into a PowerPoint presentation. I have used JFreeChart and JafaFX plots in the past (and created .pngs with then) but now I am trying to replace those plotting technologies with plotly. The link below says that it is not possible to embed .html directly into a PPT slide so it can be viewed without clicking on it without a 3rd party add-on (which I cannot use) which is why I am trying to change my .html (with JavaScript) plot files to .png plot files. https://superuser.com/questions/1221880/how-to-embed-an-html-file-into-a-powerpoint-presentation
I regularly create a 100's of ploty .html files from java (sample below) that are saved to my local hard drive. My dev/execution environment is off-line so I need a solution I can call from Java (i.e. native or Open Source 3rd party .jar I can download & use) so that I can turn these .html files (with plotly JavaScript) into .pngs.
A Swing JEditorPane solution won't work with my .html files.
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class HTML2PNG_SWING {
public static void main(String[] args) {
URL htmlFile = null;
try {htmlFile = new File("D://myPlot.html").toURI().toURL();} catch (MalformedURLException e2) {e2.printStackTrace();}
File pngFile = new File("D://myPlot.png"); // png file I want to create
JEditorPane ed = null;
//load the webpage into the editor
try {ed = new JEditorPane(htmlFile);} catch (IOException e) {e.printStackTrace();}
try { // Likely there is some callback I can use instead which can tell me when the .html file is loaded
Thread.sleep(10000);} catch (InterruptedException e1) {e1.printStackTrace();
}
// I have to put some window size here... it would be nice if the size were auto-generated based on the .html plot
ed.setSize(1000,1000);
//create a new image
BufferedImage image = new BufferedImage(ed.getWidth(), ed.getHeight(),BufferedImage.TYPE_INT_ARGB);
//paint the editor onto the image
SwingUtilities.paintComponent(image.createGraphics(), ed, new JPanel(), 0, 0, image.getWidth(), image.getHeight());
//save the image to file
try {ImageIO.write((RenderedImage)image, "png", pngFile);} catch (IOException e) {e.printStackTrace();
}
}
}
Explanation: A Swing JEditorPane works with simple .html files without the need to render the html file to the screen. When I try the Swing solution on my sample .html file below, the two Text divs show up (see sample below) but not the plotly div plot itself so it is likely that it is the plotly.js JavaScript the JEditorPane is having trouble with: https://www.tutorialspoint.com/swingexamples/using_jeditorpane_to_show_html.htm
A JavaFX Snapshot/WebView/WebEngine solution won't work with my .html files.
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class HTML2PNG_JAVAFX2 extends Application {
public void start(Stage primaryStage) throws Exception {
URL htmlFile = null;
try {htmlFile = new File("D://myPlot.html").toURI().toURL();} catch (MalformedURLException e2) {e2.printStackTrace();}
File pngFile = new File("D://myPlot.png"); // png file I want to create
System.out.println("Before loading web page- "+new Date().toString());
WebView webView = new WebView();
webView.getEngine().load(htmlFile.toExternalForm()); // comment this out and uncomment out the next line to show that 'simple' pages can be rendered' successfully
// webView.getEngine().load( "http://www.stackoverflow.com/" );
WebEngine webEngine = webView.getEngine();
VBox vBox = new VBox();
vBox.getChildren().add(new Text("Hello World")); // used so I can see something if the webView has not loaded
vBox.getChildren().add(webView);
// I am only using this to show that the page is loaded (successfully or not) and when
webEngine.getLoadWorker().stateProperty().addListener(new BrowserStatusChangeListener(vBox));
Stage tempStage = new Stage();
// Please excuse this weird threading code and the .sleeps.
// I was trying to methodically separate the different activities into isolated code pockets to better understand what was going on
// Put this in a new Thread so it is off the JavaFX Thread
new Thread(() -> {
// Delay to allow the web page to load
try {
Thread.sleep(10000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// This goes back on the JavaFX Thread
Platform.runLater(() ->{
Scene scene = new Scene(vBox);
tempStage.setScene(scene);
vBox.layout();
vBox.requestLayout();
tempStage.show(); // if this line is commented out the html will not be captured
});
// Delay to allow the above scene/stage code to be executed (may not be needed)
try {
Thread.sleep(10000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// This also goes back on the JavaFX Thread but after a 2nd delay
Platform.runLater(() ->{
System.out.println("Writing... after the delay- "+new Date().toString());
WritableImage image = vBox.snapshot(new SnapshotParameters(), null);
try {
ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", pngFile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (pngFile.length() > 50000) {
System.out.println("Success, file size: "+pngFile.length()+"- "+new Date().toString());
}
else {
System.out.println("Failure, file size: "+pngFile.length()+"- "+new Date().toString());
}
});
})
.start();
}
//
class BrowserStatusChangeListener implements ChangeListener<Worker.State> {
VBox vBox;
public BrowserStatusChangeListener(VBox vBox) {
this.vBox = vBox;
}
@Override
public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
if (newValue.equals(Worker.State.SUCCEEDED)) {
System.out.println("Succeeded loading- "+new Date().toString());
}
else if(newValue.equals(Worker.State.FAILED)){
System.out.println("Failed loading- "+new Date().toString());
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
Explanation: A JavaFX Snapshot/WebView/WebEngine solution only works if the WebView is visible (attached to JavaFX Scene and Stage and 'Show()'ed). If tempStage.show();
is commented out nothing can be captured. The following unanswered question is specific to JavaFX but otherwise is the same as mine.
JavaFX - How to create SnapShot/Screenshot of (invisble) WebView. Finally, I could not get the WebEngine to load myPlot.html correctly. The engine reports back that it succeeded when loading myPlot.html BUT the Java Script does not appear to be executing. This link implies you can force the JavaScript execution BUT I did not try it because of the tempStage.show();
issue above. http://tutorials.jenkov.com/javafx/webview.html#executing-javascript-from-java
I think a Swing, JavaFX, or 3rd Party html rendering engine that can handle Java Script and can return a graphic image of the rendered .html WITHOUT displaying it to the screen is what I am looking for. I don't believe my problem is ploty.js specific.
My ideal solution would look like the following:
// Sample ideal pseudo code (ignoring error handling code)
URL htmlFile = new File("D://myPlot.html").toURI().toURL(); // local .html file (that contains JavaScript)
File pngFile = new File("D://myPlot.png"); // png file I want to create
SomeWebEngine we = new SomeWebEngine(htmlFile); // loads the .html file (but does not display it to the screen)
we.whenLoaded(()->we.saveTo(pngFile)); // Spawns task which executes AFTER the html file is loaded, which saves the html to the png file
Bonus capability: The rendered image is the minimal image size needed to contain the html plot (see sample below) AND you do not have to set that size yourself.
Sample plotly based "plot.html" file (with html 'div' wrapper for additional text elements at the corners elements that I can't do in plotly alone):
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>My Plot</title>
<script src='D://plotly.js'></script>
<!-- <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> won't work becuase I am offline -->
</head>
<body>
<div style="position:relative;" id='plotDiv'>
<div id="topLeft">
<p style="position:absolute;top:0;left:0;"><Strong>Stuff plotly won't let me put inside the plot</Strong></p>
</div>
<div id="bottomRight">
<p style="position:absolute;bottom:0;right:0;"><Strong>More Stuff, different corner</Strong></p>
</div>
</div>
</body>
<script>
var trace0 = {
x: ["1.0", "2.0", "3.0", "4.0"],
y: ["1.0", "4.0", "2.0", "3.0"],
mode: 'lines+markers',
type: 'scatter',
name: 'My Data'
};
var data = [trace0];
Plotly.newPlot('plotDiv', data);
</script>
</html>