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

Responsive sizing on width or weight #1734

Closed
romainmartinez opened this issue Oct 17, 2019 · 11 comments
Closed

Responsive sizing on width or weight #1734

romainmartinez opened this issue Oct 17, 2019 · 11 comments

Comments

@romainmartinez
Copy link
Contributor

To enable responsive sizing on width or weight, vega-lite allow the "container" option.

However, Altair throws an error:

source = pd.DataFrame(
    {
        "a": ["A", "B", "C", "D", "E", "F", "G", "H", "I"],
        "b": [28, 55, 43, 91, 81, 53, 19, 87, 52],
    }
)
chart = (
    alt.Chart(source, width="container", height=120)
    .mark_bar()
    .encode(x="a", y="b")
    .to_json()
)

'container' is not of type 'number'.

Is there a way to allow responsive size in Altair?

As a side note, I use altair with vega-embed to display vega-lite plot on a Flask app.

@jakevdp
Copy link
Collaborator

jakevdp commented Oct 17, 2019

The "container" option will be added in Vega-Lite 4.0, which has not yet been released (The Vega-Lite team keeps their docs pinned to unreleased development versions, which leads to a dozen or so questions like this in every release cycle).

Once Vega-Lite 4.0 is released, we will work on releasing Altair 4.0, at which point this feature will be supported.

@jakevdp jakevdp closed this as completed Oct 17, 2019
@armsp
Copy link

armsp commented May 10, 2020

Any updates on this @jakevdp ?
Vega-Lite is on version 4.8.1 now I suppose.
I was looking at various options to make the charts responsive. I found 3 methods but only one works and that too the one which is not quite official -

Method 1

Using width="container" in alt.Chart()
Effect - Resizing the window crops the chart

Method 2

Using -

.properties(
    width='container'
)

Effect - Resizing the window crops the chart

Method 3

Using the following CSS in the HTML Template - #1422 (taken from this SO post)-

canvas.marks {
    max-width: 100%!important;
    height: auto!important;
}

Effect - Resizing the window resizes the chart but it looks like the canvas is not resized so things like tooltip give wrong results when viewed on mobile platforms. I put together an example here. It works perfectly on Laptop, but on mobile you will notice that the tooltip wont appear on the states but if you click on the empty area on bottom right, tooltip will appear.

Any suggestions would be very helpful.

@jakevdp
Copy link
Collaborator

jakevdp commented May 10, 2020

Hie are you displaying your chart?

@armsp
Copy link

armsp commented May 10, 2020

@jakevdp As a saved HTML file. A live example to show that using Method 3 is here.
There are some issues with it but atleast its responsive.
HTML files - https://github.com/armsp/covid19-viz/tree/master/india
I literally took the code from here - #1422

EDIT:
If you are suggesting that it may work in Notebook, I am afraid Methods 1 and 2 don't work in Notebook either.
While developing I was on VSCode, but I was saving the chart as HTML file and rendering in browser to check the responsiveness on smaller screens.

@jakevdp
Copy link
Collaborator

jakevdp commented May 10, 2020

In that case, try putting the chart in div with a set width. If the div has no width specified, the browser will set its width based on the content. When the content in turn is setting its width based on the width of its container, the result is not generally ideal.

@mattijn
Copy link
Contributor

mattijn commented Jan 31, 2023

Let me reopen this issue. When saving an Altair chart to html, I was somehow hoping that the generated html respect the width="container" definition based on some html div elements and function as usual when not used.

I'm not against saying that it uses 100% as defined div-width.

@mattijn mattijn reopened this Jan 31, 2023
@davidgilbertson
Copy link

I've been trying to get responsive width and compound charts to play nice when output to html. I made some rough notes maybe others will find useful. Hope it's OK if I just dump them here.

Auto width and compound views

Starting data for everything below:

import altair as alt
import pandas as pd
import numpy as np

rng = np.random.default_rng()

n = 100
df = pd.DataFrame(
    {
        "Weight": rng.normal(size=n),
        "Age": rng.normal(size=n),
        "Smarts": rng.normal(size=n),
        "Kind": rng.choice(["Cat", "Dog", "Fish"], n),
        "Coat": rng.choice(["Red", "Brown", "Black", "Merle", "Dappled"], n),
    }
)

Single view

Use width="container" to have the chart fill the width. This allows for the legend automatically.

chart = (
    alt.Chart(df)
    .mark_point()
    .encode(
        alt.X("Weight"),
        alt.Y("Age"),
        alt.Color("Coat"),
    )
    .properties(
        width="container",
    )
)

Vertical concatenation

You must:

  • set width="container" on the base chart (effectively on each chart)
  • set autosize="fit-x" on the top-level chart
chart = (
    alt.Chart(df)
    .mark_point()
    .encode(
        alt.X("Weight"),
        alt.Y("Age"),
    )
    .properties(width="container")
)

chart = alt.vconcat(
    chart.encode(color="Coat"),
    chart.encode(color="Kind"),
).configure(autosize="fit-x")

Horizontal concatenation

Auto-width doesn’t work, you need to manually set the (column) widths:

chart = (
    alt.Chart(df)
    .mark_point()
    .encode(
        alt.X("Weight"),
        alt.Y("Age:Q"),
    )
    .properties(width=420)
)

chart = alt.hconcat(
    chart.encode(color="Coat"),
    chart.encode(color="Kind"),
)

Or with different width charts

available_space = 900

chart = alt.Chart(df).mark_point().encode(x="Weight")

chart = alt.hconcat(
    chart.encode(y="Age").properties(width=available_space * 2 / 3),
    chart.encode(y="Smarts").properties(width=available_space * 1 / 3),
)

Facet

Auto-width doesn’t work, manually set the width(s)

Using facet encoding:

chart = (
    alt.Chart(df)
    .mark_point()
    .encode(
        alt.X("Weight"),
        alt.Y("Age:Q"),
        alt.Facet(
            "Coat",
            columns=3,
        ),
        alt.Color("Kind"),
    )
    .properties(width=300)
)

Or creating a facet chart and using variables for width/cols.

Note that .properties must come first:

available_space = 900  # Excluding legend and axes!
cols = 3

chart = (
    alt.Chart(df)
    .mark_point()
    .encode(
        alt.X("Weight"),
        alt.Y("Age:Q"),
        alt.Color("Kind"),
    )
    .properties(
        width=available_space / cols,
    )
    .facet(
        facet="Coat",
        columns=cols,
    )
)

In all cases with width="container", you must modify the generated HTML to make the chart container full width:

from pathlib import Path

html = chart.to_html().replace(
    "</head>",
    "<style>.vega-embed {width: 100%;}</style></head>",
)
Path("chart.html").write_text(html)

@mattijn
Copy link
Contributor

mattijn commented Feb 2, 2023

Thank you @davidgilbertson! Much appreciated! It would be great to have this modification of the generated html in Altair itself.

Would you mind to update the HTML_TEMPLATE in html.py?

Around here:
https://github.com/altair-viz/altair/blob/master/altair/utils/html.py#L12-L16

And change it from:

<style>
  .error {
      color: red;
  }
</style>

to:

<style>
 .vega-embed {
	width: 100%;
},
.error {
	color: red;
}
</style>

And commit these changes as a new pull request.
If you need help with this, please ping.

@davidgilbertson
Copy link

An issue with that approach is that it puts the menu icon (top right) way over on the right side of the screen even when not doing full-width charts.

image

Is that acceptable?

Also, I thought about checking the chart config for width="container", but that seems a bit brittle.

@mattijn
Copy link
Contributor

mattijn commented Feb 3, 2023

An improved suggestion is proposed in #2867.

But it requires changing defaults in the css of vega-embed. Maybe the required changes need to happen at that library.

@binste
Copy link
Contributor

binste commented May 26, 2023

Closing as #2867 was merged and released in Altair 5. Feel free to reopen if I got it wrong and there is something missing!

@binste binste closed this as completed May 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants