-
Notifications
You must be signed in to change notification settings - Fork 794
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
Improving Themes #3519
Comments
Maybe useful, maybe not, but themes are also known within vl-convert: import vl_convert as vlc
list(vlc.get_themes().keys())
|
Thanks @mattijn, that seems to end up at https://github.com/vega/vl-convert/blob/89bbda91334494fbcf29144b4c1fe7b6ed18168e/vl-convert-python/src/lib.rs#L1028-L1044 If I've understood this correctly (maybe @jonmmease can correct), this seems like the equivalent of asking This would be a pretty simple solve for keeping the |
Related to improving the experience of modifying chart styles is making it easier to modify common elements in existing themes, e.g. setting a larger default font size as tracked in this issue #2820 and the various comments linked there. It's a bit outside the scope of what you are suggesting here @dangotbanned but I wanted to link it so that you were aware of some of the previous discussion we've had on similar topics. |
Hi @dangotbanned, yes vl-convert vendors the vega-themes JavaScript library (which vega-lite pulls themes from), and extracts the theme definitions from there. I'm in the process of updating it to use the new vega-themes release that includes the carbon themes in vega/vl-convert#178. I think depending on vl-convert during My motivation for adding this info to vl-convert was so that we could eventually let Altair users inspect the definitions of these themes to use them as starting points for their own themes. I haven't thought it through in detail, but I wonder if we should inline the full theme definitions during |
Thanks for the explanation @jonmmease! That wasn't what I thought was happening, glad you cleared it up
I definitely agree with the end goal, but had an alternative way to get there. Would it be in scope for https://github.com/vega/vega-themes/ to output (during CI) a
Looking at the frequency of Simply reading the latest altair/tools/generate_schema_wrapper.py Line 39 in 95f654f
@jonmmease do you think this would be a reasonable ask? |
Are you picturing that this But if it's only at code generation time, then that seems perfectly reasonable, though I don't know if there are a lot of benefits over using vl-convert in that case. |
Apologies for being unclear, yeah I meant updated at on each
That's fair, you certainly know more than I do about the ease of coordinating between the two projects.
If you can figure this out with EditJust finished reading through vega/vl-convert#39 and feel dumb for not realising Please disregard all of #3519 (comment) |
Resolves one item in vega#3519
Resolves the *long-term* solution mentioned in vega#3519 (comment)
Resolves 1 item in vega#3519
* feat: Adds `vega-themes.json` using `vl_convert` #3519 (comment) * Update vega-themes.json * fix: Force LF * fix: Use `sort_keys` for deterministic `vega-themes.json` * build: run `generate-schema-wrapper` * ci: Introduce `vl_convert` dependency to GH `Test that schema generation has no effect` #3519 (comment) https://github.com/vega/altair/actions/runs/10263632193/job/28395892206?pr=3523 * ci: Output diff when schema generation has an effect Not sure of the right command here, as I can't repro locally * ci: Change gitattributes to always LF * fix: re-run with fresh env #3523 (comment) * feat(typing): Generate `VegaThemes` alias Resolves the *long-term* solution mentioned in #3519 (comment)
@binste I briefly mentioned in #3523 (comment) that this might take a while for me to do. Thought I'd add some notes here, prior to a PR, in case you (or anyone else) were more familiar with NotesPrior ArtSo far, these seem like the relevant starting points/outputs: PRCode
GitHub doesn't want to show the preview, but the link will still highlight inline.
|
def generate_vegalite_config_mixin(schemafile: Path) -> tuple[list[str], str]: | |
imports = [ | |
"from . import core", | |
"from altair.utils import use_signature", | |
] | |
class_name = "ConfigMethodMixin" | |
code = [ | |
f"class {class_name}:", | |
' """A mixin class that defines config methods"""', | |
] | |
with schemafile.open(encoding="utf8") as f: | |
schema = json.load(f) | |
info = SchemaInfo({"$ref": "#/definitions/Config"}, rootschema=schema) | |
# configure() method | |
method = CONFIG_METHOD.format(classname="Config", method="configure") | |
code.append("\n ".join(method.splitlines())) | |
# configure_prop() methods | |
for prop, prop_info in info.properties.items(): | |
classname = prop_info.refname | |
if classname and classname.endswith("Config"): | |
method = CONFIG_PROP_METHOD.format(classname=classname, prop=prop) | |
code.append("\n ".join(method.splitlines())) | |
return imports, "\n".join(code) |
generate_vegalite_mark_mixin
Relevant since this deals with typing, whereas the config equivalent does not.
altair/tools/generate_schema_wrapper.py
Lines 704 to 763 in f0c1e0a
def generate_vegalite_mark_mixin( | |
schemafile: Path, markdefs: dict[str, str] | |
) -> tuple[list[str], str]: | |
with schemafile.open(encoding="utf8") as f: | |
schema = json.load(f) | |
class_name = "MarkMethodMixin" | |
imports = [ | |
"from typing import Any, Sequence, List, Literal, Union", | |
"", | |
"from altair.utils.schemapi import Undefined, UndefinedType", | |
"from . import core", | |
] | |
code = [ | |
f"class {class_name}:", | |
' """A mixin class that defines mark methods"""', | |
] | |
for mark_enum, mark_def in markdefs.items(): | |
if "enum" in schema["definitions"][mark_enum]: | |
marks = schema["definitions"][mark_enum]["enum"] | |
else: | |
marks = [schema["definitions"][mark_enum]["const"]] | |
info = SchemaInfo({"$ref": f"#/definitions/{mark_def}"}, rootschema=schema) | |
# adapted from SchemaInfo.init_code | |
arg_info = codegen.get_args(info) | |
arg_info.required -= {"type"} | |
arg_info.kwds -= {"type"} | |
def_args = ["self"] + [ | |
f"{p}: " | |
+ info.properties[p].get_python_type_representation( | |
for_type_hints=True, | |
additional_type_hints=["UndefinedType"], | |
) | |
+ " = Undefined" | |
for p in (sorted(arg_info.required) + sorted(arg_info.kwds)) | |
] | |
dict_args = [ | |
f"{p}={p}" for p in (sorted(arg_info.required) + sorted(arg_info.kwds)) | |
] | |
if arg_info.additional or arg_info.invalid_kwds: | |
def_args.append("**kwds") | |
dict_args.append("**kwds") | |
for mark in marks: | |
# TODO: only include args relevant to given type? | |
mark_method = MARK_METHOD.format( | |
mark=mark, | |
mark_def=mark_def, | |
def_arglist=", ".join(def_args), | |
dict_arglist=", ".join(dict_args), | |
) | |
code.append("\n ".join(mark_method.splitlines())) | |
return imports, "\n".join(code) |
Related
While reading through these, core.Config
seemed like an example where we could be more specific in the type annotations - rather than SchemaBase
.
Not sure how common the scenario would be, but writing some logic to use the actual type when:
- There is only 1 class listed
len(SchemaInfo.title) <= some_limit
- E.g avoid long generic definitions
For cases like the above, it would mean a user wouldn't need to refer to the docs as often - if their IDE/etc can utilise the annotations
It's exciting to see this 🚀 I'll try to contribute through comments soon. For now, just some drive-by thoughts in case you find them useful: How could the bigger picture UX look like for themes?
Writing this out, we should have a solution which allows for the full flexibility (dict/typed dict) and I do see the appeal of a light convenience function/class to get started with a new theme. Inspiration
|
@binste I will try to go through #3519 (comment) in detail tomorrow, but wanted to say really appreciate all the thought you've put in to the UX! Just finished the most significant part of #3536 with
Sounds interesting! I'll say I fully didn't anticipate the level of customization you can do with a theme. |
@binste I think the key missing component of theme documentation is demonstrating patterns like in https://github.com/dangotbanned/altair/blob/c35324d5cd15bf04fbc47efa611837eefd20e138/altair/vegalite/v5/theme.py#L77-L133 This wouldn't require any additional functions/classes and would be "teaching" theme generation in a way that closely mirrored
Looking at seaborn.set_theme, this is pretty much the same idea but with 1 of 4 scale constants applied in sns.rcmod.plotting_context |
* feat: Adds `@register_theme` decorator Resolves one item in #3519 * build: run `update-init-file` Adds `@register_theme` to top-level * test: Adds `test_register_theme_decorator` * refactor(typing): Specify `dict[str, Any]` instead of `dict[Any, Any]` The latter may give false-positives for json-incompatible dicts --------- Co-authored-by: Stefan Binder <[email protected]>
Moved to #3586Make it easier for downstream libraries to safely contribute themesOriginally posted by @dangotbanned in discussion w/ @MarcoGorelliAs I understand, the `alt.Chart.configure_` calls are being used to avoid registering + enabling a theme - which could override a user's custom theme. These work fine in isolation, but AFAIK would have issues if a user were to layer/concat/facet the result - since You might want to add tests to see if these ops would still be possible
Using a theme would have the benefit of deferring these config settings until the It might be worth seeing if we can come to a good solution to this as part of #3519 since we have already discussed issues with the theme route ProblemA library like AFAIK, the "best" solution for this right now would be to override our Code blockaltair/altair/vegalite/v5/theme.py Lines 56 to 74 in df14929
Solution(s)We could extend Either when registering/enabling a theme a level will be set corresponding to the party. from enum import IntEnum
class ThemePriority(IntEnum):
USER = 1
THIRD_PARTY = 2
DEFAULT = 3 # alternatives: `ALTAIR`, `STANDARD`, `BUILTIN` For backwards-compatibility, this must default to The semantics of which theme should be enabled for The highest priority (lowest-valued) enabled theme is selected:
The basic resolution implementation for However, I think this behavior itself should be pluggable - to support alternative resolution semantics like:
Related |
@joelostblom what do you think about doing something like #2593 but with a reduced scope to recreate
|
I think that could be interesting, do you mean with a full interactive cell so that readers could interact with individual options in the theme? Rather than just a dropdown to select between existing themes like in the Vega Themes pages? I think this is already possible via replite . Long term, I think it would be neat if all code cells would be optionally interactive. Something like what the Panel docs have where you can click a button to execute the content in the cell (but in our case it should also be editable). Reflecting on @binste 's config above, I would be in favor for increasing the font sizes etc of the default altair theme, given that we are already having the charts being 300x300 instead of the vega default of 200x200 and it would make sense if fonts etc were a bit bigger then too. Maybe a v6 feature. |
@joelostblom I haven't dived into the various
To me these seem like some nice constraints to work with, and would definitely be easier to get merged than #2593. EditSee clarification in #3519 (comment) |
If we were to do a one to one copy, wouldn't it be easier to contribute the new themes upstream so that they are included in the already existing Vega page? Maybe that is the easiest implementation to get started, and a later follow up could be to create the interactive cells I mentioned (I would also be ok to go the interactive way from the start, but I am personally a bit low on time right now to explore if what I suggested is the most suitable option here) |
Apologies @joelostblom , what I wrote and what I was thinking about didn't really line up 😅 By 1:1 I meant creating the charts and arranging them using
Personally, this would've saved me a lot of time trying to debug edge cases where something that worked well for one spec - didn't translate as well for another. We could always try that approach if you'd see any value in it - but I'm still happy to wait for a more interactive experience |
Ah I see, yes I think that could be helpful, especially the second part about people creating themes being able to quickly try them out with multiple charts! |
Important
At the beginning of this effort
alt.themes
referred to aThemeRegistry
object.Following (#3610, #3618), the new interface (
alt.theme
) is a module.See (#3618 (comment)) for a short description of the deprecation process and the general versioning policy.
What is your suggestion?
Following discussion w/ @binste in
carbon
themes, provide autocomplete #3516 (comment)Original comment relating to autocomplete
For a long-term solution, I'd want to:
VegaThemes: Literal[...]
)altair
themes as (AltairThemes: Literal["opaque", ...
)->_ThemeName
DefaultThemes: TypeAlias = AltairThemes | VegaThemes
themes.enable(name: DefaultThemes)
theme.py
modules, as part of Flatten the package structure #3337The larger goal would be making the process of creating/choosing a theme as easy as possible.
After linking to the existing docs in pola-rs/polars#17995 (comment), I thought we could improve both the docs and the UX itself.
Ideas
Feat
ThemeConfig
(TypedDict
) #3536@register_theme
decorator #3526vega-themes.json
usingvl_convert
#3523Docs
Vega Theme Test
in user guide #3528@alt.theme.register
to API Reference #3607ThemeConfig
section to User Guide #3645Refactor
alt.theme(s)
,alt.typing.theme
,alt.vegalite.v5.theme
,alt.utils.theme
#3610I know that @binste had some ideas as well, maybe others do too
The text was updated successfully, but these errors were encountered: