From 6b2c2ec68d3451ce4718a9cd3c8aae0e7c4af5be Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Mon, 2 Jan 2023 09:29:07 +0200 Subject: [PATCH 01/26] Add fix for missing attribute _ncol --- src/tikzplotlib/_legend.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tikzplotlib/_legend.py b/src/tikzplotlib/_legend.py index 29d8e635..0e42bd1a 100644 --- a/src/tikzplotlib/_legend.py +++ b/src/tikzplotlib/_legend.py @@ -78,8 +78,11 @@ def draw_legend(data, obj): if alignment: data["current axes"].axis_options.append(f"legend cell align={{{alignment}}}") - if obj._ncol != 1: - data["current axes"].axis_options.append(f"legend columns={obj._ncol}") + try: + if obj._ncol != 1: + data["current axes"].axis_options.append(f"legend columns={obj._ncol}") + except AttributeError: + warnings.warn("Unable to interrogate the number of columns in legend. Using 1 as default.") # Write styles to data if legend_style: From 584153f81c4c0499e8fdc22869f1958947228067 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Mon, 2 Jan 2023 11:32:01 +0200 Subject: [PATCH 02/26] Escape percents in legend and mm's in axis-label --- src/tikzplotlib/_axes.py | 3 ++- src/tikzplotlib/_line2d.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index fa84db2f..62ed1d23 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -1,5 +1,6 @@ import matplotlib as mpl import numpy as np +import re from matplotlib.backends.backend_pgf import ( common_texification as mpl_common_texification, ) @@ -51,7 +52,7 @@ def __init__(self, data, obj): # noqa: C901 xlabel = _common_texification(xlabel) labelcolor = obj.xaxis.label.get_c() - + xlabel = re.sub(r"\smm", r" \\si{\\mm}", xlabel) if labelcolor != "black": data, col, _ = _color.mpl_color2xcolor(data, labelcolor) self.axis_options.append(f"xlabel=\\textcolor{{{col}}}{{{xlabel}}}") diff --git a/src/tikzplotlib/_line2d.py b/src/tikzplotlib/_line2d.py index b431b869..c3ff65f3 100644 --- a/src/tikzplotlib/_line2d.py +++ b/src/tikzplotlib/_line2d.py @@ -1,6 +1,7 @@ import datetime import numpy as np +import re from matplotlib.dates import num2date from . import _color as mycol @@ -100,7 +101,8 @@ def draw_line2d(data, obj): content += c if legend_text is not None: - content.append(f"\\addlegendentry{{{legend_text}}}\n") + legend_text_escaped = re.sub(r"(\d+(\.\d+)?)%", r"\\SI{\1}{\\percent}", legend_text) + content.append(f"\\addlegendentry{{{legend_text_escaped}}}\n") return data, content From 8b116165098b97e985ecbee8c88c08b3692455f4 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Mon, 2 Jan 2023 15:43:52 +0200 Subject: [PATCH 03/26] Show the axis name in math mode --- src/tikzplotlib/_axes.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index 62ed1d23..cf6b6c00 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -53,6 +53,11 @@ def __init__(self, data, obj): # noqa: C901 labelcolor = obj.xaxis.label.get_c() xlabel = re.sub(r"\smm", r" \\si{\\mm}", xlabel) + xlabel_spl = xlabel.split(",") + if len(xlabel_spl) == 2: + xlabel = ",".join(["$" + xlabel_spl[0].replace(" ", "\\ ") + "$", + xlabel_spl[1]]) + if labelcolor != "black": data, col, _ = _color.mpl_color2xcolor(data, labelcolor) self.axis_options.append(f"xlabel=\\textcolor{{{col}}}{{{xlabel}}}") @@ -67,6 +72,11 @@ def __init__(self, data, obj): # noqa: C901 if ylabel: ylabel = _common_texification(ylabel) + ylabel_spl = ylabel.split(",") + if len(ylabel_spl) == 2: + ylabel = ",".join(["$" + ylabel_spl[0].replace(" ", "\\ ") + "$", + ylabel_spl[1]]) + labelcolor = obj.yaxis.label.get_c() if labelcolor != "black": data, col, _ = _color.mpl_color2xcolor(data, labelcolor) From 60a3668703393090c066c4e7f0f58122ce02a0a0 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Mon, 2 Jan 2023 15:44:23 +0200 Subject: [PATCH 04/26] Put the +- in legend into the SI brackets --- src/tikzplotlib/_line2d.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tikzplotlib/_line2d.py b/src/tikzplotlib/_line2d.py index c3ff65f3..450c0f9a 100644 --- a/src/tikzplotlib/_line2d.py +++ b/src/tikzplotlib/_line2d.py @@ -101,7 +101,9 @@ def draw_line2d(data, obj): content += c if legend_text is not None: - legend_text_escaped = re.sub(r"(\d+(\.\d+)?)%", r"\\SI{\1}{\\percent}", legend_text) + legend_text_escaped = re.sub(r"(\d+(\.\d+)?)%", + r"\\SI{\1}{\\percent}", + legend_text).replace("+-\\SI{", "\\SI{+-") content.append(f"\\addlegendentry{{{legend_text_escaped}}}\n") return data, content From 81507b70c9e03a7cf4167d931616e696884375ad Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 10 Jan 2023 10:31:29 +0200 Subject: [PATCH 05/26] Improve the texification of axis label This uses the siunitx and some of the common possible measurement units. --- src/tikzplotlib/_axes.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index cf6b6c00..fd3d6014 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -12,6 +12,16 @@ def _common_texification(string): # Work around return mpl_common_texification(string).replace("&", "\\&") +def _siunitx_texification(string: str) -> str: + string = re.sub(r"\smm", r" \\si{\\mm}", string) + string = re.sub(r"\s°C", r" \\si{\\celsius}", string) + string = re.sub(r"\sA/s", r" \\si{\\angstrom\\per\\second}", string) + string = re.sub(r"\sg/s", r" \\si{\\gram\\per\\second}", string) + string = re.sub(r"\shour", r" \\si{\\hour}", string) + string = re.sub(r"\scc", r" \\si{\\cc}", string) + string = re.sub(r"\s\\%", r" \\si{\\percent}", string) + return string + class Axes: def __init__(self, data, obj): # noqa: C901 @@ -50,9 +60,9 @@ def __init__(self, data, obj): # noqa: C901 xlabel = obj.get_xlabel() if xlabel: xlabel = _common_texification(xlabel) + xlabel = _siunitx_texification(xlabel) labelcolor = obj.xaxis.label.get_c() - xlabel = re.sub(r"\smm", r" \\si{\\mm}", xlabel) xlabel_spl = xlabel.split(",") if len(xlabel_spl) == 2: xlabel = ",".join(["$" + xlabel_spl[0].replace(" ", "\\ ") + "$", @@ -71,10 +81,13 @@ def __init__(self, data, obj): # noqa: C901 ylabel = obj.get_ylabel() if ylabel: ylabel = _common_texification(ylabel) + ylabel = _siunitx_texification(ylabel) ylabel_spl = ylabel.split(",") if len(ylabel_spl) == 2: - ylabel = ",".join(["$" + ylabel_spl[0].replace(" ", "\\ ") + "$", + ylabel = ",".join(["$" + ylabel_spl[0].replace(" ", + "\\ ").replace("+-", r"\pm").replace("-", + r"\mhyphen ") + "$", ylabel_spl[1]]) labelcolor = obj.yaxis.label.get_c() From 5ffe6edf8fb54ef712bc6e0176594594880c5bee Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 10 Jan 2023 10:32:11 +0200 Subject: [PATCH 06/26] Make the minor ticks and minor grid work --- src/tikzplotlib/_axes.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index fd3d6014..eead5167 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -1,4 +1,5 @@ import matplotlib as mpl +import math import numpy as np import re from matplotlib.backends.backend_pgf import ( @@ -625,10 +626,16 @@ def _get_ticks(data, xy, ticks, ticklabels): pgfplots_ticks.append(tick) # if the labels are all missing, then we need to output an empty set of labels - if len(ticklabels) == 0 and len(ticks) != 0: + data[f"nticks_{xy}"] = len(ticks) + if len(ticklabels) == 0 and len(ticks) != 0 and "minor" not in xy: axis_options.append(f"{xy}ticklabels={{}}") # remove the multiplier too - axis_options.append(f"scaled {xy} ticks=" + r"manual:{}{\pgfmathparse{#1}}") + elif "minor" in xy and len(ticks) != 0: + xy_ = xy.split()[1] + if data[f"nticks_{xy_}"] != 0: + multiplier = 5 * math.ceil(len(ticks)/data[f"nticks_{xy_}"]/5) + axis_options.append(f"minor {xy_} tick num={multiplier}") + axis_options.append(f"% {data[f'nticks_{xy_}']}; {len(ticks)}") # Leave the ticks to PGFPlots if not in STRICT mode and if there are no explicit # labels. @@ -640,9 +647,8 @@ def _get_ticks(data, xy, ticks, ticklabels): xy, ",".join([f"{el:{ff}}" for el in pgfplots_ticks]) ) ) - else: - val = "{}" if "minor" in xy else "\\empty" - axis_options.append(f"{xy}tick={val}") + elif "minor" not in xy: + axis_options.append(f"{xy}tick=\\empty") if is_label_required: length = sum(len(label) for label in pgfplots_ticklabels) From 5d81febf133264b3e057ee429f0485bf70a10465 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 10 Jan 2023 13:07:52 +0200 Subject: [PATCH 07/26] Set color of the minor grid --- src/tikzplotlib/_axes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index eead5167..b02951c2 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -354,6 +354,8 @@ def _grid(self, obj, data): self.axis_options.append("xmajorgrids") if has_minor_xgrid: self.axis_options.append("xminorgrids") + # No way to check from the axis the actual style of the minor grid + self.axis_options.append("minor x grid style={gray!20}") xlines = obj.get_xgridlines() if xlines: @@ -366,6 +368,8 @@ def _grid(self, obj, data): self.axis_options.append("ymajorgrids") if has_minor_ygrid: self.axis_options.append("yminorgrids") + # No way to check from the axis the actual style of the minor grid + self.axis_options.append("minor y grid style={gray!20}") ylines = obj.get_ygridlines() if ylines: From 6246102ce0cf7cae27aa3f48fbe36b0a805f3b5e Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Fri, 20 Jan 2023 16:00:55 +0200 Subject: [PATCH 08/26] Add the g/um as a supported Si unit --- src/tikzplotlib/_axes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index b02951c2..7c5e1a86 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -21,6 +21,7 @@ def _siunitx_texification(string: str) -> str: string = re.sub(r"\shour", r" \\si{\\hour}", string) string = re.sub(r"\scc", r" \\si{\\cc}", string) string = re.sub(r"\s\\%", r" \\si{\\percent}", string) + string = re.sub(r"\sg/um", r" \\si{\\g\\per\\um}", string) return string From a781c3e0f8bf706fbdf32ba1a4c608f56edcaa09 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 7 Feb 2023 12:05:05 +0200 Subject: [PATCH 09/26] Add Angstrom as a supported Si unit --- src/tikzplotlib/_axes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index 7c5e1a86..7a32d8b9 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -17,6 +17,7 @@ def _siunitx_texification(string: str) -> str: string = re.sub(r"\smm", r" \\si{\\mm}", string) string = re.sub(r"\s°C", r" \\si{\\celsius}", string) string = re.sub(r"\sA/s", r" \\si{\\angstrom\\per\\second}", string) + string = re.sub(r"\sAngstrom", r" \\si{\\angstrom}", string) string = re.sub(r"\sg/s", r" \\si{\\gram\\per\\second}", string) string = re.sub(r"\shour", r" \\si{\\hour}", string) string = re.sub(r"\scc", r" \\si{\\cc}", string) From 325f4d37dd7a5f284c07291d7ec89aa9532ec35c Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 7 Feb 2023 12:05:19 +0200 Subject: [PATCH 10/26] Parse ", s" in the legend as a second --- src/tikzplotlib/_line2d.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tikzplotlib/_line2d.py b/src/tikzplotlib/_line2d.py index 450c0f9a..d17782ba 100644 --- a/src/tikzplotlib/_line2d.py +++ b/src/tikzplotlib/_line2d.py @@ -103,7 +103,9 @@ def draw_line2d(data, obj): if legend_text is not None: legend_text_escaped = re.sub(r"(\d+(\.\d+)?)%", r"\\SI{\1}{\\percent}", - legend_text).replace("+-\\SI{", "\\SI{+-") + legend_text).replace("+-\\SI{", + "\\SI{+-").replace(", s", + ", \\SI{\s}") content.append(f"\\addlegendentry{{{legend_text_escaped}}}\n") return data, content From 19e76f898600d285035e3ee09188c0f7e0e806c2 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 7 Feb 2023 12:05:44 +0200 Subject: [PATCH 11/26] Fix the draw-none problem in the paths --- src/tikzplotlib/_path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tikzplotlib/_path.py b/src/tikzplotlib/_path.py index 11381d59..874be880 100644 --- a/src/tikzplotlib/_path.py +++ b/src/tikzplotlib/_path.py @@ -231,7 +231,7 @@ def draw_pathcollection(data, obj): is_contour = len(dd) == 1 if is_contour: - draw_options = ["draw=none"] + draw_options = ["thick"] if marker0 is not None: data, pgfplots_marker, marker_options = _mpl_marker2pgfp_marker( From a4c362153c181e06c613aea603b63657c784dbb8 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 7 Feb 2023 12:05:59 +0200 Subject: [PATCH 12/26] Allow only drawing every N'th dot in a path This makes the output file much smaller and thus much easier for latex to compile. In a lot of cases, apparently, all the dots are not needed to produce smooth plots. --- src/tikzplotlib/_path.py | 2 +- src/tikzplotlib/_save.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tikzplotlib/_path.py b/src/tikzplotlib/_path.py index 874be880..9cf828a3 100644 --- a/src/tikzplotlib/_path.py +++ b/src/tikzplotlib/_path.py @@ -303,7 +303,7 @@ def draw_pathcollection(data, obj): plot_table = [] plot_table.append(" ".join(labels) + "\n") - for row in dd_strings: + for row in dd_strings[::data["every n dot"]]: plot_table.append(" ".join(row) + "\n") if data["externalize tables"]: diff --git a/src/tikzplotlib/_save.py b/src/tikzplotlib/_save.py index d89cadfa..d29ddbe7 100644 --- a/src/tikzplotlib/_save.py +++ b/src/tikzplotlib/_save.py @@ -40,6 +40,7 @@ def get_tikz_code( float_format: str = ".15g", table_row_sep: str = "\n", flavor: str = "latex", + every_n_dot: int = 1, ): """Main function. Here, the recursion into the image starts and the contents are picked up. The actual file gets written in this routine. @@ -136,6 +137,9 @@ def get_tikz_code( Default is ``"latex"``. :type flavor: str + :param every_n_dot: if path is encountered, only draw every Nth dot + :type every_n_dot: int + :returns: None The following optional attributes of matplotlib's objects are recognized @@ -176,6 +180,7 @@ def get_tikz_code( data["legend colors"] = [] data["add axis environment"] = add_axis_environment data["show_info"] = show_info + data["every n dot"] = every_n_dot # rectangle_legends is used to keep track of which rectangles have already # had \addlegendimage added. There should be only one \addlegenimage per # bar chart data series. From 79db7a80c6b70fb72ccd513a45d40a27e0294df2 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Mon, 20 Mar 2023 13:06:02 +0200 Subject: [PATCH 13/26] Allow to have space before space sign --- src/tikzplotlib/_line2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tikzplotlib/_line2d.py b/src/tikzplotlib/_line2d.py index d17782ba..eb138231 100644 --- a/src/tikzplotlib/_line2d.py +++ b/src/tikzplotlib/_line2d.py @@ -101,7 +101,7 @@ def draw_line2d(data, obj): content += c if legend_text is not None: - legend_text_escaped = re.sub(r"(\d+(\.\d+)?)%", + legend_text_escaped = re.sub(r"(\d+(\.\d+)?)\s?%", r"\\SI{\1}{\\percent}", legend_text).replace("+-\\SI{", "\\SI{+-").replace(", s", From 5bb60d23e291a85ebed3177c4a868603eec051a2 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Mon, 20 Mar 2023 13:06:19 +0200 Subject: [PATCH 14/26] Ignore the error when doing dash line --- src/tikzplotlib/_path.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tikzplotlib/_path.py b/src/tikzplotlib/_path.py index 9cf828a3..1d017d23 100644 --- a/src/tikzplotlib/_path.py +++ b/src/tikzplotlib/_path.py @@ -468,9 +468,13 @@ def mpl_linestyle2pgfplots_linestyle(data, line_style, line=None): # get defaults default_dashOffset, default_dashSeq = mpl.lines._get_dash_pattern(line_style) - # get dash format of line under test - dashSeq = line._us_dashSeq - dashOffset = line._us_dashOffset + try: + # get dash format of line under test + dashSeq = line._us_dashSeq + dashOffset = line._us_dashOffset + except AttributeError: + dashSeq = default_dashSeq + dashOffset = default_dashOffset lst = list() if dashSeq != default_dashSeq: From 2077b72eefe3effec3b5b992dc0d9f97ba57d7f8 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 21 Mar 2023 11:18:59 +0200 Subject: [PATCH 15/26] Add the "axis equal" parameter --- src/tikzplotlib/_axes.py | 2 ++ src/tikzplotlib/_save.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index 7a32d8b9..0cdaeb7f 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -239,6 +239,8 @@ def _set_axis_dimensions(self, data, aspect_num, xlim, ylim): else: # TODO keep an eye on https://tex.stackexchange.com/q/480058/13262 pass + if data["axis_equal"]: + self.axis_options.append("axis equal") def _ticks(self, data, obj): # get ticks diff --git a/src/tikzplotlib/_save.py b/src/tikzplotlib/_save.py index d29ddbe7..3e76b00f 100644 --- a/src/tikzplotlib/_save.py +++ b/src/tikzplotlib/_save.py @@ -41,6 +41,7 @@ def get_tikz_code( table_row_sep: str = "\n", flavor: str = "latex", every_n_dot: int = 1, + axis_equal: bool = False, ): """Main function. Here, the recursion into the image starts and the contents are picked up. The actual file gets written in this routine. @@ -140,6 +141,9 @@ def get_tikz_code( :param every_n_dot: if path is encountered, only draw every Nth dot :type every_n_dot: int + :param axis_equal: if true, have equal axis ratio + :type axis_equal: bool + :returns: None The following optional attributes of matplotlib's objects are recognized @@ -181,6 +185,7 @@ def get_tikz_code( data["add axis environment"] = add_axis_environment data["show_info"] = show_info data["every n dot"] = every_n_dot + data["axis_equal"] = axis_equal # rectangle_legends is used to keep track of which rectangles have already # had \addlegendimage added. There should be only one \addlegenimage per # bar chart data series. From 6e40267f9aa9d333edf8c20bb6bbfdfbe8042466 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 11 Apr 2023 08:52:46 +0300 Subject: [PATCH 16/26] Use the every-n-dot parameter for 2d lines as well --- src/tikzplotlib/_line2d.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tikzplotlib/_line2d.py b/src/tikzplotlib/_line2d.py index eb138231..dcbd4c5a 100644 --- a/src/tikzplotlib/_line2d.py +++ b/src/tikzplotlib/_line2d.py @@ -279,7 +279,8 @@ def _table(obj, data): # noqa: C901 data["current axes"].axis_options.append("unbounded coords=jump") plot_table = [ - f"{x:{xformat}}{col_sep}{y:{ff}}{table_row_sep}" for x, y in zip(xdata, ydata) + f"{x:{xformat}}{col_sep}{y:{ff}}{table_row_sep}" for x, y in zip( + xdata[::data["every n dot"]], ydata[::data["every n dot"]]) ] min_extern_length = 3 From d0e91fcd25edadce6ad0338a3cf809726373003a Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 8 Aug 2023 11:49:47 +0300 Subject: [PATCH 17/26] Move function "escape_text" to _text.py --- src/tikzplotlib/_line2d.py | 9 ++------- src/tikzplotlib/_text.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/tikzplotlib/_line2d.py b/src/tikzplotlib/_line2d.py index dcbd4c5a..4dfc8a87 100644 --- a/src/tikzplotlib/_line2d.py +++ b/src/tikzplotlib/_line2d.py @@ -1,13 +1,13 @@ import datetime import numpy as np -import re from matplotlib.dates import num2date from . import _color as mycol from . import _files from . import _path as mypath from ._markers import _mpl_marker2pgfp_marker +from ._text import escape_text from ._util import get_legend_text, has_legend, transform_to_data_coordinates @@ -101,12 +101,7 @@ def draw_line2d(data, obj): content += c if legend_text is not None: - legend_text_escaped = re.sub(r"(\d+(\.\d+)?)\s?%", - r"\\SI{\1}{\\percent}", - legend_text).replace("+-\\SI{", - "\\SI{+-").replace(", s", - ", \\SI{\s}") - content.append(f"\\addlegendentry{{{legend_text_escaped}}}\n") + content.append(f"\\addlegendentry{{{escape_text(legend_text)}}}\n") return data, content diff --git a/src/tikzplotlib/_text.py b/src/tikzplotlib/_text.py index 34975d3b..373f7a3e 100644 --- a/src/tikzplotlib/_text.py +++ b/src/tikzplotlib/_text.py @@ -1,8 +1,44 @@ import matplotlib as mpl +import re from matplotlib.patches import ArrowStyle from . import _color +def escape_text(text): + """ + Escapes certain patterns in a given text to make them compatible with + LaTeX formatting, especially for SI units. + + Parameters: + - text (str): The input string that needs to be processed for LaTeX- + compatible escapes. + + Returns: + - str: The text with escaped patterns suitable for LaTeX rendering. + + The function primarily performs the following conversions: + 1. Converts percentages, e.g., "45%", "45.5 %", to the LaTeX SI + unit format: "\\SI{45}{\\percent}". + 2. Fixes potential issues with the "\\SI" unit for the plus-minus + notation. + 3. Corrects ", s" patterns to ", \\SI{\\s}". + + Note: + This function assumes that the input text is likely to contain numeric + values that need to be presented using SI notation in LaTeX. For + correct rendering, the siunitx LaTeX package should be included in + the document. + + Example: + Given the input: "The efficiency is 45% and the tolerance is + +-\\SI{2}{\\degree}, s" + The output will be: "The efficiency is \\SI{45}{\\percent} and + the tolerance is \\SI{+-2}{\\degree}, \\SI{\s}" + """ + return re.sub(r"(\d+(\.\d+)?)\s?%", r"\\SI{\1}{\\percent}", + text).replace("+-\\SI{", "\\SI{+-").replace(", s", + ", \\SI{\\s}") + def draw_text(data, obj): """Paints text on the graph.""" From 0ba53746f8d4be56d82c575e05f23fffda2c580c Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 8 Aug 2023 11:49:58 +0300 Subject: [PATCH 18/26] Escape annotations in plots --- src/tikzplotlib/_text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tikzplotlib/_text.py b/src/tikzplotlib/_text.py index 373f7a3e..67450f28 100644 --- a/src/tikzplotlib/_text.py +++ b/src/tikzplotlib/_text.py @@ -151,7 +151,7 @@ def draw_text(data, obj): text = text.replace("\n ", "\\\\") props = ",\n ".join(properties) - text = " ".join(style + [text]) + text = escape_text(" ".join(style + [text])).replace("\n", "\\\\") content.append(f"\\draw {tikz_pos} node[\n {props}\n]{{{text}}};\n") return data, content From 0debea7879f04b8dccfb4a6237d155a76bcd8b15 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 8 Aug 2023 11:50:09 +0300 Subject: [PATCH 19/26] Use truly relative axis position when needed --- src/tikzplotlib/_text.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tikzplotlib/_text.py b/src/tikzplotlib/_text.py index 67450f28..8327078b 100644 --- a/src/tikzplotlib/_text.py +++ b/src/tikzplotlib/_text.py @@ -59,7 +59,8 @@ def draw_text(data, obj): if obj.axes: # If the coordinates are relative to an axis, use `axis cs`. - tikz_pos = f"(axis cs:{pos[0]:{ff}},{pos[1]:{ff}})" + rel = "rel " if abs(pos[0]) < 1 and abs(pos[1]) < 1 else "" + tikz_pos = f"({rel}axis cs:{pos[0]:{ff}},{pos[1]:{ff}})" else: # relative to the entire figure, it's a getting a littler harder. See # for a solution to the From f5ea42f876b2b9c75619ea04a960975255eb755d Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Thu, 10 Aug 2023 12:56:59 +0300 Subject: [PATCH 20/26] Use 'every-n-dot' only in larger datasets --- src/tikzplotlib/_line2d.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/tikzplotlib/_line2d.py b/src/tikzplotlib/_line2d.py index 4dfc8a87..72ea0e6e 100644 --- a/src/tikzplotlib/_line2d.py +++ b/src/tikzplotlib/_line2d.py @@ -273,10 +273,15 @@ def _table(obj, data): # noqa: C901 if "unbounded coords=jump" not in data["current axes"].axis_options: data["current axes"].axis_options.append("unbounded coords=jump") - plot_table = [ - f"{x:{xformat}}{col_sep}{y:{ff}}{table_row_sep}" for x, y in zip( - xdata[::data["every n dot"]], ydata[::data["every n dot"]]) - ] + if len(xdata) > data["every n dot"]: + plot_table = [ + f"{x:{xformat}}{col_sep}{y:{ff}}{table_row_sep}" for x, y in zip( + xdata[::data["every n dot"]], + ydata[::data["every n dot"]])] + else: + plot_table = [ + f"{x:{xformat}}{col_sep}{y:{ff}}{table_row_sep}" for x, y in zip(xdata, ydata)] + min_extern_length = 3 From 60be73cdb4cdd907b262281947fa6e7e72d4e387 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Thu, 10 Aug 2023 12:57:11 +0300 Subject: [PATCH 21/26] Escape "mm" and "deg" to SI --- src/tikzplotlib/_text.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tikzplotlib/_text.py b/src/tikzplotlib/_text.py index 8327078b..2be3e974 100644 --- a/src/tikzplotlib/_text.py +++ b/src/tikzplotlib/_text.py @@ -35,9 +35,10 @@ def escape_text(text): The output will be: "The efficiency is \\SI{45}{\\percent} and the tolerance is \\SI{+-2}{\\degree}, \\SI{\s}" """ - return re.sub(r"(\d+(\.\d+)?)\s?%", r"\\SI{\1}{\\percent}", - text).replace("+-\\SI{", "\\SI{+-").replace(", s", - ", \\SI{\\s}") + res_text = re.sub(r"(\d+(\.\d+)?)\s?%", r"\\SI{\1}{\\percent}", text) + res_text = re.sub(r"(\d+(\.\d+)?)\s?mm", r"\\SI{\1}{\\mm}", res_text) + res_text = re.sub(r"(\d+(\.\d+)?)\s?deg", r"\\SI{\1}{\\degree}", res_text) + return res_text.replace("+-\\SI{", "\\SI{+-").replace(", s", ", \\SI{\\s}") def draw_text(data, obj): From eaa46c8e6e94228992ba4ac2cebffbd16b647cc2 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Thu, 17 Aug 2023 12:24:41 +0300 Subject: [PATCH 22/26] Replace number+cc to the correct SI command --- src/tikzplotlib/_axes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index 0cdaeb7f..55ea185d 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -20,6 +20,7 @@ def _siunitx_texification(string: str) -> str: string = re.sub(r"\sAngstrom", r" \\si{\\angstrom}", string) string = re.sub(r"\sg/s", r" \\si{\\gram\\per\\second}", string) string = re.sub(r"\shour", r" \\si{\\hour}", string) + string = re.sub(r"(\d+(\.\d+)?)\s?cc", r"\\SI{\1}{\\cc}", string) string = re.sub(r"\scc", r" \\si{\\cc}", string) string = re.sub(r"\s\\%", r" \\si{\\percent}", string) string = re.sub(r"\sg/um", r" \\si{\\g\\per\\um}", string) From 4f21b097059978a5064192bdf2e31d5e96ad7f9a Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Thu, 17 Aug 2023 12:25:29 +0300 Subject: [PATCH 23/26] Use siunitx-texify for the axis title --- src/tikzplotlib/_axes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index 55ea185d..d36ef586 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -58,6 +58,7 @@ def __init__(self, data, obj): # noqa: C901 data["current axis title"] = title if title: title = _common_texification(title) + title = _siunitx_texification(title) self.axis_options.append(f"title={{{title}}}") # get axes titles From 7ba04ed08155637be53d58f762b74f87e6f85890 Mon Sep 17 00:00:00 2001 From: Gorshanov Vadim Date: Tue, 12 Sep 2023 09:54:00 +0300 Subject: [PATCH 24/26] Replace deg with siunitx in siunitxtexification --- src/tikzplotlib/_axes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index d36ef586..9696cd4d 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -24,6 +24,7 @@ def _siunitx_texification(string: str) -> str: string = re.sub(r"\scc", r" \\si{\\cc}", string) string = re.sub(r"\s\\%", r" \\si{\\percent}", string) string = re.sub(r"\sg/um", r" \\si{\\g\\per\\um}", string) + string = re.sub(r"(\d+(\.\d+)?)\s?deg", r"\\SI{\1}{\\degree}", string) return string From f033122bc190be27ad212adfca9ca5bfb6a6ac3a Mon Sep 17 00:00:00 2001 From: Kshitij Goel Date: Wed, 29 Nov 2023 18:34:15 -0500 Subject: [PATCH 25/26] setup.py to work with colcon --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..7a9a7563 --- /dev/null +++ b/setup.py @@ -0,0 +1,7 @@ +from setuptools import setup + +setup( + name='tikzplotlib', + packages=['tikzplotlib'], + package_dir={'':'src'} +) \ No newline at end of file From 860e4a5402b8f0041dabe87a1abaa01220cfb93a Mon Sep 17 00:00:00 2001 From: lucaslrodri Date: Thu, 7 Mar 2024 20:30:59 -0300 Subject: [PATCH 26/26] replacing deprecated mpl function common_texification with _tex_escape --- src/tikzplotlib/_axes.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/tikzplotlib/_axes.py b/src/tikzplotlib/_axes.py index 9696cd4d..b662b77a 100644 --- a/src/tikzplotlib/_axes.py +++ b/src/tikzplotlib/_axes.py @@ -2,16 +2,14 @@ import math import numpy as np import re -from matplotlib.backends.backend_pgf import ( - common_texification as mpl_common_texification, -) +from matplotlib.backends.backend_pgf import _tex_escape as mpl_tex_escape from . import _color -def _common_texification(string): +def _tex_escape(string): # Work around - return mpl_common_texification(string).replace("&", "\\&") + return mpl_tex_escape(string).replace("&", "\\&") def _siunitx_texification(string: str) -> str: string = re.sub(r"\smm", r" \\si{\\mm}", string) @@ -58,14 +56,14 @@ def __init__(self, data, obj): # noqa: C901 title = obj.get_title() data["current axis title"] = title if title: - title = _common_texification(title) + title = _tex_escape(title) title = _siunitx_texification(title) self.axis_options.append(f"title={{{title}}}") # get axes titles xlabel = obj.get_xlabel() if xlabel: - xlabel = _common_texification(xlabel) + xlabel = _tex_escape(xlabel) xlabel = _siunitx_texification(xlabel) labelcolor = obj.xaxis.label.get_c() @@ -86,7 +84,7 @@ def __init__(self, data, obj): # noqa: C901 ylabel = obj.get_ylabel() if ylabel: - ylabel = _common_texification(ylabel) + ylabel = _tex_escape(ylabel) ylabel = _siunitx_texification(ylabel) ylabel_spl = ylabel.split(",") @@ -630,7 +628,7 @@ def _get_ticks(data, xy, ticks, ticklabels): label = ticklabel.get_text() if "," in label: label = "{" + label + "}" - pgfplots_ticklabels.append(_common_texification(label)) + pgfplots_ticklabels.append(_tex_escape(label)) # note: ticks may be present even if labels are not, keep them for grid lines for tick in ticks: