Многолинейный интерактивный график с раскрывающимся меню

Я пытаюсь создать сюжет, похожий на этот:

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

Я хочу добавить раскрывающееся меню для выбора разных объектов.

Я изменил свой код, чтобы создать пример для иллюстрации:

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
)

Как видите, каждый раз, когда я выбираю один тип («Первый», «Второй» или «Третий»), на интерактивном графике по-прежнему отображаются точки из всех трех, а не только один, хотя отображаются только линии одного типа.


Исходный вопрос:

Я пытаюсь создать сюжет, похожий на этот:

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

с экспортом, импортом и дефицитом. Я хочу добавить раскрывающееся меню для выбора разных состояний (чтобы у каждого состояния был такой график).

Мои данные выглядят так:

    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

Я пробовал несколько разных методов, но все потерпели неудачу. Если я хочу только построить объект «линия», я могу сделать это нормально. Но я не могу сочетать «линию» с «точками».

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
)

Вот сообщение об ошибке, которое я получил.

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.

person Li Ai    schedule 05.06.2019    source источник


Ответы (1)


Новый ответ на новый вопрос:

Ваше преобразование фильтра применяется только к строковым данным, поэтому оно фильтрует только строки. Если вы хотите отфильтровать каждый слой, убедитесь, что каждый слой имеет преобразование фильтра.

Вот как это будет выглядеть с вашим кодом:

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
)

Оригинальный ответ на исходный вопрос:

Отдельный выбор можно добавить на диаграмму только один раз. Когда вы пишете что-то вроде этого:

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

points = line.mark_point()

тот же выбор добавлен как в line, так и в points (поскольку points является производным от line). Когда вы накладываете их на слои, каждый слой объявляет идентичный выбор, что приводит к ошибке Duplicate Signal Name.

Чтобы исправить это, избегайте добавления одного и того же выделения к нескольким компонентам одной диаграммы.

Например, вы можете сделать что-то вроде этого (переключиться на пример набора данных, потому что вы не предоставили данные в своем вопросе):

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

введите здесь описание изображения

Обратите внимание, что объявление выбора через add_selection() может быть вызвано только для одного компонента диаграммы, в то время как эффект выбора (здесь условие цвета) может быть добавлен к нескольким компонентам диаграммы.

person jakevdp    schedule 05.06.2019
comment
Как я могу это сделать? Я хочу иметь возможность выбирать только один объект из раскрывающегося меню за раз и показывать только его точки, поэтому кажется, что points должен быть производным от line. Например, когда я изменил код на # 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 drop-down menu line_plot = line.add_selection(select_state ).transform_filter(select_state ) , points выберите все объекты вместо одного из раскрывающегося меню. - person Li Ai; 05.06.2019
comment
Код не работает в цепочках комментариев. Я отредактировал ответ примером. - person jakevdp; 05.06.2019
comment
Ваше преобразование фильтра работает только с строкой, поэтому вы фильтруете только строки. Поместите преобразование фильтра в базу, чтобы отфильтровать все. Я отредактировал свой ответ ответом на ваш новый вопрос. - person jakevdp; 05.06.2019
comment
Пожалуйста, в будущем воздержитесь от существенного редактирования содержания вашего вопроса, если на него уже был дан ответ. Если у вас есть другой вопрос, откройте новый вопрос. - person jakevdp; 05.06.2019
comment
Я сделаю это. в будущем - я изначально не сформулировал свой вопрос четко. - person Li Ai; 05.06.2019