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

Allow users to pass vega_options #10

Merged
merged 11 commits into from
Oct 7, 2024
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ def generate_chart():

This will return a `Div` that contains your rendered altair chart.

### Custom Vega options
Under the hood, `altair2fasthtml` makes a call to the `vegaEmbed` javascript library. The `vegaEmbed` javascript library accepts [various options](https://github.com/vega/vega-embed?tab=readme-ov-file#options) that alter the rendered chart. You can optionally pass custom Vega options like so:
ddanieltan marked this conversation as resolved.
Show resolved Hide resolved

```python
# To render your chart as a svg instead of the default canvas element
altair2fasthtml(chart, vega_options={"renderer":"svg"})

# To render your chart with action links turned on (by default action links are disabled)
altair2fasthtml(chart, vega_options={"actions":True})
```

## Roadmap

This repository is originally meant to be simple helper, but if there are more advanced use-cases to consider I will gladly consider them. Please start a conversation by opening up an issue before starting a PR though.
19 changes: 12 additions & 7 deletions fh_altair/__init__.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import json
from uuid import uuid4
from fasthtml.common import Div, Script

from fasthtml.common import Div, Script

altair_headers = [
Script(src="https://cdn.jsdelivr.net/npm/vega@5"),
Script(src="https://cdn.jsdelivr.net/npm/vega-lite@5"),
Script(src="https://cdn.jsdelivr.net/npm/vega-embed@6")
Script(src="https://cdn.jsdelivr.net/npm/vega-embed@6"),
]


def altair2fasthml(chart):
"""This is the version with bad spelling"""
print("You have imported `altair2fasthml` which is a misspelled function. Sorry about that! It will be deprecated in favour of `altair2fasthtml` in a later release.")
print(
"You have imported `altair2fasthml` which is a misspelled function. Sorry about that! It will be deprecated in favour of `altair2fasthtml` in a later release."
)
return altair2fasthtml(chart)


def altair2fasthtml(chart):
def altair2fasthtml(chart, vega_options={"actions": False}):
koaning marked this conversation as resolved.
Show resolved Hide resolved
jsonstr = chart.to_json()
chart_id = f'uniq-{uuid4()}'
settings = "{actions: false}"
return Div(Script(f"vegaEmbed('#{chart_id}', {jsonstr}, {settings});"), id=chart_id)
chart_id = f"uniq-{uuid4()}"
return Div(
Script(f"vegaEmbed('#{chart_id}', {jsonstr}, {json.dumps(vega_options)});"),
id=chart_id,
)
28 changes: 25 additions & 3 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
import pandas as pd
import altair as alt
import pandas as pd
from fasthtml.common import to_xml

from fh_altair import altair2fasthtml


def test_no_err():
pltr = pd.DataFrame({'y': [1, 2, 3, 2], 'x': [3, 1, 2, 4]})
chart = alt.Chart(pltr).mark_line().encode(x='x', y='y').properties(width=400, height=200)
pltr = pd.DataFrame({"y": [1, 2, 3, 2], "x": [3, 1, 2, 4]})
chart = (
alt.Chart(pltr)
.mark_line()
.encode(x="x", y="y")
.properties(width=400, height=200)
)
return altair2fasthtml(chart)


def test_vega_options_passed_to_vegaEmbed():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit of a nit, but maybe parametrize this test so we loop over all the options?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to think about how to parameterize the 2 tests (and also asked Claude for some help) and the resulting solution felt less readable to me. I think it's because test_no_err calls altair2fasthtml() with a single argument and does not use an assert, whereas test_vega_options_passed_to_vegaEmbed calls altair2fasthtml() with multiple arguments and does use an assert.

So when trying to parameterize both of these tests into a single function, I ended up having to add what looks to be unnecessary boolean checks.

I opted instead to just put the repeated lines for generating the sample chart into a fixture so that eliminates the repetition. Let me know if you think this is an okay approach, I could be overthinking it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might advise to turn Claude off sometimes, it does give bad advise now and again.

That said, I was merely thinking along these lines:
https://github.com/koaning/fh-altair/pull/10/files#r1787928395

I agree that they are just boolean checks, but they do serve as a wide smoke test. It runs fast, but can tell us if our expectation is broken quite quickly.

It is a detail though, so I can also just do this myself if that is preferable. It is a preference that lives in my mind, but it is totally fine if not everyone has the same idea on how useful these kinds of tests are.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good advice! Thank you for the example. I think I misunderstood your original suggestion to somehow use parameterize to combine both test_no_err and test_vega_options_passed_to_vegaEmbed into a single test.

Instead it's clear to me now that you were suggesting to use parameterize to go pass all combinations of inputs solely to test_vega_options_passed_to_vegaEmbed, that's straightforward and I can make that change.

pltr = pd.DataFrame({"y": [1, 2, 3, 2], "x": [3, 1, 2, 4]})
chart = (
alt.Chart(pltr)
.mark_line()
.encode(x="x", y="y")
.properties(width=400, height=200)
)
vega_embed_call = to_xml(
altair2fasthtml(chart, vega_options={"renderer": "svg", "actions": True})
)
assert '{"renderer": "svg", "actions": true}' in vega_embed_call