Skip to content

XY transposable sim.plot() plots (issue1072) #2537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 38 additions & 17 deletions tidy3d/components/base_sim/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pydantic.v1 as pd

from tidy3d.components.base import cached_property, skip_if_fields_missing
from tidy3d.components.geometry.base import Box
from tidy3d.components.geometry.base import Box, Geometry
from tidy3d.components.medium import Medium, MediumType3D
from tidy3d.components.scene import Scene
from tidy3d.components.structure import Structure
Expand Down Expand Up @@ -251,6 +251,7 @@ def plot(
hlim: Optional[tuple[float, float]] = None,
vlim: Optional[tuple[float, float]] = None,
fill_structures: bool = True,
transpose: bool = False,
**patch_kwargs,
) -> Ax:
"""Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate.
Expand All @@ -275,32 +276,35 @@ def plot(
The z range if plotting on xz or yz planes, y plane if plotting on xy plane.
fill_structures : bool = True
Whether to fill structures with color or just draw outlines.
transpose: bool = False
Optional: Swap the horizontal and vertical axes.

Returns
-------
matplotlib.axes._subplots.Axes
The supplied or created matplotlib axes.
"""

hlim, vlim = Scene._get_plot_lims(
bounds=self.simulation_bounds, x=x, y=y, z=z, hlim=hlim, vlim=vlim
bounds=self.simulation_bounds, x=x, y=y, z=z, hlim=hlim, vlim=vlim, transpose=transpose
)

ax = self.scene.plot_structures(
ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, fill=fill_structures
ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, fill=fill_structures, transpose=transpose
)
ax = self.plot_sources(ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, alpha=source_alpha)
ax = self.plot_monitors(ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, alpha=monitor_alpha)
ax = self.plot_sources(ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, alpha=source_alpha, transpose=transpose)
ax = self.plot_monitors(ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, alpha=monitor_alpha, transpose=transpose)
ax = Scene._set_plot_bounds(
bounds=self.simulation_bounds, ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim
bounds=self.simulation_bounds, ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, transpose=transpose
)
ax = self.plot_boundaries(ax=ax, x=x, y=y, z=z)
ax = self.plot_symmetries(ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim)
ax = self.plot_boundaries(ax=ax, x=x, y=y, z=z, transpose=transpose)
ax = self.plot_symmetries(ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, transpose=transpose)

# Add the default axis labels, tick labels, and title
ax = Box.add_ax_labels_and_title(
ax=ax, x=x, y=y, z=z, plot_length_units=self.plot_length_units
)

Geometry.transpose_axis_info(ax, swap_axis_labels=transpose) # Swap axis labels if NEEDED
return ax

@equal_aspect
Expand All @@ -314,6 +318,7 @@ def plot_sources(
vlim: Optional[tuple[float, float]] = None,
alpha: Optional[float] = None,
ax: Ax = None,
transpose: bool = False,
) -> Ax:
"""Plot each of simulation's sources on a plane defined by one nonzero x,y,z coordinate.

Expand All @@ -333,6 +338,8 @@ def plot_sources(
Opacity of the sources, If ``None`` uses Tidy3d default.
ax : matplotlib.axes._subplots.Axes = None
Matplotlib axes to plot on, if not specified, one is created.
transpose: bool = False
Optional: Swap the horizontal and vertical axes.

Returns
-------
Expand All @@ -343,12 +350,13 @@ def plot_sources(
for source in self.sources:
ax = source.plot(x=x, y=y, z=z, alpha=alpha, ax=ax, sim_bounds=bounds)
ax = Scene._set_plot_bounds(
bounds=self.simulation_bounds, ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim
bounds=self.simulation_bounds, ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, transpose=transpose
)
# Add the default axis labels, tick labels, and title
ax = Box.add_ax_labels_and_title(
ax=ax, x=x, y=y, z=z, plot_length_units=self.plot_length_units
)
Geometry.transpose_axis_info(ax, swap_axis_labels=transpose) # Swap axis labels if needed
return ax

@equal_aspect
Expand All @@ -362,6 +370,7 @@ def plot_monitors(
vlim: Optional[tuple[float, float]] = None,
alpha: Optional[float] = None,
ax: Ax = None,
transpose: bool = False,
) -> Ax:
"""Plot each of simulation's monitors on a plane defined by one nonzero x,y,z coordinate.

Expand All @@ -381,6 +390,8 @@ def plot_monitors(
Opacity of the sources, If ``None`` uses Tidy3d default.
ax : matplotlib.axes._subplots.Axes = None
Matplotlib axes to plot on, if not specified, one is created.
transpose: bool = False
Optional: Swap the horizontal and vertical axes.

Returns
-------
Expand All @@ -389,14 +400,15 @@ def plot_monitors(
"""
bounds = self.bounds
for monitor in self.monitors:
ax = monitor.plot(x=x, y=y, z=z, alpha=alpha, ax=ax, sim_bounds=bounds)
ax = monitor.plot(x=x, y=y, z=z, alpha=alpha, ax=ax, sim_bounds=bounds, transpose=transpose)
ax = Scene._set_plot_bounds(
bounds=self.simulation_bounds, ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim
bounds=self.simulation_bounds, ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, transpose=transpose
)
# Add the default axis labels, tick labels, and title
ax = Box.add_ax_labels_and_title(
ax=ax, x=x, y=y, z=z, plot_length_units=self.plot_length_units
)
Geometry.transpose_axis_info(ax, swap_axis_labels=transpose) # Swap axis labels if needed
return ax

@equal_aspect
Expand All @@ -409,6 +421,7 @@ def plot_symmetries(
hlim: Optional[tuple[float, float]] = None,
vlim: Optional[tuple[float, float]] = None,
ax: Ax = None,
transpose: bool = False,
) -> Ax:
"""Plot each of simulation's symmetries on a plane defined by one nonzero x,y,z coordinate.

Expand All @@ -426,6 +439,8 @@ def plot_symmetries(
The z range if plotting on xz or yz planes, y plane if plotting on xy plane.
ax : matplotlib.axes._subplots.Axes = None
Matplotlib axes to plot on, if not specified, one is created.
transpose: bool = False
Optional: Swap the horizontal and vertical axes.

Returns
-------
Expand All @@ -442,12 +457,13 @@ def plot_symmetries(
plot_params = self._make_symmetry_plot_params(sym_value=sym_value)
ax = sym_box.plot(x=x, y=y, z=z, ax=ax, **plot_params.to_kwargs())
ax = Scene._set_plot_bounds(
bounds=self.simulation_bounds, ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim
bounds=self.simulation_bounds, ax=ax, x=x, y=y, z=z, hlim=hlim, vlim=vlim, transpose=transpose
)
# Add the default axis labels, tick labels, and title
ax = Box.add_ax_labels_and_title(
ax=ax, x=x, y=y, z=z, plot_length_units=self.plot_length_units
)
Geometry.transpose_axis_info(ax, swap_axis_labels=transpose) # Swap axis labels if needed
return ax

def _make_symmetry_plot_params(self, sym_value: Symmetry) -> PlotParams:
Expand Down Expand Up @@ -519,6 +535,7 @@ def plot_structures(
hlim: Optional[tuple[float, float]] = None,
vlim: Optional[tuple[float, float]] = None,
fill: bool = True,
# transpose: bool = False, CONTINUEHERE
) -> Ax:
"""Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate.

Expand All @@ -545,11 +562,11 @@ def plot_structures(
"""

hlim_new, vlim_new = Scene._get_plot_lims(
bounds=self.simulation_bounds, x=x, y=y, z=z, hlim=hlim, vlim=vlim
bounds=self.simulation_bounds, x=x, y=y, z=z, hlim=hlim, vlim=vlim #, transpose=transpose CONTINUEHERE
)

return self.scene.plot_structures(
x=x, y=y, z=z, ax=ax, hlim=hlim_new, vlim=vlim_new, fill=fill
x=x, y=y, z=z, ax=ax, hlim=hlim_new, vlim=vlim_new, fill=fill #, transpose=transpose CONTINUEHERE
)

@equal_aspect
Expand All @@ -566,6 +583,7 @@ def plot_structures_eps(
ax: Ax = None,
hlim: Optional[tuple[float, float]] = None,
vlim: Optional[tuple[float, float]] = None,
# transpose: bool = False, CONTINUEHERE
) -> Ax:
"""Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate.
The permittivity is plotted in grayscale based on its value at the specified frequency.
Expand Down Expand Up @@ -603,7 +621,7 @@ def plot_structures_eps(
"""

hlim, vlim = Scene._get_plot_lims(
bounds=self.simulation_bounds, x=x, y=y, z=z, hlim=hlim, vlim=vlim
bounds=self.simulation_bounds, x=x, y=y, z=z, hlim=hlim, vlim=vlim #, transpose=transpose CONTINUEHERE
)

return self.scene.plot_structures_eps(
Expand All @@ -617,6 +635,7 @@ def plot_structures_eps(
hlim=hlim,
vlim=vlim,
reverse=reverse,
# transpose=transpose CONTINUEHERE
)

@equal_aspect
Expand All @@ -632,6 +651,7 @@ def plot_structures_heat_conductivity(
ax: Ax = None,
hlim: Optional[tuple[float, float]] = None,
vlim: Optional[tuple[float, float]] = None,
# transpose: bool, CONTINUEHERE
) -> Ax:
"""Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate.
The permittivity is plotted in grayscale based on its value at the specified frequency.
Expand Down Expand Up @@ -669,7 +689,7 @@ def plot_structures_heat_conductivity(
"""

hlim, vlim = Scene._get_plot_lims(
bounds=self.simulation_bounds, x=x, y=y, z=z, hlim=hlim, vlim=vlim
bounds=self.simulation_bounds, x=x, y=y, z=z, hlim=hlim, vlim=vlim #, transpose=transpose CONTINUEHERE
)

return self.scene.plot_structures_heat_conductivity(
Expand All @@ -682,6 +702,7 @@ def plot_structures_heat_conductivity(
hlim=hlim,
vlim=vlim,
reverse=reverse,
# transpose=transpose CONTINUEHERE
)

@classmethod
Expand Down
77 changes: 70 additions & 7 deletions tidy3d/components/geometry/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import xarray as xr

try:
from matplotlib import patches
from matplotlib import patches, transforms
except ImportError:
pass

Expand Down Expand Up @@ -485,6 +485,29 @@ def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> Geomet
raise NotImplementedError(
"'_update_from_bounds' is not compatible with this geometry class."
)

@staticmethod
def transpose_axis_info(
ax: Ax = None,
swap_axis_labels: bool = False,
swap_axis_limits: bool = False,
) -> None:
"""Swaps matplotlib axis-labels and limits, but does not alter geometry (contents) of a plot."""
if swap_axis_labels:
# Swap the axis labels?
xlabel_orig = ax.get_xlabel()
ylabel_orig = ax.get_ylabel()
ax.set_xlabel(ylabel_orig)
ax.set_ylabel(xlabel_orig)
if swap_axis_limits:
# Do we want to swap the graph boundaries/limits?
xlim_orig = ax.get_xlim()
ylim_orig = ax.get_ylim()
ax.set_xlim(ylim_orig)
ax.set_ylim(xlim_orig)
# Now recalculate automatic tick locations based on new limits:
ax.relim()
ax.autoscale_view()

@equal_aspect
@add_ax_if_none
Expand All @@ -496,6 +519,7 @@ def plot(
ax: Ax = None,
plot_length_units: LengthUnit = None,
viz_spec: VisualizationSpec = None,
transpose: bool = False,
**patch_kwargs,
) -> Ax:
"""Plot geometry cross section at single (x,y,z) coordinate.
Expand All @@ -514,6 +538,8 @@ def plot(
Specify units to use for axis labels, tick labels, and the title.
viz_spec : VisualizationSpec = None
Plotting parameters associated with a medium to use instead of defaults.
transpose: bool = False
Optional: Swap the horizontal and vertical axes.
**patch_kwargs
Optional keyword arguments passed to the matplotlib patch plotting of structure.
For details on accepted values, refer to
Expand All @@ -536,17 +562,27 @@ def plot(

# for each intersection, plot the shape
for shape in shapes_intersect:
ax = self.plot_shape(shape, plot_params=plot_params, ax=ax)
ax = self.plot_shape(shape, plot_params=plot_params, ax=ax, transpose=transpose)

# clean up the axis display
ax = self.add_ax_lims(axis=axis, ax=ax)
ax.set_aspect("equal")
# Add the default axis labels, tick labels, and title
ax = Box.add_ax_labels_and_title(ax=ax, x=x, y=y, z=z, plot_length_units=plot_length_units)
Geometry.transpose_axis_info(ax, swap_axis_labels=transpose) # Swap axis labels if needed
return ax

def plot_shape(self, shape: Shapely, plot_params: PlotParams, ax: Ax) -> Ax:
"""Defines how a shape is plotted on a matplotlib axes."""
def plot_shape(
self,
shape: Shapely,
plot_params: PlotParams,
ax: Ax,
transpose: bool = False,
) -> Ax:
"""
Defines how a shape is plotted on a matplotlib axes.
If transpose==True, the horizontal and vertical coordinates are swapped.
"""
if shape.geom_type in (
"MultiPoint",
"MultiLineString",
Expand All @@ -555,19 +591,39 @@ def plot_shape(self, shape: Shapely, plot_params: PlotParams, ax: Ax) -> Ax:
):
for sub_shape in shape.geoms:
ax = self.plot_shape(shape=sub_shape, plot_params=plot_params, ax=ax)

return ax

_shape = Geometry.evaluate_inf_shape(shape)

if _shape.geom_type == "LineString":
xs, ys = zip(*_shape.coords)
ax.plot(xs, ys, color=plot_params.facecolor, linewidth=plot_params.linewidth)
if transpose:
ax.plot(ys, xs, color=plot_params.facecolor, linewidth=plot_params.linewidth)
else:
ax.plot(xs, ys, color=plot_params.facecolor, linewidth=plot_params.linewidth)
elif _shape.geom_type == "Point":
ax.scatter(shape.x, shape.y, color=plot_params.facecolor)
if transpose:
ax.scatter(shape.y, shape.x, color=plot_params.facecolor)
else:
ax.scatter(shape.x, shape.y, color=plot_params.facecolor)
else:
patch = polygon_patch(_shape, **plot_params.to_kwargs())
if transpose:
# Define a transformation which swaps horizal<-->vertical coordinates.
transpose_xy = transforms.Affine2D()
transpose_xy.set_matrix(
np.array(
[[0, 1, 0], # Swap the X and Y axes.
[1, 0, 0],
[0, 0, 1]]
)
)
# Apply this transformation to the coordinates of the patch.
patch.set_transform(transpose_xy + ax.transData)
ax.add_artist(patch)
# If transpose==True, I also update the axis labels (if any). In practice, this
# seems to have no effect (since they get overwritten elsewhere), but I do it anway.
Geometry.transpose_axis_info(ax, swap_axis_labels=transpose)
return ax

@staticmethod
Expand Down Expand Up @@ -2200,6 +2256,7 @@ def _plot_arrow(
both_dirs: bool = False,
ax: Ax = None,
arrow_base: Coordinate = None,
transpose: bool = False,
) -> Ax:
"""Adds an arrow to the axis if with options if certain conditions met.

Expand All @@ -2225,6 +2282,8 @@ def _plot_arrow(
If True, plots an arrow pointing in direction and one in -direction.
arrow_base : :class:`.Coordinate` = None
Custom base of the arrow. Uses the geometry's center if not provided.
transpose: bool = False
Optional: Swap the horizontal and vertical axes.

Returns
-------
Expand Down Expand Up @@ -2260,6 +2319,10 @@ def _plot_arrow(
v_x = (xmax - xmin) / 10
v_y = (ymax - ymin) / 10

if transpose:
x0, y0 = y0, x0
v_x, v_y = v_y, v_x

directions = (1.0, -1.0) if both_dirs else (1.0,)
for sign in directions:
arrow = patches.FancyArrowPatch(
Expand Down
Loading