Custom Visualizations

Add custom visualizations to runs

A tutorial to add interactive charts and maps to your run output using the Nextmv Python SDK.

โŒ›๏ธ Approximate time to complete: 15 min.

In this tutorial you will learn how to add custom visualizations to your Nextmv run output, so you can view interactive charts and maps directly in the Console. Complete this tutorial if you:

  • Have a working Nextmv Python application.
  • Want to attach Plotly charts to run output.
  • Are fluent using Python ๐Ÿ.

At a high level, this tutorial will go through the following steps:

  1. Understand assets and visuals.
  2. Add a Plotly bar chart to a knapsack app.
  3. Run locally and inspect the output.
  4. View the visualizations in Console.

Let's dive right in ๐Ÿคฟ.

1. Prerequisites

You need a working Nextmv Python application. If you don't have one, clone the python-highs-knapsack community app:

nextmv community clone -a python-highs-knapsack
cd python-highs-knapsack
Copy

Install the required packages:

pip install nextmv plotly
Copy

2. Understand assets and visuals

Before writing code, let's review the key concepts:

  • nextmv.Asset โ€” a piece of data attached to run output. An asset has a name, content, and an optional visual definition.
  • nextmv.Visual โ€” defines how an asset renders in the Console. It includes a schema and a label for the tab.
  • nextmv.VisualSchema โ€” determines the rendering engine. Three schemas are available: PLOTLY, GEOJSON, and CHARTJS.

When you include assets in your nextmv.Output, they appear as custom tabs in the Console run details view. Each tab is labeled with the label you provide in the Visual.

The visual_type defaults to "custom-tab", which is the only supported type currently. The content_type defaults to "json", also the only supported type.

3. Add a Plotly bar chart

Starting from the knapsack app, update main.py to add a bar chart that shows the selected items by value and weight. Here is the full updated main.py:

import json
import time
from importlib.metadata import version

import highspy
import nextmv
import plotly.graph_objects as go


def main() -> None:
    """Entry point for the program."""

    options = nextmv.Options(
        nextmv.Option("input", str, "", "Path to input file. Default is stdin.", False),
        nextmv.Option(
            "output", str, "", "Path to output file. Default is stdout.", False
        ),
        nextmv.Option("duration", int, 30, "Max runtime duration (in seconds).", False),
    )

    input = nextmv.load(options=options, path=options.input)

    nextmv.log("Solving knapsack problem:")
    nextmv.log(f"  - items: {len(input.data.get('items', []))}")
    nextmv.log(f"  - capacity: {input.data.get('weight_capacity', 0)}")

    model = DecisionModel()
    output = model.solve(input)

    nextmv.write(output, path=options.output)


class DecisionModel(nextmv.Model):
    def solve(self, input: nextmv.Input) -> nextmv.Output:
        """Solves the given problem and returns the solution."""

        start_time = time.time()

        # Creates the solver.
        solver = highspy.Highs()
        solver.silent()  # Solver output ignores stdout redirect, silence it.
        solver.setOptionValue("time_limit", input.options.duration)

        # Initializes the linear sums.
        weights = 0.0
        values = 0.0

        # Creates the decision variables and adds them to the linear sums.
        items = []
        for item in input.data["items"]:
            item_variable = solver.addVariable(0.0, 1.0, item["value"])
            items.append({"item": item, "variable": item_variable})
            weights += item_variable * item["weight"]
            values += item_variable * item["value"]

        # This constraint ensures the weight capacity of the knapsack will not be
        # exceeded.
        solver.addConstr(weights <= input.data["weight_capacity"])

        # Sets the objective function: maximize the value of the chosen items.
        status = solver.maximize(values)

        # Determines which items were chosen.
        chosen_items = [
            item["item"] for item in items if solver.val(item["variable"]) > 0.9
        ]

        input.options.version = version("highspy")

        statistics = nextmv.Statistics(
            run=nextmv.RunStatistics(duration=time.time() - start_time),
            result=nextmv.ResultStatistics(
                value=sum(item["value"] for item in chosen_items),
                custom={
                    "status": str(status),
                    "variables": solver.numVariables,
                    "constraints": solver.numConstrs,
                },
            ),
        )

        # After solving, create visualization.
        fig = go.Figure()
        fig.add_trace(
            go.Bar(
                x=[item["id"] for item in chosen_items],
                y=[item["value"] for item in chosen_items],
                name="Value",
            )
        )
        fig.add_trace(
            go.Bar(
                x=[item["id"] for item in chosen_items],
                y=[item["weight"] for item in chosen_items],
                name="Weight",
            )
        )
        fig.update_layout(
            title="Selected Items: Value vs Weight",
            barmode="group",
        )

        asset = nextmv.Asset(
            name="item-chart",
            content=[json.loads(fig.to_json())],
            visual=nextmv.Visual(
                visual_schema=nextmv.VisualSchema.PLOTLY,
                label="Item Analysis",
            ),
        )

        return nextmv.Output(
            options=input.options,
            solution={"items": chosen_items},
            statistics=statistics,
            assets=[asset],
        )


if __name__ == "__main__":
    main()
Copy

The content field for Plotly assets is a list of figure JSON objects. You can include multiple figures in a single asset, and they will all render in the same tab.

4. Run locally and inspect

Run the application locally to verify the asset is included in the output:

nextmv local run create -i input.json
Copy

Open the .nextmv folder and select the runID (e.g. local-6b1sdi64). Expand the visuals folder and inspect the generated html plot. Expand the outputs/assets folder and look for assets.json. It contains the Plotly figure JSON alongside the visual metadata:

{
  "solution": {...},
  "statistics": {...},
  "assets": [
    {
      "name": "item-chart",
      "content_type": "json",
      "content": [
        {
          "data": [...],
          "layout": {
            "title": {"text": "Selected Items: Value vs Weight"},
            "barmode": "group"
          }
        }
      ],
      "visual": {
        "schema": "plotly",
        "label": "Item Analysis",
        "type": "custom-tab"
      }
    }
  ]
}
Copy

5. View in Console

Push your application to Nextmv Cloud and create a run to see the visualizations in the Console:

nextmv app push -a your-app-id
nextmv app run -a your-app-id -i input.json
Copy

Navigate to the run details in the Console. You will see the custom tabs alongside the default tabs:

Plotly charts are fully interactive. You can hover over bars to see values, zoom in, pan, and use the Plotly toolbar.

๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰ Congratulations, you have finished this tutorial!

You now know how to attach custom visualizations to your Nextmv run output.

Page last updated

Go to on-page nav menu