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

Add mimetype renderer #195

Merged
merged 27 commits into from
Dec 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
11cedc4
Rename vegafusion-jupyter renderer to vegafusion-widget
jonmmease Dec 22, 2022
6c1e6dc
Add initial vegafusion-inline transformer and vegafusion-mime renderer
jonmmease Dec 22, 2022
c2f5fdd
Move mime renderer to vegafusion package
jonmmease Dec 23, 2022
c4f0ae4
Add local timezone configuration
jonmmease Dec 23, 2022
a0a3f6d
Add eval_transforms function that returns the result of evaluating th…
jonmmease Dec 23, 2022
18c4be3
Don't use editable install in build:dev to get nbextension working fo…
jonmmease Dec 24, 2022
6f6be5e
Add row_limit to pre_transform_values
jonmmease Dec 24, 2022
b6c4ed3
Add html, svg, and png render mime types
jonmmease Dec 24, 2022
a78c3b7
Test html mimetype
jonmmease Dec 24, 2022
0733c3f
Add transformed_dtypes
jonmmease Dec 25, 2022
7ba866d
Initial support for row/column encoding facets
jonmmease Dec 25, 2022
b7e11d1
Lookup Altair's vegalite version dynamically
jonmmease Dec 25, 2022
646061e
Thread embed options through to mime renderer
jonmmease Dec 26, 2022
feaf8f4
Merge embed options and fix bundle embed metadata
jonmmease Dec 26, 2022
8cd7f57
work around to avoid white background in dark theme
jonmmease Dec 26, 2022
8c4abd9
Support custom vegalite-to-vega compiler plugins
jonmmease Dec 26, 2022
2929133
Add better error message in local_tz if vl-convert is not available
jonmmease Dec 26, 2022
ba3a089
Remove evaluation (this is independent of mime renderer and should be…
jonmmease Dec 26, 2022
76964d4
Install vl-convert-python in CI
jonmmease Dec 26, 2022
860792b
Derive Debug for Plan
jonmmease Dec 27, 2022
9b0bb4d
Never bring _store datasets over to server
jonmmease Dec 27, 2022
95aec40
Revert "Never bring _store datasets over to server"
jonmmease Dec 27, 2022
ebad4f8
For selection stores with inline data, keep values on both client and…
jonmmease Dec 27, 2022
16a7d53
Add test case
jonmmease Dec 27, 2022
740c902
Fix "dispatch dropped without returning error" test error
jonmmease Dec 27, 2022
06d1d8d
Check client-only-vars in determining if signal is supported
jonmmease Dec 27, 2022
4297afb
fmt
jonmmease Dec 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ jobs:
python -m pip install vegafusion_python*${{ matrix.options[4] }}*${{ matrix.options[3] }}*.whl
python -m pip install vegafusion-*.whl
python -m pip install vegafusion_jupyter*.whl
python -m pip install vl-convert-python
- name: Test vegafusion
working-directory: python/vegafusion/
run: pytest
Expand Down
2 changes: 1 addition & 1 deletion python/vegafusion-jupyter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"url": "https://github.com/jonmmease/vegafusion"
},
"scripts": {
"build:dev": "npm run build:lib && npm run build:nbextension && npm run build:labextension:dev && pip install --force-reinstall --no-deps -e .",
"build:dev": "npm run build:lib && npm run build:nbextension && npm run build:labextension:dev && pip install --force-reinstall --no-deps .",
"build:prod": "npm run clean && npm run build:lib && npm run build:nbextension && npm run build:labextension",
"build:labextension": "jupyter labextension build .",
"build:labextension:dev": "jupyter labextension build --development True .",
Expand Down
52 changes: 41 additions & 11 deletions python/vegafusion-jupyter/tests/test_altair_mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,28 +46,28 @@
```
"""

vegafusion_feather_markdown_template = r"""
vegafusion_widget_feather_markdown_template = r"""
```python
import altair as alt
import vegafusion_jupyter as vf
vf.enable()
import vegafusion as vf
vf.enable_widget()
```

```python
{code}
```

```python
assert(alt.renderers.active == "vegafusion-jupyter")
assert(alt.renderers.active == "vegafusion-widget")
assert(alt.data_transformers.active == 'vegafusion-feather')
```
"""

vegafusion_default_markdown_template = r"""
vegafusion_widget_default_markdown_template = r"""
```python
import altair as alt
import vegafusion_jupyter as vf
vf.enable()
import vegafusion as vf
vf.enable_widget()
alt.data_transformers.enable("default");
```

Expand All @@ -76,11 +76,27 @@
```

```python
assert(alt.renderers.active == "vegafusion-jupyter")
assert(alt.renderers.active == "vegafusion-widget")
assert(alt.data_transformers.active == 'default')
```
"""

vegafusion_mime_markdown_template = r"""
```python
import altair as alt
import vegafusion as vf
vf.enable_mime(mimetype="html", embed_options={'actions': False})
```

```python
{code}
```

```python
assert(alt.renderers.active == "vegafusion-mime")
assert(alt.data_transformers.active == 'vegafusion-inline')
```
"""

def setup_module(module):
""" setup any state specific to the execution of the given module."""
Expand Down Expand Up @@ -265,13 +281,15 @@ def test_altair_mock(mock_name, img_tolerance, delay):

mock_code = mock_path.read_text()
altair_markdown = altair_markdown_template.replace("{code}", mock_code)
vegafusion_arrow_markdown = vegafusion_feather_markdown_template.replace("{code}", mock_code)
vegafusion_default_markdown = vegafusion_default_markdown_template.replace("{code}", mock_code)
vegafusion_arrow_markdown = vegafusion_widget_feather_markdown_template.replace("{code}", mock_code)
vegafusion_default_markdown = vegafusion_widget_default_markdown_template.replace("{code}", mock_code)
vegafusion_mime_markdown = vegafusion_mime_markdown_template.replace("{code}", mock_code)

# Use jupytext to convert markdown to an ipynb file
altair_notebook = jupytext.read(io.StringIO(altair_markdown), fmt="markdown")
vegafusion_arrow_notebook = jupytext.read(io.StringIO(vegafusion_arrow_markdown), fmt="markdown")
vegafusion_default_notebook = jupytext.read(io.StringIO(vegafusion_default_markdown), fmt="markdown")
vegafusion_mime_notebook = jupytext.read(io.StringIO(vegafusion_mime_markdown), fmt="markdown")

# Create selenium Chrome instance
chrome_opts = webdriver.ChromeOptions()
Expand All @@ -296,26 +314,38 @@ def test_altair_mock(mock_name, img_tolerance, delay):
chrome_driver, vegafusion_arrow_notebook, name + "_vegafusion_feather", actions, delay
)
vegafusion_default_imgs = export_image_sequence(
chrome_driver, vegafusion_default_notebook, name + "_vegafusion", actions, delay
chrome_driver, vegafusion_default_notebook, name + "_vegafusion_widget", actions, delay
)
vegafusion_mime_imgs = export_image_sequence(
chrome_driver, vegafusion_mime_notebook, name + "_vegafusion_mime", actions, delay
)

for i in range(len(altair_imgs)):
altair_img = altair_imgs[i]
vegafusion_arrow_img = vegafusion_arrow_imgs[i]
vegafusion_default_img = vegafusion_default_imgs[i]
vegafusion_mime_img = vegafusion_mime_imgs[i]

assert altair_img.shape == vegafusion_arrow_img.shape, "Size mismatch with Arrow data transformer"
assert altair_img.shape == vegafusion_default_img.shape, "Size mismatch with default data transformer"
assert altair_img.shape == vegafusion_mime_img.shape, "Size mismatch with mime renderer"

similarity_arrow_value = ssim(altair_img, vegafusion_arrow_img, channel_axis=2)
similarity_default_value = ssim(altair_img, vegafusion_default_img, channel_axis=2)
similarity_mime_value = ssim(altair_img, vegafusion_mime_img, channel_axis=2)

print(f"({i}) similarity_arrow_value={similarity_arrow_value}")
print(f"({i}) similarity_default_value={similarity_default_value}")
print(f"({i}) similarity_mime_value={similarity_mime_value}")

assert similarity_arrow_value >= img_tolerance, f"Similarity failed with Arrow data transformer on image {i}"
assert similarity_default_value >= img_tolerance, f"Similarity failed with default data transformer on image {i}"

# Allow slightly more image tolerance for mime renderer as floating point differences may
# be introduced by pre-transform process
mime_image_tolerance = img_tolerance * 0.99
assert similarity_mime_value >= mime_image_tolerance, f"Similarity failed with mime renderer on image {i}"

finally:
voila_proc.kill()
chrome_driver.close()
Expand Down
9 changes: 5 additions & 4 deletions python/vegafusion-jupyter/vegafusion_jupyter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .widget import VegaFusionWidget
from ._version import __version__


def enable(
download_source_link=None,
debounce_wait=30,
Expand All @@ -27,7 +28,7 @@ def enable(
# will be registered
import vegafusion.transformer
alt.renderers.enable(
'vegafusion-jupyter',
'vegafusion-widget',
debounce_wait=debounce_wait,
debounce_max_wait=debounce_max_wait,
download_source_link=download_source_link,
Expand All @@ -39,7 +40,7 @@ def enable(

def disable():
"""
Disable the VegaFusion data transformer and renderer so that Charts
Disable the VegaFusion data transformers and renderers so that Charts
are not displayed using VegaFusion

Equivalent to
Expand All @@ -52,8 +53,8 @@ def disable():

This does not affect the behavior of VegaFusionWidget
"""
alt.renderers.enable('default')
alt.data_transformers.enable('default')
from vegafusion import disable
disable()


def _jupyter_labextension_paths():
Expand Down
3 changes: 1 addition & 2 deletions python/vegafusion-jupyter/vegafusion_jupyter/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,4 @@ def vegafusion_renderer(spec, **widget_options):
display(widget)
return {'text/plain': ""}


alt.renderers.register('vegafusion-jupyter', vegafusion_renderer)
alt.renderers.register('vegafusion-widget', vegafusion_renderer)
4 changes: 2 additions & 2 deletions python/vegafusion-jupyter/vegafusion_jupyter/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __init__(self, *args, **kwargs):
else:
data_transformer_opts = dict()

with alt.renderers.enable("vegafusion-jupyter"):
with alt.renderers.enable("vegafusion-widget"):
with alt.data_transformers.enable("vegafusion-feather", **data_transformer_opts):
# Temporarily enable the vegafusion renderer and transformer so
# that we use them even if they are not enabled globally
Expand All @@ -73,7 +73,7 @@ def __init__(self, *args, **kwargs):
kwargs["spec"] = json.dumps(kwargs["spec"], indent=2)

# If vegafusion renderer is already enabled, use the configured debounce options as the default
if alt.renderers.active == "vegafusion-jupyter":
if alt.renderers.active == "vegafusion-widget":
# Use configured debounce options, if any
renderer_opts = alt.renderers.options
for opt in ["debounce_wait", "debounce_max_wait", "download_source_link"]:
Expand Down
84 changes: 81 additions & 3 deletions python/vegafusion/vegafusion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
# Please consult the license documentation provided alongside
# this program the details of the active license.
from .runtime import runtime
from .transformer import to_feather
from ._version import __version__
from .transformer import to_feather, get_inline_datasets_for_spec
from .local_tz import set_local_tz, get_local_tz
from . import renderer
from .compilers import vegalite_compilers
import altair as alt

# Import subpackages
from ._version import __version__
# Import optional subpackages
try:
import vegafusion.jupyter
except ImportError:
Expand All @@ -18,3 +22,77 @@
import vegafusion.embed
except ImportError:
pass


def altair_vl_version(vl_convert=False):
"""
Get Altair's preferred Vega-Lite version

:param vl_convert: If True, return a version string compatible with vl_convert
(e.g. v4_17 rather than 4.17.0)
:return: str with Vega-Lite version
"""
from altair.vegalite.v4 import SCHEMA_VERSION
if vl_convert:
# Compute VlConvert's vl_version string (of the form 'v5_2')
# from SCHEMA_VERSION (of the form 'v5.2.0')
return "_".join(SCHEMA_VERSION.split(".")[:2])
else:
# Return full version without leading v
return SCHEMA_VERSION.rstrip("v")


def enable_mime(mimetype="html", embed_options=None):
"""
Enable the VegaFusion data transformer and renderer so that all Charts
are displayed using VegaFusion.

This isn't necessary in order to use the VegaFusionWidget directly

:param mimetype: Mime type. One of:
- "html" (default)
- "vega"
- "svg"
- "png": Note: the PNG renderer can be quite slow for charts with lots of marks
:param embed_options: dict (optional)
Dictionary of options to pass to the vega-embed. Default
entry is {'mode': 'vega'}.
"""
# Import vegafusion.transformer so that vegafusion-inline transform
# will be registered
alt.renderers.enable('vegafusion-mime', mimetype=mimetype, embed_options=embed_options)
alt.data_transformers.enable('vegafusion-inline')


def enable_widget(
download_source_link=None,
debounce_wait=30,
debounce_max_wait=60,
data_dir="_vegafusion_data"
):
from vegafusion.jupyter import enable
enable(
download_source_link=download_source_link,
debounce_wait=debounce_wait,
debounce_max_wait=debounce_max_wait,
data_dir=data_dir
)


def disable():
"""
Disable the VegaFusion data transformers and renderers so that Charts
are not displayed using VegaFusion

Equivalent to

```python
import altair as alt
alt.renderers.enable('default')
alt.data_transformers.enable('default')
```

This does not affect the behavior of VegaFusionWidget
"""
alt.renderers.enable('default')
alt.data_transformers.enable('default')
29 changes: 29 additions & 0 deletions python/vegafusion/vegafusion/compilers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from altair.utils.plugin_registry import PluginRegistry
from typing import Callable


VegaLiteCompilerType = Callable[..., dict]


class VegaLiteCompilerRegistry(PluginRegistry[VegaLiteCompilerType]):
pass


vegalite_compilers = VegaLiteCompilerRegistry()


def vl_convert_compiler(vegalite_spec) -> dict:
try:
import vl_convert as vlc
except ImportError:
raise ImportError(
"The vl-convert Vega-Lite compiler requires the vl-convert-python package"
)

from . import altair_vl_version
vega_spec = vlc.vegalite_to_vega(vegalite_spec, vl_version=altair_vl_version(vl_convert=True))
return vega_spec


vegalite_compilers.register("vl-convert", vl_convert_compiler)
vegalite_compilers.enable("vl-convert")
41 changes: 41 additions & 0 deletions python/vegafusion/vegafusion/local_tz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
__tz_config = dict(local_tz=None)


def get_local_tz():
"""
Get the named local timezone that the VegaFusion mimetype renderer
will use for calculations.

Defaults to the kernel's local timezone as determined by vl-convert.

Has no effect on VegaFusionWidget, which always uses the
browser's local timezone

:return: named timezone string
"""
if __tz_config["local_tz"] is None:
# Fall back to getting local_tz from vl-convert if not set
try:
import vl_convert as vlc
__tz_config["local_tz"] = vlc.get_local_tz()
except ImportError:
raise ImportError(
"vl-convert is not installed and so the local system timezone cannot be determined.\n"
"Either install the vl-convert-python package or set the local timezone manually using\n"
"the vegafusion.set_local_tz function"
)

return __tz_config["local_tz"]


def set_local_tz(local_tz):
"""
Set the named local timezone that the VegaFusion mimetype renderer
will use for calculations.

Has no effect on VegaFusionWidget, which always uses the
browser's local timezone

:param local_tz: Named local timezone (e.g. "America/New_York")
"""
__tz_config["local_tz"] = local_tz
Loading