Module diskchef.engine.plot

Module with plotting helper routines for diskchef

Classes

class Plot (table: CTable,
axes: matplotlib.axes._axes.Axes = None,
xscale: Literal['linear', 'log', 'symlog', 'logit'] | matplotlib.scale.ScaleBase = 'log',
yscale: Literal['linear', 'log', 'symlog', 'logit'] | matplotlib.scale.ScaleBase = 'linear',
margins: float = 0.0,
unit_format: Literal['latex', 'cds', None] = 'latex',
maxdepth: float = 1000000.0)
Expand source code
@dataclass
class Plot:
    table: CTable
    axes: matplotlib.axes.Axes = None
    xscale: Union[Literal["linear", "log", "symlog", "logit"], matplotlib.scale.ScaleBase] = "log"
    yscale: Union[Literal["linear", "log", "symlog", "logit"], matplotlib.scale.ScaleBase] = "linear"
    margins: float = 0.
    unit_format: Literal["latex", "cds", None] = "latex"
    maxdepth: float = 1e6

    def __post_init__(self):
        self.logger = logging.getLogger(__name__ + '.' + self.__class__.__qualname__)
        self.logger.info("Creating an instance of %s", self.__class__.__qualname__)
        self.logger.debug("With parameters: %s", self.__dict__)

        if self.axes is None:
            self.axes = plt.axes()

    def normalize_axes(self):
        self.axes.set_xscale(self.xscale)
        self.axes.set_yscale(self.yscale)
        self.axes.margins(self.margins)

    def formatted(self, unit: u.Unit):
        if unit == u.dimensionless_unscaled:
            return "[--]"
        else:
            return fr"[{unit.to_string(self.unit_format)}]"

Plot(table: diskchef.engine.ctable.CTable, axes: matplotlib.axes._axes.Axes = None, xscale: Union[Literal['linear', 'log', 'symlog', 'logit'], matplotlib.scale.ScaleBase] = 'log', yscale: Union[Literal['linear', 'log', 'symlog', 'logit'], matplotlib.scale.ScaleBase] = 'linear', margins: float = 0.0, unit_format: Literal['latex', 'cds', None] = 'latex', maxdepth: float = 1000000.0)

Subclasses

Instance variables

var axes : matplotlib.axes._axes.Axes
var margins : float
var maxdepth : float
var tableCTable
var unit_format : Literal['latex', 'cds', None]
var xscale : Literal['linear', 'log', 'symlog', 'logit'] | matplotlib.scale.ScaleBase
var yscale : Literal['linear', 'log', 'symlog', 'logit'] | matplotlib.scale.ScaleBase

Methods

def formatted(self, unit: astropy.units.core.Unit)
Expand source code
def formatted(self, unit: u.Unit):
    if unit == u.dimensionless_unscaled:
        return "[--]"
    else:
        return fr"[{unit.to_string(self.unit_format)}]"
def normalize_axes(self)
Expand source code
def normalize_axes(self):
    self.axes.set_xscale(self.xscale)
    self.axes.set_yscale(self.yscale)
    self.axes.margins(self.margins)
class Plot1D (table: CTable,
axes: matplotlib.axes._axes.Axes = None,
xscale: Literal['linear', 'log', 'symlog', 'logit'] | matplotlib.scale.ScaleBase = 'log',
yscale: Literal['linear', 'log', 'symlog', 'logit'] | matplotlib.scale.ScaleBase = 'log',
margins: float = 0.0,
unit_format: Literal['latex', 'cds', None] = 'latex',
maxdepth: float = 1000000.0,
data: List[str] = None,
x_axis: Unit("AU") = None,
labels: bool = True,
cmap: matplotlib.colors.Colormap | str = None,
plot_kwargs: dict = <factory>)
Expand source code
@dataclass
class Plot1D(Plot):
    data: List[str] = None
    x_axis: u.au = None
    yscale: Union[Literal["linear", "log", "symlog", "logit"], matplotlib.scale.ScaleBase] = "log"
    labels: bool = True
    cmap: Union[matplotlib.colors.Colormap, str] = None
    plot_kwargs: dict = field(default_factory=dict)

    def __post_init__(self):
        super().__post_init__()
        if not self.data:
            raise ValueError("List of `data` arguments must be specified")
        if self.x_axis is None:
            if self.table.is_in_zr_regular_grid:
                self.x_axis = u.Quantity(sorted(set(self.table.r)))
            else:
                self.x_axis = np.geomspace(np.min(self.table.r), np.max(self.table), 100)
        self.x_unit = self.x_axis.unit
        self.y_unit = (self.table[self.data[0]][0] * self.x_axis[0]).cgs.unit
        self.normalize_axes()
        self.axes.set_xlabel(f"Radius {self.formatted(self.x_unit)}")
        self.axes.set_ylabel(f"{self.formatted(self.y_unit)}")

        for colname in self.data:
            data_to_plot = self.table.column_density(colname, self.x_axis).cgs
            self.axes.plot(self.x_axis, data_to_plot, label=from_string(colname), **self.plot_kwargs)
        self.axes.legend()

Plot1D(table: diskchef.engine.ctable.CTable, axes: matplotlib.axes._axes.Axes = None, xscale: Union[Literal['linear', 'log', 'symlog', 'logit'], matplotlib.scale.ScaleBase] = 'log', yscale: Union[Literal['linear', 'log', 'symlog', 'logit'], matplotlib.scale.ScaleBase] = 'log', margins: float = 0.0, unit_format: Literal['latex', 'cds', None] = 'latex', maxdepth: float = 1000000.0, data: List[str] = None, x_axis: Unit("AU") = None, labels: bool = True, cmap: Union[matplotlib.colors.Colormap, str] = None, plot_kwargs: dict = )

Ancestors

Instance variables

var cmap : matplotlib.colors.Colormap | str
var data : List[str]
var labels : bool
var plot_kwargs : dict
var x_axis : Unit("AU")
var yscale : Literal['linear', 'log', 'symlog', 'logit'] | matplotlib.scale.ScaleBase
class Plot2D (table: CTable,
axes: matplotlib.axes._axes.Axes = None,
xscale: Literal['linear', 'log', 'symlog', 'logit'] | matplotlib.scale.ScaleBase = 'log',
yscale: Literal['linear', 'log', 'symlog', 'logit'] | matplotlib.scale.ScaleBase = 'linear',
margins: float = 0.0,
unit_format: Literal['latex', 'cds', None] = 'latex',
maxdepth: float = 1000000.0,
data1: str = None,
data2: str = None,
x_axis: str = 'Radius',
y_axis: str = 'Height to radius',
norm: matplotlib.colors.Normalize = None,
colorbar: bool = True,
labels: bool = True,
cmap: matplotlib.colors.Colormap | str = None,
multiply_by: str | float = 1.0,
levels: astropy.units.quantity.Quantity = None,
desired_max: astropy.units.quantity.Quantity = None,
norm_lower: bool = False)
Expand source code
@dataclass
class Plot2D(Plot):
    """2D visualization of a disk"""
    data1: str = None
    data2: str = None
    x_axis: str = "Radius"
    y_axis: str = "Height to radius"
    norm: matplotlib.colors.Normalize = None
    colorbar: bool = True
    labels: bool = True
    cmap: Union[matplotlib.colors.Colormap, str] = None
    multiply_by: Union[str, float] = 1.
    levels: u.Quantity = None
    desired_max: u.Quantity = None
    norm_lower: bool = False

    def __post_init__(self):
        super().__post_init__()
        if self.norm is None:
            self.norm = LogNormMaxOrders(vmax=self.desired_max, maxdepth=self.maxdepth)

        try:
            self.multiply_by = self.table[self.multiply_by]
        except (KeyError, ValueError):
            pass

        self.table.check_zeros(self.data1)
        self.table.check_zeros(self.data2)

        data1_q = u.Quantity(self.table[self.data1] * self.multiply_by)
        data1 = data1_q.value
        if self.norm_lower:
            data2_q = u.Quantity(self.table[self.data2] * self.multiply_by)
            self.data_unit = data2_q.unit
            data2 = data2_q.value
            self.norm(data2)
        else:
            self.data_unit = data1_q.unit
            self.norm(data1)
        x_axis = self.table[self.x_axis].value
        y_axis = self.table[self.y_axis].value
        self.x_unit = self.table[self.x_axis].unit
        self.y_unit = self.table[self.y_axis].unit
        self.axes.set_xlabel(f"{self.x_axis} {self.formatted(self.x_unit)}")
        self.axes.set_ylabel(f"{self.y_axis} {self.formatted(self.y_unit)}")
        if self.levels is None:
            minlevel = np.round(np.log10(self.norm.vmin))
            maxlevel = np.round(np.log10(self.norm.vmax))
            if maxlevel - minlevel == 1:
                self.cbar_formatter = LogFormatterMathtext()
                minlevel = np.round(np.log10(self.norm.vmin), 1)
                maxlevel = np.round(np.log10(self.norm.vmax), 1)
            else:
                self.cbar_formatter = LogFormatterMathtext()
            # as we need to convert self.levels to correct units and leave dimensionless
            # noinspection PyTypeChecker
            self.levels = np.logspace(minlevel, maxlevel, 13)
        else:
            self.levels = self.levels.to_value(self.data_unit)
        if len(set(self.levels)) == 1:
            self.levels = self.levels[0] * np.array([0.5, 1., 2.])
        self.normalize_axes()
        im = self.axes.tricontourf(
            x_axis, y_axis,
            data1,
            levels=self.levels,
            norm=self.norm,
            extend="both",
            cmap=self.cmap,
        )
        if self.data2 is not None:
            data2 = u.Quantity(self.table[self.data2] * self.multiply_by).to_value(self.data_unit)
            self.axes.tricontourf(
                x_axis, -y_axis,
                data2,
                levels=self.levels,
                norm=self.norm,
                extend="both",
                cmap=self.cmap,
            )
            self.axes.axhline(0, color="black")
            try:
                formatter = matplotlib.ticker.FuncFormatter(lambda x, pos: f'{abs(x):.2f}')  # todo as function def
                self.axes.yaxis.set_major_formatter(formatter)
            except ValueError:
                self.logger.warning("Could not fix yticks for negatives: %s", self.axes.get_yticklabels())

        self.axes.margins(self.margins)
        if self.colorbar:
            im.set_clim(self.norm.vmin, self.norm.vmax)
            self.cbar = self.axes.figure.colorbar(
                im, ax=self.axes,
                format=self.cbar_formatter
            )
            self.cbar.set_label(self.formatted(data1_q.unit), rotation="horizontal")
            self.cbar.ax.minorticks_off()
        if self.labels:
            txt = self.axes.text(
                0.05, 0.05, from_string(self.data2),
                transform=self.axes.transAxes,
                verticalalignment='bottom',
                bbox=dict(
                    boxstyle="round",
                    ec=(1., 1., 1., 0.5),
                    fc=(1., 1., 1., 0.7),
                )
            )
            if self.data2 is not None:
                txt = self.axes.text(
                    0.05, 0.95, from_string(self.data1),
                    transform=self.axes.transAxes,
                    verticalalignment='top',
                    bbox=dict(
                        boxstyle="round",
                        ec=(1., 1., 1., 0.5),
                        fc=(1., 1., 1., 0.7),
                    )
                )

    def contours(
            self,
            data: str,
            levels: Union[u.Quantity, List[float]],
            x_axis: str = "Radius",
            y_axis: str = "Height to radius",
            clabel_kwargs: dict = None,
            colors: Union[str, List[str]] = "black",
            on_colorbar: bool = True,
            location: Literal["upper", "bottom", "both"] = "both",
            **kwargs
    ):
        if clabel_kwargs is None:
            clabel_kwargs = {}
        data_q = u.Quantity(self.table[data])
        data = data_q.value
        dataunit = data_q.unit
        if "fmt" not in clabel_kwargs.keys():
            clabel_kwargs["fmt"] = f"%d {dataunit.to_string(self.unit_format)}"
        x_axis = self.table[x_axis].to_value(self.x_unit)
        y_axis = self.table[y_axis].to_value(self.y_unit)
        if location == "both":
            x_axis = [*x_axis, *x_axis]
            y_axis = [*-y_axis, *y_axis]
            data = [*data, *data]
        elif location == "bottom":
            y_axis = -y_axis
        elif location == "upper":
            pass
        else:
            raise CHEFValueError('location bust be one of ["upper", "bottom", "both"]')
        conts = self.axes.tricontour(
            x_axis, y_axis,
            data,
            levels=levels.to_value(dataunit),
            colors=colors,
            **kwargs
        )
        if on_colorbar:
            try:
                levels_as_data = levels.to_value(self.data_unit)
                new_conts = copy.copy(conts)
                new_conts.levels = levels_as_data
                self.cbar.add_lines(new_conts)
            except u.core.UnitConversionError as e:
                self.logger.info(e)
        try:
            conts.clabel(levels.to_value(dataunit), use_clabeltext=True, inline=True, inline_spacing=1, **clabel_kwargs)
        except ValueError as e:
            self.logger.warning(e)

2D visualization of a disk

Ancestors

Instance variables

var cmap : matplotlib.colors.Colormap | str
var colorbar : bool
var data1 : str
var data2 : str
var desired_max : astropy.units.quantity.Quantity
var labels : bool
var levels : astropy.units.quantity.Quantity
var multiply_by : str | float
var norm : matplotlib.colors.Normalize
var norm_lower : bool
var x_axis : str
var y_axis : str

Methods

def contours(self,
data: str,
levels: astropy.units.quantity.Quantity | List[float],
x_axis: str = 'Radius',
y_axis: str = 'Height to radius',
clabel_kwargs: dict = None,
colors: str | List[str] = 'black',
on_colorbar: bool = True,
location: Literal['upper', 'bottom', 'both'] = 'both',
**kwargs)
Expand source code
def contours(
        self,
        data: str,
        levels: Union[u.Quantity, List[float]],
        x_axis: str = "Radius",
        y_axis: str = "Height to radius",
        clabel_kwargs: dict = None,
        colors: Union[str, List[str]] = "black",
        on_colorbar: bool = True,
        location: Literal["upper", "bottom", "both"] = "both",
        **kwargs
):
    if clabel_kwargs is None:
        clabel_kwargs = {}
    data_q = u.Quantity(self.table[data])
    data = data_q.value
    dataunit = data_q.unit
    if "fmt" not in clabel_kwargs.keys():
        clabel_kwargs["fmt"] = f"%d {dataunit.to_string(self.unit_format)}"
    x_axis = self.table[x_axis].to_value(self.x_unit)
    y_axis = self.table[y_axis].to_value(self.y_unit)
    if location == "both":
        x_axis = [*x_axis, *x_axis]
        y_axis = [*-y_axis, *y_axis]
        data = [*data, *data]
    elif location == "bottom":
        y_axis = -y_axis
    elif location == "upper":
        pass
    else:
        raise CHEFValueError('location bust be one of ["upper", "bottom", "both"]')
    conts = self.axes.tricontour(
        x_axis, y_axis,
        data,
        levels=levels.to_value(dataunit),
        colors=colors,
        **kwargs
    )
    if on_colorbar:
        try:
            levels_as_data = levels.to_value(self.data_unit)
            new_conts = copy.copy(conts)
            new_conts.levels = levels_as_data
            self.cbar.add_lines(new_conts)
        except u.core.UnitConversionError as e:
            self.logger.info(e)
    try:
        conts.clabel(levels.to_value(dataunit), use_clabeltext=True, inline=True, inline_spacing=1, **clabel_kwargs)
    except ValueError as e:
        self.logger.warning(e)