{ "cells": [ { "cell_type": "markdown", "id": "matplotlib-intro-header", "metadata": {}, "source": [ "# Tutorial 6: Matplotlib API for 2D Visualization\n", "\n", "While scivianna's primary visualization is built on **Panel** and **Bokeh** for interactive web-based displays, the library also provides a **Matplotlib-based API** for generating static publication-quality plots, integrating with traditional Python scientific workflows, or embedding visualizations in notebooks without a Bokeh server.\n", "\n", "## When to Use the Matplotlib API\n", "\n", "| Use Case | Recommended Approach |\n", "|----------|---------------------|\n", "| Interactive web dashboard | Panel + Bokeh (Panel2D) |\n", "| Static figure for publication | Matplotlib API |\n", "| Batch processing / automated reports | Matplotlib API |\n", "| Jupyter notebook without Bokeh server | Matplotlib API |\n", "| Custom multi-panel layouts | Matplotlib API + matplotlib gridspec |" ] }, { "cell_type": "markdown", "id": "matplotlib-setup", "metadata": {}, "source": [ "## Setup\n", "\n", "First, import the necessary modules:" ] }, { "cell_type": "code", "execution_count": null, "id": "matplotlib-imports", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "from pathlib import Path\n", "\n", "import scivianna.input_file\n", "from scivianna.interface.med_interface import MEDInterface\n", "from scivianna.slave import ComputeSlave\n", "from scivianna.plotter_2d.api import plot_frame, plot_frame_in_axes" ] }, { "cell_type": "markdown", "id": "matplotlib-load-data", "metadata": {}, "source": [ "## Loading Data\n", "\n", "The Matplotlib API works with a **ComputeSlave** (or a **Geometry2D** interface). The ComputeSlave wraps an interface and handles data communication. Let's load a MED file:" ] }, { "cell_type": "code", "execution_count": null, "id": "matplotlib-create-slave", "metadata": {}, "outputs": [], "source": [ "# Create a ComputeSlave with the MED interface\n", "slave = ComputeSlave(MEDInterface)\n", "\n", "# Load the geometry file (using the bundled test file)\n", "from scivianna.constants import GEOMETRY, MESH\n", "input_file_path = Path(scivianna.input_file.__file__).parent / 'power.med'\n", "\n", "if input_file_path.exists():\n", " slave.read_file(input_file_path, GEOMETRY)\n", " print(f\"Loaded: {input_file_path}\")\n", "else:\n", " print(f\"File not found: {input_file_path}\")\n", " print(\"Please provide a valid .med file path in the next cell.\")" ] }, { "cell_type": "markdown", "id": "matplotlib-basic-plot", "metadata": {}, "source": [ "## Basic Plotting\n", "\n", "The simplest way to create a plot is using `plot_frame()`. It creates a new figure and axes, then plots the geometry:" ] }, { "cell_type": "code", "execution_count": null, "id": "matplotlib-simple-plot", "metadata": {}, "outputs": [], "source": [ "# Create a basic plot\n", "# coloring_label specifies which field to use for coloring the polygons\n", "print(\"Available fields :\", slave.get_labels())\n", "fig, ax = plot_frame(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER', # Field name to color by\n", " color_map='BuRd', # Colormap for numeric fields\n", " edge_width=0.5 # Line width for polygon edges\n", ")\n", "\n", "ax.set_title('2D Geometry - Material Distribution')\n", "fig.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "matplotlib-axes-plot", "metadata": {}, "source": [ "## Plotting on Existing Axes\n", "\n", "For more control, use `plot_frame_in_axes()` to plot onto an existing matplotlib axis. This is useful for custom layouts:" ] }, { "cell_type": "code", "execution_count": null, "id": "matplotlib-multiple-axes", "metadata": {}, "outputs": [], "source": [ "# Create a figure with multiple subplots\n", "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n", "\n", "# Plot on the left axis - using INTEGRATED_POWER field\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER',\n", " axes=axes[0],\n", " edge_width=0.5\n", ")\n", "axes[0].set_title('Power distribution')\n", "\n", "# Plot on the right axis - using a different field (if available)\n", "# Try with MESH label for geometry-only view\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label=MESH,\n", " axes=axes[1],\n", " edge_width=0.5\n", ")\n", "axes[1].set_title('Geometry Zones')\n", "\n", "fig.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "matplotlib-coloring", "metadata": {}, "source": [ "## Coloring Options\n", "\n", "### Numeric Fields with Colormaps\n", "\n", "For numeric fields, you can specify a colormap and value range:" ] }, { "cell_type": "code", "execution_count": null, "id": "matplotlib-colormap", "metadata": {}, "outputs": [], "source": [ "# Numeric field with colormap\n", "fig, ax = plt.subplots(figsize=(8, 6))\n", "\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER', # Example numeric field name\n", " axes=ax,\n", " color_map='viridis', # Any matplotlib colormap\n", " display_colorbar=True, # Show colorbar for numeric fields\n", " edge_width=0.3\n", ")\n", "\n", "ax.set_title('Numeric Field - Power (example)')\n", "fig.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "matplotlib-projection", "metadata": {}, "source": [ "## Projection Axes (u, v, w)\n", "\n", "3D geometries can be projected onto a 2D plane defined by director vectors **u** and **v**, at a specific position **w** along the `u × v` axis:" ] }, { "cell_type": "code", "execution_count": null, "id": "matplotlib-projection-params", "metadata": {}, "outputs": [], "source": [ "# Different projection planes\n", "fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n", "\n", "# View along X axis (u=X, v=Y, w=0)\n", "from scivianna.constants import X, Y, Z\n", "\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER',\n", " axes=axes[0],\n", " u=X, v=Y, w_value=0.0,\n", " edge_width=0.5\n", ")\n", "axes[0].set_title('XY Plane (w=0)')\n", "\n", "# View along Y axis\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER',\n", " axes=axes[1],\n", " u=Y, v=Z, w_value=0.0,\n", " edge_width=0.5\n", ")\n", "axes[1].set_title('YZ Plane (w=0)')\n", "\n", "# View along Z axis\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER',\n", " axes=axes[2],\n", " u=X, v=Z, w_value=0.0,\n", " edge_width=0.5\n", ")\n", "axes[2].set_title('XZ Plane (w=0)')\n", "\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "id": "matplotlib-advanced-layout", "metadata": {}, "source": [ "## Advanced: Multi-Panel Layout with Colorbars\n", "\n", "Create a sophisticated multi-panel figure with proper colorbars and legends:" ] }, { "cell_type": "code", "execution_count": null, "id": "matplotlib-advanced-layout", "metadata": {}, "outputs": [], "source": [ "# Create a complex layout\n", "fig = plt.figure(figsize=(16, 10))\n", "gs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)\n", "\n", "# Main view (large, top-left)\n", "ax_main = fig.add_subplot(gs[0:2, 0:2])\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER',\n", " axes=ax_main,\n", " edge_width=0.8\n", ")\n", "ax_main.set_title('Main View - Power Distribution')\n", "\n", "# Detail views (smaller, right side)\n", "ax_detail1 = fig.add_subplot(gs[0, 2])\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER',\n", " axes=ax_detail1,\n", " edge_width=0.3\n", ")\n", "ax_detail1.set_title('View 1')\n", "\n", "ax_detail2 = fig.add_subplot(gs[1, 2])\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER',\n", " axes=ax_detail2,\n", " edge_width=0.3\n", ")\n", "ax_detail2.set_title('View 2')\n", "\n", "# Bottom row for additional fields\n", "ax_bottom = fig.add_subplot(gs[2, :])\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER',\n", " axes=ax_bottom,\n", " edge_width=0.5\n", ")\n", "ax_bottom.set_title('Full Domain')\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "matplotlib-saving", "metadata": {}, "source": [ "## Saving Figures\n", "\n", "Matplotlib figures can be saved in any format supported by matplotlib:" ] }, { "cell_type": "code", "execution_count": null, "id": "matplotlib-save", "metadata": {}, "outputs": [], "source": [ "# Create and save a figure\n", "fig, ax = plt.subplots(figsize=(10, 8))\n", "\n", "plot_frame_in_axes(\n", " slave=slave,\n", " coloring_label='INTEGRATED_POWER',\n", " axes=ax,\n", " edge_width=0.8\n", ")\n", "ax.set_title('2D Geometry - Material Distribution')\n", "fig.tight_layout()\n", "\n", "# Save in various formats\n", "output_dir = Path(\".\").parent / 'outputs'\n", "output_dir.mkdir(exist_ok=True)\n", "\n", "fig.savefig(output_dir / 'geometry_material.png', dpi=150, bbox_inches='tight')\n", "print(f\"Saved PNG to: {output_dir / 'geometry_material.png'}\")" ] }, { "cell_type": "markdown", "id": "matplotlib-api-reference", "metadata": {}, "source": [ "## API Reference\n", "\n", "### `plot_frame()`\n", "\n", "Creates a new figure and plots geometry on it.\n", "\n", "| Parameter | Type | Default | Description |\n", "|-----------|------|---------|-------------|\n", "| `slave` | ComputeSlave / Geometry2D | - | Data source |\n", "| `coloring_label` | str | - | Field name to color by |\n", "| `u` | Tuple[float, float, float] | X | Horizontal director vector |\n", "| `v` | Tuple[float, float, float] | Y | Vertical director vector |\n", "| `u_min` | float | 0. | Lower bound along u |\n", "| `u_max` | float | 1. | Upper bound along u |\n", "| `v_min` | float | 0. | Lower bound along v |\n", "| `v_max` | float | 1. | Upper bound along v |\n", "| `w_value` | float | 0.0 | Position along u×v axis |\n", "| `color_map` | str | \"BuRd\" | Matplotlib colormap name |\n", "| `display_colorbar` | bool | False | Show colorbar |\n", "| `edge_width` | float | 1. | Polygon edge line width |\n", "| `custom_colors` | Dict | {} | Custom colors per field value |\n", "| `rename_values` | Dict | {} | Rename values in legend |\n", "| `legend_options` | Dict | {} | Options for `ax.legend()` |\n", "| `polygonize` | bool | True | Plot as polygons (vs grid) |\n", "| `options` | Dict | {} | Extra computation options |\n", "| `plot_options` | Dict | {} | Passed to plotter |\n", "\n", "**Returns:** `(plt.Figure, matplotlib.axes.Axes)`\n", "\n", "### `plot_frame_in_axes()`\n", "\n", "Plots geometry onto an existing matplotlib axis. Same parameters as `plot_frame()` plus:\n", "\n", "| Parameter | Type | Description |\n", "|-----------|------|-------------|\n", "| `axes` | matplotlib.axes.Axes | Target axis for plotting |\n", "\n", "**Returns:** None" ] }, { "cell_type": "markdown", "id": "matplotlib-next-steps", "metadata": {}, "source": [ "## Next Steps\n", "\n", "This tutorial covered the Matplotlib-based static plotting API in scivianna. For interactive visualization, see:\n", "\n", "- [Tutorial 1: Simple 2D Panel](./1_simple_2d_panel.ipynb) - Interactive Bokeh-based panels\n", "- [Tutorial 2: Multiview Panels](./2_multiview_panels.ipynb) - Multiple synchronized views\n", "- [Tutorial 3: 2D-1D Panel Interactions](./3_panel_2d_1d_interactions.ipynb) - Cross-panel interactions\n", "\n", "For development, see:\n", "- [Tutorial 4: Developing an Interface](./4_develop_interface.ipynb)\n", "- [Tutorial 5: Developing an Extension](./5_develop_extension.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "venv_t4", "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.10.12" } }, "nbformat": 4, "nbformat_minor": 5 }