Repository: projectmesa/mesa

Issue:
Install JS dependencies using Fanstatic
We currently vendor the minified JS dependencies as files. I have looked around Stackoverflow, and a more maintainable approach is to use Fanstatic, which wraps the JS resources as if they were a Python library. See http://www.fanstatic.org/en/latest/libraries.html. E.g. to include D3.js, simply add js.d3 in the requirements in setup.py. This has the following benefits:
- creates a clear separation of which JS files are Mesa-specific, and which JS files are dependencies
- reduces the size of the repo. Imagine D3.js being updated 10 times -- we'd have 10 copies of minified D3.js in the repo, making it unnecessarily bulky


Discussion:
Additional benefit: we no longer need to create a THIRDPARTY file that attributes all the third party dependencies (including their license) that are vendored in Mesa, since those files will be downloaded by the users from PyPI separately.
It would be a great approach!

It looks like Fanstatic packages are not maintained however. The [latest](https://pypi.org/project/js.bootstrap/) Bootstrap version on there is 3.3.4 from April 2015 and the [latest](https://pypi.org/project/js.d3/) D3 version is 3.5.5 from July 2015.
Fanstatic itself seems to be still actively maintained: https://github.com/fanstatic/fanstatic.
I wonder if there is a modern vendor for those JS packages. Perhaps unofficial ones.
https://stackoverflow.com/questions/49953785/which-options-do-exist-for-defining-a-python-package-with-node-js-dependencies has an extensive answer from the author of calmjs.

However, I'm tempted to conclude that the easiest method is to just download those static js/css files on the fly during `pip install`.
I ended up doing #1220, with follow up #1236.
How about use `npm` to manage 3rd party javascript dependencies, which can be defined in a `packages.json` file? These libraries will be installed into the `node_modules` directory.

Or use [webpack](https://webpack.js.org) to bundle all dependencies from `node_modules` to our `visualization/templates/external/` directory.

In this way we can avoid writing code to download and unzip javascript libraries in `setup.py`.
The problem with `npm`/Webpack is that it creates an extra hassle to install Mesa. `npm` may be an apt-get away on Debian, but is not the case with macOS/Windows.
If we go the `npm` route, then we can require `npm` install for installing Mesa from Git, but we bundle the JS files in the releases. This is an extra hurdle for contributors, however.
It's probably a matter of trade-off between dependency management tools vs. manual maintenance in `setup.py` for each JS package. So far there aren't too many external dependencies, so I guess it's still manageable.
The maintenance effort in setup.py is constant, because the same code is use for all the dependencies. See https://github.com/rht/mesa/blob/31d768b3b2120296ef4962b1efaf40fa521d4524/setup.py#L37-L50.

Resolving patch:
diff --git a/mesa/visualization/ModularVisualization.py b/mesa/visualization/ModularVisualization.py
--- a/mesa/visualization/ModularVisualization.py
+++ b/mesa/visualization/ModularVisualization.py
@@ -117,6 +117,7 @@
     asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
 
 D3_JS_FILE = "external/d3-4.13.0.min.js"
+CHART_JS_FILE = "external/chart-3.6.1.min.js"
 
 
 class VisualizationElement:
diff --git a/mesa/visualization/modules/ChartVisualization.py b/mesa/visualization/modules/ChartVisualization.py
--- a/mesa/visualization/modules/ChartVisualization.py
+++ b/mesa/visualization/modules/ChartVisualization.py
@@ -6,7 +6,7 @@
 
 """
 import json
-from mesa.visualization.ModularVisualization import VisualizationElement
+from mesa.visualization.ModularVisualization import VisualizationElement, CHART_JS_FILE
 
 
 class ChartModule(VisualizationElement):
@@ -42,7 +42,7 @@ class ChartModule(VisualizationElement):
 
     """
 
-    package_includes = ["Chart.min.js", "ChartModule.js"]
+    package_includes = [CHART_JS_FILE, "ChartModule.js"]
 
     def __init__(
         self,
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -79,6 +79,13 @@ def ensure_JS_dep_single(url, out_name=None):
     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",
@@ -90,6 +97,13 @@ def ensure_JS_dep_single(url, out_name=None):
     f"https://cdnjs.cloudflare.com/ajax/libs/d3/{d3_version}/d3.min.js",
     out_name=f"d3-{d3_version}.min.js",
 )
+# Important: Make sure to update CHART_JS_FILE in
+# mesa/visualization/ModularVisualization.py.
+chartjs_version = "3.6.1"
+ensure_JS_dep_single(
+    f"https://cdn.jsdelivr.net/npm/chart.js@{chartjs_version}/dist/chart.min.js",
+    out_name=f"chart-{chartjs_version}.min.js",
+)
 
 
 setup(


Test patch: