0

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>

enter image description here

  • 1
    Sorry to clarify why exactly does it NEED to be invisible? – David Kroukamp Dec 08 '20 at 09:45
  • 1
    *"it is likely that it is the plotly.js JavaScript the JEditorPane is having trouble with:"* Nope. It can't have trouble with something it never supported - at all. Swing's support of HTML rendering is limited to a subset of HTML 3.2, very simple styles, and no scripts, whatsoever. The Java-FX `WebView` embeds an actual browser, though I don't know if it will render a page before it is made visible on-screen. Like @DavidKroukamp, I am curious about the need to do this without showing a page on-screen. See also [What is the XY problem?](http://meta.stackexchange.com/q/66377) – Andrew Thompson Dec 08 '20 at 10:13
  • 1
    Agreed with @AndrewThompson if this is something automated task you run locally i can't see why it can't show a window, and if it mustn't then you are using an incorrect framework you probably better off creating a CLI that integrates with chrome using the `--healdess` argument so no chrome window is produced and use its built in print/save functions or one of these options https://stackoverflow.com/a/46243263/1133011 – David Kroukamp Dec 08 '20 at 11:33
  • I run analysis software 20-30 times a day as I am doing analysis and creating the analysis software. Having 100s of windows pop-up is annoying and (in my mind) not necessary. Apparently many in the plotly community are also very annoyed with not being able to create plot images without first rendering them in a browser as well. JFreeChart and JavaFX don't have this issue. Here is an unanswered plotly question related to mine . If this were solved I might be able to use it. – Tom Schorsch Dec 09 '20 at 05:51
  • *"I run analysis software.."* Tip: Add @DavidKroukamp (or whoever, the `@` is important) to *notify* the person of a new comment. – Andrew Thompson Dec 09 '20 at 06:03
  • Thanks @AndrewThompson. Plotly users have many posts with my same "use case" but for python or R instead of Java. is one such with a post in it that ends with a frustrated "Plotly is a html / online web tool only. I will look elsewhere for use cases that require images as output." His frustration and mine is that "solutions" require downloads with complex dependencies, use a Rube-Goldberg approach, and then don't work because of new issues. I am not competent in this problem space and need a simple solution by someone who has already solved it. – Tom Schorsch Dec 10 '20 at 04:11
  • Thanks @DavidKroukamp. My use case is not an uncommon use case based on the number of requests for this capability. (including the link above which was created 8 years ago, viewed 321k times and was active as of 30 days ago. It appears to be a simple request "Render HTML to an image" but there is no easy solution. My variant is it is a complex html file with Java Script and I'd like to do it 100's of times (1000's of times a day) from Java (where I create the initial .html files). Someone has to have a simple Java solution for this!!! Right? – Tom Schorsch Dec 10 '20 at 04:58

0 Answers0