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

Is it possible to use alt.condition when passing options to text encodings? #1055

Closed
palewire opened this issue Jul 28, 2018 · 9 comments · Fixed by #3226
Closed

Is it possible to use alt.condition when passing options to text encodings? #1055

palewire opened this issue Jul 28, 2018 · 9 comments · Fixed by #3226

Comments

@palewire
Copy link
Contributor

palewire commented Jul 28, 2018

Here's the chart I want to make. You can see that it has text placed above positive bars and below negative bars.

download

Here's the data I used.

import pandas as pd
import altair as alt
last_13 = pd.read_json("https://raw.githubusercontent.com/datadesk/cpi/master/notebooks/last_13.json", dtype={"date_label": pd.np.datetime64})

Here's the base chart I wrote.

base = alt.Chart(
    last_13, 
    title="One-month percent change in CPI for All Urban Consumers (CPI-U), seasonally adjusted"
).properties(width=700)

bars = base.mark_bar().encode(
    x=alt.X(
        "date:O",
        timeUnit="yearmonth",
        axis=alt.Axis(title=None, labelAngle=0, format="%b %y"),
    ),
    y=alt.Y(
        "pct_change_rounded:Q",
        axis=alt.Axis(title=None),
        scale=alt.Scale(domain=[
            last_13['pct_change'].min()-0.1,
            last_13['pct_change'].max()+0.05
        ])
    )
)

And here's how I handled the text.

text = base.encode(
    x=alt.X("date:O", timeUnit="yearmonth"),
    y="pct_change_rounded:Q",
    text='pct_change_rounded'
)

textAbove = text.transform_filter(alt.datum.pct_change > 0).mark_text(
    align='center',
    baseline='middle',
    fontSize=14,
    dy=-10
)

textBelow = text.transform_filter(alt.datum.pct_change < 0).mark_text(
    align='center',
    baseline='middle',
    fontSize=14,
    dy=12
)

bars + textAbove + textBelow

It all works fine. But why can't I do this?

text = base.encode(
    x=alt.X("date:O", timeUnit="yearmonth"),
    y="pct_change_rounded:Q",
    text='pct_change_rounded',
    align='center',
    baseline='middle',
    fontSize=14,
    dy=alt.condition(alt.datum.pct_change_rounded >= 0, 12, -10)
)

bars + text

I get this error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-ab79ad07b15f> in <module>()
     27     baseline='middle',
     28     fontSize=14,
---> 29     dy=alt.condition(alt.datum.pct_change_rounded >= 0, 12, -10)
     30 )
     31 

/home/palewire/.virtualenvs/cpi/src/altair/altair/vegalite/v2/api.py in condition(predicate, if_true, if_false, **kwargs)
    333         if_true = {'shorthand': if_true}
    334         if_true.update(kwargs)
--> 335     condition.update(if_true)
    336 
    337     if isinstance(if_false, core.SchemaBase):

TypeError: 'int' object is not iterable
@jakevdp
Copy link
Collaborator

jakevdp commented Jul 29, 2018

conditions are only supported for encodings, unfortunately.

@jakevdp
Copy link
Collaborator

jakevdp commented Jul 29, 2018

FYI: for the power user, often the best way to answer "is this possible" questions is to look at the vega-lite schema. For example, here it's clear that dy can only be a number, not any sort of conditional expression:

https://github.com/altair-viz/altair/blob/28378bd52efbf4eae5275b6fb598e74b51bf5f4c/altair/vegalite/v2/schema/vega-lite-schema.json#L4700-L4703

@palewire
Copy link
Contributor Author

palewire commented Jul 29, 2018

Thanks for the tip. Too bad Vega doesn't support this. In a perfect world such a feature would be nice.

Short of that, is the pattern I developed above the one you would recommend. If so, perhaps it's worth enshrining it in an example?

@kanitw
Copy link
Member

kanitw commented Jul 29, 2018

FYI, we do plan to support xOffset/yOffset as encoding channels in the future, but doing so require some design in terms of how the x/yOffset channels interact with x/y channels, so that's why it doesn't happen yet.

Also, maybe reading the type descriptions in Vega-Lite docs is less painful than reading the JSON schema, which is more like a format for machine to read IMHO.

@qunderriner
Copy link

Are there workarounds with conditional formatting mark_text? I was assuming I could create a separate feature that just displayed the information I wanted to have displayed, but I can't quite get it to appear in the place that I want.

Dummy data and my latest graph code below.

import pandas as pd
import altair as alt
fig1_example = pd.DataFrame({
    'ami': ['0-50% AMI','50-80% AMI', 
'80-120% AMI','120% AMI +', '0-50% AMI', '50-80% AMI', 
'80-120% AMI', '120% AMI +'],
    'variable': ['Permits','Permits','Permits','Permits',
          'Target','Target','Target','Target'],
    'value': [1127, 37, 12998, 14862, 
1690, 1997, 10759, 9803],
     'order':[1, 2, 3, 4, 1, 2, 3, 4],
    'label':['1127', '37', '12998', '14862', '', '', '', '']})

bars = alt.Chart(fig1_example).mark_bar(stroke="#000000").encode(
    alt.X('ami:O',title=" ",axis=alt.Axis( labelAngle=360), 
sort=['0-50% AMI', '50-80% AMI', 
'80-120% AMI', '120% AMI +']), alt.Y('value:Q',title=""),
color=alt.Color('variable:O',title="",
scale=alt.Scale(domain=['Permits', 'Target'],
 range=["#FFB81D", "#FFFFFF"])), order=alt.Order(
      'order', sort= 'ascending'))
text = alt.Chart(fig1_example).mark_text(fontSize = 14, 
color='black', dy=-10).encode(
    x=alt.X('ami:O',sort=['0-50% AMI', '50-80% AMI',
 '80-120% AMI', '120% AMI +']),
    y=alt.Y('value:Q'),
    detail='label:O',
    text=alt.Text('label:O'))

layer = alt.layer(bars, text).configure_legend(symbolStrokeWidth=1, 
orient="bottom", direction="horizontal", 
labelLimit= 0, titleLimit=0, titleFontSize=18,
labelFontSize=20, labelFont="Georgia", 
titleFont="Georgia").configure_axis(domain=False, 
labelFont='Georgia', titleFont='Georgia', 
titleFontWeight="normal", labelFontSize=15,
 titleFontSize=15).properties(width=400)
layer

The current output of the above code looks like this
image

I want to have the right two bar charts text above the bars, like in this chart
image

Any advice on how to implement this would be really appreciated, been struggling with it.

@jakevdp
Copy link
Collaborator

jakevdp commented Feb 25, 2022

Once the offset encoding is available, this will be easier. I think that should be in Altair once #2528 is merged and released.

@joelostblom
Copy link
Contributor

@qunderriner A workaround for this issue is to create text labels in all the positions where you want them for all the bars and then encode their opacity with a condition as in this SO answer. Another workaround would be to create a custom dataframe with the y-values in the right positions for where you want the text to be for each bar.

Btw, I don't think the offset functionality is part of a Vega-Lite release yet, so it might only come to Altair after #2528

@qunderriner
Copy link

@joelostblom That worked perfectly - thanks so much!

@joelostblom
Copy link
Contributor

@palewire @qunderriner This is now possible to do via expressions https://altair-viz.github.io/user_guide/interactions.html#expressions. Using the same setup as above:

text = base.encode(
    x=alt.X("date:O", timeUnit="yearmonth"),
    y="pct_change_rounded:Q",
    text='pct_change_rounded',
).mark_text(
    fontSize=14,
    dy=alt.expr(alt.expr.if_(alt.datum.pct_change_rounded >= 0, -10, 10))
)

bars + text

image

I think it would be helpful to have an example of this in the docs, maybe under the text mark label section https://altair-viz.github.io/user_guide/marks/text.html#labels? Happy to review a PR if someone wants to take that on.

Another approach would be to use the yOffset channel like this yOffset=alt.datum(alt.expr(alt.expr.if_(alt.datum.pct_change_rounded >= 0, -10, 10))) but then the offset is in axis units rather than pixels so that it often less desirable. I don't think condition will work as it requires one of the outcomes to be a field encoding and not a fixed value

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants