From db84315b858a7ea148def528e75acad02eb70b1d Mon Sep 17 00:00:00 2001 From: Thomas Saigre Date: Wed, 10 Jun 2026 09:11:36 +0200 Subject: [PATCH 1/7] [feat] up first implementation of decimate option (not tested yet) #40 --- src/tikzplotly/_data_container.py | 57 +++++++++++++++++++++++++------ src/tikzplotly/_save.py | 5 +-- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/tikzplotly/_data_container.py b/src/tikzplotly/_data_container.py index 4db4f2f..88614b1 100644 --- a/src/tikzplotly/_data_container.py +++ b/src/tikzplotly/_data_container.py @@ -46,7 +46,7 @@ class Data: """Class to handle data in TikZ plots. """ - def __init__(self, name, x): + def __init__(self, name, x, decimate: int|None=None): """Initialize a Data object. Parameters @@ -59,6 +59,8 @@ def __init__(self, name, x): self.name = name self.macro_name = "\\" + replace_all_digits(name) self.x = x + # decimate: None means keep all points, otherwise integer >= 1 + self.decimate = decimate self.y_label = [] self.y_data = [] @@ -84,7 +86,7 @@ def add_y_data(self, y, y_label=None): class Data3D: """Handle 3D data in Tikz plots """ - def __init__(self, x, y, z, name): + def __init__(self, x, y, z, name, decimate: int|None=None): """Initialize the Data3D object Parameters @@ -101,6 +103,8 @@ def __init__(self, x, y, z, name): self.x = np.array(x) self.y = np.array(y) self.z = np.array(z) + # decimate: None means keep all points, otherwise integer >= 1 + self.decimate = decimate if name: self.name = sanitize_text(name, keep_space=0) else: @@ -128,7 +132,7 @@ class DataContainer: def __init__(self): self.data = [] - def add_data(self, x, y, name=None, y_label=None): + def add_data(self, x, y, name=None, y_label=None, decimate: int|None=None): """Add data to the container. Parameters @@ -141,11 +145,16 @@ def add_data(self, x, y, name=None, y_label=None): name of the y data, by default None name, optional name of the data, by default None + decimate, optional + number of data to keep, all of them if None, by default None. Returns ------- tuple (macro_name, y_label), where macro_name is the name of the data in LaTeX and y_label the name of the y data in LaTeX """ + if decimate is not None and (not isinstance(decimate, int) or decimate < 1): + raise ValueError("decimate must be None or an integer >= 1") + for data in self.data: if len(data.x) != len(x): continue @@ -157,12 +166,12 @@ def add_data(self, x, y, name=None, y_label=None): elif hasattr(are_equals, "all") and are_equals.all(): y_label_val = data.add_y_data(y, y_label or name) return data.macro_name, treat_data(y_label_val) - data_to_add = Data(f"data{index_to_letters(len(self.data))}", x) + data_to_add = Data(f"data{index_to_letters(len(self.data))}", x, decimate=decimate) y_label_val = data_to_add.add_y_data(y, y_label or name) self.data.append(data_to_add) return data_to_add.macro_name, treat_data(y_label_val) - def add_data3d(self, x, y, z, name=None): + def add_data3d(self, x, y, z, name=None, decimate: int|None=None): """Add data to the container. Parameters @@ -175,16 +184,21 @@ def add_data3d(self, x, y, z, name=None): z values of the data name, optional name of the data, by default None + decimate, optional + number of data to keep, all of them if None, by default None. Returns ------- tuple (macro_name, z_name), where macro_name is the name of the data in LaTeX and z_name the name of the z data in LaTeX """ + if decimate is not None and (not isinstance(decimate, int) or decimate < 1): + raise ValueError("decimate must be None or an integer >= 1") + for data in self.data: if hasattr(data, "x") and hasattr(data, "y") and hasattr(data, "z"): if np.array_equal(data.x, x) and np.array_equal(data.y, y) and np.array_equal(data.z, z): return data.name, data.z_name - data_obj = Data3D(x, y, z, name) + data_obj = Data3D(x, y, z, name, decimate=decimate) self.data.append(data_obj) return data_obj.name @@ -202,8 +216,20 @@ def export_data(self): if hasattr(data, "z"): export_string += "\\pgfplotstableread{\n" export_string += "x y z\n" - for x, y, z in zip(data.x, data.y, data.z): - export_string += f"{treat_data(x)} {treat_data(y)} {treat_data(z)}\n" + n = len(data.x) + if getattr(data, 'decimate', None) is None: + indices = range(n) + else: + d = data.decimate + if d <= 1: + indices = range(n) + else: + # always include first and last + indices = list(range(0, n, d)) + if indices[-1] != n-1: + indices.append(n-1) + for i in indices: + export_string += f"{treat_data(data.x[i])} {treat_data(data.y[i])} {treat_data(data.z[i])}\n" export_string += f"}}{{\\{data.name}}}\n" # 2D @@ -216,8 +242,19 @@ def export_data(self): else: header += " y" export_string += header + "\n" - for i, x in enumerate(data.x): - row = [treat_data(x)] + n = len(data.x) + if getattr(data, 'decimate', None) is None: + indices = range(n) + else: + d = data.decimate + if d <= 1: + indices = range(n) + else: + indices = list(range(0, n, d)) + if indices[-1] != n-1: + indices.append(n-1) + for i in indices: + row = [treat_data(data.x[i])] for y_col in data.y_data: row.append(treat_data(y_col[i])) export_string += " ".join(row) + "\n" diff --git a/src/tikzplotly/_save.py b/src/tikzplotly/_save.py index ea157ff..70fa6b4 100755 --- a/src/tikzplotly/_save.py +++ b/src/tikzplotly/_save.py @@ -29,6 +29,7 @@ def get_tikz_code( axis_options = None, include_disclamer = True, img_name = "heatmap.png", + decimate = None, ): """Get the tikz code of a figure. @@ -74,7 +75,7 @@ def get_tikz_code( if trace.y is None: trace.y = list(range(len(trace.x))) - data_name_macro, y_name = data_container.add_data(trace.x, trace.y, trace.name) + data_name_macro, y_name = data_container.add_data(trace.x, trace.y, trace.name, decimate=decimate) # If x is textual => symbolic x coords if all(isinstance(v, str) for v in trace.x): @@ -173,7 +174,7 @@ def get_tikz_code( if hasattr(figure_layout.scene, "title") and getattr(figure_layout.scene.title, "text", None): axis.add_option("title", f"{{{sanitize_tex_text(figure_layout.scene.title.text)}}}") - data_name_macro = data_container.add_data3d(trace.x, trace.y, trace.z, trace.name) + data_name_macro = data_container.add_data3d(trace.x, trace.y, trace.z, trace.name, decimate=decimate) data_str.append(draw_scatter3d(data_name_macro, trace, colors_set)) if trace.name and trace['showlegend'] is not False: From 0ce9aa2e55d988ef4a623bfc01d6999d0a0ad8cc Mon Sep 17 00:00:00 2001 From: Thomas Saigre Date: Thu, 11 Jun 2026 09:17:10 +0200 Subject: [PATCH 2/7] [tox] allow usage of tox offline #41 --- tox.ini | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 3fba2c2..4beb3c6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,17 @@ [tox] envlist = py3 -isolated_build = True +isolated_build = true [testenv] +package = editable deps = pandas pytest pytest-cov pytest-codeblocks pytest-randomly -commands = +commands_pre = plotly_get_chrome -y +commands = pytest {posargs} From 236c36bd79e7642af05d496010daadcc237d889d Mon Sep 17 00:00:00 2001 From: Thomas Saigre Date: Thu, 11 Jun 2026 09:50:19 +0200 Subject: [PATCH 3/7] [tests] skip some tests that need internet connexion if we are offline and raise warning close #41 --- tests/test_polar.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_polar.py b/tests/test_polar.py index 03f36e6..ef47ebe 100644 --- a/tests/test_polar.py +++ b/tests/test_polar.py @@ -6,8 +6,12 @@ import plotly.graph_objects as go import pandas as pd import pytest +import warnings +from urllib.error import URLError from .helpers import assert_equality +from warnings import warn + this_dir = pathlib.Path(__file__).resolve().parent test_name = "test_polar" @@ -252,7 +256,13 @@ def test_radar_1(): assert_equality(plot_radar_1(), os.path.join(this_dir, test_name, test_name + "_radar_1_reference.tex")) def test_radar_2(): - assert_equality(plot_radar_2(), os.path.join(this_dir, test_name, test_name + "_radar_2_reference.tex")) + try: + fig = plot_radar_2() + assert_equality(fig, os.path.join(this_dir, test_name, test_name + "_radar_2_reference.tex")) + except URLError as e: + warnings.warn(f"Offline: {e}. Skipping test_radar_2.", UserWarning) + pytest.skip("Offline: Could not fetch data for radar plot.") + def test_polar_categorial_angular(): assert_equality(fig_polar_categorial_angular(), os.path.join(this_dir, test_name, test_name + "_categorical_angular_reference.tex")) @@ -273,6 +283,12 @@ def test_polar_range_2(): assert_equality(fig_polar_range_2(), os.path.join(this_dir, test_name, test_name + "_radar_range_2_reference.tex")) def test_polar_1(): + try: + fig = fig_polar_1() + assert_equality(fig, os.path.join(this_dir, test_name, test_name + "_1_reference.tex")) + except URLError as e: + warnings.warn(f"Offline: {e}. Skipping test_polar_1.", UserWarning) + pytest.skip("Offline: Could not fetch data for radar plot.") assert_equality(fig_polar_1(), os.path.join(this_dir, test_name, test_name + "_1_reference.tex")) def test_polar_matplotlib(): From de940038cbaae7e1957fe656d0e9c6b22fd57e6f Mon Sep 17 00:00:00 2001 From: Thomas Saigre Date: Thu, 11 Jun 2026 11:52:11 +0200 Subject: [PATCH 4/7] [test] add testing and coverage for decimate option #40 --- tests/test_scatter.py | 19 +++ .../test_scatter_decimate_10_reference.tex | 27 ++++ .../test_scatter_decimate_1_reference.tex | 118 ++++++++++++++++++ tests/test_scatter3d.py | 15 ++- .../test_scatter3d_3_6_reference.tex | 30 +++++ 5 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 tests/test_scatter/test_scatter_decimate_10_reference.tex create mode 100644 tests/test_scatter/test_scatter_decimate_1_reference.tex create mode 100644 tests/test_scatter3d/test_scatter3d_3_6_reference.tex diff --git a/tests/test_scatter.py b/tests/test_scatter.py index 19f0314..48782f1 100644 --- a/tests/test_scatter.py +++ b/tests/test_scatter.py @@ -211,6 +211,11 @@ def plot_10(): fig.update_yaxes(autorange="reversed") return fig +def plot_decimate(): + x = np.linspace(-6, 6, 103) + y = np.sin(x) + fig = px.scatter(x=x, y=y) + return fig def test_1(): assert_equality(plot_1(), os.path.join(this_dir, test_name, test_name + "_1_reference.tex")) @@ -249,3 +254,17 @@ def test_9(): def test_10(): assert_equality(plot_10(), os.path.join(this_dir, test_name, test_name + "_10_reference.tex")) + +@pytest.mark.parametrize("d", [10, 1, None]) +def test_decimate(d): + if not d: + d_path = 1 + else: + d_path = d + assert_equality(plot_decimate(), os.path.join(this_dir, test_name, test_name + f"_decimate_{d_path}_reference.tex"), decimate=d) + +def test_failing_decimate(): + try: + assert_equality(plot_decimate(), os.path.join(this_dir, test_name, test_name + f"_decimate_1_reference.tex"), decimate=-1) + except ValueError as e: + assert e.__str__() == "decimate must be None or an integer >= 1" diff --git a/tests/test_scatter/test_scatter_decimate_10_reference.tex b/tests/test_scatter/test_scatter_decimate_10_reference.tex new file mode 100644 index 0000000..2415953 --- /dev/null +++ b/tests/test_scatter/test_scatter_decimate_10_reference.tex @@ -0,0 +1,27 @@ +\pgfplotstableread{ +x y0 +-6.0 0.27941549819892586 +-4.823529411764706 0.9938302570032191 +-3.6470588235294117 0.4842153677719185 +-2.4705882352941178 -0.6217729584832129 +-1.2941176470588234 -0.961968001461609 +-0.11764705882352988 -0.1173758577416406 +1.0588235294117645 0.8717797351457958 +2.235294117647058 0.7872265786208915 +3.4117647058823533 -0.2668972517323446 +4.588235294117647 -0.9923028258265024 +5.76470588235294 -0.49555997962236026 +6.0 -0.27941549819892586 +}\dataA + +\begin{tikzpicture} + +\definecolor{636efa}{HTML}{636efa} + +\begin{axis}[ +xlabel=x, +ylabel=y +] +\addplot+ [mark=*, only marks, mark options={solid, fill=636efa}, forget plot] table[y=y0] {\dataA}; +\end{axis} +\end{tikzpicture} diff --git a/tests/test_scatter/test_scatter_decimate_1_reference.tex b/tests/test_scatter/test_scatter_decimate_1_reference.tex new file mode 100644 index 0000000..3fce74b --- /dev/null +++ b/tests/test_scatter/test_scatter_decimate_1_reference.tex @@ -0,0 +1,118 @@ +\pgfplotstableread{ +x y0 +-6.0 0.27941549819892586 +-5.882352941176471 0.39018486717736317 +-5.764705882352941 0.4955599796223595 +-5.647058823529412 0.594084037906469 +-5.529411764705882 0.6843949593611752 +-5.411764705882353 0.7652442068978339 +-5.294117647058823 0.8355140498753184 +-5.176470588235294 0.8942330165850315 +-5.0588235294117645 0.9405893247246457 +-4.9411764705882355 0.9739421041859545 +-4.823529411764706 0.9938302570032191 +-4.705882352941177 0.999978831974337 +-4.588235294117647 0.9923028258265024 +-4.470588235294118 0.9709083583757229 +-4.352941176470589 0.9360912054337585 +-4.235294117647059 0.8883327097448614 +-4.117647058823529 0.8282931264831008 +-4.0 0.7568024953079282 +-3.8823529411764706 0.6748491651706653 +-3.764705882352941 0.5835661305150185 +-3.6470588235294117 0.4842153677719185 +-3.5294117647058822 0.37817038869466907 +-3.411764705882353 0.26689725173234413 +-3.2941176470588234 0.15193429395679833 +-3.1764705882352944 0.03487086374684205 +-3.058823529411765 -0.0826746517524646 +-2.9411764705882355 -0.19907720062779133 +-2.823529411764706 -0.3127275323351016 +-2.7058823529411766 -0.4220544453852783 +-2.588235294117647 -0.5255465090063693 +-2.4705882352941178 -0.6217729584832129 +-2.3529411764705883 -0.7094034752990112 +-2.235294117647059 -0.787226578620891 +-2.1176470588235294 -0.8541663738694864 +-2.0 -0.9092974268256817 +-1.882352941176471 -0.9518575576418682 +-1.7647058823529411 -0.981258377882123 +-1.6470588235294121 -0.9970934249180593 +-1.5294117647058822 -0.9991437812233548 +-1.4117647058823533 -0.9873811008809169 +-1.2941176470588234 -0.961968001461609 +-1.1764705882352944 -0.923255815856861 +-1.0588235294117645 -0.8717797351457958 +-0.9411764705882355 -0.8082514096461222 +-0.8235294117647056 -0.7335491104383254 +-0.7058823529411766 -0.6487055873788524 +-0.5882352941176467 -0.5548937914637123 +-0.47058823529411775 -0.4534106589290294 +-0.35294117647058876 -0.345659181271294 +-0.23529411764705888 -0.23312900906703335 +-0.11764705882352988 -0.1173758577416406 +0.0 0.0 +0.117647058823529 0.11737585774163972 +0.23529411764705888 0.23312900906703335 +0.35294117647058787 0.3456591812712932 +0.47058823529411775 0.4534106589290294 +0.5882352941176467 0.5548937914637123 +0.7058823529411766 0.6487055873788524 +0.8235294117647056 0.7335491104383254 +0.9411764705882355 0.8082514096461222 +1.0588235294117645 0.8717797351457958 +1.1764705882352944 0.923255815856861 +1.2941176470588234 0.961968001461609 +1.4117647058823533 0.9873811008809169 +1.5294117647058822 0.9991437812233548 +1.6470588235294112 0.9970934249180594 +1.7647058823529411 0.981258377882123 +1.8823529411764701 0.9518575576418684 +2.0 0.9092974268256817 +2.117647058823529 0.8541663738694866 +2.235294117647058 0.7872265786208915 +2.3529411764705888 0.7094034752990108 +2.4705882352941178 0.6217729584832129 +2.5882352941176467 0.5255465090063697 +2.7058823529411757 0.4220544453852791 +2.8235294117647065 0.31272753233510114 +2.9411764705882355 0.19907720062779133 +3.0588235294117645 0.08267465175246505 +3.1764705882352935 -0.03487086374684116 +3.2941176470588243 -0.15193429395679922 +3.4117647058823533 -0.2668972517323446 +3.5294117647058822 -0.37817038869466907 +3.6470588235294112 -0.4842153677719181 +3.7647058823529402 -0.5835661305150177 +3.882352941176471 -0.6748491651706656 +4.0 -0.7568024953079282 +4.117647058823529 -0.8282931264831008 +4.235294117647058 -0.888332709744861 +4.352941176470589 -0.9360912054337585 +4.470588235294118 -0.9709083583757229 +4.588235294117647 -0.9923028258265024 +4.705882352941176 -0.999978831974337 +4.8235294117647065 -0.993830257003219 +4.9411764705882355 -0.9739421041859545 +5.0588235294117645 -0.9405893247246457 +5.1764705882352935 -0.8942330165850318 +5.2941176470588225 -0.835514049875319 +5.411764705882353 -0.7652442068978339 +5.529411764705882 -0.6843949593611752 +5.647058823529411 -0.5940840379064697 +5.76470588235294 -0.49555997962236026 +5.882352941176471 -0.39018486717736317 +6.0 -0.27941549819892586 +}\dataA + +\begin{tikzpicture} + +\definecolor{636efa}{HTML}{636efa} + +\begin{axis}[ +xlabel=x, +ylabel=y +] +\addplot+ [mark=*, only marks, mark options={solid, fill=636efa}, forget plot] table[y=y0] {\dataA}; +\end{axis} +\end{tikzpicture} diff --git a/tests/test_scatter3d.py b/tests/test_scatter3d.py index 0b3b0d8..02489bc 100644 --- a/tests/test_scatter3d.py +++ b/tests/test_scatter3d.py @@ -76,7 +76,6 @@ def plot_scatter_3d_view(): ) return fig - def plot_scatter_3d_empty(): return go.Figure(data=[go.Scatter3d( x=None, @@ -84,6 +83,7 @@ def plot_scatter_3d_empty(): z=None, )]) + def test_scatter_3d_1(): assert_equality(plot_scatter_3d_1(), os.path.join(this_dir, test_name, test_name + "_1_reference.tex")) @@ -106,3 +106,16 @@ def test_scatter_3d_view(): def test_scatter_3d_empty(): with pytest.warns(UserWarning, match="Adding empty 3D trace."): assert_equality(plot_scatter_3d_empty(), os.path.join(this_dir, test_name, test_name + "_empty_reference.tex")) + +@pytest.mark.parametrize("d", [6, 1, None]) +def test_failing_decimate(d): + suffix = "6_" if d == 6 else "" + assert_equality(plot_scatter_3d_3(), os.path.join(this_dir, test_name, test_name + f"_3_{suffix}reference.tex"), decimate=d) + +def test_scatter_3d_decimate(): + try: + assert_equality(plot_scatter_3d_3(), os.path.join(this_dir, test_name, test_name + f"_3_reference.tex"), decimate=-1) + except ValueError as e: + assert e.__str__() == "decimate must be None or an integer >= 1" + + diff --git a/tests/test_scatter3d/test_scatter3d_3_6_reference.tex b/tests/test_scatter3d/test_scatter3d_3_6_reference.tex new file mode 100644 index 0000000..05c744f --- /dev/null +++ b/tests/test_scatter3d/test_scatter3d_3_6_reference.tex @@ -0,0 +1,30 @@ +\pgfplotstableread{ +x y z +1.0 0.0 0.0 +0.3510339684920502 0.9363627251042848 1.2121212121212122 +-0.7535503059294446 0.6573902466827755 2.4242424242424243 +-0.8800774771896732 -0.47483011082223947 3.6363636363636362 +0.13567612713271912 -0.9907532430056771 4.848484848484849 +0.9753313358637337 -0.22074597455506334 6.0606060606060606 +0.5490727317130796 0.8357745720522589 7.2727272727272725 +-0.5898449758557073 0.8075165041395626 8.484848484848484 +-0.9631839770525324 -0.26884312591038406 9.696969696969697 +-0.08637561184970585 -0.9962626429198221 10.909090909090908 +0.9025424294354707 -0.43060093249866344 12.121212121212121 +0.7200217133240836 0.6939515345770562 13.333333333333334 +-0.3970382705782732 0.9178020547461276 14.545454545454545 +-0.9987695528527076 -0.04959213944167377 15.757575757575758 +-0.30416580891556017 -0.9526191057745708 16.96969696969697 +0.7852244908862596 -0.6192111908811196 18.18181818181818 +0.8554467473014667 0.5178907824351968 19.393939393939394 +0.40808206181339196 0.9129452507276277 20.0 +}{\dataDFHNIDGLEFPCLNOOILBDMAJGJEBAMDGP} + +\begin{tikzpicture} + +\definecolor{darkblue}{RGB}{0, 0, 139} + +\begin{axis} +\addplot3+ [mark=none, line width=3, color=darkblue, forget plot] table[x=x, y=y, z=z] {\dataDFHNIDGLEFPCLNOOILBDMAJGJEBAMDGP}; +\end{axis} +\end{tikzpicture} From 0bf4a46a0ccb83b9f2466debd94c3d2e90af6fe9 Mon Sep 17 00:00:00 2001 From: Thomas Saigre Date: Thu, 11 Jun 2026 14:28:01 +0200 Subject: [PATCH 5/7] add support for python 3.14 and remove support for python 3.9 --- .github/workflows/ci.yml | 4 ++-- .github/workflows/documentation.yml | 2 +- pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e698037..ad2c012 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macOS-latest] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/setup-python@v3 with: @@ -38,7 +38,7 @@ jobs: pip install tox tox -- --cov tikzplotly --cov-report xml --cov-report term - uses: codecov/codecov-action@v5.4.3 - if: ${{ matrix.python-version == '3.13' && matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.python-version == '3.14' && matrix.os == 'ubuntu-latest' }} with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 7108630..df38877 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -22,7 +22,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.14 - run: pip install \ mkdocs-material[recommended] mkdocs-git-revision-date-localized-plugin - run: mkdocs gh-deploy --config-file mkdocs.yml --force diff --git a/pyproject.toml b/pyproject.toml index b891061..980bb9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,11 +13,11 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: Multimedia :: Graphics :: Graphics Conversion", From 685b30d62307c015f669237fadb162e309eb6953 Mon Sep 17 00:00:00 2001 From: Thomas Saigre Date: Thu, 11 Jun 2026 15:15:34 +0200 Subject: [PATCH 6/7] [doc] add doc for #40 --- README.md | 6 +++--- docs/plot/NB.md | 14 +++++++------- docs/plot/features.md | 7 ++++--- docs/plot/supported.md | 22 ++++++++++++++-------- docs/plot/usage.md | 13 +++++++------ src/tikzplotly/_save.py | 12 ++++++------ 6 files changed, 41 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index d01aaf5..94b5c84 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ # Tikzplotly -Convert [plotly](https://plotly.com/python/) figures to TikZ code for inclusion into [PGFPlots](https://www.ctan.org/pkg/pgfplots) ([PGF/TikZ](https://www.ctan.org/pkg/pgf)) figures. +Convert [Plotly](https://plotly.com/python/) figures to TikZ code for inclusion into [PGFPlots](https://www.ctan.org/pkg/pgfplots) ([PGF/TikZ](https://www.ctan.org/pkg/pgf)) figures. This results in a ti*k*z code, that can be easily included into your LaTeX document. This also allows to easily edit the content of the figure. @@ -101,9 +101,9 @@ To correctly compile the document, you will need to add the following packages t ## Note -* This module is in development and new features are added bit by bit, when needed. If you have a feature request, please open an issue with the plotly figure you want to convert and the desired output. +* This module is in development and new features are added bit by bit, when needed. If you have a feature request, please open an issue with the Plotly figure you want to convert and the desired output. You can also submit a pull request with the desired feature ! -* Some feature can result in different output between the plotly figure and the TikZ figure, for instance the size of markers, more details can be found in [the documentation](https://thomas-saigre.github.io/tikzplotly/). +* Some feature can result in different output between the Plotly figure and the TikZ figure, for instance the size of markers, more details can be found in [the documentation](https://thomas-saigre.github.io/tikzplotly/). ## References diff --git a/docs/plot/NB.md b/docs/plot/NB.md index 272d631..455c00a 100644 --- a/docs/plot/NB.md +++ b/docs/plot/NB.md @@ -1,9 +1,9 @@ -# Some differences between plotly ans pgfplots +# Some differences between Plotly ans pgfplots -We gather here some points that are different between a plotly figure and the corresponding Ti*k*Z figure generated with `tikzplotly`. +We gather here some points that are different between a Plotly figure and the corresponding Ti*k*Z figure generated with `tikzplotly`. -* Size of the objects: in plotly, the size is given in `px` unit, while in Ti*k*Z it is given in `pt`. A conversion is performed (`1 px = 0.75 pt`), but this still results in objects of different sizes. -* By default, the colors or the markers are not the same in plotly and pgfplots. For instance, if nothing is specified, plotly will always use a dot marker, while pgfplot will change for each trace. -* The order of displaying the traces may be unconsistent between plotly and pgfplots. For instance, for [this example](https://plotly.com/python/histograms/#several-histograms-for-the-different-values-of-one-column), the two traces are inverted. -* The angle of rotation is different between Plotly and Ti*k*Z, but the function Plotly ↦ Ti*k*Z is not know at this current point. -* When tricky names are used in symbolic expression (such as names with a space within), the space is removed by tisk plotly (*e.g.* the text `United Kingdom` in Plotly will be exported as `UnitedKindgon` in Ti*k*Z), fill free to update the exported file to render the figure you wish! \ No newline at end of file +* Size of the objects: in Plotly, the size is given in `px` unit, while in Ti*k*Z it is given in `pt`. A conversion is performed (`1 px = 0.75 pt`), but this still results in objects of different sizes. +* By default, the colors or the markers are not the same in Plotly and pgfplots. For instance, if nothing is specified, Plotly will always use a dot marker, while pgfplots will change for each trace. +* The order of displaying the traces may be inconsistent between Plotly and pgfplots. For instance, for [this example](https://Plotly.com/python/histograms/#several-histograms-for-the-different-values-of-one-column), the two traces are inverted. +* The angle of rotation is different between Plotly and Ti*k*Z, but the function Plotly ↦ Ti*k*Z is not known at this current point. +* When tricky names are used in symbolic expression (such as names with a space within), the space is removed by tikzplotly (*e.g.* the text `United Kingdom` in Plotly will be exported as `UnitedKindgon` in Ti*k*Z), fill free to update the exported file to render the figure you wish! diff --git a/docs/plot/features.md b/docs/plot/features.md index 868dbd5..6fb9156 100644 --- a/docs/plot/features.md +++ b/docs/plot/features.md @@ -6,7 +6,8 @@ From [Styling Markers in Python](https://plotly.com/python/marker-style/). -??? example "Marker Style Example" +??? Example "Marker Style Example" + ```python import plotly.express as px import tikzplotly @@ -26,6 +27,6 @@ From [Styling Markers in Python](https://plotly.com/python/marker-style/). !!! Note - - There are somes markers implemented in plotly that are not available in pgfplots (or at least not referenced in the [documentation](https://tikz.dev/pgfplots/reference-markers)). For more details, refer to the example [test_markers](https://github.com/thomas-saigre/tikzplotly/blob/main/tests/test_markers.py). By defualt, the marker style `*` will be used. - - By default, the colors or the markers are not the same in plotly and pgfplots. For instance, if nothing is specified, plotly will always use a dot marker, while pgfplot will change for each trace. + - There are some markers implemented in Plotly that are not available in pgfplots (or at least not referenced in the [documentation](https://tikz.dev/pgfplots/reference-markers)). For more details, refer to the example [test_markers](https://github.com/thomas-saigre/tikzplotly/blob/main/tests/test_markers.py). By default, the marker style `*` will be used. + - By default, the colors or the markers are not the same in Plotly and pgfplots. For instance, if nothing is specified, Plotly will always use a dot marker, while pgfplots will change for each trace. - The angle of rotation is different between Plotly and Ti*k*Z. diff --git a/docs/plot/supported.md b/docs/plot/supported.md index e903f01..cf7884d 100644 --- a/docs/plot/supported.md +++ b/docs/plot/supported.md @@ -13,7 +13,7 @@ The plots type supported by tikzplotly are presented in this page. The code has been constructed to export (almost all) the figures of the page [Line Charts in Python](https://plotly.com/python/line-charts/) of Plotly documentation. -??? example "Scatter plot Example" +??? Example "Scatter plot Example" ```python import plotly.express as px @@ -31,7 +31,8 @@ The code has been constructed to export (almost all) the figures of the page [Li The code has been constructed to export (almost all) the figures of the page [Heatmaps in Python](https://plotly.com/python/heatmaps/) of Plotly documentation. -??? example "Heatmap Example" +??? Example "Heatmap Example" + ```python import plotly.express as px import tikzplotly @@ -44,14 +45,15 @@ The code has been constructed to export (almost all) the figures of the page [He ![Heatmap Example](../assets/examples/heatmap.png) !!! Note - - If possible, TikzPlotly try to save the heatmap as a png of the smallest size possible, namely 1 pixel for each value of the heatmap. But in some case, such export does not work. In this case, the image is saved in the original size of the Plotly figure. + - If possible, TikzPlotly try to save the heatmap as a PNG of the smallest size possible, namely 1 pixel for each value of the heatmap. But in some case, such export does not work. In this case, the image is saved in the original size of the Plotly figure. ## Histograms The examples of the page [Histograms in Python](https://plotly.com/python/histograms/) of Plotly documentation are supported. -??? example "Histogram Example" +??? Example "Histogram Example" + ```python df = px.data.tips() fig = px.histogram(df, x="total_bill") @@ -68,7 +70,8 @@ The examples of the page [Histograms in Python](https://plotly.com/python/histog Some examples of the page [Bar Charts in Python](https://plotly.com/python/bar-charts/) of Plotly documentation are supported (not stacked and aggregated bars). -??? example "Bar plot" +??? Example "Bar plot" + ```python wide_df = px.data.medals_wide() fig = px.bar( @@ -88,7 +91,8 @@ Some examples of the page [Bar Charts in Python](https://plotly.com/python/bar-c The examples from the pages [Polar Charts in Python](https://plotly.com/python/polar-chart/) and [Radar Charts in Python](https://plotly.com/python/radar-chart/) are supported. -??? example "Polar plot" +??? Example "Polar plot" + ```python df = px.data.wind() fig = px.line_polar(df, r="frequency", theta="direction", color="strength", line_close=True, @@ -99,7 +103,8 @@ The examples from the pages [Polar Charts in Python](https://plotly.com/python/p ![Polar plot Example](../assets/examples/polar.png) -??? example "Radar plot" +??? Example "Radar plot" + ```python df = pd.DataFrame(dict( r=[1, 5, 2, 2, 3], @@ -116,7 +121,8 @@ The examples from the pages [Polar Charts in Python](https://plotly.com/python/p Examples from [3D Scatter Plots in Python ](https://plotly.com/python/3d-scatter-plots/) can be exported with tikzplotly. -??? example "3D scatter plot" +??? Example "3D scatter plot" + ```python df = px.data.iris() fig = px.scatter_3d(df, x='sepal_length', y='sepal_width', z='petal_width', color='species') diff --git a/docs/plot/usage.md b/docs/plot/usage.md index dc01a6b..26b83de 100644 --- a/docs/plot/usage.md +++ b/docs/plot/usage.md @@ -11,10 +11,11 @@ tikzplotly.save("example.tex", fig) The arguments of the function `tikzplotly.save` are: -* `filename` (str): The name of the file where the ti*k*z code will be saved. -* `fig` (plotly.graph_objs.Figure): The figure to be saved. -* `tikz_options` (str, optional): The options to be passed to the `tikzpicture` environment. Default is `None`. +* `filename` (`str`): The name of the file where the ti*k*z code will be saved. +* `fig` (`plotly.graph_objs.Figure`): The figure to be saved. +* `tikz_options` (`str`, optional): The options to be passed to the `tikzpicture` environment. Default is `None`. For example `tikz_options="scale=0.5"` will scale the figure by a factor 0.5. -* `axis_options` (str, optional): Option that you would like to manually add the the `axis` environment. -* `include_disclamer` (bool, optional): If `True`, the line `% This file was created with tikzplotly version XXX.` is added at the head of the generated code. Default is `True`. -* `img_name` (str, optional): only for the export of [heatmaps](supported.md#heat-maps), the name of the image that will be saved. Default is `heatmap.png`. +* `axis_options` (`str`, optional): Option that you would like to manually add to the `axis` environment. +* `include_disclamer` (`bool`, optional): If `True`, the line `% This file was created with tikzplotly version XXX.` is added at the head of the generated code. Default is `True`. +* `img_name` (`str`, optional): only for the export of [heatmaps](supported.md#heat-maps), the name of the image that will be saved. Default is `heatmap.png`. +* `decimate` (`int|None`, optional): only for the exports of scatter plots (in [2D](supported.md#scatter-plots) and [3D](supported.md#3d-scatter-plots)), reduce the number of data exported to only get the `decimate`-th ones (and the last one). If `None`, then all the data are exported. This is the default behavior. diff --git a/src/tikzplotly/_save.py b/src/tikzplotly/_save.py index 70fa6b4..74e2a2b 100755 --- a/src/tikzplotly/_save.py +++ b/src/tikzplotly/_save.py @@ -25,11 +25,11 @@ def get_tikz_code( fig, - tikz_options = None, - axis_options = None, - include_disclamer = True, - img_name = "heatmap.png", - decimate = None, + tikz_options: dict | None = None, + axis_options: dict | None = None, + include_disclamer: bool = True, + img_name: str = "heatmap.png", + decimate: int | None = None, ): """Get the tikz code of a figure. @@ -229,7 +229,7 @@ def get_tikz_code( return code -def save(filepath, *args, **kwargs): +def save(filepath: str | Path, *args, **kwargs): """Save a figure to a file or a stream. Parameters From 2ea1436009b09c8ef8f4d2af4922956838811184 Mon Sep 17 00:00:00 2001 From: Thomas Saigre Date: Thu, 18 Jun 2026 09:17:17 +0200 Subject: [PATCH 7/7] [ci] trigger doc publication only on new releases --- .github/workflows/documentation.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index df38877..e964db5 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,10 +1,7 @@ name: Build GitHub Pages on: - push: - branches: - - main - paths: - - 'docs/**' + release: + types: [published] workflow_dispatch: permissions: contents: write