Altair: Layered or Facet bar plot with label values in Altair

Created on 7 Jun 2018  路  7Comments  路  Source: altair-viz/altair

This is related to this SO question. I am trying to recreate @jakevdp 's solution, using the population dataset.

import altair as alt
from altair.expr import datum, if_
from vega_datasets import data

source = data.population.url

bars = alt.Chart(source).mark_bar(stroke='transparent').encode(
    alt.X('gender:N', scale=alt.Scale(rangeStep=12), axis=alt.Axis(title='')),
    alt.Y('sum(people):Q', axis=alt.Axis(title='population', grid=False)),
    color=alt.Color('gender:N', scale=alt.Scale(range=["#EA98D2", "#659CCA"])),
    column='age:O'
).transform_filter(
    datum.year == 2000
).transform_calculate(
    'gender', if_(datum.sex == 2, 'Female', 'Male')
)

text = bars.mark_text(
    color='black',
    dx = 10,
).encode(
    text='sum(people):Q'
)

alt.layer(bars, text).configure_view(
    stroke='transparent'
).configure_axis(
    domainWidth=0.8
)

Gives me the following error:

SchemaValidationError: Invalid specification

        altair.vegalite.v2.api.LayerChart->layer->items, validating 'anyOf'

        {'data': {'url': 'https://vega.github.io/vega-datasets/data/population.json'}, 'mark': {'type': 'bar', 'stroke': 'transparent'}, 'encoding': {'color': {'type': 'nominal', 'field': 'gender', 'scale': {'range': ['#EA98D2', '#659CCA']}}, 'column': {'type': 'ordinal', 'field': 'age'}, 'x': {'type': 'nominal', 'axis': {'title': ''}, 'field': 'gender', 'scale': {'rangeStep': 12}}, 'y': {'type': 'quantitative', 'aggregate': 'sum', 'axis': {'grid': False, 'title': 'population'}, 'field': 'people'}}, 'transform': [{'filter': '(datum.year === 2000)'}, {'calculate': "if((datum.sex === 2),'Female','Male')", 'as': 'gender'}]} is not valid under any of the given schemas

It looks like I cannot use alt.layer and column together.
When I commented out the line:

    #    column='age:O'

It renders.
image

Any help on overcoming this error and rendering text on every bar in each age bucket? Thanks.

question

Most helpful comment

Layering only works with single-panel charts, so you cannot layer a faceted charts. The reason why is that, in general, matching panels between different faceted layers is tricky.

The workaround is to facet a layered chart, which is supported, though it's important that you specify the data in the right place. There's some discussion of this in the documentation.

Modifying your example:

import altair as alt
from altair.expr import datum, if_
from vega_datasets import data

source = data.population.url

bars = alt.Chart().mark_bar(stroke='transparent').encode(
    alt.X('gender:N', scale=alt.Scale(rangeStep=12), axis=alt.Axis(title='')),
    alt.Y('sum(people):Q', axis=alt.Axis(title='population', grid=False)),
    color=alt.Color('gender:N', scale=alt.Scale(range=["#EA98D2", "#659CCA"])),
)

text = bars.mark_text(
    color='black',
    dx = 10,
).encode(
    text='sum(people):Q'
)

alt.layer(bars, text, data=source).facet(
    column='age:O'  
).transform_filter(
    datum.year == 2000
).transform_calculate(
    'gender', if_(datum.sex == 2, 'Female', 'Male')
).configure_axis(
    domainWidth=0.8
).configure_view(
    stroke='transparent'
)

All 7 comments

Layering only works with single-panel charts, so you cannot layer a faceted charts. The reason why is that, in general, matching panels between different faceted layers is tricky.

The workaround is to facet a layered chart, which is supported, though it's important that you specify the data in the right place. There's some discussion of this in the documentation.

Modifying your example:

import altair as alt
from altair.expr import datum, if_
from vega_datasets import data

source = data.population.url

bars = alt.Chart().mark_bar(stroke='transparent').encode(
    alt.X('gender:N', scale=alt.Scale(rangeStep=12), axis=alt.Axis(title='')),
    alt.Y('sum(people):Q', axis=alt.Axis(title='population', grid=False)),
    color=alt.Color('gender:N', scale=alt.Scale(range=["#EA98D2", "#659CCA"])),
)

text = bars.mark_text(
    color='black',
    dx = 10,
).encode(
    text='sum(people):Q'
)

alt.layer(bars, text, data=source).facet(
    column='age:O'  
).transform_filter(
    datum.year == 2000
).transform_calculate(
    'gender', if_(datum.sex == 2, 'Female', 'Male')
).configure_axis(
    domainWidth=0.8
).configure_view(
    stroke='transparent'
)

Thank you. Works perfectly. And thank you for the explanation. Layer first, and then facet.

I know this is a year old thread, but it was very helpful to me. One thing...how do you get the mark_text labels to show up in black instead of the gender encoded color?

how do you get the mark_text labels to show up in black instead of the gender encoded color?

Don't include a color encoding in the chart with mark_text() or the base chart that it's derived from.

For example:

base = alt.Chart(data).encode(color='color')
chart = base.mark_text()  # text will have the color encoding

base = alt.Chart(data)
chart = base.mark_text()  # text will not have the color encoding

Alternatively, you can replace the encoding with a color value:

base = alt.Chart(data).encode(color='color')
chart = base.mark_text().encode(color=alt.value('black'))  # text will not have the color encoding

The reason that base.mark_text(color='black') does not change the color in this case is because encodings always supersede mark properties; see https://altair-viz.github.io/user_guide/customization.html#global-config-vs-local-config-vs-encoding for more information.

Thanks! That worked.

Hi, this helped me. But now I'm getting an error when trying to set width and height of faceted chart -
.properties(
# add a title
title="Title",
width=240,
height=180
)

SchemaValidationError: Invalid specification

    altair.vegalite.v3.api.Chart, validating 'required'

    'data' is a required property

It's difficult to help you without a complete code snippet.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DentonGentry picture DentonGentry  路  3Comments

bmcfee picture bmcfee  路  3Comments

morberg picture morberg  路  3Comments

zanarmstrong picture zanarmstrong  路  4Comments

jtbaker picture jtbaker  路  3Comments