Repository: projectmesa/mesa
Pull request #1326 (2022-05-18 08:34:05)

refactor: frontend: Remove jQuery usage in BarChartModule.js
Partially addresses #1318.

codecov[bot]: # [Codecov](https://codecov.io/gh/projectmesa/mesa/pull/1326?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) Report
> Merging [#1326](https://codecov.io/gh/projectmesa/mesa/pull/1326?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) (3bece6d) into [main](https://codecov.io/gh/projectmesa/mesa/commit/41240b3d385e67f2eae2b2a8f097e4c21820a420?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) (41240b3) will **decrease** coverage by `0.01%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##             main    #1326      +/-   ##
==========================================
- Coverage   89.13%   89.12%   -0.02%     
==========================================
  Files          19       19              
  Lines        1252     1250       -2     
  Branches      246      242       -4     
==========================================
- Hits         1116     1114       -2     
  Misses        101      101              
  Partials       35       35              
```


| [Impacted Files](https://codecov.io/gh/projectmesa/mesa/pull/1326?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) | Coverage Δ | |
|---|---|---|
| [mesa/space.py](https://codecov.io/gh/projectmesa/mesa/pull/1326/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa#diff-bWVzYS9zcGFjZS5weQ==) | `94.44% <0.00%> (-0.04%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/projectmesa/mesa/pull/1326?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/projectmesa/mesa/pull/1326?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa). Last update [41240b3...3bece6d](https://codecov.io/gh/projectmesa/mesa/pull/1326?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa).


Patch:
diff --git a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py
index 0d996ca4..181daec4 100644
--- a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py
+++ b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py
@@ -1,10 +1,6 @@
-from mesa import Agent, Model
-from mesa.time import RandomActivation
-from mesa.datacollection import DataCollector
+import mesa
 import networkx as nx
 
-from mesa.space import NetworkGrid
-
 
 def compute_gini(model):
     agent_wealths = [agent.wealth for agent in model.schedule.agents]
@@ -14,7 +10,7 @@ def compute_gini(model):
     return 1 + (1 / N) - 2 * B
 
 
-class BoltzmannWealthModelNetwork(Model):
+class BoltzmannWealthModelNetwork(mesa.Model):
     """A model with some number of agents."""
 
     def __init__(self, num_agents=7, num_nodes=10):
@@ -22,14 +18,14 @@ class BoltzmannWealthModelNetwork(Model):
         self.num_agents = num_agents
         self.num_nodes = num_nodes if num_nodes >= self.num_agents else self.num_agents
         self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=0.5)
-        self.grid = NetworkGrid(self.G)
-        self.schedule = RandomActivation(self)
-        self.datacollector = DataCollector(
+        self.grid = mesa.space.NetworkGrid(self.G)
+        self.schedule = mesa.time.RandomActivation(self)
+        self.datacollector = mesa.DataCollector(
             model_reporters={"Gini": compute_gini},
             agent_reporters={"Wealth": lambda _: _.wealth},
         )
 
-        list_of_random_nodes = self.random.sample(self.G.nodes(), self.num_agents)
+        list_of_random_nodes = self.random.sample(list(self.G), self.num_agents)
 
         # Create agents
         for i in range(self.num_agents):
@@ -51,7 +47,7 @@ class BoltzmannWealthModelNetwork(Model):
             self.step()
 
 
-class MoneyAgent(Agent):
+class MoneyAgent(mesa.Agent):
     """An agent with fixed initial wealth."""
 
     def __init__(self, unique_id, model):
diff --git a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/server.py b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/server.py
index e24629cc..a8e48cce 100644
--- a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/server.py
+++ b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/server.py
@@ -1,7 +1,5 @@
-from mesa.visualization.ModularVisualization import ModularServer
-from mesa.visualization.UserParam import UserSettableParameter
-from mesa.visualization.modules import ChartModule
-from mesa.visualization.modules import NetworkModule
+import mesa
+
 from .model import BoltzmannWealthModelNetwork
 
 
@@ -29,13 +27,13 @@ def network_portrayal(G):
     return portrayal
 
 
-grid = NetworkModule(network_portrayal, 500, 500)
-chart = ChartModule(
+grid = mesa.visualization.NetworkModule(network_portrayal, 500, 500)
+chart = mesa.visualization.ChartModule(
     [{"Label": "Gini", "Color": "Black"}], data_collector_name="datacollector"
 )
 
 model_params = {
-    "num_agents": UserSettableParameter(
+    "num_agents": mesa.visualization.UserSettableParameter(
         "slider",
         "Number of agents",
         7,
@@ -44,7 +42,7 @@ model_params = {
         1,
         description="Choose how many agents to include in the model",
     ),
-    "num_nodes": UserSettableParameter(
+    "num_nodes": mesa.visualization.UserSettableParameter(
         "slider",
         "Number of nodes",
         10,
@@ -56,7 +54,7 @@ model_params = {
     ),
 }
 
-server = ModularServer(
+server = mesa.visualization.ModularServer(
     BoltzmannWealthModelNetwork, [grid, chart], "Money Model", model_params
 )
 server.port = 8521
diff --git a/examples/charts/charts/agents.py b/examples/charts/charts/agents.py
index a45ad725..67a0956b 100644
--- a/examples/charts/charts/agents.py
+++ b/examples/charts/charts/agents.py
@@ -9,11 +9,12 @@ Author of NetLogo code:
     Northwestern University, Evanston, IL.
 """
 
-from mesa import Agent
+import mesa
+
 from charts.random_walk import RandomWalker
 
 
-class Bank(Agent):
+class Bank(mesa.Agent):
     def __init__(self, unique_id, model, reserve_percent=50):
         # initialize the parent class with required parameters
         super().__init__(unique_id, model)
diff --git a/examples/charts/charts/model.py b/examples/charts/charts/model.py
index 394e1820..0f26e9b5 100644
--- a/examples/charts/charts/model.py
+++ b/examples/charts/charts/model.py
@@ -9,13 +9,10 @@ Author of NetLogo code:
     Northwestern University, Evanston, IL.
 """
 
-from charts.agents import Bank, Person
-from mesa import Model
-from mesa.space import MultiGrid
-from mesa.datacollection import DataCollector
-from mesa.time import RandomActivation
+import mesa
 import numpy as np
 
+from charts.agents import Bank, Person
 
 """
 If you want to perform a parameter sweep, call batch_run.py instead of run.py.
@@ -82,7 +79,7 @@ def get_total_loans(model):
     return np.sum(agent_loans)
 
 
-class Charts(Model):
+class Charts(mesa.Model):
 
     # grid height
     grid_h = 20
@@ -103,13 +100,13 @@ class Charts(Model):
         self.height = height
         self.width = width
         self.init_people = init_people
-        self.schedule = RandomActivation(self)
-        self.grid = MultiGrid(self.width, self.height, torus=True)
+        self.schedule = mesa.time.RandomActivation(self)
+        self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True)
         # rich_threshold is the amount of savings a person needs to be considered "rich"
         self.rich_threshold = rich_threshold
         self.reserve_percent = reserve_percent
         # see datacollector functions above
-        self.datacollector = DataCollector(
+        self.datacollector = mesa.DataCollector(
             model_reporters={
                 "Rich": get_num_rich_agents,
                 "Poor": get_num_poor_agents,
diff --git a/examples/charts/charts/random_walk.py b/examples/charts/charts/random_walk.py
index 448b6a4f..7e067881 100644
--- a/examples/charts/charts/random_walk.py
+++ b/examples/charts/charts/random_walk.py
@@ -8,10 +8,10 @@ Original Author: Jackie Kazil
 Generalized behavior for random walking, one grid cell at a time.
 """
 
-from mesa import Agent
+import mesa
 
 
-class RandomWalker(Agent):
+class RandomWalker(mesa.Agent):
     """
     Class implementing random walker methods in a generalized manner.
     Not intended to be used on its own, but to inherit its methods to multiple
diff --git a/examples/charts/charts/server.py b/examples/charts/charts/server.py
index 7631dd6a..195a3b5a 100644
--- a/examples/charts/charts/server.py
+++ b/examples/charts/charts/server.py
@@ -1,11 +1,5 @@
-from mesa.visualization.ModularVisualization import ModularServer
-from mesa.visualization.modules import (
-    CanvasGrid,
-    ChartModule,
-    BarChartModule,
-    PieChartModule,
-)
-from mesa.visualization.UserParam import UserSettableParameter
+import mesa
+
 from charts.agents import Person
 from charts.model import Charts
 
@@ -56,10 +50,10 @@ def person_portrayal(agent):
 
 # dictionary of user settable parameters - these map to the model __init__ parameters
 model_params = {
-    "init_people": UserSettableParameter(
+    "init_people": mesa.visualization.UserSettableParameter(
         "slider", "People", 25, 1, 200, description="Initial Number of People"
     ),
-    "rich_threshold": UserSettableParameter(
+    "rich_threshold": mesa.visualization.UserSettableParameter(
         "slider",
         "Rich Threshold",
         10,
@@ -67,7 +61,7 @@ model_params = {
         20,
         description="Upper End of Random Initial Wallet Amount",
     ),
-    "reserve_percent": UserSettableParameter(
+    "reserve_percent": mesa.visualization.UserSettableParameter(
         "slider",
         "Reserves",
         50,
@@ -78,10 +72,10 @@ model_params = {
 }
 
 # set the portrayal function and size of the canvas for visualization
-canvas_element = CanvasGrid(person_portrayal, 20, 20, 500, 500)
+canvas_element = mesa.visualization.CanvasGrid(person_portrayal, 20, 20, 500, 500)
 
 # map data to chart in the ChartModule
-line_chart = ChartModule(
+line_chart = mesa.visualization.ChartModule(
     [
         {"Label": "Rich", "Color": RICH_COLOR},
         {"Label": "Poor", "Color": POOR_COLOR},
@@ -89,7 +83,7 @@ line_chart = ChartModule(
     ]
 )
 
-model_bar = BarChartModule(
+model_bar = mesa.visualization.BarChartModule(
     [
         {"Label": "Rich", "Color": RICH_COLOR},
         {"Label": "Poor", "Color": POOR_COLOR},
@@ -97,14 +91,14 @@ model_bar = BarChartModule(
     ]
 )
 
-agent_bar = BarChartModule(
+agent_bar = mesa.visualization.BarChartModule(
     [{"Label": "Wealth", "Color": MID_COLOR}],
     scope="agent",
     sorting="ascending",
     sort_by="Wealth",
 )
 
-pie_chart = PieChartModule(
+pie_chart = mesa.visualization.PieChartModule(
     [
         {"Label": "Rich", "Color": RICH_COLOR},
         {"Label": "Middle Class", "Color": MID_COLOR},
@@ -113,7 +107,7 @@ pie_chart = PieChartModule(
 )
 
 # create instance of Mesa ModularServer
-server = ModularServer(
+server = mesa.visualization.ModularServer(
     Charts,
     [canvas_element, line_chart, model_bar, agent_bar, pie_chart],
     "Mesa Charts",
diff --git a/examples/color_patches/color_patches/model.py b/examples/color_patches/color_patches/model.py
index 1e12d954..fb15a221 100644
--- a/examples/color_patches/color_patches/model.py
+++ b/examples/color_patches/color_patches/model.py
@@ -4,12 +4,10 @@ The model - a 2D lattice where agents live and have an opinion
 
 from collections import Counter
 
-from mesa import Model, Agent
-from mesa.time import SimultaneousActivation
-from mesa.space import Grid
+import mesa
 
 
-class ColorCell(Agent):
+class ColorCell(mesa.Agent):
     """
     Represents a cell's opinion (visualized by a color)
     """
@@ -63,7 +61,7 @@ class ColorCell(Agent):
         self._state = self._next_state
 
 
-class ColorPatches(Model):
+class ColorPatches(mesa.Model):
     """
     represents a 2D lattice where agents live
     """
@@ -74,8 +72,8 @@ class ColorPatches(Model):
         The agents next state is first determined before updating the grid
         """
 
-        self._grid = Grid(width, height, torus=False)
-        self._schedule = SimultaneousActivation(self)
+        self._grid = mesa.space.Grid(width, height, torus=False)
+        self._schedule = mesa.time.SimultaneousActivation(self)
 
         # self._grid.coord_iter()
         #  --> should really not return content + col + row
diff --git a/examples/color_patches/color_patches/server.py b/examples/color_patches/color_patches/server.py
index e0636c24..44c4624e 100644
--- a/examples/color_patches/color_patches/server.py
+++ b/examples/color_patches/color_patches/server.py
@@ -4,8 +4,7 @@ the drawing of the model representation on the canvas
 """
 # import webbrowser
 
-from mesa.visualization.modules import CanvasGrid
-from mesa.visualization.ModularVisualization import ModularServer
+import mesa
 
 from .model import ColorPatches
 
@@ -46,7 +45,8 @@ def color_patch_draw(cell):
     :return: the portrayal dictionary.
 
     """
-    assert cell is not None
+    if cell is None:
+        raise AssertionError
     portrayal = {"Shape": "rect", "w": 1, "h": 1, "Filled": "true", "Layer": 0}
     portrayal["x"] = cell.get_row()
     portrayal["y"] = cell.get_col()
@@ -54,11 +54,11 @@ def color_patch_draw(cell):
     return portrayal
 
 
-canvas_element = CanvasGrid(
+canvas_element = mesa.visualization.CanvasGrid(
     color_patch_draw, grid_rows, grid_cols, canvas_width, canvas_height
 )
 
-server = ModularServer(
+server = mesa.visualization.ModularServer(
     ColorPatches,
     [canvas_element],
     "Color Patches",
diff --git a/examples/conways_game_of_life/conways_game_of_life/cell.py b/examples/conways_game_of_life/conways_game_of_life/cell.py
index 4d4ef1e6..8639288d 100644
--- a/examples/conways_game_of_life/conways_game_of_life/cell.py
+++ b/examples/conways_game_of_life/conways_game_of_life/cell.py
@@ -1,7 +1,7 @@
-from mesa import Agent
+import mesa
 
 
-class Cell(Agent):
+class Cell(mesa.Agent):
     """Represents a single ALIVE or DEAD cell in the simulation."""
 
     DEAD = 0
diff --git a/examples/conways_game_of_life/conways_game_of_life/model.py b/examples/conways_game_of_life/conways_game_of_life/model.py
index 021fceff..635ccaa9 100644
--- a/examples/conways_game_of_life/conways_game_of_life/model.py
+++ b/examples/conways_game_of_life/conways_game_of_life/model.py
@@ -1,11 +1,9 @@
-from mesa import Model
-from mesa.time import SimultaneousActivation
-from mesa.space import Grid
+import mesa
 
 from .cell import Cell
 
 
-class ConwaysGameOfLife(Model):
+class ConwaysGameOfLife(mesa.Model):
     """
     Represents the 2-dimensional array of cells in Conway's
     Game of Life.
@@ -22,10 +20,10 @@ class ConwaysGameOfLife(Model):
         # computing their next state simultaneously.  This needs to
         # be done because each cell's next state depends on the current
         # state of all its neighbors -- before they've changed.
-        self.schedule = SimultaneousActivation(self)
+        self.schedule = mesa.time.SimultaneousActivation(self)
 
         # Use a simple grid, where edges wrap around.
-        self.grid = Grid(width, height, torus=True)
+        self.grid = mesa.space.Grid(width, height, torus=True)
 
         # Place a cell at each location, with some initialized to
         # ALIVE and some to DEAD.
diff --git a/examples/conways_game_of_life/conways_game_of_life/portrayal.py b/examples/conways_game_of_life/conways_game_of_life/portrayal.py
index a2e88760..4f68468d 100644
--- a/examples/conways_game_of_life/conways_game_of_life/portrayal.py
+++ b/examples/conways_game_of_life/conways_game_of_life/portrayal.py
@@ -5,7 +5,8 @@ def portrayCell(cell):
     :param cell:  the cell in the simulation
     :return: the portrayal dictionary.
     """
-    assert cell is not None
+    if cell is None:
+        raise AssertionError
     return {
         "Shape": "rect",
         "w": 1,
diff --git a/examples/conways_game_of_life/conways_game_of_life/server.py b/examples/conways_game_of_life/conways_game_of_life/server.py
index 1cf6924b..4167b3d0 100644
--- a/examples/conways_game_of_life/conways_game_of_life/server.py
+++ b/examples/conways_game_of_life/conways_game_of_life/server.py
@@ -1,13 +1,12 @@
-from mesa.visualization.modules import CanvasGrid
-from mesa.visualization.ModularVisualization import ModularServer
+import mesa
 
 from .portrayal import portrayCell
 from .model import ConwaysGameOfLife
 
 
 # Make a world that is 50x50, on a 250x250 display.
-canvas_element = CanvasGrid(portrayCell, 50, 50, 250, 250)
+canvas_element = mesa.visualization.CanvasGrid(portrayCell, 50, 50, 250, 250)
 
-server = ModularServer(
+server = mesa.visualization.ModularServer(
     ConwaysGameOfLife, [canvas_element], "Game of Life", {"height": 50, "width": 50}
 )
diff --git a/examples/epstein_civil_violence/epstein_civil_violence/agent.py b/examples/epstein_civil_violence/epstein_civil_violence/agent.py
index 6a9d3e7a..c270a970 100644
--- a/examples/epstein_civil_violence/epstein_civil_violence/agent.py
+++ b/examples/epstein_civil_violence/epstein_civil_violence/agent.py
@@ -1,9 +1,9 @@
 import math
 
-from mesa import Agent
+import mesa
 
 
-class Citizen(Agent):
+class Citizen(mesa.Agent):
     """
     A member of the general population, may or may not be in active rebellion.
     Summary of rule: If grievance - risk > threshold, rebel.
@@ -124,7 +124,7 @@ class Citizen(Agent):
         )
 
 
-class Cop(Agent):
+class Cop(mesa.Agent):
     """
     A cop for life.  No defection.
     Summary of rule: Inspect local vision and arrest a random active agent.
diff --git a/examples/epstein_civil_violence/epstein_civil_violence/model.py b/examples/epstein_civil_violence/epstein_civil_violence/model.py
index effc882a..33c80ea0 100644
--- a/examples/epstein_civil_violence/epstein_civil_violence/model.py
+++ b/examples/epstein_civil_violence/epstein_civil_violence/model.py
@@ -1,12 +1,9 @@
-from mesa import Model
-from mesa.time import RandomActivation
-from mesa.space import Grid
-from mesa.datacollection import DataCollector
+import mesa
 
 from .agent import Cop, Citizen
 
 
-class EpsteinCivilViolence(Model):
+class EpsteinCivilViolence(mesa.Model):
     """
     Model 1 from "Modeling civil violence: An agent-based computational
     approach," by Joshua Epstein.
@@ -62,12 +59,12 @@ class EpsteinCivilViolence(Model):
         self.movement = movement
         self.max_iters = max_iters
         self.iteration = 0
-        self.schedule = RandomActivation(self)
-        self.grid = Grid(width, height, torus=True)
+        self.schedule = mesa.time.RandomActivation(self)
+        self.grid = mesa.space.Grid(width, height, torus=True)
         model_reporters = {
             "Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"),
             "Active": lambda m: self.count_type_citizens(m, "Active"),
-            "Jailed": lambda m: self.count_jailed(m),
+            "Jailed": self.count_jailed,
         }
         agent_reporters = {
             "x": lambda a: a.pos[0],
@@ -77,7 +74,7 @@ class EpsteinCivilViolence(Model):
             "condition": lambda a: getattr(a, "condition", None),
             "arrest_probability": lambda a: getattr(a, "arrest_probability", None),
         }
-        self.datacollector = DataCollector(
+        self.datacollector = mesa.DataCollector(
             model_reporters=model_reporters, agent_reporters=agent_reporters
         )
         unique_id = 0
diff --git a/examples/epstein_civil_violence/epstein_civil_violence/server.py b/examples/epstein_civil_violence/epstein_civil_violence/server.py
index 3087127c..6b835bd2 100644
--- a/examples/epstein_civil_violence/epstein_civil_violence/server.py
+++ b/examples/epstein_civil_violence/epstein_civil_violence/server.py
@@ -1,5 +1,4 @@
-from mesa.visualization.ModularVisualization import ModularServer
-from mesa.visualization.modules import CanvasGrid
+import mesa
 
 from .model import EpsteinCivilViolence
 from .agent import Citizen, Cop
@@ -49,7 +48,7 @@ model_params = dict(
     max_jail_term=1000,
 )
 
-canvas_element = CanvasGrid(citizen_cop_portrayal, 40, 40, 480, 480)
-server = ModularServer(
+canvas_element = mesa.visualization.CanvasGrid(citizen_cop_portrayal, 40, 40, 480, 480)
+server = mesa.visualization.ModularServer(
     EpsteinCivilViolence, [canvas_element], "Epstein Civil Violence", model_params
 )
diff --git a/examples/forest_fire/forest_fire/agent.py b/examples/forest_fire/forest_fire/agent.py
index b120b3fa..34ff2aa2 100644
--- a/examples/forest_fire/forest_fire/agent.py
+++ b/examples/forest_fire/forest_fire/agent.py
@@ -1,7 +1,7 @@
-from mesa import Agent
+import mesa
 
 
-class TreeCell(Agent):
+class TreeCell(mesa.Agent):
     """
     A tree cell.
 
diff --git a/examples/forest_fire/forest_fire/model.py b/examples/forest_fire/forest_fire/model.py
index 5af9a377..de74118f 100644
--- a/examples/forest_fire/forest_fire/model.py
+++ b/examples/forest_fire/forest_fire/model.py
@@ -1,12 +1,9 @@
-from mesa import Model
-from mesa.datacollection import DataCollector
-from mesa.space import Grid
-from mesa.time import RandomActivation
+import mesa
 
 from .agent import TreeCell
 
 
-class ForestFire(Model):
+class ForestFire(mesa.Model):
     """
     Simple Forest Fire model.
     """
@@ -20,10 +17,10 @@ class ForestFire(Model):
             density: What fraction of grid cells have a tree in them.
         """
         # Set up model objects
-        self.schedule = RandomActivation(self)
-        self.grid = Grid(width, height, torus=False)
+        self.schedule = mesa.time.RandomActivation(self)
+        self.grid = mesa.space.Grid(width, height, torus=False)
 
-        self.datacollector = DataCollector(
+        self.datacollector = mesa.DataCollector(
             {
                 "Fine": lambda m: self.count_type(m, "Fine"),
                 "On Fire": lambda m: self.count_type(m, "On Fire"),
diff --git a/examples/forest_fire/forest_fire/server.py b/examples/forest_fire/forest_fire/server.py
index cc81e46e..253aea0a 100644
--- a/examples/forest_fire/forest_fire/server.py
+++ b/examples/forest_fire/forest_fire/server.py
@@ -1,6 +1,4 @@
-from mesa.visualization.modules import CanvasGrid, ChartModule, PieChartModule
-from mesa.visualization.ModularVisualization import ModularServer
-from mesa.visualization.UserParam import UserSettableParameter
+import mesa
 
 from .model import ForestFire
 
@@ -18,19 +16,23 @@ def forest_fire_portrayal(tree):
     return portrayal
 
 
-canvas_element = CanvasGrid(forest_fire_portrayal, 100, 100, 500, 500)
-tree_chart = ChartModule(
+canvas_element = mesa.visualization.CanvasGrid(
+    forest_fire_portrayal, 100, 100, 500, 500
+)
+tree_chart = mesa.visualization.ChartModule(
     [{"Label": label, "Color": color} for (label, color) in COLORS.items()]
 )
-pie_chart = PieChartModule(
+pie_chart = mesa.visualization.PieChartModule(
     [{"Label": label, "Color": color} for (label, color) in COLORS.items()]
 )
 
 model_params = {
     "height": 100,
     "width": 100,
-    "density": UserSettableParameter("slider", "Tree density", 0.65, 0.01, 1.0, 0.01),
+    "density": mesa.visualization.UserSettableParameter(
+        "slider", "Tree density", 0.65, 0.01, 1.0, 0.01
+    ),
 }
-server = ModularServer(
+server = mesa.visualization.ModularServer(
     ForestFire, [canvas_element, tree_chart, pie_chart], "Forest Fire", model_params
 )
diff --git a/examples/hex_snowflake/hex_snowflake/portrayal.py b/examples/hex_snowflake/hex_snowflake/portrayal.py
index 802de1cc..a0a4020e 100644
--- a/examples/hex_snowflake/hex_snowflake/portrayal.py
+++ b/examples/hex_snowflake/hex_snowflake/portrayal.py
@@ -5,7 +5,8 @@ def portrayCell(cell):
     :param cell:  the cell in the simulation
     :return: the portrayal dictionary.
     """
-    assert cell is not None
+    if cell is None:
+        raise AssertionError
     return {
         "Shape": "hex",
         "r": 1,
diff --git a/examples/pd_grid/pd_grid/portrayal.py b/examples/pd_grid/pd_grid/portrayal.py
index 490d9433..a7df44a4 100644
--- a/examples/pd_grid/pd_grid/portrayal.py
+++ b/examples/pd_grid/pd_grid/portrayal.py
@@ -5,7 +5,8 @@ def portrayPDAgent(agent):
     :param agent:  the agent in the simulation
     :return: the portrayal dictionary
     """
-    assert agent is not None
+    if agent is None:
+        raise AssertionError
     return {
         "Shape": "rect",
         "w": 1,
diff --git a/examples/virus_on_network/virus_on_network/model.py b/examples/virus_on_network/virus_on_network/model.py
index abeb8ca9..6f54f7e9 100644
--- a/examples/virus_on_network/virus_on_network/model.py
+++ b/examples/virus_on_network/virus_on_network/model.py
@@ -81,7 +81,7 @@ class VirusOnNetwork(Model):
             self.grid.place_agent(a, node)
 
         # Infect some nodes
-        infected_nodes = self.random.sample(self.G.nodes(), self.initial_outbreak_size)
+        infected_nodes = self.random.sample(list(self.G), self.initial_outbreak_size)
         for a in self.grid.get_cell_list_contents(infected_nodes):
             a.state = State.INFECTED
 
diff --git a/examples/wolf_sheep/wolf_sheep/server.py b/examples/wolf_sheep/wolf_sheep/server.py
index e532a3b8..fcdda90a 100644
--- a/examples/wolf_sheep/wolf_sheep/server.py
+++ b/examples/wolf_sheep/wolf_sheep/server.py
@@ -48,23 +48,16 @@ chart_element = mesa.visualization.ChartModule(
 )
 
 model_params = {
-    "grass": mesa.visualization.UserSettableParameter(
-        "checkbox", "Grass Enabled", True
+    "grass": mesa.visualization.Checkbox("Grass Enabled", True),
+    "grass_regrowth_time": mesa.visualization.Slider("Grass Regrowth Time", 20, 1, 50),
+    "initial_sheep": mesa.visualization.Slider(
+        "Initial Sheep Population", 100, 10, 300
     ),
-    "grass_regrowth_time": mesa.visualization.UserSettableParameter(
-        "slider", "Grass Regrowth Time", 20, 1, 50
+    "sheep_reproduce": mesa.visualization.Slider(
+        "Sheep Reproduction Rate", 0.04, 0.01, 1.0, 0.01
     ),
-    "initial_sheep": mesa.visualization.UserSettableParameter(
-        "slider", "Initial Sheep Population", 100, 10, 300
-    ),
-    "sheep_reproduce": mesa.visualization.UserSettableParameter(
-        "slider", "Sheep Reproduction Rate", 0.04, 0.01, 1.0, 0.01
-    ),
-    "initial_wolves": mesa.visualization.UserSettableParameter(
-        "slider", "Initial Wolf Population", 50, 10, 300
-    ),
-    "wolf_reproduce": mesa.visualization.UserSettableParameter(
-        "slider",
+    "initial_wolves": mesa.visualization.Slider("Initial Wolf Population", 50, 10, 300),
+    "wolf_reproduce": mesa.visualization.Slider(
         "Wolf Reproduction Rate",
         0.05,
         0.01,
@@ -72,12 +65,10 @@ model_params = {
         0.01,
         description="The rate at which wolf agents reproduce.",
     ),
-    "wolf_gain_from_food": mesa.visualization.UserSettableParameter(
-        "slider", "Wolf Gain From Food Rate", 20, 1, 50
-    ),
-    "sheep_gain_from_food": mesa.visualization.UserSettableParameter(
-        "slider", "Sheep Gain From Food", 4, 1, 10
+    "wolf_gain_from_food": mesa.visualization.Slider(
+        "Wolf Gain From Food Rate", 20, 1, 50
     ),
+    "sheep_gain_from_food": mesa.visualization.Slider("Sheep Gain From Food", 4, 1, 10),
 }
 
 server = mesa.visualization.ModularServer(
diff --git a/mesa/batchrunner.py b/mesa/batchrunner.py
index 0a59c982..486726c9 100644
--- a/mesa/batchrunner.py
+++ b/mesa/batchrunner.py
@@ -226,9 +226,6 @@ class VariableParameterError(ParameterError):
         "These parameters were given with non-sequence values: {}"
     )
 
-    def __init__(self, bad_names):
-        super().__init__(bad_names)
-
 
 class FixedBatchRunner:
     """This class is instantiated with a model class, and model parameters
diff --git a/mesa/datacollection.py b/mesa/datacollection.py
index 469c67f3..524158de 100644
--- a/mesa/datacollection.py
+++ b/mesa/datacollection.py
@@ -237,7 +237,7 @@ class DataCollector:
 
         """
         all_records = itertools.chain.from_iterable(self._agent_records.values())
-        rep_names = [rep_name for rep_name in self.agent_reporters]
+        rep_names = list(self.agent_reporters)
 
         df = pd.DataFrame.from_records(
             data=all_records,
diff --git a/mesa/space.py b/mesa/space.py
index 6e47b9b1..f9a72309 100644
--- a/mesa/space.py
+++ b/mesa/space.py
@@ -508,15 +508,6 @@ class SingleGrid(Grid):
 
     empties: Set[Coordinate] = set()
 
-    def __init__(self, width: int, height: int, torus: bool) -> None:
-        """Create a new single-item grid.
-
-        Args:
-            width, height: The width and width of the grid
-            torus: Boolean whether the grid wraps or not.
-        """
-        super().__init__(width, height, torus)
-
     def position_agent(
         self, agent: Agent, x: Union[int, str] = "random", y: Union[int, str] = "random"
     ) -> None:
diff --git a/mesa/visualization/ModularVisualization.py b/mesa/visualization/ModularVisualization.py
index 3c302eed..63dea4ea 100644
--- a/mesa/visualization/ModularVisualization.py
+++ b/mesa/visualization/ModularVisualization.py
@@ -106,7 +106,7 @@ import tornado.escape
 import tornado.gen
 import webbrowser
 
-from mesa.visualization.UserParam import UserSettableParameter
+from mesa.visualization.UserParam import UserSettableParameter, UserParam
 
 # Suppress several pylint warnings for this file.
 # Attributes being defined outside of init is a Tornado feature.
@@ -120,6 +120,12 @@ D3_JS_FILE = "external/d3-7.4.3.min.js"
 CHART_JS_FILE = "external/chart-3.6.1.min.js"
 
 
+def is_user_param(val):
+    return isinstance(val, UserSettableParameter) or issubclass(
+        val.__class__, UserParam
+    )
+
+
 class VisualizationElement:
     """
     Defines an element of the visualization.
@@ -222,9 +228,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
 
             # Is the param editable?
             if param in self.application.user_params:
-                if isinstance(
-                    self.application.model_kwargs[param], UserSettableParameter
-                ):
+                if is_user_param(self.application.model_kwargs[param]):
                     self.application.model_kwargs[param].value = value
                 else:
                     self.application.model_kwargs[param] = value
@@ -263,9 +267,11 @@ class ModularServer(tornado.web.Application):
     EXCLUDE_LIST = ("width", "height")
 
     def __init__(
-        self, model_cls, visualization_elements, name="Mesa Model", model_params={}
+        self, model_cls, visualization_elements, name="Mesa Model", model_params=None
     ):
         """Create a new visualization server with the given elements."""
+        if model_params is None:
+            model_params = {}
         # Prep visualization elements:
         self.visualization_elements = visualization_elements
         self.package_js_includes = set()
@@ -305,7 +311,7 @@ class ModularServer(tornado.web.Application):
     def user_params(self):
         result = {}
         for param, val in self.model_kwargs.items():
-            if isinstance(val, UserSettableParameter):
+            if is_user_param(val):
                 result[param] = val.json
 
         return result
@@ -315,10 +321,9 @@ class ModularServer(tornado.web.Application):
 
         model_params = {}
         for key, val in self.model_kwargs.items():
-            if isinstance(val, UserSettableParameter):
-                if (
-                    val.param_type == "static_text"
-                ):  # static_text is never used for setting params
+            if is_user_param(val):
+                if val.param_type == "static_text":
+                    # static_text is never used for setting params
                     continue
                 model_params[key] = val.value
             else:
diff --git a/mesa/visualization/UserParam.py b/mesa/visualization/UserParam.py
index 4c149496..a4eb7c6e 100644
--- a/mesa/visualization/UserParam.py
+++ b/mesa/visualization/UserParam.py
@@ -1,3 +1,10 @@
+NUMBER = "number"
+CHECKBOX = "checkbox"
+CHOICE = "choice"
+SLIDER = "slider"
+STATIC_TEXT = "static_text"
+
+
 class UserSettableParameter:
     """A class for providing options to a visualization for a given parameter.
 
@@ -35,11 +42,11 @@ class UserSettableParameter:
     static_text = UserSettableParameter('static_text', value="This is a descriptive textbox")
     """
 
-    NUMBER = "number"
-    CHECKBOX = "checkbox"
-    CHOICE = "choice"
-    SLIDER = "slider"
-    STATIC_TEXT = "static_text"
+    NUMBER = NUMBER
+    CHECKBOX = CHECKBOX
+    CHOICE = CHOICE
+    SLIDER = SLIDER
+    STATIC_TEXT = STATIC_TEXT
 
     TYPES = (NUMBER, CHECKBOX, CHOICE, SLIDER, STATIC_TEXT)
 
@@ -53,9 +60,11 @@ class UserSettableParameter:
         min_value=None,
         max_value=None,
         step=1,
-        choices=list(),
+        choices=None,
         description=None,
     ):
+        if choices is None:
+            choices = list()
         if param_type not in self.TYPES:
             raise ValueError(f"{param_type} is not a valid Option type")
         self.param_type = param_type
@@ -117,3 +126,93 @@ class UserSettableParameter:
             "_value"
         )  # Return _value as value, value is the same
         return result
+
+
+class UserParam:
+    _ERROR_MESSAGE = "Missing or malformed inputs for '{}' Option '{}'"
+
+    @property
+    def json(self):
+        result = self.__dict__.copy()
+        result["value"] = result.pop(
+            "_value"
+        )  # Return _value as value, value is the same
+        return result
+
+    def maybe_raise_error(self, valid):
+        if not valid:
+            msg = self._ERROR_MESSAGE.format(self.param_type, self.name)
+            raise ValueError(msg)
+
+    @property
+    def value(self):
+        return self._value
+
+    @value.setter
+    def value(self, value):
+        self._value = value
+
+
+class Slider(UserParam):
+    """
+    A number-based slider input with settable increment.
+
+    Example:
+
+    slider_option = Slider("My Slider", value=123, min_value=10, max_value=200, step=0.1)
+    """
+
+    def __init__(
+        self,
+        name="",
+        value=None,
+        min_value=None,
+        max_value=None,
+        step=1,
+        description=None,
+    ):
+        self.param_type = SLIDER
+        self.name = name
+        self._value = value
+        self.min_value = min_value
+        self.max_value = max_value
+        self.step = step
+        self.description = description
+
+        # Validate option types to make sure values are supplied properly
+        valid = not (
+            self.value is None or self.min_value is None or self.max_value is None
+        )
+        self.maybe_raise_error(valid)
+
+    @property
+    def value(self):
+        return self._value
+
+    @value.setter
+    def value(self, value):
+        self._value = value
+        if self._value < self.min_value:
+            self._value = self.min_value
+        elif self._value > self.max_value:
+            self._value = self.max_value
+
+
+class Checkbox(UserParam):
+    """
+    Boolean checkbox.
+
+    Example:
+
+    boolean_option = Checkbox('My Boolean', True)
+    """
+
+    def __init__(self, name="", value=None, description=None):
+        self.param_type = CHECKBOX
+        self.name = name
+        self._value = value
+        self.description = description
+
+        # Validate option types to make sure values are supplied properly
+        valid = isinstance(self.value, bool)
+        self.maybe_raise_error(valid)
diff --git a/mesa/visualization/templates/js/BarChartModule.js b/mesa/visualization/templates/js/BarChartModule.js
index a133675b..c5cb763c 100644
--- a/mesa/visualization/templates/js/BarChartModule.js
+++ b/mesa/visualization/templates/js/BarChartModule.js
@@ -9,34 +9,31 @@ const BarChartModule = function (
   sortingKey
 ) {
   // Create the overall chart div
-  const chart_div_tag =
-    "<div class='bar chart' width='" + canvas_width + "'></div>";
-  const chart_div = $(chart_div_tag)[0];
-  $("#elements").append(chart_div);
-
-  // Create the tag:
-  const svg_tag =
-    "<svg width='" +
-    canvas_width +
-    "' height='" +
-    canvas_height +
-    "' style='border:1px dotted'></svg>";
-  // Append it to #elements
-  const svg_element = $(svg_tag)[0];
-  chart_div.append(svg_element);
+  const chartDiv = document.createElement("div");
+  chartDiv.className = "bar chart";
+  chartDiv.setAttribute("width", canvas_width);
+  const elements = document.getElementById("elements");
+  elements.appendChild(chartDiv);
+
+  // Create the svg element:
+  const svg = d3.create("svg");
+  svg
+    .attr("width", canvas_width)
+    .attr("height", canvas_height)
+    .style("border", "1px dotted");
+  chartDiv.appendChild(svg.node());
 
   //create the legend
-  const legend_tag = "<div class='legend'></div>";
-  const legend_element = $(legend_tag)[0];
-  chart_div.append(legend_element);
-
-  const legend = d3
-    .select(legend_element)
+  const legend = d3.create("div");
+  legend
+    .attr("class", "legend")
     .attr(
       "style",
-      "display:block;width:" + canvas_width + "px;text-align:center"
+      `display:block;width:${canvas_width}px;text-align:center`
     );
 
+  chartDiv.appendChild(legend.node());
+
   legend
     .selectAll("span")
     .data(fields)
@@ -53,8 +50,7 @@ const BarChartModule = function (
     })
     .attr("style", "padding-left:10px;padding-right:10px;");
 
-  // setup the d3 svg selection
-  const svg = d3.select(svg_element);
+  // setup the d3 svg
   const margin = { top: 20, right: 20, bottom: 30, left: 40 };
   const width = +svg.attr("width") - margin.left - margin.right;
   const height = +svg.attr("height") - margin.top - margin.bottom;
diff --git a/mesa/visualization/templates/js/runcontrol.js b/mesa/visualization/templates/js/runcontrol.js
index bb308f74..5689b83c 100644
--- a/mesa/visualization/templates/js/runcontrol.js
+++ b/mesa/visualization/templates/js/runcontrol.js
@@ -188,25 +188,30 @@ const initGUI = function (model_params) {
 
   const addBooleanInput = function (param, obj) {
     const domID = param + "_id";
-    sidebar.append(
-      [
-        "<div>",
-        "<p><label for='" +
-          domID +
-          "' class='badge badge-primary'>" +
-          obj.name +
-          "</label></p>",
-        "<input class='model-parameter' id='" + domID + "' type='checkbox'/>",
-        "</div>",
-      ].join("")
-    );
-    $("#" + domID).bootstrapSwitch({
-      state: obj.value,
-      size: "small",
-      onSwitchChange: function (e, state) {
-        onSubmitCallback(param, state);
-      },
+    const _switch = document.createElement("div");
+    _switch.className = "form-check form-switch";
+
+    const label = `
+      <label class="form-check-label badge bg-primary" for="${domID}">
+        ${obj.name}
+      </label>
+    `;
+    _switch.innerHTML += label;
+
+    const input = document.createElement("input");
+    Object.assign(input, {
+      className: "form-check-input model-parameter",
+      type: "checkbox",
+      id: domID,
+      checked: obj.value,
     });
+    input.setAttribute("role", "switch");
+    input.addEventListener("change", (event) =>
+      onSubmitCallback(param, event.currentTarget.checked)
+    );
+    _switch.appendChild(input);
+
+    sidebar.append(_switch);
   };
 
   const addNumberInput = function (param, obj) {
@@ -216,7 +221,7 @@ const initGUI = function (model_params) {
         "<div>",
         "<p><label for='" +
           domID +
-          "' class='badge badge-primary'>" +
+          "' class='badge bg-primary'>" +
           obj.name +
           "</label></p>",
         "<input class='model-parameter' id='" + domID + "' type='number'/>",
@@ -236,16 +241,16 @@ const initGUI = function (model_params) {
     let tooltip = "";
     // Enable tooltip label
     if (obj.description !== null) {
-      tooltip = `title='${obj.description}'`
+      tooltip = `title='${obj.description}'`;
     }
 
     sidebar.append(
       [
         "<div>",
         "<p>",
-        `<a id='${tooltipID}' ${tooltip} data-toggle='tooltip' data-placement='top' class='badge badge-primary'>`,
+        `<span id='${tooltipID}' ${tooltip} data-bs-toggle='tooltip' data-bs-placement='top' class='badge bg-primary'>`,
         obj.name,
-        "</a>",
+        "</span>",
         "</p>",
         "<input id='" + domID + "' type='text' />",
         "</div>",
@@ -274,13 +279,13 @@ const initGUI = function (model_params) {
     const template = [
       "<p><label for='" +
         domID +
-        "' class='badge badge-primary'>" +
+        "' class='badge bg-primary'>" +
         obj.name +
         "</label></p>",
       "<div class='dropdown'>",
       "<button id='" +
         domID +
-        "' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'>" +
+        "' class='btn btn-default dropdown-toggle' type='button' data-bs-toggle='dropdown'>" +
         obj.value +
         " " +
         span,
@@ -292,7 +297,7 @@ const initGUI = function (model_params) {
       const choiceID = domID + "_choice_" + i;
       choiceIdentifiers.push(choiceID);
       template.push(
-        "<li role='presentation'><a class='pick-choice' id='" +
+        "<li role='presentation'><a class='pick-choice dropdown-item' id='" +
           choiceID +
           "' role='menuitem' tabindex='-1' href='#'>",
         obj.choices[i],
diff --git a/mesa/visualization/templates/modular_template.html b/mesa/visualization/templates/modular_template.html
index 5b75089e..c126958b 100644
--- a/mesa/visualization/templates/modular_template.html
+++ b/mesa/visualization/templates/modular_template.html
@@ -1,8 +1,7 @@
 <!DOCTYPE html>
 <head>
 	<title>{{ model_name }} (Mesa visualization)</title>
-    <link href="/static/external/bootstrap-4.6.1-dist/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
-    <link href="/static/external/bootstrap-switch-3.3.4/dist/css/bootstrap3/bootstrap-switch.min.css" type="text/css" rel="stylesheet" />
+    <link href="/static/external/bootstrap-5.1.3-dist/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
     <link href="/static/external/bootstrap-slider-11.0.2/dist/css/bootstrap-slider.min.css" type="text/css" rel="stylesheet" />
     <link href="/static/css/visualization.css" type="text/css" rel="stylesheet" />
 
@@ -21,10 +20,10 @@
 <body>
 
     <!-- Navbar -->
-    <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md mb-3">
+    <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-lg mb-3">
         <div class="container">
-            <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
-                <span class="sr-only">Toggle navigation</span>
+            <button type="button" class="navbar-toggler collapsed" data-bs-toggle="collapse" data-bs-target="#navbar" aria-expanded="false" aria-controls="navbar">
+                <span class="visually-hidden">Toggle navigation</span>
                 &#x2630;
             </button>
             <a class="navbar-brand" href="#">{{ model_name }}</a>
@@ -32,12 +31,12 @@
             <div id="navbar" class="navbar-collapse collapse">
                 <ul class="nav navbar-nav">
                     <li class="nav-item">
-                        <a href="#" data-toggle="modal" data-target="#about" data-title="About" data-content="#about-content" class="nav-link">
+                        <a href="#" data-bs-toggle="modal" data-bs-target="#about" data-bs-title="About" data-bs-content="#about-content" class="nav-link">
                             About
                         </a>
                     </li>
                 </ul>
-                <ul class="nav navbar-nav ml-auto">
+                <ul class="nav navbar-nav ms-auto">
                     <li id="play-pause" class="nav-item"><a href="#" class="nav-link">Start</a>
                     </li>
                     <li id="step" class="nav-item"><a href="#" class="nav-link">Step</a>
@@ -45,7 +44,7 @@
                     <li id="reset" class="nav-item"><a href="#" class="nav-link">Reset</a>
                     </li>
                 </ul>
-            </div><!--/.nav-collapse -->
+            </div>
         </div>
     </nav>
     <div class="container d-flex flex-row">
@@ -53,7 +52,7 @@
         <div class="col-xl-8 col-lg-8 col-md-8 col-9" id="elements">
             <div id="elements-topbar">
                 <div">
-                    <label class="badge badge-primary" for="fps" style="margin-right: 15px">Frames Per Second</label>
+                    <label class="badge bg-primary" for="fps" style="margin-right: 15px">Frames Per Second</label>
                     <input id="fps" data-slider-id="fps" type="text">
                 </div>
                 <p>Current Step: <span id="currentStep">0</span>
@@ -63,12 +62,12 @@
     </div>
 
     <!-- About modal -->
-    <div id="about" class="modal fade" tabindex="-1" role="dialog">
+    <div id="about" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
         <div class="modal-dialog modal-lg">
             <div class="modal-content">
                 <div class="modal-header">
                     <h4 class="modal-title">About {{ model_name }}</h4>
-                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&#xD7;</span>
+                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
                     </button>
                 </div>
                 <div class="modal-body">
@@ -82,8 +81,7 @@
 
     <!-- Bottom-load all JavaScript dependencies -->
     <script src="/static/js/external/jquery-2.2.4.min.js"></script>
-    <script src="/static/external/bootstrap-4.6.1-dist/js/bootstrap.bundle.min.js"></script>
-    <script src="/static/external/bootstrap-switch-3.3.4/dist/js/bootstrap-switch.min.js"></script>
+    <script src="/static/external/bootstrap-5.1.3-dist/js/bootstrap.bundle.min.js"></script>
     <script src="/static/external/bootstrap-slider-11.0.2/dist/bootstrap-slider.min.js"></script>
 
     <!-- Script includes go here -->
diff --git a/setup.py b/setup.py
index 044566e4..50f988e0 100644
--- a/setup.py
+++ b/setup.py
@@ -66,7 +66,7 @@ def ensure_JS_dep_single(url, out_name=None):
 # hardcoded included files and versions in: mesa/visualization/templates/modular_template.html
 
 # Ensure Bootstrap
-bootstrap_version = "4.6.1"
+bootstrap_version = "5.1.3"
 ensure_JS_dep(
     f"bootstrap-{bootstrap_version}-dist",
     f"https://github.com/twbs/bootstrap/releases/download/v{bootstrap_version}/bootstrap-{bootstrap_version}-dist.zip",
@@ -79,13 +79,6 @@ ensure_JS_dep(
     f"https://github.com/seiyria/bootstrap-slider/archive/refs/tags/v{bootstrap_slider_version}.zip",
 )
 
-# Ensure Bootstrap Switch
-bootstrap_switch_version = "3.3.4"
-ensure_JS_dep(
-    f"bootstrap-switch-{bootstrap_switch_version}",
-    f"https://github.com/Bttstrp/bootstrap-switch/archive/refs/tags/v{bootstrap_switch_version}.zip",
-)
-
 jquery_version = "2.2.4"
 ensure_JS_dep_single(
     f"https://code.jquery.com/jquery-{jquery_version}.min.js",


Test patch:
diff --git a/tests/test_examples.py b/tests/test_examples.py
index 7ca71b72..9baa31c8 100644
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -31,7 +31,7 @@ class TestExamples(unittest.TestCase):
             yield
         finally:
             os.chdir(old_cwd)
-            added = [m for m in sys.modules.keys() if m not in old_sys_modules]
+            added = [m for m in sys.modules if m not in old_sys_modules]
             for mod in added:
                 del sys.modules[mod]
             sys.modules.update(old_sys_modules)
diff --git a/tests/test_lifespan.py b/tests/test_lifespan.py
index 6fb9d832..280b8bee 100644
--- a/tests/test_lifespan.py
+++ b/tests/test_lifespan.py
@@ -86,7 +86,7 @@ class TestAgentLifespan(unittest.TestCase):
         assert self.df.steps.max() == 1
 
     def test_agent_lifetime(self):
-        lifetimes = self.df.groupby(["AgentID"]).agg({"Step": lambda x: len(x)})
+        lifetimes = self.df.groupby(["AgentID"]).agg({"Step": len})
         assert lifetimes.Step.max() == 2
 
 
diff --git a/tests/test_usersettableparam.py b/tests/test_usersettableparam.py
index 85d767cf..64c6de66 100644
--- a/tests/test_usersettableparam.py
+++ b/tests/test_usersettableparam.py
@@ -1,11 +1,12 @@
 from unittest import TestCase
-from mesa.visualization.UserParam import UserSettableParameter
+from mesa.visualization.UserParam import UserSettableParameter, Slider, Checkbox
 
 
 class TestOption(TestCase):
     def setUp(self):
         self.number_option = UserSettableParameter("number", value=123)
         self.checkbox_option = UserSettableParameter("checkbox", value=True)
+        self.checkbox_option_standalone = Checkbox(value=True)
         self.choice_option = UserSettableParameter(
             "choice",
             value="I am your default choice",
@@ -14,6 +15,7 @@ class TestOption(TestCase):
         self.slider_option = UserSettableParameter(
             "slider", value=123, min_value=100, max_value=200
         )
+        self.slider_option_standalone = Slider(value=123, min_value=100, max_value=200)
 
     def test_number(self):
         assert self.number_option.value == 123
@@ -21,9 +23,10 @@ class TestOption(TestCase):
         assert self.number_option.value == 321
 
     def test_checkbox(self):
-        assert self.checkbox_option.value
-        self.checkbox_option.value = False
-        assert not self.checkbox_option.value
+        for option in [self.checkbox_option, self.checkbox_option_standalone]:
+            assert option.value
+            option.value = False
+            assert not option.value
 
     def test_choice(self):
         assert self.choice_option.value == "I am your default choice"
@@ -33,10 +36,14 @@ class TestOption(TestCase):
         assert self.choice_option.value == "I am your default choice"
 
     def test_slider(self):
-        assert self.slider_option.value == 123
-        self.slider_option.value = 150
-        assert self.slider_option.value == 150
-        self.slider_option.value = 0
-        assert self.slider_option.value == 100
-        self.slider_option.value = 300
-        assert self.slider_option.value == 200
+        for option in [self.slider_option, self.slider_option_standalone]:
+            assert option.value == 123
+            option.value = 150
+            assert option.value == 150
+            option.value = 0
+            assert option.value == 100
+            option.value = 300
+            assert option.value == 200
+            assert option.json["value"] == 200
+        with self.assertRaises(ValueError):
+            Slider()