|
1 | 1 | from collections.abc import Sequence |
2 | 2 | import matplotlib |
3 | 3 | import matplotlib.pyplot as plt |
| 4 | +from mpl_toolkits.axes_grid1 import make_axes_locatable |
4 | 5 | import numpy as np |
5 | 6 | from pathlib import Path |
6 | 7 | from tempfile import TemporaryDirectory |
@@ -752,9 +753,9 @@ def create_or_update_plot(plot_objects=None, tind=None, this_save_as=None): |
752 | 753 | X, Y, Z, scalars=data, vmin=vmin, vmax=vmax, **kwargs |
753 | 754 | ) |
754 | 755 | else: |
755 | | - plot_objects[ |
756 | | - region_name + str(i) |
757 | | - ].mlab_source.scalars = data |
| 756 | + plot_objects[region_name + str(i)].mlab_source.scalars = ( |
| 757 | + data |
| 758 | + ) |
758 | 759 |
|
759 | 760 | if mayavi_view is not None: |
760 | 761 | mlab.view(*mayavi_view) |
@@ -849,3 +850,181 @@ def animation_func(): |
849 | 850 | plt.show() |
850 | 851 | else: |
851 | 852 | raise ValueError(f"Unrecognised plot3d() 'engine' argument: {engine}") |
| 853 | + |
| 854 | + |
| 855 | +def plot2d_polygon( |
| 856 | + da, |
| 857 | + ax=None, |
| 858 | + cax=None, |
| 859 | + cmap="viridis", |
| 860 | + norm=None, |
| 861 | + logscale=False, |
| 862 | + antialias=False, |
| 863 | + vmin=None, |
| 864 | + vmax=None, |
| 865 | + extend="neither", |
| 866 | + add_colorbar=True, |
| 867 | + colorbar_label=None, |
| 868 | + separatrix=True, |
| 869 | + separatrix_kwargs={"color": "white", "linestyle": "-", "linewidth": 2}, |
| 870 | + targets=False, |
| 871 | + add_limiter_hatching=False, |
| 872 | + grid_only=False, |
| 873 | + linewidth=0, |
| 874 | + linecolor="black", |
| 875 | +): |
| 876 | + """ |
| 877 | + Nice looking 2D plots which have no visual artifacts around the X-point. |
| 878 | +
|
| 879 | + Parameters |
| 880 | + ---------- |
| 881 | + da : xarray.DataArray |
| 882 | + A 2D (x,y) DataArray of data to plot |
| 883 | + ax : Axes, optional |
| 884 | + Axes to plot on. If not provided, will make its own. |
| 885 | + cax : Axes, optional |
| 886 | + Axes to plot colorbar on. If not provided, will plot on the same axes as the plot. |
| 887 | + cmap : str or matplotlib.colors.Colormap, default "viridis" |
| 888 | + Colormap to use for the plot |
| 889 | + norm : matplotlib.colors.Normalize, optional |
| 890 | + Normalization to use for the color scale |
| 891 | + logscale : bool, default False |
| 892 | + If True, use a symlog color scale |
| 893 | + antialias : bool, default False |
| 894 | + Enables antialiasing. Note: this also shows mesh cell edges - it's unclear how to disable this. |
| 895 | + vmin : float, optional |
| 896 | + Minimum value for the color scale |
| 897 | + vmax : float, optional |
| 898 | + Maximum value for the color scale |
| 899 | + extend : str, optional, default "neither" |
| 900 | + Extend the colorbar. Options are "neither", "both", "min", "max" |
| 901 | + add_colorbar : bool, default True |
| 902 | + Enable colorbar in figure? |
| 903 | + colorbar_label : str, optional |
| 904 | + Label for the colorbar |
| 905 | + separatrix : bool, default True |
| 906 | + Add lines showing separatrices |
| 907 | + separatrix_kwargs : dict |
| 908 | + Keyword arguments to pass custom style to the separatrices plot |
| 909 | + targets : bool, default True |
| 910 | + Draw solid lines at the target surfaces |
| 911 | + add_limiter_hatching : bool, default True |
| 912 | + Draw hatched areas at the targets |
| 913 | + grid_only : bool, default False |
| 914 | + Only plot the grid, not the data. This sets all the polygons to have a white face. |
| 915 | + linewidth : float, default 0 |
| 916 | + Width of the gridlines on cell edges |
| 917 | + linecolor : str, default "black" |
| 918 | + Color of the gridlines on cell edges |
| 919 | + """ |
| 920 | + |
| 921 | + if ax is None: |
| 922 | + fig, ax = plt.subplots(figsize=(3, 6), dpi=120) |
| 923 | + else: |
| 924 | + fig = ax.get_figure() |
| 925 | + |
| 926 | + if cax is None: |
| 927 | + cax = ax |
| 928 | + |
| 929 | + if vmin is None: |
| 930 | + vmin = np.nanmin(da.values) |
| 931 | + |
| 932 | + if vmax is None: |
| 933 | + vmax = np.nanmax(da.max().values) |
| 934 | + |
| 935 | + if colorbar_label == None: |
| 936 | + if "short_name" in da.attrs: |
| 937 | + colorbar_label = da.attrs["short_name"] |
| 938 | + elif da.name != None: |
| 939 | + colorbar_label = da.name |
| 940 | + else: |
| 941 | + colorbar_label = "" |
| 942 | + |
| 943 | + if "units" in da.attrs: |
| 944 | + colorbar_label += f" [{da.attrs['units']}]" |
| 945 | + |
| 946 | + if "Rxy_lower_right_corners" in da.coords: |
| 947 | + r_nodes = [ |
| 948 | + "R", |
| 949 | + "Rxy_lower_left_corners", |
| 950 | + "Rxy_lower_right_corners", |
| 951 | + "Rxy_upper_left_corners", |
| 952 | + "Rxy_upper_right_corners", |
| 953 | + ] |
| 954 | + z_nodes = [ |
| 955 | + "Z", |
| 956 | + "Zxy_lower_left_corners", |
| 957 | + "Zxy_lower_right_corners", |
| 958 | + "Zxy_upper_left_corners", |
| 959 | + "Zxy_upper_right_corners", |
| 960 | + ] |
| 961 | + cell_r = np.concatenate( |
| 962 | + [np.expand_dims(da[x], axis=2) for x in r_nodes], axis=2 |
| 963 | + ) |
| 964 | + cell_z = np.concatenate( |
| 965 | + [np.expand_dims(da[x], axis=2) for x in z_nodes], axis=2 |
| 966 | + ) |
| 967 | + else: |
| 968 | + raise Exception("Cell corners not present in mesh, cannot do polygon plot") |
| 969 | + |
| 970 | + Nx = len(cell_r) |
| 971 | + Ny = len(cell_r[0]) |
| 972 | + patches = [] |
| 973 | + |
| 974 | + # https://matplotlib.org/2.0.2/examples/api/patch_collection.html |
| 975 | + |
| 976 | + idx = [np.array([1, 2, 4, 3, 1])] |
| 977 | + patches = [] |
| 978 | + for i in range(Nx): |
| 979 | + for j in range(Ny): |
| 980 | + p = matplotlib.patches.Polygon( |
| 981 | + np.concatenate((cell_r[i][j][tuple(idx)], cell_z[i][j][tuple(idx)])) |
| 982 | + .reshape(2, 5) |
| 983 | + .T, |
| 984 | + fill=False, |
| 985 | + closed=True, |
| 986 | + facecolor=None, |
| 987 | + ) |
| 988 | + patches.append(p) |
| 989 | + |
| 990 | + norm = _create_norm(logscale, norm, vmin, vmax) |
| 991 | + |
| 992 | + if grid_only is True: |
| 993 | + cmap = matplotlib.colors.ListedColormap(["white"]) |
| 994 | + colors = da.data.flatten() |
| 995 | + polys = matplotlib.collections.PatchCollection( |
| 996 | + patches, |
| 997 | + alpha=1, |
| 998 | + norm=norm, |
| 999 | + cmap=cmap, |
| 1000 | + antialiaseds=antialias, |
| 1001 | + edgecolors=linecolor, |
| 1002 | + linewidths=linewidth, |
| 1003 | + joinstyle="bevel", |
| 1004 | + ) |
| 1005 | + |
| 1006 | + polys.set_array(colors) |
| 1007 | + |
| 1008 | + if add_colorbar: |
| 1009 | + # This produces a "foolproof" colorbar which |
| 1010 | + # is always the height of the plot |
| 1011 | + # From https://joseph-long.com/writing/colorbars/ |
| 1012 | + divider = make_axes_locatable(ax) |
| 1013 | + cax = divider.append_axes("right", size="5%", pad=0.05) |
| 1014 | + fig.colorbar(polys, cax=cax, label=colorbar_label, extend=extend) |
| 1015 | + cax.grid(which="both", visible=False) |
| 1016 | + |
| 1017 | + ax.add_collection(polys) |
| 1018 | + |
| 1019 | + ax.set_aspect("equal", adjustable="box") |
| 1020 | + ax.set_xlabel("R [m]") |
| 1021 | + ax.set_ylabel("Z [m]") |
| 1022 | + ax.set_ylim(cell_z.min(), cell_z.max()) |
| 1023 | + ax.set_xlim(cell_r.min(), cell_r.max()) |
| 1024 | + ax.set_title(da.name) |
| 1025 | + |
| 1026 | + if separatrix: |
| 1027 | + plot_separatrices(da, ax, x="R", y="Z", **separatrix_kwargs) |
| 1028 | + |
| 1029 | + if targets: |
| 1030 | + plot_targets(da, ax, x="R", y="Z", hatching=add_limiter_hatching) |
0 commit comments