Repository: projectmesa/mesa
Pull request #1991 (2024-01-23 01:53:23)

feat: Implement Altair version of grid visualization
Demo for Boltzmann wealth model:

https://github.com/projectmesa/mesa/assets/395821/a74aaf4f-1547-41f8-88f5-466500506317



github-actions[bot]: Performance benchmarks:

| Model        | Size   | Init time [95% CI]     | Run time [95% CI]      |
|--------------|--------|------------------------|------------------------|
| Schelling    | small  | 🔵 +2.1% [+1.8%, +2.3%] | 🔵 +0.2% [+0.1%, +0.3%] |
| Schelling    | large  | 🔵 +1.8% [+1.3%, +2.3%] | 🔵 -0.7% [-1.5%, -0.1%] |
| WolfSheep    | small  | 🔵 +0.1% [-0.3%, +0.5%] | 🔵 -0.3% [-0.4%, -0.1%] |
| WolfSheep    | large  | 🔵 +2.7% [-1.2%, +7.9%] | 🔵 +1.5% [-0.8%, +4.1%] |
| BoidFlockers | small  | 🔵 -1.6% [-2.0%, -1.2%] | 🔵 +0.4% [-0.2%, +0.9%] |
| BoidFlockers | large  | 🔵 +0.1% [-0.5%, +0.5%] | 🔵 +0.9% [+0.3%, +1.5%] |

rht: The animation jitter could be fixed with a `plt.tight_layout()` equivalent of Altair. I just haven't figured it out yet.
rht: Fixed for multigrid, where now you may see overlapping markers:
![2024-01-22T23:22:59,903471196-05:00](https://github.com/projectmesa/mesa/assets/395821/47a18664-3785-420c-ad77-addca1a50587)

EwoutH: Thanks for this effort, will try to review it later today.

Also curious what @Corvince and @ankitk50 think
Corvince: Nice and simple solution, but the obvious question is how this relates to https://github.com/projectmesa/mesa/pull/1902

This PR has a agent_portrayal function, but I don't see how it is used. Since altair uses a declarative approach vs an imperative in matplotlib, I think its confusing to have different agent_portrayal functions. But would have to see the agent_portrayal. 

Otherwise drawing agents on the edge itself looks strange to me, but this shouldn't be a deciding factor. 
rht: > Since altair uses a declarative approach vs an imperative in matplotlib, I think its confusing to have different agent_portrayal functions. But would have to see the agent_portrayal.

I need to say that there is no modification needed in https://github.com/projectmesa/mesa-examples/blob/21624513c6ad94faabbb1cb4b97d73b897f9afae/examples/boltzmann_wealth_model_experimental/app.py#L5-L11, i.e. the `agent_portrayal` API is the same for both Matplotlib and Altair version. I just need to specify `space_drawer="altair"`.
Corvince: > > Since altair uses a declarative approach vs an imperative in matplotlib, I think its confusing to have different agent_portrayal functions. But would have to see the agent_portrayal.
> 
> I need to say that there is no modification needed in https://github.com/projectmesa/mesa-examples/blob/21624513c6ad94faabbb1cb4b97d73b897f9afae/examples/boltzmann_wealth_model_experimental/app.py#L5-L11, i.e. the `agent_portrayal` API is the same for both Matplotlib and Altair version. I just need to specify `space_drawer="altair"` in the `JupyterViz` initialization.

Not exactly. For size this works as you described, but for color in matplotlib you set the actual colors (in your example tab:red and tab:blue ). In altair/vega you set the color encoding. So you are just saying give the value "tab:red" one color and the value "tab:blue" another one. The actual colors that are used are just the default categorical ones (lightblue and orange)
rht: I was about to switch the Altair viz color behavior to match Matplotlib (i.e. color means color, instead of category encoded in color), but decided against it, because the underlying code will look more opaque for the users to modify. As such, I'm leaving the code in this PR as is, to limit its scope. The scope is to have a feature parity with Matplotlib JupyterViz's `_draw_grid` as much as possible.

We need something like the 2nd point in https://github.com/projectmesa/mesa/pull/1988#issuecomment-1904528049 that @quaquel raised. This can be in the form of abstracting out the chart definition https://github.com/projectmesa/mesa/pull/1991/files#diff-681727772982fb0400d413fc9253bbc5e1af9b51d5a9d8581f2ab9d39a3cd516R39-R51 to a function that can be specified by the user.
rht: > The animation jitter could be fixed with a plt.tight_layout() equivalent of Altair. I just haven't figured it out yet.

I fixed this by converting the data from DF to `alt.Data`.

Patch:
diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
index 8428aba7..d59663f6 100644
--- a/.github/workflows/benchmarks.yml
+++ b/.github/workflows/benchmarks.yml
@@ -29,7 +29,7 @@ jobs:
       - name: Add project directory to PYTHONPATH
         run: echo "PYTHONPATH=$PYTHONPATH:$(pwd)" >> $GITHUB_ENV
       - name: Install dependencies
-        run: pip install numpy pandas tqdm tabulate
+        run: pip install numpy pandas tqdm tabulate matplotlib solara networkx
       # Benchmarks on the projectmesa main branch
       - name: Checkout main branch
         uses: actions/checkout@v4
diff --git a/.github/workflows/build_lint.yml b/.github/workflows/build_lint.yml
index b017c817..c1dcedb8 100644
--- a/.github/workflows/build_lint.yml
+++ b/.github/workflows/build_lint.yml
@@ -73,10 +73,10 @@ jobs:
     - name: Install Mesa
       run: pip install --no-deps .
     - name: Test with pytest
-      run: pytest --cov=mesa tests/ --cov-report=xml
+      run: pytest --durations=10 --cov=mesa tests/ --cov-report=xml
     - if: matrix.os == 'ubuntu'
       name: Codecov
-      uses: codecov/codecov-action@v3
+      uses: codecov/codecov-action@v4
 
   examples:
     runs-on: ubuntu-latest
diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
deleted file mode 100644
index 119fe890..00000000
--- a/.github/workflows/codespell.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: build
-
-on:
-  push:
-    branches:
-    - main
-    - release**
-  pull_request:
-
-jobs:
-  codespell:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v4
-    - uses: codespell-project/actions-codespell@master
-      with:
-        ignore_words_file: .codespellignore
-        skip: .*bootstrap.*,*.js,.*bootstrap-theme.css.map
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 661543ba..32f2f7d5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,7 +4,7 @@ ci:
 repos:
 - repo: https://github.com/astral-sh/ruff-pre-commit
   # Ruff version.
-  rev: v0.1.6
+  rev: v0.2.0
   hooks:
     # Run the linter.
     - id: ruff
@@ -24,3 +24,11 @@ repos:
     -   id: trailing-whitespace
     -   id: check-toml
     -   id: check-yaml
+- repo: https://github.com/codespell-project/codespell
+  rev: v2.2.6
+  hooks:
+    - id: codespell
+      args: [
+        "--ignore-words",
+        ".codespellignore",
+      ]
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..ffc5d699
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,110 @@
+# Contributing
+
+As an open source project, Mesa welcomes contributions of many forms, and from beginners to experts. If you are
+curious or just want to see what is happening, we post our development session agendas
+and development session notes on [Mesa discussions]. We also have a threaded discussion forum on [Matrix]
+for casual conversation.
+
+In no particular order, examples include:
+
+- Code patches
+- Bug reports and patch reviews
+- New features
+- Documentation improvements
+- Tutorials
+
+No contribution is too small. Although, contributions can be too big, so let's
+discuss via [Matrix] OR via [an issue].
+
+**To submit a contribution**
+
+- Create a ticket for the item that you are working on.
+- Fork the Mesa repository.
+- [Clone your repository] from Github to your machine.
+- Create a new branch in your fork: `git checkout -b BRANCH_NAME`
+- Run `git config pull.rebase true`. This prevents messy merge commits when updating your branch on top of Mesa main branch.
+- Install an editable version with developer requirements locally: `pip install -e ".[dev]"`
+- Edit the code. Save.
+- Git add the new files and files with changes: `git add FILE_NAME`
+- Git commit your changes with a meaningful message: `git commit -m "Fix issue X"`
+- If implementing a new feature, include some documentation in docs folder.
+- Make sure that your submission works with a few of the examples in the examples repository. If adding a new feature to mesa, please illustrate usage by implementing it in an example.
+- Make sure that your submission passes the [GH Actions build]. See "Testing and Standards below" to be able to run these locally.
+- Make sure that your code is formatted according to [the black] standard (you can do it via [pre-commit]).
+- Push your changes to your fork on Github: `git push origin NAME_OF_BRANCH`.
+- [Create a pull request].
+- Describe the change w/ ticket number(s) that the code fixes.
+- Format your commit message as per [Tim Pope's guideline].
+
+## Testing and Code Standards
+
+```{image} https://codecov.io/gh/projectmesa/mesa/branch/main/graph/badge.svg
+:target: https://codecov.io/gh/projectmesa/mesa
+```
+
+```{image} https://img.shields.io/badge/code%20style-black-000000.svg
+:target: https://github.com/psf/black
+```
+
+As part of our contribution process, we practice continuous integration and use GH Actions to help enforce best practices.
+
+If you're changing previous Mesa features, please make sure of the following:
+
+- Your changes pass the current tests.
+- Your changes pass our style standards.
+- Your changes don't break the models or your changes include updated models.
+- Additional features or rewrites of current features are accompanied by tests.
+- New features are demonstrated in a model, so folks can understand more easily.
+
+To ensure that your submission will not break the build, you will need to install Ruff and pytest.
+
+```bash
+pip install ruff pytest pytest-cov
+```
+
+We test by implementing simple models and through traditional unit tests in the tests/ folder. The following only covers unit tests coverage. Ensure that your test coverage has not gone down. If it has and you need help, we will offer advice on how to structure tests for the contribution.
+
+```bash
+py.test --cov=mesa tests/
+```
+
+With respect to code standards, we follow [PEP8] and the [Google Style Guide]. We use [ruff format] (a more performant alternative to `black`) as an automated code formatter. You can automatically format your code using [pre-commit], which will prevent `git commit` of unstyled code and will automatically apply black style so you can immediately re-run `git commit`. To set up pre-commit run the following commands:
+
+```bash
+pip install pre-commit
+pre-commit install
+```
+
+You should no longer have to worry about code formatting. If still in doubt you may run the following command. If the command generates errors, fix all errors that are returned.
+
+```bash
+ruff .
+```
+
+## Licensing
+
+The license of this project is located in [LICENSE].  By submitting a contribution to this project, you are agreeing that your contribution will be released under the terms of this license.
+
+## Special Thanks
+
+A special thanks to the following projects who offered inspiration for this contributing file.
+
+- [Django]
+- [18F's FOIA]
+- [18F's Midas]
+
+[18f's foia]: https://github.com/18F/foia-hub/blob/master/CONTRIBUTING.md
+[18f's midas]: https://github.com/18F/midas/blob/devel/CONTRIBUTING.md
+[an issue]: https://github.com/projectmesa/mesa/issues
+[black]: https://github.com/psf/black
+[clone your repository]: https://help.github.com/articles/cloning-a-repository/
+[create a pull request]: https://help.github.com/articles/creating-a-pull-request/
+[django]: https://github.com/django/django/blob/master/CONTRIBUTING.rst
+[gh actions build]: https://github.com/projectmesa/mesa/actions/workflows/build_lint.yml
+[google style guide]: https://google.github.io/styleguide/pyguide.html
+[license]: https://github.com/projectmesa/mesa/blob/main/LICENSE
+[matrix]: https://matrix.to/#/#project-mesa:matrix.org`
+[mesa discussions]: https://github.com/projectmesa/mesa/discussions
+[pep8]: https://www.python.org/dev/peps/pep-0008
+[pre-commit]: https://github.com/pre-commit/pre-commit
+[tim pope's guideline]: https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
deleted file mode 100644
index 839fcec8..00000000
--- a/CONTRIBUTING.rst
+++ /dev/null
@@ -1,121 +0,0 @@
-Contributing
-=========================
-
-As an open source project, Mesa welcomes contributions of many forms, and from beginners to experts. If you are
-curious or just want to see what is happening, we post our development session agendas
-and development session notes on `Mesa discussions`_. We also have a threaded discussion forum on `Matrix`_
-for casual conversation.
-
-In no particular order, examples include:
-
-- Code patches
-- Bug reports and patch reviews
-- New features
-- Documentation improvements
-- Tutorials
-
-No contribution is too small. Although, contributions can be too big, so let's
-discuss via `Matrix`_ OR via `an issue`_.
-
-.. _`Mesa discussions`: https://github.com/projectmesa/mesa/discussions
-.. _`Matrix`: https://matrix.to/#/#project-mesa:matrix.org`
-.. _`an issue` : https://github.com/projectmesa/mesa/issues
-
-**To submit a contribution**
-
-- Create a ticket for the item that you are working on.
-- Fork the Mesa repository.
-- `Clone your repository`_ from Github to your machine.
-- Create a new branch in your fork: ``git checkout -b BRANCH_NAME``
-- Run ``git config pull.rebase true``. This prevents messy merge commits when updating your branch on top of Mesa main branch.
-- Install an editable version with developer requirements locally: ``pip install -e ".[dev]"``
-- Edit the code. Save.
-- Git add the new files and files with changes: ``git add FILE_NAME``
-- Git commit your changes with a meaningful message: ``git commit -m "Fix issue X"``
-- If implementing a new feature, include some documentation in docs folder.
-- Make sure that your submission works with a few of the examples in the examples repository. If adding a new feature to mesa, please illustrate usage by implementing it in an example.
-- Make sure that your submission passes the `GH Actions build`_. See "Testing and Standards below" to be able to run these locally.
-- Make sure that your code is formatted according to `the black`_ standard (you can do it via `pre-commit`_).
-- Push your changes to your fork on Github: ``git push origin NAME_OF_BRANCH``.
-- `Create a pull request`_.
-- Describe the change w/ ticket number(s) that the code fixes.
-- Format your commit message as per `Tim Pope's guideline`_.
-
-.. _`Clone your repository` : https://help.github.com/articles/cloning-a-repository/
-.. _`GH Actions build` : https://github.com/projectmesa/mesa/actions/workflows/build_lint.yml
-.. _`Create a pull request` : https://help.github.com/articles/creating-a-pull-request/
-.. _`pre-commit` : https://github.com/pre-commit/pre-commit
-.. _`black` : https://github.com/psf/black
-.. _`Tim Pope's guideline` : https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
-
-
-Testing and Code Standards
---------
-
-.. image:: https://codecov.io/gh/projectmesa/mesa/branch/main/graph/badge.svg
-  :target: https://codecov.io/gh/projectmesa/mesa
-
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
-    :target: https://github.com/psf/black
-
-As part of our contribution process, we practice continuous integration and use GH Actions to help enforce best practices.
-
-If you're changing previous Mesa features, please make sure of the following:
-
-- Your changes pass the current tests.
-- Your changes pass our style standards.
-- Your changes don't break the models or your changes include updated models.
-- Additional features or rewrites of current features are accompanied by tests.
-- New features are demonstrated in a model, so folks can understand more easily.
-
-To ensure that your submission will not break the build, you will need to install Ruff and pytest.
-
-.. code-block:: bash
-
-    pip install ruff pytest pytest-cov
-
-We test by implementing simple models and through traditional unit tests in the tests/ folder. The following only covers unit tests coverage. Ensure that your test coverage has not gone down. If it has and you need help, we will offer advice on how to structure tests for the contribution.
-
-.. code-block:: bash
-
-    py.test --cov=mesa tests/
-
-With respect to code standards, we follow `PEP8`_ and the `Google Style Guide`_. We use `ruff format`_ (a more performant alternative to `black`) as an automated code formatter. You can automatically format your code using `pre-commit`_, which will prevent `git commit` of unstyled code and will automatically apply black style so you can immediately re-run `git commit`. To set up pre-commit run the following commands:
-
-.. code-block:: bash
-
-    pip install pre-commit
-    pre-commit install
-
-You should no longer have to worry about code formatting. If still in doubt you may run the following command. If the command generates errors, fix all errors that are returned.
-
-.. code-block:: bash
-
-    ruff .
-
-
-.. _`PEP8` : https://www.python.org/dev/peps/pep-0008
-.. _`Google Style Guide` : https://google.github.io/styleguide/pyguide.html
-.. _`pre-commit` : https://github.com/pre-commit/pre-commit
-.. _`black` : https://github.com/psf/black
-
-Licensing
---------
-
-The license of this project is located in `LICENSE`_.  By submitting a contribution to this project, you are agreeing that your contribution will be released under the terms of this license.
-
-.. _`LICENSE` : https://github.com/projectmesa/mesa/blob/main/LICENSE
-
-
-Special Thanks
---------
-
-A special thanks to the following projects who offered inspiration for this contributing file.
-
-- `Django`_
-- `18F's FOIA`_
-- `18F's Midas`_
-
-.. _`Django` : https://github.com/django/django/blob/master/CONTRIBUTING.rst
-.. _`18F's FOIA` : https://github.com/18F/foia-hub/blob/master/CONTRIBUTING.md
-.. _`18F's Midas` : https://github.com/18F/midas/blob/devel/CONTRIBUTING.md
diff --git a/benchmarks/WolfSheep/wolf_sheep.py b/benchmarks/WolfSheep/wolf_sheep.py
index 90948a0c..5d4d9fef 100644
--- a/benchmarks/WolfSheep/wolf_sheep.py
+++ b/benchmarks/WolfSheep/wolf_sheep.py
@@ -84,7 +84,7 @@ class Wolf(Animal):
         sheep = [obj for obj in agents if isinstance(obj, Sheep)]
         if len(sheep) > 0:
             sheep_to_eat = self.random.choice(sheep)
-            self.energy += self.energy
+            self.energy += self.energy_from_food
 
             # Kill the sheep
             sheep_to_eat.die()
diff --git a/benchmarks/global_benchmark.py b/benchmarks/global_benchmark.py
index 78c14a57..0f8d5379 100644
--- a/benchmarks/global_benchmark.py
+++ b/benchmarks/global_benchmark.py
@@ -9,7 +9,7 @@ import timeit
 # also installed in site_packages or so.
 sys.path.insert(0, os.path.abspath(".."))
 
-from configurations import configurations  # noqa: E402
+from configurations import configurations
 
 
 # Generic function to initialize and run a model
diff --git a/docs/apis/api_main.md b/docs/apis/api_main.md
new file mode 100644
index 00000000..ae77d47a
--- /dev/null
+++ b/docs/apis/api_main.md
@@ -0,0 +1,12 @@
+# APIs
+
+```{toctree}
+:maxdepth: 3
+
+Base Classes <init>
+time
+space
+datacollection
+batchrunner
+visualization
+```
diff --git a/docs/apis/api_main.rst b/docs/apis/api_main.rst
deleted file mode 100644
index bb3b4b14..00000000
--- a/docs/apis/api_main.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-APIs
-----
-
-.. toctree::
-   :maxdepth: 3
-
-   Base Classes <init>
-   time
-   space
-   datacollection
-   batchrunner
-   visualization
diff --git a/docs/apis/batchrunner.rst b/docs/apis/batchrunner.md
similarity index 69%
rename from docs/apis/batchrunner.rst
rename to docs/apis/batchrunner.md
index 2d41aefe..29074843 100644
--- a/docs/apis/batchrunner.rst
+++ b/docs/apis/batchrunner.md
@@ -1,2 +1,4 @@
+```{eval-rst}
 .. automodule:: batchrunner
    :members:
+```
diff --git a/docs/apis/datacollection.rst b/docs/apis/datacollection.md
similarity index 70%
rename from docs/apis/datacollection.rst
rename to docs/apis/datacollection.md
index dc03b2c8..2455aac7 100644
--- a/docs/apis/datacollection.rst
+++ b/docs/apis/datacollection.md
@@ -1,2 +1,4 @@
+```{eval-rst}
 .. automodule:: datacollection
    :members:
+```
diff --git a/docs/apis/init.rst b/docs/apis/init.md
similarity index 51%
rename from docs/apis/init.rst
rename to docs/apis/init.md
index 48922db8..caa45449 100644
--- a/docs/apis/init.rst
+++ b/docs/apis/init.md
@@ -1,8 +1,11 @@
-Base Classes
-----
+# Base Classes
 
+```{eval-rst}
 .. autoclass:: mesa.Agent
    :members:
+```
 
+```{eval-rst}
 .. autoclass:: mesa.Model
-   :members:
\ No newline at end of file
+   :members:
+```
diff --git a/docs/apis/space.md b/docs/apis/space.md
new file mode 100644
index 00000000..86ad12f7
--- /dev/null
+++ b/docs/apis/space.md
@@ -0,0 +1,5 @@
+```{eval-rst}
+.. automodule:: mesa.space
+   :members:
+   :inherited-members:
+```
diff --git a/docs/apis/space.rst b/docs/apis/space.rst
deleted file mode 100644
index 715d3645..00000000
--- a/docs/apis/space.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-.. automodule:: mesa.space
-   :members:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/apis/time.rst b/docs/apis/time.md
similarity index 77%
rename from docs/apis/time.rst
rename to docs/apis/time.md
index 53c5d027..df69d86c 100644
--- a/docs/apis/time.rst
+++ b/docs/apis/time.md
@@ -1,3 +1,5 @@
+```{eval-rst}
 .. automodule:: mesa.time
    :members:
    :inherited-members:
+```
diff --git a/docs/apis/visualization.rst b/docs/apis/visualization.md
similarity index 75%
rename from docs/apis/visualization.rst
rename to docs/apis/visualization.md
index 466936d2..fde799e0 100644
--- a/docs/apis/visualization.rst
+++ b/docs/apis/visualization.md
@@ -1,26 +1,38 @@
-Visualization
--------------
+# Visualization
 
+```{eval-rst}
 .. automodule:: visualization.__init__
    :members:
+```
 
+```{eval-rst}
 .. automodule:: visualization.ModularVisualization
    :members:
+```
 
+```{eval-rst}
 .. automodule:: visualization.TextVisualization
    :members:
+```
 
-Modules
-~~~~~~~
+## Modules
 
+```{eval-rst}
 .. automodule:: visualization.modules.__init__
    :members:
+```
 
+```{eval-rst}
 .. automodule:: visualization.modules.CanvasGridVisualization
    :members:
+```
 
+```{eval-rst}
 .. automodule:: visualization.modules.ChartVisualization
    :members:
+```
 
+```{eval-rst}
 .. automodule:: visualization.modules.TextVisualization
    :members:
+```
diff --git a/docs/best-practices.md b/docs/best-practices.md
new file mode 100644
index 00000000..e7f695ac
--- /dev/null
+++ b/docs/best-practices.md
@@ -0,0 +1,80 @@
+# Best Practices
+
+Here are some general principles that have proven helpful for developing models.
+
+## Model Layout
+
+A model should be contained in a folder named with lower-case letters and
+underscores, such as `thunder_cats`. Within that directory:
+
+- `README.md` describes the model, how to use it, and any other details.
+  Github will automatically show this file to anyone visiting the directory.
+- `model.py` should contain the model class.  If the file gets large, it may
+  make sense to move the complex bits into other files, but this is the first
+  place readers will look to figure out how the model works.
+- `server.py` should contain the visualization support, including the server
+  class.
+- `run.py` is a Python script that will run the model when invoked via
+  `mesa runserver`.
+
+After the number of files grows beyond a half-dozen, try to use sub-folders to
+organize them. For example, if the visualization uses image files, put those in
+an `images` directory.
+
+The [Schelling](https://github.com/projectmesa/mesa-examples/tree/main/examples/schelling) model is
+a good example of a small well-packaged model.
+
+It's easy to create a cookiecutter mesa model by running `mesa startproject`
+
+## Randomization
+
+If your model involves some random choice, you can use the built-in `random`
+property that Mesa `Model` and `Agent` objects have. This works exactly
+like the built-in `random` library.
+
+```python
+class AwesomeModel(Model):
+  # ...
+
+  def cool_method(self):
+    interesting_number = self.random.random()
+    print(interesting_number)
+
+class AwesomeAgent(Agent):
+  # ...
+  def __init__(self, unique_id, model, ...):
+    super().__init__(unique_id, model)
+    # ...
+
+  def my_method(self):
+    random_number = self.random.randint(0, 100)
+```
+
+(The agent's random property is just a reference to its parent model's
+`random` property).
+
+When a model object is created, its random property is automatically seeded
+with the current time. The seed determines the sequence of random numbers; if
+you instantiate a model with the same seed, you will get the same results.
+To allow you to set the seed, make sure your model has a `seed` argument in its
+constructor.
+
+```python
+class AwesomeModel(Model):
+
+  def __init__(self, seed=None):
+    pass
+
+  def cool_method(self):
+    interesting_number = self.random.random()
+    print(interesting_number)
+
+>>> model0 = AwesomeModel(seed=0)
+>>> model0._seed
+0
+>>> model0.cool_method()
+0.8444218515250481
+>>> model1 = AwesomeModel(seed=0)
+>>> model1.cool_method()
+0.8444218515250481
+```
diff --git a/docs/best-practices.rst b/docs/best-practices.rst
deleted file mode 100644
index a73c125f..00000000
--- a/docs/best-practices.rst
+++ /dev/null
@@ -1,87 +0,0 @@
-Best Practices
-==============
-
-Here are some general principles that have proven helpful for developing models.
-
-Model Layout
-------------
-
-A model should be contained in a folder named with lower-case letters and
-underscores, such as ``thunder_cats``. Within that directory:
-
-* ``README.md`` describes the model, how to use it, and any other details.
-  Github will automatically show this file to anyone visiting the directory.
-
-* ``model.py`` should contain the model class.  If the file gets large, it may
-  make sense to move the complex bits into other files, but this is the first
-  place readers will look to figure out how the model works.
-
-* ``server.py`` should contain the visualization support, including the server
-  class.
-
-* ``run.py`` is a Python script that will run the model when invoked via
-  ``mesa runserver``.
-
-After the number of files grows beyond a half-dozen, try to use sub-folders to
-organize them. For example, if the visualization uses image files, put those in
-an ``images`` directory.
-
-The `Schelling
-<https://github.com/projectmesa/mesa-examples/tree/main/examples/schelling>`_ model is
-a good example of a small well-packaged model.
-
-It's easy to create a cookiecutter mesa model by running ``mesa startproject``
-
-Randomization
--------------
-
-If your model involves some random choice, you can use the built-in ``random``
-property that Mesa ``Model`` and ``Agent`` objects have. This works exactly
-like the built-in ``random`` library.
-
-.. code:: python
-
-  class AwesomeModel(Model):
-    # ...
-
-    def cool_method(self):
-      interesting_number = self.random.random()
-      print(interesting_number)
-
-  class AwesomeAgent(Agent):
-    # ...
-    def __init__(self, unique_id, model, ...):
-      super().__init__(unique_id, model)
-      # ...
-
-    def my_method(self):
-      random_number = self.random.randint(0, 100)
-
-(The agent's random property is just a reference to its parent model's
-``random`` property).
-
-When a model object is created, its random property is automatically seeded
-with the current time. The seed determines the sequence of random numbers; if
-you instantiate a model with the same seed, you will get the same results.
-To allow you to set the seed, make sure your model has a ``seed`` argument in its
-constructor.
-
-.. code:: python
-
-  class AwesomeModel(Model):
-
-    def __init__(self, seed=None):
-      pass
-
-    def cool_method(self):
-      interesting_number = self.random.random()
-      print(interesting_number)
-
-  >>> model0 = AwesomeModel(seed=0)
-  >>> model0._seed
-  0
-  >>> model0.cool_method()
-  0.8444218515250481
-  >>> model1 = AwesomeModel(seed=0)
-  >>> model1.cool_method()
-  0.8444218515250481
diff --git a/docs/howto.md b/docs/howto.md
new file mode 100644
index 00000000..7fbb08e3
--- /dev/null
+++ b/docs/howto.md
@@ -0,0 +1,83 @@
+# How-to Guide
+
+This guide provides concise instructions and examples to help you start with common tasks in Mesa.
+
+## Models with Discrete Time
+
+For models involving agents of multiple types, including those with a time attribute, you can construct a discrete-time model. This setup allows each agent to perform actions in steps that correspond to the model's discrete time.
+
+Example:
+```python
+if self.model.schedule.time in self.discrete_time:
+    self.model.space.move_agent(self, new_pos)
+```
+
+## Implementing Model-Level Functions with Staged Activation
+
+In staged activation scenarios, to designate functions that should only operate
+at the model level (and not at the agent level), prepend the function name with
+"model." in the function list definition. This approach is useful for
+model-wide operations, like adjusting a wage rate based on demand.
+
+Example:
+```python
+stage_list = [Send_Labour_Supply, Send_Labour_Demand, model.Adjust_Wage_Rate]
+self.schedule = StagedActivation(self, stage_list, shuffle=True)
+```
+
+## Using `numpy.random`
+
+To incorporate `numpy`'s random functions, such as for generating a Poisson
+distribution, initialize a random number generator in your model's constructor.
+
+Example:
+```python
+import mesa
+import numpy as np
+
+class MyModel(mesa.Model):
+    def __init__(self, seed=None):
+        super().__init__()
+        self.random = np.random.default_rng(seed)
+```
+
+Usage example:
+```python
+lambda_value = 5
+sample = self.random.poisson(lambda_value)
+```
+
+## Multi-process `batch_run` on Windows
+
+When using `batch_run` with `number_processes = None` on Windows, you might
+encounter progress display issues or `AttributeError: Can't get attribute
+'MoneyModel' on <module '__main__' (built-in)>`. To resolve this, run
+your code outside of Jupyter notebooks and use the following pattern,
+incorporating `freeze_support()` for multiprocessing support.
+
+Example:
+```python
+from mesa.batchrunner import batch_run
+from multiprocessing import freeze_support
+
+params = {"width": 10, "height": 10, "N": range(10, 500, 10)}
+
+if __name__ == '__main__':
+    freeze_support()
+    results = batch_run(
+        MoneyModel,
+        parameters=params,
+        iterations=5,
+        max_steps=100,
+        number_processes=None,
+        data_collection_period=1,
+        display_progress=True,
+    )
+```
+
+If you would still like to run your code in Jupyter, adjust your code as
+described and consider integrating external libraries like
+[nbmultitask](https://nbviewer.org/github/micahscopes/nbmultitask/blob/39b6f31b047e8a51a0fcb5c93ae4572684f877ce/examples.ipynb)
+or refer to [Stack
+Overflow](https://stackoverflow.com/questions/50937362/multiprocessing-on-python-3-jupyter)
+for multiprocessing tips.
diff --git a/docs/howto.rst b/docs/howto.rst
deleted file mode 100644
index 09792af5..00000000
--- a/docs/howto.rst
+++ /dev/null
@@ -1,70 +0,0 @@
-How-to Guide
-============
-
-Here you can find code that allows you to get to get started on common tasks in Mesa.
-
-Models with Discrete Time
--------------------------
-If you have `Multiple` type agents and one of them has time attribute you can still build a model that is run by discrete time. In this example, each step of the model, and the agents have a time attribute that is equal to the discrete time to run its own step.
-
-.. code:: python
-
-  if self.model.schedule.time in self.discrete_time:
-      self.model.space.move_agent(self, new_pos)
-
-Implementing Model Level Functions in Staged Activation
--------------------------------------------------------
-In staged activation, if you may want a function to be implemented only on the model level and not at the level of agents.
-For such functions, include the prefix "model." before the model function name, when defining the function list.
-For example, consider a central employment exchange which adjust the wage rate common to all laborers
-in the direction of excess demand.
-
-.. code:: python
-    stage_list=[Send_Labour_Supply, Send_Labour_Demand, model.Adjust_Wage_Rate]
-    self.schedule = StagedActivation(self,stage_list,shuffle=True)
-
-Using ```numpy.random```
--------------
-
-Sometimes you need to use ``numpy``'s ``random`` library, for example to get a Poisson distribution.
-
-.. code:: python
-
-  class MyModel(Model):
-      def __init__(self, ...):
-          super().__init__()
-          self.random = np.random.default_rng(seed)
-
-And just use `numpy`'s random as usual, e.g. ``self.random.poisson()``.
-
-Using multi-process ```batch_run``` on Windows
--------------
-
-You will have an issue with `batch_run` and `number_processes = None`. Your cell will
-show no progress, and in your terminal you will receive *AttributeError: Can't get attribute 'MoneyModel' on
-<module '__main__' (built-in)>*. One way to overcome this is to take your code outside of Jupyter and adjust the above
-code as follows.
-
-
-.. code:: python
-
-    from multiprocessing import freeze_support
-
-    params = {"width": 10, "height": 10, "N": range(10, 500, 10)}
-
-    if __name__ == '__main__':
-        freeze_support()
-        results = batch_run(
-                MoneyModel,
-                parameters=params,
-                iterations=5,
-                max_steps=100,
-                number_processes=None,
-                data_collection_period=1,
-                display_progress=True,
-        )
-
-
-If you would still like to run your code in Jupyter you will need to adjust the cell as noted above. Then you can
-you can add the `nbmultitask library <(https://nbviewer.org/github/micahscopes/nbmultitask/blob/39b6f31b047e8a51a0fcb5c93ae4572684f877ce/examples.ipynb)>`__
-or look at this `stackoverflow <https://stackoverflow.com/questions/50937362/multiprocessing-on-python-3-jupyter>`__.
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..22812595
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,105 @@
+# Mesa: Agent-based modeling in Python
+
+```{image} https://github.com/projectmesa/mesa/workflows/build/badge.svg
+:target: https://github.com/projectmesa/mesa/actions
+```
+
+```{image} https://codecov.io/gh/projectmesa/mesa/branch/main/graph/badge.svg
+:target: https://codecov.io/gh/projectmesa/mesa
+```
+
+```{image} https://img.shields.io/badge/code%20style-black-000000.svg
+:target: https://github.com/psf/black
+```
+
+```{image} https://img.shields.io/matrix/project-mesa:matrix.org?label=chat&logo=Matrix
+:target: https://matrix.to/#/#project-mesa:matrix.orgs
+```
+
+[Mesa] is an Apache2 licensed agent-based modeling (or ABM) framework in Python.
+
+Mesa allows users to quickly create agent-based models using built-in core components (such as spatial grids and agent schedulers) or customized implementations; visualize them using a browser-based interface; and analyze their results using Python's data analysis tools. Its goal is to be the Python-based counterpart to NetLogo, Repast, or MASON.
+
+```{image} https://raw.githubusercontent.com/projectmesa/mesa/main/docs/images/Mesa_Screenshot.png
+:alt: A screenshot of the Schelling Model in Mesa
+:scale: 100%
+:width: 100%
+```
+
+*Above: A Mesa implementation of the Schelling segregation model,
+being visualized in a browser window and analyzed in a Jupyter
+notebook.*
+
+## Features
+
+- Modular components
+- Browser-based visualization
+- Built-in tools for analysis
+
+## Using Mesa
+
+Getting started quickly:
+
+```bash
+pip install mesa
+```
+
+To launch an example model, clone the [repository](https://github.com/projectmesa/mesa) folder and invoke `mesa runserver` for one of the `examples/` subdirectories:
+
+```bash
+mesa runserver examples/wolf_sheep
+```
+
+For more help on using Mesa, check out the following resources:
+
+- [Mesa Introductory Tutorial]
+- [Mesa Visualization Tutorial]
+- [GitHub Issue Tracker]
+- [Email list]
+- [PyPI]
+
+## Contributing back to Mesa
+
+If you run into an issue, please file a [ticket] for us to discuss. If possible, follow up with a pull request.
+
+If you would like to add a feature, please reach out via [ticket] or the [email list] for discussion. A feature is most likely to be added if you build it!
+
+- [Contributors guide]
+- [Github]
+
+## Mesa Packages
+
+ABM features users have shared that you may want to use in your model
+
+- [See the Packages](https://github.com/projectmesa/mesa/wiki)
+- {ref}`Mesa-Packages <packages>`
+
+```{toctree}
+:hidden: true
+:maxdepth: 7
+
+Mesa Overview <overview>
+tutorials/intro_tutorial
+tutorials/visualization_tutorial
+Best Practices <best-practices>
+How-to Guide <howto>
+API Documentation <apis/api_main>
+Mesa Packages <packages>
+tutorials/adv_tutorial_legacy.ipynb
+```
+
+# Indices and tables
+
+- {ref}`genindex`
+- {ref}`modindex`
+- {ref}`search`
+
+[contributors guide]: https://github.com/projectmesa/mesa/blob/main/CONTRIBUTING.rst
+[email list]: https://groups.google.com/d/forum/projectmesa
+[github]: https://github.com/projectmesa/mesa/
+[github issue tracker]: https://github.com/projectmesa/mesa/issues
+[mesa]: https://github.com/projectmesa/mesa/
+[mesa introductory tutorial]: tutorials/intro_tutorial.html
+[mesa visualization tutorial]: tutorials/visualization_tutorial.html
+[pypi]: https://pypi.python.org/pypi/Mesa/
+[ticket]: https://github.com/projectmesa/mesa/issues
diff --git a/docs/index.rst b/docs/index.rst
deleted file mode 100644
index 17404bc1..00000000
--- a/docs/index.rst
+++ /dev/null
@@ -1,111 +0,0 @@
-Mesa: Agent-based modeling in Python
-====================================
-
-.. image:: https://github.com/projectmesa/mesa/workflows/build/badge.svg
-        :target: https://github.com/projectmesa/mesa/actions
-
-.. image:: https://codecov.io/gh/projectmesa/mesa/branch/main/graph/badge.svg
-        :target: https://codecov.io/gh/projectmesa/mesa
-
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
-        :target: https://github.com/psf/black
-
-.. image:: https://img.shields.io/matrix/project-mesa:matrix.org?label=chat&logo=Matrix
-        :target: https://matrix.to/#/#project-mesa:matrix.orgs
-
-`Mesa`_ is an Apache2 licensed agent-based modeling (or ABM) framework in Python.
-
-Mesa allows users to quickly create agent-based models using built-in core components (such as spatial grids and agent schedulers) or customized implementations; visualize them using a browser-based interface; and analyze their results using Python's data analysis tools. Its goal is to be the Python-based counterpart to NetLogo, Repast, or MASON.
-
-
-.. image:: https://raw.githubusercontent.com/projectmesa/mesa/main/docs/images/Mesa_Screenshot.png
-   :width: 100%
-   :scale: 100%
-   :alt: A screenshot of the Schelling Model in Mesa
-
-*Above: A Mesa implementation of the Schelling segregation model,
-being visualized in a browser window and analyzed in a Jupyter
-notebook.*
-
-.. _`Mesa` : https://github.com/projectmesa/mesa/
-
-
-Features
---------
-
-* Modular components
-* Browser-based visualization
-* Built-in tools for analysis
-
-Using Mesa
-----------
-
-Getting started quickly:
-
-.. code-block:: bash
-
-    pip install mesa
-
-To launch an example model, clone the `repository <https://github.com/projectmesa/mesa>`_ folder and invoke ``mesa runserver`` for one of the ``examples/`` subdirectories:
-
-.. code-block:: bash
-
-    mesa runserver examples/wolf_sheep
-
-For more help on using Mesa, check out the following resources:
-
-* `Mesa Introductory Tutorial`_
-* `Mesa Visualization Tutorial`_
-* `GitHub Issue Tracker`_
-* `Email list`_
-* `PyPI`_
-
-.. _`Mesa Introductory Tutorial` : tutorials/intro_tutorial.html
-.. _`Mesa Visualization Tutorial` : tutorials/visualization_tutorial.html
-.. _`GitHub Issue Tracker` : https://github.com/projectmesa/mesa/issues
-.. _`Email list` : https://groups.google.com/d/forum/projectmesa
-.. _`PyPI` : https://pypi.python.org/pypi/Mesa/
-
-Contributing back to Mesa
--------------------------
-
-If you run into an issue, please file a `ticket`_ for us to discuss. If possible, follow up with a pull request.
-
-If you would like to add a feature, please reach out via `ticket`_ or the `email list`_ for discussion. A feature is most likely to be added if you build it!
-
-* `Contributors guide`_
-* `Github`_
-
-.. _`ticket` : https://github.com/projectmesa/mesa/issues
-.. _`Contributors guide` : https://github.com/projectmesa/mesa/blob/main/CONTRIBUTING.rst
-.. _`Github` : https://github.com/projectmesa/mesa/
-
-
-Mesa Packages
---------------------------------------
-
-ABM features users have shared that you may want to use in your model
-
-* `See the Packages <https://github.com/projectmesa/mesa/wiki>`_
-* :ref:`Mesa-Packages <packages>`
-
-
-.. toctree::
-   :hidden:
-   :maxdepth: 7
-
-   Mesa Overview <overview>
-   tutorials/intro_tutorial
-   tutorials/visualization_tutorial
-   Best Practices <best-practices>
-   How-to Guide <howto>
-   API Documentation <apis/api_main>
-   Mesa Packages <packages>
-   tutorials/adv_tutorial_legacy.ipynb
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
diff --git a/docs/mesa.rst b/docs/mesa.md
similarity index 62%
rename from docs/mesa.rst
rename to docs/mesa.md
index 524a79af..08a7148c 100644
--- a/docs/mesa.rst
+++ b/docs/mesa.md
@@ -1,77 +1,82 @@
-mesa package
-============
+# mesa package
 
-Subpackages
------------
+## Subpackages
 
-.. toctree::
+```{toctree}
+mesa.visualization
+```
 
-    mesa.visualization
+## Submodules
 
-Submodules
-----------
-
-mesa.agent module
------------------
+## mesa.agent module
 
+```{eval-rst}
 .. automodule:: mesa.agent
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.batchrunner module
------------------------
+## mesa.batchrunner module
 
+```{eval-rst}
 .. automodule:: mesa.batchrunner
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.datacollection module
---------------------------
+## mesa.datacollection module
 
+```{eval-rst}
 .. automodule:: mesa.datacollection
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.main module
-----------------
+## mesa.main module
 
+```{eval-rst}
 .. automodule:: mesa.main
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.model module
------------------
+## mesa.model module
 
+```{eval-rst}
 .. automodule:: mesa.model
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.space module
------------------
+## mesa.space module
 
+```{eval-rst}
 .. automodule:: mesa.space
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.time module
-----------------
+## mesa.time module
 
+```{eval-rst}
 .. automodule:: mesa.time
     :members:
     :undoc-members:
     :show-inheritance:
 
+```
 
-Module contents
----------------
+## Module contents
 
+```{eval-rst}
 .. automodule:: mesa
     :members:
     :undoc-members:
     :show-inheritance:
+```
diff --git a/docs/mesa.visualization.md b/docs/mesa.visualization.md
new file mode 100644
index 00000000..5efa9662
--- /dev/null
+++ b/docs/mesa.visualization.md
@@ -0,0 +1,46 @@
+# mesa.visualization package
+
+## Subpackages
+
+```{toctree}
+mesa.visualization.modules
+```
+
+## Submodules
+
+## mesa.visualization.ModularVisualization module
+
+```{eval-rst}
+.. automodule:: mesa.visualization.ModularVisualization
+    :members:
+    :undoc-members:
+    :show-inheritance:
+```
+
+## mesa.visualization.TextVisualization module
+
+```{eval-rst}
+.. automodule:: mesa.visualization.TextVisualization
+    :members:
+    :undoc-members:
+    :show-inheritance:
+```
+
+## mesa.visualization.UserParam module
+
+```{eval-rst}
+.. automodule:: mesa.visualization.UserParam
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+```
+
+## Module contents
+
+```{eval-rst}
+.. automodule:: mesa.visualization
+    :members:
+    :undoc-members:
+    :show-inheritance:
+```
diff --git a/docs/mesa.visualization.modules.rst b/docs/mesa.visualization.modules.md
similarity index 51%
rename from docs/mesa.visualization.modules.rst
rename to docs/mesa.visualization.modules.md
index b96466da..6380f982 100644
--- a/docs/mesa.visualization.modules.rst
+++ b/docs/mesa.visualization.modules.md
@@ -1,70 +1,76 @@
-mesa.visualization.modules package
-==================================
+# mesa.visualization.modules package
 
-Submodules
-----------
+## Submodules
 
-mesa.visualization.modules.BarChartVisualization module
--------------------------------------------------------
+## mesa.visualization.modules.BarChartVisualization module
 
+```{eval-rst}
 .. automodule:: mesa.visualization.modules.BarChartVisualization
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.visualization.modules.CanvasGridVisualization module
----------------------------------------------------------
+## mesa.visualization.modules.CanvasGridVisualization module
 
+```{eval-rst}
 .. automodule:: mesa.visualization.modules.CanvasGridVisualization
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.visualization.modules.ChartVisualization module
-----------------------------------------------------
+## mesa.visualization.modules.ChartVisualization module
 
+```{eval-rst}
 .. automodule:: mesa.visualization.modules.ChartVisualization
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.visualization.modules.HexGridVisualization module
-------------------------------------------------------
+## mesa.visualization.modules.HexGridVisualization module
 
+```{eval-rst}
 .. automodule:: mesa.visualization.modules.HexGridVisualization
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.visualization.modules.NetworkVisualization module
-------------------------------------------------------
+## mesa.visualization.modules.NetworkVisualization module
 
+```{eval-rst}
 .. automodule:: mesa.visualization.modules.NetworkVisualization
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.visualization.modules.PieChartVisualization module
--------------------------------------------------------
+## mesa.visualization.modules.PieChartVisualization module
 
+```{eval-rst}
 .. automodule:: mesa.visualization.modules.PieChartVisualization
     :members:
     :undoc-members:
     :show-inheritance:
+```
 
-mesa.visualization.modules.TextVisualization module
----------------------------------------------------
+## mesa.visualization.modules.TextVisualization module
 
+```{eval-rst}
 .. automodule:: mesa.visualization.modules.TextVisualization
     :members:
     :undoc-members:
     :show-inheritance:
 
+```
 
-Module contents
----------------
+## Module contents
 
+```{eval-rst}
 .. automodule:: mesa.visualization.modules
     :members:
     :undoc-members:
     :show-inheritance:
+```
diff --git a/docs/mesa.visualization.rst b/docs/mesa.visualization.rst
deleted file mode 100644
index 2e8ae7ac..00000000
--- a/docs/mesa.visualization.rst
+++ /dev/null
@@ -1,45 +0,0 @@
-mesa.visualization package
-==========================
-
-Subpackages
------------
-
-.. toctree::
-
-    mesa.visualization.modules
-
-Submodules
-----------
-
-mesa.visualization.ModularVisualization module
-----------------------------------------------
-
-.. automodule:: mesa.visualization.ModularVisualization
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-mesa.visualization.TextVisualization module
--------------------------------------------
-
-.. automodule:: mesa.visualization.TextVisualization
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-mesa.visualization.UserParam module
------------------------------------
-
-.. automodule:: mesa.visualization.UserParam
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: mesa.visualization
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/modular-visualization.rst b/docs/modular-visualization.md
similarity index 58%
rename from docs/modular-visualization.rst
rename to docs/modular-visualization.md
index 247303c8..93fb4673 100644
--- a/docs/modular-visualization.rst
+++ b/docs/modular-visualization.md
@@ -1,5 +1,4 @@
-Modular Visualization - An In-Depth Look
-========================================
+# Modular Visualization - An In-Depth Look
 
 Modular visualization is one of Mesa's core features. Mesa is designed
 to provide predefined visualization modules, which can be easily
@@ -7,8 +6,7 @@ subclassed for your needs, and mixed-and-matched to visualize your
 particular model. (Some day, Mesa hopes to host a wide variety.) This
 document describes how to use and create new visualization modules.
 
-Overview
---------
+## Overview
 
 An interactive modular visualization is a set of **Elements**, each of
 which is an instance of a visualization **Module**. To visualize a
@@ -16,22 +14,22 @@ model, create a new ModularServer with a list of the elements you want
 to visualize, in the order you want them to appear on the visualization
 web page.
 
-For example, if you have a model ``MyModel``, and two elements,
-*canvas\_vis* and *graph\_vis*, you would create a visualization with
+For example, if you have a model `MyModel`, and two elements,
+*canvas_vis* and *graph_vis*, you would create a visualization with
 them via:
 
-.. code:: python
-
-        server = ModularServer(MyModel, [canvas_vis, graph_vis])
-        server.launch()
+```python
+server = ModularServer(MyModel, [canvas_vis, graph_vis])
+server.launch()
+```
 
 Then you will be able to view the elements in your browser at
-http://127.0.0.1:8521/. If you prefer a different port, for example
+<http://127.0.0.1:8521/>. If you prefer a different port, for example
 8887, you can pass it to the server as an argument.
 
-.. code:: python
-
-        server.launch(8887)
+```python
+server.launch(8887)
+```
 
 Under the hood, each visualization module consists of two parts:
 
@@ -41,8 +39,7 @@ Under the hood, each visualization module consists of two parts:
    from the Python render (via the ModularServer) and actually draws it
    in the browser.
 
-Using Pre-Built Modules
------------------------
+## Using Pre-Built Modules
 
 Mesa already comes with some pre-built modules. Using the built-ins
 allow you to build a visualization without worrying about the HTML and
@@ -55,43 +52,43 @@ agent-based models, particularly the simpler ones.
 CanvasGrid iterates over every object in every cell of your model's grid
 (it assumes that your model has a grid named **grid**) and converts it
 into a dictionary which defines how it will be drawn. It does this via a
-**portrayal\_method**: a function which the user defines, which takes an
+**portrayal_method**: a function which the user defines, which takes an
 object as an input and outputs a dictionary with the following keys:
 
-::
-
-    "Shape": Can be "circle", "rect" or "arrowHead"
-        For Circles:
-            "r": The radius, defined as a fraction of cell size. r=1 will fill the entire cell.
-        For rectangles:
-            "w", "h": The width and height of the rectangle, which are in fractions of cell width and height.
-        For arrowHead:
-            "scale": Proportion scaling as a fraction of cell size.
-            "heading_x": represents x direction unit vector.
-            "heading_y": represents y direction unit vector.
-    "Color": The color to draw the shape in; needs to be a valid HTML color, e.g."Red" or "#AA08F8"
-    "Filled": either "true" or "false", and determines whether the shape is filled or not.
-    "Layer": Layer number of 0 or above; higher-numbered layers are drawn above lower-numbered layers.
-    "text": Text to overlay on top of the shape. Normally, agent's unique_id is used .
-    "text_color": Color of the text overlay.
-    (Shapes also have "x" and "y" coordinates, for the x and y of the grid cell in which it is, but CanvasGrid adds those automatically).
+```
+"Shape": Can be "circle", "rect" or "arrowHead"
+    For Circles:
+        "r": The radius, defined as a fraction of cell size. r=1 will fill the entire cell.
+    For rectangles:
+        "w", "h": The width and height of the rectangle, which are in fractions of cell width and height.
+    For arrowHead:
+        "scale": Proportion scaling as a fraction of cell size.
+        "heading_x": represents x direction unit vector.
+        "heading_y": represents y direction unit vector.
+"Color": The color to draw the shape in; needs to be a valid HTML color, e.g."Red" or "#AA08F8"
+"Filled": either "true" or "false", and determines whether the shape is filled or not.
+"Layer": Layer number of 0 or above; higher-numbered layers are drawn above lower-numbered layers.
+"text": Text to overlay on top of the shape. Normally, agent's unique_id is used .
+"text_color": Color of the text overlay.
+(Shapes also have "x" and "y" coordinates, for the x and y of the grid cell in which it is, but CanvasGrid adds those automatically).
+```
 
 For example, suppose for a Schelling model, we want to draw all agents
 as circles; red ones for the majority (agent type=0), and blue ones for
 the minority (agent type=1). The function to do this might look like
 this:
 
-.. code:: python
-
-        def schelling_draw(agent):
-            if agent is None:
-                return
-            portrayal = {"Shape": "circle", "r": 0.5, "Filled": "true", "Layer": 0}
-            if agent.type == 0:
-                portrayal["Color"] = "Red"
-            else:
-                portrayal["Color"] = "Blue"
-            return portrayal
+```python
+def schelling_draw(agent):
+    if agent is None:
+        return
+    portrayal = {"Shape": "circle", "r": 0.5, "Filled": "true", "Layer": 0}
+    if agent.type == 0:
+        portrayal["Color"] = "Red"
+    else:
+        portrayal["Color"] = "Blue"
+    return portrayal
+```
 
 In addition, a CanvasGrid needs to know the width and height of the grid
 (in number of cells), and the width and height in pixels of the grid to
@@ -102,20 +99,19 @@ with a grid of 10x10, which we want to draw at 500px X 500px. using the
 portrayal function we wrote above, we would instantiate our
 visualization element as follows:
 
-.. code:: python
-
-        canvas_element = CanvasGrid(schelling_draw, 10, 10, 500, 500)
+```python
+canvas_element = CanvasGrid(schelling_draw, 10, 10, 500, 500)
+```
 
 Then, to launch a server with this grid as the only visualization
 element:
 
-.. code:: python
-
-        server = ModularServer(SchellingModel, [canvas_element], "Schelling")
-        server.launch()
+```python
+server = ModularServer(SchellingModel, [canvas_element], "Schelling")
+server.launch()
+```
 
-Sub-Classing Modules
---------------------
+## Sub-Classing Modules
 
 In some cases, you may want to customize the internals of an existing
 visualization module. The best way to do this is to create a subclass of
@@ -130,42 +126,41 @@ model object.
 Suppose we want a module which can get an arbitrary variable out of a
 model, and display its name and value. Let's create a new subclass:
 
-.. code:: python
+```python
+from mesa.visualization.ModularTextVisualization import TextElement
 
-        from mesa.visualization.ModularTextVisualization import TextElement
+class AttributeElement(TextElement):
+    def __init__(self, attr_name):
+        '''
+        Create a new text attribute element.
 
-        class AttributeElement(TextElement):
-            def __init__(self, attr_name):
-                '''
-                Create a new text attribute element.
+        Args:
+            attr_name: The name of the attribute to extract from the model.
 
-                Args:
-                    attr_name: The name of the attribute to extract from the model.
+        Example return: "happy: 10"
+        '''
+        self.attr_name = attr_name
 
-                Example return: "happy: 10"
-                '''
-                self.attr_name = attr_name
-
-            def render(self, model):
-                val = getattr(model, self.attr_name)
-                return attr_name + ": " + str(val)
+    def render(self, model):
+        val = getattr(model, self.attr_name)
+        return attr_name + ": " + str(val)
+```
 
 Now, if we wanted to use our new AttributeElement to add the number of
 happy agents to our Schelling visualization, it might look something
 like this:
 
-.. code:: python
-
-        happy_element = AttributeElement("happy")
-        server = ModularServer(SchellingModel, [canvas_element, happy_element], "Schelling")
-        server.launch()
+```python
+happy_element = AttributeElement("happy")
+server = ModularServer(SchellingModel, [canvas_element, happy_element], "Schelling")
+server.launch()
+```
 
 Note that, in this case, we only wanted to change the Python-side render
 method. We're still using the parent module's HTML and JavaScript
 template.
 
-Creating a new browser display
-------------------------------
+## Creating a new browser display
 
 But what if we want more than just a different Python renderer; we want
 to substantially change how a module displays in the browser, or create
@@ -175,23 +170,23 @@ as well:
 Let's take a look at the internals of **TextModule.js**, the JavaScript
 for the TextVisualization. Here it is, in all its glory:
 
-.. code:: javascript
-
-    const TextModule = function () {
-      const text = document.createElement("p");
-      text.className = "lead";
+```javascript
+const TextModule = function () {
+  const text = document.createElement("p");
+  text.className = "lead";
 
-      // Append text tag to #elements:
-      document.getElementById("elements").appendChild(text);
+  // Append text tag to #elements:
+  document.getElementById("elements").appendChild(text);
 
-      this.render = function (data) {
-        text.innerHTML = data;
-      };
+  this.render = function (data) {
+    text.innerHTML = data;
+  };
 
-      this.reset = function () {
-        text.innerHTML = "";
-      };
-    };
+  this.reset = function () {
+    text.innerHTML = "";
+  };
+};
+```
 
 This code is the JavaScript equivalent of defining a class. When
 instantiated, a TextModule object will create a new paragraph tag and
@@ -209,20 +204,20 @@ Now let's take a look at the TextModule's Python counterpart,
 **TextElement** (which resides in **TextVisualization.py**). Again,
 here's the whole thing:
 
-.. code:: python
+```python
+from mesa.visualization.ModularVisualization import VisualizationElement
 
-        from mesa.visualization.ModularVisualization import VisualizationElement
-
-        class TextElement(VisualizationElement):
-            js_includes = ["TextModule.js"]
-            js_code = "elements.push(new TextModule());"
+class TextElement(VisualizationElement):
+    js_includes = ["TextModule.js"]
+    js_code = "elements.push(new TextModule());"
+```
 
 That's it! Notice that it is lacking a *render()* method, like the one
-we defined above. Look at what is there: *js\_includes* is a list of
+we defined above. Look at what is there: *js_includes* is a list of
 JavaScript files to import into the page when this element is present.
 In this case, it just imports **TextModule.js**.
 
-Next, *js\_code* is some JavaScript code, in Python string form, to run
+Next, *js_code* is some JavaScript code, in Python string form, to run
 when the visualization page loads. In this case, *new TextModule()*
 creates a new TextModule object as defined above (which, remember, also
 appends a new paragraph to the page body) which is then appended to the
@@ -233,12 +228,12 @@ To help understand why it looks like this, here's a snippet of
 JavaScript from the overall visualization template itself, on how to
 handle incoming data:
 
-.. code:: javascript
-
-        data = msg["data"]
-        for (var i in elements) {
-            elements[i].render(data[i]);
-        }
+```javascript
+data = msg["data"]
+for (var i in elements) {
+    elements[i].render(data[i]);
+}
+```
 
 Data to visualize arrive over the websocket as a list. For each index of
 the list, the code passes that element of the data to the *render*
diff --git a/docs/overview.rst b/docs/overview.md
similarity index 50%
rename from docs/overview.rst
rename to docs/overview.md
index b2b9ddfc..a384b02e 100644
--- a/docs/overview.rst
+++ b/docs/overview.md
@@ -1,13 +1,10 @@
-Mesa Overview
-=============
+# Mesa Overview
 
 Mesa is a modular framework for building, analyzing and visualizing agent-based models.
 
 **Agent-based models** are computer simulations involving multiple entities (the agents) acting and interacting with one another based on their programmed behavior. Agents can be used to represent living cells, animals, individual humans, even entire organizations or abstract entities. Sometimes, we may have an understanding of how the individual components of a system behave, and want to see what system-level behaviors and effects emerge from their interaction. Other times, we may have a good idea of how the system overall behaves, and want to figure out what individual behaviors explain it. Or we may want to see how to get agents to cooperate or compete most effectively. Or we may just want to build a cool toy with colorful little dots moving around.
 
-
-Mesa Modules
-------------
+## Mesa Modules
 
 Mesa is modular, meaning that its modeling, analysis and visualization components are kept separate but intended to work together. The modules are grouped into three categories:
 
@@ -15,142 +12,135 @@ Mesa is modular, meaning that its modeling, analysis and visualization component
 2. **Analysis:** Tools to collect data generated from your model, or to run it multiple times with different parameter values.
 3. **Visualization:** Classes to create and launch an interactive model visualization, using a server with a JavaScript interface.
 
-
-Modeling modules
-~~~~~~~~~~~~~~~~
+### Modeling modules
 
 Most models consist of one class to represent the model itself; one class (or more) for agents; a scheduler to handle time (what order the agents act in), and possibly a space for the agents to inhabit and move through. These are implemented in Mesa's modeling modules:
 
-* ``mesa.Model``, ``mesa.Agent``
-* `mesa.time <apis/time.html>`_
-* `mesa.space <apis/space.html>`_
+- `mesa.Model`, `mesa.Agent`
+- [mesa.time](apis/time.html)
+- [mesa.space](apis/space.html)
 
 The skeleton of a model might look like this:
 
-.. code:: python
-
-    import mesa
-
-    class MyAgent(mesa.Agent):
-        def __init__(self, name, model):
-            super().__init__(name, model)
-            self.name = name
-
-        def step(self):
-            print("{} activated".format(self.name))
-            # Whatever else the agent does when activated
-
-    class MyModel(mesa.Model):
-        def __init__(self, n_agents):
-            super().__init__()
-            self.schedule = mesa.time.RandomActivation(self)
-            self.grid = mesa.space.MultiGrid(10, 10, torus=True)
-            for i in range(n_agents):
-                a = MyAgent(i, self)
-                self.schedule.add(a)
-                coords = (self.random.randrange(0, 10), self.random.randrange(0, 10))
-                self.grid.place_agent(a, coords)
-
-        def step(self):
-            self.schedule.step()
+```python
+import mesa
+
+class MyAgent(mesa.Agent):
+    def __init__(self, name, model):
+        super().__init__(name, model)
+        self.name = name
+
+    def step(self):
+        print("{} activated".format(self.name))
+        # Whatever else the agent does when activated
+
+class MyModel(mesa.Model):
+    def __init__(self, n_agents):
+        super().__init__()
+        self.schedule = mesa.time.RandomActivation(self)
+        self.grid = mesa.space.MultiGrid(10, 10, torus=True)
+        for i in range(n_agents):
+            a = MyAgent(i, self)
+            self.schedule.add(a)
+            coords = (self.random.randrange(0, 10), self.random.randrange(0, 10))
+            self.grid.place_agent(a, coords)
+
+    def step(self):
+        self.schedule.step()
+```
 
 If you instantiate a model and run it for one step, like so:
 
-.. code:: python
+```python
+model = MyModel(5)
+model.step()
+```
 
-    model = MyModel(5)
-    model.step()
+You should see agents 0-4, activated in random order. See the [tutorial](tutorials/intro_tutorial.html) or API documentation for more detail on how to add model functionality.
 
-You should see agents 0-4, activated in random order. See the `tutorial <tutorials/intro_tutorial.html>`_ or API documentation for more detail on how to add model functionality.
+To bootstrap a new model install mesa and run `mesa startproject`
 
-To bootstrap a new model install mesa and run ``mesa startproject``
-
-Analysis modules
-~~~~~~~~~~~~~~~~
+### Analysis modules
 
 If you're using modeling for research, you'll want a way to collect the data each model run generates. You'll probably also want to run the model multiple times, to see how some output changes with different parameters. Data collection and batch running are implemented in the appropriately-named analysis modules:
 
-* `mesa.datacollection <apis/datacollection.html>`_
-* `mesa.batchrunner <apis/batchrunner.html>`_
+- [mesa.datacollection](apis/datacollection.html)
+- [mesa.batchrunner](apis/batchrunner.html)
 
 You'd add a data collector to the model like this:
 
-.. code:: python
-
-    import mesa
+```python
+import mesa
 
-    # ...
+# ...
 
-    class MyModel(mesa.Model):
-        def __init__(self, n_agents):
-            # ...
-            self.dc = mesa.DataCollector(model_reporters={"agent_count":
-                                        lambda m: m.schedule.get_agent_count()},
-                                    agent_reporters={"name": lambda a: a.name})
+class MyModel(mesa.Model):
+    def __init__(self, n_agents):
+        # ...
+        self.dc = mesa.DataCollector(model_reporters={"agent_count":
+                                    lambda m: m.schedule.get_agent_count()},
+                                agent_reporters={"name": lambda a: a.name})
 
-        def step(self):
-            self.schedule.step()
-            self.dc.collect(self)
+    def step(self):
+        self.schedule.step()
+        self.dc.collect(self)
+```
 
-The data collector will collect the specified model- and agent-level data at each step of the model. After you're done running it, you can extract the data as a `pandas <http://pandas.pydata.org/>`_ DataFrame:
-
-.. code:: python
-
-    model = MyModel(5)
-    for t in range(10):
-        model.step()
-    model_df = model.dc.get_model_vars_dataframe()
-    agent_df = model.dc.get_agent_vars_dataframe()
+The data collector will collect the specified model- and agent-level data at each step of the model. After you're done running it, you can extract the data as a [pandas](http://pandas.pydata.org/) DataFrame:
 
+```python
+model = MyModel(5)
+for t in range(10):
+    model.step()
+model_df = model.dc.get_model_vars_dataframe()
+agent_df = model.dc.get_agent_vars_dataframe()
+```
 
 To batch-run the model while varying, for example, the n_agents parameter, you'd use the `batch_run` function:
 
-.. code:: python
-
-    import mesa
-
-    parameters = {"n_agents": range(1, 20)}
-    mesa.batch_run(
-        MyModel,
-        parameters,
-        max_steps=10,
-    )
+```python
+import mesa
 
+parameters = {"n_agents": range(1, 20)}
+mesa.batch_run(
+    MyModel,
+    parameters,
+    max_steps=10,
+)
+```
 
 As with the data collector, once the runs are all over, you can extract the data as a data frame.
 
-.. code:: python
+```python
+batch_df = batch_run.get_model_vars_dataframe()
+```
 
-    batch_df = batch_run.get_model_vars_dataframe()
-
-
-Visualization modules
-~~~~~~~~~~~~~~~~~~~~~
+### Visualization modules
 
 Finally, you may want to directly observe your model as it runs. Mesa's main visualization tool uses a small local web server to render the model in a browser, using JavaScript. There are different components for drawing different types of data: for example, grids for drawing agents moving around on a grid, or charts for showing how some data changes as the model runs. A few core modules are:
 
-* mesa.visualization.ModularVisualization
-* mesa.visualization.modules
+- mesa.visualization.ModularVisualization
+- mesa.visualization.modules
 
 To quickly spin up a model visualization, you might do something like:
 
-.. code:: python
-
-    import mesa
-
-    def agent_portrayal(agent):
-        portrayal = {"Shape": "circle",
-                     "Filled": "true",
-                     "Layer": 0,
-                     "Color": "red",
-                     "r": 0.5}
-        return portrayal
-
-    grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500)
-    server = mesa.visualization.ModularServer(MyModel,
-                           [grid],
-                           "My Model",
-                           {'n_agents': 10})
-    server.launch()
+```python
+import mesa
+
+def agent_portrayal(agent):
+    portrayal = {"Shape": "circle",
+                 "Filled": "true",
+                 "Layer": 0,
+                 "Color": "red",
+                 "r": 0.5}
+    return portrayal
+
+grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500)
+server = mesa.visualization.ModularServer(MyModel,
+                       [grid],
+                       "My Model",
+                       {'n_agents': 10})
+server.launch()
+```
 
 This will launch the browser-based visualization, on the default port 8521.
diff --git a/docs/packages.rst b/docs/packages.md
similarity index 55%
rename from docs/packages.rst
rename to docs/packages.md
index 4cfb4ea9..e15c6baa 100644
--- a/docs/packages.rst
+++ b/docs/packages.md
@@ -1,27 +1,24 @@
-"How To" Mesa Packages
-======================
+# "How To" Mesa Packages
 
 The Mesa core functionality is just a subset of what we believe researchers creating Agent Based Models (ABMs) will use. We designed Mesa to be extensible, so that individuals from various domains can build, maintain, and share their own packages that work with Mesa in pursuit of "unifying algorithmic theories of the relation between adaptive behavior and system complexity (Volker Grimm et al 2005)."
 
 **DRY Principle**
 
-This decoupling of code to create building blocks is a best practice in software engineering. Specifically, it exercises the `DRY principle (or don't repeat yourself) <https://en.wikipedia.org/wiki/Don%27t_repeat_yourself>`_ (Hunt and Thomas 2010). The creators of Mesa designed Mesa in order for this principle to be exercised in the development of agent-based models (ABMs). For example, a group of health experts may create a library of human interactions on top of core Mesa. That library then is used by other health experts. So, those health experts don't have to rewrite the same basic behaviors.
+This decoupling of code to create building blocks is a best practice in software engineering. Specifically, it exercises the [DRY principle (or don't repeat yourself)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) (Hunt and Thomas 2010). The creators of Mesa designed Mesa in order for this principle to be exercised in the development of agent-based models (ABMs). For example, a group of health experts may create a library of human interactions on top of core Mesa. That library then is used by other health experts. So, those health experts don't have to rewrite the same basic behaviors.
 
 **Benefits to Scientists**
 
 Besides a best practice of the software engineering community, there are other benefits for the scientific community.
 
 1. **Reproducibility and Replicability.** Decoupled shared packages also allows for reproducibility and replicability. Having a package that is shared allows others to reproduce the model results. It also allows others to apply the model to similar phenomenon and replicate the results over a diversity of data. Both are essential part of the scientific method (Leek and Peng 2015).
-
 2. **Accepted truths.** Once results are reproduced and replicated, a library could be considered an accepted truth, meaning that the community agrees the library does what the library intends to do and  the library can be trusted to do this. Part of the idea behind 'accepted truths' is that subject matter experts are the ones that write and maintain the library.
-
 3. **Building blocks.** Think of libraries like Legos. The researcher can borrow a piece from here or there to pull together the base of their model, so they can focus on the value add that they bring. For example, someone might pull from a human interactions library and a decision-making library and combine the two to look at how human cognitive function effects the physical spread of disease.
 
 **Mesa and Mesa Packages**
 
 Because of the possibilities of nuanced libraries, few things will actually make it into core Mesa. Mesa is intended to only include core functionality that everyone uses. However, it is not impossible that something written on the outside is brought into core at a later date if the value to everyone is proven through adoption.
 
-An example that is analogous to Mesa and Mesa packages is `Django <https://www.djangoproject.com/>`_ and `Django Packages <https://djangopackages.org/>`_. Django is a web framework that allows you to build a website in Python, but there are lots of things besides a basic website that you might want. For example, you might want authentication functionality. It would be inefficient for everyone to write their own authentication functionality, so one person writes it (or a group of people). They share it with the world and then many people can use it.
+An example that is analogous to Mesa and Mesa packages is [Django](https://www.djangoproject.com/) and [Django Packages](https://djangopackages.org/). Django is a web framework that allows you to build a website in Python, but there are lots of things besides a basic website that you might want. For example, you might want authentication functionality. It would be inefficient for everyone to write their own authentication functionality, so one person writes it (or a group of people). They share it with the world and then many people can use it.
 
 This process isn't perfect. Just because you write something doesn't mean people are going to use it. Sometimes two different packages will be created that do similar things, but one of them does it better or is easier to use. That is the one that will get more adoption. In the world of academia, often researchers hold on to their content until they are ready to publish it. In the world of open source software, this can backfire. The sooner you open source something the more likely it will be a success, because you will build consensus and engagement. Another thing that can happen is that while you are working on perfecting it, someone else is building in the open and establishes the audience you were looking for. So, don't be afraid to start working directly out in the open and then release it to the world.
 
@@ -29,98 +26,85 @@ This process isn't perfect. Just because you write something doesn't mean people
 
 There are two sections in this documentation. The first is the User Guide, which is aimed at users of packages. The section is a package development guide, which is aimed at those who want to develop packages. Without further ado, let's get started!
 
+## User Guide
 
-User Guide
---------------------------------------------------
-
-* Note: MESA does not endorse or verify any of the code shared through MESA packages. This is left to the domain experts of the community that created the code.*
+- Note: MESA does not endorse or verify any of the code shared through MESA packages. This is left to the domain experts of the community that created the code.\*
 
 **Step 1: Select a package**
 
-Currently, a central list of compatible packages is located on the `Mesa Wiki Packages Page <https://github.com/projectmesa/mesa/wiki/Mesa-Packages>`_.
+Currently, a central list of compatible packages is located on the [Mesa Wiki Packages Page](https://github.com/projectmesa/mesa/wiki/Mesa-Packages).
 
 **Step 2: Establish an environment**
 
 Create a virtual environment for the ABM you are building. The purpose of a virtual environment is to isolate the packages for your project from other projects. This is helpful when you need to use two different versions of a package or if you are running one version in production but want to test out another version. You can do with either virtualenv or Anaconda.
 
-   - `Why a virtual environment <https://realpython.com/blog/python/python-virtual-environments-a-primer/#why-the-need-for-virtual-environments>`_
-   - `Virtualenv and Virtualenv Wrapper <http://docs.python-guide.org/en/latest/#python-development-environments>`_
-   - `Creating a virtual environment with Anaconda <https://conda.io/docs/user-guide/tasks/manage-environments.html>`_
+> - [Why a virtual environment](https://realpython.com/blog/python/python-virtual-environments-a-primer/#why-the-need-for-virtual-environments)
+> - [Virtualenv and Virtualenv Wrapper](http://docs.python-guide.org/en/latest/#python-development-environments)
+> - [Creating a virtual environment with Anaconda](https://conda.io/docs/user-guide/tasks/manage-environments.html)
 
 **Step 3: Install the packages**
 
 Install the package(s) into your environment via pip/conda or GitHub. If the package is a mature package that is hosted in the Python package repository, then you can install it just like you did Mesa:
 
-   .. code:: bash
-
-      pip install package_name
+> ```bash
+> pip install package_name
+> ```
 
 However, sometimes it takes a little bit for projects to reach that level of maturity. In that case to use the library, you would install from GitHub (or other code repository) with something like the following:
 
-
-   .. code:: bash
-
-      pip install https://github.com/<path to project>
+> ```bash
+> pip install https://github.com/<path to project>
+> ```
 
 The commands above should also work with Anaconda, just replace the `pip` with `conda`.
 
-
-Package Development: A "How-to Guide"
---------------------------------------------------
+## Package Development: A "How-to Guide"
 
 The purpose of this section is help you understand, setup, and distribute your Mesa package as quickly as possible. A Mesa package is just a Python package or repo. We just call it a Mesa package, because we are talking about a Python package in the context of Mesa. These instructions assume that you are a little familiar with development, but that you have little knowledge of the packaging process.
 
 There are two ways to share a package:
 
-   1. Via GitHub or other service (e.g. GitLab, Bitbucket, etc.)
-   2. Via PyPI, the Python package manager
+> 1. Via GitHub or other service (e.g. GitLab, Bitbucket, etc.)
+> 2. Via PyPI, the Python package manager
 
 Sharing a package via PyPI make it easier to install for users but is more overhead for whomever is maintaining it. However, if you are truly intending for a wider/longer-term adoption, then PyPI should be your goal.
 
 Most likely you created an ABM that has the code that you want to share in it, which is what the steps below describe.
 
-
 **Sharing your package**
 
-
-   1. Layout a new file structure to move the code into and then make sure it is callable from Mesa, in a simple, easy to understand way. For example, ``from example_package import foo``.  See `Creating the Scaffolding <https://python-packaging.readthedocs.io/en/latest/minimal.html#creating-the-scaffolding>`_.
-
-   2. `Pick a name <https://python-packaging.readthedocs.io/en/latest/minimal.html#picking-a-name>`_.
-
-   3. `Create a repo on GitHub <https://help.github.com/articles/create-a-repo/>`_.
-
-      * Enter the name of the repo.
-      * Select a license (not sure-- click the blue 'i' next to the i for a great run down of licenses). We recommend something permissive Apache 2.0, BSD, or MIT so that others can freely adopt it. The more permissive the more likely it will gain followers and adoption. If you do not include a license, it is our belief that you will retain all rights, which means that people can't use your project, but it should be noted that we are also not lawyers.
-      * Create a readme.md file (this contains a description of the package) see an example: `Bilateral Shapley <https://github.com/tpike3/bilateralshapley/blob/master/README.md>`_
-
-   4. `Clone the repo to your computer <https://help.github.com/articles/cloning-a-repository/#platform-linux>`_.
-
-   5. Copy your code directory into the repo that you cloned one your computer.
-
-   6. Add a requirements.txt file, which lets people know which external Python packages are needed to run the code in your repo. To create a file, run: ``pip freeze > requirements.txt``. Note, if you are running Anaconda, you will need to install pip first: ``conda install pip``.
-
-   7. ``git add`` all the files to the repo, which means the repo starts to track the files. Then ``git commit`` the files with a meaningful message. To learn more about this see: `Saving changes <https://www.atlassian.com/git/tutorials/saving-changes>`_. Finally, you will want to ``git push`` all your changes to GitHub, see: `Git Push <https://help.github.com/articles/pushing-to-a-remote/>`_.
-
-   8. Let people know about your package on the `MESA Wiki Page <https://github.com/projectmesa/mesa/wiki>`_ and share it on the `email list <https://groups.google.com/forum/#!forum/projectmesa>`_. In the future, we will create more of a directory, but at this point we are not there yet.
+> 1. Layout a new file structure to move the code into and then make sure it is callable from Mesa, in a simple, easy to understand way. For example, `from example_package import foo`.  See [Creating the Scaffolding](https://python-packaging.readthedocs.io/en/latest/minimal.html#creating-the-scaffolding).
+>
+> 2. [Pick a name](https://python-packaging.readthedocs.io/en/latest/minimal.html#picking-a-name).
+>
+> 3. [Create a repo on GitHub](https://help.github.com/articles/create-a-repo/).
+>
+>    - Enter the name of the repo.
+>    - Select a license (not sure-- click the blue 'i' next to the i for a great run down of licenses). We recommend something permissive Apache 2.0, BSD, or MIT so that others can freely adopt it. The more permissive the more likely it will gain followers and adoption. If you do not include a license, it is our belief that you will retain all rights, which means that people can't use your project, but it should be noted that we are also not lawyers.
+>    - Create a readme.md file (this contains a description of the package) see an example: [Bilateral Shapley](https://github.com/tpike3/bilateralshapley/blob/master/README.md)
+>
+> 4. [Clone the repo to your computer](https://help.github.com/articles/cloning-a-repository/#platform-linux).
+>
+> 5. Copy your code directory into the repo that you cloned one your computer.
+>
+> 6. Add a requirements.txt file, which lets people know which external Python packages are needed to run the code in your repo. To create a file, run: `pip freeze > requirements.txt`. Note, if you are running Anaconda, you will need to install pip first: `conda install pip`.
+>
+> 7. `git add` all the files to the repo, which means the repo starts to track the files. Then `git commit` the files with a meaningful message. To learn more about this see: [Saving changes](https://www.atlassian.com/git/tutorials/saving-changes). Finally, you will want to `git push` all your changes to GitHub, see: [Git Push](https://help.github.com/articles/pushing-to-a-remote/).
+>
+> 8. Let people know about your package on the [MESA Wiki Page](https://github.com/projectmesa/mesa/wiki) and share it on the [email list](https://groups.google.com/forum/#!forum/projectmesa). In the future, we will create more of a directory, but at this point we are not there yet.
 
 From this point, someone can clone your repo and then add your repo to their Python path and use it in their project. However, if you want to take your package to the next level, you will want to add more structure to your package and share it on PyPI.
 
-
 **Next Level: PyPI**
 
+You want to do even more. The authoritative guide for python package development is through the [Python Packaging User Guide](https://packaging.python.org/). This will take you through the entire process necessary for getting your package on the Python Package Index.
 
-You want to do even more. The authoritative guide for python package development is through the `Python Packaging User Guide <https://packaging.python.org/>`_. This will take you through the entire process necessary for getting your package on the Python Package Index.
-
-The `Python Package Index <https://pypi.org>`_ is the main repository of software for Python Packages and following this guide will ensure your code and documentation meets the standards for distribution across the Python community.
-
+The [Python Package Index](https://pypi.org) is the main repository of software for Python Packages and following this guide will ensure your code and documentation meets the standards for distribution across the Python community.
 
-
-References
-===========
+# References
 
 Grimm, Volker, Eloy Revilla, Uta Berger, Florian Jeltsch, Wolf M. Mooij, Steven F. Railsback, Hans-Hermann Thulke, Jacob Weiner, Thorsten Wiegand, and Donald L. DeAngelis. 2005. “Pattern-Oriented Modeling of Agent Based Complex Systems: Lessons from Ecology.” American Association for the Advancement of Science 310 (5750): 987–91. doi:10.1126/science.1116681.
 
 Hunt, Andrew, and David Thomas. 2010. The Pragmatic Programmer: From Journeyman to Master. Reading, Massachusetts: Addison-Wesley.
 
 Leek, Jeffrey T., and Roger D. Peng. 2015. “Reproducible Research Can Still Be Wrong: Adopting a Prevention Approach.” Proceedings of the National Academy of Sciences 112 (6): 1645–46. doi:10.1073/pnas.1421412111.
-
diff --git a/mesa/agent.py b/mesa/agent.py
index 243a1873..57edf1a0 100644
--- a/mesa/agent.py
+++ b/mesa/agent.py
@@ -264,17 +264,29 @@ class AgentSet(MutableSet, Sequence):
 
         return res if return_results else self
 
-    def get(self, attr_name: str) -> list[Any]:
+    def get(self, attr_names: str | list[str]) -> list[Any]:
         """
-        Retrieve a specified attribute from each agent in the AgentSet.
+        Retrieve the specified attribute(s) from each agent in the AgentSet.
 
         Args:
-            attr_name (str): The name of the attribute to retrieve from each agent.
+            attr_names (str | list[str]): The name(s) of the attribute(s) to retrieve from each agent.
 
         Returns:
-            list[Any]: A list of attribute values from each agent in the set.
+            list[Any]: A list with the attribute value for each agent in the set if attr_names is a str
+            list[list[Any]]: A list with a list of attribute values for each agent in the set if attr_names is a list of str
+
+        Raises:
+            AttributeError if an agent does not have the specified attribute(s)
+
         """
-        return [getattr(agent, attr_name) for agent in self._agents]
+
+        if isinstance(attr_names, str):
+            return [getattr(agent, attr_names) for agent in self._agents]
+        else:
+            return [
+                [getattr(agent, attr_name) for attr_name in attr_names]
+                for agent in self._agents
+            ]
 
     def __getitem__(self, item: int | slice) -> Agent:
         """
diff --git a/mesa/datacollection.py b/mesa/datacollection.py
index 3fcd205c..f5f6ead2 100644
--- a/mesa/datacollection.py
+++ b/mesa/datacollection.py
@@ -184,7 +184,9 @@ class DataCollector:
 
         agent_records = map(
             get_reports,
-            model.schedule.agents if hasattr(model, "schedule") else model.agents,
+            model.schedule.agents
+            if hasattr(model, "schedule") and model.schedule is not None
+            else model.agents,
         )
         return agent_records
 
diff --git a/mesa/experimental/components/altair.py b/mesa/experimental/components/altair.py
new file mode 100644
index 00000000..2b08485b
--- /dev/null
+++ b/mesa/experimental/components/altair.py
@@ -0,0 +1,60 @@
+import contextlib
+from typing import Optional
+
+import solara
+
+with contextlib.suppress(ImportError):
+    import altair as alt
+
+
+@solara.component
+def SpaceAltair(model, agent_portrayal, dependencies: Optional[list[any]] = None):
+    space = getattr(model, "grid", None)
+    if space is None:
+        # Sometimes the space is defined as model.space instead of model.grid
+        space = model.space
+    chart = _draw_grid(space, agent_portrayal)
+    solara.FigureAltair(chart)
+
+
+def _draw_grid(space, agent_portrayal):
+    def portray(g):
+        all_agent_data = []
+        for content, (x, y) in space.coord_iter():
+            if not content:
+                continue
+            if not hasattr(content, "__iter__"):
+                # Is a single grid
+                content = [content]  # noqa: PLW2901
+            for agent in content:
+                # use all data from agent portrayal, and add x,y coordinates
+                agent_data = agent_portrayal(agent)
+                agent_data["x"] = x
+                agent_data["y"] = y
+                all_agent_data.append(agent_data)
+        return all_agent_data
+
+    all_agent_data = portray(space)
+    encoding_dict = {
+        # no x-axis label
+        "x": alt.X("x", axis=None, type="ordinal"),
+        # no y-axis label
+        "y": alt.Y("y", axis=None, type="ordinal"),
+    }
+    has_color = "color" in all_agent_data[0]
+    if has_color:
+        encoding_dict["color"] = alt.Color("color", type="nominal")
+    has_size = "size" in all_agent_data[0]
+    if has_size:
+        encoding_dict["size"] = alt.Size("size", type="quantitative")
+
+    chart = (
+        alt.Chart(
+            alt.Data(values=all_agent_data), encoding=alt.Encoding(**encoding_dict)
+        )
+        .mark_point(filled=True)
+        .properties(width=280, height=280)
+        # .configure_view(strokeOpacity=0)  # hide grid/chart lines
+    )
+
+    return chart
diff --git a/mesa/experimental/jupyter_viz.py b/mesa/experimental/jupyter_viz.py
index a6ae318a..9d29e08f 100644
--- a/mesa/experimental/jupyter_viz.py
+++ b/mesa/experimental/jupyter_viz.py
@@ -6,6 +6,7 @@ import reacton.ipywidgets as widgets
 import solara
 from solara.alias import rv
 
+import mesa.experimental.components.altair as components_altair
 import mesa.experimental.components.matplotlib as components_matplotlib
 from mesa.experimental.UserParam import Slider
 
@@ -28,6 +29,10 @@ def Card(
                 components_matplotlib.SpaceMatplotlib(
                     model, agent_portrayal, dependencies=[current_step.value]
                 )
+            elif space_drawer == "altair":
+                components_altair.SpaceAltair(
+                    model, agent_portrayal, dependencies=[current_step.value]
+                )
             elif space_drawer:
                 # if specified, draw agent space with an alternate renderer
                 space_drawer(model, agent_portrayal)
@@ -113,6 +118,10 @@ def JupyterViz(
                 components_matplotlib.SpaceMatplotlib(
                     model, agent_portrayal, dependencies=[current_step.value]
                 )
+            elif space_drawer == "altair":
+                components_altair.SpaceAltair(
+                    model, agent_portrayal, dependencies=[current_step.value]
+                )
             elif space_drawer:
                 # if specified, draw agent space with an alternate renderer
                 space_drawer(model, agent_portrayal)


Test patch:
diff --git a/tests/test_agent.py b/tests/test_agent.py
index 7ad538eb..0cd21112 100644
--- a/tests/test_agent.py
+++ b/tests/test_agent.py
@@ -221,6 +221,24 @@ def test_agentset_get_attribute():
     with pytest.raises(AttributeError):
         agentset.get("non_existing_attribute")
 
+    model = Model()
+    agents = []
+    for i in range(10):
+        agent = TestAgent(model.next_id(), model)
+        agent.i = i**2
+        agents.append(agent)
+    agentset = AgentSet(agents, model)
+
+    values = agentset.get(["unique_id", "i"])
+
+    for value, agent in zip(values, agents):
+        (
+            unique_id,
+            i,
+        ) = value
+        assert agent.unique_id == unique_id
+        assert agent.i == i
+
 
 class OtherAgentType(Agent):
     def get_unique_identifier(self):
diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py
index 1557666f..f5ddb0ae 100644
--- a/tests/test_batch_run.py
+++ b/tests/test_batch_run.py
@@ -124,8 +124,8 @@ def test_batch_run_with_params():
     mesa.batch_run(
         MockModel,
         {
-            "variable_model_params": range(5),
-            "variable_agent_params": ["H", "E", "L", "L", "O"],
+            "variable_model_params": range(3),
+            "variable_agent_params": ["H", "E", "Y"],
         },
         number_processes=2,
     )
@@ -148,7 +148,7 @@ def test_batch_run_no_agent_reporters():
 
 
 def test_batch_run_single_core():
-    mesa.batch_run(MockModel, {}, number_processes=1, iterations=10)
+    mesa.batch_run(MockModel, {}, number_processes=1, iterations=6)
 
 
 def test_batch_run_unhashable_param():