From 4cc0c827faa3e5bc75058fbe34ed14eb48f21ae6 Mon Sep 17 00:00:00 2001 From: rht Date: Wed, 3 Jul 2024 07:10:25 -0400 Subject: [PATCH] Remove visualization_old (mesa-viz-tornado) (#2133) Removing the old, legacy visualisation. It will still be available in 2.3.x. Checkout the Visualization Tutorial (https://mesa.readthedocs.io/en/latest/tutorials/visualization_tutorial.html) to migrate to the new visualisation. --- docs/index.md | 1 - docs/mesa.md | 6 - docs/mesa.visualization_old.md | 46 -- docs/mesa.visualization_old.modules.md | 76 --- docs/modular-visualization.md | 249 --------- docs/overview.md | 30 -- docs/tutorials/adv_tutorial_legacy.ipynb | 510 ------------------ docs/tutorials/intro_tutorial.ipynb | 4 +- mesa/__init__.py | 2 - .../{{cookiecutter.snake}}/app.pytemplate | 27 + .../{{cookiecutter.snake}}/run.pytemplate | 3 - .../{{cookiecutter.snake}}/model.pytemplate | 2 +- .../{{cookiecutter.snake}}/server.pytemplate | 36 -- pyproject.toml | 1 - tests/test_import_namespace.py | 5 - tests/test_tornado.py | 41 -- tests/test_usersettableparam.py | 57 -- tests/test_visualization.py | 105 ---- 18 files changed, 29 insertions(+), 1172 deletions(-) delete mode 100644 docs/mesa.visualization_old.md delete mode 100644 docs/mesa.visualization_old.modules.md delete mode 100644 docs/modular-visualization.md delete mode 100644 docs/tutorials/adv_tutorial_legacy.ipynb create mode 100644 mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate delete mode 100644 mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate delete mode 100644 mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate delete mode 100644 tests/test_tornado.py delete mode 100644 tests/test_usersettableparam.py delete mode 100644 tests/test_visualization.py diff --git a/docs/index.md b/docs/index.md index 117dd906729..14bc0ba026b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -83,7 +83,6 @@ Best Practices How-to Guide API Documentation Mesa Packages -tutorials/adv_tutorial_legacy.ipynb ``` # Indices and tables diff --git a/docs/mesa.md b/docs/mesa.md index 6c3e8cc8696..512bb23f208 100644 --- a/docs/mesa.md +++ b/docs/mesa.md @@ -1,11 +1,5 @@ # mesa package -## Subpackages - -```{toctree} -mesa.visualization_old -``` - ## Submodules ## mesa.agent module diff --git a/docs/mesa.visualization_old.md b/docs/mesa.visualization_old.md deleted file mode 100644 index 700eabf12c9..00000000000 --- a/docs/mesa.visualization_old.md +++ /dev/null @@ -1,46 +0,0 @@ -# mesa.visualization_old package - -## Subpackages - -```{toctree} -mesa.visualization_old.modules -``` - -## Submodules - -## mesa.visualization_old.ModularVisualization module - -```{eval-rst} -.. automodule:: mesa.visualization_old.ModularVisualization - :members: - :undoc-members: - :show-inheritance: -``` - -## mesa.visualization_old.TextVisualization module - -```{eval-rst} -.. automodule:: mesa.visualization_old.TextVisualization - :members: - :undoc-members: - :show-inheritance: -``` - -## mesa.visualization_old.UserParam module - -```{eval-rst} -.. automodule:: mesa.visualization_old.UserParam - :members: - :undoc-members: - :show-inheritance: - -``` - -## Module contents - -```{eval-rst} -.. automodule:: mesa.visualization_old - :members: - :undoc-members: - :show-inheritance: -``` diff --git a/docs/mesa.visualization_old.modules.md b/docs/mesa.visualization_old.modules.md deleted file mode 100644 index 55d94b64e28..00000000000 --- a/docs/mesa.visualization_old.modules.md +++ /dev/null @@ -1,76 +0,0 @@ -# mesa.visualization_old.modules package - -## Submodules - -## mesa.visualization_old.modules.BarChartVisualization module - -```{eval-rst} -.. automodule:: mesa.visualization_old.modules.BarChartVisualization - :members: - :undoc-members: - :show-inheritance: -``` - -## mesa.visualization_old.modules.CanvasGridVisualization module - -```{eval-rst} -.. automodule:: mesa.visualization_old.modules.CanvasGridVisualization - :members: - :undoc-members: - :show-inheritance: -``` - -## mesa.visualization_old.modules.ChartVisualization module - -```{eval-rst} -.. automodule:: mesa.visualization_old.modules.ChartVisualization - :members: - :undoc-members: - :show-inheritance: -``` - -## mesa.visualization_old.modules.HexGridVisualization module - -```{eval-rst} -.. automodule:: mesa.visualization_old.modules.HexGridVisualization - :members: - :undoc-members: - :show-inheritance: -``` - -## mesa.visualization_old.modules.NetworkVisualization module - -```{eval-rst} -.. automodule:: mesa.visualization_old.modules.NetworkVisualization - :members: - :undoc-members: - :show-inheritance: -``` - -## mesa.visualization_old.modules.PieChartVisualization module - -```{eval-rst} -.. automodule:: mesa.visualization_old.modules.PieChartVisualization - :members: - :undoc-members: - :show-inheritance: -``` - -## mesa.visualization_old.modules.TextVisualization module - -```{eval-rst} -.. automodule:: mesa.visualization_old.modules.TextVisualization - :members: - :undoc-members: - :show-inheritance: - -``` - -## Module contents - -```{eval-rst} -.. automodule:: mesa.visualization_old.modules - :members: - :undoc-members: - :show-inheritance: -``` diff --git a/docs/modular-visualization.md b/docs/modular-visualization.md deleted file mode 100644 index 716c2f1b6e3..00000000000 --- a/docs/modular-visualization.md +++ /dev/null @@ -1,249 +0,0 @@ -# Modular Visualization - An In-Depth Look (Deprecated) - -Modular visualization is one of Mesa's core features. Mesa is designed -to provide predefined visualization modules, which can be easily -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 - -An interactive modular visualization is a set of **Elements**, each of -which is an instance of a visualization **Module**. To visualize a -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 -them via: - -```python -server = ModularServer(MyModel, [canvas_vis, graph_vis]) -server.launch() -``` - -Then you will be able to view the elements in your browser at -. If you prefer a different port, for example -8887, you can pass it to the server as an argument. - -```python -server.launch(8887) -``` - -Under the hood, each visualization module consists of two parts: - -1. **Data rending** - Python code which can take a model object and - renders it into some data describing the visualization. -2. **Browser rendering** - JavaScript object which in turn receives data - from the Python render (via the ModularServer) and actually draws it - in the browser. - -## 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 -javascript. Consult the documentation for a variety of modules. - -One built-in module is **CanvasGrid**, which you can use to visualize -objects located on grid cells. The CanvasGrid will cover a majority of -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 -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). -``` - -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: - -```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 -draw in the browser. - -To continue our Schelling example: suppose we have a Schelling model -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: - -```python -canvas_element = CanvasGrid(schelling_draw, 10, 10, 500, 500) -``` - -Then, to launch a server with this grid as the only visualization -element: - -```python -server = ModularServer(SchellingModel, [canvas_element], "Schelling") -server.launch() -``` - -## 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 -it. - -For example, the TextElement module provides an HTML template to render -raw text, but nothing else. To use it, we need to create our own -subclass, which implements a **render** method to get -visualization-ready data (in this case, just a text string) out of a -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: - -```python -from mesa.visualization_old.ModularTextVisualization import TextElement - -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. - - Example return: "happy: 10" - ''' - self.attr_name = attr_name - - 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: - -```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 - -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 -a completely new module? To do this, we need to open up the JavaScript -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: - -```javascript -const TextModule = function () { - const text = document.createElement("p"); - text.className = "lead"; - - // Append text tag to #elements: - document.getElementById("elements").appendChild(text); - - this.render = function (data) { - text.innerHTML = data; - }; - - 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 -append it to the parent HTML page's *body*. The object will have two -methods attached: - -1. *render(data)* -- replaces the inner HTML contents of the - paragraph with the text it gets as an input. This function will be - called at each step of the model, to draw the data associated with - the model coming over the websocket. -2. *reset* -- replaces the contents of the div with a blank. This - function will be called when the user presses the Reset button. - -Now let's take a look at the TextModule's Python counterpart, -**TextElement** (which resides in **TextVisualization.py**). Again, -here's the whole thing: - -```python -from mesa.visualization_old.ModularVisualization import VisualizationElement - -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 -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 -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 -array *elements*, that stores all the visualization elements currently -on the page. - -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: - -```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* -function of the corresponding element, in the elements array. - -Currently, module JavaScript files live in the -*mesa/visualization_old/templates* directory, and the Python files live in -*mesa/visualization_old/modules*. - -When creating a new module, the Python and JavaScript code need to be -written in synch: the module Python-side **render** method needs to -output data in the exact same format that the JavaScript **render** -function receives as an input. diff --git a/docs/overview.md b/docs/overview.md index 923d64d1031..de0bad3b441 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -114,33 +114,3 @@ As with the data collector, once the runs are all over, you can extract the data ```python batch_df = batch_run.get_model_vars_dataframe() ``` - -### 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_old.ModularVisualization -- mesa.visualization_old.modules - -To quickly spin up a model visualization, you might do something like: - -```python -import mesa - -def agent_portrayal(agent): - portrayal = {"Shape": "circle", - "Filled": "true", - "Layer": 0, - "Color": "red", - "r": 0.5} - return portrayal - -grid = mesa.visualization_old.CanvasGrid(agent_portrayal, 10, 10, 500, 500) -server = mesa.visualization_old.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/tutorials/adv_tutorial_legacy.ipynb b/docs/tutorials/adv_tutorial_legacy.ipynb deleted file mode 100644 index e29d2732413..00000000000 --- a/docs/tutorials/adv_tutorial_legacy.ipynb +++ /dev/null @@ -1,510 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Advanced Tutorial\n", - "This is the legacy version of the advanced tutorial. We recommend you to read the newer (current) version because it is easier." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding visualization\n", - "\n", - "So far, we've built a model, run it, and analyzed some output afterwards. However, one of the advantages of agent-based models is that we can often watch them run step by step, potentially spotting unexpected patterns, behaviors or bugs, or developing new intuitions, hypotheses, or insights. Other times, watching a model run can explain it to an unfamiliar audience better than static explanations. Like many ABM frameworks, Mesa allows you to create an interactive visualization of the model. In this section we'll walk through creating a visualization using built-in components, and (for advanced users) how to create a new visualization element.\n", - "\n", - "**Note for Jupyter users: Due to conflicts with the tornado server Mesa uses and Jupyter, the interactive browser of your model will load but likely not work. This will require you to use run the code from .py files. The Mesa development team is working to develop a** [Jupyter compatible interface](https://github.com/projectmesa/mesa/issues/1363).\n", - "\n", - "First, a quick explanation of how Mesa's interactive visualization works. Visualization is done in a browser window, using JavaScript to draw the different things being visualized at each step of the model. To do this, Mesa launches a small web server, which runs the model, turns each step into a JSON object (essentially, structured plain text) and sends those steps to the browser.\n", - "\n", - "A visualization is built up of a few different modules: for example, a module for drawing agents on a grid, and another one for drawing a chart of some variable. Each module has a Python part, which runs on the server and turns a model state into JSON data; and a JavaScript side, which takes that JSON data and draws it in the browser window. Mesa comes with a few modules built in, and let you add your own as well." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Grid Visualization\n", - "\n", - "To start with, let's have a visualization where we can watch the agents moving around the grid. For this, you will need to put your model code in a separate Python source file. For now, let us use the `MoneyModel` created in the [Introductory Tutorial](https://mesa.readthedocs.io/en/stable/tutorials/intro_tutorial.html) saved to `MoneyModel.py` file provided.\n", - "Next, in a new source file (e.g. `MoneyModel_Viz.py`) include the code shown in the following cells to run and avoid Jupyter compatibility issue." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# If MoneyModel.py is where your code is:\n", - "from MoneyModel import mesa, MoneyModel" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Mesa's `CanvasGrid` visualization class works by looping over every cell in a grid, and generating a portrayal for every agent it finds. A portrayal is a dictionary (which can easily be turned into a JSON object) which tells the JavaScript side how to draw it. The only thing we need to provide is a function which takes an agent, and returns a portrayal object. Here's the simplest one: it'll draw each agent as a red, filled circle which fills half of each cell." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "def agent_portrayal(agent):\n", - " portrayal = {\n", - " \"Shape\": \"circle\",\n", - " \"Color\": \"red\",\n", - " \"Filled\": \"true\",\n", - " \"Layer\": 0,\n", - " \"r\": 0.5,\n", - " }\n", - " return portrayal" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In addition to the portrayal method, we instantiate a canvas grid with its width and height in cells, and in pixels. In this case, let's create a 10x10 grid, drawn in 500 x 500 pixels." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "grid = mesa.visualization_old.CanvasGrid(agent_portrayal, 10, 10, 500, 500)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we create and launch the actual server. We do this with the following arguments:\n", - "\n", - "* The model class we're running and visualizing; in this case, `MoneyModel`.\n", - "* A list of module objects to include in the visualization; here, just `[grid]`\n", - "* The title of the model: \"Money Model\"\n", - "* Any inputs or arguments for the model itself. In this case, 100 agents, and height and width of 10.\n", - "\n", - "Once we create the server, we set the port (use default 8521 here) for it to listen on (you can treat this as just a piece of the URL you’ll open in the browser). " - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "server = mesa.visualization_old.ModularServer(\n", - " MoneyModel, [grid], \"Money Model\", {\"N\": 100, \"width\": 10, \"height\": 10}\n", - ")\n", - "server.port = 8521 # the default" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, when you’re ready to run the visualization, use the server’s launch() method." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The full code for source file `MoneyModel_Viz.py` should now look like:\n", - "\n", - "```python\n", - "from MoneyModel import mesa, MoneyModel\n", - "\n", - "\n", - "def agent_portrayal(agent):\n", - " portrayal = {\"Shape\": \"circle\",\n", - " \"Filled\": \"true\",\n", - " \"Layer\": 0,\n", - " \"Color\": \"red\",\n", - " \"r\": 0.5}\n", - " return portrayal\n", - "\n", - "grid = mesa.visualization_old.CanvasGrid(agent_portrayal, 10, 10, 500, 500)\n", - "server = mesa.visualization_old.ModularServer(MoneyModel,\n", - " [grid],\n", - " \"Money Model\",\n", - " {\"N\":100, \"width\":10, \"height\":10})\n", - "server.port = 8521 # The default\n", - "server.launch()\n", - "```\n", - "Now run this file; this should launch the interactive visualization server and open your web browser automatically. (If the browser doesn't open automatically, try pointing it at [http://127.0.0.1:8521](http://127.0.0.1:8521) manually. If this doesn't show you the visualization, something may have gone wrong with the server launch.)\n", - "\n", - "You should see something like the figure below: the model title, an empty space where the grid will be, and a control panel off to the right.\n", - "\n", - "![Empty Visualization](files/viz_empty.png)\n", - "\n", - "Click the `Reset` button on the control panel, and you should see the grid fill up with red circles, representing agents.\n", - "\n", - "![Redcircles Visualization](files/viz_redcircles.png)\n", - "\n", - "Click `Step` to advance the model by one step, and the agents will move around. Click `Start` and the agents will keep moving around, at the rate set by the 'fps' (frames per second) slider at the top. Try moving it around and see how the speed of the model changes. Pressing `Stop` will pause the model; pressing `Start` again will restart it. Finally, `Reset` will start a new instantiation of the model.\n", - "\n", - "To stop the visualization server, go back to the terminal where you launched it, and press Control+c." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Changing the agents\n", - "\n", - "In the visualization above, all we could see is the agents moving around -- but not how much money they had, or anything else of interest. Let's change it so that agents who are broke (wealth 0) are drawn in grey, smaller, and above agents who still have money.\n", - "\n", - "To do this, we go back to our `agent_portrayal` code and add some code to change the portrayal based on the agent properties and launch the server again." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "def agent_portrayal(agent):\n", - " portrayal = {\"Shape\": \"circle\", \"Filled\": \"true\", \"r\": 0.5}\n", - "\n", - " if agent.wealth > 0:\n", - " portrayal[\"Color\"] = \"red\"\n", - " portrayal[\"Layer\"] = 0\n", - " else:\n", - " portrayal[\"Color\"] = \"grey\"\n", - " portrayal[\"Layer\"] = 1\n", - " portrayal[\"r\"] = 0.2\n", - " return portrayal" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This will open a new browser window pointed at the updated visualization. Initially it looks the same, but advance the model and smaller grey circles start to appear. Note that since the zero-wealth agents have a higher layer number, they are drawn on top of the red agents.\n", - "\n", - "![Greycircles Visualization](files/viz_greycircles.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Adding a chart\n", - "\n", - "Next, let's add another element to the visualization: a chart, tracking the model's Gini Coefficient. This is another built-in element that Mesa provides.\n", - "\n", - "The basic chart pulls data from the model's DataCollector, and draws it as a line graph using the [Charts.js](http://www.chartjs.org/) JavaScript libraries. We instantiate a chart element with a list of series for the chart to track. Each series is defined in a dictionary, and has a `Label` (which must match the name of a model-level variable collected by the DataCollector) and a `Color` name. We can also give the chart the name of the DataCollector object in the model.\n", - "\n", - "Finally, we add the chart to the list of elements in the server. The elements are added to the visualization in the order they appear, so the chart will appear underneath the grid." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "chart = mesa.visualization_old.ChartModule(\n", - " [{\"Label\": \"Gini\", \"Color\": \"Black\"}], data_collector_name=\"datacollector\"\n", - ")\n", - "\n", - "server = mesa.visualization_old.ModularServer(\n", - " MoneyModel, [grid, chart], \"Money Model\", {\"N\": 100, \"width\": 10, \"height\": 10}\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Launch the visualization and start a model run, either by launching the server here or through the full code for source file `MoneyModel_Viz.py`.\n", - "\n", - "```python\n", - "from MoneyModel import mesa, MoneyModel\n", - "\n", - "\n", - "def agent_portrayal(agent):\n", - " portrayal = {\"Shape\": \"circle\", \"Filled\": \"true\", \"r\": 0.5}\n", - "\n", - " if agent.wealth > 0:\n", - " portrayal[\"Color\"] = \"red\"\n", - " portrayal[\"Layer\"] = 0\n", - " else:\n", - " portrayal[\"Color\"] = \"grey\"\n", - " portrayal[\"Layer\"] = 1\n", - " portrayal[\"r\"] = 0.2\n", - " return portrayal\n", - "\n", - "\n", - "grid = mesa.visualization_old.CanvasGrid(agent_portrayal, 10, 10, 500, 500)\n", - "chart = mesa.visualization_old.ChartModule(\n", - " [{\"Label\": \"Gini\", \"Color\": \"Black\"}], data_collector_name=\"datacollector\"\n", - ")\n", - "\n", - "server = mesa.visualization_old.ModularServer(\n", - " MoneyModel, [grid, chart], \"Money Model\", {\"N\": 100, \"width\": 10, \"height\": 10}\n", - ")\n", - "server.port = 8521 # The default\n", - "server.launch()\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You'll see a line chart underneath the grid. Every step of the model, the line chart updates along with the grid. Reset the model, and the chart resets too.\n", - "\n", - "![Chart Visualization](files/viz_chart.png)\n", - "\n", - "**Note:** You might notice that the chart line only starts after a couple of steps; this is due to a bug in Charts.js which will hopefully be fixed soon." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Building your own visualization component\n", - "\n", - "**Note:** This section is for users who have a basic familiarity with JavaScript. If that's not you, don't worry! (If you're an advanced JavaScript coder and find things that we've done wrong or inefficiently, please [let us know](https://github.com/projectmesa/mesa/issues)!)\n", - "\n", - "If the visualization elements provided by Mesa aren't enough for you, you can build your own and plug them into the model server.\n", - "\n", - "First, you need to understand how the visualization works under the hood. Remember that each visualization module has two sides: a Python object that runs on the server and generates JSON data from the model state (the server side), and a JavaScript object that runs in the browser and turns the JSON into something it renders on the screen (the client side).\n", - "\n", - "Obviously, the two sides of each visualization must be designed in tandem. They result in one Python class, and one JavaScript `.js` file. The path to the JavaScript file is a property of the Python class.\n", - "\n", - "For this example, let's build a simple histogram visualization, which can count the number of agents with each value of wealth. We'll use the [Charts.js](http://www.chartjs.org/) JavaScript library, which is already included with Mesa. If you go and look at its documentation, you'll see that it had no histogram functionality, which means we have to build our own out of a bar chart. We'll keep the histogram as simple as possible, giving it a fixed number of integer bins. If you were designing a more general histogram to add to the Mesa repository for everyone to use across different models, obviously you'd want something more general." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Client-Side Code\n", - "\n", - "In general, the server- and client-side are written in tandem. However, if you're like me and more comfortable with Python than JavaScript, it makes sense to figure out how to get the JavaScript working first, and then write the Python to be compatible with that.\n", - "\n", - "In the same directory as your model, create a new file called `HistogramModule.js`. This will store the JavaScript code for the client side of the new module.\n", - "\n", - "JavaScript classes can look alien to people coming from other languages -- specifically, they can look like functions. (The Mozilla [Introduction to Object-Oriented JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript) is a good starting point). In `HistogramModule.js`, start by creating the class itself:\n", - "\n", - "```javascript\n", - "const HistogramModule = function(bins, canvas_width, canvas_height) {\n", - " // The actual code will go here.\n", - "};\n", - "```\n", - "\n", - "Note that our object is instantiated with three arguments: the number of integer bins, and the width and height (in pixels) the chart will take up in the visualization window.\n", - "\n", - "When the visualization object is instantiated, the first thing it needs to do is prepare to draw on the current page. To do so, it adds a [canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) tag to the page. It also gets the canvas' context, which is required for doing anything with it.\n", - "\n", - "```javascript\n", - "const HistogramModule = function(bins, canvas_width, canvas_height) {\n", - " // Create the canvas object:\n", - " const canvas = document.createElement(\"canvas\");\n", - " Object.assign(canvas, {\n", - " width: canvas_width,\n", - " height: canvas_height,\n", - " style: \"border:1px dotted\",\n", - " });\n", - " // Append it to #elements:\n", - " const elements = document.getElementById(\"elements\");\n", - " elements.appendChild(canvas);\n", - "\n", - " // Create the context and the drawing controller:\n", - " const context = canvas.getContext(\"2d\");\n", - "};\n", - "```\n", - "\n", - "Look at the Charts.js [bar chart documentation](http://www.chartjs.org/docs/#bar-chart-introduction). You'll see some of the boilerplate needed to get a chart set up. Especially important is the `data` object, which includes the datasets, labels, and color options. In this case, we want just one dataset (we'll keep things simple and name it \"Data\"); it has `bins` for categories, and the value of each category starts out at zero. Finally, using these boilerplate objects and the canvas context we created, we can create the chart object.\n", - "\n", - "```javascript\n", - "const HistogramModule = function(bins, canvas_width, canvas_height) {\n", - " // Create the canvas object:\n", - " const canvas = document.createElement(\"canvas\");\n", - " Object.assign(canvas, {\n", - " width: canvas_width,\n", - " height: canvas_height,\n", - " style: \"border:1px dotted\",\n", - " });\n", - " // Append it to #elements:\n", - " const elements = document.getElementById(\"elements\");\n", - " elements.appendChild(canvas);\n", - "\n", - " // Create the context and the drawing controller:\n", - " const context = canvas.getContext(\"2d\");\n", - "\n", - " // Prep the chart properties and series:\n", - " const datasets = [{\n", - " label: \"Data\",\n", - " fillColor: \"rgba(151,187,205,0.5)\",\n", - " strokeColor: \"rgba(151,187,205,0.8)\",\n", - " highlightFill: \"rgba(151,187,205,0.75)\",\n", - " highlightStroke: \"rgba(151,187,205,1)\",\n", - " data: []\n", - " }];\n", - "\n", - " // Add a zero value for each bin\n", - " for (var i in bins)\n", - " datasets[0].data.push(0);\n", - "\n", - " const data = {\n", - " labels: bins,\n", - " datasets: datasets\n", - " };\n", - "\n", - " const options = {\n", - " scaleBeginsAtZero: true\n", - " };\n", - "\n", - " // Create the chart object\n", - " let chart = new Chart(context, {type: 'bar', data: data, options: options});\n", - "\n", - " // Now what?\n", - "};\n", - "```\n", - "\n", - "There are two methods every client-side visualization class must implement to be able to work: `render(data)` to render the incoming data, and `reset()` which is called to clear the visualization when the user hits the reset button and starts a new model run.\n", - "\n", - "In this case, the easiest way to pass data to the histogram is as an array, one value for each bin. We can then just loop over the array and update the values in the chart's dataset.\n", - "\n", - "There are a few ways to reset the chart, but the easiest is probably to destroy it and create a new chart object in its place.\n", - "\n", - "With that in mind, we can add these two methods to the class:\n", - "\n", - "```javascript\n", - "const HistogramModule = function(bins, canvas_width, canvas_height) {\n", - " // ...Everything from above...\n", - " this.render = function(data) {\n", - " datasets[0].data = data;\n", - " chart.update();\n", - " };\n", - "\n", - " this.reset = function() {\n", - " chart.destroy();\n", - " chart = new Chart(context, {type: 'bar', data: data, options: options});\n", - " };\n", - "};\n", - "```\n", - "\n", - "Note the `this`. before the method names. This makes them public and ensures that they are accessible outside of the object itself. All the other variables inside the class are only accessible inside the object itself, but not outside of it." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Server-Side Code\n", - "\n", - "Can we get back to Python code? Please?\n", - "\n", - "Every JavaScript visualization element has an equal and opposite server-side Python element. The Python class needs to also have a `render` method, to get data out of the model object and into a JSON-ready format. It also needs to point towards the code where the relevant JavaScript lives, and add the JavaScript object to the model page.\n", - "\n", - "In a Python file (either its own, or in the same file as your visualization code), import the `VisualizationElement` class we'll inherit from, and create the new visualization class.\n", - "\n", - "```python\n", - " from mesa.visualization_old.ModularVisualization import VisualizationElement, CHART_JS_FILE\n", - "\n", - " class HistogramModule(VisualizationElement):\n", - " package_includes = [CHART_JS_FILE]\n", - " local_includes = [\"HistogramModule.js\"]\n", - "\n", - " def __init__(self, bins, canvas_height, canvas_width):\n", - " self.canvas_height = canvas_height\n", - " self.canvas_width = canvas_width\n", - " self.bins = bins\n", - " new_element = \"new HistogramModule({}, {}, {})\"\n", - " new_element = new_element.format(bins, \n", - " canvas_width, \n", - " canvas_height)\n", - " self.js_code = \"elements.push(\" + new_element + \");\"\n", - "```\n", - "\n", - "There are a few things going on here. `package_includes` is a list of JavaScript files that are part of Mesa itself that the visualization element relies on. You can see the included files in [mesa/visualization_old/templates/](https://github.com/projectmesa/mesa/tree/main/mesa/visualization_old/templates). Similarly, `local_includes` is a list of JavaScript files in the same directory as the class code itself. Note that both of these are class variables, not object variables -- they hold for all particular objects.\n", - "\n", - "Next, look at the `__init__` method. It takes three arguments: the number of bins, and the width and height for the histogram. It then uses these values to populate the `js_code` property; this is code that the server will insert into the visualization page, which will run when the page loads. In this case, it creates a new HistogramModule (the class we created in JavaScript in the step above) with the desired bins, width and height; it then appends (`push`es) this object to `elements`, the list of visualization elements that the visualization page itself maintains.\n", - "\n", - "Now, the last thing we need is the `render` method. If we were making a general-purpose visualization module we'd want this to be more general, but in this case we can hard-code it to our model.\n", - "\n", - "```python\n", - "import numpy as np\n", - "\n", - "class HistogramModule(VisualizationElement):\n", - " # ... Everything from above...\n", - "\n", - " def render(self, model):\n", - " wealth_vals = [agent.wealth for agent in model.schedule.agents]\n", - " hist = np.histogram(wealth_vals, bins=self.bins)[0]\n", - " return [int(x) for x in hist]\n", - "```\n", - "\n", - "Every time the render method is called (with a model object as the argument) it uses numpy to generate counts of agents with each wealth value in the bins, and then returns a list of these values. Note that the `render` method doesn't return a JSON string -- just an object that can be turned into JSON, in this case a Python list (with Python integers as the values; the `json` library doesn't like dealing with numpy's integer type).\n", - "\n", - "Now, you can create your new HistogramModule and add it to the server:\n", - "\n", - "```python\n", - " histogram = mesa.visualization_old.HistogramModule(list(range(10)), 200, 500)\n", - " server = mesa.visualization_old.ModularServer(MoneyModel, \n", - " [grid, histogram, chart], \n", - " \"Money Model\", \n", - " {\"N\":100, \"width\":10, \"height\":10})\n", - " server.launch()\n", - "```\n", - "\n", - "Run this code, and you should see your brand-new histogram added to the visualization and updating along with the model!\n", - "\n", - "![Histogram Visualization](files/viz_histogram.png)\n", - "\n", - "If you've felt comfortable with this section, it might be instructive to read the code for the [ModularServer](https://github.com/projectmesa/mesa/blob/main/mesa/visualization_old/ModularVisualization.py#L259) and the [modular_template](https://github.com/projectmesa/mesa/blob/main/mesa/visualization_old/templates/modular_template.html) to get a better idea of how all the pieces fit together." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Happy Modeling!\n", - "\n", - "This document is a work in progress. If you see any errors, exclusions or have any problems please contact [us](https://github.com/projectmesa/mesa/issues)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.6" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/tutorials/intro_tutorial.ipynb b/docs/tutorials/intro_tutorial.ipynb index a9fe56875e1..014bd2b53e8 100644 --- a/docs/tutorials/intro_tutorial.ipynb +++ b/docs/tutorials/intro_tutorial.ipynb @@ -29,9 +29,7 @@ "\n", "### More Tutorials: \n", "\n", - "Visualization: There is a separate [visualization tutorial](https://mesa.readthedocs.io/en/stable/tutorials/visualization_tutorial.html) that will take users through building a visualization for this model (aka Boltzmann Wealth Model).\n", - "\n", - "Advanced Visualization (legacy): There is also an [advanced visualization tutorial](https://mesa.readthedocs.io/en/stable/tutorials/adv_tutorial_legacy.html) that will show users how to use the JavaScript based visualization option, which also uses this model as its base. " + "Visualization: There is a separate [visualization tutorial](https://mesa.readthedocs.io/en/stable/tutorials/visualization_tutorial.html) that will take users through building a visualization for this model (aka Boltzmann Wealth Model)." ] }, { diff --git a/mesa/__init__.py b/mesa/__init__.py index 529507e1f8b..09f93313175 100644 --- a/mesa/__init__.py +++ b/mesa/__init__.py @@ -8,7 +8,6 @@ import mesa.space as space import mesa.time as time -import mesa.visualization_old as visualization_old from mesa.agent import Agent from mesa.batchrunner import batch_run from mesa.datacollection import DataCollector @@ -19,7 +18,6 @@ "Agent", "time", "space", - "visualization_old", "DataCollector", "batch_run", "experimental", diff --git a/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate b/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate new file mode 100644 index 00000000000..eac8824569c --- /dev/null +++ b/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate @@ -0,0 +1,27 @@ +""" +Configure visualization elements and instantiate a server +""" +from mesa.visualization import JupyterViz + +from {{ cookiecutter.snake }}.model import {{ cookiecutter.model }}, {{ cookiecutter.agent }} # noqa + +import mesa + + +def circle_portrayal_example(agent): + return { + "size": 40, + # This is Matplotlib's color + "color": "tab:pink", + } + + +model_params = {"num_agents": 10, "width": 10, "height": 10} + +page = JupyterViz( + {{cookiecutter.model}}, + model_params, + measures=["num_agents"], + agent_portrayal=circle_portrayal_example +) +page # noqa diff --git a/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate b/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate deleted file mode 100644 index 66f5cea5f6a..00000000000 --- a/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate +++ /dev/null @@ -1,3 +0,0 @@ -from {{cookiecutter.snake}}.server import server # noqa - -server.launch() diff --git a/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate b/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate index eedca040807..b33789a720a 100644 --- a/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +++ b/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate @@ -47,7 +47,7 @@ class {{cookiecutter.model}}(mesa.Model): self.grid.place_agent(agent, (x, y)) # example data collector - self.datacollector = mesa.datacollection.DataCollector() + self.datacollector = mesa.datacollection.DataCollector({"num_agents": "num_agents"}) self.running = True self.datacollector.collect(self) diff --git a/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate b/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate deleted file mode 100644 index 7b168b6d63b..00000000000 --- a/mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate +++ /dev/null @@ -1,36 +0,0 @@ -""" -Configure visualization elements and instantiate a server -""" - -from .model import {{ cookiecutter.model }}, {{ cookiecutter.agent }} # noqa - -import mesa - - -def circle_portrayal_example(agent): - if agent is None: - return - - portrayal = { - "Shape": "circle", - "Filled": "true", - "Layer": 0, - "r": 0.5, - "Color": "Pink", - } - return portrayal - - -canvas_element = mesa.visualization_old.CanvasGrid( - circle_portrayal_example, 20, 20, 500, 500 -) -chart_element = mesa.visualization_old.ChartModule([{"Label": "{{ cookiecutter.camel }}", "Color": "Pink"}]) - -model_kwargs = {"num_agents": 10, "width": 10, "height": 10} - -server = mesa.visualization_old.ModularServer( - {{cookiecutter.model}}, - [canvas_element, chart_element], - "{{ cookiecutter.camel }}", - model_kwargs, -) diff --git a/pyproject.toml b/pyproject.toml index 0a94f49104e..1f8933c56d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ dependencies = [ "click", "cookiecutter", "matplotlib", - "mesa_viz_tornado~=0.1.0,>=0.1.3", "networkx", "numpy", "pandas", diff --git a/tests/test_import_namespace.py b/tests/test_import_namespace.py index 6a001319f24..5e9679e6abb 100644 --- a/tests/test_import_namespace.py +++ b/tests/test_import_namespace.py @@ -12,11 +12,6 @@ def test_import(): _ = mesa.space.MultiGrid _ = MultiGrid - from mesa.visualization_old.ModularVisualization import ModularServer - - _ = mesa.visualization_old.ModularServer - _ = ModularServer - from mesa.datacollection import DataCollector _ = DataCollector diff --git a/tests/test_tornado.py b/tests/test_tornado.py deleted file mode 100644 index 52f40417455..00000000000 --- a/tests/test_tornado.py +++ /dev/null @@ -1,41 +0,0 @@ -import json - -import tornado -from tornado.testing import AsyncHTTPTestCase - -from mesa import Model -from mesa.visualization_old.ModularVisualization import ModularServer - - -class TestServer(AsyncHTTPTestCase): - def get_app(self): - app = ModularServer(Model, []) - return app - - def test_homepage(self): - response = self.fetch("/") - assert response.error is None - - @tornado.testing.gen_test - def test_websocket(self): - ws_url = "ws://localhost:" + str(self.get_http_port()) + "/ws" - ws_client = yield tornado.websocket.websocket_connect(ws_url) - - # Now we can run a test on the WebSocket. - response = yield ws_client.read_message() - msg = json.loads(response) - assert msg["type"] == "model_params" - - ws_client.write_message('{"type": "get_step"}') - response = yield ws_client.read_message() - msg = json.loads(response) - assert msg["type"] == "viz_state" - - ws_client.write_message('{"type": "reset"}') - response = yield ws_client.read_message() - msg = json.loads(response) - assert msg["type"] == "viz_state" - - ws_client.write_message("Unknown message!") - response = yield ws_client.read_message() - assert response is None diff --git a/tests/test_usersettableparam.py b/tests/test_usersettableparam.py deleted file mode 100644 index 52ba971ef2a..00000000000 --- a/tests/test_usersettableparam.py +++ /dev/null @@ -1,57 +0,0 @@ -from unittest import TestCase - -from mesa.visualization_old.UserParam import ( - Checkbox, - Choice, - NumberInput, - Slider, - StaticText, -) - - -class TestOption(TestCase): - def setUp(self): - self.number_option = NumberInput("number", value=123) - self.checkbox_option = Checkbox(value=True) - self.choice_option = Choice( - value="I am your default choice", - choices=["I am your default choice", "I am your other choice"], - ) - self.slider_option = Slider(value=123, min_value=100, max_value=200) - self.static_text_option = StaticText("Hurr, Durr Im'a Sheep") - - def test_number(self): - option = self.number_option - assert option.value == 123 - option.value = 321 - assert option.value == 321 - - def test_checkbox(self): - option = self.checkbox_option - assert option.value - option.value = False - assert not option.value - - def test_choice(self): - option = self.choice_option - assert option.value == "I am your default choice" - option.value = "I am your other choice" - assert option.value == "I am your other choice" - option.value = "I am not an available choice" - assert option.value == "I am your default choice" - - def test_slider(self): - option = self.slider_option - 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() - - def test_static_text(self): - assert self.static_text_option.value == "Hurr, Durr Im'a Sheep" diff --git a/tests/test_visualization.py b/tests/test_visualization.py deleted file mode 100644 index b04a76243f2..00000000000 --- a/tests/test_visualization.py +++ /dev/null @@ -1,105 +0,0 @@ -from collections import defaultdict -from unittest import TestCase - -import mesa -from mesa.model import Model -from mesa.space import MultiGrid -from mesa.time import SimultaneousActivation -from mesa.visualization_old.ModularVisualization import ModularServer -from mesa.visualization_old.modules import CanvasGrid, TextElement -from mesa.visualization_old.UserParam import ( - NumberInput, - Slider, -) - - -class MockAgent(mesa.Agent): - """ - Minimalistic agent implementation for testing purposes - """ - - def __init__(self, unique_id, model, val): - super().__init__(unique_id, model) - self.unique_id = unique_id - self.val = val - self.local = 0 - - def step(self): - self.val += 1 - self.local += 0.25 - - -class MockModel(Model): - """Test model for testing""" - - def __init__(self, width, height, key1=103, key2=104): - super().__init__() - self.width = width - self.height = height - self.key1 = (key1,) - self.key2 = key2 - self.schedule = SimultaneousActivation(self) - self.grid = MultiGrid(width, height, torus=True) - - for _c, pos in self.grid.coord_iter(): - x, y = pos - a = MockAgent(x + y * 100, self, x * y * 3) - self.grid.place_agent(a, (x, y)) - self.schedule.add(a) - - def step(self): - self.schedule.step() - - -class TestModularServer(TestCase): - """Test server for testing""" - - def portrayal(self, cell): - return { - "Shape": "rect", - "w": 1, - "h": 1, - "Filled": "true", - "Layer": 0, - "x": 0, - "y": 0, - "Color": "black", - } - - def setUp(self): - self.user_params = { - "width": 1, - "height": 1, - "key1": NumberInput("Test Parameter", 101), - "key2": Slider("Test Parameter", 200, 0, 300, 10), - } - - self.viz_elements = [ - CanvasGrid(self.portrayal, 10, 10, 20, 20), - TextElement(), - # ChartModule([{"Label": "Wolves", "Color": "#AA0000"}, # Todo - test chart module - # {"Label": "Sheep", "Color": "#666666"}]) - ] - - self.server = ModularServer( - MockModel, self.viz_elements, "Test Model", model_params=self.user_params - ) - - def test_canvas_render_model_state(self): - test_portrayal = self.portrayal(None) - test_grid_state = defaultdict(list) - test_grid_state[test_portrayal["Layer"]].append(test_portrayal) - - state = self.server.render_model() - assert state[0] == test_grid_state - - def test_text_render_model_state(self): - state = self.server.render_model() - assert state[1] == "VisualizationElement goes here." - - def test_user_params(self): - print(self.server.user_params) - assert self.server.user_params == { - "key1": NumberInput("Test Parameter", 101).json, - "key2": Slider("Test Parameter", 200, 0, 300, 10).json, - }