altair
altair copied to clipboard
Having both X and Y axes' Scales respond to a selection interval
Hello, Is there a way to get the Scale on Y-axis in the bottom chart to respond to the selection brush? I'd like to be able to pan around the top chart with a selection and see the zoomed-in results in the bottom chart. If I uncomment the alt.Y, then both the X and Y axes show Years and it's messed up. Is there a way to pass just an X or Y value in the 'brush' maybe? Thank you very much!
brush = alt.selection_interval(init={'x':[1950, 1970], 'y':[1500000, 2500000]}, encodings=['x', 'y'])
base = alt.Chart().mark_line().encode(
x=alt.X('Year:Q', title=None),
y='Deaths:Q',
color='Entity:N'
)
alt.vconcat(
base.add_selection(brush).encode().properties(height=150, width=150),
base.encode(
alt.X('Year:Q', scale=alt.Scale(domain=brush)),
#alt.Y('Deaths:Q', scale=alt.Scale(domain=brush)) # (un)commenting this line makes it work/fail only along the x-axis
).properties(
height=500, width=500
),
data='https://vega.github.io/vega-datasets/data/disasters.csv'
)
Is there a way for the selection/brush to only pass the 'x' or 'y' information into the domain?
I am not sure if this functionality is provided by Vega-Lite, so it might be a good idea to ask on StackOverflow and tag it with Vega-Lite to see if anyone from that community knows.
Posted here for future reference: https://stackoverflow.com/questions/67358979/having-both-x-and-y-axes-scales-in-altair-respond-to-a-selection-interval
Posted there
@legolego pretty late to the party, but for future reference I adapted your example with @mattijn 's Vega editor version and this worked in Python using Altair:
brush = alt.selection_interval(init={'x':[1950, 1970], 'y':[1500000, 2500000]}, encodings=['x', 'y'])
base = alt.Chart().mark_line().encode(
x=alt.X('Year:Q', title=None),
y='Deaths:Q',
color='Entity:N'
)
alt.vconcat(
base.add_selection(brush).encode().properties(height=150, width=150),
base.encode(
).properties(
height=500, width=500
).transform_filter(brush),
data='https://vega.github.io/vega-datasets/data/disasters.csv'
)
Would suggest to close this one :)
@betaigeuze I tried that and it didn't work like I wanted... the effect I was looking for was like a magnifying glass in the small chart, with the zoomed in results in the large chart. Going back to @mattijn 's answer in vega, it might not be quite right either actually, maybe I didn't notice before, but his x and y-scales move in the zoomed in version, where I wanted to make them constant. Another example would be the zoom-in effect that ebay has (https://www.ebay.com/itm/125622850855) on their item pages. You drag a window around to inspect that zoomed-in section.
@legolego I'm sorry to late, but did you find a python code that changes the chart to that area by enlarging the selected area in the chart? I really want to get help. Thanks.
@hyerming23 No, I haven't found a solution for this yet.
@mattijn Hello. Could you share the function implemented in the vega-lite page you uploaded before in python code? This function enlarges the selected area in the chart of altair to each of the x and y axes. This is the function of the page below.
https://stackoverflow.com/questions/67358979/having-both-x-and-y-axes-scales-in-altair-respond-to-a-selection-interval
There are a few variants possible. I'm not sure which one is wished here. The SO example using altair syntax:
import altair as alt
from vega_datasets import data
cars = data.cars()
brush = alt.selection_interval(value={"x": [45, 100], "y": [30, 40]})
source = alt.Chart(cars).mark_point().encode(
x='Horsepower',
y='Miles_per_Gallon',
color=alt.condition(brush, 'Cylinders:O', alt.value('lightgray'))
).add_params(brush)
detail = alt.Chart(cars).mark_point().encode(
x=alt.X('Horsepower').scale(zero=False),
y=alt.Y('Miles_per_Gallon').scale(zero=False),
color=alt.condition(brush, 'Cylinders:O', alt.value('lightgray'))
).transform_filter(brush)
(source | detail).resolve_legend(color='independent')
A variant using only the filter on the x-axis:
import altair as alt
from vega_datasets import data
cars = data.cars()
brush = alt.selection_interval(value={"x": [45, 100]}, encodings=['x'])
source = alt.Chart(cars).mark_point().encode(
x='Horsepower',
y='Miles_per_Gallon',
color=alt.condition(brush, 'Cylinders:O', alt.value('lightgray'))
).add_params(brush)
detail = alt.Chart(cars).mark_point().encode(
x=alt.X('Horsepower').scale(zero=False),
y=alt.Y('Miles_per_Gallon').scale(zero=False),
color=alt.condition(brush, 'Cylinders:O', alt.value('lightgray'))
).transform_filter(brush)
(source | detail).resolve_legend(color='independent')
And to make it complete, a variant using filtering on the y-axis:
import altair as alt
from vega_datasets import data
cars = data.cars()
brush = alt.selection_interval(value={"Miles_per_Gallon": [30, 40]}, encodings=['y'])
source = alt.Chart(cars).mark_point().encode(
x='Horsepower',
y='Miles_per_Gallon',
color=alt.condition(brush, 'Cylinders:O', alt.value('lightgray'))
).add_params(brush)
detail = alt.Chart(cars).mark_point().encode(
x=alt.X('Horsepower').scale(zero=False),
y=alt.Y('Miles_per_Gallon').scale(zero=False),
color=alt.condition(brush, 'Cylinders:O', alt.value('lightgray'))
).transform_filter(brush)
(source | detail).resolve_legend(color='independent')
If you look careful to the alt.selection_interval()
definitions then there are multiple differences in between these examples:
brush = alt.selection_interval(value={"x": [45, 100], "y": [30, 40]})
brush = alt.selection_interval(value={"x": [45, 100]}, encodings=['x'])
brush = alt.selection_interval(value={"Miles_per_Gallon": [30, 40]}, encodings=['y'])
@mattijn Thank you very much for your kind explanation. In the above code, can I show the enlarged chart in the source right away, not the separate chart of source and detail? If possible, can I ask you to share sample code?
@hyerming23, maybe this is something that can help:
import altair as alt
from vega_datasets import data
cars = data.cars()
brush_empty = alt.selection_interval(clear="pointerup")
filter_on_up = alt.selection_interval(on="[pointerdown, type_anything] > pointerup", mark={"fillOpacity":0 , "strokeOpacity": 0, "cursor":"auto"}, clear="dblclick")
alt.Chart(cars).mark_point().encode(
x=alt.X('Horsepower').scale(zero=False),
y=alt.Y('Miles_per_Gallon').scale(zero=False),
color=alt.condition(brush_empty, 'Cylinders:O', alt.value('lightgray'))
).add_params(filter_on_up, brush_empty).transform_filter(filter_on_up)
I added two interval selections, The first one is only changing the color of the selection (brush_empty)
and the second one actually filters the data (filter_on_up
). I was trying a few things for the on
operator (vl-docs) which mentions that:
For interval selections, the event stream must specify a start and end.
And the related vega-docs on interval streaming mention this:
Between Filters To capture events that occur between two other events, use a bracket notation.
[startSelector, stopSelector] > selector
Between filters are particularly useful to capture streams of drag events:
[rect:mousedown, window:mouseup] > window:mousemove
This example initiates a drag upon mousedown on a rect mark, then tracks the drag using events on the browser window. Using the window as the event source lets the drag continue if the mouse pointer leaves the initial rect mark or the view component.
I made a mistake and found out that it actually works like this: "[pointerdown, type_anything] > pointerup"
, a non-existing stopSelector
applies the interval only after the pointerup
is triggered...
It would be nice to actually understand why and how this works, but maybe you or someone else can tell more about this!
@mattijn Thank you very much😍 You helped me a lot.