forked from vega/altair
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: Add example with hover path and search box (vega#3459)
* Add example with hover path and search box - point paths on hover - search box to filter by country name Relates to [vega#2980](vega#2980) TODO: - Investigate and potentially correct North Korea and South Korea data in vega_datasets' gapminder json, which appears to have swapped labels. * Enhance scatter plot example - Improve visual design and interactivity - Add point opacity on hover for better focus - Implement dynamic background year display - Adjust chart dimensions and padding - Refine region mapping and color scheme - Update axis scaling and formatting - Enhance country label positioning and styling - Optimize code structure and comments - Note: arguments syntax version will be added after further iteration of the example * Update scatter plot and include arguments and methods syntaxes - Revise example description to clarify applicability to panel time series data - Update x-axis label to make the units more understandable - Streamline throughout and reduce unnecessary padding - Exclude North Korea and South Korea due to data discrepancy in vega-datasets source * Remove trailing # --------- Co-authored-by: Joel Ostblom <joelostblom@users.noreply.github.com>
- Loading branch information
1 parent
ca6ae15
commit eeecb66
Showing
2 changed files
with
289 additions
and
0 deletions.
There are no files selected for viewing
148 changes: 148 additions & 0 deletions
148
tests/examples_arguments_syntax/scatter_point_paths_hover.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
141 changes: 141 additions & 0 deletions
141
tests/examples_methods_syntax/scatter_point_paths_hover.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
""" | ||
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( | ||
alt.X('fertility:Q').scale(zero=False).title('Babies per woman (total fertility rate)'), | ||
alt.Y('life_expect:Q').scale(zero=False).title('Life expectancy'), | ||
alt.Color('region:N').scale(scheme='dark2').legend(orient='bottom-left', titleFontSize=14, labelFontSize=12).title('Region'), | ||
alt.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( | ||
alt.Order('year:Q').sort('ascending'), | ||
alt.Size('year:Q').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 |