We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add resource section in the documentation
What do you think about adding a section in the docs with links to and short descriptions of learning resources and packages related to / building on Altair?

I went through all Github repos mentioning Altair that was updated since Aug 2020 and labeled as either a Python and Notebook repo. From that search, the resource section could look something like this:

---

# Resources

## Learning material

- [Jupyter Notebooks tutorials and examples](https://github.com/altair-viz/altair_notebooks)
- [A data visualization curriculum from the UW data group](https://uwdata.github.io/visualization-curriculum)
- [Altair tutorial given at PyCon 2018](https://altair-viz.github.io/altair-tutorial)

## Related packages

- [Vega-Lite - The higher-level visualization grammar that Altair implements in Python](https://vega.github.io/vega-lite/)
- [altair_saver - Enables saving charts to a variety of output types](https://github.com/altair-viz/altair_saver)
- [altair_data_server - Data transformer plugin that transparently serves data for charts](https://github.com/altair-viz/altair_data_server)
- [altair_pandas - Altair backend for the pandas plotting API](https://github.com/altair-viz/altair_pandas)
- [vega_datasets -  Offline access to the Vega datasets used in the Altair documentation](https://github.com/altair-viz/vega_datasets)
- [nx_altair - Draw interactive NetworkX graphs with Altair](https://github.com/Zsailer/nx_altair)
- [gif - Create animated Altair gifs via loops](https://github.com/maxhumber/gif)
- [Vegawidget - R interface to Altair](https://vegawidget.github.io/altair/)

---

For full disclosure, I am working on a data visualization course using Altair at the University of British Columbia and a wrapper package for making a few EDA plots quicker with Altair. When they are further along, I will suggest adding them here, but also happy if this stays as is and just makes the info easier to find.



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sphinxext/altairgallery.py`
Content:
```
1 import hashlib
2 import os
3 import json
4 import random
5 import collections
6 from operator import itemgetter
7 import warnings
8 import shutil
9 
10 import jinja2
11 
12 from docutils import nodes
13 from docutils.statemachine import ViewList
14 from docutils.parsers.rst import Directive
15 from docutils.parsers.rst.directives import flag
16 
17 from sphinx.util.nodes import nested_parse_with_titles
18 
19 from .utils import (
20     get_docstring_and_rest,
21     prev_this_next,
22     create_thumbnail,
23     create_generic_image,
24 )
25 from altair.utils.execeval import eval_block
26 from tests.examples_arguments_syntax import iter_examples_arguments_syntax
27 
28 
29 EXAMPLE_MODULE = "altair.examples"
30 
31 
32 GALLERY_TEMPLATE = jinja2.Template(
33     """
34 .. This document is auto-generated by the altair-gallery extension. Do not modify directly.
35 
36 .. _{{ gallery_ref }}:
37 
38 {{ title }}
39 {% for char in title %}-{% endfor %}
40 
41 This gallery contains a selection of examples of the plots Altair can create.
42 
43 Some may seem fairly complicated at first glance, but they are built by combining a simple set of declarative building blocks.
44 
45 Many draw upon sample datasets compiled by the `Vega <https://vega.github.io/vega/>`_ project. To access them yourself, install `vega_datasets <https://github.com/altair-viz/vega_datasets>`_.
46 
47 .. code-block:: none
48 
49    python -m pip install vega_datasets
50 
51 {% for grouper, group in examples %}
52 
53 .. _gallery-category-{{ grouper }}:
54 
55 {{ grouper }}
56 {% for char in grouper %}~{% endfor %}
57 
58 .. raw:: html
59 
60    <span class="gallery">
61    {% for example in group %}
62    <a class="imagegroup" href="{{ example.name }}.html">
63    <span
64         class="image" alt="{{ example.title }}"
65 {% if example['use_svg'] %}
66         style="background-image: url(..{{ image_dir }}/{{ example.name }}-thumb.svg);"
67 {% else %}
68         style="background-image: url(..{{ image_dir }}/{{ example.name }}-thumb.png);"
69 {% endif %}
70     ></span>
71 
72      <span class="image-title">{{ example.title }}</span>
73    </a>
74    {% endfor %}
75    </span>
76 
77    <div style='clear:both;'></div>
78 
79 {% endfor %}
80 
81 
82 .. toctree::
83    :maxdepth: 2
84    :caption: Examples
85    :hidden:
86 
87    Gallery <self>
88    Tutorials <../case_studies/exploring-weather>
89 """
90 )
91 
92 MINIGALLERY_TEMPLATE = jinja2.Template(
93     """
94 .. raw:: html
95 
96     <div id="showcase">
97       <div class="examples">
98       {% for example in examples %}
99       <a
100         class="preview" href="{{ gallery_dir }}/{{ example.name }}.html"
101 {% if example['use_svg'] %}
102         style="background-image: url(.{{ image_dir }}/{{ example.name }}-thumb.svg)"
103 {% else %}
104         style="background-image: url(.{{ image_dir }}/{{ example.name }}-thumb.png)"
105 {% endif %}
106       ></a>
107       {% endfor %}
108       </div>
109     </div>
110 """
111 )
112 
113 
114 EXAMPLE_TEMPLATE = jinja2.Template(
115     """
116 :orphan:
117 :html_theme.sidebar_secondary.remove:
118 
119 .. This document is auto-generated by the altair-gallery extension. Do not modify directly.
120 
121 .. _gallery_{{ name }}:
122 
123 {{ docstring }}
124 
125 .. altair-plot::
126     {% if code_below %}:code-below:{% endif %}
127     {% if strict %}:strict:{% endif %}
128 
129     {{ code | indent(4) }}
130 """
131 )
132 
133 
134 def save_example_pngs(examples, image_dir, make_thumbnails=True):
135     """Save example pngs and (optionally) thumbnails"""
136     if not os.path.exists(image_dir):
137         os.makedirs(image_dir)
138 
139     # store hashes so that we know whether images need to be generated
140     hash_file = os.path.join(image_dir, "_image_hashes.json")
141 
142     if os.path.exists(hash_file):
143         with open(hash_file) as f:
144             hashes = json.load(f)
145     else:
146         hashes = {}
147 
148     for example in examples:
149         filename = example["name"] + (".svg" if example["use_svg"] else ".png")
150         image_file = os.path.join(image_dir, filename)
151 
152         example_hash = hashlib.md5(example["code"].encode()).hexdigest()
153         hashes_match = hashes.get(filename, "") == example_hash
154 
155         if hashes_match and os.path.exists(image_file):
156             print("-> using cached {}".format(image_file))
157         else:
158             # the file changed or the image file does not exist. Generate it.
159             print("-> saving {}".format(image_file))
160             chart = eval_block(example["code"])
161             try:
162                 chart.save(image_file)
163                 hashes[filename] = example_hash
164             except ImportError:
165                 warnings.warn("Unable to save image: using generic image")
166                 create_generic_image(image_file)
167 
168             with open(hash_file, "w") as f:
169                 json.dump(hashes, f)
170 
171         if make_thumbnails:
172             params = example.get("galleryParameters", {})
173             if example["use_svg"]:
174                 # Thumbnail for SVG is identical to original image
175                 thumb_file = os.path.join(image_dir, example["name"] + "-thumb.svg")
176                 shutil.copyfile(image_file, thumb_file)
177             else:
178                 thumb_file = os.path.join(image_dir, example["name"] + "-thumb.png")
179                 create_thumbnail(image_file, thumb_file, **params)
180 
181     # Save hashes so we know whether we need to re-generate plots
182     with open(hash_file, "w") as f:
183         json.dump(hashes, f)
184 
185 
186 def populate_examples(**kwds):
187     """Iterate through Altair examples and extract code"""
188 
189     examples = sorted(iter_examples_arguments_syntax(), key=itemgetter("name"))
190 
191     for example in examples:
192         docstring, category, code, lineno = get_docstring_and_rest(example["filename"])
193         example.update(kwds)
194         if category is None:
195             raise Exception(
196                 f"The example {example['name']} is not assigned to a category"
197             )
198         example.update(
199             {
200                 "docstring": docstring,
201                 "title": docstring.strip().split("\n")[0],
202                 "code": code,
203                 "category": category.title(),
204                 "lineno": lineno,
205             }
206         )
207 
208     return examples
209 
210 
211 class AltairMiniGalleryDirective(Directive):
212     has_content = False
213 
214     option_spec = {
215         "size": int,
216         "names": str,
217         "indices": lambda x: list(map(int, x.split())),
218         "shuffle": flag,
219         "seed": int,
220         "titles": bool,
221         "width": str,
222     }
223 
224     def run(self):
225         size = self.options.get("size", 15)
226         names = [name.strip() for name in self.options.get("names", "").split(",")]
227         indices = self.options.get("indices", [])
228         shuffle = "shuffle" in self.options
229         seed = self.options.get("seed", 42)
230         titles = self.options.get("titles", False)
231         width = self.options.get("width", None)
232 
233         env = self.state.document.settings.env
234         app = env.app
235 
236         gallery_dir = app.builder.config.altair_gallery_dir
237 
238         examples = populate_examples()
239 
240         if names:
241             if len(names) < size:
242                 raise ValueError(
243                     "altair-minigallery: if names are specified, "
244                     "the list must be at least as long as size."
245                 )
246             mapping = {example["name"]: example for example in examples}
247             examples = [mapping[name] for name in names]
248         else:
249             if indices:
250                 examples = [examples[i] for i in indices]
251             if shuffle:
252                 random.seed(seed)
253                 random.shuffle(examples)
254             if size:
255                 examples = examples[:size]
256 
257         include = MINIGALLERY_TEMPLATE.render(
258             image_dir="/_static",
259             gallery_dir=gallery_dir,
260             examples=examples,
261             titles=titles,
262             width=width,
263         )
264 
265         # parse and return documentation
266         result = ViewList()
267         for line in include.split("\n"):
268             result.append(line, "<altair-minigallery>")
269         node = nodes.paragraph()
270         node.document = self.state.document
271         nested_parse_with_titles(self.state, result, node)
272 
273         return node.children
274 
275 
276 def main(app):
277     gallery_dir = app.builder.config.altair_gallery_dir
278     target_dir = os.path.join(app.builder.srcdir, gallery_dir)
279     image_dir = os.path.join(app.builder.srcdir, "_images")
280 
281     gallery_ref = app.builder.config.altair_gallery_ref
282     gallery_title = app.builder.config.altair_gallery_title
283     examples = populate_examples(gallery_ref=gallery_ref, code_below=True, strict=False)
284 
285     if not os.path.exists(target_dir):
286         os.makedirs(target_dir)
287 
288     examples = sorted(examples, key=lambda x: x["title"])
289     examples_toc = collections.OrderedDict(
290         {
291             "Simple Charts": [],
292             "Bar Charts": [],
293             "Line Charts": [],
294             "Area Charts": [],
295             "Circular Plots": [],
296             "Scatter Plots": [],
297             "Uncertainties And Trends": [],
298             "Distributions": [],
299             "Tables": [],
300             "Maps": [],
301             "Interactive Charts": [],
302             "Advanced Calculations": [],
303             "Case Studies": [],
304         }
305     )
306     for d in examples:
307         examples_toc[d["category"]].append(d)
308 
309     # Write the gallery index file
310     with open(os.path.join(target_dir, "index.rst"), "w") as f:
311         f.write(
312             GALLERY_TEMPLATE.render(
313                 title=gallery_title,
314                 examples=examples_toc.items(),
315                 image_dir="/_static",
316                 gallery_ref=gallery_ref,
317             )
318         )
319 
320     # save the images to file
321     save_example_pngs(examples, image_dir)
322 
323     # Write the individual example files
324     for prev_ex, example, next_ex in prev_this_next(examples):
325         if prev_ex:
326             example["prev_ref"] = "gallery_{name}".format(**prev_ex)
327         if next_ex:
328             example["next_ref"] = "gallery_{name}".format(**next_ex)
329         target_filename = os.path.join(target_dir, example["name"] + ".rst")
330         with open(os.path.join(target_filename), "w", encoding="utf-8") as f:
331             f.write(EXAMPLE_TEMPLATE.render(example))
332 
333 
334 def setup(app):
335     app.connect("builder-inited", main)
336     app.add_css_file("altair-gallery.css")
337     app.add_config_value("altair_gallery_dir", "gallery", "env")
338     app.add_config_value("altair_gallery_ref", "example-gallery", "env")
339     app.add_config_value("altair_gallery_title", "Example Gallery", "env")
340     app.add_directive_to_domain("py", "altair-minigallery", AltairMiniGalleryDirective)
341
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sphinxext/altairgallery.py b/sphinxext/altairgallery.py
--- a/sphinxext/altairgallery.py
+++ b/sphinxext/altairgallery.py
@@ -38,9 +38,7 @@
 {{ title }}
 {% for char in title %}-{% endfor %}
 
-This gallery contains a selection of examples of the plots Altair can create.
-
-Some may seem fairly complicated at first glance, but they are built by combining a simple set of declarative building blocks.
+This gallery contains a selection of examples of the plots Altair can create. Some may seem fairly complicated at first glance, but they are built by combining a simple set of declarative building blocks.
 
 Many draw upon sample datasets compiled by the `Vega <https://vega.github.io/vega/>`_ project. To access them yourself, install `vega_datasets <https://github.com/altair-viz/vega_datasets>`_.
 
@@ -48,6 +46,8 @@
 
    python -m pip install vega_datasets
 
+If you can't find the plots you are looking for here, make sure to check out the :ref:`altair-ecosystem` section, which has links to packages for making e.g. network diagrams and animations.
+
 {% for grouper, group in examples %}
 
 .. _gallery-category-{{ grouper }}:

{"golden_diff": "diff --git a/sphinxext/altairgallery.py b/sphinxext/altairgallery.py\n--- a/sphinxext/altairgallery.py\n+++ b/sphinxext/altairgallery.py\n@@ -38,9 +38,7 @@\n {{ title }}\n {% for char in title %}-{% endfor %}\n \n-This gallery contains a selection of examples of the plots Altair can create.\n-\n-Some may seem fairly complicated at first glance, but they are built by combining a simple set of declarative building blocks.\n+This gallery contains a selection of examples of the plots Altair can create. Some may seem fairly complicated at first glance, but they are built by combining a simple set of declarative building blocks.\n \n Many draw upon sample datasets compiled by the `Vega <https://vega.github.io/vega/>`_ project. To access them yourself, install `vega_datasets <https://github.com/altair-viz/vega_datasets>`_.\n \n@@ -48,6 +46,8 @@\n \n    python -m pip install vega_datasets\n \n+If you can't find the plots you are looking for here, make sure to check out the :ref:`altair-ecosystem` section, which has links to packages for making e.g. network diagrams and animations.\n+\n {% for grouper, group in examples %}\n \n .. _gallery-category-{{ grouper }}:\n", "issue": "Add resource section in the documentation\nWhat do you think about adding a section in the docs with links to and short descriptions of learning resources and packages related to / building on Altair?\r\n\r\nI went through all Github repos mentioning Altair that was updated since Aug 2020 and labeled as either a Python and Notebook repo. From that search, the resource section could look something like this:\r\n\r\n---\r\n\r\n# Resources\r\n\r\n## Learning material\r\n\r\n- [Jupyter Notebooks tutorials and examples](https://github.com/altair-viz/altair_notebooks)\r\n- [A data visualization curriculum from the UW data group](https://uwdata.github.io/visualization-curriculum)\r\n- [Altair tutorial given at PyCon 2018](https://altair-viz.github.io/altair-tutorial)\r\n\r\n## Related packages\r\n\r\n- [Vega-Lite - The higher-level visualization grammar that Altair implements in Python](https://vega.github.io/vega-lite/)\r\n- [altair_saver - Enables saving charts to a variety of output types](https://github.com/altair-viz/altair_saver)\r\n- [altair_data_server - Data transformer plugin that transparently serves data for charts](https://github.com/altair-viz/altair_data_server)\r\n- [altair_pandas - Altair backend for the pandas plotting API](https://github.com/altair-viz/altair_pandas)\r\n- [vega_datasets -  Offline access to the Vega datasets used in the Altair documentation](https://github.com/altair-viz/vega_datasets)\r\n- [nx_altair - Draw interactive NetworkX graphs with Altair](https://github.com/Zsailer/nx_altair)\r\n- [gif - Create animated Altair gifs via loops](https://github.com/maxhumber/gif)\r\n- [Vegawidget - R interface to Altair](https://vegawidget.github.io/altair/)\r\n\r\n---\r\n\r\nFor full disclosure, I am working on a data visualization course using Altair at the University of British Columbia and a wrapper package for making a few EDA plots quicker with Altair. When they are further along, I will suggest adding them here, but also happy if this stays as is and just makes the info easier to find.\r\n\r\n\n", "before_files": [{"content": "import hashlib\nimport os\nimport json\nimport random\nimport collections\nfrom operator import itemgetter\nimport warnings\nimport shutil\n\nimport jinja2\n\nfrom docutils import nodes\nfrom docutils.statemachine import ViewList\nfrom docutils.parsers.rst import Directive\nfrom docutils.parsers.rst.directives import flag\n\nfrom sphinx.util.nodes import nested_parse_with_titles\n\nfrom .utils import (\n    get_docstring_and_rest,\n    prev_this_next,\n    create_thumbnail,\n    create_generic_image,\n)\nfrom altair.utils.execeval import eval_block\nfrom tests.examples_arguments_syntax import iter_examples_arguments_syntax\n\n\nEXAMPLE_MODULE = \"altair.examples\"\n\n\nGALLERY_TEMPLATE = jinja2.Template(\n    \"\"\"\n.. This document is auto-generated by the altair-gallery extension. Do not modify directly.\n\n.. _{{ gallery_ref }}:\n\n{{ title }}\n{% for char in title %}-{% endfor %}\n\nThis gallery contains a selection of examples of the plots Altair can create.\n\nSome may seem fairly complicated at first glance, but they are built by combining a simple set of declarative building blocks.\n\nMany draw upon sample datasets compiled by the `Vega <https://vega.github.io/vega/>`_ project. To access them yourself, install `vega_datasets <https://github.com/altair-viz/vega_datasets>`_.\n\n.. code-block:: none\n\n   python -m pip install vega_datasets\n\n{% for grouper, group in examples %}\n\n.. _gallery-category-{{ grouper }}:\n\n{{ grouper }}\n{% for char in grouper %}~{% endfor %}\n\n.. raw:: html\n\n   <span class=\"gallery\">\n   {% for example in group %}\n   <a class=\"imagegroup\" href=\"{{ example.name }}.html\">\n   <span\n        class=\"image\" alt=\"{{ example.title }}\"\n{% if example['use_svg'] %}\n        style=\"background-image: url(..{{ image_dir }}/{{ example.name }}-thumb.svg);\"\n{% else %}\n        style=\"background-image: url(..{{ image_dir }}/{{ example.name }}-thumb.png);\"\n{% endif %}\n    ></span>\n\n     <span class=\"image-title\">{{ example.title }}</span>\n   </a>\n   {% endfor %}\n   </span>\n\n   <div style='clear:both;'></div>\n\n{% endfor %}\n\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Examples\n   :hidden:\n\n   Gallery <self>\n   Tutorials <../case_studies/exploring-weather>\n\"\"\"\n)\n\nMINIGALLERY_TEMPLATE = jinja2.Template(\n    \"\"\"\n.. raw:: html\n\n    <div id=\"showcase\">\n      <div class=\"examples\">\n      {% for example in examples %}\n      <a\n        class=\"preview\" href=\"{{ gallery_dir }}/{{ example.name }}.html\"\n{% if example['use_svg'] %}\n        style=\"background-image: url(.{{ image_dir }}/{{ example.name }}-thumb.svg)\"\n{% else %}\n        style=\"background-image: url(.{{ image_dir }}/{{ example.name }}-thumb.png)\"\n{% endif %}\n      ></a>\n      {% endfor %}\n      </div>\n    </div>\n\"\"\"\n)\n\n\nEXAMPLE_TEMPLATE = jinja2.Template(\n    \"\"\"\n:orphan:\n:html_theme.sidebar_secondary.remove:\n\n.. This document is auto-generated by the altair-gallery extension. Do not modify directly.\n\n.. _gallery_{{ name }}:\n\n{{ docstring }}\n\n.. altair-plot::\n    {% if code_below %}:code-below:{% endif %}\n    {% if strict %}:strict:{% endif %}\n\n    {{ code | indent(4) }}\n\"\"\"\n)\n\n\ndef save_example_pngs(examples, image_dir, make_thumbnails=True):\n    \"\"\"Save example pngs and (optionally) thumbnails\"\"\"\n    if not os.path.exists(image_dir):\n        os.makedirs(image_dir)\n\n    # store hashes so that we know whether images need to be generated\n    hash_file = os.path.join(image_dir, \"_image_hashes.json\")\n\n    if os.path.exists(hash_file):\n        with open(hash_file) as f:\n            hashes = json.load(f)\n    else:\n        hashes = {}\n\n    for example in examples:\n        filename = example[\"name\"] + (\".svg\" if example[\"use_svg\"] else \".png\")\n        image_file = os.path.join(image_dir, filename)\n\n        example_hash = hashlib.md5(example[\"code\"].encode()).hexdigest()\n        hashes_match = hashes.get(filename, \"\") == example_hash\n\n        if hashes_match and os.path.exists(image_file):\n            print(\"-> using cached {}\".format(image_file))\n        else:\n            # the file changed or the image file does not exist. Generate it.\n            print(\"-> saving {}\".format(image_file))\n            chart = eval_block(example[\"code\"])\n            try:\n                chart.save(image_file)\n                hashes[filename] = example_hash\n            except ImportError:\n                warnings.warn(\"Unable to save image: using generic image\")\n                create_generic_image(image_file)\n\n            with open(hash_file, \"w\") as f:\n                json.dump(hashes, f)\n\n        if make_thumbnails:\n            params = example.get(\"galleryParameters\", {})\n            if example[\"use_svg\"]:\n                # Thumbnail for SVG is identical to original image\n                thumb_file = os.path.join(image_dir, example[\"name\"] + \"-thumb.svg\")\n                shutil.copyfile(image_file, thumb_file)\n            else:\n                thumb_file = os.path.join(image_dir, example[\"name\"] + \"-thumb.png\")\n                create_thumbnail(image_file, thumb_file, **params)\n\n    # Save hashes so we know whether we need to re-generate plots\n    with open(hash_file, \"w\") as f:\n        json.dump(hashes, f)\n\n\ndef populate_examples(**kwds):\n    \"\"\"Iterate through Altair examples and extract code\"\"\"\n\n    examples = sorted(iter_examples_arguments_syntax(), key=itemgetter(\"name\"))\n\n    for example in examples:\n        docstring, category, code, lineno = get_docstring_and_rest(example[\"filename\"])\n        example.update(kwds)\n        if category is None:\n            raise Exception(\n                f\"The example {example['name']} is not assigned to a category\"\n            )\n        example.update(\n            {\n                \"docstring\": docstring,\n                \"title\": docstring.strip().split(\"\\n\")[0],\n                \"code\": code,\n                \"category\": category.title(),\n                \"lineno\": lineno,\n            }\n        )\n\n    return examples\n\n\nclass AltairMiniGalleryDirective(Directive):\n    has_content = False\n\n    option_spec = {\n        \"size\": int,\n        \"names\": str,\n        \"indices\": lambda x: list(map(int, x.split())),\n        \"shuffle\": flag,\n        \"seed\": int,\n        \"titles\": bool,\n        \"width\": str,\n    }\n\n    def run(self):\n        size = self.options.get(\"size\", 15)\n        names = [name.strip() for name in self.options.get(\"names\", \"\").split(\",\")]\n        indices = self.options.get(\"indices\", [])\n        shuffle = \"shuffle\" in self.options\n        seed = self.options.get(\"seed\", 42)\n        titles = self.options.get(\"titles\", False)\n        width = self.options.get(\"width\", None)\n\n        env = self.state.document.settings.env\n        app = env.app\n\n        gallery_dir = app.builder.config.altair_gallery_dir\n\n        examples = populate_examples()\n\n        if names:\n            if len(names) < size:\n                raise ValueError(\n                    \"altair-minigallery: if names are specified, \"\n                    \"the list must be at least as long as size.\"\n                )\n            mapping = {example[\"name\"]: example for example in examples}\n            examples = [mapping[name] for name in names]\n        else:\n            if indices:\n                examples = [examples[i] for i in indices]\n            if shuffle:\n                random.seed(seed)\n                random.shuffle(examples)\n            if size:\n                examples = examples[:size]\n\n        include = MINIGALLERY_TEMPLATE.render(\n            image_dir=\"/_static\",\n            gallery_dir=gallery_dir,\n            examples=examples,\n            titles=titles,\n            width=width,\n        )\n\n        # parse and return documentation\n        result = ViewList()\n        for line in include.split(\"\\n\"):\n            result.append(line, \"<altair-minigallery>\")\n        node = nodes.paragraph()\n        node.document = self.state.document\n        nested_parse_with_titles(self.state, result, node)\n\n        return node.children\n\n\ndef main(app):\n    gallery_dir = app.builder.config.altair_gallery_dir\n    target_dir = os.path.join(app.builder.srcdir, gallery_dir)\n    image_dir = os.path.join(app.builder.srcdir, \"_images\")\n\n    gallery_ref = app.builder.config.altair_gallery_ref\n    gallery_title = app.builder.config.altair_gallery_title\n    examples = populate_examples(gallery_ref=gallery_ref, code_below=True, strict=False)\n\n    if not os.path.exists(target_dir):\n        os.makedirs(target_dir)\n\n    examples = sorted(examples, key=lambda x: x[\"title\"])\n    examples_toc = collections.OrderedDict(\n        {\n            \"Simple Charts\": [],\n            \"Bar Charts\": [],\n            \"Line Charts\": [],\n            \"Area Charts\": [],\n            \"Circular Plots\": [],\n            \"Scatter Plots\": [],\n            \"Uncertainties And Trends\": [],\n            \"Distributions\": [],\n            \"Tables\": [],\n            \"Maps\": [],\n            \"Interactive Charts\": [],\n            \"Advanced Calculations\": [],\n            \"Case Studies\": [],\n        }\n    )\n    for d in examples:\n        examples_toc[d[\"category\"]].append(d)\n\n    # Write the gallery index file\n    with open(os.path.join(target_dir, \"index.rst\"), \"w\") as f:\n        f.write(\n            GALLERY_TEMPLATE.render(\n                title=gallery_title,\n                examples=examples_toc.items(),\n                image_dir=\"/_static\",\n                gallery_ref=gallery_ref,\n            )\n        )\n\n    # save the images to file\n    save_example_pngs(examples, image_dir)\n\n    # Write the individual example files\n    for prev_ex, example, next_ex in prev_this_next(examples):\n        if prev_ex:\n            example[\"prev_ref\"] = \"gallery_{name}\".format(**prev_ex)\n        if next_ex:\n            example[\"next_ref\"] = \"gallery_{name}\".format(**next_ex)\n        target_filename = os.path.join(target_dir, example[\"name\"] + \".rst\")\n        with open(os.path.join(target_filename), \"w\", encoding=\"utf-8\") as f:\n            f.write(EXAMPLE_TEMPLATE.render(example))\n\n\ndef setup(app):\n    app.connect(\"builder-inited\", main)\n    app.add_css_file(\"altair-gallery.css\")\n    app.add_config_value(\"altair_gallery_dir\", \"gallery\", \"env\")\n    app.add_config_value(\"altair_gallery_ref\", \"example-gallery\", \"env\")\n    app.add_config_value(\"altair_gallery_title\", \"Example Gallery\", \"env\")\n    app.add_directive_to_domain(\"py\", \"altair-minigallery\", AltairMiniGalleryDirective)\n", "path": "sphinxext/altairgallery.py"}], "after_files": [{"content": "import hashlib\nimport os\nimport json\nimport random\nimport collections\nfrom operator import itemgetter\nimport warnings\nimport shutil\n\nimport jinja2\n\nfrom docutils import nodes\nfrom docutils.statemachine import ViewList\nfrom docutils.parsers.rst import Directive\nfrom docutils.parsers.rst.directives import flag\n\nfrom sphinx.util.nodes import nested_parse_with_titles\n\nfrom .utils import (\n    get_docstring_and_rest,\n    prev_this_next,\n    create_thumbnail,\n    create_generic_image,\n)\nfrom altair.utils.execeval import eval_block\nfrom tests.examples_arguments_syntax import iter_examples_arguments_syntax\n\n\nEXAMPLE_MODULE = \"altair.examples\"\n\n\nGALLERY_TEMPLATE = jinja2.Template(\n    \"\"\"\n.. This document is auto-generated by the altair-gallery extension. Do not modify directly.\n\n.. _{{ gallery_ref }}:\n\n{{ title }}\n{% for char in title %}-{% endfor %}\n\nThis gallery contains a selection of examples of the plots Altair can create. Some may seem fairly complicated at first glance, but they are built by combining a simple set of declarative building blocks.\n\nMany draw upon sample datasets compiled by the `Vega <https://vega.github.io/vega/>`_ project. To access them yourself, install `vega_datasets <https://github.com/altair-viz/vega_datasets>`_.\n\n.. code-block:: none\n\n   python -m pip install vega_datasets\n\nIf you can't find the plots you are looking for here, make sure to check out the :ref:`altair-ecosystem` section, which has links to packages for making e.g. network diagrams and animations.\n\n{% for grouper, group in examples %}\n\n.. _gallery-category-{{ grouper }}:\n\n{{ grouper }}\n{% for char in grouper %}~{% endfor %}\n\n.. raw:: html\n\n   <span class=\"gallery\">\n   {% for example in group %}\n   <a class=\"imagegroup\" href=\"{{ example.name }}.html\">\n   <span\n        class=\"image\" alt=\"{{ example.title }}\"\n{% if example['use_svg'] %}\n        style=\"background-image: url(..{{ image_dir }}/{{ example.name }}-thumb.svg);\"\n{% else %}\n        style=\"background-image: url(..{{ image_dir }}/{{ example.name }}-thumb.png);\"\n{% endif %}\n    ></span>\n\n     <span class=\"image-title\">{{ example.title }}</span>\n   </a>\n   {% endfor %}\n   </span>\n\n   <div style='clear:both;'></div>\n\n{% endfor %}\n\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Examples\n   :hidden:\n\n   Gallery <self>\n   Tutorials <../case_studies/exploring-weather>\n\"\"\"\n)\n\nMINIGALLERY_TEMPLATE = jinja2.Template(\n    \"\"\"\n.. raw:: html\n\n    <div id=\"showcase\">\n      <div class=\"examples\">\n      {% for example in examples %}\n      <a\n        class=\"preview\" href=\"{{ gallery_dir }}/{{ example.name }}.html\"\n{% if example['use_svg'] %}\n        style=\"background-image: url(.{{ image_dir }}/{{ example.name }}-thumb.svg)\"\n{% else %}\n        style=\"background-image: url(.{{ image_dir }}/{{ example.name }}-thumb.png)\"\n{% endif %}\n      ></a>\n      {% endfor %}\n      </div>\n    </div>\n\"\"\"\n)\n\n\nEXAMPLE_TEMPLATE = jinja2.Template(\n    \"\"\"\n:orphan:\n:html_theme.sidebar_secondary.remove:\n\n.. This document is auto-generated by the altair-gallery extension. Do not modify directly.\n\n.. _gallery_{{ name }}:\n\n{{ docstring }}\n\n.. altair-plot::\n    {% if code_below %}:code-below:{% endif %}\n    {% if strict %}:strict:{% endif %}\n\n    {{ code | indent(4) }}\n\"\"\"\n)\n\n\ndef save_example_pngs(examples, image_dir, make_thumbnails=True):\n    \"\"\"Save example pngs and (optionally) thumbnails\"\"\"\n    if not os.path.exists(image_dir):\n        os.makedirs(image_dir)\n\n    # store hashes so that we know whether images need to be generated\n    hash_file = os.path.join(image_dir, \"_image_hashes.json\")\n\n    if os.path.exists(hash_file):\n        with open(hash_file) as f:\n            hashes = json.load(f)\n    else:\n        hashes = {}\n\n    for example in examples:\n        filename = example[\"name\"] + (\".svg\" if example[\"use_svg\"] else \".png\")\n        image_file = os.path.join(image_dir, filename)\n\n        example_hash = hashlib.md5(example[\"code\"].encode()).hexdigest()\n        hashes_match = hashes.get(filename, \"\") == example_hash\n\n        if hashes_match and os.path.exists(image_file):\n            print(\"-> using cached {}\".format(image_file))\n        else:\n            # the file changed or the image file does not exist. Generate it.\n            print(\"-> saving {}\".format(image_file))\n            chart = eval_block(example[\"code\"])\n            try:\n                chart.save(image_file)\n                hashes[filename] = example_hash\n            except ImportError:\n                warnings.warn(\"Unable to save image: using generic image\")\n                create_generic_image(image_file)\n\n            with open(hash_file, \"w\") as f:\n                json.dump(hashes, f)\n\n        if make_thumbnails:\n            params = example.get(\"galleryParameters\", {})\n            if example[\"use_svg\"]:\n                # Thumbnail for SVG is identical to original image\n                thumb_file = os.path.join(image_dir, example[\"name\"] + \"-thumb.svg\")\n                shutil.copyfile(image_file, thumb_file)\n            else:\n                thumb_file = os.path.join(image_dir, example[\"name\"] + \"-thumb.png\")\n                create_thumbnail(image_file, thumb_file, **params)\n\n    # Save hashes so we know whether we need to re-generate plots\n    with open(hash_file, \"w\") as f:\n        json.dump(hashes, f)\n\n\ndef populate_examples(**kwds):\n    \"\"\"Iterate through Altair examples and extract code\"\"\"\n\n    examples = sorted(iter_examples_arguments_syntax(), key=itemgetter(\"name\"))\n\n    for example in examples:\n        docstring, category, code, lineno = get_docstring_and_rest(example[\"filename\"])\n        example.update(kwds)\n        if category is None:\n            raise Exception(\n                f\"The example {example['name']} is not assigned to a category\"\n            )\n        example.update(\n            {\n                \"docstring\": docstring,\n                \"title\": docstring.strip().split(\"\\n\")[0],\n                \"code\": code,\n                \"category\": category.title(),\n                \"lineno\": lineno,\n            }\n        )\n\n    return examples\n\n\nclass AltairMiniGalleryDirective(Directive):\n    has_content = False\n\n    option_spec = {\n        \"size\": int,\n        \"names\": str,\n        \"indices\": lambda x: list(map(int, x.split())),\n        \"shuffle\": flag,\n        \"seed\": int,\n        \"titles\": bool,\n        \"width\": str,\n    }\n\n    def run(self):\n        size = self.options.get(\"size\", 15)\n        names = [name.strip() for name in self.options.get(\"names\", \"\").split(\",\")]\n        indices = self.options.get(\"indices\", [])\n        shuffle = \"shuffle\" in self.options\n        seed = self.options.get(\"seed\", 42)\n        titles = self.options.get(\"titles\", False)\n        width = self.options.get(\"width\", None)\n\n        env = self.state.document.settings.env\n        app = env.app\n\n        gallery_dir = app.builder.config.altair_gallery_dir\n\n        examples = populate_examples()\n\n        if names:\n            if len(names) < size:\n                raise ValueError(\n                    \"altair-minigallery: if names are specified, \"\n                    \"the list must be at least as long as size.\"\n                )\n            mapping = {example[\"name\"]: example for example in examples}\n            examples = [mapping[name] for name in names]\n        else:\n            if indices:\n                examples = [examples[i] for i in indices]\n            if shuffle:\n                random.seed(seed)\n                random.shuffle(examples)\n            if size:\n                examples = examples[:size]\n\n        include = MINIGALLERY_TEMPLATE.render(\n            image_dir=\"/_static\",\n            gallery_dir=gallery_dir,\n            examples=examples,\n            titles=titles,\n            width=width,\n        )\n\n        # parse and return documentation\n        result = ViewList()\n        for line in include.split(\"\\n\"):\n            result.append(line, \"<altair-minigallery>\")\n        node = nodes.paragraph()\n        node.document = self.state.document\n        nested_parse_with_titles(self.state, result, node)\n\n        return node.children\n\n\ndef main(app):\n    gallery_dir = app.builder.config.altair_gallery_dir\n    target_dir = os.path.join(app.builder.srcdir, gallery_dir)\n    image_dir = os.path.join(app.builder.srcdir, \"_images\")\n\n    gallery_ref = app.builder.config.altair_gallery_ref\n    gallery_title = app.builder.config.altair_gallery_title\n    examples = populate_examples(gallery_ref=gallery_ref, code_below=True, strict=False)\n\n    if not os.path.exists(target_dir):\n        os.makedirs(target_dir)\n\n    examples = sorted(examples, key=lambda x: x[\"title\"])\n    examples_toc = collections.OrderedDict(\n        {\n            \"Simple Charts\": [],\n            \"Bar Charts\": [],\n            \"Line Charts\": [],\n            \"Area Charts\": [],\n            \"Circular Plots\": [],\n            \"Scatter Plots\": [],\n            \"Uncertainties And Trends\": [],\n            \"Distributions\": [],\n            \"Tables\": [],\n            \"Maps\": [],\n            \"Interactive Charts\": [],\n            \"Advanced Calculations\": [],\n            \"Case Studies\": [],\n        }\n    )\n    for d in examples:\n        examples_toc[d[\"category\"]].append(d)\n\n    # Write the gallery index file\n    with open(os.path.join(target_dir, \"index.rst\"), \"w\") as f:\n        f.write(\n            GALLERY_TEMPLATE.render(\n                title=gallery_title,\n                examples=examples_toc.items(),\n                image_dir=\"/_static\",\n                gallery_ref=gallery_ref,\n            )\n        )\n\n    # save the images to file\n    save_example_pngs(examples, image_dir)\n\n    # Write the individual example files\n    for prev_ex, example, next_ex in prev_this_next(examples):\n        if prev_ex:\n            example[\"prev_ref\"] = \"gallery_{name}\".format(**prev_ex)\n        if next_ex:\n            example[\"next_ref\"] = \"gallery_{name}\".format(**next_ex)\n        target_filename = os.path.join(target_dir, example[\"name\"] + \".rst\")\n        with open(os.path.join(target_filename), \"w\", encoding=\"utf-8\") as f:\n            f.write(EXAMPLE_TEMPLATE.render(example))\n\n\ndef setup(app):\n    app.connect(\"builder-inited\", main)\n    app.add_css_file(\"altair-gallery.css\")\n    app.add_config_value(\"altair_gallery_dir\", \"gallery\", \"env\")\n    app.add_config_value(\"altair_gallery_ref\", \"example-gallery\", \"env\")\n    app.add_config_value(\"altair_gallery_title\", \"Example Gallery\", \"env\")\n    app.add_directive_to_domain(\"py\", \"altair-minigallery\", AltairMiniGalleryDirective)\n", "path": "sphinxext/altairgallery.py"}]}