Skip to content

Commit

Permalink
Merge branch 'main' into condition-multiple
Browse files Browse the repository at this point in the history
  • Loading branch information
dangotbanned authored Jul 9, 2024
2 parents a3531fb + 8b9f697 commit 03d7ca5
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 19 deletions.
38 changes: 20 additions & 18 deletions altair/vegalite/v5/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@
GetAttrExpression,
GetItemExpression,
)
from .schema._typing import (
ImputeMethod_T,
SelectionType_T,
SelectionResolution_T,
SingleDefUnitChannel_T,
StackOffset_T,
)


ChartDataType: TypeAlias = Optional[Union[DataType, core.Data, str, core.Generator]]
_TSchemaBase = TypeVar("_TSchemaBase", bound=core.SchemaBase)
Expand Down Expand Up @@ -1251,9 +1259,7 @@ def param(
return parameter


def _selection(
type: Optional[Literal["interval", "point"]] = Undefined, **kwds
) -> Parameter:
def _selection(type: Optional[SelectionType_T] = Undefined, **kwds) -> Parameter:
# We separate out the parameter keywords from the selection keywords

select_kwds = {"name", "bind", "value", "empty", "init", "views"}
Expand Down Expand Up @@ -1283,9 +1289,7 @@ def _selection(
message="""'selection' is deprecated.
Use 'selection_point()' or 'selection_interval()' instead; these functions also include more helpful docstrings."""
)
def selection(
type: Optional[Literal["interval", "point"]] = Undefined, **kwds
) -> Parameter:
def selection(type: Optional[SelectionType_T] = Undefined, **kwds) -> Parameter:
"""
Users are recommended to use either 'selection_point' or 'selection_interval' instead, depending on the type of parameter they want to create.
Expand Down Expand Up @@ -1314,10 +1318,10 @@ def selection_interval(
bind: Optional[Binding | str] = Undefined,
empty: Optional[bool] = Undefined,
expr: Optional[str | Expr | Expression] = Undefined,
encodings: Optional[list[str]] = Undefined,
encodings: Optional[list[SingleDefUnitChannel_T]] = Undefined,
on: Optional[str] = Undefined,
clear: Optional[str | bool] = Undefined,
resolve: Optional[Literal["global", "union", "intersect"]] = Undefined,
resolve: Optional[SelectionResolution_T] = Undefined,
mark: Optional[Mark] = Undefined,
translate: Optional[str | bool] = Undefined,
zoom: Optional[str | bool] = Undefined,
Expand Down Expand Up @@ -1426,11 +1430,11 @@ def selection_point(
bind: Optional[Binding | str] = Undefined,
empty: Optional[bool] = Undefined,
expr: Optional[Expr] = Undefined,
encodings: Optional[list[str]] = Undefined,
encodings: Optional[list[SingleDefUnitChannel_T]] = Undefined,
fields: Optional[list[str]] = Undefined,
on: Optional[str] = Undefined,
clear: Optional[str | bool] = Undefined,
resolve: Optional[Literal["global", "union", "intersect"]] = Undefined,
resolve: Optional[SelectionResolution_T] = Undefined,
toggle: Optional[str | bool] = Undefined,
nearest: Optional[bool] = Undefined,
**kwds,
Expand Down Expand Up @@ -2549,9 +2553,7 @@ def transform_impute(
frame: Optional[list[int | None]] = Undefined,
groupby: Optional[list[str | FieldName]] = Undefined,
keyvals: Optional[list[Any] | ImputeSequence] = Undefined,
method: Optional[
Literal["value", "mean", "median", "max", "min"] | ImputeMethod
] = Undefined,
method: Optional[ImputeMethod_T | ImputeMethod] = Undefined,
value=Undefined,
) -> Self:
"""
Expand Down Expand Up @@ -3074,7 +3076,7 @@ def transform_stack(
as_: str | FieldName | list[str],
stack: str | FieldName,
groupby: list[str | FieldName],
offset: Optional[Literal["zero", "center", "normalize"]] = Undefined,
offset: Optional[StackOffset_T] = Undefined,
sort: Optional[list[SortField]] = Undefined,
) -> Self:
"""
Expand Down Expand Up @@ -3757,7 +3759,7 @@ def interactive(
copy of self, with interactive axes added
"""
encodings = []
encodings: list[SingleDefUnitChannel_T] = []
if bind_x:
encodings.append("x")
if bind_y:
Expand Down Expand Up @@ -4047,7 +4049,7 @@ def interactive(
copy of self, with interactive axes added
"""
encodings = []
encodings: list[SingleDefUnitChannel_T] = []
if bind_x:
encodings.append("x")
if bind_y:
Expand Down Expand Up @@ -4144,7 +4146,7 @@ def interactive(
copy of self, with interactive axes added
"""
encodings = []
encodings: list[SingleDefUnitChannel_T] = []
if bind_x:
encodings.append("x")
if bind_y:
Expand Down Expand Up @@ -4243,7 +4245,7 @@ def interactive(
copy of self, with interactive axes added
"""
encodings = []
encodings: list[SingleDefUnitChannel_T] = []
if bind_x:
encodings.append("x")
if bind_y:
Expand Down
2 changes: 1 addition & 1 deletion doc/user_guide/encodings/channels.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Channel Altair Class Description Example
angle :class:`Angle` The angle of the mark :ref:`gallery_wind_vector_map`
color :class:`Color` The color of the mark :ref:`gallery_simple_heatmap`
fill :class:`Fill` The fill for the mark :ref:`gallery_ridgeline_plot`
fillopacity :class:`FillOpacity` The opacity of the mark's fill N/A
fillOpacity :class:`FillOpacity` The opacity of the mark's fill N/A
opacity :class:`Opacity` The opacity of the mark :ref:`gallery_horizon_graph`
radius :class:`Radius` The radius or the mark :ref:`gallery_radial_chart`
shape :class:`Shape` The shape of the mark :ref:`gallery_us_incomebrackets_by_state_facet`
Expand Down
148 changes: 148 additions & 0 deletions tests/examples_arguments_syntax/scatter_point_paths_hover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""
Scatter plot with point paths on hover with search box
======================================================
This example combines cross-sectional analysis (comparing countries at a single point in time)
with longitudinal analysis (tracking changes in individual countries over time), using
an interactive visualization technique inspired by [this Vega example](https://vega.github.io/vega/examples/global-development/).
Key features:
1. Point Paths. On hover, shows data trajectories using a trail mark that
thickens from past to present, clearly indicating the direction of time.
2. Search Box. Implements a case-insensitive regex filter for country names,
enabling dynamic, flexible data point selection to enhance exploratory analysis.
"""
# category: interactive charts
import altair as alt
from vega_datasets import data

# Data source
source = data.gapminder.url

# X-value slider
x_slider = alt.binding_range(min=1955, max=2005, step=5, name='Year ')
x_select = alt.selection_point(name="x_select", fields=['year'], bind=x_slider, value=1980)

# Hover selection
hover = alt.selection_point(on='mouseover', fields=['country'], empty=False)
# A separate hover for the points since these need empty=True
hover_point_opacity = alt.selection_point(on='mouseover', fields=['country'])

# Search box for country name
search_box = alt.param(
value='',
bind=alt.binding(input='search', placeholder="Country", name='Search ')
)

# Base chart
base = alt.Chart(source).encode(
x=alt.X('fertility:Q', scale=alt.Scale(zero=False), title='Babies per woman (total fertility rate)'),
y=alt.Y('life_expect:Q', scale=alt.Scale(zero=False), title='Life expectancy'),
color=alt.Color('region:N', title='Region', legend=alt.Legend(orient='bottom-left', titleFontSize=14, labelFontSize=12), scale=alt.Scale(scheme='dark2')),
detail='country:N'
).transform_calculate(
region="""{
'0': 'South Asia',
'1': 'Europe & Central Asia',
'2': 'Sub-Saharan Africa',
'3': 'The Americas',
'4': 'East Asia & Pacific',
'5': 'Middle East & North Africa'
}[datum.cluster]"""
).transform_filter(
# Exclude North Korea and South Korea due to source data error
"datum.country !== 'North Korea' && datum.country !== 'South Korea'"
)

# Points that are always visible (filtered by slider and search)
visible_points = base.mark_circle(size=100).encode(
opacity=alt.condition(
hover_point_opacity
& alt.expr.test(alt.expr.regexp(search_box, 'i'), alt.datum.country),
alt.value(0.8),
alt.value(0.1)
)
).transform_filter(
x_select
).add_params(
hover,
hover_point_opacity,
x_select
)

hover_line = alt.layer(
# Line layer
base.mark_trail().encode(
order=alt.Order(
'year:Q',
sort='ascending'
),
size=alt.Size(
'year:Q',
scale=alt.Scale(domain=[1955, 2005], range=[1, 12]),
legend=None
),
opacity=alt.condition(hover, alt.value(0.3), alt.value(0)),
color=alt.value('#222222')
),
# Point layer
base.mark_point(size=50).encode(
opacity=alt.condition(hover, alt.value(0.8), alt.value(0)),
)
)

# Year labels
year_labels = base.mark_text(align='left', dx=5, dy=-5, fontSize=14).encode(
text='year:O',
color=alt.value('#222222')
).transform_filter(hover)

# Country labels
country_labels = alt.Chart(source).mark_text(
align='left',
dx=-15,
dy=-25,
fontSize=18,
fontWeight='bold'
).encode(
x='fertility:Q',
y='life_expect:Q',
text='country:N',
color=alt.value('black'),
opacity=alt.condition(hover, alt.value(1), alt.value(0))
).transform_window(
rank='rank(life_expect)',
sort=[alt.SortField('life_expect', order='descending')],
groupby=['country'] # places label atop highest point on y-axis on hover
).transform_filter(
alt.datum.rank == 1
).transform_aggregate(
life_expect='max(life_expect)',
fertility='max(fertility)',
groupby=['country']
)

background_year = alt.Chart(source).mark_text(
baseline='middle',
fontSize=96,
opacity=0.2
).encode(
text='year:O'
).transform_filter(
x_select
).transform_aggregate(
year='max(year)'
)

# Combine all layers
chart = alt.layer(
visible_points, year_labels, country_labels, hover_line, background_year
).properties(
width=500,
height=500,
padding=10 # Padding ensures labels fit
).configure_axis(
labelFontSize=12,
titleFontSize=12
).add_params(search_box)

chart
Loading

0 comments on commit 03d7ca5

Please sign in to comment.