{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Interactivity and Selections\n", "\n", "Altair's interactivity and grammar of selections are one of its unique features among available plotting libraries.\n", "In this section, we will walk through the variety of selection types that are available, and begin to practice creating interactive charts and dashboards.\n", "\n", "There are three basic types of selections available:\n", "\n", "- Interval Selection: ``alt.selection_interval()``\n", "- Single Selection: ``alt.selection_single()``\n", "- Multi Selection: ``alt.selection_multi()``\n", "\n", "And we will cover four basic things that you can do with these selections\n", "\n", "- Conditional encodings\n", "- Scales\n", "- Filters\n", "- Domains" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import altair as alt\n", "from vega_datasets import data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic Interactions: Panning, Zooming, Tooltips\n", "\n", "The basic interactions that Altair makes available are panning, zooming, and tooltips.\n", "This can be done in your chart without any use of the selection interface, using the\n", "``interactive()`` shortcut method and the ``tooltip`` encoding.\n", "\n", "For example, with our standard cars dataset, we can do the following:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NameMiles_per_GallonCylindersDisplacementHorsepowerWeight_in_lbsAccelerationYearOrigin
0chevrolet chevelle malibu18.08307.0130.0350412.01970-01-01USA
1buick skylark 32015.08350.0165.0369311.51970-01-01USA
2plymouth satellite18.08318.0150.0343611.01970-01-01USA
3amc rebel sst16.08304.0150.0343312.01970-01-01USA
4ford torino17.08302.0140.0344910.51970-01-01USA
\n", "
" ], "text/plain": [ " Name Miles_per_Gallon Cylinders Displacement \\\n", "0 chevrolet chevelle malibu 18.0 8 307.0 \n", "1 buick skylark 320 15.0 8 350.0 \n", "2 plymouth satellite 18.0 8 318.0 \n", "3 amc rebel sst 16.0 8 304.0 \n", "4 ford torino 17.0 8 302.0 \n", "\n", " Horsepower Weight_in_lbs Acceleration Year Origin \n", "0 130.0 3504 12.0 1970-01-01 USA \n", "1 165.0 3693 11.5 1970-01-01 USA \n", "2 150.0 3436 11.0 1970-01-01 USA \n", "3 150.0 3433 12.0 1970-01-01 USA \n", "4 140.0 3449 10.5 1970-01-01 USA " ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cars = data.cars()\n", "cars.head()" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color='Origin',\n", " tooltip='Name'\n", ").interactive()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At this point, hovering over a point will bring up a tooltip with the name of the car model, and clicking/dragging/scrolling will pan and zoom on the plot." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## More Sophisticated Interaction: Selections" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Basic Selection Example: Interval\n", "\n", "As an example of a selection, let's add an interval selection to a chart.\n", "\n", "We'll start with our cannonical scatter plot:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NameMiles_per_GallonCylindersDisplacementHorsepowerWeight_in_lbsAccelerationYearOrigin
0chevrolet chevelle malibu18.08307.0130.0350412.01970-01-01USA
1buick skylark 32015.08350.0165.0369311.51970-01-01USA
2plymouth satellite18.08318.0150.0343611.01970-01-01USA
3amc rebel sst16.08304.0150.0343312.01970-01-01USA
4ford torino17.08302.0140.0344910.51970-01-01USA
\n", "
" ], "text/plain": [ " Name Miles_per_Gallon Cylinders Displacement \\\n", "0 chevrolet chevelle malibu 18.0 8 307.0 \n", "1 buick skylark 320 15.0 8 350.0 \n", "2 plymouth satellite 18.0 8 318.0 \n", "3 amc rebel sst 16.0 8 304.0 \n", "4 ford torino 17.0 8 302.0 \n", "\n", " Horsepower Weight_in_lbs Acceleration Year Origin \n", "0 130.0 3504 12.0 1970-01-01 USA \n", "1 165.0 3693 11.5 1970-01-01 USA \n", "2 150.0 3436 11.0 1970-01-01 USA \n", "3 150.0 3433 12.0 1970-01-01 USA \n", "4 140.0 3449 10.5 1970-01-01 USA " ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cars = data.cars()\n", "cars.head()" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color='Origin'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To add selection behavior to a chart, we create the selection object and use the `add_selection` method:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "interval = alt.selection_interval()\n", "\n", "alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color='Origin'\n", ").add_selection(\n", " interval\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This adds an interaction to the plot that lets us select points on the plot; perhaps the most common use of a selection is to highlight points by conditioning their color on the result of the selection.\n", "\n", "This can be done with ``alt.condition``:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "interval = alt.selection_interval()\n", "\n", "alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=alt.condition(interval, 'Origin', alt.value('lightgray'))\n", ").add_selection(\n", " interval\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ``alt.condition`` function takes three arguments: a selection object, a value to be applied to points within the selection, and a value to be applied to points outside the selection.\n", "Here we use ``alt.value('lightgray')`` to make certain that the color is treated as an actual color, rather than the name of a data column." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Customizing the Interval selection\n", "\n", "The ``alt.selection_interval()`` function takes a number of additional arguments; for example, by specifying ``encodings``, we can control whether the selection covers x, y, or both:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "interval = alt.selection_interval(encodings=['x'])\n", "\n", "alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=alt.condition(interval, 'Origin', alt.value('lightgray'))\n", ").add_selection(\n", " interval\n", ")" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "interval = alt.selection_interval(encodings=['y'])\n", "\n", "alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=alt.condition(interval, 'Origin', alt.value('lightgray'))\n", ").add_selection(\n", " interval\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ``empty`` argument lets us control whether empty selections contain *all* values, or *none* of the values;\n", "with ``empty='none'`` points are grayed-out by default:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "interval = alt.selection_interval(empty='none')\n", "\n", "alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=alt.condition(interval, 'Origin', alt.value('lightgray'))\n", ").add_selection(\n", " interval\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Single Selections\n", "\n", "The ``alt.selection_single()`` function allows the user to click on single chart objects to select them, one at a time.\n", "We'll make the points a bit bigger so they are easier to click:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "single = alt.selection_single()\n", "\n", "alt.Chart(cars).mark_circle(size=100).encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=alt.condition(single, 'Origin', alt.value('lightgray'))\n", ").add_selection(\n", " single\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The single selection allows other behavior as well; for example, we can set ``nearest=True`` and ``on='mouseover'`` to update the highlight to the nearest point as we move the mouse:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "single = alt.selection_single(on='mouseover', nearest=True)\n", "\n", "alt.Chart(cars).mark_circle(size=100).encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=alt.condition(single, 'Origin', alt.value('lightgray'))\n", ").add_selection(\n", " single\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Multi Selection\n", "\n", "The ``alt.selection_multi()`` function is quite similar to the ``single`` function, except it lets multiple points be selected at once, while holding the shift key:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "multi = alt.selection_multi()\n", "\n", "alt.Chart(cars).mark_circle(size=100).encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=alt.condition(multi, 'Origin', alt.value('lightgray'))\n", ").add_selection(\n", " multi\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Options like ``on`` and ``nearest`` also work for multi selections:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "multi = alt.selection_multi(on='mouseover', nearest=True)\n", "\n", "alt.Chart(cars).mark_circle(size=100).encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=alt.condition(multi, 'Origin', alt.value('lightgray'))\n", ").add_selection(\n", " multi\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Selection Binding\n", "\n", "Above we have seen how ``alt.condition`` can be used to bind the selection to different aspects of the chart.\n", "Let's look at a few other ways that a selection can be used:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Binding Scales\n", "For an interval selection, another thing you can do with the selection is bind the selection region to the chart scales:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bind = alt.selection_interval(bind='scales')\n", "\n", "alt.Chart(cars).mark_circle(size=100).encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color='Origin:N'\n", ").add_selection(\n", " bind\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is essentially what the ``chart.interactive()`` method does under the hood." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Binding Scales to Other Domains\n", "\n", "It is also possible to bind scales to other domains, which can be useful in creating" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
dateprecipitationtemp_maxtemp_minwindweather
02012-01-010.012.85.04.7drizzle
12012-01-0210.910.62.84.5rain
22012-01-030.811.77.22.3rain
32012-01-0420.312.25.64.7rain
42012-01-051.38.92.86.1rain
\n", "
" ], "text/plain": [ " date precipitation temp_max temp_min wind weather\n", "0 2012-01-01 0.0 12.8 5.0 4.7 drizzle\n", "1 2012-01-02 10.9 10.6 2.8 4.5 rain\n", "2 2012-01-03 0.8 11.7 7.2 2.3 rain\n", "3 2012-01-04 20.3 12.2 5.6 4.7 rain\n", "4 2012-01-05 1.3 8.9 2.8 6.1 rain" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "weather = data.seattle_weather()\n", "weather.head()" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "base = alt.Chart(weather).mark_rule().encode(\n", " x='date:T',\n", " y='temp_min:Q',\n", " y2='temp_max:Q',\n", " color='weather:N'\n", ")\n", "\n", "base" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.VConcatChart(...)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "chart = base.properties(\n", " width=800,\n", " height=300\n", ")\n", "\n", "view = chart.properties(\n", " width=800,\n", " height=50\n", ")\n", "\n", "chart & view" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's add an interval selection to the bottom chart that will control the domain of the top chart:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.VConcatChart(...)" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "interval = alt.selection_interval(encodings=['x'])\n", "\n", "base = alt.Chart(weather).mark_rule(size=2).encode(\n", " x='date:T',\n", " y='temp_min:Q',\n", " y2='temp_max:Q',\n", " color='weather:N'\n", ")\n", "\n", "chart = base.encode(\n", " x=alt.X('date:T', scale=alt.Scale(domain=interval.ref()))\n", ").properties(\n", " width=800,\n", " height=300\n", ")\n", "\n", "view = base.add_selection(\n", " interval\n", ").properties(\n", " width=800,\n", " height=50,\n", ")\n", "\n", "chart & view" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Filtering by Selection\n", "\n", "In multi-panel charts, we can use the result of the selection to filter other views of the data.\n", "For example, here is a scatter-plot along with a histogram" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.VConcatChart(...)" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "interval = alt.selection_interval()\n", "\n", "scatter = alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=alt.condition(interval, 'Origin:N', alt.value('lightgray'))\n", ").add_selection(\n", " interval\n", ")\n", "\n", "hist = alt.Chart(cars).mark_bar().encode(\n", " x='count()',\n", " y='Origin',\n", " color='Origin'\n", ").transform_filter(\n", " interval\n", ")\n", "\n", "scatter & hist" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly, you can use a Multi selection to go the other way (allow clicking on the bar chart to filter the contents of the scatter plot.\n", "We'll add this to the previous chart:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.VConcatChart(...)" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "click = alt.selection_multi(encodings=['color'])\n", "\n", "scatter = alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color='Origin:N'\n", ").transform_filter(\n", " click\n", ")\n", "\n", "hist = alt.Chart(cars).mark_bar().encode(\n", " x='count()',\n", " y='Origin',\n", " color=alt.condition(click, 'Origin', alt.value('lightgray'))\n", ").add_selection(\n", " click\n", ")\n", "\n", "scatter & hist" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Selections Summary\n", "\n", "- Selection Types:\n", "\n", " - ``selection_interval()``\n", " - ``selection_single()``\n", " - ``selection_multi()``\n", " \n", "- Bindings\n", "\n", " - bind scales: drag & scroll to interact with plot\n", " - bind scales on another chart\n", " - conditional encodings (e.g. color, size)\n", " - filter data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise: Selections\n", "\n", "Here you have a chance to try this yourself! Choose one or more of the following interactive examples to create:\n", "\n", "1. Using the cars data, create a scatter-plot where the *size* of the points becomes larger as you hover over them.\n", "\n", "2. Using the cars data, create a two-panel histogram (say, miles per gallon counts in one panel, horsepower counts in the other) where you can drag your mouse to select data in the left panel to filter the data in the second panel.\n", "\n", "3. Change the above scatter-plus-histogram example we did above so that\n", " - you can pan and zoom on the scatterplot\n", " - the histogram only reflects points that are *visible at the given moment*\n", " \n", "4. Try something new!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.6" } }, "nbformat": 4, "nbformat_minor": 4 }