From 0fa404e00ab20331ad57078679ec8462f15c8562 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Mon, 11 May 2026 19:00:09 -0400 Subject: [PATCH 1/2] Correct notebook content to be in sync with code. --- altair_cartographic.ipynb | 18 ++------ altair_data_transformation.ipynb | 8 +--- altair_debugging.ipynb | 58 ++++---------------------- altair_interaction.ipynb | 71 ++++---------------------------- altair_introduction.ipynb | 53 +----------------------- altair_marks_encoding.ipynb | 26 +++--------- altair_scales_axes_legends.ipynb | 12 +----- marimo/01_introduction.py | 6 +-- marimo/02_marks_encoding.py | 4 +- marimo/03_data_transformation.py | 2 +- marimo/04_scales_axes_legends.py | 2 +- marimo/06_interaction.py | 24 +++++------ marimo/07_cartographic.py | 8 ++-- marimo/08_debugging.py | 8 ++-- uv.lock | 2 +- 15 files changed, 58 insertions(+), 244 deletions(-) diff --git a/altair_cartographic.ipynb b/altair_cartographic.ipynb index 0d546d1..8806495 100644 --- a/altair_cartographic.ipynb +++ b/altair_cartographic.ipynb @@ -926,13 +926,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "_To zoom in on a specific digit, add a filter transform to limit the data shown! Try adding an [interactive selection](https://github.com/uwdata/visualization-curriculum/blob/master/altair_interaction.ipynb) to filter to a single digit and dynamically update the map. And be sure to use strings (\\`'1'\\`) instead of numbers (\\`1\\`) when filtering digit values!_\n", - "\n", - "(This example is inspired by Ben Fry's classic [zipdecode](https://benfry.com/zipdecode/) visualization!)\n", - "\n", - "We might further wonder what the _sequence_ of zip codes might indicate. One way to explore this question is to connect each consecutive zip code using a `line` mark, as done in Robert Kosara's [ZipScribble](https://eagereyes.org/zipscribble-maps/united-states) visualization:" - ] + "source": "_To zoom in on a specific digit, add a filter transform to limit the data shown! Try adding an [interactive selection](https://github.com/uwdata/visualization-curriculum/blob/master/altair_interaction.ipynb) to filter to a single digit and dynamically update the map. And be sure to use strings (`'1'`) instead of numbers (`1`) when filtering digit values!_\n\n(This example is inspired by Ben Fry's classic [zipdecode](https://benfry.com/zipdecode/) visualization!)\n\nWe might further wonder what the _sequence_ of zip codes might indicate. One way to explore this question is to connect each consecutive zip code using a `line` mark, as done in Robert Kosara's [ZipScribble](https://eagereyes.org/zipscribble-maps/united-states) visualization:" }, { "cell_type": "code", @@ -1393,13 +1387,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "_Which U.S. airports have the highest number of outgoing routes?_\n", - "\n", - "Now that we can see the airports, which may wish to interact with them to better understand the structure of the air traffic network. We can add a `rule` mark layer to represent paths from `origin` airports to `destination` airports, which requires two `lookup` transforms to retrieve coordinates for each end point. In addition, we can use a `single` selection to filter these routes, such that only the routes originating at the currently selected airport are shown.\n", - "\n", - "_Starting from the static map above, can you build an interactive version? Feel free to skip the code below to engage with the interactive map first, and think through how you might build it on your own!_" - ] + "source": "_Which U.S. airports have the highest number of outgoing routes?_\n\nNow that we can see the airports, which may wish to interact with them to better understand the structure of the air traffic network. We can add a `rule` mark layer to represent paths from `origin` airports to `destination` airports, which requires two `lookup` transforms to retrieve coordinates for each end point. In addition, we can use a point selection to filter these routes, such that only the routes originating at the currently selected airport are shown.\n\n_Starting from the static map above, can you build an interactive version? Feel free to skip the code below to engage with the interactive map first, and think through how you might build it on your own!_" }, { "cell_type": "code", @@ -2264,4 +2252,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/altair_data_transformation.ipynb b/altair_data_transformation.ipynb index 4179d0b..326eed9 100644 --- a/altair_data_transformation.ipynb +++ b/altair_data_transformation.ipynb @@ -46,11 +46,7 @@ "colab_type": "text", "id": "yYtrAahLV3eU" }, - "source": [ - "We will be working with a table of data about motion pictures, taken from the [vega-datasets](https://vega.github.io/vega-datasets/) collection. The data includes variables such as the film name, director, genre, release date, ratings, and gross revenues. However, _be careful when working with this data_: the films are from unevenly sampled years, using data combined from multiple sources. If you dig in you will find issues with missing values and even some subtle errors! Nevertheless, the data should prove interesting to explore...\n", - "\n", - "Let's retrieve the URL for the JSON data file from the vega_datasets package, and then read the data into a Pandas data frame so that we can inspect its contents." - ] + "source": "We will be working with a table of data about motion pictures, taken from the [vega-datasets](https://vega.github.io/vega-datasets/) collection. The data includes variables such as the film name, director, genre, release date, ratings, and gross revenues. However, _be careful when working with this data_: the films are from unevenly sampled years, using data combined from multiple sources. If you dig in you will find issues with missing values and even some subtle errors! Nevertheless, the data should prove interesting to explore...\n\nLet's read the JSON data file into a Pandas data frame from a URL so that we can inspect its contents." }, { "cell_type": "code", @@ -2853,4 +2849,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/altair_debugging.ipynb b/altair_debugging.ipynb index 61cc4a3..d77d623 100644 --- a/altair_debugging.ipynb +++ b/altair_debugging.ipynb @@ -32,42 +32,14 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "These instructions follow [the Altair documentation](https://altair-viz.github.io/getting_started/installation.html) but focus on some specifics for this series of notebooks. \n", - "\n", - "In every notebook, we will import the [Altair](https://github.com/altair-viz/altair) and [Vega Datasets](https://github.com/altair-viz/vega_datasets) packages. If you are running this notebook on [Colab](https://colab.research.google.com), Altair and Vega Datasets should be preinstalled and ready to go. The notebooks in this series are designed for Colab but should also work in Jupyter Lab or the Jupyter Notebook (the notebook requires a bit more setup [described below](#Special-Setup-for-the-Jupyter-Notebook)) but additional packages are required. \n", - "\n", - "If you are running in Jupyter Lab or Jupyter Notebooks, you have to install the necessary packages by running the following command in your terminal.\n", - "\n", - "```bash\n", - "pip install altair vega_datasets\n", - "```\n", - "\n", - "Or if you use [Conda](https://conda.io)\n", - "\n", - "```bash\n", - "conda install -c conda-forge altair vega_datasets\n", - "```\n", - "\n", - "You can run command line commands from a code cell by prefixing it with `!`. For example, to install Altair and Vega Datasets with [Pip](https://pip.pypa.io/), you can run the following cell." - ] + "source": "These instructions follow [the Altair documentation](https://altair-viz.github.io/getting_started/installation.html) but focus on some specifics for this series of notebooks.\n\nIn every notebook, we will import the [Altair](https://github.com/altair-viz/altair) package. If you are running this notebook on [Colab](https://colab.research.google.com), Altair should be preinstalled and ready to go. The notebooks in this series are designed for Colab but should also work in Jupyter Lab or the Jupyter Notebook (the notebook requires a bit more setup [described below](#Special-Setup-for-the-Jupyter-Notebook)) but additional packages are required.\n\nIf you are running in Jupyter Lab or Jupyter Notebooks, you have to install the necessary packages by running the following command in your terminal.\n\n```bash\npip install altair\n```\n\nOr if you use [Conda](https://conda.io)\n\n```bash\nconda install -c conda-forge altair\n```\n\nYou can run command line commands from a code cell by prefixing it with `!`. For example, to install Altair with [Pip](https://pip.pypa.io/), you can run the following cell." }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/bin/bash: pip: command not found\n" - ] - } - ], - "source": [ - "!pip install altair vega_datasets" - ] + "outputs": [], + "source": "!pip install altair" }, { "cell_type": "code", @@ -142,13 +114,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "If you are not running the latest version, you can update it with `pip`. You can update Altair and Vega Datasets by running this command in your terminal.\n", - "\n", - "```\n", - "pip install -U altair vega_datasets\n", - "```" - ] + "source": "If you are not running the latest version, you can update it with `pip`. You can update Altair by running this command in your terminal.\n\n```\npip install -U altair\n```" }, { "cell_type": "markdown", @@ -302,20 +268,12 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "## Display Troubleshooting\n", - "\n", - "If you are having issues with seeing a chart, make sure your setup is correct by following the [debugging instruction above](#Installation). If you are still having issues, follow the [instruction about debugging display issues in the Altair documentation](https://iliatimofeev.github.io/altair-viz.github.io/user_guide/troubleshooting.html)." - ] + "source": "## Display Troubleshooting\n\nIf you are having issues with seeing a chart, make sure your setup is correct by following the [debugging instruction above](#Installation). If you are still having issues, follow the [instruction about debugging display issues in the Altair documentation](https://altair-viz.github.io/user_guide/troubleshooting.html)." }, { "cell_type": "markdown", "metadata": {}, - "source": [ - "### Non Existent Fields\n", - "\n", - "A common error is [accidentally using a field that does not exist](https://iliatimofeev.github.io/altair-viz.github.io/user_guide/troubleshooting.html#plot-displays-but-the-content-is-empty). " - ] + "source": "### Non Existent Fields\n\nA common error is [accidentally using a field that does not exist](https://altair-viz.github.io/user_guide/troubleshooting.html#plot-displays-but-the-content-is-empty). " }, { "cell_type": "code", @@ -735,4 +693,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/altair_interaction.ipynb b/altair_interaction.ipynb index aea42d1..ba18a2e 100644 --- a/altair_interaction.ipynb +++ b/altair_interaction.ipynb @@ -76,21 +76,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "Let's start with a basic selection: simply clicking a point to highlight it. Using the `cars` dataset, we'll start with a scatter plot of horsepower versus miles per gallon, with a color encoding for the number cylinders in the car engine.\n", - "\n", - "In addition, we'll create a selection instance by calling `alt.selection_single()`, indicating we want a selection defined over a _single value_. By default, the selection uses a mouse click to determine the selected value. To register a selection with a chart, we must add it using the `.add_selection()` method.\n", - "\n", - "Once our selection has been defined, we can use it as a parameter for _conditional encodings_, which apply a different encoding depending on whether a data record lies in or out of the selection. For example, consider the following code:\n", - "\n", - "~~~ python\n", - "color=alt.condition(selection, 'Cylinders:O', alt.value('grey'))\n", - "~~~\n", - "\n", - "This encoding definition states that data points contained within the `selection` should be colored according to the `Cylinder` field, while non-selected data points should use a default `grey`. An empty selection includes _all_ data points, and so initially all points will be colored.\n", - "\n", - "_Try clicking different points in the chart below. What happens? (Click the background to clear the selection state and return to an \"empty\" selection.)_" - ] + "source": "Let's start with a basic selection: simply clicking a point to highlight it. Using the `cars` dataset, we'll start with a scatter plot of horsepower versus miles per gallon, with a color encoding for the number cylinders in the car engine.\n\nIn addition, we'll create a selection instance by calling `alt.selection_point(toggle=False)`, indicating we want a selection defined over a _single value_. By default, the selection uses a mouse click to determine the selected value. To register a selection with a chart, we must add it using the `.add_params()` method.\n\nOnce our selection has been defined, we can use it as a parameter for _conditional encodings_, which apply a different encoding depending on whether a data record lies in or out of the selection. For example, consider the following code:\n\n~~~ python\ncolor=alt.condition(selection, 'Cylinders:O', alt.value('grey'))\n~~~\n\nThis encoding definition states that data points contained within the `selection` should be colored according to the `Cylinder` field, while non-selected data points should use a default `grey`. An empty selection includes _all_ data points, and so initially all points will be colored.\n\n_Try clicking different points in the chart below. What happens? (Click the background to clear the selection state and return to an \"empty\" selection.)_" }, { "cell_type": "code", @@ -194,15 +180,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "Of course, highlighting individual data points one-at-a-time is not particularly exciting! As we'll see, however, single value selections provide a useful building block for more powerful interactions. Moreover, single value selections are just one of the three selection types provided by Altair:\n", - "\n", - "- `selection_single` - select a single discrete value, by default on click events. \n", - "- `selection_multi` - select multiple discrete values. The first value is selected on mouse click and additional values toggled using shift-click. \n", - "- `selection_interval` - select a continuous range of values, initiated by mouse drag.\n", - "\n", - "Let's compare each of these selection types side-by-side. To keep our code tidy we'll first define a function (`plot`) that generates a scatter plot specification just like the one above. We can pass a selection to the `plot` function to have it applied to the chart:" - ] + "source": "Of course, highlighting individual data points one-at-a-time is not particularly exciting! As we'll see, however, single value selections provide a useful building block for more powerful interactions. Moreover, single value selections are just one of the selection types provided by Altair:\n\n- `selection_point(toggle=False)` - select a single discrete value, by default on click events.\n- `selection_point()` - select multiple discrete values. The first value is selected on mouse click and additional values toggled using shift-click.\n- `selection_interval()` - select a continuous range of values, initiated by mouse drag.\n\nLet's compare each of these selection types side-by-side. To keep our code tidy we'll first define a function (`plot`) that generates a scatter plot specification just like the one above. We can pass a selection to the `plot` function to have it applied to the chart:" }, { "cell_type": "code", @@ -500,11 +478,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "Now let's create a `single` selection bound to a drop-down menu.\n", - "\n", - "*Use the dynamic query menu below to explore the data. How do ratings vary by genre? How would you revise the code to filter `MPAA_Rating` (G, PG, PG-13, etc.) instead of `Major_Genre`?*" - ] + "source": "Now let's create a point selection bound to a drop-down menu.\n\n*Use the dynamic query menu below to explore the data. How do ratings vary by genre? How would you revise the code to filter `MPAA_Rating` (G, PG, PG-13, etc.) instead of `Major_Genre`?*" }, { "cell_type": "code", @@ -613,26 +587,12 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "Our construction above leverages multiple aspects of selections:\n", - "\n", - "- We give the selection a name (`'Select'`). This name is not required, but allows us to influence the label text of the generated dynamic query menu. (_What happens if you remove the name? Try it!_)\n", - "- We constrain the selection to a specific data field (`Major_Genre`). Earlier when we used a `single` selection, the selection mapped to individual data points. By limiting the selection to a specific field, we can select _all_ data points whose `Major_Genre` field value matches the single selected value.\n", - "- We initialize `init=...` the selection to a starting value.\n", - "- We `bind` the selection to an interface widget, in this case a drop-down menu via `binding_select`.\n", - "- As before, we then use a conditional encoding to control the opacity channel." - ] + "source": "Our construction above leverages multiple aspects of selections:\n\n- We give the selection a name (`'Select'`). This name is not required, but allows us to influence the label text of the generated dynamic query menu. (_What happens if you remove the name? Try it!_)\n- We constrain the selection to a specific data field (`Major_Genre`). Earlier when we used a point selection, it mapped to individual data points. By limiting the selection to a specific field, we can select _all_ data points whose `Major_Genre` field value matches the selected value.\n- We set the initial selection state with `value=...`.\n- We `bind` the selection to an interface widget, in this case a drop-down menu via `binding_select`.\n- As before, we then use a conditional encoding to control the opacity channel." }, { "cell_type": "markdown", "metadata": {}, - "source": [ - "### Binding Selections to Multiple Inputs\n", - "\n", - "One selection instance can be bound to _multiple_ dynamic query widgets. Let's modify the example above to provide filters for _both_ `Major_Genre` and `MPAA_Rating`, using radio buttons instead of a menu. Our `single` selection is now defined over a single _pair_ of genre and MPAA rating values\n", - "\n", - "_Look for surprising conjunctions of genre and rating. Are there any G or PG-rated horror films?_" - ] + "source": "### Binding Selections to Multiple Inputs\n\nOne selection instance can be bound to _multiple_ dynamic query widgets. Let's modify the example above to provide filters for _both_ `Major_Genre` and `MPAA_Rating`, using radio buttons instead of a menu. Our point selection is now defined over a single _pair_ of genre and MPAA rating values.\n\n_Look for surprising conjunctions of genre and rating. Are there any G or PG-rated horror films?_" }, { "cell_type": "code", @@ -882,15 +842,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "The example above provides dynamic queries using a _linked selection_ between charts:\n", - "\n", - "- We create an `interval` selection (`brush`), and set `encodings=['x']` to limit the selection to the x-axis only, resulting in a one-dimensional selection interval.\n", - "- We register `brush` with our histogram of films per year via `.add_selection(brush)`.\n", - "- We use `brush` in a conditional encoding to adjust the scatter plot `opacity`.\n", - "\n", - "This interaction technique of selecting elements in one chart and seeing linked highlights in one or more other charts is known as [_brushing & linking_](https://en.wikipedia.org/wiki/Brushing_and_linking)." - ] + "source": "The example above provides dynamic queries using a _linked selection_ between charts:\n\n- We create an `interval` selection (`brush`), and set `encodings=['x']` to limit the selection to the x-axis only, resulting in a one-dimensional selection interval.\n- We register `brush` with our histogram of films per year via `.add_params(brush)`.\n- We use `brush` in a conditional encoding to adjust the scatter plot `opacity`.\n\nThis interaction technique of selecting elements in one chart and seeing linked highlights in one or more other charts is known as [_brushing & linking_](https://en.wikipedia.org/wiki/Brushing_and_linking)." }, { "cell_type": "markdown", @@ -1500,14 +1452,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "The example above adds three new layers to the scatter plot: a circular annotation, white text to provide a legible background, and black text showing a film title. In addition, this example uses two selections in tandem:\n", - "\n", - "1. A single selection (`hover`) that includes `nearest=True` to automatically select the nearest data point as the mouse moves.\n", - "2. A multi selection (`click`) to create persistent selections via shift-click.\n", - "\n", - "Both selections include the set `empty='none'` to indicate that no points should be included if a selection is empty. These selections are then combined into a single filter predicate — the logical _or_ of `hover` and `click` — to include points that reside in _either_ selection. We use this predicate to filter the new layers to show annotations and labels for selected points only." - ] + "source": "The example above adds three new layers to the scatter plot: a circular annotation, white text to provide a legible background, and black text showing a film title. In addition, this example uses two selections in tandem:\n\n1. A single-point selection (`hover`) that uses `toggle=False` and includes `nearest=True` to automatically select the nearest data point as the mouse moves.\n2. A multi-point selection (`click`) to create persistent selections via shift-click.\n\nBoth selections include the setting `empty=False` to indicate that no points should be included if a selection is empty. These selections are then combined into a single filter predicate — the logical _or_ of `hover` and `click` — to include points that reside in _either_ selection. We use this predicate to filter the new layers to show annotations and labels for selected points only." }, { "cell_type": "markdown", @@ -1986,4 +1931,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/altair_introduction.ipynb b/altair_introduction.ipynb index 14b04d1..a0bdc94 100644 --- a/altair_introduction.ipynb +++ b/altair_introduction.ipynb @@ -2578,56 +2578,7 @@ "colab_type": "text", "id": "EVG-NxDd5mxp" }, - "source": [ - "## Publishing a Visualization\n", - "\n", - "Once you have visualized your data, perhaps you would like to publish it somewhere on the web. This can be done straightforwardly using the [vega-embed JavaScript package](https://github.com/vega/vega-embed). A simple example of a stand-alone HTML document can be generated for any chart using the `Chart.save` method:\n", - "\n", - "```python\n", - "chart = alt.Chart(df).mark_bar().encode(\n", - " x='average(precip)',\n", - " y='city',\n", - ")\n", - "chart.save('chart.html')\n", - "```\n", - "\n", - "\n", - "The basic HTML template produces output that looks like this, where the JSON specification for your plot produced by `Chart.to_json` should be stored in the `spec` JavaScript variable:\n", - "\n", - "```html\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " \n", - "\n", - "\n", - " ```\n", - "\n", - "The `Chart.save` method provides a convenient way to save such HTML output to file. For more information on embedding Altair/Vega-Lite, see the [documentation of the vega-embed project](https://github.com/vega/vega-embed).\n" - ] + "source": "## Publishing a Visualization\n\nOnce you have visualized your data, perhaps you would like to publish it somewhere on the web. This can be done straightforwardly using the [vega-embed JavaScript package](https://github.com/vega/vega-embed). A simple example of a stand-alone HTML document can be generated for any chart using the `Chart.save` method:\n\n```python\nchart = alt.Chart(df).mark_bar().encode(\n x='average(precip)',\n y='city',\n)\nchart.save('chart.html')\n```\n\n\nThe basic HTML template produces output that looks like this, where the JSON specification for your plot produced by `Chart.to_json` should be stored in the `spec` JavaScript variable:\n\n```html\n\n\n \n \n \n \n \n \n
\n \n\n\n ```\n\nThe `Chart.save` method provides a convenient way to save such HTML output to file. For more information on embedding Altair/Vega-Lite, see the [documentation of the vega-embed project](https://github.com/vega/vega-embed).\n" }, { "cell_type": "markdown", @@ -2663,4 +2614,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/altair_marks_encoding.ipynb b/altair_marks_encoding.ipynb index 22a53d8..38c48c6 100644 --- a/altair_marks_encoding.ipynb +++ b/altair_marks_encoding.ipynb @@ -115,9 +115,7 @@ "colab_type": "text", "id": "spJtZFQN0KCQ" }, - "source": [ - "693 rows and 6 columns! Let's take a peek at the data content:" - ] + "source": "682 rows and 6 columns! Let's take a peek at the data content:" }, { "cell_type": "code", @@ -2051,13 +2049,7 @@ "colab_type": "text", "id": "apPtlcDoi3yT" }, - "source": [ - "Spatial position is one of the most powerful and flexible channels for visual encoding, but what can we do if we already have assigned fields to the `x` and `y` channels? One valuable technique is to create a *trellis plot*, consisting of sub-plots that show a subset of the data. A trellis plot is one example of the more general technique of presenting data using [small multiples](https://en.wikipedia.org/wiki/Small_multiple) of views.\n", - "\n", - "The `column` and `row` encoding channels generate either a horizontal (columns) or vertical (rows) set of sub-plots, in which the data is partitioned according to the provided data field.\n", - "\n", - "Here is a trellis plot that divides the data into one column per \\`cluster\\` value:" - ] + "source": "Spatial position is one of the most powerful and flexible channels for visual encoding, but what can we do if we already have assigned fields to the `x` and `y` channels? One valuable technique is to create a *trellis plot*, consisting of sub-plots that show a subset of the data. A trellis plot is one example of the more general technique of presenting data using [small multiples](https://en.wikipedia.org/wiki/Small_multiple) of views.\n\nThe `column` and `row` encoding channels generate either a horizontal (columns) or vertical (rows) set of sub-plots, in which the data is partitioned according to the provided data field.\n\nHere is a trellis plot that divides the data into one column per `cluster` value:" }, { "cell_type": "code", @@ -3025,13 +3017,7 @@ "colab_type": "text", "id": "x8j5XVZJcB7l" }, - "source": [ - "### Bar Marks\n", - "\n", - "The \\`bar\\` mark type draws a rectangle with a position, width, and height.\n", - "\n", - "The plot below is a simple bar chart of the population (\\`pop\\`) of each country." - ] + "source": "### Bar Marks\n\nThe `bar` mark type draws a rectangle with a position, width, and height.\n\nThe plot below is a simple bar chart of the population (`pop`) of each country." }, { "cell_type": "code", @@ -4441,9 +4427,7 @@ "colab_type": "text", "id": "msL27DW2qoqJ" }, - "source": [ - "We can see a larger range of values in 1995, from just under 4 to just under 7. By 2005, both the overall fertility values and the variability have declined, centered around 2 children per familty." - ] + "source": "We can see a larger range of values in 1955, from just under 4 to just under 7. By 2005, both the overall fertility values and the variability have declined, centered around 2 children per family." }, { "cell_type": "markdown", @@ -4615,4 +4599,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/altair_scales_axes_legends.ipynb b/altair_scales_axes_legends.ipynb index 4dda79d..8bcb790 100644 --- a/altair_scales_axes_legends.ipynb +++ b/altair_scales_axes_legends.ipynb @@ -2820,15 +2820,7 @@ "colab_type": "text", "id": "UMH3wAeWdAXL" }, - "source": [ - "_Hmm... While the data are better segregated by genus, this cacapohony of colors doesn't seem particularly useful._\n", - "\n", - "_If we look at some of the previous charts carefully, we can see that only a handful of bacteria have a genus shared with another bacteria: Salmonella, Staphylococcus, and Streptococcus. To focus our comparison, we might add colors only for these repeated genus values._\n", - "\n", - "Let's add another `calculate` transform that takes a genus name, keeps it if it is one of the repeated values, and otherwise uses the string `\"Other\"`.\n", - "\n", - "In addition, we can add custom color encodings using explicit `domain` and `range` arrays for the color encoding `scale`.\n" - ] + "source": "_Hmm... While the data are better segregated by genus, this cacophony of colors doesn't seem particularly useful._\n\n_If we look at some of the previous charts carefully, we can see that only a handful of bacteria have a genus shared with another bacteria: Salmonella, Staphylococcus, and Streptococcus. To focus our comparison, we might add colors only for these repeated genus values._\n\nLet's add another `calculate` transform that takes a genus name, keeps it if it is one of the repeated values, and otherwise uses the string `\"Other\"`.\n\nIn addition, we can add custom color encodings using explicit `domain` and `range` arrays for the color encoding `scale`.\n" }, { "cell_type": "code", @@ -3358,4 +3350,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/marimo/01_introduction.py b/marimo/01_introduction.py index 2361095..90d20ab 100644 --- a/marimo/01_introduction.py +++ b/marimo/01_introduction.py @@ -623,9 +623,9 @@ def _(mo): - - - + + +
diff --git a/marimo/02_marks_encoding.py b/marimo/02_marks_encoding.py index fb2d831..1b3a90e 100644 --- a/marimo/02_marks_encoding.py +++ b/marimo/02_marks_encoding.py @@ -89,7 +89,7 @@ def _(data): @app.cell(hide_code=True) def _(mo): mo.md(r""" - 693 rows and 6 columns! Let's take a peek at the data content: + 682 rows and 6 columns! Let's take a peek at the data content: """) return @@ -1061,7 +1061,7 @@ def _(alt, dataNA): @app.cell(hide_code=True) def _(mo): mo.md(r""" - We can see a larger range of values in 1995, from just under 4 to just under 7. By 2005, both the overall fertility values and the variability have declined, centered around 2 children per familty. + We can see a larger range of values in 1955, from just under 4 to just under 7. By 2005, both the overall fertility values and the variability have declined, centered around 2 children per family. """) return diff --git a/marimo/03_data_transformation.py b/marimo/03_data_transformation.py index d675efd..0c478b1 100644 --- a/marimo/03_data_transformation.py +++ b/marimo/03_data_transformation.py @@ -55,7 +55,7 @@ def _(mo): mo.md(r""" We will be working with a table of data about motion pictures, taken from the [vega-datasets](https://vega.github.io/vega-datasets/) collection. The data includes variables such as the film name, director, genre, release date, ratings, and gross revenues. However, _be careful when working with this data_: the films are from unevenly sampled years, using data combined from multiple sources. If you dig in you will find issues with missing values and even some subtle errors! Nevertheless, the data should prove interesting to explore... - Let's retrieve the URL for the JSON data file from the datasets package, and then read the data into a Pandas data frame so that we can inspect its contents. + Let's read the JSON data file into a Pandas data frame from a URL so that we can inspect its contents. """) return diff --git a/marimo/04_scales_axes_legends.py b/marimo/04_scales_axes_legends.py index 67935dd..5894e2e 100644 --- a/marimo/04_scales_axes_legends.py +++ b/marimo/04_scales_axes_legends.py @@ -686,7 +686,7 @@ def _(alt, antibiotics): @app.cell(hide_code=True) def _(mo): mo.md(r""" - _Hmm... While the data are better segregated by genus, this cacapohony of colors doesn't seem particularly useful._ + _Hmm... While the data are better segregated by genus, this cacophony of colors doesn't seem particularly useful._ _If we look at some of the previous charts carefully, we can see that only a handful of bacteria have a genus shared with another bacteria: Salmonella, Staphylococcus, and Streptococcus. To focus our comparison, we might add colors only for these repeated genus values._ diff --git a/marimo/06_interaction.py b/marimo/06_interaction.py index 3ab4427..608f818 100644 --- a/marimo/06_interaction.py +++ b/marimo/06_interaction.py @@ -97,7 +97,7 @@ def _(mo): mo.md(r""" Let's start with a basic selection: simply clicking a point to highlight it. Using the `cars` dataset, we'll start with a scatter plot of horsepower versus miles per gallon, with a color encoding for the number cylinders in the car engine. - In addition, we'll create a selection instance by calling `alt.selection_single()`, indicating we want a selection defined over a _single value_. By default, the selection uses a mouse click to determine the selected value. To register a selection with a chart, we must add it using the `.add_params()` method. + In addition, we'll create a selection instance by calling `alt.selection_point(toggle=False)`, indicating we want a selection defined over a _single value_. By default, the selection uses a mouse click to determine the selected value. To register a selection with a chart, we must add it using the `.add_params()` method. Once our selection has been defined, we can use it as a parameter for _conditional encodings_, which apply a different encoding depending on whether a data record lies in or out of the selection. For example, consider the following code: @@ -122,11 +122,11 @@ def _(alt, cars): @app.cell(hide_code=True) def _(mo): mo.md(r""" - Of course, highlighting individual data points one-at-a-time is not particularly exciting! As we'll see, however, single value selections provide a useful building block for more powerful interactions. Moreover, single value selections are just one of the three selection types provided by Altair: + Of course, highlighting individual data points one-at-a-time is not particularly exciting! As we'll see, however, single value selections provide a useful building block for more powerful interactions. Moreover, single value selections are just one of the selection types provided by Altair: - - `selection_single` - select a single discrete value, by default on click events. - - `selection_multi` - select multiple discrete values. The first value is selected on mouse click and additional values toggled using shift-click. - - `selection_interval` - select a continuous range of values, initiated by mouse drag. + - `selection_point(toggle=False)` - select a single discrete value, by default on click events. + - `selection_point()` - select multiple discrete values. The first value is selected on mouse click and additional values toggled using shift-click. + - `selection_interval()` - select a continuous range of values, initiated by mouse drag. Let's compare each of these selection types side-by-side. To keep our code tidy we'll first define a function (`plot`) that generates a scatter plot specification just like the one above. We can pass a selection to the `plot` function to have it applied to the chart: """) @@ -250,7 +250,7 @@ def _(): @app.cell(hide_code=True) def _(mo): mo.md(r""" - Now let's create a `single` selection bound to a drop-down menu. + Now let's create a point selection bound to a drop-down menu. *Use the dynamic query menu below to explore the data. How do ratings vary by genre? How would you revise the code to filter `MPAA_Rating` (G, PG, PG-13, etc.) instead of `Major_Genre`?* """) @@ -284,8 +284,8 @@ def _(mo): Our construction above leverages multiple aspects of selections: - We give the selection a name (`'Select'`). This name is not required, but allows us to influence the label text of the generated dynamic query menu. (_What happens if you remove the name? Try it!_) - - We constrain the selection to a specific data field (`Major_Genre`). Earlier when we used a `single` selection, the selection mapped to individual data points. By limiting the selection to a specific field, we can select _all_ data points whose `Major_Genre` field value matches the single selected value. - - We initialize `init=...` the selection to a starting value. + - We constrain the selection to a specific data field (`Major_Genre`). Earlier when we used a point selection, it mapped to individual data points. By limiting the selection to a specific field, we can select _all_ data points whose `Major_Genre` field value matches the selected value. + - We set the initial selection state with `value=...`. - We `bind` the selection to an interface widget, in this case a drop-down menu via `binding_select`. - As before, we then use a conditional encoding to control the opacity channel. """) @@ -297,7 +297,7 @@ def _(mo): mo.md(r""" ### Binding Selections to Multiple Inputs - One selection instance can be bound to _multiple_ dynamic query widgets. Let's modify the example above to provide filters for _both_ `Major_Genre` and `MPAA_Rating`, using radio buttons instead of a menu. Our `single` selection is now defined over a single _pair_ of genre and MPAA rating values + One selection instance can be bound to _multiple_ dynamic query widgets. Let's modify the example above to provide filters for _both_ `Major_Genre` and `MPAA_Rating`, using radio buttons instead of a menu. Our point selection is now defined over a single _pair_ of genre and MPAA rating values. _Look for surprising conjunctions of genre and rating. Are there any G or PG-rated horror films?_ """) @@ -525,10 +525,10 @@ def _(mo): mo.md(r""" The example above adds three new layers to the scatter plot: a circular annotation, white text to provide a legible background, and black text showing a film title. In addition, this example uses two selections in tandem: - 1. A single selection (`hover`) that includes `nearest=True` to automatically select the nearest data point as the mouse moves. - 2. A multi selection (`click`) to create persistent selections via shift-click. + 1. A single-point selection (`hover`) that uses `toggle=False` and includes `nearest=True` to automatically select the nearest data point as the mouse moves. + 2. A multi-point selection (`click`) to create persistent selections via shift-click. - Both selections include the set `empty='none'` to indicate that no points should be included if a selection is empty. These selections are then combined into a single filter predicate — the logical _or_ of `hover` and `click` — to include points that reside in _either_ selection. We use this predicate to filter the new layers to show annotations and labels for selected points only. + Both selections include the setting `empty=False` to indicate that no points should be included if a selection is empty. These selections are then combined into a single filter predicate — the logical _or_ of `hover` and `click` — to include points that reside in _either_ selection. We use this predicate to filter the new layers to show annotations and labels for selected points only. """) return diff --git a/marimo/07_cartographic.py b/marimo/07_cartographic.py index ba92a48..dff8c30 100644 --- a/marimo/07_cartographic.py +++ b/marimo/07_cartographic.py @@ -381,7 +381,7 @@ def _(alt, zipcodes): @app.cell(hide_code=True) def _(mo): mo.md(r""" - _To zoom in on a specific digit, add a filter transform to limit the data shown! Try adding an [interactive selection](https://github.com/uwdata/visualization-curriculum/blob/master/altair_interaction.ipynb) to filter to a single digit and dynamically update the map. And be sure to use strings (\`'1'\`) instead of numbers (\`1\`) when filtering digit values!_ + _To zoom in on a specific digit, add a filter transform to limit the data shown! Try adding an [interactive selection](https://github.com/uwdata/visualization-curriculum/blob/master/altair_interaction.ipynb) to filter to a single digit and dynamically update the map. And be sure to use strings (`'1'`) instead of numbers (`1`) when filtering digit values!_ (This example is inspired by Ben Fry's classic [zipdecode](https://benfry.com/zipdecode/) visualization!) @@ -564,7 +564,7 @@ def _(mo): mo.md(r""" _Which U.S. airports have the highest number of outgoing routes?_ - Now that we can see the airports, which may wish to interact with them to better understand the structure of the air traffic network. We can add a `rule` mark layer to represent paths from `origin` airports to `destination` airports, which requires two `lookup` transforms to retrieve coordinates for each end point. In addition, we can use a `single` selection to filter these routes, such that only the routes originating at the currently selected airport are shown. + Now that we can see the airports, which may wish to interact with them to better understand the structure of the air traffic network. We can add a `rule` mark layer to represent paths from `origin` airports to `destination` airports, which requires two `lookup` transforms to retrieve coordinates for each end point. In addition, we can use a point selection to filter these routes, such that only the routes originating at the currently selected airport are shown. _Starting from the static map above, can you build an interactive version? Feel free to skip the code below to engage with the interactive map first, and think through how you might build it on your own!_ """) @@ -576,8 +576,8 @@ def _(airports, alt, flights, usa): # interactive selection for origin airport # select nearest airport to mouse cursor origin = alt.selection_point( - on='mouseover', nearest=True, - fields=['origin'], empty='none' + toggle=False, on='mouseover', nearest=True, + fields=['origin'], empty=False ) # shared data reference for lookup transforms diff --git a/marimo/08_debugging.py b/marimo/08_debugging.py index 2097bd7..cb36c0a 100644 --- a/marimo/08_debugging.py +++ b/marimo/08_debugging.py @@ -70,7 +70,7 @@ def _(mo): conda install -c conda-forge altair ``` - You can run command line commands from a code cell by prefixing it with `!`. For example, to install Altair and Vega Datasets with [Pip](https://pip.pypa.io/), you can run the following cell. + You can run command line commands from a code cell by prefixing it with `!`. For example, to install Altair with [Pip](https://pip.pypa.io/), you can run the following cell. """) return @@ -130,7 +130,7 @@ def _(): @app.cell(hide_code=True) def _(mo): mo.md(r""" - If you are not running the latest version, you can update it with `pip`. You can update Altair and Vega Datasets by running this command in your terminal. + If you are not running the latest version, you can update it with `pip`. You can update Altair by running this command in your terminal. ``` pip install -U altair @@ -213,7 +213,7 @@ def _(mo): mo.md(r""" ## Display Troubleshooting - If you are having issues with seeing a chart, make sure your setup is correct by following the [debugging instruction above](#Installation). If you are still having issues, follow the [instruction about debugging display issues in the Altair documentation](https://iliatimofeev.github.io/altair-viz.github.io/user_guide/troubleshooting.html). + If you are having issues with seeing a chart, make sure your setup is correct by following the [debugging instruction above](#Installation). If you are still having issues, follow the [instruction about debugging display issues in the Altair documentation](https://altair-viz.github.io/user_guide/troubleshooting.html). """) return @@ -223,7 +223,7 @@ def _(mo): mo.md(r""" ### Non Existent Fields - A common error is [accidentally using a field that does not exist](https://iliatimofeev.github.io/altair-viz.github.io/user_guide/troubleshooting.html#plot-displays-but-the-content-is-empty). + A common error is [accidentally using a field that does not exist](https://altair-viz.github.io/user_guide/troubleshooting.html#plot-displays-but-the-content-is-empty). """) return diff --git a/uv.lock b/uv.lock index dac6911..8ff4187 100644 --- a/uv.lock +++ b/uv.lock @@ -28,7 +28,7 @@ wheels = [ [[package]] name = "altair-tutorial" -version = "0.1.0" +version = "2.0.0" source = { virtual = "." } dependencies = [ { name = "altair" }, From 59dd22139c1589ad7b22c21ac32062eef81aa5ae Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Mon, 11 May 2026 19:03:24 -0400 Subject: [PATCH 2/2] re-run all notebooks --- altair_cartographic.ipynb | 200 +++++++------- altair_data_transformation.ipynb | 262 +++++++++--------- altair_debugging.ipynb | 207 ++++++--------- altair_interaction.ipynb | 305 ++++++++++++--------- altair_introduction.ipynb | 319 ++++++++++++---------- altair_marks_encoding.ipynb | 440 ++++++++++++++++--------------- altair_scales_axes_legends.ipynb | 302 ++++++++++----------- altair_view_composition.ipynb | 278 +++++++++---------- 8 files changed, 1209 insertions(+), 1104 deletions(-) diff --git a/altair_cartographic.ipynb b/altair_cartographic.ipynb index 8806495..057a7b3 100644 --- a/altair_cartographic.ipynb +++ b/altair_cartographic.ipynb @@ -239,23 +239,23 @@ "text/html": [ "\n", "\n", - "
\n", + "
\n", "" - ], - "text/plain": [ - "alt.Chart(...)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "alt.Chart(cars).mark_bar().encode(\n", " y='mean(Horsepower)',\n", @@ -688,9 +649,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.9" + "version": "3.13.11" } }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/altair_interaction.ipynb b/altair_interaction.ipynb index ba18a2e..532f56c 100644 --- a/altair_interaction.ipynb +++ b/altair_interaction.ipynb @@ -55,7 +55,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -76,11 +76,25 @@ { "cell_type": "markdown", "metadata": {}, - "source": "Let's start with a basic selection: simply clicking a point to highlight it. Using the `cars` dataset, we'll start with a scatter plot of horsepower versus miles per gallon, with a color encoding for the number cylinders in the car engine.\n\nIn addition, we'll create a selection instance by calling `alt.selection_point(toggle=False)`, indicating we want a selection defined over a _single value_. By default, the selection uses a mouse click to determine the selected value. To register a selection with a chart, we must add it using the `.add_params()` method.\n\nOnce our selection has been defined, we can use it as a parameter for _conditional encodings_, which apply a different encoding depending on whether a data record lies in or out of the selection. For example, consider the following code:\n\n~~~ python\ncolor=alt.condition(selection, 'Cylinders:O', alt.value('grey'))\n~~~\n\nThis encoding definition states that data points contained within the `selection` should be colored according to the `Cylinder` field, while non-selected data points should use a default `grey`. An empty selection includes _all_ data points, and so initially all points will be colored.\n\n_Try clicking different points in the chart below. What happens? (Click the background to clear the selection state and return to an \"empty\" selection.)_" + "source": [ + "Let's start with a basic selection: simply clicking a point to highlight it. Using the `cars` dataset, we'll start with a scatter plot of horsepower versus miles per gallon, with a color encoding for the number cylinders in the car engine.\n", + "\n", + "In addition, we'll create a selection instance by calling `alt.selection_point(toggle=False)`, indicating we want a selection defined over a _single value_. By default, the selection uses a mouse click to determine the selected value. To register a selection with a chart, we must add it using the `.add_params()` method.\n", + "\n", + "Once our selection has been defined, we can use it as a parameter for _conditional encodings_, which apply a different encoding depending on whether a data record lies in or out of the selection. For example, consider the following code:\n", + "\n", + "~~~ python\n", + "color=alt.condition(selection, 'Cylinders:O', alt.value('grey'))\n", + "~~~\n", + "\n", + "This encoding definition states that data points contained within the `selection` should be colored according to the `Cylinder` field, while non-selected data points should use a default `grey`. An empty selection includes _all_ data points, and so initially all points will be colored.\n", + "\n", + "_Try clicking different points in the chart below. What happens? (Click the background to clear the selection state and return to an \"empty\" selection.)_" + ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -88,23 +102,23 @@ "text/html": [ "\n", "\n", - "
\n", + "
\n", "\n \n \n \n \n
\n \n\n\n ```\n\nThe `Chart.save` method provides a convenient way to save such HTML output to file. For more information on embedding Altair/Vega-Lite, see the [documentation of the vega-embed project](https://github.com/vega/vega-embed).\n" + "source": [ + "## Publishing a Visualization\n", + "\n", + "Once you have visualized your data, perhaps you would like to publish it somewhere on the web. This can be done straightforwardly using the [vega-embed JavaScript package](https://github.com/vega/vega-embed). A simple example of a stand-alone HTML document can be generated for any chart using the `Chart.save` method:\n", + "\n", + "```python\n", + "chart = alt.Chart(df).mark_bar().encode(\n", + " x='average(precip)',\n", + " y='city',\n", + ")\n", + "chart.save('chart.html')\n", + "```\n", + "\n", + "\n", + "The basic HTML template produces output that looks like this, where the JSON specification for your plot produced by `Chart.to_json` should be stored in the `spec` JavaScript variable:\n", + "\n", + "```html\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + "\n", + "\n", + " ```\n", + "\n", + "The `Chart.save` method provides a convenient way to save such HTML output to file. For more information on embedding Altair/Vega-Lite, see the [documentation of the vega-embed project](https://github.com/vega/vega-embed).\n" + ] }, { "cell_type": "markdown", @@ -2609,9 +2658,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.9" + "version": "3.13.11" } }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/altair_marks_encoding.ipynb b/altair_marks_encoding.ipynb index 38c48c6..ec74a98 100644 --- a/altair_marks_encoding.ipynb +++ b/altair_marks_encoding.ipynb @@ -115,7 +115,9 @@ "colab_type": "text", "id": "spJtZFQN0KCQ" }, - "source": "682 rows and 6 columns! Let's take a peek at the data content:" + "source": [ + "682 rows and 6 columns! Let's take a peek at the data content:" + ] }, { "cell_type": "code", @@ -480,7 +482,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 7, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -496,23 +498,23 @@ "text/html": [ "\n", "\n", - "
\n", + "
\n", "