Reputation: 526
I would like to have two interactive legends, similar to the second example in this section of the Altair docs, but without the rectangular legend. One legend should control the color via one categorical variable ("First name"), while the second should control the marker via another categorical variable ("Last name"). While I'm able to get the aesthetics right (see below plot), the interactivity of the second legend leads to unexpected behavior.
The code I used to generate the above figure is below:
import pandas as pd
import altair as alt
# Create dataframe
data = [[7, 10, 'Alex', 'Smith'],
[12, 20, 'Bob', 'Jones'],
[10, 30, 'Clive', 'Smith'],
[42, 40, 'Alex', 'Johnson']]
df = pd.DataFrame(data,columns=['Favorite number', 'Age', 'First name', 'Last name'])
# Create selections
selection_first_name = alt.selection_multi(fields=['First name'])
selection_last_name = alt.selection_multi(fields=['Last name'])
# Create first name conditions
color_first_name = alt.condition(selection_first_name,
alt.Color('First name:N', legend=None),
alt.value('lightgray'))
# Create last name conditions
shape_last_name = alt.condition(selection_last_name,
alt.Shape('Last name:N', legend=None),
alt.value('lightgray'))
# Create interactive scatter plot
scatter = alt.Chart(df).mark_point(size=100).encode(
x='Favorite number:Q',
y='Age:Q',
color=color_first_name,
shape=shape_last_name,
tooltip=['First name', 'Last name']
).add_selection(
selection_first_name
).add_selection(
selection_last_name
).add_selection(
alt.selection_interval(bind='scales')
)
# Create interactive model name legend
legend_first_name = alt.Chart(df).mark_point(size=100).encode(
y=alt.Y('First name:N', axis=alt.Axis(orient='right')),
color=color_first_name
).add_selection(
selection_first_name
)
# Create interactive model name legend
legend_last_name = alt.Chart(df).mark_point(size=100).encode(
y=alt.Y('Last name:N', axis=alt.Axis(orient='right')),
shape=shape_last_name
).add_selection(
selection_last_name
)
# Combine plotting elements
chart = scatter | legend_first_name | legend_last_name
The resulting HTML chart is here:
<!DOCTYPE html>
<html>
<head>
<style>
.vega-actions a {
margin-right: 12px;
color: #757575;
font-weight: normal;
font-size: 13px;
}
.error {
color: red;
}
</style>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm//vega@5"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm//[email protected]"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm//vega-embed@4"></script>
</head>
<body>
<div id="vis"></div>
<script>
(function(vegaEmbed) {
var spec = {"config": {"view": {"width": 400, "height": 300}, "mark": {"tooltip": null}}, "hconcat": [{"mark": {"type": "point", "size": 100}, "encoding": {"color": {"condition": {"type": "nominal", "field": "First name", "legend": null, "selection": "selector001"}, "value": "lightgray"}, "shape": {"condition": {"type": "nominal", "field": "Last name", "legend": null, "selection": "selector002"}, "value": "lightgray"}, "tooltip": [{"type": "nominal", "field": "First name"}, {"type": "nominal", "field": "Last name"}], "x": {"type": "quantitative", "field": "Favorite number"}, "y": {"type": "quantitative", "field": "Age"}}, "selection": {"selector001": {"type": "multi", "fields": ["First name"]}, "selector002": {"type": "multi", "fields": ["Last name"]}, "selector003": {"type": "interval", "bind": "scales"}}}, {"mark": {"type": "point", "size": 100}, "encoding": {"color": {"condition": {"type": "nominal", "field": "First name", "legend": null, "selection": "selector001"}, "value": "lightgray"}, "y": {"type": "nominal", "axis": {"orient": "right"}, "field": "First name"}}, "selection": {"selector001": {"type": "multi", "fields": ["First name"]}}}, {"mark": {"type": "point", "size": 100}, "encoding": {"shape": {"condition": {"type": "nominal", "field": "Last name", "legend": null, "selection": "selector002"}, "value": "lightgray"}, "y": {"type": "nominal", "axis": {"orient": "right"}, "field": "Last name"}}, "selection": {"selector002": {"type": "multi", "fields": ["Last name"]}}}], "data": {"name": "data-ec767a72044047bc9825da631c9465fc"}, "$schema": "https://vega.github.io/schema/vega-lite/v3.4.0.json", "datasets": {"data-ec767a72044047bc9825da631c9465fc": [{"Favorite number": 7, "Age": 10, "First name": "Alex", "Last name": "Smith"}, {"Favorite number": 12, "Age": 20, "First name": "Bob", "Last name": "Jones"}, {"Favorite number": 10, "Age": 30, "First name": "Clive", "Last name": "Smith"}, {"Favorite number": 42, "Age": 40, "First name": "Alex", "Last name": "Johnson"}]}};
var embedOpt = {"mode": "vega-lite"};
function showError(el, error){
el.innerHTML = ('<div class="error" style="color:red;">'
+ '<p>JavaScript Error: ' + error.message + '</p>'
+ "<p>This usually means there's a typo in your chart specification. "
+ "See the javascript console for the full traceback.</p>"
+ '</div>');
throw error;
}
const el = document.getElementById('vis');
vegaEmbed("#vis", spec, embedOpt)
.catch(error => showError(el, error));
})(vegaEmbed);
</script>
</body>
</html>
Upvotes: 1
Views: 7551
Reputation: 86473
Your shape encoding has a condition that looks like this:
shape_last_name = alt.condition(selection_last_name,
alt.Shape('Last name:N', legend=None),
alt.value('lightgray'))
it specifies that when a point is not selected, it should have the shape named lightgray
. lightgray
is not a valid shape, so no points are drawn.
If you want to change the color based on the shape selection, you should put the condition in the color encoding. If you want to change the shape based on the shape legend selection, you should use a valid shape value within the condition.
I suspect what you were after was something like this:
import pandas as pd
import altair as alt
# Create dataframe
data = [[7, 10, 'Alex', 'Smith'],
[12, 20, 'Bob', 'Jones'],
[10, 30, 'Clive', 'Smith'],
[42, 40, 'Alex', 'Johnson']]
df = pd.DataFrame(data,columns=['Favorite number', 'Age', 'First name', 'Last name'])
# Create selections
selection_first_name = alt.selection_multi(fields=['First name'], empty='none')
selection_last_name = alt.selection_multi(fields=['Last name'], empty='none')
# Create interactive scatter plot
scatter = alt.Chart(df).mark_point(size=100).encode(
x='Favorite number:Q',
y='Age:Q',
color=alt.condition(selection_first_name & selection_last_name,
alt.Color('First name:N', legend=None),
alt.value('lightgray') ),
shape=alt.Shape('Last name:N', legend=None),
tooltip=['First name', 'Last name']
).add_selection(
alt.selection_interval(bind='scales')
)
# Create interactive model name legend
legend_first_name = alt.Chart(df).mark_point(size=100).encode(
y=alt.Y('First name:N', axis=alt.Axis(orient='right')),
color=alt.condition(selection_first_name,
alt.Color('First name:N', legend=None),
alt.value('lightgray') ),
).add_selection(
selection_first_name
)
# Create interactive model name legend
legend_last_name = alt.Chart(df).mark_point(size=100).encode(
y=alt.Y('Last name:N', axis=alt.Axis(orient='right')),
shape=alt.Shape('Last name:N', legend=None),
color=alt.condition(selection_last_name,
alt.value('black'),
alt.value('lightgray') ),
).add_selection(
selection_last_name
)
# Combine plotting elements
chart = scatter | legend_first_name | legend_last_name
Upvotes: 4