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
60 changes: 30 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
<div align="right">
<details>
<summary >🌐 Language</summary>
<div>
<div align="center">
<a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=en">English</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=zh-CN">简体中文</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=zh-TW">繁體中文</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=ja">日本語</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=ko">한국어</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=hi">हिन्दी</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=th">ไทย</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=fr">Français</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=de">Deutsch</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=es">Español</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=it">Italiano</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=ru">Русский</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=pt">Português</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=nl">Nederlands</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=pl">Polski</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=ar">العربية</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=fa">فارسی</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=tr">Türkçe</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=vi">Tiếng Việt</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=id">Bahasa Indonesia</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=as">অসমীয়া</
</div>
</div>
</details>

<div align="right">
<details>
<summary >🌐 Language</summary>
<div>
<div align="center">
<a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=en">English</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=zh-CN">简体中文</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=zh-TW">繁體中文</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=ja">日本語</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=ko">한국어</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=hi">हिन्दी</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=th">ไทย</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=fr">Français</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=de">Deutsch</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=es">Español</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=it">Italiano</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=ru">Русский</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=pt">Português</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=nl">Nederlands</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=pl">Polski</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=ar">العربية</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=fa">فارسی</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=tr">Türkçe</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=vi">Tiếng Việt</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=id">Bahasa Indonesia</a>
| <a href="https://openaitx.github.io/view.html?user=ErwindeGelder&project=matplot2tikz&lang=as">অসমীয়া</
</div>
</div>
</details>
</div>

# matplot2tikz
Expand Down
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
38 changes: 16 additions & 22 deletions src/matplot2tikz/_cleanfigure.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def _cleanline(
cfd.visual_data = _get_visual_data(cfd.axes, cfd.data)

if not isinstance(linehandle, art3d.Line3D):
cfd.visual_data = _move_points_closer(cfd.x_lim, cfd.y_lim, cfd.visual_data)
cfd.visual_data = _move_points_closer(cfd.y_lim, cfd.visual_data)

cfd.has_markers = linehandle.get_marker() != "None"
cfd.has_lines = linehandle.get_linestyle() != "None"
Expand Down Expand Up @@ -244,7 +244,7 @@ def _clean_collections(
cfd.visual_data = _get_visual_data(cfd.axes, cfd.data)

if not isinstance(collection, art3d.Path3DCollection):
cfd.visual_data = _move_points_closer(cfd.x_lim, cfd.y_lim, cfd.visual_data)
cfd.visual_data = _move_points_closer(cfd.y_lim, cfd.visual_data)
cfd.visual_data = _get_visual_data(cfd.axes, cfd.visual_data)

cfd.has_markers = True
Expand Down Expand Up @@ -530,43 +530,37 @@ def _prune_outside_box(cfd: CleanFigureData) -> np.ndarray:
return _remove_nans(data)


def _move_points_closer(x_lim: np.ndarray, y_lim: np.ndarray, data: np.ndarray) -> np.ndarray:
def _move_points_closer(y_lim: np.ndarray, data: np.ndarray) -> np.ndarray:
"""Move points closer if needed.

Move all points outside a box much larger than the visible one
to the boundary of that box and make sure that lines in the visible
box are preserved. This typically involves replacing one point by
two new ones and a NaN.

Only y-coordinates considered as x-coordinates are already dealt with
elsewhere (but where?)

Not implemented: 3D simplification of frontal 2D projection. This requires the
full transformation rather than the projection, as we have to calculate
the inverse transformation to project back into 3D.
"""
# Calculate the extension of the extended box
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 @@ -69,6 +69,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)
# Check if a line is in a legend and forget it if not.
Expand All @@ -85,8 +87,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: 6 additions & 18 deletions src/matplot2tikz/_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,9 @@ 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
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 = [
Expand All @@ -162,9 +154,7 @@ def _draw_rectangle(data: TikzData, obj: Rectangle, draw_options: list) -> list[
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 @@ -177,7 +167,7 @@ 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:
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 @@ -198,13 +188,11 @@ def _draw_ellipse(data: TikzData, obj: Ellipse, draw_options: list) -> list[str]
draw_options.append(f"rotate around={{{obj.angle:{ff}}:(axis cs:{x:{ff}},{y:{ff}})}}")

do = ",".join(draw_options)
content = [
return [
f"\\draw[{do}] (axis cs:{x:{ff}},{y:{ff}}) ellipse "
f"({0.5 * obj.width:{ff}} and {0.5 * obj.height:{ff}});\n"
f"({0.5 * obj.width:{ff}} and {0.5 * obj.height:{ff}});\n",
_patch_legend(obj, draw_options, "area legend"),
]
content.append(_patch_legend(obj, draw_options, "area legend"))

return content


def _draw_circle(data: TikzData, obj: Circle, draw_options: list) -> list[str]:
Expand Down
41 changes: 40 additions & 1 deletion src/matplot2tikz/_save.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,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,7 +385,28 @@ def _recurse(data: TikzData, obj: Artist) -> list:
Content is returned.
"""
content = _ContentManager()

for child in obj.get_children():
# 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_linewidth() == 0.0
) or not child.get_visible():
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_linewidth() == 0.0
) or not child.get_visible():
continue

# Some patches are Spines, too; skip those entirely.
# See <https://github.com/nschloe/tikzplotlib/issues/277>.
if isinstance(child, (Spine, XAxis, YAxis)):
Expand Down Expand Up @@ -433,6 +454,24 @@ def _process_axes(data: TikzData, obj: Axes, content: _ContentManager) -> None:
# 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
Loading