Li Ai
Li Ai

Reputation: 211

Multi-Line Interactive Plot with drop-down menu

I am trying to create a plot similar to this one:

https://altair-viz.github.io/gallery/multiline_tooltip.html

I want to add a drop-down menu to select different objects.

I've modified my code to create an example to illustrate:

import altair as alt
import pandas as pd
import numpy as np

np.random.seed(42)
source = pd.DataFrame(np.cumsum(np.random.randn(100, 3), 0).round(2),
                    columns=['A', 'B', 'C'], index=pd.RangeIndex(100, name='x'))
source = source.reset_index().melt('x', var_name='category', value_name='y')
source['Type'] = 'First'

source_1 = source.copy()
source_1['y'] = source_1['y'] + 5
source_1['Type'] = 'Second'

source_2 = source.copy()
source_2['y'] = source_2['y'] - 5
source_2['Type'] = 'Third'

source = pd.concat([source, source_1, source_2])

input_dropdown = alt.binding_select(options=['First', 'Second', 'Third'])
selection = alt.selection_single(name='Select', fields=['Type'],
                                   bind=input_dropdown)

# color = alt.condition(select_state,
#                       alt.Color('Type:N', legend=None),
#                       alt.value('lightgray'))

# Create a selection that chooses the nearest point & selects based on x-value
nearest = alt.selection(type='single', nearest=True, on='mouseover',
                        fields=['x'], empty='none')

# The basic line
base = alt.Chart(source).encode(
    x='x:Q',
    y='y:Q',
    color='category:N'
)

# add drop-down menu
lines = base.mark_line(interpolate='basis').add_selection(selection
).transform_filter(selection)

# Transparent selectors across the chart. This is what tells us
# the x-value of the cursor
selectors = alt.Chart(source).mark_point().encode(
    x='x:Q',
    opacity=alt.value(0),
).add_selection(
    nearest
)

# Draw points on the line, and highlight based on selection
points = base.mark_point().encode(
    opacity=alt.condition(nearest, alt.value(1), alt.value(0))
)

# Draw text labels near the points, and highlight based on selection
text = base.mark_text(align='left', dx=5, dy=-5).encode(
    text=alt.condition(nearest, 'y:Q', alt.value(' '))
)

# Draw a rule at the location of the selection
rules = alt.Chart(source).mark_rule(color='gray').encode(
    x='x:Q',
).transform_filter(
    nearest
)

#Put the five layers into a chart and bind the data
alt.layer(
    lines, selectors, points, rules, text
).properties(
    width=500, height=300
)

As you can see, every time I select one type ('First', 'Second' or 'Third), the interactive plot still shows the points from all the three instead of just one, although only the lines of one type are shown.


Original Question:

I am trying to create a plot similar to this one:

https://altair-viz.github.io/gallery/multiline_tooltip.html

with export, import and deficit. I want to add a drop-down menu to select different states (so each state will have such a plot).

My data looks like this:

    State           Year    Category        Trade, in Million Dollars
0   Texas           2008     Export         8970.979210
1   California      2008    Export          11697.850116
2   Washington      2008    Import          8851.678608
3   South Carolina  2008     Deficit        841.495319
4   Oregon          2008     Import         2629.939168

I've tried a few different methods but all failed. If I only want to plot 'line' object, I can do it fine. But I cannot combine 'line' with 'points'.

import altair as alt

states = list(df_trade_china.State.unique())
input_dropdown = alt.binding_select(options=states)
select_state = alt.selection_single(name='Select', fields=['State'],
                                   bind=input_dropdown)

# Create a selection that chooses the nearest point & selects based on x-value
nearest = alt.selection(type='single', nearest=True, on='mouseover',
                        fields=['Year'], empty='none')

# The basic line
line = alt.Chart(df_trade_china).mark_line().encode(
    x='Year:O',
    y='Trade, in Million Dollars:Q',
    color='Category:N'
).add_selection(select_state
).transform_filter(select_state
)

# Transparent selectors across the chart. This is what tells us
# the x-value of the cursor
selectors = alt.Chart(df_trade_china).mark_point().encode(
    x='Year:O',
    opacity=alt.value(0),
).add_selection(
    nearest
)

# Draw points on the line, and highlight based on selection
points = line.mark_point().encode(
    opacity=alt.condition(nearest, alt.value(1), alt.value(0))
)

# Draw text labels near the points, and highlight based on selection
text = line.mark_text(align='left', dx=5, dy=-5).encode(
    text=alt.condition(nearest, 'Trade, in Million Dollars:Q', alt.value(' '))
)

# Draw a rule at the location of the selection
rules = alt.Chart(df_trade_china).mark_rule(color='gray').encode(
    x='Year:Q',
).transform_filter(
    nearest
)

#Put the five layers into a chart and bind the data
alt.layer(
    line
).properties(
    width=500, height=300
)

Here is the error message I got.

JavaScript Error: Duplicate signal name: "Select_tuple"

This usually means there's a typo in your chart specification. See the javascript console for the full traceback.

Upvotes: 2

Views: 2039

Answers (1)

jakevdp
jakevdp

Reputation: 86443

New answer to new question:

Your filter transform is only being applied to the line data, and so it only filters the lines. If you want to filter every layer, make certain that every layer has the filter transform.

Here is how that would look with your code:

import altair as alt
import pandas as pd
import numpy as np

np.random.seed(42)
source = pd.DataFrame(np.cumsum(np.random.randn(100, 3), 0).round(2),
                    columns=['A', 'B', 'C'], index=pd.RangeIndex(100, name='x'))
source = source.reset_index().melt('x', var_name='category', value_name='y')
source['Type'] = 'First'

source_1 = source.copy()
source_1['y'] = source_1['y'] + 5
source_1['Type'] = 'Second'

source_2 = source.copy()
source_2['y'] = source_2['y'] - 5
source_2['Type'] = 'Third'

source = pd.concat([source, source_1, source_2])

input_dropdown = alt.binding_select(options=['First', 'Second', 'Third'])
selection = alt.selection_single(name='Select', fields=['Type'],
                                   bind=input_dropdown, init={'Type': 'First'})

# color = alt.condition(select_state,
#                       alt.Color('Type:N', legend=None),
#                       alt.value('lightgray'))

# Create a selection that chooses the nearest point & selects based on x-value
nearest = alt.selection(type='single', nearest=True, on='mouseover',
                        fields=['x'], empty='none')

# The basic line
base = alt.Chart(source).encode(
    x='x:Q',
    y='y:Q',
    color='category:N'
).transform_filter(
    selection
)

# add drop-down menu
lines = base.mark_line(interpolate='basis').add_selection(selection
)

# Transparent selectors across the chart. This is what tells us
# the x-value of the cursor
selectors = alt.Chart(source).mark_point().encode(
    x='x:Q',
    opacity=alt.value(0),
).add_selection(
    nearest
)

# Draw points on the line, and highlight based on selection
points = base.mark_point().encode(
    opacity=alt.condition(nearest, alt.value(1), alt.value(0))
)

# Draw text labels near the points, and highlight based on selection
text = base.mark_text(align='left', dx=5, dy=-5).encode(
    text=alt.condition(nearest, 'y:Q', alt.value(' '))
)

# Draw a rule at the location of the selection
rules = alt.Chart(source).mark_rule(color='gray').encode(
    x='x:Q',
).transform_filter(
    nearest
)

#Put the five layers into a chart and bind the data
alt.layer(
    lines, selectors, points, rules, text
).properties(
    width=500, height=300
)

Original answer to original question:

An individual selection can only be added to a chart once. When you write something like this:

line = alt.Chart(...).add_selection(selection)

points = line.mark_point()

the same selection is added in both line and points (because points is derived from line). When you layer them, each layer declares an identical selection, which leads to a Duplicate Signal Name error.

To fix this, avoid adding the same selection to multiple components of a single chart.

For example, you can do something like this (switching to an example dataset, because you didn't provide data in your question):

import altair as alt
from vega_datasets import data

stocks = data.stocks()
stocks.symbol.unique().tolist()

input_dropdown = alt.binding_select(options=stocks.symbol.unique().tolist())
selection = alt.selection_single(fields=['symbol'], bind=input_dropdown,
                                 name='Company', init={'symbol': 'GOOG'})
color = alt.condition(selection,
                      alt.Color('symbol:N', legend=None),
                      alt.value('lightgray'))


base = alt.Chart(stocks).encode(
    x='date',
    y='price',
    color=color
)

line = base.mark_line().add_selection(
    selection
)

point = base.mark_point()

line + point

enter image description here

Note that the selection declaration via add_selection() can only be called on a single component of the chart, while the effect of the selection (here, the color condition) can be added to multiple components of the chart.

Upvotes: 1

Related Questions