altair: sort channel not working when I add text on top of my bars

This sorts like I want.

import altair as alt
from vega_datasets import data

barley = data.barley()
alt.Chart(cars.head(10)).mark_bar().encode(
    x='Miles_per_Gallon:Q',
    y=alt.Y('Name:N', sort=alt.SortField(field="Miles_per_Gallon", op="sum", order="descending"))
)

visualization 24

This does not.

bars = alt.Chart(cars.head(10)).mark_bar().encode(
    x='Miles_per_Gallon:Q',
    y=alt.Y('Name:N', sort=alt.SortField(field="Miles_per_Gallon", op="sum", order="descending"))
)

text = bars.mark_text(
    align='left',
    baseline='middle',
    dx=3,
).encode(text='Miles_per_Gallon:Q')

bars + text

visualization 25

Why?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 1
  • Comments: 20 (10 by maintainers)

Most upvoted comments

I’m not sure why this is happening; I think it’s a vega-lite bug. Here’s a simplified vega-lite spec showing the issue (Open the Chart in the Vega Editor):

{
  "data": {"url": "https://vega.github.io/vega-datasets/data/barley.json"},
  "transform": [
    {
      "joinaggregate": [{"op": "sum", "field": "yield", "as": "order"}],
      "groupby": ["variety"]
    }
  ],
  "encoding": {
    "x": {
      "field": "yield",
      "aggregate": "sum",
      "type": "quantitative",
      "stack": "zero"
    },
    "y": {
      "field": "variety",
      "type": "nominal",
      "sort": {"field": "order", "order": "descending"}
    },
    "order": {"type": "nominal", "field": "site"}
  },
  "layer": [
    {
      "mark": "bar",
      "encoding": {"color": {"type": "nominal", "field": "site"}}
    },
    {
      "transform": [{"filter": "true"}],
      "mark": {"type": "text", "color": "white", "dx": -15, "dy": 3},
      "encoding": {
        "text": {
          "type": "quantitative",
          "aggregate": "sum",
          "field": "yield",
          "format": ".1f"
        }
      }
    }
  ],
  "width": 600
}

If you delete the filter line, the sort works as expected.

I think it’s probably some issue with unioned domains, though strangely there is no warning in the Vega editor.

As a workaround, you can set resolve_scale(y='independent'), which preserves the sort:

import altair as alt
from vega_datasets import data

source=data.barley.url

bars = alt.Chart(source).transform_joinaggregate(
    order='sum(yield)', groupby=['variety']  
).mark_bar().encode(
    x=alt.X('sum(yield):Q', stack='zero'),
    y=alt.Y('variety:N', sort=alt.SortField('order', order='descending')),
    color=alt.Color('site:N'),
    order = 'site:N'
)

text = bars.mark_text(
    dx=-15, dy=3,
).transform_filter(
    {'field': 'site', 'oneOf': ['Crookston', 'Duluth']}
).encode(
    color=alt.value('white'),
    text=alt.Text('sum(yield):Q', format='.1f'),
    y=alt.Y('variety:N', sort=alt.SortField('order', order='descending'), axis=None),
)

(bars + text).resolve_scale(y='independent')

visualization (17)

The default aggregation is set within Vega-Lite. It might be worth a feature request there.

Relevant Vega-Lite issue: https://github.com/vega/vega-lite/issues/5048

The bug is closed with the recommendation that datasets be joined to a single source, similar to my recommendation above (the reason, IIUC, is that unioned domains in general will not have compatible sorting semantics, and in the corner case where the semantics are identical, it is better style and more efficient to have a single data source. So rather than doing a costly check for axis compatibility to support the corner case, they instead show the warning and recommend using a single data source).

If you look in the console output, you’ll see the reason for this:

[Warning] Dropping sort property {"field":"y_label_sort_index","op":"min"} as unioned domains only support boolean or op 'count'.