Skip to content
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
4 changes: 3 additions & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
name: Tests

on:
- pull_request
pull_request:
branches:
- main # Only runs if the PR is aiming to merge INTO main

jobs:
test:
Expand Down
4 changes: 4 additions & 0 deletions src/matplot2tikz/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,10 @@ def _is_colorbar_heuristic(obj: Axes) -> bool:
)


# Public alias for use by _save when computing default axis dimensions.
is_colorbar_heuristic = _is_colorbar_heuristic


def _mpl_cmap2pgf_cmap(cmap: Colormap, data: TikzData) -> tuple[str, bool]:
"""Converts a color map as given in matplotlib to a color map as represented in PGFPlots."""
if isinstance(cmap, LinearSegmentedColormap):
Expand Down
59 changes: 41 additions & 18 deletions src/matplot2tikz/_cleanfigure.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,28 +370,45 @@ def _remove_nans(data: np.ndarray) -> np.ndarray:

I.e., those at the end/beginning of the data and consecutive ones.
"""

# do nothing if data is empty, as it would be after a call to clean_figure()
if data.size == 0:
return data

id_nan = np.any(np.isnan(data), axis=1)

# Likewise guard against all rows being NaN
valid = np.argwhere(~id_nan).reshape((-1,))

if valid.size == 0:
return data[:0]

id_remove = np.argwhere(id_nan).reshape((-1,))
if not _isempty(id_remove):
id_remove = id_remove[
np.concatenate([np.diff(id_remove, axis=0) == 1, np.array([False]).reshape((-1,))])
]
if id_remove.size != 0:
consecutive = np.diff(id_remove) == 1
id_remove = id_remove[np.concatenate([consecutive, np.array([False])])]

id_first = np.argwhere(np.logical_not(id_nan))[0]
id_last = np.argwhere(np.logical_not(id_nan))[-1]
id_first = valid[0]
id_last = valid[-1]

id_remove = np.concatenate(
[np.arange(0, id_first),
id_remove,
np.arange(id_last + 1, len(data))]
)

if _isempty(id_first):
# remove entire data
id_remove = np.arange(len(data))
else:
id_remove = np.concatenate(
[np.arange(0, id_first[0]), id_remove, np.arange(id_last[0] + 1, len(data))]
)
return np.delete(data, id_remove, axis=0)


def _sorted_limits(lim: np.ndarray) -> np.ndarray:
"""Return axis limits as [min, max] regardless of axis direction."""
return np.array([np.min(lim), np.max(lim)])


def _is_in_box(data: np.ndarray, x_lim: np.ndarray, y_lim: np.ndarray) -> np.ndarray:
"""Returns a mask that indicates, whether a data point is within the limits."""
x_lim = _sorted_limits(x_lim)
y_lim = _sorted_limits(y_lim)
mask_x = np.logical_and(data[:, 0] > x_lim[0], data[:, 0] < x_lim[1])
mask_y = np.logical_and(data[:, 1] > y_lim[0], data[:, 1] < y_lim[1])
return np.logical_and(mask_x, mask_y)
Expand Down Expand Up @@ -545,14 +562,15 @@ def _move_points_closer(y_lim: np.ndarray, data: np.ndarray) -> np.ndarray:
# Calculate the extension of the extended box
# (x_width not important for clipping, as it is already dealt with elsewhere (maybe by
# matplotlib when lim() occurs))
y_width = y_lim[1] - y_lim[0]
y_min, y_max = _sorted_limits(y_lim)
y_width = y_max - y_min

# Don't choose the larger box too large to make sure that the values inside
# it can still be treated by TeX.

extended_factor = 0.1
y_min_ext = y_lim[0] - extended_factor * y_width
y_max_ext = y_lim[1] + extended_factor * y_width
y_min_ext = y_min - extended_factor * y_width
y_max_ext = y_max + extended_factor * y_width

# Copy data to avoid modifying original array
clipped_data = np.array(data, copy=True)
Expand Down Expand Up @@ -607,8 +625,8 @@ def _simplify_line(cfd: CleanFigureData) -> np.ndarray:

# Automatically guess a tol based on the area of the figure and
# the area and resolution of the output
x_range = cfd.x_lim[1] - cfd.x_lim[0]
y_range = cfd.y_lim[1] - cfd.y_lim[0]
x_range = np.max(cfd.x_lim) - np.min(cfd.x_lim)
y_range = np.max(cfd.y_lim) - np.min(cfd.y_lim)

# Conversion factors of data units into pixels
x_to_pix = width / x_range
Expand Down Expand Up @@ -893,6 +911,8 @@ def _corners2d(
x_lim: np.ndarray, y_lim: np.ndarray
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""Determine the corners of the axes as defined by xLim and yLim."""
x_lim = _sorted_limits(x_lim)
y_lim = _sorted_limits(y_lim)
bottom_left = np.array([x_lim[0], y_lim[0]])
top_left = np.array([x_lim[0], y_lim[1]])
bottom_right = np.array([x_lim[1], y_lim[0]])
Expand All @@ -904,6 +924,9 @@ def _corners3d(
x_lim: list | np.ndarray, y_lim: list | np.ndarray, z_lim: list | np.ndarray
) -> np.ndarray:
"""Determine the corners of the 3D axes as defined by xLim, yLim and zLim."""
x_lim = _sorted_limits(np.array(x_lim))
y_lim = _sorted_limits(np.array(y_lim))
z_lim = _sorted_limits(np.array(z_lim))
# Lower square of the cube
lower_bottom_left = np.array([x_lim[0], y_lim[0], z_lim[0]])
lower_top_left = np.array([x_lim[0], y_lim[1], z_lim[0]])
Expand Down
2 changes: 2 additions & 0 deletions src/matplot2tikz/_line2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ def draw_linecollection(data: TikzData, obj: LineCollection) -> list[str]:
"""Returns Pgfplots code for a number of patch objects."""
content = []

obj.update_scalarmappable()

edgecolors = obj.get_edgecolors() # type: ignore[attr-defined]
linestyles = obj.get_linestyles() # type: ignore[attr-defined]
linewidths = obj.get_linewidths() # type: ignore[attr-defined]
Expand Down
4 changes: 4 additions & 0 deletions src/matplot2tikz/_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ def draw_pathcollection(data: TikzData, obj: PathCollection) -> list[str]:
def _draw_pathcollection_scatter_colormap(data: TikzData, pcd: PathCollectionData) -> None:
obj_array = pcd.obj.get_array()
if obj_array is not None:
# clean_figure() can cause a mismatch in color array len, so check and truncate as needed
if len(obj_array) != len(pcd.dd_strings):
obj_array = obj_array[: len(pcd.dd_strings)]

pcd.dd_strings = np.column_stack([pcd.dd_strings, obj_array])
pcd.labels.append("colordata")
pcd.draw_options.append("scatter src=explicit")
Expand Down
62 changes: 41 additions & 21 deletions src/matplot2tikz/_save.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,19 @@ def get_tikz_code( # noqa: PLR0913
TikZ/PGFPlots output. If ``axis_height`` is not given,
``matplot2tikz`` will try to preserve the original width/height
ratio. Note that ``axis_width`` can be a string literal, such as
``'\\axis_width'``.
``'\\axis_width'``. If both ``axis_width`` and ``axis_height``
are omitted and the figure has a single (non-colorbar) axis,
they default to the figure size in inches (e.g. from
``figsize`` in ``plt.subplots()``), with the ``"in"`` suffix.
:type axis_width: str

:param axis_height: If not ``None``, this will be used as figure height within the
TikZ/PGFPlots output. If ``axis_width`` is not given,
``matplot2tikz`` will try to preserve the original width/height
ratio. Note that ``axis_width`` can be a string literal, such
as ``'\\axis_height'``.
ratio. Note that ``axis_height`` can be a string literal, such
as ``'\\axis_height'``. If both ``axis_width`` and
``axis_height`` are omitted and the figure has a single
(non-colorbar) axis, they default to the figure size in inches.
:type axis_height: str

:param textsize: The text size (in pt) that the target latex document is using.
Expand Down Expand Up @@ -279,6 +284,9 @@ def save(
) -> None:
"""Same as `get_tikz_code()`, but actually saves the code to a file.

All other arguments (e.g. ``axis_width``, ``axis_height``, ``figure``) are
passed through to `get_tikz_code()`; see that function for documentation.

:param filepath: The file to which the TikZ output will be written.
:type filepath: str

Expand Down Expand Up @@ -379,32 +387,44 @@ def _draw_collection(data: TikzData, child: Collection) -> list[str]:
return _patch.draw_patchcollection(data, child)


def _set_default_axis_dimensions_from_figure(data: TikzData, fig: Figure) -> None:
"""Set axis width/height from figure size (in) when both unset and one non-colorbar axis."""
if data.axis_width is not None or data.axis_height is not None:
return
non_cb_axes = [a for a in fig.axes if not _axes.is_colorbar_heuristic(a)]
if len(non_cb_axes) != 1:
return
data.axis_width = f"{fig.get_figwidth():{data.float_format}}in"
data.axis_height = f"{fig.get_figheight():{data.float_format}}in"


def _should_skip_child(obj: Artist, child: Artist) -> bool:
"""Return True if this child should be skipped (background patch, spine, invisible, etc.)."""
if (
isinstance(obj, (Figure, Axes))
and isinstance(child, Patch)
and child is obj.patch
and child.get_facecolor() == (1.0, 1.0, 1.0, 1.0)
and child.get_linewidth() == 0.0
) or not child.get_visible():
return True
if isinstance(child, (Spine, XAxis, YAxis)):
return True
return type(child).__name__ == "_WCSAxesArtist"


def _recurse(data: TikzData, obj: Artist) -> list:
"""Iterates over all children of the current object and gathers the contents.

Content is returned.
"""
content = _ContentManager()

for child in obj.get_children():
# Some patches are Spines, too; skip those entirely.
# See <https://github.com/nschloe/tikzplotlib/issues/277>.

# Filter out the Figure's/Axes' background patch
if (
isinstance(obj, (Figure, Axes))
and isinstance(child, Patch)
and child is obj.patch
and child.get_facecolor() == (1.0, 1.0, 1.0, 1.0) # White face color
and child.get_linewidth() == 0.0
) or not child.get_visible():
continue

if isinstance(child, (Spine, XAxis, YAxis)):
continue
if isinstance(obj, Figure):
_set_default_axis_dimensions_from_figure(data, obj)

# Skip WCS axes artist - it's a placeholder with no visible content
if type(child).__name__ == "_WCSAxesArtist":
for child in obj.get_children():
if _should_skip_child(obj, child):
continue

if isinstance(child, Axes):
Expand Down
2 changes: 2 additions & 0 deletions tests/test_annotate_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
\definecolor{darkgray176}{RGB}{176,176,176}

\begin{axis}[
height=5in,
tick align=outside,
tick pos=left,
width=8in,
x grid style={darkgray176},
xmin=-1, xmax=5,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_arrows_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
\definecolor{steelblue31119180}{RGB}{31,119,180}

\begin{axis}[
height=5.6666667in,
tick align=outside,
tick pos=left,
width=5.3333333in,
x grid style={darkgray176},
xmin=0, xmax=8,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_axvline_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
\definecolor{steelblue31119180}{RGB}{31,119,180}

\begin{axis}[
height=4.8in,
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=-1.1365774, xmax=2.1074561,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_barchart_errorbars_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
\definecolor{steelblue31119180}{RGB}{31,119,180}

\begin{axis}[
height=4.8in,
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=-0.5125, xmax=2.5125,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_barchart_legend_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
\definecolor{lightgray204}{RGB}{204,204,204}

\begin{axis}[
height=4.8in,
legend cell align={left},
legend style={fill opacity=0.8, draw opacity=1, text opacity=1, draw=lightgray204},
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=-0.5125, xmax=2.5125,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_barchart_logy_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
\definecolor{steelblue31119180}{RGB}{31,119,180}

\begin{axis}[
height=4.8in,
log basis y={10},
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=0.36, xmax=5.64,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_barchart_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
\definecolor{green01270}{RGB}{0,127,0}

\begin{axis}[
height=4.8in,
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=-0.5125, xmax=2.5125,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_basic_sin_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
\begin{axis}[
axis background/.style={fill=gainsboro229},
axis line style={white},
height=4.8in,
legend cell align={left},
legend style={
fill opacity=0.8,
Expand All @@ -22,6 +23,7 @@
tick align=outside,
tick pos=left,
title={Simple plot \(\displaystyle \frac{\alpha}{2}\)},
width=6.4in,
x grid style={white},
xlabel=\textcolor{dimgray85}{time (s)},
xmajorgrids,
Expand Down
2 changes: 2 additions & 0 deletions tests/test_boxplot_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
\definecolor{darkorange25512714}{RGB}{255,127,14}

\begin{axis}[
height=4.8in,
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=0.5, xmax=3.5,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_context_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
\definecolor{darkgray176}{RGB}{176,176,176}

\startaxis[
height=4.8in,
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=0, xmax=1,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_contourf_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
\definecolor{mediumseagreen68190112}{RGB}{68,190,112}

\begin{axis}[
height=4.8in,
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=0, xmax=2,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_custom_collection_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
\definecolor{steelblue31119180}{RGB}{31,119,180}

\begin{axis}[
height=4.8in,
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=-1.5, xmax=1.5,
xtick style={color=black},
Expand Down
2 changes: 2 additions & 0 deletions tests/test_datetime_paths_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

\begin{axis}[
date coordinates in=x,
height=4.8in,
tick align=outside,
tick pos=left,
width=6.4in,
x grid style={darkgray176},
xmin=2020-01-01 10:48, xmax=2020-01-02 13:12,
xtick style={color=black},
Expand Down
Loading
Loading