Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add browser renderer to open charts in external browser and update chart.show() to display chart #3379

Merged
merged 11 commits into from
Mar 29, 2024
75 changes: 75 additions & 0 deletions altair/utils/_show.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import webbrowser
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import Union, Iterable, Optional


def open_html_in_browser(
html: Union[str, bytes],
using: Union[str, Iterable[str], None] = None,
port: Optional[int] = None,
):
"""
Display an html document in a web browser without creating a temp file.

Instantiates a simple http server and uses the webbrowser module to
open the server's URL

Based on
jonmmease marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
html: str
HTML string to display
using: str or iterable of str
Name of the web browser to open (e.g. "chrome", "firefox", etc.).
joelostblom marked this conversation as resolved.
Show resolved Hide resolved
If an iterable, choose the first browser available on the system.
If none, choose the system default browser.
port: int
Port to use. Defaults to a random port
"""
# Encode html to bytes
if isinstance(html, str):
html_bytes = html.encode("utf8")
else:
html_bytes = html

browser = None

if using is None:
browser = webbrowser.get(None)
else:
# normalize using to an iterable
if isinstance(using, str):
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("Failed to locate a browser with name 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_bytes), bufferSize):
self.wfile.write(html_bytes[i : i + bufferSize])

def log_message(self, format, *args):
# Silence stderr logging
pass

# Use specified port if provided, otherwise choose a random port (port value of 0)
server = HTTPServer(
("127.0.0.1", port if port is not None else 0), OneShotRequestHandler
)
browser.open("http://127.0.0.1:%s" % server.server_port)
server.handle_request()
33 changes: 17 additions & 16 deletions altair/vegalite/v5/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using_vegafusion as _using_vegafusion,
compile_with_vegafusion as _compile_with_vegafusion,
)
from ...utils._show import open_html_in_browser as _open_html_in_browser
from ...utils.core import DataFrameLike
from ...utils.data import DataType

Expand Down Expand Up @@ -2678,29 +2679,29 @@ def serve(
)

def show(
self, embed_opt: Optional[dict] = None, open_browser: Optional[bool] = None
self,
embed_options: Optional[dict] = None,
using: Union[str, Iterable[str], None] = None,
port: Optional[int] = None,
) -> None:
"""Show the chart in an external browser window.
"""Show the chart in an external browser tab.

This requires a recent version of the altair_viewer package.
This requires the vl-convert-python package to be installed

Parameters
----------
embed_opt : dict (optional)
embed_options : dict (optional)
The Vega embed options that control the display of the chart.
open_browser : bool (optional)
Specify whether a browser window should be opened. If not specified,
a browser window will be opened only if the server is not already
connected to a browser.
using: str or iterable of str
Name of the web browser to open (e.g. "chrome", "firefox", etc.).
joelostblom marked this conversation as resolved.
Show resolved Hide resolved
If an iterable, choose the first browser available on the system.
If None, choose the system default browser.
port: int
Port to use. Defaults to a random port
"""
try:
import altair_viewer
except ImportError as err:
raise ValueError(
"'show' method requires the altair_viewer package. "
"See http://github.com/altair-viz/altair_viewer"
) from err
altair_viewer.show(self, embed_opt=embed_opt, open_browser=open_browser)
buffer = io.StringIO()
self.save(buffer, format="html", embed_options=embed_options, inline=True)
_open_html_in_browser(buffer.getvalue(), using=using, port=port)

@utils.use_signature(core.Resolve)
def _set_resolve(self, **kwargs):
Expand Down
Loading