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

docs: Adds Vega-Altair Theme Test #3630

Merged
merged 40 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
db3557d
docs: Adds `altair_theme_test.py`
dangotbanned Oct 3, 2024
bc60d23
chore: Vendor `alt.utils.html.STANDARD` template
dangotbanned Oct 4, 2024
ffc8345
refactor: Remove static conditionals in template
dangotbanned Oct 4, 2024
4b4d4fd
feat: Add theme select input
dangotbanned Oct 4, 2024
bad2bcf
chore: Add temporary `render_write` helper
dangotbanned Oct 4, 2024
f7aeef8
refactor: Move chart concat into function
dangotbanned Oct 4, 2024
0fae2ab
feat(DRAFT): Adapt more of `index.html` script
dangotbanned Oct 5, 2024
f6971de
fix: Pass theme name to correct property
dangotbanned Oct 5, 2024
d59ce76
docs: Ensure all charts have tooltips
dangotbanned Oct 5, 2024
23a2f91
docs: Adds static `vega-altair_theme_test.html`
dangotbanned Oct 5, 2024
8c6544c
ci: Temp pin `mypy`
dangotbanned Oct 5, 2024
0b07f27
revert: Remove temp `mypy` pin
dangotbanned Oct 5, 2024
3e72fe1
Merge branch 'main' into altair-theme-test
dangotbanned Oct 5, 2024
d1ea691
docs: Link to `vega-altair_theme_test.html` in `#chart-themes`
dangotbanned Oct 5, 2024
436a65d
refactor: Remove `polars` dependency
dangotbanned Oct 5, 2024
f466d8a
refactor: Move entire chart definition to `alt_theme_test`
dangotbanned Oct 5, 2024
85ba0b6
refactor: Move imports inline
dangotbanned Oct 5, 2024
1773498
feat: Adds `tools.codemod.py`
dangotbanned Oct 5, 2024
1945d2b
feat: Adds `.. altair-code-ref::` directive
dangotbanned Oct 6, 2024
8221d6c
docs: Add folding code block for `Vega-Altair Theme Test`
dangotbanned Oct 6, 2024
db14314
fix(typing): Remove unused type ignore
dangotbanned Oct 6, 2024
aa00dc1
fix(typing): Add patch for `vl-convert-python=1.7.0`
dangotbanned Oct 6, 2024
712c646
Merge remote-tracking branch 'upstream/main' into altair-theme-test
dangotbanned Oct 6, 2024
6e1ca94
refactor: Render html after `generate_api_docs`
dangotbanned Oct 6, 2024
3a5801e
style: Trim some whitespace
dangotbanned Oct 6, 2024
42491c7
Merge branch 'main' into altair-theme-test
dangotbanned Oct 12, 2024
b04dc02
Merge branch 'main' into altair-theme-test
dangotbanned Oct 13, 2024
6568cdf
refactor: Factor out `vega_datasets` dependency
dangotbanned Oct 13, 2024
85a2a4f
fix: Run `generate-schema-wrapper`
dangotbanned Oct 13, 2024
2fe68f8
fix(typing): Add type ignore
dangotbanned Oct 14, 2024
0694233
feat(DRAFT): Adds Functional `altair-pyscript` directive
dangotbanned Oct 14, 2024
6803368
revert: Remove `generate_static_docs`
dangotbanned Oct 14, 2024
99400d5
refactor: Rewrite as `altair-theme`
dangotbanned Oct 14, 2024
cbdf854
feat: Support `:summary:` in `altair-theme`
dangotbanned Oct 15, 2024
7cf627d
docs: Add warning for `ast.unparse` use
dangotbanned Oct 16, 2024
0615228
refactor: Adds `tools.codemod.Ruff`
dangotbanned Oct 16, 2024
567796e
docs: Redesign to fit User Guide template
dangotbanned Oct 17, 2024
c82ecbf
revert: Remove `THEME_TEST_TEMPLATE`, `render_theme_test`
dangotbanned Oct 17, 2024
092fe09
docs: Adds *Built-in Themes* section
dangotbanned Oct 17, 2024
1987542
revert: Remove `vega-altair_theme_test.html`
dangotbanned Oct 17, 2024
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
96 changes: 96 additions & 0 deletions doc/_static/vega-altair_theme_test.html

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion doc/user_guide/customization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -790,8 +790,13 @@ If you would like to use any theme just for a single chart, you can use the
Currently Altair does not offer many built-in themes, but we plan to add
more options in the future.

..
_comment: Keeping both (temporary) to compare. `altair` version is a lot slower.

See `Vega Theme Test`_ for an interactive demo of themes inherited from `Vega Themes`_.

See `Vega-Altair Theme Test`_ for an interactive demo of themes inherited from `Vega Themes`_.

Defining a Custom Theme
~~~~~~~~~~~~~~~~~~~~~~~
The theme registry also allows defining and registering custom themes.
Expand Down Expand Up @@ -890,4 +895,5 @@ The configured localization settings persist upon saving.

.. _Vega Themes: https://github.com/vega/vega-themes/
.. _`D3's localization support`: https://d3-wiki.readthedocs.io/zh-cn/master/Localization/
.. _Vega Theme Test: https://vega.github.io/vega-themes/?renderer=canvas
.. _Vega Theme Test: https://vega.github.io/vega-themes/?renderer=canvas
.. _Vega-Altair Theme Test: ../_static/vega-altair_theme_test.html
dangotbanned marked this conversation as resolved.
Show resolved Hide resolved
276 changes: 276 additions & 0 deletions tests/altair_theme_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
from __future__ import annotations

import json
from pathlib import Path

import jinja2
import polars as pl

import altair as alt
from vega_datasets import data

common_data = pl.DataFrame(
[
{"Index": 1, "Value": 28, "Position": 1, "Category": "A"},
{"Index": 2, "Value": 55, "Position": 2, "Category": "A"},
{"Index": 3, "Value": 43, "Position": 3, "Category": "A"},
{"Index": 4, "Value": 91, "Position": 4, "Category": "A"},
{"Index": 5, "Value": 81, "Position": 5, "Category": "A"},
dangotbanned marked this conversation as resolved.
Show resolved Hide resolved
{"Index": 6, "Value": 53, "Position": 6, "Category": "A"},
{"Index": 7, "Value": 19, "Position": 1, "Category": "B"},
{"Index": 8, "Value": 87, "Position": 2, "Category": "B"},
{"Index": 9, "Value": 52, "Position": 3, "Category": "B"},
{"Index": 10, "Value": 48, "Position": 4, "Category": "B"},
{"Index": 11, "Value": 24, "Position": 5, "Category": "B"},
{"Index": 12, "Value": 49, "Position": 6, "Category": "B"},
{"Index": 13, "Value": 87, "Position": 1, "Category": "C"},
{"Index": 14, "Value": 66, "Position": 2, "Category": "C"},
{"Index": 15, "Value": 17, "Position": 3, "Category": "C"},
{"Index": 16, "Value": 27, "Position": 4, "Category": "C"},
{"Index": 17, "Value": 68, "Position": 5, "Category": "C"},
{"Index": 18, "Value": 16, "Position": 6, "Category": "C"},
]
)


bar = (
alt.Chart(common_data, title="Bar", width=480, height=150)
.mark_bar()
.encode(x=alt.X("Index:O").axis(offset=1), y=alt.Y("Value:Q"), tooltip="Value:Q")
)

line = (
alt.Chart(common_data, width=240, height=150, title="Line")
dangotbanned marked this conversation as resolved.
Show resolved Hide resolved
.mark_line()
.encode(
x=alt.X("Position").axis(grid=False),
y=alt.Y("Value:Q").axis(grid=False),
color=alt.Color("Category").legend(None),
tooltip=["Index", "Value", "Position", "Category"],
)
)

point_shape = (
alt.Chart(common_data, width=200, height=200, title="Point (Shape)")
.mark_point()
.encode(
x=alt.X("Position:O").axis(grid=False),
y=alt.Y("Value:Q").axis(grid=False),
shape=alt.Shape("Category").legend(None),
color=alt.Color("Category").legend(None),
tooltip=["Index", "Value", "Position", "Category"],
)
)

bar_facet = (
alt.Chart(data.barley(), width=220, title=alt.Title("Bar (Facet)", anchor="middle"))
.mark_bar(tooltip=True)
.encode(column="year:O", x="yield", y="variety", color="site")
)

rect_heatmap = (
alt.Chart(
data.seattle_weather(),
title=alt.Title(
"Rect (Heatmap)", subtitle="Daily Max Temperatures (C) in Seattle, WA"
),
height=200,
)
.mark_rect()
.encode(
x=alt.X("date(date):O").title("Day").axis(format="%e", labelAngle=0),
y=alt.Y("month(date):O").title("Month"),
color=alt.Color("max(temp_max)").title(None),
tooltip=[
alt.Tooltip("monthdate(date)", title="Date"),
alt.Tooltip("max(temp_max)", title="Max Temp"),
],
)
)

geoshape = (
alt.Chart(
alt.topo_feature(data.us_10m.url, "counties"),
title=alt.Title("Geoshape", subtitle="Unemployment rate per county"),
width=500,
height=300,
)
.mark_geoshape(tooltip=True)
.encode(color="rate:Q")
.transform_lookup(
lookup="id",
from_=alt.LookupData(data.unemployment.url, "id", ["rate"]), # pyright: ignore[reportArgumentType]
)
.project(type="albersUsa")
)

point = (
alt.Chart(data.movies.url, height=250, width=250, title="Point")
.mark_point(tooltip=True)
.transform_filter(alt.datum["IMDB_Rating"] != None) # noqa: E711
.transform_filter(
alt.FieldRangePredicate("Release_Date", [None, 2019], timeUnit="year")
)
.transform_joinaggregate(Average_Rating="mean(IMDB_Rating)")
.transform_calculate(
Rating_Delta=alt.datum["IMDB_Rating"] - alt.datum.Average_Rating
)
.encode(
x=alt.X("Release_Date:T").title("Release Date"),
y=alt.Y("Rating_Delta:Q").title("Rating Delta"),
color=alt.Color("Rating_Delta:Q").title("Rating Delta").scale(domainMid=0),
)
)

area = (
alt.Chart(data.iowa_electricity(), title="Area", height=250, width=250)
.mark_area(tooltip=True)
.encode(
alt.X("year:T").title("Year"),
alt.Y("net_generation:Q")
.title("Share of net generation")
.stack("normalize")
.axis(format=".0%"),
alt.Color("source:N").title("Electricity source"),
)
)


def alt_theme_test() -> alt.VConcatChart:
return (
(bar | line)
& (point_shape | bar_facet).resolve_scale(color="independent")
& (point | area)
& rect_heatmap
& geoshape
).properties(
title=alt.Title(
"Vega-Altair Theme Test",
fontSize=20,
subtitle="Adapted from https://vega.github.io/vega-themes/",
offset=16,
)
)


TEMPLATE = jinja2.Template(
"""\
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
#header {
padding: 10px 0 20px;
height: 20px;
}
#{{ output_div }}.vega-embed {
width: 100%;
display: flex;
}
#{{ output_div }}.vega-embed details,
#{{ output_div }}.vega-embed details summary {
position: relative;
}
</style>
<script type="text/javascript" src="{{ base_url }}/vega@{{ vega_version }}"></script>
<script type="text/javascript" src="{{ base_url }}/vega-lite@{{ vegalite_version }}"></script>
<script type="text/javascript" src="{{ base_url }}/vega-embed@{{ vegaembed_version }}"></script>
</head>
<body>
<div id="header">
Theme:
<select id="themes">
<option value="default">default</option>
<option value="excel">excel</option>
<option value="ggplot2">ggplot2</option>
<option value="quartz">quartz</option>
<option value="vox">vox</option>
<option value="dark">dark</option>
<option value="fivethirtyeight">fivethirtyeight</option>
<option value="latimes">latimes</option>
<option value="urbaninstitute">urbaninstitute</option>
<option value="googlecharts">googlecharts</option>
<option value="powerbi">powerbi</option>
<optgroup label="Carbon">
<option value="carbonwhite">carbonwhite</option>
<option value="carbong10">carbong10</option>
<option value="carbong90">carbong90</option>
<option value="carbong100">carbong100</option>
</optgroup>
</select>
<br />
</div>
<div id="{{ output_div }}"></div>
<script>
// Vega Theme Test - But single view, no renderer option
var spec = {{ spec }};
var container = document.querySelector("#{{ output_div }}");
var themes = document.querySelector("#themes");
var currentLocation = window.location;
var url = new URL(currentLocation);

themes.addEventListener("change", function () {
theme = themes.options[themes.selectedIndex].value;
url.searchParams.set("theme", `${themes.options[themes.selectedIndex].value}`);
window.history.replaceState(null, null, url);
refresh();
});

function refresh() {
const themeName = themes.options[themes.selectedIndex].value;
container.innerHTML = "";
var el = document.createElement("div");
el.setAttribute("class", "view");
container.appendChild(el);
vegaEmbed(el, spec, {
theme: themeName,
defaultStyle: true,
mode: "vega-lite"
});
}

// Mostly the standard template
// - Need to adapt these two ideas into one
(function(vegaEmbed) {
var embedOpt = { mode: "vega-lite" };

function showError(el, error){
el.innerHTML = ('<div style="color:red;">'
+ '<p>JavaScript Error: ' + error.message + '</p>'
+ "<p>This usually means there's a typo in your chart specification. "
+ "See the javascript console for the full traceback.</p>"
+ '</div>');
throw error;
}
const el = document.getElementById('{{ output_div }}');
vegaEmbed("#{{ output_div }}", spec, embedOpt)
.catch(error => showError(el, error));
})(vegaEmbed);

</script>
dangotbanned marked this conversation as resolved.
Show resolved Hide resolved
</body>
</html>
"""
)


def render_write(fp: str | Path, /) -> None:
"""
Debug tool, effectively `alt.Chart.save` w/ template.

## Remove before review
"""
content = TEMPLATE.render(
spec=json.dumps(alt_theme_test().to_dict(context={"pre_transform": False})),
vegalite_version=alt.VEGALITE_VERSION,
vegaembed_version=alt.VEGAEMBED_VERSION,
vega_version=alt.VEGA_VERSION,
base_url="https://cdn.jsdelivr.net/npm",
output_div="vis",
)
if isinstance(fp, (str, Path)):
with Path(fp).open(mode="w", encoding="utf-8") as f:
f.write(content)
else:
raise TypeError(fp)
dangotbanned marked this conversation as resolved.
Show resolved Hide resolved