4

I am plotting a choropleth map colored by the field Passenger_0_ and a line chart showing the evolution of Passenger_0_ throughout a day by zone.

I would like to select a line (zone) in the line chart and have it highlighted in the map and vice versa (select a zone in the map and have it highlighted in the line chart).

For now, I am able to change the whole color of the map when selecting the line, but have no clue of how to only change the color of the selected area.

I would appreciate any help.

In order to reproduce the sample you need to download these two files:

output_data.csv

taxi_zones.geojson

Then run this piece of code to get a GeoDataFrame named long_df:

import altair as alt
import pandas as pd
import geopandas as gpd
import json

geo_json_file_loc= './taxi_zones.geojson'

with open(geo_json_file_loc) as json_data:
    data = json.load(json_data)
    
gdf = gpd.GeoDataFrame.from_features((data))
gdf = gdf[gdf['borough']=='Manhattan']
gdf = gdf[['location_id','zone','geometry']]
gdf = gdf.rename(columns={'location_id':'LocationID'})
gdf['LocationID'] = pd.to_numeric(gdf['LocationID'])

output_data = pd.read_csv('./output_data.csv',sep=',')

def load_taxis_data(output_data, shape_data):
    df_to_visualize = shape_data.copy()
    pickups = output_data.groupby(['hour','dayofweek','LocationID']).sum()
    listofdays = pd.unique(output_data['dayofweek'])

    for hour in range(24):
        for dayofweek in listofdays:
            # get pickups for this hour and weekday
            p = pd.DataFrame(pickups.loc[(hour, dayofweek)]).reset_index()
        
            # add pickups to the Taxi Zones DataFrame       
            df_to_visualize = pd.merge(df_to_visualize, p, on="LocationID", how="left").fillna(0)
            # rename column as per day and hour
            df_to_visualize.rename(columns={"pickups" : "Passenger_%d_%d"%(dayofweek, hour)}, inplace=True)
    return df_to_visualize        

gdf_merged = load_taxis_data(output_data, gdf)

# drop unwanted days
for hour in range(24):
    for dayofweek in [5,6]:
        column_to_drop = "Passenger_%d_%d"%(dayofweek, hour)
        gdf_merged.drop([column_to_drop], axis=1, inplace=True)

gdf_merged.reset_index(level=0, inplace=True)

long_df = pd.wide_to_long(gdf_merged, ["Passenger_0_"], i='index', j="hour")
long_df = long_df.reset_index()

Once you got long_df this is the code for the plots:

dict_json = json.loads(long_df[long_df['hour']==0].to_json())

colours_obj = alt.Color('properties.Passenger_0_:Q',
              scale=alt.Scale(scheme='yelloworangered'),
             title = "Pickups")

sel_line_hover = alt.selection_single(on='mouseover', empty='none')
sel_line_col = alt.selection_single()
sel_line_size = alt.selection_single(empty='none')

base = alt.Chart(alt.Data(values=dict_json['features'])).mark_geoshape(
    stroke='black',
    strokeWidth=1
    ).encode(
    color=alt.condition(sel_line_col, colours_obj, alt.value('lightgray')),
    tooltip = ['properties.zone:O',
               'properties.Passenger_0_:Q']
    ).properties(
    width=350,
    height=750,
    ).add_selection(
    sel_line_col
    )

line = alt.Chart(long_df).mark_line().encode(
    x='hour',
    y='Passenger_0_',
    color=alt.condition(sel_line_hover|sel_line_col, 'zone', alt.value('lightgray')),
    size=alt.condition(sel_line_hover|sel_line_size, alt.value(4),alt.value(1)),
    tooltip = ['zone:O']
    ).properties(
    width=250,
    height=750,
    ).add_selection(
    sel_line_hover,sel_line_col,sel_line_size
    )

base | line

And this is what the plot does: enter image description here

Thank you in advance for your help!

angelrps
  • 77
  • 7
  • You'll be much more likely to get a useful answer to your question if you can provide a [Minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) demonstrating the issue you're seeing. When I run your code snippet I get `NameError: name 'long_df' is not defined` – jakevdp Sep 13 '20 at 15:46
  • Apologies @jakevdp you are right. I wanted to simplify the message so much that didn´t thought that actually it was impossible to reproduce the example with the information provided. I have edited the question, adding links to a ``.geojson`` and ``.csv`` files needed to calculate ``long_df```, and the piece of code necessary to calculate ``long_df```. I am very grateful for your time and help. – angelrps Sep 15 '20 at 07:18

1 Answers1

5

Here is a general example of how to achieve two-way interactivity in Altair, using data from the sample repos only. The key is to set the feature that should be filtered by the selection even via fields parameter when creating the selection. Then you add this selection and the corresponding condition to the same encoding of both the plots.

import altair as alt
from vega_datasets import data


state_pop = data.population_engineers_hurricanes()[['state', 'id', 'population']]
state_map = alt.topo_feature(data.us_10m.url, 'states')

click = alt.selection_multi(fields=['state'])

choropleth = (alt.Chart(state_map).mark_geoshape().transform_lookup(
    lookup='id',
    from_=alt.LookupData(state_pop, 'id', ['population', 'state']))
.encode(
    color='population:Q',
    opacity=alt.condition(click, alt.value(1), alt.value(0.2)),
    tooltip=['state:N', 'population:Q'])
.add_selection(click)
.project(type='albersUsa'))

bars = (
    alt.Chart(
        state_pop.nlargest(15, 'population'),
        title='Top 15 states by population').mark_bar().encode(
    x='population',
    opacity=alt.condition(click, alt.value(1), alt.value(0.2)),
    color='population',
    y=alt.Y('state', sort='x'))
.add_selection(click))

choropleth & bars

enter image description here

joelostblom
  • 43,590
  • 17
  • 150
  • 159