Search code examples
plotly

How to force Plotly to use a certain port when rendering in a browser?


Before somebody marks this question as a duplicate, I should mention that I'm not using Plotly Dash or a Jupyter Notebook which were what all of the other SO answers I could find were pertaining to.

Using regular Plotly and a regular Python script from a terminal, in Ubuntu 20.04, when I run a script, for example this:

import numpy as np
import plotly.graph_objects as PlotlyGraphObjects

SHOW_PLOTLY_MOUSEOVERS = False

def main():

    lidar_points = np.array([[1, 2, 3],
                             [4, 5, 6],
                             [7, 8, 9]], dtype=np.float32)

    print('\n' + 'lidar_points.shape')
    print(lidar_points.shape)

    s3dPoints = PlotlyGraphObjects.Scatter3d(x=lidar_points[0], y=lidar_points[1], z=lidar_points[2], mode='markers', marker={'size': 1})

    plotlyFig = PlotlyGraphObjects.Figure(data=[s3dPoints])
    plotlyFig.update_layout(scene_aspectmode='data')

    if not SHOW_PLOTLY_MOUSEOVERS:
        plotlyFig.update_layout(hovermode=False)
        plotlyFig.update_layout(scene=dict(xaxis_showspikes=False,
                                           yaxis_showspikes=False,
                                           zaxis_showspikes=False))
    # end if

    plotlyFig.show()


# end function
if __name__ == '__main__':
    main()

Plotly opens a new tab in Chrome (my default browser) and shows the data:

enter image description here

In this particular example Plotly choose port 43001, presumably because it determined that port was available. Is there a way I can set which port Plotly uses, or at least force it to use the same port each time?

The reason I'm asking is I have to use Plotly code inside a Docker container and if I can force Plotly to use a certain port then I can open that port to the host when starting the container, but since I don't currently know how to force Plotly to use a certain port I'm unable to see the rendering. I checked the documentation for Plotly show https://plotly.com/python/renderers/ and surprisingly did not find anything pertaining to setting the port.


Solution

  • Delving into the plotly codes, I found that the server's port is set to '0', which is going to be translated to a random available number.

    https://github.com/plotly/plotly.py/blob/2b6ec1e16bebe260dfc673db869805f7ceb86e00/packages/python/plotly/plotly/io/_base_renderers.py#L705

    As the '0' is hard coded, you need to overwrite the entire function that opens browser. Run this script, e.g., in a start-up script.

    import webbrowser
    
    import plotly
    from plotly.io._base_renderers import BaseHTTPRequestHandler, HTTPServer
    
    
    def open_html_in_browser(html, using=None, new=0, autoraise=True):
        """
        Display html in a web browser without creating a temp file.
        Instantiates a trivial http server and uses the webbrowser module to
        open a URL to retrieve html from that server.
        Parameters
        ----------
        html: str
            HTML string to display
        using, new, autoraise:
            See docstrings in webbrowser.get and webbrowser.open
        """
        if isinstance(html, str):
            html = html.encode("utf8")
    
        browser = None
    
        if using is None:
            browser = webbrowser.get(None)
        else:
            if not isinstance(using, tuple):
                using = (using,)
            for browser_key in using:
                try:
                    browser = webbrowser.get(browser_key)
                    if browser is not None:
                        break
                except webbrowser.Error:
                    pass
    
            if browser is None:
                raise ValueError("Can't locate a browser with key in " + str(using))
    
        class OneShotRequestHandler(BaseHTTPRequestHandler):
            def do_GET(self):
                self.send_response(200)
                self.send_header("Content-type", "text/html")
                self.end_headers()
    
                bufferSize = 1024 * 1024
                for i in range(0, len(html), bufferSize):
                    self.wfile.write(html[i : i + bufferSize])
    
            def log_message(self, format, *args):
                # Silence stderr logging
                pass
    
        server = HTTPServer(("127.0.0.1", 54321), OneShotRequestHandler)  # fixed port number
        browser.open(
            "http://127.0.0.1:%s" % server.server_port, new=new, autoraise=autoraise
        )
    
        server.handle_request()
    
    
    # overwrite the original implementation
    plotly.io._base_renderers.open_html_in_browser = open_html_in_browser