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
5 changes: 5 additions & 0 deletions src/matplot2tikz/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ def _set_plot_title(self) -> None:
self.data.current_axis_title = title
if title:
title = _common_texification(title)
if '\n' in title:
title = title.replace('\n',r'\\')
self.data.current_axis_options.add(r"title style={align=center}")
elif '\\' in title:
self.data.current_axis_options.add(r"title style={align=center}")
self.data.current_axis_options.add(f"title={{{title}}}")

def _set_axis_titles(self) -> None:
Expand Down
33 changes: 14 additions & 19 deletions src/matplot2tikz/_cleanfigure.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,30 +543,25 @@ def _move_points_closer(x_lim: np.ndarray, y_lim: np.ndarray, data: np.ndarray)
the inverse transformation to project back into 3D.
"""
# Calculate the extension of the extended box
x_width = x_lim[1] - x_lim[0]
# (x_width not important for clipping, as it is already dealt with elsewhere (maybe by matplotlib when lim() occurs))
#x_width = x_lim[1] - x_lim[0]
y_width = y_lim[1] - y_lim[0]

# 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
large_xlim = x_lim + extended_factor * np.array([-x_width, x_width])
large_ylim = y_lim + extended_factor * np.array([-y_width, y_width])

data_is_in_large_box = _is_in_box(data, large_xlim, large_ylim)
data_is_in_large_box = np.logical_or(data_is_in_large_box, np.any(np.isnan(data), axis=1))
id_replace = np.argwhere(np.logical_not(data_is_in_large_box))

data_insert = np.array([[]])
if not _isempty(id_replace):
msg = (
"There is data outside of the box. Don't know how to handle during cleaning. "
"Please check if x/ylim is to tight"
)
raise NotImplementedError(msg)
data = _insert_data(data, id_replace, data_insert)
if _isempty(id_replace):
return data
raise NotImplementedError
y_min_ext = y_lim[0] - extended_factor * y_width
y_max_ext = y_lim[1] + extended_factor * y_width

# Copy data to avoid modifying original array
clipped_data = np.array(data, copy=True)

# Clip y-values
# We assume data is Nx2: columns [x, y]
clipped_data[:, 1] = np.clip(clipped_data[:, 1], y_min_ext, y_max_ext)

return clipped_data


def _insert_data(
Expand Down
6 changes: 5 additions & 1 deletion src/matplot2tikz/_line2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def draw_line2d(data: TikzData, obj: Line2D) -> list[str]:

if len(xdata) == 0:
return []

primitive_legend = obj.axes.get_legend()

# Get several plot options
addplot_options = _get_line2d_options(data, obj)
Expand All @@ -82,8 +84,10 @@ def draw_line2d(data: TikzData, obj: Line2D) -> list[str]:

content += _table(data, obj)

if legend_text is not None:
if legend_text is not None and primitive_legend is not None:
content.append(f"\\addlegendentry{{{legend_text}}}\n")
elif legend_text is not None and primitive_legend is None:
content.append(f"\\label{{{legend_text + "_plot"}}}\n")

return content

Expand Down
24 changes: 10 additions & 14 deletions src/matplot2tikz/_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,29 +138,21 @@ def _draw_polygon(data: TikzData, obj: Patch, draw_options: list) -> list[str]:


def _draw_rectangle(data: TikzData, obj: Rectangle, draw_options: list) -> list[str]:
"""Return the PGFPlots code for rectangles."""
# Objects with labels are plot objects (from bar charts, etc). Even those without
# labels explicitly set have a label of "_nolegend_". Everything else should be
# skipped because they likely correspong to axis/legend objects which are handled by
# PGFPlots
# Skip the Axes background rectangle, which is always white (0,0)-(1,1)

label = obj.get_label()
if label == "":
return []

# Get actual label, bar charts by default only give rectangles labels of
# "_nolegend_". See <https://stackoverflow.com/q/35881290/353337>.
# Try to resolve a more useful label if it's "_nolegend_"
if isinstance(obj.axes, Axes):
handles, labels = obj.axes.get_legend_handles_labels()
labels_found = [label for h, label in zip(handles, labels) if obj in h.get_children()]
labels_found = [lab for h, lab in zip(handles, labels) if obj in h.get_children()]
if len(labels_found) == 1:
label = labels_found[0]

left_lower_x = obj.get_x()
left_lower_y = obj.get_y()

# If we are dealing with a bar plot, left_lower_y will be 0. This is a problem if the y-scale is
# logarithmic (see https://github.com/ErwindeGelder/matplot2tikz/issues/25)
# To resolve this, the lower y limit will be used as lower_left_y
# Handle log-scale bar plots
if data.current_mpl_axes is not None and data.current_mpl_axes.get_yscale() == "log":
left_lower_y = data.current_mpl_axes.get_ylim()[0]

Expand All @@ -173,7 +165,10 @@ def _draw_rectangle(data: TikzData, obj: Rectangle, draw_options: list) -> list[
f"rectangle (axis cs:{right_upper_x:{ff}},{right_upper_y:{ff}});\n"
]

if label != "_nolegend_" and str(label) not in data.rectangle_legends:
# Add legend info if needed
# content.append(_patch_legend(obj, draw_options, "area legend"))

if label not in ("", "_nolegend_") and str(label) not in data.rectangle_legends:
data.rectangle_legends.add(str(label))
draw_opts = ",".join(draw_options)
content.append(f"\\addlegendimage{{ybar,ybar legend,{draw_opts}}}\n")
Expand All @@ -182,6 +177,7 @@ def _draw_rectangle(data: TikzData, obj: Rectangle, draw_options: list) -> list[
return content



def _draw_ellipse(data: TikzData, obj: Ellipse, draw_options: list) -> list[str]:
"""Return the PGFPlots code for ellipses."""
if isinstance(obj, Circle):
Expand Down
51 changes: 48 additions & 3 deletions src/matplot2tikz/_save.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import tempfile
import warnings
from numpy import isclose, all
from pathlib import Path
from typing import TYPE_CHECKING, TypedDict

Expand All @@ -24,7 +25,7 @@
if TYPE_CHECKING:
from matplotlib.artist import Artist

from . import _axes, _legend, _line2d, _patch, _path, _text
from . import _axes, _legend, _line2d, _patch, _path, _text, _util
from . import _image as img
from . import _quadmesh as qmsh
from .__about__ import __version__
Expand Down Expand Up @@ -385,12 +386,44 @@ def _recurse(data: TikzData, obj: Artist) -> list:
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>.
if isinstance(child, (Spine, XAxis, YAxis)):

# Filter out the Figure's background patch
if (
(isinstance(obj, Figure)
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_edgecolor() is None or all(isclose(child.get_edgecolor(), (0.0, 0.0, 0.0, 0.0)))) # No edge color
and child.get_linewidth() == 0.0)
or not child.get_visible()
):
if hasattr(child, "get_edgecolor") and not (child.get_edgecolor() is None or all(isclose(child.get_edgecolor(), (0.0, 0.0, 0.0, 0.0)))):
print(child.get_edgecolor())
print(child.get_edgecolor() == (0.0, 0.0, 0.0, 0.0)) # No edge color
continue


# Filter out the Axes' background patch

if (
(isinstance(obj, 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_edgecolor() is None or all(isclose(child.get_edgecolor(), (0.0, 0.0, 0.0, 0.0)))) # No edge color
and child.get_linewidth() == 0.0)
or not child.get_visible()
):
continue


if isinstance(child, (Spine, XAxis, YAxis)):
continue

if isinstance(child, Axes):
_process_axes(data, child, content)

Expand Down Expand Up @@ -427,12 +460,24 @@ def _process_axes(data: TikzData, obj: Axes, content: _ContentManager) -> None:
# add extra axis options
if data.extra_axis_parameters:
data.current_axis_options.update(data.extra_axis_parameters)

data.current_mpl_axes = obj

# Run through the child objects, gather the content.
children_content = _recurse(data, obj)

fig = obj.figure
if obj == fig.axes[0]:
for other_ax in fig.axes:
if other_ax == obj:
continue
for child in other_ax.get_children():
legend_text = _util.get_legend_text(child)
if legend_text is not None and hasattr(child, "axes") and child.axes.get_legend() is None:
plot_label = child.get_label() + "_plot"
children_content.append(f"\\addlegendimage{{/pgfplots/refstyle={plot_label}}}\n")
children_content.append(f"\\addlegendentry{{{legend_text}}}\n")

# populate content and add axis environment if desired
if data.add_axis_environment:
content.extend(ax.get_begin_code() + children_content + [ax.get_end_code()], 0)
Expand Down
10 changes: 10 additions & 0 deletions src/matplot2tikz/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,17 @@ def get_legend_text(obj: Line2D | PathCollection) -> str | None:
"""Check if line is in legend."""
if obj.axes is None:
return None

leg = obj.axes.get_legend()

if leg is None:
fig = obj.axes.figure
for ax in fig.axes:
other_leg = ax.get_legend()
if other_leg is not None:
leg = other_leg
break

if leg is None:
return None

Expand Down