VegaLite.jl icon indicating copy to clipboard operation
VegaLite.jl copied to clipboard

Global settings

Open sswatson opened this issue 7 years ago • 15 comments

If you have some properties that you want to apply to every plot (like width, height, fonts, etc.), is there currently a way to do that other than copy-pasting?

sswatson avatar Oct 05 '18 22:10 sswatson

Can't think of any way to do that... Good idea, though!

davidanthoff avatar Oct 05 '18 23:10 davidanthoff

I found that Vega already has the concept of a config for these kinds of settings. Maybe this is easily wrapped in a Julia macro just like @vlplot. Not sure if we can avoid passing that config to all calls of @vlplot though...

Saw this in the vega themes demo.

rschwarz avatar Nov 24 '18 21:11 rschwarz

I think creating a config object and a version of @vl macros that accept the config would be a great solution. An easier one might be to have a global mutable CONFIG variable read implicitly when constructing the Vega-objects. Could be implemented as an additional macro argument in the VegaLite module and a global variable config in a separate VegaLite.REPL or VegaLite.Implicit module.

dm3 avatar Jun 24 '19 10:06 dm3

This would be extremely useful. If I could get a hint on how one best could send a config variable into the @vlplot macro I'd might try my amateur skills at open source contribution?

I'd want something like:

vlconf={background="white", axis={color="gray", legendColor="gray", labelColor="gray", titleColor="gray", gridColor="#efefef"}}

and then just

p=@vlplot(data=df, config=vlconfig row={:Number_of_Actors,title="Number of individuals"}, column=:Price, width=100, height=100, mark={:line, clip=true }, x={:Minimum_Income, axis={title="Minimum wage"}}, y={:Resource}, color={:Distribution_type,legend={title="Distribution"}, sort=false})

but the vlconfig cannot be defined as above...

zahachtah avatar Sep 12 '19 13:09 zahachtah

Maybe one option would be to just have something like:


set_vlconfig(@vlplot(whatever))

But I'm still quite unsure what a good API for this would look like...

@tkf and @mcmcgrath13 do you have thoughts on this?

davidanthoff avatar Sep 13 '19 23:09 davidanthoff

Maybe do something like we did in ElectronDisplay.CONFIG?

Base.@kwdef mutable struct VegaLiteConfig
    defaultconfig::Dict = Dict()
end

const CONFIG = VegaLiteConfig()

Then VegaLite.CONFIG.defaultconfig would be deep-copied to each Vega object.

But I'm not super sure about introducing configurability at the level of @vlplot. For example, if @vlplot is used inside a library it would be hard to diagnose the problem. Ideally, it should happen at the very end of plotting pipeline, just before rendering. We could do this if we had a pre-display hook or something (but we don't...).

In the end, I think VegaLiteConfig is not so bad. It can also be used to configure different strategies to serve data to the frontend, like altair does: https://altair-viz.github.io/user_guide/faq.html#maxrowserror-how-can-i-plot-large-datasets

tkf avatar Sep 13 '19 23:09 tkf

vega-embed handles the incorporation of the config already, so we'd just need to add the global default to the opts that are passed there. I'm not sure how that plays with file saving though...

This also isn't strictly limited to VegaLite, so the api should also make some sense from the Vega side (though as it's just a dictionary under the hood, this shouldn't be difficult). Perhaps have methods for updating the CONFIG via @vlplot, @vlstr, @vgstr, and maybe a straight Dict. The first three are easily achievable using the AbstractVegaSpec.

To be consistent with renderer and actionlinks, this function would just be called config - is that too broad/vague?

It would be cool if then we could also figure out how to import/use the vega-themes with this set up.

mcmcgrath13 avatar Sep 16 '19 13:09 mcmcgrath13

Yeah, I think the problem with vega-embed is that it doesn't help us with the save to file situation... My sense is that viewing and saving a plot should really always end up with the same output, so my gut feeling is we should probably shoot for a solution that doesn't use the vega-embed option. I think Altair also uses a theming story that is independent from the vega-embed implementation.

Here is one (maybe crazy) idea: maybe we could use piping of plots into a plot for this. For example:

df |> vgtheme("ggplot") |> @vlplot(bla bla)

default_options = @vlplot(config={something})
df |> default_options |> @vlplot(bla bla)

That would essentially allow us to get around the global config problems that @tkf mentioned above and that I think are really super valid.

Maybe we could even use this to also address https://github.com/queryverse/VegaLite.jl/issues/168:

p = df |> @vlplot(bla bla)

p2 = p |> @vlplot(somehow the stuff you want to modify)

Generally the idea here would be that whatever is in p2 in an expression like p1 |> p2 just gets added on top of p1, and a new plot with that is returned.

I haven't thought this through very much, so easily possible that there is something fundamentally flawed in this idea :)

@mcmcgrath13, are you familiar how the vega-themes work? My understand is that these are essentially just some JSON values that get added to whatever spec one has? Or did I misunderstand that?

davidanthoff avatar Sep 22 '19 03:09 davidanthoff

that looks extremely pretty and intuitive if possible to implement!

zahachtah avatar Sep 22 '19 06:09 zahachtah

Is the idea to define (right::VLSpec)(left::Vlspec) so that left |> right "merges" those specs (and right wins whenever there is a conflict)? I guess it needs to support deletion of the properties? Would it something like

plt |> @vlplot(config = {axis = {grid = nothing}})

to remove plt.config.axis.grid?

I'm a bit worried about this direction. It is mixing function types and argument types. It's like mixing matrices/vectors or datetimes/periods. For example, consider

rm_grid = @vlplot(config = {axis = {grid = nothing}})
rm_style = @vlplot(config = {style = nothing})

and observe that

rm_grid |> rm_style == rm_grid

and so (for some plt)

(plt |> rm_grid) |> rm_style == plt |> (rm_grid ∘ rm_style)
(plt |> rm_grid) |> rm_style != plt |> (rm_grid |> rm_style)

I guess it's reasonable to argue that the directionality of the symbol |> implies there is no associativity. But in this case I think it is better to just not allow invoking meaningless and confusing calls like rm_grid |> rm_style.

Maybe we could even use this to also address #168:

Is there a public API to get properties of the spec? I don't think this is a full solution to #168 if not.

If people are OK with the explicit x |> f syntax, I think it may be cleaner to solve #168 directly so that they can use

function myvlconfig(plt)
    plt.config.axis.grid = true  # for example
    return plt
end

df |> @vlplot(...) |> myvlconfig

tkf avatar Sep 22 '19 08:09 tkf

The themes are basically pre-packaged config objects to make nice defaults, so they should fit easily into any of these solutions.

I don't have an opinion on the api, but I do think this should be implemented as much as possible on the AbstractVegaSpec instead of the VLSpec as it seems generic enough to be used for both vega and vega lite. There are probably a few other methods we could consolidate to the abstract spec method signature (e.g. ==). It might be worth breaking that out into its own file (spec_utils.jl or similar).

mcmcgrath13 avatar Sep 22 '19 14:09 mcmcgrath13

Just started a PR (#188) for the spec_utils

mcmcgrath13 avatar Sep 22 '19 15:09 mcmcgrath13

Is the idea to define (right::VLSpec)(left::Vlspec) so that left |> right "merges" those specs (and right wins whenever there is a conflict)?

Yes, exactly.

I guess it needs to support deletion of the properties?

Hm, yes, that is clearly tricky... I don' think foo=nothing can be used to delete stuff, because we currently translate that into "foo": null, which I believe is valid and needed in certain circumstances.

So maybe we should generally think of something like operators on specs. Say something like this:

p_new = p_old |> @vlremove(enc.color) |> @vladd(x="foo:q")

Maybe we actually need things like @vlreplace, @vlmerge etc to handle different ways how new stuff can be merged into a spec?

And so maybe for the config parts in particular, something like vgtheme("foo") would actually return a VConfig type, which handles yet again differently... In particular, we could then say the rule is that whether you do config_object |> spec or spec |> config_object, the spec always "wins". Not sure that is actually what we want, but it might be one option...

For the default settings situation, we probably want to restrict that to entries in the config part as well?

Is there a public API to get properties of the spec?

No, and I'm very much in favor to just add the p.encodings.x type syntax to read spec elements right away as an official API. I'm just a lot more hesitant about a mutating API, if we could find a more functional story for that, I'd really like it.

but I do think this should be implemented as much as possible on the AbstractVegaSpec instead of the VLSpec

Yes, very much agreed! I tend to forget about pure vega because I never use it ;)

davidanthoff avatar Sep 22 '19 19:09 davidanthoff

I don' think foo=nothing can be used to delete stuff, because we currently translate that into "foo": null, which I believe is valid and needed in certain circumstances.

Good point. How about undef? It's used only for array construction but mapping this to Javascript's undefined is kind of nice.

I'm just a lot more hesitant about a mutating API

That's why we need Setfield's lens API :) Explained more in https://github.com/queryverse/VegaLite.jl/issues/168#issuecomment-531427058

tkf avatar Sep 22 '19 19:09 tkf

Are there any updates on this issue? That would be a pretty useful feature!

kir0ul avatar Jan 24 '22 20:01 kir0ul